Zum Inhalt springen
Alle Templates

Templates/Berufsberatung

Eine Seite für alle, die eine Stunde Aufmerksamkeit verkaufen: Beraterinnen und Berater, Therapie, Einzelconsulting. Weiß und ein sehr helles Grau tragen die ganze Seite, ein tiefes Grün markiert alles, was man anfassen kann, und ein frisches Gelbgrün taucht nur in der Bewegung auf, als Kreis, der beim Zeigen aus der linken Kante jedes Knopfes wächst. Im Ruhezustand ist nichts auf der Seite in dieser Farbe. Eine Serifenschrift hält Überschriften und Zitate gegen eine Grotesk für alles, was gelesen und nicht angesehen wird. Die beiden angehefteten Blöcke werden auf dem Telefon zu ruhigen Listen, weil ein Pin dort wie eine Seite wirkt, die nicht weiterscrollt.

Coaching & BeratungKomplette 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 { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
import {
  AnimatePresence,
  motion,
  useMotionValueEvent,
  useReducedMotion,
  useScroll,
  useTransform,
  type MotionValue,
} from "framer-motion";
import {
  ArrowRight,
  ArrowUp,
  ArrowUpRight,
  CalendarPlus,
  Check,
  ClipboardList,
  Compass,
  Eye,
  FileText,
  Flag,
  GraduationCap,
  Handshake,
  Layers,
  Mail,
  MapPin,
  MessageSquare,
  Menu,
  Phone,
  Rocket,
  Scale,
  ScrollText,
  Share2,
  Sparkle,
  Target,
  Timer,
  Users,
  Wallet,
  X,
} from "lucide-react";

/*
 * A full page for a one to one career counselling practice.
 *
 * The page is white and one very light grey, with a deep green for anything
 * that can be acted on and a fresh yellow green as the single accent. That
 * accent only ever appears in motion: every button carries a circle that
 * grows out of its left edge on hover and swaps the green for the lime.
 * Nothing on the page is that colour while it sits still.
 *
 * Typography runs on two voices. A serif carries the headlines and the
 * quotes, a grotesk carries everything that is read rather than looked at.
 *
 * Motion is tied to the scroll rather than to a one off entrance, because a
 * page like this is scrolled slowly and an entrance that finishes after a
 * second stands still for the rest of the visit:
 *
 *   - the hero is held back while the rest of the page slides over it and
 *     keeps drifting up instead of freezing,
 *   - the sentences people actually open with lie in a pile and scatter
 *     while the block is pinned, then the whole block flies up and out,
 *   - the pivot sentence recedes and blurs while the before/after pairs
 *     travel past it,
 *   - photographs drift inside their frames, the quote band grows into
 *     place, and two rows of chips and one row of cards run continuously.
 *
 * Everything checks prefers-reduced-motion and falls back to a still page,
 * and the two pinned blocks fall back to plain stacked lists on phones,
 * where a pin costs a lot and reads as a page that will not scroll.
 *
 * The practice, the counsellor and every line of text are invented.
 */

/* ------------------------------------------------------------- grundwerte */

const PAPER = "#ffffff";
const MIST = "#f3f4f1";
const EDGE = "rgba(31,36,28,0.10)";
const FOREST = "#44573d";
const DEEP = "#2c3827";
const LIME = "#bcd94a";
const INK = "#3a3d37";
const QUIET = "#6c7068";

const SERIF = '"Instrument Serif", ui-serif, Georgia, serif';
const SANS = '"Figtree", "Manrope", ui-sans-serif, system-ui, sans-serif';

/* Instrument Serif liefert nur einen Schnitt. Deshalb steht das Gewicht fest
   auf 400: erbt die Ueberschrift ein fettes Gewicht, rechnet der Browser es
   selbst aus und die Schrift wird breiig. Kursiv, weil genau das der Schnitt
   ist, der die Seite traegt. */
const display: CSSProperties = {
  fontFamily: SERIF,
  fontStyle: "italic",
  fontWeight: 400,
  letterSpacing: "-0.005em",
};

/* Feines Korn auf den hellgrauen Flaechen. Nicht skaliert, damit ein Korn auf
   einem Punkt sitzt: gedehnt wird es weich und liest sich als Schmutz. */
const GRAIN =
  "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.24 0 0 0 0 0.26 0 0 0 0 0.22 0 0 0 0.2 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")";

const EASE = [0.25, 0.8, 0.28, 1] as const;

/*
 * Sieben Aufnahmen aus einer Welt: Wiese, Nebel, spaeter Nachmittag, und
 * Menschen, die darin arbeiten. Vorher stand hier ein Querschnitt durch die
 * Galerie, ein Modeshooting neben einem dunklen Schreibtisch neben einem
 * Flughafenhimmel, und die Seite fiel bei jedem Abschnitt in eine andere
 * Stimmung.
 *
 * Ausgewaehlt ist ausserdem nach Datendichte und nicht nur nach Motiv. Die
 * vorherige Fassung hatte zwei Bilder mit rund 30 KB auf zwei Megapixel; das
 * sieht in einer kleinen Kachel gut aus und zerfaellt, sobald es ueber die
 * ganze Breite laeuft. Alle hier liegen ueber 160 KB je Megapixel.
 */
const BILD = {
  hero: "/heros/highly-realistic-vertical-editorial-campaig.webp",
  wiese: "/heros/late-afternoon-sun-casting-long-shadows-2.webp",
  sitzen: "/heros/highly-realistic-vertical-campaign.webp",
  portrait: "/heros/overhead-perspective-of-a-wide-grassland-covered.webp",
  huegel: "/heros/hero-spring-field.webp",
  wald: "/heros/a-misty-forest-scene-with-sunlight-2.webp",
  garten: "/heros/blurry-piet-oudolf-garden-photorealist-afbc.webp",
};

/* ------------------------------------------------------------ kleinteiliges */

/**
 * Ab welcher Breite die zwei angehefteten Bloecke laufen. Auf dem Telefon
 * kostet ein Pin viel und liest sich wie eine Seite, die nicht weiterscrollt;
 * dort stehen dieselben Inhalte als ruhige Liste.
 */
function useBreit(abfrage = "(min-width: 768px) and (pointer: fine)") {
  const [breit, setBreit] = useState(false);
  useEffect(() => {
    const mq = window.matchMedia(abfrage);
    const lesen = () => setBreit(mq.matches);
    lesen();
    mq.addEventListener("change", lesen);
    return () => mq.removeEventListener("change", lesen);
  }, [abfrage]);
  return breit;
}

/**
 * Erscheint einmal beim Hereinkommen: aus einer weichen Unschaerfe anheben,
 * aufblenden, stehenbleiben.
 *
 * Die Unschaerfe ist der Unterschied zwischen "war schon da" und "kommt
 * gerade": ein reines Aufblenden sieht nach einem Schalter aus, ein Aufblenden
 * aus drei Pixeln Unschaerfe nach einem Scharfstellen.
 *
 * Auf schmalen Schirmen bleibt sie weg. Eine Unschaerfe auf vielen Elementen
 * gleichzeitig ist genau das, was Scrollen auf dem Telefon stocken laesst,
 * und der Gewinn ist dort am kleinsten.
 */
function Auftritt({
  children,
  verzug = 0,
  className = "",
}: {
  children: ReactNode;
  verzug?: number;
  className?: string;
}) {
  const reduce = useReducedMotion();
  const breit = useBreit("(min-width: 768px)");
  const weich = breit && !reduce;
  return (
    <motion.div
      className={className}
      initial={
        reduce ? false : weich ? { opacity: 0, y: 16, filter: "blur(3px)" } : { opacity: 0, y: 12 }
      }
      whileInView={weich ? { opacity: 1, y: 0, filter: "blur(0px)" } : { opacity: 1, y: 0 }}
      viewport={{ once: true, margin: "-60px" }}
      transition={{ duration: weich ? 1 : 0.7, delay: verzug, ease: EASE }}
    >
      {children}
    </motion.div>
  );
}

/**
 * Ein Bild, das in seinem Rahmen mitwandert. Der Rahmen schneidet ab, das
 * Bild ist leicht groesser als er und verschiebt sich gegen den Scroll.
 */
function Wanderbild({
  src,
  alt,
  weite = 44,
  className = "",
}: {
  src: string;
  alt: string;
  /** Gesamtweg in Pixeln ueber den ganzen Durchlauf. */
  weite?: number;
  className?: string;
}) {
  const reduce = useReducedMotion();
  const rahmen = useRef<HTMLSpanElement>(null);
  const { scrollYProgress } = useScroll({
    target: rahmen,
    offset: ["start end", "end start"],
  });
  const y = useTransform(scrollYProgress, [0, 1], [weite / 2, -weite / 2]);

  return (
    <span ref={rahmen} className="absolute inset-0 block overflow-hidden">
      <motion.img
        src={src}
        alt={alt}
        draggable={false}
        loading="lazy"
        decoding="async"
        className={`h-full w-full scale-[1.12] object-cover select-none ${className}`}
        style={{ y: reduce ? 0 : y }}
      />
    </span>
  );
}

/** Kicker: gesperrte Kleinschrift auf einer ganz leichten Flaeche. */
function Marke({
  icon: Icon,
  children,
  hell = false,
}: {
  icon: typeof Compass;
  children: ReactNode;
  hell?: boolean;
}) {
  return (
    /* Zurueckgenommen: der Kicker sagt, worum es gleich geht, und ist nicht
       selbst die Nachricht. Vorher stand hier eine graue Pille in
       Textgroesse, die gegen die Ueberschrift darunter antrat. Jetzt traegt
       nur noch die Flaeche, und die Schrift ist kleiner, heller und
       gesperrt. */
    <span
      className="inline-flex items-center gap-1.5 px-2 py-1 text-[11.5px] font-medium"
      style={{
        borderRadius: 5,
        fontFamily: SANS,
        letterSpacing: "0.05em",
        background: hell ? "rgba(255,255,255,0.12)" : "rgba(31,36,28,0.04)",
        color: hell ? "rgba(255,255,255,0.82)" : "rgba(108,112,104,0.9)",
      }}
    >
      <Icon size={13} strokeWidth={1.5} aria-hidden />
      {children}
    </span>
  );
}

/**
 * Knopf mit wachsendem Kreis.
 *
 * Der Kreis sitzt hinter der Beschriftung, faengt links ausserhalb an und
 * waechst beim Zeigen ueber die ganze Flaeche. Deshalb ist der Akzent im
 * Ruhezustand nirgends zu sehen und trotzdem ueberall dort, wo etwas
 * anklickbar ist. Der Knopf selbst wird dabei nicht groesser.
 */
function Knopf({
  href,
  children,
  art = "voll",
  icon: Icon,
  onClick,
}: {
  href?: string;
  children: ReactNode;
  /** voll: gruen. hell: gruen auf Bild, fuellt weiss. leise: fast durchsichtig. */
  art?: "voll" | "hell" | "leise";
  icon?: typeof Compass;
  onClick?: () => void;
}) {
  const reduce = useReducedMotion();
  const grund = art === "leise" ? "rgba(68,87,61,0.10)" : FOREST;
  const schrift = art === "leise" ? FOREST : "#ffffff";
  /* Ein Hoverbild fuer die ganze Seite: aus dem tiefen Gruen waechst das
     helle. Auch auf den Bildern, wo frueher Weiss einlief; zwei verschiedene
     Fuellungen liessen denselben Knopf je nach Untergrund anders aussehen.
     Nur der leise Knopf bleibt anders: er ist schon fast durchsichtig und
     wird beim Zeigen zu dem, was der andere im Ruhezustand ist. */
  const kreis = art === "leise" ? FOREST : LIME;
  const nachher = art === "leise" ? "#ffffff" : INK;

  const inhalt = (
    <>
      {!reduce && (
        <span
          aria-hidden
          className="pointer-events-none absolute top-1/2 -left-[10%] -z-10 aspect-square w-[130%] origin-left scale-0 rounded-full opacity-0 transition-[transform,opacity] duration-[900ms] group-hover:scale-100 group-hover:opacity-100 group-focus-visible:scale-100 group-focus-visible:opacity-100"
          style={{
            background: kreis,
            translate: "0 -50%",
            transitionTimingFunction: "cubic-bezier(0.33,1,0.68,1)",
          }}
        />
      )}
      {children}
      {Icon && (
        <Icon
          size={17}
          strokeWidth={1.7}
          aria-hidden
          /* Das Symbol kommt erst beim Zeigen heraus. Im Ruhezustand ist es
             null Pixel breit, der Knopf aendert dabei seine Breite kaum. */
          className="max-w-0 -translate-x-1 opacity-0 transition-all duration-300 group-hover:max-w-5 group-hover:translate-x-0 group-hover:opacity-100 group-focus-visible:max-w-5 group-focus-visible:opacity-100 motion-reduce:max-w-5 motion-reduce:translate-x-0 motion-reduce:opacity-100"
        />
      )}
    </>
  );

  const gemeinsam = {
    className:
      "group relative isolate inline-flex min-h-[2.75rem] items-center justify-center gap-2 overflow-hidden px-5 text-[15px] font-medium",
    style: {
      borderRadius: 6,
      fontFamily: SANS,
      background: grund,
      color: schrift,
      transition: "color 640ms cubic-bezier(0.33,1,0.68,1)",
    } as CSSProperties,
    onMouseEnter: (ev: React.MouseEvent<HTMLElement>) => {
      ev.currentTarget.style.color = nachher;
    },
    onMouseLeave: (ev: React.MouseEvent<HTMLElement>) => {
      ev.currentTarget.style.color = schrift;
    },
  };

  if (href) {
    return (
      <a href={href} onClick={onClick} {...gemeinsam}>
        {inhalt}
      </a>
    );
  }
  return (
    <button type="button" onClick={onClick} {...gemeinsam}>
      {inhalt}
    </button>
  );
}

/** Textlink, dessen Linie beim Zeigen von rechts nach links einzieht. */
function Leiselink({
  href,
  children,
  icon: Icon = ArrowRight,
  pfeil = true,
}: {
  href: string;
  children: ReactNode;
  icon?: typeof ArrowRight;
  /** Ohne Pfeil, wo der Link neben einem Knopf steht, der schon einen hat. */
  pfeil?: boolean;
}) {
  return (
    <a
      href={href}
      className="group inline-flex items-center gap-1.5 pb-0.5 text-[15px] font-medium"
      style={{
        fontFamily: SANS,
        color: FOREST,
        backgroundImage: "linear-gradient(currentColor, currentColor)",
        backgroundSize: "100% 1px",
        backgroundRepeat: "no-repeat",
        backgroundPosition: "0 100%",
        transition: "background-size 240ms ease, color 200ms ease",
      }}
      onMouseEnter={(ev) => {
        (ev.currentTarget as HTMLElement).style.backgroundSize = "35% 1px";
      }}
      onMouseLeave={(ev) => {
        (ev.currentTarget as HTMLElement).style.backgroundSize = "100% 1px";
      }}
    >
      {children}
      {pfeil && (
        <Icon
          size={15}
          strokeWidth={1.7}
          aria-hidden
          className="max-w-0 -translate-x-1 opacity-0 transition-all duration-300 group-hover:max-w-4 group-hover:translate-x-0 group-hover:opacity-100 motion-reduce:max-w-4 motion-reduce:translate-x-0 motion-reduce:opacity-100"
        />
      )}
    </a>
  );
}

/* ------------------------------------------------------------------ kopf */

const NAV = [
  { label: "Your situation", href: "#situation" },
  { label: "How I work", href: "#method" },
  { label: "About", href: "#about" },
  { label: "Fees & format", href: "#fees" },
];

function Wortmarke({ farbe = INK, klein = false }: { farbe?: string; klein?: boolean }) {
  return (
    <span
      className={klein ? "text-[17px]" : "text-[19px]"}
      style={{ ...display, color: farbe, letterSpacing: "-0.02em" }}
    >
      Ashworth
      <span style={{ color: FOREST }}>.</span>
    </span>
  );
}

function Kopf() {
  const [offen, setOffen] = useState(false);
  const [weg, setWeg] = useState(false);
  const { scrollY } = useScroll();
  const letzte = useRef(0);

  /* Beim Herunterscrollen verschwindet die Leiste, beim kleinsten Ruck nach
     oben ist sie wieder da. Oben auf der Seite steht sie immer. */
  useMotionValueEvent(scrollY, "change", (y) => {
    const delta = y - letzte.current;
    letzte.current = y;
    if (y < 120) setWeg(false);
    else if (delta > 6) setWeg(true);
    else if (delta < -6) setWeg(false);
  });

  return (
    <header
      className="sticky top-0 z-50"
      style={{
        background: "rgba(255,255,255,0.9)",
        backdropFilter: "blur(14px) saturate(1.2)",
        WebkitBackdropFilter: "blur(14px) saturate(1.2)",
        transform: weg && !offen ? "translateY(-100%)" : "translateY(0)",
        transition: "transform 360ms cubic-bezier(0.2,0.8,0.2,1)",
      }}
    >
      <div className="mx-auto grid min-h-[4.5rem] w-full max-w-[84rem] grid-cols-[1fr_auto] items-center gap-6 px-5 sm:px-8 lg:min-h-[5.5rem] lg:grid-cols-[1fr_auto_1fr]">
        <a href="#top" className="justify-self-start">
          <Wortmarke />
        </a>

        <nav className="hidden items-center gap-9 justify-self-center lg:flex" aria-label="Main">
          {NAV.map((item) => (
            <a
              key={item.href}
              href={item.href}
              className="group relative py-1.5 text-[12px] font-medium tracking-[0.12em] whitespace-nowrap uppercase transition-colors"
              style={{ fontFamily: SANS, color: QUIET }}
            >
              {item.label}
              <span
                aria-hidden
                className="absolute inset-x-0 bottom-0 h-px origin-left scale-x-0 transition-transform duration-300 group-hover:scale-x-100"
                style={{ background: INK }}
              />
            </a>
          ))}
        </nav>

        <div className="flex items-center gap-2 justify-self-end">
          <span className="hidden md:inline-flex">
            <Knopf href="#contact" icon={CalendarPlus}>
              Book a first call
            </Knopf>
          </span>
          <button
            type="button"
            onClick={() => setOffen((v) => !v)}
            aria-expanded={offen}
            aria-controls="nav-klein"
            aria-label={offen ? "Close menu" : "Open menu"}
            className="inline-flex h-11 w-11 items-center justify-center transition-colors lg:hidden"
            style={{ borderRadius: 6, color: INK }}
          >
            {offen ? <X size={22} strokeWidth={1.6} /> : <Menu size={22} strokeWidth={1.6} />}
          </button>
        </div>
      </div>

      {/* Aufklappen ueber die Zeilenhoehe des Rasters: das braucht keine feste
          Hoehe und kommt deshalb ohne Messen aus. */}
      <div
        id="nav-klein"
        className="grid lg:hidden"
        style={{
          gridTemplateRows: offen ? "1fr" : "0fr",
          transition: "grid-template-rows 320ms cubic-bezier(0.2,0.8,0.2,1)",
        }}
      >
        <div className="overflow-hidden">
          <nav
            className="mx-auto flex w-full max-w-[84rem] flex-col px-5 py-3 sm:px-8"
            aria-label="Mobile"
          >
            {NAV.map((item) => (
              <a
                key={item.href}
                href={item.href}
                onClick={() => setOffen(false)}
                className="py-3 text-xl"
                style={{ ...display, color: INK }}
              >
                {item.label}
              </a>
            ))}
            <span className="mt-3 mb-4 md:hidden">
              <Knopf href="#contact" icon={CalendarPlus} onClick={() => setOffen(false)}>
                Book a first call
              </Knopf>
            </span>
          </nav>
        </div>
      </div>
    </header>
  );
}

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

function Hero() {
  const reduce = useReducedMotion();
  const breit = useBreit("(min-width: 768px)");
  const halter = useRef<HTMLDivElement>(null);
  /* Der Hero bleibt stehen, waehrend der Rest der Seite darueber schiebt,
     wandert dabei aber langsam weiter nach oben. Ein Bild, das festklebt und
     sich nicht ruehrt, sieht aus wie ein Fehler. */
  const { scrollYProgress } = useScroll({
    target: halter,
    offset: ["start start", "end start"],
  });
  const y = useTransform(scrollYProgress, [0, 1], [0, -110]);
  /* Das Nachwandern gehoert zum Festhalten. Ohne das Festhalten waere es nur
     ein Bild, das sich vom Rest der Seite loest und eine Luecke hinterlaesst,
     deshalb laeuft beides zusammen oder gar nicht. */
  const haelt = breit && !reduce;

  return (
    <div ref={halter} id="top">
      <motion.section
        className={haelt ? "sticky top-0 z-0" : "relative"}
        style={{ y: haelt ? y : 0 }}
      >
        <div className="mx-auto grid w-full max-w-[84rem] items-center gap-x-12 gap-y-9 px-5 py-10 sm:px-8 sm:py-14 lg:grid-cols-12 lg:py-24">
          <div className="lg:col-span-6">
            <Auftritt>
              <Marke icon={Compass}>One to one, by video call. Since 2004.</Marke>
            </Auftritt>
            <Auftritt verzug={0.08}>
              <h1
                className="mt-5 text-[clamp(2rem,3.5vw,3.05rem)] leading-[1.07] text-balance"
                style={{ ...display, color: INK }}
              >
                The job still works on paper. That is usually the hard part.
              </h1>
            </Auftritt>
            <Auftritt verzug={0.16}>
              <p
                className="mt-5 max-w-[58ch] text-[17px] leading-relaxed"
                style={{ fontFamily: SANS, color: QUIET }}
              >
                Careers rarely break in one go. They drift, and one day the week no longer fits the
                person living it. Talk it through with someone whose whole job is this conversation,
                before you hand in anything.
              </p>
            </Auftritt>
            <Auftritt verzug={0.24}>
              <div className="mt-8 flex flex-wrap items-center gap-x-6 gap-y-3">
                <Knopf href="#contact" icon={CalendarPlus}>
                  Book a first call
                </Knopf>
                <Leiselink href="#method" pfeil={false}>
                  See how a session runs
                </Leiselink>
              </div>
            </Auftritt>

            {/* Auf schmalen Schirmen steht das Portrait nicht neben dem Text,
                sondern als kleine Zeile darunter. Sonst waere es unter dem
                Falz. */}
            <Auftritt verzug={0.3}>
              <a href="#about" className="mt-8 flex items-center gap-3 lg:hidden">
                <img
                  src={BILD.portrait}
                  alt="Robin Ashworth, career counsellor"
                  className="h-11 w-11 shrink-0 rounded-full object-cover object-[57%_42%]"
                />
                <span className="text-sm leading-snug" style={{ fontFamily: SANS, color: QUIET }}>
                  <span style={{ color: INK, fontWeight: 600 }}>Robin Ashworth</span>, the person on
                  the other side of the call
                </span>
              </a>
            </Auftritt>
          </div>

          <div className="lg:col-span-6">
            <Auftritt verzug={0.12}>
              <div className="relative mx-auto max-w-[580px] lg:mr-0 lg:ml-auto">
                <figure
                  className="relative aspect-[5/6] overflow-hidden"
                  style={{ borderRadius: 12 }}
                >
                  <Wanderbild
                    src={BILD.hero}
                    alt="A person standing in a flowering meadow, open laptops resting in the grass"
                    weite={54}
                  />
                </figure>
                {/* Statt eines Siegels die Zahl selbst. Ein Siegel ist ein
                    Bild, das man nicht lesen kann, ohne es zu vergroessern. */}
                <a
                  href="#voices"
                  className="absolute -top-4 -left-4 hidden flex-col px-4 py-3 sm:flex"
                  style={{ borderRadius: 10, background: PAPER, fontFamily: SANS }}
                >
                  <span className="text-[19px] font-semibold" style={{ color: INK }}>
                    4.9 / 5
                  </span>
                  <span
                    className="text-[11.5px] tracking-[0.08em] uppercase"
                    style={{ color: QUIET }}
                  >
                    412 reviews
                  </span>
                </a>
              </div>
            </Auftritt>
          </div>
        </div>
      </motion.section>
    </div>
  );
}

/* ---------------------------------------------------- saetze, die zerfallen */

/*
 * Jede Karte liegt zuerst lose auf einem Stapel in der Mitte (sx, sy, sr) und
 * wandert an ihren Platz (zx, zy, zr). Die Werte sind Anteile der Flaeche in
 * Prozent, damit die Anordnung mit dem Fenster mitwaechst. v verzoegert die
 * einzelne Karte innerhalb des Scrollwegs.
 */
const SAETZE = [
  {
    text: "I got promoted and I have dreaded Mondays ever since.",
    sx: -4,
    sy: -7,
    sr: -7,
    zx: -30,
    zy: -33,
    zr: -4,
    v: 0,
  },
  {
    text: "Every opening I look at is the same job with a new name on it.",
    sx: 5,
    sy: -2,
    sr: 5,
    zx: 3,
    zy: -38,
    zr: 2,
    v: 0.06,
  },
  {
    text: "I have been meaning to leave for three years.",
    sx: -2,
    sy: 5,
    sr: -3,
    zx: 31,
    zy: -31,
    zr: 4,
    v: 0.12,
  },
  {
    text: "I am good at this. I just do not want to do it any more.",
    sx: 6,
    sy: 8,
    sr: 8,
    zx: -36,
    zy: 4,
    zr: -2,
    v: 0.03,
  },
  {
    text: "I took two years out and now nobody calls back.",
    sx: -7,
    sy: 1,
    sr: -6,
    zx: 35,
    zy: 7,
    zr: 3,
    v: 0.09,
  },
  {
    text: "I would take the pay cut. I do not know what for.",
    sx: 3,
    sy: -9,
    sr: 4,
    zx: -23,
    zy: 35,
    zr: 2,
    v: 0.15,
  },
  {
    text: "My CV explains my past. It does not argue for anything.",
    sx: -1,
    sy: 10,
    sr: -9,
    zx: 25,
    zy: 36,
    zr: -3,
    v: 0.18,
  },
];

const WEG_VH = 140;

function Satzkarte({
  satz,
  lauf,
  mass,
  i,
}: {
  satz: (typeof SAETZE)[number];
  lauf: MotionValue<number>;
  mass: { w: number; h: number };
  i: number;
}) {
  const p = useTransform(lauf, [satz.v, satz.v + 0.7], [0, 1], { clamp: true });
  const x = useTransform(p, [0, 1], [(satz.sx / 100) * mass.w, (satz.zx / 100) * mass.w]);
  const y = useTransform(p, [0, 1], [(satz.sy / 100) * mass.h, (satz.zy / 100) * mass.h]);
  const rotate = useTransform(p, [0, 1], [satz.sr, satz.zr]);
  const scale = useTransform(p, [0, 1], [0.94, 1]);

  return (
    <motion.li
      /* Die halbe Karte wird ueber die eigenstaendige translate-Eigenschaft
         zurueckgeschoben, nicht ueber das Transform: framer-motion belegt das
         Transform selbst, und beides zusammen in einer Angabe loescht sich
         gegenseitig aus. Gedreht wird dadurch um die Kartenmitte. */
      className="pointer-events-auto absolute top-1/2 left-1/2 w-max max-w-[17rem] -translate-x-1/2 -translate-y-1/2 px-[1.2rem] py-[0.95rem] text-center text-[15px] leading-snug font-medium text-balance lg:max-w-[14rem] xl:max-w-[17rem]"
      style={{
        borderRadius: 12,
        background: PAPER,
        color: INK,
        fontFamily: SANS,
        zIndex: 10 - i,
        x,
        y,
        rotate,
        scale,
      }}
    >
      {`„${satz.text}“`}
    </motion.li>
  );
}

function Saetze() {
  const reduce = useReducedMotion();
  const breit = useBreit();
  const an = breit && !reduce;

  const buehne = useRef<HTMLElement>(null);
  const kasten = useRef<HTMLDivElement>(null);
  const [mass, setMass] = useState({ w: 0, h: 0 });

  /* Die Zielpunkte stehen in Prozent der Flaeche. Damit daraus Pixel werden,
     muss die Flaeche gemessen werden; eine Prozentangabe im Transform bezoege
     sich sonst auf die Karte selbst. */
  useEffect(() => {
    const el = kasten.current;
    if (!el || !an) return;
    const beobachter = new ResizeObserver(([eintrag]) => {
      const r = eintrag.contentRect;
      setMass({ w: r.width, h: r.height });
    });
    beobachter.observe(el);
    return () => beobachter.disconnect();
  }, [an]);

  const { scrollYProgress } = useScroll({
    target: buehne,
    offset: ["start start", "end end"],
  });
  /* Der Stapel zerfaellt ueber die ersten zwei Drittel, im letzten Drittel
     hebt der ganze Kasten ab. So folgt auf das Auseinandergehen ein Abgang
     und nicht ein Stillstand. */
  const p = useTransform(scrollYProgress, [0, 0.72], [0, 1], { clamp: true });
  const ab = useTransform(scrollYProgress, [0.74, 1], [0, 1], { clamp: true });
  const kastenY = useTransform(ab, [0, 1], ["0vh", "-8vh"]);
  const kastenScale = useTransform(ab, [0, 1], [1, 0.8]);
  const kastenOpacity = useTransform(ab, [0, 1], [1, 0]);
  const titelOpacity = useTransform(p, [0.3, 0.68], [0, 1]);
  const bildY = useTransform(p, [0, 1], [24, -24]);

  return (
    <section
      id="situation"
      ref={buehne}
      className="relative scroll-mt-24"
      style={an ? { height: `calc(min(86vh, 56rem) + ${WEG_VH}vh)` } : undefined}
    >
      <div
        className="px-5 sm:px-8 lg:px-12 xl:px-16"
        style={
          an
            ? {
                position: "sticky",
                top: "calc((100vh - min(86vh, 56rem)) / 2)",
                height: "min(86vh, 56rem)",
              }
            : undefined
        }
      >
        <motion.div
          ref={kasten}
          className={`relative isolate flex h-full items-center justify-center overflow-hidden px-6 py-16 ${
            an ? "" : "flex-col py-14"
          }`}
          style={{
            borderRadius: 20,
            transformOrigin: "50% 0",
            y: an ? kastenY : 0,
            scale: an ? kastenScale : 1,
            opacity: an ? kastenOpacity : 1,
          }}
        >
          <motion.span
            aria-hidden
            className="absolute inset-[-10%] -z-20 block"
            style={{ y: an ? bildY : 0 }}
          >
            <img
              src={BILD.wiese}
              alt=""
              loading="lazy"
              decoding="async"
              className="h-full w-full object-cover object-[50%_40%] select-none"
            />
          </motion.span>
          <span
            aria-hidden
            className="absolute inset-0 -z-10"
            style={{ background: "rgba(0,0,0,0.48)" }}
          />

          <motion.div
            className={`relative z-[1] mx-auto w-full max-w-[46rem] text-center ${an ? "" : "order-2"}`}
            style={an ? { opacity: titelOpacity } : undefined}
          >
            <Marke icon={Users} hell>
              You are not the only one saying it
            </Marke>
            <h2
              className="mt-4 text-[clamp(1.9rem,3.2vw,2.75rem)] leading-[1.06] text-balance"
              style={{ ...display, color: "#ffffff" }}
            >
              Any of this sound familiar?
            </h2>
            <p
              className="mx-auto mt-4 max-w-[46ch] text-[16px] leading-relaxed"
              style={{ fontFamily: SANS, color: "rgba(255,255,255,0.85)" }}
            >
              Sentences people open a first call with. Most of them have been true for months before
              anyone says them out loud.
            </p>
            <p className="mt-5 font-medium" style={{ fontFamily: SANS, color: "#ffffff" }}>
              Yes. That is more or less my week.
            </p>
            <span className="mt-4 inline-flex">
              <Knopf href="#contact" art="hell" icon={MessageSquare}>
                Put yours into words
              </Knopf>
            </span>
          </motion.div>

          {an ? (
            <ul className="pointer-events-none absolute inset-0 z-[2] m-0 list-none p-0">
              {SAETZE.map((satz, i) => (
                <Satzkarte key={satz.text} satz={satz} lauf={p} mass={mass} i={i} />
              ))}
            </ul>
          ) : (
            /* Ruhige Fassung: ein Stapel im Zickzack, der das Foto zwischen
               den Karten noch durchscheinen laesst. */
            <ul className="relative z-[1] order-1 m-0 mb-8 grid w-full list-none grid-cols-[minmax(0,1fr)] gap-1.5 p-0">
              {SAETZE.map((satz, i) => (
                <li
                  key={satz.text}
                  className={`max-w-[88%] px-3.5 py-2.5 text-[15px] leading-snug font-medium ${
                    i % 2 === 1 ? "justify-self-end" : ""
                  }`}
                  style={{ borderRadius: 12, background: PAPER, color: INK, fontFamily: SANS }}
                >
                  {`„${satz.text}“`}
                </li>
              ))}
            </ul>
          )}
        </motion.div>
      </div>
    </section>
  );
}

/* ---------------------------------------------------------- so lauft es ab */

const PUNKTE = [
  { icon: Users, text: "One to one by video call. No cohort, no curriculum, no group chat." },
  {
    icon: Target,
    text: "Built around your actual constraints: money, notice period, people at home.",
  },
  {
    icon: Check,
    text: "Steps small enough that you will still have done them by the next session.",
  },
];

function Methode() {
  return (
    <section id="method" className="scroll-mt-24 pt-16 pb-14 sm:pt-24 sm:pb-20 lg:pt-32 lg:pb-28">
      <div className="mx-auto grid w-full max-w-[84rem] items-center gap-x-12 gap-y-10 px-5 sm:px-8 lg:grid-cols-12 lg:gap-x-20">
        <div className="lg:col-span-7">
          <Auftritt>
            <Marke icon={ClipboardList}>How I work</Marke>
          </Auftritt>
          <Auftritt verzug={0.06}>
            <h2
              className="mt-4 max-w-[24ch] text-[clamp(1.6rem,2.5vw,2.15rem)] leading-[1.1] text-balance"
              style={{ ...display, color: INK }}
            >
              You are not buying a framework. You are buying an hour of undivided attention.
            </h2>
          </Auftritt>
          <Auftritt verzug={0.12}>
            <div
              className="mt-5 max-w-[62ch] space-y-3 text-[16px] leading-relaxed"
              style={{ fontFamily: SANS, color: QUIET }}
            >
              <p>
                You have probably read the books, taken the strengths test and made the list of pros
                and cons twice. Most of that advice is sound. None of it knows your notice period,
                your mortgage or the conversation you are dreading at home.
              </p>
              <p>What you do not need now is another reasonable suggestion.</p>
            </div>
          </Auftritt>

          <ul
            className="m-0 mt-7 list-none space-y-2.5 p-0 text-[15px]"
            style={{ fontFamily: SANS }}
          >
            {PUNKTE.map((punkt, i) => (
              <Auftritt key={punkt.text} verzug={0.16 + i * 0.05}>
                <li className="flex items-start gap-2.5" style={{ color: QUIET }}>
                  <punkt.icon
                    size={17}
                    strokeWidth={1.6}
                    aria-hidden
                    className="mt-[0.2rem] shrink-0"
                    style={{ color: FOREST }}
                  />
                  <span>{punkt.text}</span>
                </li>
              </Auftritt>
            ))}
          </ul>

          <Auftritt verzug={0.32}>
            <div className="mt-8">
              <Knopf href="#fees" icon={ArrowRight}>
                See the format and the fees
              </Knopf>
            </div>
          </Auftritt>
        </div>

        {/* Das Zitat liegt auf dem Bild und kommt erst beim Zeigen. Auf
            Geraeten ohne Zeiger steht es dauerhaft da, sonst waere es dort
            nicht erreichbar. */}
        <Auftritt verzug={0.1} className="lg:col-span-5">
          <figure
            className="group relative aspect-square overflow-hidden"
            style={{ borderRadius: 12 }}
          >
            <Wanderbild
              src={BILD.sitzen}
              alt="A person sitting alone on a lawn in late afternoon light"
              className="object-[50%_40%]"
            />
            <figcaption
              className="absolute inset-0 z-[1] flex items-end p-6 opacity-100 transition-opacity duration-500 sm:p-8 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
              style={{
                background: "linear-gradient(180deg, rgba(0,0,0,0.05), rgba(0,0,0,0.58))",
                color: "#ffffff",
              }}
            >
              <blockquote className="m-0 translate-y-0 transition-transform duration-500 md:translate-y-2.5 md:group-hover:translate-y-0">
                <p
                  className="max-w-[34ch] text-[15px] leading-relaxed font-medium text-balance lg:text-base"
                  style={{ fontFamily: SANS }}
                >
                  {
                    "„Most people arrive with the decision already made. What is missing is the sentence they can say out loud.“"
                  }
                </p>
                <footer
                  className="mt-2 text-xs"
                  style={{ fontFamily: SANS, color: "rgba(255,255,255,0.75)" }}
                >
                  Robin Ashworth
                </footer>
              </blockquote>
            </figcaption>
          </figure>
        </Auftritt>
      </div>
    </section>
  );
}

/* --------------------------------------------------------------- ueber mich */

function UeberMich() {
  return (
    <section
      id="about"
      className="relative scroll-mt-24 pt-16 pb-14 sm:pt-24 sm:pb-20 lg:pt-32 lg:pb-28"
    >
      <div className="mx-auto grid w-full max-w-[84rem] items-center gap-x-12 gap-y-9 px-5 sm:px-8 lg:grid-cols-12">
        <Auftritt className="mx-auto max-w-[440px] lg:col-span-5 lg:mx-0">
          <figure className="relative aspect-[3/4] overflow-hidden" style={{ borderRadius: 12 }}>
            <Wanderbild
              src={BILD.portrait}
              alt="Robin Ashworth working from a laptop in tall summer grass"
              className="object-[57%_38%]"
            />
          </figure>
        </Auftritt>

        <div className="lg:col-span-7">
          <Auftritt>
            <Marke icon={Timer}>Twenty one years of these conversations</Marke>
          </Auftritt>
          <Auftritt verzug={0.06}>
            <h2
              className="mt-4 text-[clamp(1.6rem,2.5vw,2.15rem)] leading-[1.1] text-balance"
              style={{ ...display, color: INK }}
            >
              I am Robin Ashworth.
            </h2>
          </Auftritt>
          <Auftritt verzug={0.12}>
            <div
              className="mt-5 max-w-[62ch] space-y-3 text-[16px] leading-relaxed"
              style={{ fontFamily: SANS, color: QUIET }}
            >
              <p>
                I spent eleven years hiring for other people before I started sitting on this side
                of the table. That is where the habit comes from: I read a career the way the person
                reading your application will, and then we work backwards from there.
              </p>
              <p>
                Sessions run by video call, which means it does not matter where you live or which
                time zone your working day sits in. Evenings and early mornings are usually the
                easiest slots to find.
              </p>
              <p>
                Registered practitioner, member number 4412, in supervision with Elena Bright since
                2011. The longer version, including the two years I spent getting this wrong, is on{" "}
                <a
                  href="#about"
                  className="inline-flex items-center gap-0.5 font-semibold"
                  style={{
                    color: FOREST,
                    textDecoration: "underline",
                    textDecorationColor: "rgba(68,87,61,0.45)",
                    textUnderlineOffset: "0.2em",
                  }}
                >
                  the about page
                  <ArrowUpRight size={14} strokeWidth={1.7} aria-hidden />
                </a>
              </p>
            </div>
          </Auftritt>
          <Auftritt verzug={0.2}>
            <div className="mt-7">
              <Knopf href="#about" art="leise" icon={ArrowRight}>
                Read the long version
              </Knopf>
            </div>
          </Auftritt>
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ die andere frage */

function AndereFrage() {
  return (
    <section className="pb-14 sm:pb-20 lg:pb-28">
      <div className="mx-auto w-full max-w-[84rem] px-5 sm:px-8">
        <div
          /* Auf Hoehe dieses Abschnitts steht die Tintbuehne auf Weiss,
             deshalb setzt sich das hellgraue Feld hier ab. */
          className="relative isolate overflow-hidden px-6 py-10 sm:px-10 lg:px-14 lg:py-16"
          style={{ borderRadius: 16, background: MIST }}
        >
          <span
            aria-hidden
            className="pointer-events-none absolute inset-0 -z-10 opacity-55 mix-blend-multiply"
            style={{ backgroundImage: GRAIN, backgroundSize: "160px 160px" }}
          />
          <div className="grid gap-x-14 gap-y-8 lg:grid-cols-12">
            <div className="lg:col-span-6">
              <Auftritt>
                <Marke icon={Eye}>A different question</Marke>
              </Auftritt>
              <Auftritt verzug={0.06}>
                <h2
                  className="mt-5 text-[clamp(1.05rem,1.5vw,1.25rem)] leading-snug font-medium"
                  style={{ fontFamily: SANS, color: INK }}
                >
                  Looking for the job that fixes this?
                </h2>
              </Auftritt>
              {/* Der Dreh des Abschnitts traegt das Gewicht, deshalb steht er
                  gross und in der Akzentfarbe der Seite. */}
              <Auftritt verzug={0.12}>
                <p
                  className="mt-4 text-[clamp(1.6rem,3.1vw,2.55rem)] leading-[1.12] text-balance"
                  style={{ ...display, color: FOREST }}
                >
                  What do I want an ordinary Wednesday to look like?
                </p>
              </Auftritt>
            </div>

            <div className="space-y-4 lg:col-span-6 lg:pt-2">
              <Auftritt verzug={0.1}>
                <p
                  className="text-[16px] leading-relaxed"
                  style={{ fontFamily: SANS, color: QUIET }}
                >
                  When work stops fitting, the first instinct is to look for a better title.
                  Sometimes that is exactly right. More often the title was never the problem, and
                  the new one comes with the same Wednesday attached.
                </p>
              </Auftritt>
              <Auftritt verzug={0.16}>
                <p
                  className="text-[16px] leading-relaxed"
                  style={{ fontFamily: SANS, color: QUIET }}
                >
                  So we start with the week itself: the hours, the people, the commute, the part of
                  the work you would miss. Roles are easier to judge once you know what you are
                  judging them against.
                </p>
              </Auftritt>
            </div>
          </div>

          <Auftritt verzug={0.2}>
            <div
              className="mt-10 flex flex-wrap items-center justify-between gap-x-8 gap-y-2 pt-5 text-sm"
              style={{ borderTop: `1px solid ${EDGE}`, fontFamily: SANS }}
            >
              <p className="font-medium" style={{ color: INK }}>
                The week before the title
              </p>
              <Leiselink href="#method" icon={ArrowUpRight}>
                How the first two sessions are structured
              </Leiselink>
            </div>
          </Auftritt>
        </div>
      </div>
    </section>
  );
}

/* ----------------------------------------------------------------- themen */

type Thema = { label: string; icon: typeof Compass };

const REIHE_EINS: Thema[] = [
  { label: "A promotion you did not want", icon: Layers },
  { label: "Going freelance", icon: Rocket },
  { label: "Coming back after a break", icon: Flag },
  { label: "Leaving the family business", icon: Handshake },
  { label: "Burnout and what comes after", icon: Timer },
  { label: "Management or the craft", icon: Scale },
  { label: "Redundancy at short notice", icon: FileText },
];

const REIHE_ZWEI: Thema[] = [
  { label: "Asking for more money", icon: Wallet },
  { label: "Studying again at forty", icon: GraduationCap },
  { label: "Moving countries for work", icon: MapPin },
  { label: "First job after a doctorate", icon: ScrollText },
  { label: "Stepping back from leading", icon: Users },
  { label: "Turning a side project into the job", icon: Sparkle },
];

function Chips({ items, versteckt = false }: { items: Thema[]; versteckt?: boolean }) {
  return (
    <ul
      className="m-0 flex flex-none list-none gap-2.5 p-0 pr-2.5"
      aria-hidden={versteckt || undefined}
    >
      {items.map((thema) => (
        <li
          key={thema.label}
          className="inline-flex flex-none items-center gap-2 px-3.5 text-sm font-medium whitespace-nowrap"
          style={{
            minHeight: "2.4rem",
            borderRadius: 6,
            /* Weiss und nicht hellgrau: die Chips sitzen am unteren Ende der
               Tintbuehne, und dort ist die Flaeche wieder grau. Grau auf Grau
               waere eine Reihe, die man nicht mehr sieht. */
            background: PAPER,
            color: INK,
            fontFamily: SANS,
          }}
        >
          <thema.icon size={16} strokeWidth={1.6} aria-hidden style={{ color: FOREST }} />
          {thema.label}
        </li>
      ))}
    </ul>
  );
}

function Laufband({
  items,
  dauer,
  rueckwaerts = false,
}: {
  items: Thema[];
  dauer: number;
  rueckwaerts?: boolean;
}) {
  const reduce = useReducedMotion();
  return (
    <div
      className="min-w-0 max-w-full overflow-hidden"
      style={{
        maskImage: "linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent)",
        WebkitMaskImage: "linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent)",
      }}
    >
      <motion.div
        className="flex w-max"
        animate={reduce ? undefined : { x: rueckwaerts ? ["-50%", "0%"] : ["0%", "-50%"] }}
        transition={reduce ? undefined : { duration: dauer, ease: "linear", repeat: Infinity }}
      >
        <Chips items={items} />
        {/* Die zweite Haelfte ist nur die Fortsetzung des Bandes und wird
            deshalb vor Vorlesegeraeten versteckt. Sonst wird jede Zeile
            zweimal vorgelesen. */}
        <Chips items={items} versteckt />
      </motion.div>
    </div>
  );
}

function Themen() {
  return (
    <section className="py-14 sm:py-20 lg:py-28">
      <div className="mx-auto grid w-full max-w-[84rem] items-stretch gap-x-10 gap-y-10 px-5 sm:px-8 lg:grid-cols-12">
        <Auftritt className="mx-auto w-full max-w-[440px] lg:col-span-5 lg:mx-0">
          <figure className="relative aspect-[10/9] overflow-hidden" style={{ borderRadius: 12 }}>
            <Wanderbild src={BILD.huegel} alt="A wide meadow of tall grass bending in the wind" />
          </figure>
        </Auftritt>

        <div className="flex min-w-0 flex-col lg:col-span-7">
          <div>
            <Auftritt>
              <Marke icon={Layers}>What people bring</Marke>
            </Auftritt>
            <Auftritt verzug={0.06}>
              <h2
                className="mt-4 text-[clamp(1.75rem,2.8vw,2.4rem)] leading-[1.08] text-balance"
                style={{ ...display, color: INK }}
              >
                What can a session actually be about?
              </h2>
            </Auftritt>
            <Auftritt verzug={0.12}>
              <p
                className="mt-4 max-w-[54ch] text-[16px] leading-relaxed"
                style={{ fontFamily: SANS, color: QUIET }}
              >
                Every working life is its own shape. These are the situations that come up most
                often, and none of them needs to be tidy before the first call.
              </p>
            </Auftritt>
            <Auftritt verzug={0.18}>
              <div className="mt-6">
                <Knopf href="#contact" art="leise" icon={MessageSquare}>
                  Bring yours
                </Knopf>
              </div>
            </Auftritt>
          </div>

          {/* min-w-0 ist hier Bedingung und nicht Kuer: ein Flex-Kind wird von
              sich aus nicht schmaler als sein Inhalt, und das Band ist ein
              paar Meter lang. Ohne das schiebt es die ganze Seite breiter,
              statt in sich zu laufen. */}
          <Auftritt verzug={0.22} className="mt-9 min-w-0 lg:mt-auto">
            <div className="space-y-3">
              <Laufband items={REIHE_EINS} dauer={58} />
              <Laufband items={REIHE_ZWEI} dauer={66} rueckwaerts />
            </div>
          </Auftritt>
        </div>
      </div>
    </section>
  );
}

/* -------------------------------------------------------------- zitatband */

function Zitatband() {
  const reduce = useReducedMotion();
  const band = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: band,
    offset: ["start end", "center center"],
  });
  /* Das Band waechst beim Hereinkommen auf volle Groesse, statt fertig
     dazustehen. Es ist das einzige Element der Seite, das das tut. */
  const scale = useTransform(scrollYProgress, [0, 1], [0.92, 1]);

  return (
    <section
      className="relative isolate px-4 py-4 lg:px-6 lg:py-6"
      style={{ background: MIST }}
      aria-label="Quote"
    >
      <span
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10 opacity-55 mix-blend-multiply"
        style={{ backgroundImage: GRAIN, backgroundSize: "160px 160px" }}
      />
      <motion.div
        ref={band}
        className="relative isolate overflow-hidden py-24 lg:py-36"
        style={{ borderRadius: 16, scale: reduce ? 1 : scale }}
      >
        <Wanderbild src={BILD.wald} alt="" weite={70} />
        {/* Etwas mehr Schwarz als bei den anderen Bildflaechen: die Wiese ist
            hell, und eine helle Serifenschrift auf hellem Gruen liest sich
            nicht. Gedimmt, nicht ausgewaschen. */}
        <span aria-hidden className="absolute inset-0" style={{ background: "rgba(0,0,0,0.52)" }} />
        <div className="relative mx-auto w-[min(100%-2.5rem,46rem)] text-center">
          <Auftritt>
            <blockquote className="m-0">
              <p
                className="text-[clamp(1.5rem,2.8vw,2.4rem)] leading-[1.2] text-balance"
                style={{ ...display, color: "#ffffff" }}
              >
                {
                  "„A career is not a ladder. It is a series of decisions, and most of them are still open.“"
                }
              </p>
              <footer
                className="mt-5 text-sm font-medium"
                style={{ fontFamily: SANS, color: "rgba(255,255,255,0.85)" }}
              >
                Robin Ashworth
              </footer>
            </blockquote>
          </Auftritt>
        </div>
      </motion.div>
    </section>
  );
}

/* ------------------------------------------------------------- was sich dreht */

const PAARE = [
  { vor: "Drifting", nach: "A decision", icon: Compass, seite: "links", x: 24 },
  {
    vor: "Every posting open in a tab",
    nach: "Three real options",
    icon: Target,
    seite: "rechts",
    x: 22,
  },
  { vor: "Someday", nach: "A date in the calendar", icon: CalendarPlus, seite: "links", x: 34 },
  {
    vor: "Guessing what you are worth",
    nach: "A number you can say",
    icon: Wallet,
    seite: "rechts",
    x: 30,
  },
  { vor: "A CV that explains", nach: "A CV that argues", icon: FileText, seite: "links", x: 20 },
  { vor: "Waiting to feel ready", nach: "Starting anyway", icon: Rocket, seite: "rechts", x: 36 },
  { vor: "Telling nobody", nach: "Four people who know", icon: Share2, seite: "links", x: 28 },
] as const;

const SCHRITT_VH = 38;
const START_VH = 88;

function Wendung() {
  const reduce = useReducedMotion();
  const breit = useBreit();
  const an = breit && !reduce;

  const buehne = useRef<HTMLElement>(null);
  const { scrollYProgress } = useScroll({ target: buehne, offset: ["start start", "end end"] });
  /* Der Satz bleibt stehen und tritt zurueck, waehrend die Paare an ihm
     vorbeiziehen. Er verschwindet nicht, er ruecht in den Hintergrund. */
  const scale = useTransform(scrollYProgress, [0, 1], [1, 0.62]);
  const blur = useTransform(scrollYProgress, [0, 1], ["blur(0px)", "blur(7px)"]);
  const opacity = useTransform(scrollYProgress, [0, 1], [1, 0.45]);

  return (
    <section
      ref={buehne}
      className="relative isolate scroll-mt-24"
      style={{
        background: MIST,
        height: an ? `calc(100vh + ${PAARE.length * SCHRITT_VH}vh)` : undefined,
      }}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10 opacity-55 mix-blend-multiply"
        style={{ backgroundImage: GRAIN, backgroundSize: "160px 160px" }}
      />

      <div
        className={an ? "sticky top-0 flex h-screen items-center justify-center" : "pt-14 sm:pt-20"}
      >
        <motion.div
          className="mx-auto w-[min(100%-2.5rem,46rem)] text-center"
          style={an ? { scale, filter: blur, opacity } : undefined}
        >
          <h2
            className="text-[clamp(1.75rem,4.2vw,3.5rem)] leading-[1.06] text-balance"
            style={{ ...display, color: INK }}
          >
            Change starts as a sentence you finally finish.
          </h2>
          {!an && (
            <p
              className="mx-auto mt-4 max-w-[44ch] text-[16px] leading-relaxed"
              style={{ fontFamily: SANS, color: QUIET }}
            >
              What shifts in counselling shows up first in small things during an ordinary week.
            </p>
          )}
        </motion.div>
      </div>

      <ul
        className={
          an ? "absolute inset-0 m-0 list-none p-0" : "m-0 mt-10 mb-14 list-none p-0 sm:mb-20"
        }
      >
        {PAARE.map((paar, i) =>
          an ? (
            <li
              key={paar.nach}
              className="absolute inline-flex items-center gap-3 py-3.5 pr-[1.15rem] pl-[0.85rem] text-[15px] whitespace-nowrap"
              style={{
                borderRadius: 10,
                background: PAPER,
                fontFamily: SANS,
                top: `calc(${START_VH}vh + ${i * SCHRITT_VH}vh)`,
                ...(paar.seite === "links" ? { left: `${paar.x}%` } : { right: `${paar.x}%` }),
              }}
            >
              <span
                aria-hidden
                className="inline-flex h-9 w-9 items-center justify-center"
                style={{ borderRadius: 8, background: MIST, color: INK }}
              >
                <paar.icon size={18} strokeWidth={1.6} />
              </span>
              <span style={{ color: QUIET }}>{paar.vor}</span>
              <ArrowRight size={16} strokeWidth={1.8} aria-hidden style={{ color: FOREST }} />
              <span style={{ color: FOREST, fontWeight: 600 }}>{paar.nach}</span>
            </li>
          ) : (
            /* Ruhige Fassung: eine Zeile je Paar, die Pfeile in einer Spalte
               untereinander. Das liest sich schneller als sieben Karten. */
            <li
              key={paar.nach}
              className="mx-auto grid w-[min(100%-2.5rem,46rem)] grid-cols-[1.25rem_minmax(0,1.15fr)_auto_minmax(0,1fr)] items-center gap-2.5 px-0.5 py-3.5 text-sm"
              style={{
                fontFamily: SANS,
                borderTop: `1px solid ${EDGE}`,
                borderBottom: i === PAARE.length - 1 ? `1px solid ${EDGE}` : undefined,
              }}
            >
              <paar.icon size={17} strokeWidth={1.6} aria-hidden style={{ color: QUIET }} />
              <span style={{ color: QUIET }}>{paar.vor}</span>
              <ArrowRight size={15} strokeWidth={1.8} aria-hidden style={{ color: FOREST }} />
              <span style={{ color: FOREST, fontWeight: 600 }}>{paar.nach}</span>
            </li>
          ),
        )}
      </ul>
    </section>
  );
}

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

const STIMMEN = [
  {
    text: "I came in wanting a new title and left with a different question. Six months later I am doing the same work three days a week and it is the right amount.",
    wer: "Product manager, 38",
  },
  {
    text: "The part I did not expect was how practical it got. We wrote the actual sentence I would say to my head of department, and then I said it.",
    wer: "Physiotherapist, 44",
  },
  {
    text: "Nobody told me to follow my passion, which was a relief. We looked at the numbers first and the rest of the conversation was much easier after that.",
    wer: "Site engineer, 51",
  },
  {
    text: "After two years at home I had stopped believing the gap was explainable. It took one session to find the version of it that is simply true.",
    wer: "Returning after parental leave, 35",
  },
  {
    text: "Evenings by video call, no travel, no waiting room. We would never have managed it otherwise, and we did the last three sessions as a couple.",
    wer: "Teacher, 41",
  },
  {
    text: "I had the offer in my inbox for eleven days. What I needed was not more analysis, it was somebody to ask what I was actually afraid of.",
    wer: "Data analyst, 29",
  },
  {
    text: "I did not change jobs in the end. I changed how the job is set up, and that turned out to be the thing I had been avoiding all along.",
    wer: "Practice owner, 47",
  },
];

function Sterne() {
  return (
    <p
      className="m-0 mb-4 flex gap-px"
      aria-label="Five out of five stars"
      style={{ color: FOREST }}
    >
      {Array.from({ length: 5 }, (_, i) => (
        <svg key={i} viewBox="0 0 20 20" width="13" height="13" aria-hidden focusable="false">
          <path
            fill="currentColor"
            d="M10 1.6l2.47 5.01 5.53.8-4 3.9.94 5.51L10 14.21l-4.94 2.6.94-5.51-4-3.9 5.53-.8z"
          />
        </svg>
      ))}
    </p>
  );
}

function Karten({ versteckt = false }: { versteckt?: boolean }) {
  return (
    <ul
      className="m-0 flex flex-none list-none gap-6 p-0 pr-6"
      aria-hidden={versteckt || undefined}
    >
      {STIMMEN.map((stimme) => (
        <li
          key={stimme.wer}
          className="flex w-[78vw] min-h-[66vw] flex-none flex-col p-6 sm:w-[21rem] sm:min-h-[16rem] sm:p-7"
          style={{ borderRadius: 14, background: MIST }}
        >
          <Sterne />
          <p
            className="m-0 text-[15px] leading-relaxed sm:text-base"
            style={{ fontFamily: SANS, color: INK }}
          >
            {`„${stimme.text}“`}
          </p>
          <p
            className="mt-auto mb-0 pt-5 text-[11px] font-medium tracking-[0.11em] uppercase"
            style={{ fontFamily: SANS, color: QUIET }}
          >
            {stimme.wer}
          </p>
        </li>
      ))}
    </ul>
  );
}

function Stimmen() {
  const reduce = useReducedMotion();
  return (
    <section id="voices" className="scroll-mt-24 py-16 sm:py-24 lg:py-28">
      <div className="mx-auto w-[min(100%-2.5rem,46rem)]">
        <Auftritt>
          <div className="flex justify-center">
            <Marke icon={MessageSquare}>What people say afterwards</Marke>
          </div>
        </Auftritt>
        <Auftritt verzug={0.06}>
          <h2
            className="mt-4 text-center text-[clamp(1.5rem,2.4vw,2rem)] leading-[1.12] text-balance"
            style={{ ...display, color: INK }}
          >
            Seven working lives, a year later
          </h2>
        </Auftritt>
        <Auftritt verzug={0.12}>
          <p className="mt-3 text-center text-sm" style={{ fontFamily: SANS, color: QUIET }}>
            412 reviews · 4.9 out of 5
          </p>
        </Auftritt>
      </div>

      <div
        className="mt-12 overflow-hidden sm:mt-14"
        style={{
          maskImage: "linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent)",
          WebkitMaskImage: "linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent)",
        }}
      >
        <motion.div
          className="flex w-max"
          animate={reduce ? undefined : { x: ["0%", "-50%"] }}
          transition={reduce ? undefined : { duration: 110, ease: "linear", repeat: Infinity }}
        >
          <Karten />
          <Karten versteckt />
        </motion.div>
      </div>

      <p
        className="mx-auto mt-10 w-[min(100%-2.5rem,46rem)] text-center text-xs"
        style={{ fontFamily: SANS, color: QUIET }}
      >
        All reviews are published anonymously, with permission.
      </p>
    </section>
  );
}

/* ----------------------------------------------------------------- kontakt */

const FORMAT = [
  { label: "First call", wert: "25 minutes, free" },
  { label: "Single session", wert: "£140, 60 minutes" },
  { label: "Four sessions", wert: "£480, over three months" },
];

function Kontakt() {
  return (
    <section id="contact" className="scroll-mt-24 py-14 sm:py-20 lg:py-28">
      <div
        id="fees"
        className="relative isolate mx-auto flex w-[min(100%-2.5rem,64rem)] scroll-mt-24 flex-col items-center overflow-hidden px-7 py-16 text-center sm:px-14 lg:px-20 lg:py-24"
        style={{ borderRadius: 16, color: "#ffffff" }}
      >
        {/* Der Grund gehoert zur Sache und ist kein Farbschleier: dasselbe
            Motiv, nur unscharf und mit Schwarz gedimmt. */}
        <span aria-hidden className="absolute inset-[-4%] -z-20 block">
          <img
            src={BILD.garten}
            alt=""
            loading="lazy"
            decoding="async"
            className="h-full w-full object-cover object-[50%_40%] select-none"
            style={{ filter: "blur(7px) saturate(0.9)" }}
          />
        </span>
        <span
          aria-hidden
          className="absolute inset-0 -z-10"
          style={{
            backgroundColor: "rgba(0,0,0,0.5)",
            backgroundImage: GRAIN,
            backgroundSize: "160px 160px",
          }}
        />

        <Auftritt>
          <Marke icon={CalendarPlus} hell>
            The first call
          </Marke>
        </Auftritt>
        <Auftritt verzug={0.06}>
          <h2
            className="mt-4 text-[clamp(1.9rem,3.4vw,2.9rem)] leading-[1.06] text-balance"
            style={{ ...display }}
          >
            Talk it through before you decide anything.
          </h2>
        </Auftritt>
        <Auftritt verzug={0.12}>
          <p
            className="mt-4 max-w-xl text-[16px] leading-relaxed"
            style={{ fontFamily: SANS, color: "rgba(255,255,255,0.85)" }}
          >
            Twenty five minutes, by video call, at no cost. You describe the situation, I say
            whether I am the right person for it, and if I am not I will tell you who is.
          </p>
        </Auftritt>

        <Auftritt verzug={0.18}>
          <dl
            className="mt-9 flex flex-wrap items-start justify-center gap-x-10 gap-y-5 text-center"
            style={{ fontFamily: SANS }}
          >
            {FORMAT.map((zeile) => (
              <div key={zeile.label}>
                <dt
                  className="text-[11px] font-medium tracking-[0.11em] uppercase"
                  style={{ color: "rgba(255,255,255,0.7)" }}
                >
                  {zeile.label}
                </dt>
                <dd className="mt-1 ml-0 text-[15px] font-medium">{zeile.wert}</dd>
              </div>
            ))}
          </dl>
        </Auftritt>

        <Auftritt verzug={0.24}>
          <div className="mt-9">
            <Knopf href="#contact" art="hell" icon={CalendarPlus}>
              Book a first call
            </Knopf>
          </div>
        </Auftritt>
      </div>
    </section>
  );
}

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

const FUSS_EXTERN = [
  { label: "Reading list", href: "#about" },
  { label: "Notes on the work", href: "#about" },
];

const FUSS_KONTAKT = [
  { label: "+44 20 7946 1180", href: "#contact", icon: Phone },
  { label: "hello@ashworth-careers.co.uk", href: "#contact", icon: Mail },
];

function Fusszeile() {
  return (
    <footer
      className="relative isolate mx-4 overflow-hidden lg:mx-6"
      style={{ borderRadius: 16, background: MIST, color: INK }}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10 opacity-55 mix-blend-multiply"
        style={{ backgroundImage: GRAIN, backgroundSize: "160px 160px" }}
      />
      <div
        className="mx-auto grid w-full max-w-[84rem] gap-x-10 gap-y-10 px-5 pt-14 pb-12 text-sm sm:grid-cols-2 sm:px-8 lg:grid-cols-[1.5fr_1fr_1fr_1fr] lg:pt-16"
        style={{ fontFamily: SANS }}
      >
        <Auftritt className="sm:col-span-2 lg:col-span-1">
          <Wortmarke />
          <p className="mt-4 max-w-xs leading-relaxed" style={{ color: QUIET }}>
            One to one career counselling by video call. Plain, practical and paid for by the hour.
            Since 2004.
          </p>
          <div className="mt-4">
            <Leiselink href="#about" icon={ArrowUpRight}>
              Newsletter, once a month
            </Leiselink>
          </div>
        </Auftritt>

        <Auftritt verzug={0.06}>
          <p className="text-sm font-semibold" style={{ color: INK }}>
            Navigation
          </p>
          <ul className="m-0 mt-3 list-none space-y-2.5 p-0">
            {NAV.map((item) => (
              <li key={item.href}>
                <a href={item.href} className="transition-colors" style={{ color: QUIET }}>
                  {item.label}
                </a>
              </li>
            ))}
          </ul>
        </Auftritt>

        <Auftritt verzug={0.12}>
          <p className="text-sm font-semibold" style={{ color: INK }}>
            Elsewhere
          </p>
          <ul className="m-0 mt-3 list-none space-y-2.5 p-0">
            {FUSS_EXTERN.map((item) => (
              <li key={item.label}>
                <a
                  href={item.href}
                  className="inline-flex items-center gap-1 transition-colors"
                  style={{ color: QUIET }}
                >
                  {item.label}
                  <ArrowUpRight size={14} strokeWidth={1.7} aria-hidden />
                </a>
              </li>
            ))}
          </ul>
        </Auftritt>

        <Auftritt verzug={0.18}>
          <p className="text-sm font-semibold" style={{ color: INK }}>
            Contact
          </p>
          <ul className="m-0 mt-3 list-none space-y-2.5 p-0">
            {FUSS_KONTAKT.map((item) => (
              <li key={item.label}>
                <a
                  href={item.href}
                  className="inline-flex items-center gap-2 transition-colors"
                  style={{ color: QUIET }}
                >
                  <item.icon size={15} strokeWidth={1.6} aria-hidden className="shrink-0" />
                  {item.label}
                </a>
              </li>
            ))}
          </ul>
          <p className="mt-4 max-w-[26ch]" style={{ color: QUIET }}>
            Sessions run by video call, wherever you happen to live.
          </p>
        </Auftritt>
      </div>

      <div
        className="mx-auto flex w-full max-w-[84rem] flex-wrap items-center justify-between gap-x-8 gap-y-2 px-5 pt-4 pr-14 pb-8 text-xs sm:px-8 sm:pr-16 lg:pr-20"
        style={{ fontFamily: SANS, color: QUIET }}
      >
        <p className="flex flex-wrap items-center gap-x-4">
          <span2026 Robin Ashworth</span>
          <a href="#top">Imprint</a>
          <a href="#top">Privacy</a>
        </p>
        <p>The photographs on this site are generated.</p>
      </div>
    </footer>
  );
}

/* ---------------------------------------------------------- hinweis und top */

/**
 * Kleiner Hinweis, einmal je Besuch, sobald der Abschnitt ueber die Person
 * erreicht ist. Nicht beim Laden: dort ist er eine Tuer, die zugeht, bevor
 * jemand den Raum gesehen hat.
 */
function Hinweis() {
  const [offen, setOffen] = useState(false);
  const gesehen = useRef(false);

  useEffect(() => {
    try {
      if (window.sessionStorage.getItem("ashworth-hinweis")) return;
    } catch {
      // Gesperrter Speicher: der Hinweis kommt dann eben wieder.
    }
    const ziel = document.querySelector("#about");
    if (!ziel) return;
    const beobachter = new IntersectionObserver(
      (eintraege) => {
        if (!eintraege.some((e) => e.isIntersecting) || gesehen.current) return;
        gesehen.current = true;
        beobachter.disconnect();
        window.setTimeout(() => setOffen(true), 600);
      },
      { rootMargin: "-25% 0px -25% 0px" },
    );
    beobachter.observe(ziel);
    return () => beobachter.disconnect();
  }, []);

  const schliessen = () => {
    setOffen(false);
    try {
      window.sessionStorage.setItem("ashworth-hinweis", "1");
    } catch {
      // Ignorieren.
    }
  };

  return (
    <AnimatePresence>
      {offen && (
        <motion.div
          className="fixed inset-0 z-[60] flex items-center justify-center p-4"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          transition={{ duration: 0.45, ease: EASE }}
          onClick={schliessen}
          style={{ background: "rgba(31,36,28,0.18)", backdropFilter: "blur(3px)" }}
        >
          <motion.div
            role="dialog"
            aria-modal="true"
            aria-labelledby="hinweis-titel"
            className="relative w-[min(23rem,calc(100vw-2rem))] px-7 pt-7 pb-6"
            style={{ borderRadius: 16, background: PAPER }}
            initial={{ opacity: 0, y: 12, scale: 0.97 }}
            animate={{ opacity: 1, y: 0, scale: 1 }}
            exit={{ opacity: 0, y: 12, scale: 0.97 }}
            transition={{ duration: 0.56, ease: EASE }}
            onClick={(ev) => ev.stopPropagation()}
          >
            <button
              type="button"
              onClick={schliessen}
              aria-label="Close"
              className="absolute top-3 right-3 inline-flex h-9 w-9 items-center justify-center transition-colors"
              style={{ borderRadius: 6, color: QUIET }}
            >
              <X size={18} strokeWidth={1.7} aria-hidden />
            </button>
            <Marke icon={Timer}>New slots</Marke>
            <h2
              id="hinweis-titel"
              className="mt-4 pr-8 text-[1.4rem] leading-[1.15] text-balance"
              style={{ ...display, color: INK }}
            >
              Evening appointments from October
            </h2>
            <p
              className="mt-3 text-[15px] leading-relaxed"
              style={{ fontFamily: SANS, color: QUIET }}
            >
              Two evenings a week are opening up after the summer, for people who cannot get away
              during the working day.
            </p>
            <div className="mt-5">
              <Knopf art="leise" onClick={schliessen}>
                Good to know
              </Knopf>
            </div>
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

function NachOben() {
  const [da, setDa] = useState(false);
  const { scrollY } = useScroll();
  useMotionValueEvent(scrollY, "change", (y) => setDa(y > window.innerHeight * 1.5));

  return (
    <button
      type="button"
      onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
      aria-label="Back to top"
      tabIndex={da ? 0 : -1}
      className="fixed right-5 bottom-5 z-40 inline-flex h-11 w-11 items-center justify-center lg:right-8 lg:bottom-8"
      style={{
        borderRadius: 999,
        background: "rgba(243,244,241,0.92)",
        backdropFilter: "blur(10px)",
        color: INK,
        opacity: da ? 1 : 0,
        translate: da ? "0 0" : "0 0.75rem",
        pointerEvents: da ? "auto" : "none",
        transition:
          "opacity 420ms cubic-bezier(0.33,1,0.68,1), translate 420ms cubic-bezier(0.33,1,0.68,1)",
      }}
    >
      <ArrowUp size={20} strokeWidth={1.7} aria-hidden />
    </button>
  );
}

/* ------------------------------------------------------------- tintbuehne */

/**
 * Der Block hinter den ersten Abschnitten wechselt beim Scrollen die Flaeche.
 *
 * Er faengt in hellem Grau an, wird weiss, solange in der Mitte gelesen wird,
 * und geht vor seinem Ende wieder ins Graue. Damit hat die Seite einen
 * Uebergang, der nicht an einer Kante haengt: es gibt keine Stelle, an der
 * eine Farbe aufhoert und die naechste anfaengt, sondern nur eine Strecke, auf
 * der sich etwas aendert.
 *
 * Das Korn liegt nur auf dem grauen Teil und blendet mit weg. Auf Weiss ist es
 * schmutzig, auf Grau ist es Papier.
 */
function Tintbuehne({ children }: { children: ReactNode }) {
  const reduce = useReducedMotion();
  const feld = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({ target: feld, offset: ["start end", "end start"] });
  const grund = useTransform(scrollYProgress, [0, 0.22, 0.6, 0.88], [MIST, PAPER, PAPER, MIST]);
  const korn = useTransform(scrollYProgress, [0, 0.22, 0.6, 0.88], [0.5, 0, 0, 0.5]);

  return (
    <motion.div
      ref={feld}
      className="relative isolate"
      style={{ background: reduce ? PAPER : grund }}
    >
      <motion.span
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10 mix-blend-multiply"
        style={{
          backgroundImage: GRAIN,
          backgroundSize: "160px 160px",
          opacity: reduce ? 0 : korn,
        }}
      />
      {children}
    </motion.div>
  );
}

/* ------------------------------------------------------------------ seite */

export function AshworthCareerPage() {
  return (
    /* Der weisse Rand unter der Fusszeile steht hier als Innenabstand und
       nicht als Aussenabstand der Fusszeile selbst: ein Aussenabstand faellt
       durch diesen Knoten hindurch und liegt danach ausserhalb der Vorlage,
       wo ihn der naechste Hintergrund verschluckt. */
    <div className="pb-4 lg:pb-6" style={{ background: PAPER, color: INK, fontFamily: SANS }}>
      <Kopf />
      <main>
        <Hero />
        {/* Der Block ueber dem Hero: er schiebt sich mit eigener Flaeche und
            abgerundeter Oberkante darueber, deshalb bleibt vom Hero eine
            Kante stehen statt eines harten Schnitts. */}
        <div
          className="relative z-[1]"
          style={{ background: PAPER, borderRadius: "20px 20px 0 0" }}
        >
          <Tintbuehne>
            <Saetze />
            <Methode />
            <UeberMich />
            <AndereFrage />
            <Themen />
          </Tintbuehne>
          <Zitatband />
          <Wendung />
          <Stimmen />
          <Kontakt />
        </div>
      </main>
      <Fusszeile />
      <Hinweis />
      <NachOben />
    </div>
  );
}

export default AshworthCareerPage;
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