/* Tori — design system.
 *
 * Orange is the brand, not an accent. It appears constantly — active nav, the
 * primary action on every page, Tori's own voice, focus rings, selected rows,
 * progress — while the neutrals underneath are warmed by the same hue so the
 * whole interface reads as one family rather than a grey app with orange bits.
 *
 * What keeps it from becoming an orange website is one rule: orange is
 * reserved for things that are interactive, selected, or Tori speaking. Data
 * is never orange. Severity is never orange. That is what preserves hierarchy.
 *
 * Two contrast facts drive the token split:
 *   orange text on light needs --brand-text (#B84A05, 5.22:1 on white, 4.78:1 on the orange wash)
 *   text ON the orange is dark ink, never white: ink is 5.86:1 on --brand and
 *   4.78:1 on --brand-hover, where white is 3.06:1 and 3.74:1 — both failures.
 *   14px semibold is not WCAG "large text" (that starts at 18.66px bold), so
 *   there is no size at which white on this orange is permitted.
 * Using --brand for a label would fail AA, which is the usual way orange
 * interfaces go wrong.
 *
 * Density: operational pages stay tight, because operators scan. The Overview
 * greeting is the one place the type scale opens up — it is a moment, not a
 * table.
 *
 * Values mirror tori/config.py TOKENS; a test fails the build if they drift.
 */

/* Inter, self-hosted (web/fonts/) rather than loaded from Google Fonts --
 * the @import this replaced sent every visitor's IP to Google on every page
 * load, an undisclosed third-party data flow with no user-facing benefit
 * over vendoring the same two files (latin + latin-ext, covering English
 * and the accented European locales TORI already supports; CJK/Arabic
 * locales fall through to the system font stack below either way, since
 * Inter has no glyphs for those scripts regardless of where it's hosted).
 * Matches Google's own served structure: one variable file per subset,
 * aliased to the four static weights this app actually uses. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Light is a decision here, not a default, so it is declared: without this a
     dark-OS visitor gets dark UA scrollbars and form controls on a warm light
     page. There is no dark variant by design -- a screen read every morning
     over coffee is not the place for one. */
  color-scheme: light;

  /* brand */
  --brand: #F4690C;
  --brand-text: #B84A05;
  --brand-hover: #DD5C09;
  --brand-active: #B84A05;
  --brand-wash: #FEF3EA;
  /* Hero gradient, from the canonical palette. */
  --hero-from: #F4690C;
  --hero-to: #EA640B;
  --hero-edge: #DC5E0A;
  --brand-tint: #FBE3D1;
  --brand-edge: #F6C9A6;
  --warning-edge: #E8D5AE;

  /* neutrals */
  --canvas: #FAF8F5;
  --surface: #FFFFFF;
  --surface-warm: #F5F0EA;
  --border: #E8E1D8;
  --border-strong: #D6CCC0;

  /* ink. Mirrors config.py: ink_faint took over the old ink_soft value, because
     the lighter tone it used to carry measured 2.76:1 on the warm sidebar and
     ink_soft was already at the AA floor, leaving no room for a lighter third
     tier. Size, weight and tracking separate the tiers, not lightness alone. */
  --ink: #1C1613;
  --ink-soft: #5F564F;
  --ink-faint: #756B64;
  --ink-inverse: #FFFFFF;

  /* status */
  --critical: #C91D3A;
  --critical-wash: #FCEBEE;
  --warning: #8A5A08;
  --warning-wash: #FAF1DF;
  --success: #0F6B48;
  --success-wash: #E7F2EC;
  --info: #2F5D8C;
  --info-wash: #EAF0F7;

  --dark: #1C1613;
  --dark-text: #F5F0EA;
  --dark-muted: #A79C93;

  /* spacing — 4 8 12 16 24 32 48 64, nothing off-scale */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;

  /* radius */
  --r-sm: 8px; --r: 10px; --r-lg: 14px; --r-full: 999px;

  /* shadow — sparing, never to make things float */
  --shadow-sm: 0 1px 2px rgba(28,22,19,.05);
  --shadow: 0 2px 8px rgba(28,22,19,.07);
  --shadow-lg: 0 12px 40px rgba(28,22,19,.16);

  --ease: cubic-bezier(.22,.61,.36,1);
  --fast: 150ms var(--ease);
  --med: 220ms var(--ease);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; min-height: 100%; }
body {
  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 14px; line-height: 1.5;
  color: var(--ink); background: var(--canvas);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
/* Inter has true tabular figures, so numbers align without a second font. */
.num, .spark, .metric-v, .fact-v, .bar-val, td { font-variant-numeric: tabular-nums; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; padding: 0; }
a { color: var(--brand-text); text-decoration: none; }
::selection { background: var(--brand-tint); }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--r-sm); }

/* ===================== shell controls ===================== */
/* The top bar this file used to style is gone -- the sidebar absorbed search,
   alerts and the venue switcher. Its rules outlived the element by a long way,
   held in place by a test that asserted on them; both are gone now. What
   remains below is what the sidebar and the drawers actually use. */

.icon-btn {
  width: 34px; height: 34px; border-radius: var(--r); display: flex;
  align-items: center; justify-content: center; color: var(--ink-soft);
  position: relative; transition: all var(--fast);
}
.icon-btn:hover { background: var(--surface-warm); color: var(--ink); }
.icon-btn .count {
  position: absolute; top: 1px; inset-inline-end: 1px; min-width: 15px; height: 15px;
  padding: 0 4px; border-radius: var(--r-full); background: var(--brand);
  color: var(--ink); font-size: 10px; font-weight: 700; display: flex;
  align-items: center; justify-content: center; border: 2px solid var(--canvas);
}

/* restaurant selector — a first-class control, not a label */
.venue {
  position: relative; display: flex; align-items: center; gap: var(--s2);
  height: 38px; padding: 0 var(--s2) 0 var(--s1); border-radius: var(--r);
  border: 1px solid var(--border); background: var(--surface); transition: all var(--fast);
}
.venue:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.venue-mark {
  width: 28px; height: 28px; border-radius: var(--r-sm); background: var(--brand);
  color: var(--ink); font-weight: 700; font-size: 14px; display: flex;
  align-items: center; justify-content: center; flex-shrink: 0;
}
.venue-name { font-size: 13px; font-weight: 600; line-height: 1.2; text-align: start; }
.venue-city { font-size: 11.5px; color: var(--ink-soft); line-height: 1.2; text-align: start; }
.venue-chev { color: var(--ink-faint); flex-shrink: 0; }

/* language selector — same control language as .venue, distinct trigger */
/* Ghost, not a second bordered box. Language is a rare, quiet control and gave
   the foot of the rail a third card it did not need; it now sits as plain text
   under the venue and firms up only on hover. */
.lang-switcher {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  height: 32px; padding-inline: var(--s3) var(--s2); border-radius: var(--r);
  border: 1px solid transparent; background: transparent;
  color: var(--ink-soft); font-size: 12.5px; transition: all var(--fast);
}
.lang-switcher:hover { background: var(--surface); border-color: var(--border); color: var(--ink); }
.lang-switcher > svg:first-child { flex-shrink: 0; color: var(--ink-faint); }
.lang-switcher #lang-switcher-label {
  flex: 1; text-align: start; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lang-switcher > svg:last-child { color: var(--ink-faint); flex-shrink: 0; }

/* ===================== layout ===================== */

.page { max-width: 1320px; margin: 0 auto; padding: var(--s5) var(--s5) var(--s8); }

.page-head { display: flex; align-items: flex-start; gap: var(--s4); margin-bottom: var(--s5); }
.page-title { font-size: 28px; font-weight: 680; letter-spacing: -.03em; margin: 0; line-height: 1.2; }
.page-desc { font-size: 14.5px; color: var(--ink-soft); margin-top: var(--s1); max-width: 72ch; }
.page-actions { margin-inline-start: auto; display: flex; gap: var(--s2); align-items: center; flex-shrink: 0; }

/* The greeting is the one place the scale opens up. */
.greeting { margin-bottom: var(--s5); }
.greeting h1 { font-size: 40px; font-weight: 680; letter-spacing: -.035em; margin: 0; line-height: 1.1; }
.greeting p { font-size: 17px; color: var(--ink-soft); margin: var(--s2) 0 0; max-width: 70ch; }
.greeting strong { color: var(--ink); font-weight: 600; }

.section { margin-top: var(--s6); }
.section-head { display: flex; align-items: baseline; gap: var(--s3); margin-bottom: var(--s3); flex-wrap: wrap; }
.section-title { font-size: 18px; font-weight: 650; letter-spacing: -.02em; margin: 0; }
.section-desc { font-size: 13px; color: var(--ink-soft); }
.spacer { margin-inline-start: auto; }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }
.row { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }

.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.card-pad { padding: var(--s4); }

/* ===================== Tori insight ===================== */
/* The signature: Tori notices → impact → evidence → recommendation → action.
   The same shape wherever a finding appears, so the structure is as much the
   brand as the colour is. */

.insight {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden; margin-bottom: var(--s3);
  transition: box-shadow var(--fast);
}
.insight:hover { box-shadow: var(--shadow-sm); }
.insight.lead { border-color: var(--brand-edge); box-shadow: var(--shadow); }

.insight-top { padding: var(--s4); }
.tori-said { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); }
.tori-mark {
  width: 22px; height: 22px; border-radius: var(--r-sm); background: var(--brand);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.tori-mark svg { width: 13px; height: 13px; color: var(--ink-inverse); }
.tori-said span { font-size: 13px; font-weight: 600; color: var(--brand-text); }

/* An h2, not a div: the finding is the page's actual content, and as a div it
   sat outside the outline entirely -- heading navigation jumped from the page
   h1 straight past it. margin: 0 because nothing here resets UA heading margin. */
.insight-title { font-size: 20px; font-weight: 650; letter-spacing: -.025em; line-height: 1.25; margin: 0; }
.insight.lead .insight-title { font-size: 25px; }

.severity {
  display: inline-flex; align-items: center; gap: var(--s1); padding: 2px var(--s2);
  border-radius: var(--r-full); font-size: 11.5px; font-weight: 600;
  color: var(--sev); background: var(--sev-wash); flex-shrink: 0;
}
.severity i { width: var(--s1); height: var(--s1); border-radius: 50%; background: var(--sev); }

.facts { display: flex; flex-wrap: wrap; border-top: 1px solid var(--border); background: var(--canvas); }
.fact { padding: var(--s3) var(--s4); flex: 1 1 auto; min-width: 120px; }
.fact + .fact { border-inline-start: 1px solid var(--border); }
.fact-v { font-size: 20px; font-weight: 650; letter-spacing: -.02em; line-height: 1.15; }
.fact-l { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
.fact.impact .fact-v { color: var(--sev, var(--critical)); }

/* Tori's recommendation carries the orange wash: this is Tori speaking. */
.advice { padding: var(--s4); background: var(--brand-wash); border-top: 1px solid var(--brand-edge); }
.advice-l { font-size: 12px; font-weight: 600; color: var(--brand-text); margin-bottom: var(--s1); }
.advice-t { font-size: 15px; line-height: 1.5; max-width: 68ch; }

.insight-foot {
  display: flex; align-items: center; gap: var(--s3); padding: var(--s3) var(--s4);
  border-top: 1px solid var(--border); flex-wrap: wrap;
}

/* confidence — a label first, a bar second, never colour alone */
.confidence { display: flex; align-items: center; gap: var(--s2); }
.confidence-l { font-size: 12.5px; font-weight: 550; color: var(--ink-soft); }
.confidence-track { width: 46px; height: 5px; border-radius: var(--r-full); background: var(--border); overflow: hidden; }
.confidence-fill { height: 100%; background: var(--brand); border-radius: var(--r-full); }

/* ===================== explanation ===================== */

.explain { border-top: 1px solid var(--border); }
.explain-toggle {
  width: 100%; text-align: start; padding: var(--s3) var(--s4); font-size: 13.5px;
  font-weight: 550; color: var(--ink-soft); display: flex; align-items: center;
  gap: var(--s2); transition: all var(--fast);
}
.explain-toggle:hover { color: var(--brand-text); background: var(--brand-wash); }
.explain-toggle svg { transition: transform var(--fast); flex-shrink: 0; }
.explain-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }
.explain-body { padding: 0 var(--s4) var(--s4); animation: reveal var(--med); }
@keyframes reveal { from { opacity: 0; transform: translateY(-4px); } }

.explain-group { margin-top: var(--s4); }
.explain-group:first-child { margin-top: 0; }
.explain-label { font-size: 12px; font-weight: 600; color: var(--ink-faint); margin-bottom: var(--s2); }
.explain-group p { margin: 0 0 var(--s1); font-size: 13.5px; line-height: 1.55; }

.calc { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.calc td { padding: var(--s2) 0; border-bottom: 1px solid var(--border); }
.calc td:last-child { text-align: end; font-variant-numeric: tabular-nums; font-weight: 550; }
.calc td:first-child { color: var(--ink-soft); }
.calc tr:last-child td { border-bottom: none; font-weight: 650; }

/* ===================== buttons ===================== */

.btn {
  height: 34px; padding: 0 var(--s3); border-radius: var(--r);
  font-size: 13.5px; font-weight: 550; display: inline-flex; align-items: center;
  gap: var(--s1); border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--ink); transition: all var(--fast); white-space: nowrap;
}
.btn:hover { background: var(--surface-warm); border-color: var(--border-strong); }
.btn:active { transform: translateY(.5px); }
/* 14px semibold clears AA-large on the brand, which 13px regular would not. */
.btn.primary { background: var(--brand); border-color: var(--brand); color: var(--ink); font-size: 14px; font-weight: 600; }
.btn.primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); }
.btn.tertiary { border-color: transparent; background: transparent; color: var(--ink-soft); }
.btn.tertiary:hover { background: var(--surface-warm); color: var(--ink); }
.btn.danger { color: var(--critical); }
.btn.danger:hover { background: var(--critical-wash); border-color: var(--critical); }
.btn.sm { height: 28px; padding: 0 var(--s2); font-size: 12.5px; }
/* A disabled primary/danger button used to just be its own color at 45%
 * opacity -- readable, but easy to misread as "a lighter active button"
 * rather than "not clickable right now," especially for anyone with
 * reduced contrast sensitivity. A flat neutral treatment reads as inactive
 * regardless of which variant it started as, and the cursor now says so
 * semantically too, not just visually. */
.btn:disabled {
  cursor: not-allowed; transform: none; opacity: 1; pointer-events: none;
  background: var(--surface-warm); border-color: var(--border); color: var(--ink-faint);
}

/* ===================== metrics ===================== */

.metrics {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); overflow: hidden;
}
.metric { padding: var(--s3) var(--s4) var(--s4); }
.metric + .metric { border-inline-start: 1px solid var(--border); }
.metric-l { font-size: 12.5px; color: var(--ink-soft); margin-bottom: var(--s1); }
.metric-v { font-size: 26px; font-weight: 650; letter-spacing: -.03em; line-height: 1.1; }
.metric-d { font-size: 12.5px; margin-top: 6px; font-weight: 550; }
.up { color: var(--success); } .down { color: var(--critical); } .flat { color: var(--ink-faint); }

.badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px var(--s2); border-radius: var(--r-full); font-size: 11.5px; font-weight: 600; }
.badge.ok { background: var(--success-wash); color: var(--success); }
.badge.bad { background: var(--critical-wash); color: var(--critical); }
.badge.warn { background: var(--warning-wash); color: var(--warning); }
.badge.info { background: var(--info-wash); color: var(--info); }
.badge.brand { background: var(--brand-wash); color: var(--brand-text); }
.badge.neutral { background: var(--surface-warm); color: var(--ink-soft); }

/* ===================== tables ===================== */
/* Dense but not boxed: separators between rows only, never around cells. */

.tablewrap { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); overflow: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
thead th {
  position: sticky; top: 0; z-index: 1; background: var(--surface); text-align: start;
  font-size: 12px; font-weight: 600; color: var(--ink-soft);
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border);
  white-space: nowrap; cursor: pointer; user-select: none; transition: color var(--fast);
}
thead th:hover { color: var(--ink); }
thead th.sorted { color: var(--brand-text); }
tbody td { padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border); white-space: nowrap; }
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background var(--fast); }
tbody tr:hover { background: var(--brand-wash); cursor: pointer; }
tbody tr.flagged td:first-child { box-shadow: inset 3px 0 0 var(--brand); }
td.r, th.r { text-align: end; }
td.primary { font-weight: 550; }

.filters { display: flex; gap: var(--s2); align-items: center; margin-bottom: var(--s3); flex-wrap: wrap; }
.field {
  height: 34px; padding: 0 var(--s3); border-radius: var(--r);
  border: 1px solid var(--border-strong); background: var(--surface);
  font-size: 13.5px; transition: all var(--fast); min-width: 0;
}
.field:hover { border-color: var(--border-strong); }
.field:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-wash); }
.chip {
  height: 30px; padding: 0 var(--s3); border-radius: var(--r-full);
  border: 1px solid var(--border-strong); background: var(--surface);
  font-size: 13px; font-weight: 500; display: inline-flex; align-items: center;
  gap: var(--s1); transition: all var(--fast);
}
.chip:hover { border-color: var(--border-strong); }
.chip.on { background: var(--brand-wash); border-color: var(--brand); color: var(--brand-text); font-weight: 600; }

.tabs { display: flex; gap: var(--s1); border-bottom: 1px solid var(--border); margin-bottom: var(--s4); }
.tab { position: relative; padding: var(--s2) var(--s3) var(--s3); font-size: 14px; font-weight: 500; color: var(--ink-soft); transition: color var(--fast); }
.tab:hover { color: var(--ink); }
.tab.on { color: var(--brand-text); font-weight: 600; }
.tab.on::after {
  content: ''; position: absolute; inset-inline-start: var(--s3); inset-inline-end: var(--s3); bottom: -1px;
  height: 2px; background: var(--brand); border-radius: 2px 2px 0 0;
}

/* ===================== lists ===================== */

.list { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); overflow: hidden; }
.list-row { display: flex; align-items: center; gap: var(--s3); padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border); transition: background var(--fast); }
.list-row:last-child { border-bottom: none; }
.list-row.clickable:hover { background: var(--brand-wash); cursor: pointer; }
.list-t { font-size: 14px; font-weight: 550; }
.list-s { font-size: 13px; color: var(--ink-soft); margin-top: 2px; max-width: 76ch; }
.list-rank {
  width: 24px; height: 24px; border-radius: var(--r-sm); background: var(--brand-wash);
  color: var(--brand-text); font-size: 12.5px; font-weight: 700; display: flex;
  align-items: center; justify-content: center; flex-shrink: 0; font-variant-numeric: tabular-nums;
}
.list-amt { margin-inline-start: auto; font-weight: 650; font-variant-numeric: tabular-nums; flex-shrink: 0; }

/* ===================== states ===================== */

.note { font-size: 13.5px; color: var(--ink-soft); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: var(--s3) var(--s4); line-height: 1.55; }
.note.brand { background: var(--brand-wash); border-color: var(--brand-edge); color: var(--ink); }
.note.warn { background: var(--warning-wash); border-color: var(--warning-edge); color: var(--ink); }

.empty { text-align: center; padding: var(--s8) var(--s5); border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); }
.empty img { width: 44px; height: 44px; border-radius: var(--r); margin-bottom: var(--s3); }
.empty b { display: block; font-size: 17px; font-weight: 620; margin-bottom: var(--s1); }
.empty span { font-size: 14px; color: var(--ink-soft); }

/* Skeletons stand in for the layout that is coming, so they are built from the
 * same containers the real page uses (.metrics, .hero, .filters, .card) and
 * only the leaves are grey. The point is that nothing moves when the data
 * arrives -- a travelling bar cannot promise that, because it occupies a
 * different amount of space than whatever replaces it. */
.skeleton { background: var(--surface-warm); border-radius: var(--r); animation: pulse 1.3s infinite var(--ease); }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .5; } }

.skel-head { margin-bottom: var(--s5); display: flex; flex-direction: column; gap: var(--s2); }
.skel-table { padding: var(--s2) 0; }
.skel-tr {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border);
}
.skel-tr > * { flex: 1; }
.skel-tr > *:first-child { flex: 2; }
.skel-tr:last-child { border-bottom: none; }
.skel-th { padding-block: var(--s2); }
.skel-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border);
}
.skel-row:last-child { border-bottom: none; }
.skel-row > *:last-child { flex-shrink: 0; }
.skel-stack { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s3); }
/* The banner's own ground, so the placeholder sits in the same field the real
   banner will, rather than flashing white and then cream. */
.skel-hero .tile, .skel-hero .kpi { min-height: 0; }

/* The press itself, applied in the same frame as the click and before any
   request leaves. Colour and inset only -- no size, no margin, no border-width
   -- so acknowledging a press can never move the button or anything near it. */
.btn.is-pressing {
  filter: brightness(.94);
  box-shadow: inset 0 1px 2px rgba(28, 22, 19, .14);
}
.btn.primary.is-pressing { filter: brightness(.92); }

/* A button still working after that. The size is frozen in markBusy() before
   the label changes, so this never reflows the row it sits in. */
.btn[aria-busy="true"] { cursor: progress; }
.btn-spin {
  width: 13px; height: 13px; flex-shrink: 0; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  opacity: .75; animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
.btn-busy-label { white-space: nowrap; }
.bar-loading { height: 2px; background: var(--brand-wash); border-radius: 2px; overflow: hidden; }
.bar-loading i { display: block; height: 100%; width: 36%; background: var(--brand); animation: slide 1s infinite var(--ease); }
@keyframes slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(320%); } }

/* ===================== overlays ===================== */

.scrim { position: fixed; inset: 0; background: rgba(28,22,19,.28); z-index: 90; opacity: 0; pointer-events: none; transition: opacity var(--fast); backdrop-filter: blur(2px); }
.scrim.open { opacity: 1; pointer-events: auto; }

.palette {
  position: fixed; top: 14vh; inset-inline-start: 50%; z-index: 100;
  transform: translateX(-50%) scale(.98) translateY(-6px);
  width: min(640px, 92vw); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg); overflow: hidden;
  opacity: 0; pointer-events: none; transition: opacity var(--fast), transform var(--fast);
}
.palette.open { opacity: 1; pointer-events: auto; transform: translateX(-50%) scale(1) translateY(0); }
.palette input { width: 100%; height: 52px; padding: 0 var(--s4); border: none; font-size: 16px; border-bottom: 1px solid var(--border); background: transparent; }
.palette input:focus { outline: none; }
.palette-list { max-height: 52vh; overflow-y: auto; padding: var(--s1); }
.pal-row { display: flex; align-items: center; gap: var(--s3); padding: var(--s2) var(--s3); width: 100%; text-align: start; border-radius: var(--r-sm); transition: background var(--fast); }
.pal-row.on { background: var(--brand-wash); }
.pal-kind { font-size: 11.5px; font-weight: 600; color: var(--ink-faint); min-width: 76px; }
.pal-label { font-size: 14px; font-weight: 500; }
.pal-sub { font-size: 12.5px; color: var(--ink-soft); margin-inline-start: auto; }
.palette-foot { display: flex; gap: var(--s4); padding: var(--s2) var(--s4); border-top: 1px solid var(--border); background: var(--canvas); font-size: 12px; color: var(--ink-faint); flex-wrap: wrap; }

.drawer {
  position: fixed; top: 0; inset-inline-end: 0; bottom: 0; width: min(580px, 100vw); z-index: 95;
  background: var(--surface); border-inline-start: 1px solid var(--border);
  box-shadow: var(--shadow-lg); transform: translateX(100%);
  transition: transform var(--med); display: flex; flex-direction: column;
}
/* Same physical-transform issue as .sidebar above: this sits at the "end"
   edge (right in LTR, left in RTL) via inset-inline-end, so hiding it must
   shift toward whichever edge it's actually anchored to, not always right. */
[dir="rtl"] .drawer { transform: translateX(-100%); }
.drawer.open { transform: none; }
.drawer-head { display: flex; align-items: center; gap: var(--s3); padding: var(--s4); border-bottom: 1px solid var(--border); flex-shrink: 0; }
.drawer-title { font-size: 18px; font-weight: 650; letter-spacing: -.02em; }
.drawer-body { flex: 1; overflow-y: auto; padding: var(--s4); }

.menu {
  position: absolute; top: calc(100% + 6px); inset-inline-end: 0; z-index: 80; min-width: 250px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--shadow-lg); padding: var(--s1); display: none;
}
.menu.open { display: block; animation: reveal var(--fast); }
.menu-item { display: flex; align-items: center; gap: var(--s2); width: 100%; text-align: start; padding: var(--s2) var(--s3); border-radius: var(--r-sm); font-size: 13.5px; transition: background var(--fast); }
.menu-item:hover { background: var(--surface-warm); }
.menu-item.on { background: var(--brand-wash); color: var(--brand-text); font-weight: 600; }
.menu-label { font-size: 11.5px; font-weight: 600; color: var(--ink-faint); padding: var(--s2) var(--s3) var(--s1); }

.toasts { position: fixed; bottom: var(--s5); inset-inline-end: var(--s5); z-index: 120; display: flex; flex-direction: column; gap: var(--s2); }
.toast { background: var(--dark); color: var(--dark-text); border-radius: var(--r); padding: var(--s3) var(--s4); font-size: 13.5px; display: flex; align-items: center; gap: var(--s3); box-shadow: var(--shadow-lg); max-width: 420px; animation: rise var(--med); }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
.toast button { color: var(--brand); font-weight: 600; font-size: 13.5px; flex-shrink: 0; }

/* ===================== charts ===================== */

.spark { display: block; width: 100%; height: 68px; }
.bars { display: flex; flex-direction: column; gap: var(--s2); }
.bar-row { display: grid; grid-template-columns: 124px 1fr 76px; gap: var(--s3); align-items: center; font-size: 13.5px; }
.bar-track { height: 16px; background: var(--surface-warm); border-radius: var(--r-sm); overflow: hidden; }
/* No width transition: these are created with their final width on each render,
   and a CSS transition on a freshly-inserted element never fires -- it was
   animating a layout property for nothing. */
.bar-fill { height: 100%; background: var(--brand); opacity: .85; border-radius: var(--r-sm); }
.bar-val { text-align: end; font-weight: 550; }

/* ===================== responsive ===================== */

.mobilenav { display: none; }

@media (max-width: 1100px) {
  .venue-city { display: none; }
}

@media (max-width: 900px) {
  /* Navigation moves to the bottom bar; the sidebar becomes a drawer. */
  .page { padding: var(--s4) var(--s3) 88px; }
  .greeting h1 { font-size: 30px; }
  .greeting p { font-size: 15.5px; }
  .page-title { font-size: 23px; }
  .grid2 { grid-template-columns: 1fr; }
  .metrics { grid-template-columns: repeat(2, 1fr); }
  .metric:nth-child(odd) { border-inline-start: none; }
  .metric:nth-child(n+3) { border-top: 1px solid var(--border); }
  .insight.lead .insight-title { font-size: 20px; }
  .fact { min-width: 50%; }
  .fact + .fact { border-inline-start: none; }
  .fact:nth-child(n+3) { border-top: 1px solid var(--border); }
  .drawer { width: 100vw; }
  .palette { top: 0; width: 100vw; height: 100%; border-radius: 0; }
  .palette-list { max-height: calc(100% - 104px); }
  .toasts { inset-inline-start: var(--s3); inset-inline-end: var(--s3); bottom: 80px; }
  .toast { max-width: none; }

  .mobilenav {
    display: flex; position: fixed; inset-inline-start: 0; inset-inline-end: 0; bottom: 0; height: 62px;
    background: rgba(255,255,255,.94); backdrop-filter: saturate(180%) blur(12px);
    border-top: 1px solid var(--border); z-index: 70;
    padding-bottom: env(safe-area-inset-bottom);
  }
  .mob-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; font-size: 11px; font-weight: 500; color: var(--ink-soft); position: relative; transition: color var(--fast); }
  .mob-item.on { color: var(--brand-text); font-weight: 600; }
  .mob-item svg { width: 21px; height: 21px; }
  .mob-item .count { position: absolute; top: 6px; inset-inline-start: 50%; margin-inline-start: 6px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: var(--r-full); background: var(--brand); color: var(--ink); font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; }

  /* Tables stack into readable rows rather than scrolling sideways. */
  .tablewrap.stack { border: none; background: none; overflow: visible; }
  .tablewrap.stack thead { display: none; }
  .tablewrap.stack tbody tr { display: block; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); margin-bottom: var(--s2); padding: var(--s3); }
  .tablewrap.stack tbody td { display: inline-block; border: none; padding: 2px var(--s3) 2px 0; white-space: normal; }
  .tablewrap.stack tbody td[data-l]::before { content: attr(data-l) ' '; font-size: 11px; color: var(--ink-faint); font-weight: 500; }
  .tablewrap.stack tbody td.primary { display: block; font-size: 15px; font-weight: 600; margin-bottom: var(--s1); }
  .tablewrap.stack tbody td.primary::before { content: none; }
  .bar-row { grid-template-columns: 96px 1fr 64px; font-size: 13px; }
}

@media (prefers-reduced-motion: reduce) {
  /* Not a blanket .01ms kill. That froze the one indicator that carries real
     information -- .bar-loading's travelling segment snapped to the end of its
     keyframe, leaving an empty track, so a reduced-motion reader got no signal
     that a request was in flight at all. Decorative movement goes; state change
     and loading feedback stay, expressed without travel. */
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }

  /* A determinate-looking full-width bar instead of a moving segment: still
     visibly "working", no motion. */
  .bar-loading i {
    animation: none !important;
    width: 100%;
    opacity: .55;
  }
  /* Skeletons hold their mid-pulse tone rather than flashing to it. */
  .skeleton { animation: none !important; opacity: .75; }
  /* Tori's thinking dots stay visible and evenly weighted instead of blinking. */
  .thinking i, .activity .dots i { animation: none !important; opacity: .7; }
  /* The busy ring stops turning but stays a ring: it is the only thing marking
     a pressed button as still working, so it must not vanish. The gap in the
     border would read as damage once still, hence the full circle. */
  .btn-spin {
    animation: none !important;
    border-top-color: currentColor;
    opacity: .5;
  }
}

/* ===================== Ask Tori ===================== */
/* The conversational layer. Deliberately not a bubble chat: Tori's answers are
   structured objects — metrics, tables, charts, evidence — so they render as
   composed blocks rather than paragraphs in a rounded rectangle. */

/* dvh, not vh: this ships as an installable PWA, and on iOS vh includes the
   retracting toolbar, which put the composer below the visible area. */
.ask { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }
.ask-scroll { flex: 1; overflow-y: auto; padding: var(--s5) 0 var(--s4); }
.ask-inner { max-width: 760px; margin: 0 auto; padding: 0 var(--s4); }

.ask-welcome { text-align: center; padding: var(--s7) 0 var(--s5); }
.ask-pfp { width: 72px; height: 72px; border-radius: 18px; display: block; margin: 0 auto var(--s4); }
.ask-welcome h1 { font-size: 32px; font-weight: 680; letter-spacing: -.035em; margin: 0; }
.ask-welcome p { font-size: 16px; color: var(--ink-soft); margin: var(--s2) auto 0; max-width: 44ch; }

.prompt-groups { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); margin-top: var(--s6); text-align: start; }
.prompt {
  display: flex; align-items: center; gap: var(--s3); padding: var(--s3) var(--s4);
  border: 1px solid var(--border); border-radius: var(--r); background: var(--surface);
  font-size: 14px; transition: all var(--fast); width: 100%; text-align: start;
}
.prompt:hover { border-color: var(--brand); background: var(--brand-wash); }
.prompt-group { font-size: 11px; font-weight: 600; color: var(--ink-faint); min-width: 62px; }

.turn { margin-bottom: var(--s6); animation: reveal var(--med); }
.turn-user { display: flex; justify-content: flex-end; margin-bottom: var(--s5); }
.turn-user span {
  background: var(--surface-warm); border: 1px solid var(--border);
  border-radius: var(--r-lg) var(--r-lg) var(--r-sm) var(--r-lg);
  padding: var(--s3) var(--s4); font-size: 15px; max-width: 78%;
}
.turn-tori { display: flex; gap: var(--s3); align-items: flex-start; }
.turn-pfp { width: 30px; height: 30px; border-radius: var(--r-sm); flex-shrink: 0; }
.turn-body { flex: 1; min-width: 0; }
.turn-answer { font-size: 15.5px; line-height: 1.6; white-space: pre-wrap; }
.turn-answer strong { font-weight: 600; }

.activity { display: flex; align-items: center; gap: var(--s2); font-size: 13px; color: var(--ink-soft); margin-bottom: var(--s3); }
.activity .dots { display: inline-flex; gap: 3px; }
.activity .dots i { width: 4px; height: 4px; border-radius: 50%; background: var(--brand); animation: blink 1.1s infinite; }
.activity .dots i:nth-child(2) { animation-delay: .15s; }
.activity .dots i:nth-child(3) { animation-delay: .3s; }
@keyframes blink { 0%,80%,100% { opacity: .25; } 40% { opacity: 1; } }

.prov { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }
.prov-tag {
  font-size: 11.5px; font-weight: 500; padding: 2px var(--s2); border-radius: var(--r-full);
  background: var(--surface-warm); color: var(--ink-soft);
}
.prov-tag.estimate { background: var(--warning-wash); color: var(--warning); }
.prov-tag.external { background: var(--info-wash); color: var(--info); }
.prov-tag.unknown { background: var(--critical-wash); color: var(--critical); }
.prov-tag.calculated, .prov-tag.tori_data, .prov-tag.tori_insight { background: var(--brand-wash); color: var(--brand-text); }

.turn-tools { font-size: 12.5px; color: var(--ink-faint); margin-top: var(--s2); }
.turn-tools b { font-weight: 500; color: var(--ink-soft); }
.turn-acts { display: flex; gap: var(--s2); margin-top: var(--s4); flex-wrap: wrap; }
.turn-meta { display: flex; gap: var(--s3); margin-top: var(--s3); align-items: center; flex-wrap: wrap; }

.followups { display: flex; gap: var(--s2); margin-top: var(--s4); flex-wrap: wrap; }
.followup {
  font-size: 13px; padding: var(--s1) var(--s3); border-radius: var(--r-full);
  border: 1px dashed var(--border-strong); color: var(--ink-soft); transition: all var(--fast);
}
.followup:hover { border-style: solid; border-color: var(--brand); color: var(--brand-text); background: var(--brand-wash); }

.ask-compose { border-top: 1px solid var(--border); background: var(--canvas); padding: var(--s3) 0 var(--s4); }
.ask-box {
  max-width: 760px; margin: 0 auto; padding: 0 var(--s4);
  display: flex; gap: var(--s2); align-items: flex-end;
}
.ask-input {
  flex: 1; min-height: 46px; max-height: 160px; padding: var(--s3) var(--s4);
  border: 1px solid var(--border-strong); border-radius: var(--r-lg);
  background: var(--surface); font-size: 15px; resize: none; line-height: 1.5;
  transition: all var(--fast); font-family: inherit;
}
.ask-input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-wash); }
.ask-send {
  width: 46px; height: 46px; border-radius: var(--r-lg); background: var(--brand);
  color: var(--ink); display: flex; align-items: center; justify-content: center;
  transition: all var(--fast); flex-shrink: 0;
}
.ask-send:hover { background: var(--brand-hover); }
.ask-send:disabled { opacity: .4; }
.ask-note { max-width: 760px; margin: var(--s2) auto 0; padding: 0 var(--s4); font-size: 12px; color: var(--ink-faint); text-align: center; }

.ask-about {
  display: inline-flex; align-items: center; gap: var(--s1); font-size: 13px;
  color: var(--brand-text); font-weight: 550; padding: var(--s1) var(--s2);
  border-radius: var(--r-sm); transition: background var(--fast);
}
.ask-about:hover { background: var(--brand-wash); }
.ask-about img { width: 16px; height: 16px; border-radius: 4px; }

@media (max-width: 900px) {
  /* 62px is the bottom tab bar, which only exists at this breakpoint. */
  .ask { height: calc(100vh - 62px); height: calc(100dvh - 62px); }
  .prompt-groups { grid-template-columns: 1fr; }
  .ask-welcome h1 { font-size: 25px; }
  .turn-user span { max-width: 88%; }
}

/* ============================================================
   APP SHELL — persistent sidebar
   The six business sections previously sat in a top bar, which
   forced truncation as sections were added and gave the product
   no fixed sense of place. A sidebar holds the full navigation at
   readable size, keeps the group labels visible, and leaves the
   top bar quiet enough for search and business identity.
   ============================================================ */
:root {
  --sidebar-w: 268px;
  --topbar-h: 72px;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
           Georgia, serif;
}

.shell { display: flex; min-height: 100vh; min-height: 100dvh; }

.sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  background: var(--surface-warm);
  border-inline-end: 1px solid var(--border);
  padding: var(--s5) var(--s4) var(--s4);
  display: flex; flex-direction: column; gap: var(--s5);
  position: sticky; top: 0; height: 100vh; height: 100dvh; overflow-y: auto;
}
.sidebar-brand {
  display: flex; align-items: center; gap: var(--s3);
  text-decoration: none; color: var(--ink); padding: 0 var(--s2);
}
.sidebar-brand img { width: 34px; height: 34px; border-radius: var(--r-sm); }
.sidebar-brand .word {
  font-family: var(--serif); font-size: 22px; font-weight: 600;
  letter-spacing: -0.01em; line-height: 1.15;
}
.sidebar-brand .sub {
  font-size: 11.5px; color: var(--ink-faint); letter-spacing: 0.01em;
}
/* Four uppercase group headers over twenty-one rows made the rail read as an
   outline of the product rather than a way through it. The groups still exist
   -- they carry an aria-label, so the structure survives for a screen reader --
   but visually they are separated by space and a hairline instead of shouting
   their own names. */
.nav-group { display: flex; flex-direction: column; gap: 2px; }
.nav-group + .nav-group {
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.side-link {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s2) var(--s3); border-radius: var(--r);
  color: var(--ink-soft); text-decoration: none;
  font-size: 14.5px; font-weight: 500; line-height: 1.35;
  transition: background 120ms ease, color 120ms ease;
}
.side-link:hover { background: rgba(28, 22, 19, 0.045); color: var(--ink); }
/* The selected row is a raised chip, not an orange slab. Twenty-one rows with
   one saturated fill made the rail the loudest thing on screen; here the lift
   comes from the surface and the brand does the precise work -- it colours the
   icon and nothing else. */
.side-link.on {
  background: var(--surface); color: var(--ink); font-weight: 600;
  box-shadow: 0 1px 2px rgba(28, 22, 19, 0.07);
}
.side-link.on:hover { background: var(--surface); color: var(--ink); }
.side-link.on .dot { color: var(--brand); }
.side-link:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.side-link .dot {
  width: 20px; height: 20px; flex: 0 0 20px; color: var(--ink-faint);
  display: inline-flex; align-items: center; justify-content: center;
  transition: color 120ms ease;
}
.side-link:hover .dot { color: var(--ink-soft); }
/* A hairline is what separates the destinations from the account controls --
   the footer used to be three bordered boxes stacked under the nav, which read
   as three more rows in the same list. */
.sidebar-foot {
  margin-top: auto; padding-top: var(--s4);
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: var(--s2);
}
.sidebar-venue {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); padding: var(--s3); font-size: 13px;
}
.sidebar-venue .vname { font-weight: 600; color: var(--ink); }
.sidebar-venue .vcity { color: var(--ink-faint); font-size: 12px; }

/* --- the sidebar now carries the utilities too ------------------------ */
/* With no header, the sidebar owns search, alerts and the venue switcher.
   That removes a whole horizontal band of chrome and gives the content the
   full height of the window. */
.rail-utils { display: flex; align-items: center; gap: var(--s2); }
.rail-search {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--s2);
  height: 38px; padding: 0 var(--s3); border-radius: var(--r-full);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--ink-faint); font-size: 13.5px; cursor: pointer; text-align: start;
  transition: border-color 120ms ease, color 120ms ease;
}
/* Hover firms the edge rather than painting it orange: the search field is not
   a selected thing, and orange here competed with the active row. */
.rail-search:hover { border-color: var(--border-strong); color: var(--ink-soft); }
.rail-search:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.rail-search span {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-search kbd {
  font-size: 10px; padding: 2px 5px; border-radius: var(--r-sm);
  background: var(--canvas); border: 1px solid var(--border);
  color: var(--ink-faint);
}
.rail-bell { position: relative; flex: 0 0 auto; }

/* The venue button is the identity block: one control, not two. It is the only
   bordered card down here, so the restaurant's name anchors the foot of the
   rail instead of competing with two other boxes. */
.sidebar-foot .venue {
  width: 100%; display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--border);
  cursor: pointer; text-align: start;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.sidebar-foot .venue:hover {
  border-color: var(--border-strong);
  box-shadow: 0 1px 2px rgba(28, 22, 19, 0.06);
}
.sidebar-foot .venue:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.sidebar-foot .venue-chev { margin-inline-start: auto; flex: 0 0 auto; }
.sidebar-foot .menu { bottom: calc(100% + var(--s2)); top: auto; inset-inline-start: 0; inset-inline-end: 0; }
.sidebar-foot .vtag {
  display: inline-flex; align-items: center; gap: var(--s2);
  margin: var(--s2) 0 0 var(--s3);
  font-size: 11px; color: var(--brand-text); font-weight: 600;
}
.sidebar-foot .vtag::before {
  content: ""; width: var(--s1); height: var(--s1); border-radius: 50%;
  background: var(--brand);
}

/* --- reference proportions: quieter chrome, roomier nav --------------- */
.sidebar { padding: var(--s5) var(--s3) var(--s3); gap: var(--s4); }

/* A centred canvas: full-bleed content at 1800px reads as a spreadsheet. */
.page { max-width: 1180px; margin: 0 auto; width: 100%; }

.menu-note {
  padding: var(--s2) var(--s3);
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-faint);
}
.sidebar-venue .vtag {
  display: inline-flex; align-items: center; gap: var(--s2); margin-top: var(--s2);
  font-size: 11px; color: var(--brand-text); font-weight: 600;
}
.sidebar-venue .vtag::before {
  content: ""; width: var(--s1); height: var(--s1); border-radius: 50%;
  background: var(--brand);
}

.shell-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* Between tablet and laptop the rail narrows instead of vanishing: losing
   the navigation is a worse trade than losing 52px of content width. */
@media (max-width: 1040px) and (min-width: 721px) {
  :root { --sidebar-w: 216px; }
  .side-link { font-size: 14px; padding: var(--s2); }
}

/* The bottom tab bar appears only where the sidebar is a drawer. Above that
   the sidebar is persistent, and showing both would put two primary
   navigations on screen at once. */
@media (min-width: 721px) {
  .mobilenav { display: none !important; }
  .page { padding-bottom: var(--s6); }
}

/* A drawer needs something to close against, and something to dim the
   content it covers. */
.sidebar-scrim {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(32, 22, 16, 0.38);
  opacity: 0; pointer-events: none; transition: opacity 160ms ease;
}
.sidebar-scrim.on { opacity: 1; pointer-events: auto; }
@media (min-width: 721px) { .sidebar-scrim { display: none; } }

/* ---------- the banner ---------- */
/* The ground is cream, not orange. A full-bleed saturated panel made the one
   screen an owner reads every morning the loudest thing on it; inverting the
   relationship -- warm ivory field, white cards, orange as an accent -- keeps
   the brand present and lets the figures carry the weight. */
.hero {
  position: relative;
  background: var(--canvas); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--s4);
  margin-bottom: var(--s5);
  display: grid; gap: var(--s4);
}
.hero-top {
  display: grid; grid-template-columns: 1.05fr 1.35fr 0.85fr;
  gap: var(--s4); align-items: stretch;
}

/* --- greeting --- */
.hero-copy {
  position: relative; background: var(--surface-warm);
  border-radius: var(--r); padding: var(--s5) var(--s5) var(--s4);
  min-width: 0; display: flex; flex-direction: column;
}
/* A short brand rule rather than a coloured heading: the accent marks where
   the greeting starts, then gets out of the way. */
.hero-copy::before {
  content: ''; position: absolute; inset-inline-start: var(--s5); top: var(--s5);
  width: 3px; height: 58px; border-radius: 2px; background: var(--brand);
}
.hero-date {
  display: inline-flex; align-items: center; gap: var(--s2); align-self: flex-start;
  background: var(--surface); color: var(--ink-soft);
  border: 1px solid var(--border); border-radius: var(--r-full);
  padding: var(--s1) var(--s3); font-size: 11.5px; font-weight: 600;
  margin-bottom: var(--s4);
}
.hero-date::before {
  content: ''; width: var(--s2); height: var(--s2); border-radius: 50%;
  background: var(--brand);
}
.hero-hello {
  font-size: 23px; font-weight: 650; color: var(--brand-text);
  letter-spacing: -.02em; line-height: 1.1; padding-inline-start: var(--s4);
}
.hero h1 {
  font-family: var(--serif); font-size: clamp(28px, 2.6vw, 36px); font-weight: 600;
  letter-spacing: -.02em; line-height: 1.05; color: var(--ink);
  margin: 2px 0 var(--s4); padding-inline-start: var(--s4);
  border: none; text-wrap: balance;
}
.hero p {
  font-size: 13.5px; line-height: 1.6; color: var(--ink-soft);
  max-width: 46ch; margin: 0; padding-inline-start: var(--s4);
}
.hero p strong { color: var(--ink); font-weight: 650; }
.hero-mascot {
  display: block; width: 116px; height: auto; margin-top: auto;
  padding-top: var(--s4); pointer-events: none;
}

/* The mascot is drawn peeking over a ledge with its hands on the edge, so
   every placement puts it on a real one. In normal flow directly above the
   venue card, so its hands land on that card's top edge whatever its height. */
.venue-peek {
  display: block; width: 84px; height: auto;
  margin: 0 auto -1px; pointer-events: none; position: relative; z-index: 2;
}
.empty-mascot {
  width: 120px !important; height: auto !important;
  border-radius: 0 !important; margin-bottom: var(--s3);
}
/* Holds the trend's space when there is not enough history to draw one. */
.trend-empty { height: 96px; }

/* --- the headline figure --- */
.hero-lead {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r); padding: var(--s5);
  min-width: 0; display: flex; flex-direction: column; gap: var(--s2);
}
.hero-lead-l { font-size: 13px; font-weight: 650; color: var(--ink); }
.hero-lead-v {
  font-size: 36px; font-weight: 700; letter-spacing: -.035em; color: var(--ink);
  line-height: 1.1; display: flex; align-items: baseline; gap: var(--s3);
  font-variant-numeric: tabular-nums; flex-wrap: wrap;
}
.hero-lead-d { font-size: 14px; font-weight: 650; }
.hero-lead-sub {
  font-size: 11.5px; color: var(--ink-faint); font-weight: 500;
  margin-inline-start: var(--s2);
}

/* --- the KPI rail --- */
.hero-kpis {
  background: var(--surface-warm); border-radius: var(--r);
  padding: var(--s2) var(--s4); min-width: 0;
  display: flex; flex-direction: column; justify-content: center;
}
.hero-kpi {
  display: flex; align-items: baseline; gap: var(--s3);
  padding: var(--s3) 0; border-bottom: 1px solid var(--border);
}
.hero-kpi:last-child { border-bottom: none; }
.hero-kpi-main { flex: 1; min-width: 0; }
.hero-kpi-l { font-size: 11.5px; color: var(--ink-soft); }
.hero-kpi-v {
  font-size: 18px; font-weight: 700; color: var(--ink); letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; line-height: 1.25;
}
.hero-kpi-d { font-size: 12px; font-weight: 650; white-space: nowrap; }

/* --- the three analytics cards --- */
.hero-tiles {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s4);
}
.tile {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r); padding: var(--s4) var(--s5) var(--s5);
  min-width: 0; display: flex; flex-direction: column; gap: var(--s4);
}
.tile-h { font-size: 13px; font-weight: 650; color: var(--ink); letter-spacing: -.01em; }
.tile-b { flex: 1; min-width: 0; }
.tile-foot { font-size: 11.5px; color: var(--ink-faint); margin-top: var(--s3); }

/* ring: composition. Identity comes from the legend, never the ring alone. */
.tile-donut { display: flex; align-items: center; gap: var(--s5); }
.donut-wrap { position: relative; width: 108px; height: 108px; flex: 0 0 108px; }
.donut { width: 100%; height: 100%; transform: rotate(-90deg); }
.donut-face {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; pointer-events: none;
}
.donut-v { font-size: 21px; font-weight: 700; letter-spacing: -.02em; color: var(--ink); }
.donut-k { font-size: 10px; color: var(--ink-faint); }
.lg-list { display: flex; flex-direction: column; gap: var(--s2); flex: 1; min-width: 0; }
.lg { display: flex; align-items: center; gap: var(--s3); font-size: 12.5px; min-width: 0; }
.lg-k { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; }
.lg-n {
  color: var(--ink-soft); flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lg-v { color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; }

@media (max-width: 1250px) {
  .hero-top { grid-template-columns: 1fr 1fr; }
  .hero-kpis { grid-column: 1 / -1; }
  .hero-kpi { padding: var(--s2) 0; }
  .hero-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 860px) {
  .hero { padding: var(--s3); }
  .hero-top { grid-template-columns: 1fr; }
  .hero-tiles { grid-template-columns: 1fr; }
  .hero-mascot { width: 96px; }
  .hero-kpis { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--s4); }
}

/* ===================== charts ===================== */
/* Marks carry the series colour; every value, label and axis wears an ink
   token. Bars cap at 24px and never fill their slot -- the leftover is air --
   the data-end is rounded and the baseline end is square, and neighbours are
   separated by a gap in the surface rather than a stroke. */
/* A value scale on the left, because "which day is strongest" is followed
   straight away by "by how much". One definition, shared by the banner tile
   and the breakdown section below it. */
.colwrap { display: flex; gap: var(--s3); }
.colaxis {
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: 9.5px; color: var(--ink-faint); text-align: end;
  padding-bottom: 18px; flex: 0 0 auto; font-variant-numeric: tabular-nums;
}
.colchart {
  flex: 1; display: flex; align-items: flex-end; gap: 3px;
  height: 128px; width: 100%;
}
.col {
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
  align-items: center; gap: var(--s1); height: 100%;
}
.col-slot {
  flex: 1; width: 100%; position: relative;
  display: flex; align-items: flex-end; justify-content: center;
}
/* Neutral by default; only the answer takes the brand, so the highlight
   means something. */
.col-bar {
  width: 100%; max-width: 26px;
  background: var(--border-strong); border-radius: 5px 5px 0 0;
}
.col.peak .col-bar { background: var(--brand); }
.col-tag {
  position: absolute; top: -20px; inset-inline-start: 50%; transform: translateX(-50%);
  background: var(--brand); color: var(--ink); font-size: 9.5px;
  font-weight: 650; border-radius: var(--r-full); padding: 2px var(--s2);
  white-space: nowrap;
}
.col-l { font-size: 10px; color: var(--ink-faint); }

/* Ranked bars: length is the magnitude, the label is the identity, so nothing
   here depends on telling two colours apart. */
.rbars { display: flex; flex-direction: column; gap: var(--s2); }
.rbar {
  /* The name gets the room, because "Crispy chicken deluxe" truncating to
     "Crispy chicken..." makes the ranking unreadable; the track only has to
     show relative length, not measure it. */
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(44px, 1fr) auto;
  align-items: center; column-gap: var(--s3); row-gap: 2px;
}
.rbar-l {
  font-size: 12.5px; color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rbar-track { height: 10px; border-radius: 0 4px 4px 0; background: var(--surface-warm); }
.rbar-track i { display: block; height: 100%; background: var(--brand); border-radius: 0 4px 4px 0; }
.rbar-v {
  font-size: 12.5px; font-weight: 650; color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.rbar-m {
  grid-column: 2 / -1; font-size: 11px; color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* Two columns, not auto-fit: there are four panels, and auto-fit put three in
   the first row and stranded the fourth beside dead space. 2x2 also buys each
   card the width that stops menu-item names truncating. */
.charts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4); margin-top: var(--s5);
}
@media (max-width: 1040px) { .charts { grid-template-columns: 1fr; } }
.chart {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--s5);
  display: flex; flex-direction: column; gap: var(--s4); min-width: 0;
}
.chart-h { font-size: 14px; font-weight: 650; letter-spacing: -.01em; color: var(--ink); }
.chart-body { flex: 1; display: flex; align-items: flex-end; }
.chart-foot { font-size: 11.5px; color: var(--ink-faint); }

@media (max-width: 720px) {
  .charts { grid-template-columns: 1fr; gap: var(--s3); }
  .chart { padding: var(--s4); }
  .rbar { grid-template-columns: minmax(72px, 40%) 1fr auto; }
}

/* ===================== dashboard modules ===================== */
/* Not every block is a card. A section is a heading, a hairline and its
   content on the canvas; cards are spent only where a thing is genuinely a
   separate object you can act on -- the findings, the recommendations. */
.sec-head {
  display: flex; align-items: baseline; gap: var(--s3);
  margin: var(--s7) 0 var(--s4); padding-bottom: var(--s3);
  border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
.sec-head h2 {
  font-size: 19px; font-weight: 700; letter-spacing: -.025em; margin: 0;
  border: none; padding: 0;
}
.sec-sub { font-size: 13px; color: var(--ink-soft); }

/* --- key metrics: one band, hairline-divided, no boxes ------------------ */
.kpis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden; margin-top: var(--s5);
}
.kpi { padding: var(--s4); border-inline-start: 1px solid var(--border); }
.kpi:first-child { border-inline-start: none; }
.kpi-l { font-size: 12.5px; color: var(--ink-soft); }
.kpi-v {
  font-size: 23px; font-weight: 700; letter-spacing: -.025em; line-height: 1.2;
  margin-top: 2px; font-variant-numeric: tabular-nums;
}
.kpi-d { font-size: 12.5px; font-weight: 600; margin-top: var(--s1); }

/* --- the evidence strip inside a finding card -------------------------- */
/* The bar is this finding's money against the largest on the board, so the
   ranking is visible before the figures are read. */
.attn-top { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s2); }
.attn-top .attn-meta { margin: 0; }
.attn-ev {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  column-gap: var(--s3); margin: var(--s3) 0 var(--s2);
}
.attn-ev-bar {
  grid-column: 1; height: 6px; border-radius: var(--r-full);
  background: var(--surface-warm); overflow: hidden;
}
.attn-ev-bar i { display: block; height: 100%; border-radius: var(--r-full); background: var(--sev); }
.attn-ev-v {
  grid-column: 2; font-size: 15px; font-weight: 700; color: var(--sev);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.attn-ev-l { grid-column: 1 / -1; font-size: 11.5px; color: var(--ink-faint); margin-top: 3px; }

/* --- what changed: movement, drawn -------------------------------------- */
.changed {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden;
}
.chg {
  display: grid; grid-template-columns: 1.1fr 1fr auto auto;
  align-items: center; gap: var(--s4);
  width: 100%; text-align: start; padding: var(--s3) var(--s4);
  border: none; border-top: 1px solid var(--border); background: var(--surface);
  cursor: pointer; transition: background var(--fast);
}
.chg:first-child { border-top: none; }
.chg:hover { background: var(--canvas); }
.chg:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.chg-l { font-size: 13.5px; font-weight: 600; color: var(--ink); min-width: 0; }
.chg-track { height: 8px; border-radius: var(--r-full); background: var(--surface-warm); overflow: hidden; }
.chg-track i { display: block; height: 100%; border-radius: var(--r-full); }
.chg.good .chg-track i { background: var(--success); }
.chg.bad  .chg-track i { background: var(--critical); }
.chg-p { font-size: 13.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.chg.good .chg-p { color: var(--success); }
.chg.bad  .chg-p { color: var(--critical); }
.chg-v { font-size: 12.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* --- recommendations: two at most, never a second alert wall ------------ */
/* Room above for the mascot to sit on the first card's edge. */
.recs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--s4); margin-top: var(--s5);
}
.rec {
  position: relative; background: var(--brand-wash);
  border: 1px solid var(--brand-edge); border-radius: var(--r-lg);
  padding: var(--s5); display: flex; flex-direction: column;
}
/* One mascot, on the first recommendation only -- it marks where Tori is
   speaking, and a mascot beside every card would stop marking anything. It
   peeks over the card's top edge with its hands on it, which is how the art is
   drawn; sat inline in a flex row it was squashed into an unreadable squiggle. */
/* translateY(-100%) rather than a guessed negative top: it lands the art's
   bottom edge -- where the hands are drawn -- exactly on the card's top edge,
   whatever height the image resolves to. */
.rec-mascot {
  position: absolute; top: 0; inset-inline-end: var(--s5);
  transform: translateY(-100%);
  width: 66px; height: auto; pointer-events: none;
}
.rec-body { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; }
.rec-t { font-size: 15.5px; font-weight: 600; line-height: 1.45; color: var(--ink); }
.rec-why {
  font-size: 13px; line-height: 1.5; color: var(--ink-soft); margin-top: var(--s2);
}
.rec-why b { color: var(--brand-text); font-weight: 650; }
.rec .btn { margin-top: var(--s4); }

/* --- ask tori: a shortcut, not a chat panel ----------------------------- */
.askbar {
  width: 100%; text-align: start; margin-top: var(--s7);
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s4) var(--s5); border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--border); cursor: pointer;
  transition: border-color var(--fast), box-shadow var(--fast);
}
.askbar:hover { border-color: var(--brand); box-shadow: var(--shadow-sm); }
.askbar:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.askbar-mark { width: 40px; height: 40px; border-radius: var(--r); flex: 0 0 40px; }
.askbar-body { display: block; min-width: 0; flex: 1; }
.askbar-t { display: block; font-size: 15px; font-weight: 650; color: var(--ink); }
.askbar-s { display: block; font-size: 13px; color: var(--ink-soft); margin-top: 2px; }
.askbar-go { font-size: 18px; color: var(--brand-text); flex-shrink: 0; }

@media (max-width: 720px) {
  .kpis { grid-template-columns: repeat(2, 1fr); }
  .kpi { border-inline-start: none; border-top: 1px solid var(--border); }
  .kpi:nth-child(-n+2) { border-top: none; }
  .kpi:nth-child(odd) { border-inline-end: 1px solid var(--border); }
  /* The bar is the point; the "from" figures are the detail that goes. */
  .chg { grid-template-columns: 1fr auto; gap: var(--s2) var(--s3); }
  .chg-track { grid-column: 1 / -1; order: 3; }
  .chg-v { display: none; }
  .rec { padding: var(--s4); }
  .rec-mascot { display: none; }
  .askbar { padding: var(--s4); gap: var(--s3); }
  .sec-head { margin-top: var(--s6); }
}

/* ---------- selectable trend + operational snapshot ---------- */
.lc { display: flex; gap: var(--s3); }
.lc-axis {
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: 10px; color: var(--ink-faint); text-align: end;
  padding-bottom: 18px; flex: 0 0 auto; font-variant-numeric: tabular-nums;
}
.lc-plot { flex: 1; min-width: 0; }
.linechart { display: block; width: 100%; height: 200px; }
.lc-x {
  display: flex; justify-content: space-between;
  font-size: 10px; color: var(--ink-faint); margin-top: var(--s2);
  font-variant-numeric: tabular-nums;
}

/* Two columns that read as one snapshot rather than two unrelated lists. */
.snap { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s5); }
.snap-col { min-width: 0; }
.snap .sec-head { margin-top: var(--s7); }
.list-amt.bad { color: var(--critical); }
@media (max-width: 860px) { .snap { grid-template-columns: 1fr; gap: 0; } }

/* ---------- editorial section headings ---------- */
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); margin: var(--s7) 0 var(--s4);
}
.section-head h2 {
  font-family: var(--serif); font-size: 30px; font-weight: 600;
  letter-spacing: -0.015em; margin: 0; border: none; padding: 0;
}
.section-head .sub { color: var(--ink-soft); font-size: 14px; margin-top: 4px; }

/* ---------- responsive: sidebar becomes a drawer ---------- */
.sidebar-toggle { display: none; }
@media (max-width: 720px) {
  .sidebar {
    position: fixed; inset-inline-start: 0; top: 0; z-index: 60;
    /* transform: translateX() is a physical (always-left/-right) transform
       -- CSS logical properties don't cover transforms, so direction has to
       be handled explicitly here, or an RTL reader gets a sidebar sitting
       on the right (inset-inline-start correctly puts it there) that still
       slides off toward the left, the wrong edge for its own position. */
    transform: translateX(-100%); transition: transform 180ms ease;
    box-shadow: 0 0 40px rgba(28, 22, 19, 0.20);
  }
  [dir="rtl"] .sidebar { transform: translateX(100%); }
  .sidebar.open { transform: none; }
  .sidebar-toggle { display: inline-flex; }
  /* The banner's padding and 22px radius went with the banner; the opening is
     plain text on the canvas now and needs neither. */
}

/* ===================== footer ===================== */
/* Direction: light, branded. A dark slab would read as a marketing footer
   bolted onto an application — the whole product is a warm light system, and
   §19 warns against choosing dark because it "looks premium". The warm surface
   token already in the palette gives a tonal shift instead of a hard break.
   Orange appears exactly twice down here: a short accent segment and the
   status dot. */

/* Sticky-on-short-pages without absolute positioning: the shell is a column,
   the page grows, the footer settles wherever the content ends. */
body { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
.page { flex: 1 0 auto; width: 100%; }

.footer {
  position: relative; flex-shrink: 0;
  background: var(--surface-warm); border-top: 1px solid var(--border);
}
/* A short orange segment rather than a full orange rule: enough to signal the
   brand at the transition, quiet enough to stay a detail. */
.footer::before {
  content: ''; position: absolute; top: -1px; inset-inline-start: 0; width: 72px; height: 2px;
  background: var(--brand); border-radius: 0 2px 2px 0;
}

.footer-inner {
  max-width: 1320px; margin: 0 auto; padding: var(--s7) var(--s5) var(--s5);
  display: grid; grid-template-columns: 1.5fr repeat(3, 1fr); gap: var(--s6);
}

.footer-brand { max-width: 30ch; }
/* One treatment on the logo, not five: a warm plate with a hairline. */
.footer-logo {
  width: 44px; height: 44px; border-radius: var(--r); display: block;
  background: var(--surface); border: 1px solid var(--border); padding: 0;
  box-sizing: content-box;
}
.footer-wordmark {
  font-size: 17px; font-weight: 700; letter-spacing: -.03em;
  margin-top: var(--s3); color: var(--ink);
}
.footer-tagline {
  font-size: 13.5px; color: var(--ink-soft); margin-top: var(--s1); line-height: 1.5;
}

.footer-col-title {
  font-size: 11px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: var(--s3);
}
.footer-links { display: flex; flex-direction: column; gap: var(--s2); }
.footer-link {
  font-size: 13.5px; color: var(--ink-soft); text-align: start; padding: 2px 0;
  transition: color var(--fast), transform var(--fast); width: fit-content;
  display: inline-flex; align-items: center; gap: var(--s1);
  /* 13.5px over 2px padding measured 23px -- one pixel under WCAG 2.2's 24px
     minimum target (2.5.8 AA). These are a column of links, not links inside a
     sentence, so the inline exception does not apply to them. The mobile rule
     below already padded them out; this is the same courtesy on the desktop. */
  min-height: 24px;
}
.footer-link:hover { color: var(--brand-text); transform: translateX(2px); }
.footer-link.on { color: var(--ink); font-weight: 550; }
.footer-link svg { opacity: .6; }

/* A button styled to read as an inline text link -- e.g. "Terms of Service"
 * inside a sentence -- rather than the footer's own block-level links above. */
.text-link { display: inline; color: var(--brand-text); text-decoration: underline; }
.text-link:hover { color: var(--brand-hover); }
/* The same treatment where the link is NOT inside a sentence -- the Legal row
 * is four standalone links, and a flex parent blockifies them regardless. Away
 * from running text WCAG 2.2's 24px minimum applies in full, because the
 * inline-text exception these would otherwise inherit is exactly that: for
 * text. At 13.5px they measured 21px. */
.text-link.standalone { display: inline-flex; align-items: center; min-height: 24px; }

.footer-meta {
  max-width: 1320px; margin: 0 auto; padding: var(--s4) var(--s5) var(--s5);
  border-top: 1px solid var(--border);
  display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
  font-size: 12.5px; color: var(--ink-faint);
}
.footer-status { display: inline-flex; align-items: center; gap: var(--s1); color: var(--ink-soft); }
.footer-dot {
  width: var(--s2); height: var(--s2); border-radius: 50%; background: var(--success);
  flex-shrink: 0;
}
.footer-dot.warn { background: var(--warning); }
.footer-dot.bad { background: var(--critical); }
.footer-sep { color: var(--border-strong); user-select: none; }
.footer-meta .spacer { margin-inline-start: auto; }
.footer-version { font-variant-numeric: tabular-nums; }

@media (max-width: 1100px) {
  .footer-inner { grid-template-columns: 1fr 1fr 1fr; gap: var(--s5); }
  .footer-brand { grid-column: 1 / -1; max-width: none; }
}

@media (max-width: 900px) {
  /* Twelve links is short enough to stack honestly — §26 warns against
     accordions the content does not need. Two columns keeps it compact
     without hiding anything behind a tap. */
  .footer-inner {
    grid-template-columns: 1fr 1fr; gap: var(--s5) var(--s4);
    padding: var(--s6) var(--s3) var(--s4);
  }
  .footer-brand { grid-column: 1 / -1; }
  .footer-link { padding: var(--s1) 0; }     /* comfortable touch targets */
  .footer-meta { padding: var(--s3) var(--s3) var(--s4); gap: var(--s2) var(--s3); }
  .footer-meta .spacer { margin-inline-start: 0; width: 100%; height: 0; }
  .footer { margin-bottom: 62px; }           /* clears the mobile bottom nav */
}

/* ===================== standard action pattern ===================== */
/* Current → change → result → confirm. One shape for every mutation, so an
   operator learns it once. */

.act-head { font-size: 17px; font-weight: 650; margin-bottom: var(--s4); }
.act-grid {
  display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1px;
  background: var(--border); border: 1px solid var(--border);
  border-radius: var(--r); overflow: hidden; margin: var(--s4) 0;
}
.act-block { background: var(--surface); padding: var(--s3) var(--s4); }
.act-label {
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: var(--s1);
}
.act-value { font-size: 14px; font-weight: 550; line-height: 1.4; }
.act-grid .act-block:last-child .act-value { color: var(--brand-text); }

/* ===================== needs attention ===================== */
/* Not wrapped in a card each: a queue reads as a list, and stacking rounded
   rectangles is what makes a product look like a dashboard template. */

/* A grid of cards, not a stack of rows. Each finding is a separate thing the
   owner can act on, and eight of them in one bordered container read as a
   backlog to get through rather than eight decisions to make. Depth here is
   tonal -- white cards on the warm canvas, no border -- so the grid stays
   quiet and the card edges come from the ground they sit on. */
/* Two columns, not auto-fit. The dashboard shows four findings, and auto-fit
   put three in the first row and left the fourth stranded beside a column of
   dead space; 2x2 reads as one deliberate block. */
.attn {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 860px) { .attn { grid-template-columns: 1fr; } }
.attn-row {
  display: flex; flex-direction: column; gap: var(--s2);
  padding: var(--s5); border-radius: var(--r-lg);
  background: var(--surface);
  transition: box-shadow var(--fast);
}
.attn-row:hover { box-shadow: var(--shadow-sm); }
/* The severity dot became a severity-tinted icon tile: at card scale a 9px dot
   was a speck, and the icon says what kind of problem it is before the title
   is read. */
.attn-dot {
  width: 38px; height: 38px; border-radius: var(--r); flex-shrink: 0;
  background: var(--sev-wash, var(--surface-warm)); color: var(--sev);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: var(--s2);
}
.attn-dot svg { width: 19px; height: 19px; }
.attn-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s1); }
.attn-meta {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 11.5px; font-weight: 600; margin-bottom: 2px;
}
.attn-sev { color: var(--sev); }
.attn-cat { color: var(--ink-faint); }
.attn-cat::before { content: '· '; }
.attn-title { font-size: 17px; font-weight: 650; letter-spacing: -.02em; line-height: 1.3; }
.attn-impact { font-size: 13.5px; font-weight: 650; color: var(--sev); }
.attn-why { font-size: 13.5px; color: var(--ink-soft); line-height: 1.5; max-width: 46ch; }
.attn-rec { font-size: 13.5px; color: var(--ink); line-height: 1.5; margin-top: var(--s1); }
.attn-rec b { color: var(--brand-text); font-weight: 600; }
/* Actions sit on the card's floor, so every card in a row ends on the same
   line however much explanation it carries. */
.attn-side {
  display: flex; align-items: center; gap: var(--s2);
  margin-top: auto; padding-top: var(--s4); flex-wrap: wrap;
}
.attn-head {
  display: flex; align-items: baseline; gap: var(--s3);
  margin-bottom: var(--s4); flex-wrap: wrap;
}
.attn-head h2 { font-size: 27px; font-weight: 700; letter-spacing: -.03em; }
.attn-count { font-size: 14px; color: var(--ink-soft); }

/* The reference's quiet action pill: neutral fill, dark label, no border. The
   primary orange button stays for the one action that commits something; these
   are the "go look at this" actions, and eight orange buttons in a grid would
   make every card shout equally. */
.btn.soft {
  background: var(--surface-warm); border-color: transparent;
  color: var(--ink); border-radius: var(--r-full); padding: 0 var(--s4);
  font-weight: 600;
}
.btn.soft:hover { background: var(--brand-wash); border-color: transparent; color: var(--brand-text); }
.btn.quiet {
  background: transparent; border-color: transparent; color: var(--ink-soft);
  border-radius: var(--r-full); padding: 0 var(--s3);
}
.btn.quiet:hover { background: var(--surface-warm); color: var(--ink); }

@media (max-width: 720px) {
  .attn { grid-template-columns: 1fr; gap: var(--s3); }
  .attn-row { padding: var(--s4); }
  .attn-head h2 { font-size: 23px; }
}

/* ===================== activity timeline ===================== */

.tl { border-inline-start: 2px solid var(--border); margin-inline-start: var(--s3); padding-inline-start: var(--s5); }
.tl-day { font-size: 12px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-faint); margin: var(--s5) 0 var(--s3); }
.tl-row { position: relative; padding-bottom: var(--s4); }
.tl-row::before {
  content: ''; position: absolute; inset-inline-start: calc(-1 * var(--s5) - 5px); top: 5px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--brand);
  border: 2px solid var(--canvas);
}
.tl-time { font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.tl-title { font-size: 14.5px; font-weight: 600; margin-top: 2px; }
.tl-detail { font-size: 13px; color: var(--ink-soft); margin-top: 2px; }

@media (max-width: 900px) {
  .act-grid { grid-template-columns: 1fr; }
  .attn-row { flex-wrap: wrap; }
  .attn-side { width: 100%; flex-direction: row; align-items: center; }
}

/* ===================== Tori voice system ===================== */
/* §28 Tori Spark — the one branded AI indicator. It appears only where
   provenance proves Tori produced or derived the value, so it means something
   when you see it. If it were on every card it would mean nothing. */

.spark { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; }
.spark svg { width: 13px; height: 13px; color: var(--brand); }
.spark-l {
  font-size: 10.5px; font-weight: 650; letter-spacing: .07em;
  text-transform: uppercase; color: var(--brand-text);
}

/* §12/§13 the insight label system. Fixed labels, so a reading can never be
   dressed up as a measurement. */
.ai-label {
  font-size: 11px; font-weight: 700; letter-spacing: .09em;
  color: var(--brand-text); display: inline-flex; align-items: center;
  gap: var(--s1); margin-bottom: var(--s2);
}
.ai-label.why, .ai-label.evidence { color: var(--ink-faint); }

/* §16/§33 Ask Tori: white workspace, orange reserved for identity and action.
   Deliberately not an orange page. */
.ask-hero {
  display: flex; gap: var(--s5); align-items: flex-start;
  padding: var(--s6) 0 var(--s5);
}
.ask-mark {
  width: 56px; height: 56px; border-radius: var(--r);
  border: 1px solid var(--brand-edge); background: var(--brand-wash);
  padding: var(--s1); flex-shrink: 0;
}
.ask-title { font-size: 13px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brand-text); }
.ask-prompt { font-size: 26px; font-weight: 680; letter-spacing: -.03em;
  margin-top: var(--s1); line-height: 1.2; }
.ask-sub { font-size: 14.5px; color: var(--ink-soft); margin-top: var(--s2);
  max-width: 62ch; line-height: 1.55; }

.ask-cats { display: flex; gap: var(--s2); flex-wrap: wrap; margin: var(--s4) 0 var(--s3); }
.ask-cat {
  font-size: 13px; font-weight: 550; padding: var(--s2) var(--s4);
  border-radius: var(--r-full); border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--ink-soft); transition: all var(--fast);
}
.ask-cat:hover { border-color: var(--brand); color: var(--brand-text); }
.ask-cat.on { background: var(--brand); border-color: var(--brand); color: var(--ink); }

.ask-qs { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
.ask-q {
  text-align: start; font-size: 14px; padding: var(--s3) var(--s4);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink); transition: all var(--fast);
}
.ask-q:hover { border-color: var(--brand); background: var(--brand-wash); }

/* §34 motion: state changes only, nothing decorative. */
.ask-turn { animation: rise 220ms var(--ease) both; }
@keyframes rise { from { opacity: 0; transform: translateY(4px); } }

.thinking { display: inline-flex; align-items: center; gap: var(--s2);
  font-size: 13.5px; color: var(--ink-soft); }
.thinking i {
  width: var(--s1); height: var(--s1); border-radius: 50%; background: var(--brand);
  animation: pulse 1.1s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  /* .thinking i is handled with the other indicators in the single
     reduced-motion block above; only .ask-turn's entrance belongs here. */
  .ask-turn { animation: none; }
}

@media (max-width: 900px) {
  .ask-qs { grid-template-columns: 1fr; }
  .ask-hero { gap: var(--s3); padding: var(--s4) 0; }
  .ask-mark { width: 44px; height: 44px; }
  .ask-prompt { font-size: 21px; }
}

/* ===================== onboarding ===================== */

/* The validation slot keeps its height whether or not it holds a message.
   Onboarding is the one flow where a person is aiming at a button they have
   never seen before, on a card whose contents change under them between steps;
   a message that appears above the form and shoves it down is the worst
   possible moment for the target to move. One line's worth of space is
   reserved from the start, so filling it changes colour, not layout. */
.onb-err > * { visibility: hidden; }
.onb-err.is-shown > * { visibility: visible; }
