@layer hero {
  /* ── Hero ── */
  .hero {
    display: flex;
    align-items: center;
    min-height: calc(100vh - 68px);
    /* Extra bottom padding reserves the .hero-marquee strip's height. The
       strip is absolutely positioned, so without this the centred content
       would sit under it on short viewports. */
    padding-block: clamp(64px, 6vw, 100px) calc(clamp(64px, 6vw, 100px) + 50px);
    position: relative;
    overflow: hidden;
    /* Must match the hero video's own sky tone exactly — this is what's visible
       before the video fades in (or permanently, if it fails to load), so any
       mismatch is a flash or a seam. */
    background: #5F69C8;
  }
  .hero::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 48px;
    /* Fade to the BODY's exact brand purple — the section below the hero is
       transparent over the body, so matching that colour (not the hero's own
       slightly-brighter #5456EB) removes the hard seam LINE. Eased, so the bright
       low clouds dim gently into it instead of hitting a band. */
    background: linear-gradient(to bottom,
      transparent 0%,
      oklch(53.6% 0.145 276 / 0.55) 55%,
      oklch(53.6% 0.145 276) 100%);
    pointer-events: none;
    z-index: 0;
  }
  /* Home cloud hero: a crisp separator LINE at the bottom instead of the fade —
     the clouds meet the section below at a clean edge. z2 clears the video
     (z0) + scrim (z1). Other pages share `.hero` (e.g. Browse partners) WITHOUT
     those layers and keep the fade above at z0 (at z2 it covered their search bar). */
  .hero:has(.hero-video)::after {
    z-index: 2;
    height: 3px;
    background: oklch(100% 0 0 / 0.3);
  }

  /* ── Cloud wallpaper: video, over a still that covers its loading gap ──
     The video is visible whenever it has a decodable frame, PLAYING OR NOT —
     a paused video simply shows its own first frame, which is exactly the
     picture we want anyway. Doing it this way rather than swapping in the
     still image when playback stops is deliberate: Safari colour-manages
     video (BT.709, lifted toward the display's P3 gamut) differently from an
     sRGB JPEG, so the two layers do NOT render identically on a Mac even
     though the files match to within ~1.5/255 — the image read as very
     slightly shadowed next to the video. Letting one element paint both
     states makes that mismatch impossible by construction.

     The still underneath is now only a loading placeholder: it covers the
     moment before the video's first frame decodes, so the hero never pops
     from flat purple to clouds. Same framing (cover, centre-bottom) so the
     handover is invisible.

     Note the markup carries NO `autoplay` attribute — that attribute is what
     makes WebKit run its own playback attempt at parse time and paint a large
     "tap to play" glyph when Low Power Mode refuses, in a closed UA shadow
     root no stylesheet can reach. js/hero-video.js starts playback
     programmatically instead. */
  .hero-video-still {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image: url('/assets/hero-clouds-poster.jpg');
    background-size: cover;
    background-position: center bottom;
    pointer-events: none;
  }
  .hero-video {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center bottom;
    display: block;
    pointer-events: none;
    opacity: 0;
    transition: opacity .9s ease;
  }
  /* Set once the video has a decodable frame — playing or paused, both show
     correctly, so this is a loading gate, not a play-state gate. */
  .hero-video.is-ready { opacity: 1; }
  /* Scrim: darken the text column so the white headline/subtitle stay legible
     over bright cloud-lit areas. Weighted to the left (where the copy sits) and
     fading out to the right so the clouds read cleanly there. #292846 = the
     brand's dark ink, so this deepens rather than greys the purple. */
  .hero-scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background:
      /* Symmetric now that the copy is centred — this used to ramp down from
         left to right, which put the strongest shading where the text no
         longer is and left the right half of every line unprotected. */
      linear-gradient(90deg,
        oklch(24% 0.06 285 / 0.02) 0%,
        oklch(24% 0.06 285 / 0.13) 28%,
        oklch(24% 0.06 285 / 0.15) 50%,
        oklch(24% 0.06 285 / 0.13) 72%,
        oklch(24% 0.06 285 / 0.02) 100%),
      linear-gradient(180deg,
        oklch(24% 0.06 285 / 0.04) 0%,
        oklch(24% 0.06 285 / 0) 46%);
    opacity: 0;
    transition: opacity .9s ease;
  }
  .hero-video-active .hero-scrim { opacity: 1; }

  /* Phone portrait: the top-left corner (both gradients above overlap there)
     reads a bit heavy once the cloud lift moves clouds up under it — ease it
     off slightly, same phone-only bucket as the cloud height fix above. */
  @media (max-aspect-ratio: 3/5) {
    .hero-scrim {
      background:
        linear-gradient(90deg,
          oklch(24% 0.06 285 / 0.06) 0%,
          oklch(24% 0.06 285 / 0.045) 36%,
          oklch(24% 0.06 285 / 0.015) 66%,
          oklch(24% 0.06 285 / 0) 100%),
        linear-gradient(180deg,
          oklch(24% 0.06 285 / 0.01) 0%,
          oklch(24% 0.06 285 / 0) 46%);
    }
  }
  /* Belt-and-suspenders legibility over the variable cloud background — a soft
     shadow only while the animated layer is live (keeps the flat-purple fallback
     crisp and shadow-free). */
  .hero-video-active .hero-content h1,
  .hero-video-active .hero-content .hero-subtitle-custom,
  .hero-video-active .hero-content .section-label {
    text-shadow: 0 1px 12px oklch(20% 0.05 285 / 0.26);
  }

  /* Centred, not left-aligned: this hero's job is the search field, and a
     search box reads as the page's one action when it sits on the centre line
     with everything stacked symmetrically above it. Left-aligned it shared the
     row with half a viewport of empty cloud. Everything inside that has its own
     max-width (the subtitle, the search field) therefore needs margin-inline
     auto too, or it stays pinned left inside a centred column. */
  .hero-content {
    max-width: 780px;
    margin-inline: auto;
    text-align: center;
    position: relative;
    z-index: 3; /* above the video (0), scrim (1) and bottom blend (2) */
  }
  .hero-content .section-label { margin-bottom: 20px; }
  .hero-content h1 {
    margin-bottom: 24px;
    font-size: clamp(32px, 4.2vw, 60px);
    line-height: 1.08;
    letter-spacing: -0.025em;
  }
  .hero-content .hero-title-prefix {
    color: #fff;
  }
  /* Keyword sits on its own line. Left-aligned its width could change freely,
     but centred, a narrower word re-centres the whole line — so the swap would
     slide it sideways. js/… in index.html measures every keyword once the font
     is ready and pins --hero-rotate-w to the widest, which holds the line still
     in BOTH motion modes (the fade only hides the reflow when animations are
     on). Falls back to auto width if that never runs. nowrap keeps multi-word
     labels on one line. */
  .hero-rotate {
    display: inline-block;
    white-space: nowrap;
    min-width: var(--hero-rotate-w, auto);
    /* Centres the word within the reserved box on the rare frame where its
       real rendered width (glyphs + the highlight plate's padding, measured
       by index.html's reserveRotateWidth) comes out fractionally over the
       reservation — sub-pixel font rounding, not a real mismatch. Left as a
       safety net; text-align doesn't affect the equal-width case. */
    text-align: center;
  }
  /* The rotating keyword. Solid white to match the static prefix — the animated
     shimmer gradient (background-clip:text) that used to live here was removed at
     the user's request (it read as odd over the new animated wallpaper). Keep the
     fade/slide transition that plays on each word swap. */
  /* The rotating word is the only part of the headline that changes, so it
     should read as the variable — plain white-on-white next to the static
     prefix made it look like an unremarkable line two.

     Colour is the whole treatment now. It was mint originally, the only green
     anywhere on the site; oklch(92% 0.04 276) is the light lavender already
     used for the search-category tag chips and dropdown hovers, so the accent
     stays inside the site's actual (purple) palette. Subtler than the mint —
     close to the white prefix by design, so it reads as "same family, slightly
     emphasised" rather than a clashing highlight.

     Three decorations have been tried behind this word and all three are gone:
     an animated shimmer gradient (background-clip:text) read as odd over the
     moving clouds, a frosted plate plus a thick underline read as a highlighter
     mark slapped on top of the text, and the hairline rule that replaced them
     read as the stock underlined-rotating-keyword every AI-built landing page
     ships. Nothing boxed, nothing filled, nothing underlined. If a fourth is
     ever proposed, note that the pattern so far is that the word looks better
     without it. */
  .hero-rotate-word {
    display: inline-block;
    position: relative;
    color: oklch(92% 0.04 276);
    text-shadow: 0 2px 14px oklch(24% 0.06 285 / 0.5);
    transition: opacity .3s ease, transform .3s ease;
  }
  .hero-rotate-word.is-swapping {
    opacity: 0;
    transform: translateY(8px);
  }
  .hero-content .hero-subtitle-custom {
    margin-bottom: 40px;
    font-size: clamp(17px, 2vw, 20px);
    line-height: 1.7;
    max-width: 60ch;
    margin-inline: auto;
    opacity: 0.92; /* 0.85 sat at 4.49:1; 0.92 clears AA body text */
  }
  .hero-content .hero-search { margin-bottom: 28px; }
  .hero-search {
    display: flex;
    gap: 0;
    max-width: 600px;
    margin-inline: auto;
    text-align: left;   /* the input inherits text-align from .hero-content */
    background: var(--surface);
    border: 1.5px solid var(--border);
    border-radius: 14px;
    overflow: hidden;
    transition: border-color .2s, box-shadow .2s;
    box-shadow: 0 2px 8px oklch(0% 0 0 / 0.06);
  }
  .hero-search:focus-within {
    border-color: #5962C0;
    box-shadow: 0 0 0 4px oklch(92% 0.04 276 / 0.25), 0 2px 8px oklch(0% 0 0 / 0.06);
  }
  .hero-search input {
    flex: 1;
    min-width: 0;
    border: none;
    outline: none;
    padding: 18px 24px;
    font-size: 15px;
    background: transparent;
    color: var(--fg);
    /* Safety net for the placeholder: on the narrowest phones even the
       1-tag "Populært: “X”" text can outrun the field's width. Ellipsis
       reads far better than the hard mid-word cut a plain overflow clip
       gives inputs by default. */
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .hero-search input::placeholder { color: oklch(70% 0.01 276); }
  .hero-search button {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 18px 28px;
    background: #292846;
    color: #fff;
    border: none;
    font-weight: 500;
    font-size: 15px;
    cursor: pointer;
    transition: background .2s;
  }
  .hero-search button:hover,
  .hero-search button:active { background: #56556f; color: #fff; }
  /* "Browse partners" — a second path into search besides the field above it,
     for a visitor who wants to look around rather than search a specific term.
     Filled in the same light lavender as the rotating keyword
     (oklch(92% 0.04 276), see .hero-rotate-word above — that in turn is the
     tint already used for the search-category tag chips, not a one-off), so
     the CTA visually echoes the word it's introducing more of while staying
     inside the site's actual purple palette rather than the mint green that
     used to sit here. It's a light fill, so like the mint before it, it needs
     dark text rather than the ink-filled search button's white. */
  .hero-browse-cta {
    margin-top: 20px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 12px 24px;
    border-radius: 999px;
    background: oklch(92% 0.04 276);
    color: #292846;
    font-size: 14px;
    font-weight: 500;
    text-decoration: none;
    border: 1.5px solid oklch(92% 0.04 276);
    box-shadow: 0 2px 10px oklch(20% 0.05 285 / 0.18);
    transition: background .2s, border-color .2s, transform .2s;
  }
  .hero-browse-cta:visited { color: #292846; }
  .hero-browse-cta:hover {
    background: #fff;
    border-color: #fff;
    transform: translateY(-1px);
  }

  /* ── Hero trust marquee ────────────────────────────────────────────────────
     Replaces the frosted "Ingen kommission · … · GDPR" pill that used to sit
     between the search field and the tag row. That pill had to fit on one
     line, which capped it at three promises and put it in direct competition
     with the search box; pinning a slow rail to the foot of the hero instead
     gives the copy room to grow and leaves the search field the only thing in
     the middle of the screen.

     Sits ABOVE .hero::after (z-index 0), the gradient that fades the clouds
     into the body purple, so the strip reads as the hero's own base rather
     than something floating in the fade. The hero is already
     position:relative + overflow:hidden, so this needs neither. */
  .hero-marquee {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    z-index: 1;
    padding-block: 13px;
    overflow: hidden;
    background: oklch(24% 0.06 285 / 0.34);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid oklch(100% 0 0 / 0.14);
    /* Soften both ends so items arrive and leave instead of being sliced off
       at a hard edge. */
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  }
  /* width:max-content so the track is as wide as BOTH groups laid end to end
     — without it the flex track is clamped to the hero's width, the groups
     shrink, and the -50% travel no longer equals one group. */
  .hero-marquee-track {
    display: flex;
    width: max-content;
    animation: hero-marquee-scroll 54s linear infinite;
  }
  /* Hover/tap slow-down is driven from JS (index.html), via
     Animation.playbackRate on this element's running CSS animation rather
     than a CSS :hover rule. Tried animation-duration first: the CSS timing
     model recomputes PROGRESS as currentTime / duration when duration
     changes, so lengthening it mid-flight snaps the track backward toward
     its start instead of continuing from where it was (verified: a running
     animation jumped ~100px backward the instant duration went 44s -> 132s).
     playbackRate scales how fast currentTime advances from THIS point
     forward and leaves position untouched — the actually-smooth primitive
     for "same animation, different speed, right now".

     This duration was 44s; bumped to 54s for a calmer default pace. The
     hover/tap rate in index.html is derived from it precisely so the actual
     hover/tap SPEED (132s effective) is unchanged — only the not-hovering
     speed got slower. Change HOVER_EFFECTIVE_S there if that's ever meant
     to move too, and keep BASE_DURATION_S there equal to this number. */
  .hero-marquee-group { display: flex; align-items: center; flex: none; }
  .hero-marquee-item {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 0 26px;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
    color: oklch(100% 0 0 / 0.93);
  }
  /* Same mint as the old pill's single check — the one non-white accent in the
     hero, and what keeps six repeated ticks from reading as grey noise. */
  .hero-marquee-item svg {
    width: 15px;
    height: 15px;
    flex: none;
    color: oklch(84% 0.16 165);
  }
  /* The track is FOUR identical groups, so -50% travels exactly two of them
     and lands copy 3 where copy 1 began — no seam, no jump. The travel must
     also be at least as wide as the strip or the track's tail clears the right
     edge mid-loop; see the markup comment in index.html for the numbers. */
  @keyframes hero-marquee-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }
  /* Manual "Reduce animations": system.css collapses every animation to ~0ms,
     which would park the rail at its END state (translated a full group over)
     and show only the clone. Stop it at the start instead and let the row be
     scrolled by hand; the clone is redundant then, so it goes. */
  html.ds-reduce-motion .hero-marquee { overflow-x: auto; }
  html.ds-reduce-motion .hero-marquee-track { animation: none !important; transform: none !important; }
  html.ds-reduce-motion .hero-marquee-group[aria-hidden="true"] { display: none; }

  /* ── Home page rhythm ─────────────────────────────────────────────────────
     The page used to be one unbroken field of #5456E2 from the hero all the way
     to the footer, with white cards floating on it — every section the same
     weight, and the section subtitles (lavender on purple) the weakest text on
     the page. Now the page ALTERNATES: hero (deep) → how it works (light) →
     services (deep) → recently joined (light) → promises (deepest) → the ask
     (light again). Each band is a different value, so scrolling actually
     reads as progress.

     `.section-light` flips the inherited token set, which is all the shared
     components need: .section-label picks up the purple accent instead of white,
     headings and body copy go near-black, and the white cards' own token
     re-scope (system.css) already gives them a visible light border. ── */
  .section-light {
    /* Light, but not white: the cards on these bands ARE white, and they need a
       surface to sit on top of rather than dissolve into. */
    background: #ECECF7;
    color: oklch(18% 0.015 276);
    --fg: oklch(18% 0.015 276);
    --muted: oklch(42% 0.02 276);
    --accent: oklch(50% 0.145 277);
    --border: oklch(88% 0.005 276);
  }
  /* On a light band a white button is invisible — the section's one CTA becomes
     the solid brand button instead. */
  .section-light .btn-white,
  .section-light a.btn-white:visited {
    background: var(--accent-solid);
    color: #fff;
    border-color: var(--accent-solid);
  }
  .section-light .btn-white:hover {
    background: var(--accent-solid-hover);
    border-color: var(--accent-solid-hover);
    color: #fff;
  }

  /* Deep bands aren't flat any more: a soft light source behind the section
     header gives the purple some depth without touching the brand colour.
     On top of that they carry the same top-to-bottom deepening as the promises
     band further down, so a deep band reads as lit rather than as a flat fill.
     Deliberately a shallower, lighter ramp than `.section-band` (57→49% vs its
     45→38%): that strip is the one that has to stop the eye, and it can only
     do that if it stays the darkest thing on the page. */
  /* Chroma reduced to ~0.145 at band scale — see `.band-deep` (css/system.css)
     for the reasoning; these two are the same band and move together. The
     lightness ramp is unchanged, so this stays the shallower/lighter ramp the
     note above describes and `.section-band` stays the darkest thing here. */
  .section-glow {
    position: relative;
    overflow: hidden;
    background: linear-gradient(180deg, oklch(56% 0.145 276), oklch(48% 0.15 277));
  }
  .section-glow > * { position: relative; z-index: 1; }
  .section-glow::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse 900px 460px at 50% 0%,
                oklch(100% 0 0 / 0.11), transparent 70%);
    pointer-events: none;
  }
  /* The subtitle on a deep band was the weakest text on the page — the base
     `p { opacity: .88 }` on top of the lavender --muted. Full-strength near-white
     instead, which is the difference between 4.7:1 and comfortably clear. */
  .section-glow .section-header p { color: oklch(97% 0.01 276); opacity: 1; }

  /* ── How it works ── */
  .steps-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 40px;
    position: relative;
  }
  .steps-grid::before {
    content: '';
    position: absolute;
    top: 64px;
    left: calc(16.66% + 20px);
    right: calc(16.66% + 20px);
    height: 1.5px;
    background: linear-gradient(90deg, oklch(53.6% 0.145 276 / 0.15), oklch(53.6% 0.145 276 / 0.4), oklch(53.6% 0.145 276 / 0.15));
    z-index: 0;
  }
  .step {
    position: relative;
    padding: 48px 32px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    text-align: center;
    z-index: 1;
  }
  .step-number {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: oklch(53.6% 0.145 276);
    color: #fff;
    font-family: var(--font-display);
    font-size: 18px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 20px;
    position: relative;
    z-index: 1;
  }
  .step:nth-child(1) .step-number { animation: pulse-step-1 4s ease-in-out infinite; }
  .step:nth-child(2) .step-number { animation: pulse-step-2 4s ease-in-out infinite; }
  .step:nth-child(3) .step-number { animation: pulse-step-3 4s ease-in-out infinite; }
  .step h3 {
    margin-bottom: var(--space-xs);
    font-size: 20px;
  }
  .step p {
    font-size: 15px;
    color: oklch(50% 0.015 276);
    line-height: 1.6;
  }
  @keyframes pulse-step-1 {
    0%, 100% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
    15% { transform: scale(1.15) translateY(-2px); box-shadow: 0 0 0 14px oklch(53.6% 0.145 276 / 0); background: oklch(60% 0.154 276); }
    33% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
  }
  @keyframes pulse-step-2 {
    0%, 100% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
    33% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
    48% { transform: scale(1.15) translateY(-2px); box-shadow: 0 0 0 14px oklch(53.6% 0.145 276 / 0); background: oklch(60% 0.154 276); }
    66% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
  }
  @keyframes pulse-step-3 {
    0%, 100% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
    66% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
    81% { transform: scale(1.15) translateY(-2px); box-shadow: 0 0 0 14px oklch(53.6% 0.145 276 / 0); background: oklch(60% 0.154 276); }
    100% { transform: scale(1) translateY(0); box-shadow: 0 0 0 0 oklch(53.6% 0.145 276 / 0.4); background: oklch(53.6% 0.145 276); }
  }

  /* ── Categories ── */
  .categories-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-sm);
  }
  .category-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--space-lg) var(--space-md);
    gap: var(--space-sm);
    transition: transform .25s ease, box-shadow .3s ease, border-color .25s ease;
  }
  .category-card:hover {
    border-color: oklch(53.6% 0.145 276 / 0.3);
    transform: translateY(-3px);
    box-shadow: var(--elevation-2);
    background: var(--surface);
  }
  /* These icons are Unicode glyphs, and NONE of them ship in our Inter subset —
     every one falls back to an OS font. That's fine for the purely-geometric
     ones, but any codepoint the OS considers emoji-capable (⚙ U+2699) falls
     through to the colour emoji font on iOS/Android, which ignores `color: #fff`
     and paints a colour cogwheel on the gradient tile. Two guards: the markup
     appends U+FE0E (text-presentation selector) to the gear, and this asks for
     text presentation wherever it's supported. If you add an icon here, check it
     isn't emoji-capable — or give it U+FE0E too. */
  .category-icon {
    width: 56px;
    height: 56px;
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    font-weight: 600;
    color: #fff;
    margin-bottom: 4px;
    transition: transform .25s ease, box-shadow .3s ease;
    font-variant-emoji: text;
  }
  .category-card:hover .category-icon { transform: scale(1.06); }
  .category-card h4 { font-size: 15px; margin-bottom: 4px; letter-spacing: -0.01em; }
  .category-card p { font-size: 13px; color: oklch(55% 0.01 276); }
  /* One brand-purple tile for all eight, not eight unrelated hues. The rest of
     the page is a tight purple/lavender palette with exactly one accent — the
     mint "0%" in the promises band, which only reads as special because
     nothing else competes with it. A rainbow of category tiles was the one
     place that broke that, and eight colours that don't mean anything are
     decoration, not information: the glyph and the label already say which
     card is which. Same gradient as the language-switch veil, so the brand
     purple appears at one angle and one pair of stops everywhere it's used. */
  .category-icon { background: linear-gradient(135deg, oklch(64% 0.135 288), var(--accent-solid)); }

  /* ── Featured agencies ── */
  /* minmax(0, …) rather than a bare 1fr on all three of these: a grid item's
     default min-width is auto, so the track can never shrink below the item's
     min-content — and .agency-name is white-space:nowrap, which reports the
     WHOLE untruncated name as its min-content however much it ellipsizes when
     painted. One long partner name therefore pushed the track wider than the
     grid itself (360.5px inside a 335px grid at 375px wide) and put ~6px of
     horizontal scroll on the page. minmax(0, 1fr) lets the track shrink to the
     space available; min-width:0 on the header column below then does the
     actual truncating. */
  .agencies-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-md);
  }
  .agency-card {
    padding: 28px;
    display: flex;
    flex-direction: column;
    gap: 18px;
  }
  .agency-card:hover {
    transform: none;
    border-color: oklch(53.6% 0.145 276 / 0.22);
    box-shadow: var(--elevation-2);
  }

  .agency-header {
    display: flex;
    align-items: center;
    gap: 14px;
  }
  /* let the name/location column shrink so long text truncates cleanly next to
     the avatar instead of wrapping raggedly. Excludes .agency-avatar-wrap too
     (not just .agency-avatar) — it's a plain div wrapping the avatar for the
     listings badge, and without the exclusion this rule stretched it to
     flex:1, pulling the badge away from the avatar's actual corner. */
  .agency-header > div:not(.agency-avatar):not(.agency-avatar-wrap) { min-width: 0; flex: 1; }
  .agency-avatar {
    width: 48px;
    height: 48px;
    border-radius: 14px;
    background: linear-gradient(135deg, oklch(53.6% 0.145 276 / 0.08), oklch(53.6% 0.145 276 / 0.15));
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 18px;
    color: oklch(53.6% 0.145 276);
    flex-shrink: 0;
  }
  /* Avatar-corner badge: same "has published listings" signal as the profile
     page and search cards, scaled for this grid's avatar. */
  .agency-avatar-wrap { position: relative; flex-shrink: 0; }
  .agency-listings-badge {
    position: absolute; bottom: -2px; right: -2px;
    width: 18px; height: 18px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--accent-solid); color: #fff;
    border: 2px solid var(--surface);
    box-shadow: 0 1px 4px oklch(0% 0 0 / 0.2);
  }
  .agency-name { font-weight: 600; font-size: 16px; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .agency-location { font-size: 13px; color: oklch(50% 0.015 276); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .agency-description {
    font-size: 14px; color: oklch(46% 0.015 276); line-height: 1.6; margin: 0;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  }
  .agency-tags { display: flex; flex-wrap: wrap; gap: 6px; }
  .agency-tags .tag { font-size: 12px; padding: 3px 12px; background: oklch(92% 0.04 276 / 0.3); color: oklch(53.6% 0.145 276); }
  .agency-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding-top: 16px;
    border-top: 1px solid oklch(90% 0.005 276);
    margin-top: auto;
  }
  .agency-footer .btn { white-space: nowrap; }
  .agency-rating { display: flex; align-items: center; gap: 4px; font-size: 14px; font-weight: 600; color: oklch(18% 0.015 276); }
  /* Star visuals come from the shared rounded `.star` in system.css. */

  /* "List your services" CTA button in the featured-agencies join tile. Filled
     brand purple (so it pops on the white card and reads distinct from the
     lighter "View all agencies" button). Hover matches the search cards'
     "Contact" button (.search-card-actions .btn-primary:hover) for consistency;
     the "+" avatar above it nudges up in sympathy. Motion is dropped under
     reduced-motion. */
  .cta-list-btn {
    background: var(--accent-solid);
    color: var(--surface);
    border-color: var(--accent-solid);
    transition: background .2s, border-color .2s, box-shadow .2s ease;
  }
  .cta-list-btn:hover {
    background: var(--accent-solid-hover);
    border-color: var(--accent-solid-hover);
    box-shadow: 0 2px 10px oklch(53.6% 0.145 276 / 0.35);
  }
  .cta-tile .agency-avatar { transition: transform .2s ease; }
  .cta-tile:hover .agency-avatar { transform: translateY(-3px); }
  html.ds-reduce-motion .cta-tile .agency-avatar { transition: background .2s, border-color .2s; }
  html.ds-reduce-motion .cta-list-btn:hover,
  html.ds-reduce-motion .cta-tile:hover .agency-avatar { transform: none; box-shadow: none; }

  /* ── Stats / promises band ── */
  /* Darker than the page purple on purpose: this is the strip that has to stop
     the eye between the partner grid and the closing ask. */
  .section-band {
    background: linear-gradient(180deg, oklch(45% 0.145 278), oklch(38% 0.125 279));
    border-block: 1px solid oklch(100% 0 0 / 0.10);
  }
  .stats-inner {
    display: flex;
    align-items: stretch;
    justify-content: space-between;
    gap: 16px;
    padding: 40px 0;
  }
  /* Each promise is its own card, opaque white against the purple band —
     these are the four key points of the pitch, so they get the strongest
     contrast available on the page rather than blending into the band like
     translucent glass did. */
  .stat {
    flex: 1;
    text-align: center;
    padding: 28px 20px;
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: 0 16px 40px oklch(0% 0 0 / 0.3), 0 4px 12px oklch(0% 0 0 / 0.14);
  }
  .stat-number {
    font-family: var(--font-display);
    font-size: clamp(40px, 5vw, 60px);
    font-weight: 700;
    color: oklch(50% 0.21 277);
    line-height: 1;
    margin-bottom: 6px;
    letter-spacing: -0.03em;
  }
  .stat-label {
    font-size: 14px;
    color: oklch(40% 0.02 276);
    font-weight: 500;
    letter-spacing: 0.01em;
  }
  /* One promise still carries the accent — "no commission" is the reason to
     be here — via a mint-tinted card instead of a white one, rather than a
     coloured number on top of white like the other three. */
  .stat-feature {
    background: oklch(96% 0.045 175);
    box-shadow: 0 16px 40px oklch(0% 0 0 / 0.3), 0 4px 12px oklch(0% 0 0 / 0.14), 0 0 0 2px oklch(75% 0.15 180 / 0.5);
  }
  .stat-feature .stat-number { color: oklch(52% 0.12 175); }
  .stat-feature .stat-label { color: oklch(38% 0.06 175); }

  /* ── CTA ── */
  .cta-tag {
    display: inline-flex;
    align-items: center;
    padding: 8px 22px;
    border-radius: 999px;
    font-size: 15px;
    font-weight: 600;
    background: oklch(100% 0 0 / 0.1);
    color: oklch(100% 0 0 / 0.9);
    border: 1.5px solid oklch(100% 0 0 / 0.15);
    text-decoration: none;
    transition: all .2s;
  }
  .cta-tag:hover {
    background: oklch(100% 0 0 / 0.18);
    border-color: oklch(100% 0 0 / 0.28);
    color: #fff;
    transform: translateY(-2px);
    box-shadow: 0 8px 24px oklch(53.6% 0.145 276 / 0.3);
  }
  .cta-actions {
    display: flex;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
  }
  /* The closing ask, lifted onto its own white panel above the section. */
  .cta-panel {
    max-width: 760px;
    margin: 0 auto;
    text-align: center;
    background: #fff;
    border-radius: 16px;
    padding: clamp(36px, 5vw, 60px) clamp(24px, 4vw, 56px);
    box-shadow: 0 24px 70px oklch(0% 0 0 / 0.22), 0 4px 12px oklch(0% 0 0 / 0.08);
    --fg: oklch(18% 0.015 276);
    --muted: oklch(42% 0.02 276);
    --accent: oklch(50% 0.145 277);
    color: var(--fg);
  }
  .cta-sub {
    margin-inline: auto;
    margin-bottom: 36px;
    max-width: 500px;
    font-size: 17px;
    line-height: 1.75;
    color: oklch(42% 0.02 276);
    opacity: 1;
  }
  /* Two equally-white buttons gave the page no idea what it wanted you to do.
     Hiring is the primary path; listing services is the alternative. */
  .btn-cta-primary,
  a.btn-cta-primary:visited {
    background: var(--accent-solid);
    color: #fff;
    border-color: var(--accent-solid);
    transition: background .2s, border-color .2s, box-shadow .2s, transform .2s;
  }
  .btn-cta-primary:hover {
    background: var(--accent-solid-hover);
    border-color: var(--accent-solid-hover);
    color: #fff;
    transform: translateY(-2px);
    box-shadow: 0 10px 26px oklch(53.6% 0.145 276 / 0.35);
  }
  .btn-cta-secondary,
  a.btn-cta-secondary:visited {
    background: transparent;
    color: oklch(45% 0.145 277);
    border-color: oklch(53.6% 0.145 276 / 0.35);
    transition: background .2s, border-color .2s, color .2s, transform .2s;
  }
  .btn-cta-secondary:hover {
    background: oklch(53.6% 0.145 276 / 0.07);
    border-color: oklch(53.6% 0.145 276 / 0.6);
    color: oklch(40% 0.145 277);
    transform: translateY(-2px);
  }
  html.ds-reduce-motion .btn-cta-primary:hover,
  html.ds-reduce-motion .btn-cta-secondary:hover { transform: none; box-shadow: none; }

  /* ── Mobile hero wallpaper ── */
  @media (max-width: 640px) {
    .hero { min-height: calc(100svh - 68px); padding-block: 44px 76px; background: #5F69C8; position: relative; overflow: hidden; }
    /* Copy spans the full width on mobile, so swap the left-weighted scrim for a
       stronger, more even vertical one behind the whole text block. */
    .hero-scrim {
      background: linear-gradient(180deg,
        oklch(24% 0.06 285 / 0.36) 0%,
        oklch(24% 0.06 285 / 0.28) 45%,
        oklch(24% 0.06 285 / 0.13) 72%,
        oklch(24% 0.06 285 / 0.05) 100%);
    }
    .hero-search { max-width: 100%; box-sizing: border-box; }
    .hero-search input { padding: 14px 16px; font-size: 14px; min-width: 0; }
    .hero-search button { min-width: 90px; padding: 14px 16px; font-size: 14px; }
  }

  /* ── Responsive ── */
  @media (max-width: 1024px) {
    .categories-grid { grid-template-columns: repeat(2, 1fr); }
    .agencies-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .steps-grid { gap: 20px; }
    .steps-grid::before { display: none; }
  }
  @media (max-width: 768px) {
    .hero { min-height: 520px; padding-block: 48px 80px; }
    .hero-content h1 { font-size: clamp(28px, 6vw, 40px); }
    .hero-content .hero-subtitle-custom { font-size: 16px; margin-bottom: 28px; }
    .hero::after { height: 30px; }
  }
  @media (max-width: 640px) {
    .stats-inner { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 16px 0; }
    .stat { flex: none; padding: 20px 14px; }
    .stat-number { font-size: clamp(24px, 7vw, 32px); margin-bottom: 2px; }
    .stat-label { font-size: 12px; }
    .steps-grid { grid-template-columns: 1fr; gap: var(--space-md); }
    .step { padding: 28px 20px; }
    .step h3 { font-size: 17px; }
    .step p { font-size: 14px; }
    .step-number { width: 40px; height: 40px; font-size: 16px; margin-bottom: 14px; }
    .agencies-grid { grid-template-columns: minmax(0, 1fr); }
    .agency-card { padding: var(--space-md); }
    .hero-content h1 { font-size: clamp(30px, 7.5vw, 36px); }
    .hero-content .hero-subtitle-custom { font-size: 15px; margin-bottom: 20px; }
    .hero-marquee { padding-block: 10px; }
    .hero-marquee-item { font-size: 12px; padding: 0 16px; gap: 7px; }
    .hero-marquee-item svg { width: 13px; height: 13px; }
  }
  @media (min-width: 641px) and (max-width: 1024px) {
    .hero { min-height: auto; padding-block: clamp(64px, 8vw, 96px) calc(clamp(64px, 8vw, 96px) + 50px); background: #5F69C8; }
  }
}
