/* ==========================================================================
   surveyed.v1.css
   "THE SURVEYED SHEET" - the shared design layer promoted from the
   homepage so every page carries the same identity:

     - a single-family type system: the corporate font Poppins (CI Manual p.17)
       feeds all three voices (display / body / machine register), distinguished
       by weight and feature settings rather than separate families
     - a page-wide live WebGL FIELD (field.v1.js) read vivid behind the hero,
       veiled to a subtle living atmosphere behind the content
     - flat POSTER surfaces (hairline + space, not shadow-stacks)
     - bracketed drafting kickers, an amber dimension rule, a generous rhythm
     - motion that varies by mass and arrives (curves from tokens.v1.css)

   LOAD ORDER (after the Rev B sheets, so this upgrades their defaults):
     tokens.v1.css -> frame.v1.css -> components.v1.css -> surveyed.v1.css
   Poppins is self-hosted via next/font in the root layout (no Google Fonts
   <head> link); tokens.v1.css maps all --font* voices to it. Page <head>
   loads the field:
     <script src="shared/reveal.v1.js" defer></script>
     <script src="shared/field.v1.js" defer></script>   (after reveal.v1.js)
   Page <body> first child: <canvas id="glhero" aria-hidden="true"></canvas>
   The hero <section> takes the .page-hero modifier (field shown vivid there).
   Legal pages (body.doc-light) opt out of the dark field automatically below.
   ========================================================================== */

/* ==========================================================================
   1. TYPE - the three voices (tokens carry the families + the scale)
   ========================================================================== */
body{font-family:var(--font)}

/* brand voice: Poppins display - loaded weights 400/500/600/700 (next/font). The
   monument headlines use the real loaded 600 (SemiBold); Poppins has no optical-
   size axis, so no font-variation-settings/opsz here. */
/* This sheet is the SINGLE definition site for the headline scale, the
   bracketed kicker and the lede. components.v1.css carried a superseded copy
   until 2026-08-03; the colour, tabular-numeral and measure declarations it
   contributed (which nothing here overrode) were folded in below so the
   computed result is byte-identical. */
.display,.h1-sheet,.h2-sheet,.h3-sheet,.h4-sheet{font-family:var(--font-display);text-wrap:balance;color:var(--ink-1)}
.display{
  font-size:clamp(2.75rem, 1.1rem + 5vw, 5.5rem);
  font-weight:600;line-height:.95;letter-spacing:-.04em;
}
.h1-sheet{
  font-size:clamp(2.4rem, 1.2rem + 4.4vw, 5rem);
  font-weight:600;line-height:.98;letter-spacing:-.035em;
}
.h2-sheet{
  font-size:var(--t-h2);line-height:1.02;letter-spacing:-.025em;
  font-weight:600;
}
/* The two intermediate rungs, weight 600 to match the canonical h2 - the 700
   and 800 weights that page-local titles carry at these sizes are drift, not
   intent. Landing zones for the standardisation pass: no markup uses them yet,
   so adding them changes nothing on any page today. */
.h3-sheet{
  font-size:var(--t-h3);line-height:1.08;letter-spacing:-.02em;
  font-weight:600;
}
.h4-sheet{
  font-size:var(--t-h4);line-height:1.25;letter-spacing:-.01em;
  font-weight:600;
}

/* human voice: Poppins for prose + card titles (inherits --font; named here so
   intent is explicit and a later token swap stays surgical) */
.lede,.slab p,.slab h3,.prose,.nd-l,.sr-value{font-family:var(--font)}
.lede{font-size:var(--t-lede);font-weight:360;line-height:1.6;color:var(--ink-lede);max-width:min(var(--measure), 56ch)}
/* .lede.measure opts a lede out of the 56ch cap up to the full 64ch measure.
   Both are single-class, so this only wins on source order - keep it after. */
.measure{max-width:var(--measure)}

/* machine voice: Poppins (via --font-mono) for the whole annotation / drafting
   register; Geist Mono retired per ADR-0006 - the tabular/zero feature settings
   below carry the machine character, not a separate monospace family */
.kicker,.annot,.dim-fig,.dim-labels,.tb-label,.tb-cert,.bk-h,.footer-h,
.thumb-no,.chip,.sr-label,.nd-d,.cta-ghost,
.scn-tag,.scn-metric{
  font-family:var(--font-mono);
}
.tb-fig,.dim-fig,.sr-value{font-feature-settings:'tnum' 1,'zero' 1}

/* ==========================================================================
   2. THE LIVE FIELD - fixed flowing-gradient behind the WHOLE page.
   field.v1.js renders it; this is the canvas chrome + the CSS twilight wash
   fallback (shown when WebGL is unavailable or before the canvas is ready).
   ========================================================================== */
body{
  /* the whole-page ground is a THEME TOKEN (tokens.v1.css --field-wash) so
     the light theme can reach it - it was the one literal that made a light
     theme impossible */
  background:var(--field-wash);
  background-attachment:fixed;
}
#glhero{
  /* width/height:100% (NOT 100vw/100vh): <canvas> is a REPLACED element, so
     inset:0 alone leaves it at its intrinsic 300x150 - it needs an explicit
     size. 100% resolves against the fixed containing block (the layout
     viewport), so it fills the screen WITHOUT the 100vw scrollbar overshoot.
     field.v1.js reads the element's own clientWidth for the render buffer. */
  position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;display:block;
  pointer-events:none;opacity:0;transition:opacity 1.1s ease;
}
#glhero.ready{opacity:1}
/* fine static grain over the field - enriches it, kills banding (not animated) */
body::after{
  content:'';position:fixed;inset:0;z-index:0;pointer-events:none;
  opacity:.03;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:140px 140px;
}
/* content + footer sit above the field */
main{position:relative;z-index:1}
.footer{position:relative;z-index:1}
/* NO pre-footer gap at all (owner, finesse pass 2 review - the band read as
   a stray strip in light however it was painted): the footer sits flush on
   the last section's own tail padding, and its corner ticks land on that
   veiled surface. History: this was .footer{margin-top:64px} (exposed the
   raw field), then a veiled main::after strip (read as a pale band in
   light). The air lives INSIDE the last block now, nowhere else. */
/* the sticky nav floats over the live field. When backdrop-filter is
   unavailable, its 55% glass would let the field bleed through illegibly -
   give it a near-opaque fallback so the nav stays readable. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .nav{background:var(--chrome-bg-strong)}
}

/* every section paints a dark veil so the field reads as a subtle living
   atmosphere (~20% behind), plus a soft ambient glow off the top edge and a
   1px machined edge-light so sections breathe into one another */
.sheet-section:not(.page-hero){
  background:
    radial-gradient(72% 260px at 50% 0, var(--sec-amb, transparent), transparent 72%),
    linear-gradient(0deg, var(--veil), var(--veil));
  box-shadow:inset 0 1px 0 var(--sheen);
}
/* opt a section into a blue/amber breath off its top:  style="--sec-amb:..." */

/* the rhythm is the TOKEN, not a local literal. This sheet loads after
   frame.v1.css at equal specificity, so a literal here silently killed
   --space-section for every section on the site (it was
   clamp(64px, 4.5vw + 28px, 104px), before that clamp(88px,6vw+40px,148px)).
   Tune section rhythm in tokens.v1.css from here on. */
.sheet-section{padding-block:var(--space-section)}

/* ==========================================================================
   2b. THE JOIN MODEL - one rhythm for EVERY major block boundary.

   Two quantities, and they are not the same thing:
     TOTAL JOIN  = previous padding-bottom + collapsed margin + next padding-top.
                   This is what the measurement harness reports.
     PERCEIVED VOID = previous padding-bottom + collapsed margin only. Where the
                   next block is a framed surface, its own padding-top is
                   breathing room INSIDE that surface, not dead scroll, so the
                   void the reader actually sees stops at the band's edge.
   The model targets a TOTAL of --space-join (96px at 390 and 820, 121px at 1440
   and flat above) for plain joins, and holds the PERCEIVED VOID at or under
   ~49px at 1440 into every framed band. Two shapes, chosen by the next block:

     - next block is a plain section (veil + 1px edge-light, no frame): it keeps
       the full padding-bottom above the boundary and takes --space-seam as its
       lead-in below it.
     - next block is a FRAMED surface (.cta-band, .proof-band: own background,
       own hairlines, own internal padding that is part of its identity): the
       frame is never squashed. The band's stand-alone outer margin is dropped
       and the difference comes out of the preceding section's tail instead, so
       the boundary simply moves up.

   Every offset is derived from --space-section, so retuning that one token in
   tokens.v1.css retunes the whole site. Nothing here is a literal.

   :has() carries the "shorten the tail" half. Where it is unsupported those
   rules drop out and the framed joins land WIDER (168px into a CTA band, 180px
   into a proof band, at 1440) rather than broken. The hero pair is the one place
   where half the rule would be worse than none, so it is @supports-gated.
   ========================================================================== */

/* plain section -> plain section (the veil + edge-light already mark the
   boundary, so the next section only needs the seam as its lead-in) */
.sheet-section:not(.page-hero) + .sheet-section:not(.page-hero){
  padding-top:var(--space-seam);
}

/* plain section -> framed band. margin-top:0 retires the band's stand-alone
   outer gap (it stays in primitives.v1.css for a band used outside this
   sequence); the preceding section's tail carries the whole join minus the
   band's own internal lead-in.
   Home's trust band is BOTH .sheet-section and .proof-band, so it is excluded
   here and handled by the plain stacked rule above. */
.sheet-section:not(.page-hero) + .cta-band,
.sheet-section + .proof-band:not(.sheet-section){margin-top:0}
.sheet-section:not(.page-hero):has(+ .cta-band){
  padding-bottom:calc(var(--space-join) - var(--space-cta-pad));
}
/* heroes included: a hero handing over to a framed band is the block above the
   boundary like any other, so it gives up its tail the same way (photon and
   sectors open with the band directly under the hero). */
.sheet-section:has(+ .proof-band:not(.sheet-section)){
  padding-bottom:calc(var(--space-join) - var(--space-proof-pad));
}

/* hero -> plain section. The next block is a plain section, so this takes the
   seam shape: the hero keeps its tail and the section takes a seam-derived
   lead-in. The opening boundary of a page is allowed to breathe a little more
   than a body-to-body seam, so it uses --space-seam-hero (1.35x the seam), for
   a total of 104px at 390 and 820 and 130px at 1440 and above.

   The second rule pins the hero's TAIL - never its deliberate 1.15x top - to
   the shared rhythm. Four routes set a shorter tail page-side (.sheet-hero at
   .4/.42x on citadel, solutions and piam-suite; .cm-hero at clamp(24,3vw,40)
   on credential-management) and no single lead-in can serve both: at 1440 a
   96px tail needs a lead-in of 39px or less to stay under 135px, while a 38px
   tail needs 83px or more to reach 121px. Without the pin those four sit at
   72-74px at 1440 and 61-63px at 390. The pin is a no-op on the 13 routes
   already using the shared padding, and it only ever lengthens a tail today -
   a page wanting a LONGER hero tail than the rhythm would need this revisited.

   The pair is @supports-gated so it applies atomically: the lead-in without
   the pin is worse on those four routes than neither rule. */
@supports selector(:has(*)){
  .page-hero + .sheet-section:not(.page-hero){
    padding-top:var(--space-seam-hero);
  }
  .page-hero:has(+ .sheet-section:not(.page-hero)){
    padding-bottom:var(--space-section);
  }
}

/* framed band -> plain section: the band's own padding-bottom already sits
   above the boundary, so the section takes only the remainder as its lead-in
   (37px at 1440 against the band's 84px). */
.proof-band:not(.sheet-section) + .sheet-section:not(.page-hero){
  padding-top:calc(var(--space-join) - var(--space-proof-pad));
}

/* two framed surfaces meeting (proof band -> CTA band on card-personalisation
   and scenarios). Neither internal padding may be squashed, so rather than
   stacking two hairlines with a void between them they share ONE datum line:
   84px of the band's tail, the shared rule, 72px of the CTA band's lead-in. */
.proof-band + .cta-band{margin-top:0;border-top:0}

/* framed CTA band -> plain section (the re-hosted closing related-strip on
   photon, photon/demo, photon/pricing and cards): the model had a rule only
   for .proof-band in this shape, so the section took the bare
   --space-section on top of the band's 72px tail - a 168px painted join at
   1440 against the 121px datum. Same remainder arithmetic as the proof-band
   counterpart above. */
.cta-band + .sheet-section:not(.page-hero){
  padding-top:calc(var(--space-join) - var(--space-cta-pad));
}
/* ...and in that shape the strip's own 28px lead-in + hairline would draw a
   second divider directly under the band's bottom rule - one datum line, not
   two. (The generic first-child rule below zeroes only the margin.) */
.cta-band + .sheet-section > .wrap > .related-strip:first-child{
  padding-top:0;border-top:0;
}

/* (the old `.cta-band + .wrap > .related-strip` margin rule died when cards,
   photon, photon/demo and photon/pricing re-hosted their closing strip in a
   veiled .sheet-section - a bare .wrap after the band was one of the three
   unpainted boxes that stacked into the 167px exposed-field band there) */
/* a section whose ONLY content is the strip (access-management,
   credential-management, visitor-management, piam-suite): the section's own
   lead-in already carries the join, so the strip's stand-alone margin would
   stack a second full rhythm on top of it (245px at 1440 before this rule).
   The strip keeps its margin where it closes a section that has other content
   above it - that is a separator inside a section, not a join. */
.sheet-section > .wrap > .related-strip:first-child{margin-top:0}

/* ==========================================================================
   3. HERO (.page-hero) - the field shown VIVID, a contrast scrim under the
   monumental left-datum type. Drop .page-hero on the page's first section.
   ========================================================================== */
.page-hero{
  position:relative;background:transparent;isolation:isolate;overflow:hidden;
  /* token-derived so the hero tracks the section rhythm (was a
     clamp(72px, 5vw + 40px, 128px) literal that stayed airy while the sections
     around it tightened): slightly airier on top than a body section, level
     with one at the bottom. Pages with their own hero calc (sectors,
     solutions, citadel, piam-suite, home) already derive from the same token.
     The hero -> section join itself is in the join model above (it measured
     205px on /trust before this work); the tail set here is what that model
     pins the four page-shortened heroes back up to. */
  padding-block:calc(var(--space-section) * 1.15) var(--space-section);
}
/* contrast scrim over the live gradient - keeps the headline + lede crisp
   wherever the field flows beneath them (left + bottom weighted to match the
   left-anchored hero composition) */
.page-hero::before{
  content:'';position:absolute;inset:0;z-index:0;pointer-events:none;
  background:
    var(--hero-scrim-x),
    var(--hero-scrim-y);
}
/* narrow widths: content is full-bleed, so the scrim must weight vertically
   (the left->right weighting no longer covers the wrapped lede over a bright
   field patch) */
@media (max-width:760px){
  .page-hero::before{
    background:var(--hero-scrim-m);
  }
}
.page-hero > *{position:relative;z-index:1}
/* left-anchored monument by default */
.page-hero .kicker{justify-content:flex-start}
/* the word-rise mask (.line{overflow:hidden} in components.v1.css) must never
   clip glyph descenders (the y/g/p tails) OR the amber dimension rule, which
   sits just below the baseline at bottom:-.1em. Enlarge the clip box downward
   and cancel it with a matching negative margin so vertical rhythm is unchanged
   (mirrors the homepage's .home-hero .display .line fix). */
.page-hero .h1-sheet .line,
.page-hero .display .line{padding-bottom:.2em;margin-bottom:-.2em}
/* ...and the same reserve on the INLINE axis: the amber rule deliberately
   overhangs the final word by .6em (right:-.6em on ::after), which the mask
   was clipping flush on every hero that ends its line with an .amber-rule.
   The padding + equal negative margin nets to zero layout width. */
.page-hero .h1-sheet .line,
.page-hero .display .line,
.page-hero .h1-sheet .kin-line{padding-inline-end:.6em;margin-inline-end:-.6em}

/* the amber dimension rule surveyed under a hero word/phrase:
   <span class="amber-rule">door.</span>  (drawn in on .in) */
.amber-rule{position:relative;display:inline-block}
.amber-rule::after{
  content:'';position:absolute;left:.02em;right:-.6em;bottom:-.1em;height:2px;
  background:var(--amber);transform:scaleX(0);transform-origin:left;
}
html.js .amber-rule::after{transition:transform 1s var(--ease-draw) .5s}
html.js .in .amber-rule::after,html.js .amber-rule.in::after{transform:scaleX(1)}
html.no-motion .amber-rule::after{transform:scaleX(1);transition:none}

/* ==========================================================================
   4. BRACKETED DRAFTING KICKER - the connective signature. The shared amber
   bar becomes amber braces in the machine register.
   ========================================================================== */
.kicker{
  display:inline-flex;align-items:center;gap:.5em;
  font-size:var(--t-label);
  font-weight:500;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);
}
/* flex:none keeps the brace from being squeezed when the label wraps */
.kicker::before{content:'{';width:auto;height:auto;background:none;color:var(--amber);font-weight:600;transform:translateY(-0.04em);flex:none} /* decorative brace - brand amber in both themes */
.kicker::after{content:'}';color:var(--amber);font-weight:600;transform:translateY(-0.04em)}
.kicker-n{color:var(--amber-ink);font-variant-numeric:tabular-nums}

/* ==========================================================================
   5. FLAT POSTER SURFACES - kill the shadow-stacks; hierarchy via hairline +
   space + a thin top sheen. Surfaces go opaque so the live field never bleeds
   through text. (--surface remapped opaque for dark pages; doc-light keeps
   its paper white via the higher-specificity body.doc-light rule in tokens.)
   ========================================================================== */
:root{--surface:#0f1218}

.slab{
  background:linear-gradient(180deg, rgba(255,255,255,.03), transparent 32%), var(--surface);
  box-shadow:none;border-radius:12px;
}
.slab::after{box-shadow:0 0 0 1px rgba(37,99,235,.25)}
.slab:hover{box-shadow:0 22px 52px -30px rgba(0,0,0,.8)}

/* G1.2 - pointer-tilt + specular sheen on poster cards. reveal.v1.js adds
   .is-tilt on pointerenter (fine pointers only, never under no-motion) and
   writes --rx/--ry (deg) + --gx/--gy (sheen %). Transform/opacity only; the
   sheen sits UNDER the card content (z-index lift) so it never washes text
   contrast. Not applied to the flip-board (.jy-card) or pricing cards. */
.slab{transition:transform .3s var(--ease-monument)}
/* !important + a snappy transition win over the higher-specificity reveal
   transform rule (html.js .slab.r). Scoped to the hover-only .is-tilt state
   (JS adds it on pointerenter, fine pointers only, never under no-motion), so
   the reveal animation itself is untouched. */
.slab.is-tilt{transform:perspective(900px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) !important;transition:transform .12s var(--ease-monument) !important;will-change:transform}
.slab::before{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;z-index:0;background:radial-gradient(circle at var(--gx,50%) var(--gy,50%),rgba(255,255,255,.07),transparent 46%);transition:opacity .25s var(--ease-monument)}
.slab.is-tilt::before{opacity:1}
.slab.is-tilt>*{position:relative;z-index:1}

.title-block{box-shadow:0 22px 52px -32px rgba(0,0,0,.8);border-radius:12px;overflow:hidden}
.tb-cell{background:var(--surface)}
.tb-cell:hover{background:#12161d}

.chip,.spec-rail,.sheet-thumb{background:var(--surface)}
.sheet-thumb{border-radius:12px}
.nav-drop{background:linear-gradient(180deg, rgba(255,255,255,.03), transparent 40%), var(--surface)}

/* ==========================================================================
   6. MOTION CRAFT - reveal varied by mass + arrival, premium micro-states.
   transform/opacity only (CLS 0); every animated thing reduced-motion gated.
   ========================================================================== */
/* faster + tighter + role-varied reveal; blur resolves early (overrides the
   shared .8s / translateY(18px) / blur(6px) / 120ms in components.v1.css) */
html.js .r{
  --rise:12px; --rblur:4px;
  transform:translateY(var(--rise)); filter:blur(var(--rblur));
  transition:
    opacity var(--t-mid) var(--ease-monument) calc(var(--i,0) * 64ms),
    transform var(--t-mid) var(--ease-monument) calc(var(--i,0) * 64ms),
    filter 300ms var(--ease-monument) calc(var(--i,0) * 64ms);
}
html.js .kicker.r, html.js .lede.r, html.js .h2-sheet.r, html.js h2.r{ --rise:8px; --rblur:3px }
/* heavy surfaces arrive with weight (expo, longer) */
html.js .title-block.r, html.js .slab.r, html.js .home-cta-panel.r, html.js .console.r{
  --rise:18px; --rblur:5px;
  transition-duration:var(--t-slab),var(--t-slab),360ms;
  transition-timing-function:var(--ease-expo);
}

/* G2.2 - scroll-driven reveal as PROGRESSIVE ENHANCEMENT. Where the browser
   supports scroll-driven timelines (Chromium 115+ / Safari 18; not yet Baseline,
   hence @supports), the .r reveal is driven off the main thread by the element's
   own view() timeline rather than the IO-fired transition. The IntersectionObserver
   engine in reveal.v1.js stays the UNIVERSAL FLOOR - no-JS, reduced-motion and
   unsupported browsers fall back to it. Excluded under :not(.no-motion).
   transform/opacity/filter only - no layout, no CLS. */
@supports (animation-timeline: view()) {
  /* :not(.no-view-anim) - after a client-side (SPA) navigation, Chromium can
     leave view() scroll-timeline animations stuck in an unpainted state on the
     freshly-mounted DOM. reveal.v1.js sets html.no-view-anim on the first client
     navigation, which drops this rule so the universal IntersectionObserver .in
     transition reveal (the floor) takes over for the rest of the session.
     :not(.reveal-safe) - the boot watchdog's forced-visible state (see
     components.v1.css REVEAL FAILSAFE): keyframed opacity would beat the
     failsafe's plain declarations in the cascade, so the animation must drop
     away entirely under it. */
  html.js:not(.no-motion):not(.no-view-anim):not(.reveal-safe) .r {
    animation: idw-reveal-in linear both;
    animation-timeline: view();
    animation-range: entry 8% entry 64%;
    transition: none;
  }
  /* PHONES FINISH THE REVEAL EARLY.
     The range above is scroll-distance, not time: the element is only fully
     sharp 64% of the way through its ENTRY phase. On a desktop that reads as
     weight. On a phone the entry phase is most of a tall viewport, so measured
     in Chrome at 390x844 content crossed the bottom edge at blur(0.6-4px) /
     opacity 0.09-0.86 and only finished sharpening as it climbed - which is
     exactly the owner's report: "text blurs and doesn't appear until near the
     top of the phone screen." Completing by 22% of entry keeps the arrival
     gesture while putting legible text on screen the moment it appears.
     Note this is the SCROLL-DRIVEN path, which is what modern mobile Safari
     takes; the IntersectionObserver floor is tuned separately in
     components.v1.css and reveal.v1.js. */
  @media (max-width: 700px) {
    html.js:not(.no-motion):not(.no-view-anim):not(.reveal-safe) .r {
      /* 22% still let a blurred element reach 14% up an 844px screen on /photon
         (mobile-dev-qa harness, WebKit) - about a line of body text readable
         while still soft. 15% holds everything to the bottom ~9%. */
      animation-range: entry 0% entry 8%;
    }
  }
}
@keyframes idw-reveal-in {
  from { opacity: 0; transform: translateY(var(--rise, 12px)); filter: blur(var(--rblur, 4px)); }
  to   { opacity: 1; transform: none; filter: none; }
}

/* magnetic CTA pull (magnetic.v1.js writes an inline transform on [data-magnetic]; fine pointers) */
.cta-pill,.cta-ghost,.nav-cta{transition:transform .26s var(--ease-out-soft), background .2s var(--ease-monument), box-shadow .2s var(--ease-monument), filter .2s var(--ease-monument)}
/* the arrow nudges with a tactile overshoot land (Stripe/Linear signature) */
.cta-pill svg{transition:transform .26s var(--ease-spring)}
@media (hover:hover) and (pointer:fine){ .cta-pill:hover svg{transform:translateX(3px)} }

/* nav-link hover = a SLIGHT amber GLOW + tint, never a hard line (client pref:
   "a slight orange glow not a deep orange line"). The static active-page amber
   tick (components.v1.css .nav-link::after via body[data-active]) stays as the
   "you are here" cue on regular links. */
.nav-link{position:relative;transition:color .2s var(--ease-monument),text-shadow .2s var(--ease-monument),box-shadow .2s var(--ease-monument)}
.nav-link:hover,.nav-link:focus-visible{color:var(--amber-ink);text-shadow:0 0 12px color-mix(in srgb, var(--amber-ink) 45%, transparent);box-shadow:0 0 18px -6px color-mix(in srgb, var(--amber-ink) 40%, transparent)}
/* bloom is a dark-theme phenomenon - no glow haze on paper */
html[data-theme="light"] .nav-link:hover,
html[data-theme="light"] .nav-link:focus-visible{text-shadow:none;box-shadow:none}

/* ==========================================================================
   7. REDUCED MOTION / SAVE-DATA - neutralise every new transition cleanly.
   ========================================================================== */
html.no-motion .cta-pill,html.no-motion .cta-ghost,html.no-motion .nav-cta,
html.no-motion .cta-pill svg,html.no-motion .nav-link{transition:none}
html.no-motion body::after{display:none}

/* ==========================================================================
   8. LIGHT THEME - the field RUNS in light too (owner decision, finesse
   pass 2): field.v1.js selects a paper palette from html[data-theme] via a
   shader uniform, the veil/scrim tokens carry light values, and the CSS
   --field-wash below remains the no-WebGL fallback. Dark-stage sections
   (data-stage="dark", tokens.v1.css) keep their own dark surfaces over it.
   The grain overlay stays live in both themes (uplift's body::before already
   themes its own opacity).
   ========================================================================== */
