/* ============================================================================
   Nova Drive — base.css
   Shared design-system layer loaded on EVERY public page (injected by
   renderHead() at the <!--NOVA_HEAD--> position, before the page fonts and
   before each page's inline <style>).

   It owns two things only:
     1. NEW design tokens no page declares (easing, durations, fluid type
        scale, spacing scale, radius scale, layered shadows, focus ring,
        derived neutrals, layout helpers).
     2. Global element-level polish no page already sets (type rendering,
        text-wrap, :focus-visible rings, ::selection, press feedback,
        reduced-motion safety net, anchor scroll offset).

   It intentionally does NOT restyle the brand. Each page's inline <style>
   still wins for any same-name selector/token it declares (it loads later).
   The shadow scale is re-pasted into every page's inline :root so the layered
   values win there too.
   ============================================================================ */

:root{
  /* ---- easing + duration (one motion language) ---- */
  --ease-out:cubic-bezier(.22,.61,.36,1);       /* hovers, presses, control focus */
  --ease-out-soft:cubic-bezier(.33,1,.68,1);    /* entrances, reveals, popovers */
  --ease-spring:cubic-bezier(.34,1.56,.64,1);   /* accordion marker, toast pop-in */
  --dur-fast:140ms;
  --dur-base:220ms;
  --dur-slow:380ms;
  /* The interval between two staggered entrances. Not an invented value:
     index.html has shipped exactly 60ms between .reveal .card children since the
     homepage was built. Naming it means every delay is calc(var(--stagger) * n),
     and the reduced-motion block below collapses the whole page's choreography by
     setting it to 0 once, with no !important anywhere. */
  --stagger:60ms;

  /* ---- fluid type scale, ~1.2 minor third ---- */
  --fs-2xs:clamp(.69rem,.66rem + .13vw,.75rem);
  --fs-xs: clamp(.78rem,.74rem + .18vw,.8125rem);
  --fs-sm: clamp(.875rem,.84rem + .2vw,.9375rem);
  --fs-base:clamp(.95rem,.92rem + .18vw,1rem);
  --fs-md: clamp(1rem,.96rem + .25vw,1.0625rem);
  --fs-lg: clamp(1.0625rem,1rem + .45vw,1.125rem);
  --fs-xl: clamp(1.25rem,1.1rem + .7vw,1.375rem);
  --fs-2xl:clamp(1.5rem,1.25rem + 1.3vw,1.75rem);
  --fs-3xl:clamp(1.75rem,1.4rem + 1.7vw,2.25rem);
  --fs-4xl:clamp(2rem,1.5rem + 2.4vw,2.5rem);
  --lh-body:1.6;

  /* ---- spacing, 4-then-8 ladder ---- */
  --space-1:4px;  --space-2:8px;  --space-3:12px; --space-4:16px;
  --space-5:24px; --space-6:32px; --space-7:40px; --space-8:48px;
  --space-9:64px; --space-10:80px;
  --gutter:24px;  --gutter-sm:16px;

  /* ---- radius scale ---- */
  --radius-xs:8px; --radius-sm:10px; --radius-md:12px;
  --radius-lg:16px; --radius-xl:20px; --radius-pill:999px;
  /* --r is the legacy alias 20 rules still cite. It was declared in eleven page
     :root blocks and nowhere here, so this comment's own claim was false and any
     page that dropped its copy would have silently lost every rounded corner. */
  --r:var(--radius-lg);

  /* ---- layered ambient + key shadow scale ---- */
  --shadow-sm:0 1px 2px rgba(7,33,61,.06), 0 2px 8px rgba(7,33,61,.05);
  --shadow-md:0 2px 4px rgba(7,33,61,.05), 0 10px 24px -4px rgba(7,33,61,.10);
  --shadow-lg:0 4px 8px rgba(7,33,61,.06), 0 22px 48px -8px rgba(7,33,61,.16);
  --shadow-overlay:0 6px 12px rgba(7,33,61,.08), 0 28px 64px -12px rgba(7,33,61,.22);
  --shadow-up:0 -2px 4px rgba(7,33,61,.04), 0 -10px 24px -6px rgba(7,33,61,.12);

  /* ---- focus ring ----
     Two layers, not a wash. The old single amber ring blended to #F5D09A over
     white, which measures 1.45:1 — WCAG 2.2 SC 1.4.11 wants 3:1 for a focus
     indicator, so keyboard focus was effectively invisible. The white inner ring
     separates the outer ring from the control it sits on, so the same token
     works on an amber button and on a white input. */
  --focus-ring:0 0 0 2px #fff, 0 0 0 5px #0B2E59;          /* navy 13.5:1 on white */
  --focus-ring-light:0 0 0 2px rgba(7,33,61,.55), 0 0 0 5px #F4B740; /* gold 9.1:1 on navy-deep */
  --focus-radius:6px;

  /* ---- derived neutrals (NOT brand hues) ---- */
  --slate-strong:#4A5564;   /* ~7:1 on white — AA for 11-13px microcopy */
  --line-soft:#EDF1F7;
  --hairline:rgba(7,33,61,.08);

  /* ---- brand hues darkened for TEXT use only ----
     --teal at 12px on --light measures 3.85:1, under the 4.5:1 body-text floor.
     Links and small teal runs use these; --teal itself stays exactly as it is
     wherever it is a background, a rule or an icon fill (no contrast burden). */
  --teal-ink:#0E6E62;       /* 6.1:1 on white, 5.7:1 on --light */

  /* ---- status pairs (availability chips, inline errors) ----
     One set for the whole funnel. /results and the class page were carrying six
     near-miss one-off hexes between them for the same three states. */
  --ok-bg:#E6F4EE;   --ok-ink:#0E6647;    /* 6.2:1 */
  --warn-bg:#FDF0DC; --warn-ink:#8F5600;  /* 5.3:1 */
  --stop-bg:#FBE9E9; --stop-ink:#8C2323;  /* 7.5:1 */

  /* ---- money type ----
     The one numeral that outranks a headline. Sora 800 tabular; the class page's
     price slot and the /results rail both read from these, so the from-price and
     the booked total can never be set at two different sizes. */
  --fs-price:clamp(2.375rem,1.9rem + 2.1vw,3.25rem);   /* 38 → 52px */
  --fs-price-sm:clamp(1.25rem,1.1rem + .6vw,1.5rem);   /* 20 → 24px */

  /* ---- brand hues darkened for TEXT use only (see --teal-ink above) ---- */
  --amber-ink:#8F5600;      /* 6.0:1 on white — amber as TEXT */

  /* ---- surfaces ---- */
  --control-h:48px;
  --card-shadow:var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,.6);
  --card-shadow-raised:var(--shadow-md), inset 0 1px 0 rgba(255,255,255,.6);
  --plate:linear-gradient(180deg,#fbfcfe 0%,#eef2f8 100%);  /* the ground a car photo sits on */
  --car-shadow:drop-shadow(0 1px 1px rgba(11,46,89,.10)) drop-shadow(0 10px 14px rgba(11,46,89,.12));

  /* ---- layout helpers ---- */
  --header-h:62px;
  --header-offset:88px;     /* sticky header + breathing room for anchor jumps */
  --maxw-narrow:820px;
  --safe-b:env(safe-area-inset-bottom,0px);
}

/* One spin, site-wide. Three pages had defined byte-identical
   `to{transform:rotate(360deg)}` keyframes under three names (cspin/fspin/spin),
   renamed only to dodge a collision that one definition does not have. */
@keyframes nd-spin{to{transform:rotate(360deg)}}

/* The rest of the motion vocabulary, lifted verbatim from the class page, which
   was the only surface that had one. Each animates transform OR opacity, never
   both, so transform can take the spring curve while opacity takes the duration
   curve. Durations and curves stay at the call site: these say what moves, the
   rule that uses them says how long and how. */
@keyframes nd-rise {from{transform:translateY(6px)}  to{transform:none}}
@keyframes nd-pop  {from{transform:scale(.92)}       to{transform:none}}
@keyframes nd-fade {from{opacity:0}                  to{opacity:1}}
@keyframes nd-sheen{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
@keyframes nd-ring {from{opacity:1}                  to{opacity:0}}

/* The loading dial. Byte-identical on /fleet, /, /results, /services, /driver and
   /booking-confirmed, each with its own copy and its own keyframe name. One
   definition, one keyframe. */
.nd-dial{width:34px;height:34px;border:4px solid var(--line);border-top-color:var(--amber);
  border-radius:50%;margin:0 auto 14px;animation:nd-spin .8s linear infinite}

/* Visually hidden but announced. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

/* Anchor jumps glide (base.css already forces `auto!important` under
   prefers-reduced-motion below, which until now guarded a property nothing set). */
html{scroll-behavior:smooth}
img{max-width:100%;display:block}

/* ---- the one inclusion list ----
   Teal is the site's semantic "included"; green means available/saving. The
   class page rendered a green list and, 400px below it, the app module's teal
   list, for the same role. The glyph is a real element, not a ::before, because
   VoiceOver announces a ::before tick in front of every single item. */
.tick-list,.opt-list,.note-list{list-style:none;display:grid;gap:var(--space-2);margin-top:var(--space-3)}
.tick-list li,.opt-list li,.note-list li{display:flex;gap:9px;align-items:flex-start;font-size:var(--fs-sm);line-height:1.5}
.tick-list li{color:var(--ink)}
.opt-list li,.note-list li{color:var(--slate-strong)}
.tick-list .tick{color:var(--teal-ink);font-weight:800;flex:none}
.opt-list .tick{color:var(--amber-ink);font-weight:800;flex:none}
.note-list .tick{color:var(--slate-strong);font-weight:800;flex:none}

/* ---- type rendering — inherited site-wide (body never sets these) ---- */
body{
  font-optical-sizing:auto;
  /* Inherited by every page. NEVER re-declare this property on a descendant to
     add 'tnum': it REPLACES rather than merges, so a rule reaching for tabular
     numerals silently drops cv11/ss01/calt on the very figures it is improving.
     Use font-variant-numeric:tabular-nums lining-nums, which composes. */
  font-feature-settings:'cv11' 1,'ss01' 1,'calt' 1;
  text-rendering:optimizeLegibility;
}

/* ---- wrap quality ---- */
h1,h2,h3{text-wrap:balance}
p,.lead,.sub,.fdesc{text-wrap:pretty}
.prose{hanging-punctuation:first allow-end}

/* ---- focus-visible ring (keyboard / AT only; mouse users unaffected) ---- */
/* The outline removal is gated on the browser understanding :focus-visible.
   Without the gate, Safari 14.0-15.3 dropped the whole ring rule below as
   invalid (unknown pseudo-class) while still applying outline:none, so keyboard
   and switch users had NO visible focus anywhere on the site. Gated, those
   browsers keep the stock UA outline (mouse users see it too there; that is the
   accessible failure mode), and 15.4+/everything current behaves as before.
   @supports selector() itself needs Safari 14.5, and 14.0-14.4 skipping the
   whole block is exactly the same safe outcome. */
@supports selector(:focus-visible){
  :focus{outline:none}
}
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,
textarea:focus-visible,[tabindex]:focus-visible{
  outline:2px solid transparent;          /* Windows high-contrast fallback */
  box-shadow:var(--focus-ring);
  border-radius:inherit;
}
/* lighter ring on dark surfaces */
.hero :focus-visible,.final :focus-visible,footer :focus-visible,
.summary :focus-visible,.nextbar :focus-visible{box-shadow:var(--focus-ring-light)}

/* ---- sticky-header anchor offset (no page sets these) ---- */
html{scroll-padding-top:var(--header-offset)}
[id]{scroll-margin-top:var(--header-offset)}

/* narrow reading column (FAQ / legal pages) — compound selector wins over .wrap */
.wrap.wrap-narrow{max-width:var(--maxw-narrow)}

/* ---- selection ---- */
::selection{background:rgba(232,150,30,.28);color:var(--ink)}
.hero ::selection,.final ::selection,footer ::selection,.summary ::selection{
  background:rgba(244,183,64,.40);color:#fff}

/* ---- tap highlight + universal press feedback ---- */
*{-webkit-tap-highlight-color:transparent}
button:active,.btn:active,a.btn:active,.search-btn:active,.vbook:active,
.choose:active,.send:active,.confirm:active,.stab:active,.epbtn:active,
.nextbtn:active,.addbtn:active{
  transform:translateY(1px) scale(.985);transition-duration:90ms}

/* ---- coarse-pointer minimum hit areas (no visual change to labels) ---- */
@media(pointer:coarse){
  /* The funnel bar's Change-date / Change-vehicle buttons are the whole back
     navigation of steps 3 and 4, and .fact is a bare 12px button with no padding
     that lands around 19px tall against the 44px floor. The filter and view
     controls on /results are the same shape of problem. */
  /* The burger is the one control that opens the menu on a phone, and it was the
     one control this block did not cover. */
  .burger{min-width:44px;min-height:44px;justify-content:center;align-items:center}
  .fact,.summary .edit,.reset,.fopt,.addbtn,.stab{min-height:44px;display:inline-flex;align-items:center}
  .viewtoggle button{min-width:44px;height:44px}
  .menu a,footer a,.flangs a{display:inline-flex;align-items:center;min-height:44px}
  .faq .q,.info-toggle{min-height:44px}
}

/* ---- phone gutter ----
   This used to be `.wrap{padding-left:var(--gutter-sm);…}` and never once fired.
   Every page declared its own `.wrap{…padding:0 24px}` in an inline <style> that
   loads after this file, at identical specificity (0,1,0), so the page won on
   source order — and shorthand expands to the same longhands at parse time, so
   `padding:0 24px` really does beat `padding-left`. Measured at 375px: all ten
   pages rendered 24px. Inert since the commit that created this file.

   Raising specificity here would win the cascade instead, but the pages would
   still each own a literal. So the pages now spend `var(--gutter)` and the value
   moves here: one declaration, no !important. */
@media(max-width:480px){:root{--gutter:var(--gutter-sm)}}

/* ---- scroll reveal, site-wide and FAIL-OPEN --------------------------------
   The old design was fail-CLOSED: `.reveal{opacity:0}` shipped in each page's
   own <style> and JavaScript had to arrive to un-hide it. Anything that stopped
   the observer delivering stranded the content, and that is not theoretical:
   the homepage shipped its whole lower half invisible to every visitor with
   JavaScript off (fixed in #312), because a page-level copy loaded after
   base.css at equal specificity and beat the @media(scripting:none) guard.

   Everything below hangs off `html[data-nd-reveal]`, an attribute set by a
   synchronous snippet in <head> (renderHead in lib/seo.ts). The attribute is
   only ever set when JavaScript is running AND IntersectionObserver exists AND
   the visitor has not asked for reduced motion, and the same snippet arms a 4s
   timer that removes it again if /js/reveal.js never arrives. So the hidden
   state cannot outlive the thing that undoes it, and every failure mode -
   scripting off, script blocked, 404, parse error, CSP refusal, reduced motion,
   an ancient browser - lands on plain visible content. Do not move any of these
   rules out from behind the attribute. */
html[data-nd-reveal] .reveal{opacity:0;transform:translateY(14px);
  transition:opacity var(--dur-slow) var(--ease-out-soft),transform var(--dur-slow) var(--ease-out-soft)}
html[data-nd-reveal] .reveal.in{opacity:1;transform:none}
html[data-nd-reveal] .reveal :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr){
  opacity:0;transform:translateY(8px);
  transition:opacity var(--dur-base) var(--ease-out-soft),transform var(--dur-base) var(--ease-out-soft)}
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr){
  opacity:1;transform:none}
/* The stagger rides on --stagger, so the reduced-motion block's `--stagger:0ms`
   collapses every one of these to simultaneous with no !important anywhere. */
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr):nth-child(2){transition-delay:calc(var(--stagger)*1)}
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr):nth-child(3){transition-delay:calc(var(--stagger)*2)}
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr):nth-child(4){transition-delay:calc(var(--stagger)*3)}
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr):nth-child(5){transition-delay:calc(var(--stagger)*4)}
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr):nth-child(6){transition-delay:calc(var(--stagger)*5)}
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr):nth-child(7){transition-delay:calc(var(--stagger)*6)}
html[data-nd-reveal] .reveal.in :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr):nth-child(n+8){transition-delay:calc(var(--stagger)*7)}

/* ---- one sticky header, and its shadow is a scroll response ----------------
   Nine pages plus landing.css shipped `box-shadow:var(--shadow-sm)` on the
   header unconditionally and had no code to toggle it, so the shadow sat there
   at scroll 0 with nothing under it to cast onto. The class page already had
   the right shape and this is its rule: a pseudo-element whose OPACITY moves,
   which the compositor can do, rather than a box-shadow that repaints. */
header{position:sticky;top:0;z-index:50;background:#fff}
header::after{content:"";position:absolute;inset:0;pointer-events:none;
  box-shadow:var(--shadow-sm);opacity:0;transition:opacity var(--dur-base) var(--ease-out)}
header.scrolled::after{opacity:1}

/* ---- one toast ------------------------------------------------------------
   Four pages carried this component at four different travels and timings. */
.toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(120px);z-index:80;
  background:var(--navy-deep);color:#fff;padding:14px 20px;border-radius:var(--radius-md);
  box-shadow:var(--shadow-overlay);font-size:var(--fs-sm);font-weight:600;max-width:90%;
  text-align:center;opacity:0;
  transition:transform var(--dur-base) var(--ease-spring),opacity var(--dur-fast) var(--ease-out)}
.toast.show{transform:translateX(-50%) translateY(0);opacity:1}
.toast b{color:var(--gold)}

/* ---- reduced motion — global safety net ---- */
@media(prefers-reduced-motion:reduce){
  /* One line, and every staggered entrance on every page becomes simultaneous.
     The blanket net below targets animation-duration/iteration-count/transition-
     duration and never touches a custom property, so this needs no !important. */
  :root{--stagger:0ms}
  *,*::before,*::after{
    animation-duration:.001ms!important;
    animation-iteration-count:1!important;
    transition-duration:.001ms!important;
    scroll-behavior:auto!important}
  .reveal,.reveal :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr){opacity:1!important;transform:none!important}
  .pickstrip.in .pickcar,.pickstrip.in .pcwheel{animation:none!important}
}

/* Belt and braces. The data-nd-reveal gate above already makes scripting:none
   impossible to hide content under, because the attribute is set BY script. This
   stays as a net for any page that ever re-declares `.reveal{opacity:0}` in its
   own <style> - which is exactly what shipped the homepage half-invisible. Note
   it is only a partial net: a page-level copy loads later at equal specificity
   and still wins, so the real protection is not writing one. */
@media(scripting:none){
  .reveal,.reveal :is(.card,.item,.ocard,.rcard,.note-list li,.ftable tbody tr){opacity:1;transform:none}
}

/* ---- ≤374px header compaction (every page shares this chrome) ----
   At 320px (iPhone SE 1st gen, and any newer iPhone with Display Zoom on) the
   header CTA wrapped onto two lines, grew taller than the header bar and
   overlapped the wordmark (measured on production). 375px and up were fine, so
   this fires only below that. .hactions .x outranks the pages' single-class
   .btn-amber/.btn-ghost rules, which load later but at 0-1-0. */
@media(max-width:374px){
  .hactions{gap:8px}
  .hactions .btn,.hactions .btn-amber,.hactions .btn-ghost{
    white-space:nowrap;padding:8px 10px;font-size:var(--fs-2xs);letter-spacing:0}
}

/* ---- skip-link class (ships now; anchor element is a separate copy change) ---- */
.skip-link{position:absolute;left:8px;top:-48px;z-index:100;background:var(--navy);
  color:#fff;padding:10px 16px;border-radius:10px;font-weight:700;transition:top .15s}
.skip-link:focus{top:8px}

/* ---- iPhone app install module (NDW-7; markup in src/lib/site-modules.ts) ----
   Lives here rather than in a per-page <style> so the same module can be woven
   into any page that already loads base.css, with no duplicated CSS and no
   <style> element in the body. */
.nd-app{background:#fff;border:1px solid var(--line,#E4E9F0);border-radius:var(--r);
  padding:22px;box-shadow:var(--shadow-sm);margin:26px 0}
.nd-app h2{font-size:var(--fs-lg);margin:0 0 8px}
.nd-app p{color:var(--slate,#5A6573);font-size:var(--fs-sm);margin:0 0 12px}
.nd-app ul{list-style:none;margin:0 0 16px;padding:0}
.nd-app li{display:flex;gap:9px;align-items:flex-start;font-size:var(--fs-sm);
  color:var(--ink,#16202E);margin-bottom:7px}
.nd-app li b{color:var(--navy,#0B2E59)}
/* --teal-ink, not --teal: this is a ✓ GLYPH rendered as text, and --teal measures
   4.14:1 on white. Two teal sweeps missed it because a grep for `var(--teal)`
   cannot match the hedged form `var(--teal,#128C7D)` it used to carry. */
.nd-app .nd-tick{color:var(--teal-ink);font-weight:800;flex:none}
.nd-app .nd-only{font-size:var(--fs-xs);color:var(--slate,#5A6573);margin:14px 0 0}
.nd-app-wide{display:grid;grid-template-columns:1.6fr 1fr;gap:24px;align-items:center}
.nd-app-wide .nd-get{text-align:center}
@media(max-width:720px){.nd-app-wide{grid-template-columns:1fr}}
