/* base/layers.css */
/* ==========================================================================
   Layer order. This file MUST load before every other stylesheet.
   Declaring the order up front means a host platform's CSS (WordPress core,
   a plugin, a builder) can never outrank ours by specificity accident.
   ========================================================================== */

@layer tokens, base, components, sections, utilities;


/* tokens/tokens.css */
/* ==========================================================================
   Design tokens. One flat semantic tier.
   Source: Figma 7MfxVZzdqWSEsWA6V8RGJd

   Values are taken from the Figma variables where variables exist, and from
   measured usage where the design uses raw values. Both are marked.
   Mapping back to Figma names: see CONVENTIONS.md section 4.
   ========================================================================== */

@layer tokens {
  :root {
    /* ---------------------------------------------------------------------
       Colour

       The signature brand colour is a GRADIENT, not a flat value. #4dd0ff and
       #9745ff appear 34 times each on Startseite and are the most-used colours
       in the design, yet neither is a Figma variable. The variable named
       "purple" (#9d72ce) is used exactly once on the whole page.
       Reported to the designer, reproduced as drawn.
       --------------------------------------------------------------------- */

    /* Surfaces */
    --color-surface: #ffffff;            /* figma: white */
    --color-surface-dark: #17203f;       /* figma: dark-navy */
    --color-surface-muted: #f2f2f2;      /* figma: light-gray */
    --color-surface-soft: #f6d3b1;       /* figma: peach. Used as the hero glow
                                            (see --gradient-hero), not as a flat fill. */

    /* Translucent surfaces. The card system is glassmorphic: white at low
       alpha over light backgrounds, navy at high alpha over imagery. */
    --color-glass-light: rgb(255 255 255 / 0.2);
    --color-glass-lighter: rgb(255 255 255 / 0.1);
    --color-glass-strong: rgb(255 255 255 / 0.9);
    --color-glass-dark: rgb(35 35 72 / 0.9);      /* #232348 at 90% */
    --color-glass-muted: rgb(242 242 242 / 0.2);

    /* Text */
    --color-text: #000000;               /* figma: black */
    --color-heading: #17203f;            /* figma: dark-navy, 21 uses */
    --color-heading-alt: #1b1d36;        /* raw, 7 uses. Near-duplicate of the
                                            above. Design inconsistency, kept
                                            so reproduction stays exact. */
    --color-on-dark: #ffffff;

    /* Accents */
    --color-accent: #9d72ce;             /* figma: purple. 1 use */
    --color-accent-deep: #323672;        /* figma: indigo. 4 uses */

    /* The real brand identity */
    --color-brand-from: #4dd0ff;         /* raw hex, 34 uses */
    --color-brand-to: #9745ff;           /* raw hex, 34 uses */
    --gradient-brand: linear-gradient(
      90deg,
      var(--color-brand-from),
      var(--color-brand-to)
    );

    /* Hero backdrop. Two layers, both measured off the full-resolution render
       because the SVG export of Rectangle 10 came back with its fill stripped.

       Layer 1, the warm glow: peach at 0.70 alpha, centred on the top-right
       corner, falling off linearly to zero by x≈240 and y≈550. Fitted by
       solving for peach's alpha over the base at a grid of background pixels;
       the residuals were within ±0.03 across the whole strip.

       This is where `peach` actually lives. It reads as an unused variable in
       the design-context scan only because it is a gradient stop inside a
       fill, and the SVG export drops those.

       Layer 2, the base: sampled down the left edge, where nothing overlaps.
       It darkens toward 23% and lifts again at the bottom, so three stops. */
    --gradient-hero:
      radial-gradient(
        ellipse 85% 72% at 100% 6%,
        rgb(246 211 177 / 0.7) 0%,
        rgb(246 211 177 / 0) 100%
      ),
      linear-gradient(
        180deg,
        #252541 0%,
        #1e1f3a 23%,
        #302d60 100%
      );

    /* Derived states. color-mix() replaces a primitive scale. */
    --color-brand-hover-from: color-mix(in oklab, var(--color-brand-from) 88%, #000);
    --color-brand-hover-to: color-mix(in oklab, var(--color-brand-to) 88%, #000);
    --color-border: color-mix(in oklab, var(--color-heading) 14%, transparent);
    --color-muted: color-mix(in oklab, var(--color-heading) 68%, var(--color-surface));

    /* ---------------------------------------------------------------------
       Typography
       Poppins. Weights in actual use: 200 italic, 300, 400, 500, 900.
       The Stilesheet only documents 300/400/500 — 200 italic carries the
       testimonial quotes and 900 the inline arrow glyphs.
       Desktop values are the Figma 1440 anchors.
       --------------------------------------------------------------------- */

    --font-sans: "Poppins", ui-sans-serif, system-ui, -apple-system,
                 "Segoe UI", Roboto, sans-serif;

    --weight-extralight: 200;
    --weight-light: 300;
    --weight-regular: 400;
    --weight-medium: 500;
    --weight-black: 900;

    /* Fluid between 320px and 1440px. Every clamp resolves to EXACTLY the
       Figma value at 1440, so desktop is pixel-identical to the design. */
    --text-4xl: clamp(2.5rem,   1.929rem + 2.857vw, 4.5rem);    /*  40 -> 72 */
    --text-3xl: clamp(2rem,     1.571rem + 2.143vw, 3.5rem);    /*  32 -> 56 */
    --text-xl:  clamp(1.5rem,   1.321rem + 0.893vw, 2.125rem);  /*  24 -> 34 */
    --text-lg:  clamp(1.25rem,  1.143rem + 0.536vw, 1.625rem);  /*  20 -> 26 */
    --text-md:  clamp(1.125rem, 1.054rem + 0.357vw, 1.375rem);  /*  18 -> 22, unused on Startseite */
    --text-sm:  1.0625rem;                                      /*  17 fixed, 91 uses */
    --text-xs:  0.625rem;                                       /*  10 fixed, unused on Startseite */
    /* figma: font-size/2xl. Confirmed from the Geld-Zurueck-Garantie codegen,
       where the FAQ chevrons use it. Earlier guessed at 44 when no page had
       yet used it; the file's real value is 50. */
    --text-2xl: clamp(1.75rem, 1.357rem + 1.9643vw, 3.125rem);  /*  28 -> 50 */

    /* Line heights.
       Figma expresses these in px. Converted to unitless ratios so they scale
       with the fluid font sizes, which keeps 1440 pixel-exact and degrades
       proportionally below it. Faithful, including the odd ones. */
    --leading-h1: 1.0556;      /* figma 76 / 72 */
    --leading-h2: 1;           /* figma 56 / 56 */
    --leading-h3: 0.9231;      /* figma 24 / 26, deliberately below 1 */
    --leading-h4: 2.5455;      /* figma 56 / 22, an unusually tall line box */
    --leading-lead: 1.0588;    /* figma 36 / 34 */
    --leading-body: 1.1176;    /* figma 19 / 17 */
    --leading-footnote: 1.9;   /* figma 19 / 10 */
    --leading-button: 1;       /* figma reports 100, which is wrong: verified
                                  against the real CTA pill in why-us */

    --tracking-tight: 0;       /* Figma letterSpacing is 0 on every style */
    --tracking-normal: 0;

    /* ---------------------------------------------------------------------
       Layout
       Measured from the frames: content starts at x=143 in a 1440 frame,
       three columns of 365px separated by 34px gaps.
       --------------------------------------------------------------------- */

    --wrapper: 72.75rem;                              /* 1164px measured */
    --gutter: clamp(1.25rem, 5vw, 2.5rem);
    --gap: clamp(1.25rem, 2.4vw, 2.125rem);           /* 34px at 1440 */

    /* ---------------------------------------------------------------------
       Spacing
       Figma defines exactly one spacing variable, spacing/xl = 68.
       The rest are fallback defaults; each section measures its own padding
       from its frame and overrides them.
       --------------------------------------------------------------------- */

    --space-xl: 4.25rem;                                     /* figma: spacing/xl, 68px */
    --space-section: clamp(4rem, 2.857rem + 5.714vw, 8rem);  /* fallback */
    --space-block: clamp(2rem, 1.43rem + 2.86vw, 4rem);      /* fallback */
    --space-element: clamp(1rem, 0.86rem + 0.71vw, 1.5rem);  /* fallback */
    --space-tight: 0.5rem;

    /* ---------------------------------------------------------------------
       Radius
       A complete scale exists in Figma. Every step is in real use.
       --------------------------------------------------------------------- */

    --radius-xs: 5px;      /* figma: radius/xs,  3 uses */
    --radius-sm: 10px;     /* figma: radius/sm,  8 uses */
    --radius-md: 15px;     /* figma: radius/md, 20 uses, the default */
    --radius-lg: 20px;     /* figma: radius/lg, 11 uses */
    --radius-xl: 30px;     /* figma: radius/xl,  9 uses */
    --radius-2xl: 55px;    /* figma: radius/2xl, 4 uses, pill buttons */

    /* ---------------------------------------------------------------------
       Elevation and motion
       One shadow in the entire design, used 24 times.
       --------------------------------------------------------------------- */

    --shadow-card: 0 4px 4px 0 rgb(0 0 0 / 0.25);

    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --duration-fast: 150ms;
    --duration-base: 300ms;
  }

  /* ---------------------------------------------------------------------
     Inverted context. Applied via data-variant="inverted" on a section root.
     Only tokens are remapped; no section CSS changes.
     --------------------------------------------------------------------- */
  [data-variant="inverted"] {
    --color-surface: #17203f;
    --color-text: #ffffff;
    --color-heading: #ffffff;
    --color-heading-alt: #ffffff;
    --color-border: color-mix(in oklab, #ffffff 18%, transparent);
    --color-muted: color-mix(in oklab, #ffffff 74%, #17203f);
  }

  @media (prefers-reduced-motion: reduce) {
    :root {
      --duration-fast: 0ms;
      --duration-base: 0ms;
    }
  }
}

/* ==========================================================================
   Above 1440: freeze and centre.

   Revised 2026-07-31. Nothing above the drawn width grows: the composition
   locks at the size it was designed at and centres, while section backgrounds
   keep running to the viewport edge. The extra width therefore reads as the
   design's own backdrop rather than as empty margin, and type never inflates
   past the size the designer chose.

   Scaling to 1920 was tried first and rejected on sight: 96px headings and a
   1.33x hero render did not look like the design, they looked like the design
   zoomed.
   ========================================================================== */

@layer tokens {
  :root {
    /* The drawn design width, and the offset that centres it once the
       viewport is wider. 0 at and below 1440. */
    --stage-w: min(100vw, 1440px);
    --stage-x: max(0px, (100vw - 1440px) / 2);
  }
}


/* base/fonts.css */
/* ==========================================================================
   Poppins, self-hosted.

   No Google Fonts CDN: serving fonts from Google on a German site is a GDPR
   liability, not a style preference.

   Two subsets per weight. They are COMPLEMENTARY, not nested: Google's `latin`
   subset is U+0000-00FF, which is where the German set lives (ä ö ü ß at
   U+00E4/00F6/00FC/00DF). `latin-ext` starts at U+0100 and carries none of
   them. Shipping only latin-ext would break every umlaut on the site.
   ========================================================================== */

@layer base {

  /* 300 normal — documented in the Stilesheet */

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-300-latin.woff2") format("woff2");
    font-weight: 300;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
                   U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-300-latin-ext.woff2") format("woff2");
    font-weight: 300;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                   U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }

  /* 400 normal — documented in the Stilesheet */

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-400-latin.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
                   U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-400-latin-ext.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                   U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }

  /* 500 normal — documented in the Stilesheet */

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-500-latin.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
                   U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-500-latin-ext.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                   U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }

  /* 200 italic — NOT in the Stilesheet. Carries every testimonial quote. */

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-200-italic-latin.woff2") format("woff2");
    font-weight: 200;
    font-style: italic;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
                   U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-200-italic-latin-ext.woff2") format("woff2");
    font-weight: 200;
    font-style: italic;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                   U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }

  /* 900 normal — NOT in the Stilesheet. Inline arrow glyphs only. */

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-900-latin.woff2") format("woff2");
    font-weight: 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
                   U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

  @font-face {
    font-family: "Poppins";
    src: url("fonts/poppins-900-latin-ext.woff2") format("woff2");
    font-weight: 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                   U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                   U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }

}


/* base/reset.css */
/* ==========================================================================
   Reset. Minimal and opinion-free: it removes browser defaults that fight the
   design and nothing else. Anything that changes how the page LOOKS belongs in
   typography.css or a section, not here.
   ========================================================================== */

@layer base {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  html {
    scroll-behavior: smooth;
    /* Must be on the root element, not just body. Several sections bleed past
       the right edge by design (the hero figure reaches x=1562, cards run off
       the frame). Clipping only on body still lets the root grow, which raises
       a horizontal scrollbar, which narrows the viewport by ~15px, which is
       enough to wrap the 7-logo client row onto a second line and shift every
       section below it by 116px. One missing declaration, a whole-page layout
       shift.

       `clip` rather than `hidden`: hidden would make this a scroll container
       and break `position: sticky` in descendants. */
    overflow-x: clip;
  }

  @media (prefers-reduced-motion: reduce) {
    html {
      scroll-behavior: auto;
    }
    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }

  body {
    margin: 0;
    min-height: 100svh;
    /* Sections are full-bleed and several contain shapes that overhang the
       viewport horizontally. Clipping here is what keeps the 320px
       no-horizontal-scroll rule true without every section defending itself. */
    overflow-x: clip;
  }

  img,
  picture,
  svg,
  video,
  canvas {
    display: block;
    max-width: 100%;
  }

  img,
  video {
    height: auto;
  }

  input,
  button,
  textarea,
  select {
    font: inherit;
    color: inherit;
  }

  button {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
  }

  ul[class],
  ol[class] {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  /* Sections own their vertical rhythm as padding. A stray margin here would
     collapse against a neighbour and break order independence. */
  section {
    margin: 0;
  }
}

@layer utilities {
  /* The measured content column: 1164px wide, centred, from the Figma frames. */
  .wrapper {
    width: 100%;
    max-width: var(--wrapper);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }
}


/* base/typography.css */
/* ==========================================================================
   Typography. The nine Figma text styles, mapped to elements and utilities.
   Headings are styled at element level so markup stays clean: sections do not
   put a class on every h2. Utilities exist only for the paragraph variants,
   which genuinely vary within a section.

   Figma style      -> here
   H1               -> h1
   H2               -> h2
   H3               -> h3
   H4               -> h4
   <p>headline      -> .text-lead     (Focustext, 34px)
   <p>medium        -> p              (default body, 17px regular)
   <p>light         -> .text-light    (17px, weight 300)
   <p>button        -> .text-button   (17px, weight 500)
   <p>fussnote      -> .text-footnote (10px)

   FIDELITY FIRST. Values reproduce Figma exactly, including line heights that
   look wrong (H3 at 0.92, H4 at 2.55, body at 1.12). Do not "improve" them
   here. If a value should change, it changes in Figma first.
   ========================================================================== */

@layer base {

  /* @font-face declarations live in base/fonts.css. */

  /* ---------------------------------------------------------------------- */

  html {
    font-family: var(--font-sans);
    font-size: 100%;              /* never override the user's root size */
    -webkit-text-size-adjust: 100%;
  }

  body {
    font-size: var(--text-sm);
    font-weight: var(--weight-regular);
    line-height: var(--leading-body);
    color: var(--color-text);
    background-color: var(--color-surface);
  }

  /* German compounds overflow narrow viewports. Applied only below the
     desktop range so 1440 rendering stays identical to Figma. */
  @media (width < 1024px) {
    body {
      hyphens: auto;
      -webkit-hyphens: auto;
      overflow-wrap: break-word;
    }
    :lang(de) {
      hyphenate-limit-chars: 8 4 4;
    }
  }

  /* No text-wrap: balance or pretty anywhere.
     The Figma copy contains U+2028 line separators, meaning the designer set
     line breaks by hand. Those are reproduced as <br class="u-br-lg"> and
     browser rebalancing would fight them. */

  h1, h2, h3, h4 {
    font-weight: var(--weight-regular);
    color: var(--color-heading);
    letter-spacing: var(--tracking-tight);
    margin-block: 0;
  }

  h1 {
    font-size: var(--text-4xl);
    line-height: var(--leading-h1);
  }

  h2 {
    font-size: var(--text-3xl);
    line-height: var(--leading-h2);
  }

  h3 {
    font-size: var(--text-lg);
    line-height: var(--leading-h3);
  }

  h4 {
    font-size: var(--text-md);
    line-height: var(--leading-h4);
  }

  p {
    margin-block: 0;
    /* No max-width. Figma sets explicit text-box widths per node and those
       are reproduced in the section CSS. */
  }

  /* TBD: the design file does not define a distinct link colour. Measure the
     actual link styling during extraction and replace this. Inheriting for
     now so nothing invented leaks into the build. */
  a {
    color: inherit;
    text-underline-offset: 0.2em;
    transition: color var(--duration-fast) var(--ease-out);
  }

  :focus-visible {
    outline: 2px solid var(--color-accent-deep);
    outline-offset: 3px;
  }

  ul, ol {
    margin-block: 0;
    padding-inline-start: 1.25em;
  }

  strong, b {
    font-weight: var(--weight-medium);
  }
}

@layer utilities {

  /* Figma "<p>headline" / Focustext, 34 / 36 */
  .text-lead {
    font-size: var(--text-xl);
    font-weight: var(--weight-regular);
    line-height: var(--leading-lead);
  }

  /* Figma "<p>light", 17 / 19 weight 300 */
  .text-light {
    font-weight: var(--weight-light);
  }

  /* Figma "<p>button", 17px weight 500 */
  .text-button {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-button);
    letter-spacing: var(--tracking-normal);
  }

  /* Figma "<p>fussnote", 10 / 19 weight 300 */
  .text-footnote {
    font-size: var(--text-xs);
    font-weight: var(--weight-light);
    line-height: var(--leading-footnote);
  }

  /* The signature brand treatment: gradient-filled text. Used 34 times on
     Startseite, on partial headline phrases, stat numbers, and role labels.

     Wrap only the coloured RUN, never the whole heading, because the design
     gradients a phrase inside a longer line:
       <h2>Unser perfektionierter Ablauf für maximale
           <span class="u-gradient-text">Effizienz &amp; Leads</span></h2>

     background-clip: text has no accessible-name effect, and the text stays
     selectable and searchable. Falls back to the solid end colour where
     background-clip is unsupported. */
  .u-gradient-text {
    background-image: var(--gradient-brand);
    color: var(--color-brand-to);
  }

  @supports (background-clip: text) or (-webkit-background-clip: text) {
    .u-gradient-text {
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
    }
  }

  /* Testimonial quote body: Poppins 200 italic. */
  .text-quote {
    font-weight: var(--weight-extralight);
    font-style: italic;
  }

  /* Reproduces a designer's manual line break (U+2028 in the Figma copy).
     Visible at the desktop widths the design was drawn for, collapsed below,
     where the original break would land in the wrong place. */
  .u-br-lg {
    display: none;
  }

  @media (width >= 1024px) {
    .u-br-lg {
      display: inline;
    }
  }

  .visually-hidden:not(:focus):not(:active) {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}


/* sections/site-header/section.css */
/* ==========================================================================
   Section: site-header (Kopfbereich)
   Figma band y 0..120 in a 1440 frame, content bleeding to y=137.

   This section OVERLAYS the hero. It is absolutely positioned at the document
   top, over the hero's gradient, and carries no background of its own because
   the design draws none. The hero starts its own content at y=214 to leave
   room for it.

   Measured anchors, all reproduced at 1440:
     row top             y = 84   (top edge of the seal, the tallest element)
     content insets      x = 143 left, 69 right (bars end at 1371)
     trust icons         x = 143 (36x36), 408 (105x33), 690 (53x53)
     trust texts         x = 191, 524, 743 — 17/19 Poppins Light, white
     contact link        x = 1065, y = 97, box 211 wide, centred,
                         17 Poppins Medium, line box 24 ("normal")
     hamburger bars      x = 1315, y = 88 / 104 / 119, 56 x 5, radius 5

   Two sub-pixel deviations, taken deliberately so the row needs no per-item
   positioning and survives a repeater with a different number of items:
     - the three icons and the three text blocks are centred on one 53px row
       instead of being placed individually. Worst case 1.5px, on the first
       line of the trust texts.
     - the hamburger bars are distributed in a 36px box (gaps 10.5/10.5)
       rather than Figma's 11/10, so the middle bar sits 0.5px high.
   ========================================================================== */

@layer sections {
  .site-header {
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    /* Above the hero, which paints a background and isolates itself. */
    z-index: 10;
    color: var(--color-on-dark);
    /* The row starts at y=84. No bottom padding: the section is out of flow,
       so it owns no rhythm — but it also never imposes any. */
    padding-block: 84px 0;
    /* 143/1440 = 9.931vw, 69/1440 = 4.792vw. Exact at 1440. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 9.931vw, 143px))
      calc(var(--stage-x) + clamp(1.25rem, 4.792vw, 69px));
  }

  .site-header__inner {
    display: flex;
    align-items: flex-start;
    /* Contact box ends at 1276, bars start at 1315. */
    column-gap: 39px;
  }

  /* Six columns, one per element, each track the exact distance to the next
     element's left edge: 143→191→408→524→690→743. Reproducing the measured
     x positions this way avoids inventing a uniform icon-to-text gap, which
     the design does not have (it is 12, 11 and 0). */
  .site-header__trust {
    display: grid;
    grid-template-columns: 48px 217px 116px 166px 53px auto;
    align-items: center;
  }

  /* Subgrid keeps the item as a real <li> (list semantics survive) while its
     icon and text land on the parent's column edges. */
  .site-header__trust-item {
    display: grid;
    grid-column: span 2;
    grid-template-columns: subgrid;
    align-items: center;
  }

  @supports not (grid-template-columns: subgrid) {
    /* Fallback: the measured 12/11/0 gaps become one uniform 12px. */
    .site-header__trust-item {
      display: flex;
      gap: 12px;
    }
  }

  /* Sized by the width/height attributes, which are the Figma box sizes. */
  .site-header__trust-icon {
    flex: none;
  }

  .site-header__trust-text {
    font-size: var(--text-sm);
    font-weight: var(--weight-light);
    line-height: var(--leading-body);
    color: var(--color-on-dark);
  }

  .site-header__contact {
    /* Pushed to the right edge of the row; the 39px column gap then places it
       exactly 1276→1315 from the bars. */
    margin-inline-start: auto;
    margin-block-start: 13px;      /* row top 84 → text box top 97 */
    inline-size: 211px;            /* Figma text box, centred inside */
    flex: none;
    text-align: center;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    /* Figma: lineHeight "normal", node height 24 on a 17px font. This is NOT
       the <p>button style (which reports 100) — measured on this node. */
    line-height: calc(24 / 17);
    color: var(--color-on-dark);
    text-decoration: none;
  }

  /* Not in the design: links have no hover state anywhere in the file. An
     underline is the least invented thing available. */
  .site-header__contact:hover,
  .site-header__contact:focus-visible {
    text-decoration: underline;
  }

  .site-header__toggle {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: none;
    inline-size: 56px;
    block-size: 36px;              /* 88 → 124, the three bars */
    margin-block-start: 4px;       /* row top 84 → first bar 88 */
    /* Stays above the drawer scrim so it can double as the close control. */
    position: relative;
    z-index: 2;
    /* Bar geometry in the open state, read by the bars below. */
    --bar-shift: 0px;
    --bar-turn: 0deg;
    --bar-mid: 1;
  }

  .site-header__toggle[aria-expanded="true"] {
    --bar-shift: 15.5px;
    --bar-turn: 45deg;
    --bar-mid: 0;
  }

  .site-header__bar {
    block-size: 5px;
    border-radius: var(--radius-xs);
    background-color: var(--color-on-dark);
    transition:
      translate var(--duration-base) var(--ease-out),
      rotate var(--duration-base) var(--ease-out),
      scale var(--duration-base) var(--ease-out);
  }

  /* Closed: three bars. Open: an X. The middle bar collapses with scale, not
     opacity, because a section stylesheet never contains opacity:0. */
  .site-header__bar:first-child {
    translate: 0 var(--bar-shift);
    rotate: var(--bar-turn);
  }

  .site-header__bar:nth-child(2) {
    scale: var(--bar-mid) 1;
  }

  .site-header__bar:last-child {
    translate: 0 calc(-1 * var(--bar-shift));
    rotate: calc(-1 * var(--bar-turn));
  }

  /* Indigo on this gradient is close to invisible, so the focus ring is white
     here. Structural accessibility, not a design change. */
  .site-header__contact:focus-visible,
  .site-header__toggle:focus-visible,
  .site-header__nav-link:focus-visible {
    outline-color: var(--color-on-dark);
  }

  /* ------------------------------------------------------------------
     Drawer. INVENTED — the Figma file contains a hamburger at every width
     and no open state anywhere. Kept to tokens the design already uses:
     dark-navy surface, white text, the shared duration and easing.
     ------------------------------------------------------------------ */

  .site-header__drawer {
    position: fixed;
    inset: 0;
    z-index: 1;
    /* Closed state without opacity:0 — visibility also takes the panel out of
       the tab order and the accessibility tree while it is shut.
       Opening flips visibility immediately (0s, no delay) so the first link is
       focusable in the same tick; closing holds it until the slide is done. */
    visibility: hidden;
    background-color: transparent;
    --drawer-x: 100%;
    transition:
      visibility 0s linear var(--duration-base),
      background-color var(--duration-base) var(--ease-out);
  }

  .site-header__drawer[data-open="true"] {
    visibility: visible;
    background-color: rgb(0 0 0 / 0.5);
    --drawer-x: 0%;
    transition:
      visibility 0s linear 0s,
      background-color var(--duration-base) var(--ease-out);
  }

  .site-header__panel {
    position: absolute;
    inset-block: 0;
    inset-inline-end: 0;
    inline-size: min(420px, 100%);
    padding: 144px 40px 40px;      /* clears the hamburger at 1440 */
    overflow-y: auto;
    background-color: var(--color-surface-dark);
    translate: var(--drawer-x) 0;
    transition: translate var(--duration-base) var(--ease-out);
  }

  .site-header__nav-list {
    display: grid;
    gap: 24px;
  }

  .site-header__nav-link {
    font-size: var(--text-lg);
    line-height: var(--leading-body);
    color: var(--color-on-dark);
    text-decoration: none;
  }

  .site-header__nav-link:hover,
  .site-header__nav-link:focus-visible {
    text-decoration: underline;
  }

  /* ------------------------------------------------------------------
     Below 1440 there is no design. Decisions taken here:

     1360px is where the drawn row stops fitting: 143+69 of inset plus the
     796px trust row, the 211px contact box, the 56px bars and two 39px gaps
     need ~1141px of content width. Under that the header becomes two rows —
     contact and menu button on top, trust marks underneath — which is the
     only arrangement that still clears the hero's own top padding (214px
     down to 160px below 1024).

     The trust row never wraps: it stays a single line and scrolls
     horizontally instead. Wrapping it to two lines makes the header ~186px
     tall, which would collide with the hero heading on small screens.
     ------------------------------------------------------------------ */

  @media (width < 1360px) {
    .site-header {
      padding-block: 24px 0;
    }

    .site-header__inner {
      flex-wrap: wrap;
      align-items: center;
      row-gap: 12px;
      column-gap: 20px;
    }

    .site-header__trust {
      order: 1;                    /* onto its own line, under the top row */
      flex: 1 0 100%;
      display: flex;
      flex-wrap: nowrap;
      gap: 32px;
      overflow-x: auto;
      overscroll-behavior-inline: contain;
    }

    .site-header__trust-item {
      display: flex;
      flex: none;
      align-items: center;
      gap: 12px;
    }

    .site-header__contact {
      inline-size: auto;
      margin-block-start: 0;
    }

    .site-header__toggle {
      margin-block-start: 0;
    }

    .site-header__panel {
      padding-block-start: 96px;
    }
  }
}


/* sections/hero/section.css */
/* ==========================================================================
   Section: hero
   Figma band y 0..767 in a 1440 frame.

   Measured anchors, all reproduced exactly at 1440:
     content inset      x = 146
     h1                 y = 214, w = 532, 72/76, white, "mehr Umsatz" gradient
     stat values        y = 493, 34/36, gradient
     stat labels        y = 545, 17/19, white
     stat left edges    x = 147, 264, 400, 534
     CTA pill           x = 146, y = 633, 238 x 37, radius 55, gradient @ 70%
     figure             x = 690, y = -82, 872 x 889
   ========================================================================== */

@layer sections {
  .hero {
    position: relative;
    isolation: isolate;
    background-image: var(--gradient-hero);
    color: var(--color-on-dark);
    /* The header overlays this section, so the top padding has to clear it.
       84px trust bar + its own height, then the h1 lands at y=214. */
    /* Derived, not guessed: 214 top puts the h1 at its Figma y. The bottom
       value is whatever makes the section total exactly 767. */
    padding-block: 214px 97px;
    /* 146/1440 = 10.139vw. Resolves to exactly 146px at 1440. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 10.139vw, 146px))
      calc(var(--stage-x) + clamp(1.25rem, 10.139vw, 146px));
    min-height: 767px;
  }

  .hero__inner {
    position: relative;
    z-index: 1;
    max-width: 532px;
  }

  .hero__title {
    /* Figma H1: 72/76. The line break after "für" is the designer's, the
       break before it comes from the 532px measure. */
    font-size: var(--text-4xl);
    line-height: var(--leading-h1);
    color: var(--color-on-dark);
    margin: 0;
  }

  .hero__stats {
    display: grid;
    /* Left edges at 147, 264, 400, 534 give columns of 117, 136, 134, auto.
       Not a regular grid; reproduced as measured. */
    grid-template-columns: 117px 136px 134px auto;
    /* h1 occupies 214..442 (3 lines x 76). Values start at 493, so 51. */
    margin-block: 51px 0;
    /* The design's own 1px inconsistency: the h1 sits at x=146, the stat
       column at x=147. Reproduced rather than tidied. */
    margin-inline-start: 1px;
  }

  .hero__stat {
    display: flex;
    flex-direction: column;
    /* value at 493, label at 545: a 52px step, of which 36 is the value's
       own line box. */
    gap: 16px;
  }

  .hero__stat-value {
    font-size: var(--text-xl);
    line-height: var(--leading-lead);
  }

  .hero__stat-label {
    font-size: var(--text-sm);
    line-height: var(--leading-body);
    color: var(--color-on-dark);
  }

  .hero__cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 238px;
    block-size: 37px;
    margin-block-start: 50px;   /* stats occupy 493..583, pill starts 633 */
    border-radius: var(--radius-2xl);
    color: var(--color-on-dark);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-button);
    text-decoration: none;
    isolation: isolate;
  }

  /* The gradient sits at 70% opacity in Figma, the label does not. Applying
     opacity to the element itself would fade the text too, so the fill lives
     on a pseudo-element. */
  .hero__cta::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    background-image: var(--gradient-brand);
    opacity: 0.7;
    transition: opacity var(--duration-fast) var(--ease-out);
  }

  .hero__cta:hover::before,
  .hero__cta:focus-visible::before {
    opacity: 1;
  }

  .hero__figure {
    position: absolute;
    /* 690/1440, -82/767, 872/1440 — kept proportional so the composition
       holds as the hero narrows. */
    inset-block-start: -10.69%;
    /* Freezes at the drawn 690 x 4/3, then rides the centred stage. Raw %
       is what made this grow to 1550px at 2560 and spill 731px out of the
       hero. */
    inset-inline-start: calc(var(--stage-x) + 690px);
    inline-size: 872.0003px;
    aspect-ratio: 872 / 889;
    overflow: hidden;
    pointer-events: none;
    z-index: 0;
  }

  .hero__figure img {
    position: absolute;
    /* The source is larger than its frame in Figma and offset left. */
    inset-block-start: 0;
    inset-inline-start: -27.18%;
    inline-size: 127.18%;
    block-size: 124.69%;
    max-inline-size: none;
    object-fit: cover;
  }

  /* ------------------------------------------------------------------
     Below the width the design was drawn at, no comp exists. Decisions
     made here, logged in CONVENTIONS section 5:
       - the stat grid drops to two columns, then one
       - the figure stops being an overlay and becomes a block, because
         absolute positioning over the text is unreadable when narrow
     ------------------------------------------------------------------ */

  @media (width < 1024px) {
    .hero {
      padding-block: 160px 64px;
      min-height: 0;
    }

    .hero__inner {
      max-width: none;
    }

    .hero__figure {
      position: static;
      inline-size: 100%;
      margin-block-start: 48px;
    }

    .hero__stats {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 32px;
    }

    .hero__cta {
      margin-block-start: 48px;
    }
  }

  @media (width < 480px) {
    .hero__stats {
      grid-template-columns: minmax(0, 1fr);
    }

    .hero__cta {
      inline-size: 100%;
      max-inline-size: 238px;
    }
  }
}


/* sections/client-logos/section.css */
/* ==========================================================================
   Section: client-logos
   Figma band y 767..1000 in a 1440 frame, 233px tall.

   Measured anchors, all reproduced exactly at 1440. Positions verified against
   .figma/client-logos.png by pixel projection, not read off the JSON alone:

     band                 y 767 .. 1000            -> padding-block 52 / 41
     intro line box       x 146, y 819, 17 / 19    -> ink measured x 146..708,
                                                     baseline at y 834
     intro colour         #1b1d36 (--color-heading-alt)
     trailing arrow       Poppins 900, gradient    -> renders flat #9546ff,
                                                     see the note on the span
     logo row "Frame 1"   x 123, y 875, 1193 x 84  -> ink measured y 888..944
     logo boxes           112 x 84, 68px apart     -> 68 is figma spacing/xl
     logo box centres     179, 359, 539, 719, 899, 1079, 1259

   Two things in the source are reproduced rather than corrected:

   1. Two different left edges. The copy starts at x=146 (the hero's inset),
      the logo row at x=123. Reproduced as a section inset of 123 plus a start
      offset of 23 on the copy.
   2. The caritas box is 113px wide where the other six are 112, which pushes
      the last three boxes right by 1px (Figma centres measured at 719.5, 900,
      1080, 1260 against a uniform row's 719, 899, 1079, 1259). That reads as a
      nudge artefact rather than intent, and the ink inside the seven source
      PNGs is already off-centre by up to 1.5px, so the row is built from seven
      uniform 112px boxes. Reported, not encoded.
   ========================================================================== */

@layer sections {
  .client-logos {
    /* No background on purpose.

       This is the page's own white, so painting it here changes nothing
       visually — but it DOES occlude the previous section's decorative
       overhang. Each of these sections is isolated, so it paints atomically
       over whatever came before it, and a 3D shape that is drawn bleeding
       across the boundary gets a hard horizontal cut instead.

       Confirmed against the Figma render: the hero's disc overhangs 40px onto
       the white below it, and the process ring overhangs 103px. Both were
       being sliced off. See CONVENTIONS section 3. */
    /* Band 767..1000. Copy line box opens at 819, logo row closes at 959. */
    padding-block: 52px 41px;
    /* 123/1440 = 8.542vw. Resolves to exactly 123px at 1440. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 8.542vw, 123px))
      calc(var(--stage-x) + clamp(1.25rem, 8.542vw, 123px));
  }

  .client-logos__intro {
    /* The one line of copy doubles as the section heading, so the element-level
       h2 (56/56) is dropped back to the drawn "<p>medium", 17/19 regular.
       No max-inline-size: the Figma text box is 568px but the line inside it
       measures 562px, so the box never wraps. Capping it would buy nothing at
       1440 and would risk a wrap if the fallback arrow glyph is wider than
       Poppins'. */
    margin: 0;
    /* Figma puts the copy 23px inside the logo row's left edge, 146 against
       123. Kept proportional so the indent shrinks with the section inset
       instead of eating the measure on a phone. 23/1440 = 1.597vw. */
    margin-inline-start: clamp(0px, 1.597vw, 23px);
    font-size: var(--text-sm);
    font-weight: var(--weight-regular);
    line-height: var(--leading-body);
    color: var(--color-heading-alt);
  }

  .client-logos__arrow {
    font-weight: var(--weight-black);
    /* Figma paints ONE gradient across the whole 568px text node, so this glyph
       sits in its last 3% and renders flat purple (sampled #9546ff in the
       reference, i.e. --color-brand-to). .u-gradient-text scopes the paint to
       the span's own box, which across a 17px glyph would run cyan to purple
       and read visibly wrong. Stretching the paint box back to the text measure
       and anchoring it right reproduces what Figma drew, while keeping the
       shared utility as the single source of the gradient itself.
       .u-gradient-text lives in @layer utilities and therefore still wins on
       background-image, background-clip and color: only the paint box is
       overridden here. */
    background-size: 568px 100%;
    background-position: 100% 50%;
    background-repeat: no-repeat;
  }

  .client-logos__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Figma "Frame 1": seven boxes, 68px apart.

       Sizing this from vw is a trap. `vw` resolves against the viewport
       INCLUDING the scrollbar, but the row only gets the content width. On a
       1440 window with a classic 15px scrollbar the boxes and gaps compute to
       1192 inside a 1179 track, the seventh logo wraps, the section grows from
       233 to 349, and every section below it shifts down 116px.

       So: fixed boxes, and let `space-between` derive the gap from whatever
       width actually exists. At the drawn 1440 that yields 68.3px, matching
       spacing/xl; narrower, the gaps close before anything wraps. */
    justify-content: space-between;
    column-gap: 1.5rem;
    row-gap: 2rem;
    /* Copy line box ends at 838, row opens at 875. */
    margin-block-start: 37px;
  }

  /* Once the row genuinely cannot hold all seven, wrapping takes over and
     space-between would strand the last line's items across the full width.
     Left-align them so they park under column one. */
  @media (width < 60rem) {
    .client-logos__row {
      justify-content: flex-start;
      column-gap: clamp(1.5rem, 4.722vw, var(--space-xl));
    }
  }

  /* Below the desktop range the wordmarks may shrink, but not past 96px:
     three of them carry ~5px microtype ("MAX HOLZBAU", "FORMTEILE",
     "VISION GUIDED ROBOTICS") that stops reading below that. */
  @media (width < 60rem) {
    .client-logos__logo {
      inline-size: 96px;
    }
  }

  .client-logos__item {
    /* Never shrink: a squashed wordmark is worse than a wrapped row. */
    flex: 0 0 auto;
  }

  .client-logos__logo {
    /* 112 x 84 in Figma. The sources are 400 x 300, the same 4:3, so nothing is
       cropped and contain and cover are identical here.

       Fixed rather than vw: see the scrollbar note on .client-logos__row. The
       shrink-and-floor behaviour moves to the media query above, so the drawn
       desktop size can never depend on whether a scrollbar is present. */
    inline-size: 112px;
    block-size: auto;
    aspect-ratio: 4 / 3;
    object-fit: contain;
  }

  /* ------------------------------------------------------------------
     Below 1440 there is no comp. Decisions made here, for the log:

     1. The row WRAPS, it does not scroll horizontally. Client logos are a
        trust signal, so all seven have to be countable without an
        interaction; a scroller hides the tail behind a gesture that is
        invisible on a trackpad and needs a tabindex plus a visible
        affordance to stay keyboard-operable. Wrapping also keeps
        CONVENTIONS section 5's "no horizontal scroll at 320" true without
        an inner scroll container that has to be defended.
     2. Above 60rem the boxes are a fixed 112px and `space-between` derives
        the gap from the real content width, so all seven hold one line
        regardless of scrollbar width. Below 60rem the boxes shrink toward
        their 96px floor and the row wraps: 6+1, 5+2, 4+3, 3+3+1, and
        finally 2+2+2+1 at 320px, with no overflow.
     3. Wrapped rows stay LEFT aligned, as drawn. Centring them was tried
        and rejected: it strands the seventh logo in the middle of an
        otherwise empty line, where left alignment parks it under the first
        column and the whole thing reads as an ordinary grid. That also
        means this section needs no layout breakpoint at all.
     ------------------------------------------------------------------ */
}


/* sections/process/section.css */
/* ==========================================================================
   Section: process
   Figma band y 1000..1870 in a 1440 frame.

   Measured anchors, all reproduced exactly at 1440:
     content inset      x = 125 left, 77 right  (the design is asymmetric)
     h2                 y = 1046, 56/56, #1b1d36, manual break after "Ablauf",
                        "Effizienz & Leads" on the brand gradient
     intro              y = 1193, 17/19 weight 300, black, one line
     card grid          y = 1264, four 290px columns at x = 125/441/757/1073,
                        gap 26, total 1238
     card               290 x 524, radius 20, shadow 0 4px 4px rgb(0 0 0 / .25)
     card overlay       180deg: transparent 0%, rgb(35 35 72 / .9) 50%, .9 100%
     app icon           y = 1513, 54 x 54, radius 10
     card title         y = 1591, 17/19 weight 400, white
     card text          y = 1641, 17/19 weight 300, white, 215px measure
     ornament           x = -242, y = 1426, frame 657 x 547, image at 120.14%

   Vertical rhythm: 1046 - 1000 = 46 top, 1870 - 1788 = 82 bottom.
   ========================================================================== */

@layer sections {
  .process {
    position: relative;
    isolation: isolate;
    /* Stated, not inherited from the page: the section has to look the same
       wherever it is ordered. */
    /* No background on purpose.

       This is the page's own white, so painting it here changes nothing
       visually — but it DOES occlude the previous section's decorative
       overhang. Each of these sections is isolated, so it paints atomically
       over whatever came before it, and a 3D shape that is drawn bleeding
       across the boundary gets a hard horizontal cut instead.

       Confirmed against the Figma render: the hero's disc overhangs 40px onto
       the white below it, and the process ring overhangs 103px. Both were
       being sliced off. See CONVENTIONS section 3. */
    padding-block: 46px 82px;
    /* 125/1440 = 8.681vw, 77/1440 = 5.347vw. Both resolve to the Figma pixel
       at 1440. The design's own inset is asymmetric because the 1238px card
       grid was placed from the left; reproduced rather than centred. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 8.681vw, 125px))
      calc(var(--stage-x) + clamp(1.25rem, 5.347vw, 77px));
  }

  .process__title {
    /* h2 element style supplies 56/56. Colour is the raw #1b1d36 the design
       uses here, not dark-navy. */
    max-inline-size: 881px;      /* Figma text box */
    color: var(--color-heading-alt);
  }

  .process__intro {
    /* h2 box ends at 1158, intro sits at 1193. */
    margin-block-start: 35px;
    max-inline-size: 765px;      /* Figma text box, one line at 1440 */
    font-size: var(--text-sm);
    line-height: var(--leading-body);
    color: var(--color-text);
  }

  .process__steps {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 26px;                   /* 441 - (125 + 290) */
    /* intro box ends at 1212, cards start at 1264. */
    margin-block-start: 52px;
  }

  .process__card {
    position: relative;
    isolation: isolate;
    min-block-size: 524px;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    color: var(--color-on-dark);
    /* Clips the oversized backdrop photo to the rounded frame. */
    overflow: hidden;
  }

  /* The dark scrim over the photo. Figma draws it as its own rectangle per
     card with the same gradient, so it is a pseudo-element here. */
  .process__card::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: linear-gradient(
      180deg,
      rgb(0 0 0 / 0) 0%,
      rgb(35 35 72 / 0.9) 50%,
      rgb(35 35 72 / 0.9) 100%
    );
    pointer-events: none;
  }

  /* Figma scales each backdrop into the card with its own crop transform, and
     several of them are non-uniform (the source is stretched). Reproduced as
     measured, per card, via the custom properties below. Deliberately no
     object-fit: `fill` is what Figma renders. */
  .process__photo {
    position: absolute;
    inset-block-start: var(--photo-y);
    inset-inline-start: var(--photo-x);
    inline-size: var(--photo-w);
    block-size: var(--photo-h);
    max-inline-size: none;
  }

  /* Crop per card, plus the card's own text inset. These belong to the photo
     and the text as they were placed in Figma; a replacement image from the
     CMS will need its own values or a fallback to cover.

     The insets are NOT uniform in the design: the text column starts 25px
     from the card edge on the first card, 33px on the second and 24px on the
     last two, and the body copy of the first card is indented 2px further
     than its own title while the second card's body sits 6px lower than
     everyone else's. That is misalignment, not intent: reproduced under the
     fidelity rule and reported, not corrected. */
  .process__card[data-photo="strategy"] {
    --photo-x: 0.12%;
    --photo-y: -6.87%;
    --photo-w: 193.1%;
    --photo-h: 106.87%;
    --inset: 25px;        /* icon + title at x = 150 */
    --text-indent: 2px;   /* body at x = 152 */
  }

  .process__card[data-photo="design"] {
    --photo-x: -69.35%;
    --photo-y: -17.39%;
    --photo-w: 211.87%;
    --photo-h: 117.26%;
    --inset: 33px;        /* icon + title + body at x = 474 */
    --text-drop: 6px;     /* body at y = 1647, not 1641 */
  }

  .process__card[data-photo="infrastructure"] {
    --photo-x: 0.11%;
    --photo-y: -0.03%;
    --photo-w: 204.83%;
    --photo-h: 113.36%;
  }

  .process__card[data-photo="build"] {
    --photo-x: -42.75%;
    --photo-y: -30.54%;
    --photo-w: 235.7%;
    --photo-h: 130.45%;
  }

  .process__card-inner {
    position: relative;
    z-index: 1;                  /* above the photo and the scrim */
    /* Icon top sits 249px below the card top (1513 - 1264). The left inset is
       per card, see the blocks above; 24px is what three of the four use.
       The bottom padding is a gutter for longer CMS copy only: at 1440 with
       the drawn copy the card is 515px tall and min-block-size takes it to
       the 524 in the design. */
    padding-block: 249px 24px;
    padding-inline: var(--inset, 24px) 24px;
  }

  .process__icon {
    inline-size: 54px;
    block-size: 54px;
    border-radius: var(--radius-sm);
    object-fit: cover;
  }

  .process__card-title {
    /* Icon ends at 1567, title at 1591. */
    margin-block-start: 24px;
    max-inline-size: 215px;      /* Figma text box: it sets the line breaks */
    font-size: var(--text-sm);
    font-weight: var(--weight-regular);
    line-height: var(--leading-body);
    color: var(--color-on-dark);
  }

  .process__card-text {
    /* Two-line title ends at 1629, body at 1641. --text-drop and
       --text-indent carry the second and first card's own offsets. */
    margin-block-start: calc(12px + var(--text-drop, 0px));
    margin-inline-start: var(--text-indent, 0px);
    max-inline-size: 215px;
    font-size: var(--text-sm);
    line-height: var(--leading-body);
  }

  .process__ornament {
    position: absolute;
    z-index: -1;                 /* behind the cards, above the background */
    /* Figma x = -242, y = 1426, i.e. 426 below the top of the band. It bleeds
       past the bottom of the section on purpose: the next section declares it
       as bleeding in and does not draw it. Horizontal overflow is contained by
       body { overflow-x: clip } in base/reset.css. */
    inset-block-start: 426px;
    inset-inline-start: -242px;
    inline-size: 657px;
    block-size: 547px;
    overflow: hidden;
    pointer-events: none;
  }

  .process__ornament img {
    /* Square source in a shorter frame: the bottom of the ring is cropped. */
    inline-size: 100%;
    block-size: 120.14%;
    max-inline-size: none;
  }

  /* ------------------------------------------------------------------
     Below 1440 there is no comp. Decisions made here:
       - four columns hold to 1200, then two, then one at 640. Four 290px
         cards cannot narrow much further before the 17px copy shreds.
       - the 215px text measure is a Figma text box, not a design intent for
         wider cards, so it is dropped once the cards stop being 290 wide.
       - the ornament is hidden below 768: stacked cards sit exactly where it
         is drawn and it reads as an artefact rather than as decoration.
     ------------------------------------------------------------------ */

  @media (width < 1200px) {
    .process__steps {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .process__card {
      /* The per-card inset jitter is reproduced only at the width it was
         drawn at. Once the cards are not 290px wide it reads as a bug, so the
         four cards align. */
      --inset: 24px;
      --text-indent: 0px;
      --text-drop: 0px;
    }

    .process__card-title,
    .process__card-text {
      max-inline-size: none;
    }
  }

  @media (width < 768px) {
    .process__ornament {
      display: none;
    }
  }

  @media (width < 640px) {
    .process__steps {
      grid-template-columns: minmax(0, 1fr);
    }

    .process__card-inner {
      /* The photo band keeps its proportion of a shorter card. */
      padding-block-start: 200px;
    }

    .process__card {
      min-block-size: 0;
    }
  }
}


/* sections/benefits/section.css */
/* ==========================================================================
   Section: benefits
   Figma band y 1870..2800 in a 1440 frame (930 tall).

   Measured anchors, all reproduced exactly at 1440:
     h2 text box        x = 116, y = 2053, w = 762, 56/56, #1b1d36
     gradient run       "staunen lassen", gradient spans the WHOLE 762px box
     checklist cards    x = 122, w = 579, h = 110, y = 2294 / 2430 / 2566
     check badge        37 x 37 at x = 137, radius 30, brand gradient
     item title         x = 184, 17/19 regular      body x = 184, w = 486, 17/19 light
     quote card         x = 773, y = 1941, 667 x 574, radius 15 on the LEFT only
     quote text         w = 498, centred, y = 2088, 34/36 white
     gradient rule      x = 994, y = 2228, 225 x 3
     attribution        x = 994, y = 2267, w = 254, 17/19 light white
     rating card        x = 1155, y = 2392, 285 x 208, radius 15 on the LEFT only
     "5,00"             box x = 1237, y = 2419, w = 121, 34/36, gradient text
     stars              5 x 28px from x = 1218, pitch 33 (= 28 + 5), y = 2476
     rating label       box x = 1203, y = 2526, w = 190, 17/19, gradient text
     shape (image 55)   x = 689, y = 2097, 725.428 x 747.695, inner frame
                        523.278 x 573.553 rotated 26.75deg

   Two things worth knowing before editing:

   1. The quote card and the rating card run off the right edge of the frame.
      That is why both carry left-only radii in Figma. The section therefore has
      NO right padding and both cards are anchored to the section's right edge,
      so the bleed survives viewports wider than 1440.

   2. The h2 gradient is painted on the h2, not on the run. In Figma the
      gradient is mapped across the full 762px text box, so "staunen" (line 2,
      x 470..710) is violet while "lassen" (line 3, x 116..296) is cyan. Putting
      .u-gradient-text on the run instead would map the gradient across the
      run's own two fragments and invert those colours.
   ========================================================================== */

@layer sections {
  .benefits {
    position: relative;
    isolation: isolate;
    /* No background on purpose.

       This is the page's own white, so painting it here changes nothing
       visually — but it DOES occlude the previous section's decorative
       overhang. Each of these sections is isolated, so it paints atomically
       over whatever came before it, and a 3D shape that is drawn bleeding
       across the boundary gets a hard horizontal cut instead.

       Confirmed against the Figma render: the hero's disc overhangs 40px onto
       the white below it, and the process ring overhangs 103px. Both were
       being sliced off. See CONVENTIONS section 3. */
    /* Band 1870..2800. h2 line box at 2053 => 183 above it; the last checklist
       card ends at 2676 => 124 below it. Held fixed down to the breakpoint
       because the absolutely positioned rating card is measured from the
       section's top edge. */
    padding-block: 183px 124px;
    /* 116/1440 = 8.056vw. Right side is 0: the cards bleed off the edge. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 8.056vw, 116px))
      calc(var(--stage-x) + 0px);
  }

  .benefits__inner {
    display: grid;
    /* Left column is fluid, the quote card keeps the drawn 667px and stays
       flush to the right edge. 701 -> 773 is the measured 72px gap. */
    grid-template-columns: minmax(0, 1fr) 667px;
    /* Explicit rows, because the quote card spans both of them and `-1` only
       resolves against the explicit grid. */
    grid-template-rows: auto auto;
    column-gap: 72px;
    /* h2 ends at 2221, first card starts at 2294. */
    row-gap: 73px;
  }

  /* ---------------------------------------------------------------- heading */

  .benefits__title {
    grid-column: 1 / -1;
    grid-row: 1;
    /* The drawn text box, and the box the gradient is mapped across.

       It does NOT reliably produce the drawn line breaks on its own:
       "Mitbewerber staunen lassen" measures 747px in the browser against a
       762px box, so it stays on one line where Figma wrapped it, costing the
       section a whole 56px line and shifting every section below it up by
       that much. 15px of metric slack is far too little to depend on, so the
       break before "lassen" is set explicitly in the markup. */
    inline-size: 762px;
    max-inline-size: 100%;
    color: var(--color-heading-alt);
    background-image: var(--gradient-brand);
  }

  /* Fallback for browsers without background-clip: text — the run keeps the
     gradient's end colour rather than disappearing. */
  .benefits__title-run {
    color: var(--color-brand-to);
  }

  @supports (background-clip: text) or (-webkit-background-clip: text) {
    .benefits__title {
      -webkit-background-clip: text;
      background-clip: text;
    }

    /* Transparent glyphs reveal the h2's own gradient, positioned across the
       whole heading. The solid runs paint over it. */
    .benefits__title-run {
      color: transparent;
    }
  }

  /* -------------------------------------------------------------- checklist */

  .benefits__list {
    grid-column: 1;
    grid-row: 2;
    display: grid;
    /* 2294 -> 2430 is a 136 pitch on 110-tall cards. */
    gap: 26px;
    inline-size: 579px;
    max-inline-size: 100%;
    /* The cards sit at x=122, the h2 text box at x=116. Reproduced, not
       averaged away. */
    margin-inline-start: 6px;
  }

  .benefits__item {
    display: flex;
    /* Badge ends at 174, text starts at 184. */
    gap: 10px;
    /* Badge top 13 below the card top, left 15 in; text column is 486 wide,
       which puts the right padding at 31. Bottom 20 completes the 110. */
    padding: 13px 31px 20px 15px;
    min-block-size: 110px;
    border-radius: var(--radius-md);
    background-color: var(--color-glass-light);
    box-shadow: var(--shadow-card);
  }

  .benefits__check {
    flex: none;
    display: grid;
    place-items: center;
    inline-size: 37px;
    block-size: 37px;
    /* Figma says radius/xl on a 37px square; the browser clamps it to a
       circle, which is what the design renders. */
    border-radius: var(--radius-xl);
    background-image: var(--gradient-brand);
    color: var(--color-on-dark);
  }

  .benefits__item-body {
    /* Badge starts 13 below the card top, the title line box 24. */
    padding-block-start: 11px;
  }

  .benefits__item-title {
    color: var(--color-heading);
    /* Title node is 28 tall on a 19px line box; the body follows at +28. */
    margin-block-end: 9px;
  }

  .benefits__item-text {
    color: var(--color-heading);
  }

  /* ------------------------------------------------------------- quote card */

  .benefits__quote {
    grid-column: 2;
    grid-row: 1 / -1;
    align-self: start;
    /* The card starts at 1941, 112 above the h2's line box at 2053. */
    margin: -112px 0 0;
    padding-block-start: 147px;
    min-block-size: 574px;
    /* Left-only radii: the card runs off the right edge of the frame. */
    border-start-start-radius: var(--radius-md);
    border-end-start-radius: var(--radius-md);
    color: var(--color-on-dark);
    /* The fill is a mesh/image fill. Figma's codegen exports no colour for it
       at all, so it arrived as a bare div. This is the real fill, exported
       from the node at 2x (667 x 574 -> 1334 x 1148).

       It replaces four hand-fitted gradient layers that got to ~5.6% RMSE
       against the render. That was the ceiling: a mesh gradient has no
       faithful CSS expression, so the remaining error was structural rather
       than a matter of better-fitted stops.

       background-color is the paint-time fallback before the image decodes,
       sampled from the card's top-left. */
    background-color: #1c1e36;
    background-image: url("img/quote-card-fill.png");
    /* Pinned to the drawn 667 x 574, anchored top-left, NOT `cover`. The card
       is a min-height box that grows with its content, and `cover` rescales
       the whole mesh whenever it does, sliding the warm corner off-position.
       At the drawn size the fill lands exactly and simply stops if the card
       grows; the background-color underneath carries the overflow. */
    background-size: cover;
    background-position: left top;
    background-repeat: no-repeat;
  }

  .benefits__quote-text {
    /* 498 wide, centred in the 667 card: 84.5 either side (Figma 85 / 84). */
    inline-size: 498px;
    max-inline-size: 100%;
    margin: 0 auto;
    text-align: center;
  }

  /* The 225 x 3 gradient rule under the quote. Decorative, so it is a
     pseudo-element rather than an empty span. Centring it inside the 498 text
     column lands it at x=994, exactly as drawn. */
  .benefits__quote-text::after {
    content: "";
    display: block;
    inline-size: 225px;
    max-inline-size: 100%;
    block-size: 3px;
    /* Quote text ends at 2196, rule at 2228. */
    margin: 32px auto 0;
    background-image: var(--gradient-brand);
  }

  .benefits__quote-author {
    /* Left-aligned in a 254 box that starts at the rule's left edge (x=994).
       It reads as centred but is not, so it is reproduced as drawn. */
    inline-size: 254px;
    max-inline-size: 100%;
    /* Rule ends at 2231, attribution line box at 2267. */
    margin: 36px 0 0 calc((100% - 225px) / 2);
  }

  /* ------------------------------------------------------------ rating card */

  .benefits__rating {
    position: absolute;
    z-index: 1;                       /* above the 3D shape, which it covers */
    /* 2392 - 1870 = 522 from the section's top edge; flush right, like the
       quote card it overlaps. */
    inset-block-start: 522px;
    inset-inline-end: 0;
    inline-size: 285px;
    /* Score line box at 27, label ends at 172, card is 208. */
    padding-block: 27px 36px;
    border-start-start-radius: var(--radius-md);
    border-end-start-radius: var(--radius-md);
    background-color: var(--color-surface);
    box-shadow: var(--shadow-card);
    text-align: center;
  }

  .benefits__rating-score {
    /* The drawn text box. The gradient is mapped across it, not across the
       glyphs, so the width matters. */
    inline-size: 121px;
    margin-inline: auto;
  }

  .benefits__rating-stars {
    display: flex;
    justify-content: center;
    /* 1218 -> 1251 is a 33 pitch on 28px stars. */
    gap: 5px;
    /* Score line box ends at 2455, stars at 2476. */
    margin-block-start: 21px;
  }

  .benefits__rating-label {
    inline-size: 190px;
    /* Stars end at 2504, label line box at 2526. */
    margin: 22px auto 0;
  }

  /* ------------------------------------------------------------- 3D shape */

  .benefits__shape {
    position: absolute;
    /* 1440 - (689 + 725.428) = 25.572. Anchored right so it keeps its place
       against the quote card on viewports wider than the frame. */
    inset-block-start: 227px;
    inset-inline-end: 25.572px;
    inline-size: 725.428px;
    aspect-ratio: 725.428 / 747.695;
    pointer-events: none;
  }

  /* The rotated frame: Figma rotates a 523.278 x 573.553 box by 26.75deg
     inside the bounding box above, and the image overhangs it. */
  .benefits__shape-frame {
    position: absolute;
    inset-block-start: 11.645%;
    inset-inline-start: 13.934%;
    inline-size: 72.134%;
    block-size: 76.71%;
    overflow: hidden;
    transform: rotate(26.75deg);
  }

  .benefits__shape-frame img {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 129.3%;
    block-size: 117.96%;
    max-inline-size: none;
  }

  /* ------------------------------------------------------------------
     Below 1280 there is no comp. Decisions made here, logged in the
     handover notes:
       - the two columns stack. 116 + 430 + 72 + 667 = 1285 is the narrowest
         width at which the checklist still has a readable measure next to the
         fixed 667px card, so that is the breakpoint.
       - the quote card keeps its right bleed and its left-only radii, loses
         its negative top margin and its 574 minimum height (nothing overlaps
         its lower half any more, so the empty space has no purpose).
       - the rating card stops overlapping and follows the quote, still flush
         right so the bleed reads as deliberate.
       - the 3D shape is dropped: it is decorative and its whole placement is a
         function of the 1440 composition.
     ------------------------------------------------------------------ */

  @media (width < 1280px) {
    .benefits {
      padding-block: clamp(4rem, 12.708vw, 183px) clamp(3rem, 8.611vw, 124px);
    }

    .benefits__inner {
      grid-template-columns: minmax(0, 1fr);
      grid-template-rows: none;
      row-gap: 48px;
    }

    .benefits__list {
      grid-row: auto;
      /* The 6px offset against the h2 is a quirk of the drawn frame. Dropped
         here, where it would only push the cards past the right edge. */
      margin-inline-start: 0;
    }

    .benefits__quote {
      grid-column: 1;
      grid-row: auto;
      margin-block-start: 0;
      padding-block: clamp(3rem, 10vw, 147px) clamp(3rem, 10vw, 147px);
      min-block-size: 0;
    }

    .benefits__rating {
      position: static;
      inline-size: 285px;
      max-inline-size: 100%;
      margin: 24px 0 0 auto;
    }

    .benefits__shape {
      display: none;
    }
  }

  @media (width < 640px) {
    .benefits__item {
      /* The drawn 31px right inset is dead space once the card is this
         narrow, so it falls back to a symmetric one. */
      padding-inline: 15px;
    }

    .benefits__quote-author {
      /* The 254px box positioned off the rule's left edge stops making sense
         once the card is narrower than the drawn one. */
      inline-size: auto;
      margin-inline: 0;
      text-align: center;
    }
  }
}


/* sections/projects/section.css */
/* ==========================================================================
   Section: projects (Referenzen)
   Figma band y 2800..4020 in a 1440 frame.

   Measured anchors, all reproduced exactly at 1440:
     h2                  x = 136, y = 2879, 56/56, #1b1d36, "Websites" gradient
     card rects          580 x 367, radius 15, shadow 0 4px 4px rgb(0 0 0/.25)
       MAX Holzbau       x = 122,  y = 3068.85   fill white @ 20%
       neovum            x = 754,  y = 3068.71   fill white @ 20%
       Richter Formteile x = 125,  y = 3558.71   fill white @ 90%
       PulsMedia         x = 754,  y = 3556.71   fill: mesh gradient, see below
     mockups (overhang the card top, never clipped)
       mh-usercase       x = 143,  y = 2963, 541.07 x 399.99, rotated 0.43deg
       neovum-usercase   x = 791.1,y = 2949.5, 542.66 x 401.35
       formteile-case    x = 147,  y = 3461, 540 x 399
       pulsmedia-case    x = 777,  y = 3461, 540 x 399
     company names       x = card + 33, y = card + 273, 26/24, #17203f
                         (#ffffff on the dark card)
     descriptors         x = card + 33, y = card + 306.15, 17/19, brand gradient
     column pitch        632 (580 + 52 gap), row pitch 489.85 (367 + 122.85)
     section insets      122 left, 106 right, 79 above the h2, 94 below row 2

   Two coordinates in the file are off a regular grid by a hair — the Richter
   card sits 3px right of the MAX card above it, and the PulsMedia card 2px
   above the Richter card while its text stays on the same baseline as
   Richter's. Both are reproduced (see the per-card blocks) and reported.
   ========================================================================== */

@layer sections {
  .projects {
    /* h2 line box at 79, last card rect ends 94 above the band edge. */
    padding-block: 79px 94px;
    /* Left inset is the card grid's 122; the h2 sits 14px further in and
       carries that itself. 122/1440 = 8.4722vw, 106/1440 = 7.3611vw. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 8.4722vw, 122px))
      calc(var(--stage-x) + clamp(1.25rem, 7.3611vw, 106px));
  }

  .projects__title {
    /* Figma h2 style, 56/56. Colour is #1b1d36, the near-duplicate of
       dark-navy that this design uses for headings on light ground. */
    color: var(--color-heading-alt);
    /* The h2 text node is 881 wide and starts 14px right of the card grid. */
    max-inline-size: 881px;
    margin-inline-start: clamp(10.5px, 0.9722vw, 14px);
  }

  /* Figma anchors a text gradient to the whole TEXT NODE, not to the coloured
     run, so "Websites" shows the 52%..82% slice of the brand gradient and
     starts periwinkle (#7387ff), not cyan. Measured off the reference render.
     Both values are in em so the slice stays identical as the type scales:
     881px and the advance of "Küzlich gebaute " at the 56px desktop size. */
  .projects__accent {
    background-size: 15.732em 100%;
    background-position-x: -8.237em;
  }

  .projects__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 580px));
    /* 52/1440 = 3.6111vw. */
    column-gap: clamp(1.25rem, 3.6111vw, 52px);
    /* Row 2 sits 122.85 below row 1, and the h2 ends 133.85 above row 1.
       Both are floors, not fixed values: the mockups overhang the card top by
       17.9% of the card width, so as the cards narrow the gaps must stay
       ahead of that overhang. 21vw does exactly that below 1440 and is
       clamped away at 1440, where the measured numbers win. */
    row-gap: clamp(6rem, 21vw, 122.85px);
    margin-block-start: clamp(4rem, 21vw, 133.85px);
  }

  .projects__card {
    position: relative;
    /* 273 / 49 / 41.85 / 33 of 580. The padding box IS the 498px text column
       the design draws, so the name and the descriptor need no geometry of
       their own — and the card grows instead of overflowing when the
       descriptor wraps below 1440. At 1440 the card is 367 tall to the pixel:
       273 + 24 + 9.15 + 19 + 41.85. */
    padding: 47.069% 8.448% 7.216% 5.690%;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
    /* Cards 1 and 2 are white at 20%, card 3 at 90%, over the white page.
       Reproduced as measured: the glass is what lets the neighbouring
       sections' 3D shapes tint the corners they pass behind. */
    background-color: var(--color-glass-light);

    /* Mockup placement, as a share of the card width — the Figma frame is
       proportional, so the composition survives the card narrowing.
       Default = card 1. */
    --media-x: 3.879%;        /*   22.50px */
    --media-y: -17.899%;      /* -103.82px */
    --media-w: 93.287%;       /*  541.07px */
    --media-rotate: 0deg;
  }

  .projects__media {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: var(--media-x);
    /* The vertical offset is a percentage MARGIN, not an inset: percentage
       margins resolve against the containing block's width in both axes, so
       the overhang stays locked to the card's width rather than its
       content-driven height. */
    margin-block-start: var(--media-y);
    inline-size: var(--media-w);
    /* The mockups carry their own transparent margin and the card must not
       clip them: they deliberately overhang its top edge. */
    block-size: auto;
    rotate: var(--media-rotate);
  }

  /* .projects__name is an <h3>: the Figma name style is 26/24 dark-navy, which
     is exactly the H3 the base layer already draws. It carries a class for the
     contract and the motion runtime, and needs no rule of its own. */

  .projects__descriptor {
    /* Name line box ends at 297, descriptor line box starts at 306.15. */
    margin-block-start: 9.15px;
  }

  /* --- per-card geometry ------------------------------------------------
     Each mockup was hand-placed in Figma, so no two sit at the same offset.
     The defaults above are card 1's, which is what a fifth card added in the
     CMS would inherit. */

  .projects__card[data-project="max-holzbau"] {
    /* The only rotated mockup in the section. */
    --media-rotate: 0.43deg;
  }

  .projects__card[data-project="neovum"] {
    /* Placed larger and higher than the other three. */
    --media-x: 6.397%;        /*   37.10px */
    --media-y: -20.546%;      /* -119.17px */
    --media-w: 93.562%;       /*  542.66px */
  }

  .projects__card[data-project="richter-formteile"] {
    /* The one light card that is white at 90% instead of 20%. Invisible over
       the white page, but not over the 3D shape the neighbouring section
       pushes behind this corner: the reference render shows that shape at
       exactly one tenth strength here. */
    background-color: var(--color-glass-strong);
    --media-x: 3.793%;        /*   22.00px */
    --media-y: -16.846%;      /*  -97.71px */
    --media-w: 93.103%;       /*  540.00px */
    /* x = 125, 3px right of the card above it. Reproduced, not squared up.
       As a relative offset rather than a transform: the reveal in section.js
       animates the card's transform, and GSAP folds an existing `translate`
       into it and then overwrites the axis it animates. */
    inset-inline-start: 3px;
  }

  .projects__card[data-project="pulsmedia"] {
    --media-x: 3.966%;        /*   23.00px */
    --media-y: -16.515%;      /*  -95.79px */
    --media-w: 93.103%;       /*  540.00px */
    /* y = 3556.71, 2px above the Richter card, while its name and descriptor
       stay level with Richter's. The rect moves, the text block does not:
       275.29 above / 39.56 below instead of 273 / 41.85. */
    inset-block-start: -2px;
    padding-block: 47.464% 6.820%;
  }

  /* --- dark card --------------------------------------------------------
     Figma's codegen emits no fill for this rectangle (node 190:206): the fill
     is a mesh gradient it cannot express, so it came through as a bare div.

     Now using the real thing, exported from Figma at 2x. It replaces three
     hand-fitted radial stops that reached a mean error of 5/255 per channel —
     close, but a mesh gradient is not expressible as radials, so "close" was
     the ceiling. background-color stays as the paint-time fallback. */

  .projects__card[data-variant="dark"] {
    --color-heading: var(--color-on-dark);
    background-color: #1e1f40;
    background-image: url("img/pulsmedia-card-fill.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }

  /* ------------------------------------------------------------------
     Below 1440 there is no comp. Decisions made here, logged in the
     build report:
       - the two columns hold until 900, where two cards would be under
         360px wide and every descriptor would wrap; then one column
       - the 3px and 2px offsets above are absolute slips in a 1440 frame,
         so they are dropped once the cards no longer sit on that grid
       - the mockups keep their overhang at every width, because the
         gaps above and between the rows scale with it
     ------------------------------------------------------------------ */

  @media (width < 900px) {
    .projects {
      padding-block: 56px 64px;
    }

    /* One column, still capped at the drawn 580 and still starting on the
       section inset, so the h2 keeps sitting 14px inside the card edge the
       way it does at 1440. */
    .projects__grid {
      grid-template-columns: minmax(0, 580px);
    }

    /* Matches the specificity of the per-card rules above so it can undo them. */
    .projects__card[data-project] {
      inset: auto;
    }
  }
}


/* sections/why-us/section.css */
/* ==========================================================================
   Section: why-us  (Warum wir)
   Figma band y 4020..4980 in a 1440 frame. 73 owned nodes, the densest
   section on Startseite.

   Measured anchors, all reproduced exactly at 1440:
     content inset       x = 133
     h2                  y = 4134, 56/56, #1b1d36, "uns entscheiden" gradient
     block titles        y = 4299 / 4604 / 4741, 26/24, dark-navy
     block copy          y = 4342 / 4647 / 4784, 17/19 weight 300, measure 486
     step list           y = 4409, six rows, 27.2px pitch
     step dots           x = 133, 13 x 13, fully round
     CTA pill            x = 133, y = 4889, 238 x 37, radius 55, gradient @ 70%
     dark card           x = 773, y = 4288, 667 x 574, radius 15 left side only
     weekday row         y = 4359, line centred at 4373
     calendar grid       first cell x = 870, y = 4427; cells 63 x 60,
                         column gap 13.5, row gap 9, seven columns, five rows
     price card          x = 1155, y = 4740, 285 x 208, radius 15 left side only
     price headline      centre x = 1298 / y = 4814, 34/36, gradient
     price note          x = 1194, y = 4872, 17/19, gradient
     ornament            x = -344.64, y = 3393.36, 932.681 square, -21.32deg

   Column split: the left text column is 640 wide (133 -> 773) and the card
   column is 667 (773 -> 1440, bleeding off the right edge). Expressed as
   640fr / 667fr so the ratio survives every width, exact at 1440.

   THREE SURFACES ARE REBUILT, NOT EXPORTED. The dark card, the sixth step dot
   and the "31" cell carry an image fill in Figma (a blurred aurora mesh). The
   code export dropped all three — its companion asset, "Rectangle 50", exports
   as an empty 32x32 SVG — so they are reconstructed here as layered gradients
   fitted to colours sampled off the reference render. Anchors used:
     card top-left #171a31, bottom-left #363464,
     warm bloom #bea387 at 85%/18%, violet bloom #75569b at 47%/54%.

   Two selector notes, both deliberate:
     - :where() wraps every :nth-child and [data-*] qualifier so the rules stay
       at the 0-1-0 specificity ceiling the conventions set.
     - the ornament sits at z-index -1 and .why-us therefore must NOT become a
       stacking context (no isolation, no z-index, no transform on the root).
       That is what keeps it behind the neighbouring section's glass card, the
       way the design draws it.
   ========================================================================== */

@layer sections {

  .why-us {
    position: relative;
    /* h2 top 4134 - band top 4020 = 114. Price card bottom 4948 -> 4980 = 32.
       No background: the ornament paints in the page's negative z layer and a
       background here would cover it. */
    padding-block: 114px 32px;
    /* 133/1440 = 9.236vw, exactly 133px at 1440. The card bleeds off the right
       edge, so there is no end inset. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 9.236vw, 133px))
      calc(var(--stage-x) + 0px);
  }

  /* ---------------------------------------------------------------- ornament
     Figma places this 932.681 square at x -344.64 / y 3393.36 canvas, i.e.
     626.64px above this section's top edge. Only its bottom ~60px falls inside
     the band; the rest overhangs the section above, where the design has it
     sitting behind a translucent card. Hence z-index: -1. */

  .why-us__ornament {
    position: absolute;
    z-index: -1;
    inset-block-start: -835.52px;
    inset-inline-start: -459.52px;
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 932.681px;
    block-size: 932.681px;
    pointer-events: none;
  }

  .why-us__ornament img {
    inline-size: 720.135px;
    block-size: 720.135px;
    max-inline-size: none;
    object-fit: cover;
    transform: rotate(-21.32deg);
  }

  /* ------------------------------------------------------------------ title */

  .why-us__title {
    /* Figma H2: 56/56. #1b1d36 is the design's near-duplicate of dark-navy,
       reproduced rather than normalised (CONVENTIONS section 4). */
    color: var(--color-heading-alt);
    max-inline-size: 881px;
  }

  /* The gradient fills the whole 881px text frame in Figma, not the coloured
     run, so "uns entscheiden" ends on a blue-violet rather than on #9745ff.
     Sampled: #4dd0ff at x=133, (115,135,255) at x=590. Reproduced by sizing the
     background to the frame instead of to the span. 61.181vw = 881px at 1440. */
  .why-us__accent {
    background-size: clamp(20rem, 61.181vw, 881px) 100%;
    background-repeat: no-repeat;
  }

  /* ------------------------------------------------------------------- body */

  .why-us__body {
    display: grid;
    grid-template-columns: minmax(0, 640fr) minmax(0, 667fr);
    align-items: start;
    /* h2 ends 4134 + 112 = 4246, card starts 4288. */
    margin-block-start: 42px;
  }

  .why-us__col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    /* The card starts at 4288, the first block title at 4299. */
    padding-block-start: 11px;
  }

  .why-us__blocks {
    display: flex;
    flex-direction: column;
    /* Figma has 40px after the step list and 37px after the second block.
       38.5 is the average: it lands the third block and the CTA on their exact
       y, and puts the second block 1.5px high. A repeater cannot carry two
       different gaps without hardcoding item positions. */
    gap: 38.5px;
    inline-size: 100%;
  }

  .why-us__block-text {
    /* Figma text frame 486 wide. This is what produces the design's line
       breaks; do not widen it. */
    max-inline-size: 486px;
    margin-block-start: 19px;
    color: var(--color-heading);
  }

  /* -------------------------------------------------------------- step list */

  .why-us__steps {
    display: flex;
    flex-direction: column;
    /* Figma pitch runs 26, 27, 27, 28, 28 — designer drift. 8.2px of gap on a
       19px line box averages to 27.2 and lands the last row exactly on 4545. */
    gap: 8.2px;
    margin-block-start: 29px;
  }

  .why-us__step {
    position: relative;
    /* dot at x=133, label at x=157 */
    padding-inline-start: 24px;
    font-size: var(--text-sm);
    line-height: var(--leading-body);
    color: var(--color-heading);
  }

  .why-us__step::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 3px;
    inline-size: 13px;
    block-size: 13px;
    border-radius: 50%;
    background: var(--why-us-dot, transparent);
  }

  /* Six dots, six different fills, read off the Figma nodes 190:120..190:136.
     The fifth is a 20% grey that renders as #e4e4e4 on white; the sixth has the
     dropped image fill and is approximated from sampled pixels. */
  .why-us__step:where(:nth-child(1)) { --why-us-dot: var(--color-accent-deep); }
  .why-us__step:where(:nth-child(2)) { --why-us-dot: var(--color-accent); }
  .why-us__step:where(:nth-child(3)) { --why-us-dot: var(--gradient-brand); }
  .why-us__step:where(:nth-child(4)) { --why-us-dot: var(--color-accent-deep); }
  .why-us__step:where(:nth-child(5)) { --why-us-dot: rgb(120 118 118 / 0.2); }
  .why-us__step:where(:nth-child(6)) {
    --why-us-dot: linear-gradient(96deg,
      #322b52 0%, #443a66 19%, #654e89 35%, #7c5ba6 50%, #6b5681 73%, #675f6e 100%);
  }

  /* -------------------------------------------------------------------- CTA
     Same treatment as the hero's: the gradient sits at 70% opacity in Figma and
     the label does not, so the fill lives on a pseudo-element. The drop shadow
     belongs to the same node and therefore inherits the 70% too. */

  .why-us__cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 238px;
    block-size: 37px;
    /* third paragraph ends 4841, pill starts 4889 */
    margin-block-start: 48px;
    border-radius: var(--radius-2xl);
    color: var(--color-on-dark);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-button);
    text-decoration: none;
    isolation: isolate;
  }

  .why-us__cta::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    background-image: var(--gradient-brand);
    box-shadow: var(--shadow-card);
    opacity: 0.7;
    transition: opacity var(--duration-fast) var(--ease-out);
  }

  .why-us__cta:hover::before,
  .why-us__cta:focus-visible::before {
    opacity: 1;
  }

  /* ------------------------------------------------------------------ aside */

  .why-us__aside {
    position: relative;
    /* The price card is absolute and overhangs the dark card by 86px. Reserving
       it here keeps the section's own height honest (960px total, as drawn). */
    padding-block-end: 86px;
  }

  /* --------------------------------------------------------------- the card */

  .why-us__card {
    /* 71 top / 48 right / 99 bottom / 97 left, measured. 97 + 522 + 48 = 667
       and 71 + 28 + 40 + 336 + 99 = 574. */
    padding: 71px 48px 99px 97px;
    /* Bleeds off the right edge, so only the left corners are rounded. */
    border-radius: var(--radius-md) 0 0 var(--radius-md);
    color: var(--color-on-dark);
    background-color: #171a31;
    background-image:
      /* warm bloom, top right, continuing past the right edge */
      radial-gradient(61% 61% at 83% 14%,
        #d4a882 0%,
        rgb(178 158 140 / 0.92) 25%,
        rgb(160 126 148 / 0.56) 55%,
        rgb(135 112 148 / 0.28) 78%,
        rgb(40 40 80 / 0) 100%),
      /* violet bloom, centre */
      radial-gradient(52% 62% at 47% 55%,
        rgb(178 122 220 / 0.62) 0%,
        rgb(158 104 152 / 0.34) 40%,
        rgb(90 70 126 / 0.16) 70%,
        rgb(90 53 126 / 0.06) 100%),
      /* base: dark navy at the top, indigo at the bottom */
      linear-gradient(180deg,
        #29273c 0%,
        #0b142b 20%,
        #26253b 36%,
        #2f3050 48%,
        #322e5b 60%,
        #262b5c 72%,
        #373463 100%);
  }

  .why-us__weekdays {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    column-gap: 13.5px;
    /* Figma frame is 28 tall with the 19px line centred in it. */
    block-size: 28px;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-body);
    text-align: center;
  }

  .why-us__weekdays span {
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .why-us__weeks {
    display: grid;
    /* weekday frame ends 4387, first cell row starts 4427 */
    margin-block-start: 40px;
  }

  /* Two grids stacked in the same cell: the highlight runs underneath, the day
     numbers on top. Keeping them apart is what lets the numbers auto-place in
     reading order while the runs are placed explicitly. */
  .why-us__spans,
  .why-us__days {
    grid-area: 1 / 1;
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    column-gap: 13.5px;
    row-gap: 9px;
  }

  .why-us__spans {
    grid-template-rows: repeat(5, 60px);
  }

  .why-us__days {
    /* positioned so the numbers paint above the run backgrounds */
    position: relative;
    grid-auto-rows: 60px;
    font-size: var(--text-sm);
    line-height: var(--leading-body);
  }

  .why-us__day {
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .why-us__span {
    border-radius: var(--radius-md);
  }

  /* Where each highlighted run sits. Named by the days it covers so the markup
     stays readable; the grid does the arithmetic. */
  .why-us__span:where([data-span="1"])     { grid-row: 1; grid-column: 1; }
  .why-us__span:where([data-span="2-5"])   { grid-row: 1; grid-column: 2 / span 4; }
  .why-us__span:where([data-span="8-14"])  { grid-row: 2; grid-column: 1 / span 7; }
  .why-us__span:where([data-span="15-17"]) { grid-row: 3; grid-column: 1 / span 3; }
  .why-us__span:where([data-span="19"])    { grid-row: 3; grid-column: 5; }
  .why-us__span:where([data-span="22-28"]) { grid-row: 4; grid-column: 1 / span 7; }
  .why-us__span:where([data-span="29-30"]) { grid-row: 5; grid-column: 1 / span 2; }
  .why-us__span:where([data-span="31"])    { grid-row: 5; grid-column: 3; }

  /* Four fills, exactly as the nodes carry them. Only the solid and gradient
     runs have the shadow; the glass ones have a 1px light stroke instead, which
     the code export dropped but the render shows clearly (measured 168,155,199
     against a 110,98,142 interior). */
  .why-us__span:where([data-fill="deep"]) {
    background-color: var(--color-accent-deep);
    box-shadow: var(--shadow-card);
  }

  .why-us__span:where([data-fill="brand"]) {
    background-image: var(--gradient-brand);
    box-shadow: var(--shadow-card);
  }

  .why-us__span:where([data-fill="glass-violet"]) {
    background-color: rgb(157 114 206 / 0.2);   /* figma: purple @ 20% */
    border: 1px solid rgb(255 255 255 / 0.4);
    box-shadow: var(--shadow-card);
  }

  .why-us__span:where([data-fill="glass-light"]) {
    background-color: var(--color-glass-muted); /* figma: light-gray @ 20% */
    border: 1px solid rgb(255 255 255 / 0.4);
  }

  /* The "31" cell: no fill in the export, an image fill in the file. Rebuilt as
     a soft warm bloom over a slight darkening, matching the sampled interior. */
  .why-us__span:where([data-fill="glass-dark"]) {
    background-image:
      radial-gradient(58% 62% at 78% 38%,
        rgb(255 226 190 / 0.13) 0%,
        rgb(255 238 214 / 0.05) 45%,
        rgb(0 0 20 / 0.08) 100%);
    border: 1px solid rgb(255 255 255 / 0.4);
  }

  /* --------------------------------------------------------- the price card
     Overlaps the dark card by 122px and hangs 86px below it. Its right edge is
     the section's right edge, so it bleeds too. */

  .why-us__price {
    position: absolute;
    inset-block-start: 452px;     /* 4740 - 4288 */
    inset-inline-end: 0;
    inline-size: 285px;
    block-size: 208px;
    padding-block-start: 38px;
    border-radius: var(--radius-md) 0 0 var(--radius-md);
    background-color: var(--color-surface);
    box-shadow: var(--shadow-card);
    text-align: center;
  }

  .why-us__price-value {
    /* Figma frame 268 wide, centred in the 285 card. The gradient spans this
       frame, which is why both lines share one sweep. */
    inline-size: 268px;
    margin-inline: auto;
    font-size: var(--text-xl);
    line-height: var(--leading-lead);
  }

  .why-us__price-note {
    inline-size: 206px;
    margin-inline: auto;
    /* headline block ends 4850, note frame starts 4872 */
    margin-block-start: 22px;
    font-size: var(--text-sm);
    line-height: var(--leading-body);
  }

  /* ------------------------------------------------------------------------
     Below 1440 there is no comp. Decisions made here, listed in the report:
       - the two columns stack at 1024, the card keeps its left-side radius and
         still runs to the right edge
       - the price card leaves absolute positioning and sits under the calendar
       - the calendar keeps seven columns at every width; only the cell height,
         the type size and the card padding shrink, because a calendar that
         reflows to fewer columns stops reading as a calendar
       - the ornament is dropped below 1024: it is a 932px shape anchored 627px
         above the section, and there is nothing sensible for it to overhang
     ------------------------------------------------------------------------ */

  @media (width < 1024px) {
    .why-us {
      padding-block: 64px 64px;
      padding-inline: var(--gutter) 0;
    }

    .why-us__ornament {
      display: none;
    }

    .why-us__body {
      grid-template-columns: minmax(0, 1fr);
      row-gap: 48px;
    }

    .why-us__col {
      padding-block-start: 0;
    }

    .why-us__aside {
      padding-block-end: 0;
    }

    .why-us__card {
      padding: 40px 32px 48px;
    }

    .why-us__price {
      position: static;
      inline-size: min(285px, 100%);
      margin-block-start: 24px;
      block-size: auto;
      padding-block: 38px;
      border-radius: var(--radius-md);
    }
  }

  @media (width < 640px) {
    .why-us__card {
      padding: 28px 16px 32px;
    }

    .why-us__weekdays,
    .why-us__days {
      font-size: 0.8125rem;   /* 13px: seven columns at 320 leaves ~34px each */
    }

    .why-us__spans {
      grid-template-rows: repeat(5, 44px);
    }

    .why-us__days {
      grid-auto-rows: 44px;
    }

    .why-us__spans,
    .why-us__days {
      column-gap: 6px;
      row-gap: 6px;
    }

    .why-us__weekdays {
      column-gap: 6px;
    }

    .why-us__cta {
      inline-size: 100%;
      max-inline-size: 238px;
    }
  }
}


/* sections/testimonials/section.css */
/* ==========================================================================
   Section: testimonials (Kundenstimmen)
   Figma band y 4980..5760 in a 1440 frame.

   Measured anchors, all reproduced exactly at 1440:
     content inset       x = 133 left, 88 right (the band is NOT centred)
     h2                  x = 133, y = 5027, box 881 wide, 56/56, #1b1d36,
                         "glücklichen Kunden" gradient, manual break after
                         "unsere"
     card left edges     x = 133 / 552 / 971, each 381 wide, 38 gap
     card top            y = 5199   (= the dark header, the visual card top)
     header              381 x 134, radius 15 on the top corners only
     portrait            bottom-aligned to the header, overhanging it upward
                         card 1  147 x 147 at x +14   (image33)
                         card 2  154 x 154 at x +13   (image52)
                         card 3  161 x 162 at x  +0   (image51)
     head quote          x +147, y +24, 192 wide, 17/19 regular, white
     stars               x +147, y +87, 28 x 28, pitch 33 (5px gap)
     body rect           y = 5212, 381 x 471, radius 15, --shadow-card,
                         i.e. it starts 13px below the card top and its first
                         121px sit behind the header
     review text         x +28, y = 5365, 331 wide, 17/19, Poppins 200 italic
     name / role         x +28, y = 5614, 331 wide, 17/19 regular,
                         role in the brand gradient
     card bottom         y = 5683   →  band padding 47 top, 77 bottom

   Two things the Figma export did not hand over, both measured off
   .figma/testimonials.png and noted in the report:

   1. The header fill. testimonials.dc.jsx emits the three header rectangles
      with no fill at all and no image asset was exported for them, yet all
      three render pixel-identically. It is a soft four-lobe wash: navy at the
      left, violet through the middle, a warm peach glow top right, navy again
      bottom right. Refitted below as four radial gradients over a flat base;
      RMSE against the render is 3.3/255 (1.3%), worst channel 15/255.
   2. Card 3's body rectangle is rgb(255 255 255 / 0.9), not / 0.1 like the
      other two. Over the white page that is invisible, but the neighbouring
      section's 3D shape passes behind this card and the alpha decides how
      much of it ghosts through, so the difference is kept.
   ========================================================================== */

@layer sections {
  .testimonials {
    /* Band top 4980 → h2 at 5027, card bottom 5683 → band bottom 5760.
       No background: this band has no fill of its own in Figma, it sits on
       the page white, and the neighbouring 3D shape overlaps into it. */
    padding-block: 47px 77px;
    /* Asymmetric by measurement: 133 left, 88 right.
       133/1440 = 9.2361vw, 88/1440 = 6.1111vw. Exact at 1440.

       Above 1440 the padding swallows ALL the extra width, in the design's
       own 60.2/39.8 split of the 221px it leaves over, so the 1219px card row
       never grows past the width it was drawn at. A 381px card carrying a
       147px cut-out portrait is a drawn object, not a fluid one: scaled up it
       lifts the portrait clear of its own header and empties the middle of
       the card. 221 x 0.60181 = 133 and 221 x 0.39819 = 88, so 1440 is
       unchanged. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 9.2361vw, 133px))
      calc(var(--stage-x) + clamp(1.25rem, 6.1111vw, 88px));
  }

  .testimonials__title {
    /* #1b1d36, the near-duplicate of dark-navy the design uses for headings
       in this half of the page. Not --color-heading. */
    color: var(--color-heading-alt);
    max-inline-size: 881px;   /* the Figma text box */
  }

  /* Figma runs the gradient across the whole 881px text box, not across the
     phrase: "glücklichen Kunden" ends around 62% of the sweep, which is why
     it lands on blue-violet rather than #9745ff. 881/56 = 15.7321em keeps
     that ratio while the heading scales. */
  .testimonials__title-accent {
    background-size: 15.7321em 100%;
    background-repeat: no-repeat;
  }

  .testimonials__list {
    display: grid;
    /* Three 381px columns at 1440. auto-fit drops to two then one on its own;
       min() keeps the track from outgrowing a 320px viewport. The card's own
       max-inline-size, not the track's, is what stops a card from being drawn
       larger than it was designed — capping the track here instead would make
       auto-fit count columns against 381px and drop to one column at 768.
       38/1440 = 2.6389vw → exactly 38px at 1440. */
    grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
    gap: clamp(1.25rem, 2.6389vw, 38px);
    margin-block-start: 60px;   /* h2 box ends 5139, cards start 5199 */
  }

  .testimonials__card {
    display: flex;
    flex-direction: column;
    /* Never wider than drawn. Binds only in the two-column row, where the
       tracks are wider than 381; the card stays start-aligned in its track so
       the first one keeps its left edge under the h2. */
    max-inline-size: 381px;
    /* The head's composition depends on the card's own width, never the
       viewport's — the same card appears in a one, two or three column row.
       Queried below. */
    container-type: inline-size;
    /* Per-card portrait framing, measured. The three cutouts are scaled and
       inset differently in Figma; percentages of the 381px card keep each
       one's proportion as the column narrows.
       147/381, 14/381. Card fill defaults to the glass value. */
    --portrait-w: 38.5827%;
    --portrait-x: 3.6745%;
    --card-fill: var(--color-glass-lighter);
  }

  .testimonials__card:nth-child(2) {
    --portrait-w: 40.4199%;   /* 154/381 */
    --portrait-x: 3.4121%;    /*  13/381 */
  }

  .testimonials__card:nth-child(3) {
    --portrait-w: 42.2572%;   /* 161/381 */
    --portrait-x: 0;          /* flush with the card edge, unlike the others */
    --card-fill: var(--color-glass-strong);
  }

  /* ---------------------------------------------------------------- head */

  .testimonials__head {
    position: relative;
    z-index: 1;               /* painted over the body rect it overlaps */
    /* A minimum, not a fixed height: below 1440 the quote takes a fourth
       line and the bar has to grow rather than clip. */
    min-block-size: 134px;
    padding-block-start: 24px;
    /* 147/381 to the text column, 42/381 to the right edge, leaving the
       measured 192px quote box at 1440. */
    padding-inline: 38.5827% 11.0236%;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    color: var(--color-on-dark);
    /* See the header note at the top of this file. Layer order is
       top-most first, exactly as fitted. */
    background-color: rgb(57 51 103);
    background-image:
      radial-gradient(ellipse 30.1% 60.2% at 86.2% 10.8%, rgb(208 178 137), rgb(208 178 137 / 0)),
      radial-gradient(ellipse 35% 181% at 85.5% 84.1%, rgb(20 27 53), rgb(20 27 53 / 0)),
      radial-gradient(ellipse 18.2% 141.3% at 3% -24.4%, rgb(24 29 49), rgb(24 29 49 / 0)),
      radial-gradient(ellipse 63.7% 99% at 79% 49.3%, rgb(187 137 196), rgb(187 137 196 / 0));
  }

  /* Bottom-aligned to the header and taller than it, so the head overhangs
     the bar by 13 / 20 / 28px. Deliberately not clipped: that overhang is
     the effect. Height follows the width/height attributes. */
  .testimonials__portrait {
    position: absolute;
    inset-block-end: 0;
    inset-inline-start: var(--portrait-x);
    inline-size: var(--portrait-w);
    max-inline-size: none;
    object-fit: cover;
  }

  .testimonials__quote {
    font-size: var(--text-sm);
    line-height: var(--leading-body);
  }

  .testimonials__rating {
    display: flex;
    gap: 5px;                    /* 33px pitch − 28px glyph */
    /* Quote box ends at +81, stars start at +87. */
    margin-block-start: 6px;
  }

  /* Drawn at 28px, which is what it stays at 1440. Allowed to shrink below
     that rather than break out of a narrow card; aspect-ratio keeps it square
     against the reset's `img { height: auto }`. */
  .testimonials__star {
    flex: 0 1 28px;
    block-size: auto;
    aspect-ratio: 1;
    min-inline-size: 0;
  }

  /* ---------------------------------------------------------------- body */

  .testimonials__body {
    display: flex;
    flex-direction: column;
    flex: 1;                     /* fill the grid row when a sibling is taller */
    /* The rect starts 13px below the card top, so 121px of it are behind the
       134px header. Pulling it up by that amount reproduces the overlap and
       keeps the card's own height honest: 134 − 121 + 471 = 484. */
    margin-block-start: -121px;
    min-block-size: 471px;
    /* 153 from the rect's own top puts the review 32px below the header. */
    padding-block: 153px 31px;
    padding-inline: 28px 22px;   /* leaves the measured 331px text column */
    border-radius: var(--radius-md);
    background-color: var(--card-fill);
    box-shadow: var(--shadow-card);
  }

  .testimonials__review {
    /* .text-quote supplies Poppins 200 italic — the weight the Stilesheet
       does not document but this section is built on. */
    font-size: var(--text-sm);
    line-height: var(--leading-body);
    color: var(--color-text);
    /* The author block is bottom-anchored in Figma (y=5614 on all three
       cards, whatever the review length), so the slack goes here. */
    margin-block-end: auto;
  }

  .testimonials__author {
    font-size: var(--text-sm);
    line-height: var(--leading-body);
    color: var(--color-text);
  }

  .testimonials__name,
  .testimonials__role {
    display: block;
  }

  /* The role's gradient spans its 331px text box in Figma, which a block-level
     span reproduces for free: the longer role reads more violet than the
     shorter one, exactly as drawn. */

  /* Under 300px of card the drawn 38.6% portrait column leaves the quote too
     little measure and German compounds start breaking mid-word. The portrait
     gives some of it back. Only a phone in one column gets here — at 1440 the
     card is 381 and in the two-column row it is still 381. */
  @container (width < 300px) {
    .testimonials__head {
      padding-inline: 31% 6%;
    }

    .testimonials__portrait {
      inline-size: 29%;
    }
  }

  /* ------------------------------------------------------------------
     There is no comp outside 1440. Decisions made here, measured at 320,
     768, 900, 1024, 1200, 1440, 1920 and 2560:
       - the row is auto-fit, so 3 → 2 → 1 columns happen on measure, with
         no viewport breakpoint to maintain
       - the card is never wider than the 381 it was drawn at, and above
         1440 the whole band stops growing, so the card is pixel-identical
         to Figma at every width from 500 up
       - header paddings and the portrait are percentages of the card, so
         the head composition holds proportionally when a three-column row
         squeezes the card below 381 (portrait 122 at 1200, no overhang)
       - both card heights are minimums, so longer copy grows the card
         instead of overflowing it
       - the stars are allowed to shrink below their drawn 28px rather than
         push out of a narrow card
       - the 47/77 band padding is already small and stays as measured
     ------------------------------------------------------------------ */
}


/* sections/contact-cta/section.css */
/* ==========================================================================
   Section: contact-cta
   Figma band y 5760..6632 in a 1440 frame. The extractor's band stops at 6400,
   but the grey rect this section owns runs to 6632, which is the bottom edge of
   the page frame. There is no footer node anywhere in the file, so the section
   owns the page's last 872px outright.

   Measured anchors, all reproduced exactly at 1440:
     content inset      x = 105 left, 114 right  (1221 between = banner width)
     h2                 x = 105, y = 5838, w = 881, 56/56, #1b1d36
                        "Sie erreichbar!" gradient-filled, break after "für"
     banner             x = 105, y = 6020, 1221 x 286, radius 15, --shadow-card
     banner title       x = 179, y = 6092, 34/36, white
     banner text        x = 179, y = 6147, 17/19, white
     pill 1             x = 179, y = 6191, 195 x 37, radius 55, gradient @ 70%
     pill 2             x = 392, y = 6191, 159 x 37   (18px gap)
     portrait back      x = 726, y = 5819, 347 x 487  (bottom = banner bottom)
     portrait front     x = 811, y = 5807, 606 x 499  (bottom = banner bottom)
     grey band          x = -53, y = 6163, 1503 x 469, radius 15, #f2f2f2
     3D glyph           bbox x = 746.91, y = 5002.91, 1291.083 square
                        (a 927px square rotated 125.01deg)
   ========================================================================== */

@layer sections {
  .contact-cta {
    position: relative;
    /* Deliberately NOT a stacking context, and deliberately no background.

       The 3D glyph bleeds 757px up into `testimonials` and must paint BEHIND
       that section's cards, as drawn. That only works if it can reach the
       page's negative z layer, which `isolation: isolate` would seal off and
       a background on this root would paint over. The body's white shows
       through instead.

       So: never add isolation, z-index, transform, filter or opacity to this
       element. Any of them re-traps the glyph and it jumps back in front of
       the testimonial text. */
    /* Top: the h2 text box sits 78px below the band top (5838 - 5760).
       Bottom: the banner ends at 6306 and the page frame at 6632, so 326px of
       empty grey follows it. That gap is reproduced, not trimmed — it is the
       missing footer's footprint and it is flagged in the report. */
    padding-block: 78px 326px;
    /* Asymmetric by design: 105 left, 114 right, which leaves exactly the
       1221px the banner is drawn at. 105/1440 = 7.292vw, 114/1440 = 7.917vw. */
    padding-inline:
      calc(var(--stage-x) + clamp(1.25rem, 7.292vw, 105px))
      calc(var(--stage-x) + clamp(1.25rem, 7.917vw, 114px));
  }

  /* ------------------------------------------------------------------
     Background layers
     ------------------------------------------------------------------ */

  .contact-cta__band {
    position: absolute;
    /* Behind everything in the section but still in front of the section's own
       white background: negative z-index paints after the stacking context's
       background, not before it. */
    z-index: -1;
    /* Anchored to the BOTTOM, not the top. The band ends on the page frame's
       bottom edge, and 469 - 326 puts its top edge 143px below the banner's
       top edge at every width, which is the crossing the design draws. */
    inset-block-end: 0;
    block-size: 469px;
    /* x = -53 .. 1450 in a 1440 frame, kept proportional so the bleed survives
       narrower viewports. body has overflow-x: clip. */
    inset-inline-start: -3.681%;
    inline-size: 104.375%;
    border-radius: var(--radius-md);
    background-color: var(--color-surface-muted);
  }

  .contact-cta__shape {
    /* Its bounding box starts 757px above this section, so the glyph bleeds up
       into whichever section precedes this one. The overhang is in vw
       (757/1440 = 52.569vw) so it stays proportional below 1440, and capped in
       px above it so the bleed never grows past what the comp draws. */
    position: absolute;
    /* Negative layer: paints above the page background but behind the block
       content of every section, which is what puts it behind the testimonial
       cards as drawn. */
    z-index: -1;
    inset-block-start: max(-757px, -52.569vw);
    inset-inline-start: 51.869%;                 /* 746.91 / 1440 */
    inline-size: min(89.658%, 1291.083px);       /* 1291.083 / 1440 */
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
  }

  .contact-cta__shape img {
    inline-size: 71.8%;            /* 927 / 1291.083 */
    block-size: auto;
    aspect-ratio: 1;
    transform: rotate(125.01deg);
  }

  /* ------------------------------------------------------------------
     Heading
     ------------------------------------------------------------------ */

  .contact-cta__title {
    position: relative;
    z-index: 1;
    /* Figma text node 190:60. #1b1d36, not the dark-navy every other heading
       uses — a near-duplicate that CONVENTIONS records as a design accident.
       Reproduced. */
    max-inline-size: 881px;
    color: var(--color-heading-alt);
  }

  .contact-cta__title-accent {
    /* Figma runs the gradient across the WHOLE 881px text node, not across the
       coloured run: measured on the render, the green channel at x=490 is 147,
       which is the 881px ramp at 43.7% and not the ~405px run's own ramp. The
       run starts on the text box's left edge, so sizing its background to
       881px reproduces the exact fill. */
    background-size: 881px 100%;
  }

  /* ------------------------------------------------------------------
     Banner
     ------------------------------------------------------------------ */

  .contact-cta__banner {
    position: relative;
    /* h2 bottom is 78 + 2 x 56 = 190; the banner starts at 260. */
    margin-block-start: 70px;
    /* 72 top / 78 bottom and 74 either side reproduce 286px of height and put
       the content column's left edge on x=179. */
    padding-block: 72px 78px;
    padding-inline: clamp(1.25rem, 5.139vw, 74px);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
    color: var(--color-on-dark);
    /* The fill is NOT a left-to-right gradient, and Figma's own code export
       emits no fill for this node at all — see the report. It is a 2D field,
       so it is rebuilt from the reference render as three layers:

         1. base ramp (90deg), sampled along the banner's bottom edge, where
            the other two layers have died out;
         2. a dark blob over the upper-left quadrant, which is what pulls the
            ramp down to #171a31 at 15% along the TOP edge while the bottom
            edge stays flat at #2f2f60;
         3. a warm highlight bleeding in from the top-right, where a studio
            backdrop lifts behind the portraits.

       Every stop is a measured value except between 53% and 90%, which the
       portraits cover completely and which is interpolated. Residual error is
       under 8 levels everywhere except a patch around x=300 y=440 (~20 in the
       blue channel), and that patch sits behind the first pill. */
    /* The banner fill. Node 190:43 exports as radius + shadow only: the fill
       is a 2D field Figma's codegen cannot express, so it arrived with no
       colour at all.

       This is the real thing, exported from the node at 2x and cropped to the
       drawn 1221 x 286 (the raw export is 1229 x 294 because it includes the
       drop shadow's 4px blur and 4px y-offset).

       It replaces three measured gradient layers whose x 53-90% band was pure
       interpolation, because the portraits cover it completely and there was
       nothing to sample there. */
    background-image: url("img/cta-banner-fill.png");
    background-size: 100% 100%;
    background-position: left top;
    background-repeat: no-repeat;
  }

  .contact-cta__banner-body {
    position: relative;
    z-index: 1;
    /* 498px at 1440 (the widest of the two Figma text boxes), expressed as a
       fraction of the banner's content box (1073px) so that as the banner
       narrows the copy shrinks with it and never slides under the portraits,
       whose left edge sits at 50.86% of the banner. */
    max-inline-size: 46.412%;
  }

  .contact-cta__banner-title {
    /* Figma centres this text in a 143..641 box, which happens to land its left
       edge on 179 — the same x as the line below it and the first pill.
       Reproduced as left-aligned from 179 so a longer CMS string cannot drift
       off that edge. */
    color: var(--color-on-dark);
  }

  .contact-cta__banner-text {
    /* Title line box ends at 6128, this one starts at 6147. */
    margin-block-start: 19px;
    color: var(--color-on-dark);
  }

  /* ------------------------------------------------------------------
     The two CTAs. Same pill treatment as the hero: the gradient sits at 70%
     opacity in Figma and the label does not, so the fill lives on a
     pseudo-element and the text stays fully opaque.
     ------------------------------------------------------------------ */

  .contact-cta__actions {
    display: flex;
    flex-wrap: wrap;
    /* 392 - (179 + 195) */
    gap: 18px;
    /* Text line box ends at 6166, the pills start at 6191. */
    margin-block-start: 25px;
  }

  .contact-cta__cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    block-size: 37px;
    /* Figma draws 195px and 159px. Measured in Poppins Medium 17 the two
       labels set at 168.25px and 132.84px, so both pills are their label plus
       13.25px of air on each side and one padding rule reproduces both widths
       to within a third of a pixel. */
    padding-inline: 13.25px;
    border-radius: var(--radius-2xl);
    color: var(--color-on-dark);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-button);
    white-space: nowrap;
    text-decoration: none;
    isolation: isolate;
  }

  .contact-cta__cta::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    background-image: var(--gradient-brand);
    /* The shadow is on the same node as the 70% fill in Figma, so it is faded
       with it rather than drawn at full strength. */
    box-shadow: var(--shadow-card);
    opacity: 0.7;
    transition: opacity var(--duration-fast) var(--ease-out);
  }

  .contact-cta__cta:hover::before,
  .contact-cta__cta:focus-visible::before {
    opacity: 1;
  }

  /* ------------------------------------------------------------------
     Portraits. Both are transparent cut-outs whose bottom edge is exactly the
     banner's bottom edge (6306), so they are anchored to it and overhang its
     TOP by 201px and 213px — the overlap the design is built around.
     ------------------------------------------------------------------ */

  .contact-cta__portrait {
    position: absolute;
    inset-block-end: 0;
    max-inline-size: none;
    block-size: auto;
    object-fit: cover;
    pointer-events: none;
  }

  .contact-cta__portrait-back {
    /* tobias-poperl-hochauflösend 1 (190:242), 726..1073. Anchored to the
       banner's RIGHT edge (1326 - 1073 = 253) rather than its left, because
       above 1440 the banner keeps widening while the pair must stay at its
       end. The px terms freeze both portraits at design size and design
       spacing past 1440, so their overlap survives; below 1440 the percentage
       wins and the whole group scales with the banner. The box aspect matches
       the source, so cover is a straight fit. */
    inset-inline-end: min(20.721%, 253px);   /* 253 / 1221 */
    inline-size: min(28.420%, 347px);        /* 347 / 1221 */
    aspect-ratio: 347 / 487;
  }

  .contact-cta__portrait-front {
    /* IMG_0696 feed 1 (190:243), 811..1417 — its box overhangs the banner's
       right edge by 91px, which is why the inset is negative. Figma scales the
       fill to 152.09% height and offsets it -16.43%, i.e. cover with the crop
       taken 31.55% down the source instead of centred:
       82 / (758.9 - 499) = 0.3155. */
    inset-inline-end: max(-7.453%, -91px);   /* -91 / 1221 */
    inline-size: min(49.632%, 606px);        /* 606 / 1221 */
    aspect-ratio: 606 / 499;
    object-position: 50% 31.55%;
  }

  /* ------------------------------------------------------------------
     Below 1440 there is no comp. Decisions made here, listed in the report:

       - everything above scales as a fraction of the banner, so the whole
         composition (text column, portrait overlap, bleed) holds down to
         roughly 900px without a single override;
       - under 900px the copy can no longer live in 41% of the banner while
         two people occupy the rest, so the portraits are dropped and the text
         takes the full width. A mobile comp is needed to do better than that;
       - the empty grey below the banner is the missing footer's space. It is
         reproduced at 326px on desktop and cut to 120px on small screens,
         where a third of a phone screen of nothing reads as a broken page.
     ------------------------------------------------------------------ */

  @media (width < 900px) {
    .contact-cta__portrait {
      display: none;
    }

    .contact-cta__banner-body {
      max-inline-size: none;
    }
  }

  @media (width < 768px) {
    .contact-cta {
      padding-block: 48px 120px;
    }

    .contact-cta__band {
      /* Keeps the banner straddling the edge the same way: 143px of the band
         overlaps the banner, the rest is the space below it. */
      block-size: 263px;
    }

    .contact-cta__banner {
      margin-block-start: 40px;
      padding-block: 40px;
    }

    .contact-cta__cta {
      /* A pill that has to wrap its label is worse than one that grows. */
      block-size: auto;
      padding-block: 10px;
      white-space: normal;
      text-align: center;
    }
  }
}

@layer sections {
  /* ------------------------------------------------------------------
     variant: plain — the interior-page version of this banner.

     Same block, no portraits, and the design indents the heading and the
     banner's own content slightly more than on the Startseite. Measured off
     the Agentur frame (213:32): heading x = 133 against the banner's 107,
     and the banner's copy starts 93 inside it rather than 74.
     ------------------------------------------------------------------ */

  .contact-cta[data-variant="plain"] .contact-cta__title {
    margin-inline-start: 26px;
    /* Figma allocates the same 117px text box on both pages, but this page's
       title is one line where the Startseite's is two. Without reserving the
       drawn height the banner rides up 56px and the section ends short. */
    min-block-size: 112px;
  }

  .contact-cta[data-variant="plain"] .contact-cta__banner-body {
    /* 93 - 74 = 19 more than the default inset. */
    margin-inline-start: 19px;
    /* Without the portraits the copy is free to use the full banner. */
    max-inline-size: none;
  }
}

@layer sections {
  /* ------------------------------------------------------------------
     variant: solo — one portrait instead of two, and a shallower grey band.

     Measured off the Geld-Zurück-Garantie frame (217:25): banner x = 122
     w = 1208, single portrait at x = 841 (so 142 in from the banner's right
     edge against the Startseite's 253), grey band 276 tall at y = 2790
     against the Startseite's 469.
     ------------------------------------------------------------------ */

  .contact-cta[data-variant="solo"] .contact-cta__title {
    /* h2 at 2431 against a section opening at 2360. */
    min-block-size: 112px;
  }

  .contact-cta[data-variant="solo"] .contact-cta__banner {
    /* Title closes at 2543, banner opens at 2646. */
    margin-block-start: 103px;
  }

  .contact-cta[data-variant="solo"] .contact-cta__portrait-back {
    inset-inline-end: min(11.755%, 142px);   /* 142 / 1208 */
  }

  .contact-cta[data-variant="solo"] .contact-cta__band {
    /* 2790 from a section opening at 2360, and 276 tall rather than 469. */
    inset-block-start: 430px;
    block-size: 276px;
  }
}

@layer sections {
  /* ------------------------------------------------------------------
     variant: feature — the front portrait only, on a wider banner.

     Measured off the Blog frame (218:358): banner x = 130 w = 1223, the
     single portrait at x = 700 (47 in from the banner's right edge against
     the Startseite's -91, where it overhangs), heading at 136 / 2303.
     ------------------------------------------------------------------ */

  .contact-cta[data-variant="feature"] .contact-cta__title {
    margin-inline-start: 6px;
    min-block-size: 112px;
  }

  .contact-cta[data-variant="feature"] .contact-cta__banner {
    /* Title closes at 2415, banner opens at 2471. */
    margin-block-start: 56px;
  }

  .contact-cta[data-variant="feature"] .contact-cta__portrait-front {
    inset-inline-end: min(3.843%, 47px);   /* 47 / 1223 */
  }

  .contact-cta[data-variant="feature"] .contact-cta__band {
    inset-block-start: 390px;   /* 2615 from a section opening at 2225 */
  }
}
