/* Control — Horizon-inspired, light + dark, no framework, no build step.

   Layout contract (v0.5.0):
     · ≥ 721px — classic dense tables, horizontal nav.
     · ≤ 720px — every `table.rt` becomes a stack of CARDS (driven by the
       data-label attributes lib/table.js stamps on each <td>), and the nav
       becomes a slide-in drawer. Controls grow to 44px touch targets and
       inputs to 16px, because anything smaller makes iOS Safari zoom the
       page on focus. */

:root {
  --bg: #f5f6f7;
  --card: #ffffff;
  --ink: #1d2d3e;
  --muted: #556b82;
  --line: #d9dee3;
  --accent: #0070f2;
  --accent-ink: #ffffff;
  --good: #188918;
  --warn: #e76500;
  --bad: #d20a0a;
  --chip-bg: #eaf2fb;
  --radius: 10px;
  --shadow: 0 1px 3px rgba(29, 45, 62, 0.12);
  --shadow-lg: 0 10px 40px rgba(29, 45, 62, 0.22);
  --tap: 34px; /* control height — grows to 44px on touch layouts */
}
/* Dark palette. Two selectors on purpose (v0.6.0 manual theme, lib/theme.js):
   the media query applies ONLY when there is no manual override
   (`:not([data-theme])`), and `[data-theme="dark"]` forces dark regardless of
   the OS. A manual "light" pick just sets data-theme="light", which excludes
   the media block and falls back to the default `:root` light vars — so the
   choice wins in both directions without re-declaring light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg: #12171c;
    --card: #1d2329;
    --ink: #eaecee;
    --muted: #9aa8b5;
    --line: #333b44;
    --accent: #4db1ff;
    --accent-ink: #0e1216;
    --chip-bg: #24313e;
    --shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.6);
  }
}
:root[data-theme="dark"] {
  --bg: #12171c;
  --card: #1d2329;
  --ink: #eaecee;
  --muted: #9aa8b5;
  --line: #333b44;
  --accent: #4db1ff;
  --accent-ink: #0e1216;
  --chip-bg: #24313e;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.6);
}

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "72", sans-serif;
  -webkit-text-size-adjust: 100%;
}
code { font-size: 0.92em; }

/* Content links: keep the accent color even when visited — the browser's
   default visited-purple is unreadable in dark mode. More specific rules
   (.topbar nav a, a.kpi, .back) still override. */
a, a:visited { color: var(--accent); }
a:hover { text-decoration: underline; }

/* ─────────────────────────── top bar + nav ─────────────────────────── */

.topbar {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 10px 20px;
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
  background: var(--card);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}
.brand { white-space: nowrap; }
.topbar nav { display: flex; gap: 4px; flex: 1; flex-wrap: wrap; }
.topbar nav a {
  padding: 6px 12px;
  border-radius: 8px;
  color: var(--muted);
  text-decoration: none;
}
.topbar nav a.active { background: var(--chip-bg); color: var(--accent); font-weight: 600; }
.topbar nav a:hover { color: var(--accent); text-decoration: none; }
.topbar-end { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.nav-toggle { display: none; font-size: 18px; line-height: 1; }
.nav-scrim { display: none; }
.pal-open { font-size: 17px; line-height: 1; padding: 4px 10px; }

/* ─────────────────────────── layout ─────────────────────────── */

.view {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
  padding-bottom: max(20px, env(safe-area-inset-bottom));
}
/* Table-heavy pages (Licenses, Extensions) use more of the screen so the wide
   column set fits without a horizontal scroll. Bounded so it still has margins
   on very large monitors. */
.view.wide { max-width: min(1760px, 100% - 32px); }
.muted { color: var(--muted); }
.err { color: var(--bad); }
.hidden { display: none !important; }
.empty { color: var(--muted); padding: 40px; text-align: center; }
.nowrap { white-space: nowrap; }
.mono-id { font-size: 12px; word-break: break-all; }

h2 { margin: 6px 0 14px; font-size: 20px; }
h3 { margin: 20px 0 8px; font-size: 15px; }
.row-flex { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.spread { justify-content: space-between; }

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 16px;
  margin-bottom: 16px;
}

/* ─────────────────────────── tables ─────────────────────────── */

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; }
tr.click { cursor: pointer; }
tr.click:hover td { background: var(--chip-bg); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Action buttons WRAP into a fixed-width block rather than forcing the table
   wider. A licence row carries five (revoke · renew · email · copy · delete);
   keeping them on one line pushed the whole column off-screen at laptop
   widths. min-width matters as much as max-width — without it the table
   layout squeezes the cell and every button lands on its own line. */
td.actions { display: flex; flex-wrap: wrap; gap: 4px; min-width: 200px; max-width: 210px; }
td.actions > * { flex: 0 0 auto; }
@media (min-width: 721px) {
  /* Compact in-table controls: full-size buttons are for forms, not for the
     fifth button in a table row. */
  td.actions button { padding: 3px 8px; font-size: 12px; min-height: 26px; }
  td .lic-cust, td .ct-status { padding: 3px 6px; font-size: 12px; min-height: 26px; }
}
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--accent); }
th.sorted { color: var(--accent); }
tr.editing td { background: var(--chip-bg); }
tr.flash td { background: color-mix(in srgb, var(--accent) 14%, var(--card)); }

.chip {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 99px;
  background: var(--chip-bg);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.chip.good { color: var(--good); }
.chip.warn { color: var(--warn); }
.chip.bad { color: var(--bad); }
.chip.dim { color: var(--muted); }
.ent { font-size: 11px; margin-right: 3px; }

/* Entitlement overflow: a full-catalogue licence carries 13 chips, which used
   to stretch a table row to 227px tall on a phone. Past the cap they collapse
   behind "+n" and expand on click — no JS, just <details>. */
.ent-more { display: inline; }
.ent-more > summary { list-style: none; cursor: pointer; display: inline; }
.ent-more > summary::-webkit-details-marker { display: none; }
.ent-more .chip.more { background: transparent; border: 1px dashed var(--line); color: var(--muted); }
.ent-more[open] > summary { display: none; }
.ent-all { display: inline; }

/* Engine URL: truncated inline, full value revealed on TAP (a title= tooltip
   is unreachable on a touch screen). */
.url-more { display: inline; }
.url-more > summary { list-style: none; cursor: pointer; display: inline; }
.url-more > summary::-webkit-details-marker { display: none; }
.url-full { display: inline-block; max-width: 100%; word-break: break-all; }
.engine-url {
  display: inline-block;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  border-bottom: 1px dotted var(--line);
}
.url-more[open] .engine-url { display: none; }

/* Cap the in-table customer dropdown so one long customer name can't force the
   whole licenses table to scroll sideways (the native select clips the rest). */
.lic-cust { max-width: 200px; }
/* Bulk-select (admin): checkbox column + the action bar. */
.cb-col { width: 32px; text-align: center; padding-left: 4px; padding-right: 4px; }
.cb-col input { cursor: pointer; width: 16px; height: 16px; }
.bulk-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  padding: 8px 12px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--chip-bg);
  flex-wrap: wrap;
  position: sticky;
  top: 60px;
  z-index: 8;
}
.engine-td { white-space: nowrap; }
.engine-td .engine-edit { padding: 2px 6px; margin-left: 4px; opacity: 0.55; }
.engine-td .engine-edit:hover { opacity: 1; }
.engine-td .engine-input {
  min-width: 240px;
  padding: 4px 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}
/* URL + ✎ as one inline group (keeps the pencil beside the URL, not below it
   on the mobile card — see licenses.js). */
.engine-cell { display: inline-flex; align-items: baseline; min-width: 0; max-width: 100%; }

/* ─────────────────────────── controls ─────────────────────────── */

button, .btn {
  font: inherit;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  padding: 6px 12px;
  border-radius: 8px;
  cursor: pointer;
  min-height: var(--tap);
}
button:hover { border-color: var(--accent); color: var(--accent); }
button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
button.primary:hover { opacity: 0.9; color: var(--accent-ink); }
button.danger { color: var(--bad); }
button.danger:hover { border-color: var(--bad); }
button.confirming { background: var(--bad); border-color: var(--bad); color: #fff; }
button.ghost { border-color: transparent; color: var(--muted); }
button:disabled { opacity: 0.5; cursor: default; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

input, select, textarea {
  font: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 10px;
  min-height: var(--tap);
  max-width: 100%;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
input[type="checkbox"] { min-height: 0; }
/* Editable install-link base inputs (Settings) — wide enough for a full URL,
   flex so Save / Reset sit beside it and wrap under it on a phone. */
.base-input { flex: 1 1 260px; min-width: 200px; max-width: 460px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
label.f { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
/* The field itself must NOT inherit the 12px label size — under 16px iOS
   Safari zooms the whole page when the field takes focus. */
label.f input, label.f select, label.f textarea { font-size: 14px; color: var(--ink); }
/* `.fgrid` is the same field grid as `form.grid`, but usable on an inner div —
   which is what lets one form hold several field groups (v0.8.6 issue form). */
form.grid, .fgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; align-items: end; }
.checks { display: flex; gap: 10px; flex-wrap: wrap; padding: 6px 0; }
.check-all { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.checks label { display: flex; gap: 6px; align-items: center; font-size: 13px; color: var(--ink); }
.f-legend { font-size: 13px; }

/* Optional-fields disclosure inside a form (v0.8.6). Same caret grammar as the
   hint callout, but boxed rather than accented — it is a section of the form,
   not advice. The summary carries a live list of what is set inside, so
   folding can never hide a filled field. */
details.adv { border: 1px solid var(--line); border-radius: 8px; margin-top: 10px; }
details.adv > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 6px 10px;
  min-height: var(--tap);
  font-size: 13px;
  color: var(--ink);
}
details.adv > summary::-webkit-details-marker { display: none; }
details.adv > summary::before { content: '▸'; color: var(--muted); font-size: 11px; transition: transform 0.15s ease; }
details.adv[open] > summary::before { transform: rotate(90deg); }
details.adv[open] > summary { border-bottom: 1px solid var(--line); }
.adv-body { padding: 10px; }
@media (prefers-reduced-motion: reduce) { details.adv > summary::before { transition: none; } }

.jwt-box {
  width: 100%;
  min-height: 90px;
  font-family: ui-monospace, monospace;
  font-size: 11px;
  word-break: break-all;
}
.callout {
  border-left: 3px solid var(--warn);
  background: var(--chip-bg);
  padding: 8px 12px;
  border-radius: 0 8px 8px 0;
  margin: 10px 0;
  font-size: 13px;
}
/* Collapsible hint callout (lib/hints.js): the summary is a click-to-fold row
   with a rotating caret; the body carries the padding so a folded hint is a
   single tidy line. */
details.callout.hint { padding: 0; }
details.callout.hint > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font-weight: 600;
}
details.callout.hint > summary::-webkit-details-marker { display: none; }
details.callout.hint > summary::before {
  content: '▸';
  color: var(--muted);
  font-size: 11px;
  transition: transform 0.15s ease;
}
details.callout.hint[open] > summary::before { transform: rotate(90deg); }
details.callout.hint[open] > summary { padding-bottom: 2px; }
.hint-body { padding: 0 12px 9px 12px; }
.toolbar { display: flex; gap: 10px; align-items: center; margin: 0 0 10px; flex-wrap: wrap; }
.toolbar input.search { flex: 1; min-width: 200px; max-width: 380px; }
.toolbar .count { color: var(--muted); font-size: 12px; }
.danger-zone { border-color: color-mix(in srgb, var(--bad) 45%, var(--line)); }
.back { text-decoration: none; color: var(--accent); display: inline-block; margin-bottom: 8px; }
.scroll-x { overflow-x: auto; }

/* ─────────────────────────── KPIs + attention ─────────────────────────── */

.kpis { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
.kpi { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 16px; min-width: 130px; }
.kpi .v { font-size: 22px; font-weight: 700; }
.kpi .v.v-money, .kpi .v.v-sm { font-size: 17px; } /* v-sm: non-money text KPIs (e.g. "3d ago") */
.kpi .l { font-size: 12px; color: var(--muted); }
a.kpi { display: block; text-decoration: none; color: inherit; }
a.kpi:hover { border-color: var(--accent); text-decoration: none; }
.kpi.warn .v { color: var(--warn); }
.kpi.bad .v { color: var(--bad); }
.cols2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
.cols2 .card { margin-bottom: 0; overflow-x: auto; }

/* The "needs attention" strip — the daily digest's checks, in the app. */
.attention {
  border: 1px solid var(--line);
  border-left: 3px solid var(--warn);
  background: var(--card);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 10px 14px;
  margin-bottom: 16px;
}
.attention.ok { border-left-color: var(--good); }
.attention-items { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
a.attention-item { text-decoration: none; padding: 5px 12px; }
a.attention-item:hover { text-decoration: underline; }

/* ─────────────────────────── charts ─────────────────────────── */

.bar-row {
  display: grid;
  grid-template-columns: minmax(120px, 1.2fr) 3fr minmax(110px, auto);
  gap: 12px;
  align-items: center;
  padding: 5px 0;
}
.bar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-track { background: var(--chip-bg); border-radius: 99px; height: 10px; overflow: hidden; }
.bar-fill { height: 100%; border-radius: 99px; background: var(--accent); transition: width 0.3s ease; }
.bar-fill.good { background: var(--good); }
.bar-fill.warn { background: var(--warn); }
.bar-fill.bad { background: var(--bad); }
.bar-fill.dim { background: var(--muted); }
.bar-value { text-align: right; font-variant-numeric: tabular-nums; font-size: 13px; white-space: nowrap; }

.spark { width: 100%; height: 90px; display: block; overflow: visible; }
.spark-line { fill: none; stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
.spark-area { fill: var(--accent); opacity: 0.13; stroke: none; }
.spark-dot { fill: var(--accent); }
.spark-axis { display: flex; justify-content: space-between; color: var(--muted); font-size: 11px; margin-top: 2px; }

/* ─────────────────────────── skeletons ─────────────────────────── */

.skel-wrap { padding: 6px 0; }
.skel-row {
  height: 34px;
  border-radius: 8px;
  margin-bottom: 10px;
  background: linear-gradient(90deg, var(--chip-bg) 25%, var(--card) 50%, var(--chip-bg) 75%);
  background-size: 300% 100%;
  animation: skel 1.3s ease-in-out infinite;
}
@keyframes skel { from { background-position: 150% 0; } to { background-position: -150% 0; } }
@media (prefers-reduced-motion: reduce) {
  .skel-row { animation: none; }
  .bar-fill { transition: none; }
}

/* ─────────────────────────── gate + toast ─────────────────────────── */

.gate {
  position: fixed;
  inset: 0;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: 16px;
}
.gate-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 30px 34px;
  width: min(420px, 100%);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.gate-card h1 { margin: 0; font-size: 22px; }
.gate-card input { padding: 10px 12px; font-size: 16px; }

.toast {
  position: fixed;
  bottom: max(22px, env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--bg);
  padding: 9px 18px;
  border-radius: 99px;
  box-shadow: var(--shadow);
  z-index: 60;
  max-width: 90vw;
  text-align: center;
}
.toast.bad { background: var(--bad); color: #fff; }

/* Role badge (two-token roles, docs/Control.md §3.2) */
#who { white-space: nowrap; font-size: 0.9em; }

/* ─────────────────────────── command palette ─────────────────────────── */

.palette { position: fixed; inset: 0; z-index: 80; }
.pal-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.45); }
.pal-box {
  position: relative;
  margin: 10vh auto 0;
  width: min(640px, calc(100% - 24px));
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  max-height: 70vh;
}
#pal-input {
  border: none;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  padding: 14px 16px;
  font-size: 16px;
}
#pal-input:focus { outline: none; }
.pal-list { overflow-y: auto; flex: 1; }
.pal-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 16px;
  cursor: pointer;
  border-left: 3px solid transparent;
  touch-action: manipulation; /* no double-tap zoom / 300ms delay on a result row */
}
/* Roomier rows + non-interactive children on touch, so a tap always resolves to
   the row itself (the click is delegated to #pal-list by data-i). */
@media (max-width: 720px) {
  .pal-item { padding: 12px 16px; }
  .pal-item > * { pointer-events: none; }
}
.pal-item.active { background: var(--chip-bg); border-left-color: var(--accent); }
.pal-item.armed { background: color-mix(in srgb, var(--bad) 16%, var(--card)); border-left-color: var(--bad); }
.pal-confirm { color: var(--bad); font-weight: 600; }
.pal-icon { width: 20px; text-align: center; flex: 0 0 auto; }
.pal-label { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pal-hint { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.pal-kind { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; margin-left: auto; flex: 0 0 auto; }
.pal-empty { padding: 24px; text-align: center; color: var(--muted); }
.pal-foot { border-top: 1px solid var(--line); padding: 7px 16px; color: var(--muted); font-size: 11px; }
.pal-foot kbd {
  background: var(--chip-bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  font-family: inherit;
  font-size: 10px;
}

/* Anchor styled as a button (Inspect link, chain "open", ld-inspect). */
a.btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; color: var(--ink); }
a.btn:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }

/* ─────────────────── JWT inspector verdict + digest ─────────────────── */

.verdict-card { border-left: 4px solid var(--line); }
.verdict-card.good { border-left-color: var(--good); }
.verdict-card.warn { border-left-color: var(--warn); }
.verdict-card.bad { border-left-color: var(--bad); }
.verdict-head { display: flex; gap: 12px; align-items: center; }
.verdict-icon { font-size: 26px; }
.digest-list { margin: 4px 0 0; padding-left: 18px; }
.digest-list li { margin: 3px 0; }

/* ─────────────────── top loading bar ─────────────────── */

.progress {
  position: fixed;
  top: 0; left: 0;
  height: 3px;
  width: 0;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #fff));
  box-shadow: 0 0 8px var(--accent);
  z-index: 90;
  opacity: 0;
  pointer-events: none;
}

/* ─────────────────── customer health dot ─────────────────── */

.health-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; vertical-align: middle; background: var(--muted); }
.health-dot.good { background: var(--good); }
.health-dot.warn { background: var(--warn); }
.health-dot.bad { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in srgb, var(--bad) 22%, transparent); }
.health-dot.dim { background: var(--line); }
.health-col { width: 24px; text-align: center; padding-left: 4px; padding-right: 0; }

/* ─────────────────── header icon buttons + indicators ─────────────────── */

.icon-btn { font-size: 15px; line-height: 1; padding: 4px 9px; min-width: 34px; }
#refresh-btn.spinning { animation: spin 0.5s linear; }
@keyframes spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { #refresh-btn.spinning { animation: none; } }
.loaded-ago { font-size: 11px; opacity: 0.75; }

/* The "g" prefix is armed — a subtle hint that the next key navigates. */
body.goto-armed::after {
  content: "g …";
  position: fixed;
  bottom: 16px; left: 16px;
  background: var(--accent); color: var(--accent-ink);
  padding: 4px 10px; border-radius: 8px;
  font-size: 12px; font-weight: 600;
  z-index: 70; box-shadow: var(--shadow);
}

/* ─────────────────── undo toast ─────────────────── */

.toast.has-undo { display: inline-flex; align-items: center; gap: 12px; }
.toast-undo {
  font: inherit;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.4);
  color: inherit;
  padding: 3px 12px;
  border-radius: 99px;
  min-height: 0;
  cursor: pointer;
  font-weight: 600;
}
.toast-undo:hover { border-color: currentColor; color: inherit; }

/* ─────────────────── help / shortcuts modal ─────────────────── */

.help-box { max-height: 80vh; }
.help-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; border-bottom: 1px solid var(--line); }
.help-grid { padding: 8px 16px 12px; overflow-y: auto; }
.help-row { display: flex; align-items: center; gap: 14px; padding: 7px 0; border-bottom: 1px solid var(--line); }
.help-row:last-child { border-bottom: none; }
.help-keys { flex: 0 0 150px; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.help-keys kbd {
  background: var(--chip-bg);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 2px 7px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
}
.help-plus { color: var(--muted); font-size: 11px; }
.help-desc { color: var(--ink); font-size: 13px; }

/* ─────────────────── customer timeline + note composer ─────────────────── */

.note-composer { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.note-composer textarea { width: 100%; resize: vertical; min-height: 46px; }
.note-composer-row { display: flex; gap: 8px; align-items: center; }
.note-composer-row select { flex: 0 0 auto; }
.note-composer-row button { margin-left: auto; }

.timeline { list-style: none; margin: 0; padding: 0; }
.tl-item { display: flex; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line); }
.tl-item:last-child { border-bottom: none; }
.tl-icon { flex: 0 0 auto; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 50%; background: var(--chip-bg); font-size: 14px; }
.tl-body { flex: 1; min-width: 0; }
.tl-text { line-height: 1.4; word-break: break-word; }
.tl-event .tl-text { color: var(--muted); }
.tl-note .tl-text { color: var(--ink); }
/* A completed to-do reads as struck-through + muted (its ✓ chip carries it). */
.tl-item.tl-done .tl-text { text-decoration: line-through; color: var(--muted); }
.tl-done { padding: 0 6px; min-height: 0; font-size: 12px; } /* the Done/Reopen toggle button */
.tl-meta { font-size: 11px; color: var(--muted); margin-top: 2px; display: flex; align-items: center; gap: 8px; }
.tl-del { padding: 0 6px; min-height: 0; font-size: 12px; opacity: 0; transition: opacity 0.15s; }
.tl-item:hover .tl-del { opacity: 0.6; }
.tl-del:hover { opacity: 1; }
/* Touch has no hover — keep the delete affordance visible on coarse pointers. */
@media (pointer: coarse) { .tl-del { opacity: 0.6; } }

/* ─────────────────── install-link + QR modal ─────────────────── */

.install-box { width: min(560px, calc(100% - 24px)); }
.install-tabs { display: flex; gap: 6px; padding: 10px 16px 0; }
.install-tabs.hidden { display: none; }
.install-tab { font: inherit; padding: 6px 14px; border-radius: 8px 8px 0 0; border: 1px solid var(--line); border-bottom: none; background: var(--bg); color: var(--muted); cursor: pointer; }
.install-tab.active { background: var(--card); color: var(--accent); font-weight: 600; border-color: var(--accent); }
.install-body { display: flex; gap: 18px; padding: 16px; align-items: flex-start; }
.install-qr { flex: 0 0 auto; background: #fff; padding: 10px; border-radius: 10px; line-height: 0; box-shadow: var(--shadow); }
.qr-svg { display: block; width: 200px; height: 200px; }
.install-right { flex: 1; min-width: 0; }
.install-link-row { margin: 6px 0 10px; }
.install-link-row code { display: block; word-break: break-all; background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; font-size: 12px; }
.install-hint { font-size: 11px; margin: 10px 0 0; }

/* ─────────────────── reorderable / collapsible sections ─────────────────── */

.cd-hint { font-size: 12px; margin: 0 0 8px; }
.sections { display: flex; flex-direction: column; gap: 16px; }
.sec {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.sec.dragging { opacity: 0.5; }
.sec.drop-before { box-shadow: 0 -3px 0 0 var(--accent); }
.sec.drop-after { box-shadow: 0 3px 0 0 var(--accent); }
.sec-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.sec.collapsed .sec-head { border-bottom: none; }
.sec-toggle {
  flex: 1; text-align: left; border: none; background: transparent; color: var(--ink);
  font-size: 15px; font-weight: 600; padding: 0; min-height: 0; cursor: pointer;
  display: flex; align-items: center; gap: 8px;
}
.sec-toggle:hover { color: var(--accent); }
.sec-chev { display: inline-block; transition: transform 0.15s; color: var(--muted); font-size: 12px; }
.sec:not(.collapsed) .sec-chev { transform: rotate(90deg); }
.sec-count { font-weight: 600; }
.sec-grip { cursor: grab; color: var(--muted); user-select: none; padding: 0 4px; font-size: 16px; }
.sec-grip:active { cursor: grabbing; }
.sec-move { display: inline-flex; gap: 2px; }
.sec-move button { padding: 2px 7px; min-height: 26px; font-size: 11px; line-height: 1; }
.sec-body { padding: 14px 16px; }
.sec.collapsed .sec-body { display: none; }
.sec-reset { text-align: right; }
.sec-reset button { font-size: 12px; }
@media (prefers-reduced-motion: reduce) { .sec-chev { transition: none; } }

/* ─────────────────── mobile bottom tab bar ───────────────────
   Hidden entirely on desktop (the horizontal nav + ⌘K cover it). On a phone
   it's the primary-navigation shortcut in the thumb zone; the ☰ drawer still
   holds the full menu. */

.tabbar { display: none; }

/* ══════════════════════ MOBILE (≤ 720px) ══════════════════════ */

@media (max-width: 720px) {
  :root { --tap: 44px; }

  /* Bottom tab bar — five equal thumb targets, safe-area aware. */
  .tabbar {
    display: flex;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 22;
    background: var(--card);
    border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 -1px 3px rgba(29, 45, 62, 0.12);
  }
  .tabbar a, .tabbar button {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 7px 2px 6px;
    min-height: 52px;
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--muted);
    text-decoration: none;
    font: inherit;
    cursor: pointer;
    /* No double-tap zoom on a tab: kills the 300ms click delay and the
       synthetic-click replay that came with it. */
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
  }
  /* The label/icon are decoration — every tap must resolve to the tab itself,
     never to a child, so the ghost-click guard sees a consistent target. */
  .tabbar .tab-ico, .tabbar .tab-lbl { pointer-events: none; }
  .tabbar a:hover, .tabbar button:hover { color: var(--accent); text-decoration: none; }
  .tabbar a.active { color: var(--accent); }
  .tabbar a.active .tab-ico { transform: translateY(-1px); }
  .tab-ico { font-size: 19px; line-height: 1; }
  .tab-lbl { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; }
  /* Keep page content clear of the bar (its height + the home indicator). */
  body.has-tabbar .view { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
  /* Float the toast and the "g …" hint above the bar so it never covers them. */
  .toast { bottom: calc(64px + env(safe-area-inset-bottom)); }
  body.goto-armed::after { bottom: calc(70px + env(safe-area-inset-bottom)); }
  /* No native drag on touch — the ▲▼ buttons do the reordering there. */
  .sec-grip { display: none; }
  .sec-body { padding: 12px; }
  .install-body { flex-direction: column; align-items: center; }
  .install-qr { margin: 0 auto; }

  .view { padding: 12px; padding-bottom: max(24px, env(safe-area-inset-bottom)); }
  h2 { font-size: 18px; }

  /* Nav becomes a drawer. It used to wrap onto five rows and eat 257px of an
     812px screen — a third of the viewport, permanently, since it is sticky. */
  .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
  .topbar { gap: 10px; padding: 8px 12px; }
  .brand-sub { display: none; }
  #who { display: none; }
  /* No keyboard on a phone → drop the help + last-loaded chrome; keep search,
     refresh, theme, lock. */
  #help-btn, #loaded-ago { display: none; }
  .topbar-end { gap: 4px; }
  .icon-btn { padding: 4px 7px; }
  .topbar nav {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: min(280px, 82vw);
    background: var(--card);
    border-right: 1px solid var(--line);
    box-shadow: var(--shadow-lg);
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 2px;
    padding: 64px 12px 12px;
    transform: translateX(-102%);
    transition: transform 0.2s ease;
    z-index: 30;
    overflow-y: auto;
  }
  body.nav-open .topbar nav { transform: translateX(0); }
  /* The drawer lives INSIDE .topbar, which is `sticky; z-index: 20` and so
     opens its own stacking context — the drawer's z-index is scoped to it,
     and the scrim (a sibling of .topbar at 25) would paint straight over the
     open drawer. Lift the whole topbar above the scrim while it is open. */
  body.nav-open .topbar { z-index: 30; }
  .topbar nav a { padding: 12px 14px; font-size: 15px; }
  .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 25;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  body.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }
  body.nav-open { overflow: hidden; }

  /* Forms go single-column, and every field hits 16px so focusing one does
     not zoom the page (iOS Safari's rule, and it is not overridable). */
  form.grid, .fgrid { grid-template-columns: 1fr; }
  /* EVERY field, no exceptions — including the monospace JWT box and the
     inline engine-URL editor. Any focusable field under 16px makes iOS
     Safari zoom the page, and that rule is not overridable from CSS. */
  input, select, textarea,
  label.f input, label.f select, label.f textarea,
  .jwt-box, .engine-input, #pal-input { font-size: 16px; }
  .toolbar input.search { max-width: none; }
  .toolbar { gap: 8px; }

  /* ── tables → cards ──
     Driven entirely by the data-label attributes lib/table.js writes, so any
     table built through renderTable() is responsive without extra work. */
  table.rt { display: block; }
  table.rt thead { display: none; }
  table.rt tbody, table.rt tr, table.rt td { display: block; width: auto; }
  table.rt tr {
    position: relative;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    padding: 12px 14px;
    margin-bottom: 10px;
    box-shadow: var(--shadow);
  }
  table.rt tr.editing, table.rt tr.flash { border-color: var(--accent); }
  table.rt td {
    border: none;
    padding: 5px 0;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    text-align: left;
  }
  table.rt td::before {
    content: attr(data-label);
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    flex: 0 0 auto;
    padding-top: 2px;
  }
  table.rt td:not([data-label])::before { content: none; }
  table.rt td.num { text-align: right; }
  /* The title cell is the card's headline: no label, full width, own rule. */
  table.rt td.card-title {
    display: block;
    font-size: 15px;
    padding: 0 46px 8px 0;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--line);
  }
  /* Actions become a wrapping button row across the card's full width. */
  table.rt td.actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: flex-start;
    white-space: normal;
    padding-top: 10px;
    margin-top: 6px;
    border-top: 1px solid var(--line);
  }
  table.rt td.actions:empty { display: none; }
  table.rt td.actions > * { margin-right: 0; flex: 1 1 auto; min-width: 96px; }
  /* Select-all checkbox parks in the card's top-right corner. */
  table.rt td.cb-col { position: absolute; top: 12px; right: 12px; width: auto; padding: 0; }
  table.rt td.cb-col::before { content: none; }
  table.rt td.cb-col input { width: 22px; height: 22px; }
  /* Low-value columns are dropped from the card entirely. */
  table.rt .hide-m { display: none !important; }
  table.rt tr.empty-row { border: none; box-shadow: none; background: transparent; padding: 0; }
  table.rt tr.empty-row td { display: block; text-align: center; }
  table.rt tr.empty-row td::before { content: none; }
  /* Wide inline editors (engine URL, renew date) stack inside the card. */
  table.rt td .engine-input { min-width: 0; width: 100%; }
  /* The S/4-engine URL is the one long value in a licence card. Stack it as a
     labelled field — label on top, URL + ✎ together on the line below — instead
     of forcing it into the label/value two-column row (which orphaned the ✎).
     Column layout also lets the inline editor (input + Save + ✕) stack cleanly. */
  table.rt td.engine-td { flex-direction: column; align-items: stretch; gap: 3px; }
  table.rt td.engine-td::before { padding-top: 0; }
  /* Keep the URL beside its label instead of wrapping onto a second line —
     it is the single tallest field in a licence card. Tap to see it whole. */
  .engine-url { max-width: 150px; }
  .lic-cust { max-width: 60%; }
  /* Chips sit right-aligned with their label rather than starting a new row. */
  table.rt td .ent-more, table.rt td .ent-all { text-align: right; }
  /* Last-checked is 3–4 tokens: keep them as ONE right-aligned item that wraps
     inside the card (min-width:0 lets it shrink) instead of a row of flex items
     that space-between pushes off the right edge. */
  table.rt td .lc-cell { flex: 0 1 auto; min-width: 0; text-align: right; }
  /* URL + ✎ ride together on the line below the S/4-engine label (see the
     engine-td column rule) — left-aligned like a labelled field. */
  table.rt td .engine-cell { min-width: 0; max-width: 100%; justify-content: flex-start; }

  /* A card IS the horizontal layout — the scroller must not also engage. */
  .scroll-x { overflow-x: visible; }
  .cols2 { grid-template-columns: 1fr; }
  .cols2 .card { overflow-x: visible; }

  .bulk-bar { position: sticky; top: 56px; }
  .bulk-bar > * { flex: 1 1 auto; }
  .bar-row { grid-template-columns: 1fr; gap: 3px; padding: 8px 0; }
  .bar-value { text-align: left; }
  .kpis { gap: 10px; }
  .kpi { flex: 1 1 calc(50% - 10px); min-width: 0; padding: 10px 12px; }
  .pal-box { margin-top: 4vh; max-height: 86vh; }
  .callout { font-size: 13px; }
}

/* `show-m` is the mirror of `hide-m`: content that only earns its place in
   the mobile card, where the column carrying it has been dropped. */
.show-m { display: none; }
@media (max-width: 720px) { .show-m { display: inline; } }

/* Between the phone card layout and the full desktop density, keep the
   two-column dashboard from squeezing its tables. */
@media (min-width: 721px) and (max-width: 900px) {
  .cols2 { grid-template-columns: 1fr; }
}

/* Laptop width: the widest tables (Licenses) still overflowed here, pushing
   the Actions column off-screen. Drop the two lowest-value columns —
   attribution and issue date — before sacrificing the buttons. Covers the
   common 1280/1440 laptop screens; only a genuinely wide monitor keeps the
   full column set. */
@media (min-width: 721px) and (max-width: 1500px) {
  .hide-md { display: none !important; }
  .engine-url { max-width: 140px; }
  .lic-cust { max-width: 132px; }
}
