/* ============================================================================
   Niko — nikoapp.co.uk
   One stylesheet. No framework, no CDN, no webfont, no external request.

   The palette is the iOS app's, not the old marketing site's. The old site was
   purple (#7C5CFF); NikoTheme.accent is cyan (#41D5EC). The app wins, so every
   purple value here has been re-derived rather than find-and-replaced: the
   indigo stats band is gone entirely (a cyan version of that gradient is
   unreadable) and the hero glow runs at 10% alpha instead of 15% because cyan
   is far brighter than purple at the same alpha.

   Cyan is a LIGHT colour. Anything filled with --accent takes --on-accent
   (#06202A) as its text colour, never white. The app already solved this.
   ========================================================================= */

/* ---------------------------------------------------------------- tokens -- */
:root{
  /* surfaces — exact values from NikoDesignSystem/NikoTheme.swift */
  --bg:#090B13;              /* NikoTheme.background */
  --bg-2:#0B0E16;            /* NikoTheme.chrome */
  --surface:#14171F;         /* NikoTheme.surface */
  --surface-2:#1C2029;       /* NikoTheme.elevatedSurface */

  --hairline:rgba(255,255,255,.08);
  --border:rgba(255,255,255,.08);        /* alias, kept for legacy selectors */
  --border-strong:rgba(255,255,255,.16);

  /* text — --muted-2 is lifted from the old #767C90 to clear 4.5:1 on --bg */
  --text:#ECEEF4;
  --muted:#99A1B0;           /* NikoTheme.muted — 7.6:1 on --bg */
  --muted-2:#868DA2;         /* 5.9:1 on --bg */

  /* accent */
  --accent:#41D5EC;          /* NikoTheme.accent — 11.1:1 on --bg */
  --accent-hi:#7FE4F4;
  --accent-soft:rgba(65,213,236,.12);
  --accent-stroke:rgba(65,213,236,.35);
  --on-accent:#06202A;       /* NikoTheme.onAccent — 9.5:1 on --accent */

  /* state colours the old site had no equivalent for */
  --charging:#46D07E;        /* NikoTheme.charging */
  --attention:#E8A33D;       /* NikoTheme.attention */

  /* radii — the old site used one 12px value for everything, which is the
     app's *button* radius. Split it the way the app splits it. */
  --r-card:18px;
  --r-tile:16px;
  --r-button:12px;
  --r-hero:22px;
  --r-pill:999px;

  --maxw:1300px;
  --header-h:88px;

  /* no webfont: on Apple devices this resolves to SF, exactly what the app
     uses. Loading Inter would be the first third-party request this site has
     ever made and would need a Cyrillic subset for ru/uk. */
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,
         "Apple Color Emoji","Segoe UI Emoji",sans-serif;
}

/* ------------------------------------------------------------------ base -- */
*{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);
  line-height:1.6;font-size:16px;-webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;overflow-x:hidden;
}
a{color:inherit;text-decoration:none}
img,svg,video{max-width:100%;display:block}
figure{margin:0}
p{margin:0}
h1,h2,h3{margin:0;line-height:1.08;font-weight:700;letter-spacing:-.02em}
ul,ol{margin:0;padding:0;list-style:none}
button{font:inherit;color:inherit}
hr{border:0;border-top:1px solid var(--hairline);margin:0}

.wrap{width:100%;max-width:var(--maxw);margin:0 auto;padding:0 40px}

/* Every anchor target clears the 88px sticky header. The old site had
   scroll-behavior:smooth and a sticky header and no scroll-margin at all, so
   every in-page link landed with its heading hidden underneath. */
[id]{scroll-margin-top:calc(var(--header-h) + 16px)}

:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:6px}

.sr-only{
  position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

.skip{position:absolute;left:-9999px}
.skip:focus{
  left:16px;top:16px;z-index:99;background:var(--surface);padding:10px 16px;
  border-radius:var(--r-button);border:1px solid var(--border-strong);
}

/* --------------------------------------------------------------- buttons -- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  border-radius:var(--r-button);padding:12px 22px;font-size:.95rem;font-weight:600;
  border:1px solid var(--border-strong);transition:.18s ease;white-space:nowrap;
  cursor:pointer;background:none;text-align:center;
}
.btn:hover{transform:translateY(-1px)}
/* The app's primary button is a SOLID accent fill with dark text. The old site
   had no filled variant at all — only an outlined, tinted one. */
.btn-accent{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
.btn-accent:hover{background:var(--accent-hi);border-color:var(--accent-hi)}
.btn-outline{background:var(--accent-soft);border-color:var(--accent-stroke);color:var(--accent)}
.btn-outline:hover{background:rgba(65,213,236,.2)}
.btn-ghost{background:var(--surface-2);color:var(--text)}
.btn-ghost:hover{background:#242936}
.btn-quiet{background:none;border-color:var(--border-strong);color:var(--text)}
.btn-quiet:hover{background:var(--surface-2)}
.btn[disabled],.btn[aria-disabled="true"]{opacity:.5;cursor:default;transform:none}
.btn-block{width:100%}

/* The APP_STORE_URL constant is empty, and empty is the DEFAULT rendering, not
   an error state: inert text, never a disabled button and never a dead link. */
.unavailable{display:block}
.unavailable b{display:block;font-size:.95rem;font-weight:600;color:var(--text)}
.unavailable span{display:block;margin-top:6px;font-size:.86rem;color:var(--muted)}

/* ---------------------------------------------------------------- header -- */
header.site{
  border-bottom:1px solid var(--hairline);position:sticky;top:0;z-index:50;
  background:rgba(11,14,22,.86);backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);
}
.nav{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:var(--header-h)}
.brand{
  font-weight:700;font-size:1.28rem;letter-spacing:-.02em;
  display:inline-flex;align-items:center;gap:11px;flex:none;
}
.logo{width:30px;height:30px;flex:none;display:block}
.logo svg{width:100%;height:100%;display:block}
.nav-links{display:flex;gap:32px;align-items:center}
.nav-links a{color:var(--text);font-size:.95rem;opacity:.9}
.nav-links a:hover{opacity:1;color:var(--accent)}
.nav-links a[aria-current="page"]{color:var(--accent);opacity:1}
.nav-right{display:flex;align-items:center;gap:14px}

/* language menu. build.py injects the markup at {{LANG_SWITCHER}}, so the
   selectors below are deliberately broad: they style a <select>, a list of
   <a>, or a details/summary disclosure equally well. */
.lang{position:relative;font-size:.9rem}
.lang select{
  appearance:none;-webkit-appearance:none;background:var(--surface-2);color:var(--text);
  border:1px solid var(--border-strong);border-radius:var(--r-button);
  padding:9px 32px 9px 12px;font:inherit;font-size:.9rem;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),
                   linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 16px) 52%,calc(100% - 11px) 52%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
}
.lang a{color:var(--muted);padding:2px 0}
.lang a:hover{color:var(--accent)}
.lang a[aria-current],.lang [aria-current="true"]{color:var(--text);font-weight:600}

/* The language menu is a <details> disclosure - no script, the same pattern the
   mobile menu uses. It replaced a flat row of six endonyms, which was most of
   the header. */
.lang-menu{position:relative}
.lang-summary{
  list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:8px;
  background:var(--surface-2);color:var(--text);
  border:1px solid var(--border-strong);border-radius:var(--r-button);
  padding:9px 14px;white-space:nowrap;
}
.lang-summary::-webkit-details-marker{display:none}
.lang-summary::marker{content:""}
.lang-summary:hover{border-color:var(--accent-stroke)}
.lang-chevron{flex:none;color:var(--muted);transition:transform .16s ease}
.lang-menu[open] .lang-summary{border-color:var(--accent-stroke)}
.lang-menu[open] .lang-chevron{transform:rotate(180deg)}

/* Shut, the list stays in the DOM but out of view and out of the a11y tree.
   Open, it is a panel pinned to the summary rather than something that pushes
   the header around. */
.lang-menu>.lang-list{display:none}
.lang-menu[open]>.lang-list{
  position:absolute;right:0;top:calc(100% + 8px);z-index:60;display:grid;gap:2px;
  background:var(--surface);border:1px solid var(--border-strong);
  border-radius:var(--r-tile);padding:8px;min-width:170px;
  box-shadow:0 18px 40px rgba(0,0,0,.55);
}
.lang-menu>.lang-list a{display:block;padding:8px 10px;border-radius:9px}
.lang-menu>.lang-list a:hover{background:var(--surface-2);color:var(--accent)}
.lang-menu>.lang-list a[aria-current="true"]{color:var(--accent)}
/* In the footer there is nothing below it, so it opens upward instead of off
   the end of the document. */
.foot .lang-menu[open]>.lang-list,
.foot-bottom .lang-menu[open]>.lang-list{top:auto;bottom:calc(100% + 8px)}

/* Mobile disclosure. <details>/<summary> so it works with zero JavaScript and
   still exposes an expanded state to assistive tech. The old site's Menu button
   called scrollIntoView() on a display:none element — below 640px nothing on
   the whole site was reachable. */
.nav-mobile{display:none;position:relative}
.nav-mobile>summary{
  list-style:none;cursor:pointer;border:1px solid var(--border-strong);
  border-radius:var(--r-button);padding:10px 16px;font-size:.9rem;font-weight:600;
  display:inline-flex;align-items:center;
}
.nav-mobile>summary::-webkit-details-marker{display:none}
.nav-mobile .menu-close,.nav-mobile[open] .menu-open{display:none}
.nav-mobile[open] .menu-close{display:inline}
.nav-panel{
  position:absolute;right:0;top:calc(100% + 12px);z-index:60;width:min(84vw,300px);
  background:var(--surface);border:1px solid var(--border-strong);
  border-radius:var(--r-card);padding:18px;display:grid;gap:14px;
  box-shadow:0 24px 48px rgba(0,0,0,.5);
}
.nav-panel a.nav-item{font-size:1rem;padding:6px 0;color:var(--text)}
.nav-panel a.nav-item:hover{color:var(--accent)}
.nav-panel .btn{width:100%}

/* ----------------------------------------------------------------- media -- */
/* The artwork carries itself. There is no frame and no plate: every
   illustration is a transparent SVG that sits directly on the page, so the
   subject is the device, not the box around it. Depth comes from a shadow
   that follows the handset silhouette (drop-shadow reads the alpha channel,
   which a box-shadow cannot) plus a soft accent glow behind it.

   The dashed placeholder frame that used to live here is gone. It existed so
   a missing screenshot degraded to alt text on a styled box; every image it
   stood in for now exists in src/img, and the frame was cropping the hero. */
.media{
  position:relative;overflow:visible;margin:0 auto;display:block;width:100%;
  color:var(--muted);font-size:.9rem;text-align:center;
}
.media::before{
  content:"";position:absolute;z-index:0;pointer-events:none;
  left:-16%;right:-16%;top:-5%;bottom:1%;
  background:radial-gradient(56% 48% at 50% 42%,
              rgba(65,213,236,.20),rgba(65,213,236,.05) 55%,transparent 72%);
}
.media img{
  position:relative;z-index:1;display:block;width:100%;height:auto;
  /* The phone artwork draws its own rounded body and its corners are already
     transparent, so this radius does nothing there. It is here for anything
     rectangular that lands in one of these slots - the browser illustration,
     and any real screenshot later - so no image in the page is ever cut square. */
  border-radius:var(--r-card);
  filter:drop-shadow(0 2px 5px rgba(0,0,0,.55)) drop-shadow(0 30px 60px rgba(0,0,0,.6));
}
/* The phone artwork is 320x640. Cap it so a handset never inflates to the width
   of its column and stops looking like a phone - but the cap is generous: these
   illustrations ARE the argument the page is making, and they were previously
   letterboxed down to about 235px inside a frame that has now gone. */
.media--shot{max-width:360px}
.media--hero{max-width:404px}
.media--tall,.media--wide,.media--43,.media--square{max-width:360px}
/* The browser illustration is meant to fill its column. */
.media--web{max-width:100%}
.media--web::before{left:-8%;right:-8%}

/* ------------------------------------------------------------------ hero -- */
.hero{position:relative;padding:96px 0 104px}
.hero::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(48vw 42vw at 12% 0%,rgba(65,213,236,.10),transparent 62%);
}
.hero-grid{display:grid;grid-template-columns:1fr 1fr;gap:72px;align-items:center}
.pill{
  display:inline-flex;align-items:center;gap:10px;border:1px solid var(--border-strong);
  border-radius:var(--r-pill);padding:8px 16px;font-size:.87rem;color:var(--text);
  background:var(--accent-soft);
}
.pill i{width:7px;height:7px;border-radius:50%;background:var(--accent);display:block;flex:none}
h1{font-size:clamp(2.35rem,5.2vw,4.15rem);margin:26px 0 24px}
.lede{color:var(--muted);font-size:1.11rem;max-width:32em}
.cta-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin:38px 0 30px}
.cta-row .btn{padding:15px 26px}
.meta{display:flex;gap:18px;align-items:center;flex-wrap:wrap;color:var(--muted-2);font-size:.88rem}
.meta .sep{width:1px;height:14px;background:var(--border-strong);flex:none}

/* ----------------------------------------------------------------- facts -- */
/* Flat --surface with a hairline. The old indigo gradient band is deleted, and
   so are the four numbers it carried: a 4.8 store rating for an unpublished
   app, "312 parts", a 1.4s command latency nothing measures, and a
   "0 passwords" claim that was true of Tesla and false of Niko. */
.facts{background:var(--surface);border-block:1px solid var(--hairline)}
.facts-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:44px;padding:52px 0}
.fact b{display:block;font-size:2.6rem;font-weight:700;letter-spacing:-.03em;line-height:1;margin-bottom:14px;color:var(--accent)}
.fact p{font-size:.88rem;color:var(--muted);line-height:1.5}

/* -------------------------------------------------------------- sections -- */
section{padding:110px 0}
.eyebrow{
  font-size:.74rem;font-weight:700;letter-spacing:.19em;text-transform:uppercase;
  color:var(--accent);margin-bottom:22px;
}
h2{font-size:clamp(1.75rem,3.4vw,2.7rem);margin-bottom:24px}
.body{color:var(--muted);font-size:1.04rem;max-width:34em}
.split{display:grid;grid-template-columns:1fr 1fr;gap:76px;align-items:center}
.split-tight{gap:56px;align-items:start}

.checks{margin:32px 0 0;display:grid;gap:18px}
.checks li{display:grid;grid-template-columns:22px 1fr;gap:14px;align-items:start;font-size:.99rem}
.checks svg{margin-top:5px;color:var(--accent);flex:none}

/* Honesty notices. These say what the app does NOT do; they are bordered so
   they read as a statement, not as fine print to be skimmed. */
.notice{
  margin-top:30px;padding:18px 20px;border:1px solid var(--border-strong);
  border-left:3px solid var(--accent);border-radius:var(--r-button);
  background:var(--surface);color:var(--muted);font-size:.92rem;line-height:1.55;max-width:36em;
}

.cols{display:grid;grid-template-columns:repeat(3,1fr);gap:32px;margin-top:44px}
.col h3{font-size:1.05rem;margin-bottom:10px}
.col p{color:var(--muted);font-size:.94rem;line-height:1.55}

.points{display:grid;grid-template-columns:1fr 1fr;gap:32px 44px;margin-top:44px}
.point h3{font-size:1.02rem;margin-bottom:8px}
.point p{color:var(--muted);font-size:.94rem;line-height:1.55}

.media-pair{display:grid;grid-template-columns:1fr 1fr;gap:24px}

/* ----------------------------------------------------------------- panel -- */
.panel{background:var(--surface);border:1px solid var(--hairline);border-radius:var(--r-card);padding:60px}
.panel .split{gap:64px;align-items:start}
.steps li{
  display:grid;grid-template-columns:44px 1fr;gap:18px;padding:22px 0;
  border-bottom:1px solid var(--hairline);
}
.steps li:first-child{padding-top:0}
.steps li:last-child{border-bottom:0;padding-bottom:0}
.steps .n{color:var(--muted-2);font-size:.85rem;font-weight:700;font-variant-numeric:tabular-nums;padding-top:3px}
.steps h3{font-size:1rem;margin-bottom:6px}
.steps p{color:var(--muted);font-size:.92rem;line-height:1.55}
.holds{margin-top:32px;padding-top:26px;border-top:1px solid var(--hairline)}
.holds h3{font-size:1rem;margin-bottom:8px}
.holds p{color:var(--muted);font-size:.92rem;line-height:1.55}

/* ------------------------------------------------------------ final CTA -- */
.final{position:relative;padding:120px 0 130px;overflow:hidden}
.final-bg{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  width:100%;height:100%;object-fit:cover;border-radius:0;
}
/* The artwork bakes in its own left-to-right scrim; this is belt and braces so
   the copy never depends on an opacity trick for contrast. */
.final::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(90deg,var(--bg) 0%,rgba(9,11,19,.92) 42%,rgba(9,11,19,.35) 100%);
}
.final .wrap{position:relative;z-index:2}
.final .inner{max-width:36em}
.final h2{margin-bottom:20px}
.final p{color:var(--muted);font-size:1.05rem}
.final .cta-row{margin-bottom:0}

/* ---------------------------------------------------------------- footer -- */
footer.site{border-top:1px solid var(--hairline);padding:64px 0 72px;background:var(--bg-2)}
.foot{display:grid;grid-template-columns:1.7fr repeat(3,1fr);gap:56px}
.foot .brand{margin-bottom:16px}
.foot-tagline{color:var(--muted);font-size:.93rem;max-width:24em;margin-bottom:14px}
.foot-note{color:var(--muted-2);font-size:.87rem;max-width:26em;line-height:1.6}
.foot-h{
  margin:0 0 18px;font-size:.72rem;font-weight:700;letter-spacing:.17em;
  text-transform:uppercase;color:var(--muted-2);line-height:1.4;
}
.foot ul{display:grid;gap:13px}
.foot a{color:#D3D6E0;font-size:.93rem}
.foot a:hover{color:var(--accent)}
.foot-bottom{
  margin-top:56px;padding-top:28px;border-top:1px solid var(--hairline);
  display:flex;flex-wrap:wrap;gap:18px 32px;align-items:center;justify-content:space-between;
  color:var(--muted-2);font-size:.86rem;
}
.foot-storage{max-width:44em}

/* ================================================================ ACCOUNT == */
/* Mirrors NikoCardModifier: a SOLID surface, 18px radius, 1px hairline stroke.
   Deliberately not frosted glass — the app's own card modifier rejects blur
   because it takes its colour from whatever happens to be behind it. */
.card{background:var(--surface);border:1px solid var(--hairline);border-radius:var(--r-card);padding:24px}
.card+.card{margin-top:20px}
.card-head{display:flex;flex-wrap:wrap;gap:12px 20px;align-items:baseline;justify-content:space-between;margin-bottom:18px}
.card h2,.card h3{letter-spacing:-.015em}

.page{padding:48px 0 96px}
.page-head{margin-bottom:36px}
.page-head h1{font-size:clamp(1.9rem,3.6vw,2.5rem);margin:0 0 14px}
.page-head p{color:var(--muted);max-width:44em}

.account-bar{border-bottom:1px solid var(--hairline);background:var(--bg-2)}
.account-bar-inner{display:flex;flex-wrap:wrap;gap:12px 24px;align-items:center;justify-content:space-between;padding:14px 0}
.account-nav{display:flex;flex-wrap:wrap;gap:6px 20px;align-items:center}
.account-nav a{color:var(--muted);font-size:.93rem;padding:4px 0;border-bottom:2px solid transparent}
.account-nav a:hover{color:var(--text)}
.account-nav a[aria-current="page"]{color:var(--accent);border-bottom-color:var(--accent)}

.chip{
  display:inline-flex;align-items:center;gap:6px;border-radius:var(--r-pill);
  padding:4px 12px;font-size:.78rem;font-weight:600;line-height:1.5;
  background:var(--accent-soft);border:1px solid var(--accent-stroke);color:var(--accent);
}
.chip-quiet{background:rgba(255,255,255,.05);border-color:var(--border-strong);color:var(--muted)}
.chip-good{background:rgba(70,208,126,.12);border-color:rgba(70,208,126,.35);color:var(--charging)}
.chip-warn{background:rgba(232,163,61,.12);border-color:rgba(232,163,61,.35);color:var(--attention)}

/* status pill, from VehicleControlView */
.status-pill{
  display:inline-flex;align-items:center;gap:7px;border-radius:var(--r-pill);
  padding:5px 13px;font-size:.8rem;font-weight:600;
  background:var(--accent-soft);border:1px solid var(--accent-stroke);color:var(--accent);
}
.status-pill.is-offline{background:rgba(255,255,255,.05);border-color:var(--border-strong);color:var(--muted)}

/* vehicle hero band, from the app: 22px radius, elevated → background gradient */
.vehicle-band{
  border-radius:var(--r-hero);padding:24px;position:relative;overflow:hidden;
  background:linear-gradient(180deg,var(--surface-2),var(--bg));
  border:1px solid var(--hairline);
}
.vehicle-band-top{display:flex;flex-wrap:wrap;gap:12px 18px;align-items:flex-start;justify-content:space-between}
.vehicle-render{width:100%;max-width:420px;margin:8px auto 4px;aspect-ratio:16/9;object-fit:contain}
.vehicle-name{font-size:1.15rem;font-weight:700;letter-spacing:-.015em}
.vehicle-trim{color:var(--muted-2);font-size:.82rem;text-transform:uppercase;letter-spacing:.12em}
.battery-figure{font-size:40px;font-weight:700;line-height:1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.battery-summary{color:var(--muted);font-size:.9rem;margin-top:6px}
/* 4pt capsule, inset from the corner radius exactly as the app insets it */
.battery-bar{height:4px;border-radius:2px;background:rgba(255,255,255,.07);margin:20px 4px 0;overflow:hidden}
.battery-bar i{display:block;height:100%;border-radius:2px;background:var(--accent)}

/* A VIN broken across a hyphen reads as a different VIN, so it never wraps
   mid-token; it shrinks instead. word-break:break-all is banned here. */
.vin{font-variant-numeric:tabular-nums;letter-spacing:.02em;font-size:.86rem;overflow-wrap:normal;word-break:keep-all}

.attr-list{display:grid;gap:0}
.attr{
  display:grid;grid-template-columns:1fr auto;gap:16px;align-items:baseline;
  padding:13px 0;border-bottom:1px solid var(--hairline);
}
.attr:last-child{border-bottom:0}
.attr dt{color:var(--muted);font-size:.92rem}
.attr dd{margin:0;font-size:.95rem;font-weight:600;text-align:right}
/* "Not known" is a first-class value, never Off, never No, never a dash. */
.attr dd.is-unknown{color:var(--muted-2);font-weight:400;font-style:normal}

.grid-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px}
.vehicle-card{display:block;background:var(--surface);border:1px solid var(--hairline);border-radius:var(--r-card);padding:20px;transition:.18s ease}
.vehicle-card:hover{border-color:var(--accent-stroke);transform:translateY(-1px)}

/* NikoControlTile: min-height 100, 16px radius, icon + headline + caption. On
   the web these are real focusable buttons that open the gate — never
   `disabled`, which would explain nothing and could not be reached at all by a
   screen-reader user. Explaining is the entire point of the feature. */
.control-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.tile-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:14px}
.control-tile{
  position:relative;min-height:100px;border-radius:var(--r-tile);
  background:var(--surface);border:1px solid var(--hairline);
  padding:16px;display:flex;flex-direction:column;align-items:flex-start;gap:8px;
  text-align:left;cursor:pointer;transition:.18s ease;width:100%;
}
.control-tile:hover{background:var(--surface-2);border-color:var(--accent-stroke)}
.control-tile:active{transform:translateY(1px)}
.control-tile svg{color:var(--accent);flex:none}
.control-tile .t-title{font-size:.98rem;font-weight:600;margin-top:auto}
.control-tile .t-sub{font-size:.8rem;color:var(--muted)}
.control-tile .chip{position:absolute;top:12px;right:12px}

.list-rows{display:grid;gap:0}
.row{display:grid;gap:6px;padding:18px 0;border-bottom:1px solid var(--hairline)}
.row:first-child{padding-top:0}
.row:last-child{border-bottom:0;padding-bottom:0}
.row-head{display:flex;flex-wrap:wrap;gap:8px 14px;align-items:baseline;justify-content:space-between}
.row-title{font-weight:600}
.row-sub{color:var(--muted);font-size:.9rem}
.row-lines{color:var(--muted);font-size:.9rem;display:grid;gap:3px;margin-top:4px}

.empty{text-align:center;padding:48px 24px;max-width:38em;margin:0 auto}
.empty h2,.empty h3{font-size:1.2rem;margin-bottom:10px}
.empty p{color:var(--muted);margin-bottom:22px}

.state{padding:64px 0}
.state .card{max-width:34em;margin:0 auto;text-align:center}
.state h2{font-size:1.35rem;margin-bottom:12px}
.state p{color:var(--muted);margin-bottom:20px}

/* Skeletons carry a visible loading label in an aria-live region alongside;
   never a bare spinner. */
.skeleton{border-radius:var(--r-card);background:var(--surface);border:1px solid var(--hairline);height:150px;position:relative;overflow:hidden}
.skeleton::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.045),transparent);
  transform:translateX(-100%);animation:shimmer 1.5s infinite;
}
@keyframes shimmer{to{transform:translateX(100%)}}

/* Stale readings are dimmed and dated, exactly as the app dims them, rather
   than being blanked or presented as current. */
.is-stale{opacity:.55}

.dim-note{color:var(--muted-2);font-size:.86rem;margin-top:10px}

/* ------------------------------------------------------------- sign-in --- */
.auth-shell{max-width:460px;margin:0 auto;padding:56px 0 96px}
.auth-card{background:var(--surface);border:1px solid var(--hairline);border-radius:var(--r-card);padding:34px 30px}
.auth-shell h1{font-size:1.6rem;margin-bottom:12px}
.auth-sub{color:var(--muted);font-size:.95rem;margin-bottom:26px}
.tabs{display:flex;gap:8px;background:var(--surface-2);border-radius:var(--r-button);padding:5px;margin-bottom:24px}
.tab{
  flex:1;border:0;background:none;border-radius:9px;padding:10px 8px;font-size:.88rem;
  font-weight:600;color:var(--muted);cursor:pointer;transition:.15s ease;
}
.tab:hover{color:var(--text)}
.tab[aria-selected="true"]{background:var(--accent);color:var(--on-accent)}

.field{display:grid;gap:8px;margin-bottom:20px}
.field label{font-size:.88rem;font-weight:600}
.field input{
  width:100%;background:var(--bg);color:var(--text);font:inherit;font-size:1rem;
  border:1px solid var(--border-strong);border-radius:var(--r-button);padding:13px 14px;
}
.field input:focus-visible{border-color:var(--accent);outline-offset:1px}
.field input[aria-invalid="true"]{border-color:var(--attention)}
.field-hint{color:var(--muted);font-size:.85rem}
.field-error{color:var(--attention);font-size:.85rem;min-height:1.2em}
.field-row{display:flex;gap:10px;align-items:flex-end}
.field-row .field{flex:1;margin-bottom:0}
.code-input{letter-spacing:.5em;font-variant-numeric:tabular-nums;text-align:center;font-size:1.35rem!important}
.link-btn{background:none;border:0;padding:0;color:var(--accent);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left}
.link-btn:hover{color:var(--accent-hi)}
.auth-foot{margin-top:22px;display:grid;gap:12px;color:var(--muted);font-size:.86rem}
.auth-foot a{color:var(--accent)}
.auth-aside{margin-top:24px;padding-top:22px;border-top:1px solid var(--hairline)}
.auth-aside h2{font-size:1rem;margin-bottom:8px}
.auth-aside p{color:var(--muted);font-size:.9rem}
.form-status{margin:0 0 16px;font-size:.9rem;color:var(--muted)}
.form-status[data-tone="error"]{color:var(--attention)}
.form-status:empty{display:none;margin:0}

/* ------------------------------------------------------------ gate dialog -- */
dialog.gate{
  padding:0;border:0;background:transparent;color:var(--text);max-width:min(92vw,480px);width:100%;
}
dialog.gate::backdrop{background:rgba(4,6,11,.72)}
.gate-card{
  background:var(--surface);border:1px solid var(--accent-stroke);
  border-radius:var(--r-card);padding:28px 26px;position:relative;
}
.gate-card h2{font-size:1.25rem;margin-bottom:14px;padding-right:32px}
.gate-card p{color:var(--muted);font-size:.95rem;line-height:1.6}
.gate-card p+p{margin-top:12px}
.gate-reason{
  margin-top:18px;padding:14px 16px;border-radius:var(--r-button);
  background:var(--accent-soft);border:1px solid var(--accent-stroke);
  color:var(--text);font-size:.9rem;line-height:1.55;
}
.gate-actions{margin-top:24px;display:grid;gap:12px}
.gate-dismiss{
  position:absolute;top:18px;right:18px;background:none;border:0;cursor:pointer;
  color:var(--muted);line-height:0;padding:4px;border-radius:8px;
}
.gate-dismiss:hover{color:var(--text)}
dialog.gate[open]{animation:gate-in .16s ease-out}
@keyframes gate-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* ------------------------------------------------------------ suggestion -- */
.lang-suggest{
  background:var(--surface);border-bottom:1px solid var(--hairline);
  padding:12px 0;font-size:.92rem;
}
.lang-suggest .wrap{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;justify-content:center}
.lang-suggest .link-btn{font-size:.9rem}

/* ----------------------------------------------------------- breakpoints -- */
@media (max-width:1080px){
  .wrap{padding:0 28px}
  .hero-grid,.split,.media-pair{grid-template-columns:1fr;gap:48px}
  .split-reverse .media{order:2}
  .facts-grid{grid-template-columns:repeat(2,1fr);gap:36px}
  .cols{grid-template-columns:1fr;gap:28px}
  .points{grid-template-columns:1fr;gap:28px}
  .control-grid{grid-template-columns:repeat(2,1fr)}
  .foot{grid-template-columns:1fr 1fr;gap:40px}
  .panel{padding:40px 32px}
  section{padding:80px 0}
}

@media (max-width:760px){
  .nav-links,.nav-right .btn,.nav-right .lang{display:none}
  .nav-mobile{display:block}
  .tile-grid{grid-template-columns:1fr}
}

@media (max-width:640px){
  .wrap{padding:0 20px}
  .facts-grid,.foot{grid-template-columns:1fr}
  .facts-grid{gap:28px;padding:40px 0}
  .cta-row .btn,.cta-row .unavailable{flex:1 1 100%;width:100%}
  .panel{padding:28px 20px}
  section{padding:64px 0}
  .hero{padding:56px 0 64px}
  .final{padding:80px 0 88px}
  .control-grid{grid-template-columns:1fr 1fr}
  .card{padding:20px 18px}
  .attr{grid-template-columns:1fr;gap:2px}
  .attr dd{text-align:left}
  .battery-figure{font-size:34px}
  .foot-bottom{flex-direction:column;align-items:flex-start}
  .tabs{flex-wrap:wrap}
  .tab{flex:1 1 100%}
}

/* 375px is the real floor — the audience is phone users. */
@media (max-width:400px){
  .wrap{padding:0 16px}
  .brand{font-size:1.15rem}
  .logo{width:26px;height:26px}
  h1{font-size:2.05rem}
  .control-grid{grid-template-columns:1fr}
  .auth-card{padding:24px 18px}
  .gate-card{padding:22px 18px}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{transition:none!important;animation:none!important}
  .btn:hover,.vehicle-card:hover{transform:none}
  .skeleton::after{display:none}
}

@media print{
  header.site,footer.site,.nav-mobile,dialog.gate{display:none}
  body{background:#fff;color:#000}
}

/* ---------------------------------------------------------------------------
 * Utilities
 *
 * These exist because the Content-Security-Policy on the account origin is
 * `style-src 'self'`, which blocks every style="" attribute outright. The page
 * holds session tokens in JavaScript, so the CSP is the primary defence and is
 * not worth loosening to 'unsafe-inline' for nine spacing declarations.
 * ------------------------------------------------------------------------ */
.u-abs{position:absolute}
.u-mt-8{margin-top:8px}
.u-mt-10{margin-top:10px}
.u-mt-12{margin-top:12px}
.u-mt-14{margin-top:14px}
.u-mt-16{margin-top:16px}
.u-mt-18{margin-top:18px}
.u-mt-22{margin-top:22px}
.u-mt-28{margin-top:28px}
.u-mt-44{margin-top:44px}
.u-mb-0{margin-bottom:0}
.u-mb-12{margin-bottom:12px}
.u-my-22{margin:22px 0}
.u-pt-20{padding-top:20px}
.u-w-0{width:0}
.u-flush-top{margin-top:0;border-top:0;padding-top:0}
.u-media-16x9{width:100%;aspect-ratio:16/9;object-fit:contain}
.u-row-wrap{display:flex;flex-wrap:wrap;gap:8px 22px}
.u-muted-mt-10{color:var(--muted);margin-top:10px}
