/* Shared design tokens + components — Southern Foodservice CRM / SFS Rep App.
   Industrial-instrument system: flat matte plates, hairline seams, one
   signal accent (orange) for action/selection/live state, one alert color
   (red) for negative/destructive — nothing else gets color. Depth comes
   from 1px borders only. Loaded by every page. */

/* The sign-in screen's display face — the one screen that is a brand moment rather than an interface, same as
   Southern Hub's and People & Safety's own sign-in screens, which use this exact font for exactly the same reason
   (see their globals.css, "STYLE: Instrument" — this app is their source of truth for Instrument's tokens, but
   --font-display is the one face that isn't part of either named style: it is style-independent in all three
   apps, shown on sign-in whether Glass or Instrument is chosen). Self-hosted, the same file, so there's no
   third-party request; 9.8KB, used on one screen, so it isn't behind Instrument's lazy webfont loader below. */
@font-face {
  font-family: "Archivo Black";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/archivo-black.woff2") format("woff2");
}
.font-display { font-family: var(--font-display); letter-spacing: -0.01em; }

/* Instrument's webfonts (Inter Tight, JetBrains Mono) are NOT @imported here:
   an @import inside the one render-blocking stylesheet chained a second
   blocking request to another origin (DNS + TLS + CSS) in front of the first
   paint for everyone — measured ~0.9s of Lighthouse's mobile render-blocking
   time — while Glass, the default, uses the system font and never downloads a
   single face from it. index.html's head script (loadStyleFonts) adds the
   stylesheet only when Instrument is the chosen style, and setStyle() in
   shared.js adds it on a live switch. */

/* ── STYLE: Instrument ──
   The app ships two named styles, switched by `data-style` on <html> (see
   STYLES.md, and setStyle()/getStyle() in shared.js):
     - Glass       — the house style and the default. Southern Hub's Apple /
                     Liquid Glass look. Defined in the "STYLE: Glass" section
                     further down, as overrides scoped to
                     :root[data-style="glass"].
     - Instrument  — this app's original industrial-instrument look.
   The bare, unscoped tokens and component rules in this file ARE Instrument —
   nothing below is scoped to [data-style="instrument"], so selecting it gives
   exactly the app as it looked before Glass existed. Keep it that way: a
   change meant only for Glass goes in the Glass section, never here. */
:root {
  --bg: #ededea; --bg-raised: #e2e2de; --well: #ffffff;
  --ink: #141414; --ink-dim: #606060;
  --line: #7d7d73;
  --accent: #ff4a00; --accent-ink: #141414; --accent-soft: #ffe4d6;
  /* --accent itself stays full-saturation everywhere it's a fill (buttons,
     active chips, selection) — ink-on-accent already clears contrast easily
     there. This darker variant is only for the handful of spots that put
     the accent color directly on TEXT against the app's own light
     background (a "today" date label, a visit reason) — --accent as text
     there measures 2.6-3.4:1, under WCAG's 4.5:1 body-text minimum. Dark
     theme's --accent-text below is just --accent again — it clears 4.5:1+
     on the darker ground. Its --accent-soft is #2c1b0f, deeper than the
     #3a2418 it was: the active tab label and "Next stop" sit on it and
     measured 4.31:1, and the grey address on the next-stop card 4.19:1.
     Now 4.90 and 4.76. Same value as Southern Hub's --primary-subtle. */
  --accent-text: #b33400;
  --warn: #b3261e; --warn-soft: #f3dad8;
  /* Scoped exception to the one-accent rule above: a real red/amber/green
     traffic light for the price-fix margin check (rep-dashboard.js) — asked
     for explicitly, and a genuinely three-state "don't send / close / good
     to send" gate reads faster as three real colours than it would forced
     into this system's usual ink/accent/warn palette. Not used anywhere
     else — everything else in the app keeps the restrained palette above.
     Text on its own -soft fill clears 4.5:1 in both themes (light 5.65 / 5.42,
     dark 6.17 / 7.37) — same values as Southern Hub's Instrument. */
  --good: #17693a; --good-soft: #dcf0e3;
  --caution: #7f5807; --caution-soft: #f7ecd3;
  --done: var(--ink); --done-soft: var(--bg-raised);
  --pending: var(--ink-dim); --pending-soft: var(--bg-raised);
  --shadow: none;
  color-scheme: light;

  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 24px;
  --radius-sm: 2px; --radius-md: 2px; --radius-lg: 2px; --radius-pill: 2px;

  --font-ui: 'Inter Tight', -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, "SF Mono", "Roboto Mono", Menlo, Consolas, monospace;
  /* Style-independent (see the @font-face comment above) — set once here rather than in this or the Glass
     section, so switching styles never touches it. */
  --font-display: "Archivo Black", "Arial Black", var(--font-ui);
  /* Dynamic Type: --dyn is the phone's text-size setting relative to the default (set by shared.js). */
  --dyn: 1;
  --fs-10: calc(10px * var(--dyn)); --fs-11: calc(11px * var(--dyn)); --fs-12: calc(12px * var(--dyn)); --fs-13: calc(13px * var(--dyn));
  --fs-15: calc(15px * var(--dyn)); --fs-20: calc(20px * var(--dyn)); --fs-28: calc(28px * var(--dyn));
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a19; --bg-raised: #232322; --well: #141414;
    --ink: #ededea; --ink-dim: #8a8a85;
    --line: #72726a;
    --accent: #ff4a00; --accent-ink: #141414; --accent-soft: #2c1b0f; --accent-text: var(--accent);
    --warn: #ea5a4e; --warn-soft: #2e1a17;
    --good: #4cc98a; --good-soft: #163825;
    --caution: #eab861; --caution-soft: #3a2d13;
    --done: var(--ink); --done-soft: var(--bg-raised);
    --pending: var(--ink-dim); --pending-soft: var(--bg-raised);
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --bg: #1a1a19; --bg-raised: #232322; --well: #141414;
  --ink: #ededea; --ink-dim: #8a8a85;
  --line: #72726a;
  --accent: #ff4a00; --accent-ink: #141414; --accent-soft: #2c1b0f; --accent-text: var(--accent);
  --warn: #ea5a4e; --warn-soft: #2e1a17;
  --good: #4cc98a; --good-soft: #163825;
  --caution: #eab861; --caution-soft: #3a2d13;
  --done: var(--ink); --done-soft: var(--bg-raised);
  --pending: var(--ink-dim); --pending-soft: var(--bg-raised);
  color-scheme: dark;
}
:root[data-theme="light"] {
  --bg: #ededea; --bg-raised: #e2e2de; --well: #ffffff;
  --ink: #141414; --ink-dim: #606060;
  --line: #7d7d73;
  --accent: #ff4a00; --accent-ink: #141414; --accent-soft: #ffe4d6; --accent-text: #b33400;
  --warn: #b3261e; --warn-soft: #f3dad8;
  --good: #17693a; --good-soft: #dcf0e3;
  --caution: #7f5807; --caution-soft: #f7ecd3;
  --done: var(--ink); --done-soft: var(--bg-raised);
  --pending: var(--ink-dim); --pending-soft: var(--bg-raised);
  color-scheme: light;
}

/* ── STYLE: Glass (house style, default) ── tokens
   Source of truth: southern-hub/src/app/globals.css (:root + dark blocks) —
   a change here must be made there too (STYLES.md). Same three-state
   light/dark pattern as the Instrument blocks directly above, with every
   selector carrying [data-style="glass"] so it out-specifies them: the bare
   `:root[data-theme="light"]`/`[data-theme="dark"]` blocks are (0,2,0), these
   are (0,3,0) — without that, picking a theme in the More sheet would snap
   the palette back to Instrument's. Light values are declared on BOTH the
   unthemed and the explicit-light selector for the same reason. */
:root[data-style="glass"],
:root[data-style="glass"][data-theme="light"] {
  --bg: #f5f5f7; --bg-raised: #ffffff; --well: #ffffff;
  --ink: #1d1d1f; --ink-dim: #5a5a5f;
  --line: #d2d2d7;
  /* Hub --primary oklch(0.545 0.205 25). Fills carry white text here, not
     Instrument's ink-on-orange — the darker red clears contrast with white. */
  --accent: #c62a2f; --accent-ink: #ffffff; --accent-soft: #fbeceb; --accent-text: #c62a2f;
  --warn: #9e2a2f; --warn-soft: #fbeae9;
  --good: #1f7a4a; --good-soft: #e6f4ec;
  --caution: #8f5c0b; /* was #9a6410: 4.45:1 on its tint, now 5.05 (audit) */ --caution-soft: #fbf1dc;
  --done: var(--ink); --done-soft: var(--bg-raised);
  --pending: var(--ink-dim); --pending-soft: var(--bg-raised);
  --shadow: 0 1px 2px rgb(31 58 42 / .05), 0 12px 40px -12px rgb(31 58 42 / .28);
  /* Card edge softer than --line (which stays the full hairline for dividers
     and inputs) — the shadow does the lifting, the border only defines. */
  --card-edge: rgb(0 0 0 / .06);
  --glass-bg: rgb(255 255 255 / .72);
  --glass-border: rgb(255 255 255 / .55);
  --glass-solid: #f5f5f7;
  --ease-glass: cubic-bezier(0.22, 1, 0.36, 1);
  color-scheme: light;

  --radius-sm: 12px; --radius-md: 16px; --radius-lg: 22px; --radius-pill: 999px;
  --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
  /* Same stack as --font-ui on purpose (Hub has no mono face) — every
     .num/.mono/numeric-input rule already pairs --font-mono with
     font-variant-numeric: tabular-nums, so columns of figures still align. */
  --font-mono: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
}
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) {
    --bg: #000000; --bg-raised: #1c1c1e; --well: #141416;
    --ink: #f5f5f7; --ink-dim: #a1a1a6;
    --line: #38383a;
    /* Hub dark: the red lightens where it's TEXT, filled buttons keep the
       darker red so white labels stay legible. */
    --accent: #c62a2f; --accent-ink: #ffffff; --accent-soft: #3a1d1c; --accent-text: #ff6f66;
    --warn: #ff6f66; --warn-soft: #3a1d1c;
    --good: #4cc98a; --good-soft: #16301f;
    --caution: #e8b85a; --caution-soft: #33280f;
    --done: var(--ink); --done-soft: var(--bg-raised);
    --pending: var(--ink-dim); --pending-soft: var(--bg-raised);
    --shadow: 0 1px 2px rgb(0 0 0 / .5), 0 12px 40px -12px rgb(0 0 0 / .6);
    --card-edge: rgb(255 255 255 / .08);
    --glass-bg: rgb(28 28 30 / .72);
    --glass-border: rgb(255 255 255 / .12);
    --glass-solid: #1c1c1e;
    color-scheme: dark;
  }
}
:root[data-style="glass"][data-theme="dark"] {
  --bg: #000000; --bg-raised: #1c1c1e; --well: #141416;
  --ink: #f5f5f7; --ink-dim: #a1a1a6;
  --line: #38383a;
  --accent: #c62a2f; --accent-ink: #ffffff; --accent-soft: #3a1d1c; --accent-text: #ff6f66;
  --warn: #ff6f66; --warn-soft: #3a1d1c;
  --good: #4cc98a; --good-soft: #16301f;
  --caution: #e8b85a; --caution-soft: #33280f;
  --done: var(--ink); --done-soft: var(--bg-raised);
  --pending: var(--ink-dim); --pending-soft: var(--bg-raised);
  --shadow: 0 1px 2px rgb(0 0 0 / .5), 0 12px 40px -12px rgb(0 0 0 / .6);
  --card-edge: rgb(255 255 255 / .08);
  --glass-bg: rgb(28 28 30 / .72);
  --glass-border: rgb(255 255 255 / .12);
  --glass-solid: #1c1c1e;
  color-scheme: dark;
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--font-ui); font-size: var(--fs-13); font-weight: 400;
  -webkit-font-smoothing: antialiased;
  /* Without this, dragging down at the very top of the page (attachPullToRefresh's
     whole gesture) also triggers the BROWSER's own native pull-to-refresh /
     rubber-band bounce on Android Chrome and iOS Safari — visible as a second,
     conflicting bounce animation racing the custom .ptr-indicator, and on
     Android a real full-page reload competing with the in-app one. This is
     the standard fix: contain the overscroll so it never chains past this
     element to the browser's own gesture. */
  overscroll-behavior-y: contain;
}
h1, h2, h3 { font-family: var(--font-ui); font-weight: 500; }

/* Numeric / data / IDs / timestamps — always monospace, tabular */
.num, .mono {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}

.iconbtn {
  border: 1px solid var(--line); background: var(--bg-raised); color: var(--ink);
  border-radius: var(--radius-md); padding: 7px 11px; font-size: var(--fs-11); cursor: pointer;
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: .06em;
  transition: background .08s linear, color .08s linear, transform .08s linear;
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
}
.iconbtn:active { background: var(--ink); color: var(--bg); border-color: var(--ink); transform: scale(.96); }

/* ---- Plate (the .card class name is kept so no markup changes were
   needed across pages — it now renders as a flat instrument plate with
   an ALL-CAPS legend strip flush to its top edge, driven by a direct
   h2 child) ---- */
.card {
  background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-4); margin-bottom: var(--space-4); box-shadow: none;
}
.card > h2 {
  margin: calc(-1 * var(--space-4)) calc(-1 * var(--space-4)) var(--space-4);
  padding: 8px var(--space-4); border-bottom: 1px solid var(--line);
  font-family: var(--font-ui); font-size: var(--fs-10); font-weight: 500;
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-dim);
  display: flex; align-items: center; gap: 6px;
}
/* A small solid indicator dot ahead of every panel legend — no glow, no
   gradient (this system is deliberately flat/matte throughout, see the
   file header), just a single accent mark reading like a hardware
   channel/status light on an instrument panel. */
.card > h2::before { content: ""; width: 5px; height: 5px; flex-shrink: 0; background: var(--accent); border-radius: 50%; }
label {
  display: block; font-size: var(--fs-10); font-weight: 500; color: var(--ink-dim);
  text-transform: uppercase; letter-spacing: .08em; margin: 10px 0 4px;
}
input[type=text], input[type=email], input[type=password], input[type=tel], input[type=file],
input[type=number], input[type=date], input[type=month],
textarea, select {
  width: 100%; padding: 10px 11px; border-radius: var(--radius-md); border: 1px solid var(--line);
  background: var(--well); color: var(--ink); font-size: var(--fs-13); font-family: var(--font-ui);
  /* iOS Safari auto-zooms the whole page on focus for any field under 16px
     — this keeps the visual size via var(--fs-13) but the effective font
     size read by Safari's zoom heuristic must be >=16px. */
  font-size: max(var(--fs-13), 16px);
}
/* 1100, not 760: an iPad is still iOS Safari and still has the zoom-on-
   focus behavior in EITHER orientation (768/1024 wide) — reverting to a
   sub-16px font at 760px re-triggered the exact zoom this rule exists to
   prevent, on a device that never stopped being touch/iOS. Only a
   genuinely wide desktop browser (not iOS) is safe to shrink back down —
   and wide is not enough: a 12.9" iPad in landscape is 1366px. Mouse only. */
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) {
  input[type=text], input[type=email], input[type=password], input[type=tel], input[type=file],
  input[type=number], input[type=date], input[type=month],
  textarea, select {
    font-size: var(--fs-13); /* no iOS zoom risk on desktop — keep the intended size */
  }
}
input[type=number], input[type=tel], input[type=date], input[type=month], input[type=password] {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
textarea { resize: vertical; min-height: 60px; font-family: var(--font-ui); }
.msg {
  font-size: var(--fs-11); padding: 9px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  margin-top: 10px; font-family: var(--font-mono);
}
.msg.ok { border-color: var(--ink); color: var(--ink); }
.msg.err { border-color: var(--warn); color: var(--warn); background: var(--warn-soft); }
.sync-banner {
  font-size: var(--fs-11); padding: 8px 16px; font-family: var(--font-mono); text-align: center;
}
.sync-banner-ok { background: var(--well); color: var(--ink); }
.sync-banner-err { background: var(--warn-soft); color: var(--warn); }
/* Per-item failed-sync row (see renderSyncBanner in shared.js) — sits right
   under the aggregate .sync-banner-err strip, one row per failed record so
   a rep can see and retry exactly which call didn't sync, not just a count. */
.sync-fail-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 16px; background: var(--warn-soft); border-top: 1px solid var(--warn);
  font-size: var(--fs-11);
}

/* Pull-to-refresh indicator (shared.js attachPullToRefresh) — a small ring
   pinned under the header that grows/rotates with the pull distance
   (--ptr-pull, set from JS on every touchmove) rather than a fixed-duration
   animation, so it tracks the finger 1:1 same as the rest of this app's
   gestures (drag-reorder, swipe). Fixed, not sticky: it must sit above the
   content it's about to reload, never push it down. */
.ptr-indicator {
  position: fixed; top: calc(8px + env(safe-area-inset-top)); left: 50%;
  width: 30px; height: 30px; margin-left: -15px; z-index: 7;
  border-radius: 50%; background: var(--bg-raised); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transform: translateY(-100%) scale(.7);
  transition: opacity .12s linear, transform .12s linear;
  pointer-events: none;
}
.ptr-indicator.ptr-visible {
  opacity: 1;
  transform: translateY(calc(var(--ptr-pull, 0px) - 100%)) scale(1);
  transition: none; /* 1:1 with the finger while actively pulling */
}
.ptr-indicator.ptr-refreshing {
  transition: transform .18s ease-out;
}
.ptr-spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--accent);
  transform: rotate(calc(var(--ptr-pull, 0px) * 2.4deg));
}
.ptr-indicator.ptr-armed .ptr-spinner { border-top-color: var(--accent); border-color: var(--accent-soft); }
.ptr-indicator.ptr-refreshing .ptr-spinner {
  animation: ptrSpin .7s linear infinite;
}
@keyframes ptrSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .ptr-indicator.ptr-refreshing .ptr-spinner { animation: none; }
  .ptr-indicator { transition: opacity .12s linear; }
  .ptr-indicator.ptr-visible { transform: translateY(8px); }
}
.sync-fail-text { min-width: 0; }
.sync-fail-label { color: var(--warn); font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sync-fail-reason { color: var(--ink); margin-top: 2px; }
.sync-fail-actions { display: flex; gap: 6px; flex-shrink: 0; }
.sync-fail-actions .ghost { padding: 5px 9px; font-size: var(--fs-10); border-color: var(--warn); color: var(--warn); }
/* Reusable "this data sync looks stale" flag — Phocas syncs multiple times
   a day in normal operation, so a "last synced" readout past a day old
   means something's actually stopped working, not just a quiet period. */
.sync-stale { color: var(--warn); font-weight: 500; }
.hint { color: var(--ink-dim); font-size: var(--fs-11); margin-top: 6px; }
.empty {
  text-align: center; color: var(--ink-dim); padding: 28px 16px; font-size: var(--fs-11);
  font-family: var(--font-mono);
}
.badge {
  display: inline-block; font-family: var(--font-mono); font-size: var(--fs-10); font-weight: 500;
  text-transform: uppercase; letter-spacing: .04em; padding: 3px 7px;
  border-radius: var(--radius-sm); border: 1px solid var(--line); margin-top: 8px; margin-right: 6px;
  color: var(--ink-dim); background: transparent;
}
.badge.done { border-color: var(--ink-dim); color: var(--ink); }
.badge.followup { border-color: var(--warn); color: var(--warn); }
/* Same blue as the Prospect Finder map's "claimed prospect" pin (see
   prospect-finder.js's CLAIMED_COLOR) — a literal hex, not a theme token,
   deliberately kept identical in both places so a rep learns one color for
   "this is a prospect, not a real account yet" across the whole app. */
.badge.prospect { border-color: #2f6fed; color: #2f6fed; }
.tag {
  display: inline-block; padding: 2px 6px; font-family: var(--font-mono); font-size: var(--fs-10);
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: transparent; color: var(--ink-dim); margin: 1px;
}

button {
  font-family: var(--font-ui);
}
button.primary {
  background: var(--accent); color: var(--accent-ink); border: 1px solid var(--accent); border-radius: var(--radius-md);
  padding: 13px 16px; font-size: var(--fs-13); font-weight: 500; cursor: pointer;
  text-transform: uppercase; letter-spacing: .04em; min-height: 44px;
  transition: background .08s linear, color .08s linear, transform .08s linear;
}
button.primary:active { background: var(--accent-ink); color: var(--accent); transform: scale(.98); }
button.primary:disabled { opacity: .4; cursor: default; }
button.secondary {
  background: transparent; color: var(--ink); border: 1px solid var(--ink); border-radius: var(--radius-md);
  padding: 12px 16px; font-size: var(--fs-13); font-weight: 500; cursor: pointer;
  text-transform: uppercase; letter-spacing: .04em; min-height: 44px;
  transition: background .08s linear, color .08s linear, transform .08s linear;
}
button.secondary:active { background: var(--ink); color: var(--bg); transform: scale(.98); }
button.ghost {
  background: transparent; color: var(--ink-dim); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 7px 12px; font-size: var(--fs-11); cursor: pointer; text-transform: uppercase; letter-spacing: .04em;
  transition: background .08s linear, color .08s linear, transform .08s linear;
  min-height: 44px;
}
button.ghost:active { background: var(--ink); color: var(--bg); border-color: var(--ink); transform: scale(.97); }
@media (prefers-reduced-motion: reduce) {
  button.primary:active, button.secondary:active, button.ghost:active, .iconbtn:active { transform: none; }
}
button.danger { color: var(--warn); border-color: var(--warn); }
button.danger:active { background: var(--warn); color: var(--well); }

details > summary {
  list-style: none; cursor: pointer; padding: 10px 0; font-weight: 500; font-size: var(--fs-11);
  text-transform: uppercase; letter-spacing: .06em; font-family: var(--font-ui);
  display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line);
  transition: color .08s linear;
}
details > summary:active { color: var(--accent-text); }
details > summary::-webkit-details-marker { display: none; }
details > summary::after { content: "\203A"; transform: rotate(90deg); color: var(--ink-dim); transition: transform .12s ease; }
details[open] > summary::after { transform: rotate(-90deg); }
details + details { margin-top: 4px; }
@media (prefers-reduced-motion: reduce) {
  details > summary::after { transition: none; }
}

table { width: 100%; border-collapse: collapse; font-size: var(--fs-13); }
th, td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--line); vertical-align: middle; }
th {
  color: var(--ink-dim); font-weight: 500; font-size: var(--fs-10); text-transform: uppercase;
  letter-spacing: .06em; font-family: var(--font-ui);
}
tr.row-focused { position: relative; }
tr.row-focused > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.tabs {
  display: flex; margin-bottom: var(--space-4); flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: var(--radius-md); overflow: hidden; width: fit-content;
}
.tab, a.tab {
  border: none; border-right: 1px solid var(--line); background: var(--bg-raised); color: var(--ink-dim);
  border-radius: 0; padding: 8px 14px; font-size: var(--fs-10); font-weight: 500; cursor: pointer;
  text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; box-sizing: border-box;
  text-transform: uppercase; letter-spacing: .06em;
}
.tab:last-child { border-right: none; }
.tab.active { background: var(--accent); color: var(--accent-ink); }

/* Physical hard-snap toggle */
.switch { position: relative; width: 44px; height: 26px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute; inset: 0; background: var(--well); border: 1px solid var(--line);
  border-radius: var(--radius-sm); cursor: pointer; transition: background .06s linear, border-color .06s linear;
}
.slider::before {
  content: ""; position: absolute; width: 18px; height: 18px; left: 2px; top: 2px; background: var(--ink-dim);
  border-radius: 1px; transition: transform .06s linear, background .06s linear;
}
input:checked + .slider { background: var(--accent); border-color: var(--accent); }
input:checked + .slider::before { transform: translateX(18px); background: var(--accent-ink); }

.hidden { display: none !important; }
a.applink { color: var(--ink); font-size: var(--fs-11); text-decoration: none; text-transform: uppercase; letter-spacing: .06em; }

/* ---- Unified nav: bottom tab bar on phone, top inline strip on desktop
   (same markup, same JS — only placement/sizing changes via media query).
   `position: fixed`, not `sticky` — this is the LAST element in the DOM
   (after #viewMount), and a sticky bottom element only starts sticking once
   scroll reaches near the very end of the page's content; on any screen
   with a scrollable list (a full day's customers, a long table) it would
   disappear for the entire middle of the scroll. Fixed guarantees it's
   always on screen regardless of where the page is scrolled. */
.tabbar {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 6; display: flex;
  background: var(--bg-raised); border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
  max-width: 560px; margin: 0 auto;
}
body.admin-area .tabbar { max-width: 920px; }
.tabbar-item {
  position: relative;
  flex: 1; text-align: center; padding: 10px 4px 8px; font-size: var(--fs-10); font-weight: 500;
  color: var(--ink-dim); text-decoration: none; text-transform: uppercase; letter-spacing: .06em;
  border-top: 2px solid transparent; font-family: var(--font-ui); background: none; border-left: none; border-right: none; border-bottom: none;
  min-height: 48px; display: flex; align-items: center; justify-content: center;
  /* Fast and un-sprung, unlike the pill below — a :active press has to
     answer the touch with no perceptible lag in either direction; a bouncy
     or slow transition here would make the tap itself feel delayed. */
  transition: opacity .08s linear, transform .1s ease-out;
}
.tabbar-item:active { opacity: .55; transform: scale(.92); }
/* Which tab you're on, said two ways — color/weight alone was the only
   signal before, easy to miss at a glance on a phone. The filled pill
   behind the slot spring-scales in (not just fades) so tapping between
   tabs reads as a real, springy response, not a static color swap. The
   pill sits behind the label via z-index (a plain ::before in a
   position:relative parent) so it never needs the label re-wrapped in
   its own span. */
.tabbar-item::before {
  content: ""; position: absolute; inset: 6px 4px; z-index: -1;
  border-radius: var(--radius-md); background: var(--accent-soft);
  transform: scale(.92); opacity: 0;
  /* 150ms ease-out, no overshoot — the old springy bounce read as the tab
     bar wobbling on every tap (design audit C19). */
  transition: transform .15s ease-out, opacity .15s ease-out;
}
@media (prefers-reduced-motion: reduce) { .tabbar-item::before { transition: opacity .15s linear; transform: none; } }
.tabbar-item.active {
  color: var(--accent-text); border-top-color: var(--accent); font-weight: 700;
}
.tabbar-item.active::before { transform: scale(1); opacity: 1; }
.tabbar-item.action { width: 100%; }
/* Offline-queue count on the More tab — the full sync banner (shared.js
   renderSyncBanner) lives right under the header and scrolls out of view
   with the rest of the page, but this sits on the fixed tab bar so "I have
   unsynced visits" stays glanceable no matter how far down Today a rep has
   scrolled. Same visual language as .bell-dot, but this one needs a number,
   not just a presence dot. */
.tabbar-badge {
  /* Anchored to the button's own corner (same convention as .bell-dot),
     not a fraction of its width — the More button's rendered width varies
     with tab count and viewport (flex:1 on phone, auto-width in the
     desktop/narrow media query below), so a width-relative offset drifted
     off the label at some sizes. This stays correct at any width. */
  position: absolute; top: 2px; right: 4px; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 999px; background: var(--accent); color: #fff;
  font-size: calc(10px * var(--dyn)); font-weight: 700; line-height: 16px; text-align: center;
  border: 1.5px solid var(--bg-raised); font-family: var(--font-ui);
}
.tabbar-badge.tabbar-badge-err { background: var(--warn); }
/* 1100, not 760 — matches shared.js's renderNav "narrow" cutoff and the
   other touch-vs-desktop breakpoints below: an iPad in either orientation
   (768/1024) is still a touch device held in the hands, where a fixed,
   thumb-reachable bottom nav is the right pattern, not a desktop-style
   static bar under the header.
   And mouse-only, not just wide: a 12.9" iPad in landscape is 1366px wide and
   still held in the hands, and at that width this block used to take the tab
   bar out of `position: fixed` — leaving it after the page content, so a rep
   had to scroll to the very end of the day's route to change section. */
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) {
  /* Desktop: nav reverts to normal document flow, directly under the
     header, rather than fixed — header.top is already fixed/sticky at the
     very top of the viewport, and a second fixed element also pinned to
     top:0 would overlap it. Admin pages lean on the ⌘K command palette for
     fast navigation regardless of scroll position, so this is a fine trade. */
  .tabbar {
    position: static; max-width: none; margin: 0; justify-content: flex-start;
    border-top: none; border-bottom: 1px solid var(--line);
  }
  /* In flow means in DOM order, and #navMount comes after #viewMount — so the
     "nav under the header" this block promises was actually at the bottom of
     every page. Reorder within .app's flex column rather than move markup. */
  #navMount { order: 1; }
  #viewMount { order: 2; }
  .tabbar-item { flex: 0 0 auto; padding: 12px 18px; }
  /* .action's width:100% is inert on phone (flex:1 gives it flex-basis:0,
     so width is ignored per spec) but flex-basis switches to auto right
     above, which lets that dormant width:100% resolve against the tabbar's
     own width — "More" tried to be as wide as the whole bar again and shot
     off past every other item. */
  .tabbar-item.action { width: auto; }
}

/* Admin views get a wider column than the phone-first rep views */
body.admin-area .app { max-width: 920px; }
/* iPad-range: still one column (nothing here is a multi-column layout), but
   560px stayed literally phone-width even on a 768-1024px screen, reading
   as a narrow strip lost in blank margin. Comfortably wider without going
   anywhere near admin's 920px, which would stretch single-column call
   cards/forms/lists uncomfortably thin-and-wide. */
@media (min-width: 700px) and (max-width: 1099px) {
  body:not(.admin-area) .app { max-width: 720px; }
  /* .tabbar is fixed and centers itself independently of .app (it's the
     last child of .app but position:fixed takes it out of flow) — without
     matching its own max-width here it stays phone-width while the content
     column above it is visibly wider, an obvious misalignment. */
  body:not(.admin-area) .tabbar { max-width: 720px; }
}
/* Real bug, found during the first-ever wide-desktop-browser pass (this app
   had only ever been tested on iPhone) — the tablet rule above stops at
   1024px, and nothing picked rep-area .app back up above that, so it fell
   straight back to the UNCONDITIONAL 560px at the top of this file. Net
   effect: a rep view on a big desktop browser rendered NARROWER than it did
   on an iPad, while .tabbar (below, 1100px) had already switched to its
   full-width desktop layout — a visibly mismatched, half-migrated shell.
   920px, not another bespoke number: matches admin's own desktop width
   exactly, so the two areas feel like one consistent app rather than two
   differently-tuned ones. 1100, not 1025: matches the .tabbar/#viewMount
   desktop cutover directly below, so the column widens in the same
   keystroke the nav does, not one breakpoint apart. */
@media (min-width: 1100px) {
  body:not(.admin-area) .app { max-width: 920px; }
  body:not(.admin-area) .tabbar { max-width: 920px; }
  /* Two-column routes (shared.js's WIDE_ROUTE_KEYS: the Today/customer split
     and the dashboard) get a wider column — 920px split in two is two phone-
     width strips. Width alone, not mouse-only: an iPad in landscape (1180 /
     1366px) is exactly the screen a rep wants the route beside the account
     on; the tab bar stays fixed at the bottom there (the touch rules above
     don't change) and just widens to match, same as the 720px rule. */
  body.wide-route:not(.admin-area) .app,
  body.wide-route:not(.admin-area) .tabbar { max-width: 1320px; }
}

/* ---- App shell (persistent across every route) ---- */
body { overflow-x: hidden; }
.app { max-width: 560px; margin: 0 auto; min-height: 100vh; display: flex; flex-direction: column; }
header.top {
  position: sticky; top: 0; z-index: 5; background: var(--bg);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--line);
}
/* Flex items default to min-width:auto, which refuses to shrink below the
   title's unwrapped width — that let a long greeting sentence overflow into
   the button group instead of wrapping. min-width:0 lets it wrap properly. */
header.top > div:first-child { min-width: 0; }
header.top h1 { font-size: var(--fs-15); font-weight: 500; margin: 0; }
/* min-width:0 on both the row and the heading — same overflow trap as
   header.top > div:first-child above (a long greeting sentence refusing to
   shrink below its unwrapped width), one flex level deeper now that the
   logo mark sits alongside the heading instead of the heading being the
   sole child. */
.shell-title-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.shell-title-row h1 { min-width: 0; }
/* Was 22px — small enough next to the 36px header icon buttons on the other
   side that the ring read as an afterthought rather than the brand mark it
   is. The source image itself has no wasted padding to trim (it's a ring
   inscribed in a square canvas, so the four corners are transparent by
   necessity, same as any round mark); it just needed more room. */
.logo-mark { height: 28px; width: 28px; object-fit: contain; flex-shrink: 0; }
/* Full lockup (circle + wordmark) — login screen only. Two theme-specific
   files (the wordmark is literally a different colour per theme, not just
   recolourable via CSS) swapped the same way every other themed value in
   this file is: media query for the OS default, explicit [data-theme]
   overrides for the in-app toggle. */
.logo-full { height: 34px; width: auto; display: block; margin: 0 auto 18px; }
.logo-full-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logo-full-light { display: none; }
  :root:not([data-theme="light"]) .logo-full-dark { display: block; }
}
:root[data-theme="dark"] .logo-full-light { display: none; }
:root[data-theme="dark"] .logo-full-dark { display: block; }
:root[data-theme="light"] .logo-full-light { display: block; }
:root[data-theme="light"] .logo-full-dark { display: none; }
header.top .sub { color: var(--ink-dim); font-size: var(--fs-10); text-transform: uppercase; letter-spacing: .06em; }
#viewMount { padding: 14px 16px; flex: 1; position: relative; overflow-x: clip; }
/* Content must reserve space at the bottom so fixed nav never covers it.
   Real, pre-existing bug just found: this MUST come after the shorthand
   `padding: 14px 16px` above — shorthand resets all four sides, so when this
   reservation was declared earlier in the file (correct-looking in
   isolation), the later shorthand silently clobbered it back down to 14px
   on every single view, at every width, this whole session. It only stayed
   invisible because most pages' real content happened to be long enough to
   clear the tabbar zone anyway, or another element (.finishbar, before it
   went static) was separately reserving its own space right above the nav
   and masking the gap. Confirmed live: with .finishbar now static, "Log
   Call" rendered exactly 14px above the true bottom instead of ~78px,
   putting its lower half directly behind the fixed tabbar even at max
   scroll — this fixes that at the root, not just for this one page. */
#viewMount { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
/* Desktop: .tabbar above reverts to static, in-flow nav (see the 1100px
   block near the top of this file), so there's no fixed bar left to clear —
   must come after the unconditional reservation right above, same
   equal-specificity/source-order trap that caused this whole bug. */
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) {
  #viewMount { padding-bottom: 14px; }
}

/* ---- Push/pop transition between hierarchically-related routes (Today <->
   Customer detail). Peer routes (tab switches) don't use this — only a
   genuine parent/child navigation gets directional motion, per "spatial
   consistency": enter and exit along the same path. ---- */
.route-snapshot {
  position: fixed; z-index: 2; background: var(--bg); box-sizing: border-box;
  padding: 14px 16px; transition: transform .24s cubic-bezier(.32,.72,0,1);
  overflow: hidden;
}
.route-snapshot.route-exit-left { transform: translateX(-100%); }
.route-snapshot.route-exit-right { transform: translateX(100%); }
#viewMount.route-live {
  transition: transform .24s cubic-bezier(.32,.72,0,1);
}
#viewMount.route-enter-right { transform: translateX(100%); }
#viewMount.route-enter-left { transform: translateX(-100%); }
@media (prefers-reduced-motion: reduce) {
  .route-snapshot, #viewMount.route-live { transition: opacity .18s linear !important; transform: none !important; }
  .route-snapshot.route-exit-left, .route-snapshot.route-exit-right { opacity: 0; }
  #viewMount.route-enter-right, #viewMount.route-enter-left { opacity: 0; }
}

/* .scroll-reveal is deliberately inert now (design audit C19, user decision):
   dashboard cards used to fade and rise in — and fade back out — with every
   scroll, so figures a rep was reading dimmed as they scrolled past. The
   class stays on the markup but content is simply visible. */
.scroll-reveal { animation: none; opacity: 1; transform: none; }

/* Same mechanism as .scroll-reveal above, applied to gauge/progress-bar
   fills instead of whole cards: the fill "builds" (grows from empty) as it
   scrolls into view and "unbuilds" (shrinks back to empty) scrolling back
   up, since animation-timeline: view() is just live scroll position — there
   is no separate "reverse" case to handle. transform:scaleX, not width — the
   fill's actual width is still the real value (set inline per gauge), so
   this can't clash with wireCustomerList's own width transition, and scaleX
   is compositor-only (no layout thrash) unlike animating width itself. */
.gauge-fill, .margin-mini-fill { transform-origin: left center; }
/* The old scroll-linked build-in (animation-timeline: view()) is gone: gauges grow once when the dashboard first shows
   (animateGaugeFills in views/rep-dashboard.js) and then stay put however you scroll. */

.gate { max-width: 360px; margin: 60px auto; padding: 0 16px; text-align: center; }
.gate label { text-align: left; }
.hotkey-hint { font-size: var(--fs-10); color: var(--ink-dim); display: none; align-items: center; gap: 4px; }
/* 1100, matching the other touch-vs-desktop breakpoints: a ⌘K hint is
   meaningless on an iPad with no physical keyboard attached, so it should
   only appear once we're confident this is a real desktop browser window. */
/* ⌘K means nothing on a touch screen, however wide. */
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) { .hotkey-hint { display: flex; } }

/* ---- Bottom sheet (More menu, Notification Center) ---- */
.sheet-overlay {
  position: fixed; inset: 0; z-index: 20; background: rgba(20,20,20,.45);
  display: flex; align-items: flex-end; justify-content: center;
}
.sheet {
  width: 100%; max-width: 560px; max-height: 80vh; overflow-y: auto;
  background: var(--bg-raised); border-top: 1px solid var(--line); border-radius: 0;
  padding: var(--space-2) 0 calc(var(--space-4) + env(safe-area-inset-bottom));
}
/* Slide up on open, down on close (design audit C20). openSheet() adds
   .sheet-open on the next frame; closeSheet() swaps it for .sheet-closing and
   removes the overlay once the transition ends. A drag on the handle/title
   sets the transform inline (no transition) and releases into either a close
   or a spring back. Reduced motion: a plain fade, no travel. */
.sheet-overlay { opacity: 0; transition: opacity .25s cubic-bezier(.32,.72,0,1); }
.sheet-overlay .sheet { transform: translateY(100%); transition: transform .25s cubic-bezier(.32,.72,0,1); }
.sheet-overlay.sheet-open { opacity: 1; }
.sheet-overlay.sheet-open .sheet { transform: translateY(0); }
.sheet-overlay.sheet-closing { opacity: 0; pointer-events: none; }
.sheet-overlay.sheet-closing .sheet { transform: translateY(100%); }
.sheet-overlay .sheet.sheet-dragging { transition: none; }
.sheet-handle, .sheet-title { touch-action: none; }
@media (prefers-reduced-motion: reduce) {
  .sheet-overlay .sheet, .sheet-overlay.sheet-open .sheet, .sheet-overlay.sheet-closing .sheet { transform: none; transition: none; }
  .sheet-overlay { transition: opacity .18s linear; }
}
.sheet-handle { width: 32px; height: 2px; background: var(--line); margin: 10px auto 10px; }
/* The visible handle is small; its grab area is not. */
.sheet-handle { position: relative; cursor: grab; }
.sheet-handle::before { content: ""; position: absolute; inset: -14px -60px; }
.sheet-title {
  font-size: var(--fs-10); font-weight: 500; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-dim); padding: 0 var(--space-4) var(--space-2); border-bottom: 1px solid var(--line);
  padding-bottom: var(--space-2); margin-bottom: var(--space-2);
}
.sheet-item {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 13px var(--space-4); border: none; border-bottom: 1px solid var(--line); background: none; color: var(--ink);
  font-size: var(--fs-13); font-family: var(--font-ui); cursor: pointer; text-decoration: none;
}
.sheet-item:active { background: var(--accent-soft); }
.sheet-empty { padding: 24px var(--space-4); color: var(--ink-dim); font-size: var(--fs-11); text-align: center; font-family: var(--font-mono); }

/* "My card" QR share (shared.js openMyCardSheet) */
.mycard { padding: 4px var(--space-4) var(--space-4); }
.mycard-qr {
  /* Always genuine black-on-white regardless of app theme/style — a QR code
     needs real contrast to scan reliably, so this is the one place in the
     app that deliberately ignores dark mode and the Glass/Instrument toggle. */
  background: #fff; padding: 16px; border-radius: var(--radius-md);
  max-width: 220px; margin: 8px auto 14px; border: 1px solid var(--line);
}
.mycard-qr svg { display: block; width: 100%; height: auto; }
.mycard-name { text-align: center; font-size: var(--fs-15); font-weight: 600; margin-bottom: 2px; }

/* ---- Notification bell (text mark, no emoji) ---- */
.bell {
  position: relative; font-family: var(--font-ui); font-size: var(--fs-10); font-weight: 500;
  text-transform: uppercase; letter-spacing: .06em;
}
.bell-dot {
  position: absolute; top: 4px; right: 4px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); border: 1.5px solid var(--bg-raised);
}
.notif-item { padding: 12px var(--space-4); border-bottom: 1px solid var(--line); }
.notif-item.unread { border-left: 3px solid var(--accent); }
.notif-item-link { display: block; text-decoration: none; color: inherit; transition: background .08s linear; }
.notif-item-link:active { background: var(--well); }
.notif-item .notif-title { font-size: var(--fs-13); font-weight: 500; }
.notif-item .notif-body { font-size: var(--fs-11); color: var(--ink-dim); margin-top: 2px; }
.notif-item .notif-when { font-size: var(--fs-10); color: var(--ink-dim); margin-top: 4px; font-family: var(--font-mono); }

/* ---- Stat tiles (calculator, dashboard, ...) ---- */
.stat-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; margin-top: 14px; background: var(--line); border: 1px solid var(--line); }
.stat-row .stat { background: var(--well); padding: 10px 12px; }
.stat-row .stat .k { font-size: var(--fs-10); color: var(--ink-dim); text-transform: uppercase; letter-spacing: .06em; }
.stat-row .stat .v { font-size: var(--fs-20); font-weight: 500; margin-top: 2px; font-family: var(--font-mono); }
.stat-row .stat.wide { grid-column: 1 / -1; }
.stat-row .stat .v.ahead { color: var(--ink); }
.stat-row .stat .v.behind { color: var(--warn); }

/* ---- Pace meter (flat instrument gauge, not a rounded pill) ---- */
.gauge-wrap { margin: 16px 0 6px; padding-top: 18px; }
.gauge-track { position: relative; height: 16px; border: 1px solid var(--line); background: var(--well); border-radius: var(--radius-sm); }
.gauge-fill { position: absolute; left: 0; top: 0; bottom: 0; background: var(--accent); max-width: 100%; transition: width .12s linear; }
/* Deliberately no separate red/green for ahead vs behind any more — asked
   for explicitly, "tie it to the loading bar's colour" (same var(--accent)
   #topProgressBar uses in index.html). The exact "$X ahead/behind" figure
   right next to every gauge already says which one it is in words; the bar
   itself is chrome, not where that status needs to live too. */
/* Hero pace line (rep dashboard, today.js may reuse): the one number a rep
   needs, above the gauge — "$1,240 behind pace" / "On pace". */
.pace-hero { font-size: var(--fs-28); font-weight: 600; line-height: 1.15; margin: 2px 0 2px; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.pace-hero.ahead { color: var(--ink); }
.pace-hero.behind { color: var(--warn); }
.gauge-marker { position: absolute; top: -14px; bottom: -1px; width: 2px; background: var(--accent); }
.gauge-marker .gauge-marker-label {
  position: absolute; top: -16px; left: 50%; transform: translateX(-50%);
  font-size: var(--fs-10); color: var(--accent-text); white-space: nowrap; font-family: var(--font-mono);
  text-transform: uppercase; letter-spacing: .04em;
}
.gauge-labels { display: flex; justify-content: space-between; font-size: var(--fs-10); color: var(--ink-dim); margin-top: 8px; font-family: var(--font-mono); }

/* ---- Command palette (admin, desktop) ---- */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 30; background: rgba(20,20,20,.5);
  display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh;
}
.cmdk {
  width: 100%; max-width: 480px; background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius-md); max-height: 60vh; display: flex; flex-direction: column; overflow: hidden;
}
.cmdk input {
  border: none; border-bottom: 1px solid var(--line); border-radius: 0; font-size: var(--fs-15);
  padding: 14px 16px; background: var(--well);
}
.cmdk input:focus { outline: none; }
.cmdk-list { overflow-y: auto; }
.cmdk-item {
  display: block; width: 100%; text-align: left; padding: 11px 16px; border: none; border-bottom: 1px solid var(--line);
  background: none; color: var(--ink); font-size: var(--fs-13); font-family: var(--font-ui); cursor: pointer;
  text-decoration: none;
}
.cmdk-item:last-child { border-bottom: none; }
.cmdk-item.active { background: var(--accent); color: var(--accent-ink); }
.cmdk-hint { font-size: var(--fs-10); color: var(--ink-dim); padding: 8px 16px; font-family: var(--font-mono); }
/* Attio-style grouped results ("This customer" / "Go to" / "Customers") —
   a plain mono label, not a pill or divider line, matching this system's
   restraint elsewhere (card legends use the exact same treatment). */
.cmdk-group {
  font-size: var(--fs-10); color: var(--ink-dim); text-transform: uppercase; letter-spacing: .06em;
  font-family: var(--font-mono); padding: 8px 16px 4px; background: var(--bg-raised);
}

/* ---- Login photo background (web/iPad only — phone keeps the plain flat
   background it's always had, since that's the surface actually being
   tested and a full-bleed photo behind a compact form is more clutter than
   polish at phone width). 700px, matching every other touch-vs-phone cutover
   in this file: an iPad in portrait is still "the web portal", not the
   pocket device. Real Southern Foodservice photography (kitchen, chef,
   coffee, farm, harbour, produce), not stock — reused from the marketing
   site's own asset set. ---- */
.login-photo-bg { display: none; }
@media all {
  body.login-route { background: #141414; color-scheme: dark; }
  .login-photo-bg {
    display: block; position: fixed; inset: 0; z-index: -1; overflow: hidden; background: #141414;
  }
  .login-hero-video {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  }
  .login-photo-scrim {
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(15,15,14,.5), rgba(15,15,14,.72));
  }
  /* The gate card becomes a floating glass plate over the photography —
     deliberately the one place in this whole flat/matte system that uses a
     material + shadow (see the Materials & depth reasoning this app
     otherwise skips): a login screen is a one-off arrival moment, not one of
     the repeated instrument-panel utility screens the rest of the app is
     built from, so it can afford to look and feel different. Text tokens
     are hard-pinned to light values here regardless of the viewer's own
     light/dark theme choice — the backdrop is always a dark photo scrim, so
     "follow the theme" would make the dark-on-dark text of the light theme
     unreadable exactly when it's selected. */
  body.login-route .gate {
    max-width: 380px; margin-top: 10vh; padding: 36px 28px 28px;
    /* Was .42/.35 — reported unreadable, confirmed why: the crossfading
       photos aren't uniform (a bright sky vs a dark harbour at night), and
       at that opacity the blur alone didn't fully even out a bright frame
       behind the card, washing out the near-white text against it. Raised
       both the card and the well (input fields) enough that legibility no
       longer depends on which of the 6 photos happens to be showing. */
    background: rgba(20, 20, 18, .72); backdrop-filter: blur(24px) saturate(160%);
    border: 1px solid rgba(255, 255, 255, .16); border-radius: 10px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, .4);
    --ink: #f5f5f3; --ink-dim: #c7c7c2; --line: rgba(255, 255, 255, .22); --well: rgba(15, 15, 14, .6);
    /* Redefining --ink above only helps elements that reference var(--ink)
       for their OWN color (the select/input rule just below does) — the
       h1/p here have no color of their own, so without this they just
       inherit whatever `color` was already computed higher up the tree
       (the page's normal dark-mode ink), which is nearly invisible against
       this dark card. This is what made the title unreadable. */
    color: var(--ink);
  }
  body.login-route .gate select,
  body.login-route .gate input { color: var(--ink); }
  /* The floating header pill (logo, title, weather, theme, style, an
     "Alerts" button that makes no sense before signing in) was rendering
     on top of this already-complete hero composition — the gate card above
     carries its own full branding, so the header just duplicated it and
     read as a leftover mobile element pasted onto a desktop hero image
     rather than something designed for the space. Header stays on mobile
     (no photo hero there to compete with, and the theme/style toggles are
     genuinely useful one-handed before signing in on a phone) — this is
     scoped to the same >=700px breakpoint the photo treatment itself uses. */
  body.login-route header.top { display: none; }
  body.login-route #viewMount { padding-top: 0; }
  /* Same reasoning, same redundancy: "Admin" is already the second option
     in the login card's own "Your name" select (login.js) — picking it
     swaps the PIN field to an admin-password field right there. The bottom
     bar's only other item, "More", has nothing to show pre-session
     (computeNav() gates every rep item behind getSession()). So this bar
     is pure leftover mobile chrome here too, and gets the same floating-
     pill-at-the-bottom-of-a-huge-photo problem the header had. */
  body.login-route .tabbar { display: none; }
  body.login-route #viewMount { padding-bottom: 0; }
  /* The login card is dark over the photo at this width in both themes, so
     it always takes the light-text logo (the dark-text one vanished on it). */
  body.login-route .gate .logo-full-light { display: none; }
  body.login-route .gate .logo-full-dark { display: block; }
}
@media (prefers-contrast: more) {
  body.login-route .gate { background: rgba(10, 10, 9, .85); backdrop-filter: none; }
}

/* ---- Skeleton loaders — flat, hairline-bordered placeholder shapes that
   mirror the geometry of what's about to load, replacing plain "Loading…"
   text. Deliberately NOT a shimmer-gradient sweep (the common pattern
   elsewhere) — a moving gradient is a soft/glossy effect that clashes with
   this system's flat-matte-instrument-panel rule (see this file's header
   comment); a plain opacity pulse keeps the same "something is happening"
   signal without introducing a gradient anywhere. ---- */
.skel-card { background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: var(--space-4); margin-bottom: var(--space-4); }
.skel-line { height: 11px; background: var(--line); border-radius: var(--radius-sm); animation: skelPulse 1.1s ease-in-out infinite; }
.skel-line + .skel-line { margin-top: 8px; }
.skel-line.w40 { width: 40%; }
.skel-line.w60 { width: 60%; }
.skel-line.w80 { width: 80%; }
.skel-line.w100 { width: 100%; }
@keyframes skelPulse { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .skel-line { animation: none; opacity: .7; } }

/* ---- Boot loader — see index.html's #viewMount for why this is static
   markup, not JS. Purely a "something is happening" signal for the one
   moment even the top progress bar doesn't cover: a blank content area on
   a slow connection or cold cache, before the first view has anything to
   show. Same restrained language as .skel-* above (opacity pulse, no
   gradient sweep), just centered and full-height instead of mimicking a
   specific layout, since there's no layout to mimic yet. ---- */
.boot-loader {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-4); min-height: 50vh;
}
.boot-loader-mark { width: 48px; height: 48px; animation: bootMarkPulse 1.6s ease-in-out infinite; }
@keyframes bootMarkPulse { 0%, 100% { opacity: .45; transform: scale(.94); } 50% { opacity: 1; transform: scale(1); } }
.boot-loader-dots { display: flex; gap: 6px; }
.boot-loader-dots span {
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  animation: bootDotBounce 1.2s ease-in-out infinite;
}
.boot-loader-dots span:nth-child(2) { animation-delay: .15s; }
.boot-loader-dots span:nth-child(3) { animation-delay: .3s; }
@keyframes bootDotBounce { 0%, 80%, 100% { opacity: .3; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) {
  .boot-loader-mark { animation: none; opacity: 1; }
  .boot-loader-dots span { animation: none; opacity: .6; }
}

/* Keyboard-shortcut hint chip, e.g. "⌘K" — used on admin desktop pages */
kbd {
  font-family: var(--font-mono); font-size: var(--fs-10); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 1px 5px; color: var(--ink-dim); background: var(--well);
}

/* Visually hidden, still read by screen readers (the weather readout's
   full sentence, see mountWeather in shared.js). */
.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;
}

/* ---- Header right-hand group: weather, style toggle, ⌘K hint, bell ----
   Shared by BOTH styles (Glass overrides follow in its own section). */
.header-actions { display: flex; gap: 10px; align-items: center; flex-shrink: 0; }
/* One line on phones (design audit C10): the old two-word Glass | Instrument
   switch forced the actions onto a second toolbar row under 560px. The style
   control is now a single icon, so title, weather, style and bell fit on one
   row; the title column wraps (two lines at most) rather than the header. */
@media (max-width: 559px) {
  header.top { gap: 8px; }
  .header-actions { gap: 4px; }
  header.top h1 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  /* The comment above ("title, weather, style and bell fit on one row") is
     the design intent but was never actually true once the weather chip +
     style menu + alerts button's REAL widths are added up (measured ~254px
     on a 375px-wide phone, live) — flex-shrink:0 on .header-actions then
     forces the title down to a few px of unusable width instead of
     wrapping (confirmed live: h1 rendered at 37px, truncating a genuine
     "Welcome back, Test. 1 stop on today's route." greeting to "Goo af...").
     Real fix, not a band-aid on the symptom: let the header wrap onto two
     rows below this width — title+date get the FULL row (plenty of room
     for -webkit-line-clamp:2 above to do its actual job), actions drop to
     their own row underneath, right-aligned. */
  header.top { flex-wrap: wrap; row-gap: 6px; }
  header.top > div:first-child { flex: 1 1 100%; }
  .header-actions { flex: 1 1 100%; justify-content: flex-end; }
}

/* Weather — identical spec in Southern Hub (see STYLES.md). One line; the
   description and the high/low drop away as the screen narrows. Renders
   nothing until Open-Meteo answers, so there's never an empty placeholder. */
/* The weather sits on its own solid chip at full contrast — faint text at the
   top edge read as blurry on an iPhone, where iOS softens that band in a
   home-screen app. --radius-pill and --shadow already flip per style (999px /
   soft shadow in Glass, 2px / none in Instrument), so one rule draws both.
   Kept identical to the Hub; see STYLES.md. */
.wx { display: inline-flex; align-items: center; gap: 6px; font-size: calc(13px * var(--dyn)); white-space: nowrap;
      background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius-pill);
      padding: 5px 10px; box-shadow: var(--shadow); color: var(--ink); }
.wx-range { color: var(--ink-dim); }
.wx-glyph { line-height: 1; }
/* Rain truck — shown on a wet day beside the weather and on each wet forecast
   day. Same in Southern Hub (rain-truck.tsx). Full colour in both styles. */
.rain-truck { display: inline-block; width: auto; flex-shrink: 0; vertical-align: middle; }
.wx-rain { height: 15px; }
.wxf-day .wxf-rain { height: 16px; margin-left: 6px; vertical-align: -3px; }
.wx-temp { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
:root[data-style="instrument"] .wx-glyph { display: none; } /* Instrument's no-emoji rule */
:root[data-style="instrument"] .wx-temp { font-family: var(--font-mono); }
@media (max-width: 639px) { .wx-desc { display: none; } }
/* The H/L range only shows where the header has room for it: on every phone width (up to 559px) the chip is just icon + temperature, so it can never run under the buttons. */
@media (max-width: 559px) { .wx-range { display: none; } }

/* ── Style menu (both styles; identical in Southern Hub) ── the Glass /
   Instrument choice in the header: one small icon button that opens a
   two-item menu (initStyleToggle in shared.js, STYLES.md). 36px drawn, 44px
   touched — the reach is a transparent ::before past the button's edge.
   Bare rules are Instrument (square, flat, capitals); Glass is in its
   section below. Class names match the Hub's globals.css. */
.style-menu { position: relative; flex-shrink: 0; }
.style-menu-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0; margin: 4px; border-radius: 2px; cursor: pointer;
  background: var(--bg-raised); color: var(--ink); border: 1px solid var(--line); box-shadow: none;
  -webkit-tap-highlight-color: transparent; transition: background .08s linear;
}
.style-menu-btn::before { content: ""; position: absolute; inset: -5px; }
.style-menu-btn svg { width: 18px; height: 18px; display: block; }
.style-menu-btn:hover { background: var(--well); }
.style-menu-btn[aria-expanded="true"] { border-color: var(--ink); }
.style-menu-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.style-menu-list {
  position: absolute; z-index: 40; top: calc(100% + 4px); right: 0;
  min-width: 11rem; padding: 0; margin: 0;
  background: var(--bg-raised); color: var(--ink); border: 1px solid var(--line); border-radius: 2px; box-shadow: none;
  transform-origin: top right;
}
.style-menu-list[hidden] { display: none; }
.style-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 0 12px;
  border: 0; border-radius: 0; background: none; color: var(--ink); text-align: left; cursor: pointer;
  font-family: var(--font-ui); font-size: var(--fs-11); font-weight: 500; text-transform: uppercase; letter-spacing: .08em;
}
.style-menu-item + .style-menu-item { border-top: 1px solid var(--line); }
.style-menu-item:hover { background: var(--well); }
.style-menu-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
.style-menu-item[aria-checked="true"] { color: var(--accent-text); font-weight: 600; }
.style-menu-check { width: 18px; height: 18px; flex-shrink: 0; }
.style-menu-item[aria-checked="false"] .style-menu-check { visibility: hidden; }

/* ── Shared small components (design audit C18) ── one segmented control, one
   chip and one back button instead of a copy per view. Instrument first;
   Glass overrides are in the Glass section.
   .seg        — segmented control: <div class="seg" role="tablist|group"> of
                 <button>s; the current one has aria-selected="true",
                 aria-pressed="true" or .active.
   .chip       — filter / toggle chip (.active or aria-pressed="true" = on);
                 .chip-square for the 44px single-letter day pickers.
   .backbtn    — the "← Back to …" text button. */
.seg { display: flex; border: 1px solid var(--line); border-radius: var(--radius-md); overflow: hidden; margin-bottom: var(--space-4); }
.seg > button, .seg > a {
  flex: 1; border: 0; border-right: 1px solid var(--line); background: var(--well); color: var(--ink-dim);
  min-height: 44px; padding: 8px; font-family: var(--font-ui); font-size: var(--fs-10); font-weight: 500;
  text-transform: uppercase; letter-spacing: .06em; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .08s linear, color .08s linear;
}
.seg > :last-child { border-right: 0; }
.seg > :is(.active, [aria-selected="true"], [aria-pressed="true"]) { background: var(--accent); color: var(--accent-ink); }
.seg > button:active { background: var(--accent-soft); }
.seg.seg-fit { width: fit-content; }
.seg.seg-fit > button, .seg.seg-fit > a { flex: 0 0 auto; padding: 8px 14px; }
.chip {
  border: 1px solid var(--line); background: var(--well); color: var(--ink-dim); padding: 8px 10px; min-height: 44px;
  font-family: var(--font-ui); font-size: var(--fs-10); font-weight: 500; text-transform: uppercase; letter-spacing: .06em;
  cursor: pointer; border-radius: var(--radius-sm); transition: background .08s linear, color .08s linear;
}
.chip:is(.active, [aria-pressed="true"]) { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.chip.chip-square { width: 44px; height: 44px; padding: 0; font-size: var(--fs-11); text-transform: none; letter-spacing: 0; }
.chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
.backbtn {
  background: none; border: none; color: var(--ink-dim); font-size: var(--fs-11); cursor: pointer; font-family: var(--font-ui);
  margin: 0 0 8px -10px; text-transform: uppercase; letter-spacing: .06em;
  min-height: 44px; padding: 0 10px; display: inline-flex; align-items: center; transition: opacity .08s linear;
}
.backbtn:active { opacity: .5; }

/* Press feedback for tappable rows and cards (design audit C6): answer the
   touch on pointer-down, before any navigation work starts. shared.js adds a
   no-op touchstart listener so iOS Safari applies :active at all. */
:is(.acct-open, .day-cust-card, .agenda-row, .cal-cell[data-date], .cust-row, .low-margin-row-head, .notif-item-link) {
  -webkit-tap-highlight-color: transparent;
}
:is(.acct-open, .agenda-row, .cust-row):active { background: color-mix(in srgb, var(--ink) 6%, transparent); }

/* Weather chip as a button — opens the 7-day forecast. Draws nothing itself;
   it only gives the chip a 44px reach and a press. Same as Southern Hub. */
.wx-btn {
  display: inline-flex; align-items: center; min-width: 0; min-height: 44px;
  background: none; border: 0; padding: 0; color: inherit; font: inherit; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.wx-btn .wx { transition: transform .06s linear; }
.wx-btn:active .wx { transform: scale(.96); }
.wx-btn:focus-visible { outline: none; }
.wx-btn:focus-visible .wx { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── 7-day forecast sheet ── identical in Southern Hub (`.wxf-*` in
   globals.css, STYLES.md). These base rules are Instrument's flat readout:
   square segments, mono figures, ink bars, capitals, no emoji. Glass's
   Apple-Weather version is in the Glass section below. */
.wxf { padding: 0 var(--space-4) var(--space-2); }
.wxf-location { font-size: var(--fs-11); color: var(--ink-dim); margin: 2px 0 10px; }
.wxf-toggle { display: flex; border: 1px solid var(--line); margin-bottom: 8px; }
.wxf-toggle button {
  flex: 1; min-height: 40px; border: 0; background: none; color: var(--ink-dim); cursor: pointer;
  font-family: var(--font-ui); font-size: var(--fs-11); font-weight: 500; text-transform: uppercase; letter-spacing: .08em;
  transition: background .08s linear, color .08s linear;
}
.wxf-toggle button + button { border-left: 1px solid var(--line); }
.wxf-toggle button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.wxf-list { list-style: none; margin: 0; padding: 0; }
.wxf-row { display: flex; align-items: center; gap: 12px; min-height: 56px; border-top: 1px solid var(--line); font-size: var(--fs-15); color: var(--ink); }
.wxf-row:first-child { border-top: 0; }
.wxf-glyph { display: none; width: 28px; flex-shrink: 0; text-align: center; font-size: calc(22px * var(--dyn)); line-height: 1; }
.wxf-when { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.wxf-day { font-size: var(--fs-11); font-weight: 500; text-transform: uppercase; letter-spacing: .08em; }
.wxf-desc { font-size: var(--fs-13); color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wxf-list[data-view="rain"] .wxf-desc { font-family: var(--font-mono); }
.wxf-lo, .wxf-hi, .wxf-pct { flex-shrink: 0; text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.wxf-lo { width: 2.25rem; color: var(--ink-dim); }
.wxf-hi { width: 2.25rem; font-weight: 600; }
.wxf-pct { width: 3rem; font-weight: 600; }
.wxf-bar {
  position: relative; flex: 0 0 34%; max-width: 11rem; height: 8px; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 8%, transparent); box-shadow: inset 0 0 0 1px var(--line);
}
.wxf-list[data-view="rain"] .wxf-bar { flex-basis: 44%; max-width: 14rem; }
.wxf-fill { position: absolute; top: 0; bottom: 0; background: var(--ink); }
.wxf-fill-rain { left: 0; }
.wxf-source { margin: 10px 0 0; font-size: var(--fs-11); color: var(--ink-dim); }

/* ── STYLE: Glass (house style, default) ── components
   Everything below is scoped to :root[data-style="glass"] and only runs when
   Glass is selected; tokens live in the matching section near the top of
   this file. Source of truth for the look: Southern Hub's globals.css. View-
   specific Glass rules live in each view's own injected <style>, same as
   every other view-specific rule in this app. */

/* Sentence case, normal tracking — Instrument's uppercase legends are its
   single most recognisable trait and the first thing that has to go. */
:root[data-style="glass"] :is(
  label, .card > h2, .iconbtn, button.primary, button.secondary, button.ghost,
  details > summary, th, .tab, a.tab, a.applink, .tabbar-item, header.top .sub,
  .sheet-title, .bell, .badge, .gauge-marker .gauge-marker-label, .stat-row .stat .k, .cmdk-group,
  .seg > button, .seg > a, .chip, .backbtn
) { text-transform: none; letter-spacing: normal; }
/* Inline-styled legends in view markup (a handful of one-offs) — inline
   style beats any stylesheet selector, so this is the one place !important
   is the only tool that works. */
:root[data-style="glass"] [style*="text-transform:uppercase"],
:root[data-style="glass"] [style*="text-transform: uppercase"] { text-transform: none !important; letter-spacing: normal !important; }

:root[data-style="glass"] h1, :root[data-style="glass"] h2, :root[data-style="glass"] h3 { font-weight: 600; }

/* Cards: a soft edge plus the Hub's tinted shadow instead of a flat plate. */
:root[data-style="glass"] .card,
:root[data-style="glass"] .skel-card {
  border-radius: var(--radius-md); border-color: var(--card-edge); box-shadow: var(--shadow);
}
/* The legend strip becomes an ordinary card title — no flush bleed band,
   no channel-light dot. */
:root[data-style="glass"] .card > h2 {
  margin: 0 0 var(--space-3); padding: 0; border-bottom: none;
  font-size: var(--fs-15); font-weight: 600; color: var(--ink);
}
:root[data-style="glass"] .card > h2::before { display: none; }

/* ── Floating Liquid Glass chrome ── the website's toolbar (southern-
   foodservice Nav.tsx + `.glass` / `.glass-strong` / `.glass-refract` /
   `.glass-sheen` in styles.css), applied to the header and the tab bar —
   chrome only, never a surface holding body text (§6). Same recipe as
   Southern Hub's `.lg-bar` (STYLES.md):
   - a capsule floating inset from the edges, content passing beneath it;
   - thin tint, heavy blur + saturation, a lit top hairline, a faint inner
     shade at the foot and a soft tinted drop shadow;
   - a specular sheen (160° white → clear by 42%) baked into the background;
   - at rest, Chromium bends the backdrop with #lg-refraction-soft; once
     scrolled (html[data-scrolled], set by initGlassDepth) it thickens to plain
     frost, because refraction repaints late under moving content. */
:root[data-style="glass"] {
  --lg-tint: 255 255 255; --lg-tint-a: .56; --lg-tint-scrolled: .74;
  --lg-hair: rgb(255 255 255 / .6); --lg-hi: rgb(255 255 255 / .9);
  --lg-shade: rgb(31 58 42 / .05); --lg-drop: 0 12px 40px -12px rgb(31 58 42 / .28);
  --lg-sheen: rgb(255 255 255 / .5);
  --depth-a: .16;
}
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) {
    --lg-tint: 30 30 32; --lg-tint-a: .55; --lg-tint-scrolled: .8;
    --lg-hair: rgb(255 255 255 / .14); --lg-hi: rgb(255 255 255 / .16);
    --lg-shade: rgb(0 0 0 / .3); --lg-drop: 0 12px 40px -12px rgb(0 0 0 / .7);
    --lg-sheen: rgb(255 255 255 / .07);
    --depth-a: .2;
  }
}
:root[data-style="glass"][data-theme="dark"] {
  --lg-tint: 30 30 32; --lg-tint-a: .55; --lg-tint-scrolled: .8;
  --lg-hair: rgb(255 255 255 / .14); --lg-hi: rgb(255 255 255 / .16);
  --lg-shade: rgb(0 0 0 / .3); --lg-drop: 0 12px 40px -12px rgb(0 0 0 / .7);
  --lg-sheen: rgb(255 255 255 / .07);
  --depth-a: .2;
}

:root[data-style="glass"] :is(header.top, .tabbar) {
  background:
    linear-gradient(160deg, var(--lg-sheen) 0%, transparent 42%),
    rgb(var(--lg-tint) / var(--lg-tint-a));
  border: 1px solid var(--lg-hair);
  box-shadow: var(--lg-drop), inset 0 1px 0 var(--lg-hi), inset 0 -1px 1px var(--lg-shade);
  -webkit-backdrop-filter: blur(18px) saturate(185%); backdrop-filter: blur(18px) saturate(185%);
  transition: background-color .5s var(--ease-glass), box-shadow .5s var(--ease-glass);
}
@supports (backdrop-filter: url("#lg-refraction-soft")) {
  /* header.top only, not .tabbar — reported on a real phone: an irregular
     colored blob bleeding past the tab bar's edge, which is exactly the
     shape an SVG feDisplacementMap filter produces when it doesn't render
     the way it does in Chromium (this @supports check confirms the SYNTAX
     is accepted, not that the actual rendering matches). The tab bar is
     fixed for the whole session and the one piece of chrome that has to be
     visually reliable everywhere, so it isn't worth the risk for an effect
     nobody can see next to a scrolled tab bar's content anyway; the plain
     blur+saturate two lines up already gives it the glass look. */
  :root[data-style="glass"]:not([data-scrolled]) header.top {
    backdrop-filter: url("#lg-refraction-soft") blur(14px) saturate(178%);
  }
}
:root[data-style="glass"][data-scrolled] :is(header.top, .tabbar) {
  background:
    linear-gradient(160deg, var(--lg-sheen) 0%, transparent 42%),
    rgb(var(--lg-tint) / var(--lg-tint-scrolled));
  -webkit-backdrop-filter: blur(24px) saturate(195%); backdrop-filter: blur(24px) saturate(195%);
}

/* The header floats: inset from the edges, rounded, and sticky a little way
   down from the top so content visibly slides up behind it. */
:root[data-style="glass"] header.top {
  top: calc(8px + env(safe-area-inset-top)); margin: 8px 8px 0; border-radius: 22px; padding: 12px 14px;
}
/* Phones: iOS fades and blurs about the first 20pt of a home-screen web app
   under the status bar, so a bar floating 8px down read as blurry there.
   24px clears the band — the same offset as Southern Hub's .lg-topbar. */
/* iPads have the same band under their status bar: any touch screen. */
@media (max-width: 767px), (pointer: coarse) {
  :root[data-style="glass"] header.top { top: calc(24px + env(safe-area-inset-top)); margin-top: 24px; }
}
/* The tab bar is DOCKED to the bottom edge, not floating: a floating capsule
   let page content show through the gap beneath it, and on a real phone that
   read as a bar that "isn't locked at the bottom". Same glass, rounded only
   on its top corners, running down behind the home indicator — identical to
   Southern Hub's docked .lg-tabbar. #viewMount keeps the base 64px + safe-area
   reservation. */
:root[data-style="glass"] .tabbar {
  left: 0; right: 0; bottom: 0;
  padding: 4px 4px env(safe-area-inset-bottom);
  border-width: 1px 0 0; border-radius: 22px 22px 0 0;
  /* Reported on a real phone: the ambient .glass-depth glow blobs drifting
     behind the page showed through as a distinct colored shape rather than
     a frosted wash — the header's own 0.56/0.74 tint (:is(header.top,
     .tabbar) above) wasn't enough to fully obscure them here. The tab bar
     is fixed for the entire session and sits over the busiest, most
     scrolled part of the page, unlike the header, so it gets its own
     higher floor independent of backdrop-filter actually rendering (Safari
     has a long history of not reliably blurring position:fixed elements) —
     a plain, mostly-opaque tint the blur then softens further where it
     does work, rather than depending on the blur alone to hide anything
     bright passing underneath. */
  --lg-tint-a: .92; --lg-tint-scrolled: .96;
}
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) {
  /* Desktop: the bar is back in flow under the header (see the 1100px block
     near the top) — still a glass capsule, just not fixed. */
  :root[data-style="glass"] .tabbar { margin: 8px 8px 0; left: auto; right: auto; bottom: auto; border-radius: 18px; }
  :root[data-style="glass"] #viewMount { padding-bottom: 14px; }
}
:root[data-style="glass"] .tabbar-item { font-size: var(--fs-11); border-top-color: transparent; border-radius: 24px; }
:root[data-style="glass"] .tabbar-item::before { inset: 2px; border-radius: 24px; }
:root[data-style="glass"] .tabbar-item.active { color: var(--accent-text); border-top-color: transparent; font-weight: 600; }

/* Parallax depth: two soft brand glows (red/amber, green) fixed behind the
   content and drifted by initGlassDepth at 0.12× and 0.3× the scroll — the
   thing the glass has to bend. Faint enough that cards and text never sit on
   noticeable colour. */
.glass-depth { display: none; }
:root[data-style="glass"] body:not(.login-route) .glass-depth {
  display: block; position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
}
:root[data-style="glass"] .app { position: relative; z-index: 1; }
.glass-depth > span { position: absolute; inset: -30% -20%; will-change: transform; }
.glass-depth-far {
  background:
    radial-gradient(38% 30% at 12% 18%, rgb(235 36 56 / var(--depth-a)), transparent 70%),
    radial-gradient(34% 28% at 92% 62%, rgb(245 185 54 / var(--depth-a)), transparent 70%);
}
.glass-depth-near {
  background:
    radial-gradient(30% 24% at 80% 10%, rgb(82 204 60 / var(--depth-a)), transparent 70%),
    radial-gradient(28% 22% at 20% 88%, rgb(235 36 56 / calc(var(--depth-a) * .7)), transparent 70%);
}

@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  :root[data-style="glass"] :is(header.top, .tabbar),
  :root[data-style="glass"][data-scrolled] :is(header.top, .tabbar) {
    background: var(--glass-solid); -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  :root[data-style="glass"] body .glass-depth { display: none; }
}

/* Controls: Hub's 12px radius, white-on-red primary. */
:root[data-style="glass"] :is(.iconbtn, button.primary, button.secondary, button.ghost,
  input[type=text], input[type=email], input[type=password], input[type=tel], input[type=file],
  input[type=number], input[type=date], input[type=month], textarea, select) { border-radius: var(--radius-sm); }
:root[data-style="glass"] button.primary { font-weight: 600; }
:root[data-style="glass"] button.primary:active { background: var(--accent); color: var(--accent-ink); opacity: .85; }
:root[data-style="glass"] button.secondary { border-color: var(--line); }
:root[data-style="glass"] :is(button.secondary, button.ghost, .iconbtn):active {
  background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink); border-color: var(--line);
}
:root[data-style="glass"] button.danger:active { background: var(--warn); color: #fff; }
:root[data-style="glass"] :is(.badge, .tag) { border-radius: var(--radius-pill); }
:root[data-style="glass"] .tabs { border-radius: var(--radius-sm); }
:root[data-style="glass"] .stat-row { border-radius: var(--radius-sm); overflow: hidden; }
:root[data-style="glass"] .slider { border-radius: var(--radius-pill); }
:root[data-style="glass"] .slider::before { border-radius: 50%; }
:root[data-style="glass"] .sheet { border-radius: var(--radius-lg) var(--radius-lg) 0 0; border-top-color: var(--card-edge); box-shadow: var(--shadow); }
:root[data-style="glass"] .sheet-handle { height: 5px; width: 36px; border-radius: var(--radius-pill); }
:root[data-style="glass"] .sheet-title { font-size: var(--fs-13); font-weight: 600; color: var(--ink); }
:root[data-style="glass"] .cmdk { border-radius: var(--radius-lg); border-color: var(--card-edge); box-shadow: var(--shadow); }
/* Undo the control-radius rule above for the palette's own search field —
   it's a flush strip across the top of the panel, not a standalone input. */
:root[data-style="glass"] .cmdk input { border-radius: 0; }
@media (min-width: 700px) {
  :root[data-style="glass"] body.login-route .gate { border-radius: var(--radius-lg); }
}

/* Motion: 200ms ease-out-expo instead of Instrument's 60-80ms linear snap.
   Duration + timing only (never `transition` shorthand), so each rule's own
   transition-property list — and the press scale — stay exactly as they are. */
:root[data-style="glass"] :is(.iconbtn, button.primary, button.secondary, button.ghost, details > summary,
  .tabbar-item, .slider, .notif-item-link, .style-menu-btn, .seg > button, .chip, .backbtn),
:root[data-style="glass"] .slider::before {
  transition-duration: .2s; transition-timing-function: var(--ease-glass);
}

/* Style menu, Glass version — Southern Hub's exactly: round button with the
   card shadow, a 16px-rounded floating list, 12px-rounded items. */
:root[data-style="glass"] .style-menu-btn { border-radius: 999px; border-color: var(--card-edge); box-shadow: var(--shadow); }
:root[data-style="glass"] .style-menu-btn:hover { background: color-mix(in srgb, var(--ink) 5%, var(--bg-raised)); }
:root[data-style="glass"] .style-menu-list {
  top: calc(100% + 8px); padding: 4px; border-radius: 16px; border-color: var(--card-edge); box-shadow: var(--shadow);
}
:root[data-style="glass"] .style-menu-item {
  border-radius: 12px; font-size: var(--fs-15); text-transform: none; letter-spacing: normal;
}
:root[data-style="glass"] .style-menu-item + .style-menu-item { border-top: 0; }
:root[data-style="glass"] .style-menu-item:is(:hover, :focus-visible) { background: color-mix(in srgb, var(--ink) 6%, transparent); box-shadow: none; }
@media (prefers-reduced-motion: no-preference) {
  :root[data-style="glass"] .style-menu-list:not([hidden]) { animation: styleMenuPop .16s var(--ease-glass) both; }
}
@keyframes styleMenuPop { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }

/* Shared segmented control, Glass version: the iOS pill. */
:root[data-style="glass"] .seg {
  border: none; border-radius: var(--radius-pill); padding: 3px; gap: 2px;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
}
:root[data-style="glass"] .seg > button, :root[data-style="glass"] .seg > a {
  background: transparent; border-right: 0; border-radius: var(--radius-pill); min-height: 38px;
  font-size: var(--fs-13); color: var(--ink-dim);
}
/* 38px segments + the 3px track padding keep a 44px reach. */
:root[data-style="glass"] .seg > :is(.active, [aria-selected="true"], [aria-pressed="true"]) {
  background: var(--bg-raised); color: var(--ink); box-shadow: 0 1px 3px rgb(0 0 0 / .12);
}
:root[data-style="glass"] .seg > button:active { background: color-mix(in srgb, var(--ink) 6%, transparent); }
:root[data-style="glass"] .chip { font-size: var(--fs-13); }

/* Was var(--good)/var(--warn) here (a green/red split, see design audit
   C8's reasoning for why it existed) — removed on explicit request to tie
   every gauge to the loading bar's own colour instead. Both states now just
   inherit the base .gauge-fill's var(--accent); nothing Glass-specific
   needed any more. */
:root[data-style="glass"] .pace-hero { font-family: var(--font-ui); letter-spacing: -.01em; }

/* 7-day forecast, Glass version — Apple's Weather: a soft segmented control,
   each day's range as a blue-to-amber capsule against the week, rain as a
   blue bar, the condition glyph back. Blue is the Hub's --status-info,
   amber its --brand-amber (Southern Hub globals.css). */
:root[data-style="glass"] { --wx-cold: #2f6bb5; --wx-warm: #f5b936; }
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) { --wx-cold: #72a7ec; }
}
:root[data-style="glass"][data-theme="dark"] { --wx-cold: #72a7ec; }
:root[data-style="glass"] .wxf-location { font-size: var(--fs-13); margin-top: 0; }
:root[data-style="glass"] .wxf-toggle {
  border: none; border-radius: var(--radius-pill); padding: 3px; gap: 2px;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
}
:root[data-style="glass"] .wxf-toggle button {
  border-radius: var(--radius-pill); min-height: 36px; font-size: calc(14px * var(--dyn)); text-transform: none; letter-spacing: normal;
  transition-duration: .2s; transition-timing-function: var(--ease-glass);
}
:root[data-style="glass"] .wxf-toggle button + button { border-left: none; }
:root[data-style="glass"] .wxf-toggle button[aria-pressed="true"] {
  background: var(--bg-raised); color: var(--ink); box-shadow: 0 1px 2px rgb(0 0 0 / .08), 0 2px 8px rgb(0 0 0 / .06);
}
:root[data-style="glass"] .wxf-glyph { display: block; }
:root[data-style="glass"] .wxf-day { font-size: var(--fs-15); font-weight: 600; text-transform: none; letter-spacing: normal; }
:root[data-style="glass"] :is(.wxf-lo, .wxf-hi, .wxf-pct, .wxf-list[data-view="rain"] .wxf-desc) { font-family: var(--font-ui); }
:root[data-style="glass"] .wxf-bar { height: 6px; border-radius: var(--radius-pill); box-shadow: none; }
:root[data-style="glass"] .wxf-fill { border-radius: inherit; background: linear-gradient(90deg, var(--wx-cold), var(--wx-warm)); }
:root[data-style="glass"] .wxf-fill-rain { background: var(--wx-cold); }
:root[data-style="glass"] .wx-btn .wx { transition: transform .14s var(--ease-glass); }

/* ── Animated weather icons ─────────────────────────────────────────────────
 * IDENTICAL IN BOTH APPS (Hub globals.css / CRM shared.css) — markup from
 * weatherIconSvg (Hub src/lib/weather-icon.ts, CRM shared.js). STYLES.md.
 * They sit in .wx-glyph / .wxf-glyph, which Instrument hides, so this is
 * Glass's. Colours: the brand amber for sun and lightning, a soft blue for
 * rain and snow, and clouds that hold their edge on a white chip and on black.
 * Motion is slow and small — a header is glanced at, not watched — and all
 * of it stands down under prefers-reduced-motion.
 */
:root { --wxi-cloud: #e6ebf1; --wxi-cloud-edge: #9aa6b4; --wxi-moon: #b9c4de; }
@media (prefers-color-scheme: dark) {
  :root:not(.light):not([data-theme="light"]) { --wxi-cloud: #c3cad3; --wxi-cloud-edge: #8d97a3; --wxi-moon: #dfe5f5; }
}
:root.dark, :root[data-theme="dark"] { --wxi-cloud: #c3cad3; --wxi-cloud-edge: #8d97a3; --wxi-moon: #dfe5f5; }

.wx-glyph .wxi { width: 20px; height: 20px; }
.wxf-glyph .wxi { width: 28px; height: 28px; margin: 0 auto; }
.wxi { display: block; overflow: visible; }
.wxi-sun circle, .wxi-bolt { fill: #f5b936; }
.wxi-rays line { stroke: #f5b936; stroke-width: 1.8; stroke-linecap: round; }
.wxi-moon { fill: var(--wxi-moon); }
.wxi-cloud { fill: var(--wxi-cloud); stroke: var(--wxi-cloud-edge); stroke-width: 1; }
.wxi-drop { stroke: #4a8fdc; stroke-width: 1.8; stroke-linecap: round; }
.wxi-flake { fill: #8fb8e8; }
.wxi-mist line { stroke: var(--wxi-cloud-edge); stroke-width: 1.8; stroke-linecap: round; }
.wxi-rays, .wxi-moon { transform-box: view-box; }
.wxi-clear .wxi-rays, .wxi-clear .wxi-moon { transform-origin: 16px 16px; }
.wxi-partly .wxi-rays, .wxi-partly .wxi-moon { transform-origin: 12px 12px; }

@media (prefers-reduced-motion: no-preference) {
  .wxi-rays { animation: wxi-spin 14s linear infinite; }
  .wxi-moon { animation: wxi-rock 6s ease-in-out infinite; }
  .wxi-drift { animation: wxi-drift 4.5s ease-in-out infinite; }
  .wxi-mist line { animation: wxi-drift 5s ease-in-out infinite; }
  .wxi-mist line + line { animation-direction: reverse; }
  .wxi-drop { animation: wxi-fall 1.1s linear infinite; }
  .wxi-fall-drizzle .wxi-drop { animation-duration: 1.7s; }
  .wxi-flake { animation: wxi-snow 2.6s ease-in-out infinite; }
  .wxi-bolt { animation: wxi-flash 3.4s linear infinite; }
}
@keyframes wxi-spin { to { transform: rotate(360deg); } }
@keyframes wxi-rock { 0%, 100% { transform: rotate(-7deg); } 50% { transform: rotate(7deg); } }
@keyframes wxi-drift { 0%, 100% { transform: translateX(-0.9px); } 50% { transform: translateX(1.3px); } }
@keyframes wxi-fall {
  0% { transform: translateY(-3px); opacity: 0; }
  25%, 75% { opacity: 1; }
  100% { transform: translateY(4px); opacity: 0; }
}
@keyframes wxi-snow {
  0% { transform: translate(0, -3px); opacity: 0; }
  25% { opacity: 1; }
  55% { transform: translate(1.2px, 0.5px); }
  100% { transform: translate(-0.6px, 4px); opacity: 0; }
}
@keyframes wxi-flash {
  0%, 84%, 100% { opacity: 0.3; }
  86% { opacity: 1; }
  89% { opacity: 0.4; }
  92% { opacity: 1; }
}

/* ── Toast (with Undo) and error state (with Retry) ── shared feedback layer,
   same behaviour as Southern Hub's toast.tsx (STYLES.md). Tokens only, so it
   is a flat plate in Instrument and a soft pill in Glass. */
.toast-region {
  position: fixed; left: 0; right: 0; z-index: 40; display: flex; justify-content: center;
  padding: 0 16px; pointer-events: none;
  bottom: calc(76px + env(safe-area-inset-bottom));
}
/* On the customer page the sticky Log visit bar sits above the tab bar; lift
   the toast clear of it so an Undo is never hidden behind the bar. */
body:has(.logbar) .toast-region { bottom: calc(140px + env(safe-area-inset-bottom)); }
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) { .toast-region, body:has(.logbar) .toast-region { bottom: 24px; } }
.toast {
  pointer-events: auto; display: flex; align-items: center; gap: 12px; min-height: 48px;
  max-width: min(560px, 100%); padding: 6px 8px 6px 16px;
  background: var(--ink); color: var(--bg); border-radius: var(--radius-md);
  font-size: var(--fs-13); font-weight: 500;
}
.toast-error { background: var(--warn); color: #fff; }
.toast-action {
  margin-left: auto; min-height: 40px; padding: 0 12px; border: 0; background: none; color: inherit;
  font: inherit; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; cursor: pointer;
}
:root[data-style="glass"] .toast { border-radius: var(--radius-pill); box-shadow: var(--shadow); padding-left: 18px; }
:root[data-style="glass"] .toast-action { text-transform: none; letter-spacing: normal; }
@media (prefers-reduced-motion: no-preference) {
  .toast { animation: toastIn .18s ease-out; }
}
@keyframes toastIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.error-state { display: grid; gap: 8px; justify-items: start; padding: 20px 4px; }
.error-state-msg { margin: 0; font-size: var(--fs-15); font-weight: 600; color: var(--ink); }
.error-state-hint { margin: 0; font-size: var(--fs-13); color: var(--ink-dim); }
.error-state-retry { margin-top: 6px; }

/* Reinstall notice — "old install, see Jamie". Same in Southern Hub. */
.reinstall-backdrop { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgb(0 0 0 / 0.4); }
.reinstall-card { width: 100%; max-width: 360px; padding: 20px; text-align: center; background: var(--bg-raised); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow); }
.reinstall-title { margin: 0; font-size: calc(17px * var(--dyn)); font-weight: 600; }
.reinstall-body { margin: 8px 0 0; font-size: calc(15px * var(--dyn)); line-height: 1.35; color: var(--ink-dim); }
.reinstall-ok { width: 100%; margin-top: 16px; }

/* ── Nothing moves while it loads (both styles) ──────────────────────────────
   Measured with a layout-shift observer across every rep route, four screen
   sizes and both styles (motion audit, Sept 2026). The header was the main
   offender: the weather chip and the Alerts button arrive after the page, and
   each arrival squeezed the title column, re-wrapped the greeting and pushed
   the whole page down — 0.17 on a phone, every cold open. */

/* The actions row is 44px tall from the first paint (the bell's height), not
   36px until the bell lands. */
.header-actions { min-height: 44px; }

/* Phones: the title gets its own full-width row under the logo and actions,
   instead of sharing one row with them. Sharing, a 60-odd-character greeting
   was clamped to "Good morni…" beside the weather chip — the "7 visits" it
   was saying was the part cut off — and the row re-wrapped every time the
   chip or bell filled in. Now nothing that arrives late sits beside text. */
@media (max-width: 559px), (pointer: coarse) and (max-height: 520px) {
  header.top { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: 8px; }
  header.top > div:first-child, header.top .shell-title-row { display: contents; }
  header.top .logo-mark { grid-column: 1; grid-row: 1; }
  /* The whole rest of the row, right-aligned: the group's box is the same
     size before and after the weather fills in, so only the chip appears. */
  header.top .header-actions { grid-column: 2; grid-row: 1; justify-content: flex-end; }
  header.top h1 { grid-column: 1 / -1; grid-row: 2; margin-top: 6px; }
  header.top .sub { grid-column: 1 / -1; }
  /* The subtitle is written by the view once its script has run, after the
     first paint: hold its line. Logbook, Suppliers and News write a sentence
     that takes two lines at this width. */
  #shellSub { min-height: 1lh; }
  body:is([data-route="km-logbook"], [data-route="suppliers"], [data-route="news"]) #shellSub { min-height: 2lh; }
}

/* Today's greeting is written after its data lands, and it is a sentence:
   reserve its two lines so the route does not drop when it arrives. An
   account keeps the greeting above it, so the same reserve holds through the
   Today <-> account slide. Only below the desktop width — beside a wide
   header it fits on one. */
@media (max-width: 1099px) {
  body:is([data-route="today"], [data-route="customer"]) #shellTitle { min-height: 2lh; }
}

/* Tablet and up, where the title does share the row: hold the weather chip's
   width open before the reading arrives, so the title column is the same
   width before and after. Sized to a typical reading ("12° Partly cloudy
   H 15° L 7°"); an unusually long description still grows it, once. Only for
   a signed-in rep — the login screen has no weather. */
@media (min-width: 560px) {
  body:not(.login-route) #weatherSlot { display: flex; justify-content: flex-end; min-width: 96px; }
}
@media (min-width: 640px) {
  body:not(.login-route) #weatherSlot { min-width: 240px; }
  :root[data-style="instrument"] body:not(.login-route) #weatherSlot { min-width: 204px; }
}

/* Wide screens: the header and tab bar keep ONE width whichever tab is open.
   They used to be 920px on most routes and 1320px on the Today / account /
   dashboard split, so every tab change on an iPad in landscape slid the
   header, its title and the whole tab bar 130px sideways. The narrower routes
   now centre their content column under the full-width chrome instead — with
   padding rather than a narrower #viewMount, so the mount itself never
   changes size between tabs, only what is inside it. */
@media (min-width: 1100px) {
  body:not(.admin-area) .app,
  body:not(.admin-area) .tabbar { max-width: 1320px; }
  body:not(.admin-area):not(.wide-route) #viewMount { padding-inline: max(16px, calc((100% - 920px) / 2 + 16px)); }
}

/* Desktop with a mouse: the tab bar sits in the flow under the header, and it
   is drawn by shared.js after the first paint — so the page painted, then
   dropped 40-60px when the bar arrived. Hold its height from the start. */
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) {
  body:not(.login-route) #navMount { min-height: 62px; }
  :root[data-style="instrument"] body:not(.login-route) #navMount { min-height: 49px; }
}

/* The Today <-> account slide moves #viewMount a full screen sideways, and
   for those 240ms the page was twice as wide as the window. On a phone that
   re-laid-out every fixed element — the tab bar and the Glass depth layer
   both registered as moving (layout shift 1.3 per push and pop). Clipping at
   .app keeps the slide inside the window. `clip`, not `hidden`: it makes no
   scroll container, so the sticky header still sticks. */
.app { overflow-x: clip; }

/* Large text (200% text size, measured): five tab labels no longer fit five
   phone-width slots, and "Accounts" and "More" were pushed off the right edge
   where nobody could reach them. A slot may now shrink and its label wrap
   inside it. Same for a long word in the header title, which ran under the
   weather chip instead of breaking. Nothing changes at normal sizes. */
.tabbar-item { min-width: 0; overflow-wrap: anywhere; }
header.top h1 { overflow-wrap: anywhere; }

/* Placeholder text in the app's own secondary ink. It was the browser
   default (#757575), which is a light-mode grey: on the dark inputs it
   measured 4.0:1, under the 4.5:1 body-text minimum. */
::placeholder { color: var(--ink-dim); opacity: 1; }

/* Nothing but a tab label under 11px on a touch screen. The small uppercase
   labels (--fs-10: section labels, the header date, "Next stop", tabs inside
   an account, button captions) were 10px — under Apple's 11pt floor for the
   smallest readable text, and read at arm's length on a phone in a van. The
   tab bar keeps 10px, which is exactly Apple's own tab-bar size and all five
   labels still fit a phone's width. Desktop with a mouse is unchanged. */
@media (pointer: coarse) {
  :root { --fs-10: calc(11px * var(--dyn)); }
  .tabbar-item { font-size: 10px; } /* Glass's own 11px tab rule out-ranks this */
}

/* Glass: the tab bar's labels sit on whatever is scrolling beneath it at every
   scroll position, so its tint is thicker than the header's, at rest and
   scrolled. At .55 a supplier logo blurred through it took the grey labels
   down to 2.2:1 in dark mode; .86 holds them over 4.5:1 and keeps the frost.
   Same value in Southern Hub's .lg-tabbar (STYLES.md). The header gets the
   same once scrolled — unlike the Hub's, it carries the page title and date,
   and content slides beneath those. */
:root[data-style="glass"] .tabbar,
:root[data-style="glass"][data-scrolled] :is(header.top, .tabbar) {
  background:
    linear-gradient(160deg, var(--lg-sheen) 0%, transparent 42%),
    rgb(var(--lg-tint) / .86);
}
/* …and the solid bars for reduced transparency / more contrast still win. */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  :root[data-style="glass"] .tabbar,
  :root[data-style="glass"][data-scrolled] :is(header.top, .tabbar) { background: var(--glass-solid); }
}

/* "Ask your coach" link on Today and the Dashboard (coachEntryHtml in
   shared.js; the chat itself is views/coach.js). A full-width 44px+ row, so
   it reads as a place to go rather than one more small action button. */
.coach-entry {
  display: flex; flex-direction: column; gap: 2px; margin: 14px 0; padding: 12px 14px; min-height: 44px;
  border: 1px solid var(--accent); border-radius: var(--radius-lg); background: var(--accent-soft);
  color: var(--ink); text-decoration: none; transition: transform .08s linear;
}
.coach-entry-title { font-size: var(--fs-13); font-weight: 600; color: var(--accent-text); text-transform: uppercase; letter-spacing: .04em; }
.coach-entry-sub { font-size: var(--fs-11); color: var(--ink-dim); }
.coach-entry:active { transform: scale(.99); }
@media (prefers-reduced-motion: reduce) { .coach-entry:active { transform: none; } }
:root[data-style="glass"] .coach-entry { border-color: var(--card-edge); box-shadow: var(--shadow); background: var(--bg-raised); }
:root[data-style="glass"] .coach-entry-title { text-transform: none; letter-spacing: normal; }


/* ── Device audit fixes, 17 Sep 2026 (sfs-device-audit/crm/REPORT.md) ──────
   One block at the end so the reasons stay together. Same values as the Hub
   and People & Safety. */

/* F7: nothing under 11px anywhere; secondary and body text a step larger on
   touch screens, where the CRM is read at arm's length in a car park. */
:root { --fs-10: calc(11px * var(--dyn)); }
@media (pointer: coarse) { :root { --fs-11: calc(12px * var(--dyn)); --fs-13: calc(14px * var(--dyn)); } }

/* F5: finger-sized targets on touch screens. */
@media (pointer: coarse) {
  .sheet-item, .cmdk-item { min-height: 48px; }
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea { min-height: 44px; }
  .seg > button, .seg > a { min-height: 44px; }
  input[type="checkbox"], input[type="radio"] { width: 22px; height: 22px; }
  /* F6: Admin → Targets cells were 11px, so iOS zoomed on every tap. */
  input.bulkTargetCell { font-size: calc(16px * var(--dyn)); min-height: 44px; }
}

/* F2: on a phone the header gives back its second and third lines once the
   page scrolls, and the tab bar steps aside while the customer page's
   "Log visit" bar is showing (the two together left 53% of an iPhone SE
   screen for the customer). The customer page has its own Back button. */
@media (max-width: 699px) {
  html[data-scrolled] body:not(.login-route) :is(#shellSub, #shellSub2) { display: none; }
  body:has(.logbar) .tabbar { display: none; }
  body:has(.logbar) .logbar { bottom: 0; padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
}

/* ---- Inside the iPhone/iPad app (native.js adds .is-native-app) ---- */
/* Face ID lock cover. */
.app-lock { position: fixed; inset: 0; z-index: 9999; display: grid; place-items: center;
  background: color-mix(in srgb, var(--bg) 82%, transparent); -webkit-backdrop-filter: blur(28px) saturate(160%); backdrop-filter: blur(28px) saturate(160%); }
.app-lock-card { display: grid; gap: 14px; justify-items: center; text-align: center; padding: 28px; }
.app-lock-card h2 { margin: 0; font-size: var(--fs-20); }
/* An app, not a web page: no rubber-band page bounce or tap flash, no text selection on chrome. */
.is-native-app { -webkit-tap-highlight-color: transparent; overscroll-behavior-y: none; }
.is-native-app header.top, .is-native-app .tabbar, .is-native-app .sheet-item, .is-native-app .iconbtn { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   Liquid Glass, all the way through (Glass style only).
   The recipe, everywhere: a thin tint, heavy blur + saturation so the ambient
   colour behind it glows through, a specular sheen (bright at the top-left,
   clear by mid-panel), a lit top hairline, a faint inner shade at the foot and
   a soft coloured drop shadow. Surfaces that hold body text keep enough tint
   for AA contrast; small chrome (buttons, segments, chips) goes clearer.
   Long lists get the same look WITHOUT a per-row backdrop blur (one blur per
   row would stutter scrolling) — a frosted card blurs, a row inside it does not.
   ═══════════════════════════════════════════════════════════════════════════ */
:root[data-style="glass"] {
  --depth-a: .34;
  --lg-card-a: .58;            /* tint over text-bearing glass */
  --lg-clear-a: .34;           /* tint over small controls */
  --lg-spec: rgb(255 255 255 / .75);
  --lg-edge: 0 0 0 .5px rgb(255 255 255 / .55);
  --lg-lens: rgb(255 255 255 / .78);
  --lg-blur: blur(26px) saturate(190%);
  --lg-blur-soft: blur(14px) saturate(170%);
}
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) {
    --depth-a: .4; --lg-card-a: .5; --lg-clear-a: .3;
    --lg-spec: rgb(255 255 255 / .2); --lg-edge: 0 0 0 .5px rgb(255 255 255 / .1); --lg-lens: rgb(255 255 255 / .2);
  }
}
:root[data-style="glass"][data-theme="dark"] {
  --depth-a: .4; --lg-card-a: .5; --lg-clear-a: .3;
  --lg-spec: rgb(255 255 255 / .2); --lg-edge: 0 0 0 .5px rgb(255 255 255 / .1); --lg-lens: rgb(255 255 255 / .2);
}

/* Cards, stat blocks, gauges: frosted panes with a lit rim. */
:root[data-style="glass"] :is(.card, .skel-card, .custcard) {
  background:
    radial-gradient(120% 90% at 0% 0%, var(--lg-spec) 0%, transparent 46%),
    linear-gradient(160deg, rgb(255 255 255 / .10), transparent 60%),
    rgb(var(--lg-tint) / var(--lg-card-a));
  border: 1px solid var(--lg-hair);
  box-shadow: var(--lg-drop), inset 0 1px 0 var(--lg-hi), inset 0 -1px 1px var(--lg-shade), var(--lg-edge);
  -webkit-backdrop-filter: var(--lg-blur); backdrop-filter: var(--lg-blur);
}
/* A row inside a card is already on glass: no second blur, just a lighter pane. */
:root[data-style="glass"] .card :is(.custcard, .card, .stat, .margin-class-tile, .margin-area, .area-block) {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  background: linear-gradient(160deg, rgb(255 255 255 / .28), rgb(255 255 255 / .06)); box-shadow: inset 0 1px 0 var(--lg-hi), var(--lg-edge);
}
:root[data-style="glass"] .stat-row { background: transparent; border-color: var(--lg-hair); box-shadow: var(--lg-edge); }
:root[data-style="glass"] .stat-row .stat { background: rgb(var(--lg-tint) / .3); }
:root[data-style="glass"] .gauge-track {
  background: rgb(var(--lg-tint) / .28); border-color: var(--lg-hair);
  box-shadow: inset 0 2px 5px rgb(0 0 0 / .12), inset 0 -1px 0 rgb(255 255 255 / .35);
}
:root[data-style="glass"] .gauge-fill { box-shadow: 0 0 14px -2px color-mix(in srgb, var(--accent) 60%, transparent), inset 0 1px 0 rgb(255 255 255 / .45); }

/* Controls: a glass button is a small lens; the primary is tinted glass. */
:root[data-style="glass"] :is(.iconbtn, button.secondary, button.ghost, .style-menu-btn) {
  background:
    linear-gradient(160deg, var(--lg-spec) 0%, transparent 55%),
    rgb(var(--lg-tint) / var(--lg-clear-a));
  border: 1px solid var(--lg-hair);
  box-shadow: 0 6px 18px -8px rgb(31 58 42 / .3), inset 0 1px 0 var(--lg-hi), inset 0 -1px 1px var(--lg-shade), var(--lg-edge);
  -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft);
  transition: transform .18s var(--ease-glass), box-shadow .18s var(--ease-glass), background-color .18s var(--ease-glass);
}
:root[data-style="glass"] button.primary {
  background:
    linear-gradient(165deg, rgb(255 255 255 / .42) 0%, rgb(255 255 255 / 0) 46%),
    color-mix(in srgb, var(--accent) 90%, transparent);
  border: 1px solid rgb(255 255 255 / .38);
  box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--accent) 80%, transparent), inset 0 1px 0 rgb(255 255 255 / .6), inset 0 -2px 4px rgb(0 0 0 / .18);
  -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft);
  text-shadow: 0 1px 1px rgb(0 0 0 / .18);
}
:root[data-style="glass"] :is(button.primary, button.secondary, .iconbtn, .tabbar-item):active { transform: scale(.965); }

/* Segmented controls (Overview / Sales / Margins, tabs): a glass trough with a lens that slides. */
:root[data-style="glass"] .seg {
  background: linear-gradient(180deg, rgb(0 0 0 / .05), rgb(var(--lg-tint) / .22));
  box-shadow: inset 0 1px 3px rgb(0 0 0 / .14), inset 0 -1px 0 rgb(255 255 255 / .5), var(--lg-edge);
  -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft);
}
:root[data-style="glass"] .seg > :is(.active, [aria-selected="true"], [aria-pressed="true"]) {
  background: linear-gradient(165deg, var(--lg-lens), rgb(var(--lg-tint) / .5));
  box-shadow: 0 4px 12px -3px rgb(0 0 0 / .22), inset 0 1px 0 var(--lg-hi), inset 0 -1px 1px var(--lg-shade), var(--lg-edge);
  color: var(--ink);
}

/* Tab bar: the selected tab is a bright glass lens sitting in the bar. */
:root[data-style="glass"] .tabbar-item.active::before {
  background: linear-gradient(165deg, var(--lg-lens), rgb(var(--lg-tint) / .35));
  box-shadow: 0 4px 14px -4px color-mix(in srgb, var(--accent) 55%, transparent), inset 0 1px 0 var(--lg-hi), var(--lg-edge);
  opacity: 1; transform: scale(1);
}

/* Sheets, palette, menus, login card: heavy glass, dimmer scrim so the page still shows through. */
:root[data-style="glass"] .sheet-overlay { background: rgb(0 0 0 / .18); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
:root[data-style="glass"] :is(.sheet, .cmdk, .style-menu-list) {
  background:
    radial-gradient(130% 70% at 0% 0%, var(--lg-spec) 0%, transparent 50%),
    rgb(var(--lg-tint) / .66);
  border: 1px solid var(--lg-hair);
  box-shadow: 0 -18px 60px -18px rgb(31 58 42 / .35), inset 0 1px 0 var(--lg-hi), var(--lg-edge);
  -webkit-backdrop-filter: blur(34px) saturate(200%); backdrop-filter: blur(34px) saturate(200%);
}
:root[data-style="glass"] .sheet-handle { background: color-mix(in srgb, var(--ink) 30%, transparent); }
:root[data-style="glass"] .sheet-item:active { background: rgb(var(--lg-tint) / .5); }

/* Inputs: a recessed frosted well. */
:root[data-style="glass"] :is(input[type=text], input[type=email], input[type=password], input[type=tel], input[type=number],
  input[type=date], input[type=month], textarea, select) {
  background: rgb(var(--lg-tint) / .42);
  border: 1px solid var(--lg-hair);
  box-shadow: inset 0 2px 4px rgb(0 0 0 / .08), inset 0 -1px 0 rgb(255 255 255 / .4);
  -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft);
}
:root[data-style="glass"] :is(input, textarea, select):focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent), inset 0 2px 4px rgb(0 0 0 / .06);
}

/* Chips and badges: small glass pills. */
:root[data-style="glass"] :is(.badge, .tag, .chip) {
  background: linear-gradient(160deg, var(--lg-spec), transparent 70%), rgb(var(--lg-tint) / .3);
  box-shadow: inset 0 1px 0 var(--lg-hi), var(--lg-edge);
}

/* Touch-and-hold anywhere pressable: a quick specular flash, like light catching the surface. */
@media (prefers-reduced-motion: no-preference) {
  :root[data-style="glass"] :is(button.secondary, .iconbtn, .card, .custcard):active { filter: brightness(1.04) saturate(1.1); }
}

@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  :root[data-style="glass"] :is(.card, .skel-card, .custcard, .sheet, .cmdk, .style-menu-list, .seg,
    button.secondary, button.ghost, .iconbtn, input, textarea, select, .badge, .tag, .chip) {
    -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--glass-solid);
  }
  :root[data-style="glass"] button.primary { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--accent); }
}

/* ── Very Liquid Glass: a colour field for the glass to bend, clearer panes,
   lit rims on two edges, and a luminous (brightened) blur. ── */
:root[data-style="glass"] {
  --depth-a: .62;
  --lg-card-a: .40; --lg-clear-a: .2;
  --lg-blur: blur(30px) saturate(230%) brightness(1.07);
  --lg-blur-soft: blur(16px) saturate(200%) brightness(1.06);
  --lg-rim: inset 1.5px 1.5px 0 rgb(255 255 255 / .85), inset -1px -1px 0 rgb(255 255 255 / .35), inset 0 0 22px rgb(255 255 255 / .22);
  --lg-field:
    radial-gradient(60% 42% at 8% 6%, rgb(255 92 92 / .34), transparent 70%),
    radial-gradient(55% 40% at 96% 22%, rgb(255 178 64 / .30), transparent 70%),
    radial-gradient(60% 45% at 88% 70%, rgb(110 214 90 / .30), transparent 70%),
    radial-gradient(60% 45% at 6% 82%, rgb(70 140 255 / .26), transparent 70%);
}
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) {
    --depth-a: .7; --lg-card-a: .36; --lg-clear-a: .2;
    --lg-rim: inset 1.5px 1.5px 0 rgb(255 255 255 / .28), inset -1px -1px 0 rgb(255 255 255 / .1), inset 0 0 22px rgb(255 255 255 / .06);
    --lg-field:
      radial-gradient(60% 42% at 8% 6%, rgb(235 36 56 / .42), transparent 70%),
      radial-gradient(55% 40% at 96% 22%, rgb(245 150 40 / .32), transparent 70%),
      radial-gradient(60% 45% at 88% 70%, rgb(60 190 90 / .32), transparent 70%),
      radial-gradient(60% 45% at 6% 82%, rgb(60 110 235 / .36), transparent 70%);
  }
}
:root[data-style="glass"][data-theme="dark"] {
  --depth-a: .7; --lg-card-a: .36; --lg-clear-a: .2;
  --lg-rim: inset 1.5px 1.5px 0 rgb(255 255 255 / .28), inset -1px -1px 0 rgb(255 255 255 / .1), inset 0 0 22px rgb(255 255 255 / .06);
  --lg-field:
    radial-gradient(60% 42% at 8% 6%, rgb(235 36 56 / .42), transparent 70%),
    radial-gradient(55% 40% at 96% 22%, rgb(245 150 40 / .32), transparent 70%),
    radial-gradient(60% 45% at 88% 70%, rgb(60 190 90 / .32), transparent 70%),
    radial-gradient(60% 45% at 6% 82%, rgb(60 110 235 / .36), transparent 70%);
}
:root[data-style="glass"] body:not(.login-route) { background: var(--lg-field), var(--bg); background-attachment: fixed; }
:root[data-style="glass"] :is(.card, .skel-card, .custcard) {
  box-shadow: var(--lg-drop), var(--lg-rim), 0 0 0 .5px rgb(255 255 255 / .4);
}
:root[data-style="glass"] :is(header.top, .tabbar) { box-shadow: var(--lg-drop), var(--lg-rim); }
:root[data-style="glass"] :is(.iconbtn, button.secondary, button.ghost, .style-menu-btn, .seg > :is(.active, [aria-selected="true"], [aria-pressed="true"]), .badge, .tag, .chip) {
  box-shadow: 0 6px 18px -8px rgb(31 58 42 / .3), var(--lg-rim);
}
:root[data-style="glass"] :is(.sheet, .cmdk, .style-menu-list) { box-shadow: 0 -18px 60px -18px rgb(31 58 42 / .35), var(--lg-rim); }
:root[data-style="glass"] .tabbar { --lg-tint-a: .5; --lg-tint-scrolled: .62; }
:root[data-style="glass"] header.top { --lg-tint-a: .34; --lg-tint-scrolled: .5; }
:root[data-style="glass"] .card :is(.custcard, .card, .stat, .margin-class-tile, .margin-area, .area-block) {
  background: linear-gradient(160deg, rgb(255 255 255 / .34), rgb(255 255 255 / .08)); box-shadow: inset 1px 1px 0 rgb(255 255 255 / .7), inset -1px -1px 0 rgb(255 255 255 / .2);
}

/* Page furniture that carried its own solid plate: let the glass field show through. */
:root[data-style="glass"] :is(.finishbar, .logbar) { background: transparent; border-top-color: transparent; }
:root[data-style="glass"] .today-actions button {
  background: linear-gradient(160deg, var(--lg-spec), transparent 60%), rgb(var(--lg-tint) / var(--lg-clear-a));
  border: 1px solid var(--lg-hair); box-shadow: var(--lg-rim), 0 6px 18px -8px rgb(31 58 42 / .3);
  -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft); color: var(--ink);
}
:root[data-style="glass"] .today-actions button:active { background: rgb(var(--lg-tint) / .55); color: var(--ink); border-color: var(--lg-hair); }

/* ═══════════════════════════════════════════════════════════════════════════
   Legibility pass (Glass style). Liquid Glass is for CHROME — the header, tab
   bar, sheets and controls. Surfaces that hold reading text are frosted but
   mostly opaque, and the colour field behind them is quieter, so no glow sits
   under a line of text. Also reserves the top safe area in the page flow: the
   floating header is sticky `top: 24px + inset` but sat at 24px in the flow,
   so on a phone with a notch it slid 59px down over the first row of content.
   ═══════════════════════════════════════════════════════════════════════════ */
:root[data-style="glass"] .app { padding-top: env(safe-area-inset-top, 0px); }

:root[data-style="glass"],
:root[data-style="glass"][data-theme="light"] {
  --depth-a: .3;
  --lg-card-a: .8; --lg-clear-a: .5;
  --ink-dim: #4a4a50;
  --lg-field:
    radial-gradient(60% 42% at 8% 6%, rgb(255 92 92 / .2), transparent 70%),
    radial-gradient(55% 40% at 96% 22%, rgb(255 178 64 / .16), transparent 70%),
    radial-gradient(60% 45% at 88% 70%, rgb(110 214 90 / .16), transparent 70%),
    radial-gradient(60% 45% at 6% 82%, rgb(70 140 255 / .16), transparent 70%);
}
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) {
    --depth-a: .3; --lg-card-a: .74; --lg-clear-a: .42; --ink-dim: #b8b8bf;
    --lg-field:
      radial-gradient(60% 42% at 8% 6%, rgb(235 36 56 / .26), transparent 70%),
      radial-gradient(55% 40% at 96% 22%, rgb(245 150 40 / .14), transparent 70%),
      radial-gradient(60% 45% at 88% 70%, rgb(60 190 90 / .14), transparent 70%),
      radial-gradient(60% 45% at 6% 82%, rgb(60 110 235 / .2), transparent 70%);
  }
}
:root[data-style="glass"][data-theme="dark"] {
  --depth-a: .3; --lg-card-a: .74; --lg-clear-a: .42; --ink-dim: #b8b8bf;
  --lg-field:
    radial-gradient(60% 42% at 8% 6%, rgb(235 36 56 / .26), transparent 70%),
    radial-gradient(55% 40% at 96% 22%, rgb(245 150 40 / .14), transparent 70%),
    radial-gradient(60% 45% at 88% 70%, rgb(60 190 90 / .14), transparent 70%),
    radial-gradient(60% 45% at 6% 82%, rgb(60 110 235 / .2), transparent 70%);
}
/* Reading surfaces: a lit corner and rim, but the body of the pane stays a calm, near-solid tint. */
:root[data-style="glass"] :is(.card, .skel-card, .custcard) {
  background:
    radial-gradient(90% 60% at 0% 0%, var(--lg-spec) 0%, transparent 42%),
    rgb(var(--lg-tint) / var(--lg-card-a));
}
:root[data-style="glass"] .card :is(.custcard, .card, .stat, .margin-class-tile, .margin-area, .area-block) {
  background: rgb(var(--lg-tint) / .34);
}
/* Chrome stays the most glass: a touch clearer than the cards, never less than legible. */
:root[data-style="glass"] header.top { --lg-tint-a: .6; --lg-tint-scrolled: .72; }
:root[data-style="glass"] .tabbar { --lg-tint-a: .78; --lg-tint-scrolled: .86; }
:root[data-style="glass"] :is(.sheet, .cmdk, .style-menu-list) { background: radial-gradient(120% 60% at 0% 0%, var(--lg-spec) 0%, transparent 46%), rgb(var(--lg-tint) / .82); }
/* Small print carries weight, not just size: the dim greys and micro labels were the hardest to read. */
:root[data-style="glass"] :is(.today-progress-label, .stat .k, .k, .sub, .hint, .badge, .tag) { color: var(--ink-dim); font-weight: 500; }
:root[data-style="glass"] .today-progress-track { background: rgb(var(--lg-tint) / .6); }

/* ---- Swipe gestures (gestures.js) ---- */
.custcard { position: relative; }
.swipe-pill {
  position: absolute; top: 50%; transform: translateY(-50%) scale(.8); opacity: 0; pointer-events: none; white-space: nowrap;
  padding: 8px 11px; border-radius: 999px; font-size: calc(12px * var(--dyn)); font-weight: 600; letter-spacing: 0;
  background: var(--ink); color: var(--bg); box-shadow: 0 6px 18px -6px rgb(0 0 0 / .35);
  transition: background-color .15s, box-shadow .15s;
}
.swipe-pill.l { left: calc(100% + 6px); }
.swipe-pill.r { right: calc(100% + 6px); }
.swipe-pill.l.armed { background: var(--accent); color: var(--accent-ink); }
.swipe-pill.r.armed { background: var(--good, #1f7a4a); color: #fff; }
#viewMount { will-change: auto; }

/* ── Design pass: the last square, bordered leftovers become Liquid Glass ── */
/* Customer tabs (Overview / Details / Activity / Media): the same glass trough + sliding lens as the segmented controls. */
:root[data-style="glass"] body .tabs {
  border: none; border-radius: var(--radius-pill); padding: 3px; gap: 2px; overflow: visible; max-width: 100%;
  background: linear-gradient(180deg, rgb(0 0 0 / .05), rgb(var(--lg-tint) / .22));
  box-shadow: inset 0 1px 3px rgb(0 0 0 / .14), inset 0 -1px 0 rgb(255 255 255 / .5), var(--lg-edge);
  -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft);
}
:root[data-style="glass"] body .tab { background: transparent; border: none; border-radius: var(--radius-pill); min-height: 38px; font-size: var(--fs-13); text-transform: none; letter-spacing: 0; color: var(--ink-dim); transition: background-color .2s var(--ease-glass), box-shadow .2s var(--ease-glass), color .2s; }
:root[data-style="glass"] body .tab.active {
  color: var(--ink); font-weight: 600;
  background: linear-gradient(165deg, var(--lg-lens), rgb(var(--lg-tint) / .5));
  box-shadow: 0 4px 12px -3px rgb(0 0 0 / .22), inset 0 1px 0 var(--lg-hi), inset 0 -1px 1px var(--lg-shade), var(--lg-edge);
}
/* Visit outcome: four glass tiles, not a ruled grid. */
:root[data-style="glass"] body .outcomes { gap: 8px; border: none; background: none; border-radius: 0; overflow: visible; }
:root[data-style="glass"] body .outcome {
  border-radius: 16px; min-height: 54px; font-size: var(--fs-15); text-transform: none; letter-spacing: 0; font-weight: 500;
  background: linear-gradient(160deg, var(--lg-spec) 0%, transparent 60%), rgb(var(--lg-tint) / var(--lg-clear-a));
  border: 1px solid var(--lg-hair); box-shadow: var(--lg-rim), 0 6px 18px -10px rgb(31 58 42 / .3);
  -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft);
  transition: transform .18s var(--ease-glass), background-color .18s, box-shadow .18s;
}
:root[data-style="glass"] body .outcome:active { transform: scale(.97); }
:root[data-style="glass"] body .outcome.selected {
  background: linear-gradient(165deg, rgb(255 255 255 / .38) 0%, transparent 50%), color-mix(in srgb, var(--accent) 90%, transparent);
  color: var(--accent-ink); border-color: rgb(255 255 255 / .4); font-weight: 600;
  box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--accent) 80%, transparent), inset 0 1px 0 rgb(255 255 255 / .6);
}
/* The Log visit / Finish day bar floats on the same glass as the tab bar rather than sitting on a plain plate. */
:root[data-style="glass"] body :is(.logbar, .finishbar) {
  background: rgb(var(--lg-tint) / .72); border-top: 1px solid var(--lg-hair);
  -webkit-backdrop-filter: blur(22px) saturate(190%); backdrop-filter: blur(22px) saturate(190%);
}
/* Small round header buttons are 36px to look right and 44px to touch. */
.style-menu-btn, .iconbtn { position: relative; }
.style-menu-btn::after, .iconbtn::after { content: ""; position: absolute; inset: -4px; }
.chip.acct-filter-chip { min-width: 44px; }
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) .tabbar-item.active { color: #ff8f86; }
}
:root[data-style="glass"][data-theme="dark"] .tabbar-item.active { color: #ff8f86; }
/* A selected chip is tinted glass with light text — the generic glass chip above must not repaint it pale. */
:root[data-style="glass"] body :is(.chip.active, .chip.selected, .chip[aria-pressed="true"], .chip[aria-selected="true"]) {
  background: linear-gradient(165deg, rgb(255 255 255 / .38) 0%, transparent 50%), color-mix(in srgb, var(--accent) 90%, transparent);
  color: var(--accent-ink); border-color: rgb(255 255 255 / .4); font-weight: 600;
  box-shadow: 0 8px 20px -10px color-mix(in srgb, var(--accent) 80%, transparent), inset 0 1px 0 rgb(255 255 255 / .55);
}

/* ═══ Settings (the More sheet): grouped rows like iOS Settings ═══ */
.settings { padding: 0 0 calc(24px + env(safe-area-inset-bottom)); }
.set-title { font-size: calc(13px * var(--dyn)); font-weight: 500; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .04em; margin: 22px 20px 7px; }
.set-group { margin: 0 12px; border-radius: 16px; overflow: hidden; background: var(--well); border: 1px solid var(--line); }
.set-row {
  position: relative; display: flex; align-items: center; gap: 12px; width: 100%; min-height: 52px; padding: 8px 14px;
  background: none; border: 0; border-radius: 0; box-shadow: none; color: var(--ink); font: inherit; font-size: calc(17px * var(--dyn)); text-align: left;
  text-decoration: none; cursor: pointer; text-transform: none; letter-spacing: 0; box-sizing: border-box;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.set-row + .set-row::before { content: ""; position: absolute; top: 0; left: 56px; right: 0; height: .5px; background: var(--line); }
.set-row:active { background: color-mix(in srgb, var(--ink) 8%, transparent); }
.set-ico { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; color: #fff; flex-shrink: 0; box-shadow: inset 0 1px 0 rgb(255 255 255 / .28); }
.set-ico.flat { background: transparent !important; color: var(--ink-dim); box-shadow: none; }
.set-label { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.set-label small { font-size: calc(13px * var(--dyn)); color: var(--ink-dim); font-weight: 400; margin-top: 1px; line-height: 1.3; }
.set-row .chev { color: var(--ink-dim); opacity: .7; flex-shrink: 0; }
.set-row.danger { color: #d70015; justify-content: flex-start; font-weight: 500; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .set-row.danger { color: #ff6961; } }
:root[data-theme="dark"] .set-row.danger { color: #ff6961; }
.set-search {
  display: flex; align-items: center; gap: 8px; width: calc(100% - 24px); margin: 6px 12px 0; min-height: 44px; padding: 0 10px;
  border: 0; border-radius: 12px; background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink-dim); font: inherit; font-size: calc(17px * var(--dyn)); text-align: left; cursor: pointer;
}
.set-seg { margin: 0; flex: 0 0 auto; width: auto; }
.set-seg > button { min-height: 30px !important; padding: 0 11px; font-size: calc(14px * var(--dyn)) !important; }
.set-seg-row .set-label { flex: 0 0 auto; margin-right: auto; }
:root[data-style="glass"] .set-group { border-radius: 18px; background: rgb(var(--lg-tint) / .66); border-color: var(--lg-hair); box-shadow: var(--lg-rim); -webkit-backdrop-filter: var(--lg-blur-soft); backdrop-filter: var(--lg-blur-soft); }
:root[data-style="glass"] .set-search { background: rgb(var(--lg-tint) / .5); box-shadow: var(--lg-rim); }
:root[data-style="glass"] .set-seg { padding: 2px; }
:root[data-style="glass"] .set-seg > button { border-radius: 999px; }

/* Toggles the way iOS draws them: a 51×31 pill, a white thumb that stretches under the finger, green when on. */
:root[data-style="glass"] .switch { width: 51px; height: 31px; }
:root[data-style="glass"] .switch .slider {
  border: 0; border-radius: 999px; background: color-mix(in srgb, var(--ink) 20%, transparent);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / .05); transition: background-color .25s var(--ease-glass);
}
:root[data-style="glass"] .switch .slider::before {
  width: 27px; height: 27px; left: 2px; top: 2px; border-radius: 50%; background: #fff;
  box-shadow: 0 3px 8px rgb(0 0 0 / .25), 0 1px 1px rgb(0 0 0 / .16); transition: transform .32s cubic-bezier(.34, 1.4, .64, 1), width .18s ease;
}
:root[data-style="glass"] .switch input:checked + .slider { background: #34c759; border: 0; }
@media (prefers-color-scheme: dark) { :root[data-style="glass"]:not([data-theme="light"]) .switch input:checked + .slider { background: #30d158; } }
:root[data-style="glass"][data-theme="dark"] .switch input:checked + .slider { background: #30d158; }
:root[data-style="glass"] .switch input:checked + .slider::before { transform: translateX(20px); background: #fff; }
:root[data-style="glass"] .switch:active .slider::before { width: 33px; }
:root[data-style="glass"] .switch:active input:checked + .slider::before { transform: translateX(14px); }
:root[data-style="glass"] .switch input:focus-visible + .slider { outline: 3px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 2px; }
:root[data-style="glass"] .switch input:disabled + .slider { opacity: .6; }
@media (prefers-reduced-motion: reduce) { :root[data-style="glass"] .switch .slider, :root[data-style="glass"] .switch .slider::before { transition: none; } }

/* ═══ Fixes from a real phone (Dashboard, dark) ═══ */
/* 1. Scroll edge under the status bar: content used to slide up, sharp, behind the clock and battery.
      A fixed strip blurs and fades whatever passes there, the way iOS's own bars do. */
@media (pointer: coarse) {
  :root[data-style="glass"] body:not(.login-route)::before {
    content: ""; position: fixed; top: 0; left: 0; right: 0; height: calc(env(safe-area-inset-top, 0px) + 40px); z-index: 30; pointer-events: none;
    background: linear-gradient(to bottom, color-mix(in srgb, var(--bg) 97%, transparent) 0%, color-mix(in srgb, var(--bg) 92%, transparent) 66%, transparent 100%);
    -webkit-backdrop-filter: blur(22px) saturate(160%); backdrop-filter: blur(22px) saturate(160%);
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 68%, transparent 100%); mask-image: linear-gradient(to bottom, #000 0%, #000 68%, transparent 100%);
  }
}
/* 2. Stat rows inside a card block: no doubled outlines, and room for the text so nothing is clipped at the edge. */
:root[data-style="glass"] body .area-block .stat-row { gap: 0; }
:root[data-style="glass"] body .area-block .stat {
  background: none; box-shadow: none; border: 0; border-radius: 0; padding: 9px 4px;
}
:root[data-style="glass"] body .area-block .stat + .stat { border-top: .5px solid var(--line); }
:root[data-style="glass"] body .card .stat { box-shadow: none; }
/* 3. On/off checkboxes become real iOS switches (the ones that are settings, not "mark done" ticks). */
:root[data-style="glass"] :is(#custListMarginToggle, #custListLastYearToggle, #merchFridge, #merchPriceTags, #merchPos, #repMarginEmailDirect) {
  -webkit-appearance: none; appearance: none; width: 51px !important; height: 31px !important; flex-shrink: 0; margin: 0 12px 0 0; padding: 0;
  border: 0; border-radius: 999px; cursor: pointer; vertical-align: middle;
  background-color: color-mix(in srgb, var(--ink) 20%, transparent);
  background-image: radial-gradient(circle, #fff 0 13px, transparent 13.6px); background-repeat: no-repeat; background-size: 27px 27px; background-position: 2px center;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / .05);
  transition: background-color .25s var(--ease-glass), background-position .32s cubic-bezier(.34, 1.4, .64, 1);
}
:root[data-style="glass"] :is(#custListMarginToggle, #custListLastYearToggle, #merchFridge, #merchPriceTags, #merchPos, #repMarginEmailDirect):checked { background-color: #34c759; background-position: 22px center; }
@media (prefers-color-scheme: dark) { :root[data-style="glass"]:not([data-theme="light"]) :is(#custListMarginToggle, #custListLastYearToggle, #merchFridge, #merchPriceTags, #merchPos, #repMarginEmailDirect):checked { background-color: #30d158; } }
:root[data-style="glass"][data-theme="dark"] :is(#custListMarginToggle, #custListLastYearToggle, #merchFridge, #merchPriceTags, #merchPos, #repMarginEmailDirect):checked { background-color: #30d158; }
:root[data-style="glass"] label:has(> :is(#custListMarginToggle, #custListLastYearToggle, #merchFridge, #merchPriceTags, #merchPos)) { display: flex; align-items: center; min-height: 44px; }
@media (prefers-reduced-motion: reduce) { :root[data-style="glass"] :is(#custListMarginToggle, #custListLastYearToggle, #merchFridge, #merchPriceTags, #merchPos, #repMarginEmailDirect) { transition: none; } }

/* Supplier logos are drawn for a light page: they always sit on a white tile, in Light and Dark alike
   (dark lettering vanished on the dark tile: Markwell, McCain, Goodman Fielder). */
body .sup-tile { background: #fff !important; border-color: rgb(0 0 0 / .08); }
:root[data-style="glass"] body .sup-tile { border-radius: 16px; box-shadow: 0 6px 18px -10px rgb(0 0 0 / .35), inset 0 1px 0 rgb(255 255 255 / .9); -webkit-backdrop-filter: none; backdrop-filter: none; }
body .sup-tile .sup-name { color: #4a4a50; }

/* Header round buttons keep their shape however wide the weather pill gets. */
header.top .style-menu-btn, header.top .iconbtn { flex-shrink: 0; min-width: 36px; }
header.top #weatherSlot { flex: 0 1 auto; min-width: 0; overflow: hidden; -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent); }
header.top #weatherSlot > * { max-width: 100%; white-space: nowrap; }


/* ═══════════════ Apple HIG review pass ═══════════════ */
/* Smoothness: a blur per card / control / input repaints on every scroll frame. Blur belongs on the few
   chrome surfaces (header, tab bar, sheets, action bar); everything else is already 75-80% opaque tint. */
:root[data-style="glass"] :is(.card, .skel-card, .custcard, .stat-row, .set-group, .set-search, .iconbtn, .style-menu-btn,
  button.primary, button.secondary, button.ghost, .seg, .tabs, .outcome, .chip, .badge, .tag, .today-actions button,
  input, textarea, select, .swipe-pill, .switch, .sup-tile, .card .stat) {
  -webkit-backdrop-filter: none !important; backdrop-filter: none !important;
}
/* Long lists paint only what is on screen. */
.acct-row, .timeline-row { content-visibility: auto; contain-intrinsic-size: auto 96px; }
/* Text sizes: nothing below 11pt in the Glass style (10px labels were the smallest and hardest to read). */
:root[data-style="glass"] { --fs-10: calc(11px * var(--dyn)); --fs-11: calc(12px * var(--dyn)); --fs-12: calc(13px * var(--dyn)); }
/* Landscape and iPad: content clears the notch and the home-indicator side insets. */
.app { padding-left: env(safe-area-inset-left, 0px); padding-right: env(safe-area-inset-right, 0px); }
.tabbar { padding-left: max(4px, env(safe-area-inset-left, 0px)); padding-right: max(4px, env(safe-area-inset-right, 0px)); }
/* No 300ms tap delay or double-tap zoom on anything pressable. */
a, button, input, select, textarea, label, summary, [role="button"], [role="tab"] { touch-action: manipulation; }
/* Reduce Motion: every animation and transition collapses; state changes still show. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
}
/* Increase Contrast: real borders and full-strength secondary text. */
@media (prefers-contrast: more) {
  :root[data-style="glass"] { --ink-dim: var(--ink); --line: color-mix(in srgb, var(--ink) 45%, transparent); }
  :root[data-style="glass"] :is(.card, .custcard, .set-group, .outcome, button.secondary, .iconbtn) { border-color: var(--ink) !important; }
}

/* Tab bar labels grow with Dynamic Type only a little (as iOS's own tab bar does) and never wrap. */
.tabbar-item { font-size: calc(12px * min(var(--dyn, 1), 1.15)) !important; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }


/* Sign-in: the Hobart harbour video is the whole background, at every size. No colour field, glow, parallax
   blobs or status-bar strip behind it, and the glass plate stays neutral. */
body.login-route { background: #0d0d0c !important; }
body.login-route::before { display: none !important; }
body.login-route .glass-depth, body.login-route .app::before { display: none !important; }
body.login-route .login-photo-scrim { background: linear-gradient(180deg, rgba(10,10,9,.38), rgba(10,10,9,.62)); }
body.login-route .gate { margin-top: max(10vh, calc(env(safe-area-inset-top, 0px) + 24px)); }
@media (max-width: 699px) { body.login-route .gate { max-width: none; margin-left: 16px; margin-right: 16px; padding: 30px 22px 24px; } }

/* ═══════════════ Apple geometry pass — see DESIGN-SPEC.md ═══════════════ */
:root[data-style="glass"] {
  --r-capsule: 999px; --r-card: 24px; --r-inner: 16px; --r-tile: 20px; --r-field: 14px; --r-sheet: 34px;
  --radius-sm: 14px; --radius-md: 24px; --radius-lg: 34px; --radius-pill: 999px;
}
/* Buttons are capsules; the main one is 50 high. */
:root[data-style="glass"] body :is(button.primary, button.secondary, button.ghost, button.danger, .maplink, .next-dir, .today-actions button,
  .qa-btn, .cal-nav-btn, .undo-chip, .backbtn, header.top .iconbtn:not(.style-menu-btn), .set-search) { border-radius: var(--r-capsule); }
:root[data-style="glass"] body button.primary { min-height: 50px; padding-left: 22px; padding-right: 22px; }
:root[data-style="glass"] body :is(button.secondary, button.ghost, .maplink, .next-dir) { min-height: 44px; padding-left: 18px; padding-right: 18px; }
/* Fields: 14; search fields are capsules. */
:root[data-style="glass"] body :is(input[type=text], input[type=email], input[type=password], input[type=tel], input[type=number],
  input[type=date], input[type=month], input[type=search], textarea, select) { border-radius: var(--r-field); }
:root[data-style="glass"] body :is(#acctSearch, #purSearchInput, .cmdk input, input[type=search]) { border-radius: var(--r-capsule); padding-left: 18px; padding-right: 18px; }
/* Containers: 24, and what sits inside them is concentric (24 − 8 = 16). */
:root[data-style="glass"] body :is(.card, .skel-card, .custcard) { border-radius: var(--r-card); }
:root[data-style="glass"] body .custcard.is-next { border-radius: 26px; }
:root[data-style="glass"] body :is(.card, .custcard) :is(.area-block, .margin-class-tile, .margin-area, .stat-row, .custcard, .gauge-wrap) { border-radius: var(--r-inner); }
:root[data-style="glass"] body :is(.outcome) { border-radius: var(--r-tile); }
:root[data-style="glass"] body header.top { border-radius: 30px; }
:root[data-style="glass"] body .tabbar { border-radius: 30px 30px 0 0; }
:root[data-style="glass"] body :is(.sheet) { border-radius: var(--r-sheet) var(--r-sheet) 0 0; }
:root[data-style="glass"] body .sheet-handle { width: 36px; height: 5px; }
:root[data-style="glass"] body :is(.cmdk) { border-radius: 26px; }
:root[data-style="glass"] body :is(.style-menu-list) { border-radius: 22px; }
:root[data-style="glass"] body :is(.set-group) { border-radius: 22px; }
:root[data-style="glass"] body :is(.sup-tile, .gallery-tile, .visit-photo-thumb, .gallery-item img) { border-radius: 16px; }
:root[data-style="glass"] body .gate { border-radius: 28px; }
:root[data-style="glass"] body :is(.badge, .tag, .chip, .toast, .today-progress-track, .gauge-track, .gauge-fill, .margin-mini-track, .margin-mini-fill) { border-radius: var(--r-capsule); }

/* Sales gauges wear the margin bars' colours: the loading bar's red → amber → green laid across the WHOLE track
   (100cqw = the track's width, not the fill's), so the colour a bar ends on says where it stands against the "Today" pace tick (--t):
   red well short, amber close, green once it is at or past the tick, and it stays put while the bar grows. */
.gauge-track { container-type: inline-size; }
.gauge-fill, .gauge-fill.behind {
  background: linear-gradient(90deg, #e0322f 0%, #c9790f calc(max(var(--t, 100%), 8%) * .8), #2f9e4f max(var(--t, 100%), 8%)) no-repeat;
  background-size: 100cqw 100%;
}
:root[data-style="glass"] .gauge-fill { box-shadow: inset 0 1px 0 rgb(255 255 255 / .35), 0 0 12px -3px rgb(224 50 47 / .35); }


/* ═══ Edit layout (Dashboard sections) and the tab-bar editor ═══ */
.dash-grid.ordered { display: flex !important; flex-direction: column; }
.dash-grid.ordered > .dash-panel.visible { display: contents; }
.dash-grid.ordered :is(.dash-sales, .dash-margin-summary, .dash-area, .dash-trend) { grid-column: auto; grid-row: auto; }
.dash-editrow { display: flex; gap: 8px; justify-content: flex-end; margin: -4px 0 12px; }
.dash-edit-btn { min-height: 34px; padding: 0 16px; font-size: var(--fs-13); }
.dash-edit-btn.primary-ish { background: var(--accent); color: var(--accent-ink); border-color: transparent; font-weight: 600; }
.sec-editing { position: relative; outline: 1.5px dashed color-mix(in srgb, var(--ink) 30%, transparent); outline-offset: 5px; border-radius: var(--r-card, 24px); }
.sec-editing > *:not(.sec-controls) { pointer-events: none; }
.sec-controls { position: absolute; top: 8px; right: 10px; z-index: 6; display: flex; align-items: center; gap: 6px; }
.sec-editing > .sec-controls { pointer-events: auto; }
.sec-controls button {
  width: 36px; height: 36px; border-radius: 50%; border: 0; padding: 0; display: grid; place-items: center; font-size: calc(17px * var(--dyn)); line-height: 1;
  background: color-mix(in srgb, var(--ink) 10%, var(--well)); color: var(--ink); cursor: pointer;
}
.sec-grip {
  width: 44px; height: 36px; border-radius: 999px; display: grid; place-items: center; font-size: calc(22px * var(--dyn)); line-height: 1; cursor: grab; touch-action: none;
  background: color-mix(in srgb, var(--ink) 12%, var(--well)); color: var(--ink-dim); user-select: none; -webkit-user-select: none;
}
.sort-drag { z-index: 30 !important; box-shadow: 0 22px 44px -14px rgb(0 0 0 / .45) !important; opacity: .96; }
.sec-editing:not(.sort-drag) { animation: secWiggle 2.4s ease-in-out infinite; }
@keyframes secWiggle { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-.18deg); } 75% { transform: rotate(.18deg); } }
@media (prefers-reduced-motion: reduce) { .sec-editing:not(.sort-drag) { animation: none; } }

.tabedit-help { margin: 0 20px 10px; color: var(--ink-dim); font-size: var(--fs-13); line-height: 1.4; }
.tabedit-list { display: flex; flex-direction: column; margin: 0 12px; }
.tabedit-row {
  display: flex; align-items: center; gap: 12px; min-height: 58px; padding: 8px 12px; position: relative;
  background: var(--well); border: 1px solid var(--line); border-radius: 18px; margin-bottom: 8px;
}
:root[data-style="glass"] .tabedit-row { background: rgb(var(--lg-tint) / .66); border-color: var(--lg-hair); box-shadow: var(--lg-rim); }
.tabedit-row .set-label { font-size: calc(17px * var(--dyn)); }
.tabedit-row .sec-controls { position: static; }
.tabedit-sep { padding: 4px 16px 12px; font-size: calc(12px * var(--dyn)); font-weight: 600; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .05em; text-align: center; }
:root[data-style="glass"] body .dash-edit-btn.primary-ish {
  background: linear-gradient(165deg, rgb(255 255 255 / .38) 0%, transparent 50%), color-mix(in srgb, var(--accent) 92%, transparent);
  color: var(--accent-ink); border-color: rgb(255 255 255 / .4); font-weight: 600;
}


/* ═══ The glass lens (motion.js): tab bar + segmented controls ═══ */
.seg, .tabs { position: relative; }
/* The tab bar is already positioned (fixed on phones); only where it sits in the page flow does it need to be a
   containing block for its lens. Never override its `position` here: that lifted it off the bottom of the screen. */
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) { .tabbar { position: relative; } }
.lens { position: absolute; left: 0; top: 0; z-index: 0; pointer-events: none; border-radius: 999px; display: none; will-change: transform, width; }
.tabbar > :not(.lens), .seg > :not(.lens), .tabs > :not(.lens) { position: relative; z-index: 1; }
.tabbar { touch-action: none; }
:root[data-style="glass"] .lens { display: block; }

/* The bar's lens is the bold one: a bright glass bubble with a slight SFS colour wash, a lit rim, and a soft
   glow, like the magnifier lens in iOS 26. Where it sits, the label lifts and grows a touch. */
:root[data-style="glass"] .tabbar > .lens {
  background:
    radial-gradient(90% 120% at 22% 0%, rgb(255 255 255 / .95) 0%, rgb(255 255 255 / 0) 55%),
    linear-gradient(115deg, rgb(224 50 47 / .24) 0%, rgb(201 121 15 / .20) 48%, rgb(47 158 79 / .26) 100%),
    rgb(255 255 255 / .42);
  box-shadow:
    inset 0 1.5px 1px rgb(255 255 255 / .95), inset 0 -1.5px 2px rgb(0 0 0 / .10), inset 0 0 14px rgb(255 255 255 / .35),
    0 0 0 .5px rgb(255 255 255 / .8), 0 10px 26px -8px rgb(224 50 47 / .45), 0 4px 10px -4px rgb(0 0 0 / .25);
  -webkit-backdrop-filter: blur(5px) saturate(210%) brightness(1.12); backdrop-filter: blur(5px) saturate(210%) brightness(1.12);
  transition: scale .28s cubic-bezier(.34, 1.5, .64, 1), box-shadow .28s;
}
@media (prefers-color-scheme: dark) {
  :root[data-style="glass"]:not([data-theme="light"]) .tabbar > .lens {
    background:
      radial-gradient(90% 120% at 22% 0%, rgb(255 255 255 / .34) 0%, rgb(255 255 255 / 0) 55%),
      linear-gradient(115deg, rgb(224 50 47 / .40) 0%, rgb(201 121 15 / .30) 48%, rgb(47 158 79 / .40) 100%),
      rgb(255 255 255 / .12);
    box-shadow: inset 0 1.5px 1px rgb(255 255 255 / .55), inset 0 -1.5px 2px rgb(0 0 0 / .25), inset 0 0 14px rgb(255 255 255 / .12),
      0 0 0 .5px rgb(255 255 255 / .3), 0 10px 26px -8px rgb(224 50 47 / .55), 0 4px 10px -4px rgb(0 0 0 / .5);
  }
}
:root[data-style="glass"][data-theme="dark"] .tabbar > .lens {
  background:
    radial-gradient(90% 120% at 22% 0%, rgb(255 255 255 / .34) 0%, rgb(255 255 255 / 0) 55%),
    linear-gradient(115deg, rgb(224 50 47 / .40) 0%, rgb(201 121 15 / .30) 48%, rgb(47 158 79 / .40) 100%),
    rgb(255 255 255 / .12);
  box-shadow: inset 0 1.5px 1px rgb(255 255 255 / .55), inset 0 -1.5px 2px rgb(0 0 0 / .25), inset 0 0 14px rgb(255 255 255 / .12),
    0 0 0 .5px rgb(255 255 255 / .3), 0 10px 26px -8px rgb(224 50 47 / .55), 0 4px 10px -4px rgb(0 0 0 / .5);
}
:root[data-style="glass"] .tabbar > .lens.dragging { scale: 1.16; box-shadow: inset 0 1.5px 1px rgb(255 255 255 / .95), inset 0 0 18px rgb(255 255 255 / .45), 0 0 0 .5px rgb(255 255 255 / .9), 0 16px 34px -8px rgb(224 50 47 / .55), 0 6px 14px -4px rgb(0 0 0 / .3); }
/* The old per-tab pill steps aside; the lens does its job. */
:root[data-style="glass"] .tabbar:has(> .lens) .tabbar-item::before { opacity: 0 !important; }
:root[data-style="glass"] .tabbar:has(> .lens) .tabbar-item { border-top-color: transparent; transition: transform .3s cubic-bezier(.34, 1.5, .64, 1), color .2s, opacity .08s; }
:root[data-style="glass"] .tabbar:has(> .lens) .tabbar-item.active { transform: scale(1.06); }
:root[data-style="glass"] .tabbar:has(> .lens) .tabbar-item.lens-over { transform: scale(1.1); color: var(--ink); }
:root[data-style="glass"] .tabbar:has(> .lens.dragging) .tabbar-item.active:not(.lens-over) { transform: none; color: var(--ink-dim); }

/* Segmented controls and customer tabs: the same bubble, smaller. */
:root[data-style="glass"] :is(.seg, .tabs) > .lens {
  background: linear-gradient(165deg, var(--lg-lens), rgb(var(--lg-tint) / .5));
  box-shadow: 0 4px 12px -3px rgb(0 0 0 / .22), inset 0 1px 0 var(--lg-hi), inset 0 -1px 1px var(--lg-shade), var(--lg-edge);
}
:root[data-style="glass"] :is(.seg, .tabs):has(> .lens) > :is([aria-pressed="true"], [aria-selected="true"], .active) { background: transparent !important; box-shadow: none !important; color: var(--ink); }


/* ── Sign-in glance: weather + a line to start the day, over the harbour ── */
.login-glance { display: none; }
body.login-route .login-glance {
  display: block; position: fixed; left: 0; right: 0; bottom: calc(28px + env(safe-area-inset-bottom, 0px)); z-index: 1;
  padding: 0 28px; text-align: center; color: #fff; pointer-events: none; text-shadow: 0 1px 12px rgb(0 0 0 / .55), 0 0 2px rgb(0 0 0 / .35);
}
.lg-wx { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; margin-bottom: 12px; padding: 8px 16px; border-radius: 999px;
  background: rgb(20 20 18 / .38); border: 1px solid rgb(255 255 255 / .22); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); font-size: calc(15px * var(--dyn)); }
.lg-wx[hidden] { display: none; }
.lg-ico .wxi { width: 26px; height: 26px; vertical-align: middle; }
.lg-temp { font-size: calc(22px * var(--dyn)); font-weight: 700; letter-spacing: -.01em; }
.lg-desc { font-weight: 500; }
.lg-where { opacity: .85; }
.lg-dim { opacity: .8; font-size: calc(13px * var(--dyn)); }
.lg-quote { margin: 0 auto; max-width: 34ch; font-size: calc(17px * var(--dyn)); line-height: 1.4; font-weight: 500; font-style: italic; transition: opacity .32s ease, transform .32s ease; }
.lg-quote.out { opacity: 0; transform: translateY(6px); }
@media (max-height: 640px) { .login-glance { display: none !important; } }

.siri-row { align-items: flex-start; padding-top: 12px; padding-bottom: 12px; }
.siri-row .set-ico { margin-top: 2px; }
.siri-say { font-size: calc(16px * var(--dyn)); font-weight: 600; line-height: 1.3; }

/* ---- Sales/margin insights (from the Salesforce review) ---- */
.landing { margin: 10px 0 2px; padding: 10px 12px; border-radius: var(--r-inner, 12px); background: var(--well); border: 1px solid var(--line); }
.landing-main { font-size: var(--fs-14, 14px); }
.landing-main strong { font-family: var(--font-mono); font-weight: 600; }
.landing-pct { margin-left: 6px; font-weight: 600; }
.landing.ahead .landing-pct { color: var(--good); } .landing.on-pace .landing-pct { color: var(--caution); } .landing.behind .landing-pct { color: var(--warn); }
.landing-sub { margin-top: 3px; font-size: var(--fs-12, 12px); color: var(--ink-dim); }
.trend-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.trend-head h2 { margin: 0; }
.trend-chips, .cust-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.cust-chips { margin: 8px 0 4px; }
.trend-chip { font: inherit; font-size: calc(12px * var(--dyn)); padding: 5px 11px; min-height: 30px; border-radius: 999px; border: 1px solid var(--line); background: var(--well); color: var(--ink); cursor: pointer; }
.trend-chip.on { background: var(--accent, #c62a2f); border-color: transparent; color: #fff; }
.trend-band { fill: #2f9e4f; opacity: .10; }
.opp-dollars { display: block; font-size: calc(11px * var(--dyn)); font-weight: 500; color: var(--ink-dim); }
.opp-total { margin: 6px 0 10px; padding: 8px 12px; border-radius: var(--r-inner, 12px); background: var(--well); font-size: calc(13px * var(--dyn)); }
.cust-flag { display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px; font-size: calc(10px * var(--dyn)); font-weight: 600; vertical-align: 1px; }
.cust-flag.low { background: rgba(224,50,47,.14); color: var(--warn); } .cust-flag.ok { background: rgba(47,158,79,.14); color: var(--good); }
.mover-title { margin: 10px 0 4px; font-size: calc(11px * var(--dyn)); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-dim); }
.mover { display: grid; grid-template-columns: minmax(0,1.2fr) minmax(0,1fr) auto; gap: 8px; align-items: center; padding: 4px 0; font-size: calc(13px * var(--dyn)); }
.mover-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mover-bar { height: 8px; border-radius: 999px; background: rgba(128,128,128,.16); overflow: hidden; }
.mover-bar i { display: block; height: 100%; border-radius: 999px; }
.mover.up .mover-bar i { background: #2f9e4f; } .mover.down .mover-bar i { background: #e0322f; }
.mover.up .mover-val { color: var(--good); } .mover.down .mover-val { color: var(--warn); }
.gp-row { padding: 8px 0; border-top: 1px solid var(--line); } .gp-row:first-of-type { border-top: 0; }
.gp-top { display: flex; justify-content: space-between; gap: 8px; font-size: calc(13px * var(--dyn)); }
.gp-bar { height: 8px; margin: 6px 0 3px; border-radius: 999px; background: rgba(128,128,128,.16); overflow: hidden; }
.gp-bar i { display: block; height: 100%; border-radius: 999px; }
.gp-bar.low i { background: linear-gradient(90deg, #e0322f, #c9790f); } .gp-bar.ok i { background: linear-gradient(90deg, #c9790f, #2f9e4f); }
.gp-sub { font-size: calc(12px * var(--dyn)); }
.bridge-svg { width: 100%; height: auto; display: block; margin-top: 8px; }
.bridge-svg .br-total { fill: var(--ink-dim, #8e8e93); } .bridge-svg .br-pos { fill: #2f9e4f; } .bridge-svg .br-neg { fill: #e0322f; }
.bridge-svg text { fill: var(--ink); font-size: calc(13px * var(--dyn)); } .bridge-svg .br-lab { font-size: calc(12px * var(--dyn)); fill: var(--ink-dim); }
.heat-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-top: 8px; }
.heat { border-collapse: separate; border-spacing: 3px; width: 100%; font-size: calc(12px * var(--dyn)); }
.heat th { font-weight: 500; text-align: left; padding: 4px 6px; white-space: nowrap; max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
.heat thead th { text-align: center; color: var(--ink-dim); }
.heat td { text-align: center; padding: 8px 4px; border-radius: 8px; font-family: var(--font-mono); font-weight: 500; }
.heat .hm-good { background: rgba(47,158,79,.22); } .heat .hm-near { background: rgba(201,121,15,.24); } .heat .hm-bad { background: rgba(224,50,47,.24); } .heat .hm-none { color: var(--ink-dim); }
.dash-grid.ordered :is(.dash-sales, .dash-margin-summary, .dash-area, .dash-trend) { grid-column: auto; grid-row: auto; }

.trend-class { font: inherit; font-size: calc(12px * var(--dyn)); min-height: 30px; padding: 3px 26px 3px 10px; border-radius: 999px; }
.pace-strip-svg { width: 100%; height: 90px; display: block; margin-top: 8px; }
.pace-strip-svg .ps-ok { fill: #2f9e4f; } .pace-strip-svg .ps-low { fill: #c9790f; }
.pace-strip-svg .ps-flat { stroke: var(--ink-dim, #8e8e93); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.pace-strip-svg .ps-need { stroke: #e0322f; stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.ps-labels { display: flex; justify-content: space-between; font-size: calc(11px * var(--dyn)); color: var(--ink-dim); margin-top: 2px; }

/* Face ID lock cover: the same harbour video, weather and line as the sign-in, so opening the app looks the same. */
.app-lock.with-hero { background: #0d0d0c; -webkit-backdrop-filter: none; backdrop-filter: none; color: #fff; color-scheme: dark; overflow: hidden; }
.app-lock.with-hero .login-photo-bg { display: block; position: absolute; inset: 0; z-index: 0; }
.app-lock.with-hero .login-photo-scrim { background: linear-gradient(180deg, rgba(10,10,9,.38), rgba(10,10,9,.62)); }
.app-lock.with-hero .app-lock-card { position: relative; z-index: 1; text-shadow: 0 1px 12px rgb(0 0 0 / .5); }
.app-lock.with-hero .login-glance { display: block; position: absolute; left: 0; right: 0; bottom: calc(28px + env(safe-area-inset-bottom, 0px)); z-index: 1;
  padding: 0 28px; text-align: center; color: #fff; pointer-events: none; text-shadow: 0 1px 12px rgb(0 0 0 / .55), 0 0 2px rgb(0 0 0 / .35); }

/* ---- Lock Screen look picker ---- */
.ll-wrap { padding: 4px 16px 24px; }
.ll-privacy { padding: 10px 12px; border-radius: var(--r-inner, 12px); background: rgba(47,158,79,.14); font-size: calc(13px * var(--dyn)); }
.ll-option { border: 1.5px solid var(--line); border-radius: 18px; padding: 12px; margin: 12px 0; cursor: pointer; background: var(--well); }
.ll-option.on { border-color: var(--accent, #c62a2f); }
.ll-title { display: flex; justify-content: space-between; align-items: center; }
.ll-check { font-size: calc(12px * var(--dyn)); font-weight: 600; color: var(--ink-dim); }
.ll-option.on .ll-check { color: var(--accent, #c62a2f); }
.ll-card { position: relative; margin: 8px 0 10px; padding: 12px 14px; border-radius: 22px; background: #0b0b0c; color: #fff; font-size: calc(12px * var(--dyn)); display: grid; gap: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.25); }
.ll-head, .ll-row, .ll-next { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ll-logo { display: inline-flex; align-items: center; gap: 5px; font-size: calc(10px * var(--dyn)); font-weight: 700; }
.ll-logo em { font-style: italic; }
.ll-stops { text-align: left; } .ll-stops strong, .ll-count strong { font-size: calc(30px * var(--dyn)); line-height: 1; display: block; } .ll-card small { color: rgba(255,255,255,.62); font-size: calc(10px * var(--dyn)); display: block; }
.ll-rl { display: flex; align-items: center; gap: 6px; text-align: right; } .ll-rl strong { font-size: calc(12px * var(--dyn)); display: block; }
.ok { color: var(--good); } .bad { color: var(--warn); }
.ll-ringwrap { position: relative; display: inline-grid; place-items: center; flex: none; }
.ll-ring { position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(from 0deg, #e0322f, #c9790f, #2f9e4f calc(var(--p) * 1%), rgba(255,255,255,.18) 0); -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px)); mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px)); }
.ll-ringwrap b { position: relative; font-size: calc(11px * var(--dyn)); color: #fff; }
.ll-mark { width: 16px; height: 16px; border-radius: 50%; border: 3px solid; border-color: #e0322f #c9790f #2f9e4f #e0322f; display: inline-block; }
.ll-big { display: flex; gap: 10px; align-items: center; } .ll-txt strong { font-size: calc(15px * var(--dyn)); display: block; }
.ll-bars { flex: 1; display: grid; gap: 5px; } .ll-bars div { display: grid; grid-template-columns: 52px 1fr 30px; gap: 6px; align-items: center; font-size: calc(10px * var(--dyn)); } .ll-bars i { height: 5px; border-radius: 3px; background: rgba(255,255,255,.18); overflow: hidden; } .ll-bars b { display: block; height: 100%; } .ll-bars em { font-style: normal; text-align: right; font-weight: 700; }
.ll-count i { display: block; height: 5px; width: 90px; border-radius: 3px; background: rgba(255,255,255,.18); margin-top: 4px; overflow: hidden; } .ll-count i b { display: block; height: 100%; background: linear-gradient(90deg, #e0322f, #c9790f, #2f9e4f); }
.ll-list { flex: 1; display: grid; gap: 4px; font-size: calc(12px * var(--dyn)); } .ll-list div { display: flex; justify-content: space-between; align-items: center; }
.ll-next { border-top: 1px solid rgba(255,255,255,.12); padding-top: 8px; font-size: calc(13px * var(--dyn)); font-weight: 600; } .ll-next small { display: inline; }
.ll-dir { background: #c62a2f; color: #fff; padding: 4px 9px; border-radius: 999px; font-size: calc(10px * var(--dyn)); font-weight: 700; }
.ll-pin { text-decoration: none; display: inline-grid; place-items: center; width: 15px; height: 15px; margin-right: 4px; border-radius: 50%; background: #ff9f0a; color: #000; font-size: calc(9px * var(--dyn)); font-weight: 800; vertical-align: middle; font-style: normal; }
.ll-legend { margin: 6px 0 0; padding-left: 20px; font-size: calc(13px * var(--dyn)); display: grid; gap: 4px; } .ll-legend b { font-weight: 700; }
.ll-island { margin-top: 12px; padding: 12px; border-radius: 14px; background: var(--well); }

.ll-isl { margin: 12px 0 4px; } .ll-isl > b { display: block; } .ll-isl > .hint { display: block; margin-bottom: 6px; }
.ll-pill { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 250px; max-width: 100%; margin: 6px 0; padding: 8px 14px; border-radius: 999px; background: #000; color: #fff; font-size: calc(13px * var(--dyn)); font-weight: 700; }
.ll-cam { width: 70px; height: 14px; border-radius: 999px; background: #111; box-shadow: inset 0 0 0 1px #222; }
.ll-min { width: 64px; justify-content: center; }
.ll-exp { background: #000; color: #fff; border-radius: 30px; padding: 14px 16px; display: grid; gap: 8px; margin: 6px 0; max-width: 340px; }
.ll-erow { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 6px; }
.ll-erow strong { font-size: calc(30px * var(--dyn)); line-height: 1; display: block; } .ll-exp small { color: rgba(255,255,255,.62); font-size: calc(10px * var(--dyn)); display: block; }
.ll-ecen { text-align: center; font-size: calc(13px * var(--dyn)); font-weight: 600; } .ll-ering { display: grid; justify-items: end; gap: 2px; }
.ll-ebar i { display: block; height: 6px; border-radius: 3px; background: rgba(255,255,255,.18); overflow: hidden; } .ll-ebar i b { display: block; height: 100%; background: linear-gradient(90deg, #e0322f, #c9790f, #2f9e4f); } .ll-ebar small { text-align: center; margin-top: 3px; }
.ll-edir { text-align: center; color: #e0322f; font-size: calc(12px * var(--dyn)); font-weight: 700; }
.ll-exp .ll-ringwrap b { font-size: calc(10px * var(--dyn)); }

/* ---- Widgets gallery ---- */
.wg-wrap { padding: 4px 16px 28px; }
.wg-how { padding: 12px 14px; border-radius: 14px; background: var(--well); font-size: calc(13px * var(--dyn)); margin-bottom: 8px; } .wg-how ol { margin: 6px 0 0; padding-left: 18px; display: grid; gap: 5px; }
.wg-item { display: grid; grid-template-columns: 158px 1fr; gap: 12px; align-items: center; padding: 12px 0; border-top: 1px solid var(--line); }
.wg-meta strong { display: block; font-size: calc(14px * var(--dyn)); } .wg-meta small { display: block; color: var(--ink-dim); font-size: calc(11px * var(--dyn)); margin: 1px 0 4px; } .wg-meta p { margin: 0 0 4px; font-size: calc(12px * var(--dyn)); line-height: 1.35; } .wg-meta span { font-size: calc(11px * var(--dyn)); color: var(--accent-text, #c62a2f); font-weight: 600; }
.wg-c { background: #fff; color: #111; border-radius: 20px; box-shadow: 0 2px 10px rgba(0,0,0,.14), inset 0 0 0 1px rgba(0,0,0,.06); padding: 10px; display: flex; align-items: center; gap: 8px; overflow: hidden; }
:root[data-theme="dark"] .wg-c { background: #1c1c1e; color: #fff; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .wg-c { background: #1c1c1e; color: #fff; } }
.wg-s { width: 100px; height: 100px; justify-content: center; text-align: left; } .wg-m { width: 158px; height: 82px; }
.wg-c small { font-size: calc(7px * var(--dyn)); font-weight: 700; color: #8e8e93; display: block; letter-spacing: .04em; } .wg-c strong { font-size: calc(11px * var(--dyn)); display: block; line-height: 1.15; } .wg-c span, .wg-c p { font-size: calc(8px * var(--dyn)); color: #8e8e93; margin: 0; display: block; }
.wg-c .big { font-size: calc(30px * var(--dyn)); } .wg-c .ok { color: #2f9e4f; } .wg-c .bad { color: #e0322f; } .wg-c .amb { color: #c9790f; } .wg-c u { text-decoration: none; font-weight: 800; }
.wg-c em { font-style: normal; font-size: calc(7px * var(--dyn)); font-weight: 700; background: rgba(198,42,47,.14); color: #c62a2f; padding: 2px 5px; border-radius: 99px; display: inline-block; margin-top: 3px; } .wg-c .tip { font-size: calc(9px * var(--dyn)); font-weight: 600; }
.wg-l { display: grid; gap: 2px; min-width: 0; flex: 1; } .wg-sent { display: block; } .wg-sent p { font-size: calc(8px * var(--dyn)); color: #333; margin: 3px 0; } :root[data-theme="dark"] .wg-sent p { color: #ddd; }
.wg-btn { background: #c62a2f; color: #fff; border-radius: 10px; font-size: calc(8px * var(--dyn)); font-weight: 700; padding: 10px 6px; text-align: center; }
.wg-two { display: flex; gap: 4px; margin-top: 4px; } .wg-two span { flex: 1; text-align: center; padding: 6px 0; border-radius: 99px; background: rgba(128,128,128,.2); color: inherit; font-weight: 700; } .wg-two .g { background: #2f9e4f; color: #fff; }
.wg-ringwrap { position: relative; display: inline-grid; place-items: center; flex: none; } .wg-ringwrap b { position: relative; font-size: calc(9px * var(--dyn)); }
.wg-ring { position: absolute; inset: 0; border-radius: 50%; background: conic-gradient(from 0deg, #e0322f, #c9790f, #2f9e4f calc(var(--p) * 1%), rgba(128,128,128,.22) 0); -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px)); mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px)); }
.wg-bar { display: block; height: 4px; border-radius: 2px; background: rgba(128,128,128,.22); overflow: hidden; } .wg-bar b { display: block; height: 100%; background: linear-gradient(90deg, #e0322f, #c9790f, #2f9e4f); }
.wg-week { justify-content: space-around; align-items: flex-end; } .wg-week div { text-align: center; } .wg-week b { font-size: calc(8px * var(--dyn)); display: block; } .wg-week span { display: flex; align-items: flex-end; width: 14px; background: rgba(128,128,128,.22); border-radius: 3px; margin: 2px auto; } .wg-week i { display: block; width: 100%; background: #2f9e4f; border-radius: 3px; }
.wg-tiles { flex-wrap: wrap; align-content: center; gap: 4px; } .wg-tiles > div { width: 68px; background: rgba(128,128,128,.14); border-radius: 8px; padding: 4px 5px; } .wg-tiles b { font-size: calc(11px * var(--dyn)); display: block; }
.wg-cls { display: grid; gap: 4px; align-content: center; width: 100%; } .wg-cls > div { display: grid; grid-template-columns: 30px 1fr 22px; gap: 4px; align-items: center; font-size: calc(8px * var(--dyn)); } .wg-cls b { text-align: right; }
.wg-list { display: block; } .wg-list > div { display: flex; justify-content: space-between; align-items: center; margin-top: 4px; font-size: calc(8px * var(--dyn)); } .wg-list b { display: block; font-size: calc(9px * var(--dyn)); } .wg-list u { color: #e0322f; }
.wg-daily { align-items: flex-end; gap: 4px; position: relative; } .wg-daily i { flex: 1; background: #c9790f; border-radius: 3px 3px 0 0; } .wg-daily i:nth-child(even) { background: #2f9e4f; } .wg-daily hr { position: absolute; left: 8px; right: 8px; top: 38%; border: 0; border-top: 1px dashed #e0322f; margin: 0; }
.wg-three { position: relative; width: 62px; height: 62px; } .wg-three > span { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
.wg-qr i { width: 62px; height: 62px; background: repeating-conic-gradient(#111 0 25%, #fff 0 50%) 0 0 / 10px 10px; border: 4px solid #fff; outline: 1px solid #ddd; }

.wg-c .wg-ringwrap { display: inline-grid; place-items: center; } .wg-c .wg-ringwrap b { font-size: calc(9px * var(--dyn)); display: block; }
.wg-tiles { height: 92px; padding: 6px; } .wg-tiles > div { width: 70px; padding: 3px 5px; } .wg-tiles .wg-bar { display: none; } .wg-tiles b { font-size: calc(10px * var(--dyn)); }
.wg-c .wg-three { position: relative; width: 62px; height: 62px; display: block; } .wg-three > .wg-ringwrap { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }

/* The line to start the day (sign-in and lock screens): quotation marks, and a rounded, friendlier face. */
.lg-quote { font-family: ui-rounded, "SF Pro Rounded", "Nunito", "Avenir Next Rounded", system-ui, sans-serif; font-style: normal; font-weight: 600; font-size: calc(18px * var(--dyn)); line-height: 1.38; letter-spacing: .005em; }
.ll-rot { display: flex; align-items: center; gap: 6px; justify-content: space-between; } .ll-rot div { flex: 1; text-align: center; padding: 8px 4px; border-radius: 12px; background: rgba(255,255,255,.10); } .ll-rot div.on { background: rgba(198,42,47,.55); } .ll-rot b { display: block; font-size: calc(12px * var(--dyn)); } .ll-rot span { color: rgba(255,255,255,.5); }

/* ---- Logbook tracker sheet ---- */
#trBody { padding: 0 16px 28px; }
.tr-how { border-radius: 14px; background: var(--well); padding: 4px 14px; } .tr-how details { border-top: 1px solid var(--line); padding: 8px 0; } .tr-how details:first-child { border-top: 0; }
.tr-how summary { font-weight: 600; font-size: calc(14px * var(--dyn)); cursor: pointer; } .tr-how p { margin: 6px 0 2px; font-size: calc(13px * var(--dyn)); line-height: 1.4; color: var(--ink-dim); }
.tr-day { border-top: 1px solid var(--line); padding: 8px 0; } .tr-day summary { display: flex; justify-content: space-between; gap: 8px; cursor: pointer; font-size: calc(14px * var(--dyn)); } .tr-day summary span { color: var(--ink-dim); font-size: calc(13px * var(--dyn)); }
.tr-trip { padding: 8px 0 4px 8px; font-size: calc(13px * var(--dyn)); } .tr-tag { font-style: normal; font-size: calc(11px * var(--dyn)); font-weight: 700; padding: 2px 8px; border-radius: 999px; margin-left: 6px; background: rgba(128,128,128,.18); }
.tr-tag.business { background: rgba(47,158,79,.16); color: var(--good); } .tr-tag.private { background: rgba(99,99,102,.2); } .tr-tag.todo { background: rgba(201,121,15,.18); color: var(--caution); }
.km-stats { display: flex; flex-wrap: wrap; gap: 6px 14px; justify-content: center; margin-top: 8px; font-size: calc(12px * var(--dyn)); color: var(--ink-dim); } .km-stats b { color: var(--ink); }
.cn-add { display: grid; gap: 8px; margin-top: 10px; } .cn-add textarea { width: 100%; min-height: 64px; } .cn-when { display: flex; align-items: center; gap: 10px; } .cn-when input { flex: 1; min-height: 44px; }
.nt-when { display: flex; gap: 6px; flex-wrap: wrap; } .nt-when button { font: inherit; font-size: calc(12px * var(--dyn)); min-height: 32px; padding: 4px 12px; border-radius: 999px; border: 1px solid var(--line); background: var(--well); color: var(--ink); cursor: pointer; }
.nt-row { display: flex; gap: 12px; align-items: flex-start; padding: 10px 0; border-top: 1px solid var(--line); } .nt-check { flex: none; width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--ink-dim); background: none; cursor: pointer; padding: 0; } .nt-row.done .nt-check { background: #2f9e4f; border-color: #2f9e4f; } .nt-row.done .nt-text { text-decoration: line-through; color: var(--ink-dim); } .nt-meta { margin-top: 3px; font-size: calc(11px * var(--dyn)); } .nt-chip { padding: 2px 8px; border-radius: 999px; background: rgba(128,128,128,.16); font-weight: 600; } .nt-chip.due { background: rgba(198,42,47,.14); color: #c62a2f; }
.visit-action { margin-top: 3px; font-size: var(--fs-12, 12px); font-weight: 600; color: var(--ink); } .visit-remind { margin-top: 4px; font-size: calc(12px * var(--dyn)); padding: 4px 8px; border-radius: 8px; background: rgba(201,121,15,.14); color: var(--caution); display: inline-block; }
.goal-row { padding: 8px 0; } .goal-top { display: flex; justify-content: space-between; font-size: calc(13px * var(--dyn)); } .goal-top b { font-family: var(--font-mono); font-weight: 600; font-size: calc(12px * var(--dyn)); }

/* Bigger ring in the top bar, and the full logo on the loading screen. */
.logo-mark { height: 46px; width: 46px; }
header.top .logo-mark { height: 46px; width: 46px; }
.boot-loader-logo { width: min(300px, 72vw); height: auto; animation: bootMarkPulse 1.6s ease-in-out infinite; }
.boot-logo-dark { display: none; }
:root[data-theme="dark"] .boot-logo-light { display: none; } :root[data-theme="dark"] .boot-logo-dark { display: block; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .boot-logo-light { display: none; } :root:not([data-theme="light"]) .boot-logo-dark { display: block; } }
@media (prefers-reduced-motion: reduce) { .boot-loader-logo { animation: none; } }
.app-lock-card img { max-width: 78vw; height: auto; }
.visit-remind.plain { background: rgba(128,128,128,.14); color: var(--ink); }

/* Instrument style on a phone with a notch: the header sits below the status bar, and page content scrolls under a
   solid strip there, instead of the header sliding beneath the clock and battery. */
:root[data-style="instrument"] .app { padding-top: env(safe-area-inset-top, 0px); }
:root[data-style="instrument"] header.top { top: env(safe-area-inset-top, 0px); }
:root[data-style="instrument"] body:not(.login-route)::before { content: ""; position: fixed; top: 0; left: 0; right: 0; height: env(safe-area-inset-top, 0px); background: var(--bg); z-index: 6; pointer-events: none; }

/* Notes: dictation and the "Pick a time" chip that is the picker itself */
.nt-textwrap { position: relative; } .nt-textwrap textarea { width: 100%; padding-right: 52px; }
.nt-mic { position: absolute; right: 8px; top: 8px; width: 38px; height: 38px; border-radius: 50%; border: 0; background: var(--well); font-size: calc(18px * var(--dyn)); cursor: pointer; display: grid; place-items: center; }
.nt-mic.on { background: #e0322f; animation: micPulse 1.1s ease-in-out infinite; }
@keyframes micPulse { 50% { box-shadow: 0 0 0 8px rgba(224,50,47,.18); } }
@media (prefers-reduced-motion: reduce) { .nt-mic.on { animation: none; } }
.nt-pick { position: relative; display: inline-flex; align-items: center; min-height: 32px; padding: 4px 12px; border-radius: 999px; border: 1px solid var(--line); background: var(--well); font-size: calc(12px * var(--dyn)); overflow: hidden; cursor: pointer; }
.nt-pick.on { background: var(--accent, #c62a2f); border-color: transparent; color: #fff; }
.nt-pick input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; font-size: calc(16px * var(--dyn)); cursor: pointer; }
.nt-when button.on { background: var(--accent, #c62a2f); color: #fff; border-color: transparent; }
.mf-total { font-size: var(--fs-20, 20px); font-weight: 700; } .mf-total small { display: block; font-size: calc(12px * var(--dyn)); font-weight: 500; color: var(--ink-dim); margin-top: 2px; }
.mf-win { display: inline-block; margin: 8px 0; padding: 4px 10px; border-radius: 999px; background: rgba(47,158,79,.16); color: var(--good); font-size: calc(12px * var(--dyn)); font-weight: 700; }

/* Page titles: the phone header's title sat at the body size (15px, weight 500), so "Team" or "Today" read as a caption
   under the logo rather than the heading of the page. Glass only — Instrument keeps its own compact instrument-panel
   header. Same single line as before for the short titles; a long greeting still clamps at two lines (rule above). */
:root[data-style="glass"] header.top h1 { font-size: calc(21px * var(--dyn)); font-weight: 650; letter-spacing: -.018em; line-height: 1.15; }
:root[data-style="glass"] header.top .sub { font-size: var(--fs-12); }

/* Quieter nested tiles (Glass). A tile inside a card used to be a second outlined box — a hairline border on a pane that
   already has its own, which read as boxes within boxes. Now they are plain rounded fills with no outline, the way
   iOS groups settings, and the stat grid is a real two-column grid (the "wide" tile spans both) instead of a hairline
   table that left one orphan tile. Numbers use tabular figures so columns of money line up. Every selector carries
   body and :not() weight on purpose, to settle ties with the older layered Glass rules above. */
:root[data-style="glass"] body :not(.area-block) > .stat-row {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px;
  background: none; border: 0; box-shadow: none; border-radius: 0; overflow: visible; margin-top: 16px;
}
:root[data-style="glass"] body .card .stat-row .stat.wide { grid-column: 1 / -1; }
:root[data-style="glass"] body .card :is(.stat-row .stat, .margin-class-tile, .margin-area, .area-block) {
  background: rgb(120 120 128 / .09); border: 0; border-radius: 14px; box-shadow: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
:root[data-style="glass"] body .stat-row .stat { padding: 12px 14px; min-width: 0; }
:root[data-style="glass"] body :is(.stat .v, .pace-hero, .margin-class-tile, .tm-big, .tm-amt) { font-variant-numeric: tabular-nums; }
:root[data-style="glass"] body .stat-row .stat .v { font-weight: 650; letter-spacing: -.01em; }

/* The low-margin and margin-fixer lists put their <h2> inside their own title row, so the card's legend styling never
   reached it and it fell back to the browser's default heading size — twice as big as every other card title. */
.low-margin-list-title-row h2 { font-family: var(--font-ui); font-size: var(--fs-15); font-weight: 600; margin: 0; }
.card > .low-margin-list-title-row:first-child { margin-top: 0; }
/* The "on track to land" callout and the stat tiles inside a Foodservice/Produce block, in the same quiet fill. */
:root[data-style="glass"] body .card .landing { background: rgb(120 120 128 / .09); border: 0; border-radius: 14px; padding: 12px 14px; }
:root[data-style="glass"] body .card .area-block .stat { background: rgb(var(--lg-tint) / .6); }

/* Performance hero, margin strip, progress ring, pills — shared by the Team page and a single rep's page. */
.tm-hero { padding: var(--space-4); }
.tm-eyebrow { display: flex; justify-content: space-between; gap: 8px; font-size: var(--fs-12); color: var(--ink-dim); }
.tm-hero-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 8px; }
.tm-big { font-size: calc(36px * var(--dyn)); font-weight: 700; letter-spacing: -.025em; line-height: 1.05; font-variant-numeric: tabular-nums; }
.tm-big small { display: block; font-size: var(--fs-13); font-weight: 500; letter-spacing: 0; color: var(--ink-dim); margin-top: 6px; }
.tm-pace { font-size: var(--fs-13); font-weight: 600; margin-top: 10px; }
.tm-pace.good { color: var(--good); } .tm-pace.warn { color: var(--warn); } .tm-pace.caution { color: var(--caution); }
.tm-pace span { color: var(--ink-dim); font-weight: 500; }
.tm-note { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding: 9px 12px; border-radius: var(--radius-md); background: var(--caution-soft); color: var(--caution); font-size: var(--fs-12); font-weight: 600; text-decoration: none; }
.tm-note::after { content: "\203A"; margin-left: auto; font-size: calc(18px * var(--dyn)); line-height: 1; }
.tm-kpis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 16px; }
.tm-kpi { background: var(--well); border: 1px solid var(--line); border-radius: var(--radius-md); padding: 10px 12px; min-width: 0; }
.tm-kpi .k { font-size: var(--fs-11); color: var(--ink-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tm-kpi .v { display: flex; align-items: center; gap: 6px; font-size: calc(20px * var(--dyn)); font-weight: 650; margin-top: 3px; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.tm-kpi .v i, .tm-pill i, .tm-sub i { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; }
.tm-kpi .t { font-size: var(--fs-11); color: var(--ink-dim); margin-top: 1px; }
.tm-av { grid-row: 1 / span 2; position: relative; width: 44px; height: 44px; display: grid; place-items: center; font-size: var(--fs-13); font-weight: 650; border-radius: 50%; }
.tm-av.plain { background: var(--bg); border: 1px solid var(--line); color: var(--ink-dim); }
.tm-av svg { position: absolute; inset: 0; }
.tm-ring-bg { fill: none; stroke: var(--line); opacity: .45; }
.tm-ring-fg { fill: none; stroke-linecap: round; }
.tm-av.good .tm-ring-fg { stroke: var(--good); } .tm-av.caution .tm-ring-fg { stroke: var(--caution); } .tm-av.warn .tm-ring-fg { stroke: var(--warn); }
.tm-pills { grid-column: 2 / 4; display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.tm-pill { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-12); padding: 3px 10px 3px 8px; border-radius: var(--radius-pill); background: var(--well); border: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.tm-pill span { color: var(--ink-dim); }
.tm-dot-good { background: var(--good); } .tm-dot-caution { background: var(--caution); } .tm-dot-warn { background: var(--warn); } .tm-dot-none { background: var(--line); }
/* Margin strip tiles in the same quiet fill as every other tile inside a card. */
:root[data-style="glass"] body .card .tm-kpi { background: rgb(120 120 128 / .09); border: 0; border-radius: 14px; }

/* ── Bottom bar, redesigned ─────────────────────────────────────────────────────────────────────────────────────
   Every destination now has an icon above its label, the way every iOS tab bar does; it was text only, with the active
   glass lens clipped against the left edge of a full-width bar and a tall empty strip under the labels for the home
   indicator. In Glass the bar now floats: inset from both sides, fully rounded, a little above the bottom edge, with
   room around the lens so it never touches the bar's edge. Instrument keeps its flat docked bar, with the same icons.
   The wide-desktop layout (a flat row under the header) keeps text only. */
@media not all and (min-width: 1100px) and (hover: hover) and (pointer: fine) {
  .tabbar-item { flex-direction: column; gap: 3px; padding: 7px 2px 6px; min-height: 56px; text-transform: none; letter-spacing: 0; border-top-color: transparent; }
  .tabbar-item.active { border-top-color: transparent; }
  .tab-ico { display: block; width: 24px; height: 24px; }
  .tab-ico svg { display: block; width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
  .tab-label { font-size: calc(10.5px * min(var(--dyn, 1), 1.2)); font-weight: 600; line-height: 1.1; letter-spacing: 0; }
  .tabbar-badge { top: 3px; right: calc(50% - 22px); }
  :root[data-style="glass"] body .tabbar {
    left: 12px; right: 12px; bottom: calc(8px + env(safe-area-inset-bottom)); padding: 6px 10px;
    border-width: 1px; border-radius: 28px;
  }
  :root[data-style="glass"] body .tabbar-item { border-radius: 22px; }
  :root[data-style="glass"] body #viewMount { padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
}
@media (min-width: 1100px) and (hover: hover) and (pointer: fine) { .tab-ico { display: none; } .tab-label { font: inherit; } }

/* ── Header, tightened ─────────────────────────────────────────────────────────────────────────────────────────
   The header was about a quarter of the first screen on a phone: a 46px logo row, then the title, then the subtitle, with
   five buttons in the row. Appearance (light/dark/sunset) and Style (Glass/Instrument) are set-once choices, and both are
   already in Settings, so they come out of the header. The mark is smaller and the rows sit closer together; search,
   alerts and the weather chip stay. */
header.top #themeBtn, header.top .style-menu { display: none; }
@media (max-width: 559px), (pointer: coarse) and (max-height: 520px) {
  header.top .logo-mark { height: 34px; width: 34px; }
  header.top .header-actions { min-height: 38px; }
  header.top h1 { margin-top: 2px; }
  :root[data-style="glass"] header.top { padding: 10px 14px 12px; }
}
/* Under the floating tab bar: whatever scrolls past the strip beneath it fades out, so the gap never shows cut-off text. */
@media not all and (min-width: 1100px) and (hover: hover) and (pointer: fine) {
  :root[data-style="glass"] #navMount::before {
    content: ""; position: fixed; left: 0; right: 0; bottom: 0; height: calc(env(safe-area-inset-bottom) + 30px); z-index: 5; pointer-events: none;
    background: linear-gradient(to top, var(--glass-solid, var(--bg)) 20%, transparent);
    -webkit-mask-image: linear-gradient(to top, #000 45%, transparent); mask-image: linear-gradient(to top, #000 45%, transparent);
  }
}

/* Actions on a rep's page: call, message, email, leave a note. */
.tm-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.tm-act { flex: 1 1 0; min-width: 64px; display: flex; align-items: center; justify-content: center; white-space: nowrap; padding: 10px 12px; border-radius: var(--radius-pill); border: 1px solid var(--line); background: var(--well); color: var(--ink); font-size: var(--fs-13); font-weight: 600; text-decoration: none; }
.tm-act:active { opacity: .6; }
a.tm-note { display: flex; margin-bottom: var(--space-3); }


/* Customer page: the at-a-glance strip leads, optional visit fields fold away */
.cust-glance:empty { display: none; }
.cust-glance { margin-bottom: var(--space-3); }
.more-opts { margin-top: 14px; }
.more-opts > summary { text-transform: none; letter-spacing: 0; font-size: var(--fs-13); font-weight: 600; color: var(--ink); }
.more-opts > summary .hint { font-weight: 400; margin-left: 6px; flex: 1; text-align: left; }
.more-opts[open] > summary .hint { display: none; }
.entry-actions { display: flex; gap: 8px; margin-top: 8px; }
.entry-actions .ghost { padding: 6px 12px; font-size: var(--fs-12); min-height: 40px; }
.tpl-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.tpl-chip { padding: 6px 12px; font-size: var(--fs-12); min-height: 36px; border-radius: var(--radius-pill); }
.tpl-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.tpl-chip { padding: 6px 12px; font-size: var(--fs-12); min-height: 36px; border-radius: var(--radius-pill); }

/* Settings → Sync & offline */
.sync-state { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--radius-sm); background: var(--well); }
.sync-state b, .sync-row b { display: block; font-size: var(--fs-15); }
.sync-state small, .sync-row small { display: block; color: var(--ink-dim); font-size: var(--fs-12); margin-top: 2px; }
.sync-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; background: var(--good); }
.sync-state.off .sync-dot { background: var(--warn); }
.sync-row { padding: 10px 2px; border-bottom: 1px solid var(--line); }

/* First-run tour */
.tour-card { padding: 4px 4px 8px; }
.tour-h { font-size: var(--fs-20); margin: 4px 0 8px; letter-spacing: -.01em; }
.tour-card p { color: var(--ink-dim); line-height: 1.5; margin: 0 0 14px; }
.tour-dots { display: flex; gap: 6px; justify-content: center; margin-bottom: 14px; }
.tour-dots span { width: 7px; height: 7px; border-radius: 50%; background: var(--line); }
.tour-dots span.on { background: var(--accent); width: 20px; border-radius: 4px; }
.tour-btns { display: flex; gap: 10px; }
.tour-btns .primary { flex: 1; }
.sheet-pad, .tour-card, #syncSheetBody { padding-left: 18px; padding-right: 18px; padding-bottom: 14px; box-sizing: border-box; }
.sheet-pad textarea { box-sizing: border-box; }

/* Glass bars: Clear. A lighter tint and a stronger blur, so the page shows through the way it does in iOS's Clear
   Liquid Glass. Only where iOS has not asked for less transparency. */
@media (prefers-reduced-transparency: no-preference) and (prefers-contrast: no-preference) {
  :root[data-style="glass"][data-glass="clear"] :is(header.top, .tabbar),
  :root[data-style="glass"][data-glass="clear"][data-scrolled] :is(header.top, .tabbar) {
    --lg-tint-a: .26; --lg-tint-scrolled: .34; --lg-sheen: rgb(255 255 255 / .2);
    -webkit-backdrop-filter: blur(26px) saturate(210%); backdrop-filter: blur(26px) saturate(210%);
  }
  :root[data-style="glass"][data-glass="clear"] .tabbar .tab-label { font-weight: 600; }
  :root[data-style="glass"][data-glass="clear"] .tabbar .tabbar-item:not(.active) { color: var(--ink); }
}

/* In the iPhone app the native Liquid Glass bar (native.js → SFSViewController.swift) replaces this one. The web bar
   stays in the page (swipe gestures read it) but is not shown. */
html.native-tabbar .tabbar, html.native-tabbar #navMount::before { display: none !important; }

/* ── The compact top bar (shared.js "The compact top bar") ───────────────────────────────────────────────
   Phones and iPads in Glass: the big header scrolls away with the page; this slim bar floats in once it has gone. */
.mini-bar { display: none; }
@media (max-width: 767px), (pointer: coarse) {
  :root[data-style="glass"] header.top { position: relative; top: auto; }
  :root[data-style="glass"] .mini-bar {
    position: fixed; z-index: 20; left: 12px; right: 12px; top: calc(env(safe-area-inset-top) + 6px);
    max-width: 536px; margin: 0 auto; height: 46px; padding: 0 6px 0 16px;
    display: flex; align-items: center; justify-content: space-between; gap: 8px; border-radius: 23px;
    background: linear-gradient(160deg, var(--lg-sheen) 0%, transparent 42%), rgb(var(--lg-tint) / .9);
    border: 1px solid var(--lg-hair); box-shadow: var(--lg-drop), inset 0 1px 0 var(--lg-hi);
    -webkit-backdrop-filter: blur(22px) saturate(190%); backdrop-filter: blur(22px) saturate(190%);
    opacity: 0; transform: translateY(-14px) scale(.98); pointer-events: none;
    transition: opacity .22s var(--ease-glass, ease), transform .26s var(--ease-glass, ease);
  }
  :root[data-style="glass"] .mini-bar.on { opacity: 1; transform: none; pointer-events: auto; }
  .mini-title { flex: 1; min-width: 0; text-align: left; background: none; border: 0; padding: 0; color: var(--ink); font: inherit; font-size: var(--fs-15); font-weight: 700; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; min-height: 0; box-shadow: none; }
  .mini-actions { display: flex; align-items: center; gap: 6px; flex: none; }
  .mini-bar .style-menu-btn { width: 36px; height: 36px; }
  .mini-bar .iconbtn.bell { min-height: 36px; padding: 0 14px; position: relative; }
  body.login-route .mini-bar { display: none !important; }
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  :root[data-style="glass"] .mini-bar { background: var(--glass-solid); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
@media (prefers-reduced-motion: reduce) { :root[data-style="glass"] .mini-bar { transition: opacity .01s; transform: none; } }

/* ── A calmer header on a phone ──────────────────────────────────────────────────────────────────────────────
   The weather chip is the glyph and the temperature (no rain-truck, no wording), Alerts is a bell, and the line
   under the title stays on one line instead of wrapping into a paragraph. */
.bell .bell-ico { display: none; }
@media (max-width: 767px), (pointer: coarse) {
  header.top .bell, .mini-bar .bell { width: 36px; min-width: 36px; height: 36px; min-height: 36px; padding: 0; display: inline-flex; align-items: center; justify-content: center; position: relative; }
  header.top .bell .bell-label { display: none; }
  .bell .bell-ico { display: block; flex: none; width: 18px; height: 18px; }
  .bell .bell-dot { position: absolute; top: 5px; right: 6px; }
  header.top .wx-rain, header.top .wx-desc, header.top .wx-range { display: none; }
  header.top .sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

/* The compact top bar needs an edge of its own: on a light page, white glass over white content read as no bar at all. */
@media (max-width: 767px), (pointer: coarse) {
  :root[data-style="glass"] .mini-bar {
    background: linear-gradient(160deg, var(--lg-sheen) 0%, transparent 42%), rgb(var(--lg-tint) / .97);
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    box-shadow: 0 12px 30px -10px rgba(0, 0, 0, .34), 0 3px 8px rgba(0, 0, 0, .10), inset 0 1px 0 var(--lg-hi);
  }
  .mini-title { color: var(--ink); opacity: 1; }
  .mini-bar .style-menu-btn, .mini-bar .iconbtn.bell { background: var(--well); border: 1px solid var(--line); color: var(--ink); box-shadow: none; }
}
:root.dark[data-style="glass"] .mini-bar, :root[data-theme="dark"][data-style="glass"] .mini-bar { border-color: rgba(255, 255, 255, .20); }

/* ── Search (the command palette) on a touch screen ────────────────────────────────────────────────────────
   The box was 15px, and iOS zooms the whole page in on any field under 16px and then never zooms back out — that was
   the "zoomed" page. It is 17px now, the list is finger-sized, and the first row is not painted red as if already chosen
   (that is for a keyboard's arrow keys; a thumb picks its own). */
@media (pointer: coarse), (max-width: 767px) {
  .cmdk-overlay { padding: calc(env(safe-area-inset-top) + 14px) 12px 0; align-items: flex-start; }
  .cmdk { max-width: 540px; border-radius: 22px; max-height: min(70vh, 560px); box-shadow: 0 24px 60px -16px rgba(0, 0, 0, .45); }
  .cmdk input { font-size: 17px; padding: 16px 18px; min-height: 54px; border-radius: 0; }
  .cmdk-item { padding: 14px 18px; min-height: 48px; font-size: var(--fs-15); }
  .cmdk-item.active { background: transparent; color: var(--ink); }
  .cmdk-item:active { background: var(--accent-soft); }
  .cmdk-group { padding: 10px 18px 4px; font-size: var(--fs-12); font-weight: 600; text-transform: none; letter-spacing: 0; }
  .cmdk-hint { display: none; }
  .cmdk-list .cmdk-hint { display: block; padding: 14px 18px; font-size: var(--fs-13); }
}

/* ═══ iOS settings are in charge ══════════════════════════════════════════════════════════════════════════════
   Everything below follows what the person chose in iOS Settings → Accessibility (and Display & Brightness), because
   the app reads them through the web view. Text size (Dynamic Type) and dark mode are handled in their own places;
   these three are the catch-alls, so a screen added later is covered without anyone remembering.            */

/* Reduce Motion: nothing slides, springs, fades slowly or loops. Changes of state still happen, instantly. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; transition-delay: 0s !important; scroll-behavior: auto !important;
  }
}

/* Reduce Transparency: glass becomes solid. The tints that make surfaces see-through go to fully opaque, and no
   surface blurs what is behind it. */
@media (prefers-reduced-transparency: reduce) {
  :root { --lg-tint-a: 1; --lg-tint-scrolled: 1; --lg-card-a: 1; }
  *, *::before, *::after { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
  .cmdk-overlay, .sheet-overlay { background: rgba(20, 20, 20, .62); }
  .mini-bar, .tabbar, header.top, .sheet, .cmdk { background: var(--glass-solid) !important; }
}

/* Increase Contrast: dimmed text and hairlines get firmer. */
@media (prefers-contrast: more) {
  :root { --ink-dim: color-mix(in srgb, var(--ink) 82%, var(--bg)); --line: color-mix(in srgb, var(--ink) 38%, transparent); }
  .card, .cal-day, .cal-cell, .msg-bub, .cal-stop { border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent); }
  .cal-chip, .area-pill, .msg-badge { outline: 1px solid currentColor; }
}

/* ── Phone fit (from the full-app audit) ───────────────────────────────────────────────────────────────────
   Wide tables (Reps, Targets, Calendars...) used to stick out past their card and get cut off by the screen edge. On a
   phone a table now scrolls sideways inside its own box instead. Small fields and chips are raised to a finger's size. */
@media (max-width: 767px), (pointer: coarse) {
  #viewMount table { display: block; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  #viewMount table th:first-child, #viewMount table td:first-child { position: sticky; left: 0; background: var(--bg-raised, var(--bg)); z-index: 1; }
  .trend-chip { min-height: 38px; }
  #viewMount input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"], [type="hidden"]), #viewMount select { min-height: 40px; }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════════════════
   THE iOS CONTRACT  (Apple Human Interface Guidelines, applied app-wide; the rules are written up in IOS-DESIGN.md)
   This is the LAST layer on purpose: it decides type sizes, control sizes and control looks for every screen, so
   a screen added later inherits them without anyone remembering.

   Type  — Apple's text styles at the default size: Large Title 34/41, Title 1 28/34, Title 2 22/28, Title 3 20/25,
           Headline 17/22 semibold, Body 17/22, Callout 16/21, Subhead 15/20, Footnote 13/18, Caption 1 12/16,
           Caption 2 11/13 (the smallest text allowed). Everything is multiplied by --dyn (the iPhone's text size).
   Hit regions are at least 44x44. A switch is only used in a list row. iPhone has no checkboxes: a lone on/off
   choice is a switch, a chip choice shows its state with a fill and a tick. Buttons that sit together are the
   same size, and the main one is told apart by style (filled), not by size.                                  */
@media (pointer: coarse), (max-width: 767px) {
  :root:root {
    --fs-10: calc(11px * var(--dyn));   /* Caption 2 — never smaller */
    --fs-11: calc(12px * var(--dyn));   /* Caption 1 */
    --fs-12: calc(13px * var(--dyn));   /* Footnote */
    --fs-13: calc(15px * var(--dyn));   /* Subhead */
    --fs-15: calc(17px * var(--dyn));   /* Body / Headline */
    --fs-20: calc(22px * var(--dyn));   /* Title 2 */
    --fs-28: calc(28px * var(--dyn));   /* Title 1 */
    --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px; --sp-6: 24px;
    --ios-green: #34c759; --ios-hit: 44px;
  }
  body { line-height: 1.29; }

  /* Buttons */
  button.primary, button.secondary, button.ghost, button.danger { min-height: var(--ios-hit); font-size: var(--fs-15); font-weight: 600; letter-spacing: -.01em; border-radius: 999px; padding: 0 22px; }
  button.primary { border: 0; }
  button.secondary { background: var(--accent-soft); color: var(--accent-text); border: 0; }
  button.ghost { background: transparent; color: var(--accent-text); border: 0; }
  button.danger { background: color-mix(in srgb, var(--warn) 12%, transparent); color: var(--warn); border: 0; }
  button.secondary:active, button.ghost:active, button.danger:active { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent-text); }
  button.primary[style*="width:100%"], button.secondary[style*="width:100%"] { min-height: 50px; }

  /* Fields: 44 high, filled, 17pt (anything smaller makes iOS zoom the page) */
  input[type=text], input[type=email], input[type=password], input[type=tel], input[type=number], input[type=date], input[type=month], input[type=search], input[type=time], textarea, select {
    min-height: var(--ios-hit); font-size: 17px; border-radius: 12px; padding: 11px 14px; border: 1px solid transparent; background: var(--well);
  }
  textarea { min-height: 96px; }
  label { font-size: var(--fs-12); font-weight: 500; color: var(--ink-dim); text-transform: none; letter-spacing: 0; }

  /* Switch: 51x31, green, white thumb — Apple's own proportions */
  .switch { width: 51px; height: 31px; }
  .slider { background: color-mix(in srgb, var(--ink) 18%, transparent); border: 0; border-radius: 31px; transition: background .25s ease; }
  .slider::before { width: 27px; height: 27px; left: 2px; top: 2px; border-radius: 50%; background: #fff; box-shadow: 0 2px 5px rgba(0, 0, 0, .28); transition: transform .25s cubic-bezier(.3, 1.4, .5, 1); }
  input:checked + .slider { background: var(--ios-green); border: 0; }
  input:checked + .slider::before { transform: translateX(20px); background: #fff; }

  /* Any bare checkbox becomes the same switch (a checkbox is not an iPhone control) */
  input[type="checkbox"]:not(.chip-check input, .switch input) {
    -webkit-appearance: none; appearance: none; flex: none; width: 51px; height: 31px; min-height: 0; margin: 0; padding: 0; border: 0; border-radius: 31px; cursor: pointer; vertical-align: middle;
    background: radial-gradient(circle at 50% 50%, #fff 13px, transparent 13.5px) 0 0 / 31px 31px no-repeat, color-mix(in srgb, var(--ink) 18%, transparent);
    transition: background-position .25s cubic-bezier(.3, 1.4, .5, 1), background-color .25s ease;
  }
  input[type="checkbox"]:not(.chip-check input, .switch input):checked {
    background: radial-gradient(circle at 50% 50%, #fff 13px, transparent 13.5px) 20px 0 / 31px 31px no-repeat, var(--ios-green);
  }

  /* Choice chips (merchandising, "send directly"…): no tick-box; the chip fills and shows a tick when chosen */
  .chip-check { display: inline-flex; align-items: center; gap: 6px; min-height: var(--ios-hit); padding: 0 16px; border-radius: 999px; background: var(--well); border: 1px solid var(--line); font-size: var(--fs-13); font-weight: 500; color: var(--ink); cursor: pointer; }
  .chip-check input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }
  .chip-check:has(input:checked) { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-text); font-weight: 600; }
  .chip-check:has(input:checked)::before { content: "\2713"; font-weight: 700; }
  .chip-check:active { opacity: .7; }

  /* Segmented controls: tall enough to touch, capped at the width of the screen */
  .seg > button, .seg > a { min-height: 40px; font-size: var(--fs-13); font-weight: 500; }

  /* Sheets: a centred 17pt semibold title with a Done button on the trailing edge (Cancel/Back go on the leading edge) */
  .sheet-title { text-align: center; font-size: var(--fs-15); font-weight: 600; text-transform: none; letter-spacing: 0; position: relative; }
  .sheet-done { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); min-height: var(--ios-hit); padding: 0 12px; background: none; border: 0; color: var(--accent-text); font-size: var(--fs-15); font-weight: 600; }

  /* Lists and rows: 44pt minimum, a chevron on rows that go somewhere */
  .set-row, .acct-row, .agenda-row, .hub-row, .cmdk-item, .msg-row { min-height: var(--ios-hit); }
}
@media (pointer: coarse), (max-width: 767px) {
  .tpl-chip { background: var(--accent-soft) !important; color: var(--accent-text) !important; min-height: 40px; }
}

/* ── Edit tab bar: the Home Screen jiggle ──────────────────────────────────────────────────────────────────── */
.tj-overlay { position: fixed; inset: 0; z-index: 60; background: rgba(0, 0, 0, 0); display: flex; align-items: flex-end; justify-content: center; transition: background .22s ease; }
.tj-overlay.on { background: rgba(0, 0, 0, .42); }
.tj-panel {
  width: 100%; max-width: 560px; max-height: 86vh; overflow-y: auto; padding: 14px 16px calc(18px + env(safe-area-inset-bottom));
  background: var(--bg-raised, #fff); border-radius: 28px 28px 0 0; box-shadow: 0 -20px 50px -16px rgba(0, 0, 0, .4);
  transform: translateY(24px); opacity: 0; transition: transform .3s cubic-bezier(.2, .9, .3, 1), opacity .2s ease;
}
.tj-overlay.on .tj-panel { transform: none; opacity: 1; }
.tj-head { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; margin-bottom: 4px; }
.tj-head b { font-size: var(--fs-15); font-weight: 600; text-align: center; }
.tj-done, .tj-reset { background: none; border: 0; min-height: 44px; padding: 0 6px; font-size: var(--fs-15); color: var(--accent-text); font-weight: 600; }
.tj-reset { justify-self: start; font-weight: 500; color: var(--ink-dim); }
.tj-done { justify-self: end; }
.tj-help { margin: 0 4px 14px; text-align: center; color: var(--ink-dim); font-size: var(--fs-12); line-height: 1.35; }
.tj-bar { display: flex; gap: 8px; padding: 14px 8px; border-radius: 26px; background: var(--well); touch-action: none; }
.tj-tile { position: relative; flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 6px 2px 4px; border-radius: 16px; animation: tj-jiggle .26s ease-in-out infinite alternate; user-select: none; -webkit-user-select: none; cursor: grab; }
.tj-tile:nth-child(even) { animation-direction: alternate-reverse; }
.tj-tile.sel { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.tj-tile.dragging { animation: none; z-index: 3; cursor: grabbing; box-shadow: 0 10px 24px rgba(0, 0, 0, .25); background: var(--bg-raised); transition: none; }
.tj-tile.tj-more { animation: none; opacity: .55; pointer-events: none; }
.tj-bar.still .tj-tile { animation: none; }
@keyframes tj-jiggle { from { transform: rotate(-2.2deg); } to { transform: rotate(2.2deg); } }
.tj-ico { width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; color: #fff; }
.tj-ico .set-ico { background: none !important; box-shadow: none !important; width: auto; height: auto; color: #fff; }
.tj-ico svg { width: 24px; height: 24px; }
.tj-name { font-size: var(--fs-11); font-weight: 500; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.tj-minus { position: absolute; top: -4px; left: 2px; z-index: 2; width: 24px; height: 24px; min-height: 0; padding: 0; border-radius: 50%; border: 0; background: #8e8e93; color: #fff; font-size: 18px; line-height: 1; font-weight: 700; display: grid; place-items: center; box-shadow: 0 1px 4px rgba(0, 0, 0, .3); }
.tj-minus::before { content: ""; position: absolute; inset: -10px; }
.tj-sec { margin: 18px 6px 8px; font-size: var(--fs-12); font-weight: 600; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .05em; }
.tj-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px 8px; }
.tj-add { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 8px 2px; background: none; border: 0; min-height: 44px; color: inherit; border-radius: 14px; }
.tj-add:active { background: var(--accent-soft); }
.tj-plus { position: absolute; top: 0; left: 6px; z-index: 2; width: 22px; height: 22px; border-radius: 50%; background: var(--ios-green, #34c759); color: #fff; font-size: 17px; font-weight: 700; display: grid; place-items: center; line-height: 1; }
.tj-empty { grid-column: 1 / -1; text-align: center; color: var(--ink-dim); padding: 14px; font-size: var(--fs-13); }
@media (prefers-reduced-motion: reduce) { .tj-overlay, .tj-panel { transition: none; } .tj-tile { animation: none; } }

/* ---- Sheets: the grabber and title stay put while the list scrolls under them ----------------------------------
   The sheet itself scrolls, so a long one (Settings) used to carry its title and Done button out of sight and clip the
   first row against the rounded corner. Both are pinned now, on a near-solid surface so rows never show through
   them, and the sheet body is more opaque so the page behind no longer bleeds into the text. */
.sheet { padding-top: 0; }
.sheet-handle {
  position: sticky; top: 0; z-index: 4; box-sizing: border-box; width: 100% !important; height: 25px !important;
  margin: 0 !important; background: var(--bg-raised) !important; border-radius: 0 !important;
}
.sheet-handle::before { inset: 0; }
.sheet-handle::after {
  content: ""; position: absolute; left: 50%; top: 10px; width: 36px; height: 5px; margin-left: -18px;
  border-radius: 3px; background: color-mix(in srgb, var(--ink) 30%, transparent);
}
.sheet-title { position: sticky; top: 25px; z-index: 3; background: var(--bg-raised); margin-bottom: 0; }
:root[data-style="glass"] .sheet-handle, :root[data-style="glass"] .sheet-title { background: rgb(var(--lg-tint) / 1) !important; }
:root[data-style="glass"] .sheet-handle { border-radius: var(--r-sheet, 22px) var(--r-sheet, 22px) 0 0 !important; }
:root[data-style="glass"] :is(.sheet) {
  background:
    radial-gradient(130% 70% at 0% 0%, var(--lg-spec) 0%, transparent 50%),
    rgb(var(--lg-tint) / .9);
}

/* Settings: three or more choices go on their own full-width line under the label (iOS's own pattern) instead of
   running off the edge; the group surface is solid enough that the page behind does not show through the rows. */
.set-seg-row.set-seg-wide { flex-wrap: wrap; row-gap: 8px; padding-top: 10px; padding-bottom: 12px; }
.set-seg-row.set-seg-wide .set-label { flex: 1 1 0; min-width: 0; }
.set-seg-row.set-seg-wide .set-seg { flex: 1 0 100%; display: flex; width: 100%; box-sizing: border-box; }
.set-seg-row.set-seg-wide .set-seg > button { flex: 1 1 0; min-width: 0; padding: 0 4px; justify-content: center; }
:root[data-style="glass"] .set-group { background: rgb(var(--lg-tint) / .92); }
.tj-bar.dragging-any .tj-tile { animation: none; }

/* ---- Row swipe actions (quick.js): the colour and label that appear behind a row as it slides ---- */
html.swa-dragging { overflow-x: hidden; }
.swa-reveal { position: absolute; top: 0; bottom: 0; display: flex; align-items: center; overflow: hidden; color: #fff; font-weight: 600; font-size: var(--fs-13); pointer-events: none; opacity: .92; }
.swa-reveal[data-dir="right"] { right: 100%; justify-content: flex-end; padding-right: 14px; border-radius: 12px 0 0 12px; }
.swa-reveal[data-dir="left"] { left: 100%; justify-content: flex-start; padding-left: 14px; border-radius: 0 12px 12px 0; }
.swa-reveal span { white-space: nowrap; }
.swa-reveal.armed { opacity: 1; filter: saturate(1.2); }
#quickMenu .sheet-item { min-height: 50px; font-size: var(--fs-17, 17px); }
#quickMenu .sheet-item:focus { outline: none; }
