Zum Inhalt springen
Alle Templates

Templates/Meeting-Protokolle SaaS

Eine ruhige SaaS-Seite in Weiß und einem gedeckten Königsblau. Statt das Produkt zu beschreiben, zeigt sie es: die Karte im Hero schreibt ihre Zusammenfassung Wort für Wort und hakt die Aufgaben nacheinander ab, dann kommt der nächste Termin, und weiter unten läuft eine Ablagefläche dieselben vier Schritte wie ein echter Upload. Alle Illustrationen sind in der Seite selbst gezeichnet, es gibt also kein einziges Foto zu ersetzen. Tiefe entsteht über Haarlinien und Flächenwechsel, nie über Schatten.

SaaS & SoftwareKomplette Seite

Die fertige Seite zum Durchscrollen, umschaltbar zwischen Desktop, Tablet und Handy.

Quellcode

Die komplette Seite als eine Datei. Kopiere sie nach ./components und binde die Komponente in deine Route ein.

import {
  useCallback,
  useEffect,
  useRef,
  useState,
  type CSSProperties,
  type ReactNode,
} from "react";
import { AnimatePresence, motion, useInView, useReducedMotion } from "framer-motion";
import {
  ArrowRight,
  BellRing,
  Check,
  FileAudio,
  Globe2,
  Lock,
  Mail,
  MapPin,
  Menu,
  MonitorPlay,
  ShieldCheck,
  Sparkles,
  Star,
  Trash2,
  UserCheck,
  Users,
  Video,
  X,
} from "lucide-react";

/*
 * Confer: a full page for a meeting-transcription product.
 *
 * White plus one muted royal blue, with two cool near-white surfaces for the
 * section bands. Depth comes from hairline borders and a change of surface,
 * never from a shadow. The only texture is a fine dot grid, masked so it fades
 * out before it reaches the type.
 *
 * The page shows the product instead of describing it: the hero holds a live
 * recap card that writes its summary word by word and ticks its action items
 * off one at a time, then swaps in the next meeting. Further down a drop zone
 * runs the same four steps a real upload would.
 */

const TINTE = "#0a0a0a";
const LEISE = "#545a6b";
const BLAU = "#3a5bc4";
const BLAU_2 = "#5069ce";
const BLAU_SANFT = "#ebeefb";
const VIOLETT = "#6b54ce";
const FLAECHE = "#f6f7fb";
const FLAECHE_2 = "#eceff6";
const STUMM = "#f2f4f9";
const KANTE = "#e3e6ef";
const GRUEN = "#2f8f5b";

const SANS = '"Albert Sans", ui-sans-serif, system-ui, sans-serif';

/** Karten gross gerundet, Knoepfe klein: die Form der Flaeche, nicht der Taste. */
const KARTE = 24;
const KNOPF = 8;

const MARKE = { name: "Confer", mail: "hello@confer.app" };
const PRODUKTE = {
  capture: "Confer.Capture",
  recap: "Confer.Recap",
  automate: "Confer.Automate",
};

const NAV = [
  { href: "#journey", label: "Capture" },
  { href: "#journey", label: "Recap" },
  { href: "#journey", label: "Automate" },
  { href: "#pricing", label: "Pricing" },
  { href: "#security", label: "Security" },
];

type Aufgabe = { was: string; wer: string; wann: string; fertig: boolean };
type Termin = {
  titel: string;
  leute: string[];
  dauer: string;
  zusammenfassung: string[];
  aufgaben: Aufgabe[];
};

const TERMINE: Termin[] = [
  {
    titel: "Weekly Sync",
    leute: ["MK", "AR", "SL"],
    dauer: "34 min",
    zusammenfassung: [
      "12%",
      "discount",
      "to",
      "Q3",
      "approved,",
      "API",
      "blocker",
      "with",
      "Mert",
      "tomorrow.",
    ],
    aufgaben: [
      { was: "Resolve API blocker", wer: "MK", wann: "tomorrow", fertig: true },
      { was: "Send timeline", wer: "SL", wann: "Fri", fertig: true },
      { was: "Log discount approval", wer: "AR", wann: "this wk", fertig: false },
    ],
  },
  {
    titel: "Sales Call · Northwind",
    leute: ["JD", "KE"],
    dauer: "22 min",
    zusammenfassung: [
      "Sent",
      "offer",
      "with",
      "12%",
      "discount,",
      "close",
      "expected",
      "by",
      "Q3.",
    ],
    aufgaben: [
      { was: "Send the offer", wer: "JD", wann: "today", fertig: true },
      { was: "Follow up", wer: "KE", wann: "Tue", fertig: false },
    ],
  },
  {
    titel: "Client · Meyer",
    leute: ["SL", "TN", "KU"],
    dauer: "41 min",
    zusammenfassung: ["Timeline", "slips,", "new", "plan", "promised", "by", "Friday."],
    aufgaben: [
      { was: "Revise the timeline", wer: "SL", wann: "Fri", fertig: true },
      { was: "Book follow-up", wer: "TN", wann: "Wed", fertig: false },
    ],
  },
];

const VERTRAUEN = [
  { label: "Used by teams in 20+ countries", Icon: Globe2 },
  { label: "GDPR-compliant", Icon: ShieldCheck },
  { label: "Encrypted transfer", Icon: Lock },
  { label: "For Zoom, Meet and Teams", Icon: Video },
  { label: "Participants are notified", Icon: BellRing },
];

const WEG = [
  {
    schritt: "01",
    marke: "Capture",
    name: PRODUKTE.capture,
    frage: "“What was said?”",
    text: "Records and transcribes your meetings, live in Zoom, Meet and Teams or from a file.",
    farbe: BLAU,
  },
  {
    schritt: "02",
    marke: "Recap",
    name: PRODUKTE.recap,
    frage: "“What needs doing?”",
    text: "Creates a summary, decisions and action items, including a draft for the follow-up.",
    farbe: "#2c4699",
  },
  {
    schritt: "03",
    marke: "Automate",
    name: PRODUKTE.automate,
    frage: "“How does it all connect?”",
    text: "Pushes results to your tools and makes every meeting searchable.",
    farbe: BLAU_2,
  },
];

const STIMMEN = [
  {
    text: "Nobody has to take notes during the meeting anymore. By the time the call ends, the recap is already in our team channel.",
    name: "Amina",
    rolle: "Product Lead, Berlin",
  },
  {
    text: "The action-item list with owners is priceless, by the next meeting everything is actually done.",
    name: "Kerem",
    rolle: "Sales Manager, Munich",
  },
  {
    text: "I now send clients a clean summary five minutes after the call. Very professional.",
    name: "Sabine",
    rolle: "Agency Owner, Hamburg",
  },
];

const SCHRITTE = [
  {
    titel: "Connect your meeting or upload a recording",
    text: "Live in Zoom, Meet and Teams, or an MP3 or video file.",
  },
  { titel: "Confer transcribes and summarizes", text: "Calm, careful, in plain language." },
  {
    titel: "You get notes, decisions and action items",
    text: "Including a follow-up draft and open questions.",
  },
];

const ARCHIV_PUNKTE = [
  "A searchable archive of every meeting",
  "Results shared in a single click",
  "Nothing important slips away",
];

const HOCHLADEN_SCHRITTE = [
  "Transcribed audio",
  "Identified speakers",
  "Wrote the summary",
  "Extracted action items",
];

const PREISE = [
  {
    name: PRODUKTE.capture,
    marke: "Capture",
    preis: "€0",
    je: "free",
    text: "For single meetings and trying things out.",
    punkte: [
      "Up to 5 meetings per month",
      "Plain-text transcription",
      "Automatic summary",
      "Export as PDF",
    ],
    cta: "Start for free",
    hervor: false,
  },
  {
    name: PRODUKTE.recap,
    marke: "Recap",
    preis: "€12",
    je: "per user / month",
    text: "For teams with a lot of meetings.",
    punkte: [
      `Everything in ${PRODUKTE.capture}`,
      "Decisions and action items with owners",
      "Follow-up drafts",
      "Up to 30 meetings per month",
    ],
    cta: "Start 30-day trial",
    hervor: true,
  },
  {
    name: PRODUKTE.automate,
    marke: "Automate",
    preis: "€29",
    je: "per user / month",
    text: "For teams that want to connect everything.",
    punkte: [
      `Everything in ${PRODUKTE.recap}`,
      "Integrations (Slack, Notion, CRM)",
      "Searchable meeting archive",
      "Analytics across all meetings",
    ],
    cta: "Contact sales",
    hervor: false,
  },
];

const SICHERHEIT = [
  {
    Icon: MapPin,
    titel: "Hosted in the EU",
    text: "Processed and stored exclusively on servers in the European Union.",
  },
  {
    Icon: Lock,
    titel: "Encrypted in transit and at rest",
    text: "TLS in transit, encryption at rest.",
  },
  {
    Icon: Trash2,
    titel: "Audio is deleted",
    text: "The original audio is removed after transcription, you keep the recap.",
  },
  {
    Icon: UserCheck,
    titel: "With consent",
    text: "Participants are notified of the recording. You stay in control of your data.",
  },
];

const FUSS_SPALTEN = [
  { titel: "Product", links: ["How it works", "Pricing", "Testimonials", "Upload a recording"] },
  { titel: "Company", links: ["About", "Contact", "Press", "Careers"] },
  { titel: "Legal", links: ["Privacy", "Legal notice", "Terms", "Cookie settings"] },
];

/* --------------------------------------------------------------- primitives */

function Auftauchen({
  children,
  delay = 0,
  className = "",
  style,
}: {
  children: ReactNode;
  delay?: number;
  className?: string;
  style?: CSSProperties;
}) {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLDivElement>(null);
  const sichtbar = useInView(ref, { once: true, margin: "-70px" });
  return (
    <motion.div
      ref={ref}
      className={className}
      style={style}
      initial={reduce ? false : { opacity: 0, y: 20 }}
      animate={sichtbar || reduce ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
      transition={{
        duration: reduce ? 0 : 0.7,
        ease: [0.22, 1, 0.36, 1],
        delay: reduce ? 0 : delay,
      }}
    >
      {children}
    </motion.div>
  );
}

/** Die Marke: fuenf Balken wie ein Pegel. Es geht um Aufnehmen und Zuhoeren. */
function Pegel({ groesse = 40, farbe = BLAU }: { groesse?: number; farbe?: string }) {
  const balken = [
    { x: 3, h: 7, o: 0.55 },
    { x: 8.8, h: 13, o: 1 },
    { x: 14.6, h: 20, o: 0.7 },
    { x: 20.4, h: 11, o: 1 },
    { x: 26.2, h: 6, o: 0.55 },
  ];
  return (
    <svg
      width={groesse}
      height={(groesse * 3) / 4}
      viewBox="0 0 32 24"
      fill="none"
      aria-hidden
      className="shrink-0"
    >
      {balken.map((b) => (
        <rect
          key={b.x}
          x={b.x}
          y={12 - b.h / 2}
          width={2.8}
          height={b.h}
          rx={1.4}
          fill={farbe}
          fillOpacity={b.o}
        />
      ))}
    </svg>
  );
}

/** Ein Kicker: klein, in Grossbuchstaben, weit gesperrt. */
function Kicker({ children }: { children: ReactNode }) {
  return (
    <p
      className="text-sm uppercase"
      style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "0.16em", color: LEISE }}
    >
      {children}
    </p>
  );
}

function Titel({ children, id }: { children: ReactNode; id?: string }) {
  return (
    <h2
      id={id}
      className="text-4xl md:text-5xl"
      style={{ fontFamily: SANS, fontWeight: 500, letterSpacing: "-0.02em", color: TINTE }}
    >
      {children}
    </h2>
  );
}

function Bahn({ children, className = "" }: { children: ReactNode; className?: string }) {
  return (
    <div className={`mx-auto w-full max-w-[1200px] px-6 md:px-12 ${className}`}>{children}</div>
  );
}

/** Der Ring aus Anfangsbuchstaben, statt eines erfundenen Gesichts. */
function Monogramm({ name }: { name: string }) {
  return (
    <span
      aria-hidden
      className="grid size-11 shrink-0 place-items-center rounded-full text-sm"
      style={{
        background: `linear-gradient(135deg, ${BLAU} 0%, ${VIOLETT} 100%)`,
        color: "#ffffff",
        fontFamily: SANS,
        fontWeight: 700,
      }}
    >
      {name.slice(0, 1)}
    </span>
  );
}

/* ------------------------------------------------------------------- header */

function Kopfzeile() {
  const [gescrollt, setGescrollt] = useState(false);
  const [offen, setOffen] = useState(false);

  useEffect(() => {
    const beim = () => setGescrollt(window.scrollY > 12);
    beim();
    window.addEventListener("scroll", beim, { passive: true });
    return () => window.removeEventListener("scroll", beim);
  }, []);

  useEffect(() => {
    if (!offen) return;
    const vorher = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = vorher;
    };
  }, [offen]);

  return (
    <header
      className="sticky top-0 z-40 w-full transition-colors"
      style={{
        background: gescrollt ? "rgba(255,255,255,0.92)" : "transparent",
        borderBottom: gescrollt ? `1px solid ${KANTE}` : "1px solid transparent",
        backdropFilter: gescrollt ? "blur(10px)" : undefined,
      }}
    >
      <Bahn className="flex min-h-[72px] items-center justify-between gap-4">
        <a href="#top" className="group flex items-center gap-2.5" aria-label={MARKE.name}>
          <Pegel />
          <span
            className="text-lg md:text-xl"
            style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "-0.02em", color: TINTE }}
          >
            {MARKE.name}
          </span>
        </a>

        <nav aria-label="Main navigation" className="hidden lg:block">
          <ul className="flex items-center gap-1">
            {NAV.map((eintrag) => (
              <li key={eintrag.label}>
                <a
                  href={eintrag.href}
                  className="inline-flex min-h-11 items-center px-4 py-2 text-base transition-colors"
                  style={{
                    borderRadius: KNOPF,
                    fontFamily: SANS,
                    fontWeight: 500,
                    color: "#2c2f38",
                  }}
                >
                  {eintrag.label}
                </a>
              </li>
            ))}
          </ul>
        </nav>

        <div className="flex items-center gap-2">
          <a
            href="#upload"
            className="hidden min-h-11 items-center px-5 py-2.5 text-base transition-colors md:inline-flex"
            style={{
              borderRadius: KNOPF,
              background: BLAU,
              color: "#ffffff",
              fontFamily: SANS,
              fontWeight: 500,
            }}
          >
            Upload a recording
          </a>
          <button
            type="button"
            onClick={() => setOffen((v) => !v)}
            aria-expanded={offen}
            aria-label={offen ? "Close menu" : "Open menu"}
            className="inline-flex size-11 items-center justify-center lg:hidden"
            style={{
              borderRadius: KNOPF,
              border: `1px solid ${KANTE}`,
              background: "#ffffff",
              color: TINTE,
            }}
          >
            {offen ? <X className="size-5" /> : <Menu className="size-5" />}
          </button>
        </div>
      </Bahn>

      {offen ? (
        <div
          className="lg:hidden"
          style={{ borderTop: `1px solid ${KANTE}`, background: "#ffffff" }}
        >
          <Bahn className="py-4">
            <ul className="flex flex-col gap-1">
              {NAV.map((eintrag) => (
                <li key={eintrag.label}>
                  <a
                    href={eintrag.href}
                    onClick={() => setOffen(false)}
                    className="flex min-h-12 items-center px-4 py-3 text-lg"
                    style={{ borderRadius: KNOPF, fontFamily: SANS, fontWeight: 500, color: TINTE }}
                  >
                    {eintrag.label}
                  </a>
                </li>
              ))}
              <li className="mt-2">
                <a
                  href="#upload"
                  onClick={() => setOffen(false)}
                  className="flex min-h-12 items-center justify-center px-4 py-3 text-lg"
                  style={{
                    borderRadius: KNOPF,
                    background: BLAU,
                    color: "#ffffff",
                    fontFamily: SANS,
                    fontWeight: 500,
                  }}
                >
                  Upload a recording
                </a>
              </li>
            </ul>
          </Bahn>
        </div>
      ) : null}
    </header>
  );
}

/* --------------------------------------------------------------------- hero */

function Held() {
  return (
    <section id="top" className="relative overflow-hidden">
      {/* Ein feines Punktraster, weich ausmaskiert. Es gibt der Flaeche
          Struktur, ohne als Leuchten aufzutreten. */}
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0"
        style={{
          backgroundImage: `radial-gradient(circle, rgba(58,91,196,0.13) 1px, transparent 1px)`,
          backgroundSize: "28px 28px",
          maskImage: "radial-gradient(ellipse 70% 60% at 70% 40%, black 0%, transparent 75%)",
          WebkitMaskImage: "radial-gradient(ellipse 70% 60% at 70% 40%, black 0%, transparent 75%)",
        }}
      />

      <Bahn className="relative grid gap-10 py-12 md:py-28 lg:grid-cols-[1.05fr_1fr] lg:items-center lg:gap-20 lg:py-32">
        <Auftauchen>
          <p
            className="mb-5 inline-flex items-center gap-2 py-1.5 pe-4 ps-1.5 text-sm md:mb-6"
            style={{
              borderRadius: KNOPF,
              background: STUMM,
              color: "#2c2f38",
              fontFamily: SANS,
              fontWeight: 500,
            }}
          >
            <span
              className="grid size-6 place-items-center rounded-full"
              style={{ background: "#e6e9f2", color: LEISE }}
            >
              <Users className="size-3.5" strokeWidth={1.75} aria-hidden />
            </span>
            Built for teams
          </p>

          <h1
            className="text-[clamp(2rem,5.5vw,4rem)]"
            style={{ fontFamily: SANS, fontWeight: 500, lineHeight: 1.05, color: TINTE }}
          >
            Turn every <Unterstrichen>meeting</Unterstrichen> into a clear recap.
          </h1>

          <p
            className="mt-5 max-w-xl text-lg md:mt-6 md:text-xl"
            style={{ fontFamily: SANS, color: LEISE }}
          >
            {MARKE.name} listens in, transcribes and summarizes. You get decisions, action items and
            a clean recap, minutes after the call ends.
          </p>

          <div className="mt-7 flex flex-wrap items-center gap-3 md:gap-4">
            <a
              href="#upload"
              className="inline-flex min-h-14 items-center gap-2 px-7 py-4 text-lg transition-colors"
              style={{
                borderRadius: KNOPF,
                background: BLAU,
                color: "#ffffff",
                fontFamily: SANS,
                fontWeight: 600,
              }}
            >
              Upload a recording
              <ArrowRight className="size-5" aria-hidden />
            </a>
            <a
              href="#how-it-works"
              className="inline-flex min-h-14 items-center gap-1.5 px-5 py-4 text-lg"
              style={{
                fontFamily: SANS,
                fontWeight: 500,
                color: TINTE,
                textDecoration: "underline",
                textDecorationColor: BLAU_2,
                textDecorationThickness: 2,
                textUnderlineOffset: 8,
              }}
            >
              How it works
              <ArrowRight className="size-4 opacity-70" aria-hidden />
            </a>
          </div>

          <p
            className="mt-6 inline-flex items-start gap-2 text-sm"
            style={{ fontFamily: SANS, color: LEISE }}
          >
            <Lock className="mt-0.5 size-4 shrink-0" aria-hidden />
            <span>
              Encrypted in transit and at rest. The original audio is deleted after transcription,
              you keep the recap.
            </span>
          </p>
        </Auftauchen>

        <Auftauchen delay={0.15} className="mx-auto w-full max-w-2xl lg:mx-0">
          <Rueckblick />
        </Auftauchen>
      </Bahn>
    </section>
  );
}

/** Ein Wort mit einem gezeichneten Strich darunter, der sich einmal zieht. */
function Unterstrichen({ children }: { children: ReactNode }) {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLSpanElement>(null);
  const sichtbar = useInView(ref, { once: true, amount: 0.6 });
  return (
    <span ref={ref} className="relative inline-block whitespace-nowrap" style={{ color: BLAU }}>
      {children}
      <svg
        aria-hidden
        viewBox="0 0 220 12"
        preserveAspectRatio="none"
        className="pointer-events-none absolute inset-x-0 -bottom-2 h-[0.4em] w-full overflow-visible"
        style={{ color: BLAU_2 }}
      >
        <motion.path
          d="M2 7 C 40 2, 90 10, 140 5 S 210 8, 218 4"
          fill="none"
          stroke="currentColor"
          strokeWidth="2.5"
          strokeLinecap="round"
          strokeLinejoin="round"
          opacity="0.85"
          initial={reduce ? false : { pathLength: 0 }}
          animate={sichtbar || reduce ? { pathLength: 1 } : { pathLength: 0 }}
          transition={{ duration: reduce ? 0 : 0.9, ease: "easeOut", delay: 0.4 }}
        />
      </svg>
    </span>
  );
}

/**
 * Die Karte im Hero. Sie zeigt, was das Produkt liefert: der Satz schreibt
 * sich Wort fuer Wort, die Aufgaben haken sich nacheinander ab, dann kommt der
 * naechste Termin. Beim Anfahren haelt sie an, damit man lesen kann.
 */
function Rueckblick() {
  const reduce = useReducedMotion();
  const [i, setI] = useState(0);
  const pause = useRef(false);
  const ref = useRef<HTMLElement>(null);
  const sichtbar = useInView(ref, { amount: 0.3 });

  useEffect(() => {
    if (reduce || !sichtbar) return;
    const uhr = window.setInterval(() => {
      if (pause.current) return;
      setI((v) => (v + 1) % TERMINE.length);
    }, 6200);
    return () => window.clearInterval(uhr);
  }, [reduce, sichtbar]);

  const jetzt = TERMINE[i];

  return (
    <article
      ref={ref}
      className="relative mx-auto w-full max-w-xl overflow-hidden p-6 sm:p-8"
      style={{ borderRadius: 28, border: `1px solid ${KANTE}`, background: "#ffffff" }}
      onMouseEnter={() => (pause.current = true)}
      onMouseLeave={() => (pause.current = false)}
    >
      <header className="flex items-center justify-between gap-3">
        <div className="flex min-w-0 items-center gap-2.5">
          <Pegel groesse={22} />
          <p
            className="truncate text-sm"
            style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "-0.01em", color: TINTE }}
          >
            {jetzt.titel}
          </p>
        </div>
        <span
          className="inline-flex shrink-0 items-center gap-1.5 px-2.5 py-1 text-[10px]"
          style={{
            borderRadius: KNOPF,
            background: STUMM,
            color: "#3a3d47",
            fontFamily: SANS,
            fontWeight: 500,
          }}
        >
          <motion.span
            aria-hidden
            className="size-1.5 rounded-full"
            style={{ background: GRUEN }}
            animate={reduce ? undefined : { opacity: [1, 0.35, 1] }}
            transition={reduce ? undefined : { duration: 2, repeat: Infinity, ease: "easeInOut" }}
          />
          Done
        </span>
      </header>

      <div className="mt-4 flex items-center gap-3">
        <div className="flex gap-1.5">
          {jetzt.leute.map((kuerzel) => (
            <span
              key={kuerzel}
              className="grid size-7 place-items-center rounded-full text-[10px]"
              style={{ background: BLAU_SANFT, color: BLAU, fontFamily: SANS, fontWeight: 600 }}
            >
              {kuerzel}
            </span>
          ))}
        </div>
        <span className="text-xs" style={{ fontFamily: SANS, color: LEISE }}>
          {jetzt.dauer}
        </span>
      </div>

      <div className="mt-6">
        <p
          className="text-[10px] uppercase"
          style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "0.18em", color: LEISE }}
        >
          Summary
        </p>
        <p
          className="mt-2 min-h-[3.4rem] text-[17px] leading-relaxed"
          style={{ fontFamily: SANS, color: "#1a1c22" }}
        >
          <AnimatePresence mode="wait">
            <motion.span
              key={`s-${i}`}
              exit={{ opacity: 0, y: -6 }}
              transition={{ duration: 0.25 }}
            >
              {jetzt.zusammenfassung.map((wort, k) => (
                <motion.span
                  key={`${i}-${k}`}
                  className="inline-block"
                  initial={reduce ? false : { opacity: 0, y: 6 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: reduce ? 0 : 0.3, delay: reduce ? 0 : k * 0.055 }}
                >
                  {wort}
                  {k < jetzt.zusammenfassung.length - 1 ? " " : ""}
                </motion.span>
              ))}
            </motion.span>
          </AnimatePresence>
        </p>
      </div>

      <div className="mt-5 pt-5" style={{ borderTop: `1px solid ${KANTE}` }}>
        <p
          className="text-[10px] uppercase"
          style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "0.18em", color: LEISE }}
        >
          Action items
        </p>
        <ul className="mt-3 min-h-[5.75rem] space-y-2.5">
          {jetzt.aufgaben.map((aufgabe, k) => (
            <motion.li
              key={`${i}-${k}`}
              className="flex items-center gap-3"
              initial={reduce ? false : { opacity: 0, x: -6 }}
              animate={{ opacity: 1, x: 0 }}
              transition={{ duration: reduce ? 0 : 0.35, delay: reduce ? 0 : 0.5 + k * 0.13 }}
            >
              <span
                className="grid size-5 shrink-0 place-items-center"
                style={{
                  borderRadius: 6,
                  background: aufgabe.fertig ? BLAU : "#ffffff",
                  border: aufgabe.fertig ? "1px solid transparent" : `1px solid ${KANTE}`,
                  color: "#ffffff",
                }}
              >
                {aufgabe.fertig ? <Check className="size-3.5" strokeWidth={3} aria-hidden /> : null}
              </span>
              <span
                className="flex-1 truncate text-[15px]"
                style={{ fontFamily: SANS, color: TINTE }}
              >
                {aufgabe.was}
              </span>
              <span
                aria-hidden
                className="grid size-6 shrink-0 place-items-center rounded-full text-[10px]"
                style={{ background: STUMM, color: "#3a3d47", fontFamily: SANS, fontWeight: 600 }}
              >
                {aufgabe.wer}
              </span>
              <span
                className="w-14 shrink-0 text-right text-xs"
                style={{ fontFamily: SANS, color: LEISE }}
              >
                {aufgabe.wann}
              </span>
            </motion.li>
          ))}
        </ul>
      </div>

      <footer
        className="mt-6 flex flex-wrap items-center gap-x-4 gap-y-2 pt-5"
        style={{ borderTop: `1px solid ${KANTE}` }}
      >
        <div
          className="flex flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-[11px]"
          style={{ fontFamily: SANS, color: LEISE }}
        >
          <MonitorPlay className="size-3.5 shrink-0" aria-hidden />
          <span>Works with</span>
          <span style={{ fontWeight: 500, color: "#2c2f38" }}>Zoom · Meet · Teams</span>
        </div>
        <span
          className="inline-flex shrink-0 items-center gap-1.5 text-[11px]"
          style={{ fontFamily: SANS, color: LEISE }}
        >
          <Sparkles className="size-3" aria-hidden />
          42 seconds
        </span>
      </footer>
    </article>
  );
}

/* ------------------------------------------------------------------ vertrauen */

function Vertrauensleiste() {
  return (
    <section aria-label="Trust" style={{ background: FLAECHE }}>
      <Bahn className="py-6">
        <ul
          className="flex flex-wrap items-center justify-center gap-x-8 gap-y-3 text-sm md:text-base"
          style={{ fontFamily: SANS, color: LEISE }}
        >
          {VERTRAUEN.map(({ label, Icon }) => (
            <li key={label} className="inline-flex items-center gap-2">
              <Icon className="size-4" style={{ color: "#7a8090" }} aria-hidden strokeWidth={1.7} />
              <span>{label}</span>
            </li>
          ))}
        </ul>
      </Bahn>
    </section>
  );
}

/* --------------------------------------------------------------------- weg */

function Produktweg() {
  return (
    <section id="journey" className="scroll-mt-24 py-16 md:py-32">
      <Bahn>
        <Auftauchen className="mx-auto max-w-2xl text-center">
          <Kicker>How {MARKE.name} works</Kicker>
          <div className="mt-3">
            <Titel>Capture. Recap. Automate.</Titel>
          </div>
          <p className="mt-5 text-lg" style={{ fontFamily: SANS, color: LEISE }}>
            Three steps that build on each other, from the recording to the automatic follow-up.
          </p>
        </Auftauchen>

        <ol className="mt-10 grid gap-8 md:mt-16 lg:grid-cols-3">
          {WEG.map((teil, i) => (
            <li key={teil.name} className="h-full">
              <Auftauchen delay={i * 0.12} className="h-full">
                <a
                  href="#pricing"
                  className="group flex h-full w-full flex-col overflow-hidden p-8 transition-colors"
                  style={{ borderRadius: KARTE, background: FLAECHE_2 }}
                >
                  <div className="mb-6 flex items-center justify-between">
                    <span
                      className="text-sm"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.16em",
                        color: LEISE,
                      }}
                    >
                      {teil.schritt}
                    </span>
                    <span
                      className="px-3 py-1 text-xs uppercase"
                      style={{
                        borderRadius: KNOPF,
                        background: "#e2e6f0",
                        color: "#3a3d47",
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.1em",
                      }}
                    >
                      {teil.marke}
                    </span>
                  </div>

                  <div className="mb-6 flex h-12 items-center transition-transform duration-500 ease-out group-hover:scale-105">
                    <Pegel groesse={56} farbe={teil.farbe} />
                  </div>

                  <h3
                    className="text-2xl"
                    style={{ fontFamily: SANS, fontWeight: 500, color: TINTE }}
                  >
                    {teil.name}
                  </h3>
                  <p
                    className="mt-2 text-base italic"
                    style={{ fontFamily: SANS, color: "#33363f" }}
                  >
                    {teil.frage}
                  </p>
                  <p className="mt-3 text-base" style={{ fontFamily: SANS, color: LEISE }}>
                    {teil.text}
                  </p>

                  <span
                    className="mt-6 inline-flex items-center gap-2 text-base transition-all duration-300 group-hover:gap-3"
                    style={{ fontFamily: SANS, fontWeight: 500, color: TINTE }}
                  >
                    See plans
                    <ArrowRight
                      className="size-4 transition-transform duration-300 group-hover:translate-x-1"
                      aria-hidden
                    />
                  </span>
                </a>
              </Auftauchen>
            </li>
          ))}
        </ol>
      </Bahn>
    </section>
  );
}

/* ----------------------------------------------------------------- stimmen */

function Stimmen() {
  return (
    <section
      id="testimonials"
      className="scroll-mt-24 py-16 md:py-32"
      style={{ background: FLAECHE }}
    >
      <Bahn>
        <Auftauchen className="mx-auto max-w-2xl text-center">
          <Titel>No meeting slips through the cracks</Titel>
          <p className="mt-5 text-lg" style={{ fontFamily: SANS, color: LEISE }}>
            Three of many teams that never forget anything important with {MARKE.name}.
          </p>
        </Auftauchen>

        <ul className="mt-12 grid gap-8 md:mt-16 md:grid-cols-3">
          {STIMMEN.map((stimme, i) => (
            <li key={stimme.name} className="h-full">
              <Auftauchen delay={i * 0.12} className="h-full">
                <figure
                  className="flex h-full w-full flex-col p-8"
                  style={{ borderRadius: KARTE, background: "#ffffff" }}
                >
                  <span
                    className="flex gap-1"
                    aria-label="5 out of 5 stars"
                    style={{ color: VIOLETT }}
                  >
                    {Array.from({ length: 5 }).map((_, k) => (
                      <Star
                        key={k}
                        className="size-4"
                        fill="currentColor"
                        strokeWidth={0}
                        aria-hidden
                      />
                    ))}
                  </span>
                  <blockquote
                    className="mt-5 flex-1 text-lg leading-relaxed"
                    style={{ fontFamily: SANS, color: TINTE }}
                  >
                    {stimme.text}
                  </blockquote>
                  <figcaption className="mt-6 flex items-center gap-3 pt-2">
                    <Monogramm name={stimme.name} />
                    <span className="min-w-0">
                      <span
                        className="block text-base"
                        style={{ fontFamily: SANS, fontWeight: 600, color: TINTE }}
                      >
                        {stimme.name}
                      </span>
                      <span className="block text-sm" style={{ fontFamily: SANS, color: LEISE }}>
                        {stimme.rolle}
                      </span>
                    </span>
                  </figcaption>
                </figure>
              </Auftauchen>
            </li>
          ))}
        </ul>
      </Bahn>
    </section>
  );
}

/* -------------------------------------------------------------- so laeuft es */

function SoLaeuftEs() {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLDivElement>(null);
  const sichtbar = useInView(ref, { once: true, margin: "-100px" });

  return (
    <section id="how-it-works" className="scroll-mt-24 py-16 md:py-32">
      <Bahn>
        <Auftauchen className="mx-auto max-w-2xl text-center">
          <Titel>How it works</Titel>
          <p className="mt-5 text-lg" style={{ fontFamily: SANS, color: LEISE }}>
            Ready in under a minute, try it without signing up.
          </p>
        </Auftauchen>

        <div ref={ref} className="relative mt-12 md:mt-20">
          {/* Der Verbinder laeuft hinter den drei Karten durch und zeichnet
              sich einmal. Nur am Schreibtisch: darunter stehen die Karten
              untereinander, und eine Linie quer waere dort sinnlos. */}
          <svg
            aria-hidden
            viewBox="0 0 1000 400"
            preserveAspectRatio="none"
            className="pointer-events-none absolute inset-0 hidden h-full w-full overflow-visible lg:block"
            style={{ color: BLAU_2 }}
          >
            <motion.path
              d="M 157 400 L 157 414 Q 157 426 169 426 L 488 426 Q 500 426 500 414 L 500 -14 Q 500 -26 512 -26 L 831 -26 Q 843 -26 843 -14 L 843 0"
              fill="none"
              stroke="currentColor"
              strokeWidth="1.5"
              strokeLinecap="round"
              strokeLinejoin="round"
              opacity="0.4"
              initial={reduce ? false : { pathLength: 0 }}
              animate={sichtbar || reduce ? { pathLength: 1 } : { pathLength: 0 }}
              transition={{ duration: reduce ? 0 : 1.6, ease: "easeInOut" }}
            />
          </svg>

          <ol className="relative grid gap-8 md:grid-cols-3">
            {SCHRITTE.map((schritt, i) => (
              <li key={schritt.titel}>
                <Auftauchen delay={i * 0.12}>
                  <div
                    className="h-full p-6 md:p-8"
                    style={{ borderRadius: KARTE, background: FLAECHE_2 }}
                  >
                    <div className="flex items-center gap-3">
                      <span
                        className="grid size-10 shrink-0 place-items-center text-base"
                        style={{
                          borderRadius: 12,
                          background: BLAU_SANFT,
                          color: BLAU,
                          fontFamily: SANS,
                          fontWeight: 600,
                        }}
                      >
                        {String(i + 1).padStart(2, "0")}
                      </span>
                      <h3
                        className="text-xl"
                        style={{ fontFamily: SANS, fontWeight: 500, color: TINTE }}
                      >
                        {schritt.titel}
                      </h3>
                    </div>
                    <p className="mt-3 text-base" style={{ fontFamily: SANS, color: LEISE }}>
                      {schritt.text}
                    </p>
                  </div>
                </Auftauchen>
              </li>
            ))}
          </ol>
        </div>

        <p
          className="mt-16 text-center text-sm md:mt-20"
          style={{ fontFamily: SANS, fontWeight: 500, color: LEISE }}
        >
          Takes under a minute.
        </p>
      </Bahn>
    </section>
  );
}

/* ------------------------------------------------------------------- archiv */

function Archiv() {
  return (
    <section id="archive" className="scroll-mt-24 py-16 md:py-28">
      <Bahn>
        <Auftauchen>
          <div
            className="relative overflow-hidden"
            style={{
              borderRadius: 32,
              border: `1px solid ${KANTE}`,
              backgroundImage: `linear-gradient(135deg, ${FLAECHE} 0%, #ffffff 46%, ${BLAU_SANFT} 100%)`,
            }}
          >
            <div
              aria-hidden
              className="pointer-events-none absolute inset-0"
              style={{
                backgroundImage:
                  "radial-gradient(circle, rgba(58,91,196,0.14) 1px, transparent 1px)",
                backgroundSize: "24px 24px",
                maskImage: "radial-gradient(ellipse 75% 85% at 82% 55%, #000 0%, transparent 72%)",
                WebkitMaskImage:
                  "radial-gradient(ellipse 75% 85% at 82% 55%, #000 0%, transparent 72%)",
              }}
            />

            <div className="relative grid items-center gap-10 p-8 md:gap-14 md:p-14 lg:grid-cols-2">
              <div className="order-2 lg:order-1">
                <Kicker>Your meeting memory</Kicker>
                <h2
                  className="mt-3 text-3xl leading-[1.1] md:text-4xl lg:text-[2.75rem]"
                  style={{
                    fontFamily: SANS,
                    fontWeight: 500,
                    letterSpacing: "-0.02em",
                    color: TINTE,
                  }}
                >
                  Every meeting. In one place.
                </h2>
                <p
                  className="mt-5 text-lg leading-relaxed"
                  style={{ fontFamily: SANS, color: LEISE }}
                >
                  {MARKE.name} files every recap neatly, with summary, decisions and action items.
                  So you can find any point again, share results in one click, and nobody has to
                  scramble to take notes.
                </p>
                <ul className="mt-7 space-y-3">
                  {ARCHIV_PUNKTE.map((punkt) => (
                    <li key={punkt} className="flex items-start gap-3">
                      <span
                        className="mt-0.5 grid size-6 shrink-0 place-items-center rounded-full"
                        style={{ background: BLAU, color: "#ffffff" }}
                      >
                        <Check className="size-3.5" strokeWidth={3} aria-hidden />
                      </span>
                      <span className="text-base" style={{ fontFamily: SANS, color: TINTE }}>
                        {punkt}
                      </span>
                    </li>
                  ))}
                </ul>
                <a
                  href="#upload"
                  className="mt-8 inline-flex items-center gap-1.5 text-base"
                  style={{ fontFamily: SANS, fontWeight: 500, color: TINTE }}
                >
                  Try it for free
                  <ArrowRight className="size-4" aria-hidden />
                </a>
              </div>

              <div className="order-1 lg:order-2">
                <ArchivBild />
              </div>
            </div>
          </div>
        </Auftauchen>
      </Bahn>
    </section>
  );
}

/**
 * Ein Stapel Rueckblicke als Zeichnung. Tiefe kommt aus Kante und Versatz,
 * nicht aus einem Schatten. Der Pegel atmet, die Haken zeichnen sich, sobald
 * das Bild im Sichtfeld steht.
 */
function ArchivBild() {
  const reduce = useReducedMotion();
  const ref = useRef<SVGSVGElement>(null);
  const sichtbar = useInView(ref, { once: true, amount: 0.3 });
  const balken = [
    { x: 202, h: 10, f: BLAU },
    { x: 208, h: 18, f: BLAU_2 },
    { x: 214, h: 24, f: VIOLETT },
    { x: 220, h: 14, f: BLAU },
    { x: 226, h: 8, f: BLAU_2 },
  ];
  const zeilen = [
    { y: 168, w: 200 },
    { y: 190, w: 232 },
    { y: 212, w: 168 },
  ];
  const haken = [252, 292, 332];

  return (
    <svg
      ref={ref}
      viewBox="0 0 640 480"
      fill="none"
      role="img"
      aria-label="Stacked meeting recaps with search and completed action items"
      className="w-full"
    >
      <rect
        x="215"
        y="72"
        width="210"
        height="258"
        rx="18"
        fill="#e2e7f6"
        stroke="#dfe3ee"
        strokeWidth="1.5"
      />
      <rect
        x="196"
        y="88"
        width="248"
        height="266"
        rx="20"
        fill={BLAU_SANFT}
        stroke="#dfe3ee"
        strokeWidth="1.5"
      />
      <rect
        x="176"
        y="104"
        width="288"
        height="288"
        rx="22"
        fill="#ffffff"
        stroke="#e4e7ef"
        strokeWidth="1.5"
      />

      {balken.map((b, i) => (
        <motion.rect
          key={b.x}
          x={b.x}
          y={137 - b.h}
          width={3.4}
          height={b.h}
          rx={1.7}
          fill={b.f}
          style={{ originY: 1 }}
          animate={reduce ? undefined : { scaleY: [1, 0.55, 1] }}
          transition={
            reduce
              ? undefined
              : { duration: 1.8, repeat: Infinity, ease: "easeInOut", delay: i * 0.12 }
          }
        />
      ))}
      <rect x="240" y="124" width="110" height="11" rx="5.5" fill="#dfe3ee" />
      <rect x="388" y="122" width="50" height="20" rx="10" fill="#e9f4ee" />
      <circle cx="399" cy="132" r="3.4" fill={GRUEN} />
      <rect x="407" y="128" width="24" height="8" rx="4" fill="#bcdcc9" />

      {zeilen.map((z) => (
        <rect key={z.y} x="202" y={z.y} width={z.w} height="9" rx="4.5" fill="#e8ebf3" />
      ))}

      {haken.map((y, i) => (
        <g key={y}>
          <rect x="202" y={y} width="236" height="30" rx="10" fill={FLAECHE} />
          <motion.path
            d={`M 214 ${y + 15} l 5 5 l 9 -10`}
            fill="none"
            stroke={BLAU}
            strokeWidth="2.6"
            strokeLinecap="round"
            strokeLinejoin="round"
            initial={reduce ? false : { pathLength: 0 }}
            animate={sichtbar || reduce ? { pathLength: 1 } : { pathLength: 0 }}
            transition={{ duration: reduce ? 0 : 0.4, delay: reduce ? 0 : 0.3 + i * 0.22 }}
          />
          <rect x="240" y={y + 11} width={150 - i * 26} height="8" rx="4" fill="#dfe3ee" />
        </g>
      ))}

      {/* Der Sucher links: eine Lupe auf einem Feld. */}
      <rect
        x="58"
        y="196"
        width="150"
        height="44"
        rx="14"
        fill="#ffffff"
        stroke="#e4e7ef"
        strokeWidth="1.5"
      />
      <circle cx="82" cy="218" r="7.5" fill="none" stroke={LEISE} strokeWidth="2.2" />
      <path d="M 88 224 l 7 7" stroke={LEISE} strokeWidth="2.2" strokeLinecap="round" />
      <rect x="102" y="214" width="86" height="8" rx="4" fill="#e8ebf3" />

      {/* Der Schnipsel mit dem aufgenommenen Ton. */}
      <rect
        x="46"
        y="292"
        width="130"
        height="60"
        rx="16"
        fill="#ffffff"
        stroke="#e4e7ef"
        strokeWidth="1.5"
      />
      {[
        { x: 62, h: 8, f: BLAU_2 },
        { x: 70, h: 16, f: BLAU },
        { x: 78, h: 26, f: VIOLETT },
        { x: 86, h: 12, f: BLAU },
        { x: 94, h: 6, f: BLAU_2 },
        { x: 102, h: 20, f: BLAU },
        { x: 110, h: 10, f: VIOLETT },
      ].map((b, i) => (
        <motion.rect
          key={b.x}
          x={b.x}
          y={334 - b.h}
          width={3.4}
          height={b.h}
          rx={1.7}
          fill={b.f}
          style={{ originY: 1 }}
          animate={reduce ? undefined : { scaleY: [1, 0.5, 1] }}
          transition={
            reduce
              ? undefined
              : { duration: 1.6, repeat: Infinity, ease: "easeInOut", delay: i * 0.09 }
          }
        />
      ))}
      <rect x="122" y="310" width="40" height="8" rx="4" fill="#e8ebf3" />
      <rect x="122" y="324" width="26" height="8" rx="4" fill="#eef0f6" />
    </svg>
  );
}

/* --------------------------------------------------------------- hochladen */

function Hochladen() {
  const reduce = useReducedMotion();
  const [zustand, setZustand] = useState<"ruhe" | "laeuft" | "fertig">("ruhe");
  const [schritt, setSchritt] = useState(0);
  const [ueber, setUeber] = useState(false);

  const starten = useCallback(() => {
    if (zustand === "laeuft") return;
    setZustand("laeuft");
    setSchritt(0);
  }, [zustand]);

  useEffect(() => {
    if (zustand !== "laeuft") return;
    if (reduce) {
      setSchritt(HOCHLADEN_SCHRITTE.length);
      setZustand("fertig");
      return;
    }
    if (schritt >= HOCHLADEN_SCHRITTE.length) {
      const t = window.setTimeout(() => setZustand("fertig"), 500);
      return () => window.clearTimeout(t);
    }
    const t = window.setTimeout(() => setSchritt((s) => s + 1), 850);
    return () => window.clearTimeout(t);
  }, [zustand, schritt, reduce]);

  const anteil = Math.round(
    (Math.min(schritt, HOCHLADEN_SCHRITTE.length) / HOCHLADEN_SCHRITTE.length) * 100,
  );

  return (
    <section id="upload" className="scroll-mt-24 py-16 md:py-32" style={{ background: FLAECHE }}>
      <Bahn>
        <Auftauchen className="mx-auto max-w-2xl text-center">
          <Kicker>Try it now</Kicker>
          <div className="mt-3">
            <Titel>Upload a recording</Titel>
          </div>
          <p className="mt-5 text-lg" style={{ fontFamily: SANS, color: LEISE }}>
            No account needed. You will see exactly what your recap will look like.
          </p>
        </Auftauchen>

        <Auftauchen delay={0.1} className="mx-auto mt-10 max-w-3xl md:mt-14">
          <div className="p-6 sm:p-10" style={{ borderRadius: 28, background: "#ffffff" }}>
            {zustand === "ruhe" ? (
              <button
                type="button"
                onClick={starten}
                onDragOver={(e) => {
                  e.preventDefault();
                  setUeber(true);
                }}
                onDragLeave={() => setUeber(false)}
                onDrop={(e) => {
                  e.preventDefault();
                  setUeber(false);
                  starten();
                }}
                className="flex w-full flex-col items-center justify-center px-6 py-14 text-center transition-colors"
                style={{
                  borderRadius: KARTE,
                  border: `2px dashed ${ueber ? BLAU : "#ccd3e6"}`,
                  background: ueber ? BLAU_SANFT : FLAECHE,
                }}
              >
                <span
                  className="grid size-14 place-items-center rounded-full"
                  style={{ background: BLAU_SANFT, color: BLAU }}
                >
                  <FileAudio className="size-6" strokeWidth={1.7} aria-hidden />
                </span>
                <span
                  className="mt-5 text-xl"
                  style={{ fontFamily: SANS, fontWeight: 500, color: TINTE }}
                >
                  {ueber ? `Release, ${MARKE.name} takes over` : "Drop your file here"}
                </span>
                <span className="mt-2 max-w-sm text-sm" style={{ fontFamily: SANS, color: LEISE }}>
                  or click to choose. MP3, WAV, M4A or video, even a Zoom recording.
                </span>
              </button>
            ) : (
              <div>
                <div className="flex flex-wrap items-center justify-between gap-3">
                  <div className="flex min-w-0 items-center gap-3">
                    <span
                      className="grid size-10 shrink-0 place-items-center rounded-full"
                      style={{ background: BLAU_SANFT, color: BLAU }}
                    >
                      <FileAudio className="size-5" strokeWidth={1.7} aria-hidden />
                    </span>
                    <span className="min-w-0">
                      <span
                        className="block truncate text-base"
                        style={{ fontFamily: SANS, fontWeight: 500, color: TINTE }}
                      >
                        weekly-sync.mp3
                      </span>
                      <span className="block text-sm" style={{ fontFamily: SANS, color: LEISE }}>
                        {zustand === "fertig"
                          ? "summarized in 42 seconds"
                          : `Step ${Math.min(schritt + 1, HOCHLADEN_SCHRITTE.length)} of ${HOCHLADEN_SCHRITTE.length}`}
                      </span>
                    </span>
                  </div>
                  <span
                    className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs"
                    style={{
                      borderRadius: KNOPF,
                      background: zustand === "fertig" ? "#e9f4ee" : STUMM,
                      color: zustand === "fertig" ? GRUEN : "#3a3d47",
                      fontFamily: SANS,
                      fontWeight: 600,
                    }}
                  >
                    {zustand === "fertig" ? "Done" : `${anteil}%`}
                  </span>
                </div>

                {/* Der Balken laeuft mit den Schritten, nicht mit einer Uhr:
                    er zeigt, wo die Arbeit steht, nicht wie lange sie dauert. */}
                <div
                  className="mt-6 h-1.5 w-full overflow-hidden"
                  style={{ borderRadius: 999, background: STUMM }}
                >
                  <motion.div
                    className="h-full"
                    style={{ background: BLAU, borderRadius: 999 }}
                    initial={{ width: "0%" }}
                    animate={{ width: `${anteil}%` }}
                    transition={{ duration: reduce ? 0 : 0.6, ease: "easeOut" }}
                  />
                </div>

                <ul className="mt-7 space-y-3">
                  {HOCHLADEN_SCHRITTE.map((text, i) => {
                    const fertig = i < schritt;
                    return (
                      <li key={text} className="flex items-center gap-3">
                        <span
                          className="grid size-6 shrink-0 place-items-center rounded-full transition-colors"
                          style={{
                            background: fertig ? BLAU : STUMM,
                            color: fertig ? "#ffffff" : "#aeb3c2",
                          }}
                        >
                          <Check className="size-3.5" strokeWidth={3} aria-hidden />
                        </span>
                        <span
                          className="text-base transition-colors"
                          style={{ fontFamily: SANS, color: fertig ? TINTE : LEISE }}
                        >
                          {text}
                        </span>
                      </li>
                    );
                  })}
                </ul>

                {zustand === "fertig" ? (
                  <div
                    className="mt-8 p-6"
                    style={{
                      borderRadius: KARTE,
                      background: FLAECHE,
                      border: `1px solid ${KANTE}`,
                    }}
                  >
                    <p
                      className="text-[10px] uppercase"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.18em",
                        color: LEISE,
                      }}
                    >
                      Summary
                    </p>
                    <p
                      className="mt-2 text-[17px] leading-relaxed"
                      style={{ fontFamily: SANS, color: TINTE }}
                    >
                      The team approved the 12% discount for deals closed by end of Q3. The API
                      blocker will be resolved with Mert tomorrow, and the revised timeline goes to
                      the client by Friday.
                    </p>
                    <button
                      type="button"
                      onClick={() => {
                        setZustand("ruhe");
                        setSchritt(0);
                      }}
                      className="mt-6 inline-flex items-center gap-2 px-5 py-2.5 text-sm"
                      style={{
                        borderRadius: KNOPF,
                        background: TINTE,
                        color: "#ffffff",
                        fontFamily: SANS,
                        fontWeight: 500,
                      }}
                    >
                      New recording
                    </button>
                  </div>
                ) : null}
              </div>
            )}

            <ul
              className="mt-7 flex flex-wrap items-center gap-x-6 gap-y-2 text-xs"
              style={{ fontFamily: SANS, color: LEISE }}
            >
              {[
                "Encrypted in transit and at rest",
                "GDPR-compliant, servers in the EU",
                "Audio deleted after transcription",
              ].map((punkt) => (
                <li key={punkt} className="inline-flex items-center gap-1.5">
                  <ShieldCheck
                    className="size-3.5"
                    style={{ color: BLAU }}
                    strokeWidth={1.8}
                    aria-hidden
                  />
                  {punkt}
                </li>
              ))}
            </ul>
          </div>
        </Auftauchen>
      </Bahn>
    </section>
  );
}

/* ------------------------------------------------------------------- preise */

function Preise() {
  return (
    <section id="pricing" className="scroll-mt-24 py-16 md:py-32">
      <Bahn>
        <Auftauchen className="mx-auto max-w-2xl text-center">
          <Kicker>Pricing</Kicker>
          <div className="mt-3">
            <Titel>Fair pricing, no surprises</Titel>
          </div>
          <p className="mt-5 text-lg" style={{ fontFamily: SANS, color: LEISE }}>
            You only pay for what you use, and can cancel anytime.
          </p>
        </Auftauchen>

        <ul className="mt-14 grid gap-8 lg:mt-16 lg:grid-cols-3">
          {PREISE.map((plan, i) => (
            <li key={plan.name} className="h-full">
              <Auftauchen delay={i * 0.12} className="h-full">
                <div className="relative h-full w-full pt-4">
                  {plan.hervor ? (
                    <span
                      className="pointer-events-none absolute left-1/2 top-4 z-10 inline-flex -translate-x-1/2 -translate-y-1/2 items-center gap-1.5 whitespace-nowrap px-3 py-1 text-xs"
                      style={{
                        borderRadius: KNOPF,
                        background: VIOLETT,
                        color: "#ffffff",
                        fontFamily: SANS,
                        fontWeight: 600,
                      }}
                    >
                      <Sparkles className="size-3.5" aria-hidden />
                      Recommended
                    </span>
                  ) : null}
                  <div
                    className="relative flex h-full flex-col p-6 sm:p-8"
                    style={{
                      borderRadius: KARTE,
                      background: plan.hervor ? BLAU : FLAECHE,
                      color: plan.hervor ? "#ffffff" : TINTE,
                      border: `1px solid ${plan.hervor ? BLAU : KANTE}`,
                    }}
                  >
                    <span
                      className="text-xs uppercase"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.16em",
                        color: plan.hervor ? "rgba(255,255,255,0.9)" : BLAU,
                      }}
                    >
                      {plan.marke}
                    </span>

                    <h3
                      className="mt-2 text-2xl"
                      style={{ fontFamily: SANS, fontWeight: 500, letterSpacing: "-0.02em" }}
                    >
                      {plan.name}
                    </h3>
                    <p
                      className="mt-2 text-base"
                      style={{
                        fontFamily: SANS,
                        color: plan.hervor ? "rgba(255,255,255,0.85)" : LEISE,
                      }}
                    >
                      {plan.text}
                    </p>

                    <div className="mt-8 flex flex-wrap items-baseline gap-x-2 gap-y-1">
                      <span
                        className="text-4xl sm:text-5xl"
                        style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "-0.03em" }}
                      >
                        {plan.preis}
                      </span>
                      <span
                        className="text-sm"
                        style={{
                          fontFamily: SANS,
                          color: plan.hervor ? "rgba(255,255,255,0.9)" : LEISE,
                        }}
                      >
                        {plan.je}
                      </span>
                    </div>

                    <div
                      aria-hidden
                      className="my-8 h-px w-full"
                      style={{ background: plan.hervor ? "rgba(255,255,255,0.22)" : KANTE }}
                    />

                    <ul className="space-y-3.5">
                      {plan.punkte.map((punkt) => (
                        <li key={punkt} className="flex items-start gap-3 text-base">
                          <span
                            className="mt-0.5 grid size-5 shrink-0 place-items-center rounded-full"
                            style={{
                              background: plan.hervor ? "rgba(255,255,255,0.18)" : BLAU_SANFT,
                              color: plan.hervor ? "#ffffff" : BLAU,
                            }}
                          >
                            <Check className="size-3" strokeWidth={3} aria-hidden />
                          </span>
                          <span
                            style={{
                              fontFamily: SANS,
                              color: plan.hervor ? "rgba(255,255,255,0.95)" : TINTE,
                            }}
                          >
                            {punkt}
                          </span>
                        </li>
                      ))}
                    </ul>

                    <div className="mt-auto pt-10">
                      <a
                        href="#upload"
                        className="inline-flex min-h-12 w-full items-center justify-center px-6 py-3 text-base sm:w-auto"
                        style={{
                          borderRadius: KNOPF,
                          background: plan.hervor ? "#ffffff" : TINTE,
                          color: plan.hervor ? BLAU : "#ffffff",
                          fontFamily: SANS,
                          fontWeight: 600,
                        }}
                      >
                        {plan.cta}
                      </a>
                    </div>
                  </div>
                </div>
              </Auftauchen>
            </li>
          ))}
        </ul>
      </Bahn>
    </section>
  );
}

/* -------------------------------------------------------------- sicherheit */

function Sicherheit() {
  return (
    <section id="security" className="scroll-mt-24 py-16 md:py-32" style={{ background: FLAECHE }}>
      <Bahn>
        <Auftauchen className="mx-auto max-w-2xl text-center">
          <Kicker>Security and privacy</Kicker>
          <div className="mt-3">
            <Titel>Your meetings stay yours</Titel>
          </div>
          <p className="mt-5 text-lg" style={{ fontFamily: SANS, color: LEISE }}>
            Built for sensitive conversations, with clear rules on where your data goes and how long
            it stays.
          </p>
        </Auftauchen>

        <div className="mt-12 grid items-center gap-10 md:mt-16 lg:grid-cols-2 lg:gap-16">
          <Auftauchen className="order-1">
            <SchildBild />
          </Auftauchen>

          <ul className="order-2 space-y-7">
            {SICHERHEIT.map((punkt, i) => (
              <li key={punkt.titel}>
                <Auftauchen delay={i * 0.1} className="flex items-start gap-4">
                  <span
                    className="grid size-11 shrink-0 place-items-center"
                    style={{ borderRadius: 12, background: BLAU_SANFT, color: BLAU }}
                  >
                    <punkt.Icon className="size-5" strokeWidth={1.75} aria-hidden />
                  </span>
                  <div>
                    <h3
                      className="text-lg"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 500,
                        letterSpacing: "-0.01em",
                        color: TINTE,
                      }}
                    >
                      {punkt.titel}
                    </h3>
                    <p
                      className="mt-1.5 text-sm leading-relaxed"
                      style={{ fontFamily: SANS, color: LEISE }}
                    >
                      {punkt.text}
                    </p>
                  </div>
                </Auftauchen>
              </li>
            ))}
          </ul>
        </div>
      </Bahn>
    </section>
  );
}

/** Ein Schild, das sich einmal zeichnet, mit einem Schloss darin. */
function SchildBild() {
  const reduce = useReducedMotion();
  const ref = useRef<SVGSVGElement>(null);
  const sichtbar = useInView(ref, { once: true, amount: 0.4 });
  return (
    <svg
      ref={ref}
      viewBox="0 0 420 360"
      fill="none"
      role="img"
      aria-label="A shield with a lock, standing on a base of stored recaps"
      className="mx-auto w-full max-w-md lg:max-w-none"
    >
      <rect x="86" y="266" width="248" height="20" rx="10" fill="#e2e7f6" />
      <rect x="112" y="296" width="196" height="16" rx="8" fill={BLAU_SANFT} />
      <rect x="142" y="322" width="136" height="12" rx="6" fill="#eef0f6" />

      <motion.path
        d="M 210 36 L 322 74 V 158 C 322 214 274 250 210 268 C 146 250 98 214 98 158 V 74 Z"
        fill="#ffffff"
        stroke={BLAU}
        strokeWidth="2.4"
        strokeLinejoin="round"
        initial={reduce ? false : { pathLength: 0 }}
        animate={sichtbar || reduce ? { pathLength: 1 } : { pathLength: 0 }}
        transition={{ duration: reduce ? 0 : 1.3, ease: "easeInOut" }}
      />
      <path
        d="M 210 52 L 306 84 V 156 C 306 204 266 236 210 252 C 154 236 114 204 114 156 V 84 Z"
        fill={BLAU_SANFT}
        opacity="0.55"
      />

      <rect x="182" y="150" width="56" height="46" rx="10" fill={BLAU} />
      <path
        d="M 192 150 v -12 a 18 18 0 0 1 36 0 v 12"
        fill="none"
        stroke={BLAU}
        strokeWidth="7"
        strokeLinecap="round"
      />
      <circle cx="210" cy="170" r="5" fill="#ffffff" />
      <rect x="207" y="172" width="6" height="12" rx="3" fill="#ffffff" />

      <motion.circle
        cx="210"
        cy="152"
        r="108"
        fill="none"
        stroke={BLAU_2}
        strokeWidth="1.2"
        opacity="0.35"
        strokeDasharray="4 10"
        animate={reduce ? undefined : { rotate: 360 }}
        transition={reduce ? undefined : { duration: 70, repeat: Infinity, ease: "linear" }}
        style={{ originX: "210px", originY: "152px" }}
      />
    </svg>
  );
}

/* --------------------------------------------------------------- fusszeile */

function Fusszeile() {
  return (
    <footer id="about" className="scroll-mt-24" style={{ background: FLAECHE }}>
      <div className="mx-auto w-full max-w-[1200px] px-6 pb-10 pt-2 md:px-12 md:pb-16 md:pt-4">
        <div
          className="px-6 py-12 sm:px-12 md:px-16 md:py-24 lg:px-20 lg:py-28"
          style={{ borderRadius: 40, background: "#ffffff" }}
        >
          <div className="grid gap-12 md:grid-cols-12">
            <div className="md:col-span-5">
              <div className="flex items-center gap-2.5">
                <Pegel />
                <span
                  className="text-xl md:text-2xl"
                  style={{
                    fontFamily: SANS,
                    fontWeight: 600,
                    letterSpacing: "-0.02em",
                    color: TINTE,
                  }}
                >
                  {MARKE.name}
                </span>
              </div>
              <p
                className="mt-5 max-w-md text-base leading-relaxed"
                style={{ fontFamily: SANS, color: LEISE }}
              >
                {MARKE.name} turns every meeting into a clear recap, with decisions, action items
                and follow-up.
              </p>

              <a
                href={`mailto:${MARKE.mail}`}
                className="mt-6 inline-flex items-center gap-2 text-base"
                style={{ fontFamily: SANS, fontWeight: 500, color: TINTE }}
              >
                <Mail className="size-4" aria-hidden />
                {MARKE.mail}
              </a>

              <ul className="mt-8 flex flex-wrap gap-2">
                {[
                  { label: "GDPR-compliant", Icon: ShieldCheck },
                  { label: "Encrypted in transit and at rest", Icon: Lock },
                  { label: "Servers in the EU", Icon: MapPin },
                ].map(({ label, Icon }) => (
                  <li
                    key={label}
                    className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs"
                    style={{
                      borderRadius: KNOPF,
                      background: BLAU_SANFT,
                      color: "#3a3d47",
                      fontFamily: SANS,
                      fontWeight: 500,
                    }}
                  >
                    <Icon
                      className="size-3.5"
                      style={{ color: BLAU }}
                      aria-hidden
                      strokeWidth={1.8}
                    />
                    {label}
                  </li>
                ))}
              </ul>
            </div>

            <nav
              aria-label="Footer"
              className="grid grid-cols-2 gap-8 sm:grid-cols-3 md:col-span-7"
            >
              {FUSS_SPALTEN.map((spalte) => (
                <div key={spalte.titel}>
                  <h3
                    className="text-xs uppercase"
                    style={{
                      fontFamily: SANS,
                      fontWeight: 600,
                      letterSpacing: "0.16em",
                      color: "#6d7280",
                    }}
                  >
                    {spalte.titel}
                  </h3>
                  <ul className="mt-3 space-y-1">
                    {spalte.links.map((link) => (
                      <li key={link}>
                        <a
                          href="#top"
                          className="inline-block py-1.5 text-base transition-colors"
                          style={{ fontFamily: SANS, color: "#2c2f38" }}
                        >
                          {link}
                        </a>
                      </li>
                    ))}
                  </ul>
                </div>
              ))}
            </nav>
          </div>

          <div
            className="mt-14 flex flex-col gap-3 pt-6 text-sm md:flex-row md:items-center md:justify-between"
            style={{ borderTop: `1px solid ${KANTE}`, fontFamily: SANS, color: LEISE }}
          >
            <p>© 2026 {MARKE.name}. All rights reserved.</p>
            <p>{MARKE.name} produces automatic summaries, please review for accuracy.</p>
          </div>
        </div>
      </div>
    </footer>
  );
}

/* --------------------------------------------------------------------- page */

export function ConferMeetingsPage() {
  return (
    <div style={{ background: "#ffffff", color: TINTE, fontFamily: SANS }}>
      <Kopfzeile />
      <main>
        <Held />
        <Vertrauensleiste />
        <Produktweg />
        <Stimmen />
        <SoLaeuftEs />
        <Archiv />
        <Hochladen />
        <Preise />
        <Sicherheit />
      </main>
      <Fusszeile />
    </div>
  );
}

export default ConferMeetingsPage;
FAQ

Häufige Fragen

Eine kuratierte Landingpage aus aufeinander abgestimmten Sections der Library, in sinnvoller Reihenfolge von Hero bis Footer. Du kopierst die Sections einzeln und setzt sie mit dem Kompositions-Snippet zusammen.

Deine Frage war nicht dabei? Wir helfen gern weiter.

Kontakt aufnehmen