/* VIKI — design system (inherited from C-O-G-S)
   Palette is the validated dataviz reference instance; chart surfaces match it exactly. */

:root {
  color-scheme: light;
  --plane:        #f9f9f7;
  --surface:      #fcfcfb;
  --surface-2:    #f2f2ef;
  --surface-3:    #e9e9e4;
  --ink:          #0b0b0b;
  --ink-2:        #52514e;
  --ink-3:        #898781;
  --line:         #e1e0d9;
  --line-2:       #c3c2b7;
  --ring:         rgba(11, 11, 11, .10);

  --s1: #2a78d6; --s2: #eb6834; --s3: #1baf7a; --s4: #eda100;
  --s5: #e87ba4; --s6: #008300; --s7: #4a3aa7; --s8: #e34948;

  --good: #0ca30c; --warning: #fab219; --serious: #ec835a; --critical: #d03b3b;
  --good-ink: #006300;

  /* the -l / -d slots are what an organization's brand colour lands in, after
     it has been checked for contrast; the literals are the default theme */
  --accent: var(--accent-l, #2a78d6);
  --accent-ink: var(--accent-ink-l, #fff);
  --accent-soft: var(--accent-soft-l, #eaf2fd);

  --r: 10px; --r-sm: 7px; --r-lg: 14px;
  --sh-1: 0 1px 2px rgba(11,11,11,.05), 0 0 0 1px var(--ring);
  --sh-2: 0 4px 14px rgba(11,11,11,.09), 0 0 0 1px var(--ring);
  --sh-3: 0 18px 50px rgba(11,11,11,.20), 0 0 0 1px var(--ring);
  --edge-sh: rgba(11,11,11,.16);
  --nav-w: 232px;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --plane: #0d0d0d; --surface: #1a1a19; --surface-2: #222221; --surface-3: #2c2c2a;
    --ink: #fff; --ink-2: #c3c2b7; --ink-3: #898781;
    --line: #2c2c2a; --line-2: #383835; --ring: rgba(255,255,255,.10);
    --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
    --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
    --good-ink: #0ca30c;
    --accent: var(--accent-d, #3987e5);
    --accent-ink: var(--accent-ink-d, #fff);
    --accent-soft: var(--accent-soft-d, #14283d);
    --sh-1: 0 1px 2px rgba(0,0,0,.4), 0 0 0 1px var(--ring);
    --sh-2: 0 4px 14px rgba(0,0,0,.45), 0 0 0 1px var(--ring);
    --sh-3: 0 18px 50px rgba(0,0,0,.6), 0 0 0 1px var(--ring);
    --edge-sh: rgba(0,0,0,.55);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --plane: #0d0d0d; --surface: #1a1a19; --surface-2: #222221; --surface-3: #2c2c2a;
  --ink: #fff; --ink-2: #c3c2b7; --ink-3: #898781;
  --line: #2c2c2a; --line-2: #383835; --ring: rgba(255,255,255,.10);
  --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
  --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
  --good-ink: #0ca30c;
  --accent: var(--accent-d, #3987e5);
  --accent-ink: var(--accent-ink-d, #fff);
  --accent-soft: var(--accent-soft-d, #14283d);
  --sh-1: 0 1px 2px rgba(0,0,0,.4), 0 0 0 1px var(--ring);
  --sh-2: 0 4px 14px rgba(0,0,0,.45), 0 0 0 1px var(--ring);
  --sh-3: 0 18px 50px rgba(0,0,0,.6), 0 0 0 1px var(--ring);
  --edge-sh: rgba(0,0,0,.55);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* ── Holding the app still ───────────────────────────────────────────────────
   Three separate things were letting the app drift inside the window on a
   phone, and they are three separate fixes.

   1. `dvh` is the *dynamic* viewport height, and it is dynamic on purpose: it
      changes the instant a browser bar slides away or a keyboard opens. Every
      one of those changes relaid out the shell, which is the lurch you see when
      you tap into a field. `svh` is the small viewport — the height with all
      the chrome showing — and it never changes, so nothing relaid out. The app
      is now measured against a ruler that holds still.

   2. Anything one pixel too wide makes the whole document horizontally
      scrollable, and a page that can pan sideways feels exactly like an app
      sliding around in its window. `overflow-x: clip` refuses the pan without
      creating a scroll container (which `hidden` would, and which would break
      every `position: sticky` inside it).

   3. The top bar and the tab bar are sticky and fixed, and during an iOS
      rubber-band both are repainted a frame late — they visibly lag the content
      and then snap back. Promoting them to their own compositor layer means the
      rubber-band moves the content underneath them and they are never redrawn
      at all. */
html {
  /* On the root, not on body: an overflow set on body propagates to the viewport
     only while the root is `visible`, so putting it here is the one placement
     that reliably holds. `clip` and not `hidden` because `hidden` makes this a
     scroll container, and a scroll container re-anchors every `position: sticky`
     inside it — which would quietly unstick the top bar. */
  overflow-x: clip;
  overscroll-behavior: none;
}
html, body { margin: 0; padding: 0; }
body {
  background: var(--plane); color: var(--ink);
  font-family: var(--font); font-size: 15px; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
  max-width: 100%;
}
/* Tracking and leading are size-specific: large type reads too loose at 0, small
   type reads too tight. One value across the scale is wrong at one end or both. */
h1, h2, h3, h4 { margin: 0; font-weight: 650; }
h1 { letter-spacing: -.022em; line-height: 1.15; }
h2 { letter-spacing: -.016em; line-height: 1.25; }
h3 { letter-spacing: -.011em; line-height: 1.3; }
h4 { letter-spacing: -.005em; line-height: 1.35; }
h1 { font-size: 22px; } h2 { font-size: 17px; } h3 { font-size: 15px; }
a { color: var(--accent); text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.muted { color: var(--ink-3); }
.dim { color: var(--ink-2); }
.tnum { font-variant-numeric: tabular-nums; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* ------------------------------------------------------------------ layout */
#app { min-height: 100svh; }
.shell { display: grid; grid-template-columns: var(--nav-w) 1fr; min-height: 100svh; }
.main { min-width: 0; padding: 0 0 96px; }
.wrap { max-width: 1180px; margin: 0 auto; padding: 22px 20px 0; }
.wrap-narrow { max-width: 720px; margin: 0 auto; padding: 18px 16px 0; }

/* sidebar (desktop) */
/* The sidebar is a floating layer, not a slab: translucent with the page
   travelling underneath it, which is what gives an interface depth without
   drawing a single extra line. The solid background underneath is the fallback
   for a browser with no backdrop-filter, and for anyone who has asked for less
   transparency. */
.side {
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column; position: sticky; top: 0; height: 100svh;
}
@supports (backdrop-filter: blur(1px)) {
  .side {
    background: color-mix(in srgb, var(--surface) 72%, transparent);
    backdrop-filter: blur(22px) saturate(180%);
  }
}
.side-top { padding: 18px 16px 12px; }
.brand { display: flex; align-items: center; gap: 9px; font-weight: 700; letter-spacing: -.02em; font-size: 16px; }
.brand-mk {
  width: 26px; height: 26px; border-radius: 8px; flex: none;
  background: linear-gradient(145deg, var(--s1), var(--s7)); color: #fff;
  display: grid; place-items: center; font-size: 12px; font-weight: 800;
}
.org-pick {
  margin: 12px 0 0; width: 100%; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 7px 9px; font-size: 13px; font-weight: 550;
}
/* The scroll edge effect: the list fades where there is more of it, and only
   where there is more of it, so the affordance appears exactly when it is true. */
.nav {
  padding: 6px 10px; display: flex; flex-direction: column; gap: 2px;
  overflow-y: auto; flex: 1; scrollbar-width: thin;
  mask-image: linear-gradient(to bottom, transparent 0, #000 14px,
                              #000 calc(100% - 18px), transparent 100%);
}
/* A group label is a hairline and a word, not a word floating in space. The rule
   stops short of the left edge so it aligns with the labels, not the icons. */
.nav-lbl {
  font-size: 10.5px; letter-spacing: .085em; text-transform: uppercase;
  color: var(--ink-3); padding: 18px 8px 6px; font-weight: 650;
  margin-top: 6px; border-top: 1px solid var(--line);
}
.nav-lbl:first-child { border-top: 0; margin-top: 0; padding-top: 6px; }

/* Every row carries the accent rail; only the current one shows it. Animating a
   transform rather than appearing from nowhere means the marker travels. */
.nav a {
  position: relative;
  display: flex; align-items: center; gap: 11px; padding: 8px 10px;
  border-radius: 9px; color: var(--ink-2); font-size: 14px; font-weight: 500;
  transition: background .14s ease, color .14s ease;
}
.nav a::before {
  content: ""; position: absolute; left: -4px; top: 50%;
  width: 3px; height: 17px; border-radius: 2px; background: var(--accent);
  transform: translate(0, -50%) scaleY(0); transform-origin: center;
  transition: transform .18s cubic-bezier(.3, .9, .3, 1);
}
.nav a:hover { background: var(--surface-2); color: var(--ink); }
/* Response lives on the press, not the release (apple-design §1). */
.nav a:active { background: var(--surface-3); transform: scale(.985); }
.nav a.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.nav a.on::before { transform: translate(0, -50%) scaleY(1); }
.nav a.on svg { color: var(--accent); }
/* A fixed slot, so labels line up down the column whatever shape the glyph is. */
.nav svg { width: 18px; height: 18px; flex: none; color: var(--ink-3); }
.nav .cnt { margin-left: auto; font-size: 11px; font-weight: 700; background: var(--surface-3); color: var(--ink-2); padding: 1px 6px; border-radius: 20px; }
.side-btm { padding: 10px; border-top: 1px solid var(--line); }
.who { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: var(--r-sm); }
.avatar {
  width: 28px; height: 28px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: var(--surface-3); color: var(--ink-2); font-size: 11px; font-weight: 700;
}

/* top bar (mobile) */
.topbar {
  display: none; position: sticky; top: 0; z-index: 30;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--line); padding: 10px 14px;
  align-items: center; gap: 10px;
  /* --sat, not env(), and that is the point.
     iOS re-evaluates env(safe-area-inset-top) as Safari collapses and expands
     its toolbar during a scroll. Used directly here it changes the bar's
     padding mid-scroll, so the bar changes HEIGHT while somebody is looking at
     it -- which reads as the header moving around. --sat is measured once at
     launch and again only on rotation, never on scroll. */
  padding-top: max(10px, var(--sat, env(safe-area-inset-top)));
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  /* NO transform and NO will-change here, deliberately.
     They were added to give the bar its own compositor layer so an overscroll
     would scroll content underneath it rather than repaint it a frame late.
     On a `position: sticky` element that does the opposite: promoting it moves
     the sticky offset onto the compositor, which updates it asynchronously
     during a momentum scroll, so the bar visibly drifts and then snaps back.
     Combined with the sticky element sitting inside a grid (.shell), it is the
     standard recipe for a header that wanders while the page scrolls.
     Sticky bars are already composited by every engine that matters; asking
     for it again is what broke it. */
}
.tabbar { display: none; }

@media (max-width: 700px), (max-width: 960px) and (max-height: 520px) {
  .shell { grid-template-columns: 1fr; }
  .side { display: none; }
  .topbar { display: flex; }
  .wrap, .wrap-narrow { padding: 14px 14px 0; }
  .tabbar {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter: saturate(160%) blur(14px);
    -webkit-backdrop-filter: saturate(160%) blur(14px);
    border-top: 1px solid var(--line);
    padding: 6px 4px calc(6px + var(--sab, env(safe-area-inset-bottom)));
    transform: translateZ(0);
    will-change: transform;
  }
  .tabbar a, .tabbar .tab-menu {
    display: grid; justify-items: center; align-content: center; gap: 3px;
    padding: 5px 2px; min-width: 0; background: none; border: 0; cursor: pointer;
    font: inherit; font-size: 10.5px; font-weight: 600; letter-spacing: 0;
    color: var(--ink-3); border-radius: var(--r-sm);
  }
  /* four fixed tracks at 390px give ~95px each; a label that still will not fit
     is trimmed rather than allowed to widen its track and push the bar sideways */
  .tabbar a span, .tabbar .tab-menu span {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tabbar a svg, .tabbar .tab-menu svg { width: 21px; height: 21px; }
  .tabbar a.on, .tabbar .tab-menu.on { color: var(--accent); }
  .tabbar a:active, .tabbar .tab-menu:active { background: var(--surface-2); }
  .main { padding-bottom: 108px; }
  /* keep the wizard rail on one line: only the current step keeps its label */
  .steps { gap: 4px; flex-wrap: nowrap; }
  .step span { display: none; }
  .step.on span { display: inline; }
  .step-bar { min-width: 8px; }
}

/* ------------------------------------------------------------------ pieces */
.card { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-1); }
.card-p { padding: 16px 18px; }
.card-h {
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.card-h h2 { flex: 1; min-width: 120px; }
/* an icon sitting directly in a card header must not flex to fill it */
.card-h > svg { width: 18px; height: 18px; flex: none; color: var(--ink-3); }
/* the foot of a card: the mirror of .card-h, for paging a long list */
.card-f {
  padding: 12px 18px; border-top: 1px solid var(--line);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.pager .muted { text-align: center; font-size: 13px; }

/* ── dashboard ─────────────────────────────────────────────────────────────
   A heading that says which question the tiles under it answer. The dashboard
   mixes two: trade done in a period, and work outstanding right now. Without a
   line between them an open purchase order looks like this month's problem. */
.sec-h {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 4px 0 -4px;
}
.sec-h b { font-size: 15px; letter-spacing: -.01em; }
.sec-h .muted { font-size: 12.5px; }

/* The period selector. */
.seg { display: inline-flex; background: var(--surface-2, #efefea); border: 1px solid var(--line);
       border-radius: 10px; padding: 2px; gap: 2px; flex-wrap: wrap; }
.seg-b {
  border: 0; background: transparent; color: var(--ink-2); cursor: pointer;
  font: 600 12.5px/1 inherit; padding: 7px 11px; border-radius: 8px;
  white-space: nowrap; transition: background .14s ease, color .14s ease;
}
.seg-b:hover { color: var(--ink); }
.seg-b.on { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
:root[data-theme="dark"] .seg-b.on { box-shadow: none; background: var(--surface-3); }

/* Two views of one book — surgeons and the offices they work out of. An
   underline rather than a pill, so it reads as "same screen, other half" and
   not as a filter you have applied. */
.tabs-l { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: 2px; }
.tab-l {
  padding: 9px 14px 10px; font: 600 13.5px/1 inherit; color: var(--ink-3);
  text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .14s ease, border-color .14s ease;
}
.tab-l:hover { color: var(--ink-2); }
.tab-l.on { color: var(--ink); border-bottom-color: var(--accent); }

/* The map fills what is left of the window rather than taking a guessed height:
   a map you have to scroll is a map you cannot read. */
.map-canvas { width: 100%; height: clamp(360px, calc(100vh - 290px), 760px); background: var(--surface-2); }
.chip-dot { width: 9px; height: 9px; border-radius: 3px; display: inline-block; margin-right: 2px; }
/* Google's info window is its own DOM and its own light surface; the one thing
   worth taking back is the hard corner. */
.gm-style .gm-style-iw-c { border-radius: 10px; padding: 12px 14px; }

/* A line of consequence, not a decoration: used where a gap in the data stops
   something downstream from working. */
.note {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--ink-3);
  border-radius: 10px; padding: 11px 14px; font-size: 13px; line-height: 1.45;
}
.note > svg { width: 17px; height: 17px; flex: none; margin-top: 1px; color: var(--ink-3); }
.note-warn { border-left-color: var(--s4); }
.note-warn > svg { color: var(--s4); }

/* A row of small numbers that do not deserve a tile each. */
/* Each cell carries its own border rather than the grid faking one with a 1px
   gap: at three columns eight numbers leave two cells empty, and a gap-coloured
   hole reads as a rendering fault rather than as nothing to show. */
.kpis {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
}
.kpi {
  background: var(--surface); padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 12px;
}
.kpi-v { font-size: 19px; font-weight: 700; letter-spacing: -.02em; }
.kpi-k { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }

.viz-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

/* ── a KPI against the same stretch of last year ───────────────────────────
   Small multiples, each with its own scale and its unit written out. Dollars,
   people and percentages never share an axis; putting them on one is the
   quickest way to a chart that is confidently wrong. */
.pairs-h { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin: 4px 0 -4px; }
.pairs-h b { font-size: 15px; letter-spacing: -.01em; }
.pairs-h .muted { font-size: 12.5px; }
.pairs { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }
.pair {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px 14px 13px;
}
.pair-k { font-size: 11.5px; color: var(--ink-3); font-weight: 650; }
.pair-top { display: flex; align-items: baseline; gap: 8px; margin-top: 2px; }
.pair-a { font-size: 21px; font-weight: 700; letter-spacing: -.02em; flex: 1; min-width: 0; }
.chip {
  font-size: 11px; font-weight: 700; padding: 2px 6px; border-radius: 999px;
  white-space: nowrap; flex: none;
}
/* Direction is carried by the words "last year …" underneath and by the two
   bars, so colour is reinforcement rather than the only signal. */
.chip.up   { color: var(--s6); background: color-mix(in srgb, var(--s6) 14%, transparent); }
.chip.down { color: var(--s8); background: color-mix(in srgb, var(--s8) 14%, transparent); }
.chip.flat { color: var(--ink-3); background: color-mix(in srgb, var(--ink-3) 14%, transparent); }
.pair-bars { display: grid; gap: 3px; margin: 9px 0 7px; }
.pb { height: 7px; background: color-mix(in srgb, var(--ink-3) 16%, transparent); border-radius: 4px; }
.pb i { display: block; height: 100%; border-radius: 4px; }
.pb i.now  { background: var(--s1); }
.pb i.then { background: var(--ink-3); }
.pair-b { font-size: 11px; color: var(--ink-3); }

/* A legend is required the moment there are two series. */
.legend-row { display: flex; gap: 12px; flex-wrap: wrap; }
.lgi { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-2); }
.lgi i { width: 9px; height: 9px; border-radius: 3px; }

/* The ring and its legend. The legend is not decoration: two of the five hues
   fall under 3:1 against the light surface, so the written label and the value
   are what carry the meaning and the colour is only the index. */
.donut-wrap { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 10px; }
.donut-wrap > svg { flex: none; }
.legend { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 180px; }
.lg { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.lg i { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.lg-k { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); }
.lg-v { font-weight: 650; }
.lg-p { color: var(--ink-3); width: 34px; text-align: right; }

/* The week strip. */
.wk { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; margin-top: 12px; }
.wk-d {
  background: var(--surface-2, rgba(0,0,0,.03)); border: 1px solid transparent;
  border-radius: 10px; padding: 8px 4px 9px; text-align: center; min-width: 0;
}
.wk-d.now { border-color: var(--s1); background: color-mix(in srgb, var(--s1) 9%, transparent); }
/* A day with something on it should be legible at a glance from across a desk. */
.wk-d.has .wk-dt { color: var(--ink); }
.wk-d:not(.has) { opacity: .62; }
/* A panel that is one line of news rather than a chart. */
.viz-slim { padding-top: 13px; padding-bottom: 13px; }
.viz-slim > .row > svg { width: 17px; height: 17px; color: var(--ink-3); flex: none; }
.wk-n { font-size: 11px; color: var(--ink-3); font-weight: 650; }
.wk-dt { font-size: 15px; font-weight: 700; letter-spacing: -.02em; }
.wk-bar { height: 48px; display: flex; align-items: flex-end; justify-content: center; margin: 5px 0 3px; }
.wk-bar i { display: block; width: 16px; border-radius: 4px 4px 0 0; background: var(--s1); }
.wk-c { font-size: 12px; font-weight: 650; min-height: 15px; }
.wk-v { font-size: 10.5px; color: var(--ink-3); }
@media (max-width: 560px) { .wk-v { display: none; } }
.row { display: flex; align-items: center; gap: 10px; }
.row-wrap { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.stack { display: grid; gap: 14px; }
.stack-sm { display: grid; gap: 8px; }
.pagehead { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
.pagehead p { margin: 3px 0 0; color: var(--ink-2); font-size: 13.5px; }
hr.sep { border: 0; border-top: 1px solid var(--line); margin: 16px 0; }

/* buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 14px; border-radius: var(--r-sm); border: 1px solid var(--line-2);
  background: var(--surface); color: var(--ink); font-size: 14px; font-weight: 600;
  cursor: pointer; transition: background .12s, border-color .12s, transform .06s;
  white-space: nowrap;
}
.btn:hover:not(:disabled) { background: var(--surface-2); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn svg { width: 16px; height: 16px; }
.btn-pri { background: var(--accent); border-color: transparent; color: var(--accent-ink); }
.btn-pri:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 88%, #000); }
.btn-danger { color: var(--critical); border-color: color-mix(in srgb, var(--critical) 40%, var(--line-2)); }
.btn-ghost { border-color: transparent; background: transparent; color: var(--ink-2); }
.btn-ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
.btn-lg { padding: 13px 20px; font-size: 15.5px; border-radius: var(--r); }
.btn-sm { padding: 5px 9px; font-size: 12.5px; }
.btn-block { width: 100%; }
.btn-icon { padding: 7px; border-radius: var(--r-sm); }

/* fields */
.f { display: grid; gap: 5px; min-width: 0; }
.f > label { font-size: 12px; font-weight: 650; color: var(--ink-2); letter-spacing: .01em; }
/* tick boxes and radios keep their intrinsic size — a 100% width here would
   swallow the row they sit in */
.f input:not([type="checkbox"]):not([type="radio"]), .f select, .f textarea {
  width: 100%; background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 9px 10px; font-size: 14.5px;
  transition: border-color .12s, box-shadow .12s;
}
.f input[type="checkbox"], .f input[type="radio"] { width: auto; flex: none; }

/* The search and filter boxes scattered through the app were bare <input>s
   wearing a hard-coded pixel width, so they rendered as raw browser controls
   and could not be told to fill a phone's column. They are fields like any
   other, and their width is now the stylesheet's business. */
input.fld, select.fld {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
  padding: 9px 10px; font-size: 14.5px; min-width: 0; max-width: 300px;
  transition: border-color .12s, box-shadow .12s;
}
select.fld { max-width: 200px; }
/* …but a dropdown that is answering a labelled question fills its field. The
   200px cap suits a status picker in a toolbar; it truncates "Keylift
   Instrument Tray — 43 on the shelf of 44" into nonsense. */
.f input.fld, .f select.fld, .f textarea.fld { max-width: none; width: 100%; }
/* a search box in a card header takes the space the header has spare, up to
   its cap, rather than sitting at the browser's default field width */
.card-h input.fld { flex: 1 1 220px; }
input.fld:focus, select.fld:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.f input:focus, .f select:focus, .f textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.f textarea { resize: vertical; min-height: 68px; }
.f .hint { font-size: 11.5px; color: var(--ink-3); }
.f-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.f-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
@media (max-width: 620px) { .f-2, .f-3 { grid-template-columns: 1fr; } }
.f.lowconf input, .f.lowconf select { border-color: var(--warning); background: color-mix(in srgb, var(--warning) 7%, var(--surface)); }

/* pills */
.pill {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 20px;
  font-size: 11.5px; font-weight: 650; letter-spacing: .01em; white-space: nowrap;
  border: 1px solid transparent;
}
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pill-muted    { color: var(--ink-2);   background: var(--surface-3); }
.pill-info     { color: var(--s1);      background: color-mix(in srgb, var(--s1) 13%, var(--surface)); }
.pill-good     { color: var(--good-ink);background: color-mix(in srgb, var(--good) 15%, var(--surface)); }
.pill-warning  { color: #7a5600;        background: color-mix(in srgb, var(--warning) 26%, var(--surface)); }
.pill-serious  { color: #8a4116;        background: color-mix(in srgb, var(--serious) 26%, var(--surface)); }
.pill-critical { color: var(--critical);background: color-mix(in srgb, var(--critical) 13%, var(--surface)); }
:root[data-theme="dark"] .pill-warning, :root:not([data-theme="light"]) .pill-warning { color: var(--warning); }
:root[data-theme="dark"] .pill-serious, :root:not([data-theme="light"]) .pill-serious { color: var(--serious); }
.chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 6px 4px 10px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 20px;
  font-size: 12.5px; font-weight: 550; max-width: 100%; white-space: nowrap;
}
/* a strip scrolls sideways on purpose, so its chips keep their own width
   rather than being squeezed into three-line labels */
.pagestrip > * { flex: none; }
.chip.locked { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); }
.chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip > svg { width: 13px; height: 13px; flex: none; opacity: .75; }
.chip button { border: 0; background: none; cursor: pointer; color: var(--ink-3); padding: 0 2px; font-size: 15px; line-height: 1; }
.chip button:hover { color: var(--critical); }

/* tables */
/* §12: a scroll edge effect rather than a hard rule. The two `local` layers
   travel with the content and sit on top of the two fixed shadows, so a shadow
   is visible exactly when there is more table in that direction and fades out
   as you reach the end — the affordance appears only when it is true. */
.tbl-wrap, .calwrap {
  background:
    linear-gradient(to right, var(--surface) 40%, transparent) left / 26px 100% no-repeat local,
    linear-gradient(to left, var(--surface) 40%, transparent) right / 26px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--edge-sh), transparent) left / 13px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--edge-sh), transparent) right / 13px 100% no-repeat scroll;
}
.tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13.5px; }
table.tbl th {
  text-align: left; font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 700; padding: 9px 12px; border-bottom: 1px solid var(--line);
  white-space: nowrap; background: var(--surface);
}
table.tbl td { padding: 10px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
table.tbl tbody tr:last-child td { border-bottom: 0; }
table.tbl tbody tr.clickable { cursor: pointer; }
table.tbl tbody tr.clickable:hover { background: var(--surface-2); }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }

/* stat tiles */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(178px, 1fr)); gap: 12px; }
/* A tile is a number with a hairline of colour down its left edge — enough to
   make the row read as a set of cards rather than a table of boxes, and enough
   to let one of them say "this is the one to look at" without shouting. */
.tile {
  position: relative; overflow: hidden;
  background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-1);
  padding: 14px 16px 15px 17px;
  transition: box-shadow .18s ease, transform .18s ease;
}
.tile::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--s1); opacity: .5;
}
.tile .k { font-size: 11.5px; font-weight: 650; color: var(--ink-3); letter-spacing: .03em; text-transform: uppercase; }
.tile .v { font-size: 27px; font-weight: 680; letter-spacing: -.028em; margin-top: 6px; line-height: 1.1; }
.tile .sub { font-size: 12px; color: var(--ink-2); margin-top: 3px; }
.tile.tap { cursor: pointer; }
/* Respond on press, and lift by a hair rather than a jump. */
.tile.tap:hover { box-shadow: var(--sh-2); transform: translateY(-1px); }
.tile.tap:active { transform: translateY(0); box-shadow: var(--sh-1); }

/* Status, for the one tile in a row that is actually a problem. Reserved
   colours, and always with a word beside them — never colour on its own. */
.tile.warn::before    { background: var(--s4); opacity: 1; }
.tile.serious::before { background: var(--s8); opacity: 1; }
.tile.quiet::before   { background: var(--ink-3); opacity: .35; }
.tile .sub.flag { color: var(--s4); font-weight: 600; }
.tile .sub.flag-bad { color: var(--s8); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .tile, .tile.tap:hover, .tile.tap:active { transition: none; transform: none; }
}

/* pipeline board — one column per open stage */
.pipe { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
@media (max-width: 1100px) { .pipe { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px)  { .pipe { grid-template-columns: 1fr; } }
.pipe-col { display: grid; gap: 7px; align-content: start; background: var(--surface-2);
  border-radius: var(--r); padding: 9px; min-height: 60px; }
.pipe-h { display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; padding: 0 2px 2px; }
.pipe-card {
  display: grid; gap: 2px; text-align: left; width: 100%; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 8px 10px; font: inherit; color: inherit;
}
.pipe-card:hover { box-shadow: var(--sh-1); border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
.pipe-card b { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pipe-card small { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* template editor: pick an output, edit the words, watch the real thing render */
.tpl-grid { display: grid; grid-template-columns: 230px minmax(0, 1fr) minmax(0, 1fr); gap: 14px; align-items: start; }
@media (max-width: 1200px) { .tpl-grid { grid-template-columns: 210px minmax(0, 1fr); } }
@media (max-width: 700px), (max-width: 960px) and (max-height: 520px)  { .tpl-grid { grid-template-columns: 1fr; } }
.tpl-item {
  display: grid; gap: 1px; text-align: left; width: 100%; cursor: pointer;
  background: none; border: 0; border-radius: var(--r-sm); padding: 7px 9px;
  font: inherit; color: inherit;
}
.tpl-item:hover { background: var(--surface-2); }
.tpl-item.on { background: var(--accent-soft); color: var(--accent); }
.tpl-item b { font-size: 13px; }
.tpl-item small { font-size: 11.5px; }
.tpl-item .pill { justify-self: start; margin-top: 3px; }
.tpl-preview { padding: 12px; background: var(--surface-2); border-radius: 0 0 var(--r-lg) var(--r-lg); }
.tpl-subject { display: flex; gap: 8px; align-items: baseline; padding: 0 2px 9px; font-size: 13px; }
.tpl-subject .muted { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; flex: none; }
.tpl-frame { width: 100%; height: 620px; border: 1px solid var(--line); border-radius: var(--r); background: #fff; }
.tpl-doc { background: #fff; color: #11161d; border: 1px solid var(--line); border-radius: var(--r);
  padding: 22px 24px; max-height: 620px; overflow: auto; }

/* empty / loading */
.empty { text-align: center; padding: 46px 20px; color: var(--ink-2); }
.empty svg { width: 34px; height: 34px; color: var(--ink-3); margin-bottom: 10px; }
.empty h3 { margin-bottom: 4px; }
.spin {
  width: 16px; height: 16px; border-radius: 50%; flex: none;
  border: 2px solid var(--line-2); border-top-color: var(--accent);
  animation: sp .7s linear infinite;
}
@keyframes sp { to { transform: rotate(360deg); } }
.skel { background: linear-gradient(90deg, var(--surface-2), var(--surface-3), var(--surface-2));
  background-size: 200% 100%; animation: sk 1.3s ease-in-out infinite; border-radius: 6px; height: 13px; }
@keyframes sk { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

/* toasts */
.toasts { position: fixed; z-index: 90; left: 50%; transform: translateX(-50%); bottom: calc(18px + var(--sab, env(safe-area-inset-bottom)));
  display: grid; gap: 8px; width: min(520px, calc(100vw - 24px)); pointer-events: none; }
@media (min-width: 701px) and (min-height: 521px), (min-width: 961px) { .toasts { left: auto; right: 20px; transform: none; bottom: 20px; } }
.toast {
  background: var(--ink); color: var(--plane); padding: 11px 14px; border-radius: var(--r);
  font-size: 13.5px; font-weight: 550; box-shadow: var(--sh-3);
  opacity: 0; transform: translateY(8px); transition: opacity .2s, transform .2s;
}
.toast.in { opacity: 1; transform: none; }
.toast-critical { background: var(--critical); color: #fff; }
.toast-good { background: var(--good-ink); color: #fff; }
.toast-warning { background: #7a5600; color: #fff; }

/* modal / sheet */
.scrim { position: fixed; inset: 0; background: rgba(11,11,11,.45); z-index: 70;
  display: grid; place-items: center; padding: 18px; animation: fade .15s ease; }
@keyframes fade { from { opacity: 0; } }
.modal { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-3);
  width: min(620px, 100%); max-height: min(86svh, 820px); display: flex; flex-direction: column; overflow: hidden; }
.modal-h { padding: 15px 18px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 10px; }
.modal-b { padding: 16px 18px; overflow-y: auto; }
.modal-f { padding: 13px 18px; border-top: 1px solid var(--line); display: flex; gap: 9px; justify-content: flex-end; flex-wrap: wrap; }
@media (max-width: 620px) {
  .scrim { padding: 0; align-items: flex-end; }
  .modal { width: 100%; max-height: 92svh; border-radius: var(--r-lg) var(--r-lg) 0 0; }
  .modal-f { padding-bottom: calc(13px + var(--sab, env(safe-area-inset-bottom))); }
  .modal-f .btn { flex: 1; }
}

/* mobile menu sheet */
.menu-scrim { padding: 0; align-items: flex-end; }
/* The sheet is the same material as the sidebar, so the two read as one system
   rather than two menus that happen to list the same things. The bright top edge
   is light catching the surface. */
.menu-panel {
  width: 100%; max-height: 86svh; display: flex; flex-direction: column;
  background: var(--surface); border-radius: 18px 18px 0 0;
  box-shadow: var(--sh-3); overflow: hidden; touch-action: none;
  padding-bottom: var(--sab, env(safe-area-inset-bottom)); will-change: transform;
  border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
}
@supports (backdrop-filter: blur(1px)) {
  .menu-panel {
    background: color-mix(in srgb, var(--surface) 82%, transparent);
    backdrop-filter: blur(26px) saturate(180%);
  }
}
.menu-grab {
  width: 38px; height: 4px; border-radius: 2px; background: var(--line-2);
  margin: 9px auto 3px; flex: none;
}
.menu-who {
  display: flex; align-items: center; gap: 10px; padding: 8px 16px 12px;
  border-bottom: 1px solid var(--line); flex: none;
}
.menu-who-n {
  font-size: 14.5px; font-weight: 640; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.menu-scroll {
  overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  padding: 8px 10px 14px; touch-action: pan-y; min-height: 0;
}
.menu-scroll .org-pick { margin: 4px 6px 8px; width: calc(100% - 12px); }
.menu-grp { display: flex; flex-direction: column; gap: 2px; }
/* Same hairline-and-a-word as the sidebar, so a group is a group in both places. */
.menu-grp .nav-lbl {
  padding: 16px 8px 6px; margin-top: 6px; border-top: 1px solid var(--line);
}
.menu-grp:first-child .nav-lbl { border-top: 0; margin-top: 0; }
.menu-grp a {
  position: relative;
  display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 6px 12px;
  border-radius: 11px; color: var(--ink); font-size: 15px; font-weight: 520;
  transition: background .14s ease, color .14s ease;
}
.menu-grp a::before {
  content: ""; position: absolute; left: -2px; top: 50%;
  width: 3px; height: 20px; border-radius: 2px; background: var(--accent);
  transform: translate(0, -50%) scaleY(0);
  transition: transform .18s cubic-bezier(.3, .9, .3, 1);
}
.menu-grp a svg { width: 19px; height: 19px; flex: none; color: var(--ink-3); }
.menu-grp a span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-grp a.on { background: var(--accent-soft); color: var(--accent); font-weight: 620; }
.menu-grp a.on::before { transform: translate(0, -50%) scaleY(1); }
.menu-grp a.on svg { color: var(--accent); }
/* Touch-down, not touch-up. */
.menu-grp a:active { background: var(--surface-2); transform: scale(.99); }
body.no-scroll { overflow: hidden; }

/* ------------------------------------------------------------- fit the screen
   `.stack` and friends are grids, and a grid item's automatic minimum size is
   its content. One wide table therefore widened the whole column track, and
   every sibling — the page heading, its description — was stretched with it,
   which is what made five screens scroll sideways. Tracks are now explicitly
   allowed to be narrower than their contents, and anything that genuinely
   needs the width scrolls inside its own box instead of moving the page. */
.stack, .stack-sm { grid-template-columns: minmax(0, 1fr); }
.wrap > *, .wrap-narrow > *, .stack > *, .stack-sm > *, .tiles > *, .chat-grid > *,
.card, .card-p, .card-h, .tbl-wrap, .calwrap, .pagestrip { min-width: 0; }
.pagehead > * { min-width: 0; }
.pagehead h1, .pagehead p { overflow-wrap: break-word; }
/* a page heading's action buttons never wrap their own labels, so the row that
   holds them has to be the thing that wraps */
.pagehead .row { flex-wrap: wrap; }
/* and the same holds for every other control row on a phone: a picker and its
   button fit a laptop and not a 320px screen, and wrapping is what should
   happen rather than the page growing to accommodate them */
@media (max-width: 700px), (max-width: 960px) and (max-height: 520px) { .row { flex-wrap: wrap; } }
/* a pill holding a whole sentence is a status line, not a chip: on a narrow
   screen it wraps rather than pushing the page sideways. Its dot stays put. */
@media (max-width: 700px), (max-width: 960px) and (max-height: 520px) {
  .pill { max-width: 100%; white-space: normal; }
  .pill::before { flex: none; }
  /* inside a table the row height is the cost of wrapping, and the table has
     its own sideways scroll, so a status chip there stays on one line */
  table.tbl .pill { white-space: nowrap; }
  /* a filter control shares a card header with its heading; on a phone it takes
     the full line instead of whatever the browser's default field width is */
  .card-h { flex-wrap: wrap; }
  .card-h input, .card-h select, .card-h .inp { flex: 1 1 100%; min-width: 0; }
  /* no field on a phone is capped below the column it sits in */
  input.fld, select.fld { max-width: none; width: 100%; min-height: 44px; }

  /* A table squeezed to the screen wraps a facility name onto four lines and
     is harder to read than one that scrolls. The table sizes to its content
     and the card scrolls sideways — the page still does not. */
  table.tbl { width: max-content; min-width: 100%; }
  table.tbl th, table.tbl td { max-width: 190px; }

  /* A heading and its action buttons cannot share a line on a phone. Side by
     side, the buttons hold their labels and the heading column is squeezed to
     whatever is left — which is how an order number ended up broken across
     three lines. The heading takes the full width and the actions sit beneath
     it, which is also the order they are read in. */
  .pagehead { flex-direction: column; align-items: stretch; gap: 12px; }
  .pagehead > * { width: 100%; }
  .pagehead .row-wrap, .pagehead .row { width: 100%; }
  .pagehead .btn { flex: 1 1 auto; }
  /* the back link is a link, not an action — it stays its own size */
  .pagehead .grow > a.row { display: inline-flex; width: auto; }

  /* --------------------------------------------------- phone: type and touch
     §15: tracking and leading are size-specific. A page title on a phone is the
     one piece of type that should be unmistakably the title, so it grows and
     tightens rather than sitting a few points above the body copy. */
  .pagehead h1 { font-size: 27px; letter-spacing: -.028em; line-height: 1.12; }
  .pagehead p { font-size: 14px; line-height: 1.45; }
  .card-h h2 { font-size: 17.5px; }

  /* 16px is the gutter a phone screen reads as "edge", and it matches the
     inset of the cards it contains. */
  .wrap, .wrap-narrow { padding: 16px 16px 0; }
  .card { border-radius: 16px; }

  /* §1 and §10: anything tappable answers on the press, not on the release,
     and no target is smaller than a fingertip. */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 36px; }
  table.tbl td { padding-top: 13px; padding-bottom: 13px; }
  table.tbl tbody tr.clickable:active { background: var(--surface-2); }
  .tile.tap:active, .who:active, .ob-row:active { background: var(--surface-2); }
  .card-h h2 + .btn, .pagehead .btn { touch-action: manipulation; }

  /* Eight numbers in one column is eight screens of scrolling to read eight
     numbers. Two up reads as a dashboard and puts the whole thing in a glance
     and a half — the type steps down with the box, per §15. */
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .tile { padding: 13px 14px; border-radius: 14px; }
  .tile .k { font-size: 10.5px; letter-spacing: .04em; }
  .tile .v { font-size: 23px; letter-spacing: -.026em; margin-top: 5px; }
  .tile .sub { font-size: 11.5px; }
}

/* ------------------------------------------------- floating chrome (§12)
   A hairline under a bar that is already floating above the content is a
   drawn divider where the material should be doing the separating. The rule
   is the same one iOS uses: nothing while the page is at rest at the top,
   and an edge that materialises only once content has actually travelled
   underneath the bar. */
.topbar { border-bottom: 1px solid transparent; transition: border-color .2s ease, box-shadow .2s ease; }
.topbar-n {
  font-weight: 670; font-size: 15.5px; line-height: 1.2; letter-spacing: -.014em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar.edge {
  border-bottom-color: var(--line);
  box-shadow: 0 6px 16px -14px rgba(11, 11, 11, .55);
}
.tabbar {
  /* the bright top edge is light catching the material, not a border */
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--surface) 55%, transparent);
}
@media (prefers-reduced-transparency: reduce) {
  .topbar.edge { box-shadow: none; }
}

/* steps */
.steps { display: flex; align-items: center; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; }
.step { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; color: var(--ink-3); }
.step i { width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface-3); color: var(--ink-3); font-size: 11px; font-style: normal; font-weight: 700; flex: none; }
.step.on { color: var(--ink); } .step.on i { background: var(--accent); color: #fff; }
.step.done i { background: var(--good-ink); color: #fff; }
.step-bar { flex: 1; min-width: 12px; height: 1px; background: var(--line-2); }

/* capture / redaction */
.drop {
  border: 2px dashed var(--line-2); border-radius: var(--r-lg); background: var(--surface);
  padding: 30px 20px; text-align: center; transition: border-color .15s, background .15s;
}
.drop.over { border-color: var(--accent); background: var(--accent-soft); }
.drop svg { width: 34px; height: 34px; color: var(--ink-3); }
.pagestrip { display: flex; gap: 8px; overflow-x: auto; padding: 2px; }
.pagestrip img, .pagestrip canvas {
  height: 92px; border-radius: var(--r-sm); border: 1px solid var(--line); background: #fff; cursor: pointer;
}
.pagestrip .on { outline: 2px solid var(--accent); outline-offset: 1px; }
.boxer { position: relative; user-select: none; touch-action: none; background: var(--surface-2);
  border-radius: var(--r); overflow: hidden; border: 1px solid var(--line); }
.boxer canvas { display: block; width: 100%; height: auto; }
/* Positioned by transform, not left/top, so dragging stays off the layout path. */
.boxer .bx { position: absolute; top: 0; left: 0; border: 2px solid var(--s8);
  background: color-mix(in srgb, var(--s8) 18%, transparent);
  cursor: move; box-shadow: 0 0 0 9999px rgba(11,11,11,.30);
  transition: border-color .1s ease-out, background .1s ease-out; }
/* Feedback on pointer-down, not on release. */
.boxer .bx.grabbing { border-color: var(--s8); background: color-mix(in srgb, var(--s8) 28%, transparent); }
.boxer .bx .hd { position: absolute; width: 16px; height: 16px; background: var(--s8);
  border: 2px solid #fff; border-radius: 50%; touch-action: none; }
/* A 44px target around a 16px dot — the handle stays small, the finger doesn't. */
.boxer .bx .hd::before { content: ""; position: absolute; inset: -14px; border-radius: 50%; }
.boxer .bx.grabbing .hd { transform: scale(1.15); }
.boxer .bx .hd { transition: transform .1s ease-out; }
.boxer .bx .hd.se { right: -9px; bottom: -9px; cursor: nwse-resize; }
.boxer .bx .hd.nw { left: -9px; top: -9px; cursor: nwse-resize; }
.boxer .bx .tag { position: absolute; top: -22px; left: -2px; background: var(--s8); color: #fff;
  font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 4px; letter-spacing: .04em; white-space: nowrap; }

/* line items */
.lines { display: grid; gap: 7px; }
.lineitem { display: grid; grid-template-columns: 1fr 92px 80px 62px 92px 100px 32px; gap: 7px; align-items: center; }
.lineitem input { padding: 7px 8px; font-size: 13.5px; }
.lineitem-h { font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3); font-weight: 700; padding: 0 2px; }
.lineitem-h > .num { text-align: right; }
@media (max-width: 820px) {
  /* every cell gets a full-width column; the delete button sits beside the amount
     rather than defining a 32px column that squeezes the inputs above it */
  .lineitem {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "d d" "c l" "q u" "a x";
    padding: 10px; background: var(--surface-2); border-radius: var(--r-sm);
  }
  .lineitem-h { display: none; }
  .lineitem > :nth-child(1) { grid-area: d; } .lineitem > :nth-child(2) { grid-area: c; }
  .lineitem > :nth-child(3) { grid-area: l; } .lineitem > :nth-child(4) { grid-area: q; }
  .lineitem > :nth-child(5) { grid-area: u; } .lineitem > :nth-child(6) { grid-area: a; }
  .lineitem > :nth-child(7) { grid-area: x; justify-self: end; align-self: center; }
}

/* banners */
.banner { display: flex; gap: 11px; padding: 12px 14px; border-radius: var(--r); font-size: 13.5px; line-height: 1.5; }
.banner svg { width: 18px; height: 18px; flex: none; margin-top: 1px; }
.banner strong { font-weight: 650; }
.banner-info { background: var(--accent-soft); color: var(--ink); }
.banner-warn { background: color-mix(in srgb, var(--warning) 18%, var(--surface)); color: var(--ink); }
.banner-phi  { background: color-mix(in srgb, var(--s7) 12%, var(--surface)); color: var(--ink); }
.banner-crit { background: color-mix(in srgb, var(--critical) 12%, var(--surface)); color: var(--ink); }

/* charts */
.viz { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-1); padding: 15px 17px 12px; }
.viz h3 { font-size: 13.5px; }
.viz .vsub { font-size: 11.5px; color: var(--ink-3); margin-top: 1px; }
.viz svg { display: block; max-width: 100%; overflow: visible; }
.viz .grid line { stroke: var(--line); stroke-width: 1; }
.viz .axis text { fill: var(--ink-3); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.viz .base { stroke: var(--line-2); stroke-width: 1; }
.viz .dlab { fill: var(--ink-2); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.viz .mark { transition: opacity .12s; }
.viz:hover .mark.fade { opacity: .38; }
.legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 10px; }
.legend span { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-2); font-weight: 550; }
.legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.vtip {
  position: fixed; z-index: 95; pointer-events: none; background: var(--ink); color: var(--plane);
  padding: 7px 9px; border-radius: 7px; font-size: 12px; box-shadow: var(--sh-2);
  opacity: 0; transition: opacity .1s; max-width: 230px;
}
.vtip.on { opacity: 1; }
.vtip b { font-variant-numeric: tabular-nums; }
.bars2 { display: grid; gap: 7px; }
.bar2 { display: grid; grid-template-columns: minmax(80px, 150px) 1fr auto; gap: 10px; align-items: center; font-size: 12.5px; }
.bar2 .lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); }
.bar2 .track { background: var(--surface-3); border-radius: 4px; height: 13px; overflow: hidden; }
.bar2 .fill { height: 100%; border-radius: 4px; }
.bar2 .amt { font-variant-numeric: tabular-nums; font-weight: 620; font-size: 12.5px; }

/* documents */
.docrow { display: flex; gap: 14px; align-items: flex-start; padding: 10px 0; border-bottom: 1px solid var(--line); }
.docrow:last-child { border-bottom: 0; }
.docthumb {
  width: 108px; flex: none; min-height: 84px; max-height: 150px; overflow: hidden;
  border: 1px solid var(--line-2); border-radius: 8px; background: var(--surface-2);
  display: grid; place-items: center; cursor: pointer; position: relative;
  transition: border-color .12s, box-shadow .12s;
}
.docthumb:hover { border-color: var(--accent); box-shadow: var(--sh-2); }
.docthumb > div { width: 100%; }
.docthumb img, .docthumb canvas { width: 100%; height: auto; display: block; }
.docthumb-locked {
  display: grid; place-items: center; gap: 5px; padding: 18px 6px;
  color: var(--ink-3); font-size: 11px; font-weight: 600; text-align: center;
}
.docthumb-locked svg { width: 18px; height: 18px; }
@media (max-width: 620px) { .docthumb { width: 84px; } }

.modal-wide { width: min(940px, 100%); }
.modal-wide .modal-b { background: var(--plane); }

/* recipient parties */
.parties { display: grid; gap: 6px; }
.party {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); cursor: pointer; transition: border-color .12s, background .12s;
}
.party:hover { border-color: var(--line-2); background: var(--surface-2); }
.party:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.party > input[type="checkbox"] { margin-top: 2px; flex: none; }
.party-name { display: flex; align-items: center; gap: 6px; font-size: 13.5px; font-weight: 650; }
.party-name > svg { width: 15px; height: 15px; flex: none; color: var(--ink-3); }
.party-addr { font-size: 12.5px; color: var(--ink-2); margin-top: 2px; word-break: break-word; }
.party-locked { cursor: default; background: var(--surface-2); border-style: dashed; }
.party-locked > svg { width: 15px; height: 15px; flex: none; color: var(--ink-3); margin-top: 3px; }
.party-locked:hover { border-color: var(--line); background: var(--surface-2); }

/* contextual action row on an order */
.actionbar { display: flex; flex-wrap: wrap; gap: 8px; }
.actionbar .btn { flex: none; }

/* email preview */
.mailprev { border: 1px solid var(--line-2); border-radius: var(--r); overflow: hidden; background: #fff; }
.mailprev iframe { display: block; width: 100%; border: 0; height: 360px; background: #fff; }
.mailprev-head {
  background: var(--surface-2); border-bottom: 1px solid var(--line);
  padding: 9px 12px; display: grid; gap: 3px;
}
.mailprev-head .ln { font-size: 12px; color: var(--ink-2); display: flex; gap: 7px; }
.mailprev-head .ln b { color: var(--ink-3); font-weight: 650; min-width: 54px; flex: none; text-transform: uppercase; letter-spacing: .05em; font-size: 10.5px; padding-top: 1px; }
.mailprev-head .ln span { color: var(--ink); word-break: break-word; }
.mailprev-tall iframe { height: 620px; }

/* audit diff */
.diff { font-size: 12.5px; }
.diff .fld { font-weight: 650; }
.diff del { color: var(--critical); text-decoration: line-through; opacity: .85; }
.diff ins { color: var(--good-ink); text-decoration: none; }

/* auth */
.auth { min-height: 100svh; display: grid; place-items: center; padding: 22px; }
.auth-card { width: min(430px, 100%); }
.auth-mk { width: 46px; height: 46px; border-radius: 13px; margin: 0 auto 14px;
  background: linear-gradient(145deg, var(--s1), var(--s7)); color: #fff;
  display: grid; place-items: center; font-size: 17px; font-weight: 800; }
/* The logo carries its own wordmark, so the card shows it in place of a heading. */
.auth-logo { display: block; width: min(268px, 82%); height: auto; margin: 2px auto 0; }
.or { display: flex; align-items: center; gap: 10px; color: var(--ink-3); font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .08em; font-weight: 650; margin: 16px 0; }
.or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.otp { display: flex; gap: 8px; justify-content: center; }
.otp input { width: 44px; text-align: center; font-size: 20px; font-weight: 650; padding: 10px 0; }

/* --- trays, calendar, platform ------------------------------------------ */
.flip { transform: rotate(180deg); }

/* a chip that acts as a button shouldn't inherit the button reset's centring */
button.chip { cursor: pointer; font: inherit; }
button.chip:hover { border-color: var(--line-2); }

/* Seven columns will not fit a phone honestly, so the month scrolls sideways
   rather than being crushed into something unreadable. */
.calwrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.calgrid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  min-width: 640px;
}
.calhead { font-size: 11.5px; text-align: center; padding: 2px 0; color: var(--ink-3); }
@media (min-width: 760px) { .calgrid { min-width: 0; } }

/* --- chat ---------------------------------------------------------------- */
.chat-grid {
  display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 14px;
  align-items: stretch;
}
@media (max-width: 700px), (max-width: 960px) and (max-height: 520px) { .chat-grid { grid-template-columns: 1fr; } }

.chat-side { display: flex; flex-direction: column; gap: 2px; }
.chat-chan {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 10px; border-radius: var(--r-sm); border: 0; background: transparent;
  color: var(--ink-2); font: inherit; font-size: 13.5px; cursor: pointer; text-align: left;
}
.chat-chan:hover { background: var(--surface-2); color: var(--ink); }
.chat-chan.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.chat-chan svg { width: 15px; height: 15px; flex: none; opacity: .7; }
.chat-badge {
  margin-left: auto; font-size: 11px; font-weight: 700; border-radius: 20px;
  padding: 1px 6px; background: var(--critical); color: #fff;
}

.chat-pane {
  display: flex; flex-direction: column; min-height: 62svh; max-height: 78svh;
  background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden;
}
.chat-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--line); flex: none;
}
.chat-scroll { flex: 1 1 auto; overflow-y: auto; padding: 10px 6px 14px; }

.chat-day { display: flex; align-items: center; gap: 10px; margin: 14px 12px 8px; }
.chat-day::before, .chat-day::after { content: ""; height: 1px; background: var(--line); flex: 1; }
.chat-day span { font-size: 11.5px; font-weight: 600; color: var(--ink-3); white-space: nowrap; }

.chat-msg { display: flex; gap: 10px; padding: 4px 12px; align-items: flex-start; position: relative; }
.chat-msg:hover { background: var(--surface-2); }
.chat-msg.grouped { padding-top: 1px; padding-bottom: 1px; }
.chat-gutter { width: 34px; flex: none; text-align: right; }
.chat-time { font-size: 10.5px; color: var(--ink-3); opacity: 0; }
.chat-msg:hover .chat-time { opacity: 1; }
.chat-meta { display: flex; align-items: baseline; gap: 7px; font-size: 12.5px; }
.chat-meta b { font-size: 13.5px; }
.chat-body { font-size: 14px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-quote {
  border-left: 2px solid var(--line-2); padding-left: 8px; margin: 2px 0 4px;
  font-size: 12.5px; color: var(--ink-3);
}
.chat-tools {
  position: absolute; right: 10px; top: 2px; display: none; gap: 2px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 2px;
}
.chat-msg:hover .chat-tools { display: flex; }

.mention {
  background: color-mix(in srgb, var(--accent) 13%, var(--surface));
  color: var(--accent); border-radius: 4px; padding: 0 3px; font-weight: 600;
}
.mention-me { background: color-mix(in srgb, var(--warning) 26%, var(--surface)); color: var(--ink); }

.chat-reply {
  display: flex; align-items: center; gap: 8px; padding: 7px 14px;
  border-top: 1px solid var(--line); font-size: 12.5px; color: var(--ink-3);
}
.chat-reply svg { width: 14px; height: 14px; flex: none; }

.chat-composer { position: relative; padding: 10px 14px 12px; border-top: 1px solid var(--line); flex: none; }
.chat-input {
  flex: 1; resize: none; min-height: 40px; max-height: 160px;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--ink); font: inherit; font-size: 14px;
}
.chat-input:focus { outline: none; border-color: var(--accent); background: var(--surface); }

.chat-at {
  position: absolute; bottom: calc(100% - 4px); left: 14px; right: 14px; max-width: 340px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--sh-2); overflow: hidden; z-index: 5;
}
.chat-at-row {
  display: flex; gap: 9px; align-items: baseline; width: 100%; text-align: left;
  padding: 7px 11px; border: 0; background: transparent; color: var(--ink); font: inherit; font-size: 13px;
  cursor: pointer;
}
.chat-at-row.on, .chat-at-row:hover { background: var(--accent-soft); }

.chat-mention {
  display: block; width: 100%; text-align: left; border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 10px 12px; background: transparent;
  color: var(--ink); font: inherit; cursor: pointer;
}
.chat-mention:hover { background: var(--surface-2); }

/* Sandbox mode: a send that succeeds while it is on looks exactly like a send
   that reached the vendor, so the whole app carries the warning. */
.sandbox-strip {
  position: sticky; top: 0; z-index: 90;
  display: flex; align-items: center; gap: 9px;
  padding: 8px 16px;
  background: #b45309; color: #fff;
  font: 500 13px/1.4 system-ui, -apple-system, "Segoe UI", sans-serif;
}
.sandbox-strip svg { width: 16px; height: 16px; flex: none; }
.sandbox-strip span { flex: 1; min-width: 0; }
.sandbox-strip a { color: #fff; text-decoration: underline; white-space: nowrap; font-weight: 600; }
:root[data-theme="dark"] .sandbox-strip { background: #92400e; }

/* Wearing somebody else's account. A different colour from the sandbox amber on
   purpose: the two can be on at once, and they mean different things. */
.sandbox-strip.viewing { background: #6d28d9; z-index: 92; }
:root[data-theme="dark"] .sandbox-strip.viewing { background: #5b21b6; }
.strip-btn {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  padding: 5px 11px; border-radius: 999px; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.14); color: #fff;
  font: 600 12.5px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
  transition: background 120ms ease-out, transform 100ms ease-out;
}
.strip-btn:hover { background: rgba(255, 255, 255, 0.26); }
.strip-btn:active { transform: scale(0.97); }
.strip-btn[disabled] { opacity: 0.7; cursor: default; }
.strip-btn svg { width: 14px; height: 14px; }
@media (prefers-reduced-motion: reduce) { .strip-btn { transition: none; } }

/* Outbox — a stacked list, because this screen is read on a phone in a corridor. */
.ob-list { display: flex; flex-direction: column; }
.ob-row { display: flex; gap: 12px; align-items: flex-start;
  padding: 13px 14px; border-bottom: 1px solid var(--line); }
.ob-row:last-child { border-bottom: 0; }
.ob-main { flex: 1; min-width: 0; }
.ob-title { font-weight: 600; font-size: 14.5px; }
.ob-sub { font-size: 12.5px; margin-top: 2px; }
.ob-err { font-size: 12px; color: var(--critical); margin-top: 5px; word-break: break-word; }
.ob-side { display: flex; flex-direction: column; align-items: flex-end; gap: 7px; flex: none; }
@media (max-width: 460px) {
  .ob-row { flex-direction: column; gap: 9px; }
  .ob-side { flex-direction: row; align-items: center; width: 100%; }
}


/* ---- accessibility preferences -------------------------------------------
   Reduced motion is not "no feedback" — it is the same information delivered
   without vestibular movement. Transforms become cross-fades; the spring that
   settles the redaction box is skipped in JS and snaps instead. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .scrim > * { transform: none !important; }
  .toast { transform: none !important; }
}

/* Frosted chrome becomes solid rather than merely less blurred — a half-
   transparent bar is exactly what this preference is asking us not to show. */
@media (prefers-reduced-transparency: reduce) {
  .topbar, .tabbar, .side {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    background: var(--surface) !important;
  }
  .scrim { background: rgba(11, 11, 11, .72) !important; }
}

@media (prefers-contrast: more) {
  :root { --line: color-mix(in srgb, var(--ink) 38%, transparent); }
  .btn, .card, .f input, .f select, .f textarea { border-width: 2px; }
  .muted { color: var(--ink-2); }
  .topbar, .tabbar { backdrop-filter: none; background: var(--surface); }
}

/* ------------------------------------------------------------------- CRM
   The funnel list is rows, not a table: a rep reads it on a phone, and the
   four things they need — who, where they stand, when they were last seen,
   what is due — do not want to be columns. */
.crm-list { display: grid; gap: 8px; }
.crm-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
  padding: 12px 12px 12px 14px; cursor: pointer; font: inherit; color: inherit;
  transition: border-color .12s, box-shadow .12s, transform .06s;
}
.crm-row:hover { border-color: var(--line-2); box-shadow: var(--sh-1); }
.crm-row:active { transform: scale(.995); background: var(--surface-2); }
.crm-row-main { flex: 1; min-width: 0; display: grid; gap: 3px; }
.crm-row-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.crm-row-top b { font-size: 14.5px; font-weight: 640; letter-spacing: -.008em; }
.crm-row-sub { font-size: 13px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-row-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11.5px; }
.crm-row-go { flex: none; color: var(--ink-3); display: flex; }
.crm-row-go svg { width: 16px; height: 16px; }

/* a tag is smaller and quieter than a pill: it labels, it does not announce */
.tag {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px; border-radius: 5px;
  font-size: 11px; font-weight: 650; letter-spacing: .01em; white-space: nowrap;
}
.tag svg { width: 11px; height: 11px; }
.tag-key  { background: color-mix(in srgb, var(--s7) 16%, var(--surface)); color: var(--s7); }
.tag-due  { background: color-mix(in srgb, var(--critical) 13%, var(--surface)); color: var(--critical); }
.tag-out  { background: var(--surface-3); color: var(--ink-3); }
.tag-open { background: color-mix(in srgb, var(--s1) 13%, var(--surface)); color: var(--s1); }

/* chips as filters: the funnel's counts are the filter, so the two are one control */
button.chip { cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 550; color: inherit; }
.chip b {
  font-size: 11.5px; font-weight: 700; background: var(--surface); color: var(--ink-3);
  padding: 1px 6px; border-radius: 20px; margin-left: 2px;
}
.chip.on {
  background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 34%, transparent);
  color: var(--accent);
}
.chip.on b { background: color-mix(in srgb, var(--accent) 16%, var(--surface)); color: var(--accent); }
button.chip:active { transform: scale(.97); }

/* the stage control on a record header is the pill itself, made pressable */
.stage-btn {
  display: inline-flex; align-items: center; gap: 4px; background: none; border: 0;
  padding: 0; cursor: pointer; font: inherit; color: var(--ink-3);
}
.stage-btn:disabled { cursor: default; }
.stage-btn svg { width: 13px; height: 13px; }
.stage-btn:not(:disabled):active { transform: scale(.97); }

/* label / value pairs, two up where there is room */
.kv-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.kv { min-width: 0; }
.kv-k {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3);
}
.kv-v { font-size: 14px; margin-top: 2px; overflow-wrap: break-word; }

.pref-row {
  padding: 9px 0; border-bottom: 1px solid var(--line); gap: 10px; align-items: flex-start;
  color: inherit;
}
.pref-row:last-child { border-bottom: 0; }
a.pref-row:hover { background: var(--surface-2); }
a.pref-row:active { background: var(--surface-3); }
.task-late .kv-v, .task-late { color: inherit; }

/* the history timeline — a rail with a marker per entry */
.tl { display: grid; gap: 2px; }
.tl-row { display: flex; gap: 12px; position: relative; padding-bottom: 14px; }
.tl-row:last-child { padding-bottom: 0; }
.tl-row::before {
  content: ""; position: absolute; left: 13px; top: 28px; bottom: 0; width: 1px;
  background: var(--line);
}
.tl-row:last-child::before { display: none; }
.tl-dot {
  width: 27px; height: 27px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: var(--surface-2); color: var(--ink-3); z-index: 1;
}
.tl-dot svg { width: 14px; height: 14px; }
.tl-visit { background: color-mix(in srgb, var(--s3) 16%, var(--surface)); color: var(--s3); }
.tl-call  { background: color-mix(in srgb, var(--s1) 16%, var(--surface)); color: var(--s1); }
.tl-email { background: color-mix(in srgb, var(--s7) 16%, var(--surface)); color: var(--s7); }
.tl-meeting { background: color-mix(in srgb, var(--s4) 18%, var(--surface)); color: #7a5600; }
.tl-stage { background: var(--accent-soft); color: var(--accent); }
.tl-body { flex: 1; min-width: 0; padding-top: 3px; }
.tl-meta { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: 11.5px; }
.tl-meta b { font-size: 13px; font-weight: 640; }
.tl-text { font-size: 13.5px; line-height: 1.5; white-space: pre-wrap; margin-top: 2px; overflow-wrap: break-word; }

/* a count on a button, for the follow-ups badge */
.btn .cnt {
  font-size: 11px; font-weight: 700; background: var(--critical); color: #fff;
  padding: 1px 6px; border-radius: 20px; margin-left: 1px;
}

/* ---------------------------------------------------------------- onboarding
   The nudge lives above the page, not inside it, so it survives navigation.
   It is a banner and a pair of buttons and nothing else: onboarding that blocks
   a rep standing in a basement with a charge sheet is onboarding that gets the
   app uninstalled. */
.onb-host { max-width: 1180px; margin: 0 auto; padding: 14px 20px 0; }
.onb-host:empty { display: none; }
.onb-nudge { align-items: center; }
.onb-nudge .btn { white-space: nowrap; }
@media (max-width: 720px) {
  .onb-host { padding: 12px 16px 0; }
  .onb-nudge { align-items: flex-start; flex-wrap: wrap; }
  .onb-nudge > .row { width: 100%; }
}

.banner-good { background: color-mix(in srgb, var(--good) 15%, var(--surface)); color: var(--ink); }

.onb-step { padding: 11px 0; border-top: 1px solid var(--line); gap: 12px; align-items: flex-start; }
.onb-step:first-child { border-top: 0; }
.onb-tick {
  width: 21px; height: 21px; border-radius: 50%; flex: none; margin-top: 1px;
  border: 1.6px solid var(--line-2, var(--line)); display: grid; place-items: center;
  color: var(--surface);
}
.onb-tick.on { background: var(--good); border-color: var(--good); }
.onb-tick svg { width: 13px; height: 13px; }

/* ------------------------------------------------------- custody chain dots
   The chain reuses the CRM timeline, but here the dot carries a state rather
   than a kind: signed, refused, or still waiting on somebody. */
.tl-dot.on { background: color-mix(in srgb, var(--good) 20%, var(--surface)); color: var(--good-ink); }
.tl-dot.bad { background: color-mix(in srgb, var(--critical) 18%, var(--surface)); color: var(--critical); }
.tl-dot:not(.on):not(.bad):not([class*="tl-"]) {
  background: color-mix(in srgb, var(--warning) 22%, var(--surface));
}
input.fld.sig { font-family: "Segoe Script", "Bradley Hand", cursive; font-size: 19px; }

/* ---------------------------------------------------------------- the egg
   Shown once to whoever is made an owner, and to nobody else, ever. Gold on a
   dark grid — the ranks are named off the Grid, so this is where that shows.
   Everything animated is transform or opacity, and all of it stops dead under
   prefers-reduced-motion: a surprise should never be the thing that makes
   somebody feel sick. */
.egg-veil {
  position: fixed; inset: 0; z-index: 400;
  display: grid; place-items: center; padding: 24px;
  background:
    radial-gradient(1200px 600px at 50% 40%, rgba(212, 160, 23, 0.16), transparent 70%),
    linear-gradient(180deg, #05070b 0%, #0a0d14 100%);
  animation: egg-in 420ms ease-out both;
}
.egg-veil::before {                       /* the grid, receding */
  content: ""; position: absolute; inset: 0; opacity: 0.25; pointer-events: none;
  background-image:
    linear-gradient(rgba(120, 200, 255, 0.10) 1px, transparent 1px),
    linear-gradient(90deg, rgba(120, 200, 255, 0.10) 1px, transparent 1px);
  background-size: 48px 48px;
  mask-image: linear-gradient(180deg, transparent, #000 55%, transparent);
}
@keyframes egg-in { from { opacity: 0 } to { opacity: 1 } }

.egg-stage { position: relative; text-align: center; max-width: 560px; }

.egg-shell {
  width: 132px; height: 170px; margin: 0 auto 26px;
  border-radius: 50% 50% 48% 48% / 62% 62% 38% 38%;
  background: linear-gradient(155deg, #fde9a9 0%, #e8b73c 38%, #a9741a 72%, #6d4a10 100%);
  box-shadow:
    0 0 0 1px rgba(255, 240, 200, 0.35),
    0 10px 60px rgba(226, 170, 40, 0.45),
    inset -10px -16px 30px rgba(0, 0, 0, 0.35),
    inset 8px 10px 26px rgba(255, 245, 210, 0.5);
  cursor: pointer;
  animation: egg-float 4.5s ease-in-out infinite;
  transition: transform 160ms ease-out;
}
.egg-shell:hover { transform: scale(1.03); }
.egg-shell:active { transform: scale(0.98); }
@keyframes egg-float {
  0%, 100% { transform: translateY(0) rotate(-1.5deg); }
  50%      { transform: translateY(-12px) rotate(1.5deg); }
}
.egg-shell.cracked { animation: egg-crack 900ms cubic-bezier(.2,.9,.3,1) forwards; }
@keyframes egg-crack {
  0%   { transform: scale(1) rotate(0); }
  18%  { transform: scale(1.06) rotate(-4deg); }
  36%  { transform: scale(0.97) rotate(5deg); }
  60%  { transform: scale(1.12) rotate(-2deg); opacity: 1; }
  100% { transform: scale(2.2) rotate(0); opacity: 0; }
}

.egg-hint { color: #d8c48a; font: 500 13.5px/1.5 system-ui, sans-serif; letter-spacing: 0.02em; }
.egg-note {
  margin-top: 6px; padding: 26px 26px 22px; text-align: left;
  border-radius: 14px; border: 1px solid rgba(226, 190, 110, 0.35);
  background: rgba(10, 13, 20, 0.86);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
  animation: egg-rise 620ms cubic-bezier(.2,.9,.3,1) both;
}
@keyframes egg-rise { from { opacity: 0; transform: translateY(16px) scale(0.98) } to { opacity: 1; transform: none } }
.egg-note h2 {
  margin: 0 0 14px; color: #f3dca2;
  font: 650 23px/1.25 system-ui, sans-serif; letter-spacing: -0.015em;
}
.egg-note p { margin: 0 0 13px; color: #e6e3da; font: 400 15px/1.65 system-ui, sans-serif; }
.egg-note .egg-sign { margin-top: 18px; color: #d8c48a; font-weight: 600; }
.egg-veil .btn-egg {
  margin-top: 20px; width: 100%;
  background: linear-gradient(180deg, #edc455, #c6901d);
  color: #2a1d02; border: none; font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
  .egg-veil, .egg-shell, .egg-shell.cracked, .egg-note { animation: none !important; }
  .egg-shell.cracked { opacity: 0; }
}

/* ---------------------------------------------------------------- the bell
   A count in the corner that reads as part of the bar rather than stuck on it,
   and a row style for the panel behind it. The dot is only ever drawn when there
   is something unread — a badge showing "0" is a badge people stop reading. */
.bellbtn { position: relative; }
.bell-n {
  position: absolute; top: 1px; right: 1px; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 9px; background: var(--critical); color: #fff;
  font: 650 10.5px/16px system-ui, sans-serif; text-align: center;
  box-shadow: 0 0 0 2px var(--surface);
}
.notif {
  display: flex; gap: 11px; align-items: flex-start; width: 100%; text-align: left;
  padding: 11px 12px; border: 0; border-radius: var(--r); background: transparent;
  color: inherit; cursor: pointer; font: inherit;
  -webkit-tap-highlight-color: transparent;
}
.notif:hover { background: var(--plane); }
.notif:active { transform: scale(.995); }
.notif.unread { background: var(--accent-soft); }
.notif-i { flex: none; margin-top: 1px; color: var(--ink-3); }
.notif-i svg { width: 17px; height: 17px; }
.notif-b { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.notif-t { font-size: 13.8px; font-weight: 600; line-height: 1.35; }
.notif-d {
  font-size: 12.5px; color: var(--ink-3); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.notif-w { font-size: 11.5px; color: var(--ink-3); }
.notif-dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); margin-top: 6px;
}
@media (prefers-reduced-motion: reduce) { .notif:active { transform: none; } }

/* ---------------------------------------------------------------- help
   Numbered steps that read as steps. The counter sits in its own column so a
   step that wraps onto three lines still lines up under itself rather than
   stepping back under the number. */
.help-steps { list-style: none; counter-reset: hs; padding-left: 0; margin: 0 0 14px; }
.help-steps > li {
  counter-increment: hs; position: relative;
  padding-left: 30px; margin-bottom: 9px; line-height: 1.55;
}
.help-steps > li::before {
  content: counter(hs);
  position: absolute; left: 0; top: 1px;
  width: 21px; height: 21px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: #fff;
  font-size: 11.5px; font-weight: 700;
}
.help-body h4 {
  font-size: 14px; font-weight: 650; letter-spacing: -.01em;
  margin: 20px 0 7px; padding-top: 14px; border-top: 1px solid var(--line);
}
.help-body h4:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.help-body p { line-height: 1.6; }
.help-body ul { line-height: 1.6; }
.help-body > .banner { margin: 12px 0; }

/* ---------------------------------------------------------------- walkthroughs
   The coaching lives in a strip pinned to an edge, never over the content. The
   first version floated a card beside whatever was lit, which on a phone sat
   directly on top of the thing being taught — worst of all on the redaction
   step, the one step where you have to see the whole page. */

.tour { position: fixed; inset: 0; z-index: 2000; pointer-events: none; }


.tour-lit {
  position: relative; z-index: 2001;
  border-radius: var(--r-sm, 8px);
  box-shadow: 0 0 0 2.5px var(--accent), 0 0 0 7px color-mix(in srgb, var(--accent) 24%, transparent);
}

.tour-bar {
  position: fixed; z-index: 2002; pointer-events: auto;
  left: 50%; transform: translateX(-50%);
  bottom: calc(14px + var(--sab, env(safe-area-inset-bottom)));
  width: min(720px, calc(100vw - 24px));
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: saturate(170%) blur(16px);
  color: var(--ink);
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 10px 34px rgba(0,0,0,.30), 0 1px 4px rgba(0,0,0,.16);
  padding: 9px 11px;
  transition: bottom .22s cubic-bezier(.3,0,0,1), top .22s cubic-bezier(.3,0,0,1);
}
/* Moved out of the way when the lit element is itself near the bottom. */
.tour-bar.tour-bar-top { bottom: auto; top: calc(12px + var(--sat, env(safe-area-inset-top))); }

.tour-line { display: flex; align-items: center; gap: 10px; }
.tour-say {
  flex: 1; min-width: 0;
  font-size: 13px; line-height: 1.45; color: var(--ink-2);
}
.tour-say strong { color: var(--ink); font-weight: 650; }
.tour-step {
  flex: none;
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  color: var(--accent); font-variant-numeric: tabular-nums;
}
.tour-acts { display: flex; align-items: center; gap: 5px; flex: none; }
.tour-more {
  background: none; border: 0; cursor: pointer; padding: 3px 5px; border-radius: 6px;
  font: inherit; font-size: 11.5px; font-weight: 650; color: var(--ink-3);
}
.tour-more:hover { color: var(--ink); background: var(--surface-2); }
.tour-note {
  margin: 7px 0 1px; padding-left: 10px;
  border-left: 2px solid var(--line);
  font-size: 12.5px; line-height: 1.5; color: var(--ink-3);
}
.tour-x {
  background: none; border: 0; cursor: pointer; color: var(--ink-3);
  padding: 3px; border-radius: 6px; display: grid; place-items: center;
}
.tour-x svg { width: 15px; height: 15px; }
.tour-x:hover { color: var(--ink); background: var(--surface-2); }
.tour-wait {
  display: inline-flex; align-items: center; gap: 3px; white-space: nowrap;
  font-size: 11.5px; font-weight: 650; color: var(--accent);
}
.tour-wait svg { width: 13px; height: 13px; }

@media (max-width: 700px), (max-width: 960px) and (max-height: 520px) {
  /* above the tab bar, not over it */
  .tour-bar { bottom: calc(62px + var(--sab, env(safe-area-inset-bottom))); width: calc(100vw - 16px); padding: 8px 9px; }
  .tour-bar.tour-bar-top { top: calc(8px + var(--sat, env(safe-area-inset-top))); }
  .tour-line { flex-wrap: wrap; gap: 6px 8px; }
  .tour-say { flex: 1 1 100%; order: 2; font-size: 12.5px; }
  .tour-step { order: 1; }
  .tour-acts { order: 1; margin-left: auto; }
}

/* ── the construct record ────────────────────────────────────────────────────
   A dictation card is filled in standing up, on a phone, in a corridor. So the
   row is not a table: a table wide enough for level, side, three dimensions, an
   angle, a catalogue number and a lot number needs sideways scrolling to reach
   the lot number, and the lot number is the field that matters. Labelled fields
   that wrap keep every column reachable at any width. */
.backlink {
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--ink-3); font-size: 12.5px; font-weight: 550; margin-bottom: 4px;
}
.backlink:hover { color: var(--ink-2); }
.backlink svg { width: 14px; height: 14px; }
.tiny { font-size: 11.5px; }
input.fld-sm, select.fld-sm {
  padding: 6px 8px; font-size: 13px; width: 100%; max-width: none; min-height: 36px;
}

.cgroup + .cgroup { margin-top: 12px; }
.crow {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 0; border-top: 1px solid var(--line);
}
.crow:first-child { border-top: 0; }
.crow-n {
  flex: none; width: 20px; padding-top: 20px;
  font-size: 11.5px; font-weight: 700; color: var(--ink-3);
}
.crow-f {
  flex: 1; min-width: 0; display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
}
.cfield { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cfield-wide { grid-column: span 2; }
.cfield-l {
  font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 650;
}

.pickrow {
  display: flex; align-items: flex-start; gap: 10px; padding: 9px 2px;
  border-top: 1px solid var(--line); cursor: pointer;
}
.pickrow:first-of-type { border-top: 0; }
.pickrow input { margin-top: 3px; flex: none; }

/* ── operative imaging ──────────────────────────────────────────────────────
   Fixed tiles in a grid rather than a free-flowing list, so a before and its
   after stay the same size and sit next to each other. That adjacency is the
   whole value of the report: the eye does the comparison, not the caption. */
.imgrid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.imtile {
  border: 1px solid var(--line); border-radius: var(--r); overflow: hidden;
  background: var(--surface-2); display: flex; flex-direction: column;
}
.imtile-h {
  padding: 7px 10px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
  border-bottom: 1px solid var(--line); color: var(--ink-2);
}
.imtile.im-pre  .imtile-h { background: color-mix(in srgb, var(--s4) 16%, transparent); }
.imtile.im-post .imtile-h { background: color-mix(in srgb, var(--s3) 16%, transparent); }
.imtile-b {
  flex: 1; min-height: 92px; display: grid; place-items: center; gap: 5px;
  padding: 16px 10px; text-align: center;
}
.imtile-b svg { width: 22px; height: 22px; color: var(--ink-3); }
.imtile.on .imtile-b svg { color: var(--good-ink); }
.imtile-f {
  display: flex; gap: 6px; padding: 8px 10px; border-top: 1px solid var(--line);
}

/* ── the back control ────────────────────────────────────────────────────────
   §16 Wayfinding: every screen has to answer "how do I get out", and until now
   the answer was "open the menu and work it out". One control, top-left, in the
   same place on every screen — inside the bar on a phone, beside the wordmark on
   a desktop.

   It is hidden, not removed, when there is nowhere to go back to. Removing it
   would shift the wordmark sideways on every navigation, which is exactly the
   kind of movement the app was just fixed to stop doing. */
.backbtn { flex: none; margin-right: 2px; }
.backbtn svg { width: 19px; height: 19px; }
/* §1 Response: the press is acknowledged on pointer-down, not on release. */
.backbtn:active:not(:disabled) { transform: scale(0.92); }
.backbtn { transition: transform 110ms cubic-bezier(.3,0,0,1); }
@media (prefers-reduced-motion: reduce) {
  .backbtn { transition: none; }
  .backbtn:active:not(:disabled) { transform: none; }
}
/* One of the two is always the wrong one for the viewport. */
.back-side { display: none; }
.back-top  { display: inline-flex; }
@media (min-width: 701px) and (min-height: 521px), (min-width: 961px) {
  .back-side { display: inline-flex; margin-right: 8px; }
  .back-top  { display: none; }
}

/* ── the courier network ─────────────────────────────────────────────────────
   A job is read in a van, often one-handed, often in poor light. So a row is a
   single tall target with the two things that decide whether to take it —
   urgency and where it's going — set above everything else. */
.delrow {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 13px; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface-2); color: inherit;
  transition: transform 120ms cubic-bezier(.3,0,0,1), border-color 120ms ease;
}
.delrow:hover { border-color: var(--line-2); }
/* §1: the press is acknowledged on pointer-down, not on release. */
.delrow:active { transform: scale(0.988); }
.stack-sm { display: flex; flex-direction: column; gap: 8px; }

.bidrow {
  display: flex; align-items: flex-start; gap: 12px; padding: 11px 0;
  border-top: 1px solid var(--line);
}
.bidrow:first-child { border-top: 0; padding-top: 0; }

.kv { display: flex; flex-direction: column; gap: 7px; }
.kvrow { display: flex; gap: 12px; align-items: baseline; }
.kvrow > span:first-child { flex: none; width: 110px; font-size: 12.5px; }

.photostrip { display: flex; gap: 8px; flex-wrap: wrap; }
.photochip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 11px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--ink-2); font: inherit; font-size: 12.5px;
}
.photochip:hover { border-color: var(--line-2); color: var(--ink); }
.photochip svg { width: 14px; height: 14px; }
.photochip em { font-style: normal; color: var(--ink-3); }

/* The timeline. The dot column is what makes it read as a sequence rather than
   as a list — and the two custody lines sit under their own event, because a
   release and an acceptance are two separate acts with a gap between them. */
.timeline { display: flex; flex-direction: column; }
.tl-row { display: flex; gap: 11px; padding: 9px 0; position: relative; }
.tl-row::before {
  content: ""; position: absolute; left: 4.5px; top: 0; bottom: 0;
  width: 1px; background: var(--line);
}
.tl-row:first-child::before { top: 16px; }
.tl-row:last-child::before { bottom: calc(100% - 16px); }
.tl-dot {
  flex: none; width: 10px; height: 10px; border-radius: 50%; margin-top: 5px;
  background: var(--ink-3); position: relative; z-index: 1;
  box-shadow: 0 0 0 3px var(--surface);
}
.tl-picked_up .tl-dot, .tl-awarded .tl-dot { background: var(--s3); }
.tl-delivered .tl-dot { background: var(--good-ink); }
.tl-delayed .tl-dot, .tl-cancelled .tl-dot { background: var(--s2); }

.pickgrid { display: grid; gap: 7px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.pickchip {
  display: flex; align-items: flex-start; gap: 8px; padding: 9px 10px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface-2); cursor: pointer; font-size: 13px;
}
.pickchip:hover { border-color: var(--line-2); }
.pickchip input { margin-top: 2px; flex: none; }

@media (prefers-reduced-motion: reduce) {
  .delrow { transition: none; }
  .delrow:active { transform: none; }
}

/* Rating stars. Big enough to hit with a thumb in a van — §10 says a tap target
   wants ~10px of slack around it, and a 44px control is the floor on a phone. */
.starbtn {
  background: none; border: 0; cursor: pointer; font: inherit;
  font-size: 30px; line-height: 1; padding: 4px 6px; min-width: 44px; min-height: 44px;
  color: var(--line-2);
  transition: transform 110ms cubic-bezier(.3,0,0,1), color 110ms ease;
}
.starbtn.on { color: var(--s4); }
.starbtn:active { transform: scale(0.88); }
@media (prefers-reduced-motion: reduce) {
  .starbtn { transition: none; }
  .starbtn:active { transform: none; }
}

/* ===================================================== the command palette
   A floating material over a dimmed page (§12): this is a modal task, so the
   background is both dimmed and pushed back rather than merely blurred. It
   enters by materialising — blur and scale together — so it reads as a surface
   arriving rather than an opacity fade.

   Anchored to the top of the viewport, not centred, because the list grows
   downward as you type and a centred box would slide the first result out from
   under the cursor on every keystroke. */
.pal-scrim {
  position: fixed; inset: 0; z-index: 90;
  display: flex; justify-content: center; align-items: flex-start;
  padding: max(10vh, 48px) 16px 16px;
  background: color-mix(in srgb, var(--ink) 34%, transparent);
  backdrop-filter: blur(3px);
  animation: pal-in 160ms cubic-bezier(.22,.61,.36,1);
}
.pal {
  width: 100%; max-width: 620px;
  display: flex; flex-direction: column; min-height: 0;
  max-height: min(60vh, 560px);
  border-radius: 16px; overflow: hidden;
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  backdrop-filter: blur(24px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  border-top-color: color-mix(in srgb, #fff 42%, transparent);  /* light on the top edge */
  box-shadow: 0 28px 70px -12px color-mix(in srgb, var(--ink) 46%, transparent),
              0 2px 8px -2px color-mix(in srgb, var(--ink) 18%, transparent);
  animation: pal-rise 220ms cubic-bezier(.22,.61,.36,1);
}
@keyframes pal-in   { from { opacity: 0 } to { opacity: 1 } }
@keyframes pal-rise {
  from { opacity: 0; transform: translateY(-10px) scale(.985); filter: blur(5px); }
  to   { opacity: 1; transform: none; filter: none; }
}

.pal-top {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 15px; flex: none;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}
.pal-top svg { width: 18px; height: 18px; color: var(--ink-3); flex: none; }
.pal-in {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  color: var(--ink); font: inherit; font-size: 16.5px; letter-spacing: -.005em;
}
.pal-in::placeholder { color: var(--ink-3); }

.pal-list { overflow-y: auto; overscroll-behavior: contain; padding: 6px; min-height: 0; }
.pal-group {
  font-size: 11px; font-weight: 650; letter-spacing: .055em; text-transform: uppercase;
  color: var(--ink-3); padding: 10px 10px 4px;
}
.pal-row {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; border-radius: 9px; cursor: pointer;
  /* No transition on the highlight: it tracks the arrow keys, and an eased
     background lags a held keypress into a smear. */
}
.pal-row svg { width: 17px; height: 17px; color: var(--ink-3); flex: none; }
.pal-row.on { background: color-mix(in srgb, var(--accent) 13%, transparent); }
.pal-row.on svg, .pal-row.on .pal-l { color: var(--accent); }
.pal-l { font-size: 14.5px; font-weight: 520; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pal-s { font-size: 12.5px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pal-k {
  flex: none; font-size: 11px; font-weight: 600; color: var(--ink-3);
  padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
}
.pal-empty { padding: 26px 12px; text-align: center; color: var(--ink-3); font-size: 14px; }

.pal-hint {
  display: flex; gap: 16px; flex: none; padding: 9px 15px;
  border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
  font-size: 11.5px; color: var(--ink-3);
}
.pal-hint kbd {
  font: inherit; font-size: 11px; padding: 1px 5px; margin-right: 3px;
  border-radius: 4px; border: 1px solid var(--line);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}
/* On a phone there is no keyboard to tell anybody about, and the bar is better
   spent on results. */
@media (max-width: 620px) {
  .pal-scrim { padding-top: 24px; }
  .pal { max-height: 76svh; }
  .pal-hint { display: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .pal { background: var(--surface); backdrop-filter: none; }
  .pal-scrim { backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .pal { background: var(--surface); border-color: var(--ink-2); }
}
@media (prefers-reduced-motion: reduce) {
  .pal-scrim, .pal { animation: pal-in 140ms ease; }
  .pal { transform: none; filter: none; }
}

/* A row of filters above a long table. Wraps to one per line on a phone rather
   than squeezing six controls into a width none of them fit. */
.filters {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.filters .f { margin: 0; }

/* ---------------------------------------------------------------- plan the day */
.plan-ctl { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px 14px; align-items: end; }
.plan-ctl .f { margin: 0; }
.plan-grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 16px; align-items: start; }
.plan-map-card { padding: 0; overflow: hidden; position: sticky; top: 12px; }
.plan-canvas { height: clamp(320px, calc(100vh - 260px), 720px); }
@media (max-width: 700px), (max-width: 960px) and (max-height: 520px) {
  .plan-grid { grid-template-columns: 1fr; }
  .plan-map-card { position: static; order: -1; }
  .plan-canvas { height: 300px; }
}
.plan-stops { display: flex; flex-direction: column; }
.plan-stop { display: flex; gap: 12px; align-items: flex-start; padding: 12px 18px; border-top: 1px solid var(--line); }
.plan-stop.compact { padding: 9px 16px; gap: 10px; }
.plan-stop.is-done { opacity: .62; }
.plan-stop.is-skipped { opacity: .45; }
.plan-stop.is-skipped .plan-name { text-decoration: line-through; }
.plan-n { flex: none; width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent); color: #fff; font: 700 11.5px/1 system-ui, sans-serif; margin-top: 1px; }
.plan-n > svg { width: 13px; height: 13px; }
.plan-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.plan-name { font-weight: 600; color: var(--ink); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
a.plan-name:hover { text-decoration: underline; }
.plan-time { color: var(--ink-2); font-size: 13px; white-space: nowrap; margin-left: 10px; }
.plan-sub { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; }
.plan-why { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 3px; }
.plan-tag { font-size: 11.5px; line-height: 1; padding: 4px 7px; border-radius: 6px; color: var(--ink-2);
  background: color-mix(in srgb, var(--ink-3) 12%, transparent); }
.plan-tag.warn { color: var(--s4); background: color-mix(in srgb, var(--s4) 13%, transparent); }
.plan-tag.good { color: var(--s6); background: color-mix(in srgb, var(--s6) 13%, transparent); }
.plan-week { display: grid; gap: 12px; }
.cal-appt { border-style: dashed !important; background: transparent !important; }

/* ---------------------------------------------------------------- grouped sidebar
   Everyday screens stay one click away; everything else folds into groups that
   open on demand. Height animates through grid-template-rows 0fr → 1fr, which the
   browser re-targets from the current value if you click again mid-motion, so a
   group can be reversed half-open rather than finishing first (§3). */
.nav-search {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin: 2px 0 10px; padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink-3);
  font: 500 13px/1 inherit; cursor: pointer; text-align: left;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.nav-search:hover { color: var(--ink-2); border-color: color-mix(in srgb, var(--ink-3) 40%, var(--line)); }
.nav-search:active { transform: scale(.985); }
.nav-search svg { width: 15px; height: 15px; flex: none; }
.nav-search kbd {
  margin-left: auto; font: 600 10.5px/1 system-ui, sans-serif; letter-spacing: .02em;
  padding: 3px 5px; border-radius: 5px; background: var(--surface); border: 1px solid var(--line);
  color: var(--ink-3);
}
.nav-home { display: flex; flex-direction: column; gap: 1px; padding-bottom: 8px; margin-bottom: 6px;
  border-bottom: 1px solid var(--line); }

.nav-grp-h {
  width: 100%; display: flex; align-items: center; gap: 10px; padding: 8px 9px;
  border: 0; background: none; border-radius: var(--r-sm); cursor: pointer; text-align: left;
  color: var(--ink-2); font: 600 13.5px/1.2 inherit; letter-spacing: -.005em;
  transition: background .15s ease, color .15s ease;
}
.nav-grp-h:hover { background: var(--surface-2); color: var(--ink); }
.nav-grp-h:active { transform: scale(.985); }
.nav-grp-h > svg { width: 17px; height: 17px; flex: none; color: var(--ink-3); }
.nav-grp-t { flex: 1; min-width: 0; }
.nav-grp-h .nav-chev {
  width: 13px; height: 13px; color: var(--ink-3); opacity: .8;
  transition: transform .32s cubic-bezier(.32, .72, 0, 1);
}
.nav-grp.open .nav-chev { transform: rotate(90deg); }
/* A folded group that holds the screen you are on, or something waiting for you,
   says so without having to be opened. */
.nav-grp:not(.open):has(a.on) .nav-grp-h { color: var(--accent); }
.nav-grp:not(.open):has(a.on) .nav-grp-h > svg:first-child { color: var(--accent); }
.nav-grp-dot { display: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none; }
.nav-grp:not(.open):has(.cnt) .nav-grp-dot { display: block; }

.nav-grp-b {
  display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows .32s cubic-bezier(.32, .72, 0, 1), opacity .2s ease;
}
.nav-grp.open .nav-grp-b { grid-template-rows: 1fr; opacity: 1; }
.nav-grp-i {
  overflow: hidden; min-height: 0; display: flex; flex-direction: column; gap: 1px;
  margin-left: 17px; padding-left: 9px; border-left: 1px solid var(--line);
}
.nav-grp.open .nav-grp-i { padding-top: 2px; padding-bottom: 6px; }
.nav-grp-i a { padding: 7px 9px; font-size: 13.5px; }
.nav-grp-i a svg { width: 15px; height: 15px; }

/* mobile menu: the same groups, as tiles */
.menu-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; padding: 2px 4px 4px; }
.menu-grp a.menu-tile {
  flex-direction: column; justify-content: center; gap: 6px; min-height: 74px; padding: 10px 6px;
  border-radius: 14px; background: var(--surface-2); text-align: center;
  font-size: 12.5px; font-weight: 560; line-height: 1.15;
  transition: transform .12s ease, background .15s ease;
}
.menu-grp a.menu-tile svg { width: 20px; height: 20px; }
.menu-grp a.menu-tile span { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.menu-grp a.menu-tile:active { transform: scale(.96); background: var(--surface-3); }
.menu-grp a.menu-tile.on { background: var(--accent-soft); }
.menu-grp a.menu-tile .cnt { position: absolute; top: 6px; right: 8px; margin: 0; }
.menu-tile { position: relative; }
/* The sidebar's accent rail is for rows. On a tile it drew a bar half outside the
   tile's rounded edge; the tile already says "current" with its fill and colour. */
.menu-grp a.menu-tile::before { display: none; }

@media (prefers-reduced-motion: reduce) {
  .nav-grp-b, .nav-grp-h .nav-chev, .menu-grp a.menu-tile { transition: none; }
}
@media (prefers-contrast: more) {
  .nav-grp-i { border-left-color: var(--ink-3); }
  .nav-search { border-color: var(--ink-3); }
}

/* ------------------------------------------------ the menu pass, and its limits
   Everything above gives both menus the same vocabulary: a translucent surface,
   a group that is a hairline and a word, an accent rail that travels to whatever
   is current, and feedback on the press rather than the release.

   Three accessibility signals are honoured rather than assumed, because a
   translucent, animated menu is exactly the kind of thing each of them is about
   (apple-design §14). */

/* Reduced motion: the rail still marks the current screen, it just stops
   travelling, and nothing scales under a finger. */
@media (prefers-reduced-motion: reduce) {
  .nav a, .nav a::before, .menu-grp a, .menu-grp a::before { transition: none; }
  .nav a:active, .menu-grp a:active { transform: none; }
}

/* Reduced transparency: the material becomes a surface. The blur is what costs
   legibility, so the blur is what goes. */
@media (prefers-reduced-transparency: reduce) {
  .side, .menu-panel { backdrop-filter: none; background: var(--surface); }
}

/* More contrast: a defined edge rather than a soft fill, and the rail does the
   work of saying which row is current. */
@media (prefers-contrast: more) {
  .side, .menu-panel { backdrop-filter: none; background: var(--surface); }
  .nav a.on, .menu-grp a.on {
    background: transparent; outline: 1px solid var(--accent); outline-offset: -1px;
  }
}

/* The sheet's own scroll edge, so a list that continues below the fold says so.
   This is the thing Binika's screenshot could not have told us either way. */
.menu-scroll {
  mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 20px), transparent 100%);
}

/* "A new version is ready." Not the amber of the sandbox warning — nothing is
   wrong, there is simply something newer. */
.sandbox-strip.update { background: var(--accent); }
:root[data-theme="dark"] .sandbox-strip.update { background: var(--accent); }
.sandbox-strip.update .btn {
  background: rgba(255,255,255,.18); color: #fff; border-color: rgba(255,255,255,.35);
  flex: none;
}

/* ---------------------------------------------------------------- scanning */
.scan-bar {
  display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px 12px;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r);
  box-shadow: var(--sh-1); transition: box-shadow .18s, border-color .18s, transform .18s;
}
.scan-bar > svg { width: 18px; height: 18px; color: var(--ink-3); flex: none; }
.scan-bar:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.scan-bar.got { border-color: var(--good); box-shadow: 0 0 0 4px color-mix(in srgb, var(--good) 22%, transparent); transform: scale(1.006); }
.scan-input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--ink);
  font: 500 16px/1.3 var(--font); padding: 8px 0; }
.scan-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); flex: none;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 45%, transparent); animation: scan-pulse 2.4s infinite; }
@keyframes scan-pulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 45%, transparent); }
  70% { box-shadow: 0 0 0 7px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
.scan-cam { flex: none; }
.scan-stage { position: relative; border-radius: var(--r); overflow: hidden; background: #000; aspect-ratio: 4 / 3; max-height: 60vh; }
.scan-video { width: 100%; height: 100%; object-fit: cover; display: block; }
.scan-reticle { position: absolute; inset: 22% 12%; border: 2px solid rgba(255,255,255,.85); border-radius: 12px;
  box-shadow: 0 0 0 100vmax rgba(0,0,0,.28); transition: border-color .15s, transform .2s cubic-bezier(.3,1.4,.5,1); }
.scan-reticle.flash { border-color: var(--good); transform: scale(1.04); }
.scan-status { font-size: 13px; color: var(--ink-2); text-align: center; min-height: 18px; }
.scan-log { display: grid; gap: 4px; max-height: 140px; overflow: auto; }
.scan-log-row { display: flex; gap: 8px; align-items: center; font: 12.5px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--ink-2); animation: bio-in .22s ease-out; }
.scan-log-row svg { width: 14px; height: 14px; color: var(--good); }

/* ---------------------------------------------------------------- biologics */
.bio-tabs { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.bio-tabs::-webkit-scrollbar { display: none; }
.bio-tabs .tab-l { white-space: nowrap; }
.bio-feed { display: grid; gap: 6px; }
.bio-hit { display: flex; gap: 10px; align-items: center; padding: 9px 12px; border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--sh-1); animation: bio-in .24s cubic-bezier(.3,1.3,.5,1); }
.bio-hit > svg { width: 18px; height: 18px; flex: none; }
.bio-hit.ok > svg { color: var(--good); }
.bio-hit.bad { background: color-mix(in srgb, var(--critical) 8%, var(--surface)); }
.bio-hit.bad > svg { color: var(--critical); }
.bio-hit.warn > svg { color: var(--s4); }
.bio-hit .grow small { display: block; color: var(--ink-3); }
@keyframes bio-in { from { opacity: 0; transform: translateY(-4px) scale(.98); } to { opacity: 1; transform: none; } }
.bio-count { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
.bio-count .tile .v { font-variant-numeric: tabular-nums; transition: transform .2s cubic-bezier(.3,1.5,.5,1); }
.bio-count .tile.bump .v { transform: scale(1.12); }
.bio-label { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
.bio-slot { border: 1px dashed var(--line-2); border-radius: var(--r-sm); padding: 8px 10px; min-height: 52px; }
.bio-slot.on { border-style: solid; border-color: var(--accent); background: var(--accent-soft); }
.bio-slot .k { font-size: 11px; font-weight: 650; color: var(--ink-3); text-transform: uppercase; letter-spacing: .03em; }
.bio-slot .v { font: 600 14px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; }
.bio-slot .v.empty { color: var(--ink-3); font-weight: 400; font-family: var(--font); }
.bio-sig { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: var(--r-sm); background: var(--surface-2); }
.bio-sig svg { color: var(--good-ink); flex: none; margin-top: 2px; }
.bio-sig small { display: block; color: var(--ink-3); }
.bio-sig .mono { font-size: 11px; color: var(--ink-3); overflow-wrap: anywhere; }
.bio-pf { display: inline-flex; gap: 2px; background: var(--surface-2); border-radius: 8px; padding: 2px; }
.bio-pf button { border: 0; background: transparent; padding: 5px 10px; border-radius: 6px; font: 600 12.5px var(--font); color: var(--ink-2); cursor: pointer; }
.bio-pf button.on.pass { background: var(--good); color: #fff; }
.bio-pf button.on.fail { background: var(--critical); color: #fff; }
.bio-pf button.on.na { background: var(--surface); color: var(--ink); box-shadow: var(--sh-1); }
.bio-pf button:active { transform: scale(.96); }
.bio-steps { display: flex; gap: 6px; flex-wrap: wrap; }
.bio-step { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 99px; font-size: 12.5px; font-weight: 600;
  background: var(--surface-2); color: var(--ink-3); }
.bio-step.done { background: color-mix(in srgb, var(--good) 14%, var(--surface)); color: var(--good-ink); }
.bio-step.now { background: var(--accent-soft); color: var(--accent); }
.bio-step svg { width: 14px; height: 14px; }
.tbl input.fld-sm { width: 70px; }
@media (max-width: 640px) {
  .bio-label { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .scan-input { font-size: 17px; }
}
@media (prefers-reduced-motion: reduce) {
  .scan-dot, .bio-hit, .scan-log-row { animation: none; }
  .scan-bar, .scan-reticle, .bio-count .tile .v { transition: none; }
}

/* ---------------------------------------------------------------- agreements */
tr.bio-changed > td { background: color-mix(in srgb, var(--s4) 9%, transparent); }


/* ================================================================ the phone shell
   Header and tab bar that never move, and an app that never zooms.

   Every earlier fix kept the same structure — a scrolling DOCUMENT with a sticky
   header on top and a fixed tab bar underneath — and tuned it: measured safe
   areas, removed compositor hints, clipped the root. Each helped and none could
   finish the job, because on a phone the document is the one thing the browser
   reserves the right to move: Safari collapses its toolbar as the document
   scrolls, rubber-bands it past the ends, slides it under the keyboard, and
   zooms it on a pinch or a focused field. Anything attached to the document's
   scroll goes along for the ride for a frame or two, which is the drift.

   So on a phone the document no longer scrolls at all. The body is pinned to the
   screen and laid out as a column: header, then a scrolling region, then the tab
   bar. Only .scroller scrolls, the header and tab bar are plain rows with nothing
   to be sticky or fixed against, and there is no document movement left for the
   browser to animate. Desktop is untouched. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
@media (max-width: 700px), (max-width: 960px) and (max-height: 520px) {
  html, body { height: 100%; overflow: hidden; }
  html { touch-action: manipulation; }          /* no double-tap zoom */
  body {
    /* Pinned to the top, sized by height rather than by bottom: 0. On some iOS
       versions the home-screen app's layout viewport stops short of the screen,
       so `inset: 0` left a band of empty canvas under the tab bar. --app-h is set
       by fitShell() in lib.js to the real screen height when installed. */
    position: fixed; top: 0; left: 0; right: 0; width: 100%;
    height: 100%; height: var(--app-h, 100dvh);
    display: flex; flex-direction: column;
    overscroll-behavior: none;
  }
  /* the strips (sandbox, viewing-as, new version) are rows above the app */
  body > .sandbox-strip { position: relative; top: auto; flex: none; }
  body > .sandbox-strip:first-child { padding-top: calc(8px + var(--sat, env(safe-area-inset-top))); }
  #app {
    /* min-height overrides the desktop 100svh: a column row taller than the
       body pushed the tab bar's labels off the bottom of the screen */
    flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  }
  #app > .shell { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .auth { min-height: 100%; }
  .shell > .content { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .shell .topbar { position: relative; top: auto; flex: none; }
  body:has(> .sandbox-strip) .shell .topbar { padding-top: 10px; }
  .shell .scroller {
    flex: 1 1 auto; min-height: 0;
    overflow-x: hidden; overflow-y: auto;
    overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  }
  .shell .main { padding-bottom: 28px; }
  #app > .tabbar { position: relative; left: auto; right: auto; bottom: auto; flex: none; transform: none; will-change: auto; }
  /* iOS zooms into any field under 16px when it gets focus */
  input:not([type="checkbox"]):not([type="radio"]):not(.sig), select, textarea { font-size: max(16px, 1em) !important; }
}

/* ------------------------------------------------------------------ iPad
   Phones (and a phone held sideways) keep the phone shell. Anything wider and
   taller than a phone -- an iPad in either orientation, a narrow desktop window --
   gets the sidebar. iPad portrait is 744-1032px wide, which used to fall under
   the old 860px phone breakpoint and got a stretched phone. iPad Split View at
   half width or less is under 700px and correctly gets the phone shell. */

/* Tablet width: a slimmer sidebar and slightly tighter gutters, so the content
   column keeps room for a table at 744px (iPad mini portrait). */
@media (min-width: 701px) and (max-width: 1180px) and (min-height: 521px) {
  :root { --nav-w: 212px; }
  .wrap { padding: 18px 18px 0; }
  .side-top { padding: 16px 14px 10px; }
  .tiles { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
  .card-h { flex-wrap: wrap; }
}

/* Tablet portrait: the content column is 530-820px, so the side-by-side
   screens stack the way they do on a phone. */
@media (min-width: 701px) and (max-width: 1032px) and (min-height: 521px) {
  .plan-grid { grid-template-columns: 1fr; }
  .plan-map-card { position: static; order: -1; }
  .plan-canvas { height: 360px; }
  .chat-grid { grid-template-columns: 1fr; }
  .tpl-grid { grid-template-columns: 1fr; }
  /* a table sizes to its content and scrolls sideways inside its card, so an
     order number is never broken across two lines */
  table.tbl { width: max-content; min-width: 100%; }
  table.tbl th, table.tbl td { max-width: 240px; }
  /* a heading keeps its line; its buttons wrap underneath rather than squeezing it */
  .pagehead .row, .pagehead .row-wrap { flex-wrap: wrap; }
}

/* A finger, not a mouse, at any size above a phone: targets of at least 44pt,
   fields at 16px so iOS never zooms on focus, and rows a thumb can hit. The
   phone shell already does all of this for itself. */
@media (pointer: coarse) and (min-width: 701px) and (min-height: 521px), (pointer: coarse) and (min-width: 961px) {
  .btn { min-height: 44px; }
  .btn-sm { min-height: 36px; }
  .nav a { min-height: 44px; }
  input.fld, select.fld { min-height: 44px; }
  input:not([type="checkbox"]):not([type="radio"]):not(.sig), select, textarea { font-size: max(16px, 1em); }
  table.tbl td { padding-top: 12px; padding-bottom: 12px; }
  table.tbl tbody tr.clickable:active, .nav a:active { background: var(--surface-2); }
  html { touch-action: manipulation; }
}

/* The iPad app draws edge to edge (contentInset "never"), so the status bar and
   the home indicator sit over the page. In a browser these are 0 and nothing moves. */
@media (min-width: 701px) and (min-height: 521px), (min-width: 961px) {
  .side-top { padding-top: max(16px, calc(env(safe-area-inset-top) + 10px)); }
  .side-btm { padding-bottom: max(10px, calc(env(safe-area-inset-bottom) + 6px)); }
  .main { padding-top: env(safe-area-inset-top); }
}
