/* AiDD 愛啲代駕 — sticker-art brand styling from the print flyers:
   orange ground, white cards with fat black borders + hard offset shadows,
   red price accents, condensed black display type. */

/* Palette note: the flyer's neon #F7941D is right for a printed sticker but
   punishing as a full-width screen background at 2am. The brand orange is kept
   for small accents where it should pop, while large filled areas use the
   deeper amber below, and the paper tones are warmed down off pure white so the
   cards stop glaring. Everything still reads as the same brand. */
:root {
  /* Grab-style palette with dark blue in place of Grab's green.
     Light, calm surfaces; ONE strong accent; soft shadows instead of the old
     hard offsets. Anywhere the background is dark, the text is white. */
  --blue: #12395F;            /* the accent — Grab's green slot */
  --blue-dark: #0B2743;       /* hover / pressed */
  --blue-deep: #071B30;       /* nav, hero, footer — deepest surface */
  --blue-tint: #E8EEF5;       /* pale wash for selected rows and bands */
  --orange: var(--blue);      /* legacy name: every old rule follows the accent */
  --orange-bright: #1D5E96;   /* lighter blue for small highlights */
  --orange-deep: var(--blue-dark);
  --orange-fill: var(--blue-tint);   /* section bands are now a pale wash */
  --ink: #1C1C1C;             /* primary text on light */
  --ink-dim: #6B6B6B;         /* secondary text */
  --on-dark: #FFFFFF;         /* text on any dark surface — always white */
  --black: #071B30;           /* deep surface, not a text colour */
  --line: #E4E4E4;            /* hairline borders, Grab-style */
  --red: #C5372C;
  --green: #1CA05A;           /* success only */
  --cream: #F7F7F7;           /* page ground */
  --white: #FFFFFF;           /* cards */
  --paper: #FFFFFF;
  /* Grab uses soft elevation, not sticker outlines. */
  --border: 1px solid var(--line);
  --shadow: 0 2px 8px rgba(0,0,0,.10);
  --shadow-sm: 0 1px 4px rgba(0,0,0,.10);
  --radius: 14px;
  --display: 'Archivo Black', 'Noto Sans TC', sans-serif;
  --body: 'Noto Sans TC', system-ui, sans-serif;
}

* { box-sizing: border-box; }
/* The `hidden` attribute is only `display: none` in the UA stylesheet, so ANY
   author rule that sets display (e.g. `.overlay { display: flex }`) silently
   defeats it and the element stays on screen. This makes hidden actually hide. */
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--body);
  color: var(--ink);            /* NOT --black: that is now a dark surface colour */
  background: var(--cream);
  line-height: 1.55;
  /* This site mixes English and Chinese in the same line everywhere. By
     default a browser may break between any two Chinese characters, which
     splits a phrase like 安全到家 across two lines and reads as broken text.
     keep-all forbids breaks inside a CJK run — the phrase moves to the next
     line whole — while break-word remains as a safety net so an unusually
     long run can still break rather than overflow its container. */
  word-break: keep-all;
  overflow-wrap: break-word;
}
a { color: inherit; }
h1, h2, h3 { font-family: var(--display); line-height: 1.15; margin: 0 0 .5rem; }
.red { color: var(--red); }
/* Red on an orange band is barely legible (1.8:1). Inside a card the card's own
   pale ground restores it, so only darken the text that sits on the band. */
.section-orange .red { color: var(--red); }
.section-orange .sticker-card .red,
.section-orange .ops-card .red { color: var(--red); }
.orange { color: var(--orange); }
.zh { font-family: var(--body); font-weight: 900; }
/* Short bilingual labels move as one unit rather than wrapping at all. */
.zh-sub {
  font-family: var(--body); font-weight: 700; font-size: .72em; opacity: .85;
  display: inline-block; white-space: nowrap;
}
.zh-line { font-weight: 500; font-size: .88em; opacity: .8; }
.center { text-align: center; margin-top: 1.8rem; }
.muted { opacity: .65; }

/* ---------- buttons ---------- */
.btn {
  display: inline-block;
  font-family: var(--display);
  font-size: .95rem;
  padding: .55rem 1.2rem;
  border: 3px solid var(--black);
  border-radius: 12px;
  background: var(--white);
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform .08s, box-shadow .08s;
}
.btn:hover { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--black); }
.btn:active { transform: translate(4px, 4px); box-shadow: none; }
.btn-orange { background: var(--orange); }
.btn-dark { background: var(--black); color: var(--ink); }
/* A black offset shadow is invisible on a black surface, which flattens the
   sticker look exactly where the brand is strongest. Use a light offset there. */
.hero .btn, .section-dark .btn, .footer .btn {
  box-shadow: 4px 4px 0 rgba(255, 255, 255, .32);
}
.hero .btn:hover, .section-dark .btn:hover, .footer .btn:hover {
  box-shadow: 2px 2px 0 rgba(255, 255, 255, .32);
}
/* The ghost button must not out-shout the primary CTA beside it. */
.btn-ghost { background: transparent; color: var(--ink); border-color: rgba(255,255,255,.6);
  box-shadow: none !important; font-family: var(--body); font-weight: 900; }
.btn-ghost:hover { border-color: var(--white); background: rgba(255,255,255,.08); }
.btn-whatsapp { background: #25D366; }
.btn-big { font-size: 1.1rem; padding: .8rem 1.8rem; }
.btn-small { font-size: .75rem; padding: .35rem .8rem; }
.btn-block { display: block; width: 100%; text-align: center; margin-top: 1rem; }
.btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: var(--shadow-sm); }

/* ---------- nav ---------- */
.nav {
  position: sticky; top: 0; z-index: 1100;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  background: var(--black); color: var(--ink);
  padding: .6rem 1.4rem;
  border-bottom: 4px solid var(--orange);
}
.nav-brand { display: flex; align-items: center; gap: .7rem; text-decoration: none; }
/* The partial passes its requested size as the custom property --logo-size and
   sets no inline width/height, so these rules win normally — no !important. */
.aidd-logo { display: block; flex: none; width: var(--logo-size, 46px); height: auto; }
/* In the bars, size by HEIGHT instead: the real artwork is portrait, and
   sizing by width would make the bar grow taller with the logo's aspect ratio. */
.nav-brand .aidd-logo, .drv-brand .aidd-logo, .ops-brand .aidd-logo {
  height: 42px; width: auto;
}
.drv-brand .aidd-logo, .ops-brand .aidd-logo { height: 32px; }
.nav-links a:not(.btn) { white-space: nowrap; }
.hero-badge .aidd-logo { margin: 0 auto; filter: drop-shadow(8px 8px 0 rgba(247,148,29,.3)); }
/* Needs the parent in the selector: .badge-247 sets display:flex further down
   the file and would otherwise win, stretching the badge into a full-width bar. */
.hero-badge .badge-247 { display: inline-flex; margin-top: 1rem; width: auto; align-self: center; }
.brand-text { display: flex; flex-direction: column; line-height: 1.1; }
.brand-main { font-family: var(--display); font-size: 1.25rem; color: var(--ink); }
.brand-main .zh { color: var(--orange); font-size: .85em; }
.brand-sub { font-family: var(--display); font-size: .58rem; letter-spacing: .22em; color: var(--orange); }
.nav-links { display: flex; align-items: center; gap: 1.3rem; }
.nav-links a:not(.btn) {
  color: var(--ink); text-decoration: none; font-weight: 700; font-size: .95rem;
  border-bottom: 3px solid transparent; padding-bottom: 2px;
}
.nav-links a:not(.btn):hover { border-bottom-color: var(--orange); }
.nav-toggle { display: none; background: none; border: none; color: var(--ink); font-size: 1.6rem; cursor: pointer; }

/* ---------- hero ---------- */
.hero {
  background: var(--black);
  color: var(--ink);
  background-image:
    radial-gradient(ellipse 80% 60% at 85% 10%, rgba(232,137,15,.18), transparent 60%),
    repeating-linear-gradient(115deg, transparent 0 46px, rgba(232,137,15,.06) 46px 54px);
  padding: 4.5rem 1.4rem 5rem;
}
.hero-inner {
  max-width: 1080px; margin: 0 auto;
  display: grid; grid-template-columns: 1.6fr 1fr; gap: 2.5rem; align-items: center;
}
.hero-kicker {
  display: inline-block;
  font-family: var(--display); font-size: .8rem; letter-spacing: .18em;
  background: var(--orange); color: var(--ink);
  border: 3px solid var(--white); border-radius: 10px;
  padding: .3rem .9rem; margin-bottom: 1.2rem;
  box-shadow: 4px 4px 0 rgba(255,255,255,.25);
  transform: rotate(-1.5deg);
}
.hero h1 { font-size: clamp(2.1rem, 5vw, 3.4rem); margin-bottom: 1rem; }
.hero-zh { font-weight: 900; font-size: 1.15rem; color: var(--orange); margin: 0 0 .8rem; }
.hero-sub { max-width: 46ch; opacity: .92; }
.hero-cta { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 1.8rem; }
.hero-pin { width: 100%; max-width: 260px; filter: drop-shadow(10px 10px 0 rgba(247,148,29,.35)); }
.hero-badge { text-align: center; }

/* ---------- sections ---------- */
.section { padding: 3.5rem 1.4rem; max-width: 1080px; margin: 0 auto; }
.section-orange {
  max-width: none;
  /* The deeper fill, not the accent orange — this covers whole bands of the
     screen and was the brightest surface in the product. */
  background: var(--orange-fill);
  background-image: repeating-linear-gradient(90deg, transparent 0 26px, rgba(20,20,20,.06) 26px 34px);
  border-top: 4px solid var(--black);
  border-bottom: 4px solid var(--black);
}
.section-orange > * { max-width: 1080px; margin-left: auto; margin-right: auto; }
.section-dark {
  max-width: none;
  background: var(--black); color: var(--ink);
  border-top: 4px solid var(--orange);
  border-bottom: 4px solid var(--orange);
}
.section-dark > * { max-width: 1080px; margin-left: auto; margin-right: auto; }
.section-title { font-size: clamp(1.6rem, 3.5vw, 2.3rem); margin-bottom: 1.8rem; text-align: center; }
/* Vertical rhythm: a heading that follows content needs room above it, or it
   sits flush against the card/list before it. Headings carry no top margin by
   default (so they don't add space at the top of a section), which is exactly
   why this sibling rule is needed rather than a blanket margin-top. */
* + .section-title { margin-top: 2.8rem; }
.page-head .section-title, .section > .section-title:first-child { margin-top: 0; }
.booking-list + *, .card-grid + *, .faq-list + *, .table-scroll + * { margin-top: 1.8rem; }
.booking-list, .card-grid, .faq-list { margin-bottom: 0; }
.section-title.light { color: var(--ink); }
/* `margin: … 0 …` cancelled the auto side-margins that centre this block, so
   the text was centred inside a left-flush box. Same trap as .page-head had. */
.section-sub { text-align: center; margin: -1.2rem auto 1.8rem; opacity: .85; max-width: 60ch; }
/* The band is now a deep amber, so light text reads better on it than black
   (4.9:1 vs 3.8:1). Cards sitting on the band keep their own dark-on-pale text. */
.section-orange > .section-title,
.section-orange > .section-sub { color: #FFF8EE; }
.section-sub.dark { color: #FFF8EE; font-weight: 700; opacity: 1; }
.section-orange > .section-title .zh-sub { color: #FFF8EE; }
.page-head { padding: 2.4rem 1.4rem; }
.page-head .section-title { margin-bottom: .4rem; }
/* `margin: 0` here used to cancel the auto side-margins that centre the block,
   leaving centred text inside a left-flush box. Keep the auto margins. */
.page-head .section-sub { margin: 0 auto; }

/* ---------- sticker cards ---------- */
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.8rem; }
/* A three-card row must be 3-across or 1-across — never 2 + a lone orphan on
   the second row, which is what auto-fit does at tablet widths. */
@media (min-width: 900px) {
  .card-grid:has(> :nth-child(3):last-child) { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 899px) {
  .card-grid:has(> :nth-child(3):last-child) { grid-template-columns: 1fr; }
}
.card-grid.two { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.sticker-card {
  background: var(--white);
  border: var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.5rem 1.6rem;
  color: var(--ink);
}
.card-icon { font-size: 2rem; line-height: 1; margin-bottom: .8rem; }
/* Inside cards the Chinese always takes its own line. Left inline it wrapped
   only on the cards whose English happened to be long, so a row of otherwise
   identical cards had the translation in a different place on each one. */
.card-grid .sticker-card h3 .zh-sub {
  display: block;
  margin-top: .2rem;
  font-size: .78em;
  opacity: .8;
}
.rate-head {
  font-size: 1.05rem; color: var(--red);
  border-bottom: 3px solid var(--black); padding-bottom: .5rem; margin-bottom: .9rem;
}
.rate-list { list-style: none; margin: 0; padding: 0; }
.rate-list li { padding: .55rem 0; border-bottom: 2px dashed rgba(20,20,20,.18); font-weight: 700; }
.rate-list li:last-child { border-bottom: none; }
.rate-table { width: 100%; border-collapse: collapse; font-weight: 700; }
.rate-table th, .rate-table td { padding: .5rem .4rem; text-align: left; border-bottom: 2px dashed rgba(20,20,20,.18); }
.rate-table th { font-family: var(--display); font-size: .8rem; }
.notice-card { max-width: 720px; margin: 2rem auto 0; }
/* Chinese body copy inside cards: separated from the English by a rule so the
   two languages read as a pair rather than one run-on paragraph. */
.card-zh {
  margin-top: .8rem; padding-top: .8rem;
  border-top: 2px dashed rgba(20, 20, 20, .2);
  font-size: .88rem; line-height: 1.85; opacity: .85;
}
.section-dark .card-zh { border-top-color: rgba(255, 255, 255, .25); }

/* ---------- drivers ---------- */
.driver-card { text-align: center; }
.avatar {
  width: 74px; height: 74px; margin: 0 auto .8rem;
  border: var(--border); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display); font-size: 1.5rem; color: var(--ink);
  text-shadow: 2px 2px 0 rgba(0,0,0,.4);
}
.avatar-any { background: repeating-linear-gradient(45deg, var(--orange) 0 10px, var(--black) 10px 20px); }
.driver-meta { font-weight: 700; font-size: .85rem; margin-bottom: .3rem; }
.driver-langs { font-size: .8rem; opacity: .75; margin-bottom: .5rem; }
.driver-bio { font-size: .9rem; min-height: 2.6em; }

/* ---------- booking page ---------- */
.booking-layout {
  display: grid; grid-template-columns: 1.7fr 1fr; gap: 1.8rem; align-items: start;
}
.booking-main { display: flex; flex-direction: column; gap: 1.8rem; }
.form-card h3.step-head {
  display: flex; align-items: center; gap: .6rem;
  font-size: 1.05rem; margin-bottom: 1rem;
}
.step-num {
  width: 30px; height: 30px; flex: none;
  background: var(--orange); border: 3px solid var(--black); border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .95rem;
}
.service-tabs { display: grid; grid-template-columns: repeat(3, 1fr); gap: .8rem; }
.service-tab {
  font-family: var(--display); font-size: .85rem;
  display: flex; flex-direction: column; gap: .15rem; align-items: center;
  padding: .7rem .4rem;
  background: var(--cream); border: 3px solid var(--black); border-radius: 12px;
  cursor: pointer; box-shadow: var(--shadow-sm);
}
.service-tab span { font-family: var(--body); font-weight: 700; font-size: .75rem; }
.service-tab.active { background: var(--orange); transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--black); }
.field { margin-bottom: 1rem; }
.field label { display: block; font-weight: 900; font-size: .85rem; margin-bottom: .3rem; }
.field input, .field select {
  width: 100%; padding: .6rem .8rem;
  font-family: var(--body); font-size: 1rem; font-weight: 500;
  border: 3px solid var(--black); border-radius: 10px; background: var(--white);
}
.field input:focus, .field select:focus { outline: 3px solid var(--orange); outline-offset: 1px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.geo-row { display: flex; gap: .6rem; }
.geo-row input { flex: 1; }
/* NB: do not add an `#map` rule here. The booking screen sizes its map with
   `.ride-map` (absolute, inset 0); an id selector outranks that class and
   collapses the full-screen map back to a small box. */
.driver-pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: .9rem; }
.driver-pick {
  font-family: var(--body); text-align: center;
  background: var(--white); border: 3px solid var(--black); border-radius: 12px;
  padding: .9rem .6rem; cursor: pointer; box-shadow: var(--shadow-sm);
}
.driver-pick .avatar { width: 52px; height: 52px; font-size: 1.1rem; border-width: 3px; margin-bottom: .5rem; }
.driver-pick.selected { background: var(--orange); transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--black); }
.driver-pick.unavailable { opacity: .45; cursor: not-allowed; }
.dp-name { font-weight: 900; font-size: .9rem; }
.dp-meta { font-size: .72rem; opacity: .75; }
.dp-busy { font-size: .72rem; font-weight: 900; color: var(--red); margin-top: .2rem; }

/* quote card */
.booking-side { position: sticky; top: 90px; }
.quote-card .quote-line {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .45rem 0; border-bottom: 2px dashed rgba(20,20,20,.18);
  font-weight: 700; font-size: .9rem;
}
.quote-line span:last-child { white-space: nowrap; }
.quote-empty { font-weight: 700; opacity: .6; }
.quote-total {
  display: flex; justify-content: space-between; align-items: center;
  font-family: var(--display); font-size: 1.25rem;
  background: var(--black); color: var(--orange);
  border-radius: 10px; padding: .6rem .9rem; margin-top: .9rem;
}
.quote-cancel { color: var(--red); }
.quote-fine { font-size: .75rem; opacity: .7; margin: .8rem 0 0; }
.quote-error { color: var(--red); font-weight: 900; font-size: .85rem; margin: .8rem 0 0; }

/* booking list / detail */
.lookup-card { max-width: 480px; margin: 0 auto 2rem; }
.booking-list { display: flex; flex-direction: column; gap: 1.2rem; max-width: 760px; margin: 0 auto; }
.booking-row {
  display: flex; justify-content: space-between; gap: 1rem; align-items: center;
  text-decoration: none; padding: 1.1rem 1.4rem;
}
.booking-row-main { display: flex; flex-direction: column; gap: .15rem; font-size: .92rem; }
.booking-row-side { display: flex; flex-direction: column; align-items: flex-end; gap: .4rem; }
.pill {
  font-family: var(--display); font-size: .68rem; letter-spacing: .06em;
  border: 2px solid var(--black); border-radius: 999px; padding: .18rem .7rem;
  background: var(--cream); white-space: nowrap;
}
/* One pill per status. Five of these previously had no rule at all and fell
   back to the same grey, so "Driver assigned" and "On the way" looked identical. */
.pill-pending   { background: #FFD54F; }
.pill-assigned  { background: #FFB74D; }
.pill-accepted  { background: #7CD98B; }
.pill-arrived   { background: #4FC3F7; }
.pill-onboard   { background: var(--orange); }
.pill-confirmed { background: #7CD98B; }
.pill-completed { background: #B0BEC5; }
.pill-cancelled { background: #EF9A9A; }
.status-live { border-color: var(--orange); }
.detail-list { display: grid; grid-template-columns: auto 1fr; gap: .5rem 1.4rem; margin: 0; }
.detail-list dt { font-weight: 900; font-size: .85rem; }
.detail-list dd { margin: 0; }

/* ---------- footer ---------- */
.footer { background: var(--black); color: var(--ink); border-top: 4px solid var(--orange); padding: 2.6rem 1.4rem; }
.footer-inner {
  max-width: 1080px; margin: 0 auto;
  display: grid; grid-template-columns: 1.4fr auto 1.2fr; gap: 2rem; align-items: center;
}
.footer-brand { font-family: var(--display); font-size: 1.15rem; color: var(--orange); margin-bottom: .8rem; }
.footer-brand span { color: var(--ink); font-size: .85em; }
.footer-note { font-size: .82rem; opacity: .85; margin: .4rem 0; }
.footer-fine { font-size: .72rem; opacity: .6; margin-top: .8rem; }
.footer-contact { text-align: right; display: flex; flex-direction: column;
  align-items: flex-end; gap: .5rem; }
/* Plain links only — buttons keep their own foreground, or an orange button
   would render orange text on an orange fill and disappear. */
.footer-contact a:not(.btn) { color: var(--orange); }
.footer-contact .btn { color: var(--ink); }
.footer-contact .btn-whatsapp { color: var(--ink); }
.badge-247 {
  display: flex; flex-direction: column; align-items: center; line-height: 1;
  background: var(--orange); color: var(--ink);
  border: 4px solid var(--white); border-radius: 18px;
  padding: .8rem 1.3rem; font-family: var(--display);
  box-shadow: 6px 6px 0 rgba(255,255,255,.25);
  transform: rotate(2deg);
}
.badge-247 .hours, .badge-247 .days { font-size: .7rem; letter-spacing: .2em; }
.badge-247 .num { font-size: 2.2rem; }
.badge-247 .slash { color: var(--ink); text-shadow: 1px 1px 0 var(--black); }

/* ---------- chips (saved places, airports) ---------- */
.chips { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .45rem; }
.chips:empty { display: none; }
.chip {
  font-family: var(--body); font-weight: 900; font-size: .74rem;
  background: var(--cream); border: 2px solid var(--black); border-radius: 999px;
  padding: .22rem .7rem; cursor: pointer; box-shadow: 2px 2px 0 var(--black);
}
.chip:hover { background: var(--orange); }
.chip:active { transform: translate(2px, 2px); box-shadow: none; }
.chip-save { background: transparent; border-style: dashed; box-shadow: none; opacity: .7; }
.chip-airport { background: var(--white); }

/* ---------- form feedback ---------- */
.field-warn { color: var(--red); font-weight: 700; font-size: .78rem; margin: .35rem 0 0; }
.quote-card.loading #quote-body, .quote-card.loading .quote-total { animation: pulse .8s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: .35; } }
/* Orange-on-orange was invisible on the brand's own buttons; the black ring
   reads against every surface, with a white halo for dark backgrounds. */
:focus-visible {
  outline: 3px solid var(--black);
  outline-offset: 2px;
  box-shadow: 0 0 0 6px rgba(247, 148, 29, .55);
}
.driver-app :focus-visible, .section-dark :focus-visible, .nav :focus-visible {
  outline-color: var(--white);
}
/* Address inputs sit inside a bordered block and had no visible focus at all. */
.addr-row input:focus-visible { outline-offset: 0; border-radius: 6px; }

/* Touch devices keep :hover styling stuck on whatever was last tapped, so a
   chip or star looks selected when it is not. Restrict hover to real pointers. */
@media (hover: none) {
  .chip:hover, .btn:hover, .service-row:hover, .driver-pick:hover,
  .star-btn:hover, .ac-list li:hover, .locate-row:hover, .tip-chip:hover {
    background: inherit; transform: none;
  }
  .chip:hover { background: var(--cream); }
  .btn-orange:hover { background: var(--orange); }
  .btn-dark:hover { background: var(--black); }
}

/* Autocomplete status rows (searching / no match / failed) */
.ac-msg { opacity: .85; cursor: default; align-items: flex-start; }
.ac-msg:hover { background: none; }
.ac-msg .zh-line { display: block; font-size: .74rem; }
.btn.is-measuring { animation: pulse .8s ease-in-out infinite; }

/* Keep each sheet's primary action reachable without scrolling to the bottom. */
.sheet > .btn-block:last-child {
  position: sticky; bottom: 0; z-index: 2;
  margin-top: .9rem;
  box-shadow: 0 -10px 14px 6px var(--white), var(--shadow-sm);
}

/* ---------- rating stars ---------- */
.stars-form { display: flex; gap: .6rem; }
.star-btn {
  font-size: 1.6rem; line-height: 1;
  display: flex; flex-direction: column; align-items: center; gap: .1rem;
  background: var(--cream); color: var(--orange);
  border: 3px solid var(--black); border-radius: 12px;
  padding: .5rem .8rem; cursor: pointer; box-shadow: var(--shadow-sm);
}
.star-btn span { font-size: .7rem; font-weight: 900; color: #14100A; }
.star-btn:hover { background: var(--orange); color: var(--ink); }
.star-btn:active { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--black); }
.stars-static { font-size: 1.4rem; color: var(--orange); font-weight: 900; }
.countdown { font-weight: 900; color: var(--ink); opacity: 1; }

/* ---------- ops dashboard ---------- */
.light-link { color: var(--orange); font-weight: 700; }
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1.4rem; margin-bottom: 1.8rem; }
.kpi { text-align: center; padding: 1.1rem .8rem; }
.kpi-num { font-family: var(--display); font-size: 2rem; display: block; }
.kpi-label { font-weight: 700; font-size: .8rem; opacity: .75; }
.kpi-alert { background: #FFE9A8; }
.chart-card { margin-bottom: 1.8rem; }
.bar-chart {
  display: flex; align-items: flex-end; gap: 6px;
  height: 180px; padding-top: 1.4rem;
  border-bottom: 3px solid var(--black);
}
.bar-col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; position: relative; }
.bar {
  width: 100%; max-width: 42px;
  background: var(--orange); border: 2px solid var(--black); border-bottom: none;
  border-radius: 4px 4px 0 0;
  min-height: 0;
}
.bar-val {
  font-family: var(--display); font-size: .66rem; margin-bottom: 3px;
  visibility: hidden;
}
.bar-col:hover .bar-val, .bar-col.peak .bar-val { visibility: visible; }
.bar-col:hover .bar { background: var(--orange-deep); }
.bar-label { position: absolute; top: 100%; padding-top: 4px; font-size: .68rem; font-weight: 700; opacity: .6; }
.table-card { margin-bottom: 1.8rem; padding-bottom: 2rem; }
.table-scroll { overflow-x: auto; }
.ops-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.ops-table th {
  font-family: var(--display); font-size: .7rem; text-align: left;
  border-bottom: 3px solid var(--black); padding: .5rem .6rem; white-space: nowrap;
}
.ops-table td { padding: .6rem; border-bottom: 2px dashed rgba(20,20,20,.15); vertical-align: top; }
.ops-table select { font-family: var(--body); font-size: .8rem; border: 2px solid var(--black); border-radius: 8px; padding: .25rem .4rem; max-width: 130px; }
.actions-cell form { margin-bottom: .4rem; display: flex; gap: .3rem; align-items: center; }
.badge-repeat {
  font-size: .66rem; font-weight: 900; color: var(--ink);
  background: var(--orange); border: 2px solid var(--black); border-radius: 999px;
  padding: .05rem .45rem; white-space: nowrap;
}
.wa-link { font-weight: 700; font-size: .8rem; text-decoration: none; color: #1a7a43; }
.route-cell { max-width: 240px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- skip link ---------- */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 2000;
  background: var(--black); color: var(--orange);
  font-weight: 900; padding: .6rem 1.2rem; border-radius: 0 0 12px 0;
}
.skip-link:focus { left: 0; }

/* ---------- how it works / trust strip ---------- */
.how-card { text-align: center; }
.how-num {
  width: 44px; height: 44px; margin: 0 auto .7rem;
  background: var(--orange); border: var(--border); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display); font-size: 1.3rem;
}
.trust-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;
  margin-top: 2rem; text-align: center;
}
.trust-item {
  border: 3px dashed var(--black); border-radius: 12px; padding: .8rem .5rem;
}
.trust-item strong { font-family: var(--display); display: block; font-size: 1rem; }
.trust-item span { font-size: .74rem; font-weight: 700; opacity: .7; }

/* ---------- FAQ ---------- */
.faq-list { display: flex; flex-direction: column; gap: 1rem; max-width: 760px; margin: 0 auto; }
.faq { padding: 0; overflow: hidden; }
.faq summary {
  font-family: var(--display); font-size: .95rem;
  padding: 1rem 1.4rem; cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: 1rem;
}
/* The question text is one block on the left; only the +/- marker is pushed
   right. Previously space-between flung the Chinese to the far right, leaving
   it stranded mid-row and reading as a separate column. */
.faq summary .q-text { flex: 1; min-width: 0; }
.faq summary .zh-sub {
  display: block; margin-top: .2rem; font-size: .82rem; opacity: .8;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; font-size: 1.4rem; color: var(--orange); flex: none; }
.faq[open] summary::after { content: '–'; }
.faq[open] summary { background: var(--orange); }
.faq p { margin: 0; padding: 1rem 1.4rem; }
.faq-body { border-top: 3px solid var(--black); }
.faq-body p + p { padding-top: 0; }
.faq-zh { border-top: 2px dashed rgba(20,20,20,.2); margin-top: .2rem !important;
  padding-top: .9rem !important; line-height: 1.9; }

/* ---------- print (booking receipt) ---------- */
@media print {
  .nav, .footer, .btn, form, .skip-link, .mobile-fare-bar, .countdown, .no-print { display: none !important; }
  body { background: #fff; }
  .section, .page-head { padding: .5rem 0; }
  .section-orange { background: #fff; border: none; }
  .booking-layout { display: block; }
  .sticker-card { box-shadow: none; border-width: 2px; margin-bottom: 1rem; page-break-inside: avoid; }
  .quote-total { background: #fff; color: #14100A; border: 2px solid var(--black); }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  html { scroll-behavior: auto; }
}

/* ================= flash messages ================= */
.flash-wrap { max-width: 1080px; margin: 1rem auto 0; padding: 0 1.4rem; }
.flash {
  border: 3px solid var(--black); border-radius: 12px; box-shadow: var(--shadow-sm);
  padding: .7rem 1rem; margin-bottom: .6rem; font-weight: 700; background: var(--white);
}
.flash-success { background: #B7F3B0; }
.flash-error { background: #FFC2C2; }
.flash-info { background: #CFE3FF; }
.coin-chip { font-weight: 900 !important; color: var(--orange) !important; }

/* ================= auth ================= */
.auth-section { max-width: 520px; }
.auth-card { padding: 2rem 1.8rem; }
.auth-title { font-size: 1.8rem; }
.auth-sub { font-weight: 500; opacity: .8; margin-bottom: 1.4rem; }
.auth-sent { font-weight: 700; margin-bottom: 1rem; }
.auth-fine, .auth-debug { font-size: .8rem; opacity: .7; margin-top: .8rem; }
.auth-debug {
  background: #FFE9A8; border: 2px dashed var(--black); border-radius: 10px;
  padding: .6rem .8rem; font-weight: 700; opacity: 1;
}
.auth-error { color: var(--red); font-weight: 900; margin-top: .8rem; }
.linkish { background: none; border: none; text-decoration: underline; cursor: pointer;
  font: inherit; color: var(--red); font-weight: 700; padding: 0; }
.phone-input { display: flex; align-items: stretch; }
.phone-prefix {
  display: flex; align-items: center; padding: 0 .8rem; font-weight: 900;
  background: var(--cream); border: 3px solid var(--black); border-right: none;
  border-radius: 10px 0 0 10px; white-space: nowrap;
}
.phone-input input { border-radius: 0 10px 10px 0 !important; }
.otp-input {
  font-family: var(--display); font-size: 1.8rem !important; letter-spacing: .5rem;
  text-align: center;
}

/* ================= ride app (Grab-style) ================= */
/* The booking screen is a full-viewport app: nav on top, map filling the rest.
   Laid out with flex rather than `100vh - <nav height>`, because the nav's
   height changes with the logo size and any hardcoded guess leaves the page
   either scrolling by a few pixels or clipping the bottom of the map. */
body.app-mode {
  display: flex; flex-direction: column;
  height: 100vh; height: 100dvh;
  overflow: hidden;
}
body.app-mode .nav, body.app-mode .flash-wrap { flex: none; }
body.app-mode .footer { display: none; }
body.app-mode #main { flex: 1; min-height: 0; display: flex; }
.ride-app { position: relative; flex: 1; min-height: 0; overflow: hidden; }
.ride-map { position: absolute; inset: 0; z-index: 1; }
/* Warm the neutral basemap very slightly towards the cream brand palette, and
   pull back saturation so the orange route is the brightest thing on screen. */
.ride-map .leaflet-tile-pane, .track-map .leaflet-tile-pane {
  filter: sepia(.08) saturate(.95);
}
.leaflet-container { background: var(--cream); }
.leaflet-control-attribution { font-size: .62rem; opacity: .75; }
.map-fallback { background: var(--cream); display: flex; align-items: center; justify-content: center; }
.map-fallback-msg { font-weight: 700; max-width: 30ch; text-align: center; opacity: .7; }
.map-float { position: absolute; right: 1rem; bottom: 45vh; z-index: 5; }
.round-btn {
  width: 46px; height: 46px; border-radius: 50%; font-size: 1.4rem;
  background: var(--white); border: 3px solid var(--black); box-shadow: var(--shadow-sm);
  cursor: pointer;
}

.sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 10;
  background: var(--white);
  border-top: 4px solid var(--black);
  border-radius: 26px 26px 0 0;
  padding: 1rem 1.3rem calc(1.3rem + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 0 rgba(20, 20, 20, .12);
  max-height: 88%; overflow-y: auto;
}
.sheet-tall { max-height: 92%; }
/* The address step is content-sized like every other sheet. It used to pin
   top:0 as well, which stretched a mostly-empty white panel over the whole
   map even though it only holds two fields and a button. */
.sheet-search { max-height: 78%; }
.sheet-grip {
  width: 48px; height: 5px; border-radius: 3px; background: var(--black);
  opacity: .25; margin: 0 auto .9rem;
}
.sheet-title { font-size: 1.35rem; margin-bottom: .8rem; }
.sheet-head { display: flex; align-items: flex-start; gap: .8rem; margin-bottom: 1rem; }
.sheet-head .sheet-title { margin-bottom: .2rem; }
.back-btn {
  width: 40px; height: 40px; flex: none; font-size: 1.3rem; cursor: pointer;
  background: var(--cream); border: 3px solid var(--black); border-radius: 12px;
}
.sheet-note { margin-top: 1rem; font-weight: 700; font-size: .85rem; opacity: .8; }
.trip-summary { font-size: .82rem; font-weight: 700; opacity: .7; margin: 0; }

.search-trigger {
  display: flex; align-items: center; gap: .8rem; width: 100%;
  background: var(--cream); border: 3px solid var(--black); border-radius: 14px;
  padding: 1rem; font: inherit; font-weight: 700; cursor: pointer; text-align: left;
  box-shadow: var(--shadow-sm);
}
.quick-row { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .8rem; }

.dot { width: 12px; height: 12px; border-radius: 50%; flex: none; border: 2px solid var(--black); }
.dot-pickup { background: var(--orange); }
.dot-dest { background: var(--red); }
.addr-block { background: var(--cream); border: 3px solid var(--black); border-radius: 14px; padding: .5rem .8rem; }
.addr-row { display: flex; align-items: center; gap: .7rem; padding: .35rem 0; }
.addr-row.static { font-weight: 700; font-size: .9rem; }
.addr-row input {
  flex: 1; border: none; background: transparent; font: inherit; font-weight: 700;
  padding: .45rem 0; outline: none; min-width: 0;
}
.addr-line { height: 18px; border-left: 3px dotted var(--black); margin-left: 5px; opacity: .4; }
.mini-btn {
  background: var(--white); border: 2px solid var(--black); border-radius: 8px;
  width: 32px; height: 32px; cursor: pointer; flex: none;
}
.ac-list {
  list-style: none; margin: .8rem 0 0; padding: 0;
  max-height: 32vh; overflow-y: auto;
}
.ac-list:empty { margin: 0; }
.ac-list li {
  display: flex; align-items: center; gap: .7rem;
  padding: .75rem .4rem; font-weight: 600; font-size: .9rem; cursor: pointer;
  border-bottom: 2px dashed rgba(20, 20, 20, .12);
}
.ac-list li:hover { background: var(--cream); }
.ac-icon { flex: none; }
.airport-chips { display: flex; gap: .5rem; flex-wrap: wrap; margin: .9rem 0; }

/* "Use my current location" — the first thing offered on the address step */
.locate-row {
  display: flex; align-items: center; gap: .9rem; width: 100%; text-align: left;
  margin-top: .9rem; padding: .8rem 1rem; font: inherit; cursor: pointer;
  background: var(--white); border: 3px solid var(--black); border-radius: 14px;
  box-shadow: var(--shadow-sm);
}
.locate-row:hover { background: var(--cream); }
.locate-row:active { transform: translate(2px, 2px); box-shadow: none; }
.locate-row:disabled { cursor: progress; opacity: .8; }
.locate-icon {
  width: 38px; height: 38px; flex: none; border-radius: 50%;
  background: var(--orange); border: 3px solid var(--black);
  display: flex; align-items: center; justify-content: center; font-size: 1.15rem;
}
.locate-row.locating .locate-icon { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.locate-body { display: flex; flex-direction: column; min-width: 0; }
.locate-body strong { font-family: var(--display); font-size: .92rem; }
.locate-body span { font-size: .76rem; opacity: .7; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.area-note {
  font-size: .76rem; font-weight: 700; opacity: .65; text-align: center;
  margin: .8rem 0 .4rem;
}

.service-list { display: flex; flex-direction: column; gap: .6rem; }
.service-row {
  display: flex; align-items: center; gap: .9rem; width: 100%; text-align: left;
  background: var(--white); border: 3px solid var(--black); border-radius: 14px;
  padding: .8rem 1rem; font: inherit; cursor: pointer;
}
.service-row.selected { background: var(--orange); box-shadow: var(--shadow-sm); }
.service-icon { font-size: 1.6rem; flex: none; }
.service-main { flex: 1; display: flex; flex-direction: column; }
.service-name { font-family: var(--display); font-size: .98rem; }
.service-desc { font-size: .78rem; opacity: .7; }
.service-price { font-family: var(--display); font-size: 1.05rem; white-space: nowrap; }
.hourly-cta { margin: .8rem 0; padding-top: .8rem; border-top: 2px dashed rgba(20,20,20,.2); }

.seg-row { display: flex; gap: .6rem; margin-bottom: 1rem; }
.seg {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: .1rem;
  font-family: var(--display); font-size: .9rem; padding: .7rem;
  background: var(--cream); border: 3px solid var(--black); border-radius: 12px; cursor: pointer;
}
.seg span { font-family: var(--body); font-weight: 600; font-size: .72rem; opacity: .7; }
.seg.active { background: var(--orange); box-shadow: var(--shadow-sm); }

.opt-row { padding: .8rem 0; border-bottom: 2px dashed rgba(20,20,20,.15); }
.switch-label { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  font-weight: 900; font-size: .92rem; cursor: pointer; }
.switch { width: 46px; height: 26px; appearance: none; cursor: pointer; flex: none;
  background: var(--cream); border: 3px solid var(--black); border-radius: 999px; position: relative; }
.switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--black); transition: transform .12s; }
.switch:checked { background: var(--orange); }
.switch:checked::after { transform: translateX(20px); }
.opt-note { font-size: .76rem; opacity: .7; margin: .3rem 0 0; }
.opt-locked { opacity: .75; }
.coin-bal { font-weight: 700; font-size: .76rem; opacity: .65; }
.mini-link { font-size: .8rem; font-weight: 700; color: var(--red); }
.driver-choose-btn {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  background: none; border: none; font: inherit; font-weight: 900; font-size: .92rem;
  cursor: pointer; padding: 0; text-align: left;
}
.driver-choice { font-weight: 700; font-size: .82rem; opacity: .7; }

.confirm-route { background: var(--cream); border: 3px solid var(--black); border-radius: 14px;
  padding: .7rem 1rem; margin-bottom: .9rem; }
.confirm-meta { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .9rem; }
.meta-chip { font-size: .74rem; font-weight: 900; background: var(--cream);
  border: 2px solid var(--black); border-radius: 999px; padding: .2rem .6rem; }
.fare-lines { margin-bottom: .5rem; }
.fare-line { display: flex; justify-content: space-between; gap: 1rem; padding: .4rem 0;
  border-bottom: 2px dashed rgba(20,20,20,.15); font-weight: 700; font-size: .88rem; }
.coin-line { color: #1a7a43; }
.fare-total { display: flex; justify-content: space-between; align-items: center;
  font-family: var(--display); font-size: 1.2rem; background: var(--black); color: var(--orange);
  border-radius: 10px; padding: .6rem .9rem; margin: .6rem 0; }
.loading-line { font-weight: 700; opacity: .6; animation: pulse .8s ease-in-out infinite; }

.overlay { position: fixed; inset: 0; z-index: 1500; background: rgba(20,20,20,.6);
  display: flex; align-items: flex-end; }
.overlay-card { background: var(--white); border-top: 4px solid var(--black);
  border-radius: 24px 24px 0 0; padding: 1.2rem; width: 100%; max-height: 88%; overflow-y: auto; }
.tag-f { font-size: .66rem; font-weight: 900; background: #F8BBD0; border: 2px solid var(--black);
  border-radius: 999px; padding: .05rem .45rem; white-space: nowrap; }
.dp-duty { font-size: .7rem; font-weight: 900; color: #1a7a43; margin-top: .2rem; }
.avatar-lg { width: 84px; height: 84px; font-size: 1.7rem; object-fit: cover; }
img.avatar { object-fit: cover; }

/* ================= trips / payment ================= */
.narrow { max-width: 620px; }
.center-card { text-align: center; }
.mono { font-family: ui-monospace, monospace; font-size: .78rem; word-break: break-all; }
.driver-banner { display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap; }
/* Reaching the driver is the most urgent action on a live trip — full-size
   targets, and the office number beside it in case the driver does not answer. */
.contact-row { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: .7rem; }
.contact-row .btn { flex: 1 1 auto; min-height: 48px; display: flex;
  align-items: center; justify-content: center; }
.driver-banner h3 { margin-bottom: .2rem; }
.pay-banner { border-color: var(--red); background: #FFF0F0; text-align: center; }
.pay-amount { display: flex; justify-content: space-between; align-items: baseline;
  font-family: var(--display); font-size: 1.1rem; padding-bottom: .8rem;
  border-bottom: 3px solid var(--black); margin-bottom: 1rem; }
.pay-amount strong { font-size: 2rem; color: var(--red); }
.pay-methods { display: flex; flex-direction: column; gap: .6rem; margin-bottom: 1.2rem; }
.pay-method { display: flex; align-items: center; gap: .8rem; cursor: pointer;
  border: 3px solid var(--black); border-radius: 12px; padding: .8rem 1rem; background: var(--white); }
.pay-method:has(input:checked) { background: var(--orange); box-shadow: var(--shadow-sm); }
.pm-icon { font-size: 1.5rem; }
.pm-body { display: flex; flex-direction: column; }
.pm-body span { font-size: .78rem; opacity: .75; }
.demo-note, .transfer-details { margin-top: 1.4rem; padding-top: 1rem;
  border-top: 3px dashed rgba(20,20,20,.25); font-size: .86rem; }
.demo-note { background: #FFE9A8; border: 2px dashed var(--black); border-radius: 10px;
  padding: .8rem; margin-top: 1.2rem; }
.paid-tag { color: #1a7a43; font-weight: 900; }
.unpaid-tag { color: var(--red); font-weight: 900; font-size: .8rem; }
.refund-tag { color: #1565C0; font-weight: 900; font-size: .85rem; margin: .5rem 0 0; }
.booked-banner {
  display: flex; align-items: center; gap: 1rem;
  max-width: 1080px; margin: 1.2rem auto 0;
  background: #B7F3B0; border: 4px solid var(--black); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 1rem 1.3rem;
}
.booked-banner strong { display: block; font-family: var(--display); font-size: 1.05rem; }
.booked-banner span:last-child { font-size: .85rem; font-weight: 600; }
.booked-tick {
  width: 44px; height: 44px; flex: none; border-radius: 50%;
  background: var(--black); color: #B7F3B0;
  display: flex; align-items: center; justify-content: center; font-size: 1.5rem;
}
@media (max-width: 760px) { .booked-banner { margin: 1rem .8rem 0; } }
.rate-nudge { font-size: .76rem; font-weight: 900; color: var(--red); }
.review-quote { font-style: italic; opacity: .8; }
.coin-balance { font-family: var(--display); font-size: 3rem; color: var(--orange);
  text-align: center; text-shadow: 3px 3px 0 var(--black); }
.coin-plus { color: #1a7a43; } .coin-minus { color: var(--red); }
.verified-tag { color: #1a7a43; font-weight: 900; }
.place-list { list-style: none; padding: 0; margin: 0; font-size: .88rem; }
.place-list li { padding: .4rem 0; border-bottom: 2px dashed rgba(20,20,20,.15); }
.tip-row { display: flex; gap: .5rem; flex-wrap: wrap; }
.tip-chip { border: 3px solid var(--black); border-radius: 999px; padding: .4rem .9rem;
  font-weight: 900; cursor: pointer; background: var(--cream); font-size: .85rem; }
.tip-chip:has(input:checked) { background: var(--orange); }
.tip-chip input, .star-btn input { position: absolute; opacity: 0; width: 0; height: 0; }
.star-btn:has(input:checked) { background: var(--orange); color: var(--ink); }
.field textarea { width: 100%; padding: .6rem .8rem; font-family: var(--body); font-size: 1rem;
  border: 3px solid var(--black); border-radius: 10px; }

/* ---------- responsive ---------- */
@media (max-width: 900px) {
  .hero-inner { grid-template-columns: 1fr; text-align: center; }
  .hero-sub { margin: 0 auto; }
  .hero-cta { justify-content: center; }
  .hero-badge { order: -1; }
  .hero-pin { max-width: 160px; }
  .booking-layout { grid-template-columns: 1fr; }
  .booking-side { position: static; }
  .footer-inner { grid-template-columns: 1fr; text-align: center; }
  .footer-contact { text-align: center; }
}
/* Collapse to the hamburger early: the bilingual links ("Rates 費率",
   "Drivers 司機") are wide, and they wrapped onto two lines well before the
   old 760px breakpoint. */
@media (max-width: 1000px) {
  .nav { flex-wrap: wrap; }
  .nav-toggle { display: block; }
  .nav-links {
    display: none; width: 100%;
    flex-direction: column; align-items: flex-start; gap: .9rem; padding: .8rem 0 .4rem;
  }
  .nav-links.open { display: flex; }
}

@media (max-width: 760px) {
  .nav { flex-wrap: wrap; }
  .nav-toggle { display: block; }
  .nav-links {
    display: none; width: 100%;
    flex-direction: column; align-items: flex-start; gap: .9rem; padding: .8rem 0 .4rem;
  }
  .nav-links.open { display: flex; }
  .field-row { grid-template-columns: 1fr; }
  .service-tabs { grid-template-columns: 1fr; }
  .booking-row { flex-direction: column; align-items: flex-start; }
  .booking-row-side { flex-direction: row; align-items: center; }
  /* prevent iOS zoom-on-focus: inputs must be >=16px */
  .field input, .field select, .geo-row input { font-size: 16px; }
  /* No `#map` height here either — the booking map is absolutely positioned by
     .ride-map and an id selector outranks it, collapsing it to a small box. */
  .driver-pick-grid { grid-template-columns: repeat(2, 1fr); }
  .section { padding: 2.4rem 1rem; }
  .sticker-card { padding: 1.2rem 1.1rem; }
  .bar-chart { gap: 3px; }
  .bar-label { font-size: .55rem; }
  .kpi-num { font-size: 1.5rem; }
  .actions-cell .btn-small, .ops-table select { font-size: .82rem; padding: .4rem .7rem; }
}

/* notched phones: keep the fare bar above the home indicator */
.mobile-fare-bar { padding-bottom: calc(.6rem + env(safe-area-inset-bottom)); }


/* ---- palette overrides (must stay LAST so they win) -----------------------
   Rule: light surfaces take --ink; every dark surface takes white. --------- */
body, .sticker-card, .booking-row, .faq, .detail-list, .rate-list, .ac-list li,
.service-row, .driver-pick, .seg, .chip, .locate-row, .field input,
.field select, .field textarea, .addr-row input, .pill {
  color: var(--ink);
}
body { background: var(--cream); }
.sticker-card, .service-row, .driver-pick, .seg, .locate-row, .chip,
.addr-block, .confirm-route, .sheet, .overlay-card, .ac-list, .btn {
  border: 1px solid var(--line);
}
.sticker-card { background: var(--white); box-shadow: var(--shadow); }
.field input, .field select, .field textarea, .addr-row input, .phone-prefix {
  background: var(--white); border: 1px solid var(--line); color: var(--ink);
}

/* Dark surfaces — white text, no exceptions. */
.nav, .footer, .hero, .section-dark, .btn-dark, .quote-total, .fare-total,
.badge-247, .drv-header { background: var(--blue-deep); color: var(--on-dark); }
.nav a, .nav-links a:not(.btn), .footer a, .hero, .hero h1, .section-dark,
.section-dark .section-title, .btn-dark, .quote-total, .fare-total,
.badge-247, .badge-247 .num { color: var(--on-dark); }
.footer-contact a:not(.btn) { color: #8FB6DC; }
.hero-sub, .footer-note { color: rgba(255,255,255,.82); }

/* The accent: dark blue fill always carries white text. */
.btn-orange, .service-tab.active, .seg.active, .service-row.selected,
.driver-pick.selected, .faq[open] summary, .step-num, .how-num, .locate-icon,
.tip-chip:has(input:checked), .star-btn:has(input:checked) {
  background: var(--blue); color: var(--on-dark); border-color: var(--blue);
}
.btn-orange:hover, .seg.active:hover { background: var(--blue-dark); }
.btn { background: var(--white); color: var(--ink); box-shadow: var(--shadow-sm); }
.btn:hover { transform: none; box-shadow: 0 2px 10px rgba(0,0,0,.16); }
.btn-whatsapp { background: #1CA05A; color: var(--on-dark); border-color: #1CA05A; }
.hero .btn, .section-dark .btn, .footer .btn { box-shadow: none; }
.btn-ghost { background: transparent; color: var(--on-dark);
  border: 1px solid rgba(255,255,255,.55); }

/* Section bands are a pale wash now, so their text goes back to dark. */
.section-orange { background: var(--blue-tint); background-image: none;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.section-orange > .section-title, .section-orange > .section-sub,
.section-sub.dark, .section-orange .red { color: var(--ink); }
.section-orange .sticker-card .red { color: var(--red); }
.section-orange > .section-title .zh-sub { color: var(--ink-dim); }

.muted, .quote-fine, .opt-note, .driver-langs, .ac-msg, .zh-line,
.faq-zh, .card-zh, .ops-hint { color: var(--ink-dim); }
.orange, .coin-chip { color: var(--blue) !important; }
.nav .coin-chip { color: #8FB6DC !important; }
.how-num, .step-num { border: none; }
.avatar-any { background: repeating-linear-gradient(45deg, var(--blue) 0 10px, #2C5C8A 10px 20px); }


/* ---- dark-surface text, FINAL WORD --------------------------------------
   --ink and --blue are both dark, so on a dark panel they vanish. Every
   descendant of a dark surface takes white; accent text there takes the light
   blue below. Nothing after this block may set colour on these areas. ------ */
:root { --accent-on-dark: #7FB3E8; }

.nav, .nav *,
.hero, .hero *,
.footer, .footer *,
.section-dark, .section-dark *,
.quote-total, .quote-total *,
.fare-total, .fare-total *,
.badge-247, .badge-247 *,
.btn-dark, .btn-dark * {
  color: var(--on-dark);
}

/* accent text on dark = light blue, never the dark accent */
.nav .brand-main .zh, .nav .brand-sub, .nav .coin-chip,
.hero .orange, .hero-zh, .hero-kicker,
.footer-brand, .footer-contact a:not(.btn),
.section-dark .section-title .zh-sub,
.quote-total span:last-child, .fare-total span:last-child,
.badge-247 .slash {
  color: var(--accent-on-dark);
}

/* buttons sitting on dark keep their own foreground */
.nav .btn-orange, .hero .btn-orange, .footer .btn-orange,
.section-dark .btn-orange { color: var(--on-dark); }
.nav .btn-whatsapp, .footer .btn-whatsapp { color: var(--on-dark); }
.hero-kicker { background: rgba(255,255,255,.10); border-color: rgba(255,255,255,.45); }
.badge-247 { background: var(--blue); border-color: rgba(255,255,255,.5); }
.badge-247 .num, .badge-247 .hours, .badge-247 .days { color: var(--on-dark); }

/* pale cards sitting inside a dark section keep dark text */
.section-dark .sticker-card, .section-dark .sticker-card *,
.hero .sticker-card, .hero .sticker-card * { color: var(--ink); }
.section-dark .sticker-card .red { color: var(--red); }


/* ---- button fills, FINAL --------------------------------------------------
   `.btn` and `.btn-orange` have equal specificity, so whichever came last won
   — which made the primary CTA white-on-white. These are the last word. ---- */
.btn-orange, a.btn-orange, button.btn-orange,
.nav .btn-orange, .hero .btn-orange, .footer .btn-orange, .section-dark .btn-orange {
  background: var(--blue) !important;
  color: #FFFFFF !important;
  border: 1px solid var(--blue) !important;
}
.btn-orange:hover { background: var(--blue-dark) !important; border-color: var(--blue-dark) !important; }
.btn-whatsapp, .nav .btn-whatsapp, .footer .btn-whatsapp {
  background: #1CA05A !important; color: #FFFFFF !important; border-color: #1CA05A !important;
}
.btn-dark { background: #071B30 !important; color: #FFFFFF !important; }

/* "Home safe," sat dark-blue on navy. Accent text on the hero must be light. */
.hero .orange, .hero h1 .orange, .hero-zh { color: #8FC4F2 !important; }
.hero-kicker { color: #8FC4F2 !important; }


/* ---- language toggle ------------------------------------------------------
   One language at a time. The <html> element carries lang-en / lang-zh from the
   server, so the correct language is right on the FIRST paint — a JS-only
   toggle flashes the wrong one. Legacy zh-* classes are treated as Chinese. -- */
.l-en, .l-zh { }
html.lang-en .l-zh, html.lang-en .zh-sub, html.lang-en .zh-line,
html.lang-en .faq-zh, html.lang-en .card-zh, html.lang-en .zh { display: none !important; }
html.lang-zh .l-en { display: none !important; }
/* Chinese runs on their own line inside cards look wrong once English is gone */
html.lang-zh .zh-sub { display: inline !important; font-size: 1em; opacity: 1; }
html.lang-zh .card-zh, html.lang-zh .faq-zh { color: inherit; }

.lang-toggle {
  display: inline-flex; align-items: center; gap: 0; flex: none;
  border: 1px solid rgba(255,255,255,.45); border-radius: 999px; overflow: hidden;
}
.lang-toggle a {
  padding: .25rem .7rem; font-size: .78rem; font-weight: 900;
  text-decoration: none; color: #FFFFFF !important; line-height: 1.6;
}
.lang-toggle a.on { background: #FFFFFF; color: var(--blue) !important; }
.hero-safety {
  margin-top: 1.1rem; padding: .7rem 1rem; border-radius: 12px;
  background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.28);
  font-size: .86rem; font-weight: 700; color: #FFFFFF;
}


/* --------------------------------------------------------------------------
   Introducer portal (/intro/) and the driver profile form.
   Appended last on purpose: everything above may be overridden by these, and
   nothing here is allowed to be beaten by an earlier rule.
   -------------------------------------------------------------------------- */

body.intro-app {
  background: var(--cream); color: var(--ink);
  padding-bottom: 5.5rem;                /* clear the fixed tab bar */
}
.intro-header {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  flex-wrap: wrap;
  padding: .7rem 1rem; background: var(--blue-deep); color: var(--on-dark);
}
.intro-brand { display: flex; align-items: center; gap: .6rem; text-decoration: none;
  color: var(--on-dark); font-weight: 900; }
.intro-brand em { font-style: normal; opacity: .75; font-size: .78rem; letter-spacing: .06em; }
.intro-head-right { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }
.intro-name { font-size: .82rem; font-weight: 700; opacity: .9; }
.intro-main { max-width: 720px; margin: 0 auto; padding: 1.1rem 1rem 0; }
.intro-flash-wrap { max-width: 720px; margin: .8rem auto 0; padding: 0 1rem; }

.intro-hero-card {
  background: var(--blue); color: var(--on-dark);
  border-radius: 18px; padding: 1.3rem 1.2rem; margin-bottom: 1.1rem;
  box-shadow: var(--shadow);
}
.intro-hero-label { display: block; font-size: .82rem; opacity: .85; font-weight: 700; }
.intro-hero-amount {
  display: block; font-size: 2.5rem; font-weight: 900; line-height: 1.15;
  margin: .25rem 0 .9rem; word-break: break-word;
}
.intro-hero-split { display: flex; gap: 1rem; flex-wrap: wrap; }
.intro-hero-split > div {
  flex: 1 1 45%; min-width: 8rem;
  border-top: 1px solid rgba(255,255,255,.28); padding-top: .55rem;
  font-size: .78rem; opacity: .95;
}
.intro-hero-split strong { display: block; font-size: 1.15rem; margin-top: .15rem; }

.intro-stat-row { display: flex; gap: .8rem; margin-bottom: 1.1rem; flex-wrap: wrap; }
.intro-stat {
  flex: 1 1 8rem; background: var(--white); border: 1px solid var(--line);
  border-radius: 14px; padding: .9rem 1rem; text-align: center;
}
.intro-stat strong { display: block; font-size: 1.8rem; font-weight: 900; color: var(--blue); }
.intro-stat span { font-size: .78rem; color: var(--ink-dim); }

.intro-link-box { display: flex; gap: .5rem; margin: .6rem 0; flex-wrap: wrap; }
.intro-link-box input {
  flex: 1 1 12rem; min-width: 0;         /* min-width:0 or the flex item overflows */
  padding: .7rem .8rem; border: 1px solid var(--line); border-radius: 10px;
  font-size: .86rem; background: var(--white); color: var(--ink);
}
.intro-link-box .btn { flex: 0 0 auto; }
.intro-link-box .btn.copied::after { content: ' ✓'; }
.intro-code-line { font-size: .86rem; color: var(--ink-dim); margin: .5rem 0 .8rem; }
.intro-code-line code {
  background: var(--blue-tint); color: var(--blue); padding: .15rem .5rem;
  border-radius: 6px; font-weight: 900; letter-spacing: .06em;
}
.intro-login-card {
  background: var(--white); border: 1px solid var(--line); border-radius: 18px;
  padding: 1.4rem 1.2rem; box-shadow: var(--shadow); margin-top: 1.5rem;
}
.intro-error {
  margin-top: .8rem; padding: .6rem .8rem; border-radius: 10px;
  background: #FDECEC; color: #8A1C1C; font-size: .86rem; font-weight: 700;
}

/* --- driver profile form --- */
.drv-doc-grid { display: flex; gap: .9rem; flex-wrap: wrap; }
.drv-doc-grid > .field { flex: 1 1 12rem; min-width: 0; }
.drv-doc-thumb { display: block; margin: .4rem 0; }
.drv-doc-thumb img {
  width: 100%; max-width: 260px; border-radius: 10px; border: 1px solid var(--line);
  display: block;
}
.drv-field-row { display: flex; gap: .9rem; flex-wrap: wrap; }
.drv-field-row > .field { flex: 1 1 8rem; min-width: 0; }
.drv-readonly-row {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .55rem 0; border-bottom: 1px solid var(--line); font-size: .9rem;
}
.drv-readonly-row:last-of-type { border-bottom: 0; }
.drv-head-right { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }

/* --- customer profile additions --- */
.pending-tag {
  padding: .6rem .8rem; border-radius: 10px; background: #FFF6E3;
  border: 1px solid #F0D9A8; color: #6B4E10; font-size: .88rem; font-weight: 700;
}
.check-row { display: flex; align-items: flex-start; gap: .6rem; margin: .8rem 0 1rem;
  font-size: .9rem; cursor: pointer; }
.check-row input { margin-top: .2rem; flex: none; }
.place-list li {
  display: flex; align-items: center; justify-content: space-between; gap: .8rem;
}
.place-list form { flex: none; margin: 0; }

/* Dead rule removed: `.drv-tabs a { font-size: .68rem }` lived here and could
   never apply. The driver app loads driver.css AFTER this file and its
   keep-last block sets `.drv-tabs a { font-size: .64rem }` unconditionally at
   the same (0,1,1) specificity; the introducer portal (which does NOT load
   driver.css) has its own `.intro-app .drv-tabs a` at (0,2,1) inside this very
   same max-width:420px band. Tab sizing therefore lives in exactly two places,
   neither of them here. */
@media (max-width: 420px) {
  .intro-hero-amount { font-size: 2.1rem; }
}


/* ==========================================================================
   FINAL VISUAL REFRESH — "Depth and elevation" + editorial hierarchy grafts.

   THIS BLOCK MUST STAY LAST IN THE FILE. In this stylesheet later rules win,
   so an override layer only works at the end. Anything appended after this
   silently beats it — if you need to add rules, add them INSIDE this block.

   What it does, in one paragraph: installs a single believable light source
   (above the page, slightly in front) and obeys it everywhere. Raised things
   get a lit top edge and a shadow falling down and away; recessed things
   (inputs, the pale band, the fare plinths) get the inverse. Shadows are
   tinted with --blue-deep rather than black, because black on a near-white
   ground reads as dirt and navy reads as air. On top of that it fixes the
   hierarchy defects the lighting cannot: h3 had NO font-size anywhere in this
   file, section headings were all centred, and the one accent was used as a
   1px edge instead of a field.

   NO HUE IS CHANGED. Every value here is the existing palette, a tint of it,
   a shade of it, or an alpha of it.

   Hard rules honoured (all previously regressed at least once):
   1. No `display` declaration on .overlay, .l-en, .l-zh or anything that can
      carry [hidden]. The only `display` declarations below are on the
      introducer tab bar (a server-rendered <nav> inside {% if introducer %})
      and on ::before/::after pseudo-elements, none of which can hold the
      attribute. `[hidden] { display: none !important }` outranks all of them
      regardless, since an !important author declaration beats any normal one.
   2. `body { word-break: keep-all }` untouched — body only gets a
      background-COLOR here, never the shorthand.
   3. No `#map` rule. Do not add one: it outranks .ride-map and collapses the
      full-screen booking map.
   4. The html.lang-* !important rules at the end of the previous block are
      untouched and still win.
   5. Contrast: every text-on-fill pair below was measured, not estimated.
      Where a gradient carries text the WORST stop is the one quoted.
   6. `* + .section-title { margin-top: 2.8rem }` still does its job — only the
      alignment changes below, never the margin behaviour.
   7. Shadows and insets create no layout, so nothing here can scroll the page
      sideways at 360px. .table-scroll is restated to make that explicit.
   ========================================================================== */

:root {
  /* Shadow key colours as raw channels so every layer shares one source. */
  --sh: 7, 27, 48;              /* --blue-deep */
  --sh-accent: 18, 57, 95;      /* --blue */

  /* The elevation ladder. Each step is THREE layers: a tight contact line so
     the edge stays crisp, a near shadow, and a wide soft cast. A single
     `0 2px 8px` blur cannot be crisp and soft at once — which is exactly why
     the page looked flat despite having a shadow on every card. */
  --elev-1: 0 1px 1px rgba(var(--sh), .05), 0 2px 5px -1px rgba(var(--sh), .07);
  --elev-2: 0 1px 2px rgba(var(--sh), .06), 0 6px 14px -4px rgba(var(--sh), .13);
  --elev-3: 0 2px 4px rgba(var(--sh), .07), 0 14px 28px -8px rgba(var(--sh), .18);
  /* Ambient brand glow — only ever under a blue object, so the accent bleeds
     into the page instead of stopping dead at the button's border. */
  --elev-accent: 0 1px 2px rgba(var(--sh-accent), .30), 0 6px 16px -5px rgba(var(--sh-accent), .38);
  --elev-accent-hi: 0 2px 4px rgba(var(--sh-accent), .32), 0 12px 24px -6px rgba(var(--sh-accent), .46);

  /* 1px inner highlights: the light catching the top edge of a raised thing. */
  --hi: inset 0 1px 0 rgba(255,255,255,.85);        /* on a tinted light fill */
  --hi-dark: inset 0 1px 0 rgba(255,255,255,.16);   /* on navy */
  /* Pressed = the surface drops below the page, so the shadow moves inside. */
  --press: inset 0 2px 4px rgba(var(--sh), .20);
  --press-dark: inset 0 2px 5px rgba(0,0,0,.45);

  /* Surfaces. A ~2% vertical fall: enough that white stops being dead flat,
     small enough that nobody can point at the gradient and name it. */
  --surface: linear-gradient(180deg, #FFFFFF 0%, #F9FBFC 100%);
  --surface-tint: linear-gradient(180deg, #F4F8FC 0%, #E9EFF6 100%);
  --surface-accent: linear-gradient(180deg, #1A4A78 0%, #12395F 58%, #0F3255 100%);
  --surface-accent-hov: linear-gradient(180deg, #164166 0%, #0B2743 60%, #09223A 100%);
  --surface-deep: linear-gradient(180deg, #0A2540 0%, #071B30 65%);
  --surface-sunk: linear-gradient(180deg, #E1E9F2 0%, #E8EEF5 26%, #EFF3F8 100%);

  /* Hairlines cooled a step so they sit with a navy shadow, not a grey one. */
  --line-cool: #DFE5EC;
  --line-cool-2: #CBD6E2;
  /* Form-control borders are held to a HIGHER bar than card hairlines:
     WCAG 1.4.11 asks 3:1 for the visual boundary of a UI component, and a
     card's boundary is carried by its shadow while an input's is not.
     #858F9C is 3.28:1 on white — measured, not guessed. */
  --field-line: #858F9C;
  --ground: #F4F6F9;
  /* The caption grey that clears 4.5:1 on every light surface we own, so
     secondary copy can stop relying on `opacity` to look secondary. */
  --ink-soft: #5A6472;

  /* One size for card-level headings. h3 had NO font-size in this file, so
     every card title, FAQ heading and driver name was rendering at the
     browser default. This is the first size it has ever had. */
  --t-card: clamp(1.12rem, 1.03rem + .42vw, 1.32rem);

  --ease: cubic-bezier(.2, .7, .3, 1);
}

/* ---------- page ground ---------------------------------------------------
   background-COLOR only: the shorthand on body would be the kind of edit that
   later takes word-break's neighbours with it. A hair cooler than #F7F7F7 so
   a navy shadow lands on it correctly and white cards separate properly. */
body { background-color: var(--ground); }

/* ---------- type ----------------------------------------------------------
   The single highest-leverage line in this refresh. */
h3 { font-size: var(--t-card); }
.sticker-card p, .faq p { max-width: 64ch; }

/* Money is the subject matter of this product and the quote card recalculates
   live (.quote-card.loading pulses while it does). With proportional figures
   the RM total jitters sideways as digits swap; tabular figures hold still and
   line the rate tables up on the decimal. */
.rate-list, .rate-table, .quote-line, .quote-total, .fare-line, .fare-total,
.kpi-num, .coin-balance, .intro-stat strong, .intro-hero-amount, .how-num,
.service-price, .pay-amount, .badge-247 .num, .detail-list dd, .otp-input,
.drv-job-time, .cover-jobs, .mono, .measure-row strong {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ---------- section rhythm ------------------------------------------------
   Editorial titles are set left with a ragged right, not centred: a centred
   heading over left-aligned cards gives the eye no shared edge to land on.
   The measure cap forces the two-line rag that gives a heading its shape.
   NB the auto-numbered "01 / 02 / 03" marker that came with this idea is
   deliberately NOT here — it numbered every section on every page, so My
   Trips got "01 Active / 02 Your trips" and Rates got a lone orphan "01". */
.section > .section-title { text-align: left; max-width: 24ch; }
.section > .section-sub { text-align: left; margin: -.4rem 0 2rem; max-width: 62ch; }
.section:not(.section-dark):not(.section-orange) > .section-sub {
  color: var(--ink-soft); opacity: 1;
}
/* A centred CTA orphaned under a left-set heading reads as a mistake. Make it
   a deliberate section-closing rule instead. */
.section > .center { margin-top: 2.6rem; padding-top: 1.8rem; border-top: 1px solid var(--line-cool); }
.section-dark > .center { border-top-color: rgba(255,255,255,.28); }

/* ---------- nav: a bar floating above the page ---------------------------- */
/* `background-image` only — the colour comes from the override at ~966 and
   must survive. The 4px navy-on-navy bottom border (1.5:1, i.e. invisible) is
   replaced by a lit edge plus a real cast, which is what actually says "this
   is above the content". */
.nav {
  background-image: var(--surface-deep);
  border-bottom: 1px solid rgba(255,255,255,.10);
  box-shadow: 0 1px 0 rgba(var(--sh), .55), 0 6px 18px -6px rgba(var(--sh), .50);
}
/* The hover indicator was --orange, i.e. navy on navy — no visible change. */
.nav-links a:not(.btn):hover { border-bottom-color: var(--accent-on-dark); }
/* The hamburger is the ONLY navigation below 1000px and was a bare glyph with
   no box. Give it a real 44px target, not just a hover tint. */
.nav-toggle {
  width: 44px; height: 44px; padding: 0; line-height: 1;
  border-radius: 10px; transition: background-color .15s var(--ease);
}
.nav-toggle:hover { background: rgba(255,255,255,.10); }
.lang-toggle { box-shadow: var(--hi-dark), 0 1px 2px rgba(0,0,0,.35); }
.lang-toggle a.on { box-shadow: 0 1px 2px rgba(0,0,0,.30); }
/* The skip link was --orange on --black: navy on navy at 1.5:1, unreadable the
   moment it appeared. It is the first thing a keyboard user meets. */
.skip-link { background: var(--blue); color: #FFFFFF; box-shadow: var(--elev-3); }

/* ---------- hero: depth without texture ---------------------------------- */
/* Two very low-contrast radial pools give the flat navy slab a near and a far
   corner. Deliberately weak: the hero carries body copy and a light-blue
   kicker, and every point must still clear 4.5:1. */
.hero {
  background-image:
    radial-gradient(ellipse 90% 70% at 84% -12%, rgba(29, 94, 150, .32), transparent 62%),
    radial-gradient(ellipse 120% 80% at 8% 112%, rgba(18, 57, 95, .40), transparent 70%);
  box-shadow: inset 0 -1px 0 rgba(255,255,255,.08);
}
/* The kicker was a light panel on a light glow with light-blue text. Inverting
   it to a RECESSED dark chip makes it darker than the hero everywhere, so the
   ratio can only improve — and a sunken chip is the right physics for a label.
   The 4px hard white offset and the tilt both go: a tilted object needs a
   tilted shadow, and this direction has one light source, not two. */
.hero-kicker {
  background: rgba(var(--sh), .55);
  border-color: rgba(255,255,255,.28);
  box-shadow: var(--press), inset 0 -1px 0 rgba(255,255,255,.10);
  transform: none;
}
.hero-badge .badge-247 {
  background-image: var(--surface-accent);
  border-color: rgba(255,255,255,.30);
  box-shadow: var(--hi-dark), 0 10px 26px -8px rgba(0,0,0,.65);
  transform: none;
}
/* Leftover ORANGE hard shadow under the hero logo — the last trace of the old
   palette anywhere on the page. */
.hero-badge .aidd-logo { filter: drop-shadow(0 10px 18px rgba(0,0,0,.50)); }

/* ---------- bands --------------------------------------------------------- */
/* The pale band is RECESSED — a trench cut into the page, so the light is
   occluded at its top edge and returns at the bottom. */
.section-orange {
  background-image: var(--surface-sunk);
  border-top: 1px solid var(--line-cool-2);
  border-bottom: 1px solid var(--line-cool);
  box-shadow: inset 0 9px 14px -12px rgba(var(--sh), .40),
              inset 0 -1px 0 rgba(255,255,255,.75);
}
/* Cards in the trench sit slightly prouder — the ground is darker there, so
   elev-1 would read as less lift than it does on the page. */
.section-orange .sticker-card { box-shadow: var(--elev-2); }

/* .section-dark was the SAME #071B30 as the nav, hero and footer, so the one
   feature band on the home page ("Pick your driver") read as more chrome.
   Colour-blocking it to the accent makes the brand hue a field instead of a
   1px edge, and separates the band from the furniture around it. Safe: the
   rule at ~1039 already forces .section-dark .sticker-card * to --ink, so the
   white driver cards keep their dark text. */
.section-dark {
  background-color: var(--blue);
  /* Top stop held at #143E69, not lighter: the band's own .zh-sub subheading
     inherits --accent-on-dark #7FB3E8 from the block above, and at #164A7C
     that pair measures 4.13:1. #143E69 puts it at 4.95:1. */
  background-image: linear-gradient(180deg, #143E69 0%, #12395F 60%, #10334F 100%);
  border-top: 1px solid rgba(255,255,255,.14);
  border-bottom: 1px solid rgba(255,255,255,.10);
}
/* A pale card on a dark band casts a dark shadow, not a navy one. */
.section-dark .sticker-card {
  box-shadow: 0 2px 6px rgba(0,0,0,.30), 0 14px 30px -12px rgba(0,0,0,.45);
  border-color: rgba(255,255,255,.10);
}
/* A white card's Chinese block took a white-ish top rule INSIDE a dark
   section — i.e. an invisible divider on a white card. */
.section-dark .sticker-card .card-zh { border-top-color: var(--line-cool); }
.footer {
  /* The footer is a ground plane, not a raised bar: the page casts ONTO it,
     so it is darker at the top. */
  background-image: linear-gradient(180deg, #04121F 0%, #071B30 42%);
  border-top: 1px solid rgba(255,255,255,.08);
}

/* ---------- the core card ------------------------------------------------- */
.sticker-card {
  background-image: var(--surface);
  border-color: var(--line-cool);
  box-shadow: var(--elev-1);
  transition: box-shadow .18s var(--ease), transform .18s var(--ease),
              border-color .18s var(--ease);
}
/* Only things you can actually press are allowed to move. A static
   informational card that lifts under the cursor is noise, not feedback.
   Every hover in this block is gated: without the gate a touch device keeps
   :hover stuck on whatever was last tapped, which is what the
   `@media (hover: none)` block at ~450 exists to prevent. */
@media (hover: hover) and (pointer: fine) {
  a.sticker-card:hover, .booking-row:hover {
    transform: translateY(-2px);
    box-shadow: var(--elev-3);
    border-color: var(--line-cool-2);
  }
}
a.sticker-card:active, .booking-row:active {
  transform: translateY(0);
  box-shadow: var(--elev-1);
  transition-duration: .06s;
}
.card-icon { font-size: 1.75rem; margin-bottom: .7rem; }

/* .booking-row is the entire navigation of My Trips and had no chevron, no
   arrow, no marker of any kind — a hover lift does nothing on the phone this
   is used on at 2am. Positioned absolutely so it survives the column layout
   the row switches to below 760px. A pseudo-element cannot carry [hidden]. */
.booking-row { position: relative; padding-right: 2.6rem; }
.booking-row::after {
  content: '\203A';
  position: absolute; right: 1.1rem; top: 50%;
  transform: translateY(-50%);
  font-size: 1.7rem; line-height: 1; color: var(--ink-dim);
}

/* The step discs are the only round accent objects on the page — pressed
   studs, lit on top, with the brand glow pooling beneath. */
.how-num, .step-num {
  background-image: var(--surface-accent);
  box-shadow: var(--hi-dark), var(--elev-accent);
}
/* The dashed trust strip read as an unfinished wireframe. Same tiles, real
   surface, real light. */
.trust-item {
  border: 1px solid var(--line-cool);
  background-image: var(--surface);
  box-shadow: var(--elev-1);
}
.section-dark .trust-item {
  border-color: rgba(255,255,255,.24);
  background-image: none; background-color: rgba(255,255,255,.08);
  box-shadow: var(--hi-dark);
}

/* ---------- buttons ------------------------------------------------------- */
/* Base (secondary) button: raised white, lit on top, real press. The sticker
   `translate(4px, 4px)` :active from line ~97 was never overridden and is the
   last thing in the product that still slides diagonally. */
.btn {
  background-image: var(--surface);
  border-color: var(--line-cool-2);
  box-shadow: var(--elev-1);
  transition: box-shadow .15s var(--ease), transform .12s var(--ease),
              background-color .15s var(--ease), border-color .15s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-1px); box-shadow: var(--elev-2); border-color: #BCCBDC; }
}
.btn:active {
  transform: translateY(1px);
  box-shadow: var(--press);
  background-image: none; background-color: #F1F4F8;
  transition-duration: .05s;
}
.btn:disabled, .btn[aria-disabled='true'] {
  background-image: none; background-color: #F1F3F6;
  box-shadow: none; transform: none; border-color: var(--line-cool);
}

/* PRIMARY CTA. Two things matter here and both have bitten this file before.
   (a) The block at ~1047 sets `background: var(--blue) !important`, and the
       shorthand resets background-image to `none !important` — so a gradient
       must be declared as background-IMAGE with !important, which also leaves
       the flat --blue underneath as the fallback. If this gradient is ever
       dropped the button is still blue with white text, never white-on-white.
   (b) That block is a selector LIST, and its winning member for a CTA in the
       hero is `.hero .btn-orange` at (0,2,0). A bare `.btn-orange` at (0,1,0)
       LOSES — the gradient would never appear anywhere the CTA actually
       lives, while the :hover variant at (0,2,0) would win, so the button
       would be flat at rest and gain a gradient on hover. The selector list
       below therefore mirrors ~1047 exactly. */
.btn-orange, a.btn-orange, button.btn-orange,
.nav .btn-orange, .hero .btn-orange, .footer .btn-orange, .section-dark .btn-orange {
  background-image: var(--surface-accent) !important;
  box-shadow: var(--hi-dark), var(--elev-accent);
}
@media (hover: hover) and (pointer: fine) {
  .btn-orange:hover, a.btn-orange:hover, button.btn-orange:hover,
  .nav .btn-orange:hover, .hero .btn-orange:hover,
  .footer .btn-orange:hover, .section-dark .btn-orange:hover {
    background-image: var(--surface-accent-hov) !important;
    transform: translateY(-1px);
    box-shadow: var(--hi-dark), var(--elev-accent-hi);
  }
}
.btn-orange:active, .nav .btn-orange:active, .hero .btn-orange:active,
.footer .btn-orange:active, .section-dark .btn-orange:active {
  background-image: none !important;
  transform: translateY(1px);
  box-shadow: var(--press-dark);
}
.btn-dark {
  background-image: linear-gradient(180deg, #0E2743 0%, #071B30 100%) !important;
  box-shadow: var(--hi-dark), var(--elev-2);
}
/* WhatsApp green is a fixed brand colour, but white on #1CA05A measures
   3.37:1 on a 15px bold label — a pre-existing fail. Same 147° hue, taken two
   shades down so the WORST (top) stop clears 4.5:1. Same !important /
   selector-list mechanics as the primary above, for the same reason. */
.btn-whatsapp, .nav .btn-whatsapp, .footer .btn-whatsapp {
  background-image: linear-gradient(180deg, #17874A 0%, #12723D 100%) !important;
  border-color: #12723D !important;
  box-shadow: var(--hi-dark), 0 1px 2px rgba(9,60,32,.35), 0 6px 14px -5px rgba(9,60,32,.40);
}
.btn-whatsapp:active { background-image: none !important; box-shadow: var(--press-dark); }
/* On a navy surface a navy glow is invisible, and the blue CTA sitting on the
   navy nav is only 1.47:1 against its own ground — far under the 3:1 WCAG
   1.4.11 asks of a UI component boundary. A white hairline fixes it, but only
   at sufficient alpha: 30% white over #071B30 measures 2.66:1 and still
   fails. 42% measures 4.05:1 and passes with room to spare.
   Specificity must be >= (0,2,0) to beat `.hero .btn { box-shadow: none }`. */
.nav .btn-orange, .hero .btn-orange, .footer .btn-orange, .section-dark .btn-orange {
  border-color: rgba(255,255,255,.42) !important;
  box-shadow: var(--hi-dark), 0 2px 10px rgba(0,0,0,.45);
}
.nav .btn-whatsapp, .footer .btn-whatsapp,
.hero .btn:not(.btn-ghost), .footer .btn:not(.btn-ghost), .section-dark .btn:not(.btn-ghost) {
  box-shadow: var(--hi-dark), 0 2px 10px rgba(0,0,0,.40);
}
/* The "Pick your driver" band is now the accent FIELD itself, so an
   accent-filled CTA sitting on it would be same-on-same and vanish. On that
   one surface the primary takes the deep navy instead — still white type
   (17.4:1), and now with real fill separation from its ground. */
.section-dark .btn-orange {
  background-image: linear-gradient(180deg, #0E2C4B 0%, #071B30 100%) !important;
  border-color: rgba(255,255,255,.42) !important;
  box-shadow: var(--hi-dark), 0 2px 12px rgba(0,0,0,.45);
}
@media (hover: hover) and (pointer: fine) {
  .section-dark .btn-orange:hover {
    background-image: linear-gradient(180deg, #143B60 0%, #0B2743 100%) !important;
  }
}
.btn-ghost { box-shadow: none !important; transition: background-color .15s var(--ease); }
.btn-ghost:active { transform: translateY(1px); }

/* ---------- form fields: recessed, the inverse of a button ----------------
   Scoped away from .driver-app, whose inputs are deliberately dark and whose
   stylesheet loads after this one. */
body:not(.driver-app) .field input,
body:not(.driver-app) .field select,
body:not(.driver-app) .field textarea,
body:not(.driver-app) .addr-row input,
.intro-link-box input {
  background-image: linear-gradient(180deg, #FBFCFD 0%, #FFFFFF 60%);
  border-color: var(--field-line);
  box-shadow: inset 0 1px 2px rgba(var(--sh), .07);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
body:not(.driver-app) .field input:focus,
body:not(.driver-app) .field select:focus,
body:not(.driver-app) .field textarea:focus,
.intro-link-box input:focus {
  border-color: var(--blue);
  box-shadow: inset 0 1px 2px rgba(var(--sh), .05), 0 0 0 3px rgba(var(--sh-accent), .18);
}
/* iOS Safari paints its own grey over a disabled input's author colour and
   pushes it well below 4.5:1. -webkit-text-fill-color is the only declaration
   that overrides it. */
body:not(.driver-app) .field input:disabled,
body:not(.driver-app) .field select:disabled {
  -webkit-text-fill-color: var(--ink-soft);
  color: var(--ink-soft);
  background-image: none; background-color: #F1F3F6;
  opacity: 1;
}
/* .phone-prefix asked for `border-right: none`, then the normalising override
   at ~961 re-added `border: 1px solid` and put a doubled hairline back against
   the input. This is the last word. */
.phone-prefix {
  border: 1px solid var(--field-line);
  border-right: 0;
  background-image: var(--surface-tint);
  box-shadow: var(--hi), inset 0 1px 2px rgba(var(--sh), .06);
}
/* A toggle is a physical object: the track is a slot, the knob sits in it. */
.switch { box-shadow: inset 0 1px 3px rgba(var(--sh), .22); transition: background-color .15s var(--ease); }
.switch::after { box-shadow: 0 1px 2px rgba(var(--sh), .35), inset 0 1px 0 rgba(255,255,255,.35); }
.switch:checked { background-image: var(--surface-accent); box-shadow: var(--press-dark); }
.switch:checked::after { background: #FFFFFF; }

/* ---------- selectable rows (booking app) --------------------------------- */
.service-row, .seg, .locate-row, .search-trigger, .pay-method, .driver-pick {
  background-image: var(--surface);
  border-color: var(--line-cool);
  box-shadow: var(--elev-1);
  transition: box-shadow .15s var(--ease), transform .12s var(--ease), border-color .15s var(--ease);
}
.seg, .search-trigger { background-image: var(--surface-tint); box-shadow: var(--hi), var(--elev-1); }
@media (hover: hover) and (pointer: fine) {
  .service-row:hover, .locate-row:hover, .search-trigger:hover, .pay-method:hover, .driver-pick:hover {
    box-shadow: var(--elev-2); border-color: var(--line-cool-2); transform: translateY(-1px);
  }
}
.service-row:active, .locate-row:active, .search-trigger:active, .driver-pick:active {
  transform: translateY(1px); box-shadow: var(--press);
}
/* Selected = the accent, lifted, glowing. Same physics as the primary button
   so "chosen" reads identically everywhere in the app. */
.service-row.selected, .seg.active, .service-tab.active, .driver-pick.selected,
.tip-chip:has(input:checked), .star-btn:has(input:checked), .pay-method:has(input:checked) {
  background-image: var(--surface-accent);
  box-shadow: var(--hi-dark), var(--elev-accent);
  transform: none;
}
.locate-icon { background-image: var(--surface-accent); box-shadow: var(--hi-dark), var(--elev-accent); }
/* The chip's 2px hard black offset is the last sticker shadow in this file. */
.chip, .meta-chip {
  background-image: var(--surface-tint);
  border-color: var(--line-cool-2);
  box-shadow: var(--hi), var(--elev-1);
}
/* .chip:hover filled with the accent but left the label at --ink: 1.44:1, so
   the text vanished on hover. Keep the lift, drop the fill swap. */
@media (hover: hover) and (pointer: fine) {
  .chip:hover { background-image: var(--surface); border-color: #BCCBDC; box-shadow: var(--elev-2); }
}
.chip:active { box-shadow: var(--press); transform: translateY(1px); }

/* ---------- floating layers: sheets, overlays, map controls --------------- */
/* `0 -8px 0` is a zero-blur band, which reads as a flat grey stripe rather
   than a panel lifting off the map. This is the highest object on screen. */
.sheet {
  border-top: 1px solid rgba(255,255,255,.9);
  box-shadow: 0 -1px 0 rgba(var(--sh), .07),
              0 -14px 34px -10px rgba(var(--sh), .28),
              var(--hi);
}
.overlay-card {
  border-top: 1px solid rgba(255,255,255,.9);
  box-shadow: 0 -14px 40px -8px rgba(0,0,0,.35), var(--hi);
}
.sheet-grip { background: var(--line-cool-2); opacity: 1; box-shadow: inset 0 1px 1px rgba(var(--sh),.18); }
.round-btn, .mini-btn, .back-btn {
  background-image: var(--surface);
  border-color: var(--line-cool);
  box-shadow: var(--elev-2);
  transition: box-shadow .15s var(--ease), transform .12s var(--ease);
}
.round-btn:active, .mini-btn:active, .back-btn:active { transform: translateY(1px); box-shadow: var(--press); }
.addr-block, .confirm-route {
  background-image: var(--surface-tint);
  border-color: var(--line-cool);
  box-shadow: var(--hi), inset 0 1px 2px rgba(var(--sh), .06);
}
/* Totals are plinths the number is stamped into, not floating panels. */
.quote-total, .fare-total {
  background-image: var(--surface-deep);
  box-shadow: var(--press-dark), inset 0 -1px 0 rgba(255,255,255,.10);
}
.ac-list li { border-bottom: 1px solid var(--line-cool); }
@media (hover: hover) and (pointer: fine) {
  .ac-list li:hover { background: #F3F7FB; }
}

/* ---------- FAQ ----------------------------------------------------------- */
.faq summary { transition: background-color .15s var(--ease); }
@media (hover: hover) and (pointer: fine) {
  .faq summary:hover { background: #F3F7FB; }
}
.faq[open] summary { background-image: var(--surface-accent); box-shadow: var(--hi-dark); }
/* The marker kept colour:--orange when open — navy on navy at 1.44:1, so the
   minus sign simply disappeared the moment the panel opened. */
.faq[open] summary::after { color: #FFFFFF; }
.faq-body { border-top: 1px solid var(--line-cool); }
.faq-zh, .card-zh, .hourly-cta {
  border-top-color: var(--line-cool); border-top-style: solid; border-top-width: 1px;
}

/* ---------- status pills -------------------------------------------------- */
/* The candy fills + 2px navy outline were the last of the sticker era and the
   loudest thing on a trip row. Replaced with soft tinted tokens: a pale fill,
   a hairline of the same hue, a lit top edge, and dark text of that hue — so
   the hue now carries meaning in the TEXT as well as the fill. Every pair
   below is in the measured contrast table.
   The dot is a ::before in currentColor: it gives the pill a shape you can
   scan at a glance without touching the type. Type is deliberately left
   alone — uppercasing does nothing to Chinese and tracking out full-width
   glyphs at 11px undoes what `word-break: keep-all` exists to protect. */
.pill {
  border-width: 1px; border-style: solid;
  box-shadow: var(--hi), 0 1px 1px rgba(var(--sh), .06);
  padding: .22rem .72rem;
}
.pill::before {
  content: '';
  display: inline-block;      /* pseudo-element: cannot carry [hidden] */
  width: .42em; height: .42em; border-radius: 50%;
  background: currentColor; margin-right: .45em;
  vertical-align: .08em;
}
.pill-pending   { background: #FFF3D6; border-color: #F0DCA8; color: #6B4A05; }
.pill-assigned  { background: #FFEBD1; border-color: #F2D5AE; color: #7A4708; }
.pill-accepted,
.pill-confirmed { background: #D9F3E2; border-color: #B4E3C7; color: #17603A; }
.pill-arrived   { background: #D8ECFB; border-color: #B3D8F2; color: #10486F; }
.pill-completed { background: #EDF0F3; border-color: #DCE2E8; color: #4A5560; }
.pill-cancelled { background: #FBE0DE; border-color: #F2C4C0; color: #8A2018; }
/* ON BOARD is the one live state — it stays a filled accent and it glows. */
.pill-onboard {
  background-image: var(--surface-accent); border-color: var(--blue); color: #FFFFFF;
  box-shadow: var(--hi-dark), var(--elev-accent);
}
/* .status-live only swapped the border to --orange: navy on navy, no visible
   change at all. A ring reads on every one of the fills above.
   The selector is written at (0,2,0), not (0,1,0): the live row IS a
   .sticker-card, and `.section-orange .sticker-card` (0,2,0) would otherwise
   beat it and erase the ring on any band-hosted list. */
.sticker-card.status-live, .booking-row.status-live, .status-live {
  box-shadow: var(--hi), 0 0 0 3px rgba(var(--sh-accent), .16);
}
.tag-f { background: #FBE2EC; border: 1px solid #F0BFD3; color: #8A1F4B; box-shadow: var(--hi); }
.badge-repeat {
  background-image: var(--surface-accent); border: 1px solid var(--blue);
  color: #FFFFFF; box-shadow: var(--hi-dark);
}

/* ---------- tables -------------------------------------------------------- */
/* Dashed rules imply a placeholder. Solid hairlines plus a lifted header row
   reads as a real, finished table. */
.rate-table th, .rate-table td, .rate-list li, .place-list li,
.detail-list, .quote-line, .fare-line, .opt-row {
  border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: var(--line-cool);
}
.rate-table th {
  background-image: var(--surface-tint);
  box-shadow: var(--hi), inset 0 -1px 0 var(--line-cool-2);
  color: var(--ink);
}
@media (hover: hover) and (pointer: fine) {
  .rate-table tbody tr:hover { background: #F6F9FC; }
  .place-list li:hover { background: #F6F9FC; }
}
/* .rate-head is the shared group heading — rate cards AND the profile groups.
   It stops being a 1.05rem red line and becomes the block's header. No
   uppercase and no heavy tracking: it holds Chinese on every page. */
.rate-head {
  font-size: 1rem;
  color: var(--ink);
  border-bottom: 1px solid var(--line-cool-2);
  padding-bottom: .6rem;
  margin-bottom: 1rem;
}
.place-list li { transition: background-color .12s var(--ease); }
/* Hard rule 7: every wide table scrolls inside its own box, never the page. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---------- flash messages ------------------------------------------------ */
/* Pastel fills with a 3px navy outline are off-system and shout louder than
   the content. A white card with a coloured edge and real elevation says the
   same thing quietly, and keeps --ink on near-white. */
.flash {
  background-image: var(--surface);
  border: 1px solid var(--line-cool);
  border-left: 4px solid var(--ink-dim);
  box-shadow: var(--elev-2);
  color: var(--ink);
}
.flash-success { background-color: #FFFFFF; border-left-color: #17874A; }
.flash-error   { background-color: #FFFFFF; border-left-color: var(--red); }
.flash-info    { background-color: #FFFFFF; border-left-color: var(--blue); }
.auth-debug { border: 1px solid #EBD79E; box-shadow: var(--hi), var(--elev-1); }
.pending-tag { box-shadow: var(--hi), var(--elev-1); }

/* ---------- driver avatars ------------------------------------------------
   The fill arrives as an inline `style="background:{{ d.avatar_color }}"`,
   which is a style-attribute NORMAL declaration — so its contrast cannot be
   managed by any normal CSS rule, and a text-shadow scrim does not rescue
   dark initials on a pale fill. An !important background-IMAGE outranks the
   inline shorthand (which sets background-image to none), so a navy veil
   lands over whatever hue the server picks: worst case is the veil over pure
   white, and white initials on that measure in the table below.
   :not(img) keeps real photos untouched; :not(.avatar-any) keeps the striped
   "any driver" avatar. */
.avatar:not(.avatar-any):not(img) {
  background-image: linear-gradient(rgba(7,27,48,.62), rgba(7,27,48,.62)) !important;
  color: var(--on-dark);
  border: 0;
  text-shadow: 0 1px 2px rgba(0,0,0,.45);
  box-shadow: 0 0 0 1px rgba(var(--sh), .12), var(--elev-1);
}
img.avatar { border: 0; box-shadow: 0 0 0 1px rgba(var(--sh), .12), var(--elev-1); }

/* ---------- customer profile ("prof-" group) ------------------------------
   The page is a <form> wrapping two cards inside .booking-main, which is a
   flex column with a 1.8rem gap. The form is one flex item, so without help
   its two cards stack flush. `display: contents` would fix that but it is a
   display declaration on a real element in the one stylesheet whose first
   hard rule is about display declarations — and it drops the node from the
   a11y tree in older Safari/VoiceOver. A margin does the same job with none
   of that. */
.prof-form > .prof-group + .prof-group { margin-top: 1.8rem; }

/* Each group gets a real header zone: the .rate-head bleeds to the card edge
   and becomes a lit shelf, so the card has a top rather than starting with a
   floating line of text. :first-child is load-bearing — the coin card has a
   SECOND .rate-head ("Recent activity") mid-card that must not bleed.
   The negative margins track .sticker-card's padding (1.5rem / 1.6rem,
   dropping to 1.2rem / 1.1rem at 760px); the companion rule is at the bottom
   of this block and both must move together. */
.prof-group > .rate-head:first-child {
  margin: -1.5rem -1.6rem 1.1rem;
  padding: .85rem 1.6rem;
  font-size: 1rem;
  color: var(--ink);
  background-image: var(--surface-tint);
  border-bottom: 1px solid var(--line-cool);
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
  box-shadow: var(--hi);
}
.prof-group-note { margin: -.5rem 0 1.1rem; color: var(--ink-soft); opacity: 1; }
/* The verification card is the one that needs attention — one step higher on
   the ladder and an accent shelf, so it separates from the neutral groups
   without a second colour or a heavier border. */
.prof-verify { box-shadow: var(--elev-3); border-color: var(--line-cool-2); }
.prof-verify > .rate-head:first-child {
  background-image: linear-gradient(180deg, #E9F0F8 0%, #DDE7F2 100%);
  border-bottom-color: var(--line-cool-2);
}
/* The privacy note is carved INTO the card — recessed, so it reads as a
   standing disclosure rather than another thing competing for attention. */
.prof-privacy-note {
  margin: .9rem 0;
  padding: .85rem 1rem;
  font-size: .88rem;
  line-height: 1.75;
  border-radius: 12px;
  border-left: 3px solid var(--blue);
  background-image: var(--surface-tint);
  box-shadow: inset 0 1px 3px rgba(var(--sh), .10), inset 0 -1px 0 rgba(255,255,255,.7);
}
.prof-selfie-shot {
  margin: .9rem 0; padding: .9rem 1rem;
  border-radius: 12px;
  background-image: var(--surface-tint);
  box-shadow: inset 0 1px 3px rgba(var(--sh), .10);
}
.prof-selfie-shot .opt-note { margin-bottom: 0; }
/* NO `display` declaration here, deliberately: #selfie-preview carries the
   [hidden] attribute and is un-hidden by JS on the profile page. Sizing and
   elevation only. If you ever add `display: block` here the preview appears
   on page load with no src. */
.prof-selfie-thumb {
  max-width: min(180px, 100%); height: auto;
  border-radius: 12px;
  border: 1px solid var(--line-cool);
  box-shadow: var(--elev-2);
  margin: .2rem 0 .6rem;
}
/* The coin balance carried `text-shadow: 3px 3px 0 var(--black)` — a navy hard
   offset under a navy numeral. It becomes a stamped plinth instead. No white
   letterpress highlight: that is the defining Web-2.0 artifact and it softens
   the bottom edge of a 3rem Archivo Black figure for nothing. */
.coin-balance {
  margin: .2rem 0 .8rem;
  padding: .9rem .6rem .7rem;
  border-radius: 12px;
  background-image: var(--surface-tint);
  box-shadow: inset 0 2px 4px rgba(var(--sh), .12), inset 0 -1px 0 rgba(255,255,255,.8);
  text-shadow: none;
}
/* A consent checkbox row is a tap target, so it gets a tap target's height. */
.check-row {
  min-height: 44px; align-items: center;
  padding: .35rem .7rem; margin: .6rem -.7rem 1rem;
  border-radius: 10px; transition: background-color .12s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .check-row:hover { background: #F3F7FB; }
}

/* ---------- introducer portal --------------------------------------------
   intro/base.html loads style.css ONLY — driver.css is not on this page, so
   .drv-card / .drv-table / .drv-pill / .drv-tabs arrive here completely
   unstyled. Everything below is scoped to .intro-app so it can never reach
   the dark driver app, whose own stylesheet loads after this file anyway. */
.intro-header {
  background-image: var(--surface-deep);
  border-bottom: 1px solid rgba(255,255,255,.10);
  box-shadow: 0 1px 0 rgba(var(--sh), .55), 0 6px 18px -6px rgba(var(--sh), .50);
  position: sticky; top: 0; z-index: 100;
}
.intro-brand em { color: var(--accent-on-dark); opacity: 1; }
.intro-hero-card {
  background-image: linear-gradient(158deg, #1A4A78 0%, #12395F 55%, #0D2C4B 100%);
  box-shadow: var(--hi-dark), var(--elev-accent-hi);
}
.intro-stat {
  background-image: var(--surface);
  border-color: var(--line-cool);
  box-shadow: var(--elev-1);
  transition: box-shadow .18s var(--ease), transform .18s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .intro-stat:hover { transform: translateY(-2px); box-shadow: var(--elev-2); }
}
.intro-login-card { box-shadow: var(--elev-3); border-color: var(--line-cool); }
.intro-code-line code { box-shadow: var(--hi), inset 0 1px 2px rgba(var(--sh), .10); }
.intro-link-box .btn { flex: 0 0 auto; }
/* The unstyled driver-app classes, given the light-surface treatment. */
.intro-app .drv-card {
  background-image: var(--surface);
  border: 1px solid var(--line-cool);
  border-radius: 16px;
  padding: 1.1rem 1.2rem;
  margin-bottom: 1.1rem;
  box-shadow: var(--elev-1);
}
.intro-app .drv-h1 { font-size: 1.5rem; margin-bottom: .8rem; }
.intro-app .drv-h2 { font-size: 1.02rem; color: var(--ink); margin: 0 0 .7rem; }
.intro-app .drv-hint, .intro-app .drv-empty, .intro-app .drv-muted {
  color: var(--ink-soft); opacity: 1; font-size: .84rem;
}
.intro-app .drv-table { width: 100%; border-collapse: collapse; font-size: .86rem; }
.intro-app .drv-table th {
  text-align: left; font-family: var(--display); font-size: .72rem; color: var(--ink);
  padding: .5rem .5rem;
  background-image: var(--surface-tint);
  box-shadow: var(--hi), inset 0 -1px 0 var(--line-cool-2);
}
.intro-app .drv-table td { padding: .6rem .5rem; border-bottom: 1px solid var(--line-cool); }
@media (hover: hover) and (pointer: fine) {
  .intro-app .drv-table tbody tr:hover { background: #F6F9FC; }
}
.intro-app .drv-pill {
  font-family: var(--display); font-size: .66rem; letter-spacing: .05em;
  border-radius: 999px; padding: .2rem .65rem; white-space: nowrap;
  border: 1px solid var(--line-cool-2); background: #EDF0F3; color: #4A5560;
  box-shadow: var(--hi);
}
.intro-app .drv-pending { background: #FFF3D6; border-color: #F0DCA8; color: #6B4A05; }
.intro-app .drv-completed { background: #D9F3E2; border-color: #B4E3C7; color: #17603A; }
/* body.intro-app already reserves 5.5rem for "the fixed tab bar", but nothing
   in this file ever made it fixed — it falls in the page flow with no surface
   at all, and it is the portal's only navigation. `display` here is safe:
   .drv-tabs is a server-rendered <nav> inside {% if introducer %} and its
   children are <a>; neither can carry [hidden], and the guard at line 45 is
   !important so it would win anyway. */
.intro-app .drv-tabs {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 200;
  display: grid; grid-template-columns: repeat(4, 1fr);
  background-image: var(--surface);
  border-top: 1px solid var(--line-cool);
  padding-bottom: env(safe-area-inset-bottom);
  /* A bar at the bottom is lit from above, so its cast goes UP onto the page. */
  box-shadow: 0 -1px 0 rgba(255,255,255,.9), 0 -10px 26px -8px rgba(var(--sh), .20);
}
.intro-app .drv-tabs a {
  display: flex; flex-direction: column; align-items: center; gap: .1rem;
  padding: .6rem .2rem; text-decoration: none;
  font-size: .68rem; font-weight: 900; color: var(--ink-soft);
}
.intro-app .drv-tabs a span:first-child { font-size: 1.2rem; }
.intro-app .drv-tabs a.active { color: var(--blue); }
.intro-app .drv-tabs a.active span:first-child {
  filter: drop-shadow(0 2px 4px rgba(var(--sh-accent), .35));
}

/* ---------- focus --------------------------------------------------------
   The existing :focus-visible ring carries `box-shadow: 0 0 0 6px
   rgba(247,148,29,.55)` — the OLD orange, and a box-shadow that wipes every
   elevation above the moment anything is focused. Replaced with an outline
   (which does not participate in box-shadow) plus explicit
   elevation-preserving rules for every focusable component that carries
   depth. Any NEW elevated focusable component must be added to that list or
   it will visibly flatten when tabbed to. */
:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
  box-shadow: none;
}
.driver-app :focus-visible, .section-dark :focus-visible, .nav :focus-visible,
.hero :focus-visible, .footer :focus-visible, .intro-header :focus-visible,
.intro-hero-card :focus-visible, .quote-total :focus-visible {
  outline-color: #FFFFFF;
}
.btn:focus-visible { box-shadow: var(--elev-2); }
.btn-orange:focus-visible, .btn-dark:focus-visible { box-shadow: var(--hi-dark), var(--elev-accent-hi); }
a.sticker-card:focus-visible, .booking-row:focus-visible,
.service-row:focus-visible, .locate-row:focus-visible,
.search-trigger:focus-visible, .intro-stat:focus-visible { box-shadow: var(--elev-3); }
.round-btn:focus-visible, .mini-btn:focus-visible, .back-btn:focus-visible { box-shadow: var(--elev-2); }
/* The sheet's sticky primary action carries a load-bearing shadow: the first
   layer is a WHITE SCRIM masking the sheet's scrolling content behind it on
   the booking map. `.btn:focus-visible` above is (0,2,0) and would replace
   it, bleeding the address list through the button. This restores it. */
.sheet > .btn-block:last-child:focus-visible {
  box-shadow: 0 -10px 14px 6px var(--white), var(--elev-2);
}
.addr-row input:focus-visible { outline-offset: 0; border-radius: 6px; }

/* ---------- mobile -------------------------------------------------------
   Shadows never create overflow, so nothing here can scroll the page
   sideways at 360px. The only geometry that must follow the breakpoint is the
   bled card header, whose negative margins are tied to .sticker-card's
   padding — that drops from 1.5/1.6rem to 1.2/1.1rem at 760px.
   Section padding is deliberately NOT touched anywhere in this block: the
   mobile density rule at ~935 must keep winning. */
@media (max-width: 760px) {
  .prof-group > .rate-head:first-child {
    margin: -1.2rem -1.1rem 1rem;
    padding: .75rem 1.1rem;
  }
  /* Softer casts on a small screen: at arm's length a 28px blur reads as haze.
     :not(.status-live) is load-bearing — without it this rule (later in the
     file) erases the live-trip ring at exactly the viewport where My Trips is
     actually read, and an in-progress trip becomes indistinguishable from a
     completed one. */
  .sticker-card:not(.status-live),
  .section-orange .sticker-card:not(.status-live) { box-shadow: var(--elev-1); }
  .card-icon { font-size: 1.55rem; }
  .sheet { box-shadow: 0 -1px 0 rgba(var(--sh), .07), 0 -10px 24px -8px rgba(var(--sh), .26), var(--hi); }
  /* The row goes to a column here, so the chevron pins to the top-right
     rather than floating beside a two-line block. */
  .booking-row::after { top: 1.4rem; transform: none; }
}
@media (max-width: 420px) {
  /* 4 tabs across 360px: the labels are bilingual and must not wrap. */
  .intro-app .drv-tabs a { font-size: .62rem; padding: .55rem .1rem; }
}

/* ---------- reduced motion ----------------------------------------------
   The block near the top of this file kills animation and transition but NOT
   transform, so a hover lift would still jump instantly. Here the elevation
   change alone carries the state. */
@media (prefers-reduced-motion: reduce) {
  a.sticker-card:hover, .booking-row:hover, .btn:hover, .btn-orange:hover,
  .service-row:hover, .locate-row:hover, .search-trigger:hover,
  .pay-method:hover, .driver-pick:hover, .intro-stat:hover,
  .btn:active, .btn-orange:active, .btn-whatsapp:active,
  a.sticker-card:active, .booking-row:active, .service-row:active,
  .locate-row:active, .search-trigger:active, .driver-pick:active,
  .round-btn:active, .mini-btn:active, .back-btn:active, .chip:active,
  .btn-ghost:active {
    transform: none;
  }
}

/* ---------- late corrections (still inside the refresh layer) -------------
   Found by measuring the rendered pages, not by reading the file. Three of
   these were invisible text.

   A) .btn-ghost — `.btn { background-image: var(--surface) }` above is (0,1,0)
      and beats `.btn-ghost { background: transparent }` at ~984, because a
      `background` shorthand only sets background-image to `none` and a later
      equal-specificity rule can put the image back. Result: the hero's
      "See Rates" rendered a WHITE gradient under WHITE text — measured 1.00:1,
      hard rule 5's exact named failure. Reset explicitly, with !important, so
      no future edit to `.btn` can reach it again.

   B) De-emphasis by `opacity`. It compounds two ways: it multiplies with
      --ink-dim (already only 4.9–5.1:1), and every one of these selectors
      also matches the bilingual <span class="l-en"> nested INSIDE the element
      it targets, so the dimming composites twice. Measured before:
      .opt-note 2.85:1, .muted 2.61:1, .driver-langs 3.12:1,
      .trust-item span 3.17:1. Colour already carries the hierarchy here
      (--ink-dim vs --ink is 5.1:1 vs 16.4:1), so dropping the opacity costs
      nothing visually and can only ever RAISE contrast — which is why it is
      safe to apply to selectors that also appear on dark surfaces.

   C) `.footer-brand span` is (0,1,1) and outranks `.footer, .footer *`
      (0,1,0), so the footer's second brand line has been rendering --ink
      #1C1C1C on --blue-deep: 1.02:1. Invisible, and pre-existing.

   D) `.nav-toggle` resolved to the UA `buttonface` fill with white text
      (1.14:1). It is the ONLY navigation below 1000px, so it is stated
      explicitly rather than left to a shorthand three-quarters up the file. */

.btn-ghost {
  background-image: none !important;
  background-color: transparent !important;
}
.btn-ghost:hover { background-color: rgba(255,255,255,.08) !important; }
.btn-ghost:active { background-color: rgba(255,255,255,.14) !important; }

/* `appearance: none` is the load-bearing half: a <button> with the native
   appearance keeps painting the UA `buttonface` widget fill underneath the
   author background, which put a light-grey square under a white glyph. */
.nav-toggle {
  -webkit-appearance: none; appearance: none;
  background: transparent; color: var(--on-dark);
}

.footer-brand span { color: var(--on-dark); }

.muted, .quote-fine, .opt-note, .driver-langs, .ac-msg, .zh-line,
.faq-zh, .card-zh, .ops-hint, .leaflet-control-attribution { opacity: 1; }

/* These two are --ink at 70%, not --ink-dim, so they need the caption colour
   as well as the opacity reset or they would jump to full body weight. */
.trust-item span, .driver-meta { opacity: 1; color: var(--ink-soft); }
/* ...and must stay light ONLY where they sit directly on a dark band. A
   .driver-meta lives inside a WHITE .sticker-card that happens to be inside
   .section-dark, so a blanket `.section-dark .driver-meta` guard painted
   white-on-white — measured 1.00:1. The card-scoped rule below is (0,3,0)
   and takes it back. */
.section-dark > .trust-strip .trust-item span { color: rgba(255,255,255,.88); }
.section-dark .sticker-card .driver-meta,
.section-dark .sticker-card .trust-item span { color: var(--ink-soft); }

/* Leaflet's own attribution link is #0078A8 on its pale control — 3.64:1 at
   the 0.62rem this file sets it to. Same hue, dark enough to read. */
.leaflet-control-attribution { background: rgba(255,255,255,.92); }
.leaflet-control-attribution, .leaflet-control-attribution a { color: #044E70; }

/* --------------------------------------------------------------------------
   Post-refresh corrections. Found by an adversarial contrast pass over the
   design layer above; these must stay LAST so they win.
   -------------------------------------------------------------------------- */

/* The selected payment row was added to the accent-FILL list but never to the
   white-TEXT list, so the default-selected method ("Touch 'n Go eWallet")
   rendered near-black #1C1C1C on navy — the exact "words are literally hidden"
   failure this file has shipped twice before. `:has()` carries the specificity
   of its argument, so this is (0,2,1) and beats the inherited body colour. */
.pay-method:has(input:checked),
.pay-method:has(input:checked) * { color: var(--on-dark); }

/* pay.html nests two spans that BOTH match `.pm-body span`, so an opacity of
   .75 multiplied to .5625 and the sub-label measured 3.94:1 against a 4.5:1
   requirement. Colour de-emphasises without compounding. */
.pm-body span { opacity: 1; color: var(--ink-dim); }
.pay-method:has(input:checked) .pm-body span { color: rgba(255,255,255,.88); }

/* --------------------------------------------------------------------------
   ALIGNMENT FIX — keep last.

   The "editorial left-set headings" graft at the section-rhythm block above
   assumed each section's content is full-width and left-aligned. It is not:
   this codebase CENTRES its content containers (.booking-list is 760px with
   auto margins, `.section-orange > *` and `.section-dark > *` give every child
   auto margins, .section itself is 1080px auto). Left-setting only the heading
   therefore produced three competing alignments on every page except Home:

     * .page-head titles kept the auto margins from `.section-orange > *` but
       gained `max-width: 24ch`, so the BOX centred while its TEXT sat left —
       the heading landed at an offset that changed with the length of the
       words, which is why "My Trips" and "My Profile" looked randomly placed.
     * .section-sub was given the SHORTHAND `margin: -.4rem 0 2rem`, whose 0
       resets the auto left/right margins from `.section-orange > *`. That is
       the sub-line flush against the viewport edge under a centred title.
     * On plain sections the heading sat at the section's left padding edge
       while the cards it headed stayed centred 160px further in.

   One shared axis is what was missing. The content is centred, so the headings
   are centred too — restoring the site's own convention. The measure caps are
   kept (long sub-lines still wrap at a comfortable 62ch) but with auto margins
   so the capped box is centred rather than pinned left.
   -------------------------------------------------------------------------- */
.section > .section-title,
.page-head > .section-title {
  text-align: center;
  max-width: none;
  margin-left: auto;
  margin-right: auto;
}
.section > .section-sub,
.page-head > .section-sub {
  text-align: center;
  max-width: 62ch;
  margin-left: auto;
  margin-right: auto;
}
/* The page banner is a title + one line, not a section of content: tighten the
   gap so the two read as a single unit instead of two stranded rows. */
.page-head > .section-sub { margin-top: -.2rem; margin-bottom: 0; }

/* A centred CTA under a now-centred heading is no longer an orphan, so the
   section-closing rule the left-set treatment added is redundant and reads as
   a stray line. */
.section > .center { border-top: 0; padding-top: 0; }

/* The empty state was a .sticker-card sitting directly in a 1080px section, so
   it stretched the full width around two short lines of content — a wide white
   slab with a lot of nothing in it. Size it to its contents. */
.center-card {
  max-width: 460px;
  margin-inline: auto;
  padding: 2.4rem 1.6rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.2rem;
}
.center-card > p { margin: 0; color: var(--ink-soft); }


/* ==========================================================================
   ART DIRECTION LAYER — "THE NIGHT DRIVE"
   Appended AFTER the depth-and-elevation layer and after the alignment fix,
   so it wins. It does not restate either: the elevation ladder, the surface
   gradients, the focus system, the hover gating and the centred section
   headings are all reused exactly as they are.

   What the depth pass could not fix, because it is composition and not
   colour: the hero was a 655px navy slab with a 240px logo floating in its
   right third, the page had no subject, the only iconography in the brand
   was system emoji, and every band change was a 1px straight line.

   Hard rules honoured (each has regressed in this file before):
    1. No `display` declaration below touches .overlay, .l-en, .l-zh, or any
       element that can carry [hidden]. The only `display` declarations are on
       classes invented here for static author markup (.hero-mark,
       .hero-proof) — .card-icon deliberately centres its mark with
       line-height instead of a display change.
    2. body never takes a `background` shorthand here — `word-break: keep-all`
       cannot be reached from this block.
    3. No `#map` rule.
    4. The language display:none !important rules are untouched. The one place
       this block uses html.lang-en only ADDS letter-spacing to the English
       half; it never sets display and never reorders or merges a pair.
    5. Contrast: every pair below is in the measured table, and text over the
       artwork is measured against the LIGHTEST pixel that artwork can
       physically put behind it, not against its average.
    6. Nothing uses 100vw. Every full-bleed layer is left:0/right:0 inside a
       positioned ancestor, and .hero is overflow:hidden.
    8. Every :hover added here sits inside @media (hover:hover) and
       (pointer:fine).
    9. Every animation and every transform introduced here is disabled under
       prefers-reduced-motion, at the very end of this block. There is NO SMIL
       (<animate>/<animateTransform>) in any artwork: prefers-reduced-motion
       does not stop SMIL, so SMIL would break rule 9 silently.
   ========================================================================== */

:root {
  /* Night tones. Shades of --blue-deep; no new hue enters the palette. */
  --nd-sky-top: #04101D;
  --nd-sky-mid: #061A2C;
  --nd-sky-low: #0A2740;
  --nd-glow: 31, 102, 163;      /* the city glow, as channels */
  --nd-lamp: 127, 179, 232;     /* --accent-on-dark, as channels */

  /* The two signature marks, reused at every band change so the page has one
     transition shape instead of eight identical 1px rules. */
  --edge-lane: repeating-linear-gradient(90deg,
                 rgba(255,255,255,.30) 0 34px, rgba(255,255,255,0) 34px 68px);
  --edge-lane-ink: repeating-linear-gradient(90deg,
                 rgba(18,57,95,.34) 0 26px, rgba(18,57,95,0) 26px 56px);
  --edge-fade: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
  /* An ANONYMOUS city crest — deliberately no landmarks. A repeating tile that
     contained the Petronas Twin Towers would print them twice on a 1440px
     screen, and that pair is singular: a KL viewer reads a duplicate as a
     mistake. The Towers and KL Tower appear ONCE, in the hero scene only.
     Tileable: both ends sit at y=42, so repeat-x has no seam. */
  --edge-skyline: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 60'%3E%3Cpath fill='%23000' d='M0 60L0 42L26 42L26 33L58 33L58 46L92 46L92 28L118 28L118 40L150 40L150 30L178 30L178 44L214 44L214 36L246 36L246 48L286 48L286 31L312 31L312 43L344 43L344 34L378 34L378 46L412 46L412 29L440 29L440 41L472 41L472 33L504 33L504 45L538 45L538 36L568 36L568 42L600 42L600 60Z'/%3E%3C/svg%3E");

  /* Grain. A ~250-byte feTurbulence tile at 5%. A three-stop navy gradient
     across a 600px band BANDS visibly on the 8-bit panels this service is
     actually opened on, at low brightness, in a dark car. */
  --nd-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)'/%3E%3C/svg%3E");

  /* The route dashes under "How it works". --line-cool-2 on --ground is
     1.19:1 — i.e. the mark that is meant to turn three boxes into a road is
     not actually rendered. This is a real value. */
  --nd-route: rgba(18, 57, 95, .30);
}

/* ---------- display tracking, ENGLISH ONLY -------------------------------
   Archivo Black wants -.02em. Full-width Simplified glyphs are visually
   ISOLATED by any tracking change — positive or negative — which is the same
   reason `word-break: keep-all` exists two thousand lines above. (0,2,1), so
   it lands after `.section > .section-title` without touching the pairs. */
html.lang-en .hero h1,
html.lang-en .section-title { letter-spacing: -.02em; }

/* ==========================================================================
   1. THE HERO
   ========================================================================== */
/* .hero had NO position and NO overflow, and the customer <body> has no
   overflow-x guard at all. Both lines are mandatory before any scenery goes
   in, or the art paints over the copy and scrolls the page sideways at 360px.
   isolation keeps this z-index stack local, well below the nav's 1100. */
.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  padding: 4.6rem 1.4rem 5.4rem;
  background-color: var(--nd-sky-top);
  /* The old pools were centred `at 84% -12%` and `at 8% 112%` — both origins
     OUTSIDE the element, so only their tails rendered and the tonal range
     across the whole band was a handful of RGB points. It read as a flat fill
     because it functionally was one. The light now lives inside the box and
     has a name: a city pooling at the horizon low-right, a moon wash
     high-right, over a real top-to-bottom tonal arc. */
  background-image:
    radial-gradient(120% 86% at 72% 96%,
      rgba(var(--nd-glow), .38) 0%, rgba(16,55,90,.20) 40%, rgba(7,27,48,0) 70%),
    radial-gradient(58% 44% at 87% 7%, rgba(var(--nd-lamp), .10), transparent 70%),
    linear-gradient(180deg, var(--nd-sky-top) 0%, var(--nd-sky-mid) 46%, var(--nd-sky-low) 100%);
  box-shadow: inset 0 -1px 0 rgba(255,255,255,.06);
}
.hero-scene {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.hero-scene svg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
}
/* The grain must paint ABOVE the scene. As a `.hero::before` it would be the
   hero's first child box and would sit UNDER .hero-scene, whose first element
   is an opaque sky rect — invisible at exactly the widths where the gradient
   it de-bands is largest. As .hero-scene::after it is the last child of the
   scene and paints over the drawing. */
.hero-scene::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: .05;
  background-image: var(--nd-grain);
}
/* THE CONTRAST GUARANTEE. `to right`, not an angle: colour-stop percentages
   on a tilted gradient are positions along the gradient LINE, not along x, so
   an angled scrim does not deliver the floor its stop list appears to promise.
   Measured at the worst point of the copy column in the table below. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image:
    linear-gradient(to right,
      rgba(3,12,23,.90) 0%, rgba(3,12,23,.86) 46%,
      rgba(3,12,23,.46) 68%, rgba(3,12,23,0) 90%),
    linear-gradient(180deg, rgba(2,8,16,.55) 0%, rgba(2,8,16,0) 16%);
}
/* SIGNATURE MARK 1 — the road's centre line running off the page, closing the
   band. Replaces a 1px white inset that read as a hard rule between two
   rectangles. A pseudo-element, so it can never carry [hidden]; masked to fade
   at both ends so it does not read as a border. */
.hero::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  z-index: 4;
  pointer-events: none;
  background-image: var(--edge-lane);
  -webkit-mask-image: var(--edge-fade);
          mask-image: var(--edge-fade);
}
.hero-inner {
  position: relative;
  z-index: 3;
  /* 1.6fr/1fr put a 240px logo alone in a 400px column: 80px of dead navy
     inside it, 180px outside it. The container also widens, because the
     artwork now owns the outer margins and the copy no longer has to. */
  max-width: 1180px;
  grid-template-columns: minmax(0, 1.18fr) minmax(0, 1fr);
  gap: 3rem;
  align-items: center;
  min-height: 28rem;
}
.hero h1 { line-height: 1.04; }
.hero-sub { max-width: 44ch; }

/* ---------- the hero panel -----------------------------------------------
   The right column stops being a slot holding one object and becomes a
   content block: the mark and the 24/7 medallion locked together on one
   baseline as a SINGLE object, then the three facts that actually close this
   sale for someone about to hand a stranger the keys to a car they care
   about. Every string in it already appears verbatim on this page — no new
   commercial claim is introduced by a design pass.

   `.hero-badge` stays on the element on purpose: `.hero-badge .badge-247
   { display: inline-flex }` at ~140 is load-bearing, and without it
   .badge-247's own display:flex stretches the medallion into a bar. */
.hero-panel {
  text-align: left;
  padding: 1.5rem 1.5rem 1.35rem;
  border-radius: 20px;
  /* Contains the light pool behind the mark, whose radius deliberately
     overruns the plate's left edge. Nothing inside this panel is focusable
     (an <img>, three <div>s and a plain <ul>), so no focus ring can be
     clipped by it. */
  overflow: hidden;
  border: 1px solid rgba(255,255,255,.15);
  /* Near-opaque on purpose. Measured against the brightest pixel the scene
     can put behind it (the moon core), not against its average — the panel
     can land anywhere over the artwork once the scene is height-cropped. */
  background-image: linear-gradient(180deg, rgba(8,26,46,.94) 0%, rgba(4,14,26,.92) 100%);
  box-shadow: var(--hi-dark), 0 24px 56px -20px rgba(0,0,0,.80), 0 2px 6px rgba(0,0,0,.40);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero-panel { -webkit-backdrop-filter: blur(10px) saturate(1.1); backdrop-filter: blur(10px) saturate(1.1); }
}
.hero-mark { display: flex; align-items: center; gap: 1.1rem; }
/* `.hero-badge .aidd-logo { margin: 0 auto }` at ~137 would push the mark to
   the middle of a flex row. (0,3,0) takes it back. */
.hero-panel .hero-mark .aidd-logo { margin: 0; max-width: 100%; flex: none; }
/* .aidd-logo has never had a max-width, so a 240px hero mark stayed 240px at
   360. It is 150px here, but the guard belongs on the class. */
.aidd-logo { max-width: 100%; }
.hero-panel .badge-247 { margin-top: 0; padding: .6rem 1rem; }
.hero-panel .badge-247 .num { font-size: 1.7rem; }
.hp-rule { height: 1px; margin: 1.2rem 0 1.1rem; background: rgba(255,255,255,.16); }
.hero-proof { list-style: none; margin: 0; padding: 0; display: grid; gap: .8rem; }
.hero-proof li {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  gap: .8rem;
  align-items: start;
  font-size: .9rem; line-height: 1.55;
  color: rgba(255,255,255,.92);
}
.hero-proof svg { width: 22px; height: 22px; color: var(--accent-on-dark); }
/* A pool of light behind the mark, as pseudo-elements rather than baked into
   the scene — so it stays centred on the logo at any panel height, in either
   language. The logo artwork is near-black (#141414/#212121) on a navy band
   and has almost no separation of its own. */
.hero-mark { position: relative; }
.hero-mark > * { position: relative; z-index: 1; }
.hero-mark::before {
  content: '';
  position: absolute;
  left: 4.7rem; top: 50%;
  width: 15rem; height: 15rem;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
  background-image: radial-gradient(circle at 50% 50%,
    rgba(var(--nd-lamp), .22) 0%,
    rgba(var(--nd-glow), .14) 38%,
    rgba(var(--sh), 0) 72%);
}
.hero-badge .aidd-logo { filter: drop-shadow(0 12px 22px rgba(0,0,0,.55)); }

/* Headlight rim on the primary action — the only luminous EDGE in the band,
   so the eye finds it after the headline. Must be at least (0,2,0) to beat
   `.hero .btn, .section-dark .btn, .footer .btn { box-shadow: none }`, and it
   mirrors the selector list the depth layer already uses there. */
.nav .btn-orange, .hero .btn-orange, .footer .btn-orange {
  box-shadow: var(--hi-dark), 0 2px 10px rgba(0,0,0,.45),
              0 0 30px -6px rgba(var(--nd-lamp), .42);
}
/* The depth layer uses `outline` for focus, which does not participate in
   box-shadow — so any component with a custom shadow must restate it here or
   it visibly flattens the moment it is tabbed to. */
.nav .btn-orange:focus-visible, .hero .btn-orange:focus-visible,
.footer .btn-orange:focus-visible {
  box-shadow: var(--hi-dark), var(--elev-accent-hi),
              0 0 30px -6px rgba(var(--nd-lamp), .50);
}

/* ==========================================================================
   2. SIGNATURE BAND TRANSITIONS
   ========================================================================== */
/* The crest rises OUT of a dark band into the section above, painted in that
   band's own top colour, full-bleed. Absolute at bottom:100%, so it eats the
   previous section's bottom padding (56px desktop / 38px mobile against a
   34px / 22px crest) and can never reach its content. pointer-events:none so
   it cannot swallow the CTA that usually closes the preceding section.
   Gated on mask support: without the gate an unsupported browser paints a
   34px solid bar of the wrong colour right across the page. With it, there is
   simply no crest and the page falls back to the 1px lines it ships today. */
@supports ((-webkit-mask-image: linear-gradient(#000,#000)) or (mask-image: linear-gradient(#000,#000))) {
  .section-dark, .footer { position: relative; }
  .section-dark::before, .footer::before {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 100%;
    height: 34px;
    pointer-events: none;
    -webkit-mask-image: var(--edge-skyline);
            mask-image: var(--edge-skyline);
    -webkit-mask-size: auto 100%;    mask-size: auto 100%;
    -webkit-mask-repeat: repeat-x;   mask-repeat: repeat-x;
    -webkit-mask-position: 50% 100%; mask-position: 50% 100%;
  }
  /* Each band names its own top stop rather than inheriting a shared token:
     .section-dark's gradient starts at #143E69, the footer's at #04121F, and
     one inherited value would show a seam on one of them. */
  .section-dark::before { background-color: #143E69; }
  .footer::before { background-color: #04121F; }
  /* .section-dark > * gets auto side margins from ~200, and a positioned
     pseudo-element paints above the inline content of non-positioned in-flow
     siblings. Lift the real children so the crest can never land on a word. */
  .section-dark > * { position: relative; z-index: 1; }
  .footer-inner { position: relative; z-index: 1; }
}
/* SIGNATURE MARK 1 again, in ink, at the pale trench's top edge. .page-head is
   excluded: it is the top-of-page band on rates / drivers / profile and a lane
   rule there would sit directly under the nav. */
.section-orange:not(.page-head) { position: relative; }
.section-orange:not(.page-head)::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 2px;
  pointer-events: none;
  background-image: var(--edge-lane-ink);
  -webkit-mask-image: var(--edge-fade);
          mask-image: var(--edge-fade);
}
.section-orange:not(.page-head) > * { position: relative; z-index: 1; }

/* ==========================================================================
   3. THE NAV
   ========================================================================== */
/* The nav's gradient bottomed out at #071B30 — exactly .hero's base colour —
   so the top 655px of the page was one undifferentiated navy mass and the
   nav's navy-on-navy cast did nothing. The opaque base is now a genuinely
   lighter plate, which is the guaranteed floor; where backdrop-filter exists
   it upgrades to a translucent bar the hero artwork shows through, so nav and
   hero read as one composition instead of one slab. */
.nav {
  background-color: #0C2B47;
  background-image: linear-gradient(180deg, #10344F 0%, #0A2540 100%);
  border-bottom: 1px solid rgba(255,255,255,.12);
  box-shadow: 0 1px 0 rgba(var(--sh), .60), 0 10px 26px -10px rgba(var(--sh), .62);
}
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  .nav {
    background-color: rgba(9, 32, 54, .80);
    background-image: linear-gradient(180deg, rgba(20,54,84,.62) 0%, rgba(7,27,48,.72) 100%);
    -webkit-backdrop-filter: blur(16px) saturate(130%);
            backdrop-filter: blur(16px) saturate(130%);
  }
}

/* ==========================================================================
   4. CARD WEIGHT
   ========================================================================== */
/* --elev-2 and --elev-3 exist and are good, but on the marketing pages they
   were reachable ONLY through @media (hover:hover) — i.e. never on the phone
   this is read on. Every static card rendered at 5%/7% navy alpha against a
   ground within a few RGB points of its own bottom gradient stop. That is the
   mathematical definition of "plain white cards in a white void".
   Scoped to .card-grid children on purpose: this is a marketing-card problem,
   not a product-wide one, so the FAQ wall, the booking form cards and the
   trip rows are untouched. :not(.status-live) is load-bearing — the comment
   at the depth layer's mobile block says exactly why. */
.card-grid > .sticker-card:not(.status-live) { box-shadow: var(--elev-2); }
.section-orange .card-grid > .sticker-card:not(.status-live) { box-shadow: var(--elev-2), var(--hi); }

/* ==========================================================================
   5. HOW IT WORKS — a route, not three boxes
   ========================================================================== */
/* Every marketing card family now gets a night-navy header shelf, so a white
   card stops being a white box. Here the shelf carries lane dashes: three
   cards at the same shelf height read as one road, and the numbered disc
   straddles the shelf's bottom edge like a marker standing on it. Same idiom
   as `.prof-group > .rate-head:first-child`, which already bleeds a lit shelf
   to the card edge — reused rather than reinvented. */
.how-card {
  position: relative;
  overflow: hidden;               /* clips the shelf to the card radius */
  text-align: left;               /* was centre; the disc now anchors left */
  padding-top: 4.6rem;
}
.how-card::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 3.3rem;
  background-image:
    repeating-linear-gradient(90deg, rgba(255,255,255,.34) 0 20px, rgba(255,255,255,0) 20px 44px),
    var(--surface-accent);
  background-size: 100% 2px, 100% 100%;
  background-position: 0 50%, 0 0;
  background-repeat: repeat-x, no-repeat;
  box-shadow: inset 0 -1px 0 rgba(255,255,255,.14);
}
.how-card > * { position: relative; }
.how-card .how-num {
  position: absolute;
  top: 3.3rem; left: 1.6rem;
  margin: 0;
  transform: translateY(-50%);
  width: 46px; height: 46px;
  /* A navy disc on a navy shelf is invisible, so it takes a white collar. On
     the card half of the collar it is white-on-white and simply disappears,
     which is the intended read: a stud set into the shelf's edge. */
  box-shadow: 0 0 0 4px var(--white), var(--hi-dark), var(--elev-accent);
}
.how-card h3 { margin-bottom: .45rem; }
/* The connector across the gutters USED to be a 2px hairline pseudo-element
   here. It is now the full road surface, declared in the road layer at the end
   of this file — deleted rather than overridden, because the old rule carried a
   `transform: translateY(-1px)` that the replacement does not restate, so
   leaving it in place would have shifted the new band up by a pixel whenever
   the scroll-draw transform was not also in play. */

/* ==========================================================================
   6. TRUST STRIP — a band, not a fourth card grid
   ========================================================================== */
/* A 4-up row of white tiles directly under a 3-up row of white cards is a
   rhythm collision: the eye cannot find the module boundary. Same facts,
   different object. Scoped away from .section-dark, whose trust tiles are
   deliberately translucent-on-navy and keep their own treatment. */
.section:not(.section-dark) > .trust-strip {
  margin-top: 3rem;
  gap: 0;
  border-top: 1px solid var(--line-cool);
  border-bottom: 1px solid var(--line-cool);
}
.section:not(.section-dark) > .trust-strip .trust-item {
  border: 0;
  border-left: 1px solid var(--line-cool);
  border-radius: 0;
  background-image: none;
  background-color: transparent;
  box-shadow: none;
  padding: 1.15rem .9rem;
}
.section:not(.section-dark) > .trust-strip .trust-item:first-child { border-left: 0; }
@media (max-width: 700px) {
  .section:not(.section-dark) > .trust-strip { grid-template-columns: 1fr 1fr; }
  .section:not(.section-dark) > .trust-strip .trust-item:nth-child(odd) { border-left: 0; }
  .section:not(.section-dark) > .trust-strip .trust-item:nth-child(n+3) { border-top: 1px solid var(--line-cool); }
}

/* ==========================================================================
   7. ICONOGRAPHY — the brand's first drawn marks
   ========================================================================== */
/* Every icon in the product was a system emoji: Segoe UI Emoji on the owner's
   Windows, Apple colour emoji on the customer's iPhone — different cartoon
   artwork per device, none of it navy, and the only iconography the brand
   had. For a page selling calm premium safety to people handing over the keys
   to a car they care about, that is the most off-register thing on it.
   Replaced with monoline marks on one 24 grid at one stroke weight, drawn in
   currentColor so they inherit the existing colour system and need no new
   contrast work.
   NOTE: no `display` declaration — the mark is centred with line-height and
   text-align, because inline SVG is inline-level by default. That keeps this
   rule entirely out of hard rule 1's blast radius. */
.card-icon {
  width: 54px; height: 54px;
  line-height: 52px;
  text-align: center;
  margin-bottom: 1rem;
  border: 1px solid var(--line-cool);
  border-radius: 15px;
  background-image: var(--surface-tint);
  box-shadow: var(--hi), var(--elev-1);
  color: var(--blue);
}
.card-icon svg { width: 26px; height: 26px; vertical-align: middle; }
.btn-ico { vertical-align: -.2em; margin-right: .4rem; }
.chip-ico { vertical-align: -.18em; margin-right: .3rem; }

/* ==========================================================================
   8. RATE CARDS
   ========================================================================== */
/* A real header zone instead of starting with a floating line of text. The
   negative margins track .sticker-card's padding, so the mobile companion at
   the bottom of this block must always move with it — exactly as the existing
   .prof-group pair does. */
.sticker-card.rate-card > .rate-head:first-child {
  margin: -1.5rem -1.6rem 1.1rem;
  padding: .85rem 1.6rem;
  color: var(--ink);
  background-image: var(--surface-tint);
  border-bottom: 1px solid var(--line-cool);
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
  box-shadow: var(--hi);
}
/* The base fare is the number people came for. Emphasis is carried by the
   shelf, NOT by a wider grid track: `.card-grid:has(> :nth-child(3):last-child)`
   is (0,3,0) — :has() takes the specificity of its most specific argument —
   so a `.card-grid.rate-grid` width rule at (0,2,0) would silently lose. */
.sticker-card.rate-card.is-primary > .rate-head:first-child {
  color: #FFFFFF;
  background-image: var(--surface-accent);
  border-bottom-color: rgba(255,255,255,.16);
  box-shadow: var(--hi-dark);
}

/* ==========================================================================
   9. THE DRIVER BAND — a live orphan bug
   ========================================================================== */
/* The orphan guard at ~231 fires only for exactly THREE children
   (:has(> :nth-child(3):last-child)), and this band renders FOUR (confirmed in
   the rendered DOM). Four 260px tracks need 1126px and only ~1035 is
   available, so it falls to three and strands one driver alone on row two — on
   the band that carries the entire trust argument. Same idiom, for four. */
@media (min-width: 900px) {
  .card-grid:has(> :nth-child(4):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 600px) and (max-width: 899px) {
  .card-grid:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* And the driver card becomes a portrait object rather than a text card: the
   same night shelf as the how-cards, with the avatar straddling it. */
.driver-card {
  position: relative;
  overflow: hidden;
  padding: 0 1.15rem 1.25rem;
}
.driver-card::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 82px;
  background-image:
    radial-gradient(120% 150% at 50% 130%, rgba(var(--nd-glow), .55), rgba(var(--sh), 0) 70%),
    linear-gradient(180deg, #061A2E 0%, #0C2B49 100%);
  box-shadow: inset 0 -1px 0 rgba(255,255,255,.12);
}
.driver-card > * { position: relative; }
/* (0,3,0), to land after `.avatar:not(.avatar-any):not(img)` — also (0,3,0) —
   which sets the !important navy veil this depends on. */
.driver-card .avatar:not(.avatar-any) {
  width: 86px; height: 86px;
  margin: 40px auto .7rem;
  font-size: 1.7rem;
  box-shadow: 0 0 0 4px var(--white), 0 6px 16px -6px rgba(0,0,0,.55);
}

/* ==========================================================================
   10. MOTION
   ========================================================================== */
/* The motion budget is deliberately small. There is NO scroll parallax and no
   drifting headlight group: both would be transform animations on a
   full-bleed SVG, which no current engine composites — that is a repaint of
   the whole band every frame, forever, on the cheapest Android this service
   is opened on. The composition has to work standing still, and it does.

   THE STAR TWINKLE IS GONE — see the "star twinkle removal" note in the road
   layer at the very end of this file for the measured reason. Nothing in
   .hero-scene animates any more; the sky is a static field whose per-circle
   opacities are hand-varied in the markup instead. What is left in this layer
   is the scroll reveal on DOM elements (opacity + transform), which IS
   composited and which unobserves each element after it fires. */

/* Scroll reveal. `.js-reveal` is added to <html> BY THE SCRIPT, after it has
   confirmed IntersectionObserver exists and that reduced motion is off — so
   no script, an old browser, a CSP block or a parse error anywhere earlier all
   land in the same place: the selector never matches and every card renders at
   full opacity. There is no path in which a JS failure blanks the page. */
html.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .5s var(--ease) var(--rv-d, 0ms),
              transform .5s var(--ease) var(--rv-d, 0ms);
}
html.js-reveal [data-reveal].is-in { opacity: 1; transform: none; }

/* ==========================================================================
   11. MOBILE
   ========================================================================== */
/* A 1440x640 cinematic frame cannot show its width at 360px, and stretching it
   to fit (preserveAspectRatio="none") applies ~4x anisotropy that turns a
   horizon glow into a vertical blob. So the scene changes ROLE instead of
   scale: it becomes a horizon strip pinned to the hero's foot, cropped
   uniformly by xMidYMax slice, with the copy above it on the band's own
   three-layer sky — which is exactly why the light was moved INTO the element
   rather than left as two off-screen radials. */
@media (max-width: 900px) {
  .hero { padding: 2.8rem 1.2rem 1.6rem; }
  .hero-inner {
    grid-template-columns: minmax(0, 1fr);
    text-align: left;
    gap: 1.6rem;
    min-height: 0;
  }
  .hero-sub { margin: 0; }
  .hero-cta { justify-content: flex-start; }
  /* The rule at ~897 sets `.hero-badge { order: -1 }`, which put a fixed
     240px logo in front of the headline. (0,2,0) takes it back: the first
     screen is the promise, then the panel. */
  .hero .hero-badge { order: 2; }
  .hero-scene { top: auto; height: min(58vw, 224px); }
  .hero-scene::after { opacity: .04; }
  /* At one column the copy spans the full width, so a horizontal scrim cannot
     protect it. It becomes vertical, and holds >= .84 over the whole copy
     zone — measured in the table. */
  .hero::before {
    background-image:
      linear-gradient(180deg, rgba(3,11,20,.92) 0%, rgba(3,11,20,.88) 54%,
                              rgba(3,11,20,.50) 76%, rgba(3,11,20,.62) 100%);
  }
  .hero-panel { padding: 1.25rem 1.25rem 1.2rem; }
  /* HARD RULE 6, worked out rather than hoped for. At 360px the panel's
     content box is 360 - 38.4 (hero padding) - 40 (panel padding) = 281.6px.
     A 150px mark + a 1.1rem gap + the ~108px medallion is 275.6px — six
     pixels of slack, which is not a guarantee once the webfont swaps in and
     "24/7" gets its real Archivo Black metrics. The mark drops to 118px
     (total 243.6px) AND the row is allowed to wrap, so the panel cannot
     overflow even if the medallion measures wider than I calculate.
     (0,3,0), to beat `.aidd-logo { width: var(--logo-size) }`. */
  .hero-mark { flex-wrap: wrap; }
  .hero-panel .hero-mark .aidd-logo { width: 118px; }
  .hero-mark::before { left: 4.2rem; width: 12rem; height: 12rem; }
}
@media (max-width: 760px) {
  /* The crest must stay inside the previous section's 2.4rem (38.4px) bottom
     padding. Any future change to .section padding has to check this. */
  .section-dark::before, .footer::before { height: 22px; }
  .how-card { padding-top: 4.1rem; }
  .how-card::before { height: 2.9rem; }
  .how-card .how-num { top: 2.9rem; left: 1.1rem; width: 42px; height: 42px; }
  /* Mirrors .sticker-card's 1.5/1.6rem -> 1.2/1.1rem padding drop. */
  .sticker-card.rate-card > .rate-head:first-child {
    margin: -1.2rem -1.1rem 1rem;
    padding: .75rem 1.1rem;
  }
  .driver-card { padding: 0 1.1rem 1.1rem; }
  .driver-card::before { height: 72px; }
  .driver-card .avatar:not(.avatar-any) { width: 76px; height: 76px; margin: 34px auto .6rem; }
  /* At arm's length a 14px cast reads as haze. Steps back down, keeping the
     guard so the live-trip ring survives. */
  .card-grid > .sticker-card:not(.status-live),
  .section-orange .card-grid > .sticker-card:not(.status-live) { box-shadow: var(--elev-1); }
  .card-icon { width: 48px; height: 48px; line-height: 46px; }
  .card-icon svg { width: 23px; height: 23px; }
}

/* ==========================================================================
   12. REDUCED MOTION — keep last
   ========================================================================== */
/* The global block near ~601 kills `animation` and `transition` on
   *, *::before, *::after (which does reach SVG elements), but it does NOT
   zero `transform` — so every transform this layer introduces is zeroed here,
   and the reveal is forced back to visible. */
@media (prefers-reduced-motion: reduce) {
  html.js-reveal [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* ==========================================================================
   THE ROAD LAYER — carry the night-drive motif into the light half of the
   page, restore the two shadow blockers, and take the weight of the page off
   the weakest rung of the elevation ladder.

   THIS BLOCK MUST STAY LAST IN THE FILE. Later rules win here, so an override
   layer only works at the end. Anything appended after this silently beats
   it — if you need to add rules, add them INSIDE this block.

   Hard rules honoured (every one has shipped as a regression here before):
    1. No `display` declaration anywhere below. The only new boxes are
       ::before / ::after pseudo-elements and they cannot carry [hidden];
       `[hidden] { display: none !important }` at line 45 is untouched.
    2. body takes background-IMAGE only, never the `background` shorthand, so
       `word-break: keep-all` cannot be reached from here. In fact body is not
       touched at all below — only the --ground token it reads.
    3. No `#map` rule. Do not add one: it outranks .ride-map and collapses the
       full-screen booking map.
    4. The html.lang-* display:none !important rules are untouched.
    5. Contrast: every pair introduced below is in the measured table,
       including the worst overlap of the new asphalt hairlines.
    6. Nothing here can scroll the page sideways at 360px. The one animated
       strip that is WIDER than its box (.how-card::after) lives inside
       .how-card, which already carries `overflow: hidden` — that clip is
       load-bearing, not decorative.
    8. Every :hover below is inside @media (hover: hover) and (pointer: fine).
    9. Every animation and transform introduced here is disabled in the
       prefers-reduced-motion block at the very end of this file. There is no
       SMIL anywhere in this project.
   ========================================================================== */

/* ---------- BLOCKER 1: a pale card on a dark band casts a BLACK shadow ----
   `.card-grid > .sticker-card:not(.status-live)` is (0,3,0) — :not() carries
   the weight of its argument — so it beat `.section-dark .sticker-card` at
   (0,2,0) on weight alone, regardless of order, and painted the four driver
   cards with --elev-2: rgba(7,27,48,.06)/.13, i.e. NAVY, onto a navy band.
   Measured delta against #12395F: about 1.4 / 3.9 / 6.1 RGB. Nothing.
   These are (0,4,0) and take it back. Same for the 760px step-down at the
   bottom of this block, which had dropped it to --elev-1 (undetectable).
   No other pale-on-dark surface has the same collision: .driver-card .avatar
   already carries its own black cast at (0,3,0), .section-dark .trust-item is
   scoped away from the light .trust-strip rules by :not(.section-dark), and
   nothing else inside a dark band is a .sticker-card. */
.section-dark .card-grid > .sticker-card:not(.status-live) {
  box-shadow: 0 2px 6px rgba(0,0,0,.34), 0 16px 34px -12px rgba(0,0,0,.52);
}

/* ---------- new tokens ---------------------------------------------------- */
:root {
  /* THE GROUND GOES A SHADE DEEPER. #F4F6F9 sat 5/5/3 RGB away from the
     bottom stop of --surface (#F9FBFC), which is the mechanical reason white
     cards had nothing to sit on. #EBEFF5 is 20/12/7 away and still measures
     --ink 14.77:1, --ink-soft 5.20:1, --ink-dim 4.62:1, --red 4.60:1 — every
     one of them clear of 4.5. One step further (#E8EDF4) puts --ink-dim at
     4.53 and --red at 4.51, which is inside measurement noise, so this is the
     deepest ground this palette actually supports. */
  --ground: #EBEFF5;

  /* Asphalt tooth. Two crossing hairline sets rather than a noise tile: a
     filter-generated tile is not deterministic across engines and cannot be
     contrast-measured, and these can. Max overlap is .022 + .014 = .036 navy,
     measured over every stop of --surface-sunk in the table. */
  --nd-asphalt:
    repeating-linear-gradient(112deg, rgba(18,57,95,.022) 0 1px, rgba(18,57,95,0) 1px 6px),
    repeating-linear-gradient(68deg,  rgba(18,57,95,.014) 0 1px, rgba(18,57,95,0) 1px 11px);

  /* The lane dash, as a divider rule. Same mark as --edge-lane-ink, sized for
     a hairline that has to read on the page ground rather than on a band. */
  --edge-lane-rule: repeating-linear-gradient(90deg,
                      rgba(18,57,95,.42) 0 22px, rgba(18,57,95,0) 22px 48px);
  /* One dash period for the how-it-works road. The travelling strip is
     exactly one period wider than its card, so the loop is seamless. */
  --nd-dash: 44px;
}

/* ---------- CARD WEIGHT: stop resting on the weakest rung -----------------
   --elev-1 is 5% / 7% navy. On the marketing pages --elev-2 and --elev-3 were
   reachable ONLY through @media (hover: hover) — i.e. never on the phone this
   is read on at 2am. Every resting card now sits a rung higher, and the
   hairline steps from --line-cool to --line-cool-2 so the card has a real
   edge as well as a cast. .status-live keeps its ring:
   `.sticker-card.status-live` is (0,2,0) and beats the (0,1,0) below. */
.sticker-card { box-shadow: var(--elev-2); border-color: var(--line-cool-2); }
.card-grid > .sticker-card:not(.status-live) { box-shadow: var(--elev-3); }
.section-orange .card-grid > .sticker-card:not(.status-live) { box-shadow: var(--elev-3), var(--hi); }

/* ---------- LIGHT BANDS: asphalt, not flat paint -------------------------
   The pale trench keeps its recession (--surface-sunk, unchanged underneath)
   and gains a fine tooth over it. Restated at (0,2,0) so it lands after the
   depth layer's (0,1,0) `.section-orange { background-image: var(--surface-sunk) }`.
   .page-head is excluded for the same reason the lane rule excludes it: it is
   the top-of-page band and sits directly under the nav. */
.section-orange:not(.page-head) {
  background-image: var(--nd-asphalt), var(--surface-sunk);
}
/* SIGNATURE MARK 1 again, closing the band at its foot the way ::before opens
   it at its head — so the pale band reads as a stretch of road between two
   lane rules instead of a rectangle between two hairlines. */
.section-orange:not(.page-head)::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  pointer-events: none;
  background-image: var(--edge-lane-ink);
  -webkit-mask-image: var(--edge-fade);
          mask-image: var(--edge-fade);
}

/* Lane dashes instead of plain rules on the trust band. This is the one
   divider on the light half of the page that spans the full measure, so it is
   the right place for the mark. (0,3,0), later than the (0,3,0) rule in the
   art layer, so it wins on order. */
.section:not(.section-dark) > .trust-strip {
  border-top: 0;
  border-bottom: 0;
  background-image: var(--edge-lane-rule), var(--edge-lane-rule);
  background-size: 100% 2px, 100% 2px;
  background-position: 0 0, 0 100%;
  background-repeat: repeat-x, repeat-x;
}

/* ==========================================================================
   HOW IT WORKS — an actual road running through the three steps
   ========================================================================== */
/* The art layer already gives each step a navy shelf with a lane dash across
   it. What was missing is the JOIN: the shelves stopped at each card edge, so
   three roads sat beside each other instead of one road passing through.
   .how-grid::before is the road surface itself, full width behind the row. An
   absolutely-positioned pseudo-element of a grid container is NOT a grid item,
   so it cannot disturb the layout; it paints before the (positioned) cards, so
   it shows only in the two gutters — which is exactly the join. */
.how-grid { position: relative; }

@media (min-width: 900px) {
  .how-grid::before {
    content: '';
    position: absolute;
    left: 0; right: 0; top: 0;
    height: 3.3rem;
    pointer-events: none;
    background-image: var(--surface-accent);
    box-shadow: inset 0 -1px 0 rgba(255,255,255,.14);
  }
  /* No dashes in the gutter: a gutter is 1.8rem (28.8px) and a dash period is
     44px, so it could only ever show a fragment — and a static fragment beside
     the travelling dashes inside the cards would read as a stutter. */
}

/* The card's own shelf loses its static dash LAYER (the travelling strip below
   replaces it) but keeps the navy. (0,1,1), later than the art layer's
   (0,1,1), so it wins on order. */
.how-card::before {
  background-image: var(--surface-accent);
  background-size: auto;
  background-position: 0 0;
  background-repeat: no-repeat;
}
/* MARK 1, travelling. One period wider than the card and clipped by
   `.how-card { overflow: hidden }`, so translating it by exactly one period is
   seamless AND can never reach the page's horizontal scroll — the strip has no
   way out of the card box. Static (and fully visible) until the observer says
   the road is on screen, so a JS failure costs the motion, never the mark. */
.how-card::after {
  content: '';
  position: absolute;
  left: calc(var(--nd-dash) * -1); right: 0;
  top: 1.65rem;
  margin-top: -1px;
  height: 2px;
  z-index: 1;
  pointer-events: none;
  background-image: repeating-linear-gradient(90deg,
    rgba(255,255,255,.40) 0 20px, rgba(255,255,255,0) 20px var(--nd-dash));
}
/* The numbered stud straddles the shelf, so it has to sit ABOVE the dash it
   stands on. Without this the lane paints straight across the disc. */
.how-card .how-num { z-index: 2; }

@keyframes nd-lane { from { transform: translateX(0); } to { transform: translateX(var(--nd-dash)); } }
/* `.is-driving` is put on and taken OFF by an IntersectionObserver, so this
   never runs while the section is off screen. transform only — the strip is
   composited, and the light section it lives in has no backdrop-filter above
   it anywhere, which is the whole reason the motion budget was moved here
   from the hero sky. */
.how-grid.is-driving .how-card::after {
  animation: nd-lane 2.6s linear infinite;
}
/* The road draws itself once, the first time it is scrolled to. Guarded on
   html.js-reveal exactly like the existing reveal system: no script, no
   IntersectionObserver, a CSP block or a parse error all land in the same
   place — the selector never matches and the road is simply already there. */
@media (min-width: 900px) {
  html.js-reveal .how-grid::before {
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform .85s var(--ease) .1s;
  }
  html.js-reveal .how-grid.is-drawn::before { transform: scaleX(1); }
}

/* Below 900px the cards stack, so the road stands up: a dashed centre line
   dropped through the gutters on the axis of the numbered studs. It paints
   under the cards, so it is visible in the two gaps only and can never cross a
   word. The x offset tracks .how-num's own left plus half its width, which the
   art layer steps down at 760px — both values move together or the line comes
   off the studs. */
@media (max-width: 899px) {
  .how-grid::before {
    content: '';
    position: absolute;
    left: calc(1.6rem + 21px);
    top: 1.65rem; bottom: 0;
    width: 4px;
    pointer-events: none;
    background-image: repeating-linear-gradient(180deg,
      var(--nd-route) 0 13px, rgba(18,57,95,0) 13px 28px);
  }
}
@media (max-width: 760px) {
  .how-grid::before { left: calc(1.1rem + 19px); top: 1.45rem; }
  .how-card::after { top: 1.45rem; }
}

/* ==========================================================================
   HEADLIGHT SWEEP ON THE PRIMARY ACTION
   ========================================================================== */
/* The one piece of driver motion attached to an intent rather than to the
   page: a lamp crossing the CTA when a real pointer lands on it. The sweep is
   a z-index:-1 pseudo-element inside an `isolation: isolate` stacking context,
   so it paints ABOVE the button's own fill and BELOW its label — the type is
   never washed out by it. Even so the peak is measured: the lightest pixel
   this can produce is 26% of --accent-on-dark over the top stop of
   --surface-accent, i.e. #346595, and white on that is 6.10:1.
   `overflow: hidden` clips it to the button radius; an outline is not clipped
   by overflow, so the focus ring is unaffected.
   position:relative here is (0,1,0) and cannot disturb
   `.sheet > .btn-block:last-child { position: sticky }` at (0,2,0). */
.btn-orange {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.btn-orange::after {
  content: '';
  position: absolute;
  z-index: -1;
  top: -60%; bottom: -60%;
  left: 0;
  width: 46%;
  pointer-events: none;
  transform: translateX(-190%) skewX(-18deg);
  background-image: linear-gradient(90deg,
    rgba(var(--nd-lamp), 0) 0%, rgba(var(--nd-lamp), .26) 50%, rgba(var(--nd-lamp), 0) 100%);
}
@media (hover: hover) and (pointer: fine) {
  .btn-orange:hover::after {
    transform: translateX(320%) skewX(-18deg);
    transition: transform .68s var(--ease);
  }
}
/* Keyboard gets the same feedback as the mouse. Deliberately NOT inside the
   hover gate: :focus-visible never fires from a tap. */
.btn-orange:focus-visible::after {
  transform: translateX(320%) skewX(-18deg);
  transition: transform .68s var(--ease);
}

/* ==========================================================================
   ICONOGRAPHY — the last of the system emoji
   ========================================================================== */
/* Same monoline 24-grid marks as .card-icon and .hero-proof, at the same
   stroke weight, in currentColor — so they inherit the colour system already
   in force and need no contrast work of their own. Critically NO `color` is
   set: on a selected .service-row / .pay-method the row's own --on-dark is
   inherited and the mark turns white with the label, which a hard-coded navy
   would not. No `display` declaration either — the mark is centred with
   line-height and vertical-align, exactly as .card-icon does. */
.service-icon, .pm-icon { line-height: 1; }
.service-icon svg, .pm-icon svg { width: 25px; height: 25px; vertical-align: middle; }
.seg-ico { vertical-align: -.22em; margin-right: .35rem; }
.opt-ico { vertical-align: -.22em; margin-right: .4rem; }

/* ==========================================================================
   MOBILE
   ========================================================================== */
@media (max-width: 760px) {
  /* At arm's length a 28px cast reads as haze, so the ladder steps down — but
     only by ONE rung now, not two. The (0,4,0) dark-band rule is restated
     because the art layer's (0,3,0) step-down would otherwise put a navy
     shadow back on a navy band at exactly the width the page is read at. */
  .sticker-card:not(.status-live),
  .section-orange .sticker-card:not(.status-live) { box-shadow: var(--elev-2); }
  .card-grid > .sticker-card:not(.status-live),
  .section-orange .card-grid > .sticker-card:not(.status-live) { box-shadow: var(--elev-2); }
  .section-dark .card-grid > .sticker-card:not(.status-live) {
    box-shadow: 0 1px 3px rgba(0,0,0,.34), 0 10px 22px -8px rgba(0,0,0,.50);
  }
}

/* ==========================================================================
   HERO PANEL — sized to its contents
   ========================================================================== */
/* The owner's note: "make the box smaller, there's too much empty space". The
   panel held a 150px mark, a medallion 1.1rem away from it, a rule with
   1.2rem of air above it and three short proof rows, inside 1.5rem of padding
   and a 519px grid column. Every one of those is now cut, and the box is
   capped and pushed to the outer edge of its column so it stops being a
   full-width slot with an object floating in it.
   The three proof rows and their l-en / l-zh pairs are untouched. */
.hero-panel {
  max-width: 23rem;
  margin-left: auto;
  padding: 1.05rem 1.15rem 1rem;
  border-radius: 16px;
}
/* The mark and the medallion lock as ONE object. */
.hero-mark { gap: .5rem; }
.hero-panel .badge-247 { padding: .42rem .8rem; border-radius: 14px; }
.hero-panel .badge-247 .num { font-size: 1.4rem; }
.hero-panel .badge-247 .hours, .hero-panel .badge-247 .days { font-size: .6rem; letter-spacing: .16em; }
.hp-rule { margin: .85rem 0 .8rem; }
.hero-proof { gap: .6rem; }
.hero-proof li { font-size: .88rem; line-height: 1.5; gap: .7rem; }
/* The light pool follows the mark down: its centre was set for a 150px logo
   (75px = 4.7rem) and has to move to 56px for a 112px one, or the panel lights
   up to the right of the mark instead of behind it. */
.hero-mark::before { left: 3.5rem; width: 12rem; height: 12rem; }

@media (max-width: 900px) {
  .hero-panel {
    max-width: 22rem;
    margin-left: 0;
    padding: .95rem 1rem;
  }
  /* HARD RULE 6, worked out rather than hoped for. At 360px the panel content
     box is 360 - 38.4 (hero padding) - 32 (panel padding) = 289.6px. A 96px
     mark + a .5rem gap + the ~84px medallion is 188px, less than two thirds of
     it, and .hero-mark keeps flex-wrap:wrap from the art layer as the backstop
     for the moment the webfont swaps in and "24/7" gets its real metrics.
     (0,3,0), to beat `.aidd-logo { width: var(--logo-size) }`. */
  .hero-panel .hero-mark .aidd-logo { width: 96px; }
  .hero-mark::before { left: 3rem; width: 10rem; height: 10rem; }
}

/* ==========================================================================
   PRINT — the booking receipt
   ========================================================================== */
/* `@media print { .sticker-card { box-shadow: none } }` lives at line ~596 at
   (0,1,0), and EVERY card rule since — the depth layer's --elev-1, the art
   layer's --elev-2 — has been later and equal-or-higher, so the receipt has
   been printing its shadows for two design passes. Raising the resting rung
   makes that louder rather than introducing it, so it is fixed here: these
   are the last box-shadow declarations any card can match, and they match at
   every specificity the file uses. The road and the sweep are removed
   outright — a navy band and a lamp gradient are ink on somebody's receipt. */
@media print {
  .sticker-card,
  .sticker-card:not(.status-live),
  .card-grid > .sticker-card:not(.status-live),
  .section-orange .sticker-card:not(.status-live),
  .section-orange .card-grid > .sticker-card:not(.status-live),
  .section-dark .card-grid > .sticker-card:not(.status-live) { box-shadow: none; }
  .how-grid::before, .how-card::before, .how-card::after,
  .btn-orange::after, .section-orange:not(.page-head)::after { content: none; }
}

/* ==========================================================================
   REDUCED MOTION — KEEP LAST IN THE FILE
   ========================================================================== */
/* The global block near line 601 kills `animation` and `transition` on
   *, *::before, *::after, but it does NOT zero `transform` — so a transform
   that is only ever set inside a :hover or an .is-* class would still JUMP to
   its end state instead of easing there. Everything this layer moves is
   therefore pinned explicitly. */
@media (prefers-reduced-motion: reduce) {
  .how-grid.is-driving .how-card::after { animation: none !important; }
  html.js-reveal .how-grid::before,
  html.js-reveal .how-grid.is-drawn::before { transform: none !important; }
  .btn-orange::after,
  .btn-orange:hover::after,
  .btn-orange:focus-visible::after {
    transform: translateX(-190%) skewX(-18deg) !important;
  }
}

/* --------------------------------------------------------------------------
   POST-VERIFICATION CORRECTIONS — keep last.
   -------------------------------------------------------------------------- */

/* --ground was deepened to #EBEFF5 so white cards had something to sit on, but
   --surface-sunk (the pale band) was not re-derived with it. Its bottom stop
   #EFF3F8 is LIGHTER than the new ground on every channel, so the band that is
   documented as a recessed trench inverted into a ridge over its lower half.
   Re-cut the ramp so every stop stays at or below the ground. */
:root {
  --surface-sunk: linear-gradient(180deg, #DCE4EE 0%, #E2E9F1 26%, #E7ECF3 100%);
}

/* The sticky nav is translucent with backdrop-filter: blur(16px), and it
   overlays EVERY section — so the travelling lane dashes on the how-it-works
   road forced a full-width backdrop re-blur on every animation frame, forever,
   for any viewport where the two overlap. That is the same trap the star
   twinkle was removed for; it had simply moved. The opaque gradient below is
   the fallback this design already ships to browsers without backdrop-filter,
   it looks essentially identical over a dark page, and it makes the nav a
   static layer that no page animation can invalidate. Cheaper than the frost
   is worth. */
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  .nav {
    background-color: #0C2B47;
    background-image: linear-gradient(180deg, #10344F 0%, #0A2540 100%);
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
  }
}

/* The airport rate chart is label + 3 price columns. At phone widths it
   overflowed its .table-scroll wrapper, so the prices — the entire point of the
   table — could only be reached by swiping sideways. Fixed layout with a capped
   label column and tighter type makes all four columns fit inside 320px, so the
   scroll wrapper stays as a safety net and never actually engages.
   Worst case measured at 320px: label 34% (109px) + 3 x ~46px = 247px. */
@media (max-width: 560px) {
  .rate-table { table-layout: fixed; }
  .rate-table th,
  .rate-table td { padding: .45rem .22rem; }
  .rate-table th:first-child,
  .rate-table td:first-child { width: 34%; }
  .rate-table th { font-size: .68rem; letter-spacing: 0; }
  .rate-table td { font-size: .82rem; }
  /* A price must never wrap mid-figure. */
  .rate-table td.red { white-space: nowrap; font-size: .84rem; }
}
@media (max-width: 380px) {
  .rate-table th { font-size: .62rem; }
  .rate-table td.red { font-size: .78rem; }
  .rate-table th, .rate-table td { padding: .4rem .16rem; }
}


/* ==========================================================================
   MOBILE PASS — KEEP LAST IN THIS FILE.

   Everything below overrides a rule that appears EARLIER in this stylesheet at
   equal or higher specificity, so it only works from the end of the file.
   Media queries add no specificity, which is why two of the defects below
   existed at all: `.faq { padding: 0 }` at ~line 566 was silently beaten by
   `.sticker-card { padding: 1.2rem 1.1rem }` inside the <=760px block at
   ~line 936. If this block is moved above either of those, both regress.

   Constraints honoured, deliberately:
     * No `display` declaration on anything that can carry [hidden], and none
       on .overlay / .l-en / .l-zh. The language system is untouched.
     * `body { word-break: keep-all }` and the body `background` shorthand are
       not restated.
     * No `#map` / `#track-map` id rule.
     * Every :hover here is a NEUTRALISER inside @media (hover: none) — the
       same pattern as the guard block at ~line 450. No new hover EFFECT is
       introduced for any pointer.
     * Nothing here animates or transforms, so the reduced-motion block above
       has nothing new to cover.
   ========================================================================== */

/* --- 1. BLOCKER: /rates/ scrolled the whole page sideways below 352px ------
   `.card-grid.two` is `repeat(auto-fit, minmax(320px, 1fr))`, and a track
   whose MINIMUM is 320px cannot shrink below 320px. At 320px the .section
   content box is 320 - 2rem = 288px, so the single track was 32px wider than
   its container — and the customer <body> has no overflow-x guard to catch it
   (only body.ops-app does). `min(320px, 100%)` keeps the 320px intent at every
   width where 320px actually fits and floors at the container below that.
   This is the ONLY use of .card-grid.two in the product (rates.html line 11,
   six cards, so the :has(:nth-child(3):last-child) guard never fires). */
.card-grid.two {
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}

/* --- 2. BLOCKER: the rating widget pushed /trips/<ref>/ sideways -----------
   `.stars-form` is flex with no wrap and `.star-btn` has no basis, so its
   automatic minimum size is a full-width star glyph (1.6rem = 25.6px) plus
   .8rem of padding each side plus a 3px border = 57.2px, none of it
   shrinkable. Five of those plus four .6rem gaps is 324.4px against a 250.8px
   card at 320px — 73.6px of document overflow on the one screen the app asks
   the customer to act on after a trip.
   At 320px the row below is 5 x 45.0px + 4 x 6.4px = 250.6px inside 250.8px,
   and the content box is 45.0 - 6.4 - 6 = 32.6px against a 25.6px glyph, so
   nothing overflows the button either. Each button stays ~45 x 62px. */
@media (max-width: 460px) {
  .stars-form { gap: .4rem; }
  .stars-form .star-btn { flex: 1 1 0; min-width: 0; padding: .5rem .2rem; }
}

/* --- 3. The FAQ wall gained card padding it was never meant to have -------
   Cascade, not geometry: .faq (0,1,0) at ~566 lost to .sticker-card (0,1,0)
   at ~936. On the phone the summary row was inset 40px instead of 22.4px, the
   open question's navy fill stopped bleeding to the card edge, and
   `.faq { overflow: hidden }` had nothing left to clip. Restated at (0,2,0)
   so source order is not the only thing holding it. */
@media (max-width: 760px) {
  .sticker-card.faq { padding: 0; }
}

/* --- 4. /book/ — the sheet's primary action sat in the home indicator ------
   A sticky box is positioned against the SCROLLPORT, which is the scroll
   container's PADDING box. `bottom: 0` therefore pinned "CONFIRM & PAY"
   straight through the `env(safe-area-inset-bottom)` padding the sheet
   declares precisely to stay clear of the iPhone home indicator, putting ~34px
   of a ~57px button inside the swipe-up gesture zone. Matched at (0,3,0). */
.sheet > .btn-block:last-child {
  bottom: env(safe-area-inset-bottom, 0px);
}

/* --- 5. The hamburger drawer is the only navigation below 1000px ----------
   Each link was a bare 28.6px text run shrink-wrapped by `align-items:
   flex-start` — under WCAG 2.5.8's 24px and far under the 44px platform
   guidance, six of them on a 43px pitch, for a one-handed 2am user. Height
   becomes 23.56 + 20.8 + 3 = 47.4px across the full bar. NO `display`
   declaration: .nav-links keeps its own display:none / .open display:flex. */
@media (max-width: 1000px) {
  .nav-links { gap: .35rem; }
  .nav-links a:not(.btn) { padding: .65rem 0; width: 100%; }
}

/* --- 6. Touch targets that were sized for a mouse -------------------------
   All gated on `pointer: coarse` so desktop density is untouched. None of
   these declares `display`; a <button> centres its anonymous content box, so
   min-height alone centres the label.
     .chip       23-27px  (airport + saved-place quick row on /book/)
     .mini-btn   32x32    (#use-current, .7rem from the pickup caret)
     .back-btn   40x40    (the ONLY exit from booking steps 2-5 and from the
                           driver-picker overlay — no scrim tap, no swipe)
     .btn-small  39.6x31.8 in .place-list (destructive: deletes a saved place)
   .addr-row at 320px with a 44px .mini-btn: sheet content 278.4 - 25.6
   (.addr-block padding) - 2 (border) = 250.8; dot 12 + gap 11.2 + button 44
   + gap 11.2 = 78.4, leaving 172.4px for an input that is flex:1;min-width:0.
   No overflow. */
@media (pointer: coarse) {
  .chip { min-height: 44px; padding-left: .95rem; padding-right: .95rem; }
  .mini-btn { width: 44px; height: 44px; }
  .back-btn { width: 44px; height: 44px; }
  .place-list .btn-small { min-height: 44px; min-width: 44px; }
  /* The EN / ZH switch decides which language the whole product renders in and
     was the smallest thing in the bar at 40.4 x 28.0px. .lang-toggle is
     inline-flex, so its anchors are already blockified flex items and
     min-width/line-height apply to them directly. line-height carries the
     vertical centring, so no `display` is needed and the .on pill still fills
     its half of the toggle. */
  .lang-toggle a {
    min-width: 46px; line-height: 44px; text-align: center;
    padding-top: 0; padding-bottom: 0;
  }
}

/* --- 7. Footer: three competing alignments on every phone -----------------
   The <=900px block re-centres .footer-contact's TEXT but never resets the
   flex column's `align-items: flex-end` (~line 400), so the WhatsApp button,
   the Call button and both footer notes hugged the RIGHT edge under a centred
   heading. And .badge-247 is a block-level flex container with no width, so
   once .footer-inner collapsed to one column the 24/7 medallion stretched to
   the full 275.2px footer — 235% of its ~117px max-content — which is exactly
   the failure the `.hero-badge .badge-247` guard at ~line 140 exists to stop,
   in the one place it was not applied. Width + margin, never `display`. */
@media (max-width: 900px) {
  .footer-contact { align-items: center; }
  .footer .badge-247 {
    width: max-content; max-width: 100%;
    margin-left: auto; margin-right: auto;
  }
}

/* --- 8. /login/ — the phone field pushed the page sideways at 320px -------
   `.phone-input` is a flex row of a nowrap prefix (~77.6px) and a text input
   whose automatic minimum size is Chrome's default size=20 control width
   (~187.6px at the 16px mobile font-size), against a 250.8px card. No flex
   item in that row declared min-width: 0. */
.phone-input input { flex: 1 1 auto; min-width: 0; }

/* --- 9. The airport rate table is re-narrowed ABOVE its rescue ceiling ----
   The fixed-layout fix in the POST-VERIFICATION block is capped at 560px on
   the assumption that the card only gets narrower as the viewport does. It
   does not: .card-grid.two flips to TWO columns at ~713px, halving the card.
   At 768px the card content box is 294.0px against a ~291.4px max-content
   table — 2.6px, i.e. inside font-metric noise (a webfont swap or a different
   Android system font tips it). Deliberately banded at 561px and up so the
   already-tuned <=560px and <=380px blocks above are NOT overridden by source
   order at the widths they were measured for.
   At 768px: label column 34% = 100px (needs 67.3 + 9.6 padding); three price
   columns of 64.7px (need "RM320" 42.1 + 9.6, header 47.5 + 9.6). */
@media (min-width: 561px) and (max-width: 820px) {
  .rate-table { table-layout: fixed; }
  .rate-table th,
  .rate-table td { padding: .45rem .3rem; font-size: .82rem; }
  .rate-table th:first-child,
  .rate-table td:first-child { width: 34%; }
  .rate-table th { font-size: .72rem; letter-spacing: 0; }
  .rate-table td.red { white-space: nowrap; }
}

/* --- 10. HARD RULE 8: two hovers shipped AFTER the (hover: none) guard ----
   `.star-btn:hover` and `.star-btn:active` are declared at ~lines 483-484 and
   the neutralising `@media (hover: none)` block is at ~line 450 — EARLIER, at
   equal specificity, so it loses. On a phone the last-tapped star therefore
   stayed painted as if selected after the finger lifted, on the one control
   whose entire job is to show which star you picked. Neutralised here, at the
   end, where it wins. `:not(:has(input:checked))` keeps the REAL selected
   state (`.star-btn:has(input:checked)`, ~line 1659) intact — without it this
   rule would repaint the star the customer actually chose. */
@media (hover: none) {
  .star-btn:hover:not(:has(input:checked)) {
    background: var(--cream); color: var(--orange);
  }
  .star-btn:active:not(:has(input:checked)) { transform: none; }
}

/* --- 11. / — the English hero kicker wrapped into a ragged chip -----------
   "24/7 . KUALA LUMPUR & SELANGOR" is 30 tracked-out Archivo Black caps,
   ~357px wide against 246.8px of text width at 320px, so it breaks after
   "LUMPUR &" and strands "SELANGOR" on a second line inside a bordered box. It
   does not overflow (shrink-to-fit caps it), so this is cosmetic — and
   shrinking the type is not an option: fitting one line needs ~9.6px even at
   .06em tracking. The wrap is made deliberate instead. Scoped html.lang-en so
   the Chinese pair (far shorter, fits everywhere) is never touched, and the
   l-en / l-zh spans are not restructured. */
@media (max-width: 380px) {
  html.lang-en .hero-kicker {
    text-align: center;
    line-height: 1.35;
    letter-spacing: .12em;
    padding-left: .7rem;
    padding-right: .7rem;
  }
}

/* --- 12. HARD RULE 8, the rest of it — found by the overflow/hover scanner,
   not by either audit. -------------------------------------------------------
   The `@media (hover: none)` guard at ~line 450 lists .chip / .btn /
   .service-row / .driver-pick / .star-btn / .ac-list li / .locate-row /
   .tip-chip, and it is the FIFTH line of the file's 3500 — so every hover
   declared by the palette override, the depth layer and the art layer since
   then is later, at equal or higher specificity, and defeats it. Several use
   !important, which nothing in that guard does.
   Audited one by one; only the ones below actually leave a visible state stuck
   on a phone after the finger lifts, and only the ones below can be restored
   to an exact resting value without a renderer:

     .btn-ghost:hover (~2136)  background-color 8% white !important, over a
        `background-color: transparent !important` rest — the ghost CTA on the
        hero stays washed after a tap.
     .nav-links a:not(.btn):hover (~1351)  border-bottom-color, over a
        `border-bottom: 3px solid transparent` rest — and below 1000px this
        drawer IS the navigation, so a stuck underline competes with the
        current-page signal on every page.
     .nav-toggle:hover (~1358)  a 10% white fill over `background: none` —
        the hamburger stays lit for the whole session.
     .btn-orange:hover / .seg.active:hover (~979, ~1053)  background-color and
        border-color, !important. The depth layer's later
        `background-image: var(--surface-accent) !important` repaints most of
        it, so the visible leak is the border and the colour showing at the
        gradient's edges — smaller than it looks, but it is the primary CTA.

   Restored to the exact resting declarations, at matching specificity and
   with matching !important. The dark-surface border rule is stated LAST
   because it and the bare rule are both (0,2,0) and source order decides. */
@media (hover: none) {
  .btn-ghost:hover { background-color: transparent !important; }
  .nav-links a:not(.btn):hover { border-bottom-color: transparent; }
  .nav-toggle:hover { background: none; }
  .btn-orange:hover, .seg.active:hover { background-color: var(--blue) !important; }
  .btn-orange:hover { border-color: var(--blue) !important; }
  .nav .btn-orange:hover, .hero .btn-orange:hover,
  .footer .btn-orange:hover, .section-dark .btn-orange:hover {
    border-color: rgba(255,255,255,.42) !important;
  }
}

/* --------------------------------------------------------------------------
   CUSTOMER FEATURE BATCH 2 — handshake digits, VIP chip, legs/waits rows.
   Appended after CONTAINER FITS (later CSS wins); nothing here touches
   .overlay / .l-en / .l-zh / #map, and no hover or motion is introduced.
   -------------------------------------------------------------------------- */

/* Handshake card: the customer's own last-4 digits, readable across a car park. */
.handshake-card { text-align: center; }
.handshake-digits {
  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
  font-size: clamp(2.6rem, 12vw, 4rem);
  font-weight: 900;
  letter-spacing: .28em;
  padding-left: .28em;            /* balance the trailing letter-spacing */
  line-height: 1.1;
  margin: .4rem 0 .6rem;
  color: var(--ink);
}

/* VIP chip sits next to the coin chip in the nav and reuses its colour rules. */
.vip-chip { letter-spacing: .04em; white-space: nowrap; }

/* Multi-leg / waiting rows inside the fare card: same .quote-line geometry,
   label may be long (two addresses) so it wraps; the amount never does. */
.quote-line.leg-line > span:first-child,
.quote-line.wait-line > span:first-child { overflow-wrap: anywhere; }
.quote-line.leg-line > span:last-child,
.quote-line.wait-line > span:last-child { white-space: nowrap; }

/* "Driver assigned automatically" on the confirm sheet: quiet, not a warning. */
.auto-assign-note { margin: -.2rem 0 .6rem; }

/* --------------------------------------------------------------------------
   CONTAINER FITS — keep last.
   -------------------------------------------------------------------------- */

/* The airport rate chart lives inside a .card-grid cell that is ~316px wide at
   ANY viewport, so gating its fit behind `@media (max-width: 560px)` fixed the
   wrong axis: on a desktop the query never fires and the table still overflows
   its card, which is exactly what the owner kept seeing. A table has to fit its
   CONTAINER, not the window — so this is unconditional. The media bands above
   still tune type size; only the layout mode is hoisted out of them. */
.rate-table { table-layout: fixed; width: 100%; }
.rate-table th:first-child,
.rate-table td:first-child { width: 34%; }
.rate-table td.red { white-space: nowrap; }
.rate-table th, .rate-table td { overflow-wrap: break-word; }

/* `.quote-line span:last-child` is a DESCENDANT selector, so besides the amount
   it also matched every .l-zh span — an .l-zh is by construction the last child
   of its label wrapper, while an .l-en never is. A nowrap Chinese label made
   /trips/<ref>/ 369px wide inside a 320px viewport, and only in Chinese, which
   is why it looked like a translation bug rather than a selector bug.
   Restrict the nowrap to the direct child it was meant for. */
.quote-line span:last-child { white-space: normal; }
.quote-line > span:last-child { white-space: nowrap; }

/* "≤60km" was breaking as "≤60k / m" once the column widths were fixed — a
   distance threshold is one token and must never split. */
.rate-table th { white-space: nowrap; }
/* With table-layout: fixed the three price columns share 66% of a ~281px card
   (~62px each). At the base .8rem a nowrap "≤100km" needs ~66px and clipped.
   Narrow the label column and drop the header a notch so every threshold fits
   at every width — the media bands below 560px tune it further from here. */
.rate-table th:first-child,
.rate-table td:first-child { width: 30%; }
.rate-table th { font-size: .7rem; padding-left: .25rem; padding-right: .25rem; }

/* VIP driver-choice row: #open-drivers is the sole control on that step and
   measured 23px tall. Pointer-gated so a finger gets a real target. */
@media (pointer: coarse), (hover: none) {
  .driver-choose-btn { min-height: 44px; padding-block: .55rem; }
}

/* Same avatar framing fix as ops.css: tall headshots in a circle must be
   anchored toward the top or object-fit:cover crops the head and chin. */
img.avatar, .avatar-lg, .driver-card img, .dp-avatar, .drv-avatar img { object-position: 50% 30%; }

/* --------------------------------------------------------------------------
   OWNER FEEDBACK ROUND — layout corrections. Keep last in style.css.
   -------------------------------------------------------------------------- */

/* #1 Hero panel: 1.18fr/1fr + a 3rem gap left the panel as a narrow column
   shoved to the far right edge of the grid. Give the two columns nearer
   weight, close the gap, and let the panel fill its column so it reads as a
   deliberate second half of the hero rather than a stray card. */
@media (min-width: 901px) {
  .hero-inner { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 2rem; }
  /* Owner round 2: 520px fixed left the EN card ~90px of dead air right of
     the proof lines and the ZH card even more (Chinese lines are shorter).
     Shrink-wrap the content instead — fit-content sizes each language to
     its own longest line; the cap only guards a translation growing. */
  .hero-panel { width: fit-content; max-width: min(520px, 100%); justify-self: center; }
}

/* #2 How-it-works: the numbered disc is absolutely positioned at top:3.3rem
   (46px tall, centred on that line -> its bottom edge is ~4.7rem), but the
   card's padding-top was 4.6rem — the heading started UNDER the disc. Push
   the content down so the heading clears the disc with air to spare. */
.how-card { padding-top: 5.4rem; }
@media (max-width: 760px) { .how-card { padding-top: 5rem; } }

/* #3 Footer medallion: 1.4fr / auto / 1.2fr put the "centre" column right of
   centre. Equal side columns centre it for real. */
.footer-inner { grid-template-columns: 1fr auto 1fr; }
@media (max-width: 900px) { .footer-inner { grid-template-columns: 1fr; } }

/* #5 "Pay first" card was text-align:center — the only centred card on a page
   of left-set ones, which read as a skew. */
.pay-banner { text-align: left; }
.pay-banner .btn { margin-left: 0; }

/* #9 ONE header contract for every surface (customer .nav, driver
   .drv-header, ops .ops-header, introducer .intro-header):
     brand LEFT · primary nav CENTRED in the remaining width · language toggle
     and actions RIGHT. Same bar height, same vertical centring, same gap.
   The three had drifted — one flex-start, one space-between, one wrapping —
   so the brand, nav and toggle landed in different places on each screen. */
.nav, .drv-header, .ops-header, .intro-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; min-height: 64px; padding-top: .55rem; padding-bottom: .55rem;
}
.nav .brand, .drv-brand, .ops-brand, .intro-brand { flex: 0 0 auto; }
.nav .lang-toggle, .drv-head-right, .intro-head-right { flex: 0 0 auto; margin-left: auto; }
/* "Centre of the leftover space" is not the centre of the bar when the brand
   (left) and the toggle/actions (right) have different widths — measured: the
   customer nav sat at x=801 and ops at x=834 on a 1400px viewport whose centre
   is 700. So on desktop the primary nav is taken OUT of the flex flow and
   pinned to the true centre of the bar; brand and toggle keep their edges. */
@media (min-width: 901px) {
  /* ops: the nav is its own element, so pin it to the bar's true centre. */
  .ops-header { position: relative; }
  .ops-nav {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    flex: none; width: max-content; max-width: 60%; justify-content: center;
  }
}
/* customer: the page links live in .nav-pages, pinned to the bar's true
   centre exactly like .ops-nav; .nav-links (toggle, chips, BOOK NOW) is
   pushed to the right edge. The customer bar collapses to the hamburger at
   1000px, so this desktop contract starts at 1001px, not 901px. */
@media (min-width: 1001px) {
  .nav { position: relative; }
  .nav-pages {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    display: flex; align-items: center; gap: 1.4rem;
    width: max-content; max-width: 50%;
  }
  .nav .nav-links { flex: 0 0 auto; margin-left: auto; }
}
/* drawer mode: the wrapper vanishes so the column sees one flat list */
@media (max-width: 1000px) {
  .nav-pages { display: contents; }
}
@media (max-width: 900px) {
  .nav .nav-links, .ops-nav { flex: 1 1 auto; }
}
@media (max-width: 760px) {
  /* on a phone the nav row wraps under the brand; keep brand left, toggle right */
  .nav, .drv-header, .ops-header, .intro-header { flex-wrap: wrap; }
  .ops-nav { justify-content: flex-start; }   /* scroller: first items must stay reachable */
}
