/* ═══════════════════════════════════════════════════════════════════════════
   transition.css — the ash between pages, and the press that never waits.

   HIS WORDS, 2026-08-21: "when I switch tabs I want a ash animation, like
   smokey animation. and make everything smooth. to the max. insta response."

   Two jobs, and they are separate on purpose:

     1  THE ASH        a fixed veil of red-lit smoke and drifting embers that
                       blooms while the old page burns away and blows clear
                       while the new one forms out of it.
     2  INSTA RESPONSE press feedback that lands on pointerdown with no
                       transition to wait for, and no 300 ms tap delay.

   ★★★★★ NOT ONE PIXEL OF THIS IS WebGL, AND THAT IS THE POINT.
   A second WebGL context on this site gets FORCE-LOST, and a force-lost context
   keeps its canvas, keeps the painted flag it earned on its one good frame,
   logs at most a console WARNING, and screen-blends as a flat grey fog that
   reads as a design choice in every screenshot. haven.js owns the one context
   and this file never asks for another: the ash is gradients and three tiled
   SVG sprites, animated with transform and opacity only, so every frame of it
   is composited and none of it can take the scene down.

   ★ EVERY LAYER IS transform/opacity ONLY. No blur, no mask, no filter, no
     backdrop-filter: those force a full-screen render surface per frame, and
     the background already runs a half-resolution shader inside a 26 ms
     watchdog. The ash must cost the scene nothing.

   ★ THE VEIL IS display:none WHEN IDLE, not opacity:0. An element with a
     layout box is an element the rendered-proof gate measures — and a veil
     hanging over every page at opacity 0 is also one bad specificity edit away
     from covering the site.

   Sections
     1  the veil                    4  the ash sprites
     2  the keyframes               5  the content handover
     3  the phases                  6  insta response
                                    7  reduced motion
   ═══════════════════════════════════════════════════════════════════════════ */
/* ★ RE-COLOURED 2026-08-21 with the plate. He replaced the red-eyed animal
   with the purple one whose eye burns GOLD, so the ash burns gold where the
   eye's light reaches it and VIOLET down the flanks, which is what the
   photograph does. The motes keep their seeded positions and their prime
   pitches - only the fills moved, so every measurement in this file still
   describes the sheet it is written in. The cold ash went from a warm grey to
   a violet one for the same reason: a warm grey over a violet plate reads as
   dirt on the screen. */

/* ── 0. the View Transition API, and why it is not carrying this ─────────────
   transition.js CAN wrap the DOM swap in document.startViewTransition — the
   switch is `GDTransition.vt` and it is documented there. It ships OFF, and
   that is a measurement: the fence cost 74 ms per click for a guarantee the
   swap did not need, because the mutation is synchronous and the ash is
   already at full opacity across the frame it lands on. The number and the
   method are next to the switch.

   ★ AND THE ROOT SNAPSHOT IS SWITCHED OFF EITHER WAY.
   The UA stylesheet puts `view-transition-name: root` on :root, which makes
   the browser capture the whole SCROLLABLE DOCUMENT as a texture on both sides
   of every navigation. On these pages that is 1440 x 5000-odd pixels, twice,
   per click — and the scene it would capture is a fixed, full-viewport WebGL
   canvas, which is precisely the kind of element that smears in a root
   snapshot on a scrolled page. The ash overlay below already covers the
   handover at zero texture cost, so there is nothing for that capture to buy.

   With no captured element the transition has no pseudo-element tree; the
   pointer-events line is the second lock, so that even if a browser does
   materialise one it can never eat a click meant for the new page. */
:root { view-transition-name:none; }
::view-transition { pointer-events:none; }

/* ── 1. the veil ─────────────────────────────────────────────────────────────
   z-index 120 sits ABOVE the sticky chrome (90) and the profile menu (95) and
   BELOW the drawer (130) and seek (140): the ash covers the page you are
   leaving, never a dialog you have open. */
.ashveil {
  position:fixed; inset:0; z-index:120;
  pointer-events:none;                  /* the incoming page is clickable at once */
  display:none;                         /* ★ no layout box at all when idle */
  contain:layout paint style;
  /* the ash blooms from the animal's own pupil. transition.js copies the point
     haven.js MEASURED off the real plate box, so the light in the scene and the
     light in the ash can never drift apart; these two are only the fallback. */
  --ax:49%; --ay:29%;
}
.ashveil[data-phase] { display:block; }

.ashveil > i {
  position:absolute; left:0; right:0; top:-16%; bottom:-16%;
  display:block; opacity:0;
  transform:translate3d(0,0,0);
}

/* the lit smoke. The same arena the shader draws: banked up both flanks and
   across the floor, with one bloom at the pupil — never a centred flash.

   ★ AND NOT ONE mix-blend-mode ON ANY OF IT, WHICH IS A MEASUREMENT AND NOT A
     PREFERENCE. The instinct here is `screen`, to match the scene's own smoke
     canvas, and the first build used it on all four layers. Two things were
     wrong with it. The cheap one: .ashveil is a stacking context (fixed,
     z-index 120), and a stacking context ISOLATES blending - so `screen` was
     never reaching the photograph at all, only the char layer inside the veil.
     The expensive one: every blended layer forces the compositor to build a
     full-viewport render surface, and the median frame during a transition
     measured 50.0 ms with them and 33.4 ms without - the vsync floor, meaning
     not one dropped frame. Four surfaces, for a blend that was isolated
     anyway. The alphas below are raised to carry the same light on plain
     alpha compositing over a near-black page, which is what this page is. */
.ash-burn {
  background:
    radial-gradient(34% 30% at var(--ax) var(--ay), rgba(255,198,77,.60), rgba(255,176,32,0) 72%),
    radial-gradient(66% 54% at 4% 92%,  rgba(163,43,200,.50), rgba(0,0,0,0) 74%),
    radial-gradient(64% 52% at 97% 84%, rgba(163,43,200,.45), rgba(0,0,0,0) 74%),
    radial-gradient(130% 44% at 50% 106%, rgba(143,18,18,.56), rgba(0,0,0,0) 78%);
}

/* the char: the page receding into the dark before it comes apart. Plain black
   at low alpha on the normal blend — a "multiply" over a near-black page is a
   no-op that looks like it is working. */
.ash-char {
  background:
    radial-gradient(76% 60% at 50% 44%, rgba(4,3,4,0) 30%, rgba(4,3,4,.62) 100%),
    linear-gradient(180deg, rgba(4,3,4,.46) 0%, rgba(4,3,4,0) 28%,
                    rgba(4,3,4,0) 62%, rgba(4,3,4,.58) 100%);
}

/* ── 4. the ash sprites ──────────────────────────────────────────────────────
   Three seeded scatters, inline as data URIs so the site still downloads
   nothing from anyone, on three non-commensurate pitches (619 / 331 / 187, all
   prime) and drifting at three speeds. The parallax is what makes it read as
   ash IN THE AIR rather than a texture sliding over the glass; the prime
   pitches are what stop the three lattices ever lining up into a grid. Each
   sprite is authored AT ITS DISPLAY SIZE - an earlier pass drew all three at
   200 px and scaled them up, which blew the embers into 7 px blobs and put the
   bright layer's lattice on a 280 px pitch, five visible repeats across a
   desktop viewport.

   Seeded, never Math.random per frame: random-per-frame reads as noise, not as
   embers — the same law mark.js was built on.
   Plain alpha, no blend mode: see the note on .ash-burn. Measured. */
.ash-m1 { background-size:619px 619px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='619' height='619'%3E%3Cg fill='%23ffc64d'%3E%3Ccircle cx='375.3' cy='279.7' r='1.51' opacity='0.68'/%3E%3Ccircle cx='0.6' cy='211.4' r='1.05' opacity='0.68'/%3E%3Ccircle cx='27.1' cy='40.5' r='1.34' opacity='0.75'/%3E%3Ccircle cx='59.1' cy='415.1' r='1.5' opacity='0.50'/%3E%3Ccircle cx='326.3' cy='490.1' r='1.06' opacity='0.79'/%3E%3Ccircle cx='500' cy='108.4' r='1.77' opacity='0.69'/%3E%3Ccircle cx='62.6' cy='296.1' r='1.78' opacity='0.81'/%3E%3Ccircle cx='512.5' cy='83.9' r='0.91' opacity='0.68'/%3E%3C/g%3E%3Cg fill='%23ffb020'%3E%3Ccircle cx='9.6' cy='323.2' r='1.89' opacity='0.95'/%3E%3Ccircle cx='11.4' cy='294.2' r='2.15' opacity='0.76'/%3E%3Ccircle cx='347.5' cy='57.8' r='2.25' opacity='0.68'/%3E%3Ccircle cx='356.1' cy='136.4' r='1.13' opacity='0.85'/%3E%3Ccircle cx='153.9' cy='577' r='1.89' opacity='0.54'/%3E%3Ccircle cx='133.5' cy='71.4' r='1.02' opacity='0.47'/%3E%3Ccircle cx='337' cy='589.6' r='1.15' opacity='0.95'/%3E%3C/g%3E%3Cg fill='%23e8901a'%3E%3Ccircle cx='474.8' cy='421.3' r='1.18' opacity='0.73'/%3E%3Ccircle cx='427.8' cy='37.9' r='2.02' opacity='0.60'/%3E%3Ccircle cx='67.1' cy='59.3' r='2.02' opacity='0.50'/%3E%3Ccircle cx='490.2' cy='529.4' r='1.5' opacity='0.69'/%3E%3Ccircle cx='28.1' cy='302.6' r='2.26' opacity='0.70'/%3E%3Ccircle cx='227.8' cy='21.7' r='1.54' opacity='0.81'/%3E%3Ccircle cx='612.8' cy='67.8' r='1.12' opacity='0.91'/%3E%3Ccircle cx='151.4' cy='137.7' r='1.83' opacity='0.52'/%3E%3Ccircle cx='19.3' cy='602' r='1.45' opacity='0.50'/%3E%3Ccircle cx='211.7' cy='28.3' r='1.7' opacity='0.60'/%3E%3Ccircle cx='428.8' cy='111.5' r='1.43' opacity='0.48'/%3E%3C/g%3E%3C/svg%3E"); }
.ash-m2 { background-size:331px 331px; background-position:37px 11px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='331' height='331'%3E%3Cg fill='%23ffc64d'%3E%3Ccircle cx='172.7' cy='323.2' r='0.9' opacity='0.39'/%3E%3Ccircle cx='327.5' cy='166.2' r='1.04' opacity='0.43'/%3E%3Ccircle cx='261.2' cy='127.2' r='1.28' opacity='0.50'/%3E%3Ccircle cx='140.8' cy='218.5' r='1.54' opacity='0.37'/%3E%3Ccircle cx='142.5' cy='105.2' r='1.59' opacity='0.43'/%3E%3C/g%3E%3Cg fill='%23e8901a'%3E%3Ccircle cx='28.2' cy='8.7' r='1.21' opacity='0.27'/%3E%3Ccircle cx='232' cy='264.2' r='0.77' opacity='0.28'/%3E%3Ccircle cx='48.4' cy='123.3' r='1' opacity='0.38'/%3E%3Ccircle cx='265' cy='36.6' r='1' opacity='0.63'/%3E%3Ccircle cx='20.2' cy='107.3' r='1.55' opacity='0.51'/%3E%3Ccircle cx='301.9' cy='43.5' r='0.93' opacity='0.64'/%3E%3Ccircle cx='257.4' cy='117.7' r='1.51' opacity='0.47'/%3E%3Ccircle cx='207' cy='2.1' r='1.29' opacity='0.35'/%3E%3Ccircle cx='207.1' cy='10.1' r='1.08' opacity='0.33'/%3E%3C/g%3E%3Cg fill='%23a32bc8'%3E%3Ccircle cx='314.6' cy='271.9' r='1.25' opacity='0.54'/%3E%3Ccircle cx='231.1' cy='129' r='1.19' opacity='0.32'/%3E%3Ccircle cx='62.9' cy='82.9' r='1.49' opacity='0.46'/%3E%3Ccircle cx='1.7' cy='125.1' r='0.75' opacity='0.29'/%3E%3Ccircle cx='277.6' cy='89.7' r='0.97' opacity='0.42'/%3E%3Ccircle cx='237' cy='208.9' r='1.15' opacity='0.33'/%3E%3Ccircle cx='52.5' cy='153.6' r='0.92' opacity='0.47'/%3E%3Ccircle cx='206.3' cy='58.2' r='1.46' opacity='0.22'/%3E%3Ccircle cx='63' cy='139.9' r='0.73' opacity='0.55'/%3E%3C/g%3E%3Cg fill='%23c3b3c7'%3E%3Ccircle cx='84.4' cy='134.5' r='1.45' opacity='0.45'/%3E%3Ccircle cx='221.5' cy='9.1' r='0.92' opacity='0.26'/%3E%3Ccircle cx='149' cy='195.4' r='0.81' opacity='0.24'/%3E%3Ccircle cx='324.1' cy='36.3' r='1.01' opacity='0.67'/%3E%3Ccircle cx='18.5' cy='198.7' r='1.18' opacity='0.58'/%3E%3Ccircle cx='161.1' cy='275.4' r='1.2' opacity='0.25'/%3E%3Ccircle cx='28.3' cy='266.6' r='0.75' opacity='0.63'/%3E%3C/g%3E%3C/svg%3E"); }
.ash-m3 { background-size:187px 187px; background-position:71px 53px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='187' height='187'%3E%3Cg fill='%23c3b3c7'%3E%3Ccircle cx='4.2' cy='71.3' r='0.98' opacity='0.16'/%3E%3Ccircle cx='109.8' cy='36.8' r='0.53' opacity='0.44'/%3E%3Ccircle cx='86.7' cy='16.4' r='0.65' opacity='0.30'/%3E%3Ccircle cx='151.2' cy='81.1' r='0.54' opacity='0.31'/%3E%3Ccircle cx='118.8' cy='56.3' r='0.72' opacity='0.31'/%3E%3Ccircle cx='184.5' cy='40' r='0.51' opacity='0.20'/%3E%3Ccircle cx='154.4' cy='18.1' r='0.71' opacity='0.38'/%3E%3Ccircle cx='186.9' cy='58.3' r='0.48' opacity='0.36'/%3E%3C/g%3E%3Cg fill='%238b8691'%3E%3Ccircle cx='68.5' cy='5.3' r='0.79' opacity='0.44'/%3E%3Ccircle cx='176' cy='98.7' r='0.63' opacity='0.46'/%3E%3Ccircle cx='165.3' cy='170.2' r='0.82' opacity='0.35'/%3E%3Ccircle cx='102.9' cy='148.1' r='0.69' opacity='0.23'/%3E%3Ccircle cx='46' cy='0.1' r='0.6' opacity='0.23'/%3E%3Ccircle cx='178.1' cy='181.6' r='1' opacity='0.43'/%3E%3Ccircle cx='166.1' cy='91.6' r='0.62' opacity='0.35'/%3E%3Ccircle cx='115.3' cy='136.6' r='1.02' opacity='0.15'/%3E%3Ccircle cx='4.2' cy='15.8' r='0.83' opacity='0.27'/%3E%3Ccircle cx='28.5' cy='158.2' r='0.86' opacity='0.39'/%3E%3Ccircle cx='86.3' cy='70.5' r='0.67' opacity='0.39'/%3E%3Ccircle cx='186.7' cy='166.7' r='0.89' opacity='0.22'/%3E%3Ccircle cx='186.7' cy='123.6' r='0.89' opacity='0.27'/%3E%3Ccircle cx='147.3' cy='12.9' r='0.94' opacity='0.38'/%3E%3C/g%3E%3Cg fill='%235d5566'%3E%3Ccircle cx='123.3' cy='93.3' r='0.93' opacity='0.21'/%3E%3Ccircle cx='79' cy='147.4' r='0.51' opacity='0.22'/%3E%3Ccircle cx='39.8' cy='161.9' r='0.45' opacity='0.21'/%3E%3Ccircle cx='114.4' cy='179.2' r='0.74' opacity='0.17'/%3E%3Ccircle cx='174.6' cy='185.7' r='0.46' opacity='0.23'/%3E%3Ccircle cx='3.2' cy='1.1' r='0.69' opacity='0.30'/%3E%3Ccircle cx='52' cy='72' r='0.78' opacity='0.38'/%3E%3Ccircle cx='103.4' cy='39.4' r='0.57' opacity='0.28'/%3E%3Ccircle cx='107.1' cy='186.6' r='0.74' opacity='0.26'/%3E%3Ccircle cx='27.6' cy='38.4' r='0.56' opacity='0.19'/%3E%3Ccircle cx='65.3' cy='154.9' r='0.49' opacity='0.19'/%3E%3Ccircle cx='98.9' cy='166.2' r='0.48' opacity='0.26'/%3E%3C/g%3E%3C/svg%3E"); }

/* ── 2. the keyframes ────────────────────────────────────────────────────────
   ★ EVERY "out" KEYFRAME ENDS ON EXACTLY THE VALUES ITS "in" KEYFRAME STARTS
     FROM. The two phases are separate animations with a DOM swap between them,
     and if they do not meet, the ash visibly jumps at the handover — the one
     frame the reader is guaranteed to be looking at. transition.js also holds
     the swap until the out phase has run its full 120 ms for the same reason:
     an already-prefetched page would otherwise flip at frame one and snap the
     ash to full before fading it. */
@keyframes gd-burn-out  { from { opacity:0;   transform:translate3d(0,0,0)     scale(1.00); }
                          to   { opacity:.92; transform:translate3d(0,-10px,0) scale(1.06); } }
@keyframes gd-burn-in   { from { opacity:.92; transform:translate3d(0,-10px,0) scale(1.06); }
                          to   { opacity:0;   transform:translate3d(0,-40px,0) scale(1.16); } }

@keyframes gd-char-out  { from { opacity:0;   } to { opacity:.72; } }
@keyframes gd-char-in   { from { opacity:.72; } to { opacity:0;   } }

@keyframes gd-m1-out { from { opacity:0;   transform:translate3d(0,26px,0); }
                       to   { opacity:.95; transform:translate3d(0,0,0); } }
@keyframes gd-m1-in  { from { opacity:.95; transform:translate3d(0,0,0); }
                       to   { opacity:0;   transform:translate3d(-14px,-118px,0); } }

@keyframes gd-m2-out { from { opacity:0;   transform:translate3d(0,18px,0); }
                       to   { opacity:.88; transform:translate3d(0,0,0); } }
@keyframes gd-m2-in  { from { opacity:.88; transform:translate3d(0,0,0); }
                       to   { opacity:0;   transform:translate3d(9px,-78px,0); } }

@keyframes gd-m3-out { from { opacity:0;   transform:translate3d(0,10px,0); }
                       to   { opacity:.80; transform:translate3d(0,0,0); } }
@keyframes gd-m3-in  { from { opacity:.80; transform:translate3d(0,0,0); }
                       to   { opacity:0;   transform:translate3d(-5px,-46px,0); } }

/* ── 3. the phases ───────────────────────────────────────────────────────────
   The ash rises fast and HOLDS. If the next page is not in hand yet it simply
   stays at full — a thick ash cloud reads as intent, where a half-faded veil
   reads as a broken page. */
.ashveil[data-phase="out"] > i { animation-duration:120ms; animation-fill-mode:forwards;
                                 animation-timing-function:cubic-bezier(.32,.02,.28,1); }
.ashveil[data-phase="in"]  > i { animation-fill-mode:forwards;
                                 animation-timing-function:cubic-bezier(.22,.61,.36,1); }

.ashveil[data-phase="out"] .ash-burn { animation-name:gd-burn-out; }
.ashveil[data-phase="out"] .ash-char { animation-name:gd-char-out; }
.ashveil[data-phase="out"] .ash-m1   { animation-name:gd-m1-out; }
.ashveil[data-phase="out"] .ash-m2   { animation-name:gd-m2-out; animation-duration:135ms; }
.ashveil[data-phase="out"] .ash-m3   { animation-name:gd-m3-out; animation-duration:150ms; }

.ashveil[data-phase="in"] .ash-burn { animation-name:gd-burn-in; animation-duration:300ms; }
.ashveil[data-phase="in"] .ash-char { animation-name:gd-char-in; animation-duration:220ms; }
.ashveil[data-phase="in"] .ash-m1   { animation-name:gd-m1-in;   animation-duration:520ms; }
.ashveil[data-phase="in"] .ash-m2   { animation-name:gd-m2-in;   animation-duration:440ms; }
.ashveil[data-phase="in"] .ash-m3   { animation-name:gd-m3-in;   animation-duration:380ms; }

/* the compositor hint is added only while it is true. A permanent will-change
   is a permanent extra layer, and this veil is idle for 99.9% of the visit. */
.ashveil[data-phase] > i { will-change:opacity, transform; }

/* ── 5. the content handover ─────────────────────────────────────────────────
   The CONTENT burns away; the chrome and the scene do not. The topbar stays
   where it is, the animal keeps watching, and only what you were reading comes
   apart — which is the whole reason this is a same-document swap and not a page
   load with a picture over it. */
@keyframes gd-leave  { from { opacity:1; transform:translate3d(0,0,0); }
                       to   { opacity:0; transform:translate3d(0,-9px,0); } }
@keyframes gd-arrive { from { opacity:0; transform:translate3d(0,12px,0); }
                       to   { opacity:1; transform:translate3d(0,0,0); } }

body[data-gdnav="out"] #main, body[data-gdnav="out"] .foot {
  animation:gd-leave 120ms cubic-bezier(.4,0,1,1) forwards;
  will-change:opacity, transform;
}
body[data-gdnav="in"] #main, body[data-gdnav="in"] .foot {
  animation:gd-arrive 260ms cubic-bezier(.16,.84,.44,1) forwards;
  will-change:opacity, transform;
}

/* ── 6. insta response ───────────────────────────────────────────────────────
   ★ touch-action:manipulation kills the ~300 ms double-tap wait a phone puts
     in front of EVERY tap. It is the single largest "this site feels slow"
     defect available on a touch device and it costs one line.
     It is set on CONTROLS ONLY: map.css needs touch-action:none on the map
     stage to own its own drag, and a blanket rule here would take that away.

   ★ AND THE SELECTOR LIST IS FIVE, NOT THIRTEEN — WHICH IS A COST, NOT A
     TIDINESS PREFERENCE — THOUGH THE REASON IT COST SO MUCH IS NOW FIXED.

     WHEN THIS WAS MEASURED, haven.js wrote --px, --py and --eye onto :root
     every single frame. A custom property set on the ROOT invalidates
     inherited style for the WHOLE TREE, so every rule that exists anywhere on
     this site was re-matched against every element tens of times a second, and
     a stylesheet's selector count stopped being free.

     Measured on an idle front page over 3 s, paired A/B in one browser, this
     sheet disabled against enabled:
         0.400 s -> 0.528 s   the first draft, thirteen selectors twice over
         0.492 s -> 0.523 s   this version, five

     ★ SINCE THEN THE ROOT WRITE ITSELF WAS FIXED: those three properties are
     now written on `.deep`, whose own children are their only readers, so the
     blast radius is the scene rather than the document. **The numbers above
     were taken under the old behaviour and are kept as the record of why this
     list is five** — they are not a current measurement of this sheet. The
     short list stays because it is still correct and costs nothing; if anyone
     re-measures, expect both figures to fall.
     ★ The lesson survives the fix: a per-frame root write makes EVERY sheet
     look expensive, so trimming selectors "helps" and the real cause goes
     unnamed. Find who writes to :root before blaming a stylesheet.
     `a` and `button` already cover .btn, .act, .tool, .seek-row, .brand, .skip
     and every nav and drawer link, so naming them individually bought nothing
     but work. `summary` came off entirely: there is not one <details> element
     on any of the seventeen pages. The residual +0.031 s is the sheet itself;
     the reason it is charged per frame at all belongs to the root writes, and
     that is named in the report as OWED rather than fixed from here. */
a, button, select, .chip, .switch {
  touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
}

/* ★ THE PRESS HAS NO TRANSITION TO WAIT FOR. transition:none on :active puts
   the pressed state on screen on the very next frame; the RELEASE keeps
   whatever easing the theme gave it. Feedback must never queue behind work.
   On an inline link in running prose a transform is a no-op by definition, so
   this reaches every control and disturbs no sentence. */
a:active, button:active, .chip:active, .switch:active {
  transform:translate3d(0,1px,0);
  transition:none;
}

/* and the same state for a finger, which cannot rely on :active landing at all
   on iOS. transition.js sets this on pointerdown — BEFORE a click event
   exists, and before a single byte of the next page has been asked for. */
.gd-press { transform:translate3d(0,1px,0) !important; transition:none !important; }
a.gd-press { color:var(--ember-ink); }

/* the link you are actually going to. It lights while the ash builds, so the
   press and the destination read as one gesture.
   ★ --ember-ink is an INK token (6.1:1 on the panel, "live labels, hot links"
     in the ladder check_contrast enforces). The PAINT tokens — --ember,
     --ember-deep, --ember-char — never carry a word anywhere in this file. */
.gd-going { color:var(--ember-ink) !important; }

/* ── 7. reduced motion ───────────────────────────────────────────────────────
   ★★★★ A REDUCED-MOTION READER MAY ONLY EVER GET ONE FRAME, so nothing here is
   allowed to arrive late or to depend on an animation ending. The engine in
   transition.js does not arm at all for them — the link is a plain link and the
   browser navigates — and this block is the second lock on the same door: even
   if something did set the attributes there would be no veil and no fade.
   The press feedback SURVIVES, because a press is not motion: it is the answer
   to a question the reader just asked. It becomes colour instead of movement. */
@media (prefers-reduced-motion:reduce) {
  .ashveil { display:none !important; }
  body[data-gdnav] #main, body[data-gdnav] .foot {
    animation:none !important; opacity:1 !important; transform:none !important;
    will-change:auto !important;
  }
  a:active, button:active, .chip:active, .switch:active, .gd-press {
    transform:none !important; color:var(--ember-ink);
  }
}
