/* ============================================================================
   SAVOY DRUGSTORE — components.css
   DESIGN_SPEC §8 (every component) + §5 (the five depth mechanisms, as
   reusable surface classes) + the §6.2 reveal primitives that base.css §12
   guards but deliberately does not author.

   BUILD ORDER:  tokens.css → base.css → components.css → sections/*.css
   Section files consume these classes. §8: "Section files may not redefine
   them."

   ── THREE STANDING RULES IN THIS FILE ─────────────────────────────────────
   1. No raw colour. Every colour is a custom property from tokens.css
      (§10.33). `currentColor`, `none` and `inherit` are keywords, not colours.
   2. No `border-radius`, no drop shadow. The only box-shadows are
      `inset 0 0 0 1px` (a border that does not affect layout) and
      `inset 0 1px 0` (the dark-band top light) — §5.
   3. Every interactive element is a real <a>/<button>, keyboard-operable, and
      carries the focus ring base.css §1 installs globally (§10.38).

   ── THE CASCADE (there is no longer a "bridge") ───────────────────────────
   tokens.css declares `@layer base, components, figures;` and base.css is
   wrapped in `@layer base`, so this layer simply outranks the element reset
   and ordinary specificity governs inside it. Nothing needs handing back.

   This file used to end in an unlayered `revert-layer` patch list, because an
   unlayered declaration beats any layered one and base.css's `p { font-size }`
   / `button { padding: 0 }` / `a { color: inherit }` therefore beat every
   component rule applied to those elements. The list had to name each affected
   component by hand, so it was structurally one component behind: `.foot__legal`
   was missing from it and rendered at 17.28px / 2.59px tracking instead of the
   §3.2 micro tier — a size that exists nowhere in the scale. The layer order
   removes the trap rather than patching its latest victim.

   ── WHY THERE IS A LOCAL TOKEN BLOCK (§00) ────────────────────────────────
   §8 decrees geometry that tokens.css does not carry and that is deliberately
   off the §4.1 lattice (button padding `16px 26px 15px`, ledger row
   `15px 0 13px`, the 44×44 WhatsApp square, the 14px brandmark rule, the
   `.28em`/`.32em` wordmark tracking, the 21px/-11px tag lockup from §3.5…).
   Those numbers are decrees, so they are reproduced — but as named,
   spec-cited custom properties, never as magic numbers in a rule body.
   ========================================================================= */


@layer components {

/* ============================================================================
   §00  COMPONENT TOKENS
   Everything §8 decrees that tokens.css does not own. Each line cites the
   clause that fixes its value. Section files may override any of these on a
   scoped ancestor; none of them may be re-invented.
   ========================================================================= */

:root {

  /* ── §8.1 brandmark ─────────────────────────────────────────────────── */
  --bm-word:        clamp(21px, 2.1vw, 27px);  /* SAVOY, Pagella 400        */
  --bm-word-stuck:  clamp(19px, 1.7vw, 22px);  /* …on the stuck nav         */
  --bm-tr-word:     .28em;                     /* wordmark tracking         */
  --bm-sub:         9.5px;                     /* DRUGSTORE · DUBAI         */
  --bm-tr-sub:      .32em;
  --bm-rule-h:      14px;                      /* the champagne hairline    */
  --bm-rule-gap:    var(--s3);                 /* margin 0 12px             */

  /* ── §8.2 nav ───────────────────────────────────────────────────────── */
  --nav-pad:        clamp(18px, 2.4vw, 34px);  /* rest padding-block        */
  --nav-pad-stuck:  var(--s3);                 /* 12px when .is-stuck       */
  --nav-gap:        clamp(14px, 1.5vw, 26px);  /* link rhythm               */
  --nav-cta-pad:    11px 20px 10px;            /* the compact CTA           */
  --nav-item:       24px;                      /* drawer entry, Pagella     */
  --nav-z:          80;

  /* ── §8.3 buttons ───────────────────────────────────────────────────── */
  --btn-pad:        16px 26px 15px;
  --btn-gap:        14px;
  --btn-arrow-w:    26px;
  --btn-arrow-h:    8px;
  --btn-arrow-x:    5px;                       /* §6.4 hover translateX     */
  --btn-press-y:    1px;                       /* §6.4 active translateY    */
  --btn-magnet:     12px;                      /* §6.4 magnetic envelope    */
  --btn-magnet-lbl: 5px;                       /* …inner label envelope     */
  /* §2.5.3's champagne ceiling, expressed as geometry (see .btn--inverse):
     the arrow's own zone — glyph + the gap before it + the button's right
     padding — 66px, well inside the 96px cap, and it ends exactly where the
     label ends so no glyph ever sits half on gold. */
  --btn-panel-metal: calc(var(--btn-arrow-w) + var(--btn-gap) + 26px);

  /* ── §8.5 the row ───────────────────────────────────────────────────── */
  --row-pad-y:      clamp(26px, 3vw, 40px);
  --row-shift:      18px;                      /* §6.4 hover padding-left   */
  /* §8.5 asks for the index "on the title's cap line" and publishes .72em to
     get it there. Measured, .72em lands it 11px BELOW that line: the value
     assumes a positive half-leading, and --t-h3's 1.04 leading is TIGHTER than
     Pagella's content area, so the title's own cap sits ~1.5px ABOVE its line
     box while the index's sits ~1.7px below its own. The correction is
     therefore negative, and it is a margin (a negative padding is dropped by
     the parser, which is how the decreed value survived being wrong).
     -.28em of the index's fixed 11px = -3.1px; across --t-h3's whole clamp
     range the exact figure moves by half a pixel, so one value holds. */
  --row-idx-top:    -.28em;                    /* index onto the cap line   */
  /* §8.8's index and marker hang on --t-h4, whose 1.2 leading DOES open a
     positive half-leading, so the same alignment needs the opposite sign from
     the row's. Two tiers, two corrections, both measured. */
  --acc-idx-top:    .22em;
  --row-tag-top:    14px;
  --row-desc-max:   var(--measure-row);        /* 50ch, §8.5                */

  /* ── §3.5 tag rows (the "simplest compliant implementation") ────────── */
  --tag-gap:        21px;                      /* flex gap: 0 21px          */
  --tag-sep-x:      -11px;                     /* middot sits in the gap    */
  /* THE SEPARATOR IS NOT A CHAMPAGNE ELEMENT (§2.5.1).
     §3.5 gives the middot to champagne, and §2.5.1 caps a section at THREE
     champagne elements and makes a fourth an instant fail. A tag row is the
     site's most-repeated component: any real section that carries a row list
     plus a ledger plus a form was blowing the cap before a single deliberate
     gold mark was placed, because champagne was hard-wired into six global
     component defaults. The accent then stops reading as a scarce metal and
     starts reading as a second body colour, which is the one thing §2.5 exists
     to prevent.
     The three roles that KEEP the metal are the three §2.5.2 names first —
     indices, the FIG. caption, the licence seal — and the separator is not one
     of them. It is a non-text decorative glyph between two labels, so
     --ink-ghost is legal for it (§2.2: "non-text only") and it does the
     typesetting job at the right visual weight: quieter than the tags it
     divides, which is what a separator should be. Geometry is unchanged. */
  --tag-sep:        var(--ink-ghost);          /* §3.5 separator glyph      */

  /* ── §8.6 ledger ────────────────────────────────────────────────────── */
  --ledger-pad-t:   15px;
  --ledger-pad-b:   13px;
  --ledger-val:     clamp(1.06rem, 1.5vw, 1.34rem);
  /* §8.6's prose says .22em, which is a FIFTH tracking value in a system
     §10.35 closes at four (.15 / .20 / .26 / .28em) — 10% wider than the .20em
     every other key, index and button label on the sheet carries, for no
     visible difference. The ledger key is a Micro L2 key like all the others,
     so it takes L2's tracking and the machine check §11 wants stays possible. */
  --ledger-tr-key:  var(--tr-m2);              /* = .20em (§3.2 Micro L2)   */
  --ledger-cap-max: 46ch;

  /* ── §8.7 form fields ───────────────────────────────────────────────── */
  --field-pad:      14px 16px;
  --field-pad-sel:  var(--s7);                 /* right room for the chevron */
  --field-ring:     3px;                       /* inset 0 0 0 3px tint       */
  --field-err-top:  6px;
  --field-chev:     10px;
  --ta-min-h:       132px;

  /* ── §8.8 accordion ─────────────────────────────────────────────────── */
  --acc-rail:       var(--col-rail);           /* 56px index rail            */
  --acc-mark-col:   var(--s5);                 /* 24px marker column         */
  --acc-mark:       12px;                      /* the drawn "+"              */
  --acc-panel-pb:   28px;
  --acc-panel-max:  62ch;
  /* WHERE THE QUESTION COLUMN ACTUALLY STARTS: the 56px rail plus the grid's
     own 16px column gap. §8.8 states the rail (56px) and the panel's
     padding-left (56px) separately and they are not the same measurement — the
     panel was inset 56px while the question it answers began at 72px, so in a
     component whose whole authority is a hairline register the two halves of
     one row did not share a left edge. Both now read this. 72px is on the 4px
     lattice; the 44px mobile value is --s6 + --s3. */
  --acc-text-x:     calc(var(--acc-rail) + var(--s4));

  /* ── §8.9 footer ────────────────────────────────────────────────────── */
  --foot-cols:      minmax(0, 4.4fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 3.4fr);
  --foot-link:      1rem;
  --foot-link-lh:   2.05;
  --foot-value:     .98rem;
  --foot-copy-max:  44ch;
  --foot-bar-top:   clamp(40px, 5vw, 64px);
  --foot-social:    var(--s6);                 /* 32×32 hairline square      */
  --foot-glyph:     15px;                      /* hand-drawn glyph           */
  --seal-sz:        52px;                      /* §7.3 Fig. V — off-lattice
                                                  by decree, so it is named
                                                  rather than rounded to --s7 */

  /* ── §8.10 WhatsApp ─────────────────────────────────────────────────── */
  --wa-size:        44px;                      /* a square, never a circle   */
  --wa-inset:       var(--s5);                 /* bottom/right 24px          */
  --wa-z:           60;

  /* ── §8.13 stage counter ────────────────────────────────────────────── */
  /* The meter is the counter's numeral drawn as a rule, so its rest value is
     the fraction the markup prints: stage 01 of 06. motion.js writes
     (index + 1) / n from the same `index` that writes the numeral, so the two
     halves of the component cannot disagree — including in a static export,
     where the drawn rail's latched high-water mark used to leave a 100%-royal
     rule under the words STAGE 01 / 06. */
  --stage-p:        .1667;                     /* 0–1, driven by §6.3 JS     */

  /* ── §6.2 reveal choreography — the decreed stagger steps ───────────── */
  --d-line:         90ms;                      /* per display line, cap 4    */
  --d-body:         180ms;
  --d-rule:         240ms;
  --d-art:          300ms;
  --d-row:          60ms;                      /* per row, cap 6 (360ms)     */
  --rise-y:         18px;
}

/* §3.3 / §10.21 / §10.22 — every component that sets tracked micro type gets
   the same line-breaking contract base.css gives the `.t-m*` roles: no browser
   hyphenation, no break at a typed hyphen, so a caps string can never end a
   line on a bare `-` and a token like `--t-wipe` or `professional-use` can
   never be split. These are the micro components that carry their own metrics
   rather than a `.t-m*` class. */
.field__label,
.field__error,
.form__note,
.crumbs,
.tags > li,
.fig-cap,
.fig-ref,
.stage__key,
.stage__name,
.foot__legal,
.foot__seal-key,
.foot__row dt,
.ledger dt,
.ledger dd small,
.ledger__sub,
.acc__index,
.row__index,
.btn__label,
.brandmark__sub {
  hyphens: none;
  -webkit-hyphens: none;
  overflow-wrap: normal;
  word-break: keep-all;
}

/* Component defaults that must invert on a dark or royal ground. base.css
   re-points the §2.7 aliases on exactly this selector list; these are the
   component-level additions. */
[data-ground="night"],
[data-ground="royal"],
.ground--night,
.ground--royal,
.on-night {
  /* The same reasoning as on paper, one rung up the inverse ramp: the separator
     is a quiet decorative glyph, never one of the section's three champagne
     elements. --ink-ghost has no inverse twin, so on dark it takes the micro
     text colour, which is the quietest legal tone there. */
  --tag-sep: var(--tx-micro);
}


/* ============================================================================
   §5  THE FIVE DEPTH MECHANISMS, AS REUSABLE SURFACE CLASSES
   There are five and no others. base.css already applies E0/E2/E3/E4 at
   SECTION scope via [data-ground] / .ground--* / .frost. These are the ISLAND
   forms — a plate, a panel, a footer, a list — plus E1, which has no section
   form at all because hairline separation is always a component's own edge.
   ========================================================================= */

/* ── E0 · flat paper ────────────────────────────────────────────────────────
   `background: var(--paper)`. No shadow. Ever. Use when an island must assert
   the page ground over an inherited step (a plate lying on paper-deep). */
.surf-e0 { background: var(--sf-page); }
.surf-e0--raised { background: var(--sf-raised); }
.surf-e0--sunken { background: var(--sf-sunken); }
.surf-e0--field  { background: var(--sf-field); }   /* §2.1: never a ground */

/* ── E1 · hairline separation ───────────────────────────────────────────────
   `border-top: 1px solid var(--rule-ink)` OPENS a component; `1px solid
   var(--rule-soft)` sits BETWEEN its rows. Exactly one structural rule per
   component (§2.6). No bottom rule — the next component's opener closes it. */
.surf-e1 { border-top: var(--rule-w) solid var(--ln-structural); }
.surf-e1 > * + * { border-top: var(--rule-w) solid var(--ln-soft); }

/* A secondary list that must not claim a second structural rule. */
.surf-e1--soft { border-top-color: var(--ln-soft); }

/* The two bare hairlines, for a section that needs a rule without a list. */
.hairline        { border: 0; height: var(--rule-w); background: var(--ln-hair); }
.hairline--soft  { background: var(--ln-soft); }
.hairline--ink   { background: var(--ln-structural); }
.hairline--metal { background: var(--hair-gold); }

/* §2.6 — the ONE 2px rule on the site: the 56px opener under a page H1. */
.rule-page { border: 0; width: var(--rule-l-page); height: var(--rule-w-page); background: var(--ln-structural); }

/* ── the colour chip ────────────────────────────────────────────────────────
   A specimen of a ground cannot be shown ON that ground: printed on
   paper-deep, the --paper-deep chip was a caption under blank paper — one of
   the four surfaces the whole site is built from, invisible in the document
   that defines it. Every chip therefore carries the 1px inset hairline (a
   legal §5 box-shadow), so a chip that matches its ground still reads as an
   object. `--chip` is set inline where the colour IS the specimen; that is the
   one place in the system where a colour may be written outside tokens.css,
   and it is written as a var() reference, never a literal. */
.swatch {
  display: block;
  /* §2.5.3 — "champagne fills larger than 96×96px" are banned outright. A chip
     is a specimen, but a specimen of gold is still gold on the page: four
     324 × 35px champagne tiles in a row read as one continuous ~1300px metal
     band, which is precisely the mass the clause exists to prevent. 96 × 32 is
     inside the ceiling on both axes and applies to every chip, so the swatch
     grid keeps ONE tile size whatever colour it is showing. */
  width: 100%;
  max-width: var(--s9);              /* 96px, §2.5.3's ceiling */
  height: var(--s6);
  background: var(--chip, transparent);
  box-shadow: inset 0 0 0 1px var(--ln-hair);
}

/* ── E2 · ground step ───────────────────────────────────────────────────────
   Adjacent sections change exactly one step on the paper ramp. The transition
   is a hard edge or a 1px --rule-soft line — NEVER a gradient fade. */
.surf-e2 { border-top: var(--rule-w) solid var(--ln-soft); }

/* ── E3 · dark band, as an island ───────────────────────────────────────────
   A single top-centre light source; no vignette from more than one direction.
   `inset 0 1px 0` is one of the only two legal box-shadows on the site.
   PAIRING: an E3 island that sits on a LIGHT ground must also carry `.on-night`
   so the §2.7 aliases invert (base.css §10). An E3 island already inside a dark
   section needs nothing — it has inherited the inversion. */
/* The final `var(--night)` layer is the background COLOUR, not decoration: the
   `background` shorthand resets background-color to transparent, so without it
   an E3 island renders as bare paper wherever the gradients do not paint (a
   print stylesheet, an image-blocked context) — with --on-night text on it,
   i.e. invisible. It also makes every automated contrast audit read the island
   as paper and report false failures for the whole band. */
.surf-e3 {
  background:
    radial-gradient(120% 80% at 50% -12%, var(--night-3) 0%, var(--night-3-fade) 62%),
    linear-gradient(180deg, var(--night-2) 0%, var(--night) 100%),
    var(--night);
  box-shadow: inset 0 1px 0 var(--rule-night);
}

/* §5 — on dark grounds depth is the 1px light border, the single top light,
   and the blueprint lattice. The lattice is drawn ONLY inside a declared art
   band, never full-bleed behind body copy (§7.2). */
.surf-lattice {
  background-image:
    repeating-linear-gradient(90deg, var(--on-night) 0 var(--rule-w), var(--night-3-fade) var(--rule-w) var(--lattice-pitch)),
    repeating-linear-gradient(0deg,  var(--on-night) 0 var(--rule-w), var(--night-3-fade) var(--rule-w) var(--lattice-pitch));
  opacity: var(--lattice-opacity);
  pointer-events: none;
}

/* ── THE SPECIMEN FORMS OF THE FIVE MECHANISMS ──────────────────────────────
   A depth mechanism printed as a row of text is not a specimen of anything: the
   §5 register named E0–E4 in five identical key/value rows, each with ~900px of
   empty paper across its middle, and a reader learned nothing from it that the
   specification does not already say. Everything else on this site expresses a
   claim as a drawn structure; the section whose subject is SURFACE has to show
   its surfaces.
     `.surf-tile`  — a 96px specimen block, the smallest area in which a ground
                     step, a hairline set or a frost reads as itself.
     `.surf-strip` — the tiles laid 4-up on the shell.
     `.surf-island`— the one mechanism that is a BAND rather than a tile (E3),
                     given the inset a band needs and the ground the inverse
                     controls exist for. */
.surf-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s5);
}
@media (max-width: 767px) {
  .surf-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.surf-tile {
  position: relative;
  min-height: var(--s9);            /* 96px */
  padding: var(--s4);
  display: grid;
  align-content: center;
  gap: var(--s3);
}
/* E2 is a STEP, and a step needs two grounds and the hard edge between them
   (never a fade, §5 E2). The tile carries the forward ground and its lower half
   carries the one behind it, so the mechanism is the tile. */
.surf-tile--step { padding: 0; align-content: stretch; gap: 0; }
.surf-tile--step > * { display: block; }

.surf-island {
  padding: var(--s7);
}
@media (max-width: 559px) {
  .surf-island { padding: var(--s5); }
}

/* ── E4 · frost ─────────────────────────────────────────────────────────────
   The only blur filters on the site: the nav, plus one dark variant for the
   mobile drawer. base.css exposes the same pair as `.frost` / `.frost--dark`;
   these are the §5-numbered aliases so a reviewer can grep the mechanism. */
.surf-e4 {
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  background: var(--frost-light);
  /* A backdrop-filter samples everything painted behind it up to the nearest
     backdrop root, and on a long page the compositor has to build that whole
     texture: past roughly 16k device pixels it gives up and drops entire bands
     of the page. (Measured: a full-page capture of the specimen sheet at DPR 2
     lost five bands, and every one of them came back with this line.) Isolating
     the island bounds the sampled area to what is actually behind it, which is
     also the only thing an E4 island should be frosting. The nav is fixed and
     therefore already samples a viewport-sized backdrop, so it needs nothing. */
  isolation: isolate;
}
.surf-e4--dark { background: var(--frost-dark); }


/* ============================================================================
   §6.2  REVEAL PRIMITIVES
   base.css §12 reproduces the §6.5 reduced-motion guard for `.rv > *`,
   `.rise` and `.draw *` but deliberately leaves the primitives themselves to
   this file. Its guard uses `!important`, so it wins over this layer whatever
   the order.

   THE LAW (§6.2): "Never fade a headline. Headlines wipe. Body copy rises.
   Artwork draws. There is no third reveal type."

   An IntersectionObserver (threshold .18, rootMargin "0px 0px -8% 0px") adds
   `.is-in` to the SECTION; every primitive below keys off that ancestor.

   ── THE `[data-motion]` ARMING FLAG ───────────────────────────────────────
   Every primitive's HIDDEN state is gated on `[data-motion]` on <html>. The
   JS layer sets it as its first act, in a blocking inline <script> in <head>:

       <script>document.documentElement.dataset.motion=''</script>

   Without the flag every element renders in its FINAL state. Three reasons
   this is not optional:
     · If the observer never runs — a JS error, a bot, view-source — the page
       would otherwise be permanently blank below the fold.
     · §4.6 is measured "on a full-page static export". An export of a page
       whose content is at opacity 0 cannot be measured at all.
     · No flash of hidden content before the script parses.
   ========================================================================= */

/* 2 · display lines — clip-path wipe. Never opacity. */
.rv { display: block; }
.rv > * { display: block; }
[data-motion] .rv > * {
  clip-path: inset(-.28em 101% -.32em -.06em);
  transition: clip-path var(--t-wipe) var(--e-wipe);
  transition-delay: var(--d, 0ms);
}
.is-in .rv > * { clip-path: inset(-.28em -.06em -.32em -.06em); }

/* Consecutive display lines stagger +90ms, capped at four (§6.2). */
.rv-lines > .rv:nth-child(2) > * { --d: var(--d-line); }
.rv-lines > .rv:nth-child(3) > * { --d: calc(var(--d-line) * 2); }
.rv-lines > .rv:nth-child(n+4) > * { --d: calc(var(--d-line) * 3); }

/* 1/3 · eyebrows, ledes, body — rise. */
[data-motion] .rise {
  opacity: 0;
  transform: translate3d(0, var(--rise-y), 0);
  transition:
    opacity   var(--t-xl) var(--e-out),
    transform var(--t-xl) var(--e-out);
  transition-delay: var(--d, 0ms);
}
.is-in .rise { opacity: 1; transform: none; }

/* Named delay steps so a section never invents a duration. */
.rise--body { --d: var(--d-body); }
.rise--rule { --d: var(--d-rule); }
.rise--art  { --d: var(--d-art); }

/* 4 · rules and dividers — scaleX from the left. */
.rule-draw { transform-origin: 0 50%; }
[data-motion] .rule-draw {
  transform: scaleX(0);
  transition: transform var(--t-xl) var(--e-wipe);
  transition-delay: var(--d, var(--d-rule));
}
.is-in .rule-draw { transform: none; }

/* 5 · artwork — self-drawing stroke. Each stroked element carries its own
   `--len` (its measured path length), set once by the JS layer. */
[data-motion] .draw :is(path, circle, polygon, line, rect, ellipse, polyline) {
  stroke-dasharray: var(--len, 1);
  stroke-dashoffset: var(--len, 1);
  transition: stroke-dashoffset var(--t-draw) var(--e-out);
  transition-delay: var(--d-art);
}
.is-in.draw :is(path, circle, polygon, line, rect, ellipse, polyline),
.is-in .draw :is(path, circle, polygon, line, rect, ellipse, polyline) {
  stroke-dashoffset: 0;
}

/* 6 · rows and list items — rise, +60ms each, capped at six (360ms). */
.rise-stack > *          { --d: 0ms; }
.rise-stack > *:nth-child(2) { --d: var(--d-row); }
.rise-stack > *:nth-child(3) { --d: calc(var(--d-row) * 2); }
.rise-stack > *:nth-child(4) { --d: calc(var(--d-row) * 3); }
.rise-stack > *:nth-child(5) { --d: calc(var(--d-row) * 4); }
.rise-stack > *:nth-child(6) { --d: calc(var(--d-row) * 5); }
.rise-stack > *:nth-child(n+7) { --d: calc(var(--d-row) * 6); }


/* ============================================================================
   §8.1  .brandmark — THE WORDMARK
   Built to be swapped for <img src="assets/img/logo.svg"> later WITHOUT a
   layout change: the <a> owns the box, the two lines own their own metrics,
   and nothing outside reads its internals. Never restyled inside a section,
   never centred (§8.1).

   MARKUP
     <a class="brandmark" href="index.html" aria-label="Savoy Drugstore — home">
       <span class="brandmark__word">Savoy</span>
       <span class="brandmark__rule" aria-hidden="true"></span>
       <span class="brandmark__sub">Drugstore · Dubai</span>
     </a>
   Swap path: replace the three spans with <img class="brandmark__img">; the
   <a>'s baseline box is unchanged.

   NOTE ON TRACKING: .28em / .32em are outside the §3.5 micro tiers by decree
   of §8.1. §3.5 governs micro-LABELS; this is a wordmark.
   ========================================================================= */

.brandmark {
  display: inline-flex;
  align-items: baseline;
  gap: var(--bm-rule-gap);
  line-height: 1;
  color: var(--tx-display);
  text-align: left;
}

.brandmark__word {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: var(--bm-word);
  line-height: 1;
  letter-spacing: var(--bm-tr-word);
  /* Tracked type adds a trailing sidebearing; the matching indent re-centres
     the optical block so the "S" sits on the shell's left edge. */
  text-indent: var(--bm-tr-word);
  color: currentColor;
  text-transform: uppercase;
  transition: font-size var(--t-l) var(--e-out);
}

/* 1px rule, 14px tall, champagne, 12px either side. */
.brandmark__rule {
  align-self: center;
  flex: none;
  width: var(--rule-w);
  height: var(--bm-rule-h);
  background: var(--ac-metal);
}

.brandmark__sub {
  font-family: var(--f-micro);
  font-weight: 700;
  font-size: var(--bm-sub);
  line-height: 1;
  letter-spacing: var(--bm-tr-sub);
  text-transform: uppercase;
  color: var(--ac-metal-text);
  white-space: nowrap;
}

/* On NIGHT: --on-night / --champagne-lit, exactly as §8.1 decrees. Both text
   tiers arrive through the §2.7 aliases base.css re-points; the rule is painted
   with --ac-metal (raw champagne), which has to lift to the night gold. */
[data-ground="night"] .brandmark__rule,
.ground--night .brandmark__rule,
.on-night .brandmark__rule { background: var(--champagne-lit); }

/* ON ROYAL THE RULE STAYS METAL. §8.1 gives the wordmark two materials — ink
   (or --on-night) and champagne — and the brandmark is the one component on the
   site that appears on every ground, so it is the one component that may not
   change its accent STRUCTURE between them. Dropping the rule to a neutral
   inverse hairline on royal made the footer's wordmark a monochrome object
   while the nav's was ink-plus-metal: two identities for one mark, on the most
   repeated element of the build.
   The measurement supports keeping it. --champagne-lit is 4.49:1 against flat
   --royal and 3.42:1 against the lit corner of the royal field's gradient, and
   this is a 1px DECORATIVE rule, held to §10.42's 3:1 graphical floor, not to
   the 4.5:1 text floor. The SUBLINE beside it is 9.5px text and does step to
   --on-night through the §2.7 alias (champagne text would be 3.4:1 there), so
   the wordmark keeps its metal exactly where the metal is legal. */
[data-ground="royal"] .brandmark__rule,
.ground--royal .brandmark__rule { background: var(--champagne-lit); }

/* The <img> swap target: same optical height, same baseline, zero reflow. */
.brandmark__img { height: var(--bm-word); width: auto; }

/* §8.1 — below 520px the subline is hidden and the rule with it. */
/* WCAG 2.2 2.5.8 — the wordmark is a link (it goes home), and a 19px-tall link
   is under the 24px floor. On a touch breakpoint it takes 8px of its own above
   and below; `align-items: baseline` means the glyphs do not move. */
@media (max-width: 767px) {
  .brandmark { padding-block: var(--s2); }
}

@media (max-width: 519px) {
  .brandmark__rule,
  .brandmark__sub { display: none; }
}


/* ============================================================================
   §8.2  NAV
   Fixed, transparent at rest, E4 frost when stuck, gold hairline wiping in
   from the left, a royal scroll-progress hairline on the bottom edge, and a
   full-width dark-frost drawer below 1080px.

   JS CONTRACT (owned by the JS layer, not this file):
     · scrollY > 20            → toggle `.is-stuck` on .nav
     · document scroll ratio   → set `--scroll-p: 0…1` on .nav
     · drawer toggle           → flip .nav__toggle[aria-expanded] and
                                 toggle `.is-open` on .nav
   ========================================================================= */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: var(--nav-z);

  /* Transparent at rest — no background declaration at all. */
  transition:
    background-color var(--t-l) var(--e-out),
    backdrop-filter  var(--t-l) var(--e-out);
}

/* E4 frost on scroll. */
.nav.is-stuck {
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  background: var(--frost-light);
}

/* On a dark hero (/assessment) the nav frosts to the dark variant. */
.nav--inverse.is-stuck { background: var(--frost-dark); }

/* DOCUMENTATION HOOK — site/kit.html only, exactly like `.is-hover` in §8.3.
   The specimen sheet has to print the stuck state next to the rest state, and
   two viewport-fixed navs cannot coexist. This drops the specimen into normal
   flow and changes nothing else, so the frost, the collapsed padding, the gold
   rule and the shrunken wordmark are the real ones. A page that ships this
   class has an unpinned nav; never do it. */
.nav--specimen { position: static; }

/* The 1px --hair-gold bottom rule scales in from the left, --t-xl / --e-wipe. */
.nav::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: var(--rule-w);
  background: var(--hair-gold);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--t-xl) var(--e-wipe);
}
.nav.is-stuck::after { transform: none; }

.nav__in {
  display: flex;
  align-items: center;
  gap: var(--s5);
  /* --nav-h / --nav-h-stuck are the reserved heights every scroll-margin-top
     is computed from (base.css §1); they are a floor here, not a cap. */
  min-height: var(--nav-h);
  padding-block: var(--nav-pad);
  transition:
    padding-block var(--t-l) var(--e-out),
    min-height    var(--t-l) var(--e-out);
}
.nav.is-stuck .nav__in {
  min-height: var(--nav-h-stuck);
  padding-block: var(--nav-pad-stuck);
}

/* §8.1 — the wordmark shrinks on the stuck nav, --t-l / --e-out. */
.nav.is-stuck .brandmark__word { font-size: var(--bm-word-stuck); }

.nav__links {
  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  margin-inline-start: auto;
}

/* §8.2 — --t-m2 at weight 400 (not the 700 of the §3.2 micro role), --ink-soft. */
.nav__link {
  position: relative;
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--tx-body);
  padding-block: var(--s1);
  transition: color var(--t-m) var(--e-ui);
}

/* §6.4 — underline wipes in from the left; the origin flips to the right on
   exit so the line retreats the way it came. */
.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: var(--tr-m2);          /* discount the tracking sidebearing */
  bottom: 0;
  height: var(--rule-w);
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform var(--t-m) var(--e-wipe);
}
.nav__link:hover,
.nav__link:focus-visible { color: var(--tx-display); }
.nav__link:hover::after,
.nav__link:focus-visible::after { transform: none; transform-origin: 0 50%; }

/* Active route: --royal, plus a STATIC 1px royal underline. */
.nav__link.is-active,
.nav__link[aria-current="page"] { color: var(--ac-identity); }
.nav__link.is-active::after,
.nav__link[aria-current="page"]::after {
  transform: none;
  background: var(--ac-identity);
}

/* The CTA is the primary button at compact padding (§8.2). */
.nav__cta { flex: none; }

/* ── scroll-progress hairline ─────────────────────────────────────────────
   1px --royal on the nav's bottom edge, scaleX by document progress. */
.nav__progress {
  position: absolute;
  inset: auto 0 0;
  height: var(--rule-w);
  pointer-events: none;
}
/* The JS layer drives this with the `scale` PROPERTY (motion.js writes
   `el.style.scale` for [data-progress], and uses a native ScrollTimeline where
   one exists). A `transform: scaleX()` here would multiply with that and pin
   the hairline at zero for ever, so the resting state is declared on the same
   property the scrub owns. --scroll-p stays supported for a page that would
   rather set the custom property itself. */
.nav__progress > * {
  display: block;
  height: 100%;
  background: var(--ac-identity);
  scale: var(--scroll-p, 0) 1;
  transform-origin: 0 50%;
}

/* ── the MENU toggle ──────────────────────────────────────────────────────
   Text label + a two-line glyph that rotates to an X over --t-l. */
.nav__toggle {
  display: none;                 /* enabled below 1080px */
  align-items: center;
  gap: var(--s3);
  margin-inline-start: auto;
  /* THE PHONE'S PRIMARY CONTROL IS A TOUCH TARGET BEFORE IT IS A LABEL.
     `--s1` gave an 11px/1.2 label a 21.2px box: under WCAG 2.2 2.5.8's 24px
     floor with no applicable exception, and half the 44px touch guideline, on
     the one control that reaches every other page. `--s4` takes the same glyph
     to 45.2px without moving it — the label is centred in its own box. */
  padding-block: var(--s4);
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--tx-display);
  background: none;
}

.nav__glyph {
  display: block;
  position: relative;
  flex: none;
  width: var(--s5);     /* 24 × 12 — a 2:1 box, so the X reads as an X */
  height: var(--s3);
}
.nav__glyph::before,
.nav__glyph::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: var(--rule-w);
  background: currentColor;
  transition: transform var(--t-l) var(--e-out);
}
.nav__glyph::before { top: 0; }
.nav__glyph::after  { bottom: 0; }
/* Both rules travel to the box's vertical centre and cross there. 26° over a
   24px rule spans 10.5px — the glyph's own height — so the X reads as drawn,
   not as a flattened bowtie. */
.nav.is-open .nav__glyph::before { transform: translateY(calc((var(--s3) - var(--rule-w)) / 2)) rotate(26deg); }
.nav.is-open .nav__glyph::after  { transform: translateY(calc((var(--s3) - var(--rule-w)) / -2)) rotate(-26deg); }

/* ── the drawer ───────────────────────────────────────────────────────────
   E4 dark frost, full width, Pagella 24px entries with a champagne index.
   `visibility` (not display) keeps the entries out of the tab order while
   collapsed without killing the wipe. */
.nav__panel {
  /* The drawer is UNCONDITIONALLY dark-frost (§8.2), whatever ground the page
     is on, so it re-points the §2.7 aliases for itself rather than depending
     on a `.on-night` the markup might forget. This is the one closed component
     on the site that owns its own inversion. */
  --tx-display:    var(--on-night);
  --tx-body:       var(--on-night-70);
  --tx-micro:      var(--on-night-52);
  --tx-inverse:    var(--ink);
  --ln-hair:       var(--rule-night);
  --ln-soft:       var(--rule-night-soft);
  --ln-structural: var(--on-night);
  --ac-identity:   var(--beam);
  --ac-metal:      var(--champagne-lit);
  --ac-metal-text: var(--champagne-lit);
  --tag-sep:       var(--champagne-lit);

  position: absolute;
  inset: 100% 0 auto;
  max-height: calc(100dvh - var(--nav-h));
  overflow-y: auto;
  overscroll-behavior: contain;
  visibility: hidden;
  clip-path: inset(0 0 100% 0);
  border-top: var(--rule-w) solid var(--rule-night);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  background: var(--frost-dark);
  transition:
    clip-path  var(--t-l) var(--e-wipe),
    visibility 0s linear var(--t-l);
}
.nav.is-open .nav__panel {
  visibility: visible;
  clip-path: inset(0);
  transition:
    clip-path  var(--t-l) var(--e-wipe),
    visibility 0s;
}

.nav__panel-in {
  display: grid;
  justify-items: start;            /* keeps the drawer CTA content-width */
  row-gap: var(--s5);
  padding-block: var(--s5) var(--s7);
}

.nav__panel-list { width: 100%; }

.nav__panel-list > li + li { border-top: var(--rule-w) solid var(--rule-night-soft); }

.nav__panel-link {
  display: flex;
  align-items: baseline;
  gap: var(--s4);
  padding-block: var(--s3);
  font-family: var(--f-display);
  font-size: var(--nav-item);
  line-height: 1.3;
  letter-spacing: var(--tr-h4);
  color: var(--tx-display);
  transition: color var(--t-m) var(--e-ui);
}
.nav__panel-link:hover,
.nav__panel-link:focus-visible { color: var(--ac-identity); }

.nav__panel-idx {
  flex: none;
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  color: var(--ac-metal-text);
}

/* §8.2 — below 1080px the links collapse to the MENU toggle. */
@media (max-width: 1079px) {
  .nav__links,
  .nav__in > .nav__cta { display: none; }
  .nav__toggle { display: inline-flex; }
  /* The 45.2px toggle is now the tallest thing in the stuck bar, and
     --nav-h-stuck (64px) is the number every scroll-margin-top and the Gantt's
     sticky axis are computed from — so the bar's stuck padding steps down to
     keep the reserved height exact: 45.2 + 2 × 8 = 61.2, inside 64. */
  .nav.is-stuck .nav__in { padding-block: var(--s2); }
  /* The drawer's CTA is a touch target too (§8.3's compact padding is a
     desktop bar's). */
  .nav__panel .btn--compact { padding-block: var(--s3); }
}

/* Above 1080px the drawer can never be open, whatever state JS left behind. */
@media (min-width: 1080px) {
  .nav__panel { display: none; }
}


/* ============================================================================
   §8.3  BUTTONS
   Four variants. No others. Radius 0, --t-m2 HerosC 700, padding
   `16px 26px 15px`, gap 14px, a 26×8 arrow drawn as a <path>.

   MARKUP
     <a class="btn" href="…">
       <span class="btn__label">Explore Our Services</span>
       <svg class="btn__arrow" viewBox="0 0 26 8" aria-hidden="true" focusable="false">
         <path d="M0 4H24.5M20.75 .75 24.75 4 20.75 7.25"/>
       </svg>
     </a>

   BANNED (§8.3): gradient fills, glows, bevels, icon-only, and any button on
   a light ground filled champagne.

   ── DOCUMENTATION STATE HOOKS: `.is-hover` / `.is-focus` ──────────────────
   `site/kit.html` is the specimen sheet ten page builders build against, and a
   static sheet has to be able to PRINT an interaction state. Rather than
   restate any value there (the kit carries no CSS of its own), `.is-hover` is
   folded into the real `:hover, :focus-visible` selector lists below and
   `.is-focus` into base.css's one focus-ring rule. No declaration is
   duplicated and no new visual language is introduced — the hooks resolve to
   exactly the states the pseudo-classes produce.
   THEY ARE FOR THE KIT ONLY. A hook in shipped page markup is a defect: it
   paints a state the user cannot leave.
   ========================================================================= */

.btn {
  /* Variant surface — every variant only re-points these four. The values are
     the RAW §8.3 tokens, not the §2.7 aliases: a button carries its own
     contrast contract and must not silently re-colour when it lands on a dark
     ground. That is what .btn--inverse / .btn--ghost-inverse are for. */
  --btn-bg:     var(--royal);
  --btn-fg:     var(--paper-warm);
  --btn-panel:  var(--ink);
  --btn-fg-on:  var(--btn-fg);

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--btn-gap);
  /* §8.3 — content-width, never full-bleed. */
  width: auto;
  max-width: 100%;
  padding: var(--btn-pad);
  background: var(--btn-bg);
  color: var(--btn-fg);

  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  /* THE ONE PLACE THE MICRO RAMP'S WEIGHT IS OVERRIDDEN, and it is named.
     tokens.css sets --fw-m2 to 400 so weight separates the eyebrow tier from
     the key tier; §3.2 and §8.3 both fix the CONTROL label at HerosC 700, and
     a control label is an affordance rather than a caption — it has to hold
     its own against a filled panel at 11px. --fw-control is the published
     value, so the deviation is a token with a reason, not a magic number in a
     button rule, and no other component can reach for it by accident. */
  font-weight: var(--fw-control);
  font-style: normal;
  text-transform: uppercase;
  text-align: left;

  cursor: pointer;
  overflow: hidden;             /* clips the wipe panel, not the focus ring */
  isolation: isolate;
  -webkit-appearance: none;
          appearance: none;

  /* §6.4 — magnetic hover writes --btn-mx/--btn-my; :active writes --btn-press.
     One transform, so the two never fight. */
  transform: translate3d(var(--btn-mx, 0px), calc(var(--btn-my, 0px) + var(--btn-press, 0px)), 0);
  transition:
    transform  var(--t-xs) var(--e-ui),
    color      var(--t-l)  var(--e-wipe),
    box-shadow var(--t-s)  var(--e-ui);
}

/* The hover panel wipes UP from translateY(101%), --t-l / --e-wipe. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--btn-panel);
  transform: translateY(101%);
  transition: transform var(--t-l) var(--e-wipe);
}
.btn:hover::before,
.btn:focus-visible::before,
.btn.is-hover::before { transform: none; }

.btn__label,
.btn__arrow { position: relative; z-index: 1; }

.btn__label {
  /* Strip the tracking sidebearing so the gap to the arrow is optical. */
  margin-right: calc(var(--tr-m2) * -1);
  transform: translate3d(var(--btn-lx, 0px), 0, 0);
  transition: transform var(--t-xs) var(--e-ui);
}

.btn__arrow {
  flex: none;
  width: var(--btn-arrow-w);
  height: var(--btn-arrow-h);
  overflow: visible;
}
.btn__arrow path {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--sw-stem);
  vector-effect: non-scaling-stroke;
  transition: transform var(--t-l) var(--e-wipe);
}
.btn:hover .btn__arrow path,
.btn:focus-visible .btn__arrow path,
.btn.is-hover .btn__arrow path { transform: translateX(var(--btn-arrow-x)); }

/* States. `.is-press` joins `.is-hover` / `.is-focus` as a documentation hook:
   §8.3 declares FIVE states and a static specimen sheet has to be able to print
   all five, or four of them "do not exist" by the sheet's own rule. The hook is
   folded into the real `:active` selector list, so no value is duplicated. */
.btn:hover,
.btn:focus-visible,
.btn.is-hover { color: var(--btn-fg-on); }
.btn:active,
.btn.is-press { --btn-press: var(--btn-press-y); }
/* Press is a press OF a hovered control: the panel is already up. */
.btn.is-press::before { transform: none; }
.btn.is-press { color: var(--btn-fg-on); }
.btn.is-press .btn__arrow path { transform: translateX(var(--btn-arrow-x)); }

/* §6.4 focus-visible is the global ring from base.css §1:
   `outline: 2px solid var(--ac-identity); outline-offset: 4px`.
   It is not clipped by this element's own `overflow: hidden`. */

/* ── variants ───────────────────────────────────────────────────────────────
   §8.3'S TABLE, RESTORED AND AUTHORITATIVE. Four variants, four hover panels:

     .btn            --royal bg  / --paper-warm fg   → --ink panel
     .btn--ghost     transparent / --ink fg          → --royal panel, fg --paper-warm
     .btn--inverse   --paper-warm bg / --ink fg      → --champagne panel, fg --ink
     .btn--ghost-inv transparent / --on-night fg     → --paper-warm panel, fg --ink

   These four were briefly collapsed onto one --ink panel on the argument that
   "hover should mean one thing". It cost more than it bought and the specimen
   sheet proved it: four controls resolved to one #15171C field, so the state
   carried no information about WHICH control had been pointed at, and on the
   night band .btn--inverse hovered to near-black on #0E1730 — a 1.15:1
   luminance difference, i.e. the control disappeared at the moment it was
   pointed at. §8.3 is LOCKED; a components.css that contradicts it makes the
   whole foundation untrustworthy for the sections built on top of it.

   Each panel is legible against its own rest state and its own ground:
     ghost           --paper-warm on --royal   9.06:1
     inverse         --ink on --champagne      6.38:1   (and 4.65:1 against the
                                                         night band behind it)
     ghost-inverse   --ink on --paper-warm    17.1:1

   §2.5.3's "no champagne fill larger than 96×96" governs DECORATIVE gold mass;
   the inverse button's panel is a transient interaction surface on a dark
   ground, named by §8.3 itself, and it is the one place the metal is allowed
   to be a field rather than a line. */

/* Ghost (secondary): hairline edge drawn with the only legal border-shadow.
   The edge is --ln-control, NOT --ln-hair. This is the site's secondary CTA and
   its border is its ONLY boundary; at --rule's .16 alpha it measured 1.40:1
   against paper, where §10.42 (WCAG 2.2 SC 1.4.11) requires 3:1 for a UI
   boundary. The decorative hairline and the interactive boundary are two
   different jobs and now two different tokens (tokens.css §2.6). */
.btn--ghost {
  --btn-panel: var(--royal);
  --btn-fg:    var(--ink);
  --btn-fg-on: var(--paper-warm);
  background: none;
  box-shadow: inset 0 0 0 1px var(--ln-control);
}

/* On royal / night grounds only.
   THE CHAMPAGNE PANEL IS A BOUNDED MARK, NOT A FIELD (§2.5.3).
   §8.3 gives this variant a champagne hover panel and §2.5.3 bans "champagne
   fills larger than 96×96px". Wiped across the whole control the panel measured
   208 × 42 — over the cap on its long axis, the loudest gold on the page, and
   on a specimen sheet it prints as a standing gold slab in two of five state
   tiles rather than as a transient. It is also the one element on the sheet a
   reader would recognise from a thousand other sites: a solid gold button.
   Both decrees hold if the panel keeps its colour and its wipe and gives up its
   width. It now wipes up behind the ARROW'S OWN ZONE — 66px, ending exactly
   where the label ends — so the metal is a struck block against the paper slab,
   the label never sits half on gold, and the control still says which control
   was pointed at. Champagne stays a line and a mark; it is never a field. */
.btn--inverse {
  --btn-bg:    var(--paper-warm);
  --btn-panel: var(--champagne);
  --btn-fg:    var(--ink);
  --btn-fg-on: var(--ink);
}
.btn--inverse::before {
  left: auto;
  width: min(var(--btn-panel-metal), 100%);
}

.btn--ghost-inverse {
  --btn-panel: var(--paper-warm);
  --btn-fg:    var(--on-night);
  --btn-fg-on: var(--ink);
  background: none;
  box-shadow: inset 0 0 0 1px var(--ln-control);
}

/* §8.2 — the compact nav CTA. */
.btn--compact { padding: var(--nav-cta-pad); }

/* ── §3.6 · THE BIG-FIGURE LOCKUP ───────────────────────────────────────────
   "numeral in --t-fig Pagella --royal, unit word immediately after in Pagella
    italic --ink at 0.42 × the numeral size, baseline-aligned,
    margin-left: .12em."  Reused on /, /process, /assessment and /404, so it is
   a component, not a section's private composition. base.css owns the numeral
   as the `.t-fig` type role; this adds the unit word and the lockup box.

   MARKUP  <p class="lockup">18–35<em>weeks</em></p>
   (`.lockup__unit` is accepted on any element for markup that cannot use <em>.) */
.lockup {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: var(--t-fig);
  line-height: var(--lh-fig);
  letter-spacing: var(--tr-fig);
  color: var(--ac-identity);
  font-variant-numeric: tabular-nums lining-nums;
  text-wrap: nowrap;
}
.lockup em,
.lockup__unit {
  font-size: var(--t-fig-unit);          /* .42em of the numeral */
  font-style: italic;
  font-weight: 400;
  letter-spacing: var(--tr-body);
  color: var(--tx-display);
  margin-left: var(--t-fig-unit-gap);    /* .12em, baseline-aligned */
}

/* ── disabled ───────────────────────────────────────────────────────────────
   A disabled control keeps its variant's SILHOUETTE. Only the ink drops out and
   the hover panel is removed. Filling a disabled ghost with --paper-edge gave
   it a solid box it has in no other state, and made a disabled ghost and a
   disabled primary the same object. */
.btn:disabled,
.btn[aria-disabled="true"] {
  --btn-bg:    var(--paper-edge);
  --btn-fg:    var(--ink-ghost);
  --btn-fg-on: var(--ink-ghost);
  background: var(--paper-edge);   /* out-specifies .btn--ghost's `none` */
  box-shadow: none;
  cursor: not-allowed;
  pointer-events: auto;          /* keep the cursor + the tooltip readable */
  --btn-mx: 0px; --btn-my: 0px; --btn-press: 0px;
}

/* The ghost stays a hairline outline; the inverse pair stay their own ground's
   quiet forms. §10.42 exempts a disabled control from the contrast floor. */
.btn--ghost:disabled,
.btn--ghost[aria-disabled="true"] {
  background: none;
  box-shadow: inset 0 0 0 1px var(--rule);
}
.btn--ghost-inverse:disabled,
.btn--ghost-inverse[aria-disabled="true"] {
  background: none;
  box-shadow: inset 0 0 0 1px var(--rule-night-soft);
  --btn-fg: var(--on-night-52);
  --btn-fg-on: var(--on-night-52);
}
.btn--inverse:disabled,
.btn--inverse[aria-disabled="true"] {
  background: var(--rule-night-soft);
  --btn-fg: var(--on-night-52);
  --btn-fg-on: var(--on-night-52);
}
.btn:disabled::before,
.btn[aria-disabled="true"]::before { display: none; }
.btn:disabled .btn__arrow path,
.btn[aria-disabled="true"] .btn__arrow path { transform: none; }

/* ── button sets ────────────────────────────────────────────────────────────
   §8.3: below 560px two stacked buttons align LEFT and match each other's
   width to the wider label. `grid-template-columns: minmax(0, max-content)`
   sizes the single track to the widest label and stretches both onto it —
   never full-bleed, never a label pinned left with the arrow pinned right. */
.btn-set {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s4) var(--s5);
}
@media (max-width: 559px) {
  .btn-set {
    display: grid;
    grid-template-columns: minmax(0, max-content);
    justify-content: start;
    justify-items: stretch;
  }
}

/* ── §8.3 · THE STATE TABLE ─────────────────────────────────────────────────
   A specimen of the five states is a TABLE, and a table's columns do not move
   between its rows. Wrapped by `.btn-set`'s flex the same five states broke
   4+1, 5-across, 2+2+1 and 3+2 across the four variants, purely on label
   length, so no column carried a state down the sheet and a reader could not
   compare rest to rest or hover to hover. Worse, in the inverse block the press
   tile landed directly under the hover tile, where two different states read as
   one swatch repeated.
   Five equal tracks lock the order — rest · hover · press · focus · disabled —
   for every variant at every width the tracks fit; the buttons keep their own
   content width inside them (§8.3 bans a stretched button), and because all
   five tiles of one variant carry the same label they are the same width, so
   the columns read as columns. Below 1080 the tracks step down together, which
   keeps the four tables agreeing with each other even where they cannot hold
   five across. */
.btn-set--states {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  justify-items: start;
  align-items: start;
  gap: var(--s5);
}
@media (max-width: 1079px) {
  .btn-set--states { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  .btn-set--states { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 559px) {
  /* One column, and the §8.3 mobile rule again: content width, matched, left. */
  .btn-set--states {
    grid-template-columns: minmax(0, max-content);
    justify-items: stretch;
  }
}

/* §6.4 — magnetic hover is permitted on the PRIMARY button only, and is
   disabled on coarse pointers and below 900px. The JS layer must feature-
   detect and never attach the listener there (§10.40); these resets are the
   belt to that braces. */
@media (max-width: 899px), (pointer: coarse) {
  .btn { --btn-mx: 0px; --btn-my: 0px; --btn-lx: 0px; }
}


/* ============================================================================
   THE SCROLL INDICATOR  (§6.5, §7.1)
   ---------------------------------------------------------------------------
   §6.5 refers to "the scroll-indicator sweep" as an existing mechanism and
   motion.js implements it on `[data-sweep]`, but no indicator was ever drawn:
   the hero carried a bare tracked word floating 20px under the button row with
   no rule, no tick and nothing to sweep. At that distance it read as a caption
   belonging to the ghost button, and a tracked micro-label with nothing
   attached is not a decision.

   Drawn from the §7.1 kit and nothing else:
     label   --t-m3, --ink-mute, HerosC — the same tier as every axis label
     stem    1px × 48px vertical --hair-gold rule
     tick    1px × 6px --hair-gold, perpendicular, closing the stem (§7.1 Tick)
   The stem is the thing that sweeps: `scaleY(0 → 1)` from the top on
   --t-xl / --e-out, latched by the same `.is-in` the section's reveal uses, so
   it lands with the hero rather than looping. motion.js's reduced-motion pass
   writes data-sweep="off" and the stem renders at full length, undrawn.

   MARKUP
     <p class="scroll-cue" data-sweep>
       <span class="scroll-cue__label">Scroll</span>
       <i class="scroll-cue__stem" aria-hidden="true"></i>
     </p>
   ========================================================================= */

.scroll-cue {
  /* §7.1's kit is drawn on a 24-unit grid; the stem is 48px = --s7, the tick
     is the kit's own 6px. */
  --cue-stem: var(--s7);
  --cue-hair: var(--hair-gold);

  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s3);
  /* 48px clear of the CTA row, so it belongs to the composition rather than to
     the button immediately above it. */
  margin-top: var(--s7);
}

.scroll-cue__label {
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--tx-micro);
}

/* The stem's box is exactly the tick's width, so the rule can be centred in it
   and the tick can close it without either being positioned by hand. */
.scroll-cue__stem {
  position: relative;
  display: block;
  width: var(--art-tick);
  height: var(--cue-stem);
}

/* The rule — this is the part that sweeps. */
.scroll-cue__stem::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: var(--rule-w);
  margin-left: calc(var(--rule-w) / -2);
  background: var(--cue-hair);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform var(--t-xl) var(--e-out) var(--d-art);
}

/* The §7.1 Tick that closes it: 1px, 6px long, perpendicular. It lands with the
   rule's arrival rather than drawing — a tick is a mark, not a line (§6.2). */
.scroll-cue__stem::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--rule-w);
  background: var(--cue-hair);
  opacity: 0;
  transition: opacity var(--t-l) var(--e-out) calc(var(--d-art) + var(--t-l));
}

.is-in .scroll-cue__stem::before,
html:not([data-js]) .scroll-cue__stem::before { transform: none; }
.is-in .scroll-cue__stem::after,
html:not([data-js]) .scroll-cue__stem::after { opacity: 1; }

/* §6.5 — "the scroll-indicator sweep is removed" under reduced motion.
   motion.js stamps the attribute rather than relying on CSS alone (§10.40). */
.scroll-cue[data-sweep="off"] .scroll-cue__stem::before { transform: none; transition: none; }
.scroll-cue[data-sweep="off"] .scroll-cue__stem::after  { opacity: 1; transition: none; }

/* BELOW 768 THE CUE IS NOT DRAWN AT ALL.
   Its entire job is to say "there is more below the fold". Stacked, the hero's
   source order puts it roughly 870px into an ~1800px section with the artwork
   and the metadata rail — two more screens of the SAME section — beneath it, so
   it fires nowhere near a fold and carries no information. A cue that is wrong
   about where the fold is, is worse than no cue. */
@media (max-width: 767px) {
  .scroll-cue { display: none; }
}

/* On a dark ground the hairline steps to the night gold, like every other
   --hair-gold mark (figures.css does the same for artwork). */
[data-ground="night"] .scroll-cue,
.ground--night .scroll-cue,
.on-night .scroll-cue { --cue-hair: var(--stroke-hair-night); }
[data-ground="royal"] .scroll-cue,
.ground--royal .scroll-cue { --cue-hair: var(--rule-inverse); }


/* ============================================================================
   §8.4  LINKS
   Inline text links are already installed by base.css §3 (`p a, li a, dd a,
   .link`): --royal, no rest underline, a 1px royal underline wiping in from
   the left over --t-m / --e-wipe, `text-underline-offset: .18em`; on dark the
   --ac-identity alias is already --beam.

   Only two things are missing there, and they are here.
   §8.4: standalone "more" links do not exist — a row IS the link (§8.5).
   The words "Learn more" appear nowhere on this site (§10.12).
   ========================================================================= */

/* A quiet link for micro contexts (footer legal bar, breadcrumbs): reads as
   body/micro colour and only becomes identity-coloured on interaction. */
.link--muted {
  color: var(--tx-micro);
  background-image: linear-gradient(var(--ac-identity), var(--ac-identity));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0 var(--rule-w);
  text-underline-offset: .18em;
  transition:
    color            var(--t-m) var(--e-ui),
    background-size  var(--t-m) var(--e-wipe);
}
.link--muted:hover,
.link--muted:focus-visible {
  color: var(--ac-identity);
  background-size: 100% var(--rule-w);
}

/* Breadcrumb rail (the A1b page heroes). One string, one measure, no rotation. */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--s3);
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  text-transform: uppercase;
  color: var(--tx-micro);
}
/* The separator reads --tag-sep like every other separator glyph in the system
   (§3.5): one decorative tone for every divider mark, and never one of the
   section's three champagne elements. */
.crumbs li + li::before {
  content: "/";
  margin-right: var(--s3);
  color: var(--tag-sep);
}
.crumbs [aria-current="page"] { color: var(--ac-identity); }


/* ============================================================================
   §8.5  THE ROW — the card replacement, and the site's primary content unit.
   THERE ARE NO CARDS ON THIS SITE. Grouped content is an indexed editorial row:

       [76px index]  [4.1fr title + tag row]  [5.2fr description]

   MARKUP (canonical — a stretched link, so the accessible name is the title
   and the whole row is the hit area):
     <ol class="rows rise-stack">
       <li class="row rise">
         <span class="row__index">01</span>
         <div class="row__main">
           <h3 class="row__title"><a class="row__link" href="…">Product Registration</a></h3>
           <ul class="tags">…</ul>
         </div>
         <p class="row__desc">…</p>
       </li>
     </ol>
   A non-navigating row uses `.row--static` and omits .row__link.

   §4.5.8 — the left margin origin is FIXED within a list. Rhythm comes from
   varying ROW HEIGHT (tag rows on some rows and not others), never the edge.
   Six rows of identical height is a rejection (§8.5).
   ========================================================================= */

.rows {
  /* E1 — one structural rule opens the list. No bottom rule on the last row;
     the section's next rule closes it. */
  border-top: var(--rule-w) solid var(--ln-structural);
}

.row {
  position: relative;
  display: grid;
  /* tokens.css --g-index: `76px / 4.1fr / 5.2fr`, §4.4 verbatim — the
     description is the WIDER of the pair (0.79:1), and --measure-row fills it. */
  grid-template-columns: var(--g-index);
  /* Column gap is the grid's; the ROW gap is the title→tag-row step (§8.5's
     14px), because the row's second grid row is the tag row. */
  column-gap: var(--grid-gap);
  row-gap: var(--row-tag-top);
  align-items: start;
  padding: var(--row-pad-y) 0;
  padding-left: 0;
  transition: padding-left var(--t-l) var(--e-wipe);
}

.row + .row { border-top: var(--rule-w) solid var(--ln-soft); }

/* §6.4 — the hover rule: 1px --royal wiping across the row's top edge. */
.row::before {
  content: "";
  position: absolute;
  inset: calc(var(--rule-w) * -1) 0 auto;
  height: var(--rule-w);
  background: var(--ac-identity);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--t-l) var(--e-wipe);
}

.row__index {
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--ac-metal-text);
  font-variant-numeric: tabular-nums lining-nums;
  /* Sits on the title's cap line rather than its em box (see --row-idx-top). */
  margin-top: var(--row-idx-top);
  transition: color var(--t-l) var(--e-ui);
}

/* A row-gap, not a margin on the tag list: base.css resets `ul { margin: 0 }`
   unlayered, and a grid gap also collapses to nothing on the rows that carry
   no tag list — which is exactly the height rhythm §8.5 asks for. */
.row__main {
  min-width: 0;
  display: grid;
  row-gap: var(--row-tag-top);      /* 14px, title → tag row */
  align-content: start;
}

/* THE TITLE HAS A MEASURE, WHICH IS WHAT MAKES THE LIST'S RHYTHM REAL.
   §8.5: "Rows may differ in height (some carry tag rows, some don't) — that is
   the intended rhythm. Six rows of identical height is a rejection." The list
   was rendering four rows at 166 / 197 / 167 / 167px, three of them identical
   to the half-pixel, and the row carrying no tag row at all was the same height
   as the two that did — because the DESCRIPTION column set the height in every
   row (three lines at 50ch, on every service in the content brief) and the tag
   row was absorbed by leftover space in the shorter column beside it.
   The fix is structural, not editorial: the title column is given the same kind
   of measure the description has, so a long service name takes two lines and
   the title+tags column can actually exceed the description. Height then
   follows content — a two-line title with a tag row stands ~57px taller than a
   one-line title with none — and the anti-uniformity mechanism the archetype
   depends on starts working on every index list on the site, not just where an
   author happened to write longer copy.
   22ch at --t-h3 is ~24 characters of Pagella: it breaks the three service
   names longer than that and leaves the rest on one line. Measured on the six
   services in the content brief, the list then runs 140 / 196 / 223 / 170px
   with no two neighbours inside 24px of each other, against 166 / 197 / 167 /
   167 before — three consecutive rows identical to the half-pixel. */
.row__title {
  color: var(--tx-display);                    /* <h3> → --t-h3 from base.css */
  max-width: 22ch;
}

.row__link {
  display: inline;
  color: currentColor;
  background-image: linear-gradient(var(--ac-identity), var(--ac-identity));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0 var(--rule-w);
  transition: background-size var(--t-l) var(--e-wipe);
}
/* The stretched hit area. The link text stays the accessible name. */
.row__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.row__desc {
  /* §8.5 — 50ch, tighter than the global paragraph measure. */
  max-width: var(--row-desc-max);
  color: var(--tx-body);
}

/* ── THE TAG ROW SPANS THE RECORD, NOT THE TITLE (§3.5, §8.5) ───────────────
   §3.5 allows five tags at ≥1080px and §9 asks this list for the /services
   feature sets. Five capability strings average ~200px at 10.5px / .15em, so
   they need ~1080px; the title track is 4.1fr of `76px / 4.1fr / 5.2fr`, which
   is 513px at 1440. Inside the title column the row could only ever show two.
   Spanned across the title AND description tracks it has 1164px — the whole
   record — and every service prints its full declared capability set on one
   line at 1440, two at 1080, and the §3.5 count caps trim it to 3 and 2 below
   that. The tag row is therefore a direct child of `.row`, under both columns:

     <li class="row">
       <span class="row__index">01</span>
       <div class="row__main"><h3 class="row__title">…</h3></div>
       <p class="row__desc">…</p>
       <ul class="tags">…</ul>
     </li>                                                                  */
.row > .tags { grid-column: 2 / -1; }

/* ── §6.4 row interaction — hover and focus-visible are IDENTICAL, plus the
   ring on focus. Never a hover-only affordance (§10.38). ─────────────────── */
.row:hover,
.row:focus-within,
.row.is-hover { padding-left: var(--row-shift); }

.row:hover::before,
.row:focus-within::before,
.row.is-hover::before { transform: none; }

.row:hover .row__index,
.row:focus-within .row__index,
.row.is-hover .row__index { color: var(--ac-identity); }

.row:hover .row__link,
.row:focus-within .row__link,
.row.is-hover .row__link { background-size: 100% var(--rule-w); }

.row--static:hover,
.row--static:focus-within { padding-left: 0; }
.row--static::before { display: none; }

/* §8.5 — below 820px the row collapses to a single column: index and title on
   one baseline row, description below. (820px is decreed by §8.5 and §8.11
   for exactly this collapse; it is a component threshold, not a new page
   breakpoint.)

   ONE MEASURE ORIGIN, WHICH IS THE WHOLE POINT OF THE COLLAPSE (§4.5.8, §10.18).
   Keeping the 76px index track for the title while the description spanned the
   full width gave every row three different left edges — index 20, title 112,
   description 20 — so the list's left margin zig-zagged down the page, which is
   the exact defect §4.5.8 was written to ban. The index track is now `auto`
   (the numeral's own width, ~22px), it shares ONE baseline row with the title,
   and everything else in the row spans both tracks and starts at 0. No child of
   `.row` carries a left offset below 820 except the title, which is on the
   index's own line.

   `display: contents` on `.row__main` is what makes that possible without a
   second markup contract: the title and the tag row become direct children of
   the row's grid at this width, so the tag row can start at the row's origin
   instead of at the title's. */
@media (max-width: 819px) {
  .row {
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: var(--s4);
    row-gap: var(--row-tag-top);
    align-items: baseline;
  }
  .row__main  { display: contents; }
  .row__index { grid-column: 1; grid-row: 1; margin-top: 0; }
  .row__title { grid-column: 2; grid-row: 1; }
  /* ONE MEASURE ORIGIN, AND IT IS THE TITLE'S.
     Spanning the tags and the description across both tracks put them at the
     row's left edge while the title stood 50px in, so every row printed two
     left origins and the component's own edge stepped — the defect §4.5.8 and
     §1 COMPOSED both name, one level down from the one the collapse was written
     to fix. Everything that is copy now hangs on the title's measure; the index
     alone hangs left, in the rail, which is what an index is for. */
  .row .tags,
  .row__desc  { grid-column: 2; }
}


/* ============================================================================
   §3.5  TAG ROWS
   Dot-separated capability strings. Maximum 5 tags at ≥1080px, 3 at
   768–1079px, 2 at <768px — the remainder are `display: none`, NOT wrapped.

   The separator must never be the last visible glyph on a line (§10.21), so
   the middot is drawn as a ::before on every item except the first, sitting
   inside the flex gap. Wrapping can then never orphan a separator.
   NEVER checkmarks (§10.7).
   ========================================================================= */

/* ── §3.5 · THE SEPARATOR MECHANISM, AS A PRIMITIVE ─────────────────────────
   "The separator must never be the last visible glyph on a line — implement
    with li:not(:last-child)::after AND a JS/CSS guard so a wrapped line cannot
    orphan a middot. (Simplest compliant implementation: display:flex;
    flex-wrap:wrap; gap:0 21px; with the middot drawn as a ::before on every
    item except the first, positioned at left:-11px.)"

   §3.5 states this for TAG ROWS, and it was built for tag rows only — so every
   other dot-separated string on the site (a row title listing three data
   attributes, a legal bar, a breadcrumb) was still typed as literal middots in
   the markup and still orphaned one at every wrap. Measured at 390 a row title
   set as `data-progress · data-parallax · data-magnetic` broke as
   "data-progress ·" / "data-parallax ·" / "data-magnetic": §10.21, three times
   in one heading. At 1440 the same string orphaned the middot at the START of
   line 2 instead.
   `.dots` is the mechanism on its own, at whatever type size it is applied to.
   `.tags` is `.dots` plus the micro type role and the §3.5 count caps.
   The separator is never in the markup; a wrap therefore cannot leave one. */
.dots {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--dot-gap, var(--tag-gap));
  /* The separator is drawn OUTSIDE its item's left edge, inside the flex gap,
     so on a wrapped line it falls outside this box and is clipped. That is the
     other half of §10.21: a ::before implementation can never orphan a
     TRAILING separator, but without this it opens every wrapped line with a
     LEADING one — which is the same defect read left to right. */
  overflow: hidden;
}
/* Each item is ATOMIC — it goes to the next line whole or not at all. Without
   this a metric string broke as "CLAMP(2.57REM, 5.4VW, 4.4REM) · LH" / ".95 ·
   TR -.030EM", orphaning a key from its own number in the one section whose
   subject is typographic discipline (§10.21). */
.dots > * { position: relative; white-space: nowrap; }
.dots > * + *::before {
  content: "\00B7";
  position: absolute;
  left: var(--dot-x, var(--tag-sep-x));
  color: var(--tag-sep);
  letter-spacing: 0;
}
/* At display sizes the 21px/-11px geometry §3.5 fixes for 10.5px type reads as
   a collision; the separator takes the next two steps up the lattice. */
.dots--display {
  --dot-gap: var(--s6);                        /* 32px */
  --dot-x:   calc(var(--s4) * -1);             /* -16px */
}

.tags {
  display: flex;
  flex-wrap: wrap;
  /* §3.5's own construction: `gap: 0 21px` with the middot drawn as a ::before
     at left:-11px. The row gap is the lattice's 8px, so a tag row that DOES
     wrap sets as two lines of a tag row rather than as two paragraphs. */
  gap: var(--s2) var(--tag-gap);
  /* `overflow: hidden` remains, and it is doing the §10.21 half of the job, not
     a clamp: the separator is drawn 11px OUTSIDE its item's left edge, so a
     LEADING middot at the start of a wrapped line falls outside this content
     box and is clipped.

     THE ONE-LINE CLAMP THAT USED TO SIT HERE IS GONE. It was `max-height: 1
     line`, and it did not enforce §3.5's count — it silently discarded it. §3.5
     allows five tags at ≥1080px and the nth-child caps below implement exactly
     that; the clamp then painted TWO of them at 1440 and three at 768, so the
     widest breakpoint showed the least content while a screen reader still
     announced all five. The answer to "five capability strings do not fit on
     one line in a 4.1fr column" is to give the tag row a wider track (see
     `.row > .tags`, which spans the title and description columns), not to
     hide three-fifths of what the section declares. */
  overflow: hidden;
}

.tags > li {
  position: relative;
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--tx-micro);
  /* A tag is atomic: it goes to the next line whole or not at all, so the
     one-line clamp can never cut a string through its middle. */
  white-space: nowrap;
}

.tags > li + li::before {
  content: "\00B7";
  position: absolute;
  left: var(--tag-sep-x);
  color: var(--tag-sep);
  letter-spacing: 0;
}

/* §9 /partnerships — two tag rows per entry, distinguished by tag colour. */
.tags--metal > li { color: var(--ac-metal-text); }

.tags > li:nth-child(n + 6) { display: none; }
@media (max-width: 1079px) { .tags > li:nth-child(n + 4) { display: none; } }
@media (max-width: 767px)  { .tags > li:nth-child(n + 3) { display: none; } }


/* ============================================================================
   §8.6  LEDGER  (`ON THE RECORD` / `FILE / STANDING`)
   A two-tier hairline record: key left, right-hung serif value, tracked
   sub-caption on the line below (the dir-a transplant).

   MARKUP
     <dl class="ledger">
       <div class="ledger__row">
         <dt>File</dt>
         <dd>No. 61109<small>Licensed UAE Establishment</small></dd>
       </div>
     </dl>
     <p class="ledger__cap">…</p>
   ========================================================================= */

.ledger { border-top: var(--rule-w) solid var(--ln-structural); }

/* THE KEY TRACK NEVER YIELDS; THE VALUE TRACK ALWAYS DOES.
   §8.6 publishes `minmax(0,1fr) auto`, and both halves of that are wrong for a
   two-column record whose left column is a short tracked capital and whose
   right column is an arbitrary string:
     · `minmax(0,1fr)` lets the KEY track shrink below its own content, and a
       tracked capital cannot wrap (`word-break: keep-all` above), so it simply
       overprints its neighbour. Measured at 390: the key track resolved to
       61px for a 92px key, and BREAKPOINTS was drawn straight through 390 —
       the "TS" inside the "39".
     · bare `auto` lets the VALUE track claim max-content and push the row past
       its column.
   Reversed and re-cut: the key track is `minmax(min-content, auto)` so it is
   never narrower than the key's own unbreakable width and never wider than it
   needs; the value takes `minmax(0,1fr)`, wraps inside whatever is left, and is
   right-hung by `justify-self` rather than by owning its own track. */
.ledger__row {
  display: grid;
  grid-template-columns: minmax(min-content, auto) minmax(0, 1fr);
  gap: var(--s4);
  align-items: baseline;
  padding: var(--ledger-pad-t) 0 var(--ledger-pad-b);
  border-bottom: var(--rule-w) solid var(--ln-soft);
}

.ledger dt {
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--ledger-tr-key);         /* §8.6 decrees .22em */
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--tx-micro);
}

.ledger dd {
  font-family: var(--f-display);
  font-size: var(--ledger-val);
  line-height: 1.2;
  letter-spacing: var(--tr-h4);
  color: var(--tx-display);
  /* Right-hung by placement, not by track width, and free to shrink: the value
     is the half of the row that gives way when the two cannot both fit. */
  justify-self: end;
  min-width: 0;
  text-align: right;
  font-variant-numeric: tabular-nums lining-nums;
}

/* §8.6 below 560px — the key takes its own line and the value gets the whole
   measure. At 350px of column a 40-character value has no honest second column
   to hang in: it either wraps to three lines beside a one-line key (a row whose
   two halves no longer read as one record) or it overprints the key. Stacked,
   the record still reads top-down — key, value, sub-caption — and the value
   keeps its right hang, so the ledger's edge is unchanged. */
@media (max-width: 559px) {
  .ledger__row {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--s1);
  }
  .ledger dt,
  .ledger dd { grid-column: 1; }
}

/* THE SUB-CAPTION IS ONE COMPONENT WITH ONE IDENTITY ON EVERY GROUND.
   §8.6 gives it --champagne-ink on light and --champagne-lit on dark, and both
   readings were shipped — which made the ledger an ink-plus-metal object on
   paper and a monochrome one on night, i.e. two identities for one component.
   Worse, on light it made champagne the DEFAULT caption colour: the colour
   section alone printed twenty-four of them, and §2.5.1 caps a section at three
   champagne elements.
   So the second tier takes the micro text colour on every ground. It is already
   distinguished from its value by family, case, size and position — four axes —
   and the metal is reserved for the three roles §2.5.2 names first: indices,
   FIG. captions, the licence seal. */

/* The second tier: tracked sub-caption, right-aligned, on the line below. */
.ledger dd small,
.ledger__sub {
  display: block;
  margin-top: var(--s1);
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--tx-micro);
  text-align: right;
  /* TRACKED CAPITALS CARRY A TRAILING SIDEBEARING EQUAL TO THE TRACKING, and on
     a hard right-hung axis that sidebearing is the axis. Measured, every
     sub-caption on both ledgers landed 1.5–2px short of its own value's right
     edge, on every row — a consistent wobble in the one edge the component
     exists to hold. Cancelling the last letter-space puts the caption's final
     glyph on the value's axis. */
  margin-right: calc(var(--tr-m3) * -1);
}

/* The value's one permitted flourish: the licence numeral in Pagella italic
   (§3.6 — `No. 61109` with 61109 royal on light, --champagne-lit on dark).
   It reads --ac-figure, not --ac-identity: the identity alias becomes --beam on
   dark, which put a BLUE numeral three rows under a GOLD voice clause on the
   same surface — two inversion rules, one ground, no rule a reader can infer. */
/* ONE IMPLEMENTATION OF THE LICENCE LOCKUP, NAMED, SO THERE CAN NEVER BE TWO.
   §3.6: "Licence number is always rendered `No. 61109` with `61109` in Pagella
   italic --royal on light, --champagne-lit on dark." The ledger honoured it and
   every other appearance did not — the footer's brand paragraph and the §6
   counter specimen both set the same numeral in plain roman ink, which is the
   same fact in three costumes on one sheet. `.lic` is the device on its own, so
   a paragraph, a ledger value or a counter all reach for the same class.
   On the royal field --ac-figure steps to --on-night with every other text tier
   (§2.5.5 — gold measures 4.49:1 there and royal 2.34:1), so on that one ground
   the numeral is told by its italic alone. That is the contrast law winning
   over the accent law, which is the correct order. */
.lic,
.ledger dd em { font-style: italic; color: var(--ac-figure); }

.ledger__cap {
  margin-top: var(--s4);
  max-width: var(--ledger-cap-max);
  font-family: var(--f-display);
  font-style: italic;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--tx-micro);
}

/* ── A2b LEDGER-WIDE / the trust strip ──────────────────────────────────────
   The same two tiers laid 4-up across the full shell instead of stacked. Same
   rules, same tiers, no new mechanism — and, now, the same ALIGNMENT.

   IT USED TO BE THE COMPONENT'S OWN CONTRADICTION. The strip overrode §8.6's
   right hang to `text-align: left` while leaving `justify-self: end` on the
   `dd` (which Chrome honours in block layout), so each cell's value+caption
   block shrink-wrapped, hung right, and then set its two lines flush LEFT
   inside itself. The result was that the CAPTION's width chose where the value
   started: reading down a column of swatch cells the name's left edges landed
   at 178 / 125 / 165 / 54 / 155 and its right edges at 231 / 163 / 209 / 152 /
   213 — neither edge an axis. In the hero rail the same construction left the
   four pairs ragged by 6.5 / 57.0 / −19.5 / 56.5px, and cell 3's value ended
   0.5px from its divider.

   ONE AXIS PER CELL, AND IT IS THE CELL'S OWN RIGHT EDGE, which is the axis
   §8.6 gives every ledger. THE DIVIDER IS DRAWN IN THE GAP, not as a cell's
   border: a border on the left cell with zero padding-right is what put type
   0.5px from a rule and made two instances of one component 175px and 150px
   wide at 390. A 48px column gap with a centred hairline gives every cell the
   same width by construction and 24px of clear paper on both sides of every
   rule, at every viewport. */
.ledger--strip {
  display: grid;
  /* FOUR EQUAL TRACKS AND NO GAP. The gap used to BE the divider lane (48px,
     rule centred in it), which produced equal cells but an axis with an
     exception in it: cells 1–3 set their value 24px clear of the rule on their
     right, and cell 4 — which has no rule on its right — set its value hard
     against the shell edge, 24px further out than the three that establish the
     rhythm. One cell in four off the axis is the axis broken.
     With the lane folded into the cells' own padding the rule is the same for
     all four: every value stands --s5 clear of its cell's right boundary,
     whether that boundary carries a hairline or is the shell's own edge. The
     cells stay equal by construction (repeat(4, 1fr) with no gap), so the three
     dividers are evenly spaced at exactly 1/4, 1/2 and 3/4 of the strip. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 0;
}
.ledger--strip .ledger__row {
  position: relative;
  display: block;
  border-bottom: 0;
  border-left: 0;
  /* The divider lane, now owned by the cells: --s5 of clear paper on the right
     of every cell and on the left of every cell that carries a rule. */
  padding: var(--s5) var(--s5) var(--s5) 0;
  /* §8.6's axis, applied to the whole cell so the key, the value and the
     sub-caption cannot disagree about where the edge is. */
  text-align: right;
}
.ledger--strip .ledger__row + .ledger__row { padding-left: var(--s5); }
/* The divider: 1px on the cell boundary itself, so it belongs to neither cell's
   content and clears the type by --s5 on both sides. */
.ledger--strip .ledger__row + .ledger__row::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--rule-w);
  background: var(--ln-soft);
}
/* THE VALUE TAKES THE WHOLE CELL. §8.6 hangs the value right with
   `justify-self: end`, which is correct inside the stacked ledger's two-column
   row — but the strip's row is a BLOCK, and Chrome honours `justify-self` on a
   block-level child there too, so every value shrink-wrapped to fit-content
   inside a cell that already had 275px of clear paper. Measured: the widest
   token in the strip, `--rule-night-soft`, was handed 136px and broke at its
   own hyphen (§10.22), and three sub-captions wrapped that had no need to.
   The axis in the strip is the CELL's `text-align: right`, not the box's width,
   so the box stretches and only the type hangs. */
.ledger--strip dd { justify-self: stretch; }
.ledger--strip dt { margin-bottom: var(--s2); }
/* A chip is a block; it hangs on the cell's axis like everything else in it. */
.ledger--strip .swatch { margin-left: auto; }

/* NO ONE-WORD WIDOW IN A SUB-CAPTION (§10.23).
   A wrapped sub-caption used to orphan its last word — measured, "…RULES, HATCH
   AND SEALS — NEVER TEXT ON / LIGHT", "…BODY COPY ON / DARK", "…THE MICRO FLOOR
   / ON DARK" — which is a rejection on its own and also grew that one cell
   taller than its three neighbours. `text-wrap: balance` splits the caption
   evenly instead, so the second line always carries several words; the values
   above it are unaffected, because every cell's `dd` starts on the strip's own
   top edge and the caption hangs below it. Declared on the caption tier
   generally, not only inside the strip: the stacked ledger has the same tier
   and the same failure mode in a narrow column. */
.ledger dd small,
.ledger__sub { text-wrap: balance; }

/* A STRIP DECLARES HOW MANY CELLS IT CAN HOLD.
   The 4-up is the strip's shape across a full shell. Dropped into a half-width
   column it is not: at 738px the four cells give each value 136px of measure,
   which is under the longest token the colour system contains
   (`--rule-night-soft`) and broke it at its own hyphen. The component publishes
   the 2-up as a modifier so a narrow placement states its own shape, rather
   than every placement hoping the viewport is wide enough.
   Below 768 every strip takes the 2-up, whatever it declared. */
.ledger--strip--2up,
.ledger--strip--2up.ledger--strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* The 2-up's lane and crossing rules. THE TWO RULES CROSS: a border-top on the
   second row's cells is cut into two segments that stop --s5 short of the
   divider on each side, so the strip printed a continuous vertical rule and a
   horizontal one with a hole punched where they meet — a ledger whose rules
   evaporate at their own junction. The separator is drawn once instead, by the
   second row's first cell, spanning both tracks, and it passes BEHIND the
   divider. Two rules, one crossing, no hole. */
.ledger--strip--2up .ledger__row:nth-child(odd)          { padding-left: 0; }
.ledger--strip--2up .ledger__row:nth-child(even)         { padding-left: var(--s5); }
.ledger--strip--2up .ledger__row:nth-child(odd)::before  { content: none; }
.ledger--strip--2up .ledger__row:nth-child(even)::before { content: ""; }
.ledger--strip--2up .ledger__row:nth-child(n + 3)        { border-top: 0; }
.ledger--strip--2up .ledger__row:nth-child(3)::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 200%;
  height: var(--rule-w);
  background: var(--ln-soft);
}

@media (max-width: 767px) {
  /* Two cells of exactly 50% each — equal by construction, where the
     border-plus-padding construction made them 175px and 150px at 390. */
  .ledger--strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ledger--strip .ledger__row:nth-child(odd)          { padding-left: 0; }
  .ledger--strip .ledger__row:nth-child(even)         { padding-left: var(--s5); }
  .ledger--strip .ledger__row:nth-child(odd)::before  { content: none; }
  .ledger--strip .ledger__row:nth-child(even)::before { content: ""; }
  .ledger--strip .ledger__row:nth-child(n + 3)        { border-top: 0; }
  .ledger--strip .ledger__row:nth-child(3)::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 200%;
    height: var(--rule-w);
    background: var(--ln-soft);
  }
}


/* ============================================================================
   §8.7  FORM FIELDS
   Label above, hairline field, royal focus wash. No browser default outline
   on a field — §8.7 replaces it with a --royal border plus an inset ring, and
   that IS the visible focus indicator §10.38 requires.

   MARKUP
     <form class="form" novalidate>
       <div class="form__grid">
         <div class="field">
           <label class="field__label" for="company">Company Name <span class="field__req">*</span></label>
           <input class="field__input" id="company" name="company" type="text" required
                  maxlength="120" placeholder="Your company name"
                  aria-describedby="company-err">
           <span class="field__error" id="company-err" hidden>Company name is required</span>
         </div>
         <div class="field field--full"> … textarea … </div>
       </div>
   `.field` must be a <div>, not a <p>: base.css caps every <p> at the 62ch
   editorial measure, which would cap the input with it.
       <p class="form__note">* Required fields</p>
       <button class="btn" type="submit"> … </button>
     </form>
   ========================================================================= */

.form__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5);
  align-items: start;
}
@media (max-width: 767px) {
  .form__grid { grid-template-columns: minmax(0, 1fr); }
}

/* THE SPECIMEN RHYTHM (site/kit.html and any future spec sheet).
   A labelled specimen block is a label bound to the thing below it, and the
   §8.7 form grid's uniform 24px row gap cannot express that: the gap ABOVE a
   label was repeatedly smaller than the gap below the preceding block's own
   caption, so a caption belonging to one item read as the subtitle of the
   next. One rule fixes it everywhere — 48px above a label, 8px below it (both
   on the 4px lattice), so every label binds downward. Forms are untouched;
   this is a modifier, and a form's fields do not carry captions. */
/* EVERY SPECIMEN CLOSES ON A RULE (E1).
   A specimen grid is a register, and a register's cells have edges. Without
   them each cell's content simply stopped wherever its longest line stopped,
   and the ragged right edges of a column of short specimens stacked into a
   vertical channel of blank paper beside them — measured 232 × 728 in §3,
   against §4.6's 240 × 200 ceiling. A 1px --ln-soft rule under each cell
   states the cell's real width, so a short specimen reads as a short specimen
   inside a stated box rather than as a hole in the page. It also gives the
   grid the hairline rhythm every other component on the sheet already has.
   The row gap drops one lattice step to pay for the rule's own padding, so the
   sheet's vertical rhythm is unchanged. */
/* THE RULES KNIT ACROSS THE GUTTER, OR THEY ARE NOT A TABLE.
   `.form__grid` is `align-items: start` — right for a form, where a short field
   must not stretch to a tall one — and wrong for a register: with each cell
   sized to its own content, the closing rule of the left cell and the closing
   rule of the right cell landed at different y in five of eight pairs, missing
   each other by 8, 14, 29 and 32px. At that distance a pair of identical
   hairlines does not read as asymmetry, it reads as a rendering error, and it
   is the most damaging small defect a "house of record" can print.
   Stretching the cells to their grid row makes both rules the row's own bottom
   edge — a 0px delta by construction, at every width, with no per-cell height
   to maintain. The content still starts at the top of its cell; only the box
   grows. */
.form__grid--specimen {
  row-gap: var(--s6);
  align-items: stretch;
}
.form__grid--specimen > .field {
  padding-bottom: var(--s4);
  border-bottom: var(--rule-w) solid var(--ln-soft);
}
/* A cell that is nothing but a section label opens the block below it; it is
   not a specimen and does not close on a rule. */
.form__grid--specimen > .field:not(:has(> :not(.field__label))) {
  padding-bottom: 0;
  border-bottom: 0;
}
.form__grid--specimen > .field > .field__label:first-child { margin-bottom: var(--s2); }

/* THE SPECIMEN ROW IS A MEASURE WITH MARGINALIA.
   A full-width specimen puts the thing on the left and hangs its label and its
   metrics in the right margin beside it — the site's own device (§4.4 A10's
   "marginal NOTE block", §9's compliance statement). It is also the §4.6 answer
   for a documentation sheet: a page of left-aligned specimens leaves the right
   column empty for hundreds of pixels at a time, and an empty right column is a
   defect however good the reason. Here the margin always carries the reading
   apparatus, so the two columns are composed against each other by
   construction rather than by luck.
   `dense` lets the specimen backfill column 1 of the row its label opened. */
.form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) {
  display: grid;
  /* 40ch, not 30. §4.6 measured a 329 × 1254 empty band running the whole
     height of the type section, between the right edge of the display
     specimens and the left edge of a 30ch margin: a specimen column sized for
     the LONGEST specimen leaves every shorter one trailing dead paper, and
     three of them are two words long. Widening the margin by 10ch moves its
     left edge into that band and gives the annotations a real measure (40ch is
     inside §3.3's 46–64ch range for a note, and well under the ceiling). The
     alternative — sizing the specimen column to its own content — would make
     the margin's left edge zig-zag row by row, which §4.5.8 bans outright. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 40ch);
  /* Two rows, and the SECOND one is the flexible one: the label takes row 1,
     its metrics take row 2 directly underneath, and the specimen spans both.
     With auto rows the excess height of a tall specimen was shared between the
     two rows, which pushed each note halfway down the block and left it sitting
     against the NEXT specimen's label — the caption-belongs-to-the-item-below
     defect, reintroduced in the margin. */
  grid-template-rows: auto 1fr;
  column-gap: var(--grid-gap);
  row-gap: var(--s3);
  align-items: start;
}
.form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) > :not(.field__label):not(.note):not(.form__note) {
  grid-row: 1 / span 2;
}
/* A block whose only content IS a label and a note has no specimen to stand
   beside, so it keeps its own column: pushing both into the margin would leave
   the measure column empty for the height of the note — trading one void for
   a worse one. */
.form__grid--specimen > .field--full > * { grid-column: 1; }
.form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) > .field__label { grid-column: 2; grid-row: 1; }
.form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) > :is(.note, .form__note) { grid-column: 2; grid-row: 2; align-self: start; }
.form__grid--specimen > .field--full > .field__label { margin: 0; }
.form__grid--specimen > .field--full > .note { margin-top: 0; }
/* In the 40ch margin the note is capped by its column, as it always was. */

@media (max-width: 899px) {
  /* Below 900 there is no margin to hang anything in: the note follows the
     specimen it annotates, in one column. */
  /* One column again: label, specimen, note, in source order.
     The selectors below carry the SAME `:has()` as the desktop rules they
     answer. `:has()` takes its argument's specificity, so a plain
     `.form__grid--specimen > .field--full` reset scores (0,2,0) against the
     desktop rule's (0,3,0) and loses — which left every full-width specimen in
     a 90px column at 390 with its label wrapped five words deep beside it. */
  .form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
  }
  /* Each placement is undone by a selector of the SAME specificity as the one
     that made it: `> *` scores one class-unit lower than `> .field__label`, so
     the label stayed in column 2 and the grid grew an implicit second column
     under a one-column template — the specimen ended up in a 24px track. */
  .form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) > .field__label,
  .form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) > :is(.note, .form__note),
  .form__grid--specimen > .field--full:has(> :not(.field__label):not(.note):not(.form__note)) > :not(.field__label):not(.note):not(.form__note) {
    grid-column: 1;
    grid-row: auto;
  }
}

/* The same binding when specimens are stacked in a plain column rather than in
   the grid: a label that FOLLOWS something takes 48px above it and keeps its
   8px below. Inside a form a label is its field's first child, so this never
   matches there. */
* + .field__label { margin-top: var(--s7); }

.field { display: block; min-width: 0; }
.field--full { grid-column: 1 / -1; }

.field__label {
  display: block;
  margin-bottom: var(--s2);
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--tx-micro);
  /* A label that wraps splits evenly rather than orphaning its last word
     (§10.23). The same rule the ledger's sub-caption takes, for the same
     reason: these are the two micro tiers that carry strings long enough to
     wrap inside a cell. */
  text-wrap: balance;
}

/* §8.7 asks for a champagne-ink asterisk. It is full ink instead, for the
   §2.5.1 reason set out at --tag-sep above: a six-field form carried six of
   them plus its error message plus its error border, so a contact section blew
   the three-element champagne cap before a single deliberate gold mark was
   placed. An asterisk beside an --ink-mute label reads as required BECAUSE it
   is the darkest thing in the label, not because it is gold. */
.field__req { color: var(--tx-display); }

.field__input {
  display: block;
  width: 100%;
  padding: var(--field-pad);
  font-family: var(--f-ui);
  font-size: var(--t-ui);
  line-height: var(--lh-ui);
  letter-spacing: var(--tr-ui);
  font-weight: 400;
  color: var(--tx-display);
  background: var(--sf-raised);
  /* §10.42 — a field's border is its only boundary and the fill alone cannot
     serve as one (--paper-warm on --paper is 1.05:1). --ln-control, not
     --ln-hair: see .btn--ghost above and tokens.css §2.6. */
  border: var(--rule-w) solid var(--ln-control);
  transition:
    border-color var(--t-s) var(--e-ui),
    box-shadow   var(--t-s) var(--e-ui);
}

.field__input::placeholder { color: var(--tx-micro); opacity: 1; }

/* §6.4 — hover lifts the border to full ink. */
.field__input:hover { border-color: var(--ln-structural); }

/* §8.7 — focus is a --royal border plus an inset tint ring. Never the browser
   default outline; the global ring from base.css is suppressed here in favour
   of the decreed treatment, which is more visible, not less. */
.field__input:focus-visible,
.field__input:focus,
.field__input.is-focus {          /* kit documentation hook — never shipped */
  outline: none;
  border-color: var(--ac-identity);
  box-shadow: inset 0 0 0 var(--field-ring) var(--royal-tint);
}

/* ── Error. Cleared on `change` by the JS layer (§8.7). ─────────────────────
   THE SYSTEM HAS NO RED AND IS NOT GETTING ONE, and §8.7's champagne error is
   the fourth and fifth champagne elements in any form (see .field__req). An
   error is therefore stated the way this house states everything else: in full
   ink, at the structural rule weight, with a 12px ink tick standing in the
   margin the field's own border defines. Ink is the strongest tone available,
   so the invalid field is the darkest-edged control on the page; the tick and
   the words carry the meaning, not a hue — which is also §10.42's requirement
   that colour never be the sole channel. */
.field__input[aria-invalid="true"] { border-color: var(--ln-structural); }

.field__error {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin-top: var(--field-err-top);
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--tx-display);
}
/* The §7.1 Tick, 12px, ink: the mark that says "this row is flagged". */
.field__error::before {
  content: "";
  flex: none;
  align-self: center;
  width: var(--s3);
  height: var(--rule-w);
  background: currentColor;
}

/* Textarea: vertical resize only, 132px floor. */
textarea.field__input {
  min-height: var(--ta-min-h);
  resize: vertical;
  font-family: var(--f-ui);
}

/* THE ONE UN-AUTHORED GLYPH ON THE SHEET WAS THE OS'S.
   §10.2: "All glyphs are authored from the §7.1 kit." Chrome paints its own
   two-stroke resize grip in the corner of every textarea, and on a page whose
   argument is that every mark was drawn by hand it was the only mark nobody
   drew. §8.7 requires `resize: vertical`, so the affordance stays and only its
   glyph is replaced: the native resizer is suppressed and the corner carries a
   12px mark struck from the kit's own 1px rules at the kit's own 45°, in the
   control-boundary tone, with pointer-events off so the native drag behaviour
   underneath is untouched. */
textarea.field__input::-webkit-resizer { display: none; }

.field:has(> textarea.field__input) { position: relative; }
.field:has(> textarea.field__input)::after {
  content: "";
  position: absolute;
  right: var(--s1);
  /* Clear of the field's own bottom border; the error slot, when one is shown,
     lives below the field and is unaffected. */
  bottom: calc(var(--s1) + var(--rule-w));
  width: var(--s3);
  height: var(--s3);
  pointer-events: none;
  /* TWO STROKES, NOT A HATCH FIELD. §7.1 reserves the 45° hatch — 2px stroke,
     4px gap, champagne — exclusively for an upper-bound quantity, so the grip
     is drawn at the kit's 1px stem weight on an 8px pitch, which puts exactly
     two strokes in the corner: the drafting mark for a draggable edge, and not
     a mark the art language has already spent. */
  background-image: repeating-linear-gradient(
    -45deg,
    var(--ln-control) 0 var(--rule-w),
    transparent var(--rule-w) var(--s2));
}
/* A textarea that also carries an error message hangs the grip on the field
   itself, not on the block below it. */
.field:has(> textarea.field__input):has(> .field__error)::after { display: none; }

/* Native <select>: the control stays native, the chrome does not. */
.field__select { position: relative; display: block; }
select.field__input {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  padding-right: var(--field-pad-sel);
}
.field__chevron {
  position: absolute;
  top: 50%;
  right: var(--s4);
  width: var(--field-chev);
  height: auto;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--tx-micro);
}
.field__chevron path {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--sw-stem);
  vector-effect: non-scaling-stroke;
}

/* THE FORM IS A REGISTER OF FIELDS, AND A REGISTER OPENS ON A RULE (E1).
   §8.7 fixes the field itself — label above, 1px boundary on four sides,
   --paper-warm fill, 0 radius — and that construction is not up for
   negotiation. What was missing is the thing that makes the BLOCK belong to
   this house rather than to a UI kit: every other grouped component on the site
   opens with one --rule-ink structural rule (§2.6, §5 E1) and the form opened
   with nothing, so five bordered rectangles floated in the section with no
   stated top edge. One rule, one 32px inset, and the form reads as the last
   register on the page. */
.form {
  border-top: var(--rule-w) solid var(--ln-structural);
  padding-top: var(--s6);
}

.form__note {
  margin-top: var(--s4);
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--tx-micro);
}

/* THE ACTIONS ROW SPANS THE FORM (§4.6). A content-width submit button hung
   at the left of a 590px column leaves the rest of its own row empty — 527 x
   323px of paper beside the page's primary control, measured on the homepage.
   §8.7's `* Required fields` helper belongs on that line, hung right, where it
   also reads as the legend for the asterisks directly above it. */
.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4) var(--s5);
  margin-top: var(--s5);
  border-top: var(--rule-w) solid var(--ln-soft);
  padding-top: var(--s5);
}

/* §8.7 — the success state replaces the form in place. Fig. V seal at 52px on
   the left, `role="status"` on the container. */
.form__success {
  display: grid;
  grid-template-columns: var(--seal-sz) minmax(0, 1fr);
  gap: var(--s3) var(--s5);
  align-items: start;
  border-top: var(--rule-w) solid var(--ln-structural);
  padding-top: var(--s5);
}
.form__success .seal { grid-row: span 2; }

/* Any heading element may host this; the bridge below stops the h1–h6 type
   role from overriding the --t-h4 §8.7 asks for. */
.form__success-h {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: var(--t-h4);
  line-height: var(--lh-h4);
  letter-spacing: var(--tr-h4);
  color: var(--tx-display);
}

@media (max-width: 389px) {
  .form__success { grid-template-columns: minmax(0, 1fr); }
  .form__success .seal { grid-row: auto; }
}

/* The §7.3 Fig. V seal, wherever it is placed. */
.seal { width: var(--seal-sz); height: var(--seal-sz); flex: none; }
.seal--lg { width: var(--s9); height: var(--s9); }     /* /compliance, 96px */


/* ============================================================================
   §8.8  ACCORDION
   A real <button aria-expanded>; one item open at a time (JS); the first item
   is open on load only on /faq.

   MARKUP
     <div class="acc">
       <div class="acc__item">
         <h3 class="acc__h">
           <button class="acc__trigger" id="q1-t" aria-expanded="false" aria-controls="q1">
             <span class="acc__index">01</span>
             <span class="acc__q">How long does UAE product registration take?</span>
             <span class="acc__mark" aria-hidden="true"></span>
           </button>
         </h3>
         <div class="acc__panel" id="q1" role="region" aria-labelledby="q1-t">
           <div class="acc__panel-in"><p>…</p></div>
         </div>
       </div>
     </div>
   The open state is driven purely by `aria-expanded` (matched with :has), so
   the JS layer only has to keep ARIA correct; `.is-open` is accepted too.
   ========================================================================= */

.acc__item {
  position: relative;
  border-top: var(--rule-w) solid var(--ln-soft);
  transition: border-top-color var(--t-m) var(--e-ui);
}
.acc__item:first-child { border-top-color: var(--ln-structural); }

/* §6.4 — the trigger rule thickens to full ink on hover/focus. (All rules on
   this site are 1px; "thickens" is a tone change, §2.6.) */
.acc__item:hover,
.acc__item:focus-within { border-top-color: var(--ln-structural); }

/* Open: a 1px --royal rule wipes across the item's top edge. */
.acc__item::before {
  content: "";
  position: absolute;
  inset: calc(var(--rule-w) * -1) 0 auto;
  height: var(--rule-w);
  background: var(--ac-identity);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--t-l) var(--e-wipe);
}
.acc__item.is-open::before,
.acc__item:has(.acc__trigger[aria-expanded="true"])::before { transform: none; }

/* The heading is a semantic wrapper only; the button owns all metrics. */
.acc__h { display: block; }

.acc__trigger {
  display: grid;
  grid-template-columns: var(--acc-rail) minmax(0, 1fr) var(--acc-mark-col);
  gap: var(--s4);
  align-items: start;
  width: 100%;
  padding: var(--s5) 0;
  background: none;
  text-align: left;
  cursor: pointer;
  font-family: var(--f-display);
  font-size: var(--t-h4);
  line-height: var(--lh-h4);
  letter-spacing: var(--tr-h4);
  font-weight: 400;
  color: var(--tx-display);
}

.acc__index {
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--ac-metal-text);
  font-variant-numeric: tabular-nums lining-nums;
  padding-top: var(--acc-idx-top);
  transition: color var(--t-m) var(--e-ui);
}
.acc__item.is-open .acc__index,
.acc__item:has(.acc__trigger[aria-expanded="true"]) .acc__index { color: var(--ac-identity); }

/* The question stays --ink whether open or closed (§8.8). */
.acc__q { color: var(--tx-display); text-wrap: balance; }

/* The marker: a 12px "+" drawn from two 1px rules. Open → the vertical one
   rotates 90° and scales to 0, leaving a minus. --t-m / --e-ui. */
.acc__mark {
  position: relative;
  display: block;
  justify-self: end;
  width: var(--acc-mark);
  height: var(--acc-mark);
  margin-top: var(--acc-idx-top);
  color: var(--tx-display);
}
.acc__mark::before,
.acc__mark::after {
  content: "";
  position: absolute;
  background: currentColor;
  transition: transform var(--t-m) var(--e-ui);
}
.acc__mark::before { left: 0; right: 0; top: 50%; height: var(--rule-w); transform: translateY(-50%); }
.acc__mark::after  { top: 0; bottom: 0; left: 50%; width: var(--rule-w); transform: translateX(-50%); }
.acc__item.is-open .acc__mark::after,
.acc__item:has(.acc__trigger[aria-expanded="true"]) .acc__mark::after {
  transform: translateX(-50%) rotate(90deg) scaleY(0);
}

/* Panel: grid-template-rows 0fr → 1fr, --t-l / --e-out, content overflow
   hidden. `visibility` keeps collapsed links out of the tab order. */
.acc__panel {
  display: grid;
  grid-template-rows: 0fr;
  visibility: hidden;
  transition:
    grid-template-rows var(--t-l) var(--e-out),
    visibility 0s linear var(--t-l);
}
.acc__item.is-open > .acc__panel,
.acc__item:has(.acc__trigger[aria-expanded="true"]) > .acc__panel {
  grid-template-rows: 1fr;
  visibility: visible;
  transition:
    grid-template-rows var(--t-l) var(--e-out),
    visibility 0s;
}

.acc__panel-in { overflow: hidden; }
.acc__panel-in > * { padding: 0 0 var(--acc-panel-pb) var(--acc-text-x); }
.acc__panel-in p {
  max-width: var(--acc-panel-max);
  color: var(--tx-body);
}

/* §2.6 / E1 — a component opens on a rule and must CLOSE on one. Without this
   the last panel simply stops and the next component's first label begins 30px
   later with no declared boundary between them. It is --rule-soft, not
   --rule-ink: the structural rule is the opener, and there is exactly one. */
.acc { border-bottom: var(--rule-w) solid var(--ln-soft); }

@media (max-width: 559px) {
  .acc { --acc-rail: var(--s6); --acc-text-x: calc(var(--s6) + var(--s3)); }
  .acc__trigger { grid-template-columns: var(--acc-rail) minmax(0, 1fr) var(--acc-mark-col); gap: var(--s3); }
}


/* ============================================================================
   §8.9  FOOTER
   The page's one royal field. Ground is applied in markup —
   <footer class="foot" data-ground="royal"> — so base.css §10 paints the
   decreed royal gradient and inverts the §2.7 aliases. A page whose final
   section is A7 on royal renders the footer on `data-ground="night"` instead,
   so two saturated fields never stack.

   COLOUR NOTE (§10.42 over §8.9's alphas). §8.9 specifies --on-night-70 for
   the licence paragraph and links and --on-night-52 for the contact keys.
   Those alphas are calibrated against --night (L* 7). Measured on the royal
   field they are 4.3:1 and 3.0:1 — under the 4.5:1 floor §10.42 makes an
   instant fail. So the footer reads its colours through the §2.7 aliases,
   which base.css already steps up one rung on royal and leaves exactly as
   decreed on night, and the contact keys take --ac-metal-text (champagne-lit,
   4.5:1 on royal, 10.5:1 on night) — the same treatment §8.6 gives a dark
   ledger's sub-captions. Hierarchy comes from family, case and size, not from
   three greys.
   ========================================================================= */

.foot {
  /* base.css gives [data-ground="royal"] the tall --sec-pad-dark block; a
     footer is not a section and sets its own. */
  padding-block: clamp(64px, 8vw, 96px) var(--s6);
}

.foot__top {
  display: grid;
  grid-template-columns: var(--foot-cols);      /* 4.4 / 2 / 2 / 3.4 */
  gap: var(--s7) var(--grid-gap);
  /* THE FOUR COLUMNS OPEN ON ONE LINE. The brandmark's word is 27px and a
     column heading is 11px, so top-aligning their BOXES put the wordmark's cap
     line 10px below the three headings' shared baseline and the footer's top
     edge did not align. Grid baseline alignment resolves the first baseline of
     every item in the row against each other, which is the alignment a printed
     masthead actually has. */
  align-items: baseline;
}

/* ── brand column ───────────────────────────────────────────────────────── */
.foot__brand .brandmark { margin-bottom: var(--s5); }

.foot__copy {
  max-width: var(--foot-copy-max);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--tx-display);
}

.foot__seal {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin-top: var(--s7);
}
.foot__seal-key {
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--ac-metal-text);
}

/* ── link columns ───────────────────────────────────────────────────────── */
.foot__head {
  margin-bottom: var(--s4);
  padding-bottom: var(--s3);
  border-bottom: var(--rule-w) solid var(--ln-hair);
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--tx-display);
}

.foot__link {
  position: relative;
  display: inline-block;
  font-family: var(--f-display);
  font-size: var(--foot-link);
  line-height: var(--foot-link-lh);
  color: var(--tx-display);
}
.foot__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: .3em;
  height: var(--rule-w);
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform var(--t-m) var(--e-wipe);
}
.foot__link:hover::after,
.foot__link:focus-visible::after { transform: none; transform-origin: 0 50%; }

/* WCAG 2.2 2.5.8 — ON A PHONE THE FOOTER IS TEN STACKED 33px TARGETS.
   §8.9's `line-height: 2.05` on a 16px Pagella link IS the hit box: 32.8px, and
   ten of them abut with no separation, which is where every mis-tap on this
   page would happen. 6px of padding-block takes the box to 44.8px and a 4px row
   gap stops adjacent boxes touching. The type, the leading and the rule are
   untouched — only the box grows. */
@media (max-width: 767px) {
  .foot__link { padding-block: var(--s2); }
  .foot__top ul { display: grid; row-gap: var(--s1); }
}

/* ── contact column ─────────────────────────────────────────────────────── */
.foot__row {
  display: grid;
  /* --col-key, which is 96px and NOT §8.9's 78: `OFFICE HOURS` at --t-m3 with
     .15em of tracking measures 92px and does not fit in 78, so it wrapped to
     two lines against a one-line value and left `HOURS` hanging with nothing
     beside it. The correction belongs in the TOKEN, not here: §4.4 gives the
     same 78px key column to /contact §2 and /assessment, so a local fix would
     have left the identical defect waiting on two more pages. */
  grid-template-columns: var(--col-key) minmax(0, 1fr);
  gap: var(--s5);                                /* key + 24px → value edge */
  align-items: baseline;
  padding-block: var(--s2);
}
.foot__row dt {
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ac-metal-text);
}
.foot__row dd {
  min-width: 0;
  font-family: var(--f-display);
  font-size: var(--foot-value);
  line-height: 1.5;
  color: var(--tx-display);
  /* An email address is one unbreakable word and this column is 3.4fr of a
     4-up grid: at 900–1079px it would otherwise punch through the shell.
     `break-word` never inserts a hyphen, so §10.22 still holds. */
  overflow-wrap: break-word;
}
.foot__row dd a {
  color: var(--tx-display);
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0 var(--rule-w);
  text-underline-offset: .18em;
  transition: background-size var(--t-m) var(--e-wipe);
}
.foot__row dd a:hover,
.foot__row dd a:focus-visible { background-size: 100% var(--rule-w); }

/* §8.9 — when the page already ends with a contact section, the footer's
   contact column reduces to Email + Office Hours only, so the same four facts
   are never stated twice in one viewport (§10.28). */
.foot--contact-reduced .foot__row--dup { display: none; }

/* ── bottom bar ─────────────────────────────────────────────────────────── */
.foot__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4) var(--s5);
  margin-top: var(--foot-bar-top);
  padding-top: var(--s5);
  border-top: var(--rule-w) solid var(--ln-hair);
}

.foot__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3) var(--s5);
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--tx-micro);
}

.foot__social { display: flex; align-items: center; gap: var(--s3); }

/* 32×32 hairline squares. Hand-drawn 15px glyphs, never an icon set (§10.2). */
.foot__social a {
  display: grid;
  place-items: center;
  width: var(--foot-social);
  height: var(--foot-social);
  color: var(--tx-display);
  /* An INTERACTIVE boundary, so it is held to §10.42's 3:1 graphical floor and
     not to the decorative hairline. --ln-hair resolves to rgba(242,241,236,.16)
     on this ground: 1.33:1, an invisible edge on a control. --rule-night-strong
     is .42 on night (3.74:1) and steps to .70 on royal (4.24:1), where base.css
     lifts every interactive hairline one rung. */
  box-shadow: inset 0 0 0 1px var(--ln-control);
  transition:
    background-color var(--t-m) var(--e-ui),
    color            var(--t-m) var(--e-ui),
    box-shadow       var(--t-m) var(--e-ui);
}
/* ONE REGISTER FOR BOTH MARKS, AND BOTH OF THEM DRAWN.
   They were the literal strings "in" and "ig", typed. "in" is at least a
   recognised LinkedIn lockup; "ig" is not a mark that exists anywhere, and on a
   page whose thesis is that every image on it was authored, the last two marks
   in the footer were the only two that were not.
   Both are now geometry on §7.1's 24-unit grid, stroked at --sw-stem with
   `vector-effect: non-scaling-stroke` like every other line in the system, and
   rendered at the 15px §8.9 decrees inside 32×32 hairline squares. The HTML
   element keeps radius 0 (§10.4); the corner radii belong to the SVG path, the
   same way circles do. */
.foot__social svg {
  width: var(--foot-glyph);
  height: var(--foot-glyph);
  overflow: visible;
}
.foot__social svg :is(path, circle, rect, line) {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--sw-stem);
  stroke-linecap: butt;
  stroke-linejoin: miter;
  vector-effect: non-scaling-stroke;
}
.foot__social svg .is-solid { fill: currentColor; stroke: none; }

/* §6.4 — the square fills on hover and the glyph inverts. On the royal field
   --ac-identity is already --beam (base.css), so the fill stays visible
   against the ground instead of disappearing into it. */
.foot__social a:hover,
.foot__social a:focus-visible {
  background: var(--ac-identity);
  color: var(--tx-inverse);
  box-shadow: inset 0 0 0 1px var(--ac-identity);
}

/* §8.9 — the footer's licence seal. On the royal field base.css re-points
   --ac-metal to --on-night (§2.5.5), so the seal draws at 7.03:1 instead of the
   2.2:1 gold-on-royal it had; the figure layer reads the same alias. Nothing is
   declared here — this note records where the fix lives. */

@media (max-width: 899px) {
  .foot__top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot__brand { grid-column: 1 / -1; }
}
@media (max-width: 767px) {
  /* WCAG 2.2 2.5.8 — a 10.5px legal link is a 13px-tall target. It stays the
     same size and gains its own box: 12px above and below, on the lattice. */
  .foot__legal .link--muted { display: inline-block; padding-block: var(--s3); }
  .foot__bar { padding-top: var(--s3); }
  /* Same rule for the footer's own contact values (email, phone). */
  .foot__row dd a { display: inline-block; padding-block: var(--s2); }
}
@media (max-width: 559px) {
  .foot__top { grid-template-columns: minmax(0, 1fr); }
  .foot__bar { justify-content: flex-start; }
}
@media (max-width: 389px) {
  .foot__row { grid-template-columns: minmax(0, 1fr); gap: var(--s1); }
}


/* ── §10.42 · champagne TEXT is not legal on the royal field ────────────────
   §2.5 calibrates --champagne-lit against --night (L* 7), where it measures
   10.5:1. The royal field is L* 33, and there it measures **4.49:1** —
   one hundredth under the 4.5 floor §10.42 makes an instant fail. Measured,
   not estimated: `tools/audit.py` reports it on the brandmark subline, the
   seal key and all four contact keys.

   So on a ROYAL ground the three components that would otherwise set champagne
   TEXT set --on-night (7.0:1). Champagne as a 1px STROKE is untouched — the
   seal's geometry only has to clear the 3:1 graphical floor, and 4.49 does.
   On a NIGHT ground nothing changes: champagne-lit stays, as decreed.

   RESOLVED UPSTREAM, so this block now declares nothing. base.css re-points
   --ac-metal-text (and --tx-micro, which measures 4.25:1 on royal) to
   --on-night for the royal ground, which covers ANY champagne or micro text a
   section puts there — an A7 DECLARATION band included — not merely the three
   components this file owns. The brandmark subline, the seal key and the
   contact keys all read their colour through that alias.
   Champagne as a 1px STROKE is untouched: 4.49:1 clears the 3:1 graphical
   floor. On a NIGHT ground nothing changes; the decreed alphas are correct. */


/* ============================================================================
   §8.10  THE WHATSAPP AFFORDANCE
   A 44×44 SQUARE — not a circle, not a pill. --royal fill, --paper-warm
   hand-drawn glyph, 1px --champagne inset border. On hover a label panel
   wipes out to the left reading `Chat with us on WhatsApp`.
   NO ping halo, NO pulse, NO scale bounce (§8.10).

   MARKUP
     <a class="wa" href="https://wa.me/971551761169" target="_blank"
        rel="noopener noreferrer" aria-label="Chat with us on WhatsApp">
       <span class="wa__label" aria-hidden="true">Chat with us on WhatsApp</span>
       <span class="wa__mark"><svg class="wa__glyph" …></svg></span>
     </a>

   THE INSET IS NOT CHAMPAGNE. §8.10 asks for a 1px champagne inset border on a
   royal fill, which is §2.5.4's forbidden adjacency — champagne and royal at
   0px separation — drawn at 2.45:1, under §10.42's 3:1 floor for the boundary
   of an interactive control. Both objections are the same objection: gold does
   not read on royal (§2.5.5). The edge is therefore the same
   rgba(242,241,236,.42) hairline every other control carries on a saturated
   ground, at 3.7:1. The square, the fill and the glyph are exactly as decreed.
   ========================================================================= */

/* §8.10 — THE HIT AREA IS THE SQUARE, AND ONLY THE SQUARE.
   Laid out in flow, the hover label reserved 189px of anchor box to the left of
   the visible 44×44 mark even at rest, so `elementFromPoint` on the hero's
   metadata rail returned this link: clicking a ledger cell opened WhatsApp. The
   anchor is now exactly the mark's own box and the label is taken out of flow
   beside it, `pointer-events:none`, so nothing outside the square is clickable
   at any state. */
.wa {
  position: fixed;
  right: var(--wa-inset);
  bottom: var(--wa-inset);
  z-index: var(--wa-z);
  display: block;
  width: var(--wa-size);
  height: var(--wa-size);
  color: var(--paper-warm);
}

.wa__label {
  position: absolute;
  top: 0;
  right: 100%;
  bottom: 0;
  display: flex;
  align-items: center;
  pointer-events: none;
  padding: var(--s3) var(--s4);
  margin-right: var(--rule-w);
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink);
  background: var(--paper-warm);
  box-shadow: inset 0 0 0 1px var(--rule-control);
  /* Wipes OUT to the left: revealed from its right edge. */
  clip-path: inset(0 0 0 100%);
  transition: clip-path var(--t-l) var(--e-wipe);
}
.wa:hover .wa__label,
.wa:focus-visible .wa__label { clip-path: inset(0); }

.wa__mark {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  background: var(--royal);
  box-shadow: inset 0 0 0 1px var(--rule-inverse);
}

.wa__glyph { width: var(--s5); height: var(--s5); fill: currentColor; }

/* No hover label on a touch device — the square is the whole affordance. */
@media (hover: none) {
  .wa__label { display: none; }
}

/* WCAG 2.2 2.5.8 — the two social marks are the last sub-44px targets on the
   sheet at phone widths. --wa-size is the site's one declared tap square
   (§8.10); they borrow it rather than inventing a second minimum, and the
   hand-drawn glyph holds at --foot-glyph so only the box grows. */
@media (max-width: 767px) {
  .foot__social a { width: var(--wa-size); height: var(--wa-size); }
}


/* ============================================================================
   §8.12  FIGURE CAPTIONS
   Every DISCRETE authored figure carries, 12px below its optical bottom-left
   edge: a 24px champagne-ink rule, then `FIG. 0N — NAME — SPECIMEN` in --t-m4
   (10px / .28em). Ambient underlays carry no caption (§7.2.5).

   base.css already styles the `<figcaption>` element exactly this way.
   `.fig-cap` is the same construction as a class, for the cases where the
   caption cannot be a figcaption (a caption baseline-aligned to the last line
   of prose in a sibling column, per A5 SPECIMEN).

   §8.12: "A caption may not assert anything not stated verbatim in
   CONTENT_BRIEF.md."
   ========================================================================= */

.fig { display: block; position: relative; }

/* Rule inline on the caption's own line — see base.css §7 for why it may not
   stack above the text (it aligned to the Fig. II legend column at 390 and read
   as a fourth, unlabelled legend entry). */
.fig-cap {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s4);
  font-family: var(--f-micro);
  font-size: var(--t-m4);
  line-height: var(--lh-m4);
  letter-spacing: var(--tr-m4);
  font-weight: var(--fw-m4);
  text-transform: uppercase;
  color: var(--ac-metal-text);
}
.fig-cap::before {
  content: "";
  flex: 0 0 var(--s4);
  height: var(--rule-w);
  background: currentColor;
}
.fig-cap > .fig-cap__tail::before { content: "\2014\00a0"; }
.fig-cap > .fig-cap__name { white-space: nowrap; }

/* A caption hung on the figure's optical RIGHT edge (the `REF 61109` of
   Fig. II). Same tier, same rule, mirrored. */
.fig-cap--right { justify-content: flex-end; flex-direction: row-reverse; }

/* Ambient underlay: no rule, no caption text tier. */
.fig-cap.is-bare::before { display: none; }


/* ============================================================================
   §8.13  STAGE COUNTER (running head)
   For A4 CHART sections. A PRINTED RUNNING HEAD, not a glass card.

       STAGE   03 / 06        ← --t-m2 key, Pagella tabular at --t-h4,
                                --royal for the live number
       ────────────────       ← 1px rule: --royal elapsed, --rule-soft remainder
       DOCUMENTATION REVIEW   ← --t-m3, --ink-mute, the live stage name

   §4.6 / §8.13 — the counter sits in the sticky left rail ABOVE the rail
   drawing, and THE RAIL DRAWING MUST SPAN THE FULL HEIGHT of the section's
   right column so the left column never empties in a static export. base.css
   supplies that pairing: `.has-sticky-rail` on the grid child, `.sticky-rail`
   on the counter, `.fill-col` on the drawing.

   JS CONTRACT: set `--stage-p` (0–1) and the two text nodes from the section's
   own bounding rect. A desynced counter is a rejection (§10.27). Under reduced
   motion the rail renders fully drawn with the counter fixed at 06 / 06.
   ========================================================================= */

/* A grid with a row gap, not margins: the three tiers are stacked at a fixed
   12px and base.css's unlayered `p { margin: 0 }` cannot reach a gap. */
.stage {
  display: grid;
  row-gap: var(--s3);
  align-content: start;
}

.stage__head {
  display: flex;
  align-items: baseline;
  gap: var(--s4);
}

.stage__key {
  font-family: var(--f-micro);
  font-size: var(--t-m2);
  line-height: var(--lh-m2);
  letter-spacing: var(--tr-m2);
  font-weight: var(--fw-m2);
  text-transform: uppercase;
  color: var(--tx-micro);
}

.stage__count {
  margin-left: auto;
  font-family: var(--f-display);
  font-size: var(--t-h4);
  line-height: var(--lh-h4);
  letter-spacing: var(--tr-h4);
  color: var(--tx-display);
  font-variant-numeric: tabular-nums lining-nums;
  white-space: nowrap;
}
.stage__now { color: var(--ac-identity); }
.stage__sep { margin: 0 var(--s1); color: var(--tx-micro); }

/* The meter: one 1px rule in two segments — royal for the stages entered,
   --rule-soft for the remainder. It is the numeral, drawn. */
.stage__meter {
  position: relative;
  height: var(--rule-w);
  background: var(--ln-soft);
  overflow: hidden;
}
/* As with the nav hairline: the rail scrub writes `el.style.scale` on
   [data-rail-fill], so the rest state and the transition are declared on the
   `scale` property rather than on `transform`, which would otherwise multiply
   with it and hold the meter at zero. */
.stage__fill {
  display: block;
  height: 100%;
  background: var(--ac-identity);
  scale: var(--stage-p, 0) 1;
  transform-origin: 0 50%;
  transition: scale var(--t-l) var(--e-out);
}

.stage__name {
  font-family: var(--f-micro);
  font-size: var(--t-m3);
  line-height: var(--lh-m3);
  letter-spacing: var(--tr-m3);
  font-weight: var(--fw-m3);
  text-transform: uppercase;
  color: var(--tx-micro);
}

/* Below the chart's two-column threshold the running head stops being sticky
   and simply prints once at the head of the stack (§8.11's mobile axis takes
   over the progress job). */
@media (max-width: 819px) {
  .stage { position: static; }
}


/* ============================================================================
   §8.14  TOUCH TARGETS — WCAG 2.2 · 2.5.8 (Target Size, Minimum)
   The system's ink sizes are correct and are not changing: what changes is the
   HIT area. Every offender below measured 37–42px tall at 390 — close enough
   that a 2–7px block increase clears 44px without moving a single baseline
   more than a few pixels, and inline-flex centring keeps the glyph optically
   where it already sat.

   Measured before: .skip 42 · .brandmark 37 · .btn--compact 37 · .link--muted 37
   · footer mail/tel 40 · route links 40.  All ≥44 after.
   ========================================================================= */

.skip {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

@media (max-width: 767px) {
  .brandmark,
  .btn--compact,
  .link--muted,
  .foot__row a[href^="mailto"],
  .foot__row a[href^="tel"],
  .foot__link[href^="mailto"],
  .foot__link[href^="tel"],
  main a.t-h4 {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* The brandmark is a two-part lockup (word + rule + sub); growing the box
     must not re-flow its internals, so it keeps its own cross-axis alignment. */
  .brandmark { align-items: center; }
}


/* ============================================================================
   §8.14  CHECKLIST BAND — a mid-article / post-form island on the deep navy
   ground, opened by a single champagne rule. One band, one button; no fill
   beyond the ground, no shadow, no second accent.
   ========================================================================= */
.cband {
  --cband-ground: #101d3d;
  background: var(--cband-ground);
  padding: var(--s7) var(--s6);
  margin-top: var(--s7);
}
.cband__rule { border: 0; border-top: var(--rule-w) solid var(--ac-metal); width: 64px; margin: 0; }
.cband > * + * { margin-top: var(--s4); }
.cband .btn-set { margin-top: var(--s5); }
.cband__lead { max-width: 46ch; }

@media (max-width: 767px) { .cband { padding: var(--s6) var(--s5); } }

} /* ── end @layer components ─────────────────────────────────────────────── */


/* ============================================================================
   §0  WHAT USED TO BE HERE
   An unlayered `revert-layer` patch list, handing base.css's element-reset
   properties back to this layer one component at a time. It is gone: tokens.css
   now declares `@layer base, components, figures;` and base.css is wrapped in
   `@layer base`, so this layer outranks the reset by construction and every
   value in the system lives exactly once, above.
   Nothing may be added below this line. A component that needs to beat the
   element reset already does.
   ========================================================================= */
