/* Refuelia — motion.css — RF-MOTION (5 September 2026)
 *
 * One sheet for every piece of motion on the site, so it can be reasoned about
 * as one thing and switched off as one thing. Rules that hold everywhere in it:
 *
 *   - only transform, opacity and SVG stroke properties animate: nothing that
 *     lays out, nothing that paints text, nothing the map's own frames wait on;
 *   - every loop is finite. A pulse plays twice and stops. A route draws once.
 *     The idle state of every scene is STILL;
 *   - nothing here carries information. Text, links, pins-as-data and the
 *     structured data are all in the HTML; this file only moves what is already
 *     there, and a browser that ignores it shows the finished picture;
 *   - prefers-reduced-motion switches the whole file to that finished picture;
 *   - every "start hidden" rule is gated on html.rf-motion-on, which a one-line
 *     script in <head> sets before first paint (and only when motion is not
 *     reduced) and drops again if motion.js has not arrived within 4 s. So
 *     no JavaScript, blocked JavaScript, or a failed download all give the
 *     same page: the finished picture, nothing missing.
 *
 * Components (the class is the API):
 *   .rf-scroll-step    activates (.is-in) once when it enters the viewport
 *   .rf-route-draw     an SVG path drawn by --rf-progress (0..1) or by .is-in
 *   .rf-journey        the vertical road down /how-to-use with a travelling marker
 *   .rf-hero-scene     the staged hero sequence, played once (.rf-play);
 *                      on /how-to-use it is .rf-road-scene, a road with three pins
 *   .rf-pin-enter      a pin arriving: fade + scale, 220 ms, once
 *   .rf-pin-pulse      the selected pin: two soft rings, then still
 *   .rf-ripple         one ring when a driver confirmation lands
 *   .rf-data-flow      four sources → one pin, particles on CSS motion paths
 *   .rf-micro          the small scene inside each "four jobs" panel
 */

/* ---------- tokens ------------------------------------------------------- */
:root {
  /* RF-ONE-CLOCK-FOR-MOTION (22 September 2026): this WAS a fourth curve,
     and it happened to be the enter curve. It is that token now, so the
     scenes here and the surfaces in shell.css decelerate identically. */
  --rf-m-ease: var(--rf-ease-enter, cubic-bezier(.22,.61,.36,1));
  --rf-m-glow: rgba(255,201,40, .55);
  --rf-m-road: rgba(146, 175, 215, .22);
  --rf-m-road-lit: rgba(255,201,40, .85);
}
/* RF-MOTION-IN-DAYLIGHT (8 September 2026). Every value above was picked
   against a navy page: a pale blue road that reads as chalk on dark, a neon
   mint that reads as a lit lane, and a glow that only exists because there is
   something dark for it to sit on. /how-to-use, /guides and /about run on the
   light palette now, and there all three were wrong in the same measurable way
   — the road fell to rgba(146,175,215,.22) over #F4F7FB, near invisible, while
   the lit lane became a highlighter stripe. The scenes are not redrawn; the
   three tokens they are drawn from are restated for daylight, so the dark
   theme on the other ninety pages is untouched. */
html[data-theme="light"] {
  --rf-m-glow: rgba(176,128,0, .30);
  --rf-m-road: rgba(20, 40, 70, .18);
  --rf-m-road-lit: rgba(176,128,0, .85);
}
/* and the step card's "you have arrived" mark is a border, not a green bloom:
   on a white card the bloom was the loudest object in the section */
html[data-theme="light"] .rf-scroll-step.is-in {
  box-shadow: 0 1px 2px rgba(20, 40, 70, .06), 0 18px 40px -28px rgba(20, 40, 70, .26);
}

/* ---------- scroll steps ------------------------------------------------- */
.rf-scroll-step { transition: box-shadow .5s var(--rf-m-ease), border-color .5s var(--rf-m-ease); }
.rf-scroll-step.is-in {
  border-color: color-mix(in srgb, var(--brand, #FFC928) 45%, var(--line, #223));
  box-shadow: 0 0 0 1px rgba(255,201,40, .18), 0 18px 48px -26px rgba(255,201,40, .45);
}

/* ---------- route drawing ------------------------------------------------ */
.rf-route-draw path,
.rf-route-draw polyline {
  stroke-dasharray: 1;
  stroke-dashoffset: calc(1 - var(--rf-progress, 0));
  transition: stroke-dashoffset .12s linear;
}
/* A line being drawn, not a surface arriving: its own curve and its own band. */
.rf-route-draw.is-in path,
.rf-route-draw.is-in polyline { --rf-progress: 1;
  transition: stroke-dashoffset var(--rf-motion-route, 1.6s) var(--rf-ease-route, cubic-bezier(.65,0,.35,1)); }

/* ---------- the journey down /how-to-use --------------------------------- */
.rf-journey { position: relative; }
.rf-journey-road { display: none; }
.rf-motion-on .rf-journey-road { display: block; }
/* The road lives in the left gutter, outside .wrap, above the section
   backgrounds and never under any text; it cannot be clicked or selected. */
.rf-journey-road {
  position: absolute; left: max(12px, calc(50% - 640px)); top: 0; bottom: 0;
  width: 28px; pointer-events: none; z-index: 2;
}
.rf-journey-road svg { width: 28px; height: 100%; overflow: visible; }
.rf-journey-road .road { stroke: var(--rf-m-road); stroke-width: 2; fill: none; stroke-dasharray: 3 9; stroke-linecap: round; }
.rf-journey-road .lit  { stroke: var(--rf-m-road-lit); stroke-width: 2.5; fill: none; stroke-linecap: round;
  stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--rf-progress, 0)); transition: stroke-dashoffset .1s linear; }
.rf-journey-car {
  position: absolute; left: 14px; top: calc(var(--rf-progress, 0) * 100%);
  width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%;
  background: var(--brand, #FFC928);
  box-shadow: 0 0 0 4px rgba(255,201,40, .18), 0 0 18px var(--rf-m-glow);
  transition: top .1s linear;
}
@media (max-width: 899px) { .rf-motion-on .rf-journey-road { display: none; } }

/* ---------- hero scene: the road on /how-to-use --------------------------- */
.rf-road-scene { display: block; width: 100%; margin: 0 auto; }
.rf-road-scene svg { display: block; width: 100%; height: auto; overflow: visible; }
/* RF-ATLAS-GROUND. The ground is printed, not blocked in. Everything below the
   route is a hairline or a hatch so the three station marks stay the loudest
   thing in the frame — the old version filled four rounded rectangles at 5%
   ink, which is both invisible and, at that size, unmistakably a placeholder.
   Three weights are used on purpose: .25 for the fine graticule, .5–.6 for
   contours and hatching, 1 for the coarse grid and the margin furniture. */
.rf-road-scene .built { fill: url(#rfHeroHatch); }
.rf-road-scene .builtline { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 11%, transparent); stroke-width: .7; }
.rf-road-scene .hatch { stroke: color-mix(in srgb, var(--ink, #eef3ff) 9%, transparent); stroke-width: .5; }
.rf-road-scene .park { fill: color-mix(in srgb, var(--brand, #FFC928) 6%, transparent); stroke: color-mix(in srgb, var(--brand, #FFC928) 22%, transparent); stroke-width: .7; }
.rf-road-scene .cont { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 10%, transparent); stroke-width: .6; }
.rf-road-scene .water { fill: none; stroke: color-mix(in srgb, var(--blue, #7ab6ff) 13%, transparent); stroke-width: 20; stroke-linecap: round; }
.rf-road-scene .bank { fill: none; stroke: color-mix(in srgb, var(--blue, #7ab6ff) 30%, transparent); stroke-width: .7; }
/* The carriageway is drawn the atlas way: a casing hairline, a solid body, a
   broken centreline. The first attempt stroked the verge with the hatch pattern
   at 46 units and it read as a striped wall across the whole frame — the hatch
   belongs to built-up ground, not to the road. */
.rf-road-scene .verge { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 20%, transparent); stroke-width: 38; stroke-linecap: round; }
.rf-road-scene .tick { stroke: color-mix(in srgb, var(--ink, #eef3ff) 34%, transparent); stroke-width: 1; }
.rf-road-scene .km { fill: color-mix(in srgb, var(--ink, #eef3ff) 42%, transparent); font-size: 10px; text-anchor: middle; }
.rf-road-scene .ref { fill: color-mix(in srgb, var(--ink, #eef3ff) 26%, transparent); font-size: 10px; letter-spacing: .08em; }
.rf-road-scene .sub { fill: color-mix(in srgb, var(--ink, #eef3ff) 30%, transparent); font-size: 9.5px; letter-spacing: .1em; }
.rf-road-scene .scale path { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 40%, transparent); stroke-width: 1; }
.rf-road-scene .scale rect { fill: color-mix(in srgb, var(--ink, #eef3ff) 28%, transparent); }
/* RF-HERO-ROAD-2: a piece of map. The grid and side streets are the quietest
   things on it; the route and the three stations are the loudest; the status
   cards are the map's own pin cards, anchored by a stem. */
/* RF-A-MAP-IS-NOT-A-THEME (8 September 2026). Every colour in this scene used to
   be a literal rgba(146,175,215,…) — a pale blue-grey chosen to read as chalk on
   a navy page. On the daylight pages the whole map went to fog: the road, the
   water and the side streets all landed within four per cent of each other and
   of the paper, and a screenshot showed one washed diagonal with pins floating
   on it. They are mixed from --ink and --blue now, so the map inverts with the
   theme and keeps its own order: paper, blocks, streets, road, water, route. */
.rf-road-scene .bg { fill: color-mix(in srgb, var(--surface, #0e1726) 92%, transparent); stroke: var(--line-2, #2a3a5c); stroke-opacity: .55; }
.rf-road-scene .gridfill { fill: url(#rfHeroGrid); }
.rf-road-scene .gridfill.fine { fill: url(#rfHeroFine); }
.rf-road-scene .grid { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 9%, transparent); stroke-width: 1; }
.rf-road-scene .grid2 { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 4%, transparent); stroke-width: .25; }
.rf-road-scene .side { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 7%, transparent); stroke-width: 14; stroke-linecap: round; }
.rf-road-scene .road { fill: none; stroke: var(--surface, #0e1726); stroke-width: 32; stroke-linecap: round; }
.rf-road-scene .mid { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 28%, transparent); stroke-width: 2; stroke-dasharray: 10 12; stroke-linecap: round; }
.rf-road-scene .lit { fill: none; stroke: var(--rf-m-road-lit); stroke-width: 5; stroke-linecap: round; stroke-dasharray: 1; }
.rf-motion-on .rf-road-scene .lit { stroke-dashoffset: 1; }
/* RF-ONE-SWEEP. A short bright segment runs the length of the road once, after
   the route has drawn — the map reading itself, the way the product reads a
   road before it answers. One pass, never a loop: a light that keeps moving is
   traffic, and nothing on this map is traffic. Invisible without the script,
   because it starts and ends at zero opacity. */
.rf-road-scene .sweep { display: none; fill: none; stroke: var(--rf-m-road-lit); stroke-width: 4.5;
  stroke-linecap: round; stroke-dasharray: 130 870; stroke-dashoffset: 1000; }
.rf-motion-on .rf-road-scene .sweep { display: block; opacity: 0; }
.rf-road-scene .nb { fill: color-mix(in srgb, var(--ink, #eef3ff) 24%, transparent); }
.rf-road-scene .dest path { fill: none; stroke: var(--ink-3, #8ea0bf); stroke-width: 2; stroke-linejoin: round; }
.rf-road-scene .dest path + path { fill: rgba(142, 160, 191, .35); stroke-width: 1.5; }
.rf-road-scene .you { fill: var(--blue, #7ab6ff); }
.rf-road-scene .you-ring, .rf-road-scene .hp1-ring { fill: none; stroke-width: 1.5; opacity: 0; transform-origin: center; transform-box: fill-box; }
.rf-road-scene .you-ring { stroke: var(--blue, #7ab6ff); }
.rf-road-scene .hp1-ring { stroke: var(--brand, #FFC928); }
.rf-road-scene .hp { transform-origin: 50% 100%; transform-box: fill-box; filter: drop-shadow(0 2px 3px rgba(2, 6, 16, .5)); }
.rf-motion-on .rf-road-scene .hp { opacity: 0; }
.rf-road-scene .t { fill: var(--ink-3, #8ea0bf); font: 500 11px/1 'IBM Plex Mono', ui-monospace, monospace; }
.rf-road-scene .tag rect { fill: color-mix(in srgb, var(--surface, #0e1726) 94%, transparent); stroke: var(--line-2, #2a3a5c); filter: drop-shadow(0 4px 10px rgba(2, 6, 16, .45)); }
.rf-road-scene .tag .stem { fill: none; stroke: var(--line-2, #2a3a5c); stroke-width: 1.5; }
.rf-road-scene .tag .c1 { fill: var(--brand, #FFC928); font: 600 13px/1 'IBM Plex Sans', system-ui, sans-serif; }
.rf-road-scene .tag .c1.amber { fill: var(--amber, #f4b25a); }
.rf-road-scene .tag .c1.grey { fill: var(--ink-2, #c4cfe3); }
.rf-road-scene .tag .c1b { fill: var(--ink-2, #c4cfe3); font-weight: 600; }
.rf-road-scene .tag .c2 { fill: var(--ink-3, #8ea0bf); font: 500 11px/1 'IBM Plex Mono', ui-monospace, monospace; }
.rf-road-scene .tag { transform-origin: 0 50%; transform-box: fill-box; }
.rf-motion-on .rf-road-scene .tag, .rf-motion-on .rf-road-scene .lbl { opacity: 0; }
/* The sequence, once: you (0.2 s) → the route draws (0.6–2.0 s) → the first
   station and its card (1.5 / 1.9 s) → the second (2.5 / 2.8 s) → the third
   (3.2 / 3.5 s) → one sweep down the whole road (4.0 s). Five seconds, then
   still. Every delay below is a wait, not a repeat: nothing here loops. */
.rf-hero-scene.rf-play .you-ring { animation: rfRingOnce 1.4s ease-out .2s 2; }
.rf-hero-scene.rf-play .lbl0 { animation: rfRise .4s var(--rf-m-ease) .2s forwards; }
.rf-hero-scene.rf-play .lit { animation: rfDraw 1.4s var(--rf-m-ease) .6s forwards; }
.rf-hero-scene.rf-play .hp { animation: rfPinIn .5s var(--rf-m-ease) forwards; }
.rf-hero-scene.rf-play .hp1 { animation-delay: 1.5s; }
.rf-hero-scene.rf-play .hp2 { animation-delay: 2.5s; }
.rf-hero-scene.rf-play .hp3 { animation-delay: 3.2s; }
.rf-hero-scene.rf-play .hp1-ring { animation: rfRingOnce 1.6s ease-out 1.9s 2; }
.rf-hero-scene.rf-play .tag { animation: rfRise .45s var(--rf-m-ease) forwards; }
.rf-hero-scene.rf-play .tag1 { animation-delay: 1.9s; }
.rf-hero-scene.rf-play .tag2 { animation-delay: 2.8s; }
.rf-hero-scene.rf-play .tag3 { animation-delay: 3.5s; }
.rf-hero-scene.rf-play .sweep { animation: rfSweep 2.2s cubic-bezier(.38,0,.5,1) 4s 1; }
@keyframes rfSweep {
  0%   { stroke-dashoffset: 1000; opacity: 0; }
  10%  { opacity: .95; }
  80%  { opacity: .95; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}

/* ---------- pins ---------------------------------------------------------- */
/* Live map. The badge is a teardrop made by rotate(45deg) in site.css, so its
   keyframes keep that rotation. A pin arrives once; the selected pin says so
   twice and then stands still; a confirmation lands with one ring; a route
   fades in instead of "drawing" — its dotted stroke would crawl if the dash
   offset moved, and nothing on the map should look like traffic that is not. */
.rf-pin-enter { animation: rfPinIn .22s var(--rf-m-ease) both; }
.leaflet-marker-icon .pin-badge { animation: rfBadgeIn .22s var(--rf-m-ease) both; }
/* RF-2.0 FAULT FOUND 22 September 2026: the ring pin (.pin-ring, the one pin
   both maps draw) also carries .pin-badge, so the line above held every pin on
   /map at rotate(45deg) — `both` keeps the last keyframe for ever. The teardrop
   came out lying on its side, tail pointing south-west. The ring pin is an
   upright SVG and arrives by scale alone. */
.leaflet-marker-icon .pin-badge.pin-ring { animation: rfMkUp .2s var(--rf-m-ease) backwards; transform-origin: 50% 92%; }
@keyframes rfMkUp { from { opacity: 0; transform: translateY(-6px) scale(.8); } }
.rf-pin-pulse, .leaflet-marker-icon .pin-badge.is-selected { animation: rfPinPulse 1.4s ease-out 2; }
.leaflet-marker-icon .pin-badge.live-avail { animation: pinBadgePulse 1.8s ease-in-out 3; }   /* was infinite */
.leaflet-marker-icon .pin-badge.live-avail.is-selected { animation: rfPinPulse 1.4s ease-out 2; }
.rf-ripple, .leaflet-marker-icon .pin-badge.rf-ripple { animation: rfRipple .9s ease-out 1; }
.leaflet-overlay-pane path.rf-route-in { animation: rfFadeIn .6s var(--rf-m-ease) both; }

/* ---------- data flow (about: four inputs, one pin) ---------------------- */
.rf-data-flow { position: relative; display: block; width: 100%; max-width: 720px; margin: 22px auto 0; }
.rf-data-flow svg { width: 100%; height: auto; overflow: visible; }
.rf-data-flow .lane { fill: none; stroke: var(--rf-m-road); stroke-width: 1.5; stroke-dasharray: 3 7; }
.rf-data-flow .lane-lit { fill: none; stroke: var(--rf-m-road-lit); stroke-width: 1.5; stroke-dasharray: 1; opacity: .8; }
.rf-motion-on .rf-data-flow .lane-lit { stroke-dashoffset: 1; }
.rf-data-flow .node { fill: color-mix(in srgb, var(--surface-2, #16233c) 92%, transparent); stroke: var(--line, #2a3a5c); stroke-width: 1; }
.rf-data-flow .node-t { fill: var(--ink, #eef3ff); font: 600 11px/1 'IBM Plex Sans', system-ui, sans-serif; text-anchor: middle; }
.rf-data-flow .dot { fill: var(--brand, #FFC928); opacity: 0; offset-rotate: 0deg; }
.rf-data-flow .pin { transform-origin: 50% 100%; }
.rf-motion-on .rf-data-flow .pin { opacity: .55; }
.rf-data-flow .prov { fill: var(--ink-3, #8ea0bf); font: 500 10.5px/1 'IBM Plex Mono', ui-monospace, monospace; text-anchor: middle; }
.rf-data-flow.is-in .lane-lit { animation: rfDraw 1.4s var(--rf-m-ease) forwards; }
.rf-data-flow.is-in .lane-lit:nth-of-type(2) { animation-delay: .15s; }
.rf-data-flow.is-in .lane-lit:nth-of-type(3) { animation-delay: .3s; }
.rf-data-flow.is-in .dot { animation: rfTravel 1.5s var(--rf-m-ease) forwards; }
.rf-data-flow.is-in .dot.d2 { animation-delay: .15s; }
.rf-data-flow.is-in .dot.d3 { animation-delay: .3s; }
.rf-data-flow.is-in .pin { animation: rfSettle .6s var(--rf-m-ease) 1.5s forwards, rfPinPulse 1.4s ease-out 1.8s 1; }
.rf-motion-on .rf-data-flow .prov { opacity: 0; }
.rf-data-flow.is-in .prov { animation: rfRise .5s var(--rf-m-ease) 2.1s forwards; }

/* ---------- world map label (about hero) --------------------------------- */
/* RF-NO-ROUTE-ON-THE-WORLD-MAP (6 September 2026): the decorative route that
   stood here - stroke animation, five nodes, five rings - is gone at Alex's
   instruction. A made-up line laid over countries coloured from real coverage
   data reads as data. The stage and its label remain; the map is the coverage. */
.rf-world-label { position: absolute; right: 10px; top: 10px; z-index: 2; font: 600 11px/1 'IBM Plex Mono', ui-monospace, monospace;
  letter-spacing: .08em; text-transform: uppercase; color: var(--brand, #FFC928);
  background: color-mix(in srgb, var(--bg, #070d1a) 78%, transparent); border: 1px solid rgba(255,201,40, .3);
  border-radius: 999px; padding: 5px 10px; pointer-events: none; }
.cp-worldmap { position: relative; }
.rf-world-stage { position: relative; }

/* ---------- before / after story (about) --------------------------------- */
.rf-story { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 18px; }
.rf-story-card { position: relative; border: 1px solid var(--line, #2a3a5c); border-radius: 14px; padding: 16px 14px 14px;
  background: color-mix(in srgb, var(--surface, #0e1726) 85%, transparent); min-height: 150px; }
.rf-story-card .lbl { font: 600 10.5px/1 'IBM Plex Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3, #8ea0bf); }
.rf-story-card svg { display: block; width: 100%; height: auto; margin: 10px auto 0; max-width: 300px; overflow: visible; }
.rf-story-card .cap { margin: 8px 0 0; font-size: 12.5px; color: var(--ink-2, #c4cfe3); }
.rf-story .ex { display: block; margin-top: 8px; font: 500 10px/1 'IBM Plex Mono', ui-monospace, monospace; color: var(--ink-3, #8ea0bf); }
.rf-story .q { transform-origin: center; transform-box: fill-box; }
.rf-story .green { transform-origin: 50% 100%; transform-box: fill-box; opacity: .18; }
.rf-motion-on .rf-story .green { opacity: 1; }
.rf-motion-on .rf-story .q, .rf-motion-on .rf-story .conf, .rf-motion-on .rf-story .ts { opacity: 0; }
.rf-story.is-in .green { animation: rfFadeOut .8s var(--rf-m-ease) .9s forwards; }
.rf-story.is-in .q { animation: rfRise .5s var(--rf-m-ease) 1.7s forwards; }
.rf-story.is-in .conf { animation: rfRise .5s var(--rf-m-ease) 2.6s forwards; }
.rf-story.is-in .ts { animation: rfRise .5s var(--rf-m-ease) 3.1s forwards; }
.rf-story.is-in .after-pin { animation: rfPinPulse 1.4s ease-out 2.6s 1; }
@media (max-width: 560px) { .rf-story { grid-template-columns: 1fr; } }

/* ---------- the four-jobs micro scenes (/how-to-use) --------------------- */
/* RF-A-PICTURE-MUST-FILL-ITS-SLOT (9 September 2026). Alex circled the right
   half of this panel and called it empty. He was right, and the cause was this
   line: max-width 260px inside a grid column that is about 480px wide, so a
   260x90 drawing floated in the middle of a 480x250 box holding four marks.
   Roughly a fifth of the area was drawn on.

   I first reported the opposite — that the SVG had no width rule and inflated
   to the column — because I searched core-pages.css and this sheet is where the
   rule actually lives. Recording the wrong diagnosis next to the right one,
   because "no rule" and "a 260px ceiling" call for opposite fixes and the next
   person deserves to know which it was.

   The drawings are now authored at 460x220 and allowed to fill the column. */
.rf-micro { display: block; width: 100%; max-width: 480px; height: auto; margin: 10px 0 2px; overflow: visible; }
/* the panel ground, in the same three weights as the hero */
.rf-micro .pg { fill: color-mix(in srgb, var(--ink, #eef3ff) 3%, transparent); stroke: color-mix(in srgb, var(--ink, #eef3ff) 9%, transparent); stroke-width: 1; }
.rf-micro .pgrid { fill: url(#rfPanelGrid); }
.rf-micro .pgrid.fine { fill: url(#rfPanelFine); }
.rf-micro .pcont { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 9%, transparent); stroke-width: .6; }
.rf-micro .pbuilt { fill: url(#rfPanelHatch); stroke: color-mix(in srgb, var(--ink, #eef3ff) 11%, transparent); stroke-width: .7; }
.rf-micro .pverge { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 18%, transparent); stroke-width: 17; stroke-linecap: round; }
.rf-micro .proad { fill: none; stroke: var(--surface, #0e1726); stroke-width: 13; stroke-linecap: round; }
.rf-micro .pmid { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 26%, transparent); stroke-width: 1.2; stroke-dasharray: 5 7; }
.rf-micro .ptick { stroke: color-mix(in srgb, var(--ink, #eef3ff) 32%, transparent); stroke-width: .9; }
.rf-micro .lead { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 26%, transparent); stroke-width: .7; }
.rf-micro .km { fill: color-mix(in srgb, var(--ink, #eef3ff) 40%, transparent); text-anchor: middle; }
.rf-micro .lb { fill: var(--ink-2, #b9c7dd); }
.rf-micro .pscale path { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 36%, transparent); stroke-width: .9; }
.rf-micro .pscale rect { fill: color-mix(in srgb, var(--ink, #eef3ff) 26%, transparent); }
.rf-micro .pbay { fill: url(#rfPanelHatch); }
.rf-micro .pbayline, .rf-micro .ppost { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 20%, transparent); stroke-width: .9; }
.rf-micro .ppost path + path { fill: color-mix(in srgb, var(--ink, #eef3ff) 8%, transparent); }
.rf-micro .bolt { fill: var(--blue, #7ab6ff); }
.rf-micro .pkey path { fill: none; stroke: color-mix(in srgb, var(--brand, #FFC928) 45%, transparent); stroke-width: 1; }
.rf-micro .key { fill: var(--brand, #FFC928); letter-spacing: .08em; }
.rf-micro .fin { fill: var(--brand, #FFC928); }
.rf-micro .p { transform-origin: 50% 100%; transform-box: fill-box; }
.rf-motion-on .rf-micro .p { opacity: 0; }
.rf-micro .me { fill: var(--blue, #7ab6ff); }
.rf-micro .ring { fill: none; stroke: var(--blue, #7ab6ff); stroke-width: 1; opacity: 0; transform-origin: center; transform-box: fill-box; }
.rf-micro .st-unk { opacity: 0; }
.rf-motion-on .rf-micro .plug { opacity: .35; }
.rf-motion-on .rf-micro .st-ok { opacity: 0; }
.rf-motion-on .rf-micro .st-unk { opacity: 1; }
.rf-micro .rt { fill: none; stroke: var(--rf-m-road-lit); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 1; }
.rf-motion-on .rf-micro .rt { stroke-dashoffset: 1; }
.rf-micro .stop { transform-origin: center; transform-box: fill-box; }
.rf-motion-on .rf-micro .stop { opacity: 0; }
.rf-micro .t { fill: var(--ink-3, #8ea0bf); font: 500 9px/1 'IBM Plex Mono', ui-monospace, monospace; }
.rf-micro.rf-play .ring { animation: rfRingOnce 1.4s ease-out forwards; }
.rf-micro.rf-play .p { animation: rfPinIn .45s var(--rf-m-ease) forwards; }
.rf-micro.rf-play .p:nth-of-type(2) { animation-delay: .2s; }
.rf-micro.rf-play .p:nth-of-type(3) { animation-delay: .4s; }
.rf-micro.rf-play .p:nth-of-type(4) { animation-delay: .6s; }
.rf-micro.rf-play .plug { animation: rfLight .9s var(--rf-m-ease) .3s forwards; }
.rf-micro.rf-play .st-unk { animation: rfFadeOut .5s var(--rf-m-ease) .9s forwards; }
.rf-micro.rf-play .st-ok { animation: rfRise .5s var(--rf-m-ease) 1.3s forwards; }
.rf-micro.rf-play .rt { animation: rfDraw 1.6s var(--rf-m-ease) .2s forwards; }
.rf-micro.rf-play .stop { animation: rfNodeIn .4s var(--rf-m-ease) forwards; }
.rf-micro.rf-play .stop.s1 { animation-delay: .8s; }
.rf-micro.rf-play .stop.s2 { animation-delay: 1.4s; }

/* ---------- keyframes ---------------------------------------------------- */
@keyframes rfPinIn    { from { opacity: 0; transform: translateY(-6px) scale(.85); } to { opacity: 1; transform: none; } }
@keyframes rfBadgeIn  { from { opacity: 0; transform: rotate(45deg) scale(.7); } to { opacity: 1; transform: rotate(45deg); } }
@keyframes rfPinPulse { 0% { box-shadow: 0 0 0 0 var(--rf-m-glow); } 100% { box-shadow: 0 0 0 14px rgba(255,201,40, 0); } }
@keyframes rfRipple   { 0% { box-shadow: 0 0 0 0 rgba(255,201,40, .7); } 100% { box-shadow: 0 0 0 22px rgba(255,201,40, 0); } }
@keyframes rfDraw     { to { stroke-dashoffset: 0; } }
@keyframes rfFadeIn   { from { opacity: 0; } to { opacity: .85; } }
@keyframes rfFadeOut  { to { opacity: .18; transform: scale(.92); } }
@keyframes rfRise     { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes rfSettle   { to { opacity: 1; } }
@keyframes rfLight    { to { opacity: 1; filter: drop-shadow(0 0 6px rgba(122, 182, 255, .8)); } }
@keyframes rfNodeIn   { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: none; } }
@keyframes rfRingOnce { 0% { opacity: .8; transform: scale(.6); } 100% { opacity: 0; transform: scale(2.6); } }
@keyframes rfTravel   { 0% { opacity: 0; offset-distance: 0%; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { opacity: 0; offset-distance: 100%; } }

/* ---------- small screens: less, not none -------------------------------- */
/* RF-MOBILE-MOTION (9 September 2026). Alex: desktop is acceptable, mobile is
   uneven. It was, and the reasons were measurable rather than aesthetic.

   1. THE CARD "ARRIVAL" WAS A PAINT. .rf-scroll-step transitions box-shadow for
      half a second on six cards. box-shadow is not a compositor property: every
      frame of that transition repaints the card and its blur radius. On a phone
      GPU, six of them mid-scroll is the jitter. The card still says it has
      arrived — with its border, which is one cheap paint at the edge, not a
      48px blur redrawn 30 times.
   2. THE HERO SEQUENCE RAN FOR 6.2 SECONDS. Pins at 1.5s, 2.5s, 3.2s, then a
      sweep at 4.0s lasting 2.2s. On a 390px screen the reader has scrolled past
      the hero long before the sweep fires, so it is work done for an empty
      viewport - and it is the longest single animation on the page. Mobile ends
      at the third pin.
   3. THE RINGS. Two expanding rings on a small canvas read as a flicker rather
      than a pulse. One ring, on the pin the page is actually about.

   What mobile keeps is what the brief asked for and what the page means: the
   route line drawing once, one pin pulse where it is meaningful, a card edge
   that lights when you reach it, and press feedback on anything tappable.
   Nothing here hides content and then reveals it - every element is legible
   before its animation starts, so a dropped frame costs polish, never text. */
@media (max-width: 767px) {
  .rf-data-flow .dot { display: none; }             /* lanes still draw; particles are the cost */

  /* one ring, not two, and only on "you are here" */
  .rf-hero-scene.rf-play .you-ring { animation-iteration-count: 1; }
  .rf-hero-scene.rf-play .hp1-ring { display: none; }

  /* the sweep is a 2.2s animation that starts after the hero has been scrolled
     past on a phone; it is the one piece of motion here that is decoration
     rather than explanation, so it is the one that goes */
  .rf-hero-scene.rf-play .sweep { display: none; animation: none; }

  /* the card edge, not the card's shadow */
  .rf-scroll-step { transition: border-color .35s var(--rf-m-ease); }
  .rf-scroll-step.is-in { box-shadow: none; }
}

/* ---------- press feedback ----------------------------------------------- */
/* A tap with no acknowledgement reads as a dropped tap, and on a phone there is
   no hover state to fall back on. transform only: it composites, it cannot
   shift the layout, and it is over in 90ms. Pointer-coarse so a mouse keeps the
   hover treatment site.css already gives it. */
@media (pointer: coarse) {
  .rf-motion-on .btn,
  .rf-motion-on .cp-link,
  .rf-motion-on .htu-tab { transition: transform .09s ease-out; }
  .rf-motion-on .btn:active,
  .rf-motion-on .cp-link:active,
  .rf-motion-on .htu-tab:active { transform: scale(.97); }
}

/* ---------- reduced motion: the finished picture, at once ---------------- */
@media (prefers-reduced-motion: reduce) {
  .rf-scroll-step, .rf-route-draw path, .rf-journey-road .lit, .rf-journey-car { transition: none; }
  .rf-route-draw path, .rf-route-draw polyline, .rf-journey-road .lit,
  .rf-data-flow .lane-lit, .rf-micro .rt { stroke-dashoffset: 0 !important; animation: none !important; }
  .rf-data-flow .pin, .rf-data-flow .prov, .rf-micro .p, .rf-micro .plug, .rf-micro .st-ok, .rf-micro .stop,
  .rf-story .q, .rf-story .conf, .rf-story .ts { opacity: 1 !important; transform: none !important; animation: none !important; filter: none !important; }
  .rf-road-scene .hp, .rf-road-scene .tag, .rf-road-scene .chip, .rf-road-scene .lbl { opacity: 1 !important; transform: none !important; animation: none !important; }
  .rf-road-scene .lit { stroke-dashoffset: 0 !important; animation: none !important; }
  .rf-road-scene .sweep { display: none !important; animation: none !important; }
  .rf-road-scene .you-ring, .rf-road-scene .hp1-ring { display: none !important; }
  .leaflet-marker-icon .pin-badge.live-avail { animation: none !important; }
  .rf-micro .st-unk, .rf-data-flow .dot, .rf-micro .ring, .rf-journey-car { display: none !important; }
  .rf-story .green { opacity: .18 !important; animation: none !important; }
  .rf-pin-enter, .leaflet-marker-icon .pin-badge, .rf-pin-pulse, .leaflet-marker-icon .pin-badge.is-selected,
  .rf-ripple, .leaflet-overlay-pane path.rf-route-in { animation: none !important; }
  .rf-journey-road .lit { --rf-progress: 1; }
  /* press feedback is motion too: a reader who asked for less does not want the
     page moving under their thumb either. The tap still works; it just does not
     shrink. */
  .rf-motion-on .btn, .rf-motion-on .cp-link, .rf-motion-on .htu-tab { transition: none !important; }
  .rf-motion-on .btn:active, .rf-motion-on .cp-link:active,
  .rf-motion-on .htu-tab:active { transform: none !important; }
}

/* RF-KEY-STRIP (9 September 2026). Every label in a panel scene now lives in a
   key along the bottom instead of floating beside its mark. The first attempt
   placed callouts next to each mark and they collided with ticks, with hatched
   ground and with each other at four different widths — a class of bug that has
   no end, because the text length is not knowable from the geometry. A key
   cannot collide with the drawing: the drawing stops above the rule. */
.rf-micro .pkeyline { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 14%, transparent); stroke-width: 1; }
.rf-micro .kn { fill: var(--ink-2, #b9c7dd); font-weight: 600; }
.rf-micro .kt { fill: var(--ink-3, #8ea0bf); font-size: 8.5px; }
.rf-micro .pnum { fill: var(--surface, #0e1726); stroke: color-mix(in srgb, var(--ink, #eef3ff) 34%, transparent); stroke-width: .9; }
.rf-micro .nn { fill: var(--ink-2, #b9c7dd); font-size: 8px; font-weight: 600; }
.rf-micro .sc { fill: color-mix(in srgb, var(--ink, #eef3ff) 34%, transparent); font-size: 8.5px; }
.rf-micro .bay { fill: color-mix(in srgb, var(--ink, #eef3ff) 30%, transparent); font-size: 8px; }
.rf-micro .pcable { fill: none; stroke: color-mix(in srgb, var(--blue, #7ab6ff) 40%, transparent); stroke-width: 1.1; }
.rf-micro .pkeybox path { fill: none; stroke: color-mix(in srgb, var(--brand, #FFC928) 45%, transparent); stroke-width: 1; }

/* RF-LOGBOOK-SLIP (9 September 2026). The before/after pair used to be a large
   pin with three sentences floating beside it in a 220x96 box — the same
   sparseness Alex circled on the panels, and it made the argument twice as
   weakly, because the argument is about a RECORD: what is known, from whom, and
   how old. So the picture is the record. The "before" slip is the joke made
   visible: a verdict printed in green over two blank lines. */
.rf-story-card .slip { fill: color-mix(in srgb, var(--ink, #eef3ff) 3%, transparent); stroke: color-mix(in srgb, var(--ink, #eef3ff) 16%, transparent); stroke-width: 1; }
.rf-story-card .slipdot { fill: url(#rfSlipDot); }
.rf-story-card .rule { fill: none; stroke: color-mix(in srgb, var(--ink, #eef3ff) 11%, transparent); stroke-width: .8; }
.rf-story-card .rule.dash { stroke-dasharray: 3 3; stroke: color-mix(in srgb, var(--ink, #eef3ff) 20%, transparent); }
.rf-story-card .rule.dot { stroke-dasharray: 1.5 3; stroke: color-mix(in srgb, var(--ink, #eef3ff) 22%, transparent); }
.rf-story-card .t { font: 500 10px/1 'IBM Plex Mono', ui-monospace, monospace; }
.rf-story-card .hd { fill: var(--ink-2, #c4cfe3); letter-spacing: .16em; font-weight: 600; }
.rf-story-card .no { fill: color-mix(in srgb, var(--ink, #eef3ff) 30%, transparent); }
.rf-story-card .lab { fill: color-mix(in srgb, var(--ink, #eef3ff) 34%, transparent); letter-spacing: .1em; }
.rf-story-card .val { font-size: 11.5px; font-weight: 600; }
.rf-story-card .foot { font-size: 9.5px; letter-spacing: .04em; }
