/* 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.
 *
 * It is the one dark surface in the app (design v2): the shop's deep green with
 * everything written on it in near-white. The page beside it is white, so the
 * column reads as the rail of a shop rather than as another card — and the
 * `color` here is what `currentColor` (every icon in the column) inherits, which
 * is why the icons need no colour of their own. */
.sidebar {
  flex: 0 0 auto; width: var(--sidebar-w);
  background: var(--brand-900); color: var(--sidebar-text);
  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(--sidebar-line);
}
.sidebar-head .brand { flex: 1 1 auto; min-width: 0; color: var(--sidebar-text); }
/* The wordmark is a link home, so it takes focus like one — and it takes the
   column's own ring like every other control on the dark green (it was the last one
   left on the browser's default outline, which is drawn for a white page). */
.sidebar-head .brand:focus-visible { outline: none; box-shadow: var(--ring-on-dark); border-radius: var(--radius-sm); }

/* 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(--sidebar-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);
  transition: background .12s ease, color .12s ease;
}
.rail-toggle:hover { background: var(--sidebar-hover); color: var(--sidebar-text); }
.rail-toggle:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }
.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(--sidebar-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(--sidebar-hover); color: var(--sidebar-text); }
.drawer-close:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }
.brand { display: flex; align-items: center; gap: var(--space-3); text-decoration: none; color: var(--text); min-height: var(--touch); }
/* The shop's mark: a bright green tile with the colón sign in the deep green's
   own ink (4.52:1 on that green). It is the one place the bright green is a
   surface rather than a row, and it is what makes the column recognisable in a
   photograph of the counter. */
.brand-mark {
  width: 32px; height: 32px; border-radius: 9px; background: var(--accent-strong);
  color: var(--sidebar-active-ink); 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); }
.sidebar-head .brand-text small { color: var(--sidebar-muted); }

/* ------------------------------------------------------ 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%;
  min-height: var(--touch-sm);
  margin: var(--space-4) 0 0;
  padding: 0 var(--space-3); 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(--sidebar-muted);
  transition: color .12s ease, background .12s ease;
}
.nav-section:first-child .nav-group { margin-top: 0; }
.nav-group:hover { color: var(--sidebar-text); background: var(--sidebar-hover); }
.nav-group:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }
/* 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(--sidebar-text); }
/* 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 rules (one per category) 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~="inicio"] .nav-items[data-nav-section="inicio"],
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~="dinero"] .nav-items[data-nav-section="dinero"],
html[data-nav-shut~="reportes"] .nav-items[data-nav-section="reportes"],
html[data-nav-shut~="equipo"] .nav-items[data-nav-section="equipo"] { display: none; }
html[data-nav-shut~="ajustes"] .nav-items[data-nav-section="ajustes"] { 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(--sidebar-muted);
  font-weight: var(--fw-medium); font-size: var(--fs-md);
  position: relative;
  transition: background .12s ease, color .12s ease;
}
.nav a:hover { background: var(--sidebar-hover); color: var(--sidebar-text); }
.nav a:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }
/* The page you are on (design v2): the bright green row, in the deep green's own
   ink (4.52:1 — AA), with a short bar at the column's edge. The bar is a
   pseudo-element rather than an inset shadow so that the focus ring can be drawn
   around the same row without either one erasing the other. */
.nav a.active {
  background: var(--accent-strong); color: var(--sidebar-active-ink);
  font-weight: var(--fw-semibold);
}
.nav a::before {
  content: ''; position: absolute; left: 0; top: 50%; width: 3px; height: 0;
  transform: translateY(-50%); border-radius: 0 3px 3px 0;
  background: var(--sidebar-text);
  transition: height .12s ease;
}
.nav a.active::before { height: 22px; }
/* 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. It is
   the same bright green as an active row — the till *is* where you usually are —
   and what tells them apart is the bar: the row you are on has it. */
.nav a.nav-sell { background: var(--accent-strong); color: var(--sidebar-active-ink); font-weight: var(--fw-semibold); }
.nav a.nav-sell:hover { background: var(--accent-strong); color: var(--sidebar-active-ink); filter: brightness(1.06); }
.nav a.nav-sell.active { background: var(--accent-strong); color: var(--sidebar-active-ink); }
/* 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; }

/* Una fila de la columna: el enlace y, detrás, la estrella que lo fija arriba
   (dueño, 2026-10-27). La fila conserva el alto y el aspecto del enlace; la estrella
   sólo toma el borde derecho y aparece al pasar el ratón o al llegar con el teclado
   — en una pantalla táctil, donde no hay hover, se ve siempre. Un fijado se ve
   siempre también: es la señal de que ese atajo ya está arriba. */
.nav-row { display: flex; align-items: stretch; }
.nav-row > a { flex: 1 1 auto; min-width: 0; }
.nav-pin { display: flex; align-items: center; margin: 0; }
.nav-pin-btn {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border: 0;
  border-radius: var(--radius-sm); background: transparent;
  color: var(--sidebar-muted); cursor: pointer;
  font-family: inherit; font-size: 15px; line-height: 1;
  opacity: 0;
  transition: opacity .12s ease, color .12s ease, background .12s ease;
}
.nav-row:hover .nav-pin-btn,
.nav-row:focus-within .nav-pin-btn,
.nav-row.is-pinned .nav-pin-btn { opacity: 1; }
.nav-pin-btn:hover { background: var(--sidebar-hover); color: var(--sidebar-text); }
.nav-pin-btn:focus-visible { outline: none; box-shadow: var(--ring-on-dark); opacity: 1; }
.nav-row.is-pinned .nav-pin-btn { color: var(--accent-strong); }
@media (hover: none) { .nav-pin-btn { opacity: 1; } }
/* Los fijados no son una categoría: no se pliegan ni llevan `<button>`, sólo su
   palabra arriba para que el atajo tenga nombre. Un pelo de aire los separa de la
   primera categoría; el resto del dibujo es el de cualquier fila. */
.nav-pinned { margin-bottom: var(--space-2); }
.nav-pinned-label {
  padding: 0 var(--space-3); margin: 0 0 var(--space-1);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  text-transform: uppercase; letter-spacing: .06em; color: var(--sidebar-muted);
}

/* Ajustes: el orden de los grupos (2026-10-27). Una fila por grupo —la palabra a la
   izquierda, los tres botones a la derecha— alineada de fila en fila: la lista se lee
   como una tabla de decisiones y no como seis párrafos con botones sueltos. */
.sidebar-order { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.sidebar-order li { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.sidebar-order li strong { flex: 0 0 auto; min-width: 11rem; font-weight: var(--fw-medium); }
.sidebar-order form { display: flex; gap: var(--space-1); margin: 0; }

/* 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(--sidebar-line); padding: var(--space-3); }
/* The account cluster is four actions, not four stacked cards: name above, then
   one row of icon buttons (gear, help, other account, logout). The titles and
   accessible labels stay; the visible labels were what made the foot ~200 px tall. */
.who {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-1);
}
.who-name {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: var(--space-2); line-height: 1.15;
  font-weight: var(--fw-semibold); font-size: var(--fs-md); color: var(--sidebar-text);
}
.who-name small {
  font-weight: var(--fw-regular); color: var(--sidebar-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; justify-content: center; min-height: var(--touch);
  padding: 0; border-radius: var(--radius-sm);
  text-decoration: none; font-size: var(--fs-md); font-weight: var(--fw-medium);
  color: var(--sidebar-muted); border: 1px solid var(--sidebar-line);
  transition: background .12s ease, color .12s ease;
}
.sidebar .who-out .nav-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.sidebar .who-out svg { width: 18px; height: 18px; }
/* The contrast switch is a <button> among links: without this it wore the browser's white button
   face, and its white icon vanished into it (2026-11-07). On, it reads as pressed. */
button.who-out { background: transparent; font: inherit; color: var(--sidebar-muted); cursor: pointer; }
.who-out.is-on { background: var(--sidebar-hover); color: var(--sidebar-text); border-color: var(--sidebar-text); }
.who-out:hover { background: var(--sidebar-hover); color: var(--sidebar-text); }
.who-out:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }

/* 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-2); }
.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); }
/* The same act on the column's own surface (design v2): the light version above is
   the sign-in card's, and this is the till's. One control, two grounds — a switch
   that looked like a white page's control on a dark rail would read as a hole in
   it. The column's ink is 6.12:1 on the deep green even at 12 px. */
.sidebar-foot .locale-option,
.sidebar-foot .locale-cycle {
  background: transparent; border-color: var(--sidebar-line); color: var(--sidebar-muted);
}
.sidebar-foot .locale-option:hover,
.sidebar-foot .locale-cycle:hover { background: var(--sidebar-hover); color: var(--sidebar-text); }
.sidebar-foot .locale-option.active {
  background: var(--sidebar-hover); border-color: var(--accent-strong); color: var(--sidebar-text);
}
/* ...and the ring those controls show is the column's own (the combined rule in
   the buttons section hands them the light surface's ring, which is drawn for a
   white card). Scoped to the foot so the same `locale-option` in the sign-in card
   keeps the light ring. */
.sidebar .who-out:focus-visible,
.sidebar .locale-option:focus-visible,
.sidebar .locale-cycle:focus-visible,
.sidebar .alert-bell:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }

/* 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); }
/* El tablero, con jerarquía (dueño, 2026-10-27): la tarjeta del día ocupa dos
   columnas y su número es el más grande de la pantalla; el resto es contexto. */
.dash-day { grid-column: span 2; }
.dash-day .stat-value { font-size: 2.75rem; line-height: 1.05; }
.dash-day .stat-sub { font-size: var(--fs-sm); }
@media (max-width: 720px) { .dash-day { grid-column: span 1; } }

/* El tablero, en dos columnas (dueño, 2026-10-27): los cuatro paneles se apilaban a
   todo el ancho y la página se volvía interminable. Dos por fila en pantalla de
   mostrador, una en el teléfono. */
/* La tabla de inventario del tablero, plegada (2026-10-27): repite la página de
   Productos, así que se abre a demanda y no alarga la página. */
.dash-fold > summary { list-style: none; cursor: pointer; }
.dash-fold > summary::-webkit-details-marker { display: none; }
.dash-fold > summary::after { content: '▾'; margin-left: auto; color: var(--muted); }
.dash-fold[open] > summary::after { content: '▴'; }

.dash-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--space-4); margin-bottom: var(--space-6); align-items: start;
}
/* Lo que más vende y lo más reciente primero: el dueño abre el tablero para
   decidir, no para recorrer tarjetas. */
#best-card { grid-column: 1 / -1; order: -2; }
#recent-card { order: -1; }

/* La barra del import de inventario (2026-10-27): subida con porcentaje y, después,
   barra indeterminada mientras el servidor lee el archivo. */
.import-progress { margin: var(--space-3) 0; }
.import-progress progress { width: 100%; height: 10px; }
.import-progress progress::-webkit-progress-bar { background: var(--surface-3); border-radius: 999px; }
.import-progress progress::-webkit-progress-value { background: var(--accent); border-radius: 999px; }
.import-progress progress::-moz-progress-bar { background: var(--accent); border-radius: 999px; }

/* ------------------------------------------------------------ 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); }
/* Messages from the panel (2026-11-10): the text, and a small × to close it on this device. */
.panel-notice { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.panel-notice.banner-warn { border-color: var(--warn); }
/* «Visto» beside an Inicio alert's own button (2026-11-10). */
.alert-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.alert-seen { margin: 0; }
.alerts-seen { margin: 10px 0 0; }

/* ------------------------------------------------------- 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-ink); 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; }
/* Las sucursales de una cuenta (0069): una casilla por local, en columna y con el
   nombre entero — un local se llama «Sucursal Norte», no «N», así que la lista no es
   una fila de `.check` en línea. */
.branches-list { display: grid; gap: 0; margin-top: var(--space-2); }
.branches-list .check { white-space: normal; }
.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
 * One height scale (`--touch`, 44 px) and four weights, and the class that says
 * which is which is the only difference between them (design v2):
 *
 *   .btn            secondary — a white control with a hairline, the default
 *   .btn-primary    the shop's green: the one action a screen is *for*
 *   .btn-ghost      no surface at all: a tertiary act inside a card or a row
 *   .btn-danger     destructive, and never the default of a form
 *
 * `.btn-sm` and `.btn-lg` change the padding and the type, never the height: a
 * row of buttons on one line reads as one line, and nothing on the counter is
 * smaller than a finger. */
.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: background .12s ease, border-color .12s ease, color .12s ease, box-shadow .12s ease;
}
.btn:hover { background: var(--surface-2); border-color: var(--surface-3); }
.btn:active { background: var(--surface-3); }
.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); border-color: var(--accent-dark); }
.btn-primary:active { background: var(--accent-dark); filter: brightness(.94); }
.btn-danger { background: var(--danger); border-color: var(--danger); color: var(--on-accent); }
.btn-danger:hover { filter: brightness(.94); }
.btn-ghost { background: transparent; }
.btn-ghost:hover { background: var(--surface-2); }
.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; }
/* A verb *inside a record row* (`--touch-sm`): the row's own buttons sit in a
   40 px line, so they are one step smaller than the shell's 44 px targets — the
   counter's real controls (the till, the dialogs, the navigation) are untouched,
   and 30 px is well past WCAG 2.2 AA's 24 px minimum target. The accessible name
   is on every one of them (`aria-label` + `title`), because the glyph alone is
   not a word. */
.icon-btn-sm {
  width: var(--touch-sm); height: var(--touch-sm);
  min-width: var(--touch-sm); min-height: var(--touch-sm);
  padding: 0; font-size: var(--fs-md); line-height: 1;
}
.icon-btn-sm:hover { background: var(--surface-3); }
/* The drawn glyph inside one of those row buttons (design v2): the same 24 px
   Lucide grid and the same 1.7 stroke the navigation's icons carry, rendered a
   step smaller because the control is a step smaller — 22 px in the column, 16 px
   in a 40 px record row. `currentColor` means the button's hover and focus colours
   take the glyph with them. */
.row-icon { width: 16px; height: 16px; flex: 0 0 auto; }
/* The verbs a row keeps under a "more" button (the products catalogue): a popover
   anchored to the button, `position: fixed` so the table's own scroll container
   cannot clip it, and placed by `products.js` from the button's own rect. */
.rowmenu { display: inline-flex; }
.rowmenu-panel {
  position: fixed; z-index: var(--z-toast); min-width: 176px;
  padding: var(--space-1); display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-modal);
}
.rowmenu-panel button {
  font: inherit; font-size: var(--fs-md); text-align: left;
  padding: var(--space-2) var(--space-3); min-height: var(--touch-sm);
  border: 0; border-radius: var(--radius-sm); background: transparent;
  color: var(--text); cursor: pointer; white-space: nowrap;
}
.rowmenu-panel button:hover { background: var(--surface-2); }
.rowmenu-panel button:focus-visible { outline: none; box-shadow: var(--ring); }
.rowmenu-panel button.is-danger { color: var(--danger); }

/* ------------------------------------------------------------- tables
 * Two kinds of table live here: the ones a **till** screen draws (the cart, the
 * day's movements in a dialog) and the wide **record** tables a person reads back
 * — products, movements, sales, facturas, compras. The second kind opts in with
 * `class="table cols"` and gets the column rule below; everything else keeps the
 * automatic layout it was drawn for.
 */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; }
/* Inventario masivo (2026-10-27): seis campos por fila; el `.input` de la casa trae
   suelo de 210 px y la tabla se salía de la pantalla. Aquí manda la columna. */
.table .stock-cell { min-width: 0; width: 100%; }
/* 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; }
/* The header is a *lighter* band than the rows it labels (the quiet grey cell
   surface), so a list of records reads as one block with a heading rather than as
   a grid of equal boxes. */
.table thead th { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--muted); background: var(--surface-2); font-weight: var(--fw-semibold); }
.table tbody tr:hover { background: var(--surface-3); }
.table .num, .table .right { text-align: right; }
/* ---------------------------------------------------- the column rule (P42)
 * A wide record table is **sized by what each column holds**, not by whatever the
 * browser's automatic layout hands it. Before this, the products table's eleven
 * columns shared 1040 px of content box, the Actions cell was given 112 px, and
 * its six word-buttons wrapped one per line: every row stood **309 px** tall and
 * the catalogue was four screens long.
 *
 * The vocabulary — one class per kind of content, and no per-page override:
 *
 *   .col-name    the remaining width; one line, ellipsis, full text in `title`
 *   .col-code    a short identity (SKU, barcode): capped, monospace
 *   .col-key     a document's identity (a 20-digit clave): wider cap, monospace
 *   .col-tag     one word (category, warehouse, state): capped, ellipsis
 *   .col-date    a day or a moment: fixed
 *   .col-money   a colón figure: fixed, narrow, right-aligned
 *   .col-count   a count of things: fixed, narrower
 *   .col-stock   the quantity stepper's own column
 *   .col-note    free text: capped, ellipsis, full text in `title`
 *   .col-verbs   the row's own actions
 *   .col-opt     the first thing to go at ≤1024 px (a margin, a threshold)
 *
 * with **one row height** (`--table-row`) and **one padding scale** for all five
 * screens, so they read as one product instead of five tables that happen to be
 * on the same app. `table-layout: fixed` is what makes the name column take the
 * remainder; that is only safe because every cell below one line of text is
 * capped and ellipsised, and the whole value is in the cell's `title`.
 */
.table.cols { table-layout: fixed; }
.table.cols th, .table.cols td {
  padding: var(--space-1) var(--space-3);
  line-height: var(--lh-tight);
  overflow: hidden; text-overflow: ellipsis;
}
.table.cols tbody td { height: var(--table-row); }
/* A header may take a second line rather than wear an ellipsis: it is the name of
   the column, and "EXISTENCIA AQUÍ" cut to "EXISTENCIA …" is a column nobody can
   identify. The two-line header costs one row of the table's height and no data.
   The header is a step smaller than the shell's (11 px) because it is a label for
   figures, and the pixel it buys goes to the product name. */
.table.cols thead th { white-space: normal; line-height: 1.15; font-size: var(--fs-2xs); letter-spacing: .03em; }
.table.cols th.pick, .table.cols td.pick { width: 34px; }
.table.cols td.name, .table.cols .col-name { font-weight: var(--fw-semibold); }
/* A secondary fact (a barcode, an apartado, "none here") rides on the name's own
   line. As a second line it would raise that row above the one row height — and a
   table where one row in six is taller is the thing this rule exists to stop. */
.table.cols td .sub-line { display: inline; margin-left: var(--space-2); font-weight: var(--fw-regular); }
.table.cols td.empty { height: auto; padding: var(--space-6); white-space: normal; text-align: center; }
.table.cols .col-code { width: 80px; font-family: var(--mono); font-size: var(--fs-sm); }
.table.cols .col-key { width: 150px; font-family: var(--mono); font-size: var(--fs-sm); }
.table.cols .col-tag { width: 88px; }
.table.cols .col-state { width: 104px; }
.table.cols .col-date { width: 92px; }
.table.cols .col-money { width: 76px; }
.table.cols .col-count { width: 68px; }
.table.cols .col-stock { width: 112px; }
.table.cols .col-note { width: 180px; }
.table.cols .col-verbs { width: 172px; }
.table.cols .col-verbs-wide { width: 200px; }
/* The verbs sit in one line of the row's own height: the panel is `fixed` (see
   `.rowmenu`, above), so nothing here reserves room for it, and a row with fewer
   verbs simply leaves the rest of its cell empty. */
.table.cols .row-actions { gap: 2px; }
/* The stock stepper is a row's own control: it takes the row's size, not the
   shell's (`--touch` stays for the till's big one, which is a finger's). */
.table.cols .qty { gap: 0; }
.table.cols .qty button { width: var(--touch-sm); height: var(--touch-sm); min-width: var(--touch-sm); min-height: var(--touch-sm); }
.table.cols .qty input { width: 34px; padding: 0; height: var(--touch-sm); }
/* A cell's last resort is a tooltip, so only the cells that can be cut are
   allowed to cut: `title` goes on those in the template (and on the button that
   opens a record), never on a figure that already fits. */
.table.cols td.actions { padding: 0 var(--space-1); }
/* .table td.name is left as it was for tables that did not opt in; inside `.cols`
   the same cell is one line, and the ellipsis needs a block formatting context. */
.table td.name { font-weight: var(--fw-semibold); white-space: normal; min-width: 150px; }
.table.cols td.name, .table.cols td.col-name { white-space: nowrap; min-width: 0; }
/* 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). Inside `.cols` the
   verbs are compact (`--touch-sm`) and stay on one line — that is what the row
   height is for. */
.table td.actions { white-space: normal; }
.table.cols td.actions { white-space: nowrap; }

/* 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;
}
/* 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); }

/* ------------------------------------------------------------ status pills
 * One pill for every state the shop talks about, and the colour is the *state's*
 * word rather than the brand's (design v2): paid/ok green-grey, low stock amber,
 * out/rejected red, held violet, sandbox amber-ink. Green is never used for one of
 * these — it belongs to the brand and to the primary action, and a green "pending"
 * would be indistinguishable from "settled" at a glance.
 *
 * Hacienda's own states are these same pills: `aceptado` is `.badge-ok`,
 * `rechazado` is `.badge-out`, `pendiente`/`procesando` are `.badge-low`, and an
 * invoice Hacienda has not answered for yet is `.badge-expired` (grey: nothing is
 * wrong, nothing is known). */
.badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 2px var(--space-3); border-radius: var(--radius-pill);
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  line-height: 1.6; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.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-ink); }

/* 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; }
/* «Más ▾» on a sale (owner, 2026-11-10): the row keeps its receipt and «Ver factura»; the rest opens below. */
.more-menu { display: inline-block; position: relative; }
.more-menu > summary { list-style: none; cursor: pointer; }
.more-menu > summary::-webkit-details-marker { display: none; }
.more-menu .more-list { position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; display: flex;
  flex-direction: column; align-items: stretch; gap: 6px; min-width: 210px; padding: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); text-align: left; }
.more-menu .more-list .btn { width: 100%; justify-content: flex-start; }
.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-ink); 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; }
/* A dialog is one object with three bands (design v2): a head that names it, a body
   that holds the form, and a foot that holds the verbs. The card is what scrolls —
   and the head and the foot are *sticky* to its edges, so the dialog's name and its
   **Save** stay on screen while a long form (the product card, the invoice detail)
   scrolls under them. Before this, a 900 px-tall window put the product card's foot
   at y=903: the one control the screen exists for was one scroll-wheel away with
   nothing on screen saying so. `sticky` rather than a fixed-height flex body, because
   some dialogs carry a bare table instead of a `.modal-body` and a card that no
   longer scrolls would clip them. */
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 1; background: var(--surface); border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
.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; position: sticky; bottom: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
.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;
  cursor: pointer;
}
.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-ink); 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); }

/* ============================================ loading, tabs, paging (design v2)
 * Three shapes a page needs and nobody should draw for itself. They live here,
 * from the tokens, because "the same shop on every screen" is the stylesheet's
 * job and not a page's: a screen that invents its own spinner is a screen that
 * will drift from the other five.
 *
 * `.skeleton` is what a data panel shows while its figures are on the way — a
 * quietly sheening block in the page's own greys, sized like the line or the row
 * it stands in for, so the card does not jump when the numbers arrive. It moves
 * only if the device has not asked for less motion (see the end of this file).
 */
.skeleton {
  display: block; border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--surface-3), var(--surface-2), var(--surface-3));
  background-size: 200% 100%;
  animation: skeleton-sheen 1.4s ease-in-out infinite;
}
.skeleton + .skeleton { margin-top: var(--space-2); }
.skeleton-line { height: 12px; }
.skeleton-row { height: var(--table-row); }
.skeleton-figure { height: 28px; width: 45%; }
.skeleton-tile { height: 92px; border-radius: var(--radius); }
@keyframes skeleton-sheen { from { background-position: 100% 0; } to { background-position: -100% 0; } }

/* A tab row: the page's own sub-navigation (the reports' tables, the two invoice
   lists). One underline, the active one in the shop's green, and a `<button>` and
   an `<a>` look the same under it — the row is a control, not a decoration. */
.tabs { display: flex; flex-wrap: wrap; gap: var(--space-1); margin: 0 0 var(--space-5); border-bottom: 1px solid var(--border); }
.tab {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: var(--touch); padding: 0 var(--space-4);
  border: 0; border-bottom: 2px solid transparent; background: transparent;
  color: var(--muted); font: inherit; font-size: var(--fs-md); font-weight: var(--fw-medium);
  text-decoration: none; cursor: pointer;
  transition: color .12s ease, border-color .12s ease;
}
.tab:hover { color: var(--text); }
.tab[aria-selected="true"], .tab.active { color: var(--accent-dark); border-bottom-color: var(--accent); font-weight: var(--fw-semibold); }
.tab:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-sm) var(--radius-sm) 0 0; }

/* A pager: how much there is, and the two ways through it. The count is tabular so
   it does not twitch while it is being read. */
.pager { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; padding: var(--space-4) var(--space-6); border-top: 1px solid var(--border); }
.pager-count { color: var(--muted); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.pager-links { display: flex; gap: var(--space-2); }

/* An empty state that says what to do next — a new shop must not look broken:
   the sentence the page already wrote (`.empty`, above), a title, and the button
   that fixes it. */
.empty-state { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); padding: var(--space-8) var(--space-5); text-align: center; }
.empty-title { margin: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--text); }
.empty-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; justify-content: center; }

/* ======================================================= 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; }
  /* En el riel no cabe una estrella al lado de un icono de 22 px — y no hace falta:
     el atajo de arriba sigue estando, como un icono más. */
  html.nav-rail .nav-pin { display: none; }
  html.nav-rail .nav-pinned-label { display: none; }
  html.nav-rail .nav-group {
    font-size: 0; height: 0; min-height: 0; padding: 0;
    margin: var(--space-3) var(--space-1) 0; border-top: 1px solid var(--sidebar-line);
    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(--sidebar-line); border-radius: var(--radius-sm);
    color: var(--sidebar-text); font-size: var(--fs-2xs); font-weight: var(--fw-heavy);
    text-transform: uppercase;
  }
  /* Four buttons do not fit one row of the rail; they overlapped until the till started folded. */
  html.nav-rail .who { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  html.nav-rail .who-out { min-height: 30px; }
  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. In
     design v2 the mark is the same short bar the open column draws
     (`.nav a.active::before`, above) — one indicator for both shapes, so a collapsed
     rail and an open column never disagree about which row you are on. */
}

/* 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.
 */
/* POS-ai para todos (2026-10-23): un cajero ve la ayuda —temas, pasos y el enlace a
   la pantalla— y no el período ni el presupuesto, que son del dueño. */
body.assistant-help-only .ask-day-label,
body.assistant-help-only #ai-day,
body.assistant-help-only #ai-limit,
body.assistant-help-only .ask-form + .hint + .suggest-row { display: none; }
.ask-help { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px; }
.chip-button { cursor: pointer; }

.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); }
/* Los precios pedidos por la caja, en el catálogo (2026-10-27): una fila por pedido —
   qué producto, de cuánto a cuánto, y cuándo se pidió. */
.price-requests { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.price-requests li { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
.price-requests .pr-what { font-weight: var(--fw-medium); }
.price-requests .pr-prices { font-variant-numeric: tabular-nums; }
/* Los atajos de período (dueño, 2026-10-27): un clic en vez de abrir una lista o
   escribir dos fechas. El elegido queda marcado; son el mismo ajuste que el
   desplegable de al lado, así que no hay dos formas de pedir lo mismo. */
.period-chips { display: inline-flex; gap: var(--space-1); flex-wrap: wrap; align-items: center; }
.period-chips .btn.is-on {
  background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent);
}
/* El período contestado, cuando la pregunta nombró uno (2026-10-27): va sobre la
   respuesta, en la voz de las notas, para que «esta semana» no se lea como «hoy». */
.ask-period { margin: 0 0 var(--space-2); 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 shift's takings are the dashboard's headline: on a phone it takes the whole row and its size follows
     the screen, so ₡196,900,000 is never cut off (owner, 2026-11-09). Any other figure wraps rather than spill. */
  .dash-day { grid-column: 1 / -1; }
  .dash-day .stat-value { font-size: clamp(1.75rem, 10vw, 2.75rem); }
  .stat { min-width: 0; }
  .stat-value { overflow-wrap: anywhere; }
}

/* ------------------------------------------------- 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(--sidebar-text); border: 1px solid var(--sidebar-line);
  transition: background .12s ease, color .12s ease;
}
.alert-bell:hover { background: var(--sidebar-hover); color: var(--sidebar-text); }
.alert-bell:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }
/* 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-ink); }

/* ------------------------------------------------------------------------- */
/* 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-1); margin: 0 0 var(--space-5); border-bottom: 1px solid var(--border); }
/* The reports page writes its tabs as buttons; design v2 gives them the shared
   tab row's own underline (`tabs` above) rather than a second look for the same
   act — no markup change, one system. */
.report-tabs .btn {
  border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  background: transparent; color: var(--muted); font-weight: var(--fw-medium);
}
.report-tabs .btn:hover { background: transparent; color: var(--text); }
.report-tabs .btn:focus-visible { border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.report-tabs .btn.active {
  background: transparent; border-bottom-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; }

/* ------------------------------------------------- the CABYS proposal (P44)
   What the catalogue would choose, shown under the code field while the name is
   typed: the code, the catalogue's own description of it, and the rate that will
   be declared. A confident answer is a quiet note; a doubtful one is a *proposal*
   (`[data-state="review"]`) with the one button that accepts it, painted in the
   warning colour because a rate nobody confirmed is the thing this card exists to
   stop. The results list is the same shape inside the form as in the review card. */
.cabys-suggest { margin: var(--space-3) 0 0; padding: var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); }
.cabys-suggest[data-state="review"] { border-color: var(--warn); background: var(--warn-fill); }
.cabys-suggest[data-state="none"] { background: var(--surface); }
.cabys-suggest .hint { margin: 0 0 var(--space-2); }
.cabys-suggest-line { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-2); margin: 0; }
.cabys-suggest .row-actions { justify-content: flex-start; margin-top: var(--space-2); }
.cabys-results { display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-3); }
.cabys-results .cabys-result { text-align: left; justify-content: flex-start; }
.cabys-results .cabys-result code { margin-right: var(--space-2); }
/* ------------------------------------------------- the record tables narrow
 * Two floors, in the order the columns can afford to go.
 *
 * At **1024 px** — a tablet in landscape, the smallest screen on which a record
 * is still a table — the two owner-only figures leave (`col-opt`: the catalogue's
 * margin and its threshold). A column read by one person is worth less than a
 * product name that can still be read by everyone.
 *
 * At **768 px** the table stops being a table: each row becomes its own card,
 * each cell labelled with the column it came from (`data-label`, stamped from the
 * header by `stampColumnLabels()` in `common.js` — the header is already
 * translated, so a phone reads the same words as the Mac), and the record is read
 * top-to-bottom instead of sideways.
 */
@media (max-width: 1024px) {
  .table.cols .col-opt { display: none; }
}
@media (max-width: 768px) {
  .table.cols, .table.cols tbody, .table.cols tr, .table.cols td { display: block; width: auto; }
  .table.cols { table-layout: auto; }
  .table.cols thead { display: none; }
  .table.cols tr {
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    margin: 0 0 var(--space-2); padding: var(--space-2) 0;
  }
  .table.cols tr:last-child { margin-bottom: 0; }
  .table.cols td {
    display: flex; align-items: baseline; gap: var(--space-3); justify-content: space-between;
    height: auto; padding: var(--space-1) var(--space-4);
    white-space: normal; overflow: visible;
  }
  .table.cols td[data-label]::before {
    content: attr(data-label); flex: 0 0 auto;
    color: var(--muted); font-size: var(--fs-2xs);
    text-transform: uppercase; letter-spacing: .04em;
  }
  .table.cols td.name, .table.cols td.col-name { white-space: normal; min-width: 0; }
  .table.cols td.pick { display: none; }
  .table.cols td.empty { padding: var(--space-5); }
  .table.cols td.actions { padding-top: var(--space-2); padding-bottom: var(--space-2); }
  .table.cols td.actions .row-actions { justify-content: flex-start; }
  /* The received-invoices table keeps its verbs at the card's edge only while
     there is an edge to keep them at (see `.compras-table`, above). */
  .compras-table td.actions { position: static; box-shadow: 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).
   Design v2's own transitions and the skeleton's sheen are held to the same
   rule: nothing that was decoration may keep moving for someone who asked it
   not to. */
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
  .nav-group-chevron { transition: none; }
  .nav a, .nav a::before, .nav-group, .rail-toggle, .drawer-close, .who-out,
  .alert-bell, .btn, .tab, .locale-option, .locale-cycle { transition: none; }
  .skeleton { animation: none; }
}

/* The row's sticker action is an <a> (it opens a page) wearing the same shape as the
   row's buttons — without this it inherits the document's link underline and colour,
   which is what made the actions column look broken beside four buttons. */
a.icon-btn { text-decoration: none; color: var(--muted); }
a.icon-btn:hover { color: var(--text); }

/* --- los verbos de /frecuentes en una línea ------------------------------ */
.table.cols .freq-verbs { flex-wrap: nowrap; align-items: center; }
/* La fila de frecuentes lleva el selector de categoría y el porcentaje dentro de la celda
   de descuentos: ahí caben y la celda de acciones se queda con la estrella y la papelera,
   que era justo lo que se salía de la tabla. */
.table.cols .freq-add { flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2);
  width: 100%; align-items: center; }
.table.cols tbody td.discounts-cell { height: auto; min-height: var(--table-row);
  vertical-align: top; white-space: normal; }
.table.cols td.discounts-cell .freq-add { max-width: 26rem; }
.table.cols .freq-add .freq-pick { min-width: 9rem; max-width: 12rem; }
.table.cols .freq-add .freq-percent { width: 4.5rem; }
/* A verb or a figure in this cell keeps its own width, so the line breaks at no
   point but the cell's edge. */
.table.cols .freq-verbs > * { flex: 0 0 auto; }
/* A control in this cell takes the record row's own size (`--touch-sm`), the same
   two steps the products table's row verbs and stock stepper take: the shell's
   44 px target would set the row's height by itself. */
.table.cols .freq-verbs .btn,
.table.cols .freq-verbs .input,
.table.cols .freq-verbs .icon-btn { height: var(--touch-sm); min-height: var(--touch-sm); }
.table.cols .freq-verbs .btn { padding: 0 var(--space-3); }
.table.cols .freq-verbs .freq-percent {
  width: 56px; min-width: 0; padding-left: var(--space-2); padding-right: var(--space-2);
}

/* --- primeros pasos (v2 B2) ---------------------------------------------- */
.progress { height: 10px; background: var(--accent-soft); border-radius: 999px; overflow: hidden; }
.progress-fill { display: block; height: 100%; background: var(--accent);
  border-radius: 999px; transition: width .3s ease; }
.onboarding-steps { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 12px; }
.onboarding-step { display: flex; align-items: flex-start; gap: 14px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
.onboarding-step.is-done { background: var(--accent-soft); border-color: var(--ok-soft); }
.onboarding-mark { flex: 0 0 28px; width: 28px; height: 28px; border-radius: 999px;
  display: grid; place-items: center; font-weight: 700; font-size: 13px;
  background: var(--border); color: var(--muted); }
.onboarding-step.is-done .onboarding-mark { background: var(--accent); color: var(--on-accent); }
.onboarding-body { flex: 1; min-width: 0; }
.onboarding-body strong { display: block; margin-bottom: 2px; }
.onboarding-body .hint { margin: 0; }
.onboarding-ready h2 { margin-top: 0; }

/* Móvil: nada se sale por la derecha (2026-10-23). El contenedor principal y las
   rejillas no pueden ser más anchas que la pantalla; si algo no cabe, se desplaza dentro
   de su propia caja (la carta, la cuenta), no el documento entero. */
@media (max-width: 768px) {
  html, body { max-width: 100%; overflow-x: hidden; }
  .container { width: 100%; max-width: 100%; padding-left: 10px; padding-right: 10px; }
  .card { max-width: 100%; }
  .page-actions { flex-wrap: wrap; }
}

/* --- la cola del mostrador «dine out» (fase 1) --------------------------- */
/* Sólo tokens del sistema: ningún color propio (la prueba de diseño lo exige). */
.dineout-box { display: flex; flex-wrap: wrap; gap: var(--space-2, 10px);
  align-items: center; padding: var(--space-3, 12px);
  margin: var(--space-2, 8px) 0; border: 1px solid var(--border);
  border-radius: var(--radius-lg); background: var(--bg); }
.dineout-box .input { flex: 1 1 180px; min-width: 140px; }
.order-banner { margin: var(--space-2, 8px) 0; padding: var(--space-3, 12px);
  border-radius: var(--radius); font-weight: var(--fw-bold); font-size: var(--fs-lg);
  background: var(--ok-soft); color: var(--ok); }
.queue-grid { display: grid; gap: var(--space-3, 12px);
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.queue-card { border: 2px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--space-3, 12px); background: var(--bg); }
.queue-card.is-ready { border-color: var(--ok); background: var(--ok-soft); }
.queue-card.is-preparing { border-color: var(--accent); background: var(--accent-soft); }
.queue-card.is-late { box-shadow: 0 0 0 3px var(--danger-soft); }
.queue-card header { display: flex; justify-content: space-between; align-items: baseline; }
.queue-number { font-size: var(--fs-2xl); letter-spacing: .5px; }
.queue-minutes { font-variant-numeric: tabular-nums; color: var(--danger); font-weight: var(--fw-bold); }
.queue-name { margin: 4px 0 2px; font-size: var(--fs-lg); }
.dineout-ready .ready-numbers { display: flex; flex-wrap: wrap; gap: var(--space-3, 12px); }
.ready-number { font-size: var(--fs-3xl); font-weight: var(--fw-heavy); letter-spacing: 1px;
  color: var(--ok); }
/* Las opciones elegidas, bajo el nombre de la línea del carrito (2026-10-25). */
.cl-mods { font-size: var(--fs-sm); color: var(--muted); margin-top: 2px; }
.mod-group.is-off { opacity: .45; }

/* A counter PC is often 1280×720: use the room for the catalogue, not for air.
   The touch targets stay finger-sized at normal heights and tighten only here. */
@media (min-width: 1025px) and (max-height: 820px) {
  .sidebar-head { padding: var(--space-2) var(--space-3); }
  .nav { padding: var(--space-2); gap: 1px; }
  .nav a { min-height: 33px; padding: 4px var(--space-2); }
  .nav-group { min-height: 26px; margin-top: var(--space-2); }
  .sidebar-foot { padding: var(--space-2) var(--space-3); }
  .who-out { min-height: 32px; }
  .locale-switch { margin-top: var(--space-1); }
  .sidebar-foot .locale-option { min-height: 32px; }
}

/* A cashier's Dashboard with the shop's money turned off (the admin's switch in Settings): the
   server has already left the figures out; this hides the tiles that would have shown them. */
.money-hidden [data-money] { display: none !important; }

/* The setup checklist's one-field forms (Back Office plan, Phase 2): the input takes the
   room the row has, so its placeholder is readable, and the refusal sits under it. */
.onboarding-inline { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-top: var(--space-2); }
.onboarding-inline .input { flex: 1 1 18rem; min-width: 0; }
.onboarding-inline .onboarding-error { flex-basis: 100%; color: var(--danger-ink); }
/* ---------------------------------------------------------------- settings shell
   The hub and the four settings screens share one left-hand map (`settings_nav.py`).
   It sticks while the cards scroll, and drops above them on a narrow screen. */
.settings-shell { display: grid; grid-template-columns: minmax(140px, 168px) minmax(0, 1fr);
  gap: var(--space-3); align-items: start; }
/* The map sits flush with the page's left edge and stays narrow, so the cards get the width
   (owner, 2026-11-07: "it's taking up too much space and the settings on the right are squeezed"). */
main.container:has(> .settings-shell) { max-width: none; padding-left: var(--space-3); padding-right: var(--space-4); }
.settings-main { min-width: 0; }
.settings-nav { position: sticky; top: var(--space-4); max-height: calc(100vh - 2 * var(--space-4));
  overflow-y: auto; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--space-2); font-size: 0.85rem; }
.settings-search { width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; margin-bottom: var(--space-2); font-size: 0.85rem; }
.settings-nav-group + .settings-nav-group { margin-top: var(--space-3); }
.settings-nav h3 { margin: 0 0 var(--space-1); font-size: 0.75rem; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted); }
.settings-nav ul { list-style: none; margin: 0; padding: 0; }
.settings-nav a { display: block; padding: 3px var(--space-2); line-height: 1.3; border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; }
.settings-nav a:hover, .settings-nav a:focus-visible { background: var(--surface-2); }
.settings-nav a[aria-current="page"] { background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }
.settings-nav li.is-screen > a::after { content: " \203A"; color: var(--muted); }
.settings-main > section[id], .settings-main section[id] { scroll-margin-top: var(--space-4); }
@media (max-width: 900px) {
  .settings-shell { grid-template-columns: 1fr; }
  .settings-nav { position: static; max-height: none; }
  .settings-nav ul { display: flex; flex-wrap: wrap; gap: var(--space-1); }
}

/* Number boxes inside the purchase-order and variant tables stay inside their cell. */
.po-qty, .po-cost, .po-now { width: 6.5rem; max-width: 100%; }

/* The owner's status lights at the top of the dashboard: a green / yellow / red dot and a few plain words. */
.control-lights { margin: 0 0 var(--space-4); }
.alerts-fold > summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--space-2); }
.alerts-fold > summary::-webkit-details-marker { display: none; }
.alerts-fold > summary .control-title { margin: 0; }
.alerts-caret { margin-left: auto; color: var(--muted); transition: transform .15s; }
.alerts-fold:not([open]) .alerts-caret { transform: rotate(-90deg); }
.alerts-fold[open] > summary { margin-bottom: var(--space-2); }
.control-lights .control-title { font-size: var(--fs-sm); margin: 0 0 var(--space-2); color: var(--muted); font-weight: 600; }
.control-lights ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.control-lights .light a {
  display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: inherit;
  text-decoration: none; font-size: var(--fs-sm);
}
.control-lights .light a:hover { background: var(--surface-2); }
.control-lights .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--muted); flex: 0 0 auto; }
.control-lights .light-ok .dot { background: var(--ok); }
.control-lights .light-warn .dot { background: var(--warn); }
.control-lights .light-bad .dot { background: var(--danger); }
.control-lights .light-bad a { border-color: var(--danger); }

/* The dashboard's four-row panels (restored 2026-11-06): the rules from the original dashboard pass had been
 * lost, so "Últimas ventas" and "Stock bajo" drew as bulleted text with the figures run together. A row is a
 * name, the fact that qualifies it, and the figure at the end, on the record tables' own rhythm. */
.panel-list { list-style: none; margin: 0; padding: 0; }
.panel-list li {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-3) var(--space-6); border-bottom: 1px solid var(--border);
  font-size: var(--fs-md);
}
.panel-list li:last-child { border-bottom: 0; }
.panel-list .pl-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.panel-list .pl-note { display: block; color: var(--muted); font-size: var(--fs-xs); font-weight: var(--fw-regular); }
.panel-list .pl-value {
  flex: 0 0 auto; font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
}
/* The comprobantes panel: the four states with the number beside each; a state with no documents is still
   drawn, at zero, because "nothing was rejected" is a fact the owner reads on purpose. */
.status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
.status-cell {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2);
  padding: var(--space-3) var(--space-4); border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface-2);
}
.status-cell .status-count { font-size: var(--fs-lg); font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; }

/* The owner's simpler shell (2026-11-06): the search button, the tab row, the day's to-do. */
.nav-search {
  display: flex; align-items: center; gap: var(--space-2); width: calc(100% - var(--space-4));
  margin: var(--space-2); padding: var(--space-2) var(--space-3); font: inherit; font-size: var(--fs-sm);
  color: var(--sidebar-muted); background: transparent; border: 1px solid var(--sidebar-line);
  border-radius: var(--radius-sm); cursor: pointer;
}
.nav-search:hover { background: var(--sidebar-hover); color: var(--sidebar-text); }
.nav-search:focus-visible { outline: none; box-shadow: var(--ring-on-dark); }
.nav-search kbd { margin-left: auto; font-size: var(--fs-2xs); font-family: var(--mono); opacity: .8; }
html.nav-rail .nav-search { justify-content: center; width: auto; padding-left: 0; padding-right: 0; margin-left: var(--space-1); margin-right: var(--space-1); }
.page-tabs { display: flex; flex-wrap: wrap; gap: var(--space-1); margin: 0 0 var(--space-4); border-bottom: 1px solid var(--border); }
.page-tab {
  padding: var(--space-2) var(--space-3); font: inherit; font-size: var(--fs-sm); color: var(--text-label);
  text-decoration: none; background: transparent; border: 0; border-bottom: 2px solid transparent;
  margin-bottom: -1px; cursor: pointer;
}
.page-tab:hover { color: var(--text); }
.page-tab.is-on { color: var(--accent); border-bottom-color: var(--accent); font-weight: var(--fw-semibold); }
.caja-tab-off { display: none !important; }
.palette-card { padding: var(--space-3); }
.palette-card > .input { width: 100%; }
.palette-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; max-height: 50vh; overflow-y: auto; }
.palette-list a {
  display: flex; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); color: var(--text); text-decoration: none;
}
.palette-list small { color: var(--muted); }
.palette-list li.is-current a, .palette-list a:hover { background: var(--surface-2); }
.palette-empty { margin: var(--space-3); }
.control-lights .control-todo { display: flex; flex-direction: column; gap: var(--space-2); }
.control-lights .control-todo .light a { display: flex; width: 100%; }
.control-lights .control-go { margin-left: auto; color: var(--muted); }
.control-fine { margin-top: var(--space-2); font-size: var(--fs-sm); color: var(--muted); }
.control-fine summary { cursor: pointer; margin-bottom: var(--space-2); }
.dash-more { margin: 0 0 var(--space-4); }
.dash-more > summary { cursor: pointer; font-size: var(--fs-sm); color: var(--text-label); margin-bottom: var(--space-2); }
@media (min-width: 900px) {
  #stats { grid-template-columns: 2fr 1fr 1fr 1fr; }
  #stats .dash-day { grid-column: auto; }
}

/* One setting at a time on the settings hub (owner, 2026-11-07); see `settings_nav.js::focusSection`. */
.settings-focus-off { display: none !important; }
.settings-nav a.is-focused { font-weight: var(--fw-semibold); color: var(--accent); }

/* Ajustes > Pantalla (owner, 2026-11-07): each choice is a row you can tap, its name and one line on
   what it is for. */
.screen-form { display: grid; gap: var(--space-5); }
.screen-choices { border: 0; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.screen-choices legend { margin-bottom: var(--space-2); }
.screen-choice {
  display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer; min-height: var(--touch);
}
.screen-choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.screen-choice input { margin-top: 3px; }
.screen-choice > span { display: grid; gap: 2px; }
.screen-form > .btn { justify-self: start; }

/* Ajustes › Aprobaciones: the switches in two columns, each a row you can tap. */
.approval-switches { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--space-2) var(--space-5); margin: var(--space-4) 0; }
.approval-switches .check { min-height: var(--touch); align-items: center; }

/* A date typed as dd/mm/aaaa that does not exist (31/02/2026): marked, not passed on. */
.input.date-field.is-invalid { border-color: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
.input.date-field { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ dialog forms (owner, 2026-11-07)
 * "Check the entire system for field overlays: when entering temporary items the text boxes overlay
 * each other; it looks unprofessional." A dialog's labels and fields sat loose in its body, so the
 * browser put a short label beside a field and a long one above, fields kept their own widths and two
 * side-by-side fields touched or ran into a button. One rule for every dialog: a label sits above its
 * field, a field is as wide as its column, and side-by-side fields keep a gap. */
.modal-body > .field-label,
.modal-body .grid-2 .field-label,
.modal-body .discount-fields .field-label { display: block; margin: var(--space-3) 0 var(--space-1); }
.modal-body > .field-label:first-child,
.modal-body > input[type="hidden"] + .field-label,
.modal-body .grid-2 > div > .field-label:first-child,
.modal-body .discount-fields > div > .field-label:first-child { margin-top: 0; }
.modal-body > .input,
.modal-body > select.input,
.modal-body > textarea.input,
.modal-body .grid-2 .input,
.modal-body .discount-fields .input { display: block; width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; }
.modal-body .grid-2,
.modal-body .discount-fields { gap: var(--space-2) var(--space-4); margin-top: var(--space-2); }
.modal-body .grid-2 > div,
.modal-body .discount-fields > div { min-width: 0; }
.modal-body .line-builder {
  display: grid; grid-template-columns: minmax(0, 1fr) 96px auto; gap: var(--space-2); align-items: stretch;
  margin-top: var(--space-2);
}
.modal-body .line-builder .input { width: 100%; min-width: 0; box-sizing: border-box; }
/* The apartado's temporary item (0121): the description on its own line, then price, quantity, button. */
.modal-body .temp-fold .line-builder { grid-template-columns: minmax(0, 1fr) 96px auto; }
.modal-body .temp-fold .line-builder #n-temp-name { grid-column: 1 / -1; }

/* The migration check reads as a list of verdicts: a tick when the figure is right, a warning when it is not. */
.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.checklist li { position: relative; padding-left: 1.75rem; line-height: 1.45; }
.checklist li::before { position: absolute; left: 0; top: 0; width: 1.25rem; text-align: center; font-weight: 700; }
.checklist li.is-done::before { content: "\2713"; color: var(--ok); }
.checklist li.is-warn::before { content: "!"; color: var(--warn-ink); }
.checklist li.is-warn { color: var(--warn-ink); }
.settings-main > .card + .page-head { margin-top: var(--space-5); }

/* The shop's own calendar (`date_fields.js`): a date is picked, not typed (owner, 2026-11-07).
   It sits above dialogs and the till, under the lock and the toasts. */
.input.date-field.is-picker { cursor: pointer; caret-color: transparent; }
.date-picker { position: fixed; z-index: var(--z-toast); width: 268px; padding: var(--space-3);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); color: var(--text); }
.date-picker-head { display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--space-2); }
.date-picker-head strong { font-size: 0.95rem; }
.date-picker-nav { width: 32px; height: 32px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font-size: 1.1rem; line-height: 1; cursor: pointer; }
.date-picker-nav:hover { background: var(--surface-2); }
.date-picker-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.date-picker-grid.is-months { grid-template-columns: repeat(3, 1fr); gap: var(--space-1); }
.date-picker-weekday { text-align: center; font-size: 0.72rem; color: var(--muted); padding: 2px 0;
  text-transform: uppercase; }
.date-picker-day { height: 32px; border: 0; border-radius: var(--radius-sm); background: transparent;
  color: var(--text); font: inherit; font-size: 0.88rem; font-variant-numeric: tabular-nums; cursor: pointer; }
.date-picker-grid.is-months .date-picker-day { height: 40px; }
.date-picker-day:hover, .date-picker-day:focus-visible { background: var(--surface-2); outline: none; }
.date-picker-day:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.date-picker-day.is-today { font-weight: 700; color: var(--accent-strong); }
.date-picker-day.is-selected { background: var(--accent); color: var(--on-accent); }
.date-picker-day[disabled] { color: var(--muted); opacity: 0.45; cursor: not-allowed; }
.date-picker-foot { display: flex; justify-content: space-between; gap: var(--space-2);
  margin-top: var(--space-2); }

/* The Caja page's close carries the till's counter and the "later" row (styled in pos.css there). */
.closing-denoms { margin-top: var(--space-3); }
.closing-denoms summary { cursor: pointer; font-size: var(--fs-sm); color: var(--text-label); }
.closing-denom-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); margin-top: var(--space-2); }
.closing-denom { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-sm); }
.closing-card-later { display: grid; gap: var(--space-2); margin-top: var(--space-2); }

/* The settings front door: one tile per group (owner, 2026-11-07). */
.settings-nav-home { display: block; margin-bottom: var(--space-2); font-weight: 600; color: var(--accent-strong);
  text-decoration: none; padding: 3px var(--space-2); }
.settings-overview h1 { margin: 0 0 var(--space-4); font-size: 1.4rem; }
.settings-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-4); }
.settings-tile { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--space-4); }
.settings-tile h2 { margin: 0 0 var(--space-1); font-size: 1.05rem; }
.settings-tile p { margin: 0 0 var(--space-3); font-size: 0.88rem; }
.settings-tile ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.settings-tile a { display: block; padding: 4px var(--space-2); margin: 0 calc(-1 * var(--space-2));
  border-radius: var(--radius-sm); color: var(--text); text-decoration: none; }
.settings-tile a:hover, .settings-tile a:focus-visible { background: var(--surface-2); color: var(--accent-strong); }

/* A form's fields as a group the panel lock can disable at once; no frame of its own. */
.lock-set { border: 0; margin: 0; padding: 0; min-width: 0; }
.lock-set[disabled] { opacity: 0.75; }
.lock-set[disabled] button[type="submit"] { display: none; }

/* The dashboard's downloads, one menu beside the period controls (2026-11-08). */
.dash-downloads { position: relative; display: inline-block; }
.dash-downloads > summary { list-style: none; cursor: pointer; }
.dash-downloads > summary::-webkit-details-marker { display: none; }
.dash-downloads-menu { position: absolute; z-index: 5; top: calc(100% + 4px); left: 0; display: grid;
  gap: var(--space-1); padding: var(--space-2); min-width: 200px; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); }
.dash-downloads-menu .btn { justify-content: flex-start; }

/* The dashboard's period in detail: payments, hours and margin side by side, stacked on a narrow screen. */
.period-detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px 24px; }
.hour-row.is-peak .hour-label { font-weight: 700; }

/* The catalogue's selection bar and the row's own category picker (owner, 2026-11-08). */
.bulk-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 10px 16px;
  background: var(--accent-soft); border-bottom: 1px solid var(--border); }
.bulk-bar[hidden] { display: none; }
/* Reads as the category's name (as the column always did) and opens the picker on a click. */
.cat-inline { appearance: none; -webkit-appearance: none; width: 100%; min-width: 0; padding: 2px 0; border: 0;
  background: transparent; font: inherit; font-size: var(--fs-sm); color: inherit; cursor: pointer;
  text-overflow: ellipsis; text-decoration: underline dotted; text-underline-offset: 3px; box-shadow: none; }
.cat-inline:hover, .cat-inline:focus-visible { color: var(--accent-dark); outline: 2px solid var(--accent-soft); }

/* The Meta connection form: one column per field, stacked on a phone. */
.ads-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px 16px; align-items: end; }
.ads-form label { display: flex; flex-direction: column; gap: 4px; }
.ads-form .hint, .ads-form .form-actions { grid-column: 1 / -1; }

/* The ads' charts over time: one series each, the till's accent, recessive grid and axis text. */
.ads-chart { position: relative; }
.ads-chart svg { width: 100%; height: auto; display: block; overflow: visible; }
.ads-grid { stroke: var(--border); stroke-width: 1; }
.ads-axis { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.ads-mark { fill: var(--accent); opacity: 0.85; }
.ads-mark.is-on { opacity: 1; }
.ads-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ads-dot { fill: var(--accent); stroke: var(--surface); stroke-width: 2; }
.ads-hover-dot { opacity: 0; }
.ads-hover-dot.is-on { opacity: 1; }
.ads-hit { fill: transparent; cursor: crosshair; }
.ads-tip { position: absolute; top: 0; padding: 4px 8px; border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border); box-shadow: var(--shadow); color: var(--text); font-size: var(--fs-sm);
  pointer-events: none; white-space: nowrap; }

/* The dashboard's overview card (owner, 2026-11-08: the old app's overview, in this till). */
.ov-card .card-head { flex-wrap: wrap; gap: 8px; }
.ov-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.ov-tabs .btn.is-on, .ov-chips .btn.is-on { background: var(--accent); color: var(--surface); border-color: var(--accent); }
.ov-bar { display: grid; gap: 10px; }
.ov-step { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ov-step strong { font-size: var(--fs-md); text-align: center; }
.ov-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.ov-kpis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; }
@media (max-width: 900px) { .ov-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .ov-kpis { grid-template-columns: 1fr; } }
.ov-kpi { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); }
.ov-kpi-value { font-size: 1.45rem; font-variant-numeric: tabular-nums; line-height: 1.2; }
.ov-cmp { font-size: var(--fs-sm); display: flex; justify-content: space-between; gap: 8px; }
.ov-note { grid-column: 1 / -1; margin: 0; }
.ov-trend-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.ov-trend-head .input { width: auto; }
.ov-legend { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: var(--fs-sm); color: var(--muted); margin: 4px 0 6px; }
.ov-key { display: inline-block; width: 18px; height: 3px; border-radius: 2px; margin-left: 6px; }
.ov-key-now { background: var(--accent); }
.ov-key-prev { background: var(--muted); opacity: 0.6; }
.ov-line-prev { fill: none; stroke: var(--muted); stroke-opacity: 0.55; stroke-width: 2; stroke-linejoin: round; }
.ov-dist-label { font-family: inherit; min-width: 0; }
.ov-sort { background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; text-transform: inherit; letter-spacing: inherit; }
.ov-sort.is-on { color: var(--accent-dark); }
.ov-negative { color: var(--danger); }

/* The day's review tab: blocks side by side on a wide screen, a tone on the rows that deserve a "why?". */
.ov-badge { display: inline-block; min-width: 18px; padding: 0 6px; margin-left: 4px; border-radius: 999px;
  background: var(--danger); color: var(--surface); font-size: 11px; line-height: 18px; text-align: center; }
.ov-review { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 8px 24px; }
.ov-review > .hint { grid-column: 1 / -1; }
.ov-review-block .panel-list li.ov-warn .pl-value { color: var(--warn); }
.ov-review-block .panel-list li.ov-bad .pl-value { color: var(--danger); }
/* The review's lists are read, not glanced at: the exceptions take the full width and every note wraps, so
   "who approved" is never cut off. */
.ov-review > .ov-review-block:first-child { grid-column: 1 / -1; }
.ov-review .pl-note { white-space: normal; overflow: visible; text-overflow: clip; }
.ov-review .pl-name { min-width: 0; }

/* Ads efficiency (owner, 2026-11-08): verdict tags carry their word, colour only repeats it. */
.ads-eff-summary p { margin: 4px 0; }
.ads-eff-legend { list-style: none; padding: 0; margin: 0; display: grid; gap: 6px; font-size: var(--fs-sm); }
.ads-verdict, .ads-flag { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px; font-weight: 600;
  border: 1px solid var(--border); background: var(--surface); white-space: nowrap; }
.ads-verdict.v-scale, .ads-verdict.v-efficient { border-color: var(--accent); color: var(--accent-dark); }
.ads-verdict.v-costly { border-color: var(--warn); color: var(--warn); }
.ads-verdict.v-nothing { border-color: var(--danger); color: var(--danger); }
.ads-flag { color: var(--muted); }
.ads-eff-even { stroke: var(--muted); stroke-dasharray: 4 4; stroke-width: 1.5; }
.ads-eff-dot { fill: var(--accent); fill-opacity: 0.75; stroke: var(--surface); stroke-width: 2; cursor: pointer; }
.ads-eff-dot.v-costly { fill: var(--warn); }
.ads-eff-dot.v-nothing { fill: var(--danger); }

/* The owner's alerts as cards (2026-11-08): urgent ones carry a red edge and a filled button; the reason sits
   under the title; what is fine is a single row of ticks. Overrides the older pill list above. */
.alerts .alerts-head { display: flex; align-items: center; gap: 8px; margin: 0 0 var(--space-2); }
.alerts .control-title { margin: 0; font-size: var(--fs-md); color: var(--text); }
.alerts-pill { padding: 1px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; }
.alerts-pill-bad { background: var(--danger); color: var(--surface); }
.alerts .alert-list { display: grid; gap: var(--space-2); list-style: none; margin: 0; padding: 0; }
.alerts .alert-card { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 12px;
  padding: 12px 14px; border: 1px solid var(--border); border-left: 4px solid var(--warn); border-radius: var(--radius);
  background: var(--surface); }
.alerts .alert-card.light-bad { border-left-color: var(--danger); }
.alerts .alert-icon { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 50%;
  background: var(--warn); color: var(--surface); font-weight: 800; }
.alerts .light-bad .alert-icon { background: var(--danger); }
.alerts .alert-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.alerts .alert-why { color: var(--muted); font-size: var(--fs-sm); }
.alerts .alert-do { white-space: nowrap; }
.alerts .alerts-fine { display: flex; flex-wrap: wrap; gap: 4px 16px; list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.alerts .alerts-fine a { border: 0; padding: 0; background: none; color: var(--ok); font-size: var(--fs-sm); text-decoration: none; }
@media (max-width: 600px) {
  .alerts .alert-card { grid-template-columns: auto 1fr; }
  .alerts .alert-do { grid-column: 2; justify-self: start; }
}
/* The alert's button is a button, not the old pill link the list's earlier rules drew. */
.alerts .alert-card a.alert-do { display: inline-flex; width: auto; align-items: center; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); color: var(--text); padding: 6px 12px; font-weight: 600; }
.alerts .alert-card.light-bad a.alert-do { background: var(--danger); border-color: var(--danger); color: var(--surface); }

/* «¿Cómo nos conoció?» at the till and the campaigns' products on Anuncios (0118). */
.source-pick { margin: 8px 0; }
.source-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.source-chips .btn.is-on { background: var(--accent); color: var(--surface); border-color: var(--accent); }
.ads-cl { padding: 12px 0; border-bottom: 1px solid var(--border); display: grid; gap: 6px; }
.ads-cl-head { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ads-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.ads-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 10px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-dark); font-size: var(--fs-sm); }
.ads-chip-x { border: 0; background: none; cursor: pointer; color: inherit; font-size: 16px; line-height: 1; padding: 0 4px; }
.ads-cl-sold { margin: 0; font-size: var(--fs-sm); }
.ads-cl-search .input { max-width: 320px; }
.ads-cl-results { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }

/* The folded «¿Cómo nos conoció?» (owner, 2026-11-08): a quiet line until tapped. */
.source-pick > summary { cursor: pointer; color: var(--muted); font-size: var(--fs-sm); padding: 4px 0; }
.source-pick > summary strong { color: var(--accent-dark); }
.source-pick[open] > summary { margin-bottom: 4px; }

/* The day's sign-off (0119). */
.ov-sign { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 0 0 12px; }
.ov-sign .input { flex: 1 1 260px; }
.ov-sign-done { color: var(--ok); font-weight: 600; }

/* Prices and promotions (0120). */
.pr-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px 16px; align-items: end; }
.pr-form label { display: flex; flex-direction: column; gap: 4px; }
.pr-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; grid-column: 1 / -1; margin-top: 8px; }
.pr-actions .input { flex: 1 1 240px; }
tr.pr-below td { background: color-mix(in srgb, var(--danger) 8%, transparent); }

/* Temporary items in bulk (2026-11-09): the actions on the right of the head, narrow money fields. */
.temp-bulk-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.temp-bulk-actions { margin-left: auto; display: flex; gap: var(--space-2); flex-wrap: wrap; }
.temp-wh-all { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--fs-sm); color: var(--muted); }
.temp-wh-all .input { width: auto; min-width: 9rem; }
.temp-bulk-table select.temp-wh { min-width: 9rem; }
.temp-bulk-table .input { min-width: 0; width: 100%; }
.temp-bulk-table td:nth-child(2) { min-width: 180px; }
.temp-bulk-table td.num .input { width: 96px; text-align: right; }
.temp-bulk-table td:nth-child(6) .input { min-width: 120px; }

/* Phone polish (owner, 2026-11-09): the dashboard's tabs and quick picks scroll sideways on a phone instead of
   wrapping into two or three rows of buttons. */
@media (max-width: 640px) {
  .ov-tabs, .ov-chips, .period-chips {
    flex-wrap: nowrap; overflow-x: auto; max-width: 100%;
    scrollbar-width: none; -webkit-overflow-scrolling: touch; padding-bottom: 2px;
  }
  .ov-tabs::-webkit-scrollbar, .ov-chips::-webkit-scrollbar, .period-chips::-webkit-scrollbar { display: none; }
  .ov-tabs .btn, .ov-chips .btn, .period-chips .btn { flex: 0 0 auto; }
}

/* The partial return's lines (0124): the product, then how many go back. */
.return-lines { display: grid; gap: var(--space-2); margin: var(--space-2) 0 var(--space-3); }
.return-line { display: grid; grid-template-columns: minmax(0, 1fr) 84px; gap: var(--space-2); align-items: center; }
.return-line .input { width: 100%; text-align: right; }
.temp-dup { color: var(--warn); font-weight: var(--fw-semibold); }

/* The count's lines on a phone (2026-11-11): one card per product instead of a table eight columns wide. */
@media (max-width: 640px) {
  .table.stocktake-table, .stocktake-table tbody { display: block; width: 100%; min-width: 0; }
  .stocktake-table thead { display: none; }
  .stocktake-table tr { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 4px 12px;
                        padding: 10px 12px; border-bottom: 1px solid var(--border); }
  .table.stocktake-table td, .table.stocktake-table td.num { border: 0; padding: 2px 0; text-align: left; min-width: 0; }
  .table.stocktake-table td .input, .table.stocktake-table td select { width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; }
  .stocktake-table td.name { grid-column: 1 / -1; font-weight: 600; }
  .stocktake-table td[data-label]::before { content: attr(data-label) " "; display: block; font-size: 11px;
                                            text-transform: uppercase; opacity: .65; }
}

/* The WhatsApp chatbot's call for a person (0141): above everything, top right, until someone takes it. */
.bot-alert {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 1200;
  max-width: min(440px, calc(100vw - 24px));
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px;
  background: var(--surface);
  border: 2px solid var(--accent);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-toast);
}
.bot-alert-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.bot-alert-text { flex: 1 1 200px; font-weight: var(--fw-semibold); color: var(--text); }

/* A product's photo on its till tile (0143): small, whole, above the name. */
.product-tile .tile-photo {
  display: block;
  width: 100%;
  height: 72px;
  object-fit: contain;
  margin: 2px 0 4px;
  border-radius: var(--radius-sm);
  background: var(--surface);
}

/* Inventario masivo made easy (owner, 2026-11-12): the jobs as large choices, quick filters as chips, the rows
   changed marked, and the save bar always in reach on a long list. */
.stock-modes { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-2);
  margin: 0 0 var(--space-3); }
.stock-mode { display: flex; flex-direction: column; gap: 2px; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: inherit;
  text-decoration: none; }
.stock-mode span { color: var(--muted); font-size: var(--fs-xs); line-height: 1.3; }
.stock-mode.is-on { border-color: var(--accent-strong); background: var(--accent-soft); }
.stock-mode.is-on strong { color: var(--accent-strong); }
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
a.chip { color: inherit; text-decoration: none; }
a.chip.is-on { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent); }
a.chip.is-on .count { color: inherit; }
.suggest-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3);
  background: var(--accent-soft); }
.stock-table .stock-name { font-weight: var(--fw-semibold); }
.stock-table td.num .input { max-width: 110px; margin-left: auto; text-align: right; }
.stock-table tr.row-changed { box-shadow: inset 3px 0 0 var(--accent-strong); background: var(--accent-soft); }
.stock-savebar { position: sticky; bottom: 0; z-index: 2; background: var(--surface);
  border-top: 1px solid var(--border); flex-wrap: wrap; }

/* Temporary items as cards (owner, 2026-11-12: «each line is too long… I have to scroll horizontally»). */
.temp-cards { display: flex; flex-direction: column; }
.temp-card { padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border); }
.temp-card-top { display: flex; align-items: flex-start; gap: var(--space-3); }
.temp-card-pick { padding-top: 8px; }
.temp-card-name { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.temp-card-name .input { width: 100%; font-weight: var(--fw-semibold); }
.temp-card-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: var(--space-2);
  margin: var(--space-2) 0 0 calc(18px + var(--space-3)); }
.temp-field { display: flex; flex-direction: column; gap: 2px; font-size: var(--fs-xs); color: var(--muted); min-width: 0; }
.temp-field .input { width: 100%; min-width: 0; font-size: var(--fs-sm, 14px); color: var(--text, inherit); }
.temp-field .input-num { text-align: right; }
.temp-field-wide { grid-column: span 2; }
@media (max-width: 560px) {
  .temp-card-fields { margin-left: 0; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* «Ventas potenciales» and the IA/Humano switch on the chatbot page (owner, 2026-11-13). */
.lead-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-3); }
.lead-card { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--space-3);
  background: var(--surface); display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lead-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.lead-meta { display: grid; grid-template-columns: 1fr auto; gap: var(--space-2); margin-top: 4px; }
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 2px; }
.segmented form { margin: 0; }
.segmented .btn { border: 0; border-radius: var(--radius-pill); min-width: 72px; }
.segmented .btn[disabled] { opacity: 1; cursor: default; }

/* «Qué comprar» settings (stock tools v2, 2026-11-13). */
.plan-settings { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-2);
  align-items: end; margin-top: var(--space-2); }
.plan-table td .input { min-width: 0; }
.plan-table td .input-num { width: 76px !important; text-align: right; margin-left: auto; }
.plan-table td .plan-supplier { width: 150px !important; }
.plan-table td:nth-child(2) { min-width: 160px; }
