/* ==========================================================================================
   ND Website Redesign — BASE
   ------------------------------------------------------------------------------------------
   Reset + layout primitives + rhythm. Loads after the token layers.

   📱 MOBILE FIRST. Everything below is the MOBILE layout. Desktop is layered on with
   `@media (min-width: …)` only. Never write desktop-first.

   PURPOSE: make change cheap. Nathan designs by iterating — "move that, reorder those, I don't
   like it, try again". These primitives exist so that is a one-line change, never a rewrite.
   Nothing here decides how the site LOOKS; it decides what is easy to REARRANGE.

   No !important. Every value comes from a token and can be overridden at any scope.
   ========================================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* 🐛 RESERVE THE SCROLLBAR'S SPACE WHETHER OR NOT ONE IS SHOWING (2026-08-02).
     The burger menu locks scrolling while it is open. On desktop that removes the scrollbar, and
     the whole page jumped sideways by its width the instant the menu opened and back again on
     close — a visible shudder on every open. Reserving the gutter costs nothing and is the only
     fix that does not involve measuring the scrollbar and compensating in JS.
     No effect on mobile, where overlay scrollbars take no space to begin with. */
  scrollbar-gutter: stable;
}

body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ul, ol {
  margin: 0;
}
ul[role="list"], ol[role="list"] { list-style: none; padding: 0; }

body {
  min-height: 100vh;
  min-height: 100svh;            /* svh = the *small* viewport height: stable on mobile when the
                                    browser chrome shows/hides. Falls back to vh where unsupported. */
  text-rendering: optimizeSpeed;
  -webkit-font-smoothing: antialiased;
  font-family: var(--nd-typography-body-font-family);
  font-size: var(--nd-typography-body-font-size);
  font-weight: var(--nd-typography-body-font-weight);
  line-height: var(--nd-typography-body-line-height);
  /* 🐛 THIS READS --nd-page-bg, AND IT DID NOT UNTIL 2026-08-05. It was `var(--nd-surface)`, a flat
     dark that nothing could override — while <html> two lines up in nd-site-tokens.css was already
     `var(--nd-page-bg, var(--nd-surface))` and the scroll runtime was dutifully setting that variable.
     BODY PAINTS OVER HTML, so the whole mechanism was writing to a surface nobody could see. It has
     been the documented "second line of defence" since 2026-08-01 and has never once had an effect.

     Found while fixing the footer's slat gaps: with --nd-page-bg held at paper, a paint audit still
     reported the topmost painter as `body` at rgb(13,13,13). Same default as before — only now the
     variable actually reaches the element that draws it. */
  background: var(--nd-page-bg, var(--nd-surface));
  color: var(--nd-text);
}

img, picture, video, canvas, svg { display: block; max-width: 100%; height: auto; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4, h5, h6 { font-weight: inherit; font-size: inherit; }   /* type comes from role classes, not tags */

/* Accessible focus — visible, never removed. WCAG 2.2. */
:focus-visible { outline: 2px solid var(--nd-accent); outline-offset: 3px; border-radius: 2px; }

/* Skip link — keyboard users land here first. */
.nd-skip {
  position: absolute; left: -9999px; top: 0; z-index: var(--nd-z-toast);
  padding: var(--nd-space-2) var(--nd-space-3);
  background: var(--nd-accent); color: #fff;
}
.nd-skip:focus { left: var(--nd-space-2); top: var(--nd-space-2); }

/* Screen-reader-only */
.nd-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
         clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ----------------------------------------------------------------------- LAYOUT PRIMITIVES */
/* Compose pages from these. Each does ONE job, so they can be nested and swapped freely.      */

/* CONTAINER — centres content and holds the side gutter. */
.nd-container {
  width: 100%;
  max-width: var(--nd-container);
  margin-inline: auto;
  padding-inline: var(--nd-gutter);
}
.nd-container--narrow { max-width: var(--nd-container-narrow); }  /* reading measure */
.nd-container--wide   { max-width: var(--nd-container-wide); }
.nd-container--flush  { padding-inline: 0; }

/* FULL — no max-width, so content anchors to the SCREEN edges (held off them only by the gutter)
   instead of being centred inside a capped column. Nathan, 2026-07-30: the header and hero are
   edge-anchored, not centred.
   Below 1440px this is a no-op — the cap never binds — so it changes desktop only, by definition.
   Combine with --flush to go truly edge-to-edge with no gutter at all. */
.nd-container--full   { max-width: none; }

/* SECTION — the vertical rhythm between blocks. */
.nd-section       { padding-block: var(--nd-section-y); }
.nd-section--tight{ padding-block: var(--nd-section-y-tight); }
.nd-section--flush{ padding-block: 0; }

/* SCREEN — a full-viewport block. Used for the services scroll and any hero.
   Uses svh (stable on mobile) with dvh available via the modifier when you WANT it to react. */
.nd-screen      { min-height: 100vh; min-height: 100svh; }
.nd-screen--dyn { min-height: 100dvh; }
.nd-screen--half{ min-height: 60vh; min-height: 60svh; }

/* STACK — vertical spacing between children. Change one variable to respace a whole block. */
.nd-stack { display: flex; flex-direction: column; gap: var(--nd-stack-gap, var(--nd-space-3)); }
.nd-stack--xs { --nd-stack-gap: var(--nd-space-1); }
.nd-stack--sm { --nd-stack-gap: var(--nd-space-2); }
.nd-stack--md { --nd-stack-gap: var(--nd-space-4); }
.nd-stack--lg { --nd-stack-gap: var(--nd-space-8); }
.nd-stack--xl { --nd-stack-gap: var(--nd-space-12); }

/* CLUSTER — a row that wraps. Logo + nav, tag lists, button rows. */
.nd-cluster {
  display: flex; flex-wrap: wrap;
  gap: var(--nd-cluster-gap, var(--nd-space-2));
  align-items: var(--nd-cluster-align, center);
  justify-content: var(--nd-cluster-justify, flex-start);
}
.nd-cluster--between { --nd-cluster-justify: space-between; }
.nd-cluster--center  { --nd-cluster-justify: center; }
.nd-cluster--end     { --nd-cluster-justify: flex-end; }
.nd-cluster--top     { --nd-cluster-align: flex-start; }
.nd-cluster--baseline{ --nd-cluster-align: baseline; }
.nd-cluster--nowrap  { flex-wrap: nowrap; }

/* GRID — mobile: one column. Set --nd-grid-cols in a media query for desktop. */
.nd-grid {
  display: grid;
  grid-template-columns: repeat(var(--nd-grid-cols, 1), minmax(0, 1fr));
  gap: var(--nd-grid-gap, var(--nd-space-4));
}

/* CENTER — centre a block and optionally its text. */
.nd-center      { display: grid; place-items: center; }
.nd-center--text{ text-align: center; }

/* FULL-BLEED — break a child out of its container to the full viewport width. */
.nd-bleed { width: 100vw; margin-inline: calc(50% - 50vw); }

/* SPREAD — push the last child away (e.g. content top, CTA bottom of a full-screen block). */
.nd-spread { display: flex; flex-direction: column; min-height: inherit; }
.nd-spread > :last-child { margin-block-start: auto; }

/* ORDER — reorder without touching the markup. Nathan reorders a lot; this is for that. */
.nd-order-first { order: -1; }
.nd-order-last  { order: 999; }

/* ------------------------------------------------------------------------ TYPE ROLE HELPERS */
/* The brand file already ships .nd-display / .nd-section-title / .nd-eyebrow / .nd-body /
   .nd-lead / .nd-subhead. These only add colour + emphasis roles on top.                     */
.nd-text-soft   { color: var(--nd-text-soft); }
.nd-text-muted  { color: var(--nd-text-muted); }
.nd-text-accent { color: var(--nd-accent-text); }   /* ground-aware — see nd-site-tokens.css */
.nd-measure     { max-width: 62ch; }
.nd-balance     { text-wrap: balance; }   /* nicer headline line-breaks where supported */

/* ORANGE = SIGNAL. Highlight ONE word in a headline — the canonical brand rule.
   If you find yourself using this twice in a view, that's the rule being broken. */
.nd-signal { color: var(--nd-accent-text); }

/* ----------------------------------------------------------------------- EDITORIAL LINEWORK */
.nd-rule   { border: 0; block-size: var(--nd-rule); background: var(--nd-line); inline-size: 100%; }
.nd-rule--strong { block-size: var(--nd-rule-strong); background: var(--nd-line-strong); }
.nd-rule--accent { background: var(--nd-accent); }
.nd-hairline-t { border-block-start: var(--nd-rule) solid var(--nd-line); }
.nd-hairline-b { border-block-end:   var(--nd-rule) solid var(--nd-line); }

/* ------------------------------------------------------------------- THE HOVER FILL
   THE WHOLE BUTTON GOES ND ORANGE, sweeping in from the left.

   ⚠️ REPLACED THE PER-CHARACTER TEXT WIPE, 2026-08-01. Nathan: "i will be moving away from the
   button text turning orange i would like the entire button to turn orange and not the text." The
   direction is kept - it sweeps rather than fades, which is the part he picked out of six options
   the first time round - but it is now the surface that changes, not the letters.

   ⚠️ A PSEUDO-ELEMENT, NOT `background-color` ON THE BUTTON, and for two reasons. A background
   cannot sweep; it can only fade. And the button already HAS a background - a white pill, or a
   transparent outline - which the fill has to cover without destroying, so it comes back cleanly
   when the pointer leaves.

   ⚠️ z-index: -1 UNDER isolation: isolate. That pair puts the fill above the button's own
   background and below its label, in the button's OWN stacking context. Without the isolation, a
   negative z-index escapes to the nearest ancestor that has one and the fill disappears behind the
   page.

   🎉 STILL NO JAVASCRIPT on the button itself. :hover and :focus-visible are the whole trigger.
   (The header's sweep twin cannot use :hover, because it takes no pointer events - it mirrors the
   state through a class instead. See nd-header.css.)

   ⚠️ IT LIVES HERE, NOT IN nd-home.css, because the header buttons use it and nd-home.css is
   enqueued on the front page only. */
.nd-fill {
  position: relative;
  isolation: isolate;
  transition: color var(--nd-dur-fast) var(--nd-ease);
}

.nd-fill::before {
  content: "";
  position: absolute;
  inset: var(--nd-fill-inset, 0);
  z-index: -1;
  border-radius: inherit;
  background: var(--nd-fill-color, var(--nd-accent));

  /* 🐛 IT USED TO scaleX() A ROUNDED BOX, AND THAT IS WHY IT LOOKED SQUARE (fixed 2026-08-02).
     Nathan: "you can see the orange animation starting off as a square then covering the buttons
     then only sit in place and form the button shape."

     That is exactly what a transform does here. border-radius resolves BEFORE the transform, so
     scaling a pill horizontally squashes its rounded ends - at scaleX(0.2) a 22px radius is drawn
     4.4px wide and reads as a corner-less rectangle. The shape only becomes a pill again at
     scaleX(1), which is precisely the "snaps into shape at the end" that was described.

     A clip-path REVEALS the element's own geometry instead of deforming it, so the fill is the
     button's shape from the first pixel to the last, and `round` carries the radius through the
     whole animation. Still composited, still no JavaScript.

     ⚠️ THE RADIUS IS NAMED, NOT INHERITED. clip-path cannot read border-radius, so it has to be
     told - and a pill radius is safe on a rectangle too, because `round` clamps to half the box. */
  /* --nd-fill-radius lets each component say what SHAPE its fill is. Nathan, 2026-08-02: the
     editorial CTAs ("Start a project", "More about us") want a SQUARE fill, not a pill - they are
     not pills themselves, and a rounded fill behind a flat label reads as a mismatched sticker. */
  clip-path: inset(0 100% 0 0 round var(--nd-fill-radius, var(--nd-radius-pill, 999px)));

  /* THE SWEEP'S TIMING. Nathan, 2026-08-02: "must be slower with easing, for example start with
     quick (not too quick) and end slowly."

     That is an ease-OUT with a long tail, not the site's general --nd-ease, and it wants roughly
     double the duration it had. cubic-bezier(.12,.85,.22,1) covers about 60% of the distance in the
     first third of the time and spends the remaining two thirds arriving — the orange commits
     immediately and then settles rather than stopping dead.

     ⚠️ BOTH ARE TOKENS so a single button can differ without forking this rule: a small utility
     control can take a shorter --nd-fill-dur without anyone editing .nd-fill. */
  /* Defaults on :root; a single control can still override either without forking this rule. */
  transition: clip-path var(--nd-fill-dur, .62s) var(--nd-fill-ease, cubic-bezier(.12,.85,.22,1));
}

.nd-fill:hover::before,
.nd-fill:focus-visible::before { clip-path: inset(0 0 0 0 round var(--nd-fill-radius, var(--nd-radius-pill, 999px))); }

/* ⚠️ THE LABEL HAS TO CHANGE WITH IT, AND INK IS NOT A STYLE CHOICE. White on #F23207 measures
   4.0:1 and these labels are 12-13px, which does not qualify as large text - so white would fail
   AA the moment the fill arrives. Ink is 4.85:1 on the same orange and passes. */
.nd-fill:hover,
.nd-fill:focus-visible { color: var(--nd-color-ink); }

@media (prefers-reduced-motion: reduce) {
  /* The colour change is not motion and is the only hover feedback these buttons have, so it stays
     - it simply arrives at once instead of sweeping in. */
  .nd-fill::before { transition-duration: 1ms; }
}

/* ------------------------------------------------------------------------------- BREAKPOINTS
   Mobile-first. Add desktop refinements inside these — never the other way round.
   Kept in sync with the --nd-bp-* reference tokens in nd-site-tokens.css.                     */
@media (min-width: 768px) {   /* md — tablets */
  .nd-grid { --nd-grid-cols: var(--nd-grid-cols-md, var(--nd-grid-cols, 1)); }
}
@media (min-width: 1024px) {  /* lg — small laptops */
  .nd-grid { --nd-grid-cols: var(--nd-grid-cols-lg, var(--nd-grid-cols-md, var(--nd-grid-cols, 1))); }
}
@media (min-width: 1280px) {  /* xl — desktop */
  .nd-grid { --nd-grid-cols: var(--nd-grid-cols-xl, var(--nd-grid-cols-lg, var(--nd-grid-cols-md, var(--nd-grid-cols, 1)))); }
}

/* ------------------------------------------------------------------- THE NOTICE PAGE
   The minimal "there is nothing to show you here, and here is the way back" page. Used by 404.php
   and index.php (WordPress's last-resort template — search results and archives).

   Was .nd-stub until 2026-08-15, when page.php's "In build" placeholder was deleted: every page is
   built, so that branch could no longer fire. The COMPONENT survives the placeholder because 404
   and index still need it, and "stub" had stopped describing what it is.

   Lives in the base layer because it is the shell every page falls back to, and the home page's
   stylesheet is front-page-only. Deliberately quiet — it should never compete with real design. */
.nd-notice {
  min-block-size: 52svh;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}

.nd-notice__eyebrow {
  margin: 0 0 clamp(12px, calc(3.3px + 2.4vw), 96px);
  font-family: var(--nd-typography-eyebrow-font-family);
  font-size: clamp(10px, calc(7px + 0.2865vw), 18px);
  font-weight: 400;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--nd-accent-text);
}

.nd-notice__title {
  margin: 0;
  font-family: var(--nd-typography-display-font-family);
  font-weight: 800;
  /* 34px at 360 → 96px at 1920 → 168px at 3840 */
  font-size: clamp(34px, calc(19.5px + 4.02vw), 168px);
  line-height: 0.94;
  letter-spacing: -0.04em;
  color: var(--nd-text);
}

.nd-notice__body {
  margin: clamp(16px, calc(4.4px + 3.2vw), 128px) 0 0;
  max-inline-size: min(44ch, 100%);
  font-family: var(--nd-typography-body-font-family);
  font-size: clamp(14px, calc(6px + 0.625vw), 30px);
  line-height: 1.55;
  color: var(--nd-text-soft);
  text-wrap: pretty;
}

/* ⚠️ SELF-CONTAINED. The hero's CTA component lives in nd-home.css, which is front-page only —
   reusing it here would render unstyled on exactly the pages this placeholder serves. Same
   language (wide-tracked mono, arrow, rule beneath), written out rather than borrowed. */
.nd-notice__cta {
  margin-block-start: clamp(24px, calc(5.9px + 5.03vw), 200px);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5em;
  inline-size: 16em;
  max-inline-size: 100%;
  min-block-size: 24px;                /* WCAG 2.2 AA target size, as the hero's CTA learned */
  padding-block-end: 0.85em;
  font-family: var(--nd-typography-eyebrow-font-family);
  font-weight: 400;
  font-size: clamp(11px, calc(6px + 0.4688vw), 24px);
  letter-spacing: 0.22em;
  line-height: 1;
  text-transform: uppercase;
  color: var(--nd-text);
}
.nd-notice__cta::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: 1px;
  background: var(--nd-line-strong);
  transition: background var(--nd-dur-fast) var(--nd-ease);
}
.nd-notice__cta:hover::after,
.nd-notice__cta:focus-visible::after { background: var(--nd-text); }

.nd-notice__cta svg {
  flex: none;
  inline-size: 1.5em;
  block-size: auto;
  overflow: visible;
  transition: transform var(--nd-dur-base) var(--nd-ease);
}
.nd-notice__cta:hover svg,
.nd-notice__cta:focus-visible svg { transform: translateX(0.35em); }

@media (prefers-reduced-motion: reduce) {
  .nd-notice__cta svg { transition: none; }
  .nd-notice__cta:hover svg { transform: none; }
}

/* ══════════════════════════════════════════════════════════════════════ THE LINE ART
   The site's editorial rule-with-a-"+", and the ONE definition of how it draws.

   Nathan, 2026-08-15: "the line art animation used on the work that outlives the campaign
   section above the stats on the home page — i would like that line art copied and replace all
   the line art like that on the entire website … don't rebuild the lines, copy the one we want
   and remove the old one and paste the working correct one."

   THE BEHAVIOUR, copied from .nd-cs__ledger-rule (nd-case-studies.css) which is the approved
   reference: the rule is in TWO halves, each anchored at its INNER edge, so both leave the cross
   and travel OUTWARD as the progress runs 0 → 1. The "+" turns a half turn (+ → × → +) on the
   same progress. Before this, most of these rules were a STATIC full-width line with only the
   cross turning — the reference was the odd one out, and that is what this inverts.

   ⚠️ A BACKGROUND SIZED BY A NUMBER, NOT A TRANSFORM — the reference's reason, and it still
   holds: scaleX on a 1px rule blurs its ends and cannot be anchored to one side without also
   moving it.

   ⚠️ PSEUDO-ELEMENTS, NOT TWO FLEX CHILDREN like the reference uses. Same construction, different
   placement, and the reason is --nd-mark-x below: two of these rules carry their "+" at 66%, not
   at the centre, with the mark absolutely positioned. Flex halves would force it back to the
   middle and silently undo an approved layout decision (Nathan, 2026-08-02: the mark "belongs
   off-centre, sitting under the tail of the composition"). Pseudo-elements split wherever the
   mark actually is and need no markup change.

   THE THREE KNOBS — everything Nathan might flip is a one-line change:
     --nd-lineart-p     0 → 1 progress. EACH SITE MAPS ITS OWN DRIVER ONTO THIS and that is the
                        only per-place wiring: --nd-cs-in, --nd-reveal, --nd-intro, --nd-cyc.
     --nd-mark-x        where the "+" sits along the rule. 50% default; 66% on About + Services.
     --nd-lineart-lead  how far ahead of the type the line runs. 1.8 = the reference's ramp, so
                        the line is drawn while the words are still resolving — line art first,
                        then type. Shared with the reference so the two can never diverge.

   ⚠️ RESTS DRAWN. Every driver falls back to 1, so with no JS or reduced motion these are simply
   full lines — never a permanently half-drawn rule. */
/* ⚠️ ON :root SO THE REFERENCE CAN READ IT TOO. .nd-cs__ledger-rule is NOT .nd-lineart (it keeps
   its own flex construction — it is the thing that already works, and rebuilding it to prove a
   point would risk the one instance Nathan approved). Sharing the ramp here is what stops the two
   drifting: retune the lead once and the ledger and every copied rule move together. */
:root { --nd-lineart-lead: 1.8; }

.nd-lineart {
  position: relative;
  --nd-mark-x: 50%;
  --nd-lineart-draw: clamp(0, calc(var(--nd-lineart-p, 1) * var(--nd-lineart-lead)), 1);
  /* The halves below ARE the rule now; any static background on the element would sit under them
     as a permanently-drawn line and defeat the whole thing. */
  background: none;
}
.nd-lineart::before,
.nd-lineart::after {
  content: "";
  position: absolute;
  inset-block: 0;
  background: linear-gradient(var(--nd-line, rgba(48, 48, 53, 0.26)), var(--nd-line, rgba(48, 48, 53, 0.26)))
              no-repeat;
  background-size: calc(var(--nd-lineart-draw) * 100%) 100%;
  pointer-events: none;
}
.nd-lineart::before {
  inset-inline-start: 0;
  inline-size: var(--nd-mark-x);
  background-position: right center;   /* grows LEFT, away from the + */
}
.nd-lineart::after {
  inset-inline-start: var(--nd-mark-x);
  inset-inline-end: 0;
  background-position: left center;    /* grows RIGHT, away from the + */
}

/* ══════════════════════════════════════════════════════════ LINE CTA — THE TOUCH STAGGER
   🐛 ON A PHONE, ONLY THE LAST 2-4 LETTERS OF A LINE CTA EVER MOVED (Nathan, 2026-08-15: "the CTA
   once clicked starts to animate, the arrows and line is good but the problem comes in for the words
   not moving, only the last three letters move and the rest stay static … the amount of letters
   moving is different for some Line CTAs but mostly only 2-4 letters move").

   THE CAUSE, and the detail that explains every part of his report: the letter travel is a hover
   transition whose delay is staggered so the RIGHTMOST letter leads —
   `calc((var(--n) - 1 - var(--i)) * 34ms)`. On "START A PROJECT" the leftmost letter therefore waits
   14 x 34ms = 476ms before it even starts. A touch device has no hover: a tap raises the state for a
   moment and drops it, so only the letters whose delay had already elapsed ever began — the last
   two to four. And because the delay is a function of --n, the LETTER COUNT, a shorter label got a
   shorter wait and moved more of itself, which is exactly why the number differed per CTA.

   ⚠️ THE REAL FIX IS NOT HERE — IT IS IN nd-motion.js. Two earlier attempts failed because they
   both tried to solve a navigation race with the stagger value, which cannot work: on touch the tap
   IS the click, so the letters and the page-load start together and the load wins. First 16ms (too
   fast, "slides as one block" — 0ms), then a brisk cascade that still got cut because navigation
   ran under it. Nathan's decision, 2026-08-15: "play then go" — on touch, HOLD the navigation back
   until the cascade has played (nd-motion.js intercepts the tap and follows the link after a delay).

   So navigation is no longer the constraint, and the stagger is now a FREE VISUAL CHOICE: pick the
   value that reads best as a letter-by-letter cascade, and nd-motion.js's ND_CTA_NAV_DELAY gives it
   the time to finish. 22ms — clearly sequential (the 13th letter starts ~264ms after the first),
   between the block-slide of 0 and the desktop 34ms. The arrow and underline are untouched; Nathan
   confirmed both already read correctly and neither was ever staggered.

   ⚠️ TUNING IS NOW TWO KNOBS, and they move together: this stagger sets HOW strung-out the cascade
   is; ND_CTA_NAV_DELAY in nd-motion.js sets how long touch waits before following the link. Raise
   the stagger for a more obvious cascade and raise the delay to match, or the tail gets cut again.
   Do not go back to 0 (block slide). This value still applies on desktop hover too — see below.

   ⚠️ ONE TOKEN, SIX STYLESHEETS. The Line CTA component is re-provided in each page's stylesheet
   (nd-home, nd-about-page, nd-services-page, nd-case-studies-page, nd-case-study-base, nd-contact-
   page) because nd-home.css is front-page-only — so every copy declared `--nd-cta-stagger: 34ms` on
   the component itself, and a :root override here would LOSE to all six. Each now reads
   `var(--nd-cta-stagger-base, 34ms)` instead, so this is the single place the value lives. Retune
   the stagger for the whole site by changing it here.

   Uses (hover: none) rather than a width breakpoint on purpose: this is about the input device, not
   the screen. A touch laptop at 1920 has the same problem and a narrow desktop window does not. */
:root { --nd-cta-stagger-base: 34ms; }

@media (hover: none) {
  :root { --nd-cta-stagger-base: 22ms; }
}
