/* ============================================================
   ELLEVUX · site.css
   One-person web studio. Websites + SEO/AEO.
   ------------------------------------------------------------
   Direction: BLUEPRINT. Reference-locked to Patch
   (thepatchsystem.com), with the band rhythm borrowed from
   Elementor, a dotted-grid + mono-label detail from
   designstripe, and the answer-panel surface from Perplexity.

   FIVE TRAITS THAT MUST NOT SOFTEN:
     1. A visible hairline grid as page structure.
     2. Ultra-condensed heavy all-caps display type,
        leading ~0.85, tracking ~0.03em.
     3. Near-sharp radii (~5px). Flat colour blocks. No gradients.
     4. ZERO box-shadows, ever. Depth = surface inversion.
     5. ONE reserved action colour, CTA + live status only.

   Sections
     1 · Tokens
     2 · Band inversion
     3 · Reset + base elements
     4 · Accessibility
     5 · Layout primitives
     6 · Blueprint grid
     7 · Type primitives
     8 · Buttons + chips
     9 · Nav
    10 · Hero
    11 · Clients strip
    12 · Problem grid
    13 · Service cards
    14 · AI answer panel
    15 · Process steps
    16 · Work tiles
    17 · Quotes + star rating
    18 · Pricing
    19 · FAQ
    20 · Closing CTA
    21 · Footer
    22 · Forms
    23 · Reveal + reduced motion
    ------------------------------------------------------------
    INTERACTIVE LAYER (added for the Apple-familiar pass)
    24 · Segmented control + switch
    25 · The device stage (CSS 3D)
    26 · The package configurator
    27 · Sticky mobile action bar
    28 · Scroll-driven work reveal
    29 · Hero, two-column with the stage
    30 · Reduced motion for the interactive layer
   ============================================================ */

/* ============================================================
   1 · TOKENS
   ============================================================ */
:root {
  /* ---- Soft light, with one dark room ----
     The PAGE is a soft warm off-white, never hard white: #ffffff on a
     full-bleed page is glare, and the calm is the point. The dark
     stack below is not retired, it is reserved: the hero stage and
     the drawer are the only places it appears, and they are the only
     places a photograph or an overlay needs it.

     Warm-neutral throughout. Not cream, not ivory, not parchment:
     those are on the reject list and they read as AI-default. This
     is grey with a trace of warmth, the Dieter-Rams end of off-white. */
  --canvas:    #f4f3f0;    /* THE page. Soft, calm, never #fff */
  --raised:    #faf9f7;    /* cards and panels, a touch above canvas */
  --sunk:      #eceae5;    /* wells, inputs, inset recesses */
  --graphite:  #171614;    /* primary text. Soft black, not #000 */
  --slate:     #6b6963;    /* muted text  ·  5.6:1 on canvas */
  --hairline:  rgba(23, 22, 20, 0.11);
  --hairline-strong: rgba(23, 22, 20, 0.22);

  /* The dark room: the hero stage, and the drawer. */
  --obsidian:  #000000;
  --tar:       #0c0c0c;
  --carbon:    #141414;
  --smoke:     #615f5c;
  --ash:       #878581;
  --bone:      #d4d0c9;
  --chalk:     #ffffff;

  /* Aliases, so the rest of the sheet keeps its vocabulary. */
  --paper: var(--canvas);
  --paper-lift: var(--raised);
  --ink: var(--graphite);
  --ink-soft: var(--slate);
  --paper-soft: var(--ash);
  --surface-1: var(--sunk);
  --surface-2: #e4e2dc;

  /* ---- Type ---- */
  /* ONE family, the way Apple uses one.
     SF Pro cannot be licensed off Apple's platforms, and Inter is
     the canonical substitute: same design lineage, tall x-height,
     open apertures. Google serves it with an `opsz` axis, which
     reproduces SF Pro Display vs Text optical sizing automatically,
     so large text tightens and small text opens up on its own.
     The display "voice" is therefore weight and tracking, not a
     second typeface. */
  --font-display: "Inter", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-label: "Inter", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;

  --fs-display: clamp(2.5rem, 5.6vw, 5rem);
  --fs-h2: clamp(1.875rem, 3.6vw, 3.125rem);
  --fs-h3: clamp(1.3125rem, 2vw, 1.75rem);
  --fs-h4: clamp(1.0625rem, 1.6vw, 1.25rem);
  --fs-lead: clamp(1.0625rem, 0.98rem + 0.5vw, 1.3125rem);
  --fs-body: 1.0625rem;
  --fs-sm: 0.9375rem;
  --fs-xs: 0.8125rem;
  --fs-label: 0.6875rem;

  /* Apple sets display text tight and airy at once: a low line
     height with strong negative tracking. */
  --lh-display: 1.06;
  --lh-tight: 1.15;
  --lh-snug: 1.35;
  --lh-base: 1.62;

  --tr-display: -0.032em;
  --tr-label: 0.12em;
  --tr-mono: 0.02em;

  --w-light: 300;
  --w-regular: 400;
  --w-medium: 500;
  --w-semibold: 600;
  --w-display: 800;
  --w-display-max: 900;

  /* ---- Spacing · 8px rhythm (carried over unchanged) ---- */
  --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;  --s-4: 1rem;
  --s-5: 1.5rem;   --s-6: 2rem;     --s-7: 3rem;     --s-8: 4rem;
  --s-9: 6rem;     --s-10: 8rem;    --s-11: 10rem;   --s-12: 14rem;
  /* More air. Negative space is most of what separates a premium
     page from a dense one, and the bands were too tight. */
  --band-y: clamp(3.5rem, 2rem + 4.5vw, 6rem);
  --band-y-tight: clamp(2.25rem, 1.5rem + 2.5vw, 3.5rem);

  /* ---- Layout ---- */
  --container: 1240px;
  --container-narrow: 820px;
  --gutter: clamp(1.25rem, 0.5rem + 3vw, 3.5rem);

  /* ---- Radii ----
     5px everywhere was the most default-looking radius there is,
     and it read cheap. Premium work commits: either a hairline-ruled
     sharp edge, or a generous considered curve. Nothing in between. */
  --r-xs: 2px;
  --r-sm: 0;          /* structural edges are ruled, not rounded */
  --r-surface: 20px;  /* cards, panels, the device window */
  --r-control: 999px; /* buttons and chips are pills */
  --r-pill: 999px;

  /* ---- Grid ---- */
  --grid-step: clamp(32px, 6vw, 56px);

  /* ---- INTERACTIVE LAYER · the one amendment to the lock ----
     Patch says near-sharp radii and zero shadows. That stays true of
     STRUCTURE: bands, the grid, cards, dividers. But anything you can
     TOUCH now gets soft radii and a whisper of lift, so the controls
     feel like the phone the visitor already owns.
     This is not drift. Lusion (lusion.co) is a bright, sharp,
     type-led technical page that does exactly this: flat surfaces
     everywhere, soft radii and a 0.04-alpha shadow reserved for
     interactive and elevated components. --shadow-lift IS Lusion's
     value. The radii come from Sleeve (replay.software/sleeve), a
     macOS app page: 12px controls, ~24px window-like surfaces. */
  /* The reference's shape scale, exactly: tags 2, icons and inputs 6,
     cards 10, buttons 100. Soft 20px surfaces and the Lusion lift
     were the previous amendment; both are on this reference's don't
     list ("no sharp or 4px-radius buttons", "no drop shadows beyond
     a 1px whisper, no hover elevation"), so they go. */
  --r-icon: 6px;
  --r-control: 100px;
  --r-surface: 10px;
  --shadow-lift: none;
  --shadow-float: 0 1px 2px rgba(20, 21, 26, 0.05);

  /* ---- Motion ---- */
  --dur-fast: 0.2s;
  --dur-base: 0.35s;
  --dur-slow: 0.6s;
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  /* iOS's own curve: a long, soft tail. This is most of why Apple
     motion feels calm rather than snappy. */
  --ease-ios: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-spring: cubic-bezier(0.34, 1.46, 0.64, 1);

  /* ---- z-index ---- */
  --z-grid: 0;
  --z-content: 1;
  --z-nav: 100;
  --z-drawer: 200;
  --z-overlay: 9600;
}

/* ============================================================
   2 · BAND INVERSION
   ------------------------------------------------------------
   A [data-band] scope REMAPS the semantic tokens. Because
   --card-surface is defined as the OPPOSITE of --surface in
   both scopes, a card can never match its parent band. That
   makes the Elementor inversion rule structural rather than a
   convention someone has to remember.
   --focus remaps too, so the ring stays visible on both
   canvases (ink 19.4:1 on paper, signal 15.9:1 on ink).
   ============================================================ */
/* Both band values resolve to the SAME canvas. The attribute survives
   in the markup so the page can be re-banded later without touching
   every file, but nothing alternates: the reference is one continuous
   surface from the nav to the footer, and that is the single biggest
   thing making it read as one object rather than a stack of blocks.

   Cards no longer INVERT either. A card is the page tone lifted a
   step, with a hairline, which is how the reference's grids read as
   structure instead of as contrasting slabs. */
:root,
[data-band="paper"],
[data-band="ink"] {
  --surface: var(--canvas);
  --on-surface: var(--graphite);
  --on-surface-soft: var(--slate);
  --card-surface: var(--raised);
  --card-on: var(--graphite);
  --card-on-soft: var(--slate);
  --line: var(--hairline);
  --line-strong: var(--hairline-strong);
  --grid-line: rgba(23, 22, 20, 0.045);
  --well: var(--sunk);
  --focus: var(--graphite);
  --connector: rgba(23, 22, 20, 0.18);
  /* No key edge and no rim sheen: the reference puts no shadow on a
     button at all, and the tactile-key treatment belonged to the
     previous direction. */
  --btn-edge: transparent;
  --btn-sheen: transparent;
  --card-glass: var(--raised);
  --card-rim: var(--hairline);
  --card-sheen: transparent;
  --bloom-a: rgba(23, 22, 20, 0.04);
  --bloom-b: rgba(23, 22, 20, 0.025);
}

/* The section rhythm. This was lost when band inversion was replaced
   and NOTHING replaced it, so every band on every page rendered with
   zero vertical padding and the sections ran straight into each
   other. The bloom layer went with it and is not coming back: it
   existed to give frosted glass something to blur, and there is no
   frosted glass any more. */
.band {
  position: relative;
  background: var(--surface);
  color: var(--on-surface);
  padding-block: var(--band-y);
  isolation: isolate;
}
.band--tight { padding-block: var(--band-y-tight); }

/* ---- The card ----
   The page tone plus a hairline. No inversion, no frost, no shadow,
   no hover lift: all four are on the reference's explicit don't list,
   and all four were in the previous build. Depth comes from the
   border and from the recess behind it. */
.card {
  position: relative;
  background: var(--card-surface);
  color: var(--card-on);
  border-radius: var(--r-surface);
  padding: var(--s-6);
  border: 1px solid var(--line);
}

/* ============================================================
   3 · RESET + BASE ELEMENTS
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Clears the floating pill AND whatever the device reserves at
     the top, so an anchor never lands behind either. */
  scroll-padding-top: calc(env(safe-area-inset-top, 0px) + 7rem);
  /* Reserve the scrollbar track so a short page and a long page do
     not differ in content width, which otherwise reads as a jump. */
  scrollbar-gutter: stable;
}

/* Anchor targets clear the fixed nav without relying on JS offsets. */
[id] { scroll-margin-top: calc(env(safe-area-inset-top, 0px) + 7rem); }

body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--w-regular);
  line-height: var(--lh-base);
  color: var(--graphite);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  /* NOTE: deliberately NO overflow-x here. A global overflow-x of
     hidden or clip makes documentElement.scrollWidth stop reporting
     real horizontal overflow, which silently voids the automated
     overflow check and has masked a live bug in this repo before.
     Anything that legitimately bleeds (the footer wordmark) clips
     itself locally instead. */
}

img, picture, video, canvas, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; padding: 0; }
p { text-wrap: pretty; }
strong, b { font-weight: var(--w-semibold); }

/* Sentence case, semibold. With one neutral family the hierarchy has
   to come from scale, weight and tracking rather than a change of
   voice, which is exactly how Apple does it. */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: var(--w-semibold);
  font-optical-sizing: auto;
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  text-wrap: balance;
}
h1 { font-size: var(--fs-display); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }

/* h4 steps DOWN to the body face. The display face is all-caps
   only and never used below ~28px, per the type fence. */
h4 {
  font-family: var(--font-body);
  font-size: var(--fs-h4);
  font-weight: var(--w-semibold);
  line-height: var(--lh-snug);
  letter-spacing: 0;
  text-wrap: balance;
}

::selection { background: var(--graphite); color: var(--canvas); }

/* ============================================================
   4 · ACCESSIBILITY
   ============================================================ */
:focus-visible {
  outline: 2px solid var(--focus, #0d0d0d);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100%;
  z-index: var(--z-overlay);
  padding: var(--s-3) var(--s-5);
  background: var(--graphite);
  color: var(--canvas);
  border-radius: var(--r-control);
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  transition: top var(--dur-base) var(--ease-out-expo);
}
.skip-link:focus { top: var(--s-4); }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   5 · LAYOUT PRIMITIVES
   ============================================================ */
.container {
  position: relative;
  z-index: var(--z-content);
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: var(--container-narrow); }

.stack > * + * { margin-top: var(--s-4); }
.stack-lg > * + * { margin-top: var(--s-6); }

/* ============================================================
   6 · BLUEPRINT GRID
   ------------------------------------------------------------
   Two hard-stop gradients. Hard stops stay crisp at any DPR.
   Deliberately NOT background-attachment:fixed, which forces a
   full repaint on every scroll frame.
   ============================================================ */
.grid-field::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-grid);
  pointer-events: none;
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: var(--grid-step) var(--grid-step);
}

/* Quiet variant: dots instead of a full lattice (designstripe). */
.grid-field--dots::before {
  background-image: radial-gradient(var(--grid-line) 1px, transparent 1px);
}

/* Fade the lattice toward the band edges so it reads as ambient
   structure rather than graph paper. */
.grid-field--fade::before {
  -webkit-mask-image: radial-gradient(ellipse 85% 75% at 50% 42%, #000 30%, transparent 100%);
  mask-image: radial-gradient(ellipse 85% 75% at 50% 42%, #000 30%, transparent 100%);
}

/* ============================================================
   7 · TYPE PRIMITIVES
   ============================================================ */
/* Mono technical label. The designstripe borrow: small labels
   and data only, never body copy, never a headline. */
.label {
  display: block;
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
}
/* A section label and its headline are one unit; they should not be
   spaced like two separate things. */
.head .label { margin-bottom: var(--s-5); }
.label--on-card { color: var(--card-on-soft); }

.lead {
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  max-width: 54ch;
}
.prose { max-width: 62ch; }
.prose p + p { margin-top: var(--s-4); }

.num {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-mono);
  color: var(--on-surface-soft);
}

/* Section heading block: mono label over a display h2. */
.head { margin-bottom: var(--s-7); max-width: 46ch; }
.head .label { margin-bottom: var(--s-4); }
.head__lead { margin-top: var(--s-5); }

/* A head that spans the section instead of hiding in a 46ch column.
   The heading takes the left track and the lead takes the right, on
   THE SAME grid as the content below it, so the heading lines up with
   the panel and the lead lines up with the numbered points. Stacked
   in one narrow column the two measures disagreed with each other and
   with everything under them, and most of the section was empty. */
@media (min-width: 980px) {
  .head--split {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: var(--s-8);
    align-items: end;
  }
  .head--split > * { min-width: 0; }
  .head--split .head__lead { margin-top: 0; max-width: 46ch; }
}

/* ============================================================
   8 · BUTTONS + CHIPS
   ------------------------------------------------------------
   Three ranks, enforced globally:
     .btn--primary  acid fill, ink text. The ONLY acid element.
                    Four per page maximum, one per viewport.
     .btn--ghost    hairline outline, inherits band colour.
     .btn--text     text + a bottom rule.
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 0.6875rem var(--s-5);
  border-radius: var(--r-control);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
  box-shadow: none;
  transition:
    background-color var(--dur-fast) var(--ease-out-quart),
    color var(--dur-fast) var(--ease-out-quart),
    border-color var(--dur-fast) var(--ease-out-quart),
    transform var(--dur-fast) var(--ease-out-quart);
}
/* ---- Tactile press ----
   Every button behaves like a physical key: it sits on a visible
   edge, lifts a hair on hover, and on press it travels down INTO
   that edge while its contact shadow tightens. Transform plus
   box-shadow only, so it stays on the compositor.
   (Adapted from the TactileButton pattern. This site ships no
   React, so it is rebuilt in CSS rather than imported.) */
.btn:active { transform: translateY(2px); }

/* The primary action is simply the INVERSE of its band, so it needs
   no accent colour at all and it works unchanged inside an inverted
   card. This is what Sequel, Metalab, Zara and YSL all do. */
.btn--primary {
  background: var(--graphite);
  color: var(--canvas);
  border: 1px solid var(--graphite);
}
.btn--primary:hover { background: #2a2824; border-color: #2a2824; }
/* Flat. The whole tactile-key treatment, the edge, the rim sheen and
   the press travel, belongs to the previous direction and the
   reference forbids all of it on a button. What is left is a pill
   that changes tone. */
.btn--primary { box-shadow: none; }
/* A FILLED element needs its ring INSIDE the fill. On an ink band
   --focus resolves to the signal, so the default ring would be acid
   on acid: invisible. An ink ring inside the acid fill is 15.9:1 on
   either band. System rule: outline-offset is POSITIVE for
   transparent elements (the ring lands on the band, which --focus is
   tuned against) and NEGATIVE for filled ones. */
/* A ring inside a solid fill must be the fill's opposite, or it is
   invisible. --surface is exactly that on either band. */
.btn--primary:focus-visible {
  outline: 2px solid var(--surface);
  outline-offset: -5px;
}

.btn--ghost {
  background: transparent;
  color: var(--graphite);
  border: 1px solid var(--hairline-strong);
}
.btn--ghost:hover { border-color: var(--graphite); background: transparent; }
.btn--ghost:hover {
  border-color: var(--on-surface);
  background: var(--well);
  transform: translateY(-1px);
  box-shadow: 0 6px 14px -8px rgba(11, 11, 11, 0.28);
}
.btn--ghost:active { transform: translateY(2px); box-shadow: none; }

.btn--text {
  padding: 0 0 2px;
  border: none;
  border-bottom: 1px solid var(--line-strong);
  border-radius: 0;
  background: none;
  color: var(--on-surface);
}
.btn--text:hover { border-bottom-color: var(--on-surface); }
.btn--text:active { transform: none; box-shadow: none; }

/* No per-card button overrides are needed: .card is a full token
   scope (see section 2), so --on-surface and --line-strong already
   describe the card rather than the band. */
.card .btn--ghost:hover { background: transparent; }

.btn__arrow { width: 0.8125rem; height: 0.8125rem; flex: none; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
}

/* ---- Status chip. Deliberately NOT the signal: acid is
   reserved for actions, and a chip is information. ---- */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.3125rem var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
  white-space: nowrap;
}
.status-pill::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}


/* ============================================================
   9 · NAV  ·  the dock
   ------------------------------------------------------------
   A dark floating dock, not a flush bar and not a capsule: the
   brand in its own light tile, then every destination as its own
   chip with an icon and an uppercase label, then the filled action.
   Replicated from the Mirror Solutions nav at the owner's request.

   Three things are load-bearing:

   · It is dark on EVERY page, including the light ones. One state
     instead of three. The previous bar had to flip its colours
     between the dark hero and the light page, and the flip was
     driven by the stage's own geometry; none of that is needed
     when the dock carries its own surface everywhere.
   · The chips are rounded rectangles, not the 100px pills the rest
     of the site uses. That is the reference, and the dock is the
     one place it applies. Do not "fix" it to match the buttons.
   · The mark is `logo-mark-light.png`, generated by inverting
     logo-mark.png: the normal mark is a DARK tile, which disappears
     on a dark dock. It carries its own rounded tile, so there is no
     border-radius here. A second corner clips the artwork.
   ============================================================ */
.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: var(--z-nav);
  display: flex;
  justify-content: center;
  /* The safe-area insets are what keep the dock clear of a Dynamic
     Island instead of sliding under it. */
  padding: calc(env(safe-area-inset-top, 0px) + var(--s-4))
           calc(env(safe-area-inset-right, 0px) + var(--s-4))
           0
           calc(env(safe-area-inset-left, 0px) + var(--s-4));
  /* The empty space beside the dock must never swallow a click. */
  pointer-events: none;
}

.nav__dock {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  max-width: 100%;
  padding: 8px;
  border-radius: 20px;
  background: rgba(18, 18, 17, 0.82);
  -webkit-backdrop-filter: saturate(170%) blur(16px);
  backdrop-filter: saturate(170%) blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.11);
  box-shadow:
    0 20px 44px -22px rgba(0, 0, 0, 0.7),
    inset 0 1px 0 rgba(255, 255, 255, 0.07);
  transition: box-shadow var(--dur-base) var(--ease-ios);
}
.nav--scrolled .nav__dock {
  box-shadow:
    0 24px 52px -22px rgba(0, 0, 0, 0.8),
    inset 0 1px 0 rgba(255, 255, 255, 0.07);
}

/* ---- The brand tile ---- */
.nav__brand {
  display: grid;
  place-items: center;
  flex: none;
}
.nav__mark { display: block; width: 34px; height: 34px; }

/* ---- The destination chips ---- */
.nav__links {
  display: none;
  align-items: center;
  gap: var(--s-2);
}
.nav__link {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 0.8125rem var(--s-5);
  border-radius: 13px;
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid rgba(255, 255, 255, 0.07);
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-semibold);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--bone);
  transition:
    background-color var(--dur-fast) var(--ease-ios),
    border-color var(--dur-fast) var(--ease-ios),
    color var(--dur-fast) var(--ease-ios);
}
.nav__link svg { width: 14px; height: 14px; flex: none; opacity: 0.62; }
.nav__link:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.13);
  color: var(--chalk);
}
.nav__link[aria-current="page"] {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.16);
  color: var(--chalk);
}
.nav__link[aria-current="page"] svg { opacity: 1; }

/* ---- The action ---- */
.nav__cta { display: none; flex: none; }
.nav__dock .btn--primary {
  padding: 0.8125rem var(--s-6);
  border-radius: 13px;
  background: var(--chalk);
  color: var(--carbon);
  border-color: var(--chalk);
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-semibold);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
}
.nav__dock .btn--primary:hover { background: var(--bone); border-color: var(--bone); }
.nav__dock .btn__arrow { opacity: 0.75; }

/* ---- Mobile toggle, a chip like the rest ---- */
.nav__burger {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 48px;
  height: 48px;
  padding: 0 14px;
  border-radius: 13px;
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid rgba(255, 255, 255, 0.07);
  cursor: pointer;
}
.nav__burger span {
  display: block;
  height: 1.5px;
  width: 100%;
  background: var(--bone);
  transition: transform var(--dur-base) var(--ease-ios), opacity var(--dur-fast) linear;
}
.nav__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

@media (min-width: 940px) {
  .nav__links { display: flex; }
  .nav__cta { display: inline-flex; }
  .nav__burger { display: none; }
}
@media (max-width: 420px) {
  .nav__dock { gap: var(--s-2); padding: 6px; }
}

.drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s-2);
  padding: calc(env(safe-area-inset-top, 0px) + var(--s-9))
           var(--gutter)
           calc(env(safe-area-inset-bottom, 0px) + var(--s-8));
  background: var(--ink);
  color: var(--paper);
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform var(--dur-slow) var(--ease-ios),
              visibility var(--dur-slow) linear;
}
.drawer[data-open="true"] { transform: translateY(0); visibility: visible; }
.drawer__link {
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 10vw, 3.5rem);
  font-weight: var(--w-semibold);
  line-height: 1.15;
  letter-spacing: var(--tr-display);
  color: var(--paper);
  padding-block: var(--s-3);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.drawer__link:last-of-type { border-bottom: none; }
/* The white lockup, since the drawer is ink. */
.drawer__lockup {
  width: clamp(160px, 46vw, 210px);
  height: auto;
  margin-bottom: var(--s-6);
  opacity: 0.92;
}
.drawer__foot { margin-top: var(--s-7); }
.drawer .btn--primary {
  width: 100%;
  background: var(--paper);
  color: var(--ink);
  border-color: var(--paper);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 2px 0 #c2c2bb;
}
body[data-scroll-locked="true"] { overflow: hidden; }

@media (min-width: 940px) {
  .nav__links { display: flex; }
  .nav__cta { display: inline-flex; }
  .nav__burger { display: none; }
  .drawer { display: none; }
}
@media (max-width: 420px) {
  .nav__pill { padding-left: var(--s-4); gap: var(--s-2); }
  .nav__word { font-size: 1.125rem; }
}

/* ============================================================
   10 · HERO  ·  the stage
   ------------------------------------------------------------
   One dominant stage, inset from the page edge and rounded, with
   the headline sitting IN it and the console playing in the lower
   half. The reference fills that stage with a single full-bleed
   photograph. There is no honest photograph for a web studio, so
   the stage is built from tone and the blueprint grid instead,
   which is also what gives the frosted console something to blur
   against: backdrop-filter over a flat colour does nothing.
   ============================================================ */
/* The shared hero wrapper, used by every page. /pricing/, /work/,
   /about/, /contact/ and /thanks/ have no stage and no console: they
   are a heading on a band, and they still need the top padding that
   clears the floating pill. Folding the stage's padding into .hero
   put the nav straight on top of their headings. */
.hero {
  display: flex;
  align-items: center;
  min-height: 78vh;
  padding-top: calc(env(safe-area-inset-top, 0px) + clamp(7.5rem, 4rem + 7vw, 9.5rem));
  padding-bottom: var(--s-7);
}

/* The home page only: the stage carries its own clearance, so the
   wrapper gets out of the way and gives it the full width. */
.hero--stage {
  display: block;
  min-height: 0;
  padding: calc(env(safe-area-inset-top, 0px) + var(--s-3)) var(--s-3) var(--band-y);
}

.hero__stage {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Not 100vh. The label and the service split below the stage have
     to be visible at first paint, the way the reference lets its
     next section peek. */
  min-height: min(86vh, 820px);
  padding-top: calc(env(safe-area-inset-top, 0px) + clamp(5rem, 2rem + 6vh, 7.25rem));
  padding-bottom: clamp(1.5rem, 0.5rem + 2.5vh, 2.75rem);
  border-radius: 14px;
  /* Obsidian, which is only ever seen for the instant before the
     photograph paints, and behind its transparent edges. */
  background: var(--obsidian);
  border: 1px solid rgba(255, 255, 255, 0.07);

  /* THE DARK ROOM. The page is light; this one element is not, so it
     declares the whole dark scope itself. Without this every child
     inherits the page's graphite-on-canvas tokens and renders dark
     text and dark hairlines on a dark photograph: the headline, the
     chip and the ghost button all disappeared. */
  --surface: var(--obsidian);
  --on-surface: var(--bone);
  --on-surface-soft: var(--ash);
  --line: rgba(255, 255, 255, 0.16);
  --line-strong: rgba(255, 255, 255, 0.34);
  --well: rgba(255, 255, 255, 0.08);
  --focus: var(--chalk);
  /* Redefining the token is not enough on its own: .band already
     resolved `color` from the PAGE scope further up, and inherited
     colour does not re-resolve when a descendant changes the
     variable. The headline's first line stayed graphite on a dark
     photograph until this line existed. */
  color: var(--on-surface);
}

/* The atmosphere. Three soft washes plus a top scrim so the
   headline always has darkness under it, which is the job the
   reference's vignette does over its photograph. */
.hero__atmos {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* With a real photograph underneath, this layer's whole job is
     legibility, not invention: darken the top so the headline reads
     against sky, and darken the bottom so the console cards sit on
     shadow rather than on the lit valley. The painted washes that
     stood in for the photo are gone. */
  background:
    /* An even darkening now, not a column-shaped one: the headline is
       on the left and the devices are on the right, so there is no
       single hot spot to protect. */
    linear-gradient(180deg, rgba(0, 0, 0, 0.72) 0%, rgba(0, 0, 0, 0.46) 40%, rgba(0, 0, 0, 0.38) 70%, rgba(0, 0, 0, 0.55) 100%),
    radial-gradient(120% 95% at 50% 45%, transparent 40%, rgba(0, 0, 0, 0.5) 100%);
}
/* The blueprint grid, masked so it dissolves before the edges
   rather than stopping at a hard line. */
/* The photograph IS the texture now. A blueprint grid on top of it is
   two ideas fighting, and the reference puts nothing over its image. */
.hero__photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
  -webkit-user-drag: none;
  user-select: none;
}

.hero__grid {
  position: relative;
  z-index: 2;
  display: grid;
  gap: clamp(2rem, 1rem + 4vw, 3.5rem);
  align-items: center;
  width: 100%;
}
.hero__grid > * { min-width: 0; }
@media (min-width: 1040px) {
  .hero__grid { grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); }
}

/* A filled action on the page is graphite on canvas. In here the
   canvas is a photograph, so it flips to chalk. */
.hero__stage .btn--primary {
  background: var(--chalk);
  color: var(--graphite);
  border-color: var(--chalk);
}
.hero__stage .btn--primary:hover { background: var(--bone); border-color: var(--bone); }
.hero__stage .btn--ghost {
  color: var(--bone);
  border-color: rgba(255, 255, 255, 0.34);
}
.hero__stage .btn--ghost:hover { border-color: var(--bone); }

.hero__copy {
  position: relative;
  z-index: 2;
  /* Every line here sits on a photograph, and a photograph has no
     contract about what is under any given glyph. The scrim does the
     heavy lifting; this is the floor under it, so a line that drifts
     over a lit ridge still separates. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55), 0 1px 14px rgba(0, 0, 0, 0.35);
}

/* Light weight at display size, leading at 1.0, tracking tight.
   The reference sets its headline at 64px / lh 1.0 / -0.02em and
   never goes heavier than regular. */
/* The headline leads the column now that the availability chip is
   gone, so nothing sits above it to be spaced away from. */
.hero__copy > :first-child { margin-top: 0; }
.hero__title {
  margin-top: var(--s-5);
  font-size: clamp(2rem, min(3.9vw, 6.4vh), 3.25rem);
  font-weight: var(--w-light);
  line-height: 1;
  letter-spacing: -0.022em;
}
.hero__title-lead,
.hero__title-rest { display: block; }
/* Two tones from ONE colour at two opacities, which is exactly how
   the reference gets a two-tone headline without a second token. */
.hero__title-rest {
  color: var(--on-surface);
  color: color-mix(in srgb, var(--on-surface) 52%, transparent);
}

.hero__sub {
  margin: clamp(0.75rem, 1.5vh, 1.5rem) auto 0;
  max-width: 54ch;
  font-size: var(--fs-lead);
  font-weight: var(--w-light);
  line-height: 1.55;
  color: var(--on-surface-soft);
  text-wrap: pretty;
}
.hero__cta { margin-top: clamp(1.25rem, 2.4vh, 2rem); }
@media (max-width: 599px) {
  /* Six lines of subhead costs more height than the rest of the copy
     block put together, and the console is what has to stay on
     screen. */
  .hero__sub { font-size: var(--fs-body); line-height: 1.5; }
  /* At the shared floor the headline is 36px, which wraps BOTH lines
     of a two-line headline into two lines each: four ragged lines,
     144px, and the console pushed clean off a small phone. Each span
     is one line again at this size, which is what the two-tone
     headline is for. */
  .hero__title { font-size: clamp(1.625rem, 7.1vw, 2.25rem); }
}
.hero__reassure {
  margin-top: var(--s-4);
  font-size: var(--fs-sm);
  /* Ash is the muted tone for text on the CANVAS. This line sits on
     the brightest part of the photograph instead, so it takes bone
     at reduced opacity: same visual weight, far more headroom. */
  color: color-mix(in srgb, var(--bone) 82%, transparent);
}

/* The honesty label is full-size copy, never fine print. */
.hero__label {
  margin-top: var(--s-5);
  max-width: 62ch;
  font-size: var(--fs-sm);
  color: var(--on-surface-soft);
}

/* The display cap is for a short LAPTOP. A phone is short by nature
   and gets its own, smaller size further down; letting this block
   reach it put the 36px floor back and wrapped the headline into
   four lines again. */
@media (max-height: 880px) and (min-width: 600px) {
  .hero__title { font-size: clamp(2.25rem, min(5.6vw, 7.1vh), 4.25rem); }
  .console__composer { padding-top: var(--s-4); }
  .console__ticket { padding-block: var(--s-3); }
}

/* The two-service split, so "sells two things" is legible
   above the fold, which one headline cannot do. */
.hero__split {
  display: grid;
  gap: 1px;
  margin-top: clamp(2.5rem, 1.5rem + 3vw, 4rem);
  border-top: 1px solid var(--line);
  background: var(--line);
}
.hero__split-item {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-5) 0;
  background: var(--surface);
  transition: background-color var(--dur-fast) var(--ease-out-quart);
}
.hero__split-item:hover { background: var(--well); }
.hero__split-name {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: var(--w-semibold);
  letter-spacing: var(--tr-display);
  line-height: 1.1;
}
.hero__split-note { font-size: var(--fs-sm); color: var(--on-surface-soft); }
@media (min-width: 760px) {
  .hero__split { grid-template-columns: 1fr 1fr; }
  .hero__split-item { padding-inline: var(--s-6); }
  .hero__split-item:first-child { padding-left: 0; }
}

/* ============================================================
   12 · PROBLEM GRID
   ============================================================ */
.problems {
  display: grid;
  gap: 1px;
  background: var(--line);
  border-block: 1px solid var(--line);
}
.problems__item {
  background: var(--surface);
  padding: var(--s-6) var(--s-5);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.problems__item .num { margin-bottom: var(--s-2); }
.problems__item p { font-size: var(--fs-sm); color: var(--on-surface-soft); }
@media (min-width: 680px) { .problems { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1040px) { .problems { grid-template-columns: repeat(4, 1fr); } }

/* ============================================================
   13 · SERVICE CARDS
   ============================================================ */
.services { display: grid; gap: var(--s-5); }
.service {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-7) var(--s-6);
}
.service__title { margin-top: var(--s-2); }
.service__promise {
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  color: var(--card-on);
}
.service__list {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-2);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.service__list li {
  position: relative;
  padding-left: var(--s-5);
  font-size: var(--fs-sm);
  color: var(--card-on-soft);
}
.service__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.6em;
  width: 7px; height: 1px;
  background: currentColor;
}
.service__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-top: auto;
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.service__from {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--card-on-soft);
}
@media (min-width: 880px) { .services { grid-template-columns: 1fr 1fr; gap: var(--s-6); } }

/* ============================================================
   14 · AI ANSWER PANEL
   ------------------------------------------------------------
   The signature move. Left: a mock answer in the Perplexity
   surface language, quoted INSIDE the panel as a UI artifact so
   it does not leak onto the page. Right: three callouts naming
   what made that answer possible, each pointing at a real
   element on this very page.
   ============================================================ */
.aeo { display: grid; gap: var(--s-7); align-items: start; }

/* Band-aware, like a card: the panel is always the inverse of the
   band it sits on. It was hardcoded ink, which vanished the moment
   the AEO band itself became ink. */
.answer {
  background: var(--card-glass);
  color: var(--card-on);
  border: 1px solid var(--card-rim);
  border-radius: var(--r-surface);
  overflow: hidden;
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  box-shadow:
    0 20px 48px -22px rgba(11, 11, 11, 0.38),
    inset 0 1px 0 var(--card-sheen);
}
[data-band="paper"] .answer {
  --on-surface: var(--paper);
  --on-surface-soft: var(--paper-soft);
  --line: rgba(255, 255, 255, 0.12);
  --well: #141414;
}
[data-band="ink"] .answer {
  --on-surface: var(--ink);
  --on-surface-soft: var(--ink-soft);
  --line: rgba(11, 11, 11, 0.1);
  --well: var(--surface-1);
}
.answer__bar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--line);
}
.answer__bar-label {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
}
/* The rounded query field is the Perplexity anchor. */
.answer__query {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: var(--s-5);
  padding: var(--s-4) var(--s-5);
  background: var(--well);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  color: var(--on-surface);
}
.answer__query svg { width: 1rem; height: 1rem; flex: none; color: var(--on-surface-soft); }
.answer__body { padding: 0 var(--s-5) var(--s-5); }
.answer__text {
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
  color: var(--on-surface);
}
/* The cited source is marked by weight and a rule, not by colour.
   This is the one "win state" the whole service is about, so it has
   to read, but the page carries no accent to spend on it. */
.answer__text b {
  color: var(--on-surface);
  font-weight: var(--w-semibold);
  box-shadow: inset 0 -1px 0 currentColor;
}
.answer__sources {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.answer__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.25rem var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  font-family: var(--font-label);
  font-size: var(--fs-label);
  letter-spacing: var(--tr-mono);
  color: var(--on-surface-soft);
}
.answer__chip--first { color: var(--on-surface); border-color: var(--on-surface); }

/* Honesty label. First-class copy at full size, never fine print. */
.answer__disclaimer {
  margin-top: var(--s-4);
  font-size: var(--fs-sm);
  color: var(--on-surface-soft);
}

.aeo__points { display: grid; gap: var(--s-5); }
.aeo__point {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4);
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--line);
}
/* last-of-type, not last-child: .aeo__source is the real last child
   of this grid, so last-child left a stray divider under point 03. */
.aeo__point:last-of-type { border-bottom: none; padding-bottom: 0; }
.aeo__point-num {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  color: var(--on-surface-soft);
  padding-top: 0.2em;
}
.aeo__point p { margin-top: var(--s-2); font-size: var(--fs-sm); color: var(--on-surface-soft); }
/* No margin-top: the grid gap already spaces this, and having both
   produced a visible dead band under the last point. */
.aeo__source {
  padding-top: var(--s-5);
  border-top: 1px solid var(--line-strong);
  font-size: var(--fs-sm);
}
@media (min-width: 980px) {
  .aeo { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); gap: var(--s-8); }
}

/* ============================================================
   15 · PROCESS STEPS
   ------------------------------------------------------------
   Every card uses the same slots, so it reads as a contract
   rather than prose. A connector line runs between them.
   ============================================================ */
/* --connector is resolved HERE, in the band's scope, because the
   connector is drawn in the gap BETWEEN cards and so belongs to the
   band. Reading --line-strong directly from .step would pick up the
   card's inverted scope and draw a dark line on a dark band. */
.steps {
  display: grid;
  gap: var(--s-5);
  counter-reset: step;
  --connector: var(--line-strong);
}
.step {
  position: relative;
  display: grid;
  gap: var(--s-5);
  padding: var(--s-6);
}
.step__head { display: flex; align-items: baseline; gap: var(--s-4); }
.step__num {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-mono);
  color: var(--card-on-soft);
  flex: none;
}
.step__facts {
  display: grid;
  gap: var(--s-4);
  margin: 0;
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.step__facts dt {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--card-on-soft);
  margin-bottom: var(--s-1);
}
.step__facts dd { font-size: var(--fs-sm); color: var(--card-on); }
.steps__note {
  margin-top: var(--s-6);
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}
@media (min-width: 900px) {
  .steps { grid-template-columns: repeat(3, 1fr); gap: var(--s-6); }
  .step__facts { grid-template-columns: 1fr; }
  /* Connector between the three cards. */
  .step:not(:last-child)::after {
    content: "";
    position: absolute;
    top: var(--s-6);
    right: calc(var(--s-6) * -1);
    width: var(--s-6);
    height: 1px;
    background: var(--connector);
  }
}
@media (min-width: 560px) and (max-width: 899px) {
  .step__facts { grid-template-columns: 1fr 1fr; }
}

/* ============================================================
   16 · THE WORK PAGE ROWS
   ------------------------------------------------------------
   /work/ shares these class names with what used to be the home
   page's tile grid. The grid itself is gone, replaced by the
   folder below, but these six selectors are still the only
   styling /work/ has: removing the whole block with the grid
   dropped the facts list to unstyled stacked text.
   Dead with the grid, and deliberately not restored:
   .work, .work-item, .work__foot.
   ============================================================ */
.work-item__art {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--well);
}
.work-item__art img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.04);
  transition: filter var(--dur-slow) var(--ease-out-quart),
              transform var(--dur-slow) var(--ease-out-quart);
}
.work-item__art:hover img,
.work-item__art:focus-visible img {
  filter: grayscale(0);
  transform: scale(1.03);
}
.work-item__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
}
.work-item__name { margin-top: var(--s-1); }
.work-item__what { font-size: var(--fs-sm); color: var(--on-surface-soft); max-width: 46ch; }

.work-row {
  display: grid;
  gap: var(--s-6);
  padding-block: var(--s-7);
  border-top: 1px solid var(--line);
}
.work-row:last-child { border-bottom: 1px solid var(--line); }
.work-row__facts {
  display: grid;
  gap: var(--s-4);
  margin: var(--s-5) 0 0;
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.work-row__facts dt {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
  margin-bottom: var(--s-1);
}
.work-row__facts dd { font-size: var(--fs-sm); }
@media (min-width: 880px) {
  .work-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: var(--s-8); align-items: start; }
  .work-row__facts { grid-template-columns: 1fr 1fr; }
}

/* ============================================================
   16 · THE WORK FOLDER
   ------------------------------------------------------------
   One object instead of a grid of tiles: the name on the left, a
   folder on the right with the projects peeking out of it, and the
   whole folder is the link.

   Three things are load-bearing:

   · The stack order is back, papers, front. The front panel has to
     paint OVER the papers or nothing is "in" the folder, so the
     papers sit between the two halves rather than on top.
   · The papers are clipped by the folder's own box, not by their
     own. They lift on hover, and without `overflow: hidden` on the
     folder they would rise past its top edge and float free.
   · Every transform is on the compositor. The papers are real
     images, and animating anything but transform/opacity here
     repaints three bitmaps a frame.
   ============================================================ */
.work-folder { display: grid; gap: var(--s-8); align-items: center; }
.work-folder > * { min-width: 0; }
@media (min-width: 900px) {
  .work-folder {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    gap: var(--s-9);
  }
}
.work-folder__hint {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-6);
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
}
.work-folder__hint svg { width: 14px; height: 14px; }

/* ---- The folder ---- */
.folder {
  position: relative;
  display: block;
  overflow: hidden;              /* clips the papers as they rise */
  aspect-ratio: 3 / 2;
  max-width: 36rem;
  width: 100%;
  margin-inline: auto;
  padding-top: 7%;               /* room for the tab */
  color: inherit;
  border-radius: var(--r-surface);
}

/* The back half, and the tab that makes it read as a folder at all */
.folder__back {
  position: absolute;
  inset: 7% 0 0;
  border-radius: 10px 10px 4px 4px;
  background: var(--surface-2);
  border: 1px solid var(--line);
}
.folder__tab {
  position: absolute;
  left: 0;
  bottom: 100%;
  display: inline-flex;
  align-items: center;
  height: 2.25rem;
  padding: 0 var(--s-6) 0 var(--s-5);
  border-radius: 8px 14px 0 0;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-bottom: none;
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
}

/* ---- The projects, peeking ---- */
.folder__papers {
  position: absolute;
  inset: 7% 0 0;
  z-index: 1;
}
.folder__paper {
  position: absolute;
  top: 16%;
  width: 46%;
  border-radius: 5px;
  overflow: hidden;
  background: var(--paper-lift);
  border: 1px solid var(--line);
  box-shadow: 0 10px 24px -14px rgba(23, 22, 20, 0.45);
  transform-origin: 50% 85%;
  transition: transform var(--dur-slow) var(--ease-ios);
}
.folder__paper img { display: block; width: 100%; height: auto; }

/* Fanned at rest, so three distinct pieces are legible without
   anything having to move. The spread is `left`, the tilt is
   `transform`: mixing a centring translate with a per-paper offset in
   one transform makes every later value fight the first. */
.folder__paper--a { left: 5%;  transform: rotate(-7deg); z-index: 1; }
.folder__paper--c { left: 49%; transform: rotate(7deg);  z-index: 2; }
.folder__paper--b { left: 27%; transform: rotate(-1deg) translateY(-8%); z-index: 3; }

/* ---- The front half ---- */
.folder__front {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  height: 50%;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-6);
  border-radius: 6px 6px 10px 10px;
  background: var(--well);
  border: 1px solid var(--line);
  /* The lip, so the front reads as a separate sheet rather than a
     block of the same colour. */
  box-shadow: 0 -10px 20px -16px rgba(23, 22, 20, 0.5),
              inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
.folder__count {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
}
.folder__go {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--on-surface);
  color: var(--surface);
  transition: transform var(--dur-base) var(--ease-spring);
}
.folder__go svg { width: 15px; height: 15px; }

/* ---- Open ---- */
.folder:hover .folder__paper--a { transform: rotate(-11deg) translateY(-13%); }
.folder:hover .folder__paper--c { transform: rotate(11deg)  translateY(-13%); }
.folder:hover .folder__paper--b { transform: rotate(-1deg)  translateY(-20%); }
.folder:hover .folder__go { transform: translate(2px, -2px); }

.folder:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}
/* Keyboard gets the same reveal as the pointer: the papers are the
   only thing that says what is in here. */
.folder:focus-visible .folder__paper--a { transform: rotate(-11deg) translateY(-13%); }
.folder:focus-visible .folder__paper--c { transform: rotate(11deg)  translateY(-13%); }
.folder:focus-visible .folder__paper--b { transform: rotate(-1deg)  translateY(-20%); }

@media (prefers-reduced-motion: reduce) {
  .folder__paper,
  .folder__go { transition: none; }
}

/* ============================================================
   17 · QUOTES + STAR RATING
   ------------------------------------------------------------
   The star widget is ported unchanged in mechanism: ONE element,
   a star-shaped mask repeating 5x, background a hard-stop
   gradient filled to --rating/5 so halves are exact.
   ============================================================ */
.quotes { display: grid; gap: var(--s-5); }
.quote { display: flex; flex-direction: column; gap: var(--s-5); padding: var(--s-7) var(--s-6); }
.quote__text {
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  color: var(--card-on);
}
.quote__by { display: flex; flex-direction: column; gap: var(--s-1); margin-top: auto; }
.quote__name {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--w-semibold);
  letter-spacing: 0.01em;
  line-height: 1.3;
}
.quote__role { font-size: var(--fs-xs); color: var(--card-on-soft); }
.quote__url {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-mono);
  color: var(--card-on-soft);
  border-bottom: 1px solid var(--line);
  align-self: flex-start;
}

.rating { display: flex; align-items: center; gap: var(--s-3); margin: 0; }
.rating__stars {
  --star-size: 1.0625rem;
  --star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.6l2.9 6.2 6.8.7-5.1 4.6 1.4 6.7L12 17.4l-6 3.4 1.4-6.7-5.1-4.6 6.8-.7z'/%3E%3C/svg%3E");
  display: block;
  width: calc(var(--star-size) * 5);
  height: var(--star-size);
  background: linear-gradient(90deg,
    var(--card-on) calc(var(--rating, 5) / 5 * 100%),
    var(--line-strong) calc(var(--rating, 5) / 5 * 100%));
  -webkit-mask: var(--star) 0 0 / var(--star-size) var(--star-size) repeat-x;
          mask: var(--star) 0 0 / var(--star-size) var(--star-size) repeat-x;
}
.rating__value {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-mono);
  color: var(--card-on-soft);
}
.quotes__note { margin-top: var(--s-6); font-size: var(--fs-sm); color: var(--on-surface-soft); max-width: 60ch; }
@media (min-width: 880px) { .quotes { grid-template-columns: 1fr 1fr; gap: var(--s-6); } }

/* ============================================================
   18 · PRICING
   ============================================================ */
.plans { display: grid; gap: var(--s-5); align-items: stretch; }
.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  padding: var(--s-7) var(--s-6);
}
/* The highlighted plan is separated by a TOP RAIL plus the filled
   button, not a different fill. That keeps the signal doing
   structural work without becoming a surface.
   The rail is 2px where the card's own border is 1px, so without the
   compensating padding it pushes this card's whole column of content
   down by exactly that pixel and the three prices stop sharing a
   baseline. Thicken the rail and this calc has to follow it. */
.plan--pick {
  border-top: 2px solid var(--on-surface);
  padding-top: calc(var(--s-7) - 1px);
}
.plan__chip { align-self: flex-start; }
/* An empty slot on the cards without a chip, so the three prices sit
   on the same line. Scanning the price is the job of this section,
   and a stepped price row makes that measurably harder.
   The shared min-height is what keeps them equal: a hardcoded slot
   height drifts the moment the chip's padding or type size changes,
   which is exactly how the row ended up 7px out. */
.plan__chip,
.plan__chip-slot { min-height: 30px; }
.plan__chip-slot { display: block; }
@media (max-width: 979px) { .plan__chip-slot { display: none; } }
.plan__name { font-size: 1.75rem; }
.plan__for { font-size: var(--fs-sm); color: var(--card-on-soft); }
.plan__price {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.plan__from {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--card-on-soft);
}
.plan__amount {
  font-family: var(--font-display);
  font-size: 3rem;
  font-weight: var(--w-display-max);
  letter-spacing: var(--tr-display);
  line-height: 0.9;
}
.plan__time {
  font-family: var(--font-label);
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-mono);
  color: var(--card-on-soft);
}
.plan__list { display: flex; flex-direction: column; gap: var(--s-3); padding-top: var(--s-5); border-top: 1px solid var(--line); }
.plan__list li { position: relative; padding-left: var(--s-5); font-size: var(--fs-sm); color: var(--card-on); }
.plan__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 7px; height: 1px;
  background: var(--card-on-soft);
}
.plan .btn { margin-top: auto; width: 100%; }

.addon {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-top: var(--s-6);
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
}
.addon__price {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: var(--w-display-max);
  letter-spacing: var(--tr-display);
  line-height: 1;
}

.excludes { margin-top: var(--s-7); padding-top: var(--s-6); border-top: 1px solid var(--line); }
.excludes__list { display: grid; gap: var(--s-3); margin-top: var(--s-4); }
.excludes__list li {
  position: relative;
  padding-left: var(--s-5);
  font-size: var(--fs-sm);
  color: var(--on-surface-soft);
}
.excludes__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 9px; height: 1px;
  background: var(--line-strong);
}
@media (min-width: 680px) { .excludes__list { grid-template-columns: 1fr 1fr; gap: var(--s-3) var(--s-6); } }
@media (min-width: 980px) { .plans { grid-template-columns: repeat(3, 1fr); gap: var(--s-5); } }
/* The three slabs must share a baseline ROW BY ROW, not just at the
   chip. Between 980 and 1120 the longest name and strapline wrap one
   line further than the others, which stepped the prices by 54px and
   made the one number this section exists to let people compare the
   hardest thing on it to scan. iPad landscape sits inside that band.
   Subgrid is the copy-proof answer: every card adopts the SAME seven
   row tracks from .plans, so each row is sized by the tallest card
   and no slab can drift when the wording changes. Reserving heights
   by hand would need revisiting on every copy edit, which is how the
   chip row came to be 7px out in the first place.
   Seven tracks, in markup order: chip, name, strapline, price,
   timing, list, button. Add a row to a card and this span follows. */
@supports (grid-template-rows: subgrid) {
  @media (min-width: 980px) {
    .plans { grid-template-rows: repeat(7, auto); }
    .plan {
      display: grid;
      grid-row: span 7;
      grid-template-rows: subgrid;
    }
    /* In flex, align-self held the pill to its content width because
       that was the cross axis. In grid the cross axis is justify, so
       this has to be restated or the pill stretches the column. */
    .plan__chip { justify-self: start; }
    /* The button row is sized by the tallest label, and around 1024
       two of the three wrap to a second line. Filling the track keeps
       all three the same height instead of standing a one-line pill
       beside two two-line ones. The margin reset is required, not
       tidying: an auto margin absorbs the free space in a grid area
       and alignment is then ignored, so stretch would do nothing. */
    .plan .btn { align-self: stretch; margin-top: 0; }
  }
}

/* ============================================================
   19 · FAQ
   ------------------------------------------------------------
   Native details/summary so it works with zero JS.
   ============================================================ */
.faq { border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__q {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-5) 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-body);
  font-size: var(--fs-lead);
  font-weight: var(--w-semibold);
  line-height: var(--lh-snug);
  color: var(--on-surface);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--on-surface); }
.faq__icon { position: relative; width: 14px; height: 14px; flex: none; margin-top: 0.45em; }
.faq__icon::before,
.faq__icon::after {
  content: "";
  position: absolute;
  background: var(--on-surface);
  transition: transform var(--dur-base) var(--ease-out-expo);
}
.faq__icon::before { left: 0; top: 6.5px; width: 14px; height: 1.5px; }
.faq__icon::after { left: 6.5px; top: 0; width: 1.5px; height: 14px; }
.faq__item[open] .faq__icon::after { transform: scaleY(0); }
.faq__a { padding: 0 0 var(--s-6); max-width: 68ch; }
.faq__a p { font-size: var(--fs-sm); color: var(--on-surface-soft); }
.faq__foot { margin-top: var(--s-7); }

/* ============================================================
   20 · CLOSING CTA
   ============================================================ */
.close { text-align: center; }
.close__title { max-width: 24ch; margin-inline: auto; }
.close__lead { margin: var(--s-6) auto 0; }
.close__cta { justify-content: center; margin-top: var(--s-7); }
.close__promise {
  margin-top: var(--s-6);
  font-size: var(--fs-sm);
  color: var(--on-surface-soft);
  max-width: 52ch;
  margin-inline: auto;
}

/* ============================================================
   21 · FOOTER
   ------------------------------------------------------------
   The giant wordmark survives the rebuild, on PAPER, which is
   more distinctive than a conventional dark footer and keeps
   the one piece of the old site with real equity.
   ============================================================ */
.footer { padding-top: var(--s-8); padding-bottom: var(--s-6); overflow: hidden; }
.footer__top {
  display: grid;
  gap: var(--s-7);
  padding-bottom: var(--s-8);
  border-bottom: 1px solid var(--line);
}
.footer__col { display: flex; flex-direction: column; gap: var(--s-3); }
.footer__col .label { margin-bottom: var(--s-2); }
/* Band-aware, not hardcoded ink: the footer is paper on most pages
   and ink on /thanks/, where a paper footer would sit against a
   paper hero and break the alternation. */
.footer__link { font-size: var(--fs-sm); color: var(--on-surface-soft); }
.footer__link:hover { color: var(--on-surface); }
.footer__wordmark {
  display: block;
  margin: var(--s-7) 0 var(--s-5);
  font-family: var(--font-display);
  font-size: clamp(4.5rem, 21vw, 19rem);
  font-weight: var(--w-display-max);
  line-height: 0.78;
  letter-spacing: 0.01em;
  text-transform: none;
  white-space: nowrap;
  color: var(--on-surface);
}
.footer__strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  justify-content: space-between;
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.footer__fine {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  letter-spacing: var(--tr-mono);
  color: var(--on-surface-soft);
}
@media (min-width: 760px) { .footer__top { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .footer__top { grid-template-columns: 1.4fr repeat(3, 1fr); } }

/* ============================================================
   22 · FORMS
   ============================================================ */
.form { display: grid; gap: var(--s-6); }
.field { display: flex; flex-direction: column; gap: var(--s-2); }
.field__label {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface);
}
.field__hint { font-size: var(--fs-xs); color: var(--on-surface-soft); }
.field input[type="text"],
.field input[type="email"],
.field input[type="url"],
.field textarea,
.field select {
  width: 100%;
  padding: 0.8125rem var(--s-4);
  background: var(--well);
  color: var(--on-surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  transition: border-color var(--dur-fast) var(--ease-out-quart);
}
.field textarea { min-height: 7.5rem; resize: vertical; }
.field input:hover, .field textarea:hover, .field select:hover { border-color: var(--on-surface); }
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: #c4341f; }
.field__error { font-size: var(--fs-xs); color: #c4341f; }
.field::placeholder { color: var(--on-surface-soft); }

.choices { border: none; padding: 0; margin: 0; }
.choices legend {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface);
  margin-bottom: var(--s-4);
}
.choices__grid { display: grid; gap: var(--s-3); }
.choice {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out-quart),
              background-color var(--dur-fast) var(--ease-out-quart);
}
.choice:hover { border-color: var(--on-surface); }
.choice input { accent-color: var(--on-surface); flex: none; }
.choice:has(input:checked) { border-color: var(--on-surface); background: var(--well); }
@media (min-width: 620px) { .choices__grid { grid-template-columns: 1fr 1fr; } }

.form__submit { display: flex; flex-direction: column; gap: var(--s-3); align-items: flex-start; }
.form__micro { font-size: var(--fs-sm); color: var(--on-surface-soft); }
.form__status { font-size: var(--fs-sm); }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.fallback {
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
  font-size: var(--fs-sm);
  color: var(--on-surface-soft);
}
.fallback a { color: var(--on-surface); border-bottom: 1px solid var(--line-strong); }

.reassure { display: grid; gap: var(--s-5); }
.reassure__item { display: flex; flex-direction: column; gap: var(--s-2); }
.reassure__item p { font-size: var(--fs-sm); color: var(--on-surface-soft); }
@media (min-width: 760px) { .reassure { grid-template-columns: repeat(3, 1fr); gap: var(--s-6); } }

/* Contact layout: the form stays on the SAME band as the hero.
   Sending inputs to an ink band makes them fight the page. */
.contact-grid { display: grid; gap: var(--s-8); align-items: start; }
@media (min-width: 980px) {
  .contact-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: var(--s-9); }
}

/* ============================================================
   23 · REVEAL + REDUCED MOTION
   ------------------------------------------------------------
   NOTE: [data-reveal] has NO hidden state in CSS, by design.
   The from-state is set imperatively in JS so that a visitor
   with no JS, no GSAP, or reduced motion always sees fully
   rendered content. Do not add an opacity:0 rule here.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .work-item__art:hover img { transform: none; }
  .btn:active, .btn:hover { transform: none; }
}

/* ---- Forced colors (Windows High Contrast) ----
   The blueprint grid becomes noise, so it is switched off. The star
   widget specifically needs forced-color-adjust:none because its fill
   is a gradient behind a mask: forced colors would flatten it to a
   solid block and destroy the half-star precision. */
/* Transparency off, or no backdrop-filter at all: go solid. The
   panes stay fully legible either way. */
@media (prefers-reduced-transparency: reduce) {
  .card, .answer, .config {
    background: var(--card-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .config { background: var(--surface); }
  .nav__pill, .actionbar { -webkit-backdrop-filter: none; backdrop-filter: none; }
  .nav__pill { background: var(--paper); }
  .actionbar { background: var(--paper); }
  .band::after { display: none; }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .card, .answer { background: var(--card-surface); }
  .config { background: var(--surface); }
  .nav__pill, .actionbar { background: var(--paper); }
}

@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
  .grid-field::before { background-image: none; }
  .card { border: 1px solid CanvasText; }
  .btn--primary { border: 1px solid ButtonText; forced-color-adjust: none; }
  .rating__stars { forced-color-adjust: none; }
}

/* ============================================================
   24 · INTERACTIVE LAYER · Apple-familiar controls
   ------------------------------------------------------------
   The amendment in section 1 applies from here down: things you
   can TOUCH get soft radii and a whisper of lift. Structure
   above stays flat and sharp.

   None of these wear the signal colour. The acid stays on
   .btn--primary and the live dot only, which is also what
   tools/verify.mjs enforces.
   ============================================================ */

/* ---- iOS segmented control ----
   A recessed track with one raised pill. Pure CSS via :has(),
   so it switches with no JavaScript at all. */
.seg {
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  background: var(--well);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }.seg__item { position: relative; flex: none; }
.seg__item input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.seg__label {
  display: block;
  padding: 0.5rem var(--s-5);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: var(--w-semibold);
  line-height: 1.4;
  white-space: nowrap;
  color: var(--on-surface-soft);
  transition:
    background-color var(--dur-fast) var(--ease-ios),
    color var(--dur-fast) var(--ease-ios);
}
.seg__item:has(input:checked) .seg__label {
  background: var(--on-surface);
  color: var(--surface);
  box-shadow: var(--shadow-lift);
}
.seg__item:has(input:focus-visible) .seg__label {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.seg__item:hover:not(:has(input:checked)) .seg__label { color: var(--on-surface); }

/* ---- iOS switch ----
   51x31 with a 27px thumb: the real iOS metrics, which is most of
   why it reads as familiar rather than as "a toggle". */
.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--s-4);
  cursor: pointer;
}
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch__track {
  position: relative;
  flex: none;
  width: 51px;
  height: 31px;
  border-radius: var(--r-pill);
  background: var(--line-strong);
  transition: background-color 0.25s var(--ease-ios);
}
.switch__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.22);
  transition: transform 0.25s var(--ease-ios);
}
.switch:has(input:checked) .switch__track { background: var(--on-surface); }
.switch:has(input:checked) .switch__thumb { transform: translateX(20px); }
.switch:has(input:focus-visible) .switch__track {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}
.switch__text { font-size: var(--fs-sm); }
.switch__note { display: block; font-size: var(--fs-xs); color: var(--on-surface-soft); }
.switch[data-disabled="true"] { opacity: 0.45; pointer-events: none; }

/* ============================================================
   25 · THE DEVICE STAGE  (CSS 3D, zero libraries)
   ------------------------------------------------------------
   A macOS browser window and an iPhone, built from elements and
   rotated in real 3D with `transform-style: preserve-3d`. Drag
   to spin, release and it springs back to a composed three-
   quarter rest pose.

   Rotation is CLAMPED (see --ry/--rx limits in site.js) rather
   than free-spinning. CSS 3D has no backface geometry, so a full
   spin would show the flat reverse of the window. Constrained
   rotation is also what Apple's own product viewers do.

   Everything here works with no JavaScript: the rest pose is the
   default custom-property value, and the first screenshot ships
   with .is-active in the markup.
   ============================================================ */
.stage {
  position: relative;
  display: grid;
  gap: var(--s-5);
  justify-items: center;
  width: 100%;
  /* Grid and flex children default to min-width:auto, which is
     min-content. The segmented control and the hint are wider
     than 280px at 320, so without this they force the whole hero
     track wide and the page scrolls sideways. */
  min-width: 0;
}
.stage > *, .stage__ui > * { min-width: 0; max-width: 100%; }
.stage__ui { width: 100%; }

.stage__scene {
  width: 100%;
  max-width: 560px;
  padding-block: var(--s-6) 12%;
  perspective: 1500px;
  perspective-origin: 50% 42%;
  /* Vertical panning still scrolls the page; we only claim
     horizontal drag. Without this, a touch drag fights scroll. */
  touch-action: pan-y;
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
}
.stage__scene[data-dragging="true"] { cursor: grabbing; }

.stage__group {
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 7deg)) rotateY(var(--ry, -21deg));
}

/* ---- macOS browser window ---- */
.win {
  position: relative;
  border-radius: var(--r-surface);
  overflow: hidden;
  background: var(--raised);
  border: 1px solid var(--hairline);
  box-shadow: 0 24px 50px -28px rgba(23, 22, 20, 0.32);
  transform-style: preserve-3d;
}
.win__bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 14px;
  background: #f1f1ef;
  border-bottom: 1px solid rgba(13, 13, 13, 0.08);
}
.win__dot { width: 11px; height: 11px; border-radius: 50%; flex: none; }
.win__dot--r { background: #ff5f57; }
.win__dot--y { background: #febc2e; }
.win__dot--g { background: #28c840; }
.win__addr {
  flex: 1;
  margin-left: var(--s-3);
  padding: 3px 10px;
  border-radius: var(--r-pill);
  background: rgba(13, 13, 13, 0.05);
  font-family: var(--font-label);
  font-size: 10px;
  letter-spacing: var(--tr-mono);
  color: #6b6b6b;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.win__view { position: relative; aspect-ratio: 16 / 10; background: #0d0d0d; }

/* ---- iPhone ---- */
.phone {
  position: absolute;
  right: -5%;
  bottom: -17%;
  width: 24%;
  padding: 5px;
  border-radius: 30px;
  background: #0d0d0d;
  box-shadow: var(--shadow-float);
  /* Pushed toward the viewer so it reads as a separate object in
     front of the window rather than a sticker on it. */
  transform: translateZ(80px);
  transform-style: preserve-3d;
}
.phone__screen {
  position: relative;
  aspect-ratio: 390 / 844;
  border-radius: 25px;
  overflow: hidden;
  background: #111;
}
.phone__notch {
  position: absolute;
  top: 6px;
  left: 50%;
  transform: translateX(-50%);
  width: 34%;
  height: 10px;
  border-radius: var(--r-pill);
  background: #0d0d0d;
  z-index: 2;
}

/* ---- The swappable screenshots ----
   All three ship in the markup; only the active one is shown, so
   a visitor with no JavaScript still sees the first project. */
.shot { display: none; }
.shot.is-active { display: block; }
.shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  /* Without this the browser starts its own image-drag the moment
     you press on a screenshot, which fires pointercancel and kills
     the rotation drag a few pixels in. */
  -webkit-user-drag: none;
  pointer-events: none;
}

/* ---- Stage UI ---- */
.stage__ui {
  display: grid;
  justify-items: center;
  gap: var(--s-3);
}

@media (max-width: 620px) {
  .stage__scene { padding-bottom: 17%; }
  /* Tucked fully inside the scene on phones: at 320 the overhang
     is the difference between fitting and a sideways scroll. */
  .phone { width: 28%; right: 0; bottom: -15%; }
}

/* ============================================================
   26 · PRICING TOOL
   ------------------------------------------------------------
   Three slabs, all three prices visible at once. A configurator
   that reveals one price at a time forces the buyer to hunt, and
   comparison is the entire job of this page.

   The one interactive part is the audit toggle, which updates all
   three prices together. The swap is pure CSS :has(), so it works
   with JavaScript off and every figure stays real text in the
   markup for crawlers and assistants. Only approved prices appear;
   nothing is computed from an invented rate.
   ============================================================ */
.pricing-tool { display: grid; gap: var(--s-7); }

.pricing-tool__addon {
  justify-self: start;
  align-items: flex-start;
  max-width: 68ch;
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--line);
  border-radius: var(--r-surface);
}
.pricing-tool__addon .switch__track { margin-top: 2px; }

/* Base price by default; the audit price only once the toggle is on. */
.price--audit { display: none; }
.pricing-tool:has(#add-audit:checked) .price--base { display: none; }
.pricing-tool:has(#add-audit:checked) .price--audit { display: inline; }

/* ============================================================
   27 · STICKY MOBILE ACTION BAR
   ------------------------------------------------------------
   The iOS bottom bar. Frosted, safe-area aware, and it keeps the
   action one thumb-tap away once the hero has gone.
   Its button is INK, never acid: a fixed acid element would sit
   in the same viewport as the pricing CTA and break the
   one-acid-per-viewport rule.
   ============================================================ */
.actionbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-nav);
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--gutter);
  padding-bottom: calc(var(--s-4) + env(safe-area-inset-bottom, 0px));
  background: rgba(255, 255, 255, 0.82);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-top: 1px solid rgba(13, 13, 13, 0.12);
  transform: translateY(110%);
  /* visibility, not just transform: an off-screen bar that is still
     visible is still in the tab order, so a keyboard user would
     land on a link they cannot see. */
  visibility: hidden;
  transition: transform 0.45s var(--ease-ios), visibility 0.45s linear;
}
.actionbar[data-show="true"] { transform: translateY(0); visibility: visible; }
.actionbar__price { display: grid; gap: 2px; }
.actionbar__label {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-soft);
}
.actionbar__amount {
  font-family: var(--font-display);
  font-size: 1.625rem;
  font-weight: var(--w-display-max);
  letter-spacing: var(--tr-display);
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.actionbar .btn {
  background: var(--graphite);
  color: var(--canvas);
  border: 1px solid var(--graphite);
  flex: none;
}
.actionbar .btn {
  box-shadow: none;
}
.actionbar .btn:hover { background: #2a2824; border-color: #2a2824; }
@media (max-width: 899px) { .actionbar { display: flex; } }

/* The bar covers the bottom of the page, so give the footer room
   to be scrolled clear of it. */
@media (max-width: 899px) {
  body:has(.actionbar) .footer { padding-bottom: calc(var(--s-9) + env(safe-area-inset-bottom, 0px)); }
}

/* ============================================================
   28 · SCROLL-DRIVEN WORK REVEAL
   ------------------------------------------------------------
   The thumbnails tilt up out of the page as they come into view,
   the way Apple reveals product shots.

   This is a native scroll-driven animation: no JavaScript, no
   scroll listener, and it runs off the compositor. It animates
   the INNER .work-item__art only, so it composes cleanly with
   the JS [data-reveal] engine that owns the parent's opacity.
   Browsers without animation-timeline simply get the existing
   reveal, which is why there is no fallback to write.
   ============================================================ */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .work-row { perspective: 1000px; }
    .work-item__art {
      animation: art-rise linear both;
      animation-timeline: view();
      animation-range: entry 8% cover 42%;
    }
    @keyframes art-rise {
      from { transform: rotateX(9deg) scale(0.955) translateY(14px); }
      to { transform: none; }
    }
  }
}

/* ============================================================
   29 · THE DEVICE STAGE BAND
   ------------------------------------------------------------
   The stage used to share the hero with the headline, in a
   two-column grid. It has its own band now, following the hero
   the way the reference follows its photograph with product
   screens, so it gets the full measure instead of half of it.
   ============================================================ */
.stage { margin-top: 0; }


/* ============================================================
   30 · REDUCED MOTION FOR THE INTERACTIVE LAYER
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  /* The stage keeps its composed rest pose and stops responding
     to drag. site.js also refuses to wire the pointer handlers. */
  .stage__scene { cursor: default; }
  .stage__group { transition: none; }
  .actionbar { transition: none; }
  .switch__thumb, .switch__track, .seg__label { transition: none; }
}

@media (forced-colors: active) {
  .seg__item:has(input:checked) .seg__label { forced-color-adjust: none; background: Highlight; color: HighlightText; }

  .switch:has(input:checked) .switch__track { forced-color-adjust: none; background: Highlight; }
  .win, .phone, .config { border: 1px solid CanvasText; }
  .actionbar { background: Canvas; backdrop-filter: none; }
}

/* ============================================================
   32 · NARROW-SCREEN CORRECTIONS
   ------------------------------------------------------------
   Appended last on purpose. These are same-specificity overrides of
   base rules, so they only win on source order, and an earlier
   attempt at this one was spliced into the middle of the
   forced-colors block by accident: it matched nothing, the brace
   count still balanced, and every check still passed.
   ============================================================ */
@media (max-width: 599px) {
  /* "Agentic systems" is twice the width of the label it replaced, so
     three items at desktop padding come to 338px: wider than the
     track on every phone under 430. The control already scrolls, so
     it never overflowed the page and nothing caught it, but a
     silently half-clipped last option reads as broken. */
  .seg { gap: 1px; padding: 3px; }
  .seg__label { padding: 0.5rem var(--s-3); font-size: var(--fs-label); }
}

/* ============================================================
   33 · THE ABOUT PORTRAIT
   ------------------------------------------------------------
   The only photograph of a person on the site. It sits at the foot
   of /about/'s right column, under the contact links, where there
   was otherwise half a column of nothing beside a page arguing that
   there is only one person here.

   The frame clips; the caption must sit OUTSIDE it, or `overflow:
   hidden` rounds the text box too.
   ============================================================ */
.portrait { margin-top: var(--s-7); }
/* It leads its column now rather than following the contact links,
   so it takes the column's own top edge. */
.portrait--lead { margin-top: 0; }
.portrait__frame {
  display: block;
  overflow: hidden;
  border-radius: var(--r-surface);
  border: 1px solid var(--line);
  background: var(--well);
}
.portrait__frame img {
  display: block;
  width: 100%;
  height: auto;
}
.portrait__cap {
  margin-top: var(--s-3);
  font-family: var(--font-label);
  font-size: var(--fs-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--on-surface-soft);
}

/* The exclusions list has a full band to itself now, so four items in
   one column would read as a thin ribbon down the left. */
@media (min-width: 760px) {
  .excludes__list { grid-template-columns: 1fr 1fr; gap: var(--s-4) var(--s-7); }
}
