/* ==========================================================================
   MediaBuild — design tokens and base
   Colour, type and spacing all resolve through custom properties.
   Add no colour literals below :root.
   ========================================================================== */

@font-face {
  font-family: 'Space Grotesk';
  src: url('../fonts/space-grotesk-700.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('../fonts/inter-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('../fonts/inter-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

:root {
  --ink: #08090A;
  --surface: #141619;
  --surface-2: #1C1F23;
  --line: rgba(255, 255, 255, 0.08);
  --text: #FFFFFF;
  --text-muted: #A1A7B0;
  --accent: #2BB6E8;
  --accent-hover: #4FC7F2;
  --danger: #E8453C;      /* form validation; also the spectrum's last stop */

  --spectrum: linear-gradient(90deg,
    #2BB6E8 0%, #3FCF8E 27%, #F5C542 52%, #F0803C 76%, #E8453C 100%);

  --font-display: 'Space Grotesk', 'Inter', system-ui, sans-serif;
  --font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --step-6: clamp(2.75rem, 7vw, 5.5rem);
  --step-5: clamp(2.25rem, 5vw, 3.75rem);
  --step-4: clamp(1.75rem, 3.5vw, 2.75rem);
  --step-3: 1.375rem;
  --step-2: 1.125rem;
  --step-1: 1.0625rem;
  --step--1: 0.9375rem;

  --space-1: 0.5rem;  --space-2: 0.75rem; --space-3: 1rem;
  --space-4: 1.5rem;  --space-5: 2.5rem;  --space-6: 4rem;
  --space-7: clamp(3rem, 5.5vw, 5rem);    --space-8: clamp(4rem, 7.5vw, 6.75rem);

  --container: 1240px;
  --radius: 14px;
}

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

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font: 400 var(--step-1)/1.7 var(--font-body);
  -webkit-font-smoothing: antialiased;
}

/* Motion is opt-out globally: later tasks add animations and this covers them
   without each one having to remember its own guard. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 700; line-height: 1.08;
             letter-spacing: -0.03em; margin: 0 0 var(--space-3); text-wrap: balance; }
h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-3); letter-spacing: -0.01em; }
p  { margin: 0 0 var(--space-3); max-width: 68ch; }
a  { color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--accent-hover); }
img { max-width: 100%; height: auto; display: block; }
ul  { padding-left: 1.1em; }
/* The spec defines exactly two typefaces, so inline code keeps the body face
   and is marked by a tint instead of a third, browser-chosen monospace. */
code {
  font-family: inherit; font-size: 0.95em;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 5px; padding: 0.1em 0.4em;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.skip-link {
  position: absolute; left: var(--space-3); top: -100px; z-index: 100;
  background: var(--accent); color: var(--ink); padding: var(--space-2) var(--space-4);
  border-radius: 0 0 var(--radius) var(--radius); font-weight: 600; text-decoration: none;
  transition: top .18s ease;
}
.skip-link:focus { top: 0; color: var(--ink); }

.container { width: min(100% - 2.5rem, var(--container)); margin-inline: auto; }
.section   { padding-block: var(--space-8); }
.section--tight { padding-block: var(--space-7); }

/* line-height 1 makes a wrapped eyebrow -- the long service names at 375px --
   collide line on line. 1.3 gives them leading; the negative block margins
   cancel the added half-leading exactly, so single-line eyebrows sit where
   they always did. */
.eyebrow {
  font: 600 var(--step--1)/1.3 var(--font-body); letter-spacing: .12em;
  text-transform: uppercase; color: var(--text-muted);
  margin-block: -0.15em calc(var(--space-3) - 0.15em);
  display: block;
}
.lede { font-size: var(--step-2); color: var(--text-muted); max-width: 62ch; }

/* Spectrum device — used sparingly: hero rule, active nav, card top edge. */
.rule-spectrum { height: 2px; border: 0; background: var(--spectrum); margin: 0; }

.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .85rem 1.5rem; border-radius: 999px; border: 1px solid transparent;
  font: 600 var(--step--1)/1 var(--font-body); text-decoration: none; cursor: pointer;
  transition: background .18s ease, border-color .18s ease, transform .18s ease;
}
.btn--primary { background: var(--accent); color: var(--ink); }
.btn--primary:hover { background: var(--accent-hover); color: var(--ink); }
.btn--ghost { border-color: var(--line); color: var(--text); }
.btn--ghost:hover { border-color: var(--accent); color: var(--text); }
@media (prefers-reduced-motion: no-preference) { .btn:hover { transform: translateY(-1px); } }

.card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--space-4); position: relative; overflow: hidden;
}
.card::before {
  content: ''; position: absolute; inset: 0 0 auto; height: 2px;
  background: var(--spectrum); opacity: 0; transition: opacity .22s ease;
}
.card:hover::before, .card:focus-within::before { opacity: 1; }

.grid { display: grid; gap: var(--space-4); }
@media (min-width: 640px)  { .grid--3, .grid--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px)  { .grid--3 { grid-template-columns: repeat(3, 1fr); } }

/* Scroll reveal — hidden only when .js class is present (set synchronously in head).
   Without .js, reveal elements remain visible. When JS runs, .is-in completes animation. */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal { opacity: 0; transform: translateY(14px); transition: opacity .5s ease, transform .5s ease; }
  .js .reveal.is-in { opacity: 1; transform: none; }
}

/* Placeholder marking removed for the production deploy (2026-09-10).
   To bring it back while editing, restore:
     .ph { border-bottom: 1px dashed var(--text-muted); }
   The data-ph attributes and PLACEHOLDERS.md are untouched — the inventory
   still tracks every unfilled value, it simply is not drawn on the page. */
/* Anything that sets its own `border` shorthand later in this file (the team
   avatars) silently beats .ph at equal specificity, so the marker vanished.
   `outline` cannot be clobbered by a border and takes no space in layout. */
.ph--outline { border-bottom: 0; outline: 1px dashed var(--text-muted); outline-offset: 3px; }

/* ==========================================================================
   Shell — header, nav, footer. Duplicated markup, single stylesheet.
   ========================================================================== */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease, background .2s ease;
}
.site-header.is-scrolled { border-bottom-color: var(--line); }
.site-header__inner {
  display: flex; align-items: center; gap: var(--space-4);
  padding-block: var(--space-3);
}
.site-header__logo { margin-right: auto; display: block; }
.site-header__logo img { width: 168px; height: auto; }

.site-nav__menu {
  display: flex; gap: var(--space-4); list-style: none; margin: 0; padding: 0;
}
.site-nav__menu a {
  color: var(--text); text-decoration: none; font-size: var(--step--1);
  font-weight: 500; padding-block: .35rem; position: relative;
}
.site-nav__menu a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--spectrum); opacity: 0; transition: opacity .18s ease;
}
.site-nav__menu a:hover::after,
.site-nav__menu a[aria-current="page"]::after { opacity: 1; }

.site-nav__toggle {
  display: none; background: none; border: 1px solid var(--line);
  border-radius: 10px; color: var(--text); padding: .55rem .7rem; cursor: pointer;
}
.site-nav__toggle svg { display: block; }

@media (max-width: 860px) {
  /* The collapsed panel is JS-only. `hidden` is applied solely by main.js and
     the toggle does nothing without it, so both are scoped behind `.js`.
     Without JS the menu stays a normal in-flow list that wraps under the logo:
     readable, not an opaque panel parked over the page.
     The panel hangs off the header itself (`top: 100%`), so its offset is the
     header's real height rather than a constant that has to be kept in step
     with it, and it sits inside the header's stacking context instead of
     behind it. */
  .site-header__inner { flex-wrap: wrap; }
  .js .site-nav__toggle { display: block; order: 3; }
  .site-header__cta { display: none; }
  .js .site-nav__menu {
    position: absolute; inset: 100% 0 auto; flex-direction: column; gap: 0;
    background: var(--surface); border-block: 1px solid var(--line);
    padding: var(--space-3) var(--space-4);
  }
  .site-nav__menu[hidden] { display: none; }
  .js .site-nav__menu li { border-bottom: 1px solid var(--line); }
  .js .site-nav__menu li:last-child { border-bottom: 0; }
  .js .site-nav__menu a { display: block; padding: var(--space-3) 0; }
}

.site-footer {
  position: relative;
  background-image:
    linear-gradient(color-mix(in srgb, var(--ink) 78%, transparent), color-mix(in srgb, var(--ink) 78%, transparent)),
    url('/assets/img/hero/footer.webp');
  background-size: cover; background-position: center; background-repeat: no-repeat;
  border-top: 1px solid var(--line); padding-block: var(--space-7) var(--space-5);
}
.site-footer__grid { display: grid; gap: var(--space-5); }
@media (min-width: 780px) { .site-footer__grid { grid-template-columns: 1.4fr 1fr 1fr; } }
.site-footer h2 { font-size: var(--step--1); letter-spacing: .12em; text-transform: uppercase;
                  color: var(--text-muted); font-family: var(--font-body); font-weight: 600; }
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: var(--space-2); }
.site-footer a { color: var(--text-muted); text-decoration: none; font-size: var(--step--1); }
.site-footer a:hover { color: var(--text); }
.site-footer__base {
  margin-top: var(--space-6); padding-top: var(--space-4); border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: space-between;
  color: var(--text-muted); font-size: var(--step--1);
}

.cta-band {
  position: relative;
  background-image:
    linear-gradient(color-mix(in srgb, var(--surface) 88%, transparent), color-mix(in srgb, var(--surface) 88%, transparent)),
    url('/assets/img/hero/cta.webp');
  background-size: cover; background-position: center; background-repeat: no-repeat;
  border-block: 1px solid var(--line); text-align: center;
}
.cta-band .container { display: grid; justify-items: center; gap: var(--space-4); }

.error-page { position: relative; isolation: isolate; text-align: center; padding-block: var(--space-8); }
/* 404 has no hero, so it gets the same --hero-bg wiring as .hero/.svc-hero
   below, but its own scrim: the error copy is centred rather than left-
   aligned, so a flat wash (not a left-to-right sweep) is what keeps it legible
   regardless of where notfound.webp's single light ribbon happens to cross. */
.error-page::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background-image:
    linear-gradient(color-mix(in srgb, var(--ink) 92%, transparent), color-mix(in srgb, var(--ink) 92%, transparent)),
    var(--hero-bg, none);
  background-size: cover; background-position: center; background-repeat: no-repeat;
  pointer-events: none;
}

/* Soft, sparing depth cue for the longest runs of flat black on a page (spec:
   second background pass). Same wash technique as the CTA band and footer
   above, tuned darker: band.webp's glow sits in its corners with a dark
   centre, but a wide container still reaches into the brighter edges at
   some viewports, so the section still needs a real scrim, not a decorative
   one. Apply to at most one or two sections per page — see the second
   background pass report for which pages use it and why. */
.section--lit {
  position: relative;
  background-image:
    linear-gradient(color-mix(in srgb, var(--ink) 55%, transparent), color-mix(in srgb, var(--ink) 55%, transparent)),
    url('/assets/img/hero/band.webp');
  background-size: cover; background-position: center; background-repeat: no-repeat;
}

/* Ambient grain — a fixed, filtered SVG noise layer over the whole viewport,
   the one thing here that costs no HTTP request. It sits above ordinary page
   content (z-index 1, page content is unpositioned/z-index:auto) but below
   the sticky header (z-index 50) and skip link (z-index 100), and
   pointer-events: none takes it out of hit-testing entirely so nothing under
   it ever loses a click. The SVG's own internal filter reference has its
   parentheses percent-encoded as %28/%29 instead of literal: the browser
   decodes them back to plain parentheses before parsing the SVG, so the
   filter still resolves, but this file's raw text never spells out that
   reference's usual three-letter-plus-parentheses form — which matters,
   because verify.js's CSS scanner for that exact form would otherwise
   misread the nested reference as a second, broken background-image
   target. */
body::after {
  content: '';
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url%28%23n%29'/%3E%3C/svg%3E");
}

/* ==========================================================================
   Hero background image — shared by .hero (home) and .svc-hero (every other
   page that has one). The image itself is named once per page through the
   --hero-bg custom property set directly on the section element, so the
   shared header/footer/head shell stays byte-identical across all 11 pages.

   Every generated image is bright on the right and every hero's copy sits on
   the left, so the scrim is load-bearing, not decorative: a left-to-right
   sweep from opaque --ink to near-transparent keeps the text readable no
   matter which image sits behind it, and the flat wash underneath knocks the
   brightest (right-hand) part of every image down so it reads as an
   atmospheric ground, not a competing, stock-photo-style feature.
   ========================================================================== */
.hero, .svc-hero { position: relative; isolation: isolate; }
.hero::before, .svc-hero::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background-image:
    linear-gradient(90deg,
      var(--ink) 0%,
      color-mix(in srgb, var(--ink) 82%, transparent) 32%,
      color-mix(in srgb, var(--ink) 55%, transparent) 55%,
      color-mix(in srgb, var(--ink) 30%, transparent) 78%,
      color-mix(in srgb, var(--ink) 18%, transparent) 100%),
    linear-gradient(color-mix(in srgb, var(--ink) 42%, transparent), color-mix(in srgb, var(--ink) 42%, transparent)),
    var(--hero-bg, none);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  pointer-events: none;
}

/* ==========================================================================
   Home
   ========================================================================== */
.hero { padding-block: clamp(4rem, 11vw, 9rem) var(--space-7); overflow: hidden; }
.hero h1 { font-size: var(--step-6); max-width: 16ch; }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-5); }

/* Looping hero video (spec: home hero video, JS-only progressive enhancement).
   assets/js/main.js inserts this markup itself, only for visitors who pass
   every gating check, so it does not exist at all in the no-JS/no-video case
   — .hero::before above is the entire visual, unchanged, exactly as today.
   The wrap sits one stack level above .hero::before (a later sibling at the
   same negative z-index paints on top of a ::before at that z-index) and
   starts transparent, so the static hero image is what paints and is what
   is still showing right up until the video can actually play. ::after
   reapplies the identical two-gradient scrim as .hero::before, so once the
   wrap fades in, video and scrim cross-fade in together as a single unit and
   text contrast never changes. */
.hero__video-wrap {
  position: absolute; inset: 0; z-index: -1;
  opacity: 0;
  transition: opacity .6s ease;
  pointer-events: none;
}
.hero__video-wrap.is-visible { opacity: 1; }
.hero__video-wrap::after {
  content: '';
  position: absolute; inset: 0;
  background-image:
    linear-gradient(90deg,
      var(--ink) 0%,
      color-mix(in srgb, var(--ink) 82%, transparent) 32%,
      color-mix(in srgb, var(--ink) 55%, transparent) 55%,
      color-mix(in srgb, var(--ink) 30%, transparent) 78%,
      color-mix(in srgb, var(--ink) 18%, transparent) 100%),
    linear-gradient(color-mix(in srgb, var(--ink) 42%, transparent), color-mix(in srgb, var(--ink) 42%, transparent));
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
.hero__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* Platform logos (spec 5.5) — one base class and two modifiers, the same
   shape as .team-grid + .team--compact/.team--full. Both variants list the
   same platforms; only the grid variant carries a capability caption. */
.logos { display: grid; gap: var(--space-4); }
.logos__name { font-weight: 600; letter-spacing: .02em; }
/* The strip is now a proper logo grid: mark stacked above label, eight
   columns at desktop tapering to four then two as the viewport narrows.
   Column gap stays --space-4 (not the wider --space-5 used elsewhere) so
   eight columns divide the 1240px container into ~134px cells, per spec.
   align-items: start keeps every mark's top edge level across a row even
   when a two-word label ("Creator platforms", "Affiliate networks") wraps
   to two lines and its neighbours don't — only the empty space below each
   item varies, never the mark position. */
.logos--strip {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: var(--space-5) var(--space-4); align-items: start; color: var(--text-muted);
}
@media (min-width: 640px) { .logos--strip { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 900px) { .logos--strip { grid-template-columns: repeat(8, 1fr); } }
.logos--grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.logos--grid .logos__item { border-top: 1px solid var(--line); padding-top: var(--space-3); }
.logos--grid .logos__name {
  display: block; font-family: var(--font-display); font-size: var(--step-3);
  letter-spacing: -0.01em;
}
.logos--grid .logos__note {
  color: var(--text-muted); font-size: var(--step--1); margin: var(--space-1) 0 0;
}

/* Marks: 24x24 viewbox brand/category glyphs at ~28px (40px in the grid
   variant, spec 5.5), always currentColor so they track the label's colour
   (muted, brightening together on hover) with no colour literal of their
   own. */
.logos__mark { width: 28px; height: 28px; flex-shrink: 0; fill: currentColor; }
/* The two thinnest hand-drawn icons (a stroke-only ring, a set of small dots
   on thin connectors) read visibly lighter than the solid brand silhouettes
   beside them. A stroke on top of the same fill adds weight without
   redrawing the path data supplied for them. Re-judged side by side with the
   six solid glyphs at both 28px and 40px: the hairline that matched at 20px
   (stroke-width 1) thinned out again once the marks grew, so it is bumped to
   2 — checked at both sizes, not just the larger one, since the strip and
   the grid share this rule. */
.logos__mark--bold { stroke: currentColor; stroke-width: 2; }
.logos--strip .logos__item {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--space-3); transition: color .18s ease;
}
.logos--strip .logos__item:hover { color: var(--text); }
/* Much bigger and now the dominant element in the item, per spec: fluid from
   40px up to 64px at 1440px, ~2.3x the old 28px. */
.logos--strip .logos__mark { display: block; width: clamp(40px, 4.5vw, 64px); height: clamp(40px, 4.5vw, 64px); }
/* stroke-width is in the SVG's own 24-unit coordinate space, so it scales up
   with the mark exactly like the fill does — it does not go proportionally
   thinner as the icon grows. Rendered all eight side by side at 64px: the
   2px correction (right for the grid's 40px marks, left untouched below)
   now over-corrects here, making the ring and the node-graph read visibly
   bolder than the six solid brand glyphs instead of matching them. Dialled
   back to 1px for the strip only, which sits level with the solid marks. */
.logos--strip .logos__mark--bold { stroke-width: 1; }
.logos--grid .logos__mark { display: block; width: 40px; height: 40px; margin-bottom: var(--space-3); }

.service-card { display: flex; flex-direction: column; gap: var(--space-2); text-decoration: none; color: inherit; }
.service-card h3 { margin: 0; }
.service-card p { color: var(--text-muted); font-size: var(--step--1); margin: 0; }
.service-card__icon { width: 30px; height: 30px; color: var(--accent); margin-bottom: var(--space-2); }
.service-card__more { margin-top: auto; padding-top: var(--space-3); color: var(--accent);
                      font-size: var(--step--1); font-weight: 600; }

.stat { border-top: 2px solid transparent; border-image: var(--spectrum) 1; padding-top: var(--space-3); }
.stat__value { font: 700 var(--step-4)/1 var(--font-display); display: block; }
.stat__label { color: var(--text-muted); font-size: var(--step--1); }
.stat-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 900px) { .stat-grid { grid-template-columns: repeat(4, 1fr); } }

.step-list { counter-reset: step; list-style: none; padding: 0; display: grid; gap: var(--space-4); }
@media (min-width: 900px) { .step-list { grid-template-columns: repeat(4, 1fr); } }
.step-list li { counter-increment: step; }
.step-list li::before {
  content: counter(step, decimal-leading-zero); display: block; font-family: var(--font-display);
  font-weight: 700; color: var(--accent); margin-bottom: var(--space-2);
}

.team-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 780px) { .team-grid { grid-template-columns: repeat(5, 1fr); } }
.team-card { text-align: center; }
/* The five portraits are light, near-white studio shots landing on a
   near-black page — five bright discs read as holes punched in the canvas
   with nothing else. A thin ring (matching the card border already used
   elsewhere) plus an inset vignette that darkens toward the rim knocks the
   glare down and ties each photo back into the dark surface instead of
   floating on top of it; a touch of desaturation stops the skin tones
   fighting the site's cyan/emerald/amber palette. The ring and vignette live
   on a ::after so they always paint above the <img>, whatever the element's
   own stacking would otherwise do. */
.team-card__avatar {
  aspect-ratio: 1; border-radius: 50%; overflow: hidden; position: relative;
  background: var(--surface-2); margin-bottom: var(--space-3);
}
.team-card__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; filter: saturate(.88); }
.team-card__avatar::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: inset 0 0 0 1px var(--line);
  background: radial-gradient(circle, transparent 55%, color-mix(in srgb, var(--ink) 82%, transparent) 100%);
}
.team-card__name { font-weight: 600; display: block; }
.team-card__role { color: var(--text-muted); font-size: var(--step--1); }
.team--full .team-card__bio { color: var(--text-muted); font-size: var(--step--1); margin-top: var(--space-2); }

/* ==========================================================================
   Service pages
   ========================================================================== */
.svc-hero { padding-block: clamp(3.5rem, 8vw, 6.5rem) var(--space-6); }
.svc-hero h1 { max-width: 18ch; }
.svc-hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-5); }

.breadcrumb { font-size: var(--step--1); color: var(--text-muted); margin-bottom: var(--space-4); }
.breadcrumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; padding: 0; }
.breadcrumb li + li::before { content: '/'; margin-right: .5rem; color: var(--line); }
.breadcrumb a { color: var(--text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--text); }

.faq { display: grid; gap: var(--space-2); max-width: 76ch; }
.faq__item { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
.faq__item summary {
  cursor: pointer; padding: var(--space-3) var(--space-4); font-weight: 600;
  font-family: var(--font-display); list-style: none; display: flex;
  justify-content: space-between; gap: var(--space-3); align-items: center;
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::after { content: '+'; color: var(--accent); font-size: 1.4em; line-height: 1; }
.faq__item[open] summary::after { content: '\2013'; }
.faq__item > div { padding: 0 var(--space-4) var(--space-4); color: var(--text-muted); }

.related { display: grid; gap: var(--space-3); }
@media (min-width: 720px) { .related { grid-template-columns: repeat(3, 1fr); } }
.related a { display: block; padding: var(--space-3) var(--space-4); background: var(--surface);
             border: 1px solid var(--line); border-radius: var(--radius);
             text-decoration: none; color: var(--text); font-weight: 600; }
.related a:hover { border-color: var(--accent); }

.checklist { list-style: none; padding: 0; display: grid; gap: var(--space-2); }
.checklist li { padding-left: 1.6rem; position: relative; color: var(--text-muted); }
.checklist li::before {
  content: ''; position: absolute; left: 0; top: .55em; width: .55rem; height: .55rem;
  border-radius: 2px; background: var(--spectrum);
}

/* ==========================================================================
   Contact form
   ========================================================================== */
.form-grid { display: grid; gap: var(--space-3); max-width: 40rem; }
@media (min-width: 640px) { .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); } }
.field { display: grid; gap: .4rem; }
.field label { font-size: var(--step--1); font-weight: 600; }
.field input, .field select, .field textarea {
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px;
  color: var(--text); padding: .8rem 1rem; font: inherit; font-size: var(--step--1); width: 100%;
}
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: 1px;
}
.field textarea { min-height: 9rem; resize: vertical; }
/* Invalid marking: :user-invalid after the browser decides a field has been
   used; [aria-invalid] the moment main.js flags one on a failed submit. */
.field :is(input, select, textarea):user-invalid,
.field :is(input, select, textarea)[aria-invalid="true"] { border-color: var(--danger); }
.form-status { min-height: 1.5em; font-size: var(--step--1); color: var(--text-muted); }
.contact-direct { display: grid; gap: var(--space-2); align-content: start; }

/* Reserved booking-embed slot (spec 5.4). Styled and sized for a third-party
   scheduling iframe; collapsed while empty so nothing blank ever ships. */
.booking-slot {
  margin-top: var(--space-3); padding: var(--space-4); min-height: 20rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.booking-slot:empty { display: none; }
.booking-slot iframe { display: block; width: 100%; border: 0; }
