/* ============================================================================
   RF-HOME-REBUILD — Refuelia homepage, 19 September 2026.

   WHY THIS FILE EXISTS AT ALL, since site.css is already 253 KB.

   The audit that started this rebuild found something nobody had written down:
   the page served at "/" was map.html — the whole map APPLICATION, 384 KB of
   markup and a megabyte of map.js. A first-time visitor landed on a dark map
   and a rail of four filter chips, six tool buttons, two tabs, a search box and
   a station list, with no sentence anywhere saying what Refuelia is. The
   marketing homepage, index.html, had been unreachable at "/" for months.

   That is also the most likely reading of the AdSense refusal ("Low value
   content"): a crawler arriving at the front door saw a widget, not a page.

   So "/" is now a real homepage again and the map application moved back to
   /map, where it can be the whole screen without having to also introduce the
   product.

   Everything here is namespaced .rfh-*, which appears nowhere in site.css, so
   the two files cannot collide. site.css still loads, because the shared cookie
   banner, sign-in sheet and notification prompts are injected by scripts that
   style themselves out of it. The nav and footer keep their old class hooks —
   main.js binds the mobile drawer to .nav-toggle / .nav-links / .nav-close —
   and are restyled under body.rfh, which is ~20 declarations, not a fight.

   TOKENS ONLY BELOW. No bare hex anywhere in the component rules; if a colour
   is needed and has no token, the token is what gets added.
   ========================================================================= */

/* TOKENS LIVE IN shell.css.
   This file used to carry its own complete copy of the palette, the type scale,
   the spacing ramp, the radii and the shadows — about 120 lines, duplicated
   exactly once, which is the number at which two token systems start drifting
   apart without anyone noticing. shell.css defines them as --rf-*, every public
   page loads it, and this file reads them.

   What stays here is the one token that is genuinely local: the hero's height,
   which no other page has a hero to apply it to. */
:root{
  /* The hero has to END inside the first viewport, not exactly fill it. At
     1366x768 — still one of the commonest laptop screens — `100vh - header`
     put the map card's bottom edge precisely on the fold and sliced the legend
     in half, which reads as a rendering fault rather than as "scroll for more".
     The extra 90px is that breathing room: it also lets the top of the next
     section show, which is what tells a reader there IS a next section. */
  /* RF-SAFARI-HEIGHT (20 September 2026). 100vh on iOS Safari is the TALL
     viewport — the one the URL bar and the toolbar are covering when the page
     first paints — so a hero sized from it starts taller than the screen and
     the map card's bottom edge lands under the browser chrome. 100svh is the
     small viewport, the one that is actually visible. The vh value stays
     first as the fallback for a browser that does not know svh. */
  --rfh-hero: clamp(520px, calc(100vh - var(--rf-header) - 90px), 740px);
  --rfh-hero: clamp(520px, calc(100svh - var(--rf-header) - 90px), 740px);
}

/* ============================================================================
   BASE
   ========================================================================= */
body.rfh{
  margin:0; background:var(--rf-bg); color:var(--rf-ink);
  font:400 var(--rf-t-body)/1.6 var(--rf-sans);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
body.rfh *,body.rfh *::before,body.rfh *::after{box-sizing:border-box}

/* SPECIFICITY, and the bug that made this necessary.
   These started life as `body.rfh h1, body.rfh h2, …`, which is (0,1,2) — one
   class and two elements. Every component rule below is (0,1,1), one class and
   one element, so the BASE rule outranked all of them: .rfh-nearby-head h2 set
   19px and got 40px, and the panel heading wrapped onto two lines at a size
   nothing had asked for.
   :where() contributes nothing to specificity, so the base block drops to
   (0,1,1) — still enough to beat site.css's bare `h2` (0,0,1), and now a tie
   with the components, which come later in the file and therefore win. That is
   the cascade doing its job instead of being fought with !important.

   The font-family line is here for a related reason: it was MISSING, so every
   heading on this page silently inherited site.css's --display (Fraunces, a
   display serif) and the whole redesign was rendering in a face nobody chose.
   The brief asks for a modern neutral sans; it is now stated rather than
   inherited, which is the point of a token layer. */
body.rfh :where(h1,h2,h3,h4){
  margin:0; font-family:var(--rf-text); font-weight:600;
  text-wrap:balance; letter-spacing:-.022em; line-height:1.14;
}
body.rfh :where(h1){font-size:var(--rf-t-h1); font-weight:600; letter-spacing:-.012em}
body.rfh :where(h2){font-size:var(--rf-t-h2)}
body.rfh :where(h3){font-size:var(--rf-t-h3); letter-spacing:-.012em; line-height:1.3}
/* RF-WHERE-DOES-NOT-MAKE-IT-FREE (21 September 2026). This read
       body.rfh :where(p){margin:0}
   and it has been quietly beating every class on this page since it was
   written. :where() zeroes the specificity of ITS OWN argument — it does
   nothing about `body.rfh` in front of it, so the rule scores (0,1,1) and
   outranks any single class, including .rfh-hero-sub at (0,1,0).

   The visible cost, measured at 390 while the owner was asking to tidy the
   hero: `.rfh-hero-sub{margin-top:var(--rf-4)}` computed to 0px, so the
   headline and its subtitle touched — on every width, since the day the reset
   landed. Four gaps in the hero read 12 / 0 / 16 / 8; the 0 was this.

   The fix has to land BETWEEN two other rules, which is why the obvious version
   is wrong. Measured, all three:

       p{margin:0 0 1em}              site.css        (0,0,1)
       :where(body.rfh) :where(p)     first attempt   (0,0,0)   <- too weak
       .rfh-hero-sub{margin-top:…}    the class       (0,1,0)

   At (0,0,0) the reset lost to site.css instead, and 1em of margin-bottom came
   back on every paragraph — the hero's gaps measured 8 / 12 / 35 / 8, the 35
   being 20px of intended space plus 14.5px that had returned. Caught by
   measuring after the change rather than assuming the direction was enough.

   `:where(body.rfh) p` scores (0,0,1): equal to site.css and later in the
   cascade, so it still wins there; below any class, so a class with an opinion
   now wins here. That is the band a reset belongs in. */
:where(body.rfh) p{margin:0}
body.rfh :where(a){color:inherit; text-decoration:none}
body.rfh img,body.rfh svg{max-width:100%}
body.rfh :focus-visible{outline:2px solid var(--rf-brand); outline-offset:2px; border-radius:4px}

.rfh-wrap{width:100%; max-width:var(--rf-wrap); margin:0 auto; padding-inline:var(--rf-gutter)}
.rfh-wrap.rd{max-width:var(--rf-wrap-rd)}
.rfh-sr{position:absolute!important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0}

.rfh-eyebrow{
  display:inline-flex; align-items:center; gap:7px;
  font:600 var(--rf-t-label)/1 var(--rf-mono);
  letter-spacing:.15em; text-transform:uppercase; color:var(--rf-brand);
}
.rfh-lead{color:var(--rf-ink-2); font-size:var(--rf-t-lead); line-height:1.62; max-width:62ch}

/* ============================================================================
   BUTTON SYSTEM — three, and only three (§37)
   ========================================================================= */
.rfh-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  min-height:44px; padding:0 18px; border-radius:var(--rf-r-ctl);
  font:600 15px/1 var(--rf-sans); letter-spacing:-.008em;
  border:1px solid transparent; cursor:pointer; white-space:nowrap;
  transition:background var(--rf-fast), border-color var(--rf-fast), color var(--rf-fast), transform var(--rf-fast);
}
.rfh-btn svg{width:18px; height:18px; flex:0 0 auto}
/* RF-ROAD-MARKING: the CTA tokens, not the brand ones — in the light theme
   the brand is the ochre TEXT colour, and the button stays the yellow plate. */
/* body.rfh :where(a){color:inherit} is (0,1,1) and beat this (0,1,0): the
   button took the page ink, i.e. ink text on an ink plate. */
.rfh-btn--primary,body.rfh a.rfh-btn--primary{background:var(--rf-cta-bg,var(--rf-brand)); color:var(--rf-cta-ink,var(--rf-brand-ink))}
.rfh-btn--primary:hover{background:var(--rf-cta-bg-hover,var(--rf-brand-hover))}
.rfh-btn--secondary{background:transparent; color:var(--rf-ink); border-color:var(--rf-line-strong)}
.rfh-btn--secondary:hover{background:var(--rf-raised); border-color:var(--rf-brand-edge)}
.rfh-btn--text{background:none; border:0; padding:14px 0; margin:-14px 0; min-height:0; color:var(--rf-brand); font-weight:600}
.rfh-btn--text:hover{color:var(--rf-brand-hover)}
.rfh-btn--lg{min-height:52px; padding:0 24px; font-size:16px}
.rfh-btn--block{width:100%}
.rfh-btn:active{transform:translateY(1px)}
.rfh-btn[disabled]{opacity:.5; pointer-events:none}

/* ============================================================================
   CARD SYSTEM — one radius, one border, one shadow (§38)
   ========================================================================= */
.rfh-card{
  background:var(--rf-surface); border:1px solid var(--rf-line);
  border-radius:var(--rf-r-card); box-shadow:var(--rf-sh-1);
}
a.rfh-card{transition:border-color var(--rf-fast), background var(--rf-fast)}
a.rfh-card:hover{border-color:var(--rf-brand-edge); background:var(--rf-raised)}

/* THE HEADER IS NOT HERE ANY MORE.
   It moved to shell.css, where one implementation serves all 538 public pages.
   This file used to carry its own copy under `body.rfh .site-nav`, which was
   correct while the homepage was the only page with the new design and wrong
   the moment a second page got it: two rules for one component is how six
   different headers happened in the first place. */

/* ============================================================================
   HERO (§7) — left product column, centre map, right nearby
   ========================================================================= */
.rfh-hero{position:relative; padding-block:var(--rf-5) var(--rf-6)}
.rfh-hero-grid{
  display:grid; grid-template-columns:minmax(0,31fr) minmax(0,46fr) minmax(0,23fr);
  gap:var(--rf-5); align-items:stretch;
  /* HEIGHT, not min-height, and this is the difference between a hero that ends
     where it was told to and one that ends wherever the Nearby list happens to
     stop. Measured at 1366x768: --rfh-hero asked for 656px, the five station
     cards plus their heading and footnote wanted 717, min-height let them win,
     and the map card grew with them until its legend was sliced by the fold.
     The list already scrolls inside itself, so capping the row costs nothing
     but the fifth card on a short screen. min-height is the floor for the very
     short windows where the left column is the taller of the two. */
  height:var(--rfh-hero); min-height:520px;
}
.rfh-hero-left{display:flex; flex-direction:column; justify-content:center; padding-block:var(--rf-2)}
/* RF-PHONE-MAP-FIRST: the phone's primary action, and only the phone's. The
   desktop header already carries "Open the map"; a second one on the same
   screen is the duplicate-intent fault check-duplicate-cta.py exists to
   catch. Shown again in the max-width:767px block below. */
.rfh-hero-go{display:none; gap:var(--rf-3)}
.rfh-hero h1{margin-top:var(--rf-4)}
.rfh-hero h1 em{font-style:normal; color:var(--rf-brand)}
/* RF-NOT-GENERATED: the headline is one colour. A highlighted or coloured
   second line is the landing-page trick the owner's standard names. */
:root[data-theme="light"] .rfh-hero h1 em{color:var(--rf-ink); -webkit-text-fill-color:currentColor}
.rfh-hero-sub{margin-top:var(--rf-4); color:var(--rf-ink-2); font-size:var(--rf-t-lead); line-height:1.55; max-width:40ch}

/* ---- search (§9): one box, the main action ---------------------------- */
.rfh-search{
  display:flex; align-items:center; gap:var(--rf-2); margin-top:var(--rf-6);
  background:var(--rf-surface); border:1px solid var(--rf-line-strong);
  border-radius:var(--rf-r-ctl); padding:6px 6px 6px 14px;
  transition:border-color var(--rf-fast), box-shadow var(--rf-fast);
}
.rfh-search:focus-within{border-color:var(--rf-brand-edge); box-shadow:0 0 0 3px var(--rf-brand-dim)}
.rfh-search > svg{width:19px; height:19px; color:var(--rf-ink-3); flex:0 0 auto}
.rfh-search input{
  flex:1 1 auto; min-width:0; height:44px; border:0; background:none; outline:none;
  color:var(--rf-ink); font:400 15.5px/1 var(--rf-sans);
}
.rfh-search input::placeholder{color:var(--rf-ink-3)}
.rfh-search .rfh-btn{min-height:40px; padding:0 16px}
.rfh-hero-loc{margin-top:var(--rf-3); display:flex; flex-wrap:wrap; gap:var(--rf-2)}

/* ---- quick filters (§10): five fuels, two states, one Filters ---------- */
.rfh-quick{display:flex; flex-wrap:wrap; align-items:center; gap:var(--rf-2); margin-top:var(--rf-4)}
.rfh-chip{
  display:inline-flex; align-items:center; gap:6px; height:36px; padding:0 13px;
  border-radius:var(--rf-r-pill); border:1px solid var(--rf-line);
  background:var(--rf-surface); color:var(--rf-ink-2);
  font:600 var(--rf-t-xs)/1 var(--rf-sans); cursor:pointer; white-space:nowrap;
  transition:background var(--rf-fast), border-color var(--rf-fast), color var(--rf-fast);
}
.rfh-chip svg{width:15px; height:15px}
.rfh-chip:hover{border-color:var(--rf-line-strong); color:var(--rf-ink)}
.rfh-chip[aria-pressed="true"]{background:var(--rf-brand-dim); border-color:var(--rf-brand-edge); color:var(--rf-brand)}
.rfh-chip--ev[aria-pressed="true"]{background:var(--rf-ev-dim); border-color:var(--rf-ev); color:var(--rf-ev)}
.rfh-chip--more{border-style:dashed}
.rfh-chip .rfh-chip-n{font-family:var(--rf-mono); font-size:11px; opacity:.8}

/* ---- RF-FUEL-PICK (24 September 2026): the fuel list behind one button ---
   The owner's choice "B". One control that fits every language; the list it
   drops carries each fuel's legend colour, so the choice and the map speak
   the same colours. .is-ready is set by home-map.js: until then (or with no
   script) the list stays a row of chips, which still works. */
.rfh-fuelpick{position:relative; margin-top:var(--rf-3)}
.rfh-fuelpick-btn{display:none}
.rfh-fuelpick.is-ready .rfh-fuelpick-btn{
  display:flex; align-items:center; gap:8px; width:100%; height:40px; padding:0 12px;
  border:1px solid var(--rf-line-strong); border-radius:8px; background:var(--rf-surface);
  font:500 14px/1 var(--rf-sans); color:var(--rf-ink-2); cursor:pointer; text-align:left;
  transition:border-color var(--rf-fast);
}
.rfh-fuelpick.is-ready .rfh-fuelpick-btn:hover{border-color:var(--rf-ink-3)}
.rfh-fuelpick.is-open .rfh-fuelpick-btn{border-color:var(--rf-mark,#FFC928)}
.rfh-fuelpick-btn:focus-visible{outline:2px solid var(--rf-ink); outline-offset:2px}
.rfh-fuelpick-lbl::after{content:":"}
.rfh-fuelpick-val{color:var(--rf-ink); font-weight:600; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.rfh-fuelpick-car{width:16px; height:16px; margin-left:auto; flex:none; color:var(--rf-ink-3); transition:transform var(--rf-fast)}
.rfh-fuelpick.is-open .rfh-fuelpick-car{transform:rotate(180deg)}
.rfh-fuelpick-dots{display:flex; gap:3px; flex:none}
.rfh-fuelpick-dots i{width:7px; height:7px; border-radius:50%}
.rfh-fuelpick .d-petrol, .rfh-fuelpick [data-rfh-filter="petrol"]::before{background:var(--pin-petrol)}
.rfh-fuelpick .d-diesel, .rfh-fuelpick [data-rfh-filter="diesel"]::before{background:var(--pin-diesel)}
.rfh-fuelpick .d-lpg, .rfh-fuelpick [data-rfh-filter="lpg"]::before{background:var(--pin-lpg)}
.rfh-fuelpick .d-cng, .rfh-fuelpick [data-rfh-filter="cng"]::before{background:var(--pin-cng)}
.rfh-fuelpick .d-ev, .rfh-fuelpick [data-rfh-filter="ev"]::before{background:var(--pin-ev)}
.rfh-fuelpick .d-service, .rfh-fuelpick [data-rfh-filter="service"]::before{background:var(--pin-service)}
.rfh-fuelpick [data-rfh-filter="lng"]::before{background:var(--pin-lng)}
.rfh-fuelpick [data-rfh-filter="hydrogen"]::before{background:var(--pin-hydrogen)}
/* the list itself, once the button is in charge of it */
.rfh-fuelpick.is-ready .rfh-quick{
  display:none; position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:950;
  flex-direction:column; align-items:stretch; flex-wrap:nowrap; gap:2px; margin:0; padding:6px;
  overflow:visible; background:var(--rf-surface); border:1px solid var(--rf-line-strong);
  border-radius:10px; box-shadow:0 12px 32px rgba(22,24,29,.18);
  transform-origin:top center; animation:rfhFuelDrop .16s ease-out both;
}
.rfh-fuelpick.is-ready.is-open .rfh-quick{display:flex}
@keyframes rfhFuelDrop{from{opacity:0; transform:translateY(-4px)} to{opacity:1; transform:none}}
@media (prefers-reduced-motion:reduce){ .rfh-fuelpick.is-ready .rfh-quick{animation:none} .rfh-fuelpick-car{transition:none} }
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip{
  display:flex !important; align-items:center; gap:10px; width:100%; min-height:40px !important; height:auto !important;
  padding:0 10px !important; border:0 !important; border-radius:6px !important; background:transparent !important;
  color:var(--rf-ink) !important; font:500 14.5px/1.2 var(--rf-sans) !important; text-align:left; white-space:normal;
}
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip:hover,
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip:focus-visible{background:var(--rf-bg-2) !important; outline:none}
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip[aria-pressed="true"]{background:var(--rf-brand-dim,rgba(255,201,40,.22)) !important; font-weight:600 !important}
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip[aria-pressed="true"]::after{content:"\2713"; margin-left:auto; font-weight:600}
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip::before{content:""; width:10px; height:10px; border-radius:50%; flex:none}
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip[data-rfh-filter="all"]::before{background:transparent; box-shadow:inset 0 0 0 1.5px var(--rf-ink-3)}
.rfh-fuelpick.is-ready .rfh-quick .rfh-chip svg{display:none}

/* ---- trust strip (§22): three measured facts, no invented numbers ------ */
.rfh-trust{
  display:flex; flex-wrap:wrap; gap:var(--rf-2) var(--rf-5); margin-top:var(--rf-6);
  padding-top:var(--rf-5); border-top:1px solid var(--rf-line-faint);
}
.rfh-trust li{display:flex; align-items:flex-start; gap:8px; list-style:none; font-size:var(--rf-t-xs); color:var(--rf-ink-2); line-height:1.45}
.rfh-trust svg{width:15px; height:15px; color:var(--rf-brand); flex:0 0 auto; margin-top:1px}
.rfh-trust ul{display:contents}

/* ============================================================================
   MAP PANEL (§15) — the visual centre, minimum overlays
   ========================================================================= */
.rfh-mapcard{
  position:relative; border-radius:var(--rf-r-panel); overflow:hidden;
  border:1px solid var(--rf-line); background:var(--rf-bg-2);
  box-shadow:var(--rf-sh-3); min-height:360px;
}
.rfh-map{position:absolute; inset:0}
.rfh-map.leaflet-container{background:var(--rf-bg-2); font-family:var(--rf-text)}

.rfh-map-tools{position:absolute; top:12px; right:12px; z-index:420; display:flex; gap:8px; align-items:flex-start}
.rfh-maptool{
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  height:36px; min-width:36px; padding:0 10px; border-radius:var(--rf-r-chip);
  background:color-mix(in srgb, var(--rf-surface) 92%, transparent);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  border:1px solid var(--rf-line-strong); color:var(--rf-ink);
  font:600 var(--rf-t-xs)/1 var(--rf-sans); cursor:pointer;
  box-shadow:var(--rf-sh-1); transition:background var(--rf-fast), border-color var(--rf-fast);
}
.rfh-maptool:hover{background:var(--rf-raised); border-color:var(--rf-brand-edge)}
.rfh-maptool svg{width:16px; height:16px}
.rfh-maptool[aria-pressed="true"]{border-color:var(--rf-brand-edge); color:var(--rf-brand)}

/* RF-LOCATE-B (24 September 2026). The owner's choice "B": the locate control
   was a 36px grey square nobody noticed. Now it is the primary plate with its
   words on it, "Near me". Motion is spent once, on arrival: it slides in, a
   sheen crosses it twice and the crosshair breathes twice — then it is still,
   so a driver who comes back every day is not nagged. No infinite loop, and
   none of it under prefers-reduced-motion. */
.rfh-maptool.rfh-locate{
  position:relative; overflow:hidden; isolation:isolate;
  height:40px; padding:0 14px 0 12px; gap:8px; border-radius:var(--rf-cta-radius, 999px);
  background:var(--rf-cta-bg, #FFC928); color:var(--rf-cta-ink, #16181D);
  border:1px solid #E0AE10; -webkit-backdrop-filter:none; backdrop-filter:none;
  font:600 14px/1 var(--rf-sans); white-space:nowrap;
  box-shadow:0 2px 8px rgba(22,24,29,.18);
  animation:rfhLocIn .5s cubic-bezier(.2,.8,.2,1) .2s both;
}
.rfh-maptool.rfh-locate:hover{background:var(--rf-cta-bg-hover, #FFD659); border-color:#E0AE10}
.rfh-maptool.rfh-locate:focus-visible{outline:2px solid var(--rf-ink); outline-offset:2px}
.rfh-maptool.rfh-locate svg{width:18px; height:18px; flex:0 0 auto; animation:rfhLocBreathe 1.2s ease-in-out .9s 2}
.rfh-maptool.rfh-locate::after{
  content:""; position:absolute; top:0; bottom:0; left:-60%; width:40%; z-index:-1; pointer-events:none;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.75), transparent);
  animation:rfhLocSheen 1.1s ease-in-out .8s 2;
}
.rfh-maptool.rfh-locate.is-locating{cursor:progress}
.rfh-maptool.rfh-locate.is-locating svg{animation:rfhLocBreathe 1s ease-in-out 8}
@keyframes rfhLocIn{from{opacity:0; transform:translateY(-8px)} to{opacity:1; transform:none}}
@keyframes rfhLocSheen{to{left:120%}}
@keyframes rfhLocBreathe{50%{transform:scale(1.25)}}
@media (prefers-reduced-motion:reduce){
  .rfh-maptool.rfh-locate, .rfh-maptool.rfh-locate svg, .rfh-maptool.rfh-locate::after{animation:none}
}

.rfh-map-zoom{position:absolute; right:12px; bottom:14px; z-index:420; display:flex; flex-direction:column;
  border-radius:var(--rf-r-chip); overflow:hidden; border:1px solid var(--rf-line-strong); box-shadow:var(--rf-sh-1)}
.rfh-map-zoom button{
  width:36px; height:34px; border:0; background:color-mix(in srgb, var(--rf-surface) 94%, transparent);
  color:var(--rf-ink); font:400 19px/1 var(--rf-sans); cursor:pointer; transition:background var(--rf-fast);
}
.rfh-map-zoom button+button{border-top:1px solid var(--rf-line)}
.rfh-map-zoom button:hover{background:var(--rf-raised)}

/* legend (§15): five words, no explanation */
.rfh-legend{
  position:absolute; left:12px; bottom:14px; z-index:420;
  display:flex; flex-wrap:wrap; gap:4px 12px; max-width:calc(100% - 80px);
  padding:9px 12px; border-radius:var(--rf-r-chip);
  background:color-mix(in srgb, var(--rf-surface) 92%, transparent);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  border:1px solid var(--rf-line); box-shadow:var(--rf-sh-1);
}
.rfh-legend span{display:inline-flex; align-items:center; gap:6px; font:500 11.5px/1 var(--rf-sans); color:var(--rf-ink-2)}
.rfh-legend i{width:8px; height:8px; border-radius:50%; flex:0 0 auto}
/* RF-COLOUR-IS-THE-FUEL: the legend now has two groups, and the swatch shape
   says which axis an entry belongs to. A FILL entry is a solid disc, like the
   pin's body. A RING entry is a hollow circle, like the ring around it. Without
   that, ten dots in one box are ten equal facts and the reader has to guess
   which colour means what. */
.rfh-legend-h{
  flex-basis:100%; font:600 9.5px/1 var(--rf-mono) !important; letter-spacing:.09em;
  text-transform:uppercase; color:var(--rf-ink-3) !important; margin-top:2px;
}
.rfh-legend-h:first-child{ margin-top:0 }
.rfh-legend .l-petrol i{background:var(--pin-petrol)}
.rfh-legend .l-diesel i{background:var(--pin-diesel)}
.rfh-legend .l-lpg i{background:var(--pin-lpg)}
.rfh-legend .l-cng i{background:var(--pin-cng)}
/* RF-SEVEN-FAMILIES (22 September 2026). The swatches for the two families
   the tiles cannot answer for yet are written NOW, not when they are switched
   on: home-map.js adds the span from the registry, and a span whose dot has
   no rule is a bullet point with a hole in it. Cheap to have early, and the
   one thing that cannot be added later by the same code that adds the span. */
/* The popup's fuel line. The dots themselves are .rf-fuelfam in shell.css —
   one vocabulary for a named fuel, wherever it is named. */
.rfh-pop-fuel{display:flex; flex-wrap:wrap; gap:4px 10px; margin-top:6px;
  font:600 11.5px/1.3 var(--rf-sans); color:var(--rf-ink-2)}

.rfh-legend .l-lng i{background:var(--pin-lng)}
.rfh-legend .l-hydrogen i{background:var(--pin-hydrogen)}
.rfh-legend .l-ev i{background:var(--pin-ev)}
.rfh-legend .l-service i{background:var(--pin-service)}
.rfh-legend .l-station i{background:var(--pin-station)}
/* Status entries: a ring, not a disc — the same shape they take on the pin. */
.rfh-legend .l-ok i,
.rfh-legend .l-lim i,
.rfh-legend .l-out i{ background:transparent; border:2px solid currentColor }
.rfh-legend .l-ok i{color:var(--rf-ok)}
.rfh-legend .l-lim i{color:var(--rf-limited)}
.rfh-legend .l-out i{color:var(--rf-out)}

/* the one line that says where the view is and how old it is */
.rfh-map-note{
  position:absolute; left:12px; top:12px; z-index:420; max-width:calc(100% - 172px);
  padding:8px 12px; border-radius:var(--rf-r-chip);
  background:color-mix(in srgb, var(--rf-surface) 92%, transparent);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  border:1px solid var(--rf-line);
  font:500 var(--rf-t-xs)/1.35 var(--rf-sans); color:var(--rf-ink-2);
}
.rfh-map-note b{color:var(--rf-ink); font-weight:600}

/* ---- pins (§17) ----------------------------------------------------------
   RF-ONE-MARKER-EVERYWHERE (22 September 2026). Twenty-six lines of .rfh-pin
   stood here: a 26px rounded square, rotated -45deg, bordered, shadowed, with
   the fuel painted as a CSS background and any child svg forced to 13px and
   counter-rotated +45deg. It was a good pin. It was also the SECOND pin on
   this site, and /map had the other one — 34x40, a drawn collar, a tail, no
   rotation. One forecourt, two shapes, depending on which URL you opened.

   home-map.js now calls RFMarker at the 'sm' variant, the same component
   /map calls at 'lg'. The rules live once, in shell.css under RF-ONE-MARKER,
   and they style .rf-mk at every size.

   Nothing replaces this block. That is the point: there is no home-page pin
   to style any more, only a marker seen closer. The Leaflet wrapper reset
   went with it — the divIcon is built with className:'' now, so Leaflet's
   white box is never switched on in the first place. */

/* ---- the basemap, toned to the page ------------------------------------
   The same two rules map.html has carried since 15 September, for the same
   reasons, which are written out in full there under RF-DARK-BASEMAP and
   RF-LIGHT-MAP-IS-GLARING. The short version of both:

     OpenStreetMap's raster tiles paint motorway casings at #FFFFFF. On a dark
     page that is a lightbulb, and on a light one it is still the brightest
     object on the screen by a wide margin. A CSS filter fixes it with no extra
     request, no tile provider and no licence, and it flips in the same frame as
     the theme.

   THE ONE THING THAT MUST NOT SLIP, copied across with the rule because the
   reason travels with it: the filter goes on .leaflet-tile-pane and NOWHERE
   wider. Leaflet keeps markers in a SIBLING pane, so putting this on
   .leaflet-container or on #rfh-map would invert THE PINS — green would come
   out magenta, red would come out cyan, and a map whose entire promise is that
   the colour means something would be saying the opposite of the truth. That is
   a lie, not a styling bug. tests/dark-reaches-the-map.test.js fails if the
   selector ever widens.

   invert flips the sheet; hue-rotate(180deg) puts the hues back, so water stays
   blue and parks stay green. The light rule deliberately has no invert, no
   hue-rotate, no grayscale and no sepia — those move hues — and no contrast(),
   which would push label text and paper toward the same mid-grey. */
html[data-theme="dark"] .rfh-map .leaflet-tile-pane{
  filter:invert(1) hue-rotate(180deg) brightness(.92) contrast(.9) saturate(.82);
}
html[data-theme="light"] .rfh-map .leaflet-tile-pane{
  filter:brightness(.91) saturate(.92);
}
/* The system-default state stamps no data-theme at all, and site.css ships
   dark-first with theme.js stamping before paint — but a visitor whose stamp
   fails to land must not get a white sheet inside a dark page. */
html:not([data-theme="light"]) .rfh-map .leaflet-tile-pane{
  filter:invert(1) hue-rotate(180deg) brightness(.92) contrast(.9) saturate(.82);
}
/* RF-HOME-LCP: the early copy of the basemap the Worker writes into #rfh-map
   (_worker.js homeTilePlaceholder) wears exactly the tile pane's filter, so the
   moment Leaflet's tiles replace it nothing changes colour. Still only tiles:
   no pin is ever inside .rf-tile-ph. */
html[data-theme="dark"] .rfh-map .rf-tile-ph{
  filter:invert(1) hue-rotate(180deg) brightness(.92) contrast(.9) saturate(.82);
}
html[data-theme="light"] .rfh-map .rf-tile-ph{
  filter:brightness(.91) saturate(.92);
}
html:not([data-theme="light"]) .rfh-map .rf-tile-ph{
  filter:invert(1) hue-rotate(180deg) brightness(.92) contrast(.9) saturate(.82);
}

/* ---- Leaflet chrome, dressed in our tokens ----------------------------- */
.rfh-map .leaflet-popup-content-wrapper{
  background:var(--rf-surface); color:var(--rf-ink);
  border:1px solid var(--rf-line-strong); border-radius:var(--rf-r-card);
  box-shadow:var(--rf-sh-2); padding:0;
}
.rfh-map .leaflet-popup-content{margin:0; width:auto!important; font-family:var(--rf-text)}
.rfh-map .leaflet-popup-tip{background:var(--rf-surface); border:1px solid var(--rf-line-strong); box-shadow:none}
.rfh-map .leaflet-popup-close-button{color:var(--rf-ink-3)!important; padding:7px 8px 0 0}
.rfh-map .leaflet-popup-close-button:hover{color:var(--rf-ink)!important; background:none}
.rfh-pop{display:flex; flex-direction:column; gap:7px; padding:13px 15px 14px; min-width:180px}
.rfh-pop strong{font-size:14.5px; font-weight:650; letter-spacing:-.012em; padding-right:14px}
.rfh-pop-sub{font-size:var(--rf-t-xs); color:var(--rf-ink-3); line-height:1.4}
.rfh-pop-st{display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.rfh-pop-go{font-size:var(--rf-t-xs); font-weight:600; color:var(--rf-brand); margin-top:2px}
.rfh-pop-go:hover{color:var(--rf-brand-hover)}
.rfh-map .leaflet-control-attribution{
  background:color-mix(in srgb, var(--rf-surface) 86%, transparent);
  color:var(--rf-ink-3); font-size:10.5px; padding:2px 7px; border-radius:6px 0 0 0;
}
.rfh-map .leaflet-control-attribution a{color:var(--rf-ink-2)}

/* ============================================================================
   NEARBY PANEL (§19) + STATION CARD (§20)
   ========================================================================= */
.rfh-nearby{display:flex; flex-direction:column; min-height:0}
.rfh-nearby-head{display:flex; align-items:baseline; justify-content:space-between; gap:var(--rf-3); margin-bottom:var(--rf-3)}
.rfh-nearby-head h2{font-size:var(--rf-t-h3); letter-spacing:-.012em; white-space:nowrap}
.rfh-nearby-head .rfh-btn--text{flex:0 0 auto; font-size:var(--rf-t-xs)}
.rfh-nearby-list{display:flex; flex-direction:column; gap:var(--rf-2); overflow-y:auto; overscroll-behavior:contain; flex:1 1 auto; min-height:0; padding-right:2px}
.rfh-nearby-list::-webkit-scrollbar{width:6px}
.rfh-nearby-list::-webkit-scrollbar-thumb{background:var(--rf-line-strong); border-radius:3px}
.rfh-nearby-foot{margin-top:var(--rf-3); font-size:11.5px; color:var(--rf-ink-3); line-height:1.45}

.rfh-st{display:block; padding:13px 14px}
.rfh-st-top{display:flex; align-items:baseline; justify-content:space-between; gap:var(--rf-3)}
.rfh-st-name{font-size:14.5px; font-weight:650; letter-spacing:-.012em; color:var(--rf-ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.rfh-st-dist{font:600 12.5px/1 var(--rf-mono); color:var(--rf-ink-2); flex:0 0 auto}
.rfh-st-addr{margin-top:3px; font-size:var(--rf-t-xs); color:var(--rf-ink-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.rfh-st-foot{display:flex; align-items:center; gap:8px; margin-top:9px; flex-wrap:wrap}
.rfh-st-kinds{font:500 11.5px/1 var(--rf-mono); color:var(--rf-ink-3); letter-spacing:.02em}

/* status pill — colour AND word, never colour alone (§45) */
.rfh-status{display:inline-flex; align-items:center; gap:5px; height:22px; padding:0 8px;
  border-radius:var(--rf-r-pill); font:600 11px/1 var(--rf-sans); white-space:nowrap}
.rfh-status i{width:6px; height:6px; border-radius:50%; flex:0 0 auto}
.rfh-status.s-ok{background:var(--rf-ok-dim); color:var(--rf-ok)}
.rfh-status.s-ok i{background:var(--rf-ok)}
.rfh-status.s-limited{background:var(--rf-limited-dim); color:var(--rf-limited)}
.rfh-status.s-limited i{background:var(--rf-limited)}
.rfh-status.s-out{background:var(--rf-out-dim); color:var(--rf-out)}
.rfh-status.s-out i{background:var(--rf-out)}
.rfh-status.s-unknown{background:var(--rf-unknown-dim); color:var(--rf-unknown)}
.rfh-status.s-unknown i{background:var(--rf-unknown)}
.rfh-status.s-ev{background:var(--rf-ev-dim); color:var(--rf-ev)}
.rfh-status.s-ev i{background:var(--rf-ev)}
.rfh-age{font:500 11px/1 var(--rf-mono); color:var(--rf-ink-3)}

/* skeletons — a shape, never a fake reading */
.rfh-sk{border-radius:var(--rf-r-card); border:1px solid var(--rf-line-faint); background:var(--rf-surface); height:78px; position:relative; overflow:hidden}
.rfh-sk::after{content:""; position:absolute; inset:0; transform:translateX(-100%);
  background:linear-gradient(90deg, transparent, var(--rf-line-faint), transparent); animation:rfhSk 1.5s infinite}
@keyframes rfhSk{100%{transform:translateX(100%)}}
.rfh-empty{padding:16px; border:1px dashed var(--rf-line); border-radius:var(--rf-r-card); font-size:var(--rf-t-sm); color:var(--rf-ink-2); line-height:1.5}

/* ============================================================================
   DRAWERS (§12, §13) — filters, route, tools
   ========================================================================= */
.rfh-scrim{position:fixed; inset:0; z-index:90; background:rgba(3,10,15,.62);
  opacity:0; pointer-events:none; transition:opacity var(--rf-mid)}
.rfh-scrim.is-open{opacity:1; pointer-events:auto}
.rfh-drawer{
  position:fixed; top:0; right:0; bottom:0; z-index:100; width:min(420px, 100vw);
  display:flex; flex-direction:column;
  background:var(--rf-surface); border-left:1px solid var(--rf-line);
  box-shadow:var(--rf-sh-3); transform:translateX(100%);
  transition:transform var(--rf-mid); visibility:hidden;
}
.rfh-drawer.is-open{transform:none; visibility:visible}
.rfh-drawer-head{display:flex; align-items:center; justify-content:space-between; gap:var(--rf-3);
  padding:var(--rf-5) var(--rf-5) var(--rf-4); border-bottom:1px solid var(--rf-line)}
.rfh-drawer-head h2{font-size:var(--rf-t-h3)}
.rfh-x{width:38px; height:38px; display:inline-flex; align-items:center; justify-content:center;
  border-radius:var(--rf-r-chip); border:1px solid var(--rf-line); background:transparent;
  color:var(--rf-ink-2); cursor:pointer; transition:background var(--rf-fast), color var(--rf-fast)}
.rfh-x:hover{background:var(--rf-raised); color:var(--rf-ink)}
.rfh-x svg{width:17px; height:17px}
.rfh-drawer-body{flex:1 1 auto; overflow-y:auto; padding:var(--rf-5); display:flex; flex-direction:column; gap:var(--rf-6)}
.rfh-drawer-foot{padding:var(--rf-4) var(--rf-5); border-top:1px solid var(--rf-line); display:flex; gap:var(--rf-2)}
.rfh-fgroup > h3{font:600 var(--rf-t-label)/1 var(--rf-mono); letter-spacing:.14em; text-transform:uppercase; color:var(--rf-ink-3); margin-bottom:var(--rf-3)}
/* Inside a drawer the chips wrap. The hero's swipe-strip treatment below 768px
   sets negative side margins to bleed to the viewport edge, which inside a
   panel would push them under its padding. */
.rfh-drawer .rfh-quick{margin:0!important; padding-inline:0!important; flex-wrap:wrap!important; overflow:visible!important}
.rfh-field{display:flex; flex-direction:column; gap:7px}
.rfh-field label{font-size:var(--rf-t-xs); color:var(--rf-ink-2); font-weight:500}
.rfh-input,.rfh-select{
  height:44px; padding:0 13px; border-radius:var(--rf-r-ctl);
  background:var(--rf-raised); border:1px solid var(--rf-line);
  color:var(--rf-ink); font:400 15px/1 var(--rf-sans); outline:none; width:100%;
  transition:border-color var(--rf-fast);
}
.rfh-input:focus,.rfh-select:focus{border-color:var(--rf-brand-edge)}
.rfh-note{font-size:var(--rf-t-xs); color:var(--rf-ink-3); line-height:1.5}

/* ============================================================================
   SECTIONS
   ========================================================================= */
.rfh-sec{padding-block:var(--rf-10)}
.rfh-sec--tight{padding-block:var(--rf-9)}
.rfh-sec--alt{background:var(--rf-bg-2); border-block:1px solid var(--rf-line-faint)}
.rfh-sec-head{display:flex; flex-direction:column; gap:var(--rf-3); margin-bottom:var(--rf-8); max-width:64ch}
.rfh-sec-head.c{align-items:center; text-align:center; margin-inline:auto}
.rfh-grid{display:grid; gap:var(--rf-4)}
.rfh-grid--4{grid-template-columns:repeat(4,minmax(0,1fr))}
.rfh-grid--3{grid-template-columns:repeat(3,minmax(0,1fr))}
.rfh-grid--2{grid-template-columns:repeat(2,minmax(0,1fr))}

/* value strip (§23) */
.rfh-val{padding:var(--rf-5); display:flex; flex-direction:column; gap:var(--rf-3)}
.rfh-ic{width:38px; height:38px; border-radius:var(--rf-r-chip); display:inline-flex; align-items:center; justify-content:center;
  background:var(--rf-brand-dim); color:var(--rf-brand); border:1px solid var(--rf-brand-edge)}
.rfh-ic svg{width:19px; height:19px}
.rfh-ic--ev{background:var(--rf-ev-dim); color:var(--rf-ev); border-color:var(--rf-ev)}
.rfh-ic--amber{background:var(--rf-limited-dim); color:var(--rf-limited); border-color:var(--rf-limited)}
.rfh-val p{color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.55}

/* data trust (§24) — two big cards, provenance never mixed */
.rfh-src{padding:var(--rf-6); display:flex; flex-direction:column; gap:var(--rf-4)}
.rfh-src-tag{display:inline-flex; align-items:center; gap:7px; align-self:flex-start;
  height:26px; padding:0 11px; border-radius:var(--rf-r-pill);
  font:600 var(--rf-t-label)/1 var(--rf-mono); letter-spacing:.12em; text-transform:uppercase}
.rfh-src--official .rfh-src-tag{background:var(--rf-brand-dim); color:var(--rf-brand); border:1px solid var(--rf-brand-edge)}
.rfh-src--driver .rfh-src-tag{background:var(--rf-ev-dim); color:var(--rf-ev); border:1px solid var(--rf-ev)}
.rfh-src p{color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.6}
.rfh-dl{display:grid; grid-template-columns:auto 1fr; gap:9px var(--rf-4); margin:0; font-size:var(--rf-t-xs)}
.rfh-dl dt{color:var(--rf-ink-3)}
.rfh-dl dd{margin:0; color:var(--rf-ink); font-family:var(--rf-mono); font-size:12.5px}

/* how it works (§26)

   RF-ONE-JOURNEY (20 September 2026). This was .rfh-step: a .rfh-card with 20px
   of padding, a border and a shadow, three of them in a 3-up grid. On a phone
   the grid collapsed to one column and the section became three bordered
   rectangles — measured at 375px, 186px each, so one step filled most of what a
   reader could see and the sequence stopped reading as a sequence.

   A border says "separate object". These are one process in a fixed order, and
   the order is the whole point. So the boxes are gone and the border budget is
   spent on the line that connects them instead — which is the only decoration
   here that states something true.

   Costed rather than guessed: dropping the card takes 20px of padding top and
   bottom, a 1px border and a shadow off every step. */
.rfh-journey{list-style:none; margin:0; padding:0; display:grid}
.rfh-leg{position:relative; display:grid; grid-template-columns:28px minmax(0,1fr);
  gap:0 var(--rf-4); padding:0 0 var(--rf-6)}
.rfh-leg:last-child{padding-bottom:0}

/* The route line, in the dot gutter. A pseudo-element rather than a border so
   it can be scaled from one end without touching layout — transform and opacity
   are the only two things worth animating (GUIDELINES §H5). */
.rfh-leg::before{content:""; position:absolute; left:13px; top:30px; bottom:2px; width:2px;
  border-radius:2px; background:var(--rf-brand-edge);
  transform-origin:top; transform:scaleY(1)}
.rfh-leg:last-child::before{content:none}

.rfh-leg-n{grid-column:1; position:relative; z-index:1;
  display:inline-flex; align-items:center; justify-content:center; width:28px; height:28px;
  border-radius:50%; background:var(--rf-brand-dim); border:1px solid var(--rf-brand-edge);
  color:var(--rf-brand); font:600 13px/1 var(--rf-mono)}
.rfh-leg-body{grid-column:2; display:flex; flex-direction:column; gap:5px; padding-top:3px}
.rfh-leg-body h3{margin:0; font-size:var(--rf-t-h3); letter-spacing:-.012em; line-height:1.3}
.rfh-leg-body p{margin:0; color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.55}

/* The one-time draw. Default is DRAWN: if main.js never runs, or the reader has
   asked for less motion, the line is simply there rather than parked at zero
   waiting for an observer that may never fire. main.js adds --anim only when the
   section is still below the fold and motion is allowed, then .is-drawn when it
   arrives. Runs once; nothing here loops, glows or pulses. */
.rfh-journey--anim .rfh-leg::before{transform:scaleY(0)}
.rfh-journey--anim.is-drawn .rfh-leg::before{transform:scaleY(1);
  transition:transform 620ms var(--rf-ease)}
.rfh-journey--anim.is-drawn .rfh-leg:nth-child(2)::before{transition-delay:120ms}
@media (prefers-reduced-motion:reduce){
  .rfh-journey--anim .rfh-leg::before{transform:scaleY(1); transition:none}
}

/* Desktop: the same journey laid on its side, so it still reads as one road
   rather than three columns that happen to be numbered. */
@media (min-width:768px){
  .rfh-journey{grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--rf-6)}
  .rfh-leg{grid-template-columns:minmax(0,1fr); gap:var(--rf-3); padding:0}
  /* RF-THE-LINE-WENT-THROUGH-THE-WORDS (21 September 2026). Reported with a
     screenshot: on desktop the route line ran straight through all three
     headings, so "Search or share your location" read as struck out.

     The cause is one declaration that was NOT overridden here. The mobile
     layout puts the dot in column 1 and the text in column 2:

         .rfh-leg-n   {grid-column:1}
         .rfh-leg-body{grid-column:2}

     This block changed the TRACK LIST to a single column but left those two
     alone. grid-column:2 with only one track defined does not wrap — it makes
     an IMPLICIT second column. So the dot stayed beside the heading instead of
     above it, the heading's text sat at the dot's own vertical centre, and the
     line drawn at top:13px (the centre of a 28px dot) crossed it.

     Both children are put back in column 1 and given explicit rows, so the dot
     is above the text and 13px is clear of it. */
  .rfh-leg-n{grid-column:1; grid-row:1}
  .rfh-leg-body{grid-column:1; grid-row:2; padding-top:0}
  .rfh-leg::before{left:36px; right:calc(var(--rf-6) * -1); top:13px; bottom:auto;
    width:auto; height:2px; transform-origin:left; transform:scaleX(1)}
  .rfh-journey--anim .rfh-leg::before{transform:scaleX(0)}
  .rfh-journey--anim.is-drawn .rfh-leg::before{transform:scaleX(1)}
  @media (prefers-reduced-motion:reduce){
    .rfh-journey--anim .rfh-leg::before{transform:scaleX(1)}
  }
}

/* ============================================================================
   RF-SCENE-TRUST (22 September 2026) — scene 01 of the lower homepage.

   Three sections became one: .rfh-caps (1084px), .rfh-sec--tight (351px) and
   .rfh-flow (729px), measured at 1440 before the change. They answered one
   question between them and each restarted the reader with its own eyebrow,
   heading and grid.

   THE LAYOUT IS ASYMMETRIC ON PURPOSE. The layers take the wide column and the
   two pin states sit beside them, narrower, because they are the evidence for
   layer 3 rather than a fourth thing to read. Centred columns of equal weight
   are what made the old page read as a catalogue.

   REUSE, NOT INVENTION. The layer list is .rfh-journey / .rfh-leg — the
   component the three steps already used — and the ring is .rfh-dot from the
   loop section. Everything below is the scene's own frame: two overrides on
   the journey, and type for the parts that had no home before.

   THE NAME. This block was called .rfh-trust for about twenty minutes, and
   .rfh-trust is the hero's own list of three facts — "Every station comes from
   OpenStreetMap", and the two under it. The collision did two things, neither
   of them visible in the source: the scene's padding landed on that list, and
   the phone block's `.rfh-trust{display:none}` hid the entire scene on every
   phone. It was found by photographing the scene and getting a picture of the
   hero instead. A class name is an interface; reusing one is the same fault as
   a second copy of a component, and it hides better.
   ========================================================================= */
.rfh-readpin{padding-block:var(--rf-10)}
.rfh-readpin-head{max-width:34ch; margin-bottom:var(--rf-7)}
.rfh-readpin-head h2{margin:var(--rf-2) 0 0; font-size:var(--rf-t-h2); letter-spacing:-.02em}
.rfh-readpin-body{display:grid; gap:var(--rf-7)}

/* The source line under each layer. Not a caption and not body copy: it is the
   answer to "says who", so the name carries the weight and the rest steps
   back. This is what .rfh-flow's whole diagram was for, attached to the claim
   it supports instead of living two sections away. */
.rfh-readpin-src{margin-top:var(--rf-2) !important; font-size:var(--rf-t-xs);
  color:var(--rf-ink-3); line-height:1.5}
.rfh-readpin-src b{color:var(--rf-ink-2); font-weight:600}

.rfh-readpin-pin{margin:0; display:grid; gap:var(--rf-4); align-content:start}
.rfh-readpin-state{display:grid; grid-template-columns:auto minmax(0,1fr);
  grid-template-rows:auto auto; gap:2px var(--rf-3); align-items:center}
.rfh-readpin-state .rfh-dot{grid-row:1 / span 2}
.rfh-readpin-state b{font-size:var(--rf-t-sm); font-weight:600; color:var(--rf-ink); line-height:1.25}
.rfh-readpin-state em{font-style:normal; font-size:var(--rf-t-xs); color:var(--rf-ink-3);
  line-height:1.4}
.rfh-readpin-pin figcaption{font-size:var(--rf-t-sm); color:var(--rf-ink-2); line-height:1.55;
  padding-top:var(--rf-4); border-top:1px solid var(--rf-line); max-width:44ch}

/* The seven destinations that were seven rows of a card wall. A row of links
   is not a navigation bar — it is the end of the scene saying where each of
   these lives, so it takes the quiet weight and the last one, which leaves the
   product for an explanation, is marked as the different one. */
.rfh-readpin-links{margin-top:var(--rf-7); padding-top:var(--rf-4);
  border-top:1px solid var(--rf-line);
  display:flex; flex-wrap:wrap; gap:var(--rf-2) var(--rf-5);
  font-size:var(--rf-t-sm)}
.rfh-readpin-links a{color:var(--rf-ink-2); text-decoration:none;
  border-bottom:1px solid transparent; padding-bottom:1px;
  transition:color var(--rf-fast), border-color var(--rf-fast)}
.rfh-readpin-links a:hover{color:var(--rf-ink); border-bottom-color:var(--rf-brand-edge)}
.rfh-readpin-links a:focus-visible{outline:2px solid var(--rf-brand); outline-offset:3px}
.rfh-readpin-links .rfh-readpin-more{color:var(--rf-brand); font-weight:600; margin-left:auto}
.rfh-readpin-links .rfh-readpin-more:hover{color:var(--rf-brand-hover)}

@media (min-width:768px){
  /* The journey turns three-across at 768 because the three steps were a
     sequence read left to right. These layers are a pin read from the inside
     out, beside its evidence, so the track list is put back to one column and
     the dot returns to the gutter — both declarations the desktop block above
     changed, changed back here rather than duplicated as a second component. */
  .rfh-readpin-layers{grid-template-columns:minmax(0,1fr); gap:0}
  .rfh-readpin-layers .rfh-leg{grid-template-columns:28px minmax(0,1fr);
    gap:0 var(--rf-4); padding:0 0 var(--rf-6)}
  .rfh-readpin-layers .rfh-leg:last-child{padding-bottom:0}
  .rfh-readpin-layers .rfh-leg-n{grid-column:1; grid-row:1}
  .rfh-readpin-layers .rfh-leg-body{grid-column:2; grid-row:1; padding-top:3px}
  .rfh-readpin-layers .rfh-leg::before{left:13px; right:auto; top:30px; bottom:2px;
    width:2px; height:auto; transform-origin:top; transform:scaleY(1)}
  .rfh-readpin-layers.rfh-journey--anim .rfh-leg::before{transform:scaleY(0)}
  .rfh-readpin-layers.rfh-journey--anim.is-drawn .rfh-leg::before{transform:scaleY(1)}

  /* The two pin states are the evidence for layer 3, not a fourth thing to
     read, so they sit beside it rather than beside the heading. Photographed
     top-aligned first: 300px of empty column under the caption, and the
     evidence floating a long way from the claim it supports. */
  .rfh-readpin-body{grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
    gap:var(--rf-8); align-items:end}
  .rfh-readpin-head{max-width:40ch}
}
@media (min-width:1100px){
  .rfh-readpin-body{gap:var(--rf-9)}
  .rfh-readpin-pin{padding-top:var(--rf-3)}
}
@media (prefers-reduced-motion:reduce){
  .rfh-readpin-layers.rfh-journey--anim .rfh-leg::before{transform:scaleY(1); transition:none}
  .rfh-readpin-links a{transition:none}
}

/* RF-CAPABILITIES-ARE-LINKS (21 September 2026). These were <div>s dressed as
   cards: icon, heading, sentence, no destination. They are <a>s now, and the
   styling has to admit it — a thing that looks pressable and is not costs the
   reader a click to discover.

   The arrow is the affordance. It sits at the end of the card, moves 3px on
   hover, and is the only moving part: no lift, no shadow bloom, no scale.
   GUIDELINES §H5 — Refuelia has no bounce. */
.rfh-val--go{position:relative; text-decoration:none; color:inherit; display:flex;
  transition:border-color var(--rf-fast), background var(--rf-fast)}
.rfh-val--go:hover{border-color:var(--rf-brand-edge); background:var(--rf-raised)}
.rfh-val--go:focus-visible{outline:2px solid var(--rf-brand); outline-offset:2px}
.rfh-val-go{width:18px; height:18px; margin-top:auto; color:var(--rf-brand);
  transition:transform var(--rf-fast)}
.rfh-val--go:hover .rfh-val-go{transform:translateX(3px)}
@media (prefers-reduced-motion:reduce){
  .rfh-val--go,.rfh-val-go{transition:none}
  .rfh-val--go:hover .rfh-val-go{transform:none}
}

/* The secondary row. Two entries that are real but not headline features, so
   they get a row rather than two more cards the same size as the four above —
   which is how a capability section turns into a card wall. */
.rfh-more{display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--rf-3); margin-top:var(--rf-4)}
@media (max-width:760px){.rfh-more{grid-template-columns:1fr}}
.rfh-more-item{display:flex; align-items:center; gap:var(--rf-3);
  padding:var(--rf-3) var(--rf-4); border:1px solid var(--rf-line);
  border-radius:var(--rf-r-ctl); text-decoration:none; color:inherit;
  transition:border-color var(--rf-fast), background var(--rf-fast)}
.rfh-more-item:hover{border-color:var(--rf-brand-edge); background:var(--rf-raised)}
.rfh-more-item:focus-visible{outline:2px solid var(--rf-brand); outline-offset:2px}
.rfh-more-item .rfh-ic{width:34px; height:34px; flex:0 0 auto}
.rfh-more-item .rfh-ic svg{width:17px; height:17px}
.rfh-more-item span{display:flex; flex-direction:column; gap:1px; min-width:0; flex:1 1 auto}
.rfh-more-item strong{font-size:var(--rf-t-sm); font-weight:650; color:var(--rf-ink)}
.rfh-more-item em{font-style:normal; font-size:var(--rf-t-xs); color:var(--rf-ink-3)}
.rfh-more-item .rfh-val-go{margin-top:0; width:16px; height:16px}
.rfh-more-item:hover .rfh-val-go{transform:translateX(3px)}

/* route band (§27) */
.rfh-band{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:var(--rf-8); align-items:center}
.rfh-band-copy{display:flex; flex-direction:column; gap:var(--rf-4); align-items:flex-start}

/* prose blocks (§29) */
.rfh-prose{display:flex; flex-direction:column; gap:var(--rf-6)}
.rfh-prose article{display:flex; flex-direction:column; gap:var(--rf-3); padding-bottom:var(--rf-6); border-bottom:1px solid var(--rf-line-faint)}
.rfh-prose article:last-child{border-bottom:0; padding-bottom:0}
.rfh-prose h3{font-size:17.5px}
.rfh-prose p{color:var(--rf-ink-2); font-size:15.5px; line-height:1.68; max-width:72ch}
.rfh-prose a{color:var(--rf-brand); text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:1px}
.rfh-prose a:hover{color:var(--rf-brand-hover)}

/* guides (§30) */
.rfh-guide{padding:var(--rf-5); display:flex; flex-direction:column; gap:var(--rf-3); height:100%}
.rfh-guide p{color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.55; flex:1 1 auto}
.rfh-guide-meta{font:500 11.5px/1 var(--rf-mono); color:var(--rf-ink-3); letter-spacing:.03em}

/* final CTA (§31) */
.rfh-cta{padding:var(--rf-10) var(--rf-6); text-align:center; display:flex; flex-direction:column; align-items:center; gap:var(--rf-5);
  border-radius:var(--rf-r-panel); border:1px solid var(--rf-brand-edge);
  background:linear-gradient(180deg, var(--rf-brand-dim), transparent 70%), var(--rf-surface)}
.rfh-cta-row{display:flex; flex-wrap:wrap; gap:var(--rf-3); justify-content:center}

/* THE FOOTER IS NOT HERE ANY MORE — see the note where the header used to be.
   shell.css owns .site-footer for every page. */

/* THE MOBILE BOTTOM NAV IS NOT HERE ANY MORE.
   RF-ONE-TABBAR (19 September 2026): .rfh-tabbar was this page's own bar, with
   its own items, beside a different .rf-tabbar on /map, /chat and /account with
   different items again, and nothing at all on the other 534 pages. Three
   mobile navigations, two of them site navigation, disagreeing about what the
   sections of this site are — and this one's "Stations" tab pointed at
   /fuel-prices, which is a page about prices.
   One component now: templates/core/tabbar.tpl, styled in shell.css, written in
   by apply-shell.py, on the homepage and /map only. */

/* ============================================================================
   RESPONSIVE
   ========================================================================= */

/* ---- 1200 and below: the Nearby column moves under the map ------------- */
@media (max-width:1199px){
  :root{--rfh-hero:clamp(520px, calc(100vh - var(--rf-header) - 90px), 660px)}
  :root{--rfh-hero:clamp(520px, calc(100svh - var(--rf-header) - 90px), 660px)}
  /* The Nearby column drops under the map here, so the row is two rows and its
     height is no longer one number to pin. */
  .rfh-hero-grid{grid-template-columns:minmax(0,38fr) minmax(0,62fr); height:auto; min-height:var(--rfh-hero)}
  .rfh-nearby{grid-column:1 / -1; min-height:0}
  .rfh-nearby-list{flex-direction:row; overflow-x:auto; overflow-y:hidden; padding-bottom:4px}
  .rfh-nearby-list > *{flex:0 0 clamp(230px, 32vw, 290px)}
  .rfh-grid--4{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ---- tablet ------------------------------------------------------------ */
@media (max-width:900px){
  .rfh-grid--3{grid-template-columns:repeat(2,minmax(0,1fr))}
  .rfh-band{grid-template-columns:1fr; gap:var(--rf-6)}
}

/* ---- phone: a different composition, not a squeezed desktop (§21) ------ */
@media (max-width:767px){

  /* RF-ONE-PAGE-END (23 September 2026). This was a flat 64px "room for the
     tab bar", and it was the empty band the owner circled under the home
     page's footer on an iPhone: the bar leaves once the reader is past the
     product area (main.js, .rf-content-view), so at the end of the page it is
     never there, and the 64px reserved room for nothing. Every other page ends
     at its footer. The room now follows the bar: --rf-tabbar-h is 56px while
     the bar is on screen and 0 once it has gone. */
  body.rfh{padding-bottom:var(--rf-tabbar-h,0px)}

  /* The phone header — hamburger, drawer, close button — is shell.css's job
     now, for every page rather than this one. */

  .rfh-hero{padding-block:var(--rf-4) var(--rf-5)}
  .rfh-hero-grid{display:flex; flex-direction:column; gap:var(--rf-4); height:auto; min-height:0}

  /* RF-PHONE-MAP-FIRST (22 September 2026). MEASURED at 390x844 on the local
     worker render, before any of this:

         map begins at            y446
         map visible on screen    369px, its lower half under the cookie banner
         prose before the map     200 characters
         controls reaching /map   one, the tab bar's "Stations" at y788
         document                 8661px

     A phone read the product description before it met the product. The order
     below inverts that: brand, one claim, the live map, then the action, then
     the workflow that was already there.

     HOW, without touching the desktop. `.rfh-hero-left` is one grid child
     holding seven blocks, so its children cannot be interleaved with the map
     card beside it — unless the box itself stops existing. `display:contents`
     removes the wrapper and promotes its children to flex items of the hero
     grid, which is exactly what `order` needs. It is safe here for one
     measured reason: this element carries no background, border, shadow or
     transform at any width — only `display:flex` and padding, and the padding
     is already zeroed on this breakpoint. If it ever gains one of those, this
     rule has to go, because `display:contents` would silently drop it.

     Nothing is hidden and nothing is deleted. The subtitle, the trust list and
     the chips all still render, below the fold, where a visitor who wants the
     explanation will find it. */
  /* EVERY ONE OF THESE IS SCOPED TO THE HERO, and the first version was not.
     `order` only means anything to a flex or grid child, so a bare
     `.rfh-eyebrow{order:1}` looked harmless — until scene 03 arrived with a
     flex column of its own. Its eyebrow inherited order 1 while its siblings
     sat at the default 0, and the label that introduces the scene rendered
     UNDERNEATH the scene's own footnote. Photographed at 390, not reasoned
     about. A bare class in a breakpoint is a global. */
  .rfh-hero-left{display:contents}
  .rfh-hero .rfh-eyebrow  { order:1 }
  .rfh-hero h1            { order:2 }
  .rfh-hero .rfh-mapcard  { order:3 }
  .rfh-hero .rfh-hero-go  { order:4; display:flex }
  .rfh-hero .rfh-search   { order:5 }
  .rfh-hero .rfh-hero-loc { order:6 }
  .rfh-hero .rfh-quick    { order:7 }
  .rfh-hero .rfh-fuelpick { order:7 }   /* RF-FUEL-PICK: the list now sits inside this */
  .rfh-hero .rfh-hero-sub { order:8 }
  .rfh-hero .rfh-trust    { order:9 }
  .rfh-hero .rfh-nearby   { order:10 }

  /* `display:contents` dissolved the column that owned these margins, so the
     rhythm is restated as the flex gap plus the few deliberate exceptions.
     RF-HERO-RHYTHM's meanings are unchanged: 8 binds a label to what it
     labels, 12 separates two lines of one thought, 20 ends the thought. */
  .rfh-hero .rfh-eyebrow{margin-bottom:0}
  .rfh-hero-go .rfh-btn{flex:1; min-height:48px; font-size:16px}
  /* RF-HERO-RHYTHM (21 September 2026). The owner asked to "подровнять" the
     hero on a phone. Measured at 390 before touching it, gap above each block:

         eyebrow -> h1      12px
         h1 -> subtitle      0px      <- collapsed; they read as one block
         subtitle -> search 16px
         search -> locate    8px

     Four different gaps, no pattern, and the headline touching its own
     subtitle. Not a spacing scale — four numbers that were each right next to
     the thing they were written for.

     The rhythm below says something instead: 8 binds a label to what it
     labels, 12 separates two lines of the same thought, 20 ends the thought.
     The subtitle's 0 came from --rf-2 landing under an h1 whose own margin
     collapsed through it; it is explicit now. */
  /* These four carried their own margin-top while they were stacked inside
     .rfh-hero-left. They are flex items of the hero grid now and the grid's
     gap already separates them, so a margin here would be that gap counted
     twice — the exact fault RF-HERO-RHYTHM was written to end. */
  .rfh-hero h1{font-size:clamp(26px,7.2vw,32px); margin-top:0}
  .rfh-hero-sub{margin-top:0; font-size:14.5px; line-height:1.5; max-width:none}
  .rfh-search{margin-top:0; padding:5px 5px 5px 12px}
  .rfh-search input{height:42px; font-size:16px}   /* 16px: iOS must not zoom */
  /* RF-PLACEHOLDER-FITS (24 September 2026): at 360px the placeholder read
     "Search a city or addre" — cut mid-word. The input carried the site-wide
     14px side padding inside a box that already has its own; the icon's gap
     is enough. And if a language's words are still longer, they end in an
     ellipsis instead of a cut. */
  .rfh-search input{padding:0 4px !important; text-overflow:ellipsis}
  .rfh-search input::placeholder{text-overflow:ellipsis}
  .rfh-search .rfh-btn{padding:0 14px}
  .rfh-hero-loc{margin-top:0}
  .rfh-hero-loc .rfh-btn{flex:1 1 auto}

  /* chips: one swipeable row, with the edge fade that says there is more */
  .rfh-quick{flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
    margin-inline:calc(var(--rf-gutter) * -1); padding-inline:var(--rf-gutter); scroll-padding-inline:var(--rf-gutter)}
  .rfh-quick::-webkit-scrollbar{display:none}
  .rfh-chip{height:38px}

  .rfh-trust{display:none}            /* the same facts live in the section below */

  /* 46vh was sized for a map arriving at y446, where anything taller ran off
     the screen. It is the first thing on the page now and 52vh of 844 is
     439px — enough for the note, the legend and a readable spread of pins,
     while still leaving the action and the search field above the fold. */
  .rfh-mapcard{height:52vh; min-height:300px; border-radius:var(--rf-r-card)}
  /* On a phone the map is about 350px wide, and three overlays cannot share the
     top edge. Measured at 390px: the note had 96px of the width taken by the
     tools and lost the words "area ·" mid-sentence — and the clause it was
     losing ("none checked by a driver recently") is the honest half of the
     line, so truncating it is the one thing that must not happen.
     The tools move to the bottom-right instead, where the only neighbour is the
     legend, and the note gets the whole top edge and as many lines as it needs. */
  .rfh-map-note{max-width:calc(100% - 24px); font-size:12px; padding:7px 10px}
  .rfh-map-tools{top:auto; bottom:12px; right:10px}
  /* "Full map" comes off the map on a phone. The tab bar already carries Map,
     and two controls for one destination inside a 350px-wide card is exactly
     the crowding this redesign exists to undo. What is left is the recentre
     icon, 36px, which leaves the legend room to sit on two lines instead of
     three and stop before it. */
  .rfh-map-tools a.rfh-maptool{display:none}
  .rfh-legend{gap:3px 9px; padding:7px 10px; max-width:calc(100% - 62px)}
  .rfh-legend span{font-size:11px}
  .rfh-map-zoom{display:none}          /* pinch is the gesture people use */

  .rfh-nearby-list{flex-direction:column; overflow:visible}
  .rfh-nearby-list > *{flex:none}

  .rfh-sec{padding-block:var(--rf-9)}
  .rfh-grid--4,.rfh-grid--3,.rfh-grid--2{grid-template-columns:1fr}
  .rfh-cta{padding:var(--rf-8) var(--rf-5)}
  .rfh-drawer{width:100vw; top:auto; height:92dvh; border-radius:var(--rf-r-panel) var(--rf-r-panel) 0 0;
    border-left:0; border-top:1px solid var(--rf-line); transform:translateY(100%)}
  .rfh-drawer.is-open{transform:none}



  /* RF-FAB-CLEARS-THE-TABBAR (19 September 2026). Photographed at 390x844 on
     production: the assistant's floating button sat exactly on top of the
     fourth tab. Measured — the grid is 4 x 98px, "More" occupies x 293-391,
     and the button lands there — so on the one page most likely to be opened
     on a phone, the fourth navigation item could not be tapped.

     site.css already owns the mechanism: --rf-fab-base is the floor every
     floating control measures from, and it is raised for /map (html.rfa-map)
     and for the sticky CTA (html.rf-sticky-on). This bar arrived later and was
     never added to that list. 60px is the tab's min-height; the safe-area
     inset is already inside --rf-fab-safe, and the bar adds it as padding, so
     it is not counted twice.

     GUIDELINES §19: nothing floats over a tab bar, and every new bar joins
     this mechanism. */
  /* RF-ONE-TABBAR: 60px was this bar's own height, typed here. The bar is a
     shared component now and publishes --rf-tabbar-h, so the clearance is
     measured from the bar rather than from a number — the same rule
     map.html, and the shell-parity test, have followed all along. */
  body.rfh{--rf-fab-base:calc(16px + var(--rf-fab-safe) + var(--rf-tabbar-h,0px))}
}

@media (max-width:400px){
  /* RF-PHONE-MAP-FIRST (22 September 2026): this 44vh quietly beat the 52vh
     set for phones above, because 390 — the width the owner reviews at — is
     inside max-width:400px. The measurement said so: the map card came back
     369px tall when 439 was asked for, and the 70px difference was this rule.
     A narrower phone gets slightly less map, not a different layout. */
  .rfh-mapcard{height:50vh}
  .rfh-hero h1{font-size:26px}
}

/* ============================================================================
   MOTION (§42) — light, and honest about being optional
   ========================================================================= */
@media (prefers-reduced-motion:reduce){
  body.rfh *,body.rfh *::before,body.rfh *::after{
    animation-duration:.001ms!important; animation-iteration-count:1!important;
    transition-duration:.001ms!important; scroll-behavior:auto!important;
  }
}

/* RF-PHONE-TYPE (20 September 2026), the prose half. Measured at 375px: the
   paragraph inside every card on this page rendered at 14px, because they all
   read --rf-t-sm. Asked for: 16-18.

   The token is NOT raised. --rf-t-sm is also the size of buttons, chips,
   eyebrows and nav labels, and inflating the chrome to fix the reading is how a
   phone layout stops fitting. These five selectors are the ones where that
   token was being used for SENTENCES rather than for controls, so only they
   move, and only on a phone — on desktop these paragraphs sit beside each other
   in narrow columns where 14px is right.

   Line height goes up with the size for the same reason it exists: 1.55 at 16px
   is the same rhythm 1.55 was at 14px, in a measure that is now wider. */
@media (max-width:767px){
  .rfh-val p,
  .rfh-src p,
  .rfh-leg-body p,
  .rfh-guide p{font-size:16px; line-height:1.6}

  /* RF-NOT-EVERYTHING-IS-A-CARD (20 September 2026, GUIDELINES §11).
     Measured on production at 375px, the homepage carried 20 bordered cards.
     Eight of them were .rfh-val:

         Live availability / Fuel prices / EV charging / Route planning
                                              205px each, 20px padding, icon box
         Confirm the fuel / queue / price / charger worked
                                              130-155px each, 20px padding

     Every one is an icon, a heading and ONE sentence. None is clickable, none
     is an independent object, and a border means "separate object" — so eight
     borders were drawn around eight sentences, 1,365px of stacked rectangles on
     a phone, to say four things and then four more.

     On a phone they become editorial rows: a hairline between them carries the
     separation a border was doing, at a fraction of the height. On desktop they
     sit four-across in a real grid, where a card IS the right object, so this
     is scoped to the phone and the desktop layout is untouched.

     Nothing shrinks that should not: the type is the 16px set above, the icon
     keeps a 28px box, and nothing here is a tap target. */
  /* Scoped with :has() and not written as a bare .rfh-grid--4. Caught in
     preview: the plain version also hit the GUIDES grid, which is four-across
     too — and those cards are real links, so removing their gap would have left
     four tappable cards touching each other. Without :has() the gap simply
     stays, which is the harmless direction to fail in. */
  .rfh-grid--4:has(.rfh-val){gap:0}
  .rfh-card.rfh-val{background:none; border:0; box-shadow:none; border-radius:0;
    padding:var(--rf-4) 0; gap:var(--rf-2)}
  .rfh-card.rfh-val + .rfh-card.rfh-val{border-top:1px solid var(--rf-line)}
  .rfh-val .rfh-ic{width:28px; height:28px; border-radius:8px}
  .rfh-val .rfh-ic svg{width:16px; height:16px}
  .rfh-prose p{font-size:16.5px; line-height:1.62}
}

/* Print: the map is a screen object; the words are not. */
@media print{
  .rfh-mapcard,.rf-tabbar,.rfh-drawer,.rfh-scrim{display:none!important}   /* the nav is shell.css's to hide */
  body.rfh{background:#fff; color:#000}
}

/* ============================================================================
   RF-FLOW — the homepage stops being a stack of boxes. 21 September 2026.
   ============================================================================
   Measured before this block, on the live homepage at 1440:

       9 sections, 5,289px tall
       344 elements inside <main> with a rounded border

   That number is what the owner means by "card wall", and by "generic AI
   website". The fix is not a softer border. It is that most of those thoughts
   were never objects: a sentence about where data comes from is prose, and
   prose in a bordered rectangle reads as a product tile.

   What separates content here instead: space, type scale, alignment and
   asymmetry. A border is spent only where something really is an object you
   can act on — a station result, a guide, a control.

   Two sections are rebuilt in this pass, the two the brief named:
     .rfh-flow   the data story, drawn as three sources converging on one map
     .rfh-loop   the driver story, drawn as the loop it actually is
   ========================================================================= */

/* ---- shared: sections that flow rather than sit in stripes --------------- */
/* RF-SCENE-REPORT (22 September 2026). This read:

       "No border-block, no --alt background. Separation is 1.4x the old
        padding and a change of composition."

   That was true while the section stood on the same dark ground as its
   neighbours — with nothing else to mark the boundary, the padding had to do
   it alone, and 9vw is 130px a side at 1440. Scene 02 stands on .rfs-paper
   now: the ground itself changes, edge to edge, which is the strongest
   separator the design system has. Keeping both is paying twice, and it cost
   259px of a 667px section.

   .rfh-flow is gone from the selector because the section is gone — merged
   into scene 01. A selector kept after its element dies reads as coverage. */
.rfh-loop{
  padding-block:clamp(48px, 5.6vw, 88px);
  position:relative;
}

/* ---- the data story ------------------------------------------------------ */
/* RF-FLOW-FITS (21 September 2026). Was max-width:20ch, and the owner
   photographed the result: "здесь много свободного места".

   MEASURED on the live page at 1440 before the fix:

       .rfh-flow-head   202 x 363 px      a 54px headline in a 202px column
       section height   963 px

   20ch is a sane measure for 16px body text and a trap for a clamp() headline:
   the ch unit tracks the FONT the element is set in, so at 54px it resolved to
   202px and six words wrapped onto six lines. The section then inherited that
   height, the three columns beside it were vertically centred against a
   363px-tall neighbour, and the whole thing became a tall room with the
   furniture round the edges.

   Measured in characters of ITS OWN size, a display line wants 16-22ch to
   break two or three times. 22ch at this scale is ~700px, which is the
   headline reading as one statement instead of a column. */
.rfh-flow-head{ margin-bottom:clamp(28px, 3vw, 48px); }
/* The measure belongs on the HEADLINE, not on the box around it. Both versions
   of this rule put max-width on .rfh-flow-head, a div inheriting 16px — so
   `22ch` resolved against 16px type and produced 222px, and a 52px headline
   wrapped eight times inside it. Measured: 202px at 20ch, 222px at 22ch, both
   wrong for the same reason. On the h2 the unit finally means what it reads
   like: 18 characters of the size the words are actually set in. */
.rfh-flow-head .rfh-flow-h,
.rfh-flow-head h2{ max-width:18ch; }
.rfh-flow-head h2{
  font-size:clamp(30px, 3.6vw, 54px); line-height:1.06; letter-spacing:-.02em;
  margin:var(--rf-2) 0 0; text-wrap:balance;
}
/* 3 / wire / 1 — deliberately not equal columns. The sources are a list, the
   result is a statement; equal thirds would say they are the same kind of
   thing, which is the mistake the four-card grid made. */
/* align-items:start, not center. Centring three columns of different heights
   against each other is what put 200px of air above the sources and below the
   result — each one floated in the middle of the tallest. They start together
   now, and the wire is positioned to meet them rather than the other way
   round. */
.rfh-flow-body{ display:grid; gap:clamp(24px, 3vw, 44px); align-items:start; }
.rfh-src-list{ list-style:none; margin:0; padding:0; display:grid; gap:clamp(20px, 2.4vw, 34px); }
.rfh-src-item h3{
  margin:0 0 4px; font-size:clamp(17px, 1.3vw, 21px); font-weight:600; color:var(--rf-ink);
}
.rfh-src-item p{ margin:0; color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.55; max-width:34ch; }
/* The one place a rule earns its keep on this section: a hairline that marks
   each source as a separate input without drawing a box round it. */
.rfh-src-item{ padding-left:var(--rf-4); border-left:1px solid var(--rf-line); }

/* Height comes from the sources beside it, not from a number of its own: a
   260px fixed wire next to a 283px list is a coincidence that breaks the first
   time the copy changes. 100% of the row, and the SVG stretches to it. */
.rfh-flow-wire{ display:none; width:100%; height:100%; min-height:150px; overflow:visible; }
.rfh-flow-wire path{
  fill:none; stroke:var(--rf-brand); stroke-width:1.5; opacity:.55;
  stroke-linecap:round; vector-effect:non-scaling-stroke;
}

.rfh-flow-out-h{ margin:0 0 var(--rf-2); font-weight:750; color:var(--rf-ink); font-size:clamp(18px,1.5vw,23px); }
.rfh-flow-out-p{ margin:0 0 var(--rf-4); color:var(--rf-ink-2); line-height:1.6; max-width:40ch; }

@media (min-width:900px){
  .rfh-flow-body{
    grid-template-columns:minmax(0,1fr) clamp(96px,10vw,170px) minmax(0,.95fr);
    align-items:stretch;            /* so the wire column has a height to fill */
  }
  .rfh-flow-wire{ display:block; align-self:stretch; }
  /* The wire converges on the result, so the result sits at the convergence —
     centred against the SOURCES column, which is the tallest thing in the row. */
  .rfh-flow-out{ align-self:center; }
}
@media (max-width:899px){
  /* Three lines converging sideways is unreadable at 390px. The same idea as a
     single vertical run: sources above, result below, one rule connecting. */
  .rfh-flow-out{
    margin-top:var(--rf-6); padding-top:var(--rf-6);
    border-top:1px solid var(--rf-line);
  }
}

/* ---- the driver loop ----------------------------------------------------- */
/* Asymmetric on purpose (§19): the copy takes a third, the steps a third, the
   pin demo the rest — three unequal columns, no card among them. */
.rfh-loop .rfh-wrap{ display:grid; gap:clamp(28px, 3.4vw, 56px); align-items:start; }
.rfh-loop-copy h2{
  font-size:clamp(28px, 3.2vw, 46px); line-height:1.08; letter-spacing:-.02em;
  margin:var(--rf-2) 0 var(--rf-3); text-wrap:balance;
}
.rfh-loop-lead{ margin:0 0 var(--rf-4); color:var(--rf-ink-2); line-height:1.6; max-width:38ch; }

.rfh-loop-steps{ list-style:none; margin:0; padding:0; display:grid; gap:var(--rf-4); counter-reset:none; }
.rfh-loop-steps li{
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:var(--rf-3);
  align-items:baseline; color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.55;
}
/* Exactly two children per row, always. See the note in index.html: an
   unwrapped <b> plus a bare text node is three grid items, not two. */
.rfh-loop-t{ display:block; min-width:0; }
.rfh-loop-steps b{ color:var(--rf-ink); font-weight:600; display:block; }
/* The number is a marker, not a badge: no circle, no fill, no border. */
.rfh-loop-n{
  font-family:var(--rf-mono); font-size:12px; color:var(--rf-brand);
  font-variant-numeric:tabular-nums;
}

/* RF-DOT-IS-NOT-A-PIN (21 September 2026). These were called .rfh-pin, and
   .rfh-pin is ALREADY the class home-map.js puts on every marker on the
   homepage map — 400 of them, 26px, rotated -45deg, coloured by status. My
   demo rule (34px, border-radius 50%) sits later in this file, so it would have
   won on all 400 and reshaped the live map to illustrate a diagram.

   Caught by measuring the live page: getComputedStyle listed .rfh-pin twice,
   once as the map's marker and once as mine. Exactly the collision that broke
   the chat this morning — a name already taken, in a scope that does not
   isolate. Renamed rather than made more specific: two different things should
   not share a name and then fight about it.

   RF-SAY-WHAT-THE-DOTS-MEAN, same day: each state carries its own label now.
   The owner read the unlabelled version as "непонятно", and he was right — the
   meaning was in a caption two lines below, which is not where a reader looks
   when they are looking at a picture. */
.rfh-dot-demo{
  display:grid; grid-template-columns:auto minmax(48px,1fr) auto;
  align-items:start; gap:var(--rf-3);
}
.rfh-dot-step{ display:grid; justify-items:start; gap:6px; max-width:16ch; }
.rfh-dot-step b{ font-size:var(--rf-t-sm); font-weight:600; color:var(--rf-ink); line-height:1.25; }
.rfh-dot-step em{ font-style:normal; font-size:var(--rf-t-xs); color:var(--rf-ink-3); line-height:1.4; }
.rfh-dot{
  width:30px; height:30px; border-radius:50%; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center;
  background:var(--rf-surface); border:2px solid var(--rf-unknown);
}
.rfh-dot i{ width:10px; height:10px; border-radius:50%; background:var(--rf-unknown); display:block; }
.rfh-dot--fresh{ border-color:var(--rf-ok); box-shadow:0 0 0 5px var(--rf-ok-dim); }
.rfh-dot--fresh i{ background:var(--rf-ok); }
/* The arrow names the thing that happens between the two states — which is the
   whole point of the picture, and was previously left unsaid. */
.rfh-dot-arrow{
  position:relative; margin-top:14px; display:block; height:1px;
  background:linear-gradient(90deg,var(--rf-line),var(--rf-ok));
}
.rfh-dot-arrow::after{
  content:''; position:absolute; right:0; top:-3px;
  border-left:6px solid var(--rf-ok); border-top:3.5px solid transparent; border-bottom:3.5px solid transparent;
}
.rfh-dot-arrow span{
  position:absolute; left:50%; top:-20px; transform:translateX(-50%);
  font-family:var(--rf-mono); font-size:10px; letter-spacing:.06em;
  text-transform:uppercase; color:var(--rf-brand); white-space:nowrap;
}
@media (max-width:520px){
  /* Three columns of labelled states do not fit a 390px screen. Stacked, with
     the arrow turned down the page — same reading order, same meaning. */
  .rfh-dot-demo{ grid-template-columns:1fr; gap:var(--rf-4); }
  .rfh-dot-step{ max-width:none; }
  .rfh-dot-arrow{
    width:1px; height:34px; margin:0 0 0 15px;
    background:linear-gradient(180deg,var(--rf-line),var(--rf-ok));
  }
  .rfh-dot-arrow::after{
    right:auto; top:auto; left:-3px; bottom:0;
    border-left:3.5px solid transparent; border-right:3.5px solid transparent;
    border-top:6px solid var(--rf-ok); border-bottom:0;
  }
  .rfh-dot-arrow span{ left:14px; top:6px; transform:none; }
}
.rfh-loop-pin{ margin:0; }
.rfh-loop-pin figcaption{
  margin-top:var(--rf-4); color:var(--rf-ink-2); font-size:var(--rf-t-xs); line-height:1.6; max-width:36ch;
}

/* ============================================================================
   RF-SCENE-REPORT (22 September 2026) — scene 02 of the lower homepage.

   The section is unchanged in substance: same four taps, same two honest
   states, same link. What changed is that the three parts stopped being three
   columns standing next to each other and became one flow with a line through
   it, on the paper band, in the order the thing actually happens:

       a station that exists  ->  four taps  ->  a station somebody checked
                                                 -> and the next driver sees it

   THE LINE IS THE POINT. It runs from the first state, down the taps, to the
   second — the report travelling from the forecourt to the pin. It is the
   page's existing route motif at a moment where it means something, not a
   rule drawn to fill a gutter. Same mechanism as .rfh-leg::before: a
   pseudo-element scaled from one end, so the draw costs no layout.

   ON PAPER. .rfs-paper remaps the token vocabulary, so nothing below names a
   colour: the dots, the numbers and the ink all arrive in paper values. The
   one thing the band did NOT remap was the status colours, which is fixed in
   shell.css where the band is defined rather than patched here.
   ========================================================================= */
.rfh-loop-flow{ position:relative; display:grid; gap:var(--rf-5); }
.rfh-loop-state{ position:relative; z-index:1;
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:var(--rf-3); align-items:center; }
.rfh-loop-state-t{ display:block; min-width:0; }
.rfh-loop-state-t b{ display:block; color:var(--rf-ink); font-weight:600;
  font-size:var(--rf-t-sm); line-height:1.3; }
.rfh-loop-state-t em{ font-style:normal; display:block; color:var(--rf-ink-3);
  font-size:var(--rf-t-xs); line-height:1.45; }
/* The dot sits on the line, so the line must not cross it. */
.rfh-loop-state .rfh-dot{ background:var(--rf-bg); }

.rfh-loop-flow .rfh-loop-steps{ position:relative; z-index:1;
  margin-left:calc(15px - var(--rf-3)); padding-left:var(--rf-6); gap:var(--rf-3); }

/* the report, travelling */
.rfh-loop-flow::before{ content:""; position:absolute; left:15px; top:30px; bottom:76px;
  width:2px; border-radius:2px; background:var(--rf-brand-edge);
  transform-origin:top; transform:scaleY(1); }

.rfh-loop-out{ margin:0; padding-top:var(--rf-4);
  border-top:1px solid var(--rf-line); color:var(--rf-ink-2);
  font-size:var(--rf-t-sm); line-height:1.6; max-width:46ch; }

@media (min-width:980px){
  /* Two columns, not three: the argument on the left, the flow on the right.
     The flow is one object now and splitting it across columns would undo the
     only thing this scene had to say. */
  .rfh-loop .rfh-wrap{ grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr); align-items:center; }
  .rfh-loop-out{ max-width:52ch; }
}

/* MOTION — Stream. The taps arrive one after another, then the receiving ring
   settles: cause and effect in the same frame. Default is ARRIVED, exactly as
   RF-ONE-JOURNEY requires — if main.js never runs, if the observer never
   fires, or if the reader asked for less motion, the flow is simply complete.
   main.js adds --anim only when the scene is still below the fold. */
@media (prefers-reduced-motion:no-preference){
  .rfh-loop-flow.rfh-journey--anim::before{ transform:scaleY(0); }
  .rfh-loop-flow.rfh-journey--anim.is-drawn::before{
    transform:scaleY(1); transition:transform 700ms var(--rf-ease); }
  .rfh-loop-flow.rfh-journey--anim .rfh-loop-steps li{ opacity:0; }
  .rfh-loop-flow.rfh-journey--anim.is-drawn .rfh-loop-steps li{
    animation:rfhTapIn .42s var(--rf-ease) both; }
  .rfh-loop-flow.rfh-journey--anim.is-drawn .rfh-loop-steps li:nth-child(1){ animation-delay:.10s }
  .rfh-loop-flow.rfh-journey--anim.is-drawn .rfh-loop-steps li:nth-child(2){ animation-delay:.22s }
  .rfh-loop-flow.rfh-journey--anim.is-drawn .rfh-loop-steps li:nth-child(3){ animation-delay:.34s }
  .rfh-loop-flow.rfh-journey--anim.is-drawn .rfh-loop-steps li:nth-child(4){ animation-delay:.46s }
  @keyframes rfhTapIn{ from{ opacity:0; transform:translateX(-10px) } to{ opacity:1; transform:none } }
  /* last, and only once: the ring is the consequence of the four above it */
  .rfh-loop-flow.rfh-journey--anim.is-drawn .rfh-dot--fresh{
    animation:rfhPinSettle .42s var(--rf-ease) .62s both; }
}
@media (prefers-reduced-motion:reduce){
  .rfh-loop-flow.rfh-journey--anim::before{ transform:scaleY(1); transition:none; }
  .rfh-loop-flow.rfh-journey--anim .rfh-loop-steps li{ opacity:1; }
}

/* Motion: the two signatures this page already uses, and nothing else. The
   wire draws once when the data story arrives; the fresh pin settles. Both are
   one-shot — the brief bans looping emphasis, and a pulsing dot beside a
   status word would also be claiming something is live. */
/* RF-SCENE-REPORT (22 September 2026). Two rules stood here and both are gone:

     .rfh-flow-wire        the three-source diagram, whose section was merged
                           into scene 01 — the selector had nothing left to match
     .rfh-loop.is-drawn    the settle, keyed to the SECTION. The observer marks
                           the element carrying data-rf-reveal, which is
                           .rfh-loop-flow now, so this rule would never have
                           fired again. A rule that cannot match is worse than
                           no rule: it reads as coverage.

   The settle itself lives in the RF-SCENE-REPORT block above, sequenced after
   the four taps. @keyframes rfhPinSettle is still declared here because it is
   the shared definition both used. */
@media (prefers-reduced-motion:no-preference){
  @keyframes rfhPinSettle{
    from{ transform:scale(.82); box-shadow:0 0 0 0 var(--rf-ok-dim) }
    to  { transform:none;       box-shadow:0 0 0 6px var(--rf-ok-dim) }
  }
}

/* ============================================================================
   RF-SCENE-ROUTE (22 September 2026) — scene 03 of the lower homepage.

   The line is the layout. Not a rule drawn down a gutter for decoration: it is
   the road, the endpoints are its ends, and the stop hangs off it where a stop
   would be. What the planner knows about that stop is attached to the stop.

   This replaced the page's last .rfh-card — a bordered panel holding a <dl>
   under the tag "What it accounts for". The four rows were right and the
   container was wrong: they describe a thing on a road.

   ONE MEASUREMENT WORTH KEEPING. That tag carried inline styles reading
   var(--rfh-brand-dim) and var(--rfh-brand). Neither is declared anywhere in
   the stylesheets — four --rfh-* variables were used across index.html and not
   one of them existed — so it had been rendering with no background and no
   colour. It goes with the card.
   ========================================================================= */
/* Same rhythm as scene 02: the scenes are one journey, so the distance
   between them is one number, not a value chosen per section. */
.rfh-route{ padding-block:clamp(48px, 5.6vw, 88px); position:relative; }
.rfh-route .rfh-wrap{ display:grid; gap:clamp(28px, 3.4vw, 56px); align-items:start; }
.rfh-route-copy{ display:flex; flex-direction:column; gap:var(--rf-4); align-items:flex-start; }
.rfh-route-copy h2{ margin:var(--rf-2) 0 0; font-size:var(--rf-t-h2); letter-spacing:-.02em; }
.rfh-route-copy .rfh-lead{ margin:0; max-width:36ch; }
.rfh-route-copy .rfh-note{ margin:0; max-width:44ch; }

/* the road */
.rfh-route-line{ position:relative; display:grid; gap:var(--rf-5);
  padding-left:var(--rf-2); }
.rfh-route-line::before{ content:""; position:absolute; left:14px; top:10px; bottom:10px;
  width:2px; border-radius:2px; background:var(--rf-brand-edge);
  transform-origin:top; transform:scaleY(1); }

.rfh-route-end{ position:relative; z-index:1; margin:0;
  display:grid; grid-template-columns:30px minmax(0,1fr); align-items:center;
  font-family:var(--rf-mono); font-size:var(--rf-t-xs); letter-spacing:.08em;
  text-transform:uppercase; color:var(--rf-ink-3); }
.rfh-route-node{ width:11px; height:11px; margin-left:5px; border-radius:50%;
  background:var(--rf-brand); display:block;
  box-shadow:0 0 0 4px var(--rf-bg); }
/* The destination is the open end: same node, hollow, so the two ends of the
   road are not the same mark. */
.rfh-route-end--to .rfh-route-node{ background:var(--rf-bg);
  border:2px solid var(--rf-brand); }

.rfh-route-stop{ position:relative; z-index:1;
  display:grid; grid-template-columns:30px minmax(0,1fr); gap:0 var(--rf-4);
  align-items:start; }
/* The dot sits ON the road, so the road must not show through it. */
.rfh-route-stop .rfh-dot{ background:var(--rf-bg); }
.rfh-route-stop-body{ min-width:0; padding-top:2px; }
.rfh-route-stop-body h3{ margin:0 0 var(--rf-3); font-size:var(--rf-t-h3);
  letter-spacing:-.012em; line-height:1.3; }

/* What the planner knows about that stop. A definition list because that is
   what it is — term and value — and no box, because the stop is already
   located by the road it is standing on. */
.rfh-route-facts{ display:grid; grid-template-columns:auto minmax(0,1fr);
  gap:8px var(--rf-4); margin:0; font-size:var(--rf-t-xs); }
.rfh-route-facts dt{ color:var(--rf-ink-3); }
.rfh-route-facts dd{ margin:0; color:var(--rf-ink); font-family:var(--rf-mono);
  font-size:12.5px; line-height:1.5; }

@media (min-width:980px){
  .rfh-route .rfh-wrap{ grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);
    align-items:center; }
}
@media (max-width:520px){
  /* At 390 the term column would squeeze the value to four words a line, so
     the pair stacks: term, then value under it, still on the same road. */
  .rfh-route-facts{ grid-template-columns:minmax(0,1fr); gap:2px; }
  .rfh-route-facts dd{ margin-bottom:var(--rf-3); }
  .rfh-route-facts dd:last-child{ margin-bottom:0; }
}

/* MOTION — Connect. The road draws from origin to destination, then the stop's
   facts arrive on it. Default is DRAWN, as every scene here is: main.js adds
   --anim only when the scene is still below the fold and motion is allowed. */
@media (prefers-reduced-motion:no-preference){
  .rfh-route-line.rfh-journey--anim::before{ transform:scaleY(0); }
  .rfh-route-line.rfh-journey--anim.is-drawn::before{
    transform:scaleY(1); transition:transform 760ms var(--rf-ease); }
  .rfh-route-line.rfh-journey--anim .rfh-route-stop{ opacity:0; }
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-stop{
    animation:rfhStopLand .44s var(--rf-ease) .34s both; }
  @keyframes rfhStopLand{ from{ opacity:0; transform:translateY(-8px) } to{ opacity:1; transform:none } }
  .rfh-route-line.rfh-journey--anim .rfh-route-facts dt,
  .rfh-route-line.rfh-journey--anim .rfh-route-facts dd{ opacity:0; }
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dt,
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dd{
    animation:rfhFactIn .34s var(--rf-ease) both; }
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dt:nth-of-type(1),
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dd:nth-of-type(1){ animation-delay:.58s }
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dt:nth-of-type(2),
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dd:nth-of-type(2){ animation-delay:.68s }
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dt:nth-of-type(3),
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dd:nth-of-type(3){ animation-delay:.78s }
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dt:nth-of-type(4),
  .rfh-route-line.rfh-journey--anim.is-drawn .rfh-route-facts dd:nth-of-type(4){ animation-delay:.88s }
  @keyframes rfhFactIn{ from{ opacity:0 } to{ opacity:1 } }
}
@media (prefers-reduced-motion:reduce){
  .rfh-route-line.rfh-journey--anim::before{ transform:scaleY(1); transition:none; }
  .rfh-route-line.rfh-journey--anim .rfh-route-stop,
  .rfh-route-line.rfh-journey--anim .rfh-route-facts dt,
  .rfh-route-line.rfh-journey--anim .rfh-route-facts dd{ opacity:1; }
}

/* ============================================================================
   RF-SCENE-COMMUNITY (22 September 2026) — scene 04 of the lower homepage.

   Three moments on one line, each carrying the guide that belongs to it. The
   line is the same device as scenes 02 and 03 — a pseudo-element in the node
   gutter, scaled from one end — because it is the same journey, still running.

   A GUIDE IS A LINK, NOT AN OBJECT. It gets a title and a real reading time
   and nothing else: no excerpt, no kicker paragraph, no cover, no border, no
   "read this one". The hover moves the arrow and underlines the title, which
   is the whole affordance a text link needs. This is what replaced the last
   editorial furniture on the page.
   ========================================================================= */
.rfh-join{ padding-block:clamp(48px, 5.6vw, 88px); position:relative; }
.rfh-join-head{ max-width:32ch; margin-bottom:var(--rf-7); }
.rfh-join-head h2{ margin:var(--rf-2) 0 var(--rf-3); font-size:var(--rf-t-h2);
  letter-spacing:-.02em; }
.rfh-join-head .rfh-lead{ margin:0; }

.rfh-join-line{ position:relative; list-style:none; margin:0; padding:0; display:grid;
  gap:var(--rf-6); }
.rfh-join-moment{ position:relative; display:grid; grid-template-columns:26px minmax(0,1fr);
  gap:0 var(--rf-4); }
/* the line between the moments, in the node gutter */
.rfh-join-moment::before{ content:""; position:absolute; left:12px; top:22px; bottom:calc(var(--rf-6) * -1);
  width:2px; border-radius:2px; background:var(--rf-brand-edge);
  transform-origin:top; transform:scaleY(1); }
.rfh-join-moment:last-child::before{ content:none; }
.rfh-join-node{ grid-column:1; width:12px; height:12px; margin:5px 0 0 6px; border-radius:50%;
  background:var(--rf-bg); border:2px solid var(--rf-brand); display:block;
  position:relative; z-index:1; }
.rfh-join-body{ grid-column:2; min-width:0; display:flex; flex-direction:column; gap:6px; }
.rfh-join-body h3{ margin:0; font-size:var(--rf-t-h3); letter-spacing:-.012em; line-height:1.3; }
.rfh-join-body p{ margin:0; color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.55;
  max-width:44ch; }

.rfh-join-read{ margin-top:var(--rf-2); display:inline-flex; align-items:baseline;
  gap:var(--rf-3); flex-wrap:wrap; text-decoration:none; color:inherit; width:fit-content; }
.rfh-join-title{ color:var(--rf-brand); font-weight:600; font-size:var(--rf-t-sm);
  border-bottom:1px solid transparent; transition:border-color var(--rf-fast); }
/* The arrow hangs off the META, not off the link, so the two travel together
   when the line wraps. Photographed at 390 the other way round: the title and
   the reading time took the first line and the arrow sat alone on the second,
   an affordance pointing at nothing. */
.rfh-join-meta{ order:2; font-family:var(--rf-mono); font-size:var(--rf-t-xs);
  color:var(--rf-ink-3); letter-spacing:.03em; white-space:nowrap; }
.rfh-join-meta::after{ content:"\2192"; margin-left:.6em; color:var(--rf-brand);
  display:inline-block; transition:transform var(--rf-fast); }
.rfh-join-read:hover .rfh-join-title{ border-bottom-color:var(--rf-brand-edge); }
.rfh-join-read:hover .rfh-join-meta::after{ transform:translateX(3px); }
.rfh-join-read:focus-visible{ outline:2px solid var(--rf-brand); outline-offset:3px;
  border-radius:4px; }

.rfh-join-ends{ margin:var(--rf-5) 0 0 42px; color:var(--rf-ink-2);
  font-size:var(--rf-t-sm); line-height:1.6; max-width:46ch; }
.rfh-join-links{ margin-top:var(--rf-5); padding-top:var(--rf-4);
  border-top:1px solid var(--rf-line); display:flex; flex-wrap:wrap;
  gap:var(--rf-2) var(--rf-6); font-size:var(--rf-t-sm); }
.rfh-join-links a{ color:var(--rf-brand); font-weight:600; text-decoration:none; }
.rfh-join-links a:hover{ color:var(--rf-brand-hover); }
.rfh-join-links a:focus-visible{ outline:2px solid var(--rf-brand); outline-offset:3px; }

@media (min-width:980px){
  /* The heading holds its own column and the three moments run beside it, so
     the scene reads as one road with a title rather than a stack. */
  .rfh-join .rfh-wrap{ display:grid; grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);
    gap:clamp(28px, 3.4vw, 56px); align-items:start; }
  .rfh-join-head{ margin-bottom:0; }
  .rfh-join-ends,.rfh-join-links{ grid-column:2; }
}

/* MOTION — Stream. The three moments arrive in order and the closing line
   last, so what the reader sees is the contribution moving forward rather than
   three items appearing. Default is ARRIVED; main.js adds --anim only when the
   scene is still below the fold. */
@media (prefers-reduced-motion:no-preference){
  .rfh-join-line.rfh-journey--anim .rfh-join-moment::before{ transform:scaleY(0); }
  .rfh-join-line.rfh-journey--anim.is-drawn .rfh-join-moment::before{
    transform:scaleY(1); transition:transform 520ms var(--rf-ease); }
  .rfh-join-line.rfh-journey--anim.is-drawn .rfh-join-moment:nth-child(2)::before{ transition-delay:140ms }
  .rfh-join-line.rfh-journey--anim .rfh-join-body{ opacity:0; }
  .rfh-join-line.rfh-journey--anim.is-drawn .rfh-join-body{
    animation:rfhMomentIn .42s var(--rf-ease) both; }
  .rfh-join-line.rfh-journey--anim.is-drawn .rfh-join-moment:nth-child(1) .rfh-join-body{ animation-delay:.06s }
  .rfh-join-line.rfh-journey--anim.is-drawn .rfh-join-moment:nth-child(2) .rfh-join-body{ animation-delay:.24s }
  .rfh-join-line.rfh-journey--anim.is-drawn .rfh-join-moment:nth-child(3) .rfh-join-body{ animation-delay:.42s }
  @keyframes rfhMomentIn{ from{ opacity:0; transform:translateX(-10px) } to{ opacity:1; transform:none } }
}
@media (prefers-reduced-motion:reduce){
  .rfh-join-line.rfh-journey--anim .rfh-join-moment::before{ transform:scaleY(1); transition:none; }
  .rfh-join-line.rfh-journey--anim .rfh-join-body{ opacity:1; }
}

/* ============================================================================
   RF-SCENE-END (22 September 2026) — scene 05, where the journey stops.

   The line runs to the BOTTOM EDGE of the section and no further. The footer's
   own border-top — 1px in var(--rf-line), across the full page — is what it
   turns into, and shell.css already sets margin:0 on the footer, so the two
   meet with nothing between them. That is the whole bridge: no footer rule is
   written here, because the footer belongs to 548 pages and not to this one.

   This replaced .rfh-cta: a centred panel with a radius, the last card and the
   last centred block on the page. Everything above it is left-aligned against
   the line, so ending on a centred box read as a different page arriving.
   ========================================================================= */
.rfh-end{ padding-block:clamp(48px, 5.6vw, 88px) 0; position:relative; }
/* The road stops just above what comes next, not a screen early. What comes
   next is the Preferred Sources card: _worker.js inserts it before
   footer.site-footer on every page that is not an article, by the
   RF-PSRC-ONE-ASK decision of 20 September — one ask per page. So the line
   ends at that card rather than running through it, and the fade in the
   gradient above is where it hands over. */
.rfh-end-arrive{ position:relative; display:grid; grid-template-columns:26px minmax(0,1fr);
  gap:0 var(--rf-4); padding-bottom:clamp(28px, 3vw, 48px); }

/* the last stretch of road. It ends where the section does — the footer's
   border continues it sideways. */
.rfh-end-arrive::before{ content:""; position:absolute; left:12px; top:0; bottom:0;
  width:2px; border-radius:2px 2px 0 0;
  background:linear-gradient(180deg, var(--rf-brand-edge) 0%, var(--rf-brand-edge) 62%, var(--rf-line) 100%);
  transform-origin:top; transform:scaleY(1); }

/* arrival. Filled, like the start node in scene 03 — you are somewhere now. */
.rfh-end-node{ grid-column:1; width:14px; height:14px; margin:9px 0 0 5px; border-radius:50%;
  background:var(--rf-brand); display:block; position:relative; z-index:1;
  box-shadow:0 0 0 5px var(--rf-bg); }
.rfh-end-body{ grid-column:2; min-width:0; display:flex; flex-direction:column;
  align-items:flex-start; gap:var(--rf-4); }
.rfh-end-body h2{ margin:0; font-size:var(--rf-t-h2); letter-spacing:-.02em; max-width:18ch; }
.rfh-end-body .rfh-lead{ margin:0; max-width:42ch; }
.rfh-end-body .rfh-cta-row{ justify-content:flex-start; margin-top:var(--rf-2); }

/* MOTION. The line arrives, then the node settles, and nothing else moves.
   rfhPinSettle is the definition scene 02 already declared — one spring for
   the page, used twice. Default is ARRIVED. */
@media (prefers-reduced-motion:no-preference){
  .rfh-end-arrive.rfh-journey--anim::before{ transform:scaleY(0); }
  .rfh-end-arrive.rfh-journey--anim.is-drawn::before{
    transform:scaleY(1); transition:transform 640ms var(--rf-ease); }
  .rfh-end-arrive.rfh-journey--anim.is-drawn .rfh-end-node{
    animation:rfhPinSettle .42s var(--rf-ease) .46s both; }
}
@media (prefers-reduced-motion:reduce){
  .rfh-end-arrive.rfh-journey--anim::before{ transform:scaleY(1); transition:none; }
}

/* ---- guides, as editorial rather than four tiles ------------------------- */
/* RF-GUIDES-ARE-EDITORIAL. The lead story carries its weight in TYPE, not in a
   box: no border, no fill, no radius. The two beside it are links with a rule
   between them, which is what a list of further reading looks like in print and
   costs nothing in ink. */
.rfh-reads{ padding-block:clamp(72px, 9vw, 132px); }
.rfh-reads-head{ margin-bottom:clamp(32px, 3.6vw, 56px); max-width:26ch; }
.rfh-reads-head h2{
  font-size:clamp(28px, 3.2vw, 46px); line-height:1.08; letter-spacing:-.02em;
  margin:var(--rf-2) 0 0; text-wrap:balance;
}
.rfh-reads-body{ display:grid; gap:clamp(28px, 3.2vw, 56px); align-items:start; }

.rfh-read-lead{ display:block; text-decoration:none; color:inherit; }
.rfh-read-lead h3{
  font-size:clamp(24px, 2.6vw, 38px); line-height:1.12; letter-spacing:-.015em;
  margin:var(--rf-2) 0 var(--rf-3); color:var(--rf-ink); text-wrap:balance;
}
.rfh-read-lead p{ margin:0 0 var(--rf-4); color:var(--rf-ink-2); line-height:1.6; max-width:46ch; font-size:var(--rf-t-lead); }
.rfh-read-kicker{
  display:block; font-family:var(--rf-mono); font-size:var(--rf-t-label);
  letter-spacing:.08em; text-transform:uppercase; color:var(--rf-ink-3);
}
.rfh-read-go{ display:inline-block; color:var(--rf-brand); font-weight:650; font-size:var(--rf-t-sm); }
/* The whole lead is one link, so the hover has to read at that scale: the
   headline warms and the arrow moves. No lift, no shadow, no card. */
.rfh-read-lead:hover h3{ color:var(--rf-brand); }
.rfh-read-lead:hover .rfh-read-go{ text-decoration:underline; text-underline-offset:3px; }

.rfh-read-more{ list-style:none; margin:0; padding:0; }
.rfh-read-more li{ border-top:1px solid var(--rf-line); }
.rfh-read-more li:last-child{ border-bottom:1px solid var(--rf-line); }
.rfh-read-more a{
  display:block; padding:var(--rf-4) 0; text-decoration:none; color:inherit;
}
.rfh-read-more h3{
  margin:0 0 4px; font-size:clamp(16px, 1.25vw, 19px); font-weight:600; color:var(--rf-ink);
  line-height:1.3;
}
.rfh-read-more a:hover h3{ color:var(--rf-brand); }
.rfh-read-all a{ color:var(--rf-brand); font-weight:650; font-size:var(--rf-t-sm); }

@media (min-width:900px){
  /* Asymmetric: the lead takes the larger share, the list the rest. */
  .rfh-reads-body{ grid-template-columns:minmax(0,1.25fr) minmax(0,.85fr); gap:clamp(40px,5vw,96px); }
}

/* ---- capabilities, as moments rather than tiles -------------------------- */
/* RF-CAPABILITIES-ARE-MOMENTS. The whole section spends exactly one border per
   row — a hairline ABOVE it — which is a rule, not a box: it separates without
   enclosing, and it costs nothing when two rows sit side by side. */
.rfh-caps{ padding-block:clamp(72px, 9vw, 132px); }
.rfh-caps-h{
  font-size:clamp(28px, 3.4vw, 50px); line-height:1.08; letter-spacing:-.02em;
  margin:0 0 clamp(28px, 3.4vw, 52px); max-width:22ch; text-wrap:balance;
}
.rfh-cap{
  display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:start;
  column-gap:var(--rf-5); row-gap:6px;
  padding:clamp(20px, 2.2vw, 30px) 0;
  border-top:1px solid var(--rf-line);
  text-decoration:none; color:inherit;
  transition:border-color var(--rf-fast);
}
.rfh-cap-k{
  grid-column:1; font-family:var(--rf-mono); font-size:var(--rf-t-label);
  letter-spacing:.08em; text-transform:uppercase; color:var(--rf-brand);
}
.rfh-cap-t{
  grid-column:1; font-size:clamp(18px, 1.6vw, 26px); line-height:1.24;
  font-weight:650; color:var(--rf-ink); letter-spacing:-.012em; text-wrap:balance;
}
.rfh-cap-p{
  grid-column:1; color:var(--rf-ink-2); font-size:var(--rf-t-sm); line-height:1.55; max-width:46ch;
}
/* The arrow spans the rows so it sits beside the whole thought, not beside
   the first line of it. Explicit row placement, because an auto-placed child
   in a two-track grid lands wherever the previous item left off — the implicit
   column trap that sheared the driver steps earlier today. */
.rfh-cap-go{
  grid-column:2; grid-row:1 / -1; align-self:center;
  width:22px; height:22px; color:var(--rf-ink-3); flex:0 0 auto;
  transition:transform var(--rf-fast), color var(--rf-fast);
}
.rfh-cap:hover{ border-top-color:var(--rf-brand-edge); }
.rfh-cap:hover .rfh-cap-t{ color:var(--rf-brand); }
.rfh-cap:hover .rfh-cap-go{ color:var(--rf-brand); transform:translateX(4px); }
.rfh-cap:focus-visible{ outline:2px solid var(--rf-brand); outline-offset:4px; border-radius:4px; }

/* The lead capability is bigger in TYPE, not in chrome. */
.rfh-cap--lead .rfh-cap-t{ font-size:clamp(23px, 2.5vw, 38px); line-height:1.14; }
.rfh-cap--lead .rfh-cap-p{ font-size:var(--rf-t-lead); max-width:52ch; }
/* And the last row is quieter, because "where the data comes from" is a
   footnote to the four above it, not a fifth offer. */
.rfh-cap--quiet .rfh-cap-t{ font-size:clamp(16px, 1.2vw, 19px); font-weight:600; color:var(--rf-ink-2); }
.rfh-cap--quiet:hover .rfh-cap-t{ color:var(--rf-brand); }

.rfh-cap-pair{ display:grid; }
@media (min-width:860px){
  /* Two to a row, and the pair shares one rule across the top so the page
     reads as bands rather than as four separate objects. */
  .rfh-cap-pair{ grid-template-columns:1fr 1fr; column-gap:clamp(32px,4vw,80px); }
}
@media (prefers-reduced-motion:reduce){
  .rfh-cap,.rfh-cap-go{ transition:none }
  .rfh-cap:hover .rfh-cap-go{ transform:none }
}

/* ---- pins carry two facts at once --------------------------------------- */
/* RF-COLOUR-IS-THE-FUEL (21 September 2026). The homepage map was grey because
   its only colour axis was status, and 298 of 400 pins had no recent driver
   check. Fuel type is the second axis: a fact from OpenStreetMap that does not
   expire, so it can carry colour without ever claiming freshness.

       FILL  what the forecourt sells      (petrol / diesel / LPG / CNG / EV)
       RING  what a driver confirmed       (the status classes, unchanged)

   A station whose fuels OSM does not list keeps the neutral fill. That is not
   an oversight — colouring it would be asserting a product nobody recorded. */
/* The rules that carried out those two sentences were .rfh-pin.tint-* and
   .rfh-pin.is-*: eight background declarations, a 90deg gradient for the
   two-fuel split, two glyph colours and three box-shadow rings. All of it is
   now .rf-mk in shell.css, where a fill is an SVG fill, the split is two half
   paths instead of a gradient tuned to cancel a -45deg rotation, and the
   status ring is a stroke on the body circle.

   The vocabulary did not change — same tint-* names, same --pin-* tokens,
   same rule that an unlisted forecourt keeps the neutral station colour
   rather than borrowing a fuel it may not sell. Only the number of places
   that implement it did: two, then one. */

/* RF-MAP-FAST (24 September 2026). While the stations for a view are on their
   way, a 2px bar runs along the top edge of the map card, so a slow network
   reads as "loading" and never as an empty area. The same bar at rest is
   invisible; nothing else about the map changes. A failed load says so in the
   note and offers a retry there. */
.rfh-mapcard.is-loading::after, .rd-home .rfh-mapcard.rd-stage-map.is-loading::after{content:""; position:absolute; left:0; top:0; height:2px; width:100%;
  background:linear-gradient(90deg, transparent 0, var(--rf-cta-bg, #FFC928) 40%, var(--rf-cta-bg, #FFC928) 60%, transparent 100%);
  background-size:40% 100%; background-repeat:no-repeat; pointer-events:none; z-index:1000;
  animation:rfhMapLoad 1.1s linear infinite}
@keyframes rfhMapLoad{from{background-position:-40% 0} to{background-position:140% 0}}
@media (prefers-reduced-motion:reduce){ .rfh-mapcard.is-loading::after, .rd-home .rfh-mapcard.rd-stage-map.is-loading::after{animation:none; background:var(--rf-cta-bg, #FFC928); opacity:.6} }
.rfh-note-retry{font:inherit; font-weight:600; color:var(--rf-ink); background:none; border:0; padding:0 0 0 2px;
  text-decoration:underline; text-underline-offset:2px; cursor:pointer}

/* RF-MAP-FAILED (3 October 2026). When the map cannot start, the note is the
   message: it already has a place on every layout that nothing else covers.
   It gets the room to say what happened and two ways on, each a full-size
   touch target, and the controls that need a map go. */
.rfh-map-note.is-fail{max-width:min(440px, calc(100% - 24px)); font-size:var(--rf-t-sm); line-height:1.45}
.rfh-fail-go{display:flex; flex-wrap:wrap; gap:0 18px; margin-top:2px}
.rfh-fail-msg{display:block}
.rfh-fail-go .rfh-note-retry{min-height:44px; display:inline-flex; align-items:center; padding:0}
.rfh-mapcard.is-failed .rfh-map-tools, .rfh-mapcard.is-failed .rfh-map-zoom, .rfh-mapcard.is-failed .rfh-legend{display:none}
.rfh-empty .rfh-note-retry{min-height:44px; padding:0 0 0 6px}
/* the inline Try again inside the note keeps the note's size and gets a 44px target by padding it and pulling the margin back */
.rfh-map-note > .rfh-note-retry{padding:14px 8px; margin:-14px -2px}
