Öffentliche Startseite: Produktseite unter /
Aufbau: Hero mit Plantafel-Vorschau, Leitgedanken, 12 Funktionen, vertiefte Abschnitte zu Planung, Baustelle und Lotse, Ablauf, Gewerke, Sicherheit, Testphase, häufige Fragen und Fußzeile. Texte auf Deutsch und Englisch, Vorschauen als Markup statt Screenshots. Angemeldete Nutzer leitet die Route weiterhin auf Dashboard bzw. mobile Ansicht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import {
|
||||
ArrowRight,
|
||||
Bot,
|
||||
CalendarRange,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
ClipboardList,
|
||||
Download,
|
||||
EyeOff,
|
||||
FileInput,
|
||||
FileSignature,
|
||||
FolderOpen,
|
||||
KeyRound,
|
||||
Layers,
|
||||
MapPin,
|
||||
Package,
|
||||
Receipt,
|
||||
ScrollText,
|
||||
Siren,
|
||||
Timer,
|
||||
UsersRound,
|
||||
WifiOff,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import { CraftviaLogo } from "@/components/brand/craftvia-logo";
|
||||
import { BRAND } from "@/lib/brand";
|
||||
import { BoardMockup, FieldMockup, LotseMockup } from "./mockups";
|
||||
|
||||
/**
|
||||
* Öffentliche Startseite (Produktseite). Wird von `/` gerendert, solange keine Session besteht;
|
||||
* angemeldete Nutzer leitet die Route auf Dashboard bzw. mobile Ansicht. Alle Texte kommen aus
|
||||
* `messages/<locale>/marketing.json` — keine Screenshots, die Vorschauen sind Markup (mockups.tsx).
|
||||
*/
|
||||
|
||||
const FEATURES: { key: string; icon: LucideIcon }[] = [
|
||||
{ key: "orders", icon: ClipboardList },
|
||||
{ key: "board", icon: CalendarRange },
|
||||
{ key: "live", icon: MapPin },
|
||||
{ key: "time", icon: Timer },
|
||||
{ key: "offline", icon: WifiOff },
|
||||
{ key: "reports", icon: FileSignature },
|
||||
{ key: "material", icon: Package },
|
||||
{ key: "billing", icon: Receipt },
|
||||
{ key: "emergency", icon: Siren },
|
||||
{ key: "lotse", icon: Bot },
|
||||
{ key: "documents", icon: FolderOpen },
|
||||
{ key: "imports", icon: FileInput },
|
||||
];
|
||||
|
||||
const SECURITY: { key: string; icon: LucideIcon }[] = [
|
||||
{ key: "item1", icon: Layers },
|
||||
{ key: "item2", icon: UsersRound },
|
||||
{ key: "item3", icon: KeyRound },
|
||||
{ key: "item4", icon: ScrollText },
|
||||
{ key: "item5", icon: Download },
|
||||
{ key: "item6", icon: EyeOff },
|
||||
];
|
||||
|
||||
const NAV = ["features", "planning", "mobile", "security", "pricing", "faq"] as const;
|
||||
const FAQ = ["q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8"] as const;
|
||||
const INDUSTRIES = ["shk", "electric", "cooling", "lifts", "windows", "service"] as const;
|
||||
|
||||
const ctaCls =
|
||||
"inline-flex min-h-12 items-center justify-center gap-2 rounded-lg bg-cta px-5 text-[15px] font-semibold text-cta-foreground hover:brightness-95 focus-visible:outline-2 focus-visible:outline-offset-2";
|
||||
const outlineCls =
|
||||
"inline-flex min-h-12 items-center justify-center gap-2 rounded-lg border px-5 text-[15px] font-semibold hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-2";
|
||||
|
||||
export async function MarketingHome({ contactEmail }: { contactEmail?: string }) {
|
||||
const t = await getTranslations("marketing");
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col">
|
||||
<header className="sticky top-0 z-20 border-b bg-background/95 backdrop-blur">
|
||||
<div className="mx-auto flex max-w-6xl items-center gap-4 px-4 py-3 sm:px-6">
|
||||
<Link href="/" className="flex min-h-11 items-center" aria-label={BRAND.name}>
|
||||
<CraftviaLogo variant="horizontal" height={30} />
|
||||
</Link>
|
||||
<nav aria-label={t("nav.label")} className="ml-auto hidden lg:block">
|
||||
<ul className="flex items-center gap-1">
|
||||
{NAV.map((key) => (
|
||||
<li key={key}>
|
||||
<a href={`#${key}`} className="inline-flex min-h-11 items-center rounded-md px-3 text-[14px] font-medium text-muted-foreground hover:text-foreground">
|
||||
{t(`nav.${key}`)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
||||
<Link href="/login" className="inline-flex min-h-11 items-center px-2 text-[14px] font-semibold text-[var(--ui-primary)]">
|
||||
{t("nav.login")}
|
||||
</Link>
|
||||
<Link href="/testen" className="inline-flex min-h-11 items-center rounded-lg bg-cta px-4 text-[14px] font-semibold text-cta-foreground hover:brightness-95">
|
||||
{t("nav.cta")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="flex-1">
|
||||
{/* Hero */}
|
||||
<section className="border-b bg-card">
|
||||
<div className="mx-auto grid max-w-6xl gap-10 px-4 py-12 sm:px-6 lg:grid-cols-[1.1fr_0.9fr] lg:items-center lg:py-20">
|
||||
<div>
|
||||
<p className="text-[13px] font-semibold tracking-wide text-[var(--ui-accent)] uppercase">{t("hero.eyebrow")}</p>
|
||||
<h1 className="mt-3 font-heading text-[34px] leading-[1.1] font-bold text-[var(--ui-primary)] sm:text-[46px]">{t("hero.title")}</h1>
|
||||
<p className="mt-4 max-w-xl text-[16.5px] leading-relaxed text-muted-foreground">{t("hero.subtitle")}</p>
|
||||
<div className="mt-7 flex flex-wrap gap-3">
|
||||
<Link href="/testen" className={ctaCls}>
|
||||
{t("hero.ctaPrimary")}
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Link>
|
||||
<a href="#features" className={outlineCls}>
|
||||
{t("hero.ctaSecondary")}
|
||||
</a>
|
||||
</div>
|
||||
<ul className="mt-6 grid gap-2">
|
||||
{(["bullet1", "bullet2", "bullet3"] as const).map((k) => (
|
||||
<li key={k} className="flex items-start gap-2 text-[14.5px]">
|
||||
<CheckCircle2 className="mt-0.5 size-[18px] shrink-0 text-[var(--ok)]" aria-hidden />
|
||||
{t(`hero.${k}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<BoardMockup />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Leitgedanken */}
|
||||
<section aria-labelledby="pillars" className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
|
||||
<h2 id="pillars" className="font-heading text-[26px] font-semibold">{t("pillars.title")}</h2>
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-3">
|
||||
{(["one", "two", "three"] as const).map((k) => (
|
||||
<article key={k} className="shadow-card rounded-xl border bg-card p-5">
|
||||
<h3 className="font-heading text-[17px] font-semibold">{t(`pillars.${k}.title`)}</h3>
|
||||
<p className="mt-2 text-[14.5px] leading-relaxed text-muted-foreground">{t(`pillars.${k}.text`)}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Funktionen */}
|
||||
<section id="features" aria-labelledby="features-title" className="scroll-mt-20 border-y bg-card">
|
||||
<div className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
|
||||
<h2 id="features-title" className="font-heading text-[26px] font-semibold">{t("features.title")}</h2>
|
||||
<p className="mt-2 max-w-2xl text-[15px] text-muted-foreground">{t("features.sub")}</p>
|
||||
<ul className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{FEATURES.map(({ key, icon: Icon }) => (
|
||||
<li key={key} className="rounded-xl border bg-background p-5">
|
||||
<span className="flex size-10 items-center justify-center rounded-lg bg-[var(--ui-primary-soft)] text-[var(--ui-primary)]">
|
||||
<Icon className="size-5" aria-hidden />
|
||||
</span>
|
||||
<h3 className="mt-3 font-heading text-[16px] font-semibold">{t(`features.${key}.title`)}</h3>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-muted-foreground">{t(`features.${key}.text`)}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Planung */}
|
||||
<section id="planning" aria-labelledby="planning-title" className="scroll-mt-20">
|
||||
<div className="mx-auto grid max-w-6xl gap-10 px-4 py-14 sm:px-6 lg:grid-cols-2 lg:items-center">
|
||||
<div>
|
||||
<p className="text-[13px] font-semibold tracking-wide text-[var(--ui-accent)] uppercase">{t("planning.eyebrow")}</p>
|
||||
<h2 id="planning-title" className="mt-2 font-heading text-[26px] font-semibold">{t("planning.title")}</h2>
|
||||
<p className="mt-3 text-[15px] leading-relaxed text-muted-foreground">{t("planning.text")}</p>
|
||||
<ul className="mt-5 grid gap-2.5">
|
||||
{(["point1", "point2", "point3", "point4"] as const).map((k) => (
|
||||
<li key={k} className="flex items-start gap-2 text-[14.5px]">
|
||||
<Check className="mt-0.5 size-[18px] shrink-0 text-[var(--ui-primary)]" aria-hidden />
|
||||
{t(`planning.${k}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<BoardMockup />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Baustelle */}
|
||||
<section id="mobile" aria-labelledby="mobile-title" className="scroll-mt-20 border-y bg-card">
|
||||
<div className="mx-auto grid max-w-6xl gap-10 px-4 py-14 sm:px-6 lg:grid-cols-[0.9fr_1.1fr] lg:items-center">
|
||||
<div>
|
||||
<p className="text-[13px] font-semibold tracking-wide text-[var(--ui-accent)] uppercase">{t("mobile.eyebrow")}</p>
|
||||
<h2 id="mobile-title" className="mt-2 font-heading text-[26px] font-semibold">{t("mobile.title")}</h2>
|
||||
<p className="mt-3 text-[15px] leading-relaxed text-muted-foreground">{t("mobile.text")}</p>
|
||||
<p className="mt-4 flex items-start gap-2 rounded-lg border border-[var(--info)] bg-background px-3 py-2 text-[14px]">
|
||||
<WifiOff className="mt-0.5 size-[18px] shrink-0 text-[var(--info)]" aria-hidden />
|
||||
{t("mobile.offlineNote")}
|
||||
</p>
|
||||
</div>
|
||||
<FieldMockup />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Lotse */}
|
||||
<section aria-labelledby="lotse-title">
|
||||
<div className="mx-auto grid max-w-6xl gap-10 px-4 py-14 sm:px-6 lg:grid-cols-2 lg:items-center">
|
||||
<div>
|
||||
<p className="text-[13px] font-semibold tracking-wide text-[var(--ui-accent)] uppercase">{t("lotse.eyebrow")}</p>
|
||||
<h2 id="lotse-title" className="mt-2 font-heading text-[26px] font-semibold">{t("lotse.title")}</h2>
|
||||
<p className="mt-3 text-[15px] leading-relaxed text-muted-foreground">{t("lotse.text")}</p>
|
||||
<p className="mt-4 text-[13.5px] text-muted-foreground">{t("lotse.note")}</p>
|
||||
</div>
|
||||
<LotseMockup />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Ablauf */}
|
||||
<section aria-labelledby="steps-title" className="border-y bg-card">
|
||||
<div className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
|
||||
<h2 id="steps-title" className="font-heading text-[26px] font-semibold">{t("steps.title")}</h2>
|
||||
<ol className="mt-6 grid gap-4 md:grid-cols-3">
|
||||
{(["one", "two", "three"] as const).map((k, i) => (
|
||||
<li key={k} className="rounded-xl border bg-background p-5">
|
||||
<span className="flex size-9 items-center justify-center rounded-full bg-[var(--ui-primary)] text-[15px] font-bold text-white tabular-nums">{i + 1}</span>
|
||||
<h3 className="mt-3 font-heading text-[16px] font-semibold">{t(`steps.${k}.title`)}</h3>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-muted-foreground">{t(`steps.${k}.text`)}</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Gewerke */}
|
||||
<section aria-labelledby="industries-title" className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
|
||||
<h2 id="industries-title" className="font-heading text-[26px] font-semibold">{t("industries.title")}</h2>
|
||||
<p className="mt-2 text-[15px] text-muted-foreground">{t("industries.sub")}</p>
|
||||
<ul className="mt-6 flex flex-wrap gap-2.5">
|
||||
{INDUSTRIES.map((key) => (
|
||||
<li key={key} className="rounded-full border bg-card px-4 py-2 text-[14px] font-medium">
|
||||
{t(`industries.${key}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Sicherheit */}
|
||||
<section id="security" aria-labelledby="security-title" className="scroll-mt-20 border-y bg-card">
|
||||
<div className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
|
||||
<p className="text-[13px] font-semibold tracking-wide text-[var(--ui-accent)] uppercase">{t("security.eyebrow")}</p>
|
||||
<h2 id="security-title" className="mt-2 font-heading text-[26px] font-semibold">{t("security.title")}</h2>
|
||||
<p className="mt-3 max-w-3xl text-[15px] leading-relaxed text-muted-foreground">{t("security.text")}</p>
|
||||
<ul className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{SECURITY.map(({ key, icon: Icon }) => (
|
||||
<li key={key} className="rounded-xl border bg-background p-5">
|
||||
<span className="flex size-10 items-center justify-center rounded-lg bg-[var(--ui-primary-soft)] text-[var(--ui-primary)]">
|
||||
<Icon className="size-5" aria-hidden />
|
||||
</span>
|
||||
<h3 className="mt-3 font-heading text-[16px] font-semibold">{t(`security.${key}.title`)}</h3>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-muted-foreground">{t(`security.${key}.text`)}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Testphase */}
|
||||
<section id="pricing" aria-labelledby="trial-title" className="scroll-mt-20">
|
||||
<div className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
|
||||
<div className="shadow-card grid gap-8 rounded-2xl border bg-card p-6 sm:p-9 lg:grid-cols-[1.1fr_0.9fr] lg:items-center">
|
||||
<div>
|
||||
<p className="text-[13px] font-semibold tracking-wide text-[var(--ui-accent)] uppercase">{t("trial.eyebrow")}</p>
|
||||
<h2 id="trial-title" className="mt-2 font-heading text-[26px] font-semibold">{t("trial.title")}</h2>
|
||||
<p className="mt-3 text-[15px] leading-relaxed text-muted-foreground">{t("trial.text")}</p>
|
||||
<ul className="mt-5 grid gap-2.5">
|
||||
{(["point1", "point2", "point3"] as const).map((k) => (
|
||||
<li key={k} className="flex items-start gap-2 text-[14.5px]">
|
||||
<CheckCircle2 className="mt-0.5 size-[18px] shrink-0 text-[var(--ok)]" aria-hidden />
|
||||
{t(`trial.${k}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Link href="/testen" className={ctaCls}>
|
||||
{t("trial.cta")}
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Link>
|
||||
{contactEmail && (
|
||||
<a href={`mailto:${contactEmail}`} className={outlineCls}>
|
||||
{t("trial.secondary")}
|
||||
</a>
|
||||
)}
|
||||
<p className="text-[13px] leading-relaxed text-muted-foreground">{t("trial.note")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Fragen */}
|
||||
<section id="faq" aria-labelledby="faq-title" className="scroll-mt-20 border-y bg-card">
|
||||
<div className="mx-auto max-w-4xl px-4 py-14 sm:px-6">
|
||||
<h2 id="faq-title" className="font-heading text-[26px] font-semibold">{t("faq.title")}</h2>
|
||||
<div className="mt-6 divide-y rounded-xl border bg-background">
|
||||
{FAQ.map((key) => (
|
||||
<details key={key} className="group px-4">
|
||||
<summary className="flex min-h-14 cursor-pointer list-none items-center justify-between gap-3 text-[15px] font-semibold">
|
||||
{t(`faq.${key}.q`)}
|
||||
<span aria-hidden className="text-[var(--ui-primary)] transition-transform group-open:rotate-45">+</span>
|
||||
</summary>
|
||||
<p className="pb-4 text-[14.5px] leading-relaxed text-muted-foreground">{t(`faq.${key}.a`)}</p>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Abschluss */}
|
||||
<section aria-labelledby="closing-title" className="mx-auto max-w-6xl px-4 py-16 text-center sm:px-6">
|
||||
<h2 id="closing-title" className="font-heading text-[28px] font-semibold text-[var(--ui-primary)]">{t("closing.title")}</h2>
|
||||
<p className="mx-auto mt-3 max-w-xl text-[15.5px] text-muted-foreground">{t("closing.text")}</p>
|
||||
<div className="mt-6 flex flex-wrap justify-center gap-3">
|
||||
<Link href="/testen" className={ctaCls}>
|
||||
{t("closing.cta")}
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Link>
|
||||
<Link href="/login" className={outlineCls}>
|
||||
{t("closing.login")}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className="border-t bg-card">
|
||||
<div className="mx-auto grid max-w-6xl gap-8 px-4 py-10 sm:px-6 md:grid-cols-[1.4fr_1fr_1fr]">
|
||||
<div>
|
||||
<CraftviaLogo variant="horizontal" height={28} />
|
||||
<p className="mt-3 max-w-xs text-[13.5px] text-muted-foreground">{t("footer.tagline")}</p>
|
||||
</div>
|
||||
<nav aria-labelledby="footer-product">
|
||||
<h2 id="footer-product" className="text-[13px] font-semibold tracking-wide uppercase">{t("footer.product")}</h2>
|
||||
<ul className="mt-2 grid">
|
||||
<li><a href="#features" className="inline-flex min-h-11 items-center text-[14px] text-muted-foreground hover:text-foreground">{t("footer.features")}</a></li>
|
||||
<li><Link href="/testen" className="inline-flex min-h-11 items-center text-[14px] text-muted-foreground hover:text-foreground">{t("footer.trial")}</Link></li>
|
||||
<li><Link href="/login" className="inline-flex min-h-11 items-center text-[14px] text-muted-foreground hover:text-foreground">{t("footer.login")}</Link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav aria-labelledby="footer-legal">
|
||||
<h2 id="footer-legal" className="text-[13px] font-semibold tracking-wide uppercase">{t("footer.company")}</h2>
|
||||
<ul className="mt-2 grid">
|
||||
<li><Link href="/testen/nutzungsbedingungen" className="inline-flex min-h-11 items-center text-[14px] text-muted-foreground hover:text-foreground">{t("footer.terms")}</Link></li>
|
||||
<li><Link href="/testen/datenschutz" className="inline-flex min-h-11 items-center text-[14px] text-muted-foreground hover:text-foreground">{t("footer.privacy")}</Link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<p className="border-t px-4 py-4 text-center text-[12.5px] text-muted-foreground sm:px-6">
|
||||
© {new Date().getFullYear()} {BRAND.name} · {BRAND.tagline} · {t("footer.rights")}
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user