/* ==========================================================================
   BYD Caroline Springs — Lead Intelligence
   --------------------------------------------------------------------------
   Derived from BYD's own public sites, driven and measured rather than read
   about: bydautomotive.com.au (primary) and byd.com/en. Nothing is copied —
   no markup, stylesheet, script, font file or image.

   What their site actually is: a full-bleed page with a *floating* control
   bar over it, and navigation that lives behind a hamburger rather than in a
   permanent rail. That is the structural idea taken here. A rail that costs
   236px of every screen is enterprise-software furniture; BYD's navigation is
   somewhere you go.

   Also observed and taken: a contained rounded menu panel over a dimmed,
   blurred backdrop; chip styling; and the wide side-profile vehicle crop that
   keeps a model recognisable at thumbnail size.

   Observed and deliberately refused: their durations. The menu opens over 1s,
   the header fades over 1s. That is right for a page you scroll once and
   wrong for software someone drives all day — VYLI's drawer opens in 260ms.
   Also refused: 30-50px pill buttons on operational controls, carousels,
   ambient hero motion, and the AU site's Bootstrap-blue primary button.

   THEMING. One set of declarations serves both modes through `light-dark()`,
   switched by `color-scheme`. Each token carries a plain light value first as
   a fallback: on an engine without `light-dark()` the second declaration is
   invalid and dropped, so the app renders light rather than breaking. There
   is no second stylesheet and no duplicated palette.
   ========================================================================== */

/* --- Montserrat, self-hosted ----------------------------------------------
   BYD's own sites set Montserrat, and it is free to use: SIL Open Font
   License 1.1, upstream github.com/JulietaUla/Montserrat, verified against two
   independent sources on 10 September 2026 before a byte was added. The full
   licence text and the exact provenance of these two files are in
   `static/fonts/SOURCE.md` and `static/fonts/OFL.txt`.

   Two variable files, one per subset, each covering weight 400-800 — latin
   plus latin-ext, because customer names arrive from VirtualYard verbatim and
   carry diacritics. Served from this application; no request leaves the Mac.

   NOT bundled, and never to be: Gotham-Black and Conthrax, the two
   proprietary faces BYD's site also loads.

   `font-display: swap` so a cold cache shows Avenir Next immediately rather
   than a blank bar — the fallback stack is metric-similar enough that the
   swap does not throw the layout around. */
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/fonts/montserrat-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/fonts/montserrat-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

:root {
  /* --- ground and ink --------------------------------------------------- */
  /* The page ground is NOT the card surface. They were both #ffffff, which is
     why the app read as one flat sheet and why a translucent island would have
     had nothing to be translucent against. Light gets a cool off-white with a
     touch of the focus hue; dark already had the separation and keeps it. */
  --ground:        #f0f3f6;
  --ground:        light-dark(#f0f3f6, #141618);
  --surface:       #ffffff;
  --surface:       light-dark(#ffffff, #1c1e21);
  --surface-alt:   #f5f6f8;
  --surface-alt:   light-dark(#f5f6f8, #101214);
  --surface-sunk:  #f0f2f5;
  --surface-sunk:  light-dark(#f0f2f5, #101214);
  --border:        #e4e7ec;
  --border:        light-dark(#e4e7ec, #2c2f34);
  --hairline:      #e4e7ec;
  --hairline:      light-dark(#e4e7ec, #2c2f34);
  --hairline-soft: #eef0f3;
  --hairline-soft: light-dark(#eef0f3, #24272b);
  --hairline-strong: #c9cdd4;
  --hairline-strong: light-dark(#c9cdd4, #3c4046);

  --ink:           #1c1c20;
  --ink:           light-dark(#1c1c20, #f2f3f5);
  --ink-secondary: #5b5f66;
  --ink-secondary: light-dark(#5b5f66, #aeb2b9);
  --ink-muted:     #5b5f66;
  --ink-muted:     light-dark(#5b5f66, #aeb2b9);
  --muted:         #63676e;
  --muted:         light-dark(#63676e, #9ba0a7);
  --ink-faint:     #63676e;
  --ink-faint:     light-dark(#63676e, #9ba0a7);

  /* --- brand ------------------------------------------------------------
     Red is not BYD's interface colour: byd.com uses essentially none in its
     chrome and the AU site none at all. It stays on the mark, the primary
     action and the active nav indicator. */
  --brand:         #e91b21;
  --brand-hover:   #d3161c;
  --brand-hover:   light-dark(#d3161c, #f23a3f);
  --brand-pressed: #b91217;
  --brand-ink:     #c41219;
  --brand-ink:     light-dark(#c41219, #ff8a86);
  --brand-strong:  #a90f16;
  --brand-strong:  light-dark(#a90f16, #ff6b66);
  --brand-wash:    #fdecec;
  --brand-wash:    light-dark(#fdecec, #37151700);
  --brand-wash:    light-dark(#fdecec, #3a1416);
  --on-brand:      #ffffff;
  --accent:        var(--brand);
  --accent-strong: var(--brand-ink);
  --accent-wash:   var(--brand-wash);
  --on-accent:     var(--on-brand);
  --focus:         #1a6ee8;
  --focus:         light-dark(#1a6ee8, #7cb2ff);

  /* --- state ------------------------------------------------------------ */
  --success:  #0a7a52;  --success: light-dark(#0a7a52, #4ec49a);
  --warning:  #b54708;  --warning: light-dark(#b54708, #eda366);
  --danger:   #b42318;  --danger:  light-dark(#b42318, #ff8b7e);
  --positive: var(--success);
  --negative: var(--danger);
  --warn:     var(--warning);

  /* --- queue identity. Semantics, never branding. ----------------------- */
  --q-call-now:     #b42318; --q-call-now:     light-dark(#b42318, #ff8b7e);
  --q-call-now-bg:  #fdeceb; --q-call-now-bg:  light-dark(#fdeceb, #3a1a17);
  --q-call-today:   #b54708; --q-call-today:   light-dark(#b54708, #eda366);
  --q-call-today-bg:#fdf0e6; --q-call-today-bg:light-dark(#fdf0e6, #3a2515);
  --q-follow-up:    #175cd3; --q-follow-up:    light-dark(#175cd3, #8bb4f8);
  --q-follow-up-bg: #eaf1fd; --q-follow-up-bg: light-dark(#eaf1fd, #16233c);
  --q-monitor:      #5b5f66; --q-monitor:      light-dark(#5b5f66, #aeb2b9);
  --q-monitor-bg:   #eff1f4; --q-monitor-bg:   light-dark(#eff1f4, #26292e);
  /* The only queue colour that was ever the same value in both themes, and
     the only one that failed the contrast floor — 3.66:1 light, 3.97:1 dark
     against its own wash. Split like the other five: 4.96:1 and 6.13:1. It is
     still the quietest of the six, which is the point of it. */
  --q-excluded:     #66696f; --q-excluded:     light-dark(#66696f, #9ba0a8);
  --q-excluded-bg:  #f2f3f6; --q-excluded-bg:  light-dark(#f2f3f6, #1f2126);
  --q-win-back:     #6941c6; --q-win-back:     light-dark(#6941c6, #b699f0);
  --q-win-back-bg:  #f1ecfb; --q-win-back-bg:  light-dark(#f1ecfb, #251a3c);

  /* --- glass ------------------------------------------------------------
     One glass tier, spent only on chrome: the navigation island, the drawer,
     the settings panel and the scrims. Never on a lead row, a table or a
     metric — a glass surface has to have something worth seeing behind it.

     BYD's own edge is rgba(255,255,255,0.4). That reads on photography and
     disappears on an operational ground, so the light edge here is ink at 8%
     instead. Measured then adapted, which is how the rest of this file was
     arrived at too. */
  --scrim:      rgba(20, 22, 24, 0.42);
  --scrim:      light-dark(rgba(20, 22, 24, 0.42), rgba(0, 0, 0, 0.62));
  /* The navigation is a dropdown now, not a full-height panel taking the
     screen. It still needs a surface to catch a click outside, but dimming the
     page as hard as a modal would overstate what just happened. */
  --scrim-soft: rgba(20, 22, 24, 0.20);
  --scrim-soft: light-dark(rgba(20, 22, 24, 0.20), rgba(0, 0, 0, 0.45));
  /* The two alphas are not the same number, and the difference is measured
     rather than tasteful. Worst case is the brightest thing in the app passing
     under the shell — a white vehicle photograph — and the shell's own text
     having to survive it. Light at 0.55 composites to #959697 against ink at
     5.73:1. Dark at the same 0.55 composites to #808183, and #f2f3f5 on that is
     3.51:1: under the floor. At 0.68 it is #626365 and 5.42:1. So dark frosts a
     little harder, and both stay comfortably legible over anything. */
  --glass:      rgba(255, 255, 255, 0.55);
  --glass:      light-dark(rgba(255, 255, 255, 0.55), rgba(24, 26, 29, 0.68));
  --glass-edge: rgba(20, 22, 24, 0.10);
  --glass-edge: light-dark(rgba(20, 22, 24, 0.10), rgba(255, 255, 255, 0.14));
  /* The inner highlight along the top edge is what makes a translucent panel
     read as a pane of glass rather than a hole in the page. It is the one
     "skeuomorphic" thing here and it is doing real work. */
  --glass-highlight: rgba(255, 255, 255, 0.65);
  --glass-highlight: light-dark(rgba(255, 255, 255, 0.65), rgba(255, 255, 255, 0.09));
  /* A very slight vertical tint inside the surface, so the frosting is legible
     even where the page behind it is flat colour and there is nothing to
     diffuse. Without it the effect only exists over content. */
  /* `light-dark()` returns a <color>. It cannot wrap a gradient and it cannot
     wrap a number — the same trap that silently removed every shadow in this
     file until 10 September. So it goes *inside* the gradient, around each
     colour stop, and the brightness below is one value for both themes rather
     than an invalid declaration that drops the whole filter. */
  --glass-sheen: linear-gradient(180deg,
                   rgba(255, 255, 255, 0.30),
                   rgba(255, 255, 255, 0.04) 46%,
                   rgba(255, 255, 255, 0.12));
  --glass-sheen: linear-gradient(180deg,
                   light-dark(rgba(255,255,255,0.30), rgba(255,255,255,0.07)),
                   light-dark(rgba(255,255,255,0.04), rgba(255,255,255,0.012)) 46%,
                   light-dark(rgba(255,255,255,0.12), rgba(255,255,255,0.045)));
  --glass-blur: 30px;
  --glass-saturate: 1.9;
  --glass-brightness: 1.05;

  /* The login card is the one glass surface in the application that floats on a
     photograph rather than on the application's own ground, and a photograph
     can be anything: a black tyre and a lit horizon can sit behind the same
     word. So it frosts harder than the island, and the two alphas are
     arithmetic rather than taste.

     Light — worst case is the card over black, where white at alpha a
     composites to 255a. `--ink-muted` (#5b5f66, L .111) needs 4.5:1 as small
     text, which wants a background luminance of .676 — grey 215 — so a >= .84.
     At 0.86 the composite is grey 219 and muted reads 4.70:1; `--ink` reads
     12.2:1.

     Dark — worst case is the card over the brightest part of the sky, where
     (24,26,29) at alpha a over white composites to 255 - 231a. `--ink-muted`
     (#aeb2b9, L .46) wants that under grey 71, so a >= .80. At 0.84 the
     composite is grey 61 and muted reads 5.28:1; `--ink` reads 9.70:1.

     Blur averages the backdrop, so a real photograph never presents either
     extreme cleanly. These are the floor, not the expectation. */
  --login-glass: rgba(255, 255, 255, 0.86);
  --login-glass: light-dark(rgba(255,255,255,0.86), rgba(24,26,29,0.84));
  /* Less blur and no brightness lift: 30px and brightness(1.05) are tuned for
     flat operational colour, and both wash a photograph out. */
  --login-blur: 26px;
  --login-saturate: 1.35;

  /* --- type -------------------------------------------------------------
     Montserrat is now bundled (see the @font-face block above and
     static/fonts/SOURCE.md), so the first name in this stack finally resolves
     to a real file. Avenir Next stays behind it and still matters: it is what
     renders during `font-display: swap` and if the font ever fails to load. */
  --font: "Montserrat", "Avenir Next", "Avenir", "Century Gothic",
          ui-sans-serif, system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  --f-hero:    2.25rem;
  --f-display: 1.75rem;
  --f-title:   1.25rem;
  --f-body:    0.9375rem;
  --f-small:   0.8125rem;
  --f-micro:   0.6875rem;

  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;

  /* Two families, decided by role. byd.com measures ~1px on its own content
     surfaces; the AU site's 30px pills are chrome styling. So chrome gets the
     pill and content keeps the small radii — which is also how the earlier
     refusal of "30-50px pills on operational controls" stays true. */
  --radius-sm: 3px;
  --radius-md: 4px;
  --radius-lg: 8px;
  --radius:    var(--radius-md);

  --radius-xl:   20px;   /* floating cards */
  --radius-2xl:  24px;   /* overlay sheets */
  --radius-pill: 999px;  /* the island's own small controls */
  /* Exactly half `--island-h`, which is the whole trick and it is BYD's: at 60px
     tall this value is a perfect pill, and at any greater height the same value
     is a rounded card. So the shell can grow from bar to menu without anything
     animating its corners. Measured on their nav: 30px, unchanged in both
     states. Keep these two in step. */
  --radius-island: 30px;

  /* `light-dark()` returns a <color>, so it can only ever wrap the colour of a
     shadow — never the whole value. These four tokens used to read
     `light-dark(0 1px 2px …, none)`, which is not a colour, so every
     `box-shadow: var(--shadow-*)` in the file resolved to an invalid value and
     computed to `none`. The app has therefore had no shadows at all, in either
     theme, since the token system was written; the hover lifts on the metric
     tiles, the lead cards and the dashboard queues have never fired. Verified
     in the browser on 10 September 2026 before changing anything.
     The alpha is what varies by theme, so the alpha is what light-dark() wraps.
     A `transparent` colour is how "no shadow in dark" is expressed. */
  --shadow-sm: 0 1px 2px rgba(20, 22, 24, 0.06);
  --shadow-sm: 0 1px 2px light-dark(rgba(20,22,24,0.06), transparent);
  --shadow-md: 0 2px 6px rgba(20, 22, 24, 0.08), 0 12px 28px rgba(20, 22, 24, 0.08);
  --shadow-md: 0 2px 6px light-dark(rgba(20,22,24,0.08), rgba(0,0,0,0.30)),
               0 12px 28px light-dark(rgba(20,22,24,0.08), rgba(0,0,0,0.55));
  --lift: var(--shadow-sm);
  --lift-raised: var(--shadow-md);

  /* BYD's single signature shadow, 0 6px 24px rgba(0,0,0,0.2), counted six
     times across their homepage and essentially nothing else at that scale.
     Dialled to 0.16 because it sits on a light operational ground here rather
     than on a photograph. */
  --shadow-float:    0 6px 24px rgba(20, 22, 24, 0.16);
  --shadow-float:    0 6px 24px light-dark(rgba(20,22,24,0.16), rgba(0,0,0,0.55));
  --shadow-float-hi: 0 10px 40px rgba(20, 22, 24, 0.20);
  --shadow-float-hi: 0 10px 40px light-dark(rgba(20,22,24,0.20), rgba(0,0,0,0.65));

  /* The island. 78vw against BYD's measured 79.2%, capped where theirs caps.
     The old `--topbar-h` alias is gone with the bar it was named after —
     nothing had read it since the island landed. */
  --island-h:      60px;
  --island-top:    14px;
  --island-gutter: 12px;
  --island-w:      min(78vw, 1140px);

  /*  is gone: the navigation is not a drawer with a width of its
     own any more, it is a view inside the shell, and the shell is one width. */
  --shell-max: 1560px;
  /* Content is centred rather than run edge to edge, so the page reads as a
     composition with the island rather than an admin table under a bar. */
  --content-max: 1320px;

  /* --- motion ----------------------------------------------------------- */
  --ease-standard: cubic-bezier(0.4, 0, 0.25, 1);
  --ease-enter:    cubic-bezier(0.16, 0.8, 0.3, 1);
  --ease-exit:     cubic-bezier(0.4, 0, 1, 1);
  --motion-fast:     120ms;
  --motion-standard: 200ms;
  --motion-slow:     360ms;
  --motion-drawer:   260ms;
  /* The shell's own expansion, and the only slow thing in the application.
     BYD run theirs at 1s, which is right for a page you scroll once and wrong
     for software somebody drives all day; Cazsper approved 450-700ms for this
     one surface. Controls *inside* it keep `--motion-fast`. The curve is a
     plain decelerate with no overshoot — a shell that bounces reads as a toy. */
  --motion-shell:    520ms;
  --ease-shell:      cubic-bezier(0.22, 0.61, 0.24, 1);
}

/* --- reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
/* The UA's `[hidden] { display: none }` lives in the user-agent sheet, so any
   author rule that sets a display — `.drawer { display: flex }`, say — beats it
   and a hidden element renders anyway. Both shell views hit exactly that and
   rendered on top of each other. Author-level, and `!important` because the
   whole point is that it outranks whatever the element's own rule says. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--ground); color: var(--ink);
  font-family: var(--font); font-size: var(--f-body); line-height: 1.5;
  -webkit-font-smoothing: antialiased; font-feature-settings: "tnum" 1;
}
h1,h2,h3,h4 { margin: 0; font-weight: 620; letter-spacing: -0.015em; line-height: 1.25; }
p { margin: 0 0 var(--s3); }
/* `--accent-strong`, not `--accent`. The raw brand red is a *fill* colour;
   as 13-15px link text on the page ground it measures 4.06:1 in light and
   4.01:1 in dark, both under the 4.5:1 floor. The ink variant of the same red
   reads 5.47:1 and 7.97:1. Measured 11 September 2026 across every screen. */
a { color: var(--accent-strong); text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px;
  border-radius: var(--radius-md); }

/* --- the app shell: one object that grows ---------------------------------
   BYD Australia does not have a header and does not have a drawer. It has a
   single container with a 30px radius that animates its own height, so opening
   the menu reads as the bar unrolling rather than a tray sliding out from
   somewhere. Measured on their nav, 10 September 2026: `transition: height 1s`,
   `border-radius: 30px`, `overflow: auto scroll` — on the container itself.

   Taken here: the structure, the constant radius, and the character of the
   motion. Refused: the second. 520ms is the whole shell's expansion and it is
   the only slow thing in this application.

   The radius is the elegant part. 30px is exactly half the 60px bar, so one
   value is a perfect pill closed and a rounded card open, and nothing has to
   animate the corners. `overflow: hidden` then clips the growing content to
   those corners for free — which is also why the identity popover lives
   outside this element rather than inside it.

   The rail is transparent and takes no pointer events, so the page runs
   underneath the shell edge to edge and the space either side of it does not
   swallow a click. */
.shell-bar {
  position: sticky; top: 0; z-index: 55;
  display: flex; justify-content: center;
  padding: var(--island-top) var(--island-gutter);
  pointer-events: none;
}
/* Holds the shell and the popover on one width, so "same width as the island"
   is structural rather than two numbers that have to be kept equal. */
.shell-stack {
  position: relative; width: var(--island-w); pointer-events: auto;
}

.shell {
  width: 100%;
  border-radius: var(--radius-island);
  background-color: var(--glass);
  background-image: var(--glass-sheen);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate))
                   brightness(var(--glass-brightness));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate))
                           brightness(var(--glass-brightness));
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-highlight), var(--shadow-float);
  overflow: hidden;
  transition: box-shadow var(--motion-standard) var(--ease-standard);
}
/* Once the page has travelled under it the shell lifts a little further off it,
   and while it is open it carries the deeper shadow the extra height earns. */
.is-scrolled .shell,
.shell-open .shell { box-shadow: inset 0 1px 0 var(--glass-highlight), var(--shadow-float-hi); }

/* Backdrop blur is not universal. Without it the shell becomes an opaque
   surface rather than an unreadable one, and it keeps its own edge. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .shell, .who-pop { background-color: var(--surface); background-image: none; }
}

/* The bar itself carries no surface any more — the shell around it does. */
.island {
  height: var(--island-h);
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--s3); padding: 0 10px;
}

/* --- the growing part -----------------------------------------------------
   An explicit pixel height, set by app.js and transitioned. `max-height` with
   a guessed ceiling animates at the wrong speed for short content and
   `grid-template-rows: 0fr -> 1fr` cannot animate *between* two open heights,
   which is exactly what opening Settings from the menu is. Height is what BYD
   animate too, and this is one small element rather than a page of them. */
.shell__grow {
  height: 0; overflow: hidden;
  transition: height var(--motion-shell) var(--ease-shell);
}
.shell__panels { min-height: 0; }

/* Content arrives just behind the shell it is arriving in, never ahead of it. */
.shell-panel {
  overflow-y: auto; overscroll-behavior: contain;
  max-height: calc(100dvh - (var(--island-top) * 2) - var(--island-h) - var(--s3));
  padding: var(--s4) var(--s5) var(--s5);
  opacity: 0; transform: translateY(-6px);
  transition: opacity var(--motion-standard) var(--ease-standard),
              transform var(--motion-standard) var(--ease-enter);
}
.shell-open .shell-panel {
  opacity: 1; transform: none;
  transition-delay: 130ms;
}

.island__menu {
  grid-column: 1; justify-self: start;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 40px; flex: 0 0 auto;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--radius-pill); cursor: pointer; color: var(--ink);
  transition: background-color var(--motion-fast) var(--ease-standard);
}
.island__bars { display: block; width: 18px; }
.island__bars i {
  display: block; height: 1.5px; background: currentColor; border-radius: 1px;
  transition: transform var(--motion-standard) var(--ease-standard);
}
.island__bars i + i { margin-top: 4.5px; }
/* The bars spread on hover — moved into the pointer-guarded block below, so a
   tap on a phone does not leave them spread. */

.island__brand {
  grid-column: 2; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 0;
  justify-content: center;
  /* It is a link to Today, and it was a 14px-tall target in a 60px bar. */
  min-height: 44px;
  text-decoration: none;
}
/* BYD's own mark occupies about 120x46 of a 60px bar and carries no weight it
   does not need. Ours is a dealership name rather than a logo, so it keeps two
   lines — but at 700 rather than 800 and a little smaller, which is the
   difference between navigation branding and an internal tool shouting its own
   name. Capped at 260px so a long name truncates instead of pushing the cell. */
.island__mark {
  font-size: 0.75rem; font-weight: 700; letter-spacing: 0.085em;
  text-transform: uppercase; color: var(--ink); line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* `--f-micro`, not 0.625rem. 10px was the smallest text in the
   application and it sits in the chrome of every screen. */
.island__sub {
  font-size: var(--f-micro); font-weight: 500; color: var(--ink-faint);
  letter-spacing: 0.04em; line-height: 1.3; white-space: nowrap; margin-top: 2px;
}
.island__brand { max-width: 260px; }

/* The identity gate keeps the island's shape and loses its controls: the two
   empty side cells are still 1fr each, so the mark stays on the centre line. */
.island--bare { grid-template-columns: 1fr auto 1fr; }

.island__right {
  grid-column: 3; justify-self: end; min-width: 0;
  display: flex; align-items: center; gap: var(--s2);
}
/* A real button: it opens the same drawer the hamburger does, so the identity
   affordance behaves like one. Hover and focus have to say so. */
/* A 40x40 control, which is what BYD's own account button measures. It shows
   a generic person mark rather than initials — Cazsper's call, and it keeps
   the island minimal: the name belongs in the popover, where there is room to
   read it. */
.island__who {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  border: 1px solid transparent; border-radius: var(--radius-pill);
  background: transparent; font: inherit; cursor: pointer; color: var(--ink);
  transition: background-color var(--motion-fast) var(--ease-standard);
}
.island__avatar {
  width: 34px; height: 34px; flex: 0 0 auto; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-sunk); border: 1px solid var(--hairline);
  color: var(--ink-muted);
  transition: background-color var(--motion-fast) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}
/* Who is at the keyboard decides who every note is attributed to, so an
   unset identity is visibly unfinished rather than merely blank. With the
   chooser in front of the app this should now be a state nobody reaches, but
   it stays: a gate that fails open is worse than a plus sign. */
.island__who--unset .island__avatar {
  background: var(--brand-wash); border-color: var(--brand); color: var(--brand-ink);
}

/* --- the identity popover -------------------------------------------------
   Attached to the island, not a panel over the app: choosing who you are is a
   two-second correction, and opening the whole navigation to do it was the
   wrong shape. It posts to /identity like everything else. */
.island__right { position: relative; }
.who-pop {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 45;
  min-width: 216px; max-width: min(280px, calc(100vw - (var(--island-gutter) * 4)));
  padding: var(--s3);
  border-radius: var(--radius-xl);
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow-float-hi);
  transform-origin: 100% 0;
  transform: translateY(-6px) scale(0.97); opacity: 0;
  transition: transform var(--motion-standard) var(--ease-enter),
              opacity var(--motion-standard) var(--ease-standard);
}
.identity-open .who-pop { transform: none; opacity: 1; }
.who-pop__label {
  margin: 0 0 var(--s2); padding: 0 var(--s2);
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-faint); font-weight: 700;
}
.who-pop__list { display: flex; flex-direction: column; gap: 1px; }
.who-pop__person {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  padding: 9px var(--s2); border: 0; border-radius: var(--radius-lg);
  background: transparent; color: var(--ink); font: inherit;
  font-size: var(--f-small); font-weight: 600; text-align: left; cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-standard);
}
.who-pop__person[aria-current="true"] { color: var(--brand-ink); }
.who-pop__tick { margin-left: auto; font-weight: 700; }
.who-pop__empty {
  margin: 0; padding: 0 var(--s2) var(--s1);
  font-size: var(--f-small); color: var(--ink-muted);
}

/* --- the scrim -------------------------------------------------------------
   The shell is a dropdown, not a modal, so the page dims a little and no more.
   It exists mostly to catch a click outside. */
.scrim {
  position: fixed; inset: 0; z-index: 50;
  background: var(--scrim-soft); opacity: 0;
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  transition: opacity var(--motion-shell) var(--ease-shell);
}
.shell-open .scrim { opacity: 1; }

/* --- the two views inside the shell ---------------------------------------
   Neither is a panel any more. They are what the shell is currently showing,
   which is why only one is ever in flow: the growing height is the active
   view's height, so switching from the menu to Settings animates the shell
   further down rather than swapping one box for another. */
.drawer { display: flex; flex-direction: column; gap: var(--s5); }

.drawer__nav { display: flex; flex-direction: column; gap: 3px; }
.drawer__link {
  display: flex; align-items: center; gap: var(--s2);
  padding: 11px var(--s3); border-radius: var(--radius-lg);
  color: var(--ink); text-decoration: none; font-weight: 600;
  transition: background-color var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}
/* The current page is a filled row in brand wash rather than a 3px bar down
   its left edge. Same information, one less side-stripe: the app had four of
   them and they had stopped meaning anything individually. */
.drawer__link[aria-current="page"] { background: var(--brand-wash); color: var(--brand-ink); }
.drawer__count {
  margin-left: auto; font-style: normal; font-size: var(--f-micro); font-weight: 700;
  padding: 2px 7px; border-radius: var(--radius-pill);
  background: var(--surface-sunk); color: var(--ink-muted);
}

/* The parent of the page you are on: present, but not competing with it. */

/* Children of Admin, shown only while you are inside it. Indented to the
   width of the parent's text, quieter type, same hit area — a hierarchy, not
   a second nav. */
.drawer__sub-nav {
  display: flex; flex-direction: column; gap: 3px;
  margin: 3px 0 3px var(--s3); padding-left: var(--s3);
  border-left: 1px solid var(--hairline-soft);
}
.drawer__link--sub { font-weight: 600; font-size: var(--f-small); color: var(--ink-muted); }
.drawer__link--sub[aria-current="page"] { color: var(--ink); }

.drawer__foot {
  margin-top: auto; display: flex; flex-direction: column; gap: var(--s5);
  padding-top: var(--s5); border-top: 1px solid var(--hairline-soft);
}
.drawer__signout { justify-content: center; }

/* The foot resolves to three things, in the order the brief asked for: the
   operational door, who you are, then the way out beside the way in to
   settings. Sign out keeps the width it earns; the gear sits hard right,
   which is where a thumb reaches on a phone without fouling the bottom bar
   (the drawer's own bottom padding already clears the safe-area inset). */

/* Admin lives down here rather than in the list above: it is not a place a
   salesperson works. A hairline above it, and quieter type than the primary
   destinations, so it reads as a different kind of thing without becoming
   hard to find. */
.drawer__admin {
  display: flex; align-items: center; gap: var(--s2);
  padding: 10px var(--s3); margin: 0 calc(var(--s3) * -1);
  border-radius: var(--radius-lg);
  font-size: var(--f-small); font-weight: 600;
  color: var(--ink-muted); text-decoration: none;
  transition: background var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}
.drawer__admin[aria-current="page"] { background: var(--brand-wash); color: var(--ink); }
.drawer__admin--ancestor { color: var(--ink); }
/* The padlock states the gate rather than hiding the door. Hiding it would
   only mean a person who needs Admin cannot find where to ask for the PIN. */
.drawer__admin-lock {
  display: inline-flex; align-items: center; margin-left: auto;
  color: var(--ink-faint);
}

/* The identity picker, in the sidebar itself. Full width so the name is
   readable at a glance — this is the one control here whose current value
   matters more than its affordance. */
.drawer__identity { display: flex; flex-direction: column; gap: var(--s2); }
.drawer__who-label {
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-faint); font-weight: 700;
}
.drawer__identity-form { display: flex; gap: var(--s2); align-items: center; }
.drawer__identity-select { flex: 1 1 auto; min-width: 0; }
.drawer__who { margin: 0; font-size: var(--f-small); color: var(--ink-muted); }
.drawer__who strong { color: var(--ink); font-weight: 700; }
.drawer__who--unset { color: var(--ink-faint); }
.drawer__foot-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3);
}
.drawer__settings {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 36px; padding: 7px var(--s2);
  font-size: var(--f-small); font-weight: 600;
  color: var(--ink-muted); background: transparent;
  border: 1px solid transparent; border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}
.drawer__settings svg { flex: 0 0 auto; }

/* --- settings, one level deeper in the same shell --------------------------
   It used to fly in from the right, which made it read as an unrelated surface
   that happened to be opened from the menu. It is the shell's second view now:
   same object, same width, same glass, and the shell simply extends further
   down to hold it. The way out of it is Back, to the menu — closing the whole
   thing is still the hamburger, Escape or a click outside. */
.settings { display: flex; flex-direction: column; gap: var(--s5); }
.settings__back {
  display: inline-flex; align-items: center; gap: var(--s2);
  align-self: flex-start; padding: 6px 10px 6px 6px;
  margin-left: calc(var(--s2) * -1);
  border: 0; border-radius: var(--radius-lg); background: transparent;
  color: var(--ink-muted); font: inherit; font-size: var(--f-small);
  font-weight: 600; cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}

.settings__head { display: flex; align-items: flex-start; gap: var(--s3); }
.settings__title {
  flex: 1 1 auto; margin: 0;
  font-size: 0.9375rem; font-weight: 800; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--ink); line-height: 1.15;
}
.settings__body { display: flex; flex-direction: column; gap: var(--s4); }
.settings__section { display: flex; flex-direction: column; gap: var(--s2); }
.settings__label {
  margin: 0;
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-faint); font-weight: 700;
}
.settings__note { margin: 0; font-size: var(--f-small); color: var(--ink-muted); }
.settings__add { display: flex; gap: var(--s2); }
.settings__add input { flex: 1 1 auto; min-width: 0; }
.settings__facts {
  display: grid; grid-template-columns: auto 1fr; gap: 4px var(--s3);
  margin: 0; font-size: var(--f-small);
}
.settings__facts dt { color: var(--ink-faint); }
.settings__facts dd { margin: 0; color: var(--ink); }

/* --- appearance control -------------------------------------------------- */
.segmented {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px;
  padding: 2px; background: var(--surface-sunk);
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
}
.segmented button {
  padding: 7px 4px; font-size: var(--f-small); font-weight: 600;
  color: var(--ink-muted); background: transparent; border: 0;
  border-radius: var(--radius-sm); cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}
.segmented button[aria-checked="true"] {
  background: var(--surface); color: var(--ink); font-weight: 700;
  box-shadow: var(--shadow-sm);
}

/* --- main ----------------------------------------------------------------
   The island floats over the page rather than sitting on top of it, so the
   sticky rail above already reserves the vertical space and `main` only owns
   its own breathing room. Content is centred at `--content-max` instead of
   running to the viewport edge: the island is 1140px at its widest, and a
   table wider than the navigation above it reads as an accident. */
.main {
  min-width: 0; max-width: var(--content-max); margin: 0 auto;
  padding: var(--s5) var(--s5) var(--s8);
}
@media (min-width: 1200px) {
  .main { padding-left: var(--s6); padding-right: var(--s6); }
}
/* The per-page slot that used to live in the top bar. Nothing renders into it
   today, but the block is part of the template contract. */
.page-context:not(:empty) { margin-bottom: var(--s5); }

/* --- page header ---------------------------------------------------------- */
.page-head { display: flex; flex-wrap: wrap; gap: var(--s4);
  align-items: flex-end; justify-content: space-between; margin-bottom: var(--s5); }
/* A header carrying one control on the right. `flex-end` on the parent
   already bottom-aligns them; this only guarantees the two halves never
   collapse onto each other before they wrap. */
.page-head--split > :first-child { flex: 1 1 320px; min-width: 0; }
.page-head h1 { font-size: var(--f-display); }
.page-head__sub { color: var(--ink-muted); font-size: var(--f-small); margin-top: 2px; }
.page-head__actions {
  display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
  flex: 0 0 auto;
}
.page-head__hint { font-size: var(--f-micro); color: var(--ink-faint); }
@media (max-width: 720px) {
  /* Full-width action under the heading rather than squeezed beside it. */
  .page-head__actions { align-items: stretch; width: 100%; }
  .page-head__actions .btn { justify-content: center; }
  .page-head__hint { text-align: center; }
}

/* --- metric strip --------------------------------------------------------- */
.metrics { display: grid; gap: var(--s3); margin-bottom: var(--s5);
  grid-template-columns: repeat(auto-fit, minmax(min(148px, 100%), 1fr)); }
.metric {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: var(--s3) var(--s4);
  display: flex; flex-direction: column; gap: 2px; box-shadow: var(--lift);
}
.metric__value { font-size: 1.5rem; font-weight: 660; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; }

/* --- §5 KPI hierarchy ------------------------------------------------------
   Six numbers of equal weight is six numbers nobody reads. The two that start
   a phone call lead; the four that describe the yard support them. */
.metrics { grid-template-columns: repeat(2, minmax(0, 1fr)) repeat(4, minmax(0, 0.72fr)); }
.metric--primary {
  border-color: var(--hairline-strong);
  box-shadow: var(--lift-raised);
}
.metric--primary .metric__value { font-size: 2.125rem; font-weight: 680; }
.metric--primary .metric__label { color: var(--ink); font-weight: 620; }
/* Call now is the first number the eye should land on. */
.metric--call-now { border-top: 3px solid var(--q-call-now); }
.metric--call-now .metric__value { color: var(--q-call-now); }
.metric--call-today { border-top: 3px solid var(--q-call-today); }
.metric--secondary { background: transparent; box-shadow: none;
  border-color: var(--hairline-soft); }
.metric--secondary .metric__value { font-size: 1.1875rem; font-weight: 620;
  color: var(--ink-muted); }
.metric--secondary .metric__label { font-size: var(--f-micro); }
.metric--secondary[data-zero="true"] .metric__value { color: var(--ink-faint); }
.metric__label { font-size: var(--f-small); color: var(--ink-muted); }

/* --- queue tabs ----------------------------------------------------------- */
.queues { display: flex; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s5); }
/* BYD's own tab logic at operational scale: a bordered pill that fills to ink
   when it is the current one. Their marketing version is 44px tall with a 30px
   radius and a 2px border; this is 36px with the content radius and one
   hairline, because a filter above a list is not a call to action.

   Colour deliberately stays out of the tab. The badges in the rows carry the
   queue identity, and a row of five coloured tabs would compete with them. */
.queue-tab {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 36px; padding: 0 var(--s3);
  border: 1px solid var(--hairline-strong); border-radius: var(--radius-lg);
  background: transparent; color: var(--ink-muted);
  font-size: var(--f-small); font-weight: 600; text-decoration: none;
}
.queue-tab[aria-current="true"] {
  background: var(--ink); border-color: var(--ink); color: var(--ground);
}
.queue-tab__n {
  font-size: var(--f-micro); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}
.queue-tab[aria-current="true"] .queue-tab__n { color: var(--ground); opacity: 0.7; }

.qbadge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--f-micro); font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 3px 8px; border-radius: var(--radius-pill);
  border: 1px solid currentColor;
}
.qbadge::before { content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: none; }
.q-call_now   { color: var(--q-call-now);   background: var(--q-call-now-bg); }
.q-call_today { color: var(--q-call-today); background: var(--q-call-today-bg); }
.q-follow_up  { color: var(--q-follow-up);  background: var(--q-follow-up-bg); }
.q-monitor    { color: var(--q-monitor);    background: var(--q-monitor-bg); }
.q-excluded   { color: var(--q-excluded);   background: var(--q-excluded-bg); }
.q-win_back   { color: var(--q-win-back);   background: var(--q-win-back-bg); }

/* --- lead cards ----------------------------------------------------------- */
.leads { display: flex; flex-direction: column; gap: var(--s3); }
.lead {
  display: grid; grid-template-columns: 1fr auto; gap: var(--s4);
  padding: var(--s4); background: var(--surface);
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); align-items: start;
}
/* The claimed stripe is gone — the card says who is working it, in words. */
/* Who, then what they want: the same order the desktop row reads in, so the
   two screens are learned once. */
.lead__who { display: flex; gap: var(--s3); align-items: center; min-width: 0; }
.lead__art {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  width: 96px; height: 54px; border-radius: var(--radius-md);
  background: var(--surface-sunk); overflow: hidden;
}
.lead__ident { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lead__main { min-width: 0; display: flex; flex-direction: column; gap: var(--s2); }
.lead__top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
.lead__name { font-size: 1.0625rem; font-weight: 640; letter-spacing: -0.01em; }
.lead__name a { color: inherit; display: inline-flex;
  align-items: center; min-height: 24px; }
.lead__priority {
  font-size: var(--f-small); color: var(--ink-muted); font-variant-numeric: tabular-nums;
}
/* The car is what the conversation is about, so it reads before the CRM's
   bookkeeping rather than inside it. */
.lead__vehicle { margin: 0; font-size: var(--f-small); font-weight: 500;
  color: var(--ink); letter-spacing: -0.005em; }

.lead__facts { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--s1) var(--s3); font-size: var(--f-small); color: var(--ink-faint); }
/* VYLI's reasons sit above VirtualYard's facts. Without a marker the two read
   as one list, and nobody can tell which system said what. */
.lead__facts-tag {
  flex: 0 0 auto;
  font-size: var(--f-micro); font-weight: 640; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-faint);
  padding: 1px 6px; border: 1px solid var(--hairline); border-radius: var(--radius-sm);
}

.reasons { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s2); margin: 2px 0 0; padding: 0; list-style: none; }
.reason { display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--f-small); color: var(--ink-muted); }
.reason::before { content: "+"; font-weight: 700; color: var(--positive); }
.reason--negative::before { content: "−"; color: var(--negative); }
/* A signal that scored nothing must not wear a plus sign; it did not add. */
.reason--informational { color: var(--ink-faint); }
.reason--informational::before { content: "·"; color: var(--ink-faint); }

.lead__side { display: flex; flex-direction: column; align-items: flex-end; gap: var(--s2); }
/* One row, three weights. Splitting the tiers onto separate lines detached
   the quiet buttons from the card they belong to; ranking them in place says
   the same thing without the whitespace. */
.lead__actions { display: flex; gap: var(--s1); flex-wrap: wrap;
  justify-content: flex-end; align-items: center; }
.lead__actions-minor { display: flex; gap: var(--s1); flex-wrap: wrap;
  justify-content: flex-end; align-items: center; }
.lead__actions .btn--primary { margin-right: var(--s1); }

/* --- flags ---------------------------------------------------------------- */
.flag { display: inline-flex; align-items: center; gap: 5px; font-size: var(--f-micro);
  font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 2px 7px; border-radius: var(--radius-sm);
  background: var(--surface-sunk); color: var(--ink-muted); border: 1px solid var(--hairline-soft); }
.flag--overdue { color: var(--warn); border-color: currentColor; background: transparent; }
.flag--due { color: var(--q-follow-up); border-color: currentColor; background: transparent; }
.flag--claim { color: var(--accent-strong); background: var(--accent-wash);
  border-color: transparent; text-transform: none; letter-spacing: 0; }
.flag--claim-mine { font-weight: 700; }
.flag--override { color: var(--q-win-back); border-color: currentColor; background: transparent; }

/* --- buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 13px; border-radius: var(--radius-sm); border: 1px solid var(--hairline);
  background: var(--surface); color: var(--ink); font-size: var(--f-small);
  font-weight: 580; cursor: pointer; text-decoration: none; min-height: 36px;
}
.btn--primary { background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); }
.btn--secondary { border-color: var(--hairline-strong); font-weight: 600; }
/* tertiary: present, reachable, and not competing with Call */
.btn--quiet { border-color: transparent; background: transparent;
  color: var(--ink-muted); font-weight: 500; padding-left: 9px; padding-right: 9px; }
.btn--ghost { border-color: transparent; background: transparent; color: var(--ink-muted); }
.btn[disabled] { opacity: .5; cursor: not-allowed; pointer-events: none; }
.btn--sm { padding: 4px 9px; min-height: 30px; font-size: var(--f-micro); }

/* --- panels / cards ------------------------------------------------------- */
.panel { background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: var(--s4); margin-bottom: var(--s4);
  box-shadow: var(--lift); }
.panel__head { display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); margin-bottom: var(--s3); }
.panel__title { font-size: var(--f-small); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--ink-faint); }

/* --- forms ---------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: var(--s3); }
.field label { font-size: var(--f-small); font-weight: 560; color: var(--ink-muted); }
input[type=text], input[type=search], input[type=password], input[type=number],
input[type=datetime-local], select, textarea {
  width: 100%; padding: 8px 10px; font-size: 16px; /* 16px: iOS must not zoom */
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--hairline); border-radius: var(--radius-sm);
}
@media (min-width: 768px) {
  input[type=text], input[type=search], input[type=password], input[type=number],
  input[type=datetime-local], select, textarea { font-size: var(--f-body); }
}
textarea { min-height: 84px; resize: vertical; }
.inline-form { display: inline; }

/* --- filter bar ----------------------------------------------------------- */
.toolbar { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
  margin-bottom: var(--s4); }
.toolbar__search { flex: 1 1 260px; min-width: 0; }
/* Inputs are width:100% by default, which would drop the sort and the button
   onto rows of their own and push the first lead a screenful further down. */
.toolbar select { width: auto; flex: 0 1 auto; max-width: 220px; }
.toolbar .btn { flex: 0 0 auto; }
.chips { display: flex; flex-wrap: wrap; gap: var(--s1); }
.chip { display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px;
  border: 1px solid var(--hairline); border-radius: var(--radius-pill); background: var(--surface);
  color: var(--ink-muted); font-size: var(--f-small); font-weight: 540;
  text-decoration: none; min-height: 32px; }
.chip[aria-pressed="true"] { background: var(--accent-wash); border-color: var(--accent);
  color: var(--accent-strong); font-weight: 620; }

/* --- tables --------------------------------------------------------------- */
.table-wrap { overflow-x: auto; border: 1px solid var(--hairline);
  border-radius: var(--radius); background: var(--surface); }
table.data { width: 100%; border-collapse: collapse; font-size: var(--f-small); }
table.data th { text-align: left; font-weight: 640; color: var(--ink-faint);
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.06em;
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--hairline); white-space: nowrap; }
table.data td { padding: var(--s3) var(--s4); border-bottom: 1px solid var(--hairline-soft);
  vertical-align: top; }
table.data tr:last-child td { border-bottom: none; }
table.data td.num { font-variant-numeric: tabular-nums; text-align: right; }

/* --- timeline ------------------------------------------------------------- */
.timeline { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: var(--s3); }
.tl { display: grid; grid-template-columns: 108px 1fr; gap: var(--s3); align-items: baseline; }
.tl__when { font-size: var(--f-micro); color: var(--ink-faint);
  font-variant-numeric: tabular-nums; }
.tl__body { border-left: 2px solid var(--hairline); padding-left: var(--s3); }
.tl--source .tl__body { border-color: var(--q-follow-up); }
.tl--intelligence .tl__body { border-color: var(--accent); }
.tl--human .tl__body { border-color: var(--q-win-back); }
.tl__kind { font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); }

/* --- status pills --------------------------------------------------------- */
.pill { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px;
  border-radius: var(--radius-pill); font-size: var(--f-micro); font-weight: 640;
  text-transform: uppercase; letter-spacing: 0.05em; }
.pill--ok { background: var(--accent-wash); color: var(--accent-strong); }
.pill--warn { background: var(--q-call-today-bg); color: var(--q-call-today); }
.pill--bad { background: var(--q-call-now-bg); color: var(--q-call-now); }
.pill--idle { background: var(--surface-sunk); color: var(--ink-muted); }

/* --- notices -------------------------------------------------------------- */
.notice { padding: var(--s3) var(--s4); border-radius: var(--radius);
  border: 1px solid var(--hairline); background: var(--surface-sunk);
  font-size: var(--f-small); margin-bottom: var(--s4); }
.notice--error { border-color: var(--negative); color: var(--negative); background: transparent; }
.notice--ok { border-color: var(--accent); color: var(--accent-strong); background: var(--accent-wash); }

/* --- pagination ----------------------------------------------------------- */
.pager { display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); margin-top: var(--s4); font-size: var(--f-small); color: var(--ink-muted); }
.pager__count { padding: 0 var(--s2); }
/* The per-page choices measured 18x16 at 375px — the smallest targets in the
   application. Padded to the 24px floor rather than made bigger typographically. */
.pager a, .pager strong {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; min-height: 24px; padding: 0 2px;
}

/* §13 Empty is not an error. One heading, one sentence, one way forward —
   the same shape everywhere, so an empty screen never reads as a fault. */

/* --- lead workspace ------------------------------------------------------- */
.workspace { display: grid; gap: var(--s4); grid-template-columns: minmax(0,1fr) 340px;
  align-items: start; }
.workspace__aside { display: flex; flex-direction: column; gap: var(--s4); }

/* §9 The context rail follows you down a long activity timeline, so the car,
   the CRM state and the override controls stay to hand.

   Only where it behaves: the rail must be shorter than the viewport, or
   sticking it would trap its own overflow and create a second scrollbar. The
   query below is the honest condition for that, and the rail falls back to
   ordinary flow whenever it is not met. */
/* The rail only sticks where it genuinely fits. Capping its height and giving
   it its own scrollbar was the obvious implementation and the wrong one: it
   traps the wheel over a third of the screen and hides its own last panel.
   The context stack is about 870px tall, so a viewport with room to spare gets
   the sticky behaviour and everything shorter keeps ordinary flow. */
@media (min-width: 1100px) and (min-height: 1000px) {
  .workspace__aside { position: sticky; top: var(--s4); }
}
.kv { display: grid; grid-template-columns: auto 1fr; gap: var(--s1) var(--s3);
  font-size: var(--f-small); }
.kv dt { color: var(--ink-faint); }
.kv dd { margin: 0; color: var(--ink); }

/* The three short fields on "Log a call". This borrowed `.advanced__body` —
   a disclosure's inner padding block — and then cancelled its padding with an
   inline style, which is a class doing the opposite of its name. */
.lead-form__grid { display: grid; gap: var(--s3); }
@media (min-width: 620px) {
  .lead-form__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* One follow-up. Written as an inline flex row with its own hand-rolled
   hairline until 11 September. */
.fup {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s2); padding: var(--s2) 0;
  border-bottom: 1px solid var(--hairline-soft);
  font-size: var(--f-small);
}
.fup:last-of-type { border-bottom: none; }
.fup__what { min-width: 0; }

/* A form or a block that follows something else inside a panel. Three
   different inline `margin-top` values were doing this job. */
.panel__form { margin-top: var(--s3); }
.panel__form--tight { margin-top: var(--s2); }

/* Quiet body text. `.page-head__sub` was being used for this throughout the
   application, including inside panels, timelines and description lists that
   have no page head and do not want its 2px head offset. */
.say { color: var(--ink-muted); font-size: var(--f-small); }
.say--after  { margin: var(--s1) 0 0; }
.say--before { margin: 0 0 var(--s3); }

/* A monospaced value in running text — a policy hash, an identifier. */
.mono { font-family: var(--mono); font-size: var(--f-micro); }

.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; }

/* --- mobile --------------------------------------------------------------- */
.mobile-bar { display: none; }
@media (max-width: 900px) {
  /* The island goes nearly full width with the same 12px margins BYD uses at
     390px, and keeps its height, radius, glass and shadow exactly. One object
     at every width — which is the whole point of the shape. */
  :root { --island-w: calc(100vw - (var(--island-gutter) * 2)); --island-top: 12px; }

  /* The fixed bar must never sit on top of content. 84px was once a guess
     that ignored the home indicator: on a notched iPhone the bar grows by up
     to 34px of safe-area inset and the last card went under it. The clearance
     is derived from the bar itself — app.js measures it and sets
     --mobile-bar-h; the value here is the no-JavaScript floor. */
  .main {
    padding-left: var(--s4); padding-right: var(--s4); padding-top: var(--s4);
    padding-bottom: calc(var(--mobile-bar-h, 64px) + env(safe-area-inset-bottom, 0px) + var(--s5));
  }
  /* The bar covered the panels' last few rows exactly as it once covered the
     last lead card — measured 9 September 2026 with the settings gear at
     760-796 and the bar at 751-812. There is one shape to clear it now rather
     than two: whichever view the shell is showing, what the bar constrains is
     how far down the shell may grow. The number is still derived from the bar
     itself via `--mobile-bar-h`, never guessed — that is how the home
     indicator ate 34px the first time. */
  .shell-panel {
    max-height: calc(100dvh - var(--island-top) - var(--island-h)
                     - var(--mobile-bar-h, 64px) - env(safe-area-inset-bottom, 0px)
                     - var(--s4));
    padding: var(--s4);
  }
  .workspace { grid-template-columns: 1fr; }
  .lead { grid-template-columns: 1fr; }
  /* The phone opens on the one card that matters, at a size a thumb can hit,
     and the supporting three go two-up beneath it. Nothing is squeezed: the
     hero loses its desktop padding, not its authority. */
  .today-queues { gap: var(--s3); margin-bottom: var(--s6); }
  .hero-queue { padding: var(--s5); border-radius: var(--radius-lg); }
  .hero-queue__n { font-size: 3rem; }
  .hero-queue__say { font-size: var(--f-small); }
  .prio { margin-bottom: var(--s6); }
  .prio__list { grid-template-columns: 1fr; }
  .insights { gap: var(--s5); grid-template-columns: 1fr; }
  .dash-lower { gap: var(--s5); }
  .wb-card { padding: var(--s4); border-radius: var(--radius-lg); }
  .lead__side { align-items: stretch; }
  .lead__actions { justify-content: stretch; }
  .lead__actions .btn { flex: 1; }
  .page-head h1 { font-size: 1.5rem; }
  .mobile-bar {
    display: grid; grid-auto-flow: column; position: fixed; inset: auto 0 0 0;
    background: var(--surface); border-top: 1px solid var(--hairline-strong);
    box-shadow: 0 -1px 3px rgba(24, 21, 12, 0.06);
    padding: 6px max(var(--s2), env(safe-area-inset-left)) max(6px, env(safe-area-inset-bottom));
    z-index: 20;
  }
  .mobile-bar a, .mobile-bar button { display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 6px 4px; font-size: var(--f-micro); color: var(--ink-muted);
    text-decoration: none; min-height: 48px; justify-content: center; font-weight: 560; }
  .mobile-bar button { background: none; border: 0; cursor: pointer; font: inherit;
    font-size: var(--f-micro); font-weight: 560; color: var(--ink-muted); }
  .mobile-bar a[aria-current="page"] { color: var(--brand-ink); font-weight: 700; }
  .tl { grid-template-columns: 1fr; gap: 2px; }
  /* The point of the phone view is the next call, not the numbers. Compact the
     metrics so the first lead card is reachable without two screens of scroll. */
  .metric { padding: var(--s2) var(--s3); }
  .metric__value { font-size: 1.125rem; }
  .metric__label { font-size: var(--f-micro); }
  .lead__actions { width: 100%; }
  .lead__actions .btn { flex: 1 1 auto; min-height: 44px; white-space: nowrap; }
  /* Nine filter chips wrap to three rows and push the first lead off the
     screen. One row that scrolls sideways costs nothing and shows the same
     nine. */
  .chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch; padding-bottom: 2px; }
  .chips::-webkit-scrollbar { display: none; }
  .chip { flex: 0 0 auto; }
}
@media (max-width: 560px) {
  /* The whole control is one line now, so nothing needs hiding. */
}
@media (max-width: 390px) {
  /* The lockup must never wrap — a wrapped brand looks like a bug, and it
     pushes the mark off the island's centre line. The sub-label goes first. */
  .island__sub { display: none; }
  .island__mark { font-size: 0.75rem; letter-spacing: 0.04em; }
}
@media (max-width: 420px) {
  .metrics { grid-template-columns: repeat(3, 1fr); gap: var(--s2); }
  .metric { padding: var(--s2); }
  /* Four actions do not fit across 390px without the labels wrapping. */
  .lead__actions { display: grid; grid-template-columns: repeat(2, 1fr); }
}
/* Reduced motion is not a softened version — it is off. Killing the
   transition alone would leave the hover transform snapping into place, which
   is exactly the movement somebody asking for less motion asked not to see, so
   the transform goes too. Colour, tint and shadow feedback all remain. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  *:hover, *:active { transform: none !important; }
}


/* --- system verdict ------------------------------------------------------- */
/* The first thing on Sync & Health, because "is it working?" is the first
   question and the detailed table below is a poor way to answer it. */
.verdict { display: flex; gap: var(--s3); align-items: flex-start;
  border: 1px solid var(--hairline); border-left-width: 3px;
  border-radius: var(--radius); background: var(--surface);
  padding: var(--s4); margin-bottom: var(--s4); }
.verdict--ok { border-left-color: var(--positive); }
.verdict--attention { border-left-color: var(--warn); }
.verdict__mark { flex: 0 0 auto; width: 10px; height: 10px; margin-top: 7px;
  border-radius: 50%; background: var(--positive); }
.verdict--attention .verdict__mark { background: var(--warn); }
.verdict__headline { font-size: var(--f-title); }
.verdict--attention .verdict__headline { color: var(--warn); }
.verdict__summary { margin: 2px 0 0; color: var(--ink-muted); font-size: var(--f-small); }
.verdict__list { margin: var(--s3) 0 0; padding-left: 18px;
  font-size: var(--f-small); color: var(--ink-muted); display: grid; gap: 6px; }
.verdict__list b { color: var(--ink); font-weight: 600; }

/* A row the engine scored at or below zero is in the archive but is not an
   opportunity. It stays listed — hiding it would be a lie about the data — and
   recedes so the eye goes to the ones worth a call. */
.lead--faded { opacity: 0.62; }
.lead--faded:focus-within { opacity: 1; }

/* --- skip link ------------------------------------------------------------ */
.skip { position: absolute; left: -9999px; top: 0; z-index: 90;
  background: var(--surface); color: var(--ink); padding: 10px 14px;
  border: 1px solid var(--accent); border-radius: var(--radius-sm); font-weight: 600; }
.skip:focus { left: var(--s3); top: var(--s3); text-decoration: none; }

/* Metrics became links: each one is a filter somebody wants to apply. */
a.metric { text-decoration: none; color: inherit; transition: border-color .12s ease; }

/* --- §8 reason density ----------------------------------------------------
   The strongest handful decide the queue and are always visible; the rest are
   one click away. Every stored reason is rendered either way. */
.reasons--stacked { flex-direction: column; align-items: stretch; gap: 0; }
/* Three columns, because ::before is a grid item too: marker, label, weight. */
.reasons--stacked .reason { display: grid; grid-template-columns: auto 1fr auto;
  gap: 0 var(--s2); align-items: baseline; padding: 6px 0;
  border-bottom: 1px solid var(--hairline-soft); }
.reasons--stacked .reason__label { text-align: left; }
.reasons--stacked .reason:last-child { border-bottom: none; }
.reason--lead .reason__label { color: var(--ink); font-weight: 520; }
.reason--lead.reason--informational .reason__label { color: var(--ink-muted); font-weight: 400; }
.reason__weight { font-size: var(--f-small); color: var(--ink-faint);
  font-variant-numeric: tabular-nums; }
.reason__weight--none { font-style: italic; }
.reasons-more { margin-top: var(--s2); }
.reasons-more > summary { cursor: pointer; list-style: none; padding: var(--s2) 0;
  font-size: var(--f-small); font-weight: 560; color: var(--accent-strong); }
.reasons-more > summary::-webkit-details-marker { display: none; }
.reasons-more > summary::after { content: " ▾"; color: var(--ink-faint); }
.reasons-more[open] > summary::after { content: " ▴"; }

/* --- §10 queue provenance -------------------------------------------------
   Automatic recommendation, the person's override, and the queue actually in
   force are three separate facts. Presenting them as one sentence made a
   reader do the work; presenting them as three labelled rows does not. */
.provenance { margin-top: var(--s3); border: 1px solid var(--hairline);
  border-radius: var(--radius-sm); background: var(--surface-sunk);
  padding: var(--s3); display: grid; gap: var(--s2); }
.provenance__row { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); font-size: var(--f-small); }
.provenance__label { flex: 0 0 128px; color: var(--ink-muted); }
.provenance__row--manual { color: var(--q-win-back); }
.provenance__row--effective { padding-top: var(--s2);
  border-top: 1px solid var(--hairline-soft); font-weight: 600; }
.provenance__row--effective .provenance__label { color: var(--ink); font-weight: 600; }
.provenance__by { color: var(--ink-faint); font-size: var(--f-micro); }
.provenance__reason { margin: 0; font-size: var(--f-small); color: var(--ink-muted);
  font-style: italic; }

/* --- §11 sync runs --------------------------------------------------------
   A sixteen-column table is the right record and the wrong first read. The
   summary answers "did it work and what moved"; the accounting is one
   disclosure away, per run, and nothing has been dropped from it. */
.runs { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.run { border: 1px solid var(--hairline); border-left-width: 3px;
  border-left-color: var(--hairline); border-radius: var(--radius);
  background: var(--surface); padding: var(--s3); }
.run--completed { border-left-color: var(--positive); }
.run--partial   { border-left-color: var(--warn); }
.run--failed    { border-left-color: var(--negative); background: var(--q-call-now-bg); }
.run--running   { border-left-color: var(--q-follow-up); }
.run__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
.run__scope { font-weight: 620; }
.run__count { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.run__when { margin-left: auto; color: var(--ink-faint); font-size: var(--f-small);
  font-variant-numeric: tabular-nums; }
.run__movement { margin: var(--s1) 0 0; font-size: var(--f-small);
  color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.run--failed .run__movement b, .run--partial .run__movement b { color: var(--ink); }
.run__detail > summary { cursor: pointer; list-style: none; margin-top: var(--s2);
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); font-weight: 640; }
.run__detail > summary::-webkit-details-marker { display: none; }
.run__detail > summary::after { content: " ▾"; }
.run__detail[open] > summary::after { content: " ▴"; }
.run__grid { display: grid; gap: var(--s2) var(--s4); margin: var(--s3) 0 0;
  grid-template-columns: repeat(auto-fit, minmax(min(104px, 100%), 1fr)); }
.run__grid dt { font-size: var(--f-micro); color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: 0.05em; }
.run__grid dd { margin: 2px 0 0; font-variant-numeric: tabular-nums; font-weight: 560; }

/* §12 The briefing leads with what someone acts on, then the rest. */
.metrics--briefing { grid-template-columns: repeat(auto-fit, minmax(min(168px, 100%), 1fr)); }
.metrics--briefing-minor { grid-template-columns: repeat(auto-fit, minmax(min(124px, 100%), 1fr));
  margin-top: calc(var(--s5) * -1 + var(--s3)); }
.metrics--briefing .metric--primary { box-shadow: var(--lift); }
.metrics--briefing .metric--primary .metric__value { font-size: 1.75rem; }
.metric[data-zero="true"] .metric__value { color: var(--ink-faint); }

/* A customer's name is arbitrary-length text from another system: it wraps
   rather than escaping its card, and never pushes the badges off the row. */
.lead__name { overflow-wrap: anywhere; }
.page-head h1 { overflow-wrap: anywhere; }


/* ==========================================================================
   BYD shell, table and dashboard
   ========================================================================== */

/* --- brand lockup --------------------------------------------------------- */
/* Text branding. No BYD logo asset is bundled, and none is fabricated: the
   dealership name carries the identity until a licensed mark is supplied. */
.brand { display: flex; flex-direction: column; gap: 1px; }
.brand__mark {
  font-size: 0.9375rem; font-weight: 800; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink); line-height: 1.15;
}
.brand__mark::after {
  content: ""; display: block; width: 26px; height: 3px;
  margin-top: 5px; background: var(--brand); border-radius: 1px;
}
.brand__sub {
  font-size: var(--f-small); color: var(--ink-muted); font-weight: 500;
  margin-top: 5px;
}

/* --- navigation ----------------------------------------------------------- */

/* --- identity, promoted --------------------------------------------------- */
/* Who is at the keyboard decides who every note is attributed to, so an unset
   identity has to be visibly unfinished rather than merely blank. */

/* --- count-first page heading --------------------------------------------- */
.result-count { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.result-count__n { font-size: var(--f-display); font-weight: 700; letter-spacing: -0.02em; }
.result-count__unit { font-size: var(--f-body); color: var(--ink-muted); }
.result-summary {
  margin-top: 2px; font-size: var(--f-small); color: var(--ink-faint);
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}
.result-summary__sep { color: var(--hairline-strong); }

/* --- filter toolbar ------------------------------------------------------- */
/* --- the filter toolbar ----------------------------------------------------
   No box. It was a bordered, filled panel as visually heavy as the data it
   narrowed, which made the page read as a form with a table under it rather
   than a list you can narrow. It is a row of controls on the page now, with a
   hairline under it and nothing else. */
.filterbar {
  display: flex; flex-wrap: wrap; gap: var(--s3); align-items: flex-end;
  padding: 0 0 var(--s4); margin-bottom: var(--s5);
  border-bottom: 1px solid var(--hairline);
}
.filterbar__field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.filterbar__field > label {
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); font-weight: 650;
}
.filterbar__field select, .filterbar__field input { width: auto; min-width: 132px; }

/* Status is the one multi-value control, so it gets a little more room and a
   scrolling list rather than a 19-item popup. `size` in the markup sets the
   rows; this bounds the height so a long vocabulary cannot push the first
   lead off the screen — which is the whole reason the filters live in a
   panel on a phone. */
.filterbar__hint { font-size: var(--f-micro); color: var(--ink-faint); }
.filterbar__hint a { color: var(--ink-muted); }
/* Two tidy rows rather than one clever one: search across the top, controls
   beneath. Letting the search and the panel compete for the same row wrapped
   the panel into a column with a hole in the middle of the toolbar. */
.filterbar__field--search { flex: 1 1 100%; }
.filterbar__field--search input {
  width: 100%; min-width: 0; min-height: 42px; font-size: var(--f-body);
}
.filterbar__actions { display: flex; gap: var(--s2); align-items: flex-end; margin-left: auto; }

/* "More filters" is a disclosure so the common four do not cost the table
   two rows of screen on every visit. */
.more-filters { width: 100%; }
.more-filters > summary {
  cursor: pointer; font-size: var(--f-small); color: var(--ink-muted);
  font-weight: 600; padding: 4px 0; list-style: none;
}
.more-filters > summary::-webkit-details-marker { display: none; }
.more-filters > summary::before { content: "▸ "; color: var(--ink-faint); }
.more-filters[open] > summary::before { content: "▾ "; }
.more-filters__panel {
  display: flex; flex-wrap: wrap; gap: var(--s2); align-items: flex-end;
  padding-top: var(--s2); margin-top: var(--s2);
  border-top: 1px solid var(--hairline-soft);
}
.custom-range { display: none; gap: var(--s2); align-items: flex-end; }
.custom-range--on { display: flex; }

/* --- the lead table ------------------------------------------------------- */
/* A real table: <thead>, <th scope>, one row per lead. A grid of divs would
   read as a list of nothing to a screen reader, and this is the screen a
   salesperson lives in. */
/* --- the lead rows --------------------------------------------------------
   Nine columns of equal weight meant reading every one to find the two that
   matter. Four now: the car, the lead, its queue and its age — with stage,
   status, source and owner recomposed into one quiet line inside the lead's
   own cell rather than dropped.

   The chrome went with them. No box around the table, a hairline that stops
   short of the artwork, and 72px rows so a name has room to be a name.
   ========================================================================== */
.lead-table-wrap { min-width: 0; }
.lead-table { width: 100%; border-collapse: collapse; }
.lead-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--ground); color: var(--ink-faint);
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.07em;
  font-weight: 700; text-align: left; white-space: nowrap;
  padding: 0 var(--s3) var(--s2); border-bottom: 1px solid var(--hairline);
}
.lead-table tbody td {
  padding: 10px var(--s3);
  border-bottom: 1px solid var(--hairline-soft);
  vertical-align: middle;
}
/* Inset to the text column: a rule that runs under the artwork chops the row
   into two boxes and makes the list look like a spreadsheet again. */
.lead-table tbody td:first-child { border-bottom-color: transparent; }
.lead-table tbody tr:last-child td { border-bottom: 0; }
.lead-table tbody tr { background: transparent; }
.lead-table tbody tr:focus-within { background: var(--brand-wash); }

.lead-table__thumb { width: 120px; padding-right: 0 !important; }
/* A contained plate, so a silhouette and a photograph occupy the same box and
   the column stays a column whatever the mapper returned. */
.vart {
  display: flex; align-items: center; justify-content: center;
  width: 96px; height: 54px; border-radius: var(--radius-md);
  background: var(--surface-sunk); overflow: hidden;
}

.lead-table__lead { min-width: 0; }
.lead-table__name {
  display: block; font-weight: 650; color: var(--ink); font-size: 1rem;
  letter-spacing: -0.01em; line-height: 1.25; overflow-wrap: anywhere;
}
.lead-table__vehicle {
  display: block; margin-top: 1px; line-height: 1.35;
  font-size: var(--f-small); color: var(--ink-muted); overflow-wrap: anywhere;
}
.lead-table__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 10px;
  margin-top: 4px; line-height: 1.3;
  font-size: var(--f-micro); color: var(--ink-faint);
}
.lead-table__meta > span { white-space: nowrap; }
.lead-table__owner--none { font-style: italic; }
/* The claim used to be a 3px bar down the row's left edge — the last of the
   four side-stripes this redesign has been retiring. It says who, in words. */
.lead-table__claim {
  font-size: var(--f-micro); font-weight: 650; color: var(--ink-muted);
  background: var(--surface-sunk); border-radius: var(--radius-sm);
  padding: 1px 7px;
}
.lead-table__queue { white-space: nowrap; width: 1%; }
.lead-table__age {
  white-space: nowrap; width: 1%; text-align: right;
  font-size: var(--f-small); color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.lead-row--overdue .lead-table__age { color: var(--warn); font-weight: 650; }

/* --- pagination ----------------------------------------------------------- */
.pager {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-top: var(--s3); font-size: var(--f-small); color: var(--ink-muted);
}
.pager__spacer { margin-left: auto; }

/* --- dashboard ------------------------------------------------------------
   One question, answered once: who do I ring. The page used to open with four
   tiles of identical weight, so CALL NOW carried exactly as much visual
   authority as MONITOR — which means "do nothing". It opens with one card now.

   Red stays rationed. The hero is an ordinary surface with a tinted hairline,
   a queue badge and a red number; it is not a red card. Nothing on this page
   is glass: glass is chrome, and a lead count is not chrome.
   ========================================================================== */
.today-queues { display: grid; gap: var(--s4); margin-bottom: var(--s7); }


.hero-queue {
  display: grid; gap: var(--s2); justify-items: start; align-content: center;
  padding: var(--s6);
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--q-call-now) 26%, var(--hairline));
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-float);
  color: inherit; text-decoration: none;
  transition: box-shadow var(--motion-standard) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard);
}
/* Nothing waiting is a good state, not a broken one. It loses the tint and
   the urgency and keeps the shape, so the page does not lurch when it clears. */
.hero-queue--clear { border-color: var(--hairline); box-shadow: var(--shadow-sm); }
.hero-queue--clear .hero-queue__n { color: var(--ink-faint); }

.hero-queue__lede { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.hero-queue__meta {
  font-size: var(--f-micro); font-weight: 650; color: var(--ink-faint);
  letter-spacing: 0.03em;
}
.hero-queue__n {
  display: block; font-size: 3.5rem; font-weight: 700; letter-spacing: -0.04em;
  line-height: 0.95; color: var(--q-call-now); font-variant-numeric: tabular-nums;
  margin-top: var(--s2);
}
.hero-queue__say {
  display: block; max-width: 46ch; color: var(--ink-secondary);
  font-size: var(--f-body);
}
.hero-queue__go {
  margin-top: var(--s3); font-weight: 650; font-size: var(--f-small);
  color: var(--brand-ink);
}

.sub-queues {
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
}
.sub-queue {
  display: grid; gap: 2px; align-content: start;
  padding: var(--s4);
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  color: inherit; text-decoration: none;
}
.sub-queue__n {
  font-size: 1.75rem; font-weight: 700; letter-spacing: -0.03em; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.sub-queue__label {
  font-size: var(--f-micro); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--ink-muted);
}
.sub-queue__meta { font-size: var(--f-small); color: var(--ink-faint); margin-top: 4px; }
/* Three cards in a two-column grid leaves the last one alone at half width,
   which reads as a gap rather than a decision. It takes the whole row. */
.sub-queues > :last-child:nth-child(odd) { grid-column: 1 / -1; }
/* A 2px foot rather than a 3px head: the number is what the eye lands on, and
   a coloured rule above it competes with the count for the top of the card. */
.sub-queue--call-today { box-shadow: inset 0 -2px 0 var(--q-call-today), var(--shadow-sm); }
.sub-queue--follow-up  { box-shadow: inset 0 -2px 0 var(--q-follow-up), var(--shadow-sm); }
.sub-queue--monitor    { box-shadow: inset 0 -2px 0 var(--q-monitor), var(--shadow-sm); }

/* Mobile-first above, desktop here, and this block is declared AFTER both so
   nothing can win it back on source order — the same shape as the filter
   panel fix. From 900px the hero takes the left two-thirds and the three
   supporting queues stack beside it in one column: a full-width hero left
   most of its own card empty, which reads as an unfinished page rather than a
   spacious one, and stacking the others beside it states the hierarchy twice,
   once in size and once in position. */
@media (min-width: 900px) {
  .today-queues {
    grid-template-columns: minmax(0, 1.5fr) minmax(min(280px, 100%), 1fr);
    align-items: stretch;
  }
  .sub-queues { grid-template-columns: 1fr; }
  .sub-queue { align-content: center; }
}

/* --- priority leads -------------------------------------------------------
   The rows the Leads page opens on, shown rather than counted. This is the
   only part of the Dashboard that names a customer, and it is the reason the
   page is worth opening at all.
   ========================================================================== */
.prio { margin-bottom: var(--s7); }
.prio__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); margin-bottom: var(--s4);
}
.prio__title { font-size: var(--f-title); }
.prio__all { font-size: var(--f-small); font-weight: 650; color: var(--ink-muted); }

.prio__list {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}
.pcard {
  display: flex; gap: var(--s4); align-items: flex-start; height: 100%;
  padding: var(--s4);
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  color: inherit; text-decoration: none;
  transition: box-shadow var(--motion-standard) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard);
}
.pcard__art {
  flex: 0 0 auto; width: 96px; height: 54px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-sunk); border-radius: var(--radius-md);
  overflow: hidden;
}
/* No size override here any more. `.vthumb` was 72x26 for the Leads table and
   this card needed 96x54; Phase 4 gave the table the same 96x54, so one base
   rule serves both surfaces and the artwork is literally the same object on
   the Dashboard and in the list. */
.pcard__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.pcard__top { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.pcard__flag {
  font-size: var(--f-micro); font-weight: 650; color: var(--ink-faint);
}
.pcard__flag--mine { color: var(--brand-ink); }
.pcard__name {
  font-size: var(--f-body); font-weight: 650; color: var(--ink);
  overflow-wrap: anywhere;
}
.pcard__vehicle { font-size: var(--f-small); color: var(--ink-muted); overflow-wrap: anywhere; }
.pcard__why {
  font-size: var(--f-small); color: var(--ink-secondary); margin-top: 3px;
}
.pcard__why::before { content: "+ "; color: var(--success); font-weight: 700; }
.pcard__meta {
  display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 5px;
  font-size: var(--f-micro); color: var(--ink-faint);
}

/* --- secondary insights and win-back --------------------------------------
   Counts that matter but do not decide the next call. They were three bordered
   panels of equal weight to everything above them; they are quiet rows now,
   and the one card down here is the opportunity rather than the obligation.
   ========================================================================== */
.dash-lower {
  display: grid; gap: var(--s5);
  grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
  align-items: start;
}
@media (max-width: 899px) { .dash-lower { grid-template-columns: 1fr; } }

.insights { display: grid; gap: var(--s5); grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.insight__title {
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-faint); font-weight: 700; margin-bottom: var(--s2);
}
.insight__rows { list-style: none; margin: 0; padding: 0; }
.insight__rows li {
  display: flex; align-items: baseline; gap: var(--s3);
  padding: 9px 0; border-bottom: 1px solid var(--hairline-soft);
}
.insight__rows li:last-child { border-bottom: 0; }
.insight__rows a { color: var(--ink); text-decoration: none;
  display: inline-flex; align-items: center; min-height: 24px; }
.insight__n {
  margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums;
  font-size: var(--f-body);
}
.insight__n--zero { color: var(--ink-faint); font-weight: 500; }

.wb-card {
  padding: var(--s5); background: var(--surface);
  border: 1px solid var(--hairline); border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  display: grid; gap: 2px; justify-items: start;
}
.wb-card__title {
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--q-win-back); font-weight: 700; margin-bottom: var(--s2);
}
.wb-card__n {
  margin: 0; font-size: 2rem; font-weight: 700; letter-spacing: -0.03em;
  line-height: 1.05; font-variant-numeric: tabular-nums;
}
.wb-card__label {
  margin: 0; font-size: var(--f-micro); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--ink-muted);
}
.wb-card__strong { margin: var(--s3) 0 0; font-size: var(--f-small); color: var(--ink-muted); }
.wb-card__strong b { color: var(--ink); font-variant-numeric: tabular-nums; }
.wb-card__strong--zero { color: var(--ink-faint); font-weight: 500; }
.wb-card__go { margin-top: var(--s4); }

/* --- the identity gate ----------------------------------------------------
   One screen between signing in and the app. Narrow, centred and quiet: it is
   a question with a short answer, not a page to work on.
   ========================================================================== */
.main--gate { max-width: 520px; }
.gate { padding-top: var(--s6); }
.gate__title { font-size: var(--f-hero); letter-spacing: -0.03em; }
.gate__say {
  margin: var(--s3) 0 var(--s6); color: var(--ink-secondary);
  font-size: var(--f-body); max-width: 46ch;
}
.gate__list { display: flex; flex-direction: column; gap: var(--s2); }
.gate__person {
  display: flex; align-items: center; gap: var(--s4); width: 100%;
  padding: var(--s3) var(--s4); min-height: 56px;
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  color: var(--ink); font: inherit; text-align: left; cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-standard),
              box-shadow var(--motion-standard) var(--ease-standard);
}
.gate__avatar {
  width: 38px; height: 38px; flex: 0 0 auto; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-sunk); border: 1px solid var(--hairline);
  color: var(--ink-muted);
}
.gate__name { font-size: var(--f-body); font-weight: 650; min-width: 0; overflow-wrap: anywhere; }
.gate__go { margin-left: auto; color: var(--ink-faint); }
.gate__note { margin-top: var(--s5); font-size: var(--f-small); color: var(--ink-faint); }
.gate__bootstrap {
  padding: var(--s5); background: var(--surface);
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
}
.gate__bootstrap .gate__say { margin: 0 0 var(--s3); }
.gate__add { display: flex; gap: var(--s2); flex-wrap: wrap; }
.gate__add input { flex: 1 1 200px; min-width: 0; }
.gate__out { margin-top: var(--s7); }

/* --- login ----------------------------------------------------------------
   One environment, one object. This was a two-column split until 11 September
   2026 — hero left, form right, hero hidden below 900px — which reads as a
   layout with a picture in it rather than a room with a panel floating in it.
   See templates/login.html for the composition; this is how it is built. */
.login {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  /* A ground under the fixed hero so there is never a white flash before the
     photograph decodes, and so the fallback field has something to sit on. */
  background: #101215;
}

/* Fixed rather than absolute: the photograph is the room, and a room does not
   scroll. It also keeps the image behind mobile browser chrome instead of
   stopping at a soft viewport edge and leaving a band of ground. */
.login__hero {
  position: fixed; inset: 0; z-index: 0;
  background: #101215;
  --hero-image: url("/static/hero/login-hero.webp");
  --hero-overlay: 0.28;
  /* Restated after the shorthand, longhand, and on purpose. `background:` is a
     shorthand and resets background-size and background-position; an earlier
     version of this file lost both that way and only looked right because the
     image happens to be larger than its box in both axes. */
  background-image: var(--hero-image, none);
  background-size: cover;
  background-repeat: no-repeat;
  /* 22%, not centre, and the arithmetic is worth writing down. The photograph
     is 1600x901 (1.78:1) and puts the car from about 2% to 51% of its width,
     with empty dusk sky to the right. Full-bleed on a landscape screen crops
     only the sides — roughly 10% of the width at 16:10 — so 22% keeps the
     whole car in frame and leaves the right third, where the card lands, as
     sky and road. On a portrait phone `cover` shows about a quarter of the
     width, and 22% frames the cabin and front wheel rather than a wheel arch. */
  background-position: 22% center;
}
/* Two jobs in one cheap layer, and no second blurred surface: this is the
   composed fallback field for when no photograph is supplied — a deep charcoal
   ground with one brand sweep rather than a borrowed image — and, with the
   photograph present, the mild scrim that stops a lit horizon from dissolving
   the card's own edge. Unset --hero-image and the field returns unchanged. */
.login__hero::before {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(120% 90% at 15% 10%, rgba(233, 27, 33, 0.20), transparent 55%),
    linear-gradient(160deg, #1c1c20 0%, #26282c 55%, #14161a 100%);
  opacity: var(--hero-overlay, 1);
}

/* The layer that positions the card. Centred on a phone; from 900px it moves
   right of centre, where the photograph leaves dusk sky to sit a card on.
   The outer padding is the safe area or 16px, whichever is larger, so the card
   never sits under a notch or a home indicator. */
.login__stage {
  position: relative; z-index: 1;
  min-height: 100vh;
  min-height: 100dvh;
  display: grid; align-content: center; justify-items: center;
  padding:
    max(var(--s4), env(safe-area-inset-top))
    max(var(--s4), env(safe-area-inset-right))
    max(var(--s4), env(safe-area-inset-bottom))
    max(var(--s4), env(safe-area-inset-left));
}

/* The same material as the navigation island, so signing in and using the
   application are visibly the same product — one blurred surface, and the only
   one on this screen. */
.login__card {
  width: 100%; max-width: 400px;
  padding: var(--s6) var(--s5) var(--s5);
  /* 22px: inside the chrome family the island established, and short of its
     30px because this is a card that stays a card rather than a bar that
     becomes one. */
  border-radius: 22px;
  background-color: var(--login-glass);
  background-image: var(--glass-sheen);
  backdrop-filter: blur(var(--login-blur)) saturate(var(--login-saturate));
  -webkit-backdrop-filter: blur(var(--login-blur)) saturate(var(--login-saturate));
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-highlight), var(--shadow-float-hi);
}
/* Backdrop blur is not universal, and on this screen the thing behind it is a
   photograph. Without blur the card becomes an opaque surface rather than an
   unreadable one. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .login__card {
    background-color: rgba(255, 255, 255, 0.97);
    background-color: light-dark(rgba(255,255,255,0.97), rgba(24,26,29,0.96));
    background-image: none;
  }
}

.login__brand { margin-bottom: var(--s5); }
.login__brand .brand__mark { font-size: 1.0625rem; }
.login__title {
  font-size: var(--f-title); font-weight: 700; letter-spacing: -0.01em;
  margin-bottom: var(--s5);
}

/* Labels take full ink rather than muted here. Muted clears the contrast floor
   on this card by arithmetic (see --login-glass) but only just, and a label
   over a photograph is the wrong place to spend the last of the margin. */
.login__card .field { margin-bottom: var(--s4); }
.login__card .field label { color: var(--ink); font-weight: 600; }
.login__card input[type=text],
.login__card input[type=password] {
  padding: 11px 12px;
  border-radius: var(--radius-lg);
  /* A surface on glass, not a hole in it. Font size is deliberately not set:
     the base rule keeps inputs at 16px so iOS Safari does not zoom the page in
     and then refuse to zoom back out. */
  background: rgba(255, 255, 255, 0.72);
  background: light-dark(rgba(255,255,255,0.72), rgba(255,255,255,0.06));
  border-color: rgba(20, 22, 24, 0.18);
  border-color: light-dark(rgba(20,22,24,0.18), rgba(255,255,255,0.18));
}
.login__card input[type=text]:focus,
.login__card input[type=password]:focus {
  border-color: var(--focus);
}

/* Login has one unambiguous primary action, so the one brand-red button in the
   application that fills its container is this one. 44px is a touch target. */
.login__submit {
  width: 100%; min-height: 44px; margin-top: var(--s2);
  font-size: var(--f-body); font-weight: 640;
  border-radius: var(--radius-lg);
}

.login__note {
  margin-top: var(--s5); font-size: var(--f-micro); color: var(--ink-muted);
}

/* Errors and throttle messages render inside the card, in the flow, above the
   form — so the card grows by the height of one line and nothing else on the
   screen moves. No popup, no second layout. */
.login__card .notice { margin-bottom: var(--s4); }

@media (min-width: 900px) {
  .login__stage { justify-items: end; }
  .login__stage { padding-right: clamp(48px, 12vw, 200px); }
  .login__card { max-width: 420px; padding: var(--s7) var(--s6) var(--s6); }
}

/* A phone is not a desktop with less room: it is a worse backdrop (the crop is
   a quarter of the photograph, so whatever is behind the card is likely to be
   one flat tone) on hardware that pays more for a large blur. So the card
   becomes more opaque and frosts less, and signing in wins over the picture. */
@media (max-width: 599px) {
  .login__card {
    --login-glass: rgba(255, 255, 255, 0.94);
    --login-glass: light-dark(rgba(255,255,255,0.94), rgba(24,26,29,0.93));
    --login-blur: 14px;
  }
}

/* The Dashboard's manual sync, in the Today header as of 10 September 2026.
   Button first so it lines up with the heading's baseline, the last-synced
   time under it in the quiet size — compact enough that a header row keeps
   reading as a header row rather than a toolbar. */
.head-sync {
  display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
  flex: 0 0 auto;
}
.head-sync__hint { font-size: var(--f-micro); color: var(--ink-faint); }
@media (max-width: 720px) {
  /* Under the heading, left-aligned and compact. It used to stretch to the
     full row, which on a sales screen made the one operational control the
     biggest thing above the fold. It is a real 44px target, and it is not the
     dominant action on the page. */
  .head-sync { align-items: flex-start; width: 100%; }
  .head-sync .btn {
    justify-content: center; min-height: 44px;
    padding: 10px var(--s4); font-size: var(--f-small);
  }
  .head-sync__hint { text-align: left; }
}

/* --- admin landing ------------------------------------------------------- */
/* Two cards, built from the panel and pill tokens the Dashboard already uses,
   so this reads as the same app rather than a new screen. */
.admin-grid {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
}
.admin-card {
  display: grid; gap: var(--s2); align-content: start;
  grid-template-areas: "title meta" "body body" "foot foot";
  grid-template-columns: 1fr auto;
  padding: var(--s4) var(--s5);
  background: var(--surface-alt); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); text-decoration: none;
  transition: border-color var(--motion-fast) var(--ease-standard),
              transform var(--motion-standard) var(--ease-standard);
}
.admin-card__title {
  grid-area: title; font-size: var(--f-title); font-weight: 700;
  letter-spacing: -0.01em; color: var(--ink);
}
.admin-card__meta { grid-area: meta; justify-self: end; align-self: center; }
.admin-card__body { grid-area: body; font-size: var(--f-small); color: var(--ink-muted); }
.admin-card__foot { grid-area: foot; font-size: var(--f-micro); color: var(--ink-faint); }

/* --- acquisition lab ------------------------------------------------------ */
/* Section navigation inside the Acquisition Lab. Built from `.chip`'s shape
   rather than a new one — the Lab is a part of this application, not a
   visiting design — but as real links with `aria-current="page"` rather than
   pressed buttons, because these navigate. `min-height: 34px` clears the 24px
   touch-target floor the consistency sweep set, with room for the 1px border.

   `__item`, not `__link`, and not for taste: the `__link` spelling contains the
   substring `nav__link`, a class retired in the island redesign that the
   consistency sweep bans by substring. The test was right to fail — a reader
   searching this file for the dead class should not find a live one. */
.lab-nav { display: flex; flex-wrap: wrap; gap: var(--s1); margin-bottom: var(--s4); }
.lab-nav__item {
  display: inline-flex; align-items: center; padding: 6px 12px; min-height: 34px;
  border: 1px solid var(--hairline); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--ink-muted);
  font-size: var(--f-small); font-weight: 540; text-decoration: none;
}
.lab-nav__item--current {
  background: var(--accent-wash); border-color: var(--accent);
  color: var(--accent-strong); font-weight: 620;
}

/* --- empty states --------------------------------------------------------- */
/* One empty state, one definition of it. There were two `.empty` rules 900
   lines apart with different padding and border colours — the later one won,
   so the first had never applied — and three different inner shapes across
   the templates (h3+p+a, div+p+div, p+p). The h3 was also a heading-level
   skip: on What Changed and Win-back it followed an h1 with no h2 between. */
.empty {
  padding: var(--s6) var(--s4); text-align: center;
  color: var(--ink-muted);
  border: 1px dashed var(--hairline-strong); border-radius: var(--radius);
  background: var(--surface);
}
.empty__title { margin: 0 0 var(--s1); font-size: var(--f-title);
  font-weight: 650; color: var(--ink); }
.empty__body { color: var(--ink-faint); font-size: var(--f-small); margin: 0 auto; max-width: 46ch; }
.empty__action { margin-top: var(--s3); }

/* --- responsive ----------------------------------------------------------- */
/* The table is shown, then swapped for cards — never shrunk until it is
   unusable. 900px is where the eight columns stop fitting honestly. */
.leads-table-view { display: none; }
.leads-card-view  { display: block; }
@media (min-width: 900px) {
  .leads-table-view { display: block; }
  .leads-card-view  { display: none; }
}

/* On a phone the filter set is a disclosure, not a toolbar. */
@media (max-width: 899px) {
  .filterbar { flex-direction: column; align-items: stretch; }
  /* `.filters-panel__row` is NOT overridden here any more — see the
     mobile-first definition further down, and the note with it. */
  .filterbar__field select, .filterbar__field input { width: 100%; min-width: 0; }
  .filterbar__actions { margin-left: 0; }
  .sub-queues { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* --- link restraint -------------------------------------------------------
   Red carries identity and action. A panel whose every row is a red link
   spends the brand on navigation and leaves nothing for the button that
   actually starts a phone call, so links inside dense lists read as ink and
   only warm on hover. The deliberate "→" calls to action keep their red. */
.insight__rows a,
.pager a {
  color: var(--ink);
  text-decoration: none;
}
.pager a { color: var(--ink-muted); }

/* The signed-out shell is full-bleed: no app padding, no shell max-width. */
.main--bleed { padding: 0; max-width: none; }


/* --- the filter panel -----------------------------------------------------
   One control set, two shapes. On a phone it is a disclosure so the first
   lead stays near the top; from 900px the summary is hidden and the panel is
   forced open, so it reads as an ordinary toolbar row. Overriding the UA's
   `details:not([open])` rule is what lets one piece of markup do both. */
/* Not `display: contents`: Chrome stops rendering a <details> altogether when
   its box is removed, which hid every control the panel holds. It is a real
   flex box that joins the toolbar row instead. */
/* Chrome puts a <details>'s content into a single internal box, so flexing
   the element itself lays out "summary + one block" and every control stacks.
   The row that actually holds the fields is a plain div inside it. */
.filters-panel { flex: 1 1 100%; min-width: 0; }

/* MOBILE-FIRST, and deliberately so. This rule used to be declared twice: the
   phone shape lived in `@media (max-width: 899px)` above and this block then
   re-declared `align-items: flex-end` unconditionally, further down the file.
   Equal specificity, later source order wins — so the phone kept the column
   direction and lost the stretch, and the four selects hugged the right edge
   at their natural widths in a ragged stair-step (measured at 390px: 162, 115,
   158 and 154px inside a 332px container).
   Fixing the order alone would have left the same trap for the next edit, so
   the two shapes are now one declaration plus one min-width override. There is
   nothing left to collide. */
.filters-panel__row {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s2);
}
@media (min-width: 900px) {
  .filters-panel__row { flex-direction: row; flex-wrap: wrap; align-items: flex-end; }
}
.filters-panel > summary {
  cursor: pointer; font-weight: 650; font-size: var(--f-small);
  padding: 9px 12px; border: 1px solid var(--hairline-strong);
  border-radius: var(--radius); list-style: none; width: 100%;
  background: var(--surface-sunk);
}
.filters-panel > summary::-webkit-details-marker { display: none; }
.filters-panel > summary::after { content: " ▾"; color: var(--ink-faint); }
.filters-panel[open] > summary::after { content: " ▴"; }

@media (min-width: 900px) {
  /* Desktop never collapses it. The markup no longer arrives `open`, so the
     content has to be revealed here rather than assumed: `::details-content`
     is the element the UA hides, and making it visible is what turns the
     disclosure back into a toolbar row. */
  .filters-panel > summary { display: none; }
  .filters-panel::details-content { content-visibility: visible; block-size: auto; }
}
/* An engine without `::details-content` would hide the controls on desktop and
   hide the summary that opens them — filters unreachable. So on that engine the
   summary stays, and the cost is one click rather than a dead end. */
@supports not selector(::details-content) {
  @media (min-width: 900px) {
    .filters-panel > summary { display: block; }
  }
}


/* ==========================================================================
   Motion
   --------------------------------------------------------------------------
   BYD's own site animates on the way in and stays still once you are there.
   That is the principle worth taking: motion for orientation and feedback,
   never for decoration, and never on the path between a salesperson and a
   phone number.

   Rules kept deliberately:
   * transform and opacity only — nothing here animates layout;
   * no motion on the lead rows themselves beyond a hover tint. Two thousand
     rows staggering in is a marketing trick that would cost real seconds;
   * entrances run once, on load, and never on filter or pagination — a list
     that re-animates every time you narrow it is exhausting to work;
   * every non-essential animation is removed entirely under reduced motion,
     and nothing needs animation to be understood.
   ========================================================================== */

/* --- interactive feedback -------------------------------------------------- */
.btn,
.queue-tab,
.chip,
.sub-queue,
.lead-table tbody tr,
.metric,
.filters-panel > summary,
.more-filters > summary {
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    border-color var(--motion-fast) var(--ease-standard),
    color var(--motion-fast) var(--ease-standard),
    box-shadow var(--motion-standard) var(--ease-standard),
    transform var(--motion-fast) var(--ease-standard);
}

/* The primary action is the one thing that moves under the finger. */
.btn--primary:active { background: var(--brand-pressed); border-color: var(--brand-pressed); }
.btn:active { transform: translateY(1px); }

/* A clickable block lifts a little; it does not scale. Scaling a card full of
   numbers resamples the text and looks cheap at this density. */

/* Arrow links: the arrow moves, the text does not. Straight from BYD's own
   link treatment, and the one flourish worth having here. */
/* 24px minimum, which is WCAG 2.5.8's floor for a target. These measured
   20-23px tall at 375px — a 13px line box with nothing around it. */
.arrow-link { display: inline-flex; align-items: center; gap: 6px;
  min-height: 24px; }
.arrow-link::after {
  content: "→";
  transition: transform var(--motion-standard) var(--ease-standard);
}
/* The arrow's nudge lives in the pointer-guarded block below. */

/* Focus must never be animated away or hidden behind a transition. */
:focus-visible { transition: none; }

/* --- pointer feedback -----------------------------------------------------
   One interaction language, and it only exists where a pointer does.

   `@media (hover: hover) and (pointer: fine)` is the whole guard: a phone
   reports `hover: none`, so a tap never leaves a card stuck in a hover state —
   which is what "sticky hover" is, and it is worse than no hover at all.

   The rule for what gets it: something that *does* something when clicked. Not
   a heading that happens to sit inside a card, not a label, not a row of text
   the CSS gives a pointer cursor to for unrelated reasons. Every selector below
   is a real control or a real link.

   1px, not a zoom. A card full of tabular numbers that scales resamples its
   text and looks cheap at this density, and anything larger than a pixel is
   layout the eye reads as movement. Transform and shadow only — nothing here
   touches a layout property, so nothing reflows.
   ========================================================================== */
@media (hover: hover) and (pointer: fine) {
  .btn,
  .island__menu,
  .island__who,
  .who-pop__person,
  .drawer__link,
  .drawer__admin,
  .drawer__settings,
  .queue-tab,
  .chip,
  .hero-queue,
  .sub-queue,
  .pcard,
  .gate__person,
  .lead,
  .segmented button,
  .pager a,
  .filters-panel > summary,
  .more-filters > summary {
    transition:
      transform var(--motion-fast) var(--ease-standard),
      background-color var(--motion-fast) var(--ease-standard),
      border-color var(--motion-fast) var(--ease-standard),
      color var(--motion-fast) var(--ease-standard),
      box-shadow var(--motion-standard) var(--ease-standard);
  }

  .btn:hover,
  .island__menu:hover,
  .island__who:hover,
  .queue-tab:hover,
  .chip:hover,
  .hero-queue:hover,
  .sub-queue:hover,
  .pcard:hover,
  .gate__person:hover,
  .lead:hover,
  .segmented button:hover,
  .filters-panel > summary:hover,
  .more-filters > summary:hover { transform: translateY(-1px); }

  /* Press returns it to the surface. The control ends where it started, so a
     click never moves the thing being clicked out from under the pointer. */
  .btn:active,
  .island__menu:active,
  .island__who:active,
  .queue-tab:active,
  .chip:active,
  .hero-queue:active,
  .sub-queue:active,
  .pcard:active,
  .gate__person:active,
  .lead:active,
  .segmented button:active { transform: translateY(0); }

  /* Rows inside a dense table get emphasis, never movement: two thousand rows
     that lift on hover is a wobble, not feedback. The tint already exists. */
  .lead-table tbody tr { transform: none !important; }

  /* A control that cannot be used does not answer. `[disabled]` covers the
     real thing; `[aria-disabled]` covers the ones that only look disabled. */
  .btn[disabled],
  .btn[aria-disabled="true"],
  [disabled]:hover,
  [aria-disabled="true"]:hover { transform: none !important; box-shadow: none; }

  /* The small flourishes that used to sit ungated further up the file. Each
     is a nudge on a pointer and nothing at all on a touch screen, where a
     "hover" is a tap that has already finished. */
  .island__menu:hover .island__bars i:first-child { transform: translateX(2px); }
  .island__menu:hover .island__bars i:last-child { transform: translateX(-2px); }
  .admin-card:hover { transform: translateY(-1px); }
  .lab-nav__item:hover { border-color: var(--hairline-strong); color: var(--ink); }
  .arrow-link:hover::after { transform: translateX(1px); }
  .arrow-link--back:hover::before { transform: translateX(-1px); }
  .lead-table tbody tr:hover .vthumb { transform: translateX(1px); }

  /* Links inside prose and dense lists keep the underline they had and gain
     nothing else: they are text, not surfaces. */
  .insight__rows a:hover,
  .drawer__link:hover,
  .drawer__admin:hover,
  .drawer__settings:hover,
  .who-pop__person:hover,
  .pager a:hover { transform: none; }
}


/* --- hover, and only where a pointer exists ------------------------------
   Every rule below used to sit at top level beside the component it
   decorates. Only the 1px lift above was ever guarded, so on a phone a tap
   left the tapped card, button, tab or row holding its hover colours until
   something else was tapped. `@media (hover: hover) and (pointer: fine)` is
   the whole fix; the rules are unchanged and in their original order, so
   nothing about how they resolve against each other has moved.

   New rules belong here, not beside their component. */
@media (hover: hover) and (pointer: fine) {
  a:hover { text-decoration: underline; }
  .island__menu:hover { background: var(--surface-sunk); }
  .island__brand:hover { text-decoration: none; }
  .island__who:hover { background: var(--surface-sunk); }
  .island__who:hover .island__avatar { border-color: var(--hairline-strong); color: var(--ink); }
  .island__who--unset:hover .island__avatar { background: var(--brand); color: var(--on-brand); }
  .who-pop__person:hover { background: var(--surface-sunk); }
  .drawer__link:hover { background: var(--surface-sunk); text-decoration: none; }
  .drawer__admin:hover { background: var(--surface-sunk); color: var(--ink); }
  .drawer__settings:hover { background: var(--surface-sunk); color: var(--ink); }
  .settings__back:hover { background: var(--surface-sunk); color: var(--ink); }
  .segmented button:hover { color: var(--ink); }
  .queue-tab:hover { background: var(--surface-sunk); color: var(--ink); text-decoration: none; }
  .lead:hover { border-color: var(--hairline-strong); box-shadow: var(--shadow-md); }
  .btn:hover { background: var(--surface-sunk); text-decoration: none; }
  .btn--primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
  .btn--secondary:hover { background: var(--surface-sunk); }
  .btn--quiet:hover { background: var(--surface-sunk); color: var(--ink); }
  .btn--ghost:hover { background: var(--surface-sunk); color: var(--ink); }
  .chip:hover { background: var(--surface-sunk); color: var(--ink); text-decoration: none; }
  a.metric:hover { text-decoration: none; border-color: var(--hairline-strong); }
  a.metric--call-now:hover { border-top-color: var(--q-call-now); }
  a.metric--call-today:hover { border-top-color: var(--q-call-today); }
  .reasons-more > summary:hover { text-decoration: underline; }
  .run__detail > summary:hover { color: var(--ink); }
  .filterbar__hint a:hover { color: var(--ink); }
  .lead-table tbody tr:hover { background: var(--surface); }
  .lead-table__name:hover { color: var(--brand-ink); }
  .hero-queue:hover { text-decoration: none; box-shadow: var(--shadow-float-hi); }
  .sub-queue:hover { text-decoration: none; box-shadow: var(--shadow-md); }
  .prio__all:hover { color: var(--brand-ink); text-decoration: none; }
  .pcard:hover {
    text-decoration: none; box-shadow: var(--shadow-md);
    border-color: var(--hairline-strong);
  }
  .pcard:hover .vthumb { color: var(--ink); }
  .pcard:hover .vthumb--unknown { color: var(--ink-faint); }
  .insight__rows a:hover { color: var(--brand-ink); text-decoration: underline; }
  .gate__person:hover { border-color: var(--hairline-strong); box-shadow: var(--shadow-md); }
  .admin-card:hover { border-color: var(--hairline-strong); }
  .pager a:hover { color: var(--brand-ink); text-decoration: underline; }
  .btn--primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); }
  .sub-queue:hover { border-color: var(--hairline-strong); }
  .lead-table tbody tr:hover .vthumb { color: var(--ink); }
  .lead-table tbody tr:hover .vthumb--unknown { color: var(--ink-faint); }
}

/* --- entrances ------------------------------------------------------------- */
/* One shape, used sparingly: 8px up and a fade, on the standard curve. */
@keyframes vyli-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* Dashboard panels arrive in a short stagger. Six panels, 40ms apart, done in
   under half a second — enough to read as ordered, not as a performance. */
/* Three blocks, 60ms apart, done in under half a second. Not the six-step
   stagger the old grid had: the page has a hierarchy now, so the motion only
   has to follow it, and the lead cards do not animate in one by one. */
.today-queues, .prio, .dash-lower {
  animation: vyli-rise var(--motion-slow) var(--ease-enter) both;
}
.prio { animation-delay: 60ms; }
.dash-lower { animation-delay: 120ms; }

/* One entrance on the login screen, and it is the card: opacity and 8px of
   travel, once. The hero used to ease out of a 1.04 scale as well — a
   full-bleed photograph that zooms on every load is a screensaver, and it
   moved again on every failed sign-in, which is the last moment to be
   decorative. The notice inside the card is deliberately left out of the
   staggered `vyli-rise` group for the same reason: an error should be there
   when the card arrives, not announced after it. */
.login__card { animation: vyli-rise 340ms var(--ease-enter) both; }
.login__card .notice { animation: none; }

/* The filter disclosure. Height cannot be transitioned from auto, and
   animating it would thrash layout on every open, so the panel fades and
   lifts its contents instead. */
.filters-panel[open] > .filters-panel__row,
.more-filters[open] > .more-filters__panel {
  animation: vyli-rise var(--motion-standard) var(--ease-enter) both;
}

/* The custom date range appears in place when the band is chosen. */
.custom-range--on { animation: vyli-rise var(--motion-fast) var(--ease-enter) both; }

/* A flash message is feedback and should arrive, not blink into existence. */
.notice { animation: vyli-rise var(--motion-standard) var(--ease-enter) both; }

/* --- reduced motion --------------------------------------------------------
   Not a softened version — off. Every state above is legible without motion,
   because none of it carries meaning on its own. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .btn:active { transform: none; }
  .arrow-link:hover::after { transform: none; }
}


/* ==========================================================================
   Vehicle imagery
   --------------------------------------------------------------------------
   The artwork is VYLI's own: flat silhouettes drawn for this application, one
   per body style, in `currentColor` so a single file works in both themes.
   **No BYD photograph or render is bundled.** The reference library records
   the official imagery as URLs and metadata; shipping any of it is a decision
   that is not the build's to make. See docs/VEHICLE-ASSETS.md.

   The wide side-profile crop is the one idea taken from BYD's own menu
   artwork — it is what still identifies a model at 72px.
   ========================================================================== */
.vthumb {
  display: block; width: 96px; height: 54px; object-fit: contain;
  color: var(--ink-secondary);
  transition: transform var(--motion-standard) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard);
}
/* A model we could not identify must not look as confident as one we could. */
.vthumb--unknown { color: var(--ink-faint); opacity: 0.55; }

/* --- the lead workspace's header -----------------------------------------
   Who, then what they want, then what state it is in — the same order the
   Leads table and the mobile cards read in.

   What this replaced: `.lead-hero`, a tinted band holding a 280x104
   letterboxed photograph, sitting *above* the customer's name. It made the
   workspace the only screen where the vehicle outranked the person, and the
   only place in the application with its own vehicle size. The plate is
   `.lead__art` and the image is `.vthumb` now — literally the same objects as
   the table, the mobile cards and the Dashboard's priority list. */
.lead-head {
  display: flex; flex-direction: column; gap: var(--s3);
  margin-bottom: var(--s5);
}
.lead-head__back { font-size: var(--f-small); }
.lead-head__row {
  display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
}
.lead-head__ident {
  flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 2px;
}
.lead-head__ident h1 {
  font-size: var(--f-display); letter-spacing: -0.02em; overflow-wrap: anywhere;
}
.lead-head__vehicle {
  margin: 0; font-size: var(--f-small); color: var(--ink-muted);
  overflow-wrap: anywhere;
}
.lead-head__actions { flex: 0 0 auto; }
/* The badges are a row of their own. They used to trail the heading as a
   middot-separated sentence, which is the shape the rest of the application
   stopped using in Phase 4. */
.lead-head__state {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
}
.lead-head__score {
  font-size: var(--f-small); color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 640px) {
  .lead-head__actions { width: 100%; justify-content: flex-start; }
  .lead-head__ident h1 { font-size: 1.375rem; }
}

/* --- arrow links --------------------------------------------------------- */
.arrow-link--back::after { content: none; }
.arrow-link--back::before {
  content: "←"; transition: transform var(--motion-standard) var(--ease-standard);
}
/* The back arrow's nudge lives in the pointer-guarded block below. */

/* --- dark-mode imagery ---------------------------------------------------
   No white rectangle floating on black: the artwork inherits the surface it
   sits on and takes its colour from the text, so it never needs a plate. */
@media (prefers-color-scheme: dark) {
  :root[data-theme="system"] .vthumb { color: var(--ink-secondary); }
}


/* --- admin unlock --------------------------------------------------------- */
/* One column, narrow, centred on the reading measure rather than the viewport:
   a single short field in the middle of a wide screen looks like an error
   page. Built from the same panel tokens as everything else. */
.unlock {
  max-width: 30rem; display: flex; flex-direction: column; gap: var(--s4);
  padding: var(--s6); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); background: var(--surface);
}
.unlock__body { margin: 0; color: var(--ink-muted); font-size: var(--f-small); }
.unlock__body strong { color: var(--ink); }
.unlock__form { display: flex; flex-direction: column; gap: var(--s2); }
.unlock__label {
  font-size: var(--f-micro); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--ink-faint); font-weight: 700;
}
/* Wide letter-spacing so a PIN is countable at a glance while typing, and
   16px so iOS Safari does not zoom the page in and refuse to zoom back out. */
.unlock__input {
  font-size: 1rem; letter-spacing: 0.35em; padding: var(--s3);
}
.unlock__form .btn { justify-content: center; }
.unlock__foot { margin: 0; }
