Produktseite ausgebaut: Unterseiten und Bildschirmfotos aus der App
Neue öffentliche Routen unter src/app/(marketing): Übersicht /funktionen, sechs Detailseiten (planung, baustelle, zeiterfassung, berichte, abrechnung, lotse), /sicherheit und /preise, gemeinsames Gerüst mit Kopf- und Fußzeile. scripts/marketing-shots.ts nimmt die Bilder direkt aus der laufenden App auf (Playwright über den installierten Chrome, Session wie im Smoke-Test, ohne Passworteingabe); 14 Aufnahmen in public/marketing ersetzen die gezeichneten Vorschauen. Plantafel: „Vorschläge" hängt jetzt als Fußzeile in der Auftragskarte statt frei zwischen zwei Karten zu stehen (neue Prop footer an OrderCard, Schlüssel unplanned.suggestionsFor). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Download, EyeOff, KeyRound, Layers, ScrollText, UsersRound, type LucideIcon } from "lucide-react";
|
||||
import { CtaBand, PageHero, ShotFrame } from "@/components/marketing/blocks";
|
||||
|
||||
/** Sicherheit und Datenschutz — Mandantentrennung, Rollen, Protokoll, Export. */
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const t = await getTranslations("marketing.securityPage");
|
||||
return { title: t("title"), description: t("lead") };
|
||||
}
|
||||
|
||||
const ITEMS: { 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 },
|
||||
];
|
||||
|
||||
export default async function SecurityPage() {
|
||||
const t = await getTranslations("marketing");
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero eyebrow={t("security.eyebrow")} title={t("securityPage.title")} lead={t("securityPage.lead")} />
|
||||
|
||||
<section aria-label={t("securityPage.title")} className="mx-auto max-w-6xl px-4 pb-12 sm:px-6">
|
||||
<ul className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{ITEMS.map(({ key, icon: Icon }) => (
|
||||
<li key={key} className="shadow-card rounded-xl border bg-card 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>
|
||||
<h2 className="mt-3 font-heading text-[16px] font-semibold">{t(`security.${key}.title`)}</h2>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-muted-foreground">{t(`security.${key}.text`)}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="security-detail" className="border-y bg-card">
|
||||
<div className="mx-auto grid max-w-6xl gap-8 px-4 py-12 sm:px-6 lg:grid-cols-[0.9fr_1.1fr] lg:items-center">
|
||||
<div>
|
||||
<h2 id="security-detail" className="font-heading text-[24px] font-semibold">{t("securityPage.detailTitle")}</h2>
|
||||
<dl className="mt-5 grid gap-4">
|
||||
{(["d1", "d2", "d3"] as const).map((k) => (
|
||||
<div key={k}>
|
||||
<dt className="font-heading text-[16px] font-semibold">{t(`securityPage.${k}title`)}</dt>
|
||||
<dd className="mt-1 text-[14px] leading-relaxed text-muted-foreground">{t(`securityPage.${k}text`)}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
<ShotFrame shot={{ src: "audit", kind: "desktop" }} caption={t("securityPage.shot1")} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-label={t("securityPage.shot2")} className="mx-auto max-w-5xl px-4 py-12 sm:px-6">
|
||||
<ShotFrame shot={{ src: "teams", kind: "desktop" }} caption={t("securityPage.shot2")} />
|
||||
</section>
|
||||
|
||||
<CtaBand />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user