/*
  One system, three surfaces.

  The direction is editorial: photography and air. A product photograph does the
  work that icons and colour usually pretend to do, hairlines separate things
  instead of boxes, and white space is the main material. The customer screen is
  read standing up at a metre; the staff consoles are read on a tablet held close.
  `data-surface` on <body> swaps the scale, not the system.

  Three rules this file exists to hold:

  * A card must earn its border by needing elevation. Almost nothing does. Rules
    and space separate more quietly and survive being looked at from a distance.
  * Colour carries meaning or it is absent. Emerald means "go" — the pay button
    and an approval. Amber means a person is needed. Rose means the law or the
    money says no. There is no fourth colour.
  * The number a person acts on is the largest thing on screen: the total for a
    customer, the age for an attendant. Everything else steps down sharply.
*/

:root {
  /* The shop's own colours.
     
     Five are chosen and the rest are worked out from them. That is the whole
     arrangement: a shop picks the paper, the ink, the accent and the text on
     the accent, and every shade in between — the hover surfaces, the hairlines,
     the quiet second-rank text, the pressed state of a button — is mixed from
     those in perceptual space, so they stay related to each other however
     unusual the choice.
     
     Five rather than thirty because thirty is not more control, it is thirty
     chances to make two things almost the same colour. And five rather than one
     because a shop asked for its own colours and got a hue slider, which is a
     designer's idea of control rather than a shopkeeper's.
     
     What a shop cannot repaint is warning amber and refusal red, below. Those
     are not decoration: they mean "a person is needed" and "the law says no",
     and a shop that painted its refusals green would be a shop whose staff stop
     reading them. */
  --bg: #f5f7f4;          /* the paper: what a screen is mostly made of */
  --surface: #fdfefd;     /* cards and panels sitting on it */
  --ink: #121915;         /* the text: the near-black of the shop's roundel */
  /* The one colour that means "this is the action", pitched at the shop's own
     green without becoming it. The logo's green is about 3.3 : 1 on white —
     handsome on a sign, unreadable under white text on a button — so this keeps
     its hue and takes the weight down to 5.4 : 1, which a customer can read
     across a lane. */
  --accent: #25783f;
  --accent-ink: #fdfefd;  /* text on the accent */

  /* Everything below is mixed from those. color-mix works in oklab, which is
     perceptual, so "12% of the ink into the paper" is a line that looks like a
     line at any lightness — rather than the muddy grey the same arithmetic in
     sRGB would give. */
  --surface-2: color-mix(in oklab, var(--bg), var(--ink) 4%);
  --surface-3: color-mix(in oklab, var(--bg), var(--ink) 8%);
  --line: color-mix(in oklab, var(--bg), var(--ink) 12%);
  --line-2: color-mix(in oklab, var(--bg), var(--ink) 24%);

  --ink-2: color-mix(in oklab, var(--ink), var(--bg) 32%);
  --ink-3: color-mix(in oklab, var(--ink), var(--bg) 52%);

  --accent-hi: color-mix(in oklab, var(--accent), var(--ink) 22%);
  --accent-soft: color-mix(in oklab, var(--bg), var(--accent) 12%);
  --accent-line: color-mix(in oklab, var(--bg), var(--accent) 34%);

  /* A person is needed. */
  --warn: oklch(52% 0.105 72);
  --warn-soft: oklch(96.5% 0.032 82);
  --warn-line: oklch(85% 0.075 80);

  /* The law, or the money, says no. */
  --stop: oklch(48% 0.155 27);
  --stop-soft: oklch(96% 0.024 27);
  --stop-line: oklch(84% 0.07 27);

  /* 4pt scale, named for what it does rather than how many pixels it is. */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 12px;
  --space-lg: 16px;
  --space-xl: 24px;
  --space-2xl: 32px;
  --space-3xl: 48px;
  --space-4xl: 64px;
  --space-5xl: 96px;

  --r-sm: 8px;
  --r: 14px;
  --r-lg: 20px;
  --r-xl: 28px;

  /* Tinted toward the surface hue rather than neutral black. */
  --shadow-1: 0 1px 2px color-mix(in oklab, var(--ink) 5%, transparent);
  --shadow-2: 0 16px 34px -14px color-mix(in oklab, var(--ink) 16%, transparent);
  --shadow-3: 0 40px 80px -26px color-mix(in oklab, var(--ink) 30%, transparent);

  /* Exponential deceleration: things arrive and settle, they do not bounce. */
  --ease: cubic-bezier(.22, 1, .36, 1);
  --dur: .26s;

  --font: 'Golos Text', system-ui, sans-serif;
  --font-display: 'Unbounded', 'Golos Text', system-ui, sans-serif;

  /* Per-surface scale, overridden below. */
  --step: 1.0625rem;
  --tap: 48px;
}

/* The customer screen: read standing, at a metre, once. */
body[data-surface="kiosk"] {
  --step: 1.125rem;
  --tap: 60px;
}

/* The staff consoles: read close, all shift, a hundred times. */
body[data-surface="console"] {
  --step: 0.9375rem;
  /* 52, not 42. This is a tablet held by somebody standing up, and the two
     buttons on an age card mean opposite things: one sells alcohol to whoever
     is in front of them, the other refuses. 42px is below both platform
     minimums and neither of these presses can be taken back. */
  --tap: 52px;
}

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

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font: 400 var(--step)/1.5 var(--font);
  font-feature-settings: "ss01";
  -webkit-font-smoothing: antialiased;
}

body[data-surface="kiosk"] {
  /* A panel, not a page. The mouse cursor is deliberately left alone: hiding it
     breaks the machine the moment anyone plugs a mouse in. */
  -webkit-user-select: none;
  user-select: none;
  overflow: hidden;
}

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.02em; line-height: 1.12; }
p { margin: 0; }

/* Display type is for the few moments that carry the screen: the greeting, the
   total, the age. Everywhere else would be shouting. */
.display { font-family: var(--font-display); font-weight: 700; letter-spacing: -.035em; }

.num { font-variant-numeric: tabular-nums; }

.eyebrow {
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.prose { max-width: 68ch; line-height: 1.6; color: var(--ink-2); }

/* --- icons ---------------------------------------------------------------- */

.i {
  width: 1.15em; height: 1.15em; flex: none; display: block;
  stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round; fill: none;
}
.i.lg { width: 1.5em; height: 1.5em; }

/* --- buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-sm);
  min-height: var(--tap);
  padding: 0 var(--space-lg);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  font: inherit; font-weight: 500;
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              transform .12s var(--ease), color var(--dur) var(--ease);
}
.btn:hover:not(:disabled) { background: var(--surface-2); border-color: var(--line-2); }
.btn:active:not(:disabled) { transform: scale(.985); }
.btn:disabled { opacity: .38; cursor: not-allowed; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
.btn.primary:hover:not(:disabled) { background: var(--accent-hi); border-color: var(--accent-hi); }

.btn.ink { background: var(--ink); border-color: var(--ink); color: var(--surface); }
.btn.ink:hover:not(:disabled) { background: color-mix(in oklab, var(--ink), var(--bg) 12%); }

.btn.stop { background: var(--stop); border-color: var(--stop); color: #fff; font-weight: 600; }
.btn.stop:hover:not(:disabled) { background: oklch(42% 0.155 27); }

.btn.quiet { background: none; border-color: transparent; color: var(--ink-2); }
.btn.quiet:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }

.btn.big { min-height: calc(var(--tap) + 16px); font-size: 1.1em; border-radius: var(--r-lg); }
.btn.wide { width: 100%; }
.btn.left { justify-content: flex-start; text-align: left; }

/* --- tags ----------------------------------------------------------------- */

.tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: none;
  color: var(--ink-2);
  font-size: .78em; font-weight: 500; white-space: nowrap;
}
.tag.age {
  background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn);
  font-variant-numeric: tabular-nums; font-weight: 600;
}
.tag.go { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-hi); }
.tag.warn { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn); }
.tag.stop { background: var(--stop-soft); border-color: var(--stop-line); color: var(--stop); }

/* A row of chips: a set of small things chosen by pressing them, or a row of
   counts read off a report. Both consoles had their own copy of this, and the
   import report — which is shared code — is drawn with it, so it lives here. */
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chips button {
  padding: 7px 13px; border-radius: 999px; border: 1px solid var(--line-2);
  background: var(--surface); color: var(--ink); font: inherit;
  font-size: .86em; font-weight: 600; cursor: pointer; white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.chips button:hover { border-color: var(--ink-3); }
.chips button.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* A status dot that breathes, so a live screen never looks frozen. */
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); animation: breathe 2.8s var(--ease) infinite; }
.dot.warn { background: oklch(65% 0.13 72); }
.dot.stop { background: var(--stop); }
.dot.idle { background: var(--ink-3); animation: none; }
@keyframes breathe { 50% { opacity: .35; } }

/* --- notices -------------------------------------------------------------- */

/*
  A tinted block with a full hairline border, not a coloured stripe down one
  edge. The stripe is the most worn-out gesture in admin interfaces and it never
  reads as a decision — a tint that covers the whole message does.
*/
.notice {
  display: flex; gap: var(--space-md); align-items: flex-start;
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--surface-2);
  line-height: 1.45;
}
.notice .i { margin-top: .2em; }
.notice.info,
.notice.success { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-hi); }
.notice.warn { background: var(--warn-soft); border-color: var(--warn-line); color: oklch(40% 0.09 72); }
.notice.error { background: var(--stop-soft); border-color: var(--stop-line); color: oklch(40% 0.13 27); }

/* --- surfaces ------------------------------------------------------------- */

.overlay {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  padding: var(--space-2xl);
  background: color-mix(in oklab, var(--ink) 50%, transparent);
  animation: fade .2s var(--ease);
}
@keyframes fade { from { opacity: 0; } }

.sheet {
  width: min(100%, 680px); max-height: 100%; overflow: auto;
  background: var(--surface);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-3);
  padding: var(--space-2xl);
  animation: rise .34s var(--ease);
}
.sheet.wide { width: min(100%, 1060px); }
@keyframes rise { from { opacity: 0; transform: translateY(12px); } }

/* --- forms ---------------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: var(--space-sm); }
label { font-size: .8125rem; font-weight: 600; color: var(--ink-2); }
.help { font-size: .78rem; color: var(--ink-3); }

input, select, textarea {
  font: inherit; color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  width: 100%;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input[type="checkbox"] { width: 19px; height: 19px; accent-color: var(--accent); }

/* --- loading -------------------------------------------------------------- */

.skel {
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--surface-2) 0%, var(--surface-3) 45%, var(--surface-2) 90%);
  background-size: 300% 100%;
  animation: shimmer 1.6s linear infinite;
}
.skel.line { height: 13px; }
.skel.row { height: 44px; border-radius: var(--r); }
.skel.tile { height: 190px; border-radius: var(--r-lg); }
@keyframes shimmer { from { background-position: 120% 0; } to { background-position: -80% 0; } }

/* --- staggered reveal ----------------------------------------------------- */

.enter { animation: enter .45s var(--ease) both; animation-delay: calc(var(--i, 0) * 40ms); }
@keyframes enter { from { opacity: 0; transform: translateY(6px); } }

/* --- product imagery ------------------------------------------------------ */

/*
  A photograph where one exists; a monogram in the shelf group's colour where one
  does not. Never a pictogram pretending to be the product — a drawing of a
  generic bottle next to a real photograph of bread reads as a missing file, and
  that is exactly what it is.
*/
.shot {
  position: relative; overflow: hidden;
  border-radius: var(--r);
  background: var(--surface-2);
  flex: none;
  display: grid; place-items: center;
}
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shot .mono {
  font-family: var(--font-display);
  font-weight: 700; letter-spacing: -.04em;
  font-size: .34em;
}

/* --- utility -------------------------------------------------------------- */

.row { display: flex; align-items: center; gap: var(--space-md); }
.spacer { flex: 1; }
.muted { color: var(--ink-2); }
.dim { color: var(--ink-3); }
.stack { display: flex; flex-direction: column; gap: var(--space-md); }
.rule { height: 1px; background: var(--line); border: 0; margin: 0; }
.scroll-x { overflow-x: auto; }

.offline-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  padding: var(--space-md);
  text-align: center;
  background: var(--stop); color: #fff; font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}


/* ---- the palette editor ---------------------------------------------------
   A grid of swatches: the colour itself is the biggest thing in each cell,
   because that is what somebody is looking for, and the contrast reading sits
   under the name where it is read at a glance rather than hunted for. */
.palette {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-md);
}
.swatch {
  display: grid;
  grid-template-columns: 40px 1fr;
  grid-template-areas: "colour name" "colour note" "hint hint";
  align-items: center;
  gap: 2px var(--space-sm);
  padding: var(--space-sm);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.swatch input[type="color"] {
  grid-area: colour;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: none;
  cursor: pointer;
}
.swatch .sw-name { grid-area: name; font-size: .9em; line-height: 1.2; }
.swatch .sw-note { grid-area: note; font-size: .78em; font-variant-numeric: tabular-nums; }
.swatch .sw-note.good { color: var(--accent); }
.swatch .sw-note.warn { color: var(--warn); }
.swatch .sw-note.bad  { color: var(--stop); font-weight: 500; }
.swatch .sw-note.dim  { color: var(--ink-3); }
.swatch .sw-hint {
  grid-area: hint;
  margin-top: var(--space-xs);
  font-size: .76em;
  line-height: 1.35;
  color: var(--ink-3);
}

/* ---- the keyboard a till does not have ------------------------------------
   Docked to the bottom edge, because that is where a hand rests on a panel and
   because anything floating would cover the field it is filling. It rises on
   focus and leaves on blur; see useKeyboard in ui.js.

   The keys are 56px because that is what a finger is. Hairlines rather than
   boxes, like everything else here — a keyboard drawn as sixty little cards is
   sixty little shadows. */
.keyboard {
  position: fixed; inset: auto 0 0 0; z-index: 90;
  padding: var(--space-md) var(--space-md) calc(var(--space-md) + env(safe-area-inset-bottom));
  background: var(--surface-2);
  border-top: 1px solid var(--line);
  transform: translateY(101%);
  transition: transform var(--dur) var(--ease);
  /* No height cap: a keyboard with its last row clipped off is a keyboard with
     no space bar. The keys shrink instead, below. */
}
.keyboard.up { transform: none; }
.keyboard .keys { display: grid; gap: 6px; max-width: 1100px; margin: 0 auto; }
.keyboard .krow { display: flex; gap: 6px; justify-content: center; }
.keyboard .key {
  flex: 1 1 auto; min-width: 0; max-width: 96px; height: 56px;
  display: grid; place-items: center;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--bg); color: var(--ink);
  font: inherit; font-size: 1.1rem; line-height: 1;
  cursor: pointer; touch-action: manipulation; user-select: none;
  transition: background .1s var(--ease), transform .08s var(--ease);
}
.keyboard .key:active { background: var(--surface-3); transform: translateY(1px); }
.keyboard .key.wide { max-width: 120px; }
.keyboard .key.space { max-width: none; flex: 4 1 auto; }
.keyboard .key.mod { max-width: 110px; font-size: .9rem; color: var(--ink-2); }
.keyboard .key.mod.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-hi); }
.keyboard .key.done { color: var(--accent-hi); border-color: var(--accent-line); }
.keyboard .digits .key { font-variant-numeric: tabular-nums; }

/* A short screen — a laptop, a landscape panel — gets shorter keys rather than
   fewer rows. Under 560px there is no arrangement that leaves the field visible
   and the keys thumb-sized, so the keys win: a person can scroll to the field,
   but they cannot type on a key that is not drawn. */
@media (max-height: 760px) {
  .keyboard { padding: var(--space-sm) var(--space-sm) calc(var(--space-sm) + env(safe-area-inset-bottom)); }
  .keyboard .keys { gap: 4px; }
  .keyboard .krow { gap: 4px; }
  .keyboard .key { height: 44px; font-size: 1rem; }
}
@media (max-height: 560px) {
  .keyboard .key { height: 36px; font-size: .92rem; }
}

/* ---- admin surfaces --------------------------------------------------------
   The back office and the till's manager console are tools, not pages. See
   .impeccable.md: a shell with a rail, tables rather than cards, density on
   purpose, and colour as a mark rather than a fill.

   The customer screen keeps everything above this line. Nothing here applies
   to it. */

body[data-surface="admin"] {
  --step: 0.9375rem;
  --tap: 34px;
  --rail: 232px;
  background: var(--surface-2);
}

.app {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  min-height: 100vh;
}
@media (max-width: 900px) {
  .app { grid-template-columns: minmax(0, 1fr); }
}

/* The rail: where you are, and everywhere else you could be. */
.rail {
  position: sticky; top: 0; align-self: start; height: 100vh;
  display: flex; flex-direction: column; gap: var(--space-xs);
  padding: var(--space-lg) var(--space-md);
  background: var(--bg); border-right: 1px solid var(--line);
  overflow-y: auto;
}
@media (max-width: 900px) {
  .rail {
    position: sticky; top: 0; z-index: 30; height: auto;
    flex-direction: row; flex-wrap: nowrap; align-items: center;
    padding: var(--space-sm); overflow-x: auto; overscroll-behavior-x: contain;
    border-right: 0; border-bottom: 1px solid var(--line);
  }
  .rail .who, .rail .spacer, .rail .foot { display: none; }
  .rail a, .rail button.nav { width: auto; flex: 0 0 auto; white-space: nowrap; }
  .work-head { position: static; }
}
.rail .who { padding: 0 var(--space-md) var(--space-lg); }
.rail .who .shop {
  font-family: var(--font-display); font-weight: 700; font-size: 1.05rem;
  line-height: 1.2; word-break: break-word;
}
.rail .who .kind { color: var(--ink-3); font-size: .8rem; }
.rail a, .rail button.nav {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: 9px var(--space-md); border: 0; border-radius: var(--r-sm);
  background: none; color: var(--ink-2); font: inherit; font-weight: 500;
  text-align: left; cursor: pointer; width: 100%;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.rail button.nav:hover { background: var(--surface-2); color: var(--ink); }
.rail button.nav.on { background: var(--ink); color: var(--bg); }
.rail button.nav .count {
  margin-left: auto; min-width: 20px; height: 19px; padding: 0 6px;
  display: grid; place-items: center; border-radius: 999px;
  background: oklch(65% 0.13 72); color: #fff;
  font-size: .75rem; font-variant-numeric: tabular-nums;
}
.rail button.nav.on .count { background: var(--bg); color: var(--ink); }
.rail .spacer { flex: 1; }
.rail .foot { padding: var(--space-md); color: var(--ink-3); font-size: .78rem; line-height: 1.5; }

/* The content area: a header that says what this screen is, then the work. */
.work-head {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: baseline; gap: var(--space-md); flex-wrap: wrap;
  padding: var(--space-lg) var(--space-2xl);
  background: color-mix(in oklab, var(--surface-2), transparent 8%);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
/* The header is repainted into itself, so the node the painter builds is a
   pass-through: without this it becomes a flex item of its own, and everything
   pushed to the right edge stops at the width of the text instead. */
.work-head > .work-head { display: contents; }
.work-head h1 { font-size: 1.25rem; font-family: var(--font-display); font-weight: 700; margin: 0; }
.work-head .sub { color: var(--ink-3); font-size: .86rem; }
.work-head .right { margin-left: auto; display: flex; align-items: center; gap: var(--space-md); }
.work-body { padding: var(--space-xl) var(--space-2xl) var(--space-5xl); display: grid; gap: var(--space-2xl); }
/* A table wider than the screen scrolls inside itself rather than pushing the
   page sideways. */
.panel { min-width: 0; }
.panel .scroll-x { overflow-x: auto; max-width: 100%; }
.panel .grid-table { min-width: 100%; }
/* A few report tables are built directly by their section. Keep their many
   accounting columns inside the work area too; otherwise one long shift row
   widens the entire admin shell and leaves controls off-screen. */
.panel > .grid-table { display: block; max-width: 100%; overflow-x: auto; }
@media (max-width: 700px) {
  .work-head, .work-body { padding-left: var(--space-lg); padding-right: var(--space-lg); }
}

/* A panel is a titled area of work. It earns a border only by being a surface
   somebody edits; a list of facts does not need one. */
/* Panels are separated by a rule, not by a box. Three of them in a column with
   nothing between them read as one paragraph of headings. */
.panel { display: grid; gap: var(--space-md); }
/* Any two panels in a row, not only the ones that happen to be direct children
   of the work area: a screen that wraps its panels in a div is still a screen
   with panels on it. */
.panel + .panel { padding-top: var(--space-2xl); border-top: 1px solid var(--line); }
/* An empty state is a fact, not a paragraph: quiet, one line, in the space the
   content would have occupied. */
.panel .empty {
  color: var(--ink-3); padding: var(--space-lg) 0; margin: 0;
  border-top: 1px solid var(--line);
}
.panel > header h2 { font-size: 1rem; font-weight: 650; margin: 0; }
.panel > header .hint { color: var(--ink-3); font-size: .84rem; line-height: 1.5; margin-top: 2px; max-width: 76ch; }

/* Tables: the main material of a tool. */
.grid-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.grid-table th {
  text-align: left; font-weight: 600; color: var(--ink-3); font-size: .76rem;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 0 var(--space-md) var(--space-sm); white-space: nowrap;
}
.grid-table td {
  padding: 10px var(--space-md); border-top: 1px solid var(--line);
  vertical-align: middle;
}
.grid-table tbody tr { transition: background .12s var(--ease); }
.grid-table tbody tr:hover { background: var(--bg); }
.grid-table tbody tr.pick { cursor: pointer; }
.grid-table tbody tr.on { background: var(--bg); box-shadow: inset 2px 0 0 var(--accent); }
.grid-table .num { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
.grid-table .quiet { color: var(--ink-3); }
.grid-table .mono { font-family: ui-monospace, SFMono-Regular, monospace; font-size: .86em; }

/* A state mark, in its own column, the same width on every row. */
.mark { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.mark i { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-3); flex: none; }
.mark.up i { background: var(--accent); }
.mark.warn i { background: oklch(65% 0.13 72); }
.mark.down i { background: var(--stop); }
.mark.off i { background: var(--line-2); }

/* Figures: the numbers a screen is about. One is large, the rest step down. */
.figures { display: flex; gap: var(--space-3xl); flex-wrap: wrap; align-items: flex-end; }
.figure .k { color: var(--ink-3); font-size: .78rem; letter-spacing: .06em; text-transform: uppercase; }
.figure .v { font-family: var(--font-display); font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.05; }
.figure.lead .v { font-size: clamp(2rem, 4vw, 2.8rem); }
.figure .v { font-size: 1.35rem; }
.figure .note { color: var(--ink-3); font-size: .82rem; }

/* The side panel: one object, opened from a table row. */
.side {
  position: fixed; inset: 0 0 0 auto; width: min(460px, 100vw); z-index: 60;
  background: var(--bg); border-left: 1px solid var(--line);
  box-shadow: -24px 0 60px oklch(0% 0 0 / .10);
  display: flex; flex-direction: column;
  animation: slide-in .24s var(--ease);
}
@keyframes slide-in { from { transform: translateX(16px); opacity: 0 } to { transform: none; opacity: 1 } }
.side > header {
  display: flex; align-items: center; gap: var(--space-md);
  padding: var(--space-lg) var(--space-xl); border-bottom: 1px solid var(--line);
}
.side > header h2 { font-size: 1.05rem; margin: 0; font-family: var(--font-display); }
.side .body { padding: var(--space-xl); overflow-y: auto; display: grid; gap: var(--space-xl); }
.side .facts { display: grid; gap: 6px; }
.side .facts div { display: flex; gap: var(--space-md); font-size: .88rem; }
.side .facts dt { color: var(--ink-3); min-width: 148px; }
.side .facts dd { margin: 0; }
.side .acts { display: grid; gap: var(--space-sm); }
.scrim { position: fixed; inset: 0; z-index: 50; background: oklch(0% 0 0 / .18); }
