Skip to content
All templates

Templates/Meeting Recaps SaaS

A calm SaaS page in white and one muted royal blue. Instead of describing the product it shows it: the hero card writes its summary word by word and ticks its action items off one at a time, then swaps in the next meeting, and further down a drop zone runs the same four steps a real upload would. Every illustration is drawn in the page itself, so there is not a single photograph to replace. Depth comes from hairline borders and a change of surface, never from a shadow.

SaaS & SoftwareFull page

The finished page to scroll through, switchable between desktop, tablet and mobile.

Source code

The complete page as a single file. Copy it into ./components and add the component to your route.

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 INK = "#0a0a0a";
const QUIET = "#545a6b";
const BLUE = "#3a5bc4";
const BLUE_2 = "#5069ce";
const BLUE_GENTLE = "#ebeefb";
const VIOLET = "#6b54ce";
const AREA = "#f6f7fb";
const AREA_2 = "#eceff6";
const MUTED = "#f2f4f9";
const EDGE = "#e3e6ef";
const GREEN = "#2f8f5b";

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

// Cards rounded generously, buttons tightly: the shape of the surface, not of the key.
const CARD = 24;
const BUTTON = 8;

const MARK = { name: "Confer", mail: "hello@confer.app" };
const PRODUCTS = {
  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 Task = { what: string; who: string; when: string; done: boolean };
type Appointment = {
  title: string;
  leute: string[];
  duration: string;
  zusammenfassung: string[];
  aufgaben: Task[];
};

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

const TRUST = [
  { label: "Used by teams in 20+ countries", Icon: Globe2 },
  { label: "GDPR-compliant", Icon: ShieldCheck },
  { label: "Encrypted transfer", Icon: Lock },
  { label: "Works with your video calls", Icon: Video },
  { label: "Participants are notified", Icon: BellRing },
];

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

const VOICES = [
  {
    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 STEPS = [
  {
    title: "Connect your meeting or upload a recording",
    text: "Live in your video call, or an MP3 or video file.",
  },
  { title: "Confer transcribes and summarizes", text: "Calm, careful, in plain language." },
  {
    title: "You get notes, decisions and action items",
    text: "Including a follow-up draft and open questions.",
  },
];

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

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

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

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

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

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

function Appear({
  children,
  delay = 0,
  className = "",
  style,
}: {
  children: ReactNode;
  delay?: number;
  className?: string;
  style?: CSSProperties;
}) {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLDivElement>(null);
  const visible = useInView(ref, { once: true, margin: "-70px" });
  return (
    <motion.div
      ref={ref}
      className={className}
      style={style}
      initial={reduce ? false : { opacity: 0, y: 20 }}
      animate={visible || 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>
  );
}

// The brand: five bars like a level meter. It is about recording and listening.
function Level({ groesse = 40, color = BLUE }: { groesse?: number; color?: 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={color}
          fillOpacity={b.o}
        />
      ))}
    </svg>
  );
}

/** A kicker: small, in capitals, widely tracked. */
function Kicker({ children }: { children: ReactNode }) {
  return (
    <p
      className="text-sm uppercase"
      style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "0.16em", color: QUIET }}
    >
      {children}
    </p>
  );
}

function Title({ 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: INK }}
    >
      {children}
    </h2>
  );
}

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

// The ring of initials, instead of an invented face.
function Monogram({ 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, ${BLUE} 0%, ${VIOLET} 100%)`,
        color: "#ffffff",
        fontFamily: SANS,
        fontWeight: 700,
      }}
    >
      {name.slice(0, 1)}
    </span>
  );
}

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

function Header() {
  const [scrolled, setScrolled] = useState(false);
  const [open, setOpen] = useState(false);

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

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

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

        <nav aria-label="Main navigation" className="hidden lg:block">
          <ul className="flex items-center gap-1">
            {NAV.map((entry) => (
              <li key={entry.label}>
                <a
                  href={entry.href}
                  className="inline-flex min-h-11 items-center px-4 py-2 text-base transition-colors"
                  style={{
                    borderRadius: BUTTON,
                    fontFamily: SANS,
                    fontWeight: 500,
                    color: "#2c2f38",
                  }}
                >
                  {entry.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: BUTTON,
              background: BLUE,
              color: "#ffffff",
              fontFamily: SANS,
              fontWeight: 500,
            }}
          >
            Upload a recording
          </a>
          <button
            type="button"
            onClick={() => setOpen((v) => !v)}
            aria-expanded={open}
            aria-label={open ? "Close menu" : "Open menu"}
            className="inline-flex size-11 items-center justify-center lg:hidden"
            style={{
              borderRadius: BUTTON,
              border: `1px solid ${EDGE}`,
              background: "#ffffff",
              color: INK,
            }}
          >
            {open ? <X className="size-5" /> : <Menu className="size-5" />}
          </button>
        </div>
      </Track>

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

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

function Held() {
  return (
    <section id="top" className="relative overflow-hidden">
      {/* A fine dot grid, softly masked out. It gives the surface structure without
          appearing as a glow. */}
      <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%)",
        }}
      />

      <Track 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">
        <Appear>
          <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: BUTTON,
              background: MUTED,
              color: "#2c2f38",
              fontFamily: SANS,
              fontWeight: 500,
            }}
          >
            <span
              className="grid size-6 place-items-center rounded-full"
              style={{ background: "#e6e9f2", color: QUIET }}
            >
              <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: INK }}
          >
            Turn every <Underlined>meeting</Underlined> into a clear recap.
          </h1>

          <p
            className="mt-5 max-w-xl text-lg md:mt-6 md:text-xl"
            style={{ fontFamily: SANS, color: QUIET }}
          >
            {MARK.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: BUTTON,
                background: BLUE,
                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: INK,
                textDecoration: "underline",
                textDecorationColor: BLUE_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: QUIET }}
          >
            <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>
        </Appear>

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

// A word with a drawn stroke under it that draws itself once.
function Underlined({ children }: { children: ReactNode }) {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLSpanElement>(null);
  const visible = useInView(ref, { once: true, amount: 0.6 });
  return (
    <span ref={ref} className="relative inline-block whitespace-nowrap" style={{ color: BLUE }}>
      {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: BLUE_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={visible || reduce ? { pathLength: 1 } : { pathLength: 0 }}
          transition={{ duration: reduce ? 0 : 0.9, ease: "easeOut", delay: 0.4 }}
        />
      </svg>
    </span>
  );
}

/* The card in the hero. It shows what the product delivers: the sentence writes itself word
   by word, the tasks tick off one after another, then the next meeting comes. On hover it
   stops so it can be read. */
function Recap() {
  const reduce = useReducedMotion();
  const [i, setI] = useState(0);
  const pause = useRef(false);
  const ref = useRef<HTMLElement>(null);
  const visible = useInView(ref, { amount: 0.3 });

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

  const jetzt = APPOINTMENTS[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 ${EDGE}`, 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">
          <Level groesse={22} />
          <p
            className="truncate text-sm"
            style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "-0.01em", color: INK }}
          >
            {jetzt.title}
          </p>
        </div>
        <span
          className="inline-flex shrink-0 items-center gap-1.5 px-2.5 py-1 text-[10px]"
          style={{
            borderRadius: BUTTON,
            background: MUTED,
            color: "#3a3d47",
            fontFamily: SANS,
            fontWeight: 500,
          }}
        >
          <motion.span
            aria-hidden
            className="size-1.5 rounded-full"
            style={{ background: GREEN }}
            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((short) => (
            <span
              key={short}
              className="grid size-7 place-items-center rounded-full text-[10px]"
              style={{ background: BLUE_GENTLE, color: BLUE, fontFamily: SANS, fontWeight: 600 }}
            >
              {short}
            </span>
          ))}
        </div>
        <span className="text-xs" style={{ fontFamily: SANS, color: QUIET }}>
          {jetzt.duration}
        </span>
      </div>

      <div className="mt-6">
        <p
          className="text-[10px] uppercase"
          style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "0.18em", color: QUIET }}
        >
          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((word, 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 }}
                >
                  {word}
                  {k < jetzt.zusammenfassung.length - 1 ? " " : ""}
                </motion.span>
              ))}
            </motion.span>
          </AnimatePresence>
        </p>
      </div>

      <div className="mt-5 pt-5" style={{ borderTop: `1px solid ${EDGE}` }}>
        <p
          className="text-[10px] uppercase"
          style={{ fontFamily: SANS, fontWeight: 600, letterSpacing: "0.18em", color: QUIET }}
        >
          Action items
        </p>
        <ul className="mt-3 min-h-[5.75rem] space-y-2.5">
          {jetzt.aufgaben.map((task, 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: task.done ? BLUE : "#ffffff",
                  border: task.done ? "1px solid transparent" : `1px solid ${EDGE}`,
                  color: "#ffffff",
                }}
              >
                {task.done ? <Check className="size-3.5" strokeWidth={3} aria-hidden /> : null}
              </span>
              <span
                className="flex-1 truncate text-[15px]"
                style={{ fontFamily: SANS, color: INK }}
              >
                {task.what}
              </span>
              <span
                aria-hidden
                className="grid size-6 shrink-0 place-items-center rounded-full text-[10px]"
                style={{ background: MUTED, color: "#3a3d47", fontFamily: SANS, fontWeight: 600 }}
              >
                {task.who}
              </span>
              <span
                className="w-14 shrink-0 text-right text-xs"
                style={{ fontFamily: SANS, color: QUIET }}
              >
                {task.when}
              </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 ${EDGE}` }}
      >
        <div
          className="flex flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-[11px]"
          style={{ fontFamily: SANS, color: QUIET }}
        >
          <MonitorPlay className="size-3.5 shrink-0" aria-hidden />
          <span>Works with</span>
          <span style={{ fontWeight: 500, color: "#2c2f38" }}>
            Video calls · Webinars · Recordings
          </span>
        </div>
        <span
          className="inline-flex shrink-0 items-center gap-1.5 text-[11px]"
          style={{ fontFamily: SANS, color: QUIET }}
        >
          <Sparkles className="size-3" aria-hidden />
          42 seconds
        </span>
      </footer>
    </article>
  );
}

/* ---------------------------------------------------------------------- trust */

function TrustBar() {
  return (
    <section aria-label="Trust" style={{ background: AREA }}>
      <Track 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: QUIET }}
        >
          {TRUST.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>
      </Track>
    </section>
  );
}

/* -------------------------------------------------------------------- path */

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

        <ol className="mt-10 grid gap-8 md:mt-16 lg:grid-cols-3">
          {WEG.map((part, i) => (
            <li key={part.name} className="h-full">
              <Appear 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: CARD, background: AREA_2 }}
                >
                  <div className="mb-6 flex items-center justify-between">
                    <span
                      className="text-sm"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.16em",
                        color: QUIET,
                      }}
                    >
                      {part.step}
                    </span>
                    <span
                      className="px-3 py-1 text-xs uppercase"
                      style={{
                        borderRadius: BUTTON,
                        background: "#e2e6f0",
                        color: "#3a3d47",
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.1em",
                      }}
                    >
                      {part.marke}
                    </span>
                  </div>

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

                  <h3
                    className="text-2xl"
                    style={{ fontFamily: SANS, fontWeight: 500, color: INK }}
                  >
                    {part.name}
                  </h3>
                  <p
                    className="mt-2 text-base italic"
                    style={{ fontFamily: SANS, color: "#33363f" }}
                  >
                    {part.frage}
                  </p>
                  <p className="mt-3 text-base" style={{ fontFamily: SANS, color: QUIET }}>
                    {part.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: INK }}
                  >
                    See plans
                    <ArrowRight
                      className="size-4 transition-transform duration-300 group-hover:translate-x-1"
                      aria-hidden
                    />
                  </span>
                </a>
              </Appear>
            </li>
          ))}
        </ol>
      </Track>
    </section>
  );
}

/* ------------------------------------------------------------------ voices */

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

        <ul className="mt-12 grid gap-8 md:mt-16 md:grid-cols-3">
          {VOICES.map((voice, i) => (
            <li key={voice.name} className="h-full">
              <Appear delay={i * 0.12} className="h-full">
                <figure
                  className="flex h-full w-full flex-col p-8"
                  style={{ borderRadius: CARD, background: "#ffffff" }}
                >
                  <span
                    className="flex gap-1"
                    aria-label="5 out of 5 stars"
                    style={{ color: VIOLET }}
                  >
                    {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: INK }}
                  >
                    {voice.text}
                  </blockquote>
                  <figcaption className="mt-6 flex items-center gap-3 pt-2">
                    <Monogram name={voice.name} />
                    <span className="min-w-0">
                      <span
                        className="block text-base"
                        style={{ fontFamily: SANS, fontWeight: 600, color: INK }}
                      >
                        {voice.name}
                      </span>
                      <span className="block text-sm" style={{ fontFamily: SANS, color: QUIET }}>
                        {voice.rolle}
                      </span>
                    </span>
                  </figcaption>
                </figure>
              </Appear>
            </li>
          ))}
        </ul>
      </Track>
    </section>
  );
}

/* ------------------------------------------------------------ how it works */

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

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

        <div ref={ref} className="relative mt-12 md:mt-20">
          {/* The connector runs behind the three cards and draws itself once. Only on the
              desktop: below that the cards stand under each other, and a line across
              would make no sense there. */}
          <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: BLUE_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={visible || reduce ? { pathLength: 1 } : { pathLength: 0 }}
              transition={{ duration: reduce ? 0 : 1.6, ease: "easeInOut" }}
            />
          </svg>

          <ol className="relative grid gap-8 md:grid-cols-3">
            {STEPS.map((step, i) => (
              <li key={step.title}>
                <Appear delay={i * 0.12}>
                  <div
                    className="h-full p-6 md:p-8"
                    style={{ borderRadius: CARD, background: AREA_2 }}
                  >
                    <div className="flex items-center gap-3">
                      <span
                        className="grid size-10 shrink-0 place-items-center text-base"
                        style={{
                          borderRadius: 12,
                          background: BLUE_GENTLE,
                          color: BLUE,
                          fontFamily: SANS,
                          fontWeight: 600,
                        }}
                      >
                        {String(i + 1).padStart(2, "0")}
                      </span>
                      <h3
                        className="text-xl"
                        style={{ fontFamily: SANS, fontWeight: 500, color: INK }}
                      >
                        {step.title}
                      </h3>
                    </div>
                    <p className="mt-3 text-base" style={{ fontFamily: SANS, color: QUIET }}>
                      {step.text}
                    </p>
                  </div>
                </Appear>
              </li>
            ))}
          </ol>
        </div>

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

/* ------------------------------------------------------------------ archive */

function Archiv() {
  return (
    <section id="archive" className="scroll-mt-24 py-16 md:py-28">
      <Track>
        <Appear>
          <div
            className="relative overflow-hidden"
            style={{
              borderRadius: 32,
              border: `1px solid ${EDGE}`,
              backgroundImage: `linear-gradient(135deg, ${AREA} 0%, #ffffff 46%, ${BLUE_GENTLE} 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: INK,
                  }}
                >
                  Every meeting. In one place.
                </h2>
                <p
                  className="mt-5 text-lg leading-relaxed"
                  style={{ fontFamily: SANS, color: QUIET }}
                >
                  {MARK.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_DOTS.map((dot) => (
                    <li key={dot} className="flex items-start gap-3">
                      <span
                        className="mt-0.5 grid size-6 shrink-0 place-items-center rounded-full"
                        style={{ background: BLUE, color: "#ffffff" }}
                      >
                        <Check className="size-3.5" strokeWidth={3} aria-hidden />
                      </span>
                      <span className="text-base" style={{ fontFamily: SANS, color: INK }}>
                        {dot}
                      </span>
                    </li>
                  ))}
                </ul>
                <a
                  href="#upload"
                  className="mt-8 inline-flex items-center gap-1.5 text-base"
                  style={{ fontFamily: SANS, fontWeight: 500, color: INK }}
                >
                  Try it for free
                  <ArrowRight className="size-4" aria-hidden />
                </a>
              </div>

              <div className="order-1 lg:order-2">
                <ArchivImage />
              </div>
            </div>
          </div>
        </Appear>
      </Track>
    </section>
  );
}

/* A stack of recaps as a drawing. Depth comes from edge and offset, not from a shadow. The
   level meter breathes, the ticks draw themselves as soon as the picture is in view. */
function ArchivImage() {
  const reduce = useReducedMotion();
  const ref = useRef<SVGSVGElement>(null);
  const visible = useInView(ref, { once: true, amount: 0.3 });
  const balken = [
    { x: 202, h: 10, f: BLUE },
    { x: 208, h: 18, f: BLUE_2 },
    { x: 214, h: 24, f: VIOLET },
    { x: 220, h: 14, f: BLUE },
    { x: 226, h: 8, f: BLUE_2 },
  ];
  const lines = [
    { y: 168, w: 200 },
    { y: 190, w: 232 },
    { y: 212, w: 168 },
  ];
  const check = [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={BLUE_GENTLE}
        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={GREEN} />
      <rect x="407" y="128" width="24" height="8" rx="4" fill="#bcdcc9" />

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

      {check.map((y, i) => (
        <g key={y}>
          <rect x="202" y={y} width="236" height="30" rx="10" fill={AREA} />
          <motion.path
            d={`M 214 ${y + 15} l 5 5 l 9 -10`}
            fill="none"
            stroke={BLUE}
            strokeWidth="2.6"
            strokeLinecap="round"
            strokeLinejoin="round"
            initial={reduce ? false : { pathLength: 0 }}
            animate={visible || 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>
      ))}

      {/* The finder on the left: a magnifier on a field. */}
      <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={QUIET} strokeWidth="2.2" />
      <path d="M 88 224 l 7 7" stroke={QUIET} strokeWidth="2.2" strokeLinecap="round" />
      <rect x="102" y="214" width="86" height="8" rx="4" fill="#e8ebf3" />

      {/* The clip with the recorded sound. */}
      <rect
        x="46"
        y="292"
        width="130"
        height="60"
        rx="16"
        fill="#ffffff"
        stroke="#e4e7ef"
        strokeWidth="1.5"
      />
      {[
        { x: 62, h: 8, f: BLUE_2 },
        { x: 70, h: 16, f: BLUE },
        { x: 78, h: 26, f: VIOLET },
        { x: 86, h: 12, f: BLUE },
        { x: 94, h: 6, f: BLUE_2 },
        { x: 102, h: 20, f: BLUE },
        { x: 110, h: 10, f: VIOLET },
      ].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>
  );
}

/* ------------------------------------------------------------------ upload */

function Hochladen() {
  const reduce = useReducedMotion();
  const [state, setState] = useState<"idle" | "running" | "done">("idle");
  const [step, setStep] = useState(0);
  const [ueber, setUeber] = useState(false);

  const starten = useCallback(() => {
    if (state === "running") return;
    setState("running");
    setStep(0);
  }, [state]);

  useEffect(() => {
    if (state !== "running") return;
    if (reduce) {
      setStep(UPLOAD_STEPS.length);
      setState("done");
      return;
    }
    if (step >= UPLOAD_STEPS.length) {
      const t = window.setTimeout(() => setState("done"), 500);
      return () => window.clearTimeout(t);
    }
    const t = window.setTimeout(() => setStep((s) => s + 1), 850);
    return () => window.clearTimeout(t);
  }, [state, step, reduce]);

  const portion = Math.round((Math.min(step, UPLOAD_STEPS.length) / UPLOAD_STEPS.length) * 100);

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

        <Appear 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" }}>
            {state === "idle" ? (
              <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: CARD,
                  border: `2px dashed ${ueber ? BLUE : "#ccd3e6"}`,
                  background: ueber ? BLUE_GENTLE : AREA,
                }}
              >
                <span
                  className="grid size-14 place-items-center rounded-full"
                  style={{ background: BLUE_GENTLE, color: BLUE }}
                >
                  <FileAudio className="size-6" strokeWidth={1.7} aria-hidden />
                </span>
                <span
                  className="mt-5 text-xl"
                  style={{ fontFamily: SANS, fontWeight: 500, color: INK }}
                >
                  {ueber ? `Release, ${MARK.name} takes over` : "Drop your file here"}
                </span>
                <span className="mt-2 max-w-sm text-sm" style={{ fontFamily: SANS, color: QUIET }}>
                  or click to choose. MP3, WAV, M4A or video, even a call 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: BLUE_GENTLE, color: BLUE }}
                    >
                      <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: INK }}
                      >
                        weekly-sync.mp3
                      </span>
                      <span className="block text-sm" style={{ fontFamily: SANS, color: QUIET }}>
                        {state === "done"
                          ? "summarized in 42 seconds"
                          : `Step ${Math.min(step + 1, UPLOAD_STEPS.length)} of ${UPLOAD_STEPS.length}`}
                      </span>
                    </span>
                  </div>
                  <span
                    className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs"
                    style={{
                      borderRadius: BUTTON,
                      background: state === "done" ? "#e9f4ee" : MUTED,
                      color: state === "done" ? GREEN : "#3a3d47",
                      fontFamily: SANS,
                      fontWeight: 600,
                    }}
                  >
                    {state === "done" ? "Done" : `${portion}%`}
                  </span>
                </div>

                {/* The bar runs with the steps, not with a clock: it shows where the work
                    stands, not how long it takes. */}
                <div
                  className="mt-6 h-1.5 w-full overflow-hidden"
                  style={{ borderRadius: 999, background: MUTED }}
                >
                  <motion.div
                    className="h-full"
                    style={{ background: BLUE, borderRadius: 999 }}
                    initial={{ width: "0%" }}
                    animate={{ width: `${portion}%` }}
                    transition={{ duration: reduce ? 0 : 0.6, ease: "easeOut" }}
                  />
                </div>

                <ul className="mt-7 space-y-3">
                  {UPLOAD_STEPS.map((text, i) => {
                    const done = i < step;
                    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: done ? BLUE : MUTED,
                            color: done ? "#ffffff" : "#aeb3c2",
                          }}
                        >
                          <Check className="size-3.5" strokeWidth={3} aria-hidden />
                        </span>
                        <span
                          className="text-base transition-colors"
                          style={{ fontFamily: SANS, color: done ? INK : QUIET }}
                        >
                          {text}
                        </span>
                      </li>
                    );
                  })}
                </ul>

                {state === "done" ? (
                  <div
                    className="mt-8 p-6"
                    style={{
                      borderRadius: CARD,
                      background: AREA,
                      border: `1px solid ${EDGE}`,
                    }}
                  >
                    <p
                      className="text-[10px] uppercase"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.18em",
                        color: QUIET,
                      }}
                    >
                      Summary
                    </p>
                    <p
                      className="mt-2 text-[17px] leading-relaxed"
                      style={{ fontFamily: SANS, color: INK }}
                    >
                      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={() => {
                        setState("idle");
                        setStep(0);
                      }}
                      className="mt-6 inline-flex items-center gap-2 px-5 py-2.5 text-sm"
                      style={{
                        borderRadius: BUTTON,
                        background: INK,
                        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: QUIET }}
            >
              {[
                "Encrypted in transit and at rest",
                "GDPR-compliant, servers in the EU",
                "Audio deleted after transcription",
              ].map((dot) => (
                <li key={dot} className="inline-flex items-center gap-1.5">
                  <ShieldCheck
                    className="size-3.5"
                    style={{ color: BLUE }}
                    strokeWidth={1.8}
                    aria-hidden
                  />
                  {dot}
                </li>
              ))}
            </ul>
          </div>
        </Appear>
      </Track>
    </section>
  );
}

/* ------------------------------------------------------------------ pricing */

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

        <ul className="mt-14 grid gap-8 lg:mt-16 lg:grid-cols-3">
          {PRICES.map((plan, i) => (
            <li key={plan.name} className="h-full">
              <Appear 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: BUTTON,
                        background: VIOLET,
                        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: CARD,
                      background: plan.hervor ? BLUE : AREA,
                      color: plan.hervor ? "#ffffff" : INK,
                      border: `1px solid ${plan.hervor ? BLUE : EDGE}`,
                    }}
                  >
                    <span
                      className="text-xs uppercase"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 600,
                        letterSpacing: "0.16em",
                        color: plan.hervor ? "rgba(255,255,255,0.9)" : BLUE,
                      }}
                    >
                      {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)" : QUIET,
                      }}
                    >
                      {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)" : QUIET,
                        }}
                      >
                        {plan.je}
                      </span>
                    </div>

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

                    <ul className="space-y-3.5">
                      {plan.points.map((dot) => (
                        <li key={dot} 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)" : BLUE_GENTLE,
                              color: plan.hervor ? "#ffffff" : BLUE,
                            }}
                          >
                            <Check className="size-3" strokeWidth={3} aria-hidden />
                          </span>
                          <span
                            style={{
                              fontFamily: SANS,
                              color: plan.hervor ? "rgba(255,255,255,0.95)" : INK,
                            }}
                          >
                            {dot}
                          </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: BUTTON,
                          background: plan.hervor ? "#ffffff" : INK,
                          color: plan.hervor ? BLUE : "#ffffff",
                          fontFamily: SANS,
                          fontWeight: 600,
                        }}
                      >
                        {plan.cta}
                      </a>
                    </div>
                  </div>
                </div>
              </Appear>
            </li>
          ))}
        </ul>
      </Track>
    </section>
  );
}

/* ---------------------------------------------------------------- security */

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

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

          <ul className="order-2 space-y-7">
            {SAFETY.map((dot, i) => (
              <li key={dot.title}>
                <Appear delay={i * 0.1} className="flex items-start gap-4">
                  <span
                    className="grid size-11 shrink-0 place-items-center"
                    style={{ borderRadius: 12, background: BLUE_GENTLE, color: BLUE }}
                  >
                    <dot.Icon className="size-5" strokeWidth={1.75} aria-hidden />
                  </span>
                  <div>
                    <h3
                      className="text-lg"
                      style={{
                        fontFamily: SANS,
                        fontWeight: 500,
                        letterSpacing: "-0.01em",
                        color: INK,
                      }}
                    >
                      {dot.title}
                    </h3>
                    <p
                      className="mt-1.5 text-sm leading-relaxed"
                      style={{ fontFamily: SANS, color: QUIET }}
                    >
                      {dot.text}
                    </p>
                  </div>
                </Appear>
              </li>
            ))}
          </ul>
        </div>
      </Track>
    </section>
  );
}

// A shield that draws itself once, with a lock inside it.
function PlateImage() {
  const reduce = useReducedMotion();
  const ref = useRef<SVGSVGElement>(null);
  const visible = 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={BLUE_GENTLE} />
      <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={BLUE}
        strokeWidth="2.4"
        strokeLinejoin="round"
        initial={reduce ? false : { pathLength: 0 }}
        animate={visible || 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={BLUE_GENTLE}
        opacity="0.55"
      />

      <rect x="182" y="150" width="56" height="46" rx="10" fill={BLUE} />
      <path
        d="M 192 150 v -12 a 18 18 0 0 1 36 0 v 12"
        fill="none"
        stroke={BLUE}
        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={BLUE_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>
  );
}

/* ------------------------------------------------------------------ footer */

function Footer() {
  return (
    <footer id="about" className="scroll-mt-24" style={{ background: AREA }}>
      <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">
                <Level />
                <span
                  className="text-xl md:text-2xl"
                  style={{
                    fontFamily: SANS,
                    fontWeight: 600,
                    letterSpacing: "-0.02em",
                    color: INK,
                  }}
                >
                  {MARK.name}
                </span>
              </div>
              <p
                className="mt-5 max-w-md text-base leading-relaxed"
                style={{ fontFamily: SANS, color: QUIET }}
              >
                {MARK.name} turns every meeting into a clear recap, with decisions, action items and
                follow-up.
              </p>

              <a
                href={`mailto:${MARK.mail}`}
                className="mt-6 inline-flex items-center gap-2 text-base"
                style={{ fontFamily: SANS, fontWeight: 500, color: INK }}
              >
                <Mail className="size-4" aria-hidden />
                {MARK.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: BUTTON,
                      background: BLUE_GENTLE,
                      color: "#3a3d47",
                      fontFamily: SANS,
                      fontWeight: 500,
                    }}
                  >
                    <Icon
                      className="size-3.5"
                      style={{ color: BLUE }}
                      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"
            >
              {FOOT_COLUMNS.map((column) => (
                <div key={column.title}>
                  <h3
                    className="text-xs uppercase"
                    style={{
                      fontFamily: SANS,
                      fontWeight: 600,
                      letterSpacing: "0.16em",
                      color: "#6d7280",
                    }}
                  >
                    {column.title}
                  </h3>
                  <ul className="mt-3 space-y-1">
                    {column.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 ${EDGE}`, fontFamily: SANS, color: QUIET }}
          >
            <p>© 2026 {MARK.name}. All rights reserved.</p>
            <p>{MARK.name} produces automatic summaries, please review for accuracy.</p>
          </div>
        </div>
      </div>
    </footer>
  );
}

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

export function ConferMeetingsPage() {
  return (
    <div style={{ background: "#ffffff", color: INK, fontFamily: SANS }}>
      <Header />
      <main>
        <Held />
        <TrustBar />
        <ProductPath />
        <Voices />
        <SoRunsEs />
        <Archiv />
        <Hochladen />
        <Prices />
        <Safety />
      </main>
      <Footer />
    </div>
  );
}

export default ConferMeetingsPage;
FAQ

Frequently asked questions

A curated landing page made of matching library sections in a sensible order from hero to footer. You copy the sections one by one and assemble them with the composition snippet.

Didn't find your question? We're happy to help.

Get in touch