/* ==========================================================================
   Magichouse Media — components
   References tokens.css only. If you are about to type a hex code or a
   spacing value in this file, it belongs in tokens.css instead.

   BREAKPOINTS — three, and only these three:
     640px   phone  -> large phone / small tablet
     900px   tablet -> desktop (columns appear here)
     1080px  desktop -> wide (two-column heroes split here)
   ========================================================================== */

/* --- Reset and base ------------------------------------------------------ */
*, *::before, *::after { box-sizing:border-box; }
* { margin:0; padding:0; }
html { color-scheme:dark;   /* native checkboxes, selects and scrollbars render
                               light-on-light without this and look bolted on */
       scroll-behavior:smooth; -webkit-text-size-adjust:100%;
       /* .nav is position:sticky, so an anchor target would otherwise scroll to
          sit *under* the header. Pad every scroll destination by the header
          height plus a little breathing room. */
       scroll-padding-top:calc(var(--nav-h) + var(--s-5)); }
body {
  background:var(--bg); color:var(--text);
  font-family:var(--font-body); font-size:var(--fs-base); font-weight:var(--w-body);
  line-height:var(--lh-body); -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img, svg { display:block; max-width:100%; height:auto; }
a { color:inherit; text-decoration:none; }
::selection { background:var(--accent); color:var(--on-accent); }

:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:var(--r-sm); }
@media (prefers-reduced-motion:reduce) {
  html { scroll-behavior:auto; }
  *, *::before, *::after { transition:none !important; animation:none !important; }
}

.skip { position:absolute; left:-9999px; }
.skip:focus { left:var(--s-4); top:var(--s-4); z-index:100; background:var(--accent);
              color:var(--on-accent); padding:var(--s-3) var(--s-5); border-radius:var(--r-pill); }

/* --- Typography ---------------------------------------------------------- */
h1, h2, h3, h4 { font-family:var(--font-display); font-weight:var(--w-display);
                 line-height:var(--lh-heading); letter-spacing:var(--track-tight);
                 overflow-wrap:break-word; }
h1 { font-size:var(--fs-display); line-height:var(--lh-tight); }
h2 { font-size:var(--fs-4xl); }
h3 { font-size:var(--fs-xl); }
p  { overflow-wrap:break-word; }

.accent-word { font-family:var(--font-accent); font-style:italic; font-weight:500;
               color:var(--accent); letter-spacing:0; }
.label   { font-family:var(--font-body); font-weight:var(--w-medium); font-size:var(--fs-label);
           letter-spacing:var(--track-label); text-transform:uppercase; color:var(--text-subtle); }
.muted   { color:var(--text-muted); }
.subtle  { color:var(--text-subtle); }
.lead    { font-size:var(--fs-lg); color:var(--text-muted); }

/* --- Layout -------------------------------------------------------------- */
.wrap { width:80%; max-width:var(--measure); margin-inline:auto; }
@media (max-width:760px) { .wrap { width:88%; } }
section { padding-block:var(--section-y); }

/* --- Section banding ------------------------------------------------------
   Applied per-section in the templates rather than by :nth-child, so moving a
   section around doesn't silently invert the whole page. The hairlines are
   what actually sell the depth — without them the two greys are close enough
   to look like a rendering artefact. The inner gradient keeps a large band
   from reading as a flat slab. */
.band { background:var(--band);
        background-image:linear-gradient(180deg,
          rgba(var(--ink), 0.022) 0%, transparent 32%, transparent 68%,
          rgba(0,0,0,0.18) 100%);
        border-block:1px solid var(--band-edge); }
/* Two bands in a row would double the hairline into a 2px seam. */
.band + .band { border-top:0; }
.grid { display:grid; gap:var(--s-6); }

.section-head { max-width:62ch; margin-bottom:var(--s-9); }
.section-head p { margin-top:var(--s-4); color:var(--text-muted); font-size:var(--fs-md); }
/* Grid, not a wrapping flex row. As flex with `justify-content:space-between`
   and `flex-wrap:wrap`, a heading long enough to fill the line pushed the sub
   onto a row of its own — where `text-align:right` kept right-aligning it
   inside a narrow box on the LEFT of the page, which read as a bug. A grid
   either puts them side by side in fixed tracks or stacks them left-aligned;
   there is no in-between state to get wrong. */
.section-head.row { max-width:none; display:grid; gap:var(--s-5) var(--s-8);
                    align-items:start; }
.section-head.row p { margin-top:0; max-width:44ch; }
.section-head.row .label { margin-top:0; }
@media (min-width:900px) {
  .section-head.row { grid-template-columns:minmax(0, 1.5fr) minmax(0, 1fr); }
  .section-head.row > :not(h2) { text-align:right; justify-self:end; }
}

/* --- Nav ----------------------------------------------------------------- */
/* A floating island, not a full-bleed bar. .wrap gives it exactly the content
   width, so its left edge lines up with the H1 and its right edge with the
   quote card — the header stops being a separate band sitting on top of the
   page and starts being part of the same column as everything else.
   The surface lives on .nav-in; .nav itself stays transparent and full width
   because that is what has to be sticky.
   pointer-events: the transparent gutters either side of the island would
   otherwise swallow clicks on the content scrolling underneath. */
.nav { position:sticky; top:0; z-index:80; padding-block:var(--s-3);
       pointer-events:none;
       transition:padding-block var(--dur-nav) var(--ease-out); }
.nav > .wrap { pointer-events:auto; }
.nav.is-stuck { padding-block:var(--s-2); }

.nav-in { display:flex; align-items:center; justify-content:space-between;
          gap:var(--s-5); padding:var(--s-2) var(--s-2) var(--s-2) var(--s-5);
          border-radius:var(--nav-r);
          /* Solid by default. Without JS .is-top never lands, and a
             transparent header over scrolling content is unreadable — so the
             fallback is the surface, and JS takes it AWAY at the top rather
             than adding it on scroll. */
          background:color-mix(in srgb, var(--surface-sunken) 72%, transparent);
          backdrop-filter:blur(16px) saturate(1.4);
          -webkit-backdrop-filter:blur(16px) saturate(1.4);
          border:1px solid var(--hairline);
          box-shadow:0 1px 0 rgba(var(--ink), .05) inset,
                     0 20px 44px -30px rgb(0 0 0 / .95);
          transition:background var(--dur-nav) var(--ease-out),
                     border-color var(--dur-nav) var(--ease-out),
                     box-shadow var(--dur-nav) var(--ease-out); }
/* At the very top the hero should read as one uninterrupted field, so the
   island dissolves and only the logo, links and CTA are left floating. */
.nav.js-on.is-top .nav-in { background:transparent; border-color:transparent;
                            box-shadow:none;
                            backdrop-filter:none; -webkit-backdrop-filter:none; }

.brand { display:inline-flex; align-items:center; padding-block:var(--s-2); }
.brand img { display:block; width:auto; height:26px; }
@media (max-width:520px) { .brand { min-width:var(--tap); } }
/* the yellow dot survives as a standalone device in eyebrows */
.brand-dot { width:11px; height:11px; border-radius:var(--r-pill); background:var(--accent); flex:none; }
.foot-brand { display:inline-flex; align-items:center; gap:var(--s-3); }
.foot-brand img { display:block; width:auto; height:20px; opacity:.8; }

.nav-links { position:relative; display:flex; gap:var(--s-1); align-items:center; }
.nav-links a { position:relative; z-index:1;
               font-size:var(--fs-sm); font-weight:var(--w-medium); color:var(--text-muted);
               padding:var(--s-3) var(--s-4); border-radius:var(--r-pill);
               transition:color var(--dur) var(--ease); }
/* Fallback only. With JS the pill below does the highlighting, and a second
   background underneath it would just look like a bug. */
.nav-links a:hover { color:var(--accent); background:var(--hairline); }
.nav.js-on .nav-links a:hover { background:none; }
.nav-links a:hover, .nav-links a[aria-current] { color:var(--text); }
.nav.js-on .nav-links a:hover { color:var(--accent); }

/* One pill, moved and resized to whichever link is hovered — or, with nothing
   hovered, parked on the section you are actually reading. inset-block rather
   than a height so it always matches the link box exactly. */
.nav-ind { position:absolute; inset-block:0; left:0; width:var(--nav-w, 0px);
           transform:translate3d(var(--nav-x, 0px), 0, 0);
           border-radius:var(--r-pill);
           background:var(--accent-soft);
           box-shadow:0 0 0 1px rgba(255, 230, 0, .22) inset;
           opacity:0; pointer-events:none;
           transition:transform var(--dur-nav) var(--ease-out),
                      width var(--dur-nav) var(--ease-out),
                      opacity var(--dur) linear; }
.nav-ind.is-on { opacity:1; }
@media (prefers-reduced-motion:reduce) {
  .nav-ind { transition:opacity var(--dur) linear; }
  .nav, .nav-in { transition:none; }
}

.nav-right { display:flex; align-items:center; gap:var(--s-3); }
/* The arrow is the only moving part in the CTA — enough to make it feel
   answerable without turning the header into a toy. */
.nav-cta::after { content:'\2192'; display:inline-block; margin-left:-.15em;
                  transition:transform var(--dur) var(--ease); }
.nav-cta:hover::after { transform:translateX(3px); }

/* The toggle stays focusable (never display:none) so the menu is keyboard-operable. */
.nav-toggle { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
/* Given a real surface and a visible edge. It used to be bare bars behind a
   0.18-alpha hairline on near-black, which barely registered as a control. */
.nav-btn { display:none; align-items:center; justify-content:center;
           width:var(--tap); height:var(--tap); flex:none; cursor:pointer;
           background:var(--surface);
           border:1px solid var(--hairline-strong); border-radius:var(--r-pill);
           color:var(--text);
           transition:color var(--dur) var(--ease),
                      background var(--dur) var(--ease),
                      border-color var(--dur) var(--ease); }
.nav-btn:hover { border-color:var(--accent); color:var(--accent); }
.nav-toggle:focus-visible + .nav .nav-btn,
.nav-btn:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
/* gap 5 + height 2 puts the bar centres 7px apart, which is why the open
   state translates by exactly 7px to collapse them into an X. Change one and
   you must change the other or the X comes out lopsided. */
.nav-bars { display:grid; gap:5px; }
.nav-bars i { display:block; width:20px; height:2px; background:currentColor; border-radius:1px;
              transition:transform var(--dur-menu) var(--ease), opacity var(--dur) var(--ease); }

/* --- Full-screen menu ---------------------------------------------------- */
.nav-overlay { position:fixed; inset:0; z-index:70; display:grid;
               background:var(--bg);              /* opaque: it is a full-screen menu */
               opacity:0; visibility:hidden; pointer-events:none;
               transition:opacity var(--dur-menu) var(--ease),
                          visibility var(--dur-menu) var(--ease); }
/* transparent click-catcher behind the links, so tapping empty space closes */
.nav-scrim { position:absolute; inset:0; cursor:default; }
/* `safe center` matters here. With plain `center`, once the menu is taller
   than the viewport — which is exactly what happens when the Services submenu
   opens on a small phone — flexbox centres the overflow in BOTH directions and
   the top items get pushed above the scroll origin, where they cannot be
   scrolled back to. "Work" was being clipped under the header with no way to
   reach it. `safe` falls back to flex-start the moment content overflows.
   Both lines on purpose: a browser without `safe` keeps the plain `center`. */
.nav-full { position:relative; display:flex; flex-direction:column;
            justify-content:center;
            justify-content:safe center;
            gap:var(--s-7); min-height:100dvh; overflow-y:auto;
            padding:calc(var(--nav-h) + var(--s-6)) var(--gutter) var(--s-8); }

/* An open submenu adds four rows; tighten the top-level rhythm to pay for it
   rather than letting the menu grow past the screen. */
.nav-full-list:has(.nav-sub[open]) { gap:0; }
.nav-full-list:has(.nav-sub[open]) > li > a,
.nav-full-list:has(.nav-sub[open]) > li > .nav-sub > summary { padding-block:var(--s-2); }
.nav-full-list { list-style:none; display:grid; gap:var(--s-2); }
.nav-full-list li { opacity:0; transform:translateY(14px);
                    transition:opacity var(--dur-menu) var(--ease),
                               transform var(--dur-menu) var(--ease); }
/* Scoped to the top-level items only. `.nav-full a` also matched the
   "Get in touch" button — (0,1,1) beats `.btn` at (0,1,0) — so the CTA
   rendered as 44px accent italic inside a pill, which is what made it
   unreadable. The sublinks have their own rule below; the button now just
   looks like every other .btn on the site. */
.nav-full-list > li > a, .nav-sub > summary {
  font-family:var(--font-accent); font-style:italic; font-weight:500;
  font-size:clamp(28px, 7.4vw, 42px); line-height:1.18; color:var(--text);
  letter-spacing:0; }
.nav-full-list > li > a, .nav-sub > summary {
  display:flex; align-items:center; gap:var(--s-4);
  padding-block:var(--s-3); min-height:var(--tap); cursor:pointer; }
.nav-full-list > li > a:hover, .nav-sub > summary:hover { color:var(--accent); }

.nav-sub > summary { list-style:none; }
.nav-sub > summary::-webkit-details-marker { display:none; }
.arrow { width:11px; height:11px; flex:none; border-right:2px solid currentColor;
         border-bottom:2px solid currentColor; transform:rotate(45deg) translateY(-3px);
         transition:transform var(--dur) var(--ease); }
.nav-sub[open] > summary { color:var(--accent); }
.nav-sub[open] .arrow { transform:rotate(-135deg) translateY(-3px); }
/* The sublinks sat at 17px directly under a 42px italic parent, which read as
   a different component rather than a child of it. Bigger, and hung off an
   accent rule so the relationship is obvious. */
.nav-sublinks { display:grid; padding:var(--s-2) 0 var(--s-4) var(--s-5);
                border-left:2px solid var(--accent-soft); margin-left:var(--s-3); }
.nav-sublinks a { font-family:var(--font-body); font-style:normal; font-weight:var(--w-medium);
                  font-size:clamp(17px, 4.6vw, 20px); color:var(--text-muted);
                  display:flex; align-items:center; min-height:var(--tap);
                  transition:color var(--dur) var(--ease); }
.nav-sublinks a:hover { color:var(--accent); }

.nav-full-foot { display:grid; gap:var(--s-5); justify-items:start;
                 opacity:0; transform:translateY(14px);
                 transition:opacity var(--dur-menu) var(--ease),
                            transform var(--dur-menu) var(--ease); }
.nav-full-foot .btn { width:auto; min-width:220px; }
.nav-full-tel { display:inline-flex; align-items:center; min-height:var(--tap);
                font-family:var(--font-display); font-weight:var(--w-display);
                font-size:var(--fs-xl); letter-spacing:var(--track-tight); }
.nav-full-tel:hover { color:var(--accent); }

/* --- Open state ---------------------------------------------------------- */
.nav-toggle:checked ~ .nav-overlay { opacity:1; visibility:visible; pointer-events:auto; }
.nav-toggle:checked ~ .nav-overlay .nav-full-list li,
.nav-toggle:checked ~ .nav-overlay .nav-full-foot {
  opacity:1; transform:none;
  transition-delay:calc(60ms + var(--i, 1) * 45ms);
}
.nav-toggle:checked ~ .nav .nav-btn { color:var(--accent); border-color:var(--accent); }
.nav-toggle:checked ~ .nav .nav-bars i:nth-child(1) { transform:translateY(7px) rotate(45deg); }
.nav-toggle:checked ~ .nav .nav-bars i:nth-child(2) { opacity:0; }
.nav-toggle:checked ~ .nav .nav-bars i:nth-child(3) { transform:translateY(-7px) rotate(-45deg); }
body:has(.nav-toggle:checked) { overflow:hidden; }

@media (max-width:900px) {
  .nav-links { display:none; }
  /* .nav-right scope needed: a bare .nav-cta ties with .btn on specificity
     and .btn is declared later in this file, so it would win. */
  .nav-right .nav-cta { display:none; }
  .nav-btn { display:inline-flex; }
}
@media (min-width:901px) { .nav-overlay { display:none; } }

/* --- Buttons and pills --------------------------------------------------- */
.btn { display:inline-flex; align-items:center; justify-content:center; gap:var(--s-2);
       min-height:var(--tap);
       font-family:var(--font-display); font-weight:var(--w-display); font-size:var(--fs-sm);
       padding:var(--s-3) var(--s-6); border-radius:var(--r-pill); border:1px solid transparent;
       white-space:nowrap;
       cursor:pointer; transition:transform var(--dur) var(--ease), background var(--dur) var(--ease); }
.btn-primary { background:var(--accent); color:var(--on-accent); }
.btn-primary:hover { color:var(--on-accent);   /* pin it: see .contact-lines */
                     background:color-mix(in srgb, var(--accent) 86%, #fff);
                     transform:translateY(-2px);
                     box-shadow:0 8px 24px rgba(255,230,0,.20); }
.btn-ghost { border-color:var(--hairline-strong); color:var(--text); }
.btn-ghost:hover { border-color:var(--accent); color:var(--accent);
                   transform:translateY(-2px); }
.btn-lg { font-size:var(--fs-base); padding:var(--s-4) var(--s-7); width:100%; }

.pill { display:inline-flex; align-items:center; gap:var(--s-2);
        padding:var(--s-2) var(--s-4); border-radius:var(--r-pill);
        border:1px solid var(--hairline); font-size:var(--fs-sm); }
.pill b { font-weight:var(--w-bold); color:var(--accent); }
.pill-row { display:flex; flex-wrap:wrap; gap:var(--s-3); }

.card { background:var(--surface); border:1px solid var(--hairline);
        border-radius:var(--r-card); padding:var(--s-7); }
.card-quiet { background:var(--surface-sunken); }

/* --- Hero (homepage) ----------------------------------------------------- */
/* Hero backdrop, two layers, both behind the content (z-index:-1 under
   `isolation:isolate`, so nothing here can ever sit over the form controls):

     ::before — accent bloom + a faint 64px grid, both pure CSS.
     ::after  — the artwork, full-bleed behind the whole section.
                hero-art-mono.webp is the old WordPress illustration with the
                black cut to real alpha (see IMAGE_BRIEF.md), desaturated and
                contrast-lifted so the shapes still read once it is dropped to
                a tenth of full strength. Decorative only: no <img>, no alt
                text, and it carries no information the copy doesn't state.

   Opacity is deliberately low, and the vignette mask keeps it clear of the
   headline's left edge. The point is for it to register as texture, not as a
   picture sitting under the h1. */
.hero { padding-top:clamp(40px, 6vw, 84px); position:relative; isolation:isolate;
        overflow:hidden;                       /* the bloom and the art both bleed */
        padding-bottom:clamp(56px, 8vw, 104px); }
.hero::before {
  content:''; position:absolute; z-index:-1; pointer-events:none;
  inset:0 0 auto 0; height:min(760px, 100%);
  background-image:
    radial-gradient(46% 52% at 84% 26%, rgba(255,230,0,.18), transparent 70%),
    radial-gradient(38% 42% at 26% 92%, rgba(255,230,0,.16), transparent 72%),
    linear-gradient(to right,  rgba(var(--ink),.045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--ink),.045) 1px, transparent 1px);
  background-size:100% 100%, 100% 100%, 64px 64px, 64px 64px;
  -webkit-mask-image:radial-gradient(135% 108% at 66% 26%, #000 0%, transparent 78%);
          mask-image:radial-gradient(135% 108% at 66% 26%, #000 0%, transparent 78%);
}
.hero::after {
  content:''; position:absolute; z-index:-1; pointer-events:none;
  inset:0;
  /* cover, so it fills the section however tall the copy makes it. The strip
     is 2.7:1 and the hero is nearer 2:1, so this crops the ends — fine now
     that it is texture; it was not fine when the art was the subject. */
  background:url('/img/hero-art-mono.webp') no-repeat center/cover;
  opacity:.11;
  /* Fade toward the top-left, which is where the h1 and the lead sit. Keeps
     the strongest part of the image out from under the text. */
  -webkit-mask-image:radial-gradient(120% 130% at 78% 78%, #000 12%, rgba(0,0,0,.35) 62%, transparent 92%);
          mask-image:radial-gradient(120% 130% at 78% 78%, #000 12%, rgba(0,0,0,.35) 62%, transparent 92%);
}
@media (max-width:1080px) {
  /* The art is 2.7:1 and a stacked hero is taller than it is wide, so cover
     zooms one object to fill the screen. Pin it to the width instead — it can
     only ever be a band here — and put that band up behind the headline
     rather than down behind the form, where the card covers it and the
     section reads as if the image had been dropped. */
  .hero::after { background-size:165% auto; background-position:54% 4%;
                 opacity:.12;
                 -webkit-mask-image:linear-gradient(180deg, #000 0%, rgba(0,0,0,.55) 46%, transparent 76%);
                         mask-image:linear-gradient(180deg, #000 0%, rgba(0,0,0,.55) 46%, transparent 76%); }
}
@media (prefers-contrast:more) {
  .hero::after { opacity:.05; }
}

.hero-grid { display:grid; gap:var(--s-10); align-items:start; }
@media (min-width:1080px) { .hero-grid { grid-template-columns:1.15fr 0.85fr; gap:var(--s-9); } }
/* nowrap: the dot and the label are one unit. With wrap enabled the label was
   too wide to sit beside the dot on a narrow screen, so the dot took a line to
   itself and the pill rendered as an empty row above the text. If the label
   ever does outgrow the pill it now wraps INSIDE its own span, beside the dot,
   which still looks deliberate. */
.hero-eyebrow { display:inline-flex; align-items:center; gap:var(--s-2);
                flex-wrap:nowrap;
                border:1px solid var(--hairline); border-radius:var(--r-pill);
                padding:var(--s-2) var(--s-4); margin-bottom:var(--s-7); }
.hero-eyebrow > span:last-child { min-width:0; }
/* Tightened just enough to keep it on one line down to 320px. Measured: at
   320 the content box is ~239px and the label sets at ~234px here. */
@media (max-width:520px) {
  .hero-eyebrow { padding:var(--s-2) var(--s-3); font-size:11px;
                  letter-spacing:0.06em; }
}
/* 320px (SE-class) is the only width where the label still did not fit on one
   line. Tracking, not size — dropping below 11px on an uppercase label at this
   width hurts legibility more than tight tracking does. */
@media (max-width:360px) {
  /* Measured at 320px: the label needs 238px and had 237px. The last pixel
     comes out of the dot gap rather than the type — tracking is already tight
     and going smaller on an uppercase label costs more legibility than 4px of
     whitespace does. */
  .hero-eyebrow { letter-spacing:0.02em; gap:var(--s-1); padding-inline:var(--s-3); }
}
.hero h1 { margin-bottom:var(--s-6); }
.hero .lead { max-width:56ch; margin-bottom:var(--s-7); }

/* --- Quote form ---------------------------------------------------------- */
.quote-card { background:var(--surface); border:1px solid var(--hairline);
              border-radius:var(--r-lg); padding:var(--s-7); }
.quote-head { display:flex; align-items:baseline; justify-content:space-between;
              gap:var(--s-4); flex-wrap:wrap; margin-bottom:var(--s-6); }
.quote-head h2 { font-size:var(--fs-2xl); }
.opts { display:flex; flex-wrap:wrap; gap:var(--s-3); margin-block:var(--s-5); }
.opt { display:inline-flex; align-items:center; min-height:var(--tap);
       padding:var(--s-2) var(--s-5); border-radius:var(--r-pill);
       border:1px solid var(--hairline-strong); background:transparent; color:var(--text);
       font-family:var(--font-body); font-size:var(--fs-sm); font-weight:var(--w-medium);
       cursor:pointer; transition:border-color var(--dur) var(--ease), background var(--dur) var(--ease); }
.opt:hover { border-color:var(--accent); color:var(--accent); }
.opt input { position:absolute; opacity:0; width:0; height:0; }
.opt:has(input:checked) { background:var(--accent); color:var(--on-accent); border-color:var(--accent); }
.opt:has(input:focus-visible) { outline:2px solid var(--accent); outline-offset:3px; }
.field { display:block; margin-bottom:var(--s-4); }
.field span { display:block; font-size:var(--fs-sm); color:var(--text-muted); margin-bottom:var(--s-2); }
.field input, .field textarea, .field select {
  width:100%; min-height:var(--tap); padding:var(--s-3) var(--s-4); border-radius:var(--r-sm);
  background:var(--surface-sunken); border:1px solid var(--hairline-strong);
  color:var(--text); font-family:var(--font-body); font-size:var(--fs-base); }
.field input:focus, .field textarea:focus, .field select:focus {
  outline:2px solid var(--accent); outline-offset:1px; }
.field input::placeholder, .field textarea::placeholder { color:var(--text-subtle); }
/* 16px minimum, or iOS Safari zooms the whole page in on focus and never
   zooms back out. --fs-base is 16px; this only guards a future change. */
@media (max-width:760px) { .field input, .field textarea, .field select { font-size:max(16px, var(--fs-base)); } }

/* Native arrow removed so the control matches the inputs; the caret is drawn
   as a background so there is still an affordance. */
.field select { appearance:none; -webkit-appearance:none; padding-right:var(--s-8);
  background-image:linear-gradient(45deg, transparent 50%, rgba(var(--ink),.55) 50%),
                   linear-gradient(135deg, rgba(var(--ink),.55) 50%, transparent 50%);
  background-position:calc(100% - 20px) calc(50% + 1px), calc(100% - 14px) calc(50% + 1px);
  background-size:6px 6px, 6px 6px; background-repeat:no-repeat; }
.field select option { background:var(--surface); color:var(--text); }

/* Two-up on wide cards, stacked when the column gets narrow. minmax(0,1fr)
   rather than 1fr so a long value can't push the row wider than the card. */
.field-row { display:grid; gap:var(--s-4); grid-template-columns:repeat(2, minmax(0, 1fr)); }
@media (max-width:520px) { .field-row { grid-template-columns:1fr; } }

.fieldset { border:0; min-width:0; }   /* fieldsets refuse to shrink without this */

/* --- Multi-step form ------------------------------------------------------
   Default state is the NO-SCRIPT state: every panel visible, stepper hidden,
   Next/Back hidden, one long form with native validation. js/app.js adds
   .js-on and only then does any of the stepping appear. Nothing here may hide
   a panel unless .js-on is present — that is the whole fallback. */

.steps-nav { display:none; }
.form-steps.js-on .steps-nav {
  display:flex; align-items:center; gap:var(--s-2); list-style:none;
  margin-bottom:var(--s-6); }
.steps-nav li { display:flex; align-items:center; gap:var(--s-2); min-width:0; }
.steps-nav li + li::before { content:''; width:14px; height:1px; flex:none;
                             background:var(--hairline-strong); }
.steps-nav button { display:inline-flex; align-items:center; gap:var(--s-2);
                    min-height:var(--tap); padding:0 var(--s-2) 0 0;
                    background:none; border:0; cursor:pointer;
                    color:var(--text-subtle); font-family:var(--font-body);
                    font-size:var(--fs-xs); white-space:nowrap;
                    transition:color var(--dur) var(--ease); }
.steps-nav b { display:grid; place-items:center; width:24px; height:24px; flex:none;
               border-radius:50%; border:1px solid var(--hairline-strong);
               font-family:var(--font-display); font-weight:var(--w-display);
               font-size:var(--fs-label); line-height:1;
               transition:background var(--dur) var(--ease),
                          border-color var(--dur) var(--ease),
                          color var(--dur) var(--ease); }
.steps-nav button:hover { color:var(--text); }
.steps-nav button.is-done b { border-color:var(--accent); color:var(--accent); }
.steps-nav button.is-active { color:var(--text); }
.steps-nav button.is-active b { background:var(--accent); border-color:var(--accent);
                                color:var(--on-accent); }
.steps-nav button:focus-visible { outline:2px solid var(--accent); outline-offset:3px;
                                  border-radius:var(--r-pill); }
@media (max-width:400px) { .steps-nav span { display:none; } }

/* Panels are separated in the no-script form so it does not read as one wall
   of inputs; in stepped mode only one shows, so the rule is dropped. */
.panel + .panel { margin-top:var(--s-7); padding-top:var(--s-7);
                  border-top:1px solid var(--hairline); }
.form-steps.js-on .panel + .panel { margin-top:0; padding-top:0; border-top:0; }

/* Next/Back do nothing without script, so they are not rendered without it. */
[data-step-next], [data-step-back] { display:none; }
.form-steps.js-on [data-step-next],
.form-steps.js-on [data-step-back] { display:inline-flex; }

.step-row { display:flex; align-items:center; gap:var(--s-3); margin-top:var(--s-5); }
.step-row .btn { flex:1 1 auto; justify-content:center; }
.step-row .btn-ghost { flex:0 0 auto; }

/* Only the last panel's submit shows in stepped mode; in the no-script form
   the middle "or just send what I have" is redundant, so it goes too. */
.form-steps.js-on .panel:not(:last-child) .linkish { display:block; }
.form-steps:not(.js-on) .linkish { display:none; }

.linkish { display:block; width:100%; margin-top:var(--s-4); padding:var(--s-2);
           background:none; border:0; cursor:pointer;
           color:var(--text-subtle); font-family:var(--font-body);
           font-size:var(--fs-xs); text-decoration:underline;
           text-underline-offset:3px; }
.linkish:hover { color:var(--accent); }

/* Inline validation, written by js/app.js. */
.field-err { margin-top:var(--s-2); font-size:var(--fs-xs); color:#ff8a8a; }
.field input[aria-invalid="true"],
.field select[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color:#ff6b6b; }

/* Native validation still runs in the no-script form, so keep the styling for
   it — :user-invalid, not :invalid, or every empty required box is red on the
   first paint. */
.form-steps:not(.js-on) .field input:user-invalid,
.form-steps:not(.js-on) .field select:user-invalid { border-color:#ff6b6b; }

.checks { display:grid; gap:var(--s-2); margin-block:var(--s-3) var(--s-5); }
@media (min-width:520px) { .checks { grid-template-columns:repeat(2, minmax(0, 1fr)); } }
.check { display:flex; align-items:center; gap:var(--s-3); min-height:var(--tap);
         font-size:var(--fs-sm); color:var(--text-muted); cursor:pointer; }
.check input { flex:none; width:18px; height:18px; accent-color:var(--accent); }
.check:hover { color:var(--text); }

.honeypot { position:absolute; left:-9999px; }
.quote-note { font-size:var(--fs-sm); margin-bottom:var(--s-4); }
.quote-call { font-size:var(--fs-xs); margin-top:var(--s-4); }
.quote-call a { color:var(--accent); display:inline-block; padding-block:var(--s-2);
                font-weight:var(--w-medium); }

/* --- Marquee (work + testimonials) --------------------------------------- */
/* #000 below is a mask stop, not a palette colour — only its alpha matters. */
.marquee { overflow-x:auto; overflow-y:hidden; padding-inline:10%;
           scrollbar-width:none;
           -webkit-mask-image:linear-gradient(90deg,transparent,#000 3%,#000 97%,transparent);
                   mask-image:linear-gradient(90deg,transparent,#000 3%,#000 97%,transparent); }
.marquee::-webkit-scrollbar { display:none; }
.marquee-track { display:flex; gap:var(--s-5); width:max-content;
                 animation:marquee 48s linear infinite; }
.marquee-track--slow { animation-duration:70s; }
.marquee:hover .marquee-track,
.marquee:focus-within .marquee-track { animation-play-state:paused; }
@keyframes marquee { from { transform:translateX(0); } to { transform:translateX(-50%); } }

.work-card { border:1px solid var(--hairline); border-radius:var(--r-card); overflow:hidden;
             background:var(--surface-sunken); transition:border-color var(--dur) var(--ease); }
.work-card:hover { border-color:var(--accent); transform:translateY(-3px); }
.work-card { transition:border-color var(--dur) var(--ease), transform var(--dur) var(--ease); }
.marquee .work-card { width:248px; flex:none; }
.work-shot { aspect-ratio:4/5; display:grid; place-items:center; overflow:hidden;
             background:var(--surface); color:var(--text-subtle); }
.work-shot img { width:100%; height:100%; object-fit:cover; }
.work-meta { padding:var(--s-5); }
.work-meta h3 { font-size:var(--fs-md); margin-bottom:var(--s-1); }
.work-meta p  { font-size:var(--fs-xs); color:var(--text-subtle); }

.marquee .slot { width:320px; flex:none; }
.slot { border:1px dashed var(--hairline-strong); border-radius:var(--r-card);
        padding:var(--s-6); background:transparent; }
.slot blockquote { font-family:var(--font-accent); font-style:italic; font-weight:500;
                   font-size:var(--fs-xl); color:var(--text-subtle);
                   margin-block:var(--s-4); line-height:1.32; }
.slot cite { font-style:normal; font-size:var(--fs-sm); color:var(--text-subtle); }

/* --- Services ------------------------------------------------------------ */
.svc-grid  { display:grid; gap:var(--s-5); }
.svc-stack { display:grid; gap:var(--s-5); min-width:0; }
@media (min-width:900px) { .svc-grid { grid-template-columns:1.05fr 0.95fr; align-items:start; } }
.svc { min-width:0; }
.svc-head { display:flex; align-items:center; justify-content:space-between;
            gap:var(--s-4); flex-wrap:wrap; }
/* The icon sits with the heading, so the price stays pushed to the far end by
   margin-left:auto rather than by being the last flex child. */
.svc-icon { flex:none; width:44px; height:44px; object-fit:contain; }
.svc--lead .svc-icon { width:56px; height:56px; }
.svc-head h3 { margin-right:auto; }
@media (max-width:420px) { .svc-icon { width:36px; height:36px; }
                           .svc--lead .svc-icon { width:44px; height:44px; } }
.svc-head h3 { min-width:0; }
.svc h3 { font-size:var(--fs-2xl); }
.svc .price { font-family:var(--font-display); font-weight:var(--w-display);
              font-size:var(--fs-sm); color:var(--accent); white-space:nowrap; }
.svc .price--pill { background:var(--accent); color:var(--on-accent);
                    padding:var(--s-2) var(--s-4); border-radius:var(--r-pill); }
.svc p  { margin-block:var(--s-5); color:var(--text-muted); }
/* Tags, not buttons. They were pill-shaped, outlined and roughly the size of
   the real CTA below them, so seven of them read as a row of controls you were
   meant to click — inside a card that is itself one big link. Flatter, smaller,
   tighter, and clearly inert. */
.svc ul { list-style:none; display:flex; flex-wrap:wrap; gap:var(--s-2); }
.svc li { font-size:var(--fs-xs); line-height:1; color:var(--text-subtle);
          background:var(--surface-sunken);
          border:1px solid var(--hairline); border-radius:var(--r-sm);
          padding:7px var(--s-3); white-space:nowrap; }
/* No min-height:100%. Stretching this card to match the two stacked cards
   beside it left ~130px of dead space between the tag list and the CTA,
   because it simply has less content than they do. .svc-grid already uses
   align-items:start, so letting it size to its content just ends the card
   where the content ends. */
.svc--lead { display:flex; flex-direction:column; }
.svc--lead h3 { font-size:var(--fs-3xl); }
/* A pool of accent light that follows the cursor across the card. app.js
   writes --mx/--my; with no JS they never change and the gradient just sits
   centred, which still reads as a glow rather than as a broken effect.
   z-index, explicitly: a positioned ::before paints above in-flow content, so
   the children have to be lifted over it or the glow washes out the copy. */
a.svc { position:relative; overflow:hidden;
        transition:border-color var(--dur) var(--ease),
                   transform var(--dur-nav) var(--ease-out),
                   box-shadow var(--dur-nav) var(--ease-out); }
a.svc > * { position:relative; z-index:1; }
a.svc::before { content:''; position:absolute; inset:0; z-index:0; border-radius:inherit;
                background:radial-gradient(260px circle at var(--mx, 50%) var(--my, 0%),
                                           rgba(255, 230, 0, .15), transparent 62%);
                opacity:0; transition:opacity var(--dur-nav) var(--ease); }
a.svc:hover::before, a.svc:focus-visible::before { opacity:1; }
a.svc:hover, a.svc:focus-visible {
  border-color:var(--accent); transform:translateY(-4px);
  box-shadow:0 26px 50px -34px rgb(0 0 0 / .9); }
.svc-more { transition:transform var(--dur) var(--ease); }
a.svc:hover .svc-more, a.svc:focus-visible .svc-more { transform:translateX(4px); }
/* The illustrations are the most characterful thing in these cards, so they
   get the most movement. */
.svc-icon { transition:transform 420ms var(--ease-out); }
a.svc:hover .svc-icon { transform:translateY(-4px) rotate(-6deg) scale(1.06); }
a.svc li { transition:border-color var(--dur) var(--ease), color var(--dur) var(--ease); }
a.svc:hover li { border-color:var(--hairline-strong); color:var(--text-muted); }
.svc-more { margin-top:var(--s-5); font-size:var(--fs-sm); font-weight:var(--w-medium);
            color:var(--accent); }
/* margin-top:auto pinned the list to the bottom of the card, which on a tall
   card left a canyon between the body copy and the tags and made them look
   orphaned. They belong with the description, so they sit under it; the CTA
   keeps the auto margin and stays pinned to the bottom instead. */
.svc--lead ul { margin-top:var(--s-6); }
.svc--lead .svc-more { margin-top:var(--s-7); }

/* --- Process ------------------------------------------------------------- */
.steps { grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); }
/* isolation, not just position: the ghost numeral sits at z-index -1 so it
   paints behind the card's own text, and without a stacking context here it
   would fall behind the card's background instead and vanish. */
.step { position:relative; overflow:hidden; isolation:isolate;
        transition:transform var(--dur-nav) var(--ease-out),
                   border-color var(--dur) var(--ease),
                   background var(--dur) var(--ease); }
/* A rail that fills left to right — these are sequential steps, so the hover
   affordance may as well say "progress" rather than just "hovered". */
.step::before { content:''; position:absolute; inset:0 0 auto 0; height:2px;
                background:linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 35%, transparent));
                transform:scaleX(0); transform-origin:0 50%;
                transition:transform 520ms var(--ease-out); }
.step:hover, .step:focus-within { transform:translateY(-4px);
                                  border-color:var(--hairline-strong);
                                  background:var(--surface); }
.step:hover::before, .step:focus-within::before { transform:scaleX(1); }
/* Pushed well past the corner on both axes: at rest it is a texture, but at
   hover opacity a numeral parked over the tag line just makes the tag harder
   to read. Most of it hangs outside the card and gets clipped. */
.step-ghost { position:absolute; z-index:-1; right:-.16em; bottom:-.36em;
              font-family:var(--font-display); font-weight:var(--w-display);
              font-size:clamp(92px, 11vw, 140px); line-height:1; letter-spacing:-.04em;
              color:transparent; -webkit-text-stroke:1px var(--accent);
              /* opacity, not stroke-colour: only one of the two animates
                 reliably across engines. */
              opacity:.10; pointer-events:none; user-select:none;
              transition:opacity 420ms var(--ease-out), transform 520ms var(--ease-out); }
.step:hover .step-ghost, .step:focus-within .step-ghost {
  opacity:.34; transform:translateY(-6px) scale(1.05); }
.step .n { font-family:var(--font-display); font-weight:var(--w-display);
           font-size:var(--fs-sm); color:var(--accent); }
.step .when { transition:color var(--dur) var(--ease); }
.step:hover .when { color:var(--text); }
.step .when { margin-block:var(--s-2); }
.step h3 { margin-bottom:var(--s-3); }
.step p  { color:var(--text-muted); font-size:var(--fs-sm); }
.step .tag { margin-top:var(--s-4); font-size:var(--fs-xs); color:var(--text-subtle); }

/* --- Plans (hosting page; also sections/pricing.php if restored) ---------- */
.plans { display:grid; gap:var(--s-4); grid-template-columns:repeat(3, minmax(0, 1fr)); }
@media (max-width:900px) { .plans { grid-template-columns:1fr; } }
.plan { background:var(--surface); border:1px solid var(--hairline);
        border-radius:var(--r-card); padding:var(--s-6); display:flex; flex-direction:column; }
.plan--featured { background:var(--accent); color:var(--on-accent); border-color:var(--accent); }
.plan--featured .plan-list li,
.plan--featured .plan-foot,
.plan--featured .plan-promise { color:color-mix(in srgb, var(--on-accent) 78%, transparent); }
.plan--featured .plan-list li::before { color:var(--on-accent); }
.plan-top { display:flex; flex-direction:column; align-items:flex-start; gap:var(--s-2); }
.plan-top h3 { font-size:var(--fs-lg); }
.badge { font-size:var(--fs-label); font-weight:var(--w-bold); padding:var(--s-1) var(--s-3);
         border-radius:var(--r-pill); border:1px solid currentColor; opacity:.75; }
.plan-price { font-family:var(--font-display); font-weight:var(--w-display);
              font-size:var(--fs-3xl); margin-top:var(--s-3); letter-spacing:var(--track-tight); }
.plan-price small { font-family:var(--font-body); font-weight:var(--w-body);
                    font-size:var(--fs-xs); opacity:.7; }
.plan-promise { font-size:var(--fs-sm); margin-block:var(--s-3); color:var(--text-muted); }
.plan-list { list-style:none; display:grid; gap:var(--s-2); align-content:start; flex:1; }
.plan-list li { font-size:var(--fs-sm); color:var(--text-muted);
                padding-left:var(--s-4); position:relative; }
.plan-list li::before { content:'·'; position:absolute; left:0; color:var(--accent); }
.plan-foot { margin-top:var(--s-5); font-size:var(--fs-xs); color:var(--text-subtle); }

/* --- FAQ ----------------------------------------------------------------- */
/* Separate cards rather than rows divided by hairlines. A shared divider makes
   every question part of one undifferentiated wall; a card has an edge that
   can respond, which is what gives the open one somewhere to go. */
.faq-list { display:grid; gap:var(--s-3); }
.faq-item { position:relative; overflow:hidden;
            border:1px solid var(--hairline); border-radius:var(--r-card);
            background:var(--surface-sunken); padding-inline:var(--s-6);
            transition:border-color var(--dur) var(--ease),
                       background var(--dur) var(--ease); }
@media (min-width:700px) { .faq-item { padding-inline:var(--s-7); } }
.faq-item:hover, .faq-item[open] { border-color:var(--hairline-strong);
                                   background:var(--surface); }
/* An accent edge that grows down the left as you arrive and stays while the
   answer is open, so an open item still reads as open once you move away. */
.faq-item::before { content:''; position:absolute; left:0; top:0; bottom:0; width:2px;
                    background:var(--accent); transform:scaleY(0); transform-origin:50% 0;
                    transition:transform 420ms var(--ease-out); }
.faq-item:hover::before, .faq-item[open]::before { transform:scaleY(1); }

.faq-item summary { position:relative; cursor:pointer; list-style:none;
                    padding-block:var(--s-6); padding-right:var(--s-9);
                    font-family:var(--font-display); font-weight:var(--w-display);
                    font-size:var(--fs-lg);
                    transition:color var(--dur) var(--ease); }
.faq-item summary::-webkit-details-marker { display:none; }
.faq-item summary:hover, .faq-item[open] summary { color:var(--accent); }
.faq-item summary:focus-visible { outline:2px solid var(--accent); outline-offset:4px;
                                  border-radius:var(--r-sm); }
/* The marker is two bars, not a swapped '+' and '–' glyph. Swapping characters
   cannot animate and jumps a couple of pixels because the two glyphs have
   different metrics; rotating the upright bar onto the flat one is the same
   idea done continuously. Absolutely positioned, so it is out of the summary's
   flow — hence the padding-right that reserves its space. */
.faq-item summary::before,
.faq-item summary::after { content:''; position:absolute; right:2px; top:50%;
                           width:15px; height:2px; border-radius:1px;
                           background:var(--accent);
                           transition:transform 380ms var(--ease-out); }
.faq-item summary::before { transform:translateY(-50%); }
.faq-item summary::after  { transform:translateY(-50%) rotate(90deg); }
.faq-item[open] summary::after { transform:translateY(-50%) rotate(0deg); }

.faq-item p { padding-bottom:var(--s-6); color:var(--text-muted); max-width:72ch; }

/* Animating a <details> open used to need JavaScript measuring heights. It
   doesn't now: interpolate-size lets a transition run to `auto`, and
   ::details-content is the anonymous box wrapping everything after the
   <summary>. Behind @supports because it is Chromium-only so far — everywhere
   else the answer simply appears, which is what <details> has always done. */
@supports (interpolate-size: allow-keywords) {
  @media (prefers-reduced-motion: no-preference) {
    :root { interpolate-size: allow-keywords; }
    .faq-item::details-content {
      block-size:0; overflow:hidden; opacity:0;
      transition:block-size 420ms var(--ease-out),
                 opacity 260ms var(--ease-out),
                 content-visibility 420ms allow-discrete; }
    .faq-item[open]::details-content { block-size:auto; opacity:1; }
  }
}

/* --- Contact and footer -------------------------------------------------- */
.contact h2 { font-size:var(--fs-5xl); }
.contact .lead { margin-block:var(--s-5) var(--s-7); }
.contact-lines { display:flex; flex-wrap:wrap; gap:var(--s-5); align-items:center; }
.contact-lines > a { display:inline-flex; align-items:center; min-height:var(--tap); }
.contact-lines .tel { font-family:var(--font-display); font-weight:var(--w-display);
                      font-size:var(--fs-3xl); letter-spacing:var(--track-tight); }
/* :not(.btn) — this is a container rule, and .btn-primary's own colour is a
   single class, so without the exclusion the CTA's label turned yellow on a
   yellow background on hover. */
.contact-lines a:not(.btn):hover { color:var(--accent); }
.foot-areas { border-top:1px solid var(--hairline); padding-top:var(--s-7);
              font-size:var(--fs-xs); color:var(--text-subtle); }
.foot { padding-block:var(--s-7); display:flex; flex-wrap:wrap; gap:var(--s-4);
        justify-content:space-between; font-size:var(--fs-sm); color:var(--text-subtle); }

/* --- Service detail pages ------------------------------------------------ */
.crumbs { font-size:var(--fs-sm); color:var(--text-subtle); margin-bottom:var(--s-7); }
.crumbs a { padding-block:var(--s-2); transition:color var(--dur) var(--ease); }
.crumbs a:hover { color:var(--accent); }
.crumbs span { margin-inline:var(--s-2); }
.page-hero { padding-block:var(--s-9) var(--s-8); }
.page-hero-grid { display:grid; gap:var(--s-9); align-items:center; }
@media (min-width:1080px) { .page-hero-grid { grid-template-columns:1.05fr 0.95fr; gap:var(--s-8); } }
.page-hero h1 { font-size:clamp(30px, 4.4vw, 56px); margin-bottom:var(--s-5); }
.page-intro { margin-top:var(--s-4); max-width:70ch; font-size:var(--fs-md); }
.page-proof { display:flex; flex-wrap:wrap; gap:var(--s-8); margin-block:var(--s-7); }
.page-cta { margin-top:var(--s-6); }
.page-figure { position:relative; margin:0; }
.page-figure img { width:100%; border-radius:var(--r-lg); border:1px solid var(--hairline);
                   background:var(--surface-sunken); }
.page-figure figcaption { position:absolute; left:var(--s-5); bottom:var(--s-5);
                          background:var(--accent); color:var(--on-accent);
                          font-family:var(--font-display); font-weight:var(--w-display);
                          font-size:var(--fs-sm); padding:var(--s-2) var(--s-5);
                          border-radius:var(--r-pill); }
/* Transparent illustrations sit on the page rather than in a framed panel.
   They also can't take an absolutely-positioned caption: the PNG has empty
   margins, so a pill pinned to the figure's bottom-left lands in dead space
   with nothing above it. For --art the pill goes back into normal flow,
   centred under the drawing, where it stays attached to what it labels. */
.page-figure--art { text-align:center; }
.page-figure--art img { border:0; background:none; border-radius:0;
                        max-height:420px; width:auto; max-width:100%;
                        margin-inline:auto; }
.page-figure--art figcaption { position:static; display:inline-block;
                               margin-top:var(--s-4); }

.plans-section { padding-block:var(--s-8); }
.blocks { padding-block:var(--s-8) var(--s-9); }
/* stretch, NOT start: these cards hold lists of wildly different lengths
   ("What the build includes" has 8 bullets, "What moves the price" has 5).
   With align-items:start each card kept its natural height and the short one
   left a hole under itself, which read as a broken layout. Stretching makes
   every card in a row share the tallest height; the content still sits at the
   top because .card is a column flex container with the spacer below. */
.block-grid { display:grid; gap:var(--s-5); align-items:stretch; }
.block { display:flex; flex-direction:column; }
.block > :last-child { margin-bottom:0; }
@media (min-width:900px) { .block-grid { grid-template-columns:repeat(2, minmax(0, 1fr)); } }
.block h2 { font-size:var(--fs-xl); margin-bottom:var(--s-4); }
.block p  { font-size:var(--fs-sm); }
.block ul { list-style:none; margin-top:var(--s-4); display:grid; gap:var(--s-2); }
.block li { font-size:var(--fs-sm); color:var(--text-muted);
            padding-left:var(--s-4); position:relative; }
.block li::before { content:'·'; position:absolute; left:0; color:var(--accent); }
.page-foot { margin-bottom:var(--s-7); max-width:70ch; }

/* --- About (homepage) -----------------------------------------------------
   Two columns at the same 1.1/0.9 split the services grid and service-page
   heroes use, so it reads as part of the page rather than bolted on.
   Stacks copy-first below 900px. */
.about-grid { display:grid; gap:var(--s-8); }
@media (min-width:900px) {
  .about-grid { grid-template-columns:1.1fr 0.9fr; gap:var(--s-10); align-items:center; }
}
.about-copy { min-width:0; }
.about-copy .label { margin-bottom:var(--s-4); }
.about-copy p + p { margin-top:var(--s-4); }
.about-copy .muted { max-width:58ch; }

/* The lead as a pull quote. It is the sentence the whole section exists for,
   and at body size it read as preamble to the paragraphs under it. Hung off an
   accent rule rather than quote marks — the site has no other quote mark and
   one here would look imported. */
.about-lead { margin-top:var(--s-6); padding-left:var(--s-6);
              border-left:2px solid var(--accent);
              font-size:clamp(19px, 2.2vw, 25px); line-height:1.4;
              color:var(--text); max-width:34ch; }

/* Signature line. Cheap way to put a person at the end of a section about
   there being a person. The rule before it does the "signed" work. */
.about-sign { display:flex; align-items:center; gap:var(--s-4);
              margin-top:var(--s-7); font-size:var(--fs-sm);
              color:var(--text-subtle); }
.about-sign span { width:34px; height:1px; flex:none; background:var(--accent); }

/* --- the stats panel ---
   Was a plain bordered box with three grey numbers, which is why it read as
   filler. Now: accent numerals at display size, a hairline between rows rather
   than around them, and the brand mark bled into the corner at low opacity so
   the panel has something to look at. */
.about-panel { position:relative; overflow:hidden; isolation:isolate;
               border:1px solid var(--hairline); border-radius:var(--r-card);
               background:var(--surface);
               background-image:radial-gradient(120% 90% at 88% 8%,
                                 rgba(255,230,0,.10), transparent 62%);
               padding:var(--s-7) var(--s-7) var(--s-6); }
.about-mark { position:absolute; z-index:-1; right:-14px; bottom:-10px;
              width:140px; height:auto; opacity:.05; pointer-events:none;
              filter:grayscale(1) brightness(2.4); }

/* The numerals need to right-align as a column, which means the column has to
   be sized by the WIDEST of them. A fixed ch width guesses, and guessed wrong:
   3.2ch is narrower than "4–6", so it wrapped onto two lines.
   `display:contents` on the row wrappers promotes dt/dd into this grid, so a
   single `max-content` column measures every number and sizes to the largest.
   Row rules move onto the cells, since the wrapper no longer generates a box. */
/* Row rules need to be ONE box, and the numeral column needs to be sized by
   the widest number across ALL rows. Those two requirements fight:
     - nested per-row grids give a single bordered box but size their columns
       independently, so the numbers do not line up;
     - display:contents promotes the cells into one grid so the columns align,
       but then the border is drawn on two separate cells — and under
       `align-items:baseline` a 44px numeral and a 14px label have different
       box tops, so the rule renders as two segments at different heights.
   subgrid is the tool that does both: the row stays a real element (one
   border) while inheriting the parent's column tracks (aligned numbers).
   The plain grid-template-columns line above it is the fallback for a browser
   without subgrid — columns then size per row, which is merely less tidy. */
.about-stats { display:grid; grid-template-columns:max-content 1fr; }
.about-stats > div { grid-column:1 / -1;
                     display:grid;
                     grid-template-columns:max-content 1fr;
                     grid-template-columns:subgrid;
                     align-items:baseline; padding-block:var(--s-5); }
.about-stats > div:first-child { padding-top:0; }
.about-stats > div:last-child  { padding-bottom:0; }
.about-stats > div + div { border-top:1px solid var(--hairline); }
.about-stats dt { font-family:var(--font-display); font-weight:var(--w-display);
                  font-size:clamp(32px, 4.2vw, 44px); line-height:0.95;
                  color:var(--accent); letter-spacing:var(--track-tight);
                  text-align:right; white-space:nowrap;
                  font-variant-numeric:tabular-nums; }
.about-stats dd { margin:0; padding-left:var(--s-5); font-size:var(--fs-sm);
                  color:var(--text-muted); line-height:1.35; }
@media (max-width:400px) {
  .about-panel { padding:var(--s-6); }
  .about-stats dt { font-size:32px; }
}

/* --- Stats (service page proof; also sections/about.php if restored) ------ */
.stats { display:grid; gap:var(--s-6); grid-template-columns:repeat(3, 1fr); margin-top:var(--s-8); }
.stat b { display:block; font-family:var(--font-display); font-weight:var(--w-display);
          font-size:var(--fs-xl); }
.stat span { font-size:var(--fs-sm); color:var(--text-subtle); }
@media (max-width:640px) { .stats { grid-template-columns:1fr; gap:var(--s-4); } }

.link-accent { color:var(--accent); }

/* --- Form result states -------------------------------------------------- */
.form-note { font-size:var(--fs-sm); line-height:var(--lh-body);
             padding:var(--s-4) var(--s-5); border-radius:var(--r-sm);
             margin-bottom:var(--s-5); }
.form-note--ok  { background:var(--accent); color:var(--on-accent); font-weight:var(--w-medium); }
.form-note--err { border:1px solid var(--hairline-strong); color:var(--text-muted); }

/* --- Scroll reveals -------------------------------------------------------
   CSS scroll-driven animations, no JavaScript and no IntersectionObserver.

   The whole block sits behind @supports (animation-timeline: view()). That is
   load-bearing, not decoration: the usual JS pattern starts elements at
   opacity:0 and reveals them on intersect, so if the script never runs the
   page is blank. Here, a browser without scroll timelines never applies the
   keyframes at all and simply renders everything — the content is visible by
   default and the animation is the enhancement.

   animation-range entry 8% cover 34% means an element starts moving as it
   enters the viewport and has finished well before it reaches the middle, so
   nothing is still fading while you are trying to read it.

   Reveals are applied to whole groups, never to body text, and never to the
   marquees (already animating) or the hero (above the fold — animating it just
   delays the headline). --i staggers a row without needing a delay per child.
*/
/* `translate`, not `transform`. An animation with fill-mode:both keeps
   writing its final value into the cascade forever, and that value beats any
   plain hover rule — so while these keyframes animated `transform`, every
   `:hover { transform: translateY(...) }` on a revealed element (.svc, .step,
   .plan, .block, .stat, .page-figure) silently did nothing. The independent
   `translate` property does the same job on a channel nothing else claims. */
@keyframes mh-reveal {
  from { opacity:0; translate:0 20px; }
  to   { opacity:1; translate:none; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Deliberately a flat list of leaf elements. Animating a container AND its
       children compounds the two opacities and the inner one flickers, so
       nothing here may contain anything else here. */
    .section-head,
    .svc, .step, .block, .plan, .stat,
    .about-copy, .about-stats > div,
    .faq-item, .contact-lines, .page-figure {
      animation:mh-reveal linear both;
      animation-timeline:view();
      animation-range:entry 8% cover 34%;
    }
    /* Stagger: each item in a row starts a little later than the one before it,
       which reads as the row assembling rather than snapping in. */
    .svc-stack > .svc:nth-child(2)  { animation-range:entry 8% cover 42%; }
    .steps > *:nth-child(2)         { animation-range:entry 8% cover 38%; }
    .steps > *:nth-child(3)         { animation-range:entry 8% cover 44%; }
    .steps > *:nth-child(4)         { animation-range:entry 8% cover 50%; }
    .plans > *:nth-child(2)         { animation-range:entry 8% cover 40%; }
    .plans > *:nth-child(3)         { animation-range:entry 8% cover 46%; }
    .block-grid > *:nth-child(even) { animation-range:entry 8% cover 40%; }
    .about-stats > div:nth-child(2)  { animation-range:entry 8% cover 40%; }
    .about-stats > div:nth-child(3)  { animation-range:entry 8% cover 46%; }
    .faq-list > :nth-child(2)        { animation-range:entry 8% cover 38%; }
    .faq-list > :nth-child(3)        { animation-range:entry 8% cover 42%; }
    .faq-list > :nth-child(4)        { animation-range:entry 8% cover 46%; }
    .faq-list > :nth-child(n+5)      { animation-range:entry 8% cover 50%; }

    /* The nav used to fade in a full-width background here. The island does
       that job now — on .nav-in, where it stays inside the pill — and this
       rule was painting a second, edge-to-edge bar behind it. */
  }
}

/* Hover states stay — they are feedback, not decoration — but the parts that
   move do not. */
@media (prefers-reduced-motion: reduce) {
  a.svc, a.svc::before, .svc-icon, .svc-more,
  .step, .step::before, .step-ghost,
  .faq-item, .faq-item::before, .faq-item summary::before, .faq-item summary::after {
    transition:none; }
  a.svc:hover, .step:hover, .step:focus-within { transform:none; }
  a.svc:hover .svc-icon { transform:none; }
  a.svc:hover .svc-more { transform:none; }
  .step:hover .step-ghost { transform:none; }
}

/* --- Case study ------------------------------------------------------------
   Behance-shaped: a cover that fills the screen, a short intro, then a long
   run of imagery on black with real space between, a brand specimen block,
   the full-page scroll, and a sign-off.

   The imagery is the content here, so almost everything below is about giving
   screenshots room and rhythm rather than decorating them. */

.cs-cover { position:relative; isolation:isolate; overflow:hidden;
            padding-block:clamp(56px, 10vw, 130px) clamp(48px, 7vw, 96px); }

/* The project name again, outlined, at a scale that runs off both edges. It is
   what stops a dark page opening on one small headline in a lot of black.
   -webkit-text-stroke rather than a border: it follows the letterforms. */
.cs-ghost { position:absolute; z-index:-1; left:50%; top:-0.16em;
            transform:translateX(-50%); white-space:nowrap; pointer-events:none;
            font-family:var(--font-display); font-weight:var(--w-display);
            font-size:clamp(90px, 20vw, 300px); line-height:0.82;
            letter-spacing:var(--track-tight);
            color:transparent; -webkit-text-stroke:1px rgba(var(--ink), 0.07);
            -webkit-mask-image:linear-gradient(#000 30%, transparent 92%);
                    mask-image:linear-gradient(#000 30%, transparent 92%); }
.cs-kicker { margin-bottom:var(--s-4); }
.cs-cover h1 { font-size:clamp(42px, 8.4vw, 104px); line-height:0.98;
               margin-bottom:var(--s-7); }
.cs-intro { max-width:52ch; font-size:clamp(17px, 1.5vw, 21px);
            line-height:1.55; color:var(--text-muted); }
.cs-visit { margin-top:var(--s-7); }

/* Meta strip. Wraps to as many columns as fit rather than a fixed count, so a
   project with three facts and one with six both look deliberate. */
.cs-meta { padding-block:var(--s-8); }
.cs-meta-grid { display:grid; gap:var(--s-6) var(--s-8);
                grid-template-columns:repeat(auto-fit, minmax(160px, 1fr)); }
.cs-meta-grid dt { margin-bottom:var(--s-2); }
.cs-meta-grid dd { margin:0; font-family:var(--font-display);
                   font-weight:var(--w-display); font-size:var(--fs-md); }

/* --- the image run --- */
.cs-shot { margin:0; padding-block:clamp(40px, 7vw, 96px); }
.cs-shot figcaption { margin-top:var(--s-5); font-size:var(--fs-sm);
                      color:var(--text-subtle); max-width:48ch; }
.cs-frame { border-radius:var(--r-lg); overflow:hidden;
            border:1px solid var(--hairline); background:var(--surface-sunken); }
.cs-frame img { display:block; width:100%; height:auto; }

/* full-bleed: escapes .wrap without a wrapper element or negative margins that
   break at narrow widths. 100vw minus the scrollbar, centred on the viewport. */
.cs-shot--full .cs-frame { width:min(100vw, 100%); margin-inline:auto;
                           border-radius:0; border-inline:0; }
@media (min-width:900px) {
  .cs-shot--full .cs-frame { width:92vw; border-radius:var(--r-lg);
                             border-inline:1px solid var(--hairline); }
}
.cs-shot--inset .cs-frame,
.cs-shot--angle .cs-frame { width:80%; max-width:var(--measure); margin-inline:auto; }
.cs-shot--inset figcaption, .cs-shot--angle figcaption,
.cs-shot--full figcaption { width:80%; max-width:var(--measure); margin-inline:auto; }
@media (max-width:760px) {
  .cs-shot--inset .cs-frame, .cs-shot--angle .cs-frame,
  .cs-shot figcaption { width:88%; }
}

/* The tilted one. A CSS 3D transform on the real screenshot — no separate
   mockup asset to render, and it stays correct when the screenshot changes.
   Flattened under reduced-motion and on narrow screens, where a rotated image
   just loses legibility. */
.cs-shot--angle { perspective:1600px; }
.cs-shot--angle .cs-frame {
  transform:rotateX(6deg) rotateY(-11deg) rotate(1deg);
  transform-origin:60% 50%;
  box-shadow:-40px 50px 90px rgba(0,0,0,.55);
  transition:transform var(--dur-menu) var(--ease); }
@media (max-width:900px), (prefers-reduced-motion:reduce) {
  .cs-shot--angle .cs-frame { transform:none; box-shadow:none; }
}

/* --- brand specimen --- */
.cs-brand { padding-block:var(--s-10); }
.cs-brand-grid { display:grid; gap:var(--s-9); }
@media (min-width:900px) { .cs-brand-grid { grid-template-columns:1.2fr 0.8fr; } }
.cs-face + .cs-face { margin-top:var(--s-7); padding-top:var(--s-7);
                      border-top:1px solid var(--hairline); }
.cs-face-name { font-family:var(--face), var(--font-display);
                font-weight:var(--w-display); font-size:var(--fs-2xl);
                margin-top:var(--s-4); }
.cs-face-set { font-family:var(--face), var(--font-body);
               font-size:clamp(19px, 2.4vw, 30px); line-height:1.3;
               color:var(--text-muted); margin-top:var(--s-3); }
.cs-face-use { margin-top:var(--s-3); font-size:var(--fs-sm); color:var(--text-subtle); }

.cs-swatches { list-style:none; display:grid; gap:var(--s-4); margin-top:var(--s-5); }
.cs-swatches li { display:flex; align-items:center; gap:var(--s-4);
                  font-size:var(--fs-sm); }
.cs-chip { width:44px; height:44px; flex:none; border-radius:var(--r-sm);
           border:1px solid var(--hairline-strong); }
.cs-swatches b { font-family:var(--font-display); font-weight:var(--w-display); }
.cs-swatches code { margin-left:auto; color:var(--text-subtle);
                    font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
                    font-size:var(--fs-xs); }

/* --- full page scroll --- */
.cs-full { padding-block:var(--s-10); }
.cs-scroller { height:min(78vh, 760px); overflow-y:auto; overflow-x:hidden;
               border-radius:var(--r-lg); border:1px solid var(--hairline);
               background:var(--surface-sunken); scrollbar-width:thin; }
.cs-scroller:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
.cs-scroller img { display:block; width:100%; height:auto; }

/* --- outro --- */
.cs-outro { padding-block:var(--s-10); }
.cs-sign { display:flex; align-items:center; gap:var(--s-4);
           font-size:var(--fs-sm); color:var(--text-subtle); }
.cs-sign span { width:34px; height:1px; flex:none; background:var(--accent); }
.cs-next { display:inline-flex; flex-direction:column; gap:var(--s-2);
           margin-top:var(--s-8); }
.cs-next-name { font-family:var(--font-accent); font-style:italic; font-weight:500;
                font-size:clamp(28px, 5vw, 46px); color:var(--text);
                transition:color var(--dur) var(--ease); }
.cs-next:hover .cs-next-name { color:var(--accent); }
