/* ============================================================
   Seek Digital — editorial stylesheet
   Print-first: cream stock, charcoal ink, hairline rules,
   flat colour plates. No radius, no shadow. Gradients in three
   places only — the field (33), the accent ramp (35), nowhere else.

   1. Tokens
   2. Reset & base
   3. Pre-animation states
   4. Rules, grid & shared heads
   5. Buttons & links
   6. Stripbar & masthead
   7. Hero & ledger
   8. Ticker
   9. Services
   10. Process (pinned)
   11. Work
   12. About
   13. Voices
   14. Rates
   15. FAQ
   16. Contact & form
   17. Footer
   18. Responsive
   19. Reduced motion & print
   20. Intro overlay
   21. Craft — focus, caret, optical, widows, transitions
   22. Cursor (removed)
   23. Link kinetics
   24. Live details
   27. Loader (curtain)
   30. Composition — breaking the mirror
   31. The eyes
   33. The stage & the difference blend
   34. Crop marks
   35. The accent ramp & the sampled ink
   36. Consent banner
   28. Line reveals
   25. Dark build — the decisions the token flip could not make
   ============================================================ */

/* ---------- 1. Tokens ----------
   The dark build. Values are taken from hobro.digital's own custom properties
   (--c-bg-primary #000, --c-brand-secondary #DBDCCA,
   --c-border-default #636262, --c-bg-aims #161616).

   The NAMES are deliberately unchanged from the light build. --paper is still
   "the page" and --ink is still "what is written on it"; only which end of the
   scale each sits at has moved. That is what lets two thousand lines of rules
   and the whole of page.css invert from this block alone, instead of from a
   find-and-replace that would have to be re-read line by line. */
:root {
  --ink:    #FFFFFF;   /* the written mark */
  --paper:  #000000;   /* the ground */

  /* ---- The primary ----
     THE PRIMARY IS NO LONGER ONE VALUE. It was #FF2928, a single flat red, and
     a single flat hue is the one thing the reference this build is measured
     against never does: gradient-hero-prompt.png has no primary colour in it
     at all, it has a warm border and a cool centre that trade places.

     So the accent is now a RAMP, and the ramp is the brand. --primary is the
     ramp's anchor — the warm mass the reference frames itself with — and it is
     what every property that cannot take a gradient falls back to: border-color,
     caret-color, text-decoration-color, a 1px rule. --grad-accent below is the
     same ramp as a paintable image, and it is what every filled SURFACE takes.

     The anchor is 6.53:1 on black (the red was 5.60:1) and 3.22:1 on white, so
     it is a better mark than the red was and a slightly worse one on a light
     plate — which costs nothing, because section 25 already moved every
     accent-on-plate case to the plate's own ink. */
  /* THE ACCENT IS #950606 SINCE 2026-10-12, the owner's choice. It is a deep
     red at L .065: white on it is 9.1:1, so it is a FILL with white type on it,
     and it is only 2.3:1 on black, so it is never the colour of type or of
     anything that has to be read. Buttons, bars, rules, bullets and underlines
     take it; every label that used to be accent-coloured is white. */
  --primary: #950606;

  /* THE RAMP. Every stop is L >= .27 and therefore >= 6.5:1 on black, so the
     ramp reads as one bright band against the page at every phase rather than
     as a stripe that dims in the middle. What it is NOT safe against is a
     single fixed ink: black clears every stop, white clears none of them, and
     the deep end added by --grad-accent-wide clears white but not black. That
     is what the ink sampler in main.js module 0c is for — see section 35. */
  --grad-accent: linear-gradient(100deg,
    #D5773A  0%,    /* orange   L .276 */
    #C9924F 24%,    /* amber    L .335 */
    #CF9F7D 42%,    /* sand     L .395 */
    #8FA4B1 60%,    /* periwinkle L .356 */
    #4E9EA4 80%,    /* cyan     L .288 */
    #D5773A 100%);  /* back to the anchor, so the sweep is seamless */

  /* The same ramp with the reference's dark end in it. This one genuinely
     crosses the ink boundary — the green at L .137 wants white type where the
     sand at L .395 wants black — and it is used where that flip is the point. */
  --grad-accent-wide: linear-gradient(100deg,
    #D5773A  0%, #C9924F 18%, #CF9F7D 32%, #8FA4B1 48%,
    #4E9EA4 64%, #3F7253 80%, #D5773A 100%);

  /* --blue and --salmon keep their names because ~65 rules and page.css
     reference them; they are the accent SLOT, and the slot is the ramp's
     anchor. They stayed when the slot was bone and they stayed when it was red;
     a rename is a find-and-replace nobody can re-verify by eye.

     THE SLOT NOW DOES ONE JOB IN TWO MATERIALS, and the split is by property,
     not by name. Anything that sets `color`, `border-color`, `caret-color` or a
     1px bar takes --blue/--salmon and gets the flat anchor: a hue ramp inside a
     letterform or a hairline is a smear, not a gradient, and it costs the
     contrast measurement that a flat value gives for free. Anything that fills
     a SURFACE takes --grad-accent through the .accent-fill rules in section 35
     and gets the ramp moving.

     THE PLATE ROLE IS STILL SEPARATE. It moved out to --plate when the slot
     stopped being bone, and nothing here brings it back: --grad-accent is a
     mark-bright band, not a light ground, and black type on it is only safe
     because every stop was chosen to clear 6.5:1 on black. Do not set the ramp
     behind body copy.

     Both names still resolve to the same value, exactly as they always have. */
  --blue:   var(--ink);   /* the accent slot AS INK: white, because the red
                             cannot be type on black. Fills use --primary. */
  --salmon: var(--ink);   /* the same slot, second name */

  /* The light plate ground, and the old bone value unchanged. This is what the
     case plates, the filled process step, the hero stamp and the focus ring sit
     on or are drawn in, and it is what section 25's ratios were measured
     against — so it had to keep the value, not the name. */
  --plate:  #FFFFFF;   /* the light plate. It was bone (#DBDCCA) until 2026-10-12 */

  /* ---- The palette ----
     RE-SAMPLED FROM THE REFERENCE, and this time the whole of it, not one hue
     out of it. gradient-hero-prompt.png was k-means clustered at k=9 with the
     film grain blurred out first, and these nine values ARE those nine
     clusters — the hex, the luminance and the share of frame each one holds:

       #408A91 26.0%  L .213   teal, the centre mass
       #D57739 15.2%  L .276   orange, the left and right borders
       #377E78 10.0%  L .171   deep teal, the top band
       #A7814D  9.7%  L .245   amber, the band just inside the orange
       #7F94A1  8.9%  L .283   periwinkle, the lower-left quadrant
       #7A7A5F  8.6%  L .189   olive, the transitions
       #3F7253  8.0%  L .137   green, upper-mid and bottom-right
       #CF9F7D  6.7%  L .395   peach, the centre-bottom highlight
       #CDD3CF  6.9%  L .641   the pale wash, a highlight and nothing more

     A PREVIOUS BUILD RE-ANCHORED ALL OF THIS ON A SINGLE RED and this reverses
     that, on purpose. The argument for the red was that a red accent over a
     teal field is a spot colour on someone else's picture. That was true, and
     the resolution it chose — make everything red — bought coherence by
     spending the only thing the reference actually has, which is the trade
     between a warm border and a cool centre. Red's maximum luminance is .2126,
     so a family built out of it cannot hold both a mark and a highlight; the
     old ladder had to invent #FF8A5C and #F5A08C to get its pale end, and those
     are not colours in the reference.

     THE NAMES STILL DID NOT MOVE, for the third time and the same reason: they
     are read by the aurora, the ticker separators, the process numbers, the
     case plates and the featured rate card. --c-cyan is not cyan and --c-mauve
     is not mauve. They are the rungs of a luminance ladder and the ladder is
     what every placement in this file was measured against.

     White copy needs a backdrop at L<=.30 for large text (3:1) and L<=.183 for
     small (4.5:1). Every value below is annotated with its ratio on BLACK,
     because that is the page these are marks on. */
  --c-orange: #950606;   /* L .276 — 6.53:1 on black. THE ANCHOR. was .230 */
  --c-teal:   #2C6B6E;   /* L .122 — 3.44:1. the deep ground.     was .063 */
  --c-cyan:   #4E9EA4;   /* L .288 — 6.75:1. the centre mass      was .321 */
  --c-green:  #3F7253;   /* L .137 — 3.74:1                       was .090 */
  --c-moss:   #7A7A5F;   /* L .189 — 4.78:1. the olive transition was .178 */
  --c-amber:  #C9924F;   /* L .335 — 7.71:1. pale, marks only, and the featured
                            rate card's plate: black on it 7.71:1. was .402 */
  --c-sand:   #CF9F7D;   /* L .395 — 8.91:1. pale, marks only, and the sand
                            case plate: black on it 8.91:1.       was .464 */
  --c-mauve:  #8FA4B1;   /* L .356 — 8.11:1. the periwinkle       was .290 */
  /* deep grounds, for type to sit on */
  --c-teal-deep:  #0C2C2E;
  --c-green-deep: #12312A;

  --ink-70: rgba(255, 255, 255, .70);
  --ink-55: rgba(255, 255, 255, .62);   /* 7.4:1 on black — AA at any size */
  --ink-15: rgba(255, 255, 255, .16);
  --paper-70: rgba(0, 0, 0, .70);
  --paper-45: rgba(0, 0, 0, .55);       /* on bone and on green, both AA */
  --paper-20: rgba(0, 0, 0, .22);

  --panel:  #161616;   /* raised surface — their --c-bg-aims */
  --border: #636262;   /* their --c-border-default */
  --grid:   rgba(65, 65, 65, .40);

  --rule: 1px solid var(--border);
  --hair: 1px solid var(--grid);

  /* Their four faces are commercial and self-hosted (PP Neue Montreal,
     Kamerik 205, Akkurat Mono LL, Freight Big Pro Light Italic), so these are
     the nearest free equivalents rather than the fonts themselves. Role for
     role, matched to their --font-title / --font-text / --font-typewriter /
     --font-cursive. */
  --display: "Outfit", "Helvetica Neue", Arial, sans-serif;          /* ~Kamerik 205 */
  --text: "Inter Tight", "Helvetica Neue", Arial, sans-serif;        /* ~PP Neue Montreal */

  --page: 1240px;
  --gutter: clamp(18px, 4vw, 48px);
  --sheet-y: clamp(72px, 9vw, 140px);
}

/* ---------- 2. Reset & base ---------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* No scroll-behavior here. It was `smooth`, which eased every anchor jump —
     and module 18's lerp scroller was written around escaping it, asking for
     'instant' on each of its sixty writes a second. Both went at once: an
     anchor is navigation, and navigation should land, not glide. Anything that
     genuinely wants easing asks per call (see main.js module 27). */
  scroll-padding-top: 90px;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-size: clamp(16px, .3vw + 15.4px, 17.5px);
  font-weight: 400;
  line-height: 1.5;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg { display: block; max-width: 100%; }
p { margin: 0; }
ul, ol, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3 {
  margin: 0;
  font-family: var(--display);
  text-transform: uppercase;
  /* Bebas was a single-weight condensed face carrying the display work on its
     own width. Outfit is neither, so the weight and the negative tracking are
     what stand in for that — the same 700 / .88 / -.01em hobro runs. */
  font-weight: 700;
  line-height: .88;
  letter-spacing: -.01em;
}

::selection { background: var(--primary); color: var(--ink); }

:focus-visible {
  /* Bone, deliberately, and the one accent-shaped thing the primary does not
     get. A ring has to be visible on the black page, on the pale plates AND on
     the hero's field at once; red on a red field is the exact failure the ring
     exists to prevent, and bone is still the only value in the build that
     clears all three. */
  outline: 2px solid var(--plate);
  outline-offset: 3px;
}

.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;
}

.skip-link {
  position: absolute;
  top: -80px; left: var(--gutter);
  z-index: 300;
  background: var(--primary);
  color: var(--ink);
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: .08em;
  padding: 12px 20px;
  transition: top .2s ease;
}
.skip-link:focus { top: 8px; }

.nb { white-space: nowrap; }
.ink-light { color: var(--ink-55); }

/* ---------- 3. Pre-animation states ----------
   Applied only when scripting is on. main.js strips the .js class
   the moment GSAP is unavailable, so content can never stay hidden. */
.js [data-animate] { opacity: 0; }
.js .mask__i { transform: translateY(105%); }
.js [data-rule] { transform: scaleX(0); }
.js .hl__block { transform: scaleX(0); }

/* Room on both sides so overflow:hidden clips the incoming line and never the
   glyphs: bottom for descenders and periods, top for the diacritics Romanian
   needs (Ă Â Î Ș Ț sit above the cap height). Negative margins hand the space
   back to the layout so line spacing is unchanged.

   BETWEEN TWO MASKS THE MARGINS COLLAPSE, THEY DO NOT ADD. Adjoining vertical
   margins resolve to the most negative of the pair, so the -.18em under one
   mask and the -.16em over the next came to -.18em against .36em of padding,
   and every break between two masks carried .18em more than every break
   inside one. It showed wherever a heading was more than one mask and a mask
   wrapped: "Book a free / strategy call." set BOOK and A FREE 79px apart and
   A FREE and STRATEGY 96px apart; the hero did the same between its lines.
   So the whole .36em is handed back BELOW each mask, where it collapses with
   the next mask's zero top margin to exactly -.36em. The first and last masks
   keep their own edges, which is what a single mask always had. */
.mask {
  display: block;
  overflow: hidden;
  padding-top: .16em;
  margin-top: 0;
  padding-bottom: .2em;
  margin-bottom: -.36em;
}
.mask:first-child { margin-top: -.16em; }
.mask:last-child  { margin-bottom: -.18em; }
.mask__i { display: block; will-change: transform; }

/* ---------- 4. Rules, grid & shared heads ---------- */
.sheet {
  max-width: var(--page);
  margin-inline: auto;
  padding: var(--sheet-y) var(--gutter);
}

.sec-head {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 24px;
  align-items: start;
  margin-bottom: clamp(48px, 6vw, 88px);
}
/* full-bleed across the head's own grid; the 24px row gap replaces the 18px
   padding the border used to sit above */
.sec-head__rule {
  grid-column: 1 / -1;
  height: 1px;
  background: var(--ink);
  transform-origin: left;
  margin-bottom: -6px;
}
.sec-head__no {
  grid-column: 1 / 2;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1.25rem;
  letter-spacing: .1em;
  color: var(--blue);
}
.sec-head__no--cream { color: var(--salmon); }
.sec-head__title {
  grid-column: 2 / 8;
  font-size: clamp(2.6rem, 7vw, 5.6rem);
  line-height: .84;
}
.sec-head__lead {
  grid-column: 9 / 13;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--ink-70);
  padding-top: .5em;
}
.sec-head__lead a {
  color: var(--blue);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------- 5. Buttons & links ---------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 26px 11px;
  border: 1px solid transparent;
  border-radius: 0;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1.02rem;
  letter-spacing: .07em;
  line-height: 1;
  /* a label that wraps on a phone stays centred like the one-line buttons
     beside it, instead of hugging the left edge of its box */
  text-align: center;
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  transition: color .35s cubic-bezier(.4,0,.1,1), border-color .35s ease;
}
.btn--sm { padding: 11px 18px 8px; font-size: .92rem; }
.btn--lg { padding: 18px 34px 15px; font-size: 1.16rem; }
.btn--block { display: flex; width: 100%; }

/* wipe-up fill on hover — a print-shop interaction, not a glow */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform .4s cubic-bezier(.4,0,.1,1);
}
.btn:hover::before, .btn:focus-visible::before { transform: scaleY(1); }

.btn--blue { background: var(--primary); color: var(--ink); }
.btn--blue::before { background: var(--ink); }

.btn--line { border-color: var(--ink); color: var(--ink); }
.btn--line::before { background: var(--ink); }
.btn--line:hover, .btn--line:focus-visible { color: var(--paper); }

.btn--cream { background: var(--paper); color: var(--ink); }
.btn--cream::before { background: var(--primary); }

.btn--line-cream { border-color: var(--paper); color: var(--paper); }
.btn--line-cream::before { background: var(--paper); }
.btn--line-cream:hover, .btn--line-cream:focus-visible { color: var(--ink); }

.arrow-link {
  display: inline-block;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1rem;
  letter-spacing: .07em;
  color: var(--blue);
  padding-bottom: 3px;
  background-image: linear-gradient(var(--primary), var(--primary));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 1px;
  transition: background-size .4s cubic-bezier(.4,0,.1,1), color .3s ease;
}
.arrow-link::after { content: " →"; }
a:hover .arrow-link, .arrow-link:hover { background-size: 0% 1px; }

/* ---------- 6. Stripbar & masthead ---------- */
.masthead {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--paper);
  border-bottom: var(--rule);
  will-change: transform;
}
.masthead__inner {
  max-width: var(--page);
  margin-inline: auto;
  padding: 14px var(--gutter) 11px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.wordmark {
  display: flex;
  align-items: center;
  /* NO `gap` here. `Seek` is a bare text node, so flex makes it an ANONYMOUS
     flex item and a gap applies between it and the <span> as well — the
     wordmark comes apart into "SEEK DIGITAL". The spacing belongs to the mark
     and only the mark, so it is a margin on the mark. */
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1.72rem;
  letter-spacing: .02em;
  line-height: 1;
  white-space: nowrap;
}

/* ---------- The mark ----------
   `assets/img/brand/seek-mark.svg` — the intro's eye held on one frame and
   filled. See BRAND_MARK.md; it is GENERATED from `sdDraw` with the intro's own
   seed, so it is the same three paths the home page draws rather than a tidied
   copy of them. Do not redraw it by hand.

   IT IS A MASK, NOT AN <img> AND NOT INLINE PATHS.

   The mark's counters are cut with `fill-rule: evenodd` and the file paints in
   `currentColor`, which is the whole reason one file is correct on the black
   page, on the bone plates and on the white footer alike. An <img> throws that
   away — the file would render in its own default ink, which is black, and the
   masthead sits on black. Inlining the paths keeps currentColor but copies
   ~2KB of path data into all 26 pages, which is 52KB of markup that has to be
   re-synced by hand every time the mark is regenerated. Exactly the drift
   BRAND_MARK.md §5 exists to prevent.

   A mask has neither problem: the file is fetched and cached once, the paint is
   `currentColor` for real, and there is one copy of the drawing on disk. The
   client logos in the ticker stay inline because their PATHS are animated —
   that is a different requirement, not an inconsistency.

   `mask` is the property; the -webkit- prefix is for Safari < 15.4. Both must
   stay: without the prefix the element paints as a plain currentColor block. */
.wordmark__mark {
  display: block;
  flex: none;
  margin-right: .42em;
  /* the mark's ink runs 6..234 x 6.8..114.5 in a 236x116 viewBox — ~2:1 */
  width: 1.94em;
  height: .95em;
  background-color: currentColor;
  -webkit-mask: url("../img/brand/seek-mark.svg") center / contain no-repeat;
          mask: url("../img/brand/seek-mark.svg") center / contain no-repeat;
}
/* Masking is one of the properties that opens a BACKDROP ROOT, so anything
   inside it stops seeing the page behind it. That is fine here and is why the
   mark is a sibling of the type rather than wrapped around it — but it is the
   reason this element must never be given the difference blend the ticker
   logos and the corner eye use (rule 11). It has no blend; it takes the
   wordmark's own ink, which section 1 already gets right on every ground.

   Forced colours drop mask images in some engines, leaving a currentColor
   rectangle where the eye was. A rectangle is worse than nothing beside a
   wordmark that already says the name, so it goes. */
@media (forced-colors: active) {
  .wordmark__mark { display: none; }
}
/* On the field, not on the page — the masthead is transparent over the hero.
   The field is the primary now, so the primary is the one colour the wordmark
   cannot take there. Bone. */
.wordmark span { color: var(--plate); }

.masthead__nav {
  display: flex;
  gap: clamp(16px, 2.2vw, 30px);
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1.02rem;
  letter-spacing: .09em;
  margin-left: auto;
}
.masthead__nav a {
  position: relative;
  padding-bottom: 2px;
}
.masthead__nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform .35s cubic-bezier(.4,0,.1,1);
}
.masthead__nav a:hover::after { transform: scaleX(1); transform-origin: left; }
.masthead__nav a.is-active { color: var(--blue); }
.masthead__nav a.is-active::after { background: var(--primary); transform: scaleX(1); }

.masthead__end { display: flex; align-items: center; gap: 12px; }

/* Sound toggle. Off is the resting state and the only state a visitor gets
   without asking, so it is set quietly — ink-55, the same display face and
   tracking as the language switcher beside it, and no colour of its own. */
.langswitch {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .95rem;
  letter-spacing: .1em;
  white-space: nowrap;
}
.langswitch > * { padding-bottom: 1px; }
.langswitch [aria-current] {
  color: var(--ink);
  border-bottom: 1px solid var(--ink);
}
/* .78, not --ink-55. The inactive language has to stay quieter than the active
   one, and on the black page --ink-55 does that at 7.4:1. In the masthead it
   also crosses the hero's aurora, where the right-edge orange bleeds up under
   it through a 90px blur and took it to 4.34:1 — under the 4.5:1 it needs at
   15px. Moving the lobe did not fix it; the glow reaches too far. So the mark
   carries more ink and is still plainly the unselected one, since the active
   language is full --ink AND underlined.

   .85, not .78: the field churns now, so a single measurement is not a
   guarantee. At .78 it sat at 4.59:1 against a 4.5 requirement, which is inside
   the swing the three layers produce between them. */
.langswitch a {
  color: rgba(255, 255, 255, .85);
  border-bottom: 1px solid transparent;
  transition: color .25s ease, border-color .25s ease;
}
/* Same reason as the wordmark: this bar is transparent over the field. */
.langswitch a:hover { color: var(--plate); border-bottom-color: var(--plate); }
.langswitch::before {
  content: "";
  align-self: stretch;
  width: 1px;
  background: var(--ink-15);
  margin-right: 5px;
}
@media (max-width: 900px) {
  .langswitch::before { display: none; }
}

.burger {
  display: none;
  width: 42px; height: 38px;
  padding: 10px 8px;
  background: none;
  border: 1px solid var(--ink);
  cursor: pointer;
  flex-direction: column;
  justify-content: space-between;
}
.burger span {
  display: block;
  height: 1.5px;
  background: var(--ink);
  transition: transform .35s cubic-bezier(.4,0,.1,1), opacity .2s ease;
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

.mobile-menu {
  border-top: var(--rule);
  padding: 8px var(--gutter) 28px;
  background: var(--paper);
  max-height: calc(100dvh - 60px);
  overflow-y: auto;
}
.mobile-menu[hidden] { display: none; }
.mobile-menu nav { display: block; margin-bottom: 22px; }
.mobile-menu nav a {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 15px 0 12px;
  border-bottom: var(--hair);
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 2rem;
  letter-spacing: .02em;
}
.mobile-menu nav a i {
  font-style: normal;
  font-size: .85rem;
  letter-spacing: .12em;
  color: var(--blue);
}
.mobile-menu nav a:hover { color: var(--blue); }

/* ---------- 7. Hero & ledger ---------- */
.hero {
  max-width: var(--page);
  margin-inline: auto;
  padding: clamp(40px, 6vw, 84px) var(--gutter) 0;
}

/* CAPPED BY HEIGHT AS WELL AS WIDTH. At 11vw alone a 1440x820 laptop set the
   headline at the 9.5rem cap, four lines of it filled the whole first screen,
   and the lead, the primary button and the proof line all started below the
   fold (the lead's first line measured at y 846). The min() only bites where
   the viewport is wider than about 1.4:1 — phones and tall screens are still
   width-bound and unchanged — and svh rather than vh so a mobile toolbar
   sliding away cannot resize the headline mid-read. The vh line is the
   fallback for browsers without the small-viewport units. */
.hero__title {
  font-size: clamp(2.8rem, min(11vw, 15.5vh), 9.5rem);
  font-size: clamp(2.8rem, min(11vw, 15.5svh), 9.5rem);
  line-height: .84;
  letter-spacing: .008em;
  margin-bottom: clamp(34px, 4.5vw, 60px);
}

/* ROMANIAN CAPS NEED THEIR OWN LEADING. Î, Â and Ă carry marks above the cap
   height, and at .84 the mark on one line lands in the letters of the line
   above it: "ÎL" put its circumflex under the U of SITE-UL, where it read as
   a stray tick. This used to be hidden by accident — the mask margins
   collapsed (§3) and every break between masks carried .18em of slack — but
   breaks INSIDE a mask never had it, so ĂĂ under APOI was always touching.
   Leading, not slack: it applies at every break, in both headings that set
   several lines of display caps. */
:lang(ro) .hero__title,
:lang(ro) .contact__title { line-height: .96; }

.hl {
  position: relative;
  display: inline-block;
  font-style: normal;
}
.hl__block {
  position: absolute;
  inset: .06em -.05em .04em;
  background: var(--plate);
  transform-origin: left;
  z-index: 0;
}
.hl__t { position: relative; z-index: 1; }

/* The split rule under the headline. It used to be a border on .hero__aside;
   a border cannot be drawn, and on this page the rules ARE the brand — the
   layout should rule itself up the way a printed page is set. */
.hero__rule {
  display: block;
  height: 1px;
  background: var(--ink);
  transform-origin: left;
}
.hero__aside {
  display: grid;
  grid-template-columns: minmax(0, 46ch) 1fr auto;
  column-gap: clamp(24px, 4vw, 64px);
  row-gap: 14px;
  align-items: end;
  padding-top: 26px;
}
.hero__lead {
  grid-column: 1;
  font-size: clamp(1.02rem, .45vw + .95rem, 1.2rem);
  line-height: 1.55;
  /* Full ink, not --ink-70. That token is 70% white, which is ~10:1 on the
     black page and 2.47:1 on the hero's aurora — it was written for a ground
     this element no longer has. Everywhere else on the site --ink-70 is still
     correct, so the fix is scoped here rather than to the token. */
  color: var(--ink);
}
.hero__lead--tight { color: var(--ink); }
.hero__lead strong { font-weight: 600; }

.hero__actions {
  grid-column: 3;
  grid-row: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: flex-end;
}
/* One line of reassurance under the buttons. It was a bone chip with a square
   bullet and tracked capitals; it is a sentence now, in the text face. */
.stamp {
  grid-column: 3;
  grid-row: 2;
  justify-self: end;
  font-family: var(--text);
  font-size: .95rem;
  line-height: 1.4;
  color: var(--ink);
}

/* sits inside .hero, which already supplies the page gutter */
.ledger {
  margin-top: clamp(56px, 7vw, 96px);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.ledger__cell {
  border-top: var(--rule);
  border-bottom: var(--rule);
  border-left: var(--hair);
  padding: 18px 20px 22px;
}
.ledger__cell:first-child { border-left: 0; padding-left: 0; }
.ledger__cell b {
  display: block;
  font-family: var(--display);
  text-transform: uppercase;
  font-weight: 400;
  font-size: clamp(2.6rem, 4.6vw, 4.2rem);
  line-height: .9;
  letter-spacing: .01em;
  color: var(--blue);
  font-variant-numeric: tabular-nums;
  margin-bottom: 8px;
}
.ledger__cell span {
  font-size: .88rem;
  line-height: 1.45;
  color: var(--ink-70);
}

/* ---------- 8. Ticker ---------- */
.ticker {
  margin-top: clamp(56px, 7vw, 96px);
  border-top: var(--rule);
  border-bottom: var(--rule);
  padding: clamp(16px, 2.2vw, 26px) 0;
  overflow: hidden;
}
.ticker__track { display: flex; width: max-content; }
/* Until the loop is built the strip is static, so it must sit on the page gutter like
   everything else. Once cloned it runs full-bleed, and the padding would break the cycle. */
.ticker__track:not([data-built]) {
  padding-left: max(var(--gutter), calc((100% - var(--page)) / 2 + var(--gutter)));
}
.ticker__group {
  display: flex;
  align-items: center;
  gap: clamp(40px, 6vw, 92px);
  padding-right: clamp(40px, 6vw, 92px);
  white-space: nowrap;
}
.ticker__item { display: flex; align-items: center; }

/* real client marks — set large and widely spaced, so the strip reads as a
   considered rhythm rather than a wall of repeats.

   THE LOGOS ARE INVERTED AGAINST WHATEVER IS BEHIND THEM, and that is why both
   source files are now pure white on transparent. Negru Zi shipped as a white
   mark on its own #282828 tile: a black square is a sticker on the page, and on
   a black page it is an invisible sticker with a mark floating in it. The tile
   is gone, the viewBox is cropped to the mark itself, and both files carry
   fill:#FFFFFF and nothing else.

   difference against white returns |255 - backdrop|, which is the exact
   inversion of the backdrop, so a white source is the ONLY source that gives a
   true invert. That is the contrast fix: over the black page the marks come out
   white, over the aurora's trace they come out as its complement, and over a
   bone case plate scrolling past they come out dark. There is no value that
   would have cleared all three on its own, which is the whole reason the strip
   had a contrast problem.

   This is the exception to the "no mix-blend-mode" note in section 33. That
   note is about TYPE: difference against a coloured mid-tone returns another
   coloured mid-tone, which is unreadable at 14px. A logo is a large solid
   shape, and a shifted hue at the same separation still reads as the mark.

   The one failure case difference has is a backdrop at exactly mid-grey, which
   inverts to itself. The strip sits on --paper with only the faded stage behind
   it, so it never meets one; the guard is in the ticker's own background, not
   here. */
.ticker__logo {
  display: block;
  width: auto;
  height: clamp(46px, 5.8vw, 76px);
  mix-blend-mode: difference;
}
/* the tile lockup is optically heavier than a wordmark, so it sits a touch smaller.
   Cropped to its own bbox it is a TALL mark (229x440) where it used to be a
   595-square tile, so this is now measuring the mark and not the plate around it. */
.ticker__logo--negru-zi { height: clamp(52px, 6.4vw, 84px); }

/* Forced-colours and print both flatten the blend to nothing, and a white mark
   on white paper is a blank strip. Drop the blend and re-ink there. */
@media (forced-colors: active) {
  .ticker__logo { mix-blend-mode: normal; }
}

/* THE SEPARATOR IS THE MARK, NOT A GLYPH. It was an eight-spoke asterisk, which
   is a dingbat every template uses and says nothing about whose strip this is.

   It is masked rather than <img> for the same reason the masthead's is (see the
   .wordmark__mark note above): a mask paints in real currentColor, so the warm/
   cool alternation in section 35 still drives it, and there is one copy of the
   drawing on disk instead of one per page. An <img> would have frozen it at a
   single colour and broken that ramp.

   It must NOT take the difference blend the ticker LOGOS carry (rule 11): those
   are white sources inverting their backdrop, this is a coloured mark taking a
   measured palette token, and masking opens a backdrop root anyway. */
.ticker__sep {
  flex: none;
  /* the mark's ink runs 6..234 x 6.8..114.5 in a 236x116 viewBox — ~2:1 */
  width: clamp(2.2rem, 3.2vw, 3rem);
  height: clamp(1.08rem, 1.57vw, 1.47rem);
  background-color: currentColor;
  color: var(--salmon);
  -webkit-mask: url("../img/brand/seek-mark-compact.svg") center / contain no-repeat;
          mask: url("../img/brand/seek-mark-compact.svg") center / contain no-repeat;
}
/* Forced colours drops mask images in some engines and would leave a solid
   currentColor rectangle between the two client logos. The asterisk comes back
   instead — worse than the mark, far better than a block. */
@media (forced-colors: active) {
  .ticker__sep {
    -webkit-mask: none; mask: none;
    background-color: transparent;
    width: auto; height: auto;
    font-family: var(--display);
    font-size: clamp(1.1rem, 1.6vw, 1.5rem);
    line-height: 1;
  }
  .ticker__sep::before { content: "\2733"; }
}

/* ---------- 9. Services ---------- */
.pillar {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 24px;
  border-top: var(--rule);
  padding-top: 28px;
  padding-bottom: clamp(48px, 6vw, 84px);
}
.pillar__id { grid-column: 1 / 2; }
.pillar__no {
  display: block;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 2.6rem;
  line-height: 1;
  color: var(--blue);
}
.pillar__rule {
  display: block;
  height: 1px;
  background: var(--ink);
  margin-top: 14px;
  transform-origin: left;
}
.pillar__head { grid-column: 2 / 7; }
.pillar__title {
  font-size: clamp(2.2rem, 4.4vw, 3.7rem);
  margin-bottom: 20px;
}
.pillar__lead {
  font-size: 1.05rem;
  color: var(--ink-70);
  margin-bottom: 22px;
  max-width: 40ch;
}
.pillar__list { grid-column: 8 / 13; }
.pillar__list > div {
  border-top: var(--hair);
  padding: 15px 0 16px;
}
.pillar__list > div:first-child { border-top: 0; padding-top: 0; }
.pillar__list dt {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1.28rem;
  letter-spacing: .05em;
  margin-bottom: 4px;
}
.pillar__list dt a {
  background-image: linear-gradient(var(--primary), var(--primary));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  padding-bottom: 2px;
  transition: background-size .35s cubic-bezier(.4,0,.1,1), color .25s ease;
}
.pillar__list dt a:hover { color: var(--blue); background-size: 100% 1px; }
.pillar__list dd {
  font-size: .95rem;
  line-height: 1.5;
  color: var(--ink-70);
}

/* ---------- 10. Process (pinned horizontal) ---------- */
/* TRANSPARENT, not --paper. An opaque section occludes the fixed field behind
   it, and this one is 900px of flat black in the middle of the page: the colour
   ran down to it, stopped dead on its top rule and resumed underneath. That is
   the same clipping the per-section washes had, arriving by a different route —
   a background does not have to be a layer to cut a gradient in half. The
   section still reads as its own block; it has a rule at the top and the
   densest type on the page. */
.process {
  border-top: var(--rule);
  background: transparent;
}
.process__viewport {
  padding: clamp(40px, 5vw, 72px) 0 0;
  overflow: hidden;
}
.process__head {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin-bottom: clamp(30px, 4vw, 52px);
}
.process__head .sec-head__no { grid-column: auto; }
.process__head .sec-head__title { grid-column: auto; }
.process__hint {
  margin-left: auto;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .95rem;
  letter-spacing: .14em;
  color: var(--ink-55);
}
.process__hint span { display: inline-block; }

.process__track {
  display: flex;
  padding-left: max(var(--gutter), calc((100vw - var(--page)) / 2 + var(--gutter)));
  will-change: transform;
}
.pstep {
  flex: none;
  width: clamp(300px, 34vw, 460px);
  margin-right: 1px;
  padding: 30px 34px 34px;
  border-top: var(--rule);
  border-bottom: var(--rule);
  /* Translucent, for the same reason .process is. Five opaque cards in a row
     are a black band across the page, and the field died at them exactly as it
     died at the section. The rules top and bottom are what make these read as
     cards; the ground never was. At .55 over the field the copy still sits on
     L .005, which is darker than the page it replaced. */
  background: rgba(0, 0, 0, .55);
  position: relative;
}
.pstep::after {
  content: "";
  position: absolute;
  top: 0; right: -1px; bottom: 0;
  width: 1px;
  background: var(--ink-15);
}
/* THE NUMERAL IS THE SECTION'S ONE BIG GESTURE. It was 4.2rem of JetBrains
   Mono — a label pretending to be a heading, and the single most template-
   shaped thing on the page, because a numbered timeline with a small mono
   figure above a title is the default composition of the genre.

   Now it is the display face at plate scale, sitting ON a hairline rather than
   floating above the title, so the number and the rule read as one marker and
   the step title starts below the line rather than under a stacked label.
   tabular-nums because these are a counted sequence and 01/02/03/04 must hold
   the same width; the negative tracking is Outfit's own, at a size where its
   default spacing opens up. Colour still counts through the palette — see
   section 35 — so the sequence reads as a sequence. */
.pstep__no {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(5rem, 7vw, 7.6rem);
  line-height: .84;
  letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
  color: var(--salmon);
  margin-bottom: 0;
  padding-bottom: 16px;
  border-bottom: var(--hair);
}
.pstep h3 {
  font-size: clamp(1.9rem, 2.6vw, 2.5rem);
  margin-top: 24px;
  margin-bottom: 14px;
}
.pstep p {
  font-size: 1rem;
  color: var(--ink-70);
  margin-bottom: 26px;
  max-width: 34ch;
}
.pstep__when {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .95rem;
  letter-spacing: .12em;
  color: var(--ink);
  border-top: var(--hair);
  padding-top: 12px;
  display: block;
}
/* A whole card, not a mark. In the primary it is a 300-460px slab of red with
   black type at 5.60:1 and its lead paragraph (--paper-70) at 3.97:1, under a
   4.5:1 requirement. It stays on the plate. */
.pstep--fill {
  background: var(--plate);
  border-color: var(--plate);
  color: var(--paper);
}
.pstep--fill .pstep__no { color: var(--salmon); }
.pstep--fill p { color: var(--paper-70); }
.pstep--fill .pstep__when { color: var(--paper); border-top-color: var(--paper-20); }
.pstep--fill::after { display: none; }

/* Held to the CONTENT column, not the page box. At width:100% / max-width
   --page it ran a gutter wider than the head above it on both sides — 100px
   to 1340px against a 148–1292 column at 1440 — so the one horizontal line
   in the section was the one thing not on the grid. */
.process__bar {
  width: calc(100% - 2 * var(--gutter));
  max-width: calc(var(--page) - 2 * var(--gutter));
  margin: clamp(30px, 4vw, 48px) auto 0;
  height: 2px;
  background: var(--ink-15);
  position: relative;
}
.process__bar span {
  position: absolute;
  inset: 0 auto 0 0;
  width: 100%;
  background: var(--primary);
  transform: scaleX(0);
  transform-origin: left;
}

/* ---------- 11. Work ---------- */
.case {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 48px);
  align-items: start;
  border-top: var(--rule);
  padding: clamp(34px, 4vw, 56px) 0;
  color: inherit;
}
.case__plate {
  grid-column: 1 / 6;
  position: relative;
  aspect-ratio: 5 / 4;
  overflow: hidden;
  display: grid;
  place-items: center;
}
.case--flip .case__plate { grid-column: 8 / 13; grid-row: 1; }
.case--flip .case__body { grid-column: 1 / 7; grid-row: 1; }

/* the detail column runs far taller than the plate — hold the plate alongside it */
@media (min-width: 901px) {
  .case__plate {
    position: sticky;
    top: 104px;
    align-self: start;
  }
}

/* One plate per case, no two the same — rule 12 applied to colour rather than
   to layout. All three still carry BLACK type, so all three had to stay pale:
   sand is L .453, which puts black on it at 10.1:1, comfortably past the 4.5:1
   the small stamps need. A saturated plate here would have meant re-inking
   every label sitting on it. */
.case__plate--blue   { background: var(--plate); }       /* bone — see --plate */
.case__plate--salmon { background: var(--c-sand); }      /* pale sand */
.case__plate--ink    { background: var(--ink); }

/* printed screen texture instead of a fake screenshot */
.case__plate::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(0,0,0,.10) 0 1px,
    transparent 1px 7px
  );
}
.case__plate--salmon::after {
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(40,40,40,.10) 0 1px,
    transparent 1px 7px
  );
}

/* plate furniture: running number top-left, live stamp bottom-right */
.case__no,
.case__stamp {
  position: absolute;
  z-index: 2;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .8rem;
  letter-spacing: .16em;
  line-height: 1;
  color: var(--paper-70);
}
.case__no    { top: 0; left: 0;  padding: 13px 15px; }
.case__stamp { bottom: 0; right: 0; padding: 13px 15px; }
.case__stamp::before {
  content: "";
  display: inline-block;
  width: 6px; height: 6px;
  margin-right: 8px;
  vertical-align: .08em;
  background: currentColor;
}
.case__plate--salmon .case__no,
.case__plate--salmon .case__stamp { color: var(--ink-70); }

/* ---- client logo plate: solid mark that redraws itself as line art on hover ----
   Every path carries pathLength="1", so one dasharray value works for both logos
   whatever their viewBox. GSAP parallaxes .case__logo, so hover transforms are
   applied to the inner <svg> instead — never to the same element. */
.case__logo {
  position: relative;
  z-index: 1;              /* over the screen texture — logos read clean */
  width: 66%;
  height: 60%;
  color: var(--paper);
  will-change: transform;
}
.case__logo--negru-zi { width: 46%; height: 62%; }
.case__plate--salmon .case__logo { color: var(--ink); }
.case__logo svg {
  width: 100%;
  height: 100%;
  overflow: visible;
  transition: transform .8s cubic-bezier(.4,0,.1,1);
}
.case__logo path {
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 0;
  vector-effect: non-scaling-stroke;
}

@media (hover: hover) {
  .case__logo path {
    fill-opacity: 1;
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    transition:
      fill-opacity .5s cubic-bezier(.4,0,.1,1) .28s,
      stroke-dashoffset .5s cubic-bezier(.4,0,.1,1),
      stroke-width 0s linear .5s;
  }
  .case:hover .case__logo path,
  .case:focus-within .case__logo path {
    fill-opacity: 0;
    stroke-dashoffset: 0;
    stroke-width: 1.35;
    transition:
      fill-opacity .22s cubic-bezier(.4,0,.1,1),
      stroke-dashoffset .95s cubic-bezier(.4,0,.1,1) calc(var(--i, 1) * .035s),
      stroke-width 0s linear;
  }
  .case:hover .case__logo svg,
  .case:focus-within .case__logo svg { transform: scale(1.035); }
}

.case__body { grid-column: 7 / 13; }
.case__tag {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .95rem;
  letter-spacing: .12em;
  color: var(--blue);
  margin-bottom: 12px;
}
.case__title {
  font-size: clamp(2.4rem, 5vw, 4.2rem);
  margin-bottom: 14px;
  transition: color .3s ease;
}
/* stretched link — the whole row is clickable, but only one <a> is nested */
.case__link { color: inherit; }
.case__link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
.case:hover .case__title { color: var(--blue); }

.case__desc {
  font-size: 1.05rem;
  color: var(--ink-70);
  max-width: 46ch;
  margin-bottom: 26px;
}
.case__figures {
  display: flex;
  gap: clamp(24px, 4vw, 56px);
  border-top: var(--hair);
  border-bottom: var(--hair);
  padding: 16px 0;
  margin-bottom: 24px;
}
.case__figures dt {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: clamp(2.2rem, 3.4vw, 3rem);
  line-height: 1;
  color: var(--blue);
  font-variant-numeric: tabular-nums;
}
.case__figures dd {
  font-size: .86rem;
  color: var(--ink-70);
  margin-top: 4px;
}

/* factual spec grid — same four facts the case study page opens with */
.case__spec {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: var(--hair);
  margin-bottom: 26px;
}
.case__spec > div {
  border-bottom: var(--hair);
  padding: 11px 0;
}
.case__spec > div:nth-child(even) {
  border-left: var(--hair);
  padding-left: clamp(16px, 2vw, 26px);
}
.case__spec > div:nth-child(odd) {
  padding-right: clamp(16px, 2vw, 26px);
}
.case__spec dt {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .8rem;
  letter-spacing: .14em;
  color: var(--ink-55);
}
.case__spec dd {
  font-size: .95rem;
  margin-top: 3px;
}

/* what was actually built */
.case__list {
  display: grid;
  gap: 8px;
  margin-bottom: 26px;
  max-width: 48ch;
}
.case__list li {
  position: relative;
  padding-left: 24px;
  font-size: .97rem;
  color: var(--ink-70);
  line-height: 1.5;
}
.case__list li::before {
  content: "*";
  position: absolute;
  left: 0;
  top: .12em;
  font-family: var(--display);
  text-transform: uppercase;
  color: var(--blue);
}

.case__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px clamp(22px, 3vw, 40px);
}
/* sits above the stretched link so the live site stays separately clickable */
.case__live {
  position: relative;
  z-index: 2;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .95rem;
  letter-spacing: .07em;
  color: var(--ink-55);
  padding-bottom: 3px;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size .4s cubic-bezier(.4,0,.1,1), color .3s ease;
}
.case__live::after { content: " \2197"; }
.case__live:hover, .case__live:focus-visible {
  color: var(--blue);
  background-size: 100% 1px;
}

/* ---------- 12. About ---------- */
.about__grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(28px, 4vw, 64px);
}
.about__statement { grid-column: 1 / 8; }
.about__big {
  font-size: clamp(1.5rem, 2.6vw, 2.2rem);
  line-height: 1.28;
  letter-spacing: -.01em;
  margin-bottom: 28px;
}
.about__body {
  font-size: 1.02rem;
  color: var(--ink-70);
  max-width: 52ch;
  margin-bottom: 16px;
}
.about__facts { grid-column: 9 / 13; align-self: start; }
.about__facts > div {
  border-top: var(--rule);
  padding: 16px 0 18px;
}
.about__facts dt {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1.25rem;
  letter-spacing: .05em;
  margin-bottom: 3px;
}
.about__facts dd { font-size: .93rem; color: var(--ink-70); }

/* ---------- 13. Voices ---------- */
/* the measure is set on the quote itself, at its own display size */
.voice--lead { margin: 0 0 clamp(40px, 5vw, 72px); }
.voice--lead blockquote { margin: 0; }
.voice--lead p {
  font-size: clamp(1.5rem, 3.4vw, 2.9rem);
  line-height: 1.18;
  font-style: italic;
  font-weight: 300;
  letter-spacing: -.015em;
  max-width: 24ch;
  margin-bottom: 22px;
}
.voice figcaption {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1rem;
  letter-spacing: .09em;
  color: var(--ink-55);
}
.voice figcaption b { font-weight: 400; color: var(--ink); }

.voices__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(28px, 4vw, 60px);
}
.voices__grid .voice {
  margin: 0;
  border-top: var(--rule);
  padding: 22px 0 26px;
}
.voices__grid blockquote { margin: 0 0 16px; }
.voices__grid p {
  font-size: 1.05rem;
  line-height: 1.55;
  max-width: 40ch;
}

/* ---------- 14. Rates ---------- */
.ratecard {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: var(--rule);
}
.rate {
  display: flex;
  flex-direction: column;
  padding: 30px 30px 34px;
  border-left: var(--hair);
}
.rate:first-child { border-left: 0; padding-left: 0; }
.rate:last-child { padding-right: 0; }

/* Cross-column alignment. Three cards side by side are read ACROSS — that is
   the whole point of the format — so the name, the price rule and the list
   must begin at the same y in all three.

   The ::before slot below and .rate__for's min-height were an earlier attempt
   at this by hand, and they only got as far as the flag and the descriptor:
   measured at 1440 the three names aligned at 530/529/530 but the prices came
   in at 643/677/643 and the lists at 777/811/828 — a 51px spread. Reserving
   space by hand cannot work here, because what pushes a row down is WRAPPING,
   and which strings wrap changes with the width: "Growth Engine" takes two
   lines where "Partner" takes one, and "Quoted per phase" takes two where
   "From €800" takes one.

   Subgrid removes the guess. The rows are declared once on the container and
   every card snaps to them whatever its copy does, at every width. Guarded by
   @supports, so a browser without it keeps the old flex column and the old
   raggedness rather than losing the layout. */
@supports (grid-template-rows: subgrid) {
  .ratecard {
    /* flag · name · descriptor · price · list · cta */
    grid-template-rows: auto auto auto auto 1fr auto;
  }
  .rate {
    display: grid;
    grid-row: span 6;
    grid-template-rows: subgrid;
  }
  .rate__flag,
  .rate:not(.rate--feature)::before { grid-row: 1; }
  /* Bottom of its row, so a one-line name sits on its descriptor exactly as a
     two-line one does. Top-aligned, "Starter Site" floated with a line of air
     under it beside a "Growth Retainer" that wrapped, and the aligned rows
     read as misaligned ones. */
  .rate__name  { grid-row: 2; align-self: end; }
  .rate__for   { grid-row: 3; }
  .rate__list  { grid-row: 5; }
  .rate > .btn { grid-row: 6; }
  .rate__price {
    grid-row: 4;
    /* the row already owns the spacing; the margin would double it */
    margin-bottom: 0;
  }
}
/* Amber, not bone. "Featured" was signalled by the same plate colour used for
   every other emphasis on the page, so it repeated rather than distinguished.
   Black on amber is 9.2:1. */
.rate--feature {
  background: var(--c-amber);
  color: var(--paper);
  border-left: 0;
  margin-top: -1px;
  padding-inline: 30px;
}
.rate__flag {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .88rem;
  line-height: 1.3;
  letter-spacing: .14em;
  color: var(--salmon);
  margin-bottom: 12px;
}
/* the unflagged columns reserve the same slot so every rule lines up */
.rate:not(.rate--feature)::before {
  content: "";
  display: block;
  font-size: .88rem;
  line-height: 1.3;
  height: 1.3em;
  margin-bottom: 12px;
}
.rate__name {
  font-size: clamp(1.9rem, 2.8vw, 2.5rem);
  margin-bottom: 8px;
}
.rate__for {
  font-size: .95rem;
  color: var(--ink-55);
  margin-bottom: 24px;
  min-height: 3em;
}
.rate--feature .rate__for { color: var(--paper-70); }
.rate__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  border-top: var(--hair);
  border-bottom: var(--hair);
  padding: 14px 0;
  margin-bottom: 24px;
}
.rate--feature .rate__price { border-color: var(--paper-20); }
.rate__price i {
  font-style: normal;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .9rem;
  letter-spacing: .1em;
  color: var(--ink-55);
}
.rate--feature .rate__price i { color: var(--paper-70); }
/* Sized for WORDS now, not a figure. This slot was set for a price at 3.2rem,
   and with prices unpublished it holds "Fixed price" / "Monthly" — at that
   size a phrase set like a number, the largest type on each card and bigger
   than the package's own name above it. At heading scale it reads as the
   pricing MODEL, which is what it now is. */
.rate__price b {
  font-family: var(--display);
  text-transform: uppercase;
  font-weight: 400;
  font-size: clamp(1.6rem, 2.3vw, 2.1rem);
  line-height: 1;
  letter-spacing: .01em;
}
.rate:not(.rate--feature) .rate__price b { color: var(--blue); }
/* Always its own line. Inline, it sat beside "Fixed price" in two cards and
   wrapped under it in the narrower amber one, so the three cards set the same
   note two different ways. */
.rate__price span { flex-basis: 100%; font-size: .88rem; color: var(--ink-55); }
.rate--feature .rate__price span { color: var(--paper-70); }

.rate__list { flex: 1; margin-bottom: 28px; }
.rate__list li {
  position: relative;
  padding-left: 20px;
  margin-bottom: 10px;
  font-size: .97rem;
  line-height: 1.45;
  color: var(--ink-70);
}
.rate--feature .rate__list li { color: var(--paper); }
.rate__list li::before {
  content: "";
  position: absolute;
  left: 0; top: .62em;
  width: 9px; height: 1px;
  background: var(--primary);
}
.rate--feature .rate__list li::before { background: var(--primary); }

.rates__note {
  border-top: var(--rule);
  padding-top: 16px;
  margin-top: 0;
  font-size: .92rem;
  color: var(--ink-55);
  max-width: 60ch;
}

/* ---------- 15. FAQ ---------- */
.acc { border-top: var(--rule); }
.accordion .acc:last-child { border-bottom: var(--rule); }
.acc h3 { margin: 0; }
.acc__btn {
  width: 100%;
  display: grid;
  grid-template-columns: 3.2rem 1fr 2rem;
  align-items: baseline;
  gap: 12px;
  padding: 22px 0 20px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  font-family: var(--text);
  font-size: clamp(1.12rem, 1.7vw, 1.45rem);
  font-weight: 500;
  line-height: 1.3;
  transition: color .3s ease;
}
.acc__btn i {
  font-style: normal;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1rem;
  letter-spacing: .1em;
  color: var(--blue);
}
.acc__btn em {
  position: relative;
  justify-self: end;
  align-self: center;
  width: 16px; height: 16px;
  flex: none;
}
.acc__btn em::before, .acc__btn em::after {
  content: "";
  position: absolute;
  top: 50%; left: 0;
  width: 16px; height: 1.5px;
  background: var(--ink);
  transition: transform .4s cubic-bezier(.4,0,.1,1);
}
.acc__btn em::after { transform: rotate(90deg); }
.acc__btn:hover { color: var(--blue); }
.acc__btn[aria-expanded="true"] { color: var(--blue); }
.acc__btn[aria-expanded="true"] em::before,
.acc__btn[aria-expanded="true"] em::after { background: var(--primary); }
.acc__btn[aria-expanded="true"] em::after { transform: rotate(0deg); }

.acc__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .45s cubic-bezier(.4,0,.1,1);
}
.acc__panel.is-open { grid-template-rows: 1fr; }
.acc__inner { overflow: hidden; }
.acc__inner p {
  padding: 0 2rem 26px 4.4rem;
  max-width: 68ch;
  color: var(--ink-70);
  opacity: 0;
  transition: opacity .4s ease .06s;
}
.acc__panel.is-open .acc__inner p { opacity: 1; }

/* ---------- 16. Contact & form ---------- */
.contact {
  background: var(--primary);
  color: var(--paper);
  margin-top: clamp(60px, 8vw, 120px);
}
.contact__inner {
  max-width: var(--page);
  margin-inline: auto;
  padding: clamp(56px, 7vw, 110px) var(--gutter);
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(32px, 5vw, 72px);
}
.contact__copy { grid-column: 1 / 7; }
.contact__title {
  font-size: clamp(2.8rem, 6.4vw, 5.8rem);
  line-height: .86;
  margin: 10px 0 26px;
}
.contact__lead {
  font-size: clamp(1.02rem, .5vw + .95rem, 1.18rem);
  color: var(--paper-70);
  max-width: 42ch;
  margin-bottom: 28px;
}
.contact__points { margin-bottom: 30px; }
.contact__points li {
  border-top: 1px solid var(--paper-20);
  padding: 11px 0 11px 26px;
  position: relative;
  font-size: 1rem;
}
.contact__points li::before {
  content: "";
  position: absolute;
  left: 0; top: 1.35em;
  width: 12px; height: 1px;
  background: var(--primary);
}
.contact__alt {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: 1.05rem;
  letter-spacing: .06em;
}
.contact__alt a {
  border-bottom: 1px solid var(--paper-45);
  padding-bottom: 2px;
  transition: border-color .3s ease, color .3s ease;
}
.contact__alt a:hover { color: var(--salmon); border-color: var(--salmon); }

.contact__formwrap { grid-column: 8 / 13; }

.form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px 18px;
}
.form[hidden], .form__success[hidden] { display: none; }
.field { display: grid; gap: 6px; align-content: start; }
.field--full { grid-column: 1 / -1; }
.field label {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .92rem;
  letter-spacing: .1em;
  color: var(--paper-70);
}
.req { color: var(--salmon); }
.opt { color: var(--paper-45); }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 9px 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--paper-45);
  border-radius: 0;
  color: var(--paper);
  font-family: var(--text);
  font-size: 1rem;
  transition: border-color .3s ease;
  appearance: none;
}
.field textarea { resize: vertical; min-height: 46px; }
.field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='13' viewBox='0 0 24 24' fill='none' stroke='%23F9F6EF' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='m5 9 7 7 7-7'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 2px center;
  padding-right: 26px;
  cursor: pointer;
}
/* The native popup. The OS draws its own selection highlight over this and
   nothing here can measure that, so it stays on the neutral plate. */
.field select option { background: var(--plate); color: var(--paper); }
.field ::placeholder { color: var(--paper-45); }
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--salmon);
  border-bottom-width: 2px;
  padding-bottom: 8px;
}
/* The rule above drops the outline for everyone, which left a keyboard user with
   nothing but a colour change on a line that was already there. These fields sit
   on the blue plate, where a blue ring is invisible — so the ring is paper. */
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 3px;
}
.field [aria-invalid="true"] { border-bottom-color: var(--salmon); }

.error {
  font-size: .82rem;
  color: var(--salmon);
  opacity: 0;
  transition: opacity .25s ease;
}
.error.is-shown { opacity: 1; }

.form > .btn { grid-column: 1 / -1; margin-top: 4px; }
.form__fine, .form__status {
  grid-column: 1 / -1;
  font-size: .85rem;
  color: var(--paper-45);
}
.form__status:empty { display: none; }
.form__status { color: var(--salmon); }

.btn__spinner {
  display: none;
  width: 15px; height: 15px;
  border: 2px solid rgba(0,0,0,.25);
  border-top-color: var(--paper);   /* the spinner rides a green button now */
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.btn.is-loading { pointer-events: none; }
.btn.is-loading .btn__spinner { display: block; }

.form__success {
  display: grid;
  gap: 16px;
  justify-items: start;
  border-top: 1px solid var(--paper-45);
  padding-top: 26px;
}
.form__success h3 { font-size: 2.2rem; }
.form__success p { color: var(--paper-70); max-width: 42ch; }

/* ---------- 17. Footer ---------- */
.footer {
  background: var(--ink);
  color: var(--paper);
  padding-top: clamp(48px, 6vw, 80px);
  overflow: hidden;
}
.footer__grid {
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr 1.3fr;
  gap: clamp(24px, 3vw, 48px);
  padding-bottom: clamp(40px, 5vw, 64px);
}
.footer__tag {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: clamp(1.5rem, 2vw, 1.9rem);
  line-height: 1.05;
  letter-spacing: .02em;
  margin-bottom: 22px;
}
/* needs to out-specify `.footer__col ul { display: grid }` below */
.footer__col .footer__social { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.footer__social a {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .95rem;
  letter-spacing: .1em;
  color: var(--paper-70);
  border-bottom: 1px solid var(--paper-20);
  padding-bottom: 2px;
  transition: color .25s ease, border-color .25s ease;
}
.footer__social a:hover { color: var(--salmon); border-color: var(--salmon); }

.footer__col h3 {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .92rem;
  font-weight: 400;
  letter-spacing: .16em;
  color: var(--salmon);
  margin-bottom: 16px;
}
.footer__col ul { display: grid; gap: 9px; }
.footer__col li, .footer__col a { font-size: .95rem; color: var(--paper-70); }
.footer__col a { transition: color .25s ease; }
.footer__col a:hover { color: var(--paper); }

.newsletter { margin-top: 24px; display: grid; gap: 8px; }
.newsletter label { font-size: .88rem; color: var(--paper-45); }
.newsletter__row { display: flex; }
.newsletter__row input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--paper-45);
  border-radius: 0;
  color: var(--paper);
  padding: 8px 0;
  font-family: var(--text);
  font-size: .95rem;
}
.newsletter__row input::placeholder { color: var(--paper-45); }
.newsletter__row input:focus { outline: none; border-bottom-color: var(--salmon); }
/* same again, on the ink footer */
.newsletter__row input:focus-visible { outline: 2px solid var(--paper); outline-offset: 3px; }
.newsletter__row input[aria-invalid="true"] { border-bottom-color: var(--salmon); }
.newsletter__row button {
  width: 44px;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--paper-45);
  color: var(--paper);
  font-size: 1.2rem;
  cursor: pointer;
  transition: color .25s ease;
}
.newsletter__row button:hover { color: var(--salmon); }
.newsletter .error { color: var(--salmon); }
.newsletter__status { font-size: .85rem; color: var(--salmon); }
.newsletter__status:empty { display: none; }

/* oversized cropped wordmark — the printed colophon */
/* The colophon. It was set at 19vw with a .74 line-height and cropped on all
   four sides on purpose — a much larger word passing behind the page. That
   worked while the display face was Bebas, which is condensed. Outfit is not:
   at the old size "SEEK DIGITAL" measured 1822px inside a 1144px column, so a
   third of it was simply gone and the crop read as a mistake rather than as a
   decision. Sized to fit now, with the line-height opened up so the caps are
   whole. The container keeps overflow:hidden only to catch the sideways drift
   at the extremes of the scrub. */
.footer__mark {
  padding-inline: var(--gutter);
  max-width: var(--page);
  margin-inline: auto;
  overflow: hidden;
  line-height: .92;
}
.footer__mark span {
  display: block;
  font-family: var(--display);
  text-transform: uppercase;
  font-weight: 700;
  /* FILLS THE COLUMN, centred. At 10.6vw it filled three quarters of a phone's
     column and sat against its left edge (30px of air on the left, 92 on the
     right at 390). "SEEK DIGITAL" in Outfit 700 is ~6.6 ems wide, so the column
     divided by 6.7 is the size that fills it; the 9.6rem cap still holds it
     inside the 1240px column on anything wider. */
  font-size: min(9.6rem, calc((100vw - 2 * var(--gutter)) / 6.7));
  text-align: center;
  letter-spacing: -.01em;
  /* A TINT, NOT AN OUTLINE. This was `color: transparent` with a 1px
     -webkit-text-stroke, and Outfit is a VARIABLE font: its glyphs are built
     from overlapping contours (the E's bars over its stem, the K's arms over
     its spine, the G's spur), and a text stroke outlines every contour rather
     than the union of them. So the wordmark shipped with the construction
     lines showing — double rules inside E, K, G, A and L at 9.6rem, the
     largest type on the page. A fill has no contours to show. Same quiet
     register as the outline was going for, and the same decorative-only
     status: the element is aria-hidden and the name is in the lockup above. */
  color: var(--paper-20);
  white-space: nowrap;
}

.footer__bottom {
  max-width: var(--page);
  margin-inline: auto;
  padding: 18px var(--gutter) 22px;
  margin-top: 8px;
  position: relative;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .9rem;
  letter-spacing: .1em;
  color: var(--paper-45);
}
/* rule sits inside the gutter so it lines up with every other rule on the page */
.footer__bottom::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--gutter);
  right: var(--gutter);
  height: 1px;
  background: var(--paper-20);
}
.footer__bottom ul { display: flex; gap: 20px; }
.footer__bottom a:hover { color: var(--salmon); }

/* the pinned frame fills the screen so the steps travel across it */
@media (min-width: 901px) {
  .process__viewport {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-block: clamp(40px, 5vw, 72px);
  }

  /* THE STEPS DO NOT SIT ON ONE BASELINE. Four equal cards in a row at one
     height is the genre's default composition and rule 13's exact complaint —
     it was the shape every other agency's process section has. The offsets are
     OUT OF STEP on purpose (0 / 38 / 11 / 52), the same reasoning as the
     --sheet-y multipliers per section id: a regular ramp is just a diagonal,
     and reads as an effect. Uneven reads as a hand.

     Only the TOPS move. align-items stays at its default stretch, so the cards
     still finish on one line and the bottom rule runs unbroken across the whole
     track while the top rule steps. One edge loose, one edge held — the same
     trick the case plates use, and the reason this does not read as drift.

     Mobile is excluded deliberately: below 901px the track is a horizontal
     snap-scroller, one card at a time, so a stagger there is invisible at best
     and a ragged scroll position at worst. */
  .pstep:nth-child(1) { margin-top: 0; }
  .pstep:nth-child(2) { margin-top: 38px; }
  .pstep:nth-child(3) { margin-top: 11px; }
  .pstep:nth-child(4) { margin-top: 52px; }
  .pstep:nth-child(5) { margin-top: 22px; }
}
@media (max-width: 900px) {
  .process__track {
    padding-inline: var(--gutter);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .process__track::-webkit-scrollbar { display: none; }
  .pstep { scroll-snap-align: start; width: min(78vw, 360px); }
  .process__bar { margin-inline: var(--gutter); }
}

/* ---------- 18. Responsive ---------- */
@media (max-width: 1100px) {
  .sec-head__title { grid-column: 2 / 9; }
  .sec-head__lead { grid-column: 2 / 10; padding-top: 14px; }

  .pillar__head { grid-column: 2 / 13; }
  .pillar__list { grid-column: 2 / 13; margin-top: 26px; }

  .case__plate, .case--flip .case__plate { grid-column: 1 / 6; grid-row: 1; }
  .case__body, .case--flip .case__body { grid-column: 6 / 13; grid-row: 1; }

  .about__statement { grid-column: 1 / 13; }
  .about__facts { grid-column: 1 / 13; }

  /* one column outright — 12 tracks plus 11 gaps overflow a narrow page */
  .contact__inner { grid-template-columns: minmax(0, 1fr); }
  .contact__copy, .contact__formwrap { grid-column: 1; }

  .footer__grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 900px) {
  .masthead__nav { display: none; }
  .masthead__end .btn { display: none; }
  .burger { display: flex; }

  /* the masthead is down to a wordmark and a burger here — the sound control
     moves into the menu rather than crowding what is left */

  .hero__aside { grid-template-columns: 1fr; row-gap: 22px; }
  .hero__lead, .hero__actions, .stamp {
    grid-column: 1;
    grid-row: auto;
    justify-self: start;
    justify-content: flex-start;
  }

  .ledger { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ledger__cell { border-left: 0; padding-left: 0; }
  .ledger__cell:nth-child(2) { border-left: var(--hair); padding-left: 20px; }
  .ledger__cell:nth-child(4) { border-left: var(--hair); padding-left: 20px; }
  .ledger__cell:nth-child(3), .ledger__cell:nth-child(4) { border-top: 0; }

  .ratecard { grid-template-columns: 1fr; }
  .rate { border-left: 0; padding-inline: 0; border-top: var(--hair); }
  .rate:first-child { border-top: 0; }
  .rate--feature { padding-inline: 26px; margin-top: 0; }

  .voices__grid { grid-template-columns: 1fr; }
}

@media (max-width: 720px) {
  .case { gap: 20px; }
  .case__plate, .case--flip .case__plate { grid-column: 1 / 13; grid-row: 1; aspect-ratio: 16 / 9; }
  .case__body, .case--flip .case__body { grid-column: 1 / 13; grid-row: 2; }
  .case__logo { width: 54%; height: 58%; }
  .case__logo--negru-zi { width: 30%; height: 62%; }
  .case__no, .case__stamp { padding: 10px 12px; font-size: .74rem; }

  .sec-head { gap: 6px 16px; }
  .sec-head__no { grid-column: 1 / 13; }
  .sec-head__title { grid-column: 1 / 13; }
  .sec-head__lead { grid-column: 1 / 13; }

  .pillar__id {
    grid-column: 1 / 13;
    display: flex;
    align-items: center;
    gap: 16px;
  }
  .pillar__rule { flex: 1; margin-top: 0; }
  .pillar__head, .pillar__list { grid-column: 1 / 13; }

  .form { grid-template-columns: 1fr; }
  .footer__grid { grid-template-columns: 1fr; }
  .acc__btn { grid-template-columns: 2.4rem 1fr 1.4rem; }
  .acc__inner p { padding-left: 3.4rem; padding-right: 0; }
}

@media (max-width: 480px) {
  .case__logo { width: 66%; height: 62%; }
  .case__logo--negru-zi { width: 40%; }
  .case__spec { grid-template-columns: 1fr; }
  .case__spec > div:nth-child(even) { border-left: 0; padding-left: 0; }
  .case__spec > div:nth-child(odd) { padding-right: 0; }

  .ledger { grid-template-columns: 1fr; }
  .ledger__cell { border-left: 0; padding-left: 0; border-top: 0; }
  .ledger__cell:first-child { border-top: var(--rule); }
  .ledger__cell:nth-child(2), .ledger__cell:nth-child(4) { border-left: 0; padding-left: 0; }
  .hero__actions { flex-direction: column; align-items: stretch; width: 100%; }
  .hero__actions .btn { width: 100%; }
}

/* comfortable touch targets */
@media (hover: none) {
  .btn { padding-block: 17px 14px; }
  .acc__btn { padding-block: 24px 22px; }
}

/* ---------- 18b. Phone fixes ----------
   Found on a 390x844 pass of both home pages and the interior tree. Each rule
   names what it fixes; none of them changes the desktop layout. */

/* The mobile menu ended at its last link, so on a tall phone the hero showed
   underneath the open menu — it read as a panel that had not finished opening.
   It now fills the screen below the bar. */
@media (max-width: 900px) {
  .mobile-menu { min-height: calc(100dvh - 60px); }
}

/* 44x44, the platform minimum for a thumb. It was 42x38. The inner box stays
   18px tall (44 - 2 x 13), so the three bars keep their 8.25px pitch and the
   ±7.5px that folds them into an X still lands. */
.burger { width: 44px; height: 44px; padding: 13px 9px; }

/* The inactive language was an 18x23 target next to the active one. A larger
   invisible hit area, not padding: padding would move the underline. */
@media (max-width: 900px) {
  .langswitch a { position: relative; }
  .langswitch a::after { content: ""; position: absolute; inset: -12px -8px; }
}

/* The stacked rate cards kept the empty "Recommended" slot and the three-line
   descriptor floor that only exist so three cards side by side line up; stacked,
   they were 60px of nothing above "Starter Site" and "Growth Retainer". */
@media (max-width: 900px) {
  .rate:not(.rate--feature)::before { display: none; }
  .rate__for { min-height: 0; }
}

/* iOS zooms the page into any field set under 16px the moment it is focused,
   and the newsletter field was 15.2px — tapping it threw the footer sideways. */
@media (max-width: 900px) {
  .newsletter__row input { font-size: 16px; }
}

@media (max-width: 480px) {
  /* "Book a Free Strategy Call" broke as "…STRATEGY / CALL" in the hero and in
     the featured rate card, with the two lines touching at line-height 1. A
     step down in size and tracking fits the English label on one line at 390;
     the longer Romanian one still wraps, so wrapping is balanced and given a
     line-height that separates the lines. */
  .btn { letter-spacing: .05em; line-height: 1.15; text-wrap: balance; }
  .btn--lg { font-size: 1rem; padding-inline: 18px; }
  .btn--block { padding-inline: 16px; }

  .stamp { text-wrap: balance; }
}

/* ---------- 19. Reduced motion & print ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  /* opacity only. A blanket transform:none here would also flatten any static
     transform a component carries as design rather than motion — the JS
     branch zeroes the animated x/y through GSAP, which preserves the rest. */
  .js [data-animate] { opacity: 1; }
  .js .mask__i { transform: none; }
  .js [data-rule], .js .hl__block { transform: scaleX(1); }
  /* the colophon drifts sideways under full motion; here it simply sits still */
  .js [data-footer-mark] { transform: none; }

  /* the logo redraw is decoration — hold the solid mark instead of snapping to outline */
  .case__logo path,
  .case:hover .case__logo path,
  .case:focus-within .case__logo path {
    fill-opacity: 1;
    stroke-width: 0;
    stroke-dashoffset: 0;
  }
  .case:hover .case__logo svg,
  .case:focus-within .case__logo svg { transform: none; }

  /* Section 23: the label swap is a transition, so the blanket rule above has
     already stopped it — but a stopped swap is still two labels stacked in one
     box. The arriving row is removed outright, leaving the plain word and the
     rule sweep the nav had before. */
  .kin__row--in { display: none; }
  .kin__c { transform: none !important; }

  /* The velocity lean is refused in JS under this query, so there is
     nothing here to undo. Section 24 keeps its progress hairline: it reports position, which is information, not motion. */

  /* Belt and braces: the gate in main.js module 16 already refuses to engage
     the intro under reduced motion. If that gate is ever bypassed, the
     overlay still must not appear. */
  #intro { display: none !important; }
}

/* This site is set like printed matter, so it should survive being printed.
   On a jury laptop somebody WILL hit Cmd-P, and a page that comes out as a
   readable document rather than four sheets of navigation and dead plates is a
   genuine differentiator that costs nothing. */
@media print {
  @page { margin: 18mm 16mm; }

  /* screen furniture: gone. None of it means anything on paper. */
  .masthead, .ticker, .process__bar, .burger, .mobile-menu,
  .skip-link, .intro, .form, .newsletter, .footer__social,
  .btn, .arrow-link, .stamp,
  .masthead__progress { display: none !important; }

  body {
    background: #fff;
    color: #000;
    font-size: 11pt;
    line-height: 1.45;
  }

  /* flat plates cost a whole cartridge and say nothing in greyscale */
  .contact, .footer, .case__plate, .rate--feature, .hl__block {
    background: #fff !important;
    color: #000 !important;
  }
  .hl__block { display: none; }
  .case__plate, .case__logo { display: none; }

  /* nothing may arrive mid-animation on paper */
  [data-animate], .mask__i, [data-rule], [data-footer-mark] {
    opacity: 1 !important;
    transform: none !important;
  }

  /* the rules are the design; keep them, at a weight that actually prints */
  .sec-head__rule, .hero__rule, .pillar__rule {
    background: #000;
    height: .4pt;
  }

  h1, h2, h3 { break-after: avoid-page; }
  .acc, .rate, .pillar, .case, .sec-head { break-inside: avoid-page; }
  section { break-before: page; }
  .hero { break-before: auto; }

  /* every accordion answer, since nobody can click paper */
  .acc__panel { display: block !important; height: auto !important; }

  /* a printed link is useless without its address */
  main a[href^="http"]::after,
  main a[href^="mailto"]::after,
  main a[href^="tel"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    color: #444;
    word-break: break-all;
  }
}

/* ---------- 20. Intro overlay — the eye ----------
   Opt-in, never opt-out. The overlay is display:none until main.js module 16
   puts .intro-ready on <html>, so the default state of the document — no JS,
   dead CDN, crawler, reader mode, reduced motion — is the finished page with
   no overlay at all. Nothing here can strand a visitor behind a blank screen,
   because nothing here is visible until something proves it can be dismissed.

   IT IS GLASS, NOT A BLACK CARD, and that is the whole idea of the sequence.

   The panel used to be `background: var(--paper)` — flat black, the site
   hidden behind it, and the ending a white flash that cut to the page. Three
   things were wrong with that and they were all the same thing: the intro had
   nothing to do with the site it was introducing. It could have been in front
   of any page. The flash was a cut, so the first frame of the real site was
   also the first time you saw it.

   Now the page is ALREADY THERE from the first frame, out of focus behind
   frosted glass, and the sequence is an eye opening and bringing it in. The
   field's colour is in the room the whole time — blurred, tinted down, but
   moving — so the intro and the hero are visibly the same picture before the
   handoff rather than because of it. And the ending is a focus pull rather
   than a cut: the tint lifts, the pupil opens onto the sharp page, the glass
   clears. Nothing is ever revealed; it just comes into focus.

   THE APPEARANCE LIVES HERE AND THE TIMING LIVES IN THE TIMELINE. Every state
   below is a class the sequence toggles — .is-seeing, .is-sharp, .is-clearing
   — so intro.js says *when* and never *what*. That is rule 9 of CLAUDE.md
   working as intended: there are still no colour values in intro.js and no
   --intro-* namespace, and a retint is an edit in one file.

   It is the same motif as the corner eyes in section 31 — the intro introduces
   them, and then they stay for the rest of the visit. */
.intro { display: none; }

.intro-ready .intro {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  inset: 0;
  z-index: 400;              /* over the crop marks (95) and the skip link (300) */
  /* OPAQUE BLACK, AND THAT IS THE WHOLE POINT. This was frosted glass over the
     live page — tinted rgba(5,21,22,.80) with backdrop-filter: blur(52px) —
     so the field was already in the room, blurred and tinted down, and the
     ending was a tint lifting off a picture that had been there all along.

     It is a black card now. Nothing behind it shows through, so the ONLY
     colour that enters the sequence is the field that floods out of the pupil
     at 2.70. THE GRADIENT ARRIVES THROUGH THE IRIS rather than being uncovered
     by a tint coming off it, which is a reveal instead of a focus pull and is
     what this sequence is now about.

     Two consequences, both deliberate:
     - No backdrop-filter. It is invisible behind an opaque layer, and it was a
       full-bleed re-blur over a MOVING backdrop — the exact shape rule 11 says
       costs a frame. Removing it is free here.
     - The two state classes below no longer touch the panel. If either is
       given a background again the flood stops being the transition: taking an
       opaque panel to transparent reveals the whole page in one step, which is
       a cut, and the flood then grows underneath something already gone. */
  background: var(--paper);
}

/* .is-seeing AND .is-clearing NO LONGER PAINT THE PANEL — deliberately, see
   above. Both classes are still toggled by intro.js on the same beats and both
   still do work: .is-seeing sharpens the eye (further down this section), and
   .is-clearing is kept as the marker for the beat where the flood takes over,
   so the timeline reads the same and rule 9 still holds — appearance here,
   timing there. Do not re-add a background to either. */

/* NO BACKDROP-FILTER, NO GLASS — and then the tint has to become opaque or the
   overlay stops being one. An 80% tint over a SHARP page is a dimmed page you
   can still read, which is worse than either the glass or the old black card:
   the sequence would be playing over legible copy. Older Safari lands here.

   IT HAS TO COME AFTER THE STATE RULES AND NAME THEM ALL. `.intro-ready
   .intro.is-seeing` is one class more specific than `.intro-ready .intro`, so
   a fallback written above it — which is where it was first put, next to the
   declaration it was correcting — is overridden by the very states it exists
   to suppress: the panel would go opaque for a tenth of a second and then
   .is-seeing would make it 46% translucent again over a sharp page. @supports
   does not raise specificity. Put the escape hatch last and list every state. */
/* THE @supports FALLBACK THAT WAS HERE IS GONE, and the reason is worth
   keeping: it existed to make the panel opaque when backdrop-filter was
   unavailable, because an 80% tint over a sharp page is a dimmed page you can
   still read. The panel is opaque in every browser now, so the fallback's job
   is done by the default rule and a browser without backdrop-filter gets
   exactly the intended picture rather than a repair of it.

   The specificity warning it carried still applies to anything added here: a
   rule meant to suppress the state classes must come AFTER them and name every
   one, because `.intro-ready .intro.is-seeing` outranks `.intro-ready .intro`
   and @supports does not raise specificity. */

/* FORCED COLOURS REPLACES THE PANEL'S COLOUR WITH THE PLATFORM'S. --paper is a
   custom property and forced-colors mode overrides the painted result anyway,
   so the panel is pinned to Canvas explicitly rather than left to chance: a
   panel that comes back transparent is the sequence playing over legible copy.
   Every state class is still named even though neither paints the panel now —
   the selector list is the guard, and it costs nothing to keep it correct if
   one of them is ever given a background again. Same argument as the
   difference-blend fallbacks in sections 8 and 31: an effect that degrades to
   nothing has to be told what to be instead. */
@media (forced-colors: active) {
  .intro-ready .intro,
  .intro-ready .intro.is-seeing,
  .intro-ready .intro.is-clearing {
    background: Canvas;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    transition: none;
  }
  .intro__eye,
  .intro-ready .intro.is-seeing .intro__eye { filter: none; transition: none; }
}

/* It was min(46vw, 380px) and read as a small icon on a large screen. The
   viewBox is 240x140 with the centre line at y=60, which is the exact middle
   of that box, so the eye stays optically centred at any width. */
/* A small mark rather than a graphic. overflow:visible matters more at this
   size than it did: the eye is rotating continuously and its corners swing
   outside the SVG's own box. */
.intro__eye {
  width: min(28vw, 320px);
  height: auto;
  display: block;
  overflow: visible;
  /* The eye arrives out of focus with the page and sharpens with it. The two
     focus pulls are the same gesture at two scales, which is what stops the
     panel's blur reading as a filter laid over a separate drawing. */
  filter: blur(9px);
  transition: filter .9s cubic-bezier(.33, 0, .15, 1);
}
.intro-ready .intro.is-seeing .intro__eye { filter: blur(0px); }

/* AND THEN THE FILTER GOES AWAY ENTIRELY, which is not the same as blur(0).
   .intro__all inside this element rotates continuously for the whole sequence
   (the drift timeline), and a filter on an element whose child moves is
   re-rasterised every frame — the same cliff section 33 documents for the
   field, at a smaller scale. blur(0px) is a no-op that still costs the pass.
   The sequence adds .is-sharp once the transition above has landed, and from
   there the eye is drawn straight. */
.intro-ready .intro.is-sharp .intro__eye {
  filter: none;
  transition: none;
}

/* Drawn, not plotted. Two strokes per contour at slightly different weights and
   opacities, over jittered geometry, is what reads as a hand — a single clean
   stroke over the same wobble just reads as a wobbly line.

   pathLength="1" normalises every path to a length of one regardless of its
   actual geometry, so the draw-on is dasharray:1 / dashoffset:1 and needs no
   measurement in JS. The client logo marks in section 11 use the same trick;
   this is that idea applied to a shape whose d is being rewritten every frame. */
/* No vector-effect here, deliberately. non-scaling-stroke makes Chrome compute
   the dash pattern in SCREEN space, which throws away the pathLength="1"
   normalisation the draw-on depends on — dasharray:1 stops meaning "the whole
   path" and starts meaning "one CSS pixel", so most of the lid simply is not
   stroked. Nothing scales this SVG any more (the ending is a slide, not a
   zoom), so the effect has nothing left to correct for. */
.intro__lid,
.intro__lid2,
.intro__iris,
.intro__iris2 {
  fill: none;
  stroke: var(--ink);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
}
/* Widths are in viewBox units and there is no non-scaling-stroke (see above),
   so the rendered weight moves with the size of the SVG. At 320px against a
   240-unit viewBox the scale is 1.33x, and these land at roughly 2.6 / 1.3 /
   2 / 1 CSS px — pen weight at the new size. Change the width above and these
   have to move with it or the drawing turns to hairlines. */
.intro__lid   { stroke-width: 1.95; }
.intro__lid2  { stroke-width: .98; opacity: .5; }
.intro__iris  { stroke-width: 1.5;  }
.intro__iris2 { stroke-width: .75; opacity: .42; }
.intro__pupil { fill: var(--ink); }

/* The flash. A clip-path circle, not a border-radius — a mask being drawn, the
   same argument that lets the eye be round at all. It begins life exactly
   covering the pupil, which is already white, so the moment it appears is
   invisible: all you see is the pupil growing until it is the screen. */
/* ---------- The aurora ----------
   One material, used three times: inside the pupil, in the flood that follows
   it, and then as the hero's own background for the rest of the visit. The
   colour is carried out of the eye and left on the page, still moving.

   HOW THE FOG IS MADE. Ten large radial gradients would read as ten circles;
   what makes them melt is the blur ON TOP of them — filter: blur(90px) erases
   every edge and leaves only the colour relationships. That is the whole
   technique, and it is why the stops can be placed crudely: nothing survives
   the blur except where each hue sits.

   COMPOSITION. Teal and cyan hold the centre and about 60% of the frame;
   orange glows in from the left and right borders only; amber sits just inside
   it; pink, lavender and a little lime appear as highlights and nothing more.

   WHY THE STOPS CARRY ALPHA. The brief's palette at full strength is far too
   bright to put white copy on — #29BFC5 alone is 1.9:1 against white. Laying
   the same hues at partial alpha over a deep teal-black keeps every colour
   relationship and every bit of the depth while holding the composite dark
   enough to read on. Cinematic aurora is dark; only the hue survives.

   THE DRIFT IS A TRANSFORM, NOT A BACKGROUND-POSITION. An earlier build
   animated background-position and repainted the whole layer every frame:
   measured p99 of 29.1ms against a 16.7ms budget and 7 dropped frames. An
   oversized child moved with translate/rotate composites instead. Keep it. */
.aurora {
  position: relative;
  overflow: hidden;
  /* A DEEP TEAL, not a near-black and not a mid-tone. Edge-to-edge colour with
     no dark ground showing anywhere is the point — the ground is most of what
     decides that, and an earlier #03171d read as colour ON black rather than as
     colour.

     WHY IT IS DARKER THAN THE REFERENCE'S OWN DARKEST REGION. The reference's
     deep teal sits at L .171 and this is L .152, because the reference is a
     picture and this is a picture with a paragraph on it. Every stop above is
     laid at partial alpha, so the ground shows through wherever two lobes do
     not overlap, and those gaps are exactly where the composite would otherwise
     be brightest. Holding the floor down is what buys the headline its 3:1
     without the scrim having to cover the whole frame. */
  background-color: #205658;
}
/* THREE LAYERS, not one background-image list.

   Two reasons, and they are the whole of "liquid".

   1. One list is one picture. However slowly it drifts, every stop moves
      together, so the composition is rigid — it slides, it does not change.
      Three layers on three different clocks slide THROUGH each other, and the
      colour where they overlap is never the same twice. Nothing here fades or
      cross-dissolves; the churn is entirely a by-product of relative motion.
   2. Scroll can then move them at different rates. That is depth: the far
      layer barely moves, the near one moves most, and the mix at any scroll
      position is one the page has not shown before.

   ORDER IS STACKING ORDER, and it is the old background-image order preserved:
   in a multi-background list the FIRST entry is on top, so the warm mass that
   led the list is now the TOP layer (c), and the deep counterweight lobes that
   closed it are the BOTTOM one (a). Reordering these repaints the composition.

   Each layer keeps inset:-25% and every gradient keeps the exact position it
   was tuned and measured at — see the mapping note below. Splitting the list
   was allowed to change the clock, not the picture. */
.aurora__l {
  content: "";
  position: absolute;
  display: block;
  /* -25%, not more. The gradients are positioned in the LAYER's percentages,
     so an oversized layer pushes edge-placed colour out of the visible window:
     at -55% the container occupied only the middle 48% of the layer, and every
     stop authored near 0% or 100% — all the orange, all the amber, all the
     highlights — fell outside the frame. The hero rendered as nothing but the
     centre teal, and it looked like the palette had been ignored.

     At -25% the window is 16.7%..83.3%, and every position below is mapped
     into it: layer = 16.7 + viewport x 0.667, and every size scaled by 0.667.
     The viewport figure each one is aiming at is in the comment beside it.

     It is also the headroom the scroll parallax travels into: the overhang is
     25% of the box (183px at 732 tall), the idle animation uses about 55px of
     it, and module 28 caps its own offset at 100px. Raise either and the layer
     shows its own edge as a hard band. */
  inset: -25%;
  background-repeat: no-repeat;
  animation-delay: var(--aurora-delay);
  /* Scroll goes on `translate`, the idle drift on `transform`. They are
     separate CSS properties, so an animation can own one while script writes
     the other — which is the only reason per-layer parallax and per-layer
     drift can coexist. Putting both on `transform` is a fight the animation
     always wins. */
  translate: 0 var(--ay, 0px);
  /* THE BLUR IS NOT ON THIS ELEMENT. It is on .aurora__blur inside it, and
     that split is the single most expensive thing in this section to get wrong.

     A blurred layer that does not move is rasterised once and composited as a
     texture. A blurred layer that DOES move is re-blurred every frame, and a
     full-bleed re-blur does not fit in 16.7ms. This element carries the drift,
     so while it also carried the filter the field ran at half rate — which is
     exactly what it did. Measured, median frame interval scrolling the hero:

       blur and animation on ONE element             33.4ms
       blur on a static child, animation on parent   16.7ms
       no blur at all                                16.7ms

     THE RADIUS IS IRRELEVANT TO THE COST, which is what makes this so easy to
     misdiagnose. Sweeping an animated layer from 4px to 40px measured 49.9,
     33.2, 33.0, 33.3, 33.8, 33.3, 33.3, 33.2 — flat, and no better at the
     bottom than at the top. It is not "the blur is too wide", it is "the blur
     is being redone". Turning the radius down looks like it should help and
     does nothing, and the profile shows only the compositor sitting at 30fps
     with no one frame to blame.

     So this element owns the MOTION — transform for the drift, translate for
     the scroll — and paints nothing at all. The child owns the PICTURE and
     never moves. Do not merge them back. Do not put a will-change on the
     child either: promoting it is what stops it being cached. */
  will-change: transform, translate;
}

/* The picture, blurred once. Same box as its parent (inset: 0), so every
   gradient percentage below still resolves against the rectangle it was
   measured against — the split changed which element paints, not where any
   colour lands.

   Radius is free now, so these are what looks right rather than what was
   affordable: enough to erase every radial edge and leave only the colour
   relationships, which is the whole technique. */
.aurora__blur {
  position: absolute;
  inset: 0;
  display: block;
  background-repeat: no-repeat;
}

/* EVERY STOP BELOW IS A REGION OF THE REFERENCE, not a colour that resembles
   one. The image was blurred to kill its grain, clustered into nine hues, and
   then each hue's centroid and spread were measured as a percentage of the
   frame; the comment on the right of every line is that measurement, in
   VIEWPORT percentages, and the numbers on the left are the same figures mapped
   into the layer's own space by  layer = 16.7 + viewport x 0.667  (sizes x
   0.667). Edit the right-hand figure and re-run the mapping; do not edit the
   left-hand one directly, because it is the one that no longer says where the
   colour actually goes.

   THE COMPOSITION IS A WARM FRAME ROUND A COOL CENTRE, which is the whole of
   what the reference is doing and the one thing a single-hue field cannot do.
   Orange holds the left and right borders and the bottom corners; amber sits in
   a narrow band just inside it; teal, green and periwinkle hold the middle 60%;
   peach is a single highlight low and centre. Nothing here is a wash over
   everything — every hue has a place, and the places are why it reads as
   photographed rather than as a filter. */

/* a — the deep counterweight. Slowest, and it sits under everything.
   Both lobes are darker than any cluster in the reference: this layer's job is
   not to be seen but to hold the floor down under the gaps in layer b, so the
   composite stays inside the headline's contrast budget. */
.aurora__l--a {
  --ay: var(--ay-a, 0px);
  animation: aurora-a 53s ease-in-out infinite alternate;
}
.aurora__l--a .aurora__blur {
  background-image:
    radial-gradient(21.3% 17.3% at 36.7% 76.7%, rgba(7,59,56,.52) 0%, transparent 76%),   /* deep teal-green  32x26 @ 30,90 */
    radial-gradient(18.7% 20.0% at 67.4% 27.4%, rgba(6,52,63,.44) 0%, transparent 74%);   /* deep teal        28x30 @ 76,16 */
  filter: blur(60px);
}


/* b — the cool mass: the teal centre, the greens, the periwinkle quadrant and
   the peach highlight. This is the body of the field and 60% of the frame, and
   it is the layer that carries the reference's actual subject. */
.aurora__l--b {
  --ay: var(--ay-b, 0px);
  animation: aurora-b 34s ease-in-out infinite alternate;
}
.aurora__l--b .aurora__blur {
  background-image:
    radial-gradient(29.3% 38.7% at 63.4% 46.0%, rgba(45,167,176,.92)  0%, transparent 74%), /* teal centre-right 44x58 @ 70,44 */
    radial-gradient(32.0% 16.0% at 50.0% 20.7%, rgba(33,149,140,.82)  0%, transparent 72%), /* deep teal top     48x24 @ 50, 6 */
    radial-gradient(20.0% 28.0% at 38.7% 63.4%, rgba(38,156,163,.80)  0%, transparent 74%), /* teal centre-left  30x42 @ 33,70 */
    radial-gradient(18.7% 22.7% at 40.7% 30.0%, rgba(52,137,86,.82)   0%, transparent 72%), /* green upper-mid   28x34 @ 36,20 */
    radial-gradient(17.3% 13.3% at 63.4% 80.7%, rgba(47,129,84,.70)   0%, transparent 72%), /* green low-right   26x20 @ 70,96 */
    radial-gradient(24.0% 21.3% at 34.7% 58.1%, rgba(118,154,175,.66) 0%, transparent 74%), /* periwinkle        36x32 @ 27,62 */
    radial-gradient(14.7% 26.7% at 51.4% 60.7%, rgba(225,148,93,.70) 0%, transparent 72%); /* peach highlight   22x40 @ 52,66 */
  filter: blur(70px);
}


/* c — the warm frame. Nearest, smallest blur, fastest, and on top.
   THE ORANGE IS AT THE BORDERS AND NOWHERE ELSE. In the reference it never
   crosses the middle: it comes in from both edges, is handed to amber a fifth
   of the way in, and returns in the two bottom corners. A build that put the
   warm mass in the centre had the same nine colours and did not look like the
   picture, because the picture is the arrangement. */
.aurora__l--c {
  --ay: var(--ay-c, 0px);
  animation: aurora-c 27s ease-in-out infinite alternate;
}
.aurora__l--c .aurora__blur {
  background-image:
    radial-gradient(20.0% 44.0% at 21.4% 32.7%, rgba(255,119,22,1)   0%, transparent 78%),  /* orange left     30x66 @  7,24 */
    radial-gradient(18.7% 40.0% at 78.7% 31.4%, rgba(255,119,22,.98) 0%, transparent 78%),  /* orange right    28x60 @ 93,22 */
    radial-gradient(14.7% 17.3% at 20.0% 79.4%, rgba(255,110,15,.84) 0%, transparent 74%),  /* orange low-left 22x26 @  5,94 */
    radial-gradient(13.3% 16.0% at 80.1% 79.4%, rgba(255,110,15,.80) 0%, transparent 74%),  /* orange low-right 20x24 @ 95,94 */
    radial-gradient(11.3% 46.7% at 31.4% 54.1%, rgba(239,155,50,.60) 0%, transparent 72%),  /* amber inner-left 17x70 @ 22,56 */
    radial-gradient(10.7% 30.7% at 73.4% 58.1%, rgba(239,155,50,.56) 0%, transparent 72%);  /* amber inner-right 16x46 @ 85,62 */
  filter: blur(52px);
}


/* Grain. Generated feTurbulence rather than a bitmap, so it costs no request
   and tiles at any size. It is what stops a twelve-stop blur from banding, and
   it is most of what reads as "printed" rather than "rendered". The rule that
   paints it is shared with the section fields, in section 31b, so the hero and
   everything below it are visibly the same material.

   It is far heavier than it was. Measured on the reference, adjacent pixels
   along a row differ by 15.2/255 on average; at opacity .05 this build was
   nowhere near that, and the field read as clean CGI next to it. */


/* The drift is asymmetric too. A path that goes out and comes back the same
   distance is a pendulum, and a pendulum is legible as a loop however slow it
   is — the field has to arrive somewhere it did not start. */
/* Three paths, three periods — 53s, 34s and 27s. Deliberately not multiples of
   one another: layers on harmonic periods re-align at the shortest common
   multiple and the field visibly "ticks" there. These share no factor, so the
   overlap pattern does not repeat inside any sitting.

   Each still ends somewhere it did not start (`alternate` walks it back), which
   is what keeps a drift from reading as a pendulum. */
/* THE SCALES ARE SMALL AND THAT IS A CORRECTION, not a lack of nerve. A scale
   of S about the centre moves a lobe at viewport p to 50 + (p-50)S, so the two
   orange masses that frame the picture — the whole reason this field reads as
   the reference and not as a teal wash — were being pushed off the frame at the
   top of the cycle: at S 1.16 a lobe at 93% lands at 99.9% and one at 99% lands
   past 106%, and the border went grey for a third of the loop. The travel is in
   the translate and the rotate now, which move the composition without changing
   how much of it is inside the window.

   THE EDGE BUDGET, for anything added here: no lobe may leave 0..100 under the
   largest scale in its own keyframe. At 1.09 that is a placement range of
   4%..96%, and the two orange masses sit at 7% and 93% with exactly that in
   mind. Widen the scale and they have to come in with it. */
@keyframes aurora-a {
  0%   { transform: translate3d(-3%,  2%, 0) rotate(-2deg) scale(1.02); }
  50%  { transform: translate3d( 4%, -3%, 0) rotate(3deg)  scale(1.06); }
  100% { transform: translate3d(-1%, -5%, 0) rotate(-4deg) scale(1.03); }
}
@keyframes aurora-b {
  0%   { transform: translate3d(-5%, -2%, 0) rotate(-3deg) scale(1.03); }
  38%  { transform: translate3d( 2%,  5%, 0) rotate(6deg)  scale(1.09); }
  71%  { transform: translate3d( 6%, -1%, 0) rotate(2deg)  scale(1.05); }
  100% { transform: translate3d(-1%,  7%, 0) rotate(-7deg) scale(1.07); }
}
@keyframes aurora-c {
  0%   { transform: translate3d( 4%, -4%, 0) rotate(4deg)  scale(1.03); }
  44%  { transform: translate3d(-3%,  3%, 0) rotate(-5deg) scale(1.09); }
  100% { transform: translate3d( 5%,  6%, 0) rotate(8deg)  scale(1.05); }
}

@media (prefers-reduced-motion: reduce) {
  /* No drift and no parallax. The FADE stays: it is a change of state as you
     move down the page, not a movement, and losing it would leave the field at
     full strength behind the whole document. */
  .aurora__l { animation: none; translate: none !important; }
}

.intro__flash {
  position: fixed;
  inset: 0;
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  clip-path: circle(0px at 50% 50%);
}

/* The butterfly. Its own SVG so the lid's clip cannot reach it, positioned from
   the panel's top-left and moved by GSAP in pixels — the flight is measured
   against the viewport, not against the eye. */
.intro__fly {
  position: absolute;
  top: 0;
  left: 0;
  width: 96px;
  height: auto;
  overflow: visible;
  pointer-events: none;
}
.intro__wing,
.intro__body,
.intro__abdomen,
.intro__ant {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* THE SWARM WEARS THE FIELD. intro.js stamps each butterfly with an index and
   nothing else; the colour is decided here, from the nine sampled tokens,
   because rule 9 keeps palette values out of the timeline file.

   These were nine hues borrowed from other sites — acid pink, lemon, lime,
   mint — and a swarm of them over black was the one moment of the sequence
   that read as a toy. They are now rungs of the field the pupil is about to
   flood with: the anchor leads (the visitor has seen it on the button they
   are about to press), then cool, warm, cool, warm, the same alternation the
   ticker separators use because the field is a warm frame round a cool
   centre. Only the bright rungs: teal and green sit at 3.4–3.7:1 on black and
   would read as smudges at this stroke weight. */
.intro__fly               { color: var(--ink); }
.intro__fly[data-fly="0"] { color: var(--c-orange); }
.intro__fly[data-fly="1"] { color: var(--c-cyan); }
.intro__fly[data-fly="2"] { color: var(--c-sand); }
.intro__fly[data-fly="3"] { color: var(--c-mauve); }
.intro__fly[data-fly="4"] { color: var(--c-amber); }

/* THE RACK FOCUS. When the eye chooses (.is-found, 1.98 in the timeline),
   everything it did not choose goes soft and dim — a camera pulling focus to
   one subject, which is the same gesture the eye itself made at the start
   (.is-seeing, blur to sharp) turned outward. The blur sits on the small
   per-butterfly SVG, not on anything full-bleed, so its per-frame cost is four
   96px rasters rather than a screen; the dimming is on the inner group
   because GSAP owns the outer element's opacity inline. */
.intro__fly { transition: filter .45s cubic-bezier(.33, 0, .15, 1); }
.intro__fly-in { transition: opacity .45s cubic-bezier(.33, 0, .15, 1); }
.intro.is-found .intro__fly:not(.is-chosen) { filter: blur(3.5px); }
.intro.is-found .intro__fly:not(.is-chosen) .intro__fly-in { opacity: .3; }
/* The one sits ABOVE the flood (z 3) so that when it lands, it lands on the
   colour and dissolves into it, rather than being painted over by it. */
.intro__fly.is-chosen { z-index: 4; }

/* THE PATH. The chosen butterfly's flight, drawn behind it in its own ink
   (the anchor — data-fly 0 is always the one). It starts as a faint pencil
   line while it is one of five, and becomes ink the moment the eye chooses:
   the journey only reads as a journey once someone is following it. Full
   viewport, CSS-pixel units (no viewBox), and no vector-effect, for the same
   pathLength reason as the lid above. */
.intro__trail {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.intro__trail path {
  fill: none;
  stroke: var(--c-orange);
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  opacity: .22;
  transition: opacity .45s cubic-bezier(.33, 0, .15, 1);
}
.intro.is-found .intro__trail path { opacity: .85; }

/* FILM GRAIN on the black card — the same feTurbulence tile as the field's
   grain (§33), so the card and the colour that floods it share one surface,
   and the flood does not arrive as a clean vector on a dead black. It moves on
   STEPS, four positions a cycle, like grain on film rather than noise
   crawling; oversized by 60px so the offsets never expose an edge; and it is
   a transform on a pseudo-element, so it composites without a repaint. Below
   the flood (z 3), which carries its own grain. */
.intro-ready .intro::before {
  content: "";
  position: absolute;
  inset: -60px;
  z-index: 0;
  pointer-events: none;
  opacity: .075;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.7' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: intro-grain .48s steps(4) infinite;
}
@keyframes intro-grain {
  0%   { transform: translate3d(0, 0, 0); }
  25%  { transform: translate3d(-23px, 17px, 0); }
  50%  { transform: translate3d(31px, -9px, 0); }
  75%  { transform: translate3d(-11px, -29px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}
@media (forced-colors: active) {
  .intro-ready .intro::before { display: none; }
  .intro.is-found .intro__fly:not(.is-chosen) { filter: none; }
}
/* 96px against an 80-unit viewBox is 1.2x, so these land near 2.4 / 4.2 / 1.7
   CSS px — the same pen the eye is drawn with. */
/* The body's taper is these two widths. One path at one width drew a sausage;
   the thorax is thick, the abdomen thinner and shorter, and that is the whole
   trick at this size. */
.intro__wing    { stroke-width: 2; }
.intro__body    { stroke-width: 3.6; }
.intro__abdomen { stroke-width: 2; }
.intro__ant     { stroke-width: 1.4; opacity: .75; }

/* One keystroke out. A jury member who has seen four hundred intros this week
   must be able to leave immediately, so this is a real button, first in the
   tab order while the overlay is up, and never hidden behind a hover. */
.intro__skip {
  position: absolute;
  top: clamp(14px, 2.4vw, 26px);
  right: var(--gutter);
  z-index: 2;
  padding: 11px 18px 8px;
  border: 1px solid var(--ink);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-weight: 500;
  text-transform: uppercase;
  font-size: .78rem;
  letter-spacing: .14em;
  line-height: 1;
  cursor: pointer;
}
@media (hover: hover) {
  .intro__skip:hover { background: var(--ink); color: var(--paper); }
}

/* THE LOCKUP — the masthead's wordmark set as a title card under the mark.
   Absolutely placed so the eye stays at the exact centre it floods from;
   .30 of the eye's width clears the lower lid (which sits .22 below centre)
   with a pen-stroke of air. Same face, same SEEK/DIGITAL split in ink and
   bone as §6's .wordmark, tracked wide because it is a title here and a logo
   there. The trailing tracking is cancelled with a matching indent, or the
   name sits a letter-space right of the eye. The outer box is the mask; the
   timeline only moves the inner line through it. */
.intro__word {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(50% + min(28vw, 320px) * .30 + 8px);
  z-index: 1;
  margin: 0;
  overflow: hidden;
  text-align: center;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: clamp(1rem, 1.5vw, 1.35rem);
  letter-spacing: .32em;
  text-indent: .32em;
  line-height: 1;
  padding-bottom: .1em;
  color: var(--ink);
  pointer-events: none;
}
.intro__word-i { display: inline-block; }
/* The masthead runs SEEKDIGITAL solid and lets the ink/bone split do the word
   break. At .32em tracking the split alone does not hold — the eye reads one
   long word — so the name gets the space the logo does not need. */
.intro__word-i span { color: var(--plate); margin-left: .45em; }

/* The caption row. Chrome, not content: small, tracked, --ink-55.
   The hairline between the labels is a clock: --ink-15 track, --ink fill,
   scaled from the left by the timeline. */
.intro__cap {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: clamp(16px, 3vw, 30px);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 18px;
  font-family: var(--text);
  font-weight: 500;
  text-transform: uppercase;
  font-size: .7rem;
  letter-spacing: .14em;
  line-height: 1;
  color: var(--ink-55);
  white-space: nowrap;
  pointer-events: none;
}
.intro__bar {
  flex: 1 1 auto;
  min-width: 48px;
  height: 1px;
  background: var(--ink-15);
}
.intro__bar-i {
  display: block;
  height: 100%;
  background: var(--ink);
  transform-origin: 0 50%;
  transform: scaleX(0);
}
/* Under 560px there is not room for both labels and a line worth reading as
   progress, so the descriptor goes and the place stays. */
@media (max-width: 560px) {
  .intro__cap-r { display: none; }
}

/* The handoff fade. This has to outlive .intro-ready — the page comes alive
   underneath while the overlay is still on top of it — so it carries its own
   display and drops pointer-events, being only a picture by then. */
/* The panel leaves by COMING INTO FOCUS. It still carries backdrop-filter:
   blur(52px) at the moment the timeline fades it, so fading it out is a
   cross-dissolve from the blurred page to the sharp one rather than a cut off
   a white card — which is what it used to be, and which is why that white was
   the only place in the build a colour appeared that is nowhere else in it.
   The fade is written inline by the timeline, which beats this class, so all
   .intro--out does is keep the panel out of the pointer's way between the fade
   finishing and teardown. */
.intro--out {
  display: flex !important;
  position: fixed;
  inset: 0;
  z-index: 400;
  pointer-events: none;
}

/* ---------- 21. Craft — focus, caret, optical, widows, transitions ----------
   The details a reader never consciously notices and always feels. Nothing here
   changes a layout; all of it changes how finished the page looks. */

/* Optical left alignment. Every element on this page shares one left edge, but
   a glyph's ink does not start at its box: Bebas puts about .015em of air in
   front of a cap, Newsreader about .04em. At 152px against 17px that is 2.3px
   against 0.7px, so a metrically aligned headline sits ~1.6px right of the copy
   beneath it. Too small to name, big enough to read as sloppiness — so the
   display face is pulled back by its own bearing and the INK lines up.

   Measured, not guessed: at 1440 the hero's ink sat 1.0px right of the copy
   beneath it, and -.007em closes that to under a tenth of a pixel. Applied only
   where a display line actually SHARES the edge with something else and is big
   enough for the gap to be visible — .sec-head__title sits in its own grid
   column with nothing aligned to it, so correcting it there would be a
   superstition rather than a fix. */
:root { --optical: -.007em; }
.hero__title,
.page__title { margin-left: var(--optical); }

/* Widows. A single word alone on the last line of a headline is the cheapest
   tell there is. Both properties degrade silently where unsupported. */
h1, h2, h3, .sec-head__title, .case__title, .rate__name, .acc__q {
  text-wrap: balance;
}
p, .sec-head__lead, .hero__lead, .case__desc { text-wrap: pretty; }

/* Hanging punctuation on quotes, so an opening mark sits in the margin and the
   first real letter keeps the shared edge. Safari honours this today; the
   others ignore it harmlessly. Nothing on the page quotes anyone yet — this is
   here so that the day a real testimonial arrives, it is set correctly. */
blockquote {
  hanging-punctuation: first last;
  text-indent: -.42em;
}
@supports (hanging-punctuation: first) {
  blockquote { text-indent: 0; }
}

/* The caret is a brand surface too. Ink where the field sits on paper, salmon
   where it sits on the blue plate or the ink footer — never invisible. */
input, textarea, select { caret-color: var(--blue); }
.field input, .field textarea, .newsletter__row input { caret-color: var(--salmon); }

/* Scrollbar, where the platform allows it. Firefox and Chromium take the
   standard properties; anything else keeps its own and loses nothing. */
@supports (scrollbar-color: black white) {
  html { scrollbar-color: var(--ink-55) var(--paper); }
}
@supports selector(::-webkit-scrollbar) {
  html::-webkit-scrollbar { width: 12px; height: 12px; }
  html::-webkit-scrollbar-track { background: var(--paper); }
  html::-webkit-scrollbar-thumb {
    background: var(--ink-55);
    border: 3px solid var(--paper);   /* the inset that keeps it a hairline, not a slab */
  }
  html::-webkit-scrollbar-thumb:hover { background: var(--ink); }
}

/* Cross-document view transitions. One line, feature-detected by the parser
   itself: a browser without it never sees the at-rule, and navigation stays
   exactly as it is. Twenty-four static pages start behaving like one document. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}


/* ---------- 22. Cursor — REMOVED ----------
   The registration crosshair that replaced the native pointer is gone
   (2026-10-14, owner's request) along with main.js module 19. The page uses
   the browser's own cursor; `cursor: pointer` on controls is unchanged. */


/* ---------- 23. Link kinetics ----------
   The nav label steps up out of its frame while an identical one arrives from
   below, a letter at a time. Two rows exactly one line apart, clipped by the
   link itself, so the swap is a substitution rather than a slide.

   The rows are built in main.js module 20; if that never runs, the markup is
   still a plain word and every rule below has nothing to match. */
.masthead__nav a { line-height: 1.1; }

.kin {
  display: block;
  overflow: hidden;          /* the frame. Its own focus ring is drawn outside it. */
  white-space: nowrap;
}
.kin__row { display: block; }
.kin__row--in { position: absolute; left: 0; top: 0; }

.kin__c {
  display: inline-block;
  transform: translateY(0);
  transition: transform .48s cubic-bezier(.4,0,.1,1);
  transition-delay: calc(var(--i, 0) * 24ms);
}
.kin__row--in .kin__c { transform: translateY(120%); }

/* Hover only. A touch device has no hover to leave, and would be handed the
   swapped state permanently — the one failure this gesture can have. */
@media (hover: hover) and (pointer: fine) {
  .kin:hover .kin__row--out .kin__c,
  .kin:focus-visible .kin__row--out .kin__c { transform: translateY(-120%); }

  .kin:hover .kin__row--in .kin__c,
  .kin:focus-visible .kin__row--in .kin__c { transform: translateY(0); }
}


/* ---------- 24. Live details ----------
   The parts of the page that are true rather than animated. Both are built or
   driven from main.js module 22, which needs no GSAP — so they are also the
   parts that keep running on a visit where the CDN never answers. */

/* how much page is left, drawn on the masthead's own bottom rule */
.masthead__progress {
  position: absolute;
  left: 0; bottom: -1px;
  width: 100%; height: 1px;
  background: var(--primary);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}



/* ---------- 25. Dark build — the decisions the token flip could not make ----------
   Section 1 inverts the whole site from four values. These are the places where
   inverting the token was not enough, because a rule encoded not just a colour
   but an assumption about which side of the page it was sitting on. Each one is
   a contrast failure if left alone, so each is listed with the ratio it fixes. */

/* The contact block was a full-bleed plate in the accent. In the light build
   that accent was navy, then briefly a full-bleed electric green. The build is
   monochrome now, and their large surfaces are #000, #fff and #161616 in any
   case. So the plate takes their raised-panel value and the copy on it stays
   white, which also keeps every bone-coloured form detail inside it legible
   (bone on #161616 is 12.8:1). */
/* Translucent, so the field carries through the panel instead of ending at it.
   --panel is #161616; at .62 over the field the surface still reads as raised
   against the page, which is the whole job of the token, while the colour
   behind it stays continuous. */
.contact { background: rgba(22, 22, 22, .62); color: var(--ink); }

/* The featured rate card keeps the green, because a single small plate is
   exactly the surface the accent is for. Black on the bone plate is 15.4:1,
   and its details take the plate's own ink rather than the accent. */
.rate--feature .rate__list li::before { background: var(--paper); }
.rate--feature .rate__flag { color: var(--paper); }
.pstep--fill .pstep__no { color: var(--paper); }

/* Every case plate is a light surface now, so the base black-on-plate colour
   is right for all three. The bone plate used to be the one exception and
   carried an override to light text; that exception is now the bug. */
.case__plate--salmon .case__no,
.case__plate--salmon .case__stamp { color: var(--paper-70); }

/* The contact block's whole interior — labels, rules, placeholders, the lot —
   was written in --paper tones because the plate underneath it was dark and
   they were the light end of the scale. After the flip those tones ARE the
   dark end, so on the raised panel above they are black on near-black. Each
   one moves to its --ink counterpart. This is the price of re-grounding that
   section, and it is paid here rather than by leaving fifteen rules wrong. */
.contact__lead,
.contact__points li,
.field label,
.form__success p { color: var(--ink-70); }

.opt,
.field ::placeholder,
.form__fine { color: var(--ink-55); }

.contact__points li::before { border-top-color: var(--ink-15); }
.contact__alt { border-top-color: var(--ink-15); }
.contact__alt a { border-bottom-color: var(--ink-55); }

.contact .field input,
.contact .field textarea,
.contact .field select {
  color: var(--ink);
  border-bottom-color: var(--ink-55);
}
.contact .field input:focus-visible,
.contact .field textarea:focus-visible,
.contact .field select:focus-visible { outline-color: var(--ink); }

/* The footer is the one large light plate on the site — their --c-bg-secondary
   treatment. Bone was a highlight against a dark ground; on white it is 1.3:1,
   so on this plate the highlight role becomes the plate's own ink. */
.footer__col h3 { color: var(--paper); }
.footer__social a:hover,
.footer__bottom a:hover,
.newsletter__row button:hover { color: var(--paper); border-color: var(--paper); }
.newsletter__row input:focus,
.newsletter__row input[aria-invalid="true"] { border-bottom-color: var(--paper); }
.newsletter .error,
.newsletter__status { color: var(--paper-70); }

/* The cream button wipes to bone on hover but never restated its text colour,
   which was white — 1.3:1 against the arriving fill. */
.btn--cream:hover,
.btn--cream:focus-visible { color: var(--paper); }


/* ---------- 26. The headline word and the small labels ----------
   This section used to bring in two more faces, a serif italic and a
   typewriter. Both are gone: the build is two families now. */

/* The last word of the headline used to be set in a light serif italic with a
   rule drawn under it. It is the headline's own face and ink now: one italic
   serif word in a sans headline is the most copied gesture there is, and it
   read as a template. The markup hook stays so the hero timeline still finds
   it; the block it drew is switched off here, not in the timeline. */
.hl__block { display: none; }

/* Every small uppercase label. These were set in a typewriter face; they are
   the text face at 500 now, with far less tracking, so a label reads as a
   label and not as a system readout.

   .pstep__no IS NOT IN THIS LIST and must not be added back. It was, and that
   silently overrode the --display it is declared with in section 10: mono is
   the role for a small tracked-out label, and the process numerals are not
   labels — they are the largest type in the section and carry its rhythm. A
   figure is not automatically a label. See section 10. */
.sec-head__no,
.case__tag,
.case__stamp,
.case__no,
.process__hint,
.pillar__no,
.langswitch,
.footer__col h3,
.crumbs {
  font-family: var(--text);
  font-weight: 500;
  letter-spacing: .05em;
}



/* ---------- 27. Loader — the curtain ----------
   Rebuilt from hobro's own structure: .loader__content holding two
   .loader__curtain panels with transform-origin center top and center bottom,
   a .loader__line that grows from the left, and the label sliding up inside
   it. Their values where I could read them (--anim-default-time .75s,
   --anim-default-delay .27s).

   It is a CSS animation and nothing else. That is the whole safety argument:
   there is no state, no listener and no callback that can fail to fire, so the
   overlay cannot outlive its own timeline. The intro next door needs a
   dead-man timer in the <head> because a shader can wedge; this cannot.

   Scoped to html.js, so a visitor with scripting off is never shown a loading
   screen for a page that is already there. */
.loader { display: none; }

.js .loader {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 1000;              /* over the intro overlay (400) and skip link (300) */
  pointer-events: none;
  animation: loader-done 0s linear 1.62s forwards;
}

.js .loader__curtain {
  position: absolute;
  left: 0;
  width: 100%;
  height: 50.5%;             /* the .5 overlaps the seam so no hairline shows */
  background: var(--paper);
  will-change: transform;
}
.js .loader__curtain--top {
  top: 0;
  transform-origin: center top;
  animation: loader-part .75s cubic-bezier(.76,0,.24,1) .87s forwards;
}
.js .loader__curtain--bottom {
  bottom: 0;
  transform-origin: center bottom;
  animation: loader-part .75s cubic-bezier(.76,0,.24,1) .87s forwards;
}

/* the bar: 25px, paper-coloured, growing from the left edge like theirs */
.js .loader__line {
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 25px;
  margin-top: -12.5px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left center;
  animation: loader-fill .87s cubic-bezier(.62,0,.2,1) forwards,
             loader-drop .28s ease-in .87s forwards;
}

/* the label rides inside the bar, so it is a sibling rather than a child —
   a child would be scaled along with it */
.js .loader__text {
  position: absolute;
  top: 50%;
  left: var(--gutter);
  margin-top: -12.5px;
  height: 25px;
  display: flex;
  align-items: center;
  font-family: var(--text);
  font-weight: 500;
  font-size: .72rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--paper);
  overflow: hidden;
}
.js .loader__text span {
  display: block;
  transform: translateY(150%);
  animation: loader-slide .5s cubic-bezier(.4,0,.1,1) .12s forwards,
             loader-drop .28s ease-in .87s forwards;
}

@keyframes loader-fill  { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes loader-slide { to   { transform: translateY(0); } }
@keyframes loader-drop  { to   { opacity: 0; } }
@keyframes loader-part  { from { transform: scaleY(1); } to { transform: scaleY(0); } }
@keyframes loader-done  { to   { visibility: hidden; } }

/* Reduced motion gets the page, not a shorter curtain. The element is removed
   from the flow outright rather than animated quickly, because the point of
   the preference is not to see the transition at all. */
@media (prefers-reduced-motion: reduce) {
  .js .loader { display: none; }
}


/* ---------- 28. Line reveals ----------
   The word spans main.js module 23 wraps text nodes in. inline-block is what
   gives them a transform and an offsetTop to be grouped by; everything else
   about how the paragraph sets — its wrapping, its justification, the links
   and bold runs inside it — is unchanged, because the spans sit inside the
   original markup rather than replacing it. */
.w { display: inline-block; }

/* Under reduced motion module 23 never runs, so no .w exists and there is
   nothing here to stand down. This is only the belt-and-braces case where the
   split happened and the preference changed underneath it. */
@media (prefers-reduced-motion: reduce) {
  .w { opacity: 1 !important; transform: none !important; }
}


/* ---------- 29. Masthead fit ----------
   Bebas is a condensed face; Outfit and Inter Tight are not. The masthead was
   sized to the old widths and the row now runs 51px past the viewport between
   the burger breakpoint (901px) and about 1180px — the one band where the full
   nav, the sound toggle, the language switch and the CTA all have to share a
   line. Nothing is dropped: the tracking and the gaps give the width back,
   which is what they were there to lend. */
@media (max-width: 1180px) {
  .masthead__nav {
    gap: clamp(10px, 1.5vw, 20px);
    font-size: .92rem;
    letter-spacing: .03em;
  }
  .masthead__end { gap: 9px; }
  .langswitch { font-size: .86rem; letter-spacing: .05em; }
  .btn--sm { padding: 10px 13px 7px; letter-spacing: .03em; }
  .wordmark { font-size: 1.5rem; }
}


/* ---------- 30. Composition — breaking the mirror ----------
   The work section was two case studies in a perfect left/right mirror: plate
   1–7 / body 8–13, then exactly the reverse. That symmetry is the single most
   template-looking thing on the page — it reads as a layout that was filled in
   rather than composed.

   socialclubparis.com never repeats a block. One project runs wide with its
   title centred beneath; the next is a narrow card at the far left against a
   large one at the right, sitting at a different height, with the title ranged
   left. Nothing shares a baseline and no two blocks are the same shape.

   So: different spans, different aspect ratios, and a vertical offset that
   stops the two rows from lining up. Scoped above 1100px, which is where the
   existing responsive rules already collapse the grid to a single pair of
   columns — below that the reset at line ~1640 owns the layout and this must
   not reach it. */
@media (min-width: 1101px) {
  /* first: a wide landscape plate, the body narrow and dropped down its side */
  .case__plate { grid-column: 1 / 8; aspect-ratio: 4 / 3; }
  .case__body  { grid-column: 8 / 13; padding-top: clamp(36px, 5vw, 88px); }

  /* second: a taller, narrower plate pushed right AND down, body ranged left
     and sitting high — the two cases now overlap in height instead of facing
     each other across a centre line */
  .case--flip .case__plate {
    grid-column: 7 / 13;
    aspect-ratio: 5 / 6;
    margin-top: clamp(56px, 8vw, 140px);
  }
  .case--flip .case__body { grid-column: 1 / 6; padding-top: 0; }
}

/* ---------- 30b. The third case ----------
   The same card as the two above it (a plate with the client's mark, the body
   beside it) and, per the rule this section exists for, not the same shape: a
   SQUARE plate on five columns with the body running wide and level with its
   top edge, where the first is a landscape plate with a dropped body and the
   second a tall plate pushed down. The work itself is a reel and lives on the
   case study page; this card carries only the mark.

   The mark is a roundel: a black disc, white type, black counters. The redraw
   on hover needs every path in one ink, so the disc and the counters take
   currentColor like any other logo here and the type (.k) is knocked out in
   the plate's own colour. Flattening all of it to currentColor would fill the
   disc solid. On hover every fill drops and the outlines draw, as they do for
   the other two. */
.case__logo--5togo { width: 58%; height: 58%; }
.case__logo--5togo path.k { fill: var(--plate); }

@media (min-width: 1101px) {
  .case--third .case__plate { grid-column: 1 / 6; aspect-ratio: 1; }
  .case--third .case__body  { grid-column: 7 / 13; padding-top: 0; }
}
@media (max-width: 720px) {
  .case__logo--5togo { width: 40%; height: 66%; }
}

/* Their category tag: a filled dot, then the label tracked out. The dot is
   square here — the site already sets one that way on .case__stamp, and a
   circle would be the only rounded thing in the build (rule 3). */
.case__tag::before {
  content: "";
  display: inline-block;
  width: .46em;
  height: .46em;
  margin-right: .75em;
  background: currentColor;
  vertical-align: .06em;
}

/* Section rhythm. Every sheet used one --sheet-y top and bottom, so the page
   breathed at exactly one interval from top to bottom — metronomic, and the
   thing that makes a long page feel generated. These are the same intervals
   pulled out of step with each other. */
#services { padding-block: calc(var(--sheet-y) * .78) calc(var(--sheet-y) * 1.42); }
#work     { padding-block: calc(var(--sheet-y) * 1.46) calc(var(--sheet-y) * .72); }
#about    { padding-block: calc(var(--sheet-y) * .88) calc(var(--sheet-y) * 1.3); }
#rates    { padding-block: calc(var(--sheet-y) * 1.24) var(--sheet-y); }
/* #faq was the last section still breathing 1x/1x — symmetric, and identical
   to nothing else only because nothing else is symmetric. Measured 129.6px top
   and bottom while every neighbour was lopsided. #process and #contact are
   absent from this list on purpose: both carry padding 0 and set their own
   spacing internally. */
#faq      { padding-block: calc(var(--sheet-y) * 1.06) calc(var(--sheet-y) * .84); }

/* Section heads. Section 32 gave #work and #process compositions of their own
   and stopped there. Measured at 1440, #services, #rates and #faq opened at
   identical coordinates — title left 245, width 560, lead in 9/13 — so three
   of the seven sections announced themselves with the same gesture. (#about
   already differs: it carries no lead.) Rule 13 says no two blocks may share a
   shape, and a head repeated verbatim is the most visible way to break it,
   because it is the thing the visitor meets at every scroll stop.

   #services keeps the default. It is the first section, so it is the one that
   establishes the pattern the other two depart from — recomposing all three
   would just be three new patterns.

   Scoped above 1100px, which is where the twelve-column head is actually in
   play. Below it the existing max-width rules collapse every head to one
   column and must keep winning. */
@media (min-width: 1101px) {
  /* #rates — mirrored. The three price columns below start hard at column 1,
     so the head leans the other way and the section reads as a spread rather
     than a stack. */
  #rates .sec-head__lead  { grid-column: 2 / 6; }
  #rates .sec-head__title { grid-column: 7 / 13; }

  /* #faq — dropped. The accordion below is full-width rows, so the head stops
     competing for a right-hand column: a wider title measure, and the lead
     stepping down beneath it rather than sitting beside it. */
  #faq .sec-head__title { grid-column: 2 / 11; }
  #faq .sec-head__lead  { grid-column: 2 / 7; grid-row: 3; }
}


/* ---------- 31. The eye ----------
   Straight from socialclubparis.com, where a pair of eyes sits fixed in the
   corner and the pupils follow the pointer. It is the most memorable thing on
   that site and it costs almost nothing: it is the piece of personality that a
   correctly-built page otherwise has no room for.

   It is one eye now, and it is the intro's eye — same wobbled almond, same
   double-stroked iris, same stepped tilt, drawn by the hand main.js publishes
   in module 0 and intro.js draws from. Two eyes on one page that were drawn by
   different code would be two things to keep in sync.

   Drawn as wobbled paths rather than border-radius, so rule 3 stands — that
   rule is about square cards and buttons, not about what a drawing may
   contain. */
.eye {
  position: fixed;
  left: var(--gutter);
  bottom: clamp(18px, 3vw, 34px);
  z-index: 90;              /* under the masthead (100), over the page */
  width: 70px;
  /* WHITE, and never anything else — see the blend below. */
  color: #fff;
  pointer-events: none;
  opacity: 0;
  transition: opacity .5s ease;
  /* THE EYE INVERTS ITS BACKDROP. It is fixed in the corner for the whole
     scroll, so in one visit it passes over the black page, the aurora's trace,
     the raised contact panel, all three bone and white case plates and the
     white footer. No single ink clears that set: it was cyan, which is right on
     black and 1.3:1 on the footer plate, and the eye simply disappeared for the
     last screen of the page.

     difference against a white source returns |255 - backdrop|, the exact
     inversion of whatever it is standing on, so the separation is guaranteed
     rather than measured per section. On black it draws white, on the bone
     plate it draws near-black, and over the field it draws the field's
     complement — the same mechanism the ticker logos now use (section 8).

     It has to sit on .eye, not on the paths: the opacity below groups the SVG's
     descendants, so a blend applied inside it would composite against the group
     and not against the page. */
  mix-blend-mode: difference;
}
/* Full strength. At .85 the blend was 85% inversion plus 15% of the backdrop
   showing through, which pulls the result back towards the thing it is meant to
   be separating from — worst on the mid-tone plates, where it needs the
   separation most. The inversion is what gives the eye its presence now, so the
   opacity that used to soften it has nothing left to do. */
.eye.is-awake { opacity: 1; }
/* No blend to compose against in forced colours; fall back to plain ink. */
@media (forced-colors: active) {
  .eye { mix-blend-mode: normal; color: var(--ink); }
}
.eye svg { width: 100%; height: auto; display: block; overflow: visible; }

/* Rule 9, and it applies here for the same reason it applies to the intro: the
   transform origin lives in CSS and ONLY in CSS. Giving GSAP an svgOrigin as
   well puts two coordinate spaces against each other — the drawing is 240
   viewBox units rendered at 78px — and the result is not an error, it is an
   eye somewhere off screen and a corner that looks empty. One owner, one
   space. */
.eye__tilt,
.eye__gaze {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}
.eye__tilt { will-change: transform; }
.eye__gaze { will-change: transform; }

.eye__lid,
.eye__lid2,
.eye__iris,
.eye__iris2 {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Widths are in viewBox units with no non-scaling-stroke, so rendered weight
   moves with the size of the SVG. The intro draws these same shapes at 320px
   with 1.95/.98/1.5/.75; this is 70px against the same 240-unit box, so each
   width is scaled by the ratio of the two renders to put the same pen on the
   page — about 1.75 / .88 / 1.37 / .68 CSS px. Change the width above and
   these have to move with it, or the drawing turns to hairlines.

   70px, not 78. The eye stands in the outer margin and the interior pages'
   column starts at 128px at the 1400px threshold: at 78 wide it ended at 126
   and left 2px of air, which is a collision waiting for the first padding
   change. At 70 it ends at 118 and the clearance is 10. */
.eye__lid   { stroke-width: 6.0; }
.eye__lid2  { stroke-width: 3.0; opacity: .5; }
.eye__iris  { stroke-width: 4.7; }
.eye__iris2 { stroke-width: 2.35; opacity: .42; }
.eye__pupil { fill: currentColor; }

/* It lives in the page's outer margin, and that margin only exists once the
   viewport is wider than --page (1240px) plus room for it. Below that the text
   column reaches the screen edge and the eye would sit on top of it — measured
   at 1400px, where the interior column starts at 128px and the eye ends at
   118. So it is shown only where there is somewhere for it to stand, and a
   fine pointer is in any case the only thing it can follow. The min-width here and the one in main.js module 24 are the
   same number on purpose: a hidden eye must not be built at all, or it sits
   there running a repeating tween nobody can see. */
.eye { display: none; }
@media (min-width: 1400px) and (hover: hover) and (pointer: fine) {
  .eye { display: block; }
}
@media (prefers-reduced-motion: reduce) {
  .eye { display: none; }
}



/* --ink-70 and --ink-55 are calibrated for the BLACK page, where they are 10:1
   and 7.4:1. A wash under them is a mid-tone, and the budget collapses: at 70%
   white the backdrop has to stay under L .061 for 4.5:1, which is a wash so
   faint there is no point painting it. Full ink lifts that ceiling to .183 —
   three times the headroom — and is what lets the washes be visible at all.
   Scoped to the washed sections; everywhere else the tokens are still right. */
#services p, #services li, #rates p, #rates li,
#about p, #about li, .contact p, .contact li,
#services .sec-head__lead, #rates .sec-head__lead, #about .sec-head__lead {
  color: var(--ink);
}
/* ...but NOT on a plate. The lift above is written with ID selectors, which
   outrank the class rules that ink the pale plates — it silently turned the
   featured rate card's black type white on amber, measured at 2.28:1. Anything
   sitting on its own opaque plate keeps that plate's ink, so this has to win
   back, and to win it needs the ID too. */
#rates .rate--feature,
#rates .rate--feature p,
#rates .rate--feature li,
#rates .rate--feature b,
#rates .rate--feature span,
#rates .rate--feature i { color: var(--paper); }
#rates .rate--feature .rate__for,
#rates .rate--feature .rate__price i,
#rates .rate--feature .rate__price span { color: var(--paper-70); }

/* ---------- 31c. The marks ----------
   The washes put colour in the grounds; this puts it in the ink. Without it the
   page reads as monochrome type sitting ON something coloured, rather than as a
   coloured page — the palette has to reach the marks or it stays scenery.

   Everything here sits on the black page or on a light plate, never on the
   hero's field, which is why these can be saturated: the contrast budget on
   #000 is enormous, and the whole difficulty in the hero was that its ground is
   already a mid-tone.

   The one thing deliberately NOT coloured is the focus ring. It has to be
   visible on the black page, on the pale plates and on the hero's field alike,
   and bone is the only value in the build that clears all three. A cyan ring
   would look better on two of them and disappear on the third. */

/* The separators cycle, so the marquee carries the whole palette past rather
   than repeating one accent.

   THE CYCLE ALTERNATES WARM AND COOL, and the field is why. The rule for this
   strip has never changed — a separator must not be the colour of the thing
   directly behind it — but what is behind it has moved twice. Against the old
   flat red field, --c-orange WAS the backdrop and --c-moss sat at 1.6:1 over
   it, so the strip retreated to four pale values and read as four of the same
   mark. This field is a warm frame round a cool centre, so no single hue is the
   backdrop anywhere: the strip can take the warm half and the cool half and
   alternate them, and each mark lands where the other one would have vanished.

   Measured on black, which is what the strip actually sits on once the field
   has faded to its trace: 6.53 / 8.11 / 8.91 / 6.75 to 1. */
.ticker__sep:nth-of-type(4n+1) { color: var(--c-orange); }   /* L .276 warm */
.ticker__sep:nth-of-type(4n+2) { color: var(--c-mauve);  }   /* L .356 cool */
.ticker__sep:nth-of-type(4n+3) { color: var(--c-sand);   }   /* L .395 warm */
.ticker__sep:nth-of-type(4n+4) { color: var(--c-cyan);   }   /* L .288 cool */

/* The process numbers count through the palette in order, so the sequence
   reads as a sequence and not as four of the same mark. */
.pstep:nth-child(1) .pstep__no { color: var(--c-orange); }
.pstep:nth-child(2) .pstep__no { color: var(--c-amber); }
.pstep:nth-child(3) .pstep__no { color: var(--c-cyan); }
.pstep:nth-child(4) .pstep__no { color: var(--c-moss); }
.pstep:nth-child(5) .pstep__no { color: var(--c-sand); }

/* The corner eye is NOT in this list any more. It used to take the cool end of
   the palette so it read as belonging to the field; the trouble is that it is
   fixed and the page under it is not, so one ink had to work on the black page,
   on the bone plates and on the white footer at once, and cyan on white is
   1.3:1. It now inverts its backdrop instead — section 31 — which is the same
   idea (it takes its colour from what it is over) with the contrast guaranteed
   rather than hoped for. */

/* The section washes are GONE, and this note is why.

   There were four of them — a wedge, a band, a glow, a deep ground — each an
   absolutely positioned layer inside its own section with overflow:hidden. Each
   therefore ENDED at its section's box, and the boundaries between sections
   were visible as hard horizontal edges where one field stopped and black
   resumed. Full-bleeding them fixed the left and right margins but could not
   fix that: a per-section layer clips per section by construction.

   The single fixed field in section 33 replaces all four. It never meets a
   section boundary because it is not inside a section, and its colour varies
   down the page because its three layers scroll at three different rates
   rather than because four separate elements were given four different
   treatments. */

/* Grain. Generated feTurbulence rather than a bitmap, so it costs no request
   and tiles at any size. It is what stops a blurred stack of gradients from
   banding, and it is most of what reads as "printed" rather than "rendered".

   It sits on .aurora, above all three layers, so the whole field shares one
   grain rather than each layer carrying its own — three grains would move
   against each other and read as noise crawling, which is the exact thing the
   baked-wobble rule elsewhere in this build exists to avoid.

   Measured on the reference, adjacent pixels along a row differ by 15.2/255 on
   average; at opacity .05 this build was nowhere near that and the field read
   as clean CGI beside it. */
.aurora::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .18;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.7' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
}


/* ---------- 32. Composition, continued — the service pillars ----------
   Same problem as the work grid, one section earlier: two pillars built to
   identical spans (id 1–2, head 2–7, list 8–13), so the second is a carbon
   copy of the first and the eye stops reading after the top one.

   Social Club's answer is that no two blocks share a shape. Here the second
   pillar inverts the relationship instead of repeating it — the list takes the
   wide left side and the heading sits narrow at the right, so the pair reads as
   a spread rather than as two rows of a table. */
@media (min-width: 1101px) {
  .pillar + .pillar .pillar__id   { grid-column: 12 / 13; grid-row: 1; text-align: right; }
  .pillar + .pillar .pillar__head { grid-column: 7 / 12; grid-row: 1; }
  .pillar + .pillar .pillar__list { grid-column: 1 / 6; grid-row: 1; }

  /* and it starts lower, so the two do not sit on one shared top edge */
  .pillar + .pillar .pillar__head,
  .pillar + .pillar .pillar__list { padding-top: clamp(20px, 3vw, 52px); }

  /* the rule above a pillar is a full-width divider; on the inverted one it
     stops short, which is what stops the two dividers reading as a table */
  .pillar + .pillar { border-top: 0; position: relative; }
  .pillar + .pillar::before {
    content: "";
    position: absolute;
    top: 0; right: 0;
    width: 58%;
    border-top: var(--rule);
  }
}

/* One section head is centred rather than ranged left. Social Club alternate
   this block to block — a title under a wide image is centred, a title beside
   a narrow one is ranged left. Work is the section that introduces the
   portfolio, so it is the one that gets the change of address. */
@media (min-width: 901px) {
  #work .sec-head__title,
  #work .sec-head__no { grid-column: 1 / 13; text-align: center; }
  #work .sec-head__lead { grid-column: 4 / 10; text-align: center; }
  #work .sec-head__rule { grid-column: 1 / 13; }
}


/* ---------- 33. The stage & the transparent chrome ----------
   This section began as noartmusic.com's move: full-bleed footage with the
   navigation, logo and markers all set to mix-blend-mode: difference, so the
   chrome has no colour of its own and re-colours itself continuously as the
   frame plays.

   THE BLEND IS GONE. It did not survive the aurora. difference returns
   |255 - backdrop|, so it needs a backdrop at one end of the scale; against a
   coloured mid-tone it returns another coloured mid-tone, and the navigation
   went olive over the teal and blue over the orange. It was only ever viable
   while the hero was flat black, where difference against #000 is the identity.
   No mix-blend-mode declaration survives in this stylesheet — if you are here
   because a comment mentioned one, that is why.

   WHAT REPLACED IT is a bar with no paint at all over the hero, tinting to
   frosted glass once it is past it (section 29). A tint plus a blur puts a
   floor on the luminance behind the type whatever drifts underneath, which is
   something no blend mode can promise, and it holds over the aurora, the black
   page and the bone plates alike.

   The stage still may not take a transform, and .hero still may not take a
   z-index — not for the blend any more, but because .stage is absolutely
   positioned against .hero and full-bleeds by negative margin. */
/* The hero has to be the stage's containing block, and it was static. */
.hero { position: relative; }

/* Full-bleed, not column-width. .hero is capped at --page (1240px), so an
   inset:0 stage left black bars down both sides of the aurora.

   Done with a negative margin rather than a transform on purpose: a transform
   would make .stage the containing block for its own fixed descendants and
   re-root the lift applied in main.js module 25. Keep it a margin. */
/* FIXED, and therefore anchored to the viewport rather than to the hero.

   It used to be absolute inside .hero, full-bleed by a negative margin and
   lifted behind the header — so it scrolled away with the hero and the colour
   ended at the ticker. Fixed, it stays under the whole document and is faded
   out by main.js module 28 as the hero leaves, which is a different thing from
   ending: the page keeps a trace of the field all the way down.

   z-index -1, NOT 0. A fixed element at z-index 0 is a positioned box and would
   paint above the in-flow content of every section below, covering the text. At
   -1 it paints below all in-flow content and above the canvas background, which
   is exactly the slot a ground wants. It also means .hero must still never open
   a stacking context (rule 11) — at -1 the stage would be trapped inside it and
   clipped to the hero's box, which is the bug it used to have by design.

   Sections with their own opaque background (the process, the contact panel,
   the footer) occlude it naturally, and that is wanted: it is what stops the
   field from being a flat tint over the entire page. */
.stage {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background: var(--paper);
}
/* The aurora fills the stage and sits under any video that is dropped in.

   TWO transforms, on two elements, because they answer to different clocks. The
   element carries the SCROLL — a slow parallax written by module 28 — and the
   ::before carries the idle drift as a CSS animation. They cannot share a
   property: an animation and a scroll-written inline style on the same
   transform is a fight the animation always wins. */
.stage__aurora {
  position: absolute;
  inset: 0;
  /* The base scale is not decoration — it is the headroom the parallax travels
     into. The stage clips (overflow:hidden), so translating the field down N px
     leaves N px of nothing at the top, which renders as a hard black band
     across the screen: the field appears to start a fifth of the way down. A
     scale of S gives (S-1)/2 x height of cover, so 1.16 on a 732px viewport is
     58px and 1.24 is 88px — both comfortably past the 50px the parallax uses,
     at every point in between. Raise --aurora-y in module 28 and this has to
     rise with it. */
  /* No transform here any more. The parallax moved onto the individual layers,
     which have 25% of overhang to travel into; this element has none, so any
     translate on it exposed the stage's clip as a hard black band. That also
     retires the base scale that existed only as cover for it — and with it the
     1.35x compounding it caused in the intro's flood. */
  will-change: opacity;
}

/* The copy scrim, and it is not optional.

   Stacking the aurora's teal stops at their brightest overlap composites to
   about rgb(28,160,167) — 3.2:1 against white. That is fine for the headline,
   which is 100px+ and bold and only needs 3:1, and it is NOT fine for the lead
   paragraph and the stamp underneath it, which need 4.5:1.

   So the frame keeps the full aurora exactly where the big type is and darkens
   only the band the small copy sits in. Nothing is dimmed that did not have to
   be. */
/* ONE scrim, worn by both. The intro's flood has to be the same picture as the
   hero it hands off to, and the scrim is part of that picture: an unscrimmed
   flood is a brighter field than the page behind it, so the panel fading out
   reads as the colour dimming — a seam exactly where the whole sequence is
   trying not to have one. */
/* The scrim FADES SEPARATELY from the field, and it has to.

   It was written to protect two things that only exist at the top of the page:
   the transparent masthead, and the hero copy. Fixing the stage turned it into
   a permanent vignette — a hard dark band across the top of every screen for
   the whole scroll, which is what it looked like: the field appeared to start
   a fifth of the way down the viewport. Module 28 takes --scrim-o to 0 across
   the hero, by which point the masthead has gone .is-solid and carries its own
   floor anyway. The intro's flood does not set the variable, so it keeps the
   full scrim and stays identical to the hero it hands off to. */
.stage::after,
.intro__flash::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: var(--scrim-o, 1);
  pointer-events: none;
  /* Cut back hard. The reference has no scrim at all, and this was most of why
     the build read as colour-on-black beside it: .74 at the top and .90 at the
     bottom over a field whose own median luminance is .24. What is left is the
     minimum each band actually needs — the nav and the lead are SMALL text at
     4.5:1, so they get a floor; the display headline is large text at 3:1 and
     needs none, which is why 17%..70% is now completely open. */
  background: linear-gradient(to bottom,
    /* the top band is what carries the transparent masthead — see .masthead */
    /* These stops are in VIEWPORT percentages now, not the old stage's. The
       stage is fixed, so the scrim is fixed with it and no longer tracks any
       particular paragraph down the page — it protects bands of the SCREEN.
       That is sound because the only moment the field is at full strength is
       scroll 0, and module 28 has faded it to a trace by the time the copy has
       moved anywhere. Tuned for what is on screen at scroll 0: the nav across
       the top, and the headline's lower lines and the bone cursive word across
       the bottom. */
    /* The scrim's black is COOL again, and it has to follow the field's hue
       every time the field changes. A darkening layer only reads as "darker"
       when it shares a hue with what it is darkening: a blue-black over the old
       red field composited to a dull mauve haze, and by the same arithmetic the
       warm rgba(20,3,5) that replaced it hazes pink over this teal one. Same
       alphas, same stops — only the hue of the darkening moved, so every ratio
       below holds. */
    rgba(5, 21, 22, .58) 0%,
    rgba(5, 21, 22, .34) 8%,
    rgba(5, 21, 22, .20) 13%,
    transparent 19%,
    /* This ramp exists for ONE element. The cursive word is bone, not white —
       L .70 — so it needs its backdrop under L .20 to clear 3:1, where white
       only needs .30. Measured, it sat at 2.69:1 over the cyan mass. The fix is
       here rather than on .hl__t because that colour is the signature of the
       whole type system; darkening a band is reversible, re-inking it is not.

       The stops went up (.16/.30/.40 to .22/.34/.44) when the field was rebuilt
       on the reference. The teal field is BRIGHTER than the red one it replaced
       — median L .194 against .13, which is the point of it — and swept across
       the phase of the drift the cursive word bottomed out at 3.01:1 against a
       3.0 requirement. Three hundredths is not a margin.

       AND THE RAMP MOVED UP (open to 48%, .22 by 64%, .34 by 82%) when the
       headline was capped by height (§7). At 1440x820 the capped headline puts
       the word at 62–75% of the frame rather than 71–95%, which is the gentle
       start of the old 56–76% ramp, and it measured 3.08:1 there. Same alphas,
       same shape, shifted up by the distance the word moved. It also covers the
       lead, which now sits on the first screen at ~86% instead of below it. */
    transparent 48%,
    rgba(5, 21, 22, .22) 64%,
    rgba(5, 21, 22, .34) 82%,
    rgba(5, 21, 22, .44) 100%);
}

/* The open window is tuned to where the copy ISN'T, and that changes with the
   viewport. On a wide screen the hero is tall and the lead sits below 70%,
   inside the lower scrim. Narrow it and the same copy rides up into the open
   band: measured at 500px, the lead came out at 2.47:1 and the paragraph under
   it at 3.70:1, both against a 4.5:1 requirement, while the desktop audit
   passed because the text was somewhere else entirely. So the window closes
   earlier when there is less height to spend.

   IT CLOSES EARLIER AGAIN NOW, and the same trap caught the same element a
   second time. At 900x800 the cursive word occupies 52%..68% of the viewport,
   which was exactly the old `transparent 52%` to `.22 at 68%` ramp — the word
   sat in the open half of it and measured 2.84:1 against its 3.0. The wide
   audit had passed at 3.40:1 because at 1440 the word is somewhere else. The
   window now shuts at 44% with an extra stop at 56%, which is 3.19:1 at the
   brightest phase of the drift. ANY RETUNE HERE HAS TO BE MEASURED AT BOTH
   WIDTHS: the two variants protect different elements because the same elements
   are in different places. */
@media (max-width: 1100px) {
  .stage::after,
  .intro__flash::after {
    background: linear-gradient(to bottom,
      rgba(5, 21, 22, .58) 0%,
      rgba(5, 21, 22, .34) 8%,
      rgba(5, 21, 22, .20) 13%,
      transparent 19%,
      /* Shifted up 4–5% when the mask margins stopped collapsing (§3): the
         headline lost .18em per line break and at 900x800 the word rode up
         36px, from 3.56:1 to 3.20:1 on the old stops. Same shape, moved by
         the distance the copy moved. */
      transparent 40%,
      rgba(5, 21, 22, .18) 51%,
      rgba(5, 21, 22, .30) 65%,
      rgba(5, 21, 22, .40) 82%,
      rgba(5, 21, 22, .52) 100%);
  }
}

/* AND A THIRD, for the phone. Two variants were not enough for the same reason
   one was not: the hero's copy does not scale with the viewport, it REFLOWS,
   and every reflow puts it somewhere new in the frame. Measured at 500px the
   cursive word had moved up to 34%..42% and the lead to 48%..61% — both inside
   the ≤1100 variant's open window, which had been tuned around a 900px layout
   where the same two elements sit at 52%..68% and 74%..82%. They came out at
   2.93:1 and 4.33:1.

   THE WINDOW DOES NOT FULLY OPEN AT THIS WIDTH. There is no band of the frame
   that the copy is reliably not in — at 500px the hero fills the screen — so
   what would have been transparent is a .10 floor instead. That is a real cost
   and it is the right one: a tenth of an alpha over the field is a slightly
   deeper version of the same picture, where an unreadable lead is not a picture
   at all. Above this breakpoint the window still opens completely.

   Any retune is a THREE-width measurement now: 1440, 900 and 500. */
@media (max-width: 720px) {
  .stage::after,
  .intro__flash::after {
    background: linear-gradient(to bottom,
      rgba(5, 21, 22, .58) 0%,
      rgba(5, 21, 22, .34) 8%,
      rgba(5, 21, 22, .20) 13%,
      rgba(5, 21, 22, .10) 20%,
      rgba(5, 21, 22, .12) 34%,
      rgba(5, 21, 22, .22) 48%,
      rgba(5, 21, 22, .34) 66%,
      rgba(5, 21, 22, .50) 100%);
  }
}

.stage__media,
.stage__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The generated field is the understudy. The moment a real file is present in
   .stage__media the canvas is taken out, because two moving layers is noise. */
.stage.has-media .stage__canvas { display: none; }
.stage:not(.has-media) .stage__media { display: none; }

/* the hero's own content has to sit above the stage */
.hero__inner { position: relative; z-index: 1; }

/* THE BLEND IS GONE FROM THE HERO, and the aurora is why.

   difference returns |255 − backdrop|, so it needs a backdrop at one end of
   the scale or the other. Against a COLOURED mid-tone it returns another
   colourful mid-tone: over teal the navigation went olive, over orange it went
   blue, and both were unreadable. That was survivable while the hero was flat
   black — identity — but the aurora is the design now.

   What replaces it is the translucent masthead below, which is a better answer
   anyway: it works over the aurora, over the black page, and over the bone and
   white case plates, without depending on what is behind it at all.
 */

/* Blending is the one thing that must never take legibility with it, and
   difference has exactly one failure case: a mid-grey backdrop returns a
   mid-grey result. The generated field below is deliberately built out of
   near-black and near-white bands so that cannot happen — and it is the single
   constraint on any footage dropped in later. Mid-grey video will grey out the
   navigation. */

/* The masthead is TRANSPARENT over the hero, and only over the hero.

   Nothing is painted here: no tint, no blur, no rule. The nav sits directly on
   the aurora, which is what makes the top of the page read as one image rather
   than as a bar sitting on one. What keeps it legible is not the bar — it is
   the stage's top scrim, which shapes the aurora underneath instead of covering
   it.

   Past the hero it has to come back. The masthead is sticky, so it travels over
   the bone and white case plates, where white type on nothing is white type on
   white. main.js module 13 adds .is-solid once the hero is behind the visitor,
   and from there it is frosted glass again: a dark tint plus a blur puts a floor
   on the luminance behind the type whatever it is passing over. */
.masthead {
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color .35s ease, border-color .35s ease,
              -webkit-backdrop-filter .35s ease, backdrop-filter .35s ease;
}

/* The blur stays. I tried removing it for performance and could not justify it:
   interleaved rAF-driven scrolls put blur, flat and layer-promoted arms all
   inside each other's spread (~4-7ms run to run, no consistent ordering), so
   its cost is below this page's noise floor. It earns its place where the
   backdrop is NOT flat -- the bar crossing a case plate's inlined logo. */
.masthead.is-solid {
  background: rgba(5, 21, 22, .62);
  border-bottom-color: rgba(255, 255, 255, .10);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .masthead.is-solid { background: rgba(5, 21, 22, .93); }
}

/* The hero headline sits directly on the aurora with no blend and no tint, so
   it is the one thing whose legibility depends on the aurora staying dark. The
   stops carry alpha over a deep ground for exactly this reason — see the
   aurora block above. */
.hero__title { color: var(--ink); }

/* Reduced motion stops the field but keeps the blend — the inversion is the
   design, it is only the movement behind it that is motion. */
@media (prefers-reduced-motion: reduce) {
  .stage__canvas, .stage__media { animation: none !important; }
}


/* ---------- 34. (crop marks: removed) ---------- */
@media print { .stage { display: none !important; } }


/* ---------- 35. The accent ramp & the sampled ink ----------
   Two halves of one change, and they only make sense together.

   THE RAMP. The accent was a single flat hue and is now a moving band of five
   — see --grad-accent in section 1. Rule 3 of this build allows a gradient as a
   GROUND and forbids it as a fill on a component, and that rule is intact:
   what is below is not a decorative sheen on a button, it is the same field the
   page is made of, cropped to the shape of a surface. Every stop is a value out
   of the palette, in the order the reference lays them out from its left border
   to its centre, so a button and the hero behind it are two windows onto the
   same picture.

   WHERE IT IS ALLOWED. Four surfaces: the primary button, the wipe that fills
   the cream button on hover, and the two progress bars. None of them carries
   body copy, and the two bars carry no type at all — a full-width 1px rule is
   not the "gradient in a hairline" this build forbids, because the ramp runs
   along its 1440px length rather than across its 1px depth.

   WHERE IT IS NOT. Not the case plates, the featured rate card or the filled
   process step: those are light grounds under paragraphs and client logos,
   their neutrality is doing work, and a moving band behind a client's logo is a
   moving band behind a client's logo.

   AND NOT THE STRIPBAR, which is the one that had to be taken back out. It
   looks like the obvious candidate — a full-bleed band across the top of every
   page, `background: var(--primary)` right there in section 5 — and that
   declaration is dead. Section 29 overrides the strip to `background:
   transparent; color: var(--ink-55)` so that the top of the document reads as
   one open frame over the field rather than as two stacked bars, and its ink
   went white to suit. Painting the ramp back over it put white type at 62% on a
   band whose luminance runs from .28 to .40: measured 2.1:1. The lesson
   generalises — an accent declaration that a later section has already
   overridden is not a surface looking for a gradient, it is a surface that has
   already been given to something else.

   WHY IT MOVES ON background-position AND THE FIELD DOES NOT. Section 33 is
   explicit that the field must drift on a transform, because animating
   background-position there repaints a blurred full-bleed layer every frame and
   measured 7 dropped frames. That argument is about area, not about the
   property: these surfaces are a 34px bar and a couple of buttons, the repaint
   is a few thousand pixels, and a transform cannot slide a background under a
   fixed box without also moving the box. Do not copy this back into the field.

   THE INK. A five-stop ramp has no single safe ink. --grad-accent is held to
   the bright half of the palette so black clears every stop at 6.5:1 or better,
   which is what makes a static fallback honest; --grad-accent-wide reaches the
   green at L .137, where black is 3.74:1 and white is 5.61:1, and no fixed
   choice is right at both ends. So the ink is SAMPLED — main.js module 0c reads
   what is actually behind the type and writes the higher-contrast of black and
   white into --ink-auto. Every rule here falls back to a fixed value if that
   never arrives, and the fallbacks are chosen so a JS-less page is merely less
   clever, never less legible. */

/* The ramp needs somewhere to travel: the box shows a third of it at a time
   and the other two thirds are what slides through. */
.accent-fill {
  background-image: var(--grad-accent);
  background-size: 300% 100%;
  background-position: 0% 50%;
  animation: accent-drift 21s linear infinite;
}
/* 21s, and it shares no factor with the field's 53 / 34 / 27. Everything
   coloured on this page is on its own clock for the same reason those three
   are: periods that divide into one another re-align on a visible tick. */
@keyframes accent-drift {
  from { background-position:   0% 50%; }
  to   { background-position: 300% 50%; }
}

/* The two progress bars — the masthead's own bottom rule and the process
   section's. Both are full-width bands with nothing written on them, both are
   already the accent, and both are scaled by script on the x axis, which
   stretches the ramp along with them: the band compresses as the bar retreats,
   which reads as the same material under tension rather than as a second
   animation. */
.masthead__progress,
.process__bar span {
  background-image: var(--grad-accent);
  background-size: 300% 100%;
  animation: accent-drift 21s linear infinite;
}

/* The primary button, and the one place the WIDE ramp is used — the deep green
   is in it precisely so the label has to change ink as the band goes past. That
   is the effect; on a surface this small it is legible as one thing changing
   colour rather than as a flicker, and the transition below carries it. */
.btn--blue {
  background-image: var(--grad-accent-wide);
  background-size: 300% 100%;
  /* 12s, not 21s. At 21s the deep green passed under the label about once every
     ten seconds and the ink sat at one flat value in between — which is exactly
     what the effect is meant not to do. At 12s the label is inverting on a beat
     you can actually see. The sampler's interval moved with it (main.js module
     0c, 60ms -> 34ms): the ramp is 1.75x faster, so a rate tuned against the old
     speed would go stale between samples and hand back the contrast this whole
     mechanism exists to guarantee. THE TWO NUMBERS ARE A PAIR. Move one and you
     must move the other, then re-measure by screenshot. */
  animation: accent-drift 12s linear infinite;
  color: var(--ink-auto, var(--paper));
  transition: color .28s ease, border-color .35s ease;
}
/* The wipe that arrives on hover is the flat ink, as it always was: a ramp
   sliding up out of the bottom edge is two motions at once. */

/* The cream button's wipe fill takes the narrow ramp, which is black-safe at
   every stop, so its label can stay a fixed black — section 25 already sets
   that and it does not need the sampler. */
.btn--cream::before {
  background-image: var(--grad-accent);
  background-size: 300% 100%;
  animation: accent-drift 21s linear infinite;
}

/* ---- The sampled ink ----
   An element opts in by carrying data-ink-auto; main.js assigns the attribute
   from a selector list rather than the markup carrying it, so neither language
   tree has to be touched and the two cannot drift apart.

   --ink-auto is #000 or #FFF, whichever wins on contrast against what was
   actually measured behind the element. --ink-auto-soft is the same choice at
   the weight the old --ink-70 / --paper-70 tones had, for secondary copy inside
   the same box. Both ALWAYS have a fallback in the var() call, and the fallback
   is the value the rule had before the sampler existed. */
/* .28s, and it is short on purpose. A cross-fade from white to black passes
   THROUGH mid-grey, and mid-grey on a mid-tone backdrop is the one value that
   clears nothing — the transition is a small legibility hole in the middle of a
   change made for legibility. Half a second was long enough to read a word in.
   Measured on the primary button, the ink flips for about 1.5s once every 10s
   or so, when the ramp's deep green passes under the label; at .28s the transit
   is below the threshold at which anyone is mid-word, and it is still long
   enough not to read as a pop. Do not lengthen this to make it smoother. */
[data-ink-auto] {
  transition: color .28s ease, border-color .28s ease;
}

/* The chrome over the hero. The masthead paints nothing there (section 33), so
   the nav sits directly on the field and its backdrop swings from the deep teal
   ground at L .08 to the peach highlight at L .40 as the field drifts. White
   held that at 7.78:1 at the point it was measured and 2.4:1 at the point it
   was not; the sampler removes the question. */
.masthead:not(.is-solid) .masthead__nav a[data-ink-auto],
.masthead:not(.is-solid) .masthead__lang a[data-ink-auto] {
  color: var(--ink-auto, var(--ink));
}

/* Past the hero the bar is frosted glass with a floor on its luminance, so the
   sampler is switched off there and the fixed ink comes back. A tinted blur is
   a better guarantee than a measurement, and two mechanisms answering for the
   same pixels is one more than can be reasoned about. */
.masthead.is-solid .masthead__nav a[data-ink-auto],
.masthead.is-solid .masthead__lang a[data-ink-auto] {
  color: inherit;
}

/* Reduced motion keeps the ink and drops the drift. The sampler still runs
   once, because a static field still has a colour and the type still has to
   clear it — it is the movement that is motion, not the measurement. */
@media (prefers-reduced-motion: reduce) {
  .accent-fill,
  .masthead__progress,
  .process__bar span,
  .btn--blue,
  .btn--cream::before { animation: none; background-position: 12% 50%; }
  [data-ink-auto] { transition: none; }
}

/* Forced colours flattens gradients to nothing and overrides every ink we could
   choose, so both halves of this section stand down and let the platform paint.
   Same argument as the difference-blend fallbacks in sections 8 and 31. */
@media (forced-colors: active) {
  .accent-fill,
  .masthead__progress,
  .process__bar span,
  .btn--blue,
  .btn--cream::before { background-image: none; animation: none; }
  [data-ink-auto] { color: CanvasText !important; }
}

/* A printout has no field behind it and no drift, and a five-stop band across a
   button is a grey smear on paper. */
@media print {
  .accent-fill,
  .masthead__progress,
  .process__bar span,
  .btn--blue,
  .btn--cream::before { background-image: none !important; animation: none !important; }
  [data-ink-auto] { color: #000 !important; }
}

/* ---------- 36. Consent banner ----------
   Built by assets/js/analytics.js, and only when GA_ID is set, so none of
   this renders on a build without analytics.

   An OPAQUE panel, not a pane over the field: it holds body copy, and rule 2
   says anything reading over the drift has to be measured at three widths and
   several phases. On --panel it is white on #161616, 18.1:1, at any phase.
   Flat, square, one rule on top — the same material as the rest of the build.

   Bottom-RIGHT, because the corner eye (§31) owns bottom-left. Below 600px it
   spans the gutters and simply covers the eye; z 250 is over the eye (90) and
   the masthead (100), under the skip link (300) and the intro (400), so the
   question is asked after the intro, not over it.

   No entrance motion: nothing to register in gsap.matchMedia, nothing for
   §19 to cancel. */
.consent {
  position: fixed;
  right: var(--gutter);
  bottom: calc(var(--gutter) + env(safe-area-inset-bottom, 0px));
  z-index: 250;
  width: min(30rem, calc(100vw - 2 * var(--gutter)));
  padding: 18px 20px 20px;
  background: var(--panel);
  border: var(--rule);
  color: var(--ink);
}
.consent__kicker {
  margin: 0 0 8px;
  font-family: var(--text);
  font-weight: 500;
  font-size: .72rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-55);
}
.consent__body {
  margin: 0 0 16px;
  font-family: var(--text);
  font-size: .95rem;
  line-height: 1.45;
}
.consent__body a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* Two equal answers. Same class, same size, same order of weight. */
.consent__actions { display: flex; flex-wrap: wrap; gap: 10px; }
/* These are the build's only <button>.btn — every other .btn is an <a> — so
   the UA button grey has to be cleared or it sits under white type. */
.consent__actions .btn { flex: 1 1 10rem; background: transparent; }

@media (max-width: 600px) {
  .consent { left: var(--gutter); width: auto; }
}

/* The footer control reads as one more legal link, not as a button. */
.consent-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
}
.footer__bottom .consent-link:hover { color: var(--paper); }
/* A fourth legal item ("Setări cookie-uri") does not fit one row at 390px. */
.footer__bottom ul { flex-wrap: wrap; }

@media (forced-colors: active) {
  .consent { background: Canvas; color: CanvasText; border-color: CanvasText; }
}
@media print {
  .consent { display: none !important; }
}


/* ---------- 37. The flat build ----------
   Black, white and one accent, #950606. The owner's calls, 2026-10-12: the
   teal and orange gradient field read as a generated backdrop, so did a button
   that changed colour on a timer, and the bone plate went the same day.

   This section is the whole switch for the field. Sections 33 and 35 are still
   above it and still describe the field, the scrim, the ramp and the sampled
   ink, but none of that paints any more:

     - `--field: off` is read by main.js. Module 0c (the ink sampler) and
       module 28 (the field on scroll) both return at their first line when
       they see it, so every `var(--ink-auto, X)` in section 35 resolves to X.
     - The page's ground is the stage's own black, the same as the body.

   To bring the field back, delete this section. The accent and the plate are
   tokens in section 1 and are not switched from here. */
:root { --field: off; }

.aurora,
.stage::after,
.intro__flash::after { display: none; }

/* The accent is one flat red with white type on it, 9.1:1. */
.accent-fill,
.masthead__progress,
.process__bar span,
.btn--blue,
.btn--cream::before {
  background-image: none;
  background-color: var(--primary);
  animation: none;
}
.btn--blue { color: var(--ink); }
/* The hover wipe on the primary button is white, so its label goes black as
   the wipe arrives. The black button's wipe is the red, so its label stays
   white: section 25 turned it black for a pale wipe that no longer exists. */
.btn--blue:hover,
.btn--blue:focus-visible { color: var(--paper); }
.btn--cream:hover,
.btn--cream:focus-visible { color: var(--ink); }

/* Past the hero the bar is a neutral black, not a teal one. */
.masthead.is-solid { background: rgba(0, 0, 0, .72); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .masthead.is-solid { background: rgba(0, 0, 0, .94); }
}

/* The marks. They were spread across five hues. The big process numerals are
   type, so they are white; the red cannot be read at 2.3:1. */
.ticker__sep:nth-of-type(n) { color: var(--ink-55); }
.pstep:nth-child(n) .pstep__no { color: var(--ink); }
.pstep--fill:nth-child(n) .pstep__no { color: var(--paper); }
.intro__fly:not([data-fly="0"]) { color: var(--ink-55); }

/* The plates are white, and the middle case plate is a neutral grey so it
   differs from the two beside it. Black type on the grey is 12:1. */
.rate--feature { background: var(--plate); }
.case__plate--salmon { background: #C4C4C4; }
/* and its logo takes the plate's ink: it was white on the pale sand, 1.9:1 */
.case__plate--salmon .case__logo { color: var(--paper); }


/* ---------- 38. The liquid ----------
   The hero's ground, drawn by main.js module 25b: the accent, moving. The
   owner asked for it on 2026-10-12, after the flat build, so this is the ONE
   gradient on the site and it is made of the one accent. The old teal and
   orange field stays off (section 37).

   The canvas is small and stretched; `.stage--static` is what the stage wears
   when WebGL is missing or the context is lost, and it is the same picture
   held still. */
.stage__liquid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.stage--static {
  background:
    radial-gradient(60% 70% at 78% 30%, #950606 0%, rgba(149, 6, 6, 0) 70%),
    radial-gradient(55% 60% at 12% 85%, #5C0404 0%, rgba(92, 4, 4, 0) 72%),
    #000;
}

/* Over the field the primary button cannot be the accent: it would be red on
   red and the one thing the hero is for would disappear. It is white there,
   with a black wipe on hover, and goes back to red wherever the ground is
   black: below the hero, and in the bar once it has gone solid. */
.has-liquid .hero .btn--blue,
.has-liquid .masthead:not(.is-solid) .btn--blue {
  background-color: var(--ink);
  color: var(--paper);
}
.has-liquid .hero .btn--blue::before,
.has-liquid .masthead:not(.is-solid) .btn--blue::before { background: var(--paper); }
.has-liquid .hero .btn--blue:hover,
.has-liquid .hero .btn--blue:focus-visible,
.has-liquid .masthead:not(.is-solid) .btn--blue:hover,
.has-liquid .masthead:not(.is-solid) .btn--blue:focus-visible { color: var(--ink); }

/* The intro's last beat: the pupil opens onto the same red the hero wears. */
.intro__flash {
  background:
    radial-gradient(60% 70% at 78% 30%, #950606 0%, rgba(149, 6, 6, 0) 70%),
    radial-gradient(55% 60% at 12% 85%, #5C0404 0%, rgba(92, 4, 4, 0) 72%),
    #000;
}

@media print { .stage__liquid { display: none !important; } }
