/* Mini POS — the shell and the shared components (P14).
 *
 * What lives here: the topbar and its navigation, the page frame, and every
 * component more than one screen draws (cards, buttons, inputs, tables, badges,
 * banners, modals, toasts). No colour is named in this file — it is all
 * `var(--…)` from `tokens.css`, which is imported first so that a sheet linked on
 * its own is still complete. The till's own surface lives in `pos.css`.
 *
 * The print sheets (`receipt.css`, `zreport.css`) are untouched by any of this:
 * a receipt extends no layout from here and is pinned by its own tests.
 */
@import url("tokens.css");

* { box-sizing: border-box; }

/* Author styles override the UA [hidden] rule, so make it explicit: without
   this, .modal { display: grid } would keep "hidden" modals on screen. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
}

/* One type scale, said once (P14, tightened in P24): the page's own title, the
   heading of a card, and the soft grey line under the title that says what the page
   is for. `h1` carries no bottom margin of its own — the gap belongs to the line
   under it (`.page-head .sub`), so a page with a subtitle and a page with only a
   title do not read as two different rhythms. */
h1 { font-size: var(--fs-2xl); margin: 0; letter-spacing: -.01em; font-weight: var(--fw-bold); line-height: var(--lh-tight); }
h2, h3 { font-size: var(--fs-lg); margin: 0; font-weight: var(--fw-semibold); }
a { color: var(--accent); }

/* ---------------------------------------------------------- the shell (P17)
 * The frame every page is drawn in: a navigation column on the left and the page
 * beside it. It replaced the top bar of P14/P15 — whose one scrolling line needed
 * ~1060 px for the bilingual labels (so it scrolled sideways on anything smaller)
 * and whose four rows took 317 px of a 390 px phone before the first figure of the
 * day. The same markup is the phone's drawer at ≤1024 px (see the end of this
 * file): one navigation, two shapes, and no second copy of the links to keep in
 * step.
 */
.shell { display: flex; align-items: stretch; min-height: 100vh; }
/* The page's own column. `min-width: 0` is load-bearing: without it a wide table
   inside a card floors the column at its contents and the page scrolls sideways.
   It is a *block*, not a flex column: a column would stretch `.container` to the
   line's cross size — its own max-content — and a 947 px dashboard on a 390 px
   phone is exactly the sideways scroll this shell exists to avoid (measured). */
.shell-main { flex: 1 1 auto; min-width: 0; }

/* The navigation column: brand, the sections, the account. `sticky` rather than
   `fixed`, so it is laid out *beside* the page (nothing has to be told its width)
   and stays put while the day's records scroll past it. */
.sidebar {
  flex: 0 0 auto; width: var(--sidebar-w);
  background: var(--surface); border-right: 1px solid var(--border);
  position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column;
  z-index: var(--z-nav);
  transition: width .16s ease;
}
/* Collapsed: the rail. The class is on `<html>` — a script in the page's head sets
   it before the first paint and `js/nav.js` keeps the button in step — and the
   rail's own rules live at the end of this file, inside the width they apply at. */
.sidebar-head {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3); border-bottom: 1px solid var(--border);
}
.sidebar-head .brand { flex: 1 1 auto; min-width: 0; }

/* How much room the navigation takes is the shop's own decision, so it has a
   button — the same one in both directions, its glyph saying which way it goes. */
.rail-toggle {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  min-width: var(--touch); min-height: var(--touch); border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; font-size: var(--fs-lg); font-weight: var(--fw-bold);
}
.rail-toggle:hover { background: var(--surface-2); color: var(--text); }
.rail-toggle:focus-visible { outline: none; box-shadow: var(--ring); }
.rail-toggle .glyph-rail { display: none; }

/* The drawer's own close (P17): the same corner on a phone, where there is no rail
   to come back to. It is drawn only at the drawer's widths (end of file). */
.drawer-close {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  min-width: var(--touch); min-height: var(--touch); border-radius: var(--radius-sm);
  display: none; align-items: center; justify-content: center;
  font: inherit; font-size: var(--fs-xl); line-height: 1;
}
.drawer-close:hover { background: var(--surface-2); color: var(--text); }
.drawer-close:focus-visible { outline: none; box-shadow: var(--ring); }
.brand { display: flex; align-items: center; gap: var(--space-3); text-decoration: none; color: var(--text); min-height: var(--touch); }
.brand-mark {
  width: 32px; height: 32px; border-radius: 9px; background: var(--accent);
  color: var(--on-accent); display: grid; place-items: center;
  font-weight: var(--fw-heavy); font-size: var(--fs-lg);
}
.brand-text { font-weight: var(--fw-bold); display: flex; flex-direction: column; line-height: 1.1; }
.brand-text small { font-weight: var(--fw-regular); color: var(--muted); font-size: var(--fs-2xs); }

/* ------------------------------------------------------ the navigation list
 * A column of sections, not a row of links (P15's bar was a row and had to scroll
 * sideways to hold on to its labels). Each link is a full-width touch target with
 * the shop's icon and the bilingual label; the rail takes the label away and
 * leaves the mark, and the same link, class and `aria-current` are what the phone's
 * drawer shows. The list is the one thing here that may scroll on its own: eight
 * sections do not fit a 700 px-tall window. */
.nav {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-3);
}
/* A section of the shop's own day: where the money is taken, the records read back
   afterwards, the drawer's money, and the owner's corner. */
/* The column is four categories (t_96421ccd) — Venta, Inventario, Caja,
   Administración — and each one's heading is a control: the whole width of the
   column, so it is as easy to hit as a link, folding the links below it away. The
   markup is a `<section>` per category; this file only says how one looks and what
   a folded one does. */
.nav-section, .nav-items {
  display: flex; flex-direction: column; gap: 2px;
}
.nav-group {
  display: flex; align-items: center; gap: var(--space-2); width: 100%;
  margin: var(--space-4) var(--space-2) var(--space-1);
  padding: 0; border: 0; background: transparent; border-radius: var(--radius-sm);
  cursor: pointer; text-align: left;
  font-family: inherit; font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.nav-section:first-child .nav-group { margin-top: 0; }
.nav-group:hover { color: var(--text); }
.nav-group:focus-visible { outline: none; box-shadow: var(--ring); }
/* Where you are, even with the category folded away: the heading of the category
   that holds the page you are on is the one that is not grey. A navigation that hid
   the current page with no trace would stop answering the one question it exists
   for; marking the heading costs the folded column nothing (`js/nav.js` adds the
   class from the link the server already marked). */
.nav-group.is-current { color: var(--accent-dark); }
/* The chevron points down while the category is open and right while it is shut —
   the same glyph, turned, so there is nothing to keep in step with the state. It sits
   a step in from the column's edge: on a long column the list scrolls, and the
   scrollbar would otherwise be drawn on top of it. */
.nav-group-chevron {
  flex: 0 0 auto; margin-left: auto; margin-right: var(--space-4);
  width: 7px; height: 7px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg);
  transition: transform .15s ease;
}
.nav-group[aria-expanded="false"] .nav-group-chevron { transform: rotate(-45deg); }
/* A category this person keeps folded away. The head script writes the folded names
   onto `<html>` before the first paint and these four rules are what hides them, so
   there is one copy of the fact; `js/nav.js` only ever moves that attribute, and a
   browser with no script keeps every category open (nothing here matches then). */
html[data-nav-shut~="venta"] .nav-items[data-nav-section="venta"],
html[data-nav-shut~="inventario"] .nav-items[data-nav-section="inventario"],
html[data-nav-shut~="caja"] .nav-items[data-nav-section="caja"],
html[data-nav-shut~="admin"] .nav-items[data-nav-section="admin"] { display: none; }
.nav a {
  display: flex; align-items: center; gap: var(--space-3); flex: 0 0 auto;
  min-height: var(--touch);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  text-decoration: none; color: var(--muted);
  font-weight: var(--fw-medium); font-size: var(--fs-md);
}
.nav a:hover { background: var(--surface-2); color: var(--text); }
.nav a:focus-visible { outline: none; box-shadow: var(--ring); }
/* The page you are on — the same link, and `aria-current` beside the class, so the
   section is told apart by a screen reader as well as by eye (P17). */
.nav a.active { background: var(--accent-soft); color: var(--accent-dark); }
/* The one link that is the shop's work rather than its records: it reads as a
   button, so the first thing in the column is the thing you press all day. */
.nav a.nav-sell { background: var(--accent); color: var(--on-accent); font-weight: var(--fw-semibold); }
.nav a.nav-sell:hover { background: var(--accent-dark); color: var(--on-accent); }
.nav a.nav-sell.active { background: var(--accent-dark); color: var(--on-accent); }
/* An icon is `currentColor` (see `base.html`) at one size, so the rail and the open
   column line up on the same marks whatever colour the row is. */
.nav-icon { flex: 0 0 auto; width: 22px; height: 22px; }

/* The drawer's backdrop (P17): `fixed`, so it covers the page the drawer slid over,
   and in the DOM only while the drawer is open (`hidden`). */
.nav-scrim { position: fixed; inset: 0; background: var(--overlay); z-index: var(--z-scrim); }

/* ------------------------------------------------------- who is signed in
   The column's one line about people (P12): the account this browser is signed in
   as, its role, and the way out. It moved to the foot of the column from the
   top-right corner of the old bar (P17) — and in the rail it is the way out alone,
   an icon the same shape as the sections'. */
.sidebar-foot { border-top: 1px solid var(--border); padding: var(--space-3); }
.who { display: flex; flex-direction: column; gap: var(--space-2); }
.who-name {
  display: flex; flex-direction: column; line-height: 1.15;
  font-weight: var(--fw-semibold); font-size: var(--fs-md);
}
.who-name small {
  font-weight: var(--fw-regular); color: var(--muted); font-size: var(--fs-2xs);
  text-transform: uppercase; letter-spacing: .04em;
}
/* The role's initial: the rail's stand-in for the account line (P17). A cashier's
   screen and the owner's are never the same screen — the nav says so in the rail as
   well as in the open column, where the role is written out. */
.who-initial { display: none; }
.who-out {
  display: flex; align-items: center; gap: var(--space-3); min-height: var(--touch);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  text-decoration: none; font-size: var(--fs-md); font-weight: var(--fw-medium);
  color: var(--muted); border: 1px solid var(--border);
}
.who-out:hover { background: var(--surface-2); color: var(--text); }

/* The language switch (Trilingual B): ONE control in two places — here in the foot,
   beside the account line, and in the sign-in card's header (`.login-head` below).
   Three small buttons, each naming its language in that language, and the one you
   are reading in carrying `active` (the same class the navigation marks a page
   with) plus `aria-current` for a screen reader. The control is the shell's `--touch`
   tall like every other one, because it is pressed with a finger on the counter — and
   `--touch` *wide* too (P24): the measured 390 px/1440 px pass found `中文` drawn 42 px
   wide on the sign-in card, which is the one control on that card a person types
   nothing into. */
.locale-switch { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: var(--space-3); }
.locale-option {
  flex: 1 1 auto; min-height: var(--touch); min-width: var(--touch);
  padding: 0 var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--muted);
  font: inherit; font-size: var(--fs-xs); font-weight: var(--fw-medium); cursor: pointer;
}
.locale-option:hover { background: var(--surface-3); color: var(--text); }
.locale-option.active {
  background: var(--surface); color: var(--text);
  border-color: var(--accent); font-weight: var(--fw-semibold);
}
/* The rail's own control (`i18n.next_locale`): one 44 px target with a `文` glyph,
   the same mechanism the account line uses — a glyph where the open column writes
   words. It is in the markup on every page and *shown only in the rail*, so a page
   with no rail (the sign-in card) never draws a second control beside the three. */
.locale-cycle {
  display: none; align-items: center; justify-content: center;
  min-height: var(--touch); padding: 0 var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--muted);
  font: inherit; font-size: var(--fs-md); cursor: pointer;
}
.locale-cycle:hover { background: var(--surface-3); color: var(--text); }

/* What a cashier or a supervisor may not use (P12, three tiers 0047). The role is
   on <body> (`data-role`), set by base.html from the session, and anything the
   server would refuse is marked `admin-only`: the rule is written down once here
   instead of in every template, and the *security* is still the route guard — this
   is only the UI not offering a button that would come back 403. A cost column is
   marked the same way, because the server strips those figures from a non-owner's
   JSON in the first place. The middle tier is included: a supervisor holds
   `can_approve` and nothing else, so every `admin-only` control on their screen
   (products, prices, settings, accounts, costs, the owner's reports) is one the
   server would refuse — see `db.CAPABILITIES`. */
body[data-role="cashier"] .admin-only,
body[data-role="supervisor"] .admin-only { display: none !important; }

.container { max-width: var(--shell-max); margin: 0 auto; padding: var(--space-7) var(--space-6) 60px; }
/* The till's frame (P14): the counter display is wider than a record page. */
.container-wide { max-width: var(--shell-max-wide); }
.footer { max-width: var(--shell-max); margin: 0 auto; padding: 0 var(--space-6) var(--space-8); color: var(--muted); font-size: var(--fs-xs); }

/* --------------------------------------------------------- page head */
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--space-5); margin-bottom: var(--space-6); flex-wrap: wrap;
}
.page-head .sub { margin: var(--space-2) 0 0; color: var(--muted); font-size: var(--fs-sm); }
/* The Users screen's three tiers (0047): one line each under the page's own
   subtitle. No bullets — every line is a sentence about a role, and the names are
   already bold inside it. */
.role-legend { list-style: none; margin: var(--space-2) 0 0; padding: 0; font-size: var(--fs-sm); }
.role-legend li { margin-top: var(--space-1); }
.page-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; }

/* -------------------------------------------------------------- cards */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden;
}
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--border);
  background: var(--surface-2); flex-wrap: wrap;
}
.count { color: var(--muted); font-weight: var(--fw-regular); font-size: var(--fs-sm); }
/* A form (or a table) that lives inside a card rather than inside a dialog (P9's
   Caja page): the padding `.modal-body` gives a modal's contents. */
.card-body { padding: var(--space-5) var(--space-6) var(--space-6); }
.toolbar { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
/* A card's filters take the room the heading beside them does not need, and sit
   at its end. Without this the toolbar is only as wide as its own contents, so
   the search box inside it (which grows) was capped at its 280 px minimum while
   the card had half a metre of empty head next to it (P15, /sales). `min-width: 0`
   is the other half of that: without it the toolbar's own floor is its widest
   child, so at 390 px the filter row stood 2 px past the card and `.card`
   (`overflow: hidden`) cut its edge off (P15, measured). */
.card-head .toolbar { flex: 1 1 auto; justify-content: flex-end; min-width: 0; }
/* A filter box holds a sentence ("Search product, date (2026-10-12) or sale #…"),
   so it asks for the room to show it: at the shell's 210 px minimum the
   placeholder was cut mid-word in every toolbar (P15). It still shrinks to fit a
   phone rather than forcing a sideways scroll. */
.toolbar .input[type="search"] { min-width: min(280px, 100%); flex: 1 1 200px; }

/* -------------------------------------------------------------- stats */
/* Four across on the counter Mac, three on a tablet, two on a phone. At the
   165 px minimum this grid fitted six of the dashboard's seven figures on one
   row and left the seventh alone underneath, which read as a mistake (P15). */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--space-4); margin-bottom: var(--space-6); }
.stat {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-5); box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: var(--space-1);
}
.stat-label { font-size: var(--fs-xs); color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.stat-value { font-size: var(--fs-xl); font-weight: var(--fw-bold); letter-spacing: -.02em; }
.stat-sub { font-size: var(--fs-xs); color: var(--muted); }
.stat-warn { border-color: var(--warn-line); background: linear-gradient(var(--warn-fill), var(--surface)); }
.stat-warn .stat-value { color: var(--warn); }
.stat-ok .stat-value { color: var(--ok); }

/* ------------------------------------------------------------ banners */
.banner {
  border-radius: var(--radius); padding: var(--space-4) var(--space-5); margin-bottom: var(--space-5);
  background: var(--warn-bg); border: 1px solid var(--warn-line); color: var(--warn-ink); font-size: var(--fs-md);
}
.banner strong { color: var(--warn); }
/* The same banner, saying that something worked (0049: a reset the server accepted).
   The success tokens are the ones the till's own confirmations use, so a green line
   means the same thing on this screen as on every other. */
.banner-ok { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }

/* ------------------------------------------------------- the second factor (0049) */
/* The QR sits in a quiet frame so a phone camera has the contrast it needs, and it
   is never larger than its column on a phone. */
.security-qr { margin: var(--space-4) 0; padding: var(--space-4); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius); display: inline-block; max-width: 100%; }
.security-qr svg { display: block; width: 220px; height: 220px; max-width: 100%; }
/* The secret in text for manual entry: monospace and selectable, because the person
   reads it off this screen and types it on a phone. */
.secret { display: inline-block; margin: var(--space-2) 0 var(--space-4);
  padding: var(--space-3) var(--space-4); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--mono); font-size: var(--fs-md); letter-spacing: 0.08em;
  user-select: all; word-break: break-all; }
/* The ten recovery codes, in two columns so they are all on one screen — they are
   shown once and a person has to copy them from here. */
.security-recovery { margin-top: var(--space-4); padding: var(--space-4);
  border: 1px solid var(--warn-line); border-radius: var(--radius); background: var(--warn-bg); }
.recovery-codes { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-2); margin: var(--space-3) 0 var(--space-4); padding: 0; list-style: none;
  font-family: var(--mono); font-size: var(--fs-md); }
.recovery-codes li { padding: var(--space-2) var(--space-3); background: var(--surface);
  border-radius: var(--radius-sm); user-select: all; }
/* A form whose fields sit beside its button — the disable and reset forms. */
.inline-form { display: flex; align-items: flex-end; gap: var(--space-3); flex-wrap: wrap;
  margin: var(--space-4) 0; }
.inline-form .field { margin: 0; }

/* ------------------------------------------------------------- inputs */
.input {
  font: inherit; padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); min-width: 210px;
  min-height: var(--touch); outline: none;
}
.input:focus { border-color: var(--accent); box-shadow: var(--ring); }
.input[type="number"] { min-width: 120px; }
/* A `<select>` is the one control here that is as wide as its *content*: its
   intrinsic width is its longest option, not the box it was given. In a card's
   toolbar it stood 2 px wider than the card at 390 px and was clipped (P15: the
   filter row is the one place a select shares its line with other controls, and
   `.card` is `overflow: hidden`, so "too wide" is not a scrollbar — it is a cut
   edge); in a dialog's two-column grid it was 274 px wider (see `.grid-2`). */
select.input { max-width: 100%; }
.input-cash { width: 100%; min-width: 0; font-size: var(--fs-lg); font-weight: var(--fw-semibold); margin-bottom: var(--space-3); }
.input-mono { font-family: var(--mono); letter-spacing: .04em; }
.field { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-5); }
.field label, .field-label { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-label); }
.form-error { color: var(--danger); background: var(--danger-soft); padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm); margin: var(--space-1) 0 0; font-size: var(--fs-sm); }
.readonly { padding: var(--space-3) var(--space-4); border: 1px dashed var(--border); border-radius: var(--radius-sm); color: var(--muted); background: var(--surface-2); font-weight: var(--fw-semibold); }
.check { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--muted); font-size: var(--fs-sm); white-space: nowrap; min-height: var(--touch); }
/* A checkbox is a 13 px box in every browser, and `padding` on one is ignored —
   measured, not assumed: Chrome computes `padding: 12px` on a native
   `input[type=checkbox]` back to `0px`, so padding cannot grow it. What a finger
   actually hits here is the *label*, which is a full touch target (`--touch`,
   above); taking the box out of the hit test leaves no smaller target pretending
   to be one, and the label's own activation still toggles the box (a click on the
   text and a click on the artwork both flip it — verified in the browser). */
.check input[type="checkbox"] { width: 20px; height: 20px; margin: 0; pointer-events: none; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
/* A `<select>` is the one control in the shell that is as wide as its *content*:
   its intrinsic width is its longest option, not the box it is in. A two-column
   grid sizes each track from what is in it, so `Bodega (back room)` (331 px) became
   the track's floor — the transfer dialog stood **274 px wider than its own 420 px
   card**, with the `To` select cut off at the card's edge, and the new-apartado
   dialog (75 px) with it (P15, measured inside the open dialog). A column is a
   column: what lives in one fills it, and the grid may shrink it. */
.grid-2 > * { min-width: 0; }
.grid-2 .input { width: 100%; min-width: 0; }
.hint { color: var(--muted); font-size: var(--fs-xs); margin: var(--space-3) 0 0; }

/* ------------------------------------------------------------ buttons
 * Every button in the shell and the till is at least `--touch` tall: the counter
 * has tablets and phones on the same LAN as the Mac (P14). */
.btn {
  font: inherit; font-weight: var(--fw-semibold);
  min-height: var(--touch); padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm); cursor: pointer;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2); transition: .12s;
}
.btn:hover { background: var(--surface-2); }
.btn:focus-visible { outline: none; box-shadow: var(--ring); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-dark); }
.btn-danger { background: var(--danger); border-color: var(--danger); color: var(--on-accent); }
.btn-danger:hover { filter: brightness(.94); }
.btn-ghost { background: transparent; }
.btn-sm { padding: var(--space-2) var(--space-4); font-size: var(--fs-sm); }
.btn-lg { padding: var(--space-4) var(--space-6); font-size: var(--fs-lg); }
.btn-block { width: 100%; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
/* One ring for everything a finger or a Tab can land on (P24). The shell always had
   it on the buttons, the fields and the navigation; these are the controls the later
   cards added — the icon buttons, the quantity steppers in a table, the account line,
   the language switch, the tick boxes — and without it a keyboard at the counter
   could not see where it was. `:focus-visible` and not `:focus`, so a mouse click
   does not leave a ring behind. */
.icon-btn:focus-visible, .qty button:focus-visible, .qty input:focus-visible,
.who-out:focus-visible, .locale-option:focus-visible, .locale-cycle:focus-visible,
.table input:focus-visible, .table select:focus-visible, .check input:focus-visible {
  outline: none; box-shadow: var(--ring);
}
.icon-btn {
  border: 0; background: transparent; font-size: var(--fs-lg); cursor: pointer; color: var(--muted);
  min-width: var(--touch); min-height: var(--touch); padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm); display: inline-flex; align-items: center; justify-content: center;
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.row-actions { display: flex; gap: var(--space-1); justify-content: flex-end; flex-wrap: wrap; }

/* ------------------------------------------------------------- tables */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; }
/* A record page's rows are read, not tapped, so the padding is a step tighter
   than the shell's largest (P15): at 16 px a side the movement ledger — nine
   columns — stood 122 px wider than the card it lives in at 1280 px, which put
   the last column (who wrote the row) behind a sideways scroll nobody was told
   about. */
.table th, .table td { padding: var(--space-4) var(--space-4); text-align: left; border-bottom: 1px solid var(--border); white-space: nowrap; }
.table thead th { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--muted); background: var(--surface); font-weight: var(--fw-semibold); }
.table tbody tr:hover { background: var(--surface-2); }
.table .num, .table .right { text-align: right; }
/* A column of figures is read by the digit, not by the glyph (P24). With the shop's
   proportional figures `₡2,500` and `₡3,200` did not line up under each other, and a
   right-aligned column of mixed-width digits reads as ragged. `tabular-nums` is the
   *only* thing these rules say: no separator, no symbol, no rounding and no
   formatting anywhere is touched — every price still comes from the server's own
   `colones()` and the browser is only asked to give each digit the same width. */
.num, .stat-value, .sale-total, .pay-part, .cart-foot strong, .cart-line .cl-total {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.table td.name { font-weight: var(--fw-semibold); white-space: normal; min-width: 150px; }
/* The row-actions cell is the one cell that may wrap: six verbs in a nowrap cell
   would widen the catalogue past the screen and push the last of them out of
   reach (the widened cell is what P15 measured on /products). */
.table td.actions { white-space: normal; }
/* One shape for the three things a page that fetches can show instead of rows (P24):
   what it is doing, what it found, and what went wrong. Every page already writes
   `.empty` for the first two; a failure is the modifier, so a page that could not
   load is not painted in the same grey as a page with nothing to show — it was, and
   an error read as "there is nothing here", which is the one thing it must not say. */
.table .empty, .empty { color: var(--muted); text-align: center; padding: var(--space-7) var(--space-5); }
.empty.is-error { color: var(--danger); font-weight: var(--fw-semibold); }
.sub-line { display: block; color: var(--muted); font-size: var(--fs-xs); font-weight: var(--fw-regular); }
tr.row-low { background: var(--warn-fill); }
tr.row-low:hover { background: var(--warn-bg); }

.badge { display: inline-block; padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill); font-size: var(--fs-xs); font-weight: var(--fw-semibold); }
.badge-ok { background: var(--ok-soft); color: var(--ok); }
.badge-low { background: var(--warn-bg); color: var(--warn); }
.badge-out { background: var(--danger-soft); color: var(--danger); }

/* A quantity stepper. Shared by the products and apartados tables (they size it
   their own way); it is a touch target like every other control the shell draws
   — the till only makes it louder (P14/P15). */
.qty { display: inline-flex; align-items: center; gap: var(--space-1); }
.qty button {
  width: var(--touch); height: var(--touch);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface);
  cursor: pointer; font-size: var(--fs-base); line-height: 1; color: var(--text);
}
.qty button:hover { border-color: var(--accent); color: var(--accent-dark); }
.qty input { width: 46px; text-align: center; padding: var(--space-1); border: 1px solid var(--border); border-radius: var(--radius-sm); font: inherit; }

/* The change due / margin figure a page paints by signing the element colour. */
.tone-ok { color: var(--ok); }
.tone-warn { color: var(--warn); }
.tone-danger { color: var(--danger); }

/* --------------------------------------------------------- sales list */
.sales-list { display: flex; flex-direction: column; }
.sale { border-bottom: 1px solid var(--border); }
.sale:last-child { border-bottom: 0; }
.sale-head {
  display: grid; grid-template-columns: 92px 1fr auto auto; gap: var(--space-4); align-items: center;
  padding: var(--space-5) var(--space-6); cursor: pointer;
}
.sale-head:hover { background: var(--surface-2); }
.sale-id { font-weight: var(--fw-heavy); color: var(--accent-dark); }
.sale-when { color: var(--muted); font-size: var(--fs-xs); }
.sale-total { font-weight: var(--fw-heavy); font-size: var(--fs-lg); text-align: right; }
.sale-meta { color: var(--muted); font-size: var(--fs-xs); text-align: right; min-width: 130px; }
.sale-body { padding: 0 var(--space-6) var(--space-5); background: var(--surface-2); }
.sale-body .table th, .sale-body .table td { background: transparent; padding: var(--space-3) var(--space-4); font-size: var(--fs-sm); }
.sale-body .table { border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.caret { color: var(--muted); font-size: var(--fs-xs); }

/* ------------------------------------------------- voided sales (P6) */
/* A voided sale keeps its place in the list: the id and the total are muted, the
   total struck through, and the reason it came back sits under the sale. */
.sale.is-void { background: var(--surface-2); }
.sale.is-void .sale-id, .sale.is-void .sale-meta { color: var(--muted); }
.sale.is-void .sale-total { text-decoration: line-through; color: var(--muted); }
.void-line { display: block; color: var(--danger); font-size: var(--fs-xs); margin-top: 2px; }
.badge-void { background: var(--danger-soft); color: var(--danger); margin-left: var(--space-2); }
/* A quick-switch PIN is set (migration 0024): a fact about the account rather than
   a state of it, so it is told apart from the account's own active/off badge — in
   the violet the apartados badge uses, and never a word about the digits. */
.badge-pin { background: var(--violet-soft); color: var(--violet-ink); margin-left: var(--space-2); }
.sale-head .btn-danger { font-size: var(--fs-xs); }

/* How a sale was paid, in the sales list and the details view. */
.pay-part { white-space: nowrap; }
.pay-part + .pay-part::before { content: ' + '; color: var(--muted); }
.pay-breakdown { color: var(--muted); font-size: var(--fs-xs); padding: var(--space-3) var(--space-4) 2px; }

/* ------------------------------------------------------------- modal */
.modal {
  position: fixed; inset: 0; background: var(--overlay);
  display: grid; place-items: center; z-index: var(--z-modal); padding: var(--space-6);
}
.modal-card {
  background: var(--surface); border-radius: var(--radius-lg); width: 100%; max-width: 520px;
  box-shadow: var(--shadow-modal); max-height: 92vh; overflow: auto;
}
.modal-sm { max-width: 420px; }
/* A dialog that carries a table (the stock history: the ledger's rows and the
   deliveries behind them, seven columns each) asks for the room the figures need
   rather than making the table scroll inside a 520 px card (P15). */
.modal-lg { max-width: 880px; }
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--border); }
.modal-body { padding: var(--space-6); }
.modal-foot { display: flex; justify-content: flex-end; gap: var(--space-3); padding: var(--space-5) var(--space-6); border-top: 1px solid var(--border); background: var(--surface-2); flex-wrap: wrap; }
.receipt-line { display: flex; justify-content: space-between; padding: var(--space-2) 0; font-size: var(--fs-md); }
.receipt-total { border-top: 1px dashed var(--border); margin-top: var(--space-2); padding-top: var(--space-4); font-size: var(--fs-xl); font-weight: var(--fw-heavy); }
.muted { color: var(--muted); font-size: var(--fs-sm); }
/* A second heading inside a dialog — the stock history shows the ledger and then
   the deliveries behind it (P11). Same scale as an `h3`, with the room above a
   section inside a modal needs; the `.count` beside it is the shell's own. */
.sub-head {
  display: flex; align-items: baseline; gap: var(--space-3);
  font-size: var(--fs-lg); font-weight: var(--fw-semibold);
  margin: var(--space-6) 0 var(--space-3);
}

/* ------------------------------------------------------- the till's lock (P23) */
/* Not one of the dialogs above: a dialog is opened and closed by the person, and
   this is a *state* — the server refuses every request while it is on. So it takes
   the whole viewport, it is drawn above everything the shell has (`--z-lock`), and
   nothing in it is a way out except signing in. Same tokens as the sign-in card it
   stands in for, so the two doors look like the same shop. */
.till-lock {
  position: fixed; inset: 0; background: var(--overlay);
  display: grid; place-items: center; gap: var(--space-4); z-index: var(--z-lock);
  padding: var(--space-6); overflow: auto;
}
.till-lock-card {
  background: var(--surface); border-radius: var(--radius-lg); width: 100%;
  max-width: 420px; box-shadow: var(--shadow-modal); padding: var(--space-6);
}
.till-lock-card h2 { margin: 0 0 var(--space-2); font-size: var(--fs-lg); }
.till-lock-card h3 { margin: 0 0 var(--space-2); font-size: var(--fs-md); }
.till-lock-card .sub { color: var(--muted); font-size: var(--fs-sm); margin: 0 0 var(--space-5); }
.till-lock-card .btn-block + .btn-block { margin-top: var(--space-3); }
/* The quick switch (migration 0024) is a *second* card under the sign-in one: two
   sibling forms rather than one, because a form inside a form is not HTML a browser
   would honour, and because the two are genuinely different acts — "sign in again"
   and "the counter changes hands". Same card, same tokens, told apart by the
   heading and the ghost button. */
.till-lock-switch { border-top: 3px solid var(--violet-soft); }

/* -------------------------------------------------------------- lone card
 * One card, centred, with nothing beside it: the sign-in screen, the first-run screen
 * and the answer an error gets (P24 — the error handlers render `base.html` with no
 * content of their own, and before this they got a header, a blank page and a footer).
 * Kept under its old name because three templates and a test already ask for it. */
.login-card { max-width: 420px; margin: 8vh auto 0; padding: var(--space-7); }
/* The card's header holds the shop's name and the language switch (Trilingual B):
   this is the screen somebody meets before there is an account to keep a language
   on, and a person who cannot read it has to be able to change it here. */
.login-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.login-head .locale-switch { margin-top: 0; }
.login-card .sub { color: var(--muted); font-size: var(--fs-sm); margin: var(--space-2) 0 var(--space-6); }

/* -------------------------------------------------------------- toast
 * The shell's one floating voice — a refusal, a confirmation, "saved". It wears the
 * cards' own radius and a coloured edge rather than a bar of pixels of its own, so a
 * toast and the dialog it belongs to read as the same shop (P24): the edge says which
 * kind of news it is, the surface stays the one dark strip whatever it is saying. */
.toast {
  position: fixed; right: var(--space-6); bottom: var(--space-6); z-index: var(--z-toast);
  background: var(--text); color: var(--on-accent);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius); border-left: 3px solid var(--accent);
  box-shadow: var(--shadow-toast); font-size: var(--fs-md); max-width: 340px;
}
.toast.err { background: var(--danger); border-left-color: var(--danger-soft); }
.toast.ok { background: var(--ok); border-left-color: var(--ok-soft); }

/* -------------------------------------------------- stock movements (P5) */
/* A movement's change: green when stock came in, red when it went out, and a
   muted dash-like zero that never happens (the ledger refuses a delta of 0). */
.delta { font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; white-space: nowrap; }
.delta-up { color: var(--ok); }
.delta-down { color: var(--danger); }
.delta-zero { color: var(--muted); }
/* The ledger's note column is prose, not a number: let it wrap instead of
   stretching the table sideways. */
.table td.note { white-space: normal; color: var(--muted); max-width: 320px; }

/* ------------------------------------------------------ apartado (P8) */
/* The state of an arrangement, and the number that says where the stock went:
   held goods are *out* of every sellable figure, so they are shown beside it. */
.badge-done { background: var(--violet-soft); color: var(--violet-ink); }
.badge-expired { background: var(--surface-2); color: var(--muted); }
.badge-overdue { background: var(--danger-soft); color: var(--danger); margin-left: var(--space-2); }
.apartado-line, .tile-apartado { color: var(--violet); font-weight: var(--fw-semibold); }
.apartado-count { color: var(--violet); font-weight: var(--fw-bold); }
/* A held arrangement on the apartados list (P8): the row wears the same violet as
   every other figure about goods that are spoken for, at its edge — so the list of
   arrangements reads apart from a list of sales at a glance, which is the one thing
   an apartado is not (money has not changed hands for the goods). */
.apartado { box-shadow: inset 3px 0 0 var(--violet); }

/* The new-apartado line builder: product, quantity, add. */
.line-builder { display: grid; grid-template-columns: 1fr 96px auto; gap: var(--space-3); margin-bottom: var(--space-3); }
.line-builder .btn { align-self: center; }
#n-total { font-weight: var(--fw-heavy); }

/* ======================================================= the shell's widths
 * (P17) One navigation, two shapes, one breakpoint: **1025 px and up** is the
 * column beside the page, **1024 px and below** is the drawer over it. The
 * breakpoint is set by the till's own layout rather than by taste — its cart and
 * product grid need ~1000 px of content, and the column takes 248 px of the window
 * (see `pos.css`), so 1248 px is where a two-column till and a sidebar first fit
 * side by side. A tablet in landscape (1024) gets the drawer; the counter Mac
 * (1280) gets the column.
 */
@media (min-width: 1025px) {
  /* The compact bar yields to the column: two headers for one shop would be chrome
     for its own sake. `:has()` — a page with no navigation at all (login, first
     run) keeps its bar, and that is the only page that needs one. */
  body:has(.sidebar) .topbar { display: none; }

  /* The rail: the labels are what it takes away, the rows centre on their icons,
     and the sections stay told apart by a hairline instead of a heading. */
  html.nav-rail .sidebar { width: var(--rail-w); }
  html.nav-rail .sidebar-head { flex-direction: column; gap: var(--space-1); padding: var(--space-3) var(--space-2); }
  html.nav-rail .sidebar-head .brand { flex: 0 0 auto; min-width: var(--touch); justify-content: center; }
  html.nav-rail .brand-text { display: none; }
  html.nav-rail .nav { padding: var(--space-2) var(--space-1); }
  html.nav-rail .nav-label { display: none; }
  html.nav-rail .nav a { justify-content: center; padding-left: 0; padding-right: 0; }
  html.nav-rail .nav-group {
    font-size: 0; height: 0; padding: 0;
    margin: var(--space-3) var(--space-1) 0; border-top: 1px solid var(--border);
    pointer-events: none;
  }
  /* …and the heading's own two children go with the label: in a 56 px column the
     heading *is* the hairline, so there is no word to read and nothing to fold. */
  html.nav-rail .nav-group > * { display: none; }
  /* The rail never hides a category. With the label gone the heading is a hairline,
     and a folded one would be an invisible door into a whole group — so whatever
     this person folded away in the open column is open here. `!important` because
     the open column's rule is an attribute match on `<html>` and would otherwise
     out-rank this one; the two are a deliberate override, not an accident. */
  html.nav-rail .nav-items { display: flex !important; }
  html.nav-rail .sidebar-foot { padding: var(--space-2) var(--space-1); }
  /* The bell (P25) loses its label the way the links do, and keeps the badge: what
     the count *is* is told by the number itself, which is the one thing a rail has
     room for. */
  html.nav-rail .alert-bell { justify-content: center; padding-left: 0; padding-right: 0; }
  html.nav-rail .alert-text { display: none; }
  html.nav-rail .who-user, html.nav-rail .who-role { display: none; }
  html.nav-rail .who-initial {
    display: grid; place-items: center; width: 24px; height: 24px; margin: 0 auto;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    color: var(--muted); font-size: var(--fs-2xs); font-weight: var(--fw-heavy);
    text-transform: uppercase;
  }
  html.nav-rail .who-out { justify-content: center; padding-left: 0; padding-right: 0; }
  /* The switch, collapsed: three buttons do not fit in a 56 px column, so the rail
     carries the one glyph the open column's three buttons are about (the account
     line's own trick, above). */
  html.nav-rail .locale-switch { flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
  html.nav-rail .locale-option { display: none; }
  html.nav-rail .locale-cycle { display: flex; }
  html.nav-rail .rail-toggle .glyph-column { display: none; }
  html.nav-rail .rail-toggle .glyph-rail { display: inline; }
  /* Where you are, in a 56 px column (P24). The soft surface alone was enough when
     the label was beside it; with the label gone the active section and a hovered one
     were the same picture, which is the one question a navigation has to answer. The
     mark goes *inside* the link (an inset shadow, not a border), so nothing about the
     touch target's own box changes: still `--touch` tall, still full width. */
  html.nav-rail .nav a.active { box-shadow: inset 3px 0 0 var(--accent); }
  html.nav-rail .nav a.nav-sell.active { box-shadow: inset 3px 0 0 var(--on-accent); }
}

/* The compact bar (P17): a phone's own header — the drawer's handle and the shop's
   mark. It sticks, because on a phone the way back to the navigation has to be
   reachable without scrolling up first, and it is exactly `--topbar-h` tall, which
   is the offset the till's own sticky bar and cart stick below. */
.topbar {
  display: flex; align-items: center; gap: var(--space-3);
  min-height: var(--topbar-h); padding: var(--space-2) var(--space-4);
  background: var(--surface); border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-nav);
  position: sticky; top: 0; z-index: var(--z-nav);
}
.nav-burger {
  border: 0; background: transparent; color: var(--text); cursor: pointer; padding: 0;
  min-width: var(--touch); min-height: var(--touch); border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
}
.nav-burger:hover { background: var(--surface-2); }
.nav-burger:focus-visible { outline: none; box-shadow: var(--ring); }
/* A menu button that opens nothing is a lie: the login and first-run screens have
   no navigation, so they have no handle. */
body:not(:has(.sidebar)) .nav-burger { display: none; }

/* --------------------------------------------------------------- the drawer
 * A phone or a tablet (P17): the column leaves the flow, waits off-canvas and
 * slides in over the page with a backdrop. `visibility` is part of that on purpose
 * — a drawer that is off-screen but still in the tab order is the classic version
 * of this bug, and hidden from the tab order also means hidden from a screen
 * reader. It is closed on every load: an overlay that opened itself over the day's
 * figures before they could be read would be the opposite of remembering. */
@media (max-width: 1024px) {
  .shell { display: block; }
  .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0;
    height: auto; width: var(--sidebar-w); max-width: 86vw;
    transform: translateX(-100%); visibility: hidden;
    /* The slide is the transform; `visibility` follows it — hidden *after* the
       drawer has left (0 s after a 0.18 s delay) and visible **at once** when it
       opens. That second half is what lets the drawer's close button take the
       focus as the drawer arrives (measured: with `visibility` inside the one
       transition the first instant of the open still computes to `hidden`, and a
       focus on a hidden element is silently dropped). */
    transition: transform .18s ease, visibility 0s .18s;
    box-shadow: var(--shadow-modal); z-index: var(--z-drawer);
  }
  .sidebar.is-open {
    transform: translateX(0); visibility: visible;
    transition: transform .18s ease, visibility 0s;
  }
  .rail-toggle { display: none; }
  .drawer-close { display: inline-flex; }
  /* The page behind the drawer does not scroll while the drawer is over it. */
  body.nav-drawer-open { overflow: hidden; }
  /* A finger's own room: the drawer slides over the page, so the list inside it is
     the one thing on the screen that has to be hit rather than read (P24). Its rows
     were already `--touch` tall; the gap between them grows to the shell's smallest
     step so a thumb aiming at "Ventas" does not land on "Productos". */
  .sidebar .nav { gap: var(--space-1); }
}

/* ------------------------------------------------- shelf labels (P22) */
/* The tick column on the catalogue, and the dialog that prints what is ticked.
   The dialog is a two-column layout on purpose: the form on the left, and the
   *document itself* in the frame on the right — `/labels/preview` renders the
   same template the printer is handed, so the owner is reading the sheet rather
   than a picture of one. */
.table th.pick, .table td.pick { width: 40px; text-align: center; }
.table th.pick input, .table td.pick input { margin: 0; }
.modal-wide { max-width: 980px; }
.labels-layout { display: grid; grid-template-columns: minmax(280px, 1fr) minmax(320px, 1.1fr); gap: var(--space-6); align-items: start; }
.labels-form .sub-head { margin: var(--space-6) 0 var(--space-3); font-size: var(--fs-md); }
.labels-jobs { max-height: 220px; overflow: auto; }
.labels-jobs table { font-size: var(--fs-sm); }
.labels-preview {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); padding: var(--space-2); overflow: auto;
}
.labels-preview iframe { display: block; width: 100%; height: 420px; border: 0; background: var(--surface); }
/* The label editor's own "Extra line" hint sits under a select; the shell's
   `.hint` is already the right look, so nothing new is needed for it. */
@media (max-width: 900px) {
  .labels-layout { grid-template-columns: 1fr; }
  .labels-preview iframe { height: 320px; }
}

@media (max-width: 900px) {
  .sale-head { grid-template-columns: 70px 1fr; }
  .sale-total, .sale-meta { grid-column: 2; text-align: left; }
  .grid-2 { grid-template-columns: 1fr; }
  .line-builder { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------ the assistant (P24)
 * The owner's panel for asking about the shop's figures in plain language. It is a
 * shell page like any other — a heading, one question field, then the answers — and
 * everything here is the panel's own shape rather than a new component language:
 * the card, the inputs, the button and the hint come from above, and this section
 * adds only the question row and the chips under an answer.
 *
 * The chips are the point of the screen. Each one is a figure the *server* resolved
 * against the shop's own data (`ai_assistant.resolve_citations`), so it is drawn to
 * be read as a fact rather than as a tag: the model's few words for it, the number
 * (whole colones or a count), and the day it came from. `flex-wrap` keeps a
 * comparison's two chips on one line and lets a longer list fall to the next.
 */
.ask-form { display: flex; align-items: flex-end; gap: var(--space-4); flex-wrap: wrap; }
.ask-field { display: flex; flex-direction: column; gap: var(--space-2); flex: 1 1 360px; min-width: 0; }
.ask-label, .ask-day-label { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-label); }
.ask-input { width: 100%; min-width: 0; resize: vertical; }
.ask-log {
  list-style: none; margin: 0; padding: var(--space-5) var(--space-6) var(--space-6);
  display: flex; flex-direction: column; gap: var(--space-4);
}
.ask-empty { color: var(--muted); font-size: var(--fs-sm); }
.ask-item {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); padding: var(--space-4) var(--space-5);
}
.ask-question { margin: 0; font-weight: var(--fw-semibold); }
.ask-asked {
  color: var(--muted); font-weight: var(--fw-regular); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .04em; margin-right: var(--space-2);
}
.ask-answer { margin: var(--space-3) 0 0; white-space: pre-wrap; }
.ask-outcome { margin: var(--space-3) 0 0; color: var(--warn); }
.ask-reason { margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--text-label); }
.ask-sources { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
.ask-source-label {
  font-size: var(--fs-xs); color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.ask-meta { margin: var(--space-3) 0 0; color: var(--muted); font-size: var(--fs-xs); }
.chip {
  display: inline-flex; align-items: baseline; gap: var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface); padding: var(--space-1) var(--space-3);
  font-size: var(--fs-xs);
}
.chip strong { font-weight: var(--fw-semibold); color: var(--accent-dark); }
.chip-day { color: var(--muted); }

/* The phone (P15): the shell's own scale for the smallest screen. The figures pair
   up rather than going one to a row (seven tiles a phone-length apart is a scroll,
   not a dashboard). */
@media (max-width: 720px) {
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
}

/* ------------------------------------------------- the owner's alert feed (P25)
   The bell in the shell's foot, and the feed it opens. Everything here reads the
   tokens (`tokens.css` is the only file that names a colour) and every control is
   a finger's: the bell is `--touch` tall like the account line beside it. */
.alert-bell {
  display: flex; align-items: center; gap: var(--space-3); min-height: var(--touch);
  margin-bottom: var(--space-3);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  text-decoration: none; color: var(--muted); border: 1px solid var(--border);
}
.alert-bell:hover { background: var(--surface-2); color: var(--text); }
.alert-bell:focus-visible { outline: none; box-shadow: var(--ring); }
/* The label and the count stack the way the account line does, so the number is
   read as part of the bell rather than as a second thing beside it. */
.alert-text { display: flex; flex-direction: column; line-height: 1.15; flex: 1 1 auto; min-width: 0; }
.alert-label { font-size: var(--fs-md); font-weight: var(--fw-medium); }
.alert-unread { font-size: var(--fs-2xs); color: var(--muted); }
/* The unread count: the one thing on the shell that is *red*, because it is the
   one thing that is asking to be looked at. */
.alert-badge {
  flex: 0 0 auto; min-width: 22px; padding: 0 var(--space-2);
  border-radius: var(--radius-pill); background: var(--danger); color: var(--on-accent);
  font-size: var(--fs-xs); font-weight: var(--fw-bold); text-align: center;
  line-height: 20px;
}

/* The feed itself: one card per finding, told apart by the severity stripe on its
   left edge (the same three states the badges carry by name). */
.feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.finding {
  border: 1px solid var(--border); border-left: 3px solid var(--border);
  border-radius: var(--radius-sm); padding: var(--space-4);
  background: var(--surface-2);
}
.finding-info { border-left-color: var(--accent); }
.finding-warning { border-left-color: var(--warn); }
.finding-alert { border-left-color: var(--danger); }
.finding-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.finding-when { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-label); }
.finding-period { font-size: var(--fs-xs); color: var(--muted); }
.finding-text { margin: 0 0 var(--space-2); font-size: var(--fs-base); color: var(--text); }
.finding-link { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--accent-dark); text-decoration: none; }
.finding-link:hover { text-decoration: underline; }
.feed-empty {
  padding: var(--space-6); text-align: center; color: var(--muted);
  border: 1px dashed var(--border); border-radius: var(--radius-sm);
}
/* The two vocabularies the feed's own badges print: the *kind* (what sort of thing
   this is) and the *severity* (how loudly it is said). */
.badge-kind { background: var(--accent-soft); color: var(--accent-dark); }
.badge-info { background: var(--surface-3); color: var(--muted); }
.badge-warning { background: var(--warn-bg); color: var(--warn-ink); }
.badge-alert { background: var(--danger-soft); color: var(--danger); }

/* ------------------------------------------------------------------------- */
/* The reports page (P31)                                                    */
/*                                                                           */
/* Two shapes the record pages did not have before: a row of buttons that    */
/* chooses which table the page is showing, and a `<progress>` read as a bar */
/* (the hour of the day the till works). Both are written once here, from    */
/* the tokens, rather than grown inline in the page or painted by a script — */
/* the rule every other sheet in the app follows.                            */
/* ------------------------------------------------------------------------- */
.report-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0 0 var(--space-5); }
.report-tabs .btn.active {
  background: var(--accent-soft); border-color: var(--accent);
  color: var(--accent-dark); font-weight: var(--fw-semibold);
}
.report-section + .report-section { margin-top: var(--space-6); }
.report-section h3 { margin: 0 0 var(--space-3); font-size: var(--fs-md); color: var(--text-label); }
.hour-chart { display: grid; gap: var(--space-1); }
.hour-row {
  display: grid; grid-template-columns: 62px minmax(80px, 1fr) 64px 112px;
  align-items: center; gap: var(--space-3);
}
.hour-label { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); }
.hour-bar { width: 100%; height: 12px; accent-color: var(--accent); }
.hour-count, .hour-total {
  text-align: right; font-variant-numeric: tabular-nums; font-size: var(--fs-sm);
}
@media (max-width: 720px) {
  .hour-row { grid-template-columns: 54px minmax(60px, 1fr) 46px; }
  .hour-total { display: none; }
}

/* ------------------------------------------------- received invoices (P39, card 2)
   Eight columns of a supplier's fiscal document are wider than a laptop, so the
   row keeps its verbs at the card's edge: the actions cell is sticky to the right
   and the fiscal columns scroll under it. A decision button that scrolls away is a
   button the owner cannot press, and the two status words are the reason he opens
   this screen. Hacienda's own sentence wraps inside its cell rather than widening
   the table by its length, and a sentence the shop has not been given takes no room
   at all (`:empty` — which stops applying the moment the poll writes it). */
.compras-table th.actions, .compras-table td.actions {
  position: sticky; right: 0; background: var(--surface);
  box-shadow: -1px 0 0 var(--border);
}
.compras-table tbody tr:hover td.actions { background: var(--surface-2); }
.compras-verbs { display: flex; flex-wrap: wrap; gap: var(--space-2); min-width: 210px; }
.compras-table .sub-line { white-space: normal; max-width: 210px; }
.compras-table .sub-line:empty { display: none; }

/* Nothing about the way around the records belongs on paper (P17): a printed page
   is a receipt, a Z-report or a list — never the navigation. */
@media print {
  .shell { display: block; }
  .sidebar, .topbar, .nav-scrim { display: none !important; }
}

/* The rail and the drawer move; a device that asked for less motion gets none
   (P17 — the preference is honoured rather than overridden by a transition). */
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
  .nav-group-chevron { transition: none; }
}
