/* Inventory admin UI (Phase-A UI). Ports the artifact Blueprint's `pt-admin` design baseline — straight
   edges, navy/coral, the tabs → panel → table/form structure — scoped under `.pt-admin` so its tokens never
   leak into the rest of the app. The design is provisional (iterating with Cam); kept token-driven so a
   restyle is cheap. Tokens mirror the artifact (light) + a dark override under the app's theme. */
.pt-admin {
    --surface: #ffffff; --surface-2: #eceaf4; --bg: #f4f4fa;
    --ink: #17172b; --muted: #5b5b76; --faint: #8a8aa3; --line: #dcdce8;
    --navy: #000075; --navy-2: #3838a6; --coral: #ec607e; --coral-ink: #c73e5c; --coral-fill: #fbe3ea;
    /* #619: an unselected group tab, in the admin's folder tabs' unselected colour (app.css --text-secondary, light). */
    --tab-idle: #64748b;
    --teal: #137a72; --teal-fill: #dcefed; --teal-line: #9fcfc9; --amber: #9a6a12; --amber-fill: #f4e9d4;
    /* RF1 alert chips. The inks are darker than --coral-ink / --amber: at 11px bold those measure 4.0 and 3.9 on their
       fills, under the 4.5 small text needs. These measure 5.6 (crit) and 5.7 (warn). */
    --crit-ink: #a3304a; --crit-fill: #fbe3ea; --crit-line: #eaa3b3;
    --warn-ink: #7a520c; --warn-fill: #f4e9d4; --warn-line: #dcc28e;
    /* #463b (Cam's mockup) — Discrepancias in navy: the brand's own ink on a lavender fill (15:1; its subtitle, --muted,
       5.3:1). */
    --info-ink: #000075; --info-fill: #e8e8f6; --info-line: #b9b9e0;
    /* #465 v2 (Cam) — the Menús table's header (.mi-thead: --pt-subhead-bg, --text-secondary) and the shade its rows
       take under the pointer (--background-color), as Inventario's own tokens. The app's switch with the OS's scheme
       and Inventario does not, so naming them here would paint a dark band in this light page.
       InventoryCamBatch2ContractTests compares the two in one cascade. */
    --head-fill: #fafbfd; --head-ink: #64748b; --row-shade: #f8fafc;
    /* #528 (Cam) — a row's state is the Menús table's tag (MenuDisplayCard's .mt-status, #411): its green when the row
       is on or done, its slate when it is off. The menu follows the OS's scheme and Inventario does not, so the values
       are copied, not shared (as --head-* above). 6.5:1 and 6.9:1 at 12px/600. */
    --state-on-fill: #dcfce7; --state-on-ink: #166534; --state-off-fill: #f1f5f9; --state-off-ink: #475569;
    /* #593 (Cam: the search and the filter fields "should match across all pages") — the admin's field tokens, at
       app.css's light values (its :root copied, as #503's drawer block below does). app.css draws every admin select
       from them (.app-layout select, which outranks .pt-select), and they follow the OS, which Inventario doesn't: on a
       dark OS its selects were dark boxes on this light page. The fields below and PtStepper read them too, so a search,
       a date, a number and a select are one field: the admin's grey, at its 44px. */
    --pt-field-bg: #ffffff; --pt-field-border: #ced4da; --pt-field-text: #212529; --pt-field-focus: #000075;
    --pt-field-disabled-bg: #e9ecef;
    color: var(--ink);
}
[data-theme="dark"] .pt-admin, .theme-dark .pt-admin {
    --surface: #14141f; --surface-2: #1b1b2a; --bg: #0b0b15;
    --ink: #eceaf6; --muted: #a2a2bd; --faint: #6f6f8c; --line: #2a2a3d;
    --navy: #9a9af0; --navy-2: #b7b7f7; --coral: #f27f97; --coral-ink: #f8a7b8; --coral-fill: #33202a;
    --tab-idle: #cbd5e1;
    --teal: #4fb8ae; --teal-fill: #12312d; --teal-line: #2a6b64; --amber: #d7a24e; --amber-fill: #33280f;
    /* dark: 8.1 (crit) and 6.3 (warn) on their fills */
    --crit-ink: #f8a7b8; --crit-fill: #33202a; --crit-line: #6b3444;
    --warn-ink: #d7a24e; --warn-fill: #33280f; --warn-line: #5e4a1c;
    /* #463b dark: 8.4 on its fill (the subtitle 6.6) */
    --info-ink: #b7b7f7; --info-fill: #1c1c3a; --info-line: #3a3a72;
    /* #465 v2: the Menús table's dark values (app.css's dark palette). */
    --head-fill: #162032; --head-ink: #cbd5e1; --row-shade: #0f172a;
    /* #528: the Menús tag's dark values (MenuDisplayCard.razor.css). */
    --state-on-fill: rgba(34, 197, 94, .16); --state-on-ink: #86efac; --state-off-fill: #334155; --state-off-ink: #cbd5e1;
    /* #593: the fields in this palette's own colours, as they were drawn before. */
    --pt-field-bg: var(--surface); --pt-field-border: var(--line); --pt-field-text: var(--ink);
    --pt-field-focus: var(--navy); --pt-field-disabled-bg: var(--surface-2);
}
/* #503 — an Inventario drawer stays on Inventario's light surface on a dark OS. Its panel paints the APP's tokens, which
   follow the OS, and everything in it paints Inventario's, which don't: dark text on a dark panel, 1.2:1 on an input's name
   and 2.2:1 on a label (measured). So inside the drawer the app's tokens keep their light values, app.css's :root copied, as
   --head-* above copy the Menús table's; InventoryDrawerDarkOsContractTests compares the two. A light OS is untouched. */
@media (prefers-color-scheme: dark) {
    .menu-item-panel.pt-inventory-drawer {
        --modal-bg: #ffffff; --modal-border: #d1d5db; --modal-footer-bg: #f9fafb; --modal-header-bg: #f9fafb;
        --modal-input-bg: #ffffff; --modal-input-border: #9ca3af; --modal-text: #000000; --modal-text-secondary: #374151;
        --surface-color: #ffffff; --background-color: #f8fafc; --border-color: #e2e8f0;
        --text-primary: #1e293b; --text-secondary: #64748b; --text-muted: #94a3b8; --text-black: #000000;
        --pt-field-bg: #ffffff; --pt-field-border: #ced4da; --pt-field-disabled-bg: #e9ecef; --pt-field-text: #212529;
        --pt-field-focus: #000075; --pt-field-invalid-text: #b91c1c; --pt-navy-fg: #000075;
        --pt-validation-summary-bg: #fef2f2; --pt-validation-summary-text: #212529;
    }
}


/* View header — a white container with a coral (red) bottom-underline holding the nav icon + name,
   to match the other admin views (Menús / Pedidos / Cuentas). */
/* #462 (Cam) — the Recent Actions button stands at the header's end, as on Pedidos and Cuentas (space-between; its
   popup and backdrop are fixed, so they take no part in the row). */
.pt-admin .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-bottom: 3px solid var(--coral);
    padding: 12px 16px;
    margin-bottom: 16px;
}
.pt-admin .header h3 {
    margin: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--pt-font-heading, "Poppins", system-ui, sans-serif);
    font-weight: 700;
    font-size: 1.25rem;
    color: var(--ink);
}
/* #602 (Cam: "Update the icon color in the Inventory page header to navy so it matches the icon color on the other pages.
   The size/line weight … feels a bit thin") — the other pages draw theirs in navy at their title's 1.5rem. Inventario's
   own navy, which never darkens, as the page doesn't (--pt-navy-fg turns light indigo on a dark OS). Bootstrap's cube is a
   hairline beside the PayTable glyphs of Pedidos and Menú; a 0.8px stroke in its own colour gives it their weight
   (compared side by side at 3×), and it stays the outline the side menu shows. */
.pt-admin .header h3 > .bi { color: var(--navy); font-size: 1.5rem; line-height: 1; -webkit-text-stroke: .8px currentColor; }

/* tabs */
/* #443 (Cam) — Inventario's tabs, on the shared strip (PtTabbar; its mechanics are app.css's .pt-tabbar since #461 gave
   the Menu Manager the same strip): ONE row that scrolls sideways, a chevron at an edge that has more tabs, the tabs
   fading under it. What stays here is Inventario's LOOK, in its own tokens: the tab, the chevrons dressed as unselected
   tabs, and the selected tab merging into the panel. The chevron rules are scoped to .pt-admin, where those tokens
   live: this sheet loads on every page, and unscoped they would reach the Menu Manager's strip with --line and --bg
   undefined. */
.pt-tabs { gap: 6px; }
.pt-tab { display: flex; flex: 0 0 auto; align-items: center; gap: 7px; padding: 10px 15px; border: 2px solid var(--line);
    border-bottom: none; border-radius: 0; background: var(--bg); color: var(--muted); font-weight: 600;
    font-size: 14px; white-space: nowrap; cursor: pointer; position: relative; }
/* #443 (Cam, 29 Sep: "Update the design of the chevron buttons to look like this") — the chevrons as the mockup draws them:
   a white box with the tabs' border and a dark navy chevron, apart from the tabs, which fade under it. Hovered, its border
   darkens to the ink a pressed tab takes. */
.pt-admin .pt-tabbar-arrow { border-color: var(--line); background: var(--surface); color: var(--navy); }
.pt-admin .pt-tabbar-arrow:hover { border-color: var(--navy); background: var(--surface); color: var(--navy); }
.pt-tab:hover { background: var(--surface); color: var(--ink); }
.pt-tab.on { background: var(--surface); border-color: var(--navy); color: var(--navy-2); z-index: 3; }
.pt-tab.on::after { content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 2px; background: var(--surface); }
/* #619 (Cam) — two levels, on Inventario and Reportes. The groups are these folder tabs, wider and taller than a screen's
   used to be (the mockup's three fill the row on a desktop), still opening into the panel. Inside the panel, the group's
   screens are a text row: muted, the selected one ink and bold over a 2px coral underline, a hairline under the row. It is
   a PtTabbar too (Class pt-subtabbar), so it keeps the strip's scroll and chevrons, but not its folder merge: it sits in
   the panel, not on its edge. */
/* Cam, 6 Nov: "Ensure the height of the top level tabs are the same on all pages" and their "non-selected ... font colors
   match": the groups take the admin's folder tabs' metrics (app.css .pt-folder-tab, the Menu Manager's and Mesas': a 1rem
   label, a 1.1rem glyph, 0.75rem above and below), so their height is the folder tabs', and their unselected colour.
   That colour is a token here: Inventario's page stays light in a dark OS, where the app's own token would go dark. */
.pt-tab--group { justify-content: center; min-width: 200px; padding: 0.75rem 28px; font-size: 1rem; color: var(--tab-idle); }
.pt-tab--group > .bi { color: var(--navy); font-size: 1.1rem; }
.pt-admin .pt-subtabbar { margin: 0 0 18px; }
/* Cam, 6 Nov: the screens "just slightly" further apart (26px before), and the selected one in the selected group's
   colour (.pt-tab.on's). */
.pt-admin .pt-subtabs { gap: 32px; padding-bottom: 0; border-bottom: 1px solid var(--line); }
.pt-subtab { display: inline-flex; align-items: center; gap: 6px; padding: 10px 2px 12px; border: none; background: none;
    color: var(--muted); font-size: 14px; font-weight: 500; text-decoration: none; cursor: pointer; position: relative; }
.pt-subtab:hover { color: var(--ink); }
.pt-subtab.on { color: var(--navy-2); font-weight: 700; }
.pt-subtab.on::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--coral); }
/* Análisis in Inventario's row is a link to Reportes, not a tab: at the row's end, in the link colour, with the arrow out. */
.pt-subtab--link { margin-left: auto; color: var(--navy-2); }
.pt-subtab--link:hover { color: var(--navy); text-decoration: underline; }
/* The chevrons of a text row: no box to look like a tab, the panel's own fill. */
.pt-admin .pt-subtabbar .pt-tabbar-arrow { border: none; bottom: 1px; background: var(--surface); }

/* panel */
/* #423 — position: relative but NO z-index. A z-index here made the panel a stacking context, and the edit drawers
   (position: fixed, z-index 100) mount inside it — so their z-index only counted within the panel, and the tab strip
   (z-index 2) painted over the open drawer and its scrim (measured in Chromium). A tab then stayed clickable, and
   switching tabs unmounts the component that owns the drawer, silently discarding the edit. The active-tab merge
   still works without it: .pt-tabbar keeps its own z-index 2 context, which paints above this unstacked panel. */
/* #463 — the padding is a token (--pt-panel-pad), so a table that bleeds through it (.pt-bleed) can't drift from it. */
.pt-panel { --pt-panel-pad: 20px; border: 2px solid var(--line); border-radius: 0; background: var(--surface);
    padding: var(--pt-panel-pad); position: relative; }
.pt-screentitle { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
    flex-wrap: wrap; margin-bottom: 16px; }
.pt-screentitle h4 { font-weight: 600; font-size: 17px; color: var(--ink); margin: 0; }
.pt-screentitle .sub { color: var(--muted); font-size: 13px; display: block; margin-top: 2px; }
/* #491 + #496 (Cam) — instructions and notes that always show live in the header's description, a line each, instead of
   blue banners between the controls. */
.pt-screentitle .sub .sub-line { display: block; margin-top: 4px; }
/* #465 (Cam) — a screen's actions stand at the top right of its title row ("Move the 'Registrar produccion' button to
   the top right to save vertical space"). The row wraps, and the text block had no basis, so a long description
   (Producción's runs 1,300px on one line) claimed the whole row and pushed the actions under it. The text now starts
   from 20rem and takes whatever the actions leave. The row wraps only when even that won't fit beside them (a phone),
   and then the actions go under a readable description rather than squeezing it. The actions keep their default
   shrink: on a phone, two buttons wider than the row wrap inside .pt-actions instead of running off the panel. Every
   inventory screen shares this (InventoryCamBatch2ContractTests). */
.pt-screentitle > div:not(.pt-actions) { flex: 1 1 20rem; min-width: 0; }
/* #465 v2 (Cam: "Move the button down a few pixels so its top edge aligns with the top of the Produccion header text").
   The row aligns its items' tops, and a title's box starts above its letters: the 17px Montserrat capitals begin 4px
   under the line's top. So the actions drop by that much. Measured on the title's letters, in the font the admin ships
   (InventoryCamBatch2ContractTests). */
.pt-screentitle > .pt-actions { margin-top: 4px; }
/* #447 — a row's items share a centre line, so text beside a 44px button (Recibir's total) sits level with it instead
   of at its top. */
.pt-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* #447 — an action row under a table, or under another action row, gets air; they used to touch. */
.pt-tablewrap + .pt-actions, .pt-actions + .pt-actions { margin-top: 14px; }
/* #486 + #491 (Cam) — and the other way round: a table, a form or an empty note under an action row (Proveedores'
   «Agregar», Conteos' «← Volver») gets the same air. */
.pt-actions + .pt-tablewrap, .pt-actions + .pt-form, .pt-actions + .pt-empty { margin-top: 14px; }
/* #513 (Cam) — a receipt's comprobantes, in the receipt's drawer and under Recibir's posted receipt: «Comprobantes»
   clear of the document's buttons above it, and «Adjuntar» clear of the line above it ("Sin comprobantes…", a
   picture's «Cerrar»; a listed file's table already gets it). Its own class: a field under an action row elsewhere
   keeps its place. */
.pt-actions + .pt-receipt-docs, .pt-receipt-docs > .pt-actions { margin-top: 14px; }
/* #447 — a running total reads at the row's far end, under the table's last column. */
.pt-total { margin-left: auto; font-weight: 600; color: var(--ink); }
/* #448 — a checkbox after a button is a separate control, not part of the button group: more than the group's gap. */
.pt-actions > .pt-btn + .pt-check { margin-left: 16px; }
/* #494 (Cam) — and a checkbox before a button (Conteos' «Incluir enviadas» then «Crear»), the same distance. */
.pt-actions > .pt-check + .pt-btn { margin-left: 16px; }

/* buttons */
/* #422 — line-height is set explicitly. Inherited (1.5 × 13px = 19.5) it made every button 41.5px, which made
   every table row 60.5px, which put alternate rows on a half pixel so the status pills rasterized at different
   sizes row to row. 44px is also the touch-target minimum in the design rules; these screens run on tablets. */
.pt-btn { font-weight: 600; font-size: 13px; line-height: 20px; min-height: 44px; border-radius: 0; padding: 9px 14px;
    border: 2px solid var(--navy); background: var(--navy); color: #fff; cursor: pointer; display: inline-flex;
    align-items: center; gap: 7px; white-space: nowrap; }
.pt-btn:disabled { opacity: .55; cursor: not-allowed; }
.pt-btn.coral { background: var(--coral); border-color: var(--coral); }
.pt-btn.ghost { background: transparent; color: var(--navy-2); border-color: var(--line); }
.pt-btn.danger { background: transparent; color: var(--coral-ink); border-color: var(--coral); }
/* #601 — a button that is only its glyph, a 44px square (its words go in aria-label and title). */
.pt-btn.pt-iconbtn { width: 44px; padding: 0; justify-content: center; }

/* table */
.pt-tablewrap { overflow-x: auto; border: 1px solid var(--line); position: relative; }
/* #601 (Cam: "Table rows should fit within the drawer without needing to show a horizontal scrollbar", by changing Quitar to
   an 'X', removing "the container around the table" and the +/- of Cantidad and Precio sin IVA) — «Nueva orden de
   compra»'s lines in the drawer's 828px. Artículo takes the room the three free, so a name reads whole. The wrapper
   keeps scrolling: under 768px the drawer is the screen, and the row can't fit a phone. */
.pt-tablewrap.pt-po-formlines { border: none; }
/* Measured at 828px: the row took 914px of the body's 780, 13px of padding on each side of six cells among it. 6px keeps
   the fields apart. Presentación's options are units ("caja (20 kg)"), never «— Selecciona —», which is what the
   tables' 176px floor is for (#446). Cantidad and Precio keep room for 12345.50 at a fixed 6rem: left to the table,
   Cantidad gives way as Importe grows and clips it. Artículo takes the rest (258px, from 176), down to 200px so an
   eight-figure Importe still fits where the monospace is 0.6em wide. The admin never loads JetBrains Mono, so .pt-mono
   is Consolas on Windows and about 0.6em on macOS, Android and Linux, 10px more over a seven-figure amount: at 220px
   that row overflowed CI's Linux runner by 2px. */
.pt-po-formlines .pt-table th, .pt-po-formlines .pt-table td { padding-left: 6px; padding-right: 6px; }
.pt-po-formlines .pt-table .pt-select { min-width: 140px; }
.pt-po-formlines .pt-table .pt-select.pt-po-item { min-width: 200px; }
.pt-po-formlines .pt-po-num { width: 6rem; }
.pt-po-notes { margin-top: 20px; }
.pt-table { width: 100%; border-collapse: collapse; font-size: 13.5px; min-width: 560px; }
/* #476 — a concept and its amount (Formas de pago's reconciliation): two columns fit a phone, so the pair drops the
   tables' minimum and keeps the amount on one line; the concept wraps. */
.pt-table.pt-pair { min-width: 0; }
.pt-table.pt-pair td.pt-mono { white-space: nowrap; }
/* #476 — a wide report (Ventas con descuento: ten columns): its figures stay on one line; the text around them wraps. */
.pt-table.pt-amounts td.pt-mono { white-space: nowrap; }
.pt-table th { background: var(--navy); color: #fff; text-align: left; padding: 10px 13px; font-size: 12px;
    font-weight: 600; white-space: nowrap; }
/* #422 — rows must be a whole number of pixels at every display scale, or the same pill lands on a different
   sub-pixel on alternate rows and visibly changes size. Two things do that together: an integral line box, and a
   row separator drawn as an inset shadow instead of a border. A collapsed 1px border is split half-and-half
   between neighbouring rows, which reintroduces the half pixel even when everything else is whole. Rows come out
   at 64px (with a 44px button) and 40px (text only) — both multiples of 4, so they stay on whole device pixels at
   100%, 125%, 150% and 200% scaling (measured in Chromium at each). */
.pt-table td { padding: 10px 13px; line-height: 20px; border-top: 0; box-shadow: inset 0 1px 0 var(--line);
    vertical-align: middle; color: var(--ink); }
.pt-table tr:nth-child(even) td { background: var(--surface-2); }
.pt-table .nm { font-weight: 600; color: var(--ink); }
/* A buttons cell stays a table cell (Igor, 13 Oct: "the row shows a color error on the secondary color rows").
   .pt-actions is the flex row for buttons under a table, and a td made flex stops being a cell: it was only as tall
   as its buttons, so wherever another cell was taller (Recibir's "por base", a name on two lines) the table's white
   showed under it in a striped row, and its buttons sat at the top of a tall row. In a cell the buttons sit inline:
   8px apart, and on one line, since a wrapped pair would touch (the table scrolls sideways instead, as a figure does,
   #463). A flex row ignored the whitespace Razor leaves between two buttons on separate lines (the categories'
   Guardar and Cancelar); inline it is a space, so the cell's own text is sized to nothing (a .pt-btn sets its own).
   Pinned by InventoryTableCellContractTests. */
td.pt-actions { display: table-cell; white-space: nowrap; font-size: 0; }
td.pt-actions > :not(:last-child) { margin-right: 8px; }

/* #464 (Cam) — an editable row keeps its controls on one line. A caption under a control (Recibir's "por {unidad}",
   a presentation's conversion in Recibir and Conteos, a pill or a problem under a Producción input's name) made its
   cell taller, and every other cell re-centred against it: the controls stepped by half a caption (11px, measured).
   In a .pt-edit table the cells align to the TOP, so each caption hangs under its own control, and a cell holding
   text or a button is padded to centre on the controls' line. The numbers are the measured geometry, pinned by
   InventoryCamBatch2ContractTests: a control is the admin's 44px field (#593; its 2px border, 9px padding and 20px line
   make 42, and inside the admin's layout a select and a PtStepper were 44 already) and a line of text 20px, so
   10px + (44 − 20) / 2 = 22px; a .pt-btn is 44px, so 10px + (44 − 44) / 2 = 10px. An amount or a unit is one
   token ("$960 MXN"): squeezed by three selects at 176px, it broke across two lines and lost the controls' line, so it
   does not wrap (the table wrapper scrolls instead). A name may wrap; its first line is the one on the controls' line. */
.pt-table.pt-edit td { vertical-align: top; }
.pt-table.pt-edit td.pt-mono, .pt-table.pt-edit td.nm, .pt-table.pt-edit td.pt-text { padding-top: 22px; }
.pt-table.pt-edit td.pt-mono { white-space: nowrap; }
.pt-table.pt-edit td.pt-actions { vertical-align: top; padding-top: 10px; }

/* #465 (Cam) — the tree table, the Menús tree's grammar for a list whose rows open ("Follow the new table UX/nesting
   with the left side chevron expand/collapse row"); Producción's history first. The header is a light band of small
   capitals (the tree's: 11px, 700, 0.08em) instead of the navy bar. There is no zebra: a detail row between two runs
   would put any stripe out of step. A row opens from its chevron (its own 44px column, the admin's touch width), from
   anywhere on it, or from its "N insumos" link, and stays tinted while open. The tint is lighter than the editing
   row's (#423): a gain's teal measured 4.37:1 on 0.08, under AA, and 4.64 on 0.05. The chevron turns a quarter, as the
   tree's does. A text row stays 40px (#422): the chevron fills its cell at 40px, and the link is text. Pinned by
   InventoryCamBatch2ContractTests. #463 shares the header and the plain rows with a table whose rows don't open
   (.pt-flat): Existencias', as Cam's mockup draws them. */
/* line-height is set: inherited (1.5 × 11px) it made the header 36.5px and put every row under it on a half pixel. */
/* #465 v2 (Cam: "the same header row and selected row colors that are used in the Menus Manager table") — the Menús
   table's header fill and ink. #64748b reads 4.6:1 on #fafbfd. */
.pt-table.pt-tree th, .pt-table.pt-flat th { background: var(--head-fill); color: var(--head-ink); font-size: 11px; line-height: 16px;
    font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.pt-table.pt-tree tr:nth-child(even) td, .pt-table.pt-flat tr:nth-child(even) td { background: var(--surface); }
/* #463 — in a flat table a figure never breaks across lines. Rendered, "120 bot (≈ 5 caja)" split after the 5, and a
   phone split "115.25 pza" from its unit. The column widens and the table scrolls sideways instead; text (a name,
   "Afecta a", a note under a figure) still wraps. */
.pt-table.pt-flat .pt-mono { white-space: nowrap; }
/* #497 (Cam: "Avoid wrapping cell values, instead give the table columns breathing room and add horizontal overflow
   scroll") — Análisis' tables: no cell wraps, a name and its pills included; the table scrolls sideways in its wrap. */
.pt-table.pt-nowrap th, .pt-table.pt-nowrap td { white-space: nowrap; }
/* #497 — Variación beyond the branch's count tolerance (Discrepancias' own setting): over it in the amber ink, over
   twice it in the red one; Merma, a loss, in the red one. The figure stays; a title says why. */
.pt-table td.pt-var-warn { color: var(--warn-ink); font-weight: 600; }
.pt-table td.pt-var-crit { color: var(--crit-ink); font-weight: 600; }
/* #490 (Cam) — a header that sorts its table (PtSortHeader). Its text is a button; a pseudo-element stretched over the
   cell makes the whole header the target, and the cell is 44px tall, the admin's touch target, a whole pixel (#422)
   whatever the header's line height. The chevron shows only on the column the table is sorted by. */
.pt-table th.pt-th-sort { position: relative; height: 44px; padding-top: 0; padding-bottom: 0; vertical-align: middle; }
.pt-th-sortbtn { display: inline-flex; align-items: center; gap: 4px; margin: 0; padding: 0; border: 0; background: none;
    color: inherit; font: inherit; letter-spacing: inherit; text-transform: inherit; white-space: nowrap; cursor: pointer; }
.pt-th-sortbtn::after { content: ""; position: absolute; inset: 0; }
.pt-th-sortbtn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.pt-th-chev { font-size: 10px; line-height: 1; }
.pt-table.pt-tree > tbody > tr:not(.pt-tree-detail) { cursor: pointer; }
/* #465 v2 — a Menús row's shade under the pointer (Menús tints no open row): here a row takes it under the pointer and
   keeps it while open. A loss's red reads 6.52:1 on it and a gain's teal 4.95:1. */
.pt-table.pt-tree > tbody > tr:not(.pt-tree-detail):hover > td,
.pt-table.pt-tree > tbody > tr.is-open > td { background: var(--row-shade); }
.pt-table.pt-tree .pt-chev { width: 44px; padding: 0; }
.pt-chevbtn { display: flex; align-items: center; justify-content: center; width: 100%; height: 40px; padding: 0;
    border: 0; background: none; color: var(--ink); font-size: 14px; cursor: pointer; }
.pt-chevbtn .bi { display: inline-block; transition: transform .2s ease; }
.pt-chevbtn[aria-expanded="true"] .bi { transform: rotate(90deg); }
.pt-chevbtn:focus-visible { outline: 3px solid var(--coral); outline-offset: -3px; }
@media (prefers-reduced-motion: reduce) { .pt-chevbtn .bi { transition: none; } }
.pt-link { padding: 0; border: 0; background: none; font: inherit; font-weight: 600; color: var(--navy-2); cursor: pointer; }
.pt-link:hover, .pt-link:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
/* Diferencia: a loss (short) in the alert chips' darker red, 6.12:1 on the open row's tint, where --coral-ink measured
   4.41:1, under AA; a gain (over) in teal. */
.pt-table td.pt-var-loss { color: var(--crit-ink); }
.pt-table td.pt-var-gain { color: var(--teal); }
/* An open row's detail: Cam's "slight variation" for the nested table. It starts under the first column's text (the
   chevron's 44px plus a cell's 13px), behind a 2px rule drawn down from the chevron, and has no frame of its own. */
/* #465 v2 (Cam: "The vertical line … should not touch the row border above it"; "Increase the margin above the nested
   table and to the right of the nested table just a bit") — 14px above the table and 24px at its right, as his mockup
   leaves; the rule runs alongside the table only, with the same 14px clear of the border above as of the foot. */
.pt-table.pt-tree > tbody > tr.pt-tree-detail > td { position: relative; padding: 14px 24px 14px 57px; cursor: default; }
.pt-table.pt-tree > tbody > tr.pt-tree-detail > td::before { content: ""; position: absolute; left: 21px; top: 14px;
    bottom: 14px; width: 2px; background: var(--line); }
.pt-subtable { width: 100%; border-collapse: collapse; }
/* #504 (Cam: "Use the nested table UX we have on the Produccion tab"; "Add category groups") — Recetas in this tree. A
   category's row heads its dishes: its name in the ink, on the second surface, never a toggle and never shaded. */
.pt-table.pt-tree > tbody > tr.pt-tree-group { cursor: default; }
.pt-table.pt-tree > tbody > tr.pt-tree-group > th { background: var(--surface-2); color: var(--ink); font-size: 12px;
    font-weight: 700; letter-spacing: .02em; text-transform: none; padding: 8px 13px; }
/* #504 (Cam: "Remove the blue message banners … display static messages as normal text") — what an open recipe says
   about itself: plain text in the muted ink, no box. */
.pt-recipe-note { margin: 0 0 10px; font-size: 13px; color: var(--muted); line-height: 1.55; }
/* The recipe's buttons share one right edge: Quitar, Añadir and the foot's Guardar. */
.pt-recipe-lines td.pt-actions { text-align: right; padding-right: 0; }
/* #503 — in a drawer the table has no .pt-tablewrap, and a drawer's body never scrolls sideways: on a phone a long input
   pushed Quitar past its edge, out of reach. It scrolls on its own, as it did in the table. */
.pt-recipe-tablewrap { overflow-x: auto; }
/* #576 (Cam: "Increase space between rows"; "Add the ability to edit a row"; "Ideally the input fields fit within their
   respective columns (I think we should remove the +/- buttons for Cantidad in this view to conserve space)") — the
   drawer's recipe, .pt-recipe-edit. In Recetas' open row this table borrows .pt-table's cells. In the drawer it had the
   browser's 1px, and its rows touched. Here each row has the tables' 10px above and below its tallest field, and the
   tables' rule between rows. Every line edits in place, its fields in their columns as Recibir's are (#464), and the
   add row is the same fields under the same headers (it was one wrapping strip, #504). The ingredient's column takes
   what's left; the others are as wide as their fields (measured at the drawer's 828px, an Editar and a Quitar on every
   row made the table 148px too wide). The first column's text starts where the notes over it and the cost under it do.
   A number field shows no arrows: the − and + went to make room, and the browser's arrows are the same buttons, smaller.
   Pinned by InventoryRecipeDrawerContractTests. */
.pt-recipe-edit th, .pt-recipe-edit td { padding: 10px 8px; }
/* A text row (a group's recipe) is the tables' 40px (#422). */
.pt-recipe-edit td { line-height: 20px; box-shadow: inset 0 1px 0 var(--line); }
.pt-recipe-edit th:first-child { width: 100%; }
.pt-recipe-edit th:first-child, .pt-recipe-edit td:first-child { padding-left: 0; }
.pt-recipe-edit td.pt-mono { white-space: nowrap; }
.pt-recipe-edit td.nm .pt-pill { margin-left: 6px; }
.pt-recipe-edit .pt-recipe-qty { width: 5.5rem; }
.pt-recipe-edit .pt-recipe-unit { width: 6.5rem; }
.pt-recipe-edit .pt-recipe-dir { width: 7rem; }
/* #601: the purchase order's Cantidad and Precio, bare fields since Cam's #601, have no browser arrows either. */
.pt-recipe-edit input[type="number"], .pt-po-formlines input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.pt-recipe-edit input[type="number"]::-webkit-outer-spin-button,
.pt-recipe-edit input[type="number"]::-webkit-inner-spin-button,
.pt-po-formlines input[type="number"]::-webkit-outer-spin-button,
.pt-po-formlines input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* A quantity Guardar refused (its line is named over the drawer). */
.pt-recipe-edit .pt-input[aria-invalid="true"] { border-color: var(--crit-ink); }
/* The add row's refusal hangs under it, with no rule between them, across the table to Añadir's edge. */
.pt-recipe-edit tr.pt-recipe-adderror > td { box-shadow: none; padding-top: 0; padding-right: 0; }
/* As a flat table's (#463): a figure in the list never breaks («$4» over «MXN»); a name still wraps. */
.pt-table.pt-recipes > tbody > tr > td.pt-mono { white-space: nowrap; }
/* A row stays the tree's 40px (#422) with a 22px pill in it: centred on its 20px line, the pill made the line 22.5px and
   every row 42.5, a half pixel. At the line's top, a pixel above and below it, it is centred in the row on whole pixels. */
.pt-table.pt-recipes > tbody > tr > td > .pt-pill { vertical-align: top; margin: -1px 0; }
/* #504 (Cam: "Put the cost info at the bottom aligned to the left side, across from the Guardar/Elimina buttons") */
/* #599 — the recipe's «Notas», under its lines: room above it, as between a form's groups, and the group's notes at a
   branch as text that keeps its line breaks, at the box's 14px (so «Usar una receta propia» doesn't resize them), without
   the 1rem Bootstrap puts under a paragraph. */
.pt-recipe-notes { margin-top: 20px; }
.pt-recipe-notes-text { margin: 0; font-size: 14px; white-space: pre-line; overflow-wrap: anywhere; }
.pt-recipe-foot { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px 20px;
    margin-top: 14px; }
.pt-recipe-cost { flex: 1 1 320px; font-size: 13px; color: var(--muted); line-height: 1.55; }
.pt-recipe-cost b { color: var(--ink); }
.pt-recipe-foot .pt-actions { margin-left: auto; }

/* #463 (Cam) — "stock table uses full content area width": the table bleeds through the panel's padding to its borders.
   The first and last columns take the padding back as their own, so the first column's text keeps the page's left
   edge, the filters' and the title's. Pinned by InventoryStockContractTests.
   .pt-tablewrap is positioned (above) so that it, not the panel, holds what a header positions absolutely: the ⋮
   column's visually-hidden "Acciones" escaped the scroll box and made a phone's page 1,042px wide. */
.pt-tablewrap.pt-bleed { margin-left: calc(-1 * var(--pt-panel-pad, 0px)); margin-right: calc(-1 * var(--pt-panel-pad, 0px));
    border-left: 0; border-right: 0; }
.pt-bleed .pt-table th:first-child, .pt-bleed .pt-table td:first-child { padding-left: var(--pt-panel-pad, 13px); }
.pt-bleed .pt-table th:last-child, .pt-bleed .pt-table td:last-child { padding-right: var(--pt-panel-pad, 13px); }
/* #463 — a row's ⋮ (PtOverflowMenu). Its cell has no padding but a little at the end, so the 44px trigger, the admin's
   touch target, sets the row's height: every row is 44px, a whole pixel at every scale (#422). */
.pt-table td.pt-rowmenu, .pt-bleed .pt-table td.pt-rowmenu:last-child { width: 52px; padding: 0 8px 0 0; }
/* #463 — PtOverflowMenu draws in the app's tokens, which follow the OS theme. Inventario's follow [data-theme], which
   nothing in the admin sets, so Inventario stays light: in a dark OS the ⋮ went near-white on a white row (measured,
   #f1f5f9 on #fff) and its menu opened dark on a light page. Inside .pt-admin the tokens it reads are Inventario's. */
.pt-admin .pt-om { --text-primary: var(--ink); --text-secondary: var(--muted); --surface-color: var(--surface);
    --background-color: var(--bg); --border-color: var(--line); }
/* #492 - PtStepper's buttons in Inventario: the field's own 2px line and colours, from the field tokens .pt-admin holds
   (#593; Inventario never darkens, and the app's values would follow the OS theme, as the overflow menu's did above). */
.pt-admin .pt-stepper { --pt-stepper-line: var(--pt-field-border); --pt-stepper-inner: var(--line);
    --pt-stepper-fill: var(--pt-field-bg); --pt-stepper-fill-off: var(--pt-field-bg); --pt-stepper-ink: var(--pt-field-text);
    --pt-stepper-focus: var(--pt-field-focus); }
/* #500 (Cam) — a classification toggle («Consumible», ToggleField Kind="Classification") in Inventario's tokens, for the
   ⋮'s reason: the app's would follow a dark OS into this light page. The off grey is --faint, 3.4:1 on the light
   surface and 3.8:1 on the dark one; the blue is Cam's, and the dark theme's is the app's dark one. */
.pt-admin .pt-toggle-field.pt-toggle-field--classification { --ptf-c-line: var(--line); --ptf-c-ink: var(--ink);
    --ptf-c-muted: var(--muted); --ptf-c-off: var(--faint); --ptf-c-accent: #0000ee; }
/* #533/#534 (Cam) — a status toggle (Avisos por correo, Agotado automático) keeps its light look in Inventario, for the
   same reason: app.css darkens it with a dark OS, which this light page does not follow. Its help line reads
   Inventario's --muted, not the app's. Under the dark theme it takes the app's dark look again. */
@media (prefers-color-scheme: dark) {
    .pt-admin .pt-toggle-field--on:not(.pt-toggle-field--classification) { background-color: #d1fae5; }
    .pt-admin .pt-toggle-field--off:not(.pt-toggle-field--classification) { background-color: #fee2e2; }
    .pt-admin .pt-toggle-field--on:not(.pt-toggle-field--classification) .pt-toggle-field__status i { color: #059669; }
    .pt-admin .pt-toggle-field--off:not(.pt-toggle-field--classification) .pt-toggle-field__status i { color: #dc2626; }
    .pt-admin .pt-toggle-field--on:not(.pt-toggle-field--classification) .pt-toggle-field__label { color: #065f46; }
    .pt-admin .pt-toggle-field--off:not(.pt-toggle-field--classification) .pt-toggle-field__label { color: #991b1b; }
    [data-theme="dark"] .pt-admin .pt-toggle-field--on:not(.pt-toggle-field--classification) { background-color: #064e3b; }
    [data-theme="dark"] .pt-admin .pt-toggle-field--off:not(.pt-toggle-field--classification) { background-color: #450a0a; }
    [data-theme="dark"] .pt-admin .pt-toggle-field--on:not(.pt-toggle-field--classification) .pt-toggle-field__status i { color: #34d399; }
    [data-theme="dark"] .pt-admin .pt-toggle-field--off:not(.pt-toggle-field--classification) .pt-toggle-field__status i { color: #f87171; }
    [data-theme="dark"] .pt-admin .pt-toggle-field--on:not(.pt-toggle-field--classification) .pt-toggle-field__label { color: #a7f3d0; }
    [data-theme="dark"] .pt-admin .pt-toggle-field--off:not(.pt-toggle-field--classification) .pt-toggle-field__label { color: #fecaca; }
}
.pt-admin .pt-toggle-field__help { color: var(--muted); }
[data-theme="dark"] .pt-admin .pt-toggle-field.pt-toggle-field--classification,
.theme-dark .pt-admin .pt-toggle-field.pt-toggle-field--classification { --ptf-c-accent: #8080ff; }
/* #606 (Cam: "Increase the space between the active toggle subtext and fields below") — a drawer opens with its on/off
   (#571), the help 6px under the box, and then what follows starts a row's distance below: the 28px between two rows
   of fields (.pt-field's 14px and .pt-grid2's gap). Artículos, Áreas, Proveedores, Unidades, Recibir's IEPS; not at
   the form's end, where it would only pad the drawer. A toggle inside a grid already has the grid's rhythm. */
.pt-admin .pt-drawer-form > .pt-toggle-field__help:not(:last-child) { margin-bottom: 28px; }
/* #605 (Cam: "introduce section headers like we do in the menu item details drawer") — the dish drawer's header
   (app.css .section-header: 1.1rem, bold, its icon navy), with the 2px rule under the row that app.css draws for
   .edit-item-grid only, in Inventario's ink and line. A section ends with a grid whose last field keeps its 14px, so
   10px more gives the dish drawer's 24px between sections; the first starts at the top of its form. */
.pt-admin .pt-drawer-form .section-header-row { margin: 10px 0 16px; padding-bottom: 12px; border-bottom: 2px solid var(--line); }
.pt-admin .pt-drawer-form .section-header-row:first-child { margin-top: 0; }
.pt-admin .pt-drawer-form .section-header { color: var(--ink); }
.pt-admin .pt-drawer-form .section-header i { color: var(--navy); }

/* #423 — the row whose editor is open. The drawer is capped at 51.75rem, so the start of the row stays visible
   beside it; this says which item the form belongs to. The menu editor's is-editing treatment (app.css): a navy
   4px leading stripe and a navy tint — navy, not coral, which reads as a warning (Igor, 7 Sep). On the CELLS, not
   the <tr>: the zebra rule above paints td backgrounds, which would cover a row background. The first cell restates
   the row separator, because a second box-shadow replaces the first rather than adding to it. */
.pt-table tbody tr.is-editing td { background: rgba(0, 0, 117, 0.08); }
.pt-table tbody tr.is-editing td:first-child { box-shadow: inset 4px 0 0 var(--navy), inset 0 1px 0 var(--line); }
.pt-table tbody tr.is-editing .nm { color: var(--navy); }
[data-theme="dark"] .pt-admin .pt-table tbody tr.is-editing td,
.theme-dark .pt-admin .pt-table tbody tr.is-editing td { background: rgba(148, 148, 255, 0.16); }
[data-theme="dark"] .pt-admin .pt-table tbody tr.is-editing .nm,
.theme-dark .pt-admin .pt-table tbody tr.is-editing .nm { color: var(--navy-2); }
.pt-mono { font-family: "JetBrains Mono", ui-monospace, monospace; font-variant-numeric: tabular-nums; }

/* pills */
/* #422 — a fixed 22px box rather than one derived from line-height (it was 22.5px), with the admin's status-chip
   anatomy: fill + ink + a 1px border in the chip's own tone (cf. --pt-status-*-border in app.css). The border is
   what keeps a chip's SHAPE visible on any row background. The muted chip used to be painted var(--surface-2) —
   the exact colour of the zebra stripe — so an "Inactivo" tag was a chip on white rows and bare text on striped
   ones. It now takes the base surface, which differs from the stripe in both themes. */
.pt-pill { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; height: 22px;
    padding: 0 9px; border: 1px solid transparent; border-radius: 0; font-size: 11px; font-weight: 700; line-height: 1;
    white-space: nowrap; vertical-align: middle; }
.pt-pill.ok { background: var(--teal-fill); color: var(--teal); border-color: var(--teal-line); }
.pt-pill.muted { background: var(--surface); color: var(--muted); border-color: var(--line); }
/* RF1 — Agotado / Bajo. */
.pt-pill.crit { background: var(--crit-fill); color: var(--crit-ink); border-color: var(--crit-line); }
.pt-pill.warn { background: var(--warn-fill); color: var(--warn-ink); border-color: var(--warn-line); }
/* #463 — Estado's chip leads with a dot in its own ink, the mockup's status light. #528 (Cam: Recetas' «Completa» "should
   use same tag styling as menu manager table green tag", "across pages") — every row's state is that tag, the Menús
   table's .mt-status: a dot and a word on a pale fill, no border, 12px/600, the green when on or done and the slate when
   off; Revisar, Bajo, Agotado and the other warnings keep their tints in the same shape. Still 22px, so the 40px row
   (#422) holds. A flag inside a cell (Estimado beside a cost, Sin conversión) has no dot and keeps the bordered chip. */
/* #626 (Cam: "Status tag circle/dot vertical alignment off") — the dot sits on the word's own baseline, raised by half its
   capitals (Montserrat's are 0.70em), so it is level with them whatever metrics lay the line out. Centred in the box
   beside the word, it followed the line box instead, and where a screen puts the capitals 1-2px above the line's middle
   (Cam's) it sat that much low. The word keeps its place, centred in the 22px tag by its line height. */
.pt-pill.pt-dot { display: inline-block; padding: 0 8px; border-width: 0; font-size: 12px; font-weight: 600; line-height: 22px;
    text-align: center; }
.pt-pill.pt-dot::before { content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 6px; border-radius: 50%;
    background: currentColor; vertical-align: calc(0.35em - 3.5px); }
.pt-pill.pt-dot.ok { background: var(--state-on-fill); color: var(--state-on-ink); }
.pt-pill.pt-dot.muted { background: var(--state-off-fill); color: var(--state-off-ink); }
/* #529 (Cam: the white «contenedor» tag, "match the tag style/color we use elsewhere and fix the spacing when it's
   shown to the right of the name") — a fact about a row (combo, contenedor, Consumible, de paquete abierto) is a tag
   with no dot: the menu's slate filled, no border, and never the state column's 72px. The Menús table's own kind tag
   (.mi-pill--dish) is near-white (#f8fafc, 1.04:1 on a white row), the unfilled look Cam asked to replace. Blazor trims
   the space between a name and the tag after it, so the tag keeps its own 8px. It sits on its line's top, the line's
   own height: centred, it made a 40px row 40.5 (#422, as the state pill once did). */
.pt-tag { display: inline-flex; align-items: center; box-sizing: border-box; height: 20px; padding: 0 7px; margin-left: 8px;
    background: var(--state-off-fill); color: var(--state-off-ink); font-size: 11px; font-weight: 700; line-height: 1;
    white-space: nowrap; vertical-align: top; }

/* #463 (Cam) — Existencias' alert banners, which replace RF1's "Atención" card: "compact banners", and "don't have a
   border box for the banners". The row draws nothing of its own. Each banner is a toggle in its tone's tint with a 3px
   bar on its left, #444's accent for a warning: coral for Agotados, amber for Bajo and for the expired packages (Igor's
   R2). The count and label are in the chip inks and the description in --muted, each measured at AA on its tint in
   every scheme. A banner is 64px (13 + 38 + 13), a whole pixel at every scale (#422). Pressed (its view showing), it
   wears a ring in its own ink and its chevron turns down, as a tree row's does when open. The columns are at least
   300px, so a description stays on one line at a desktop width: at 260 the row took four, and "En el punto de reorden o
   por debajo" wrapped and stretched the whole row to 82px. They keep their width as banners come and go (auto-fill),
   and are never wider than the row, so a 320px phone does not scroll sideways. Pinned by InventoryStockContractTests. */
.pt-banners { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 12px;
    margin-bottom: 16px; }
.pt-banner { display: flex; align-items: center; gap: 12px; width: 100%; margin: 0; padding: 13px 16px 13px 15px; border: 0;
    border-left: 3px solid; border-radius: 0; font: inherit; text-align: left; cursor: pointer; }
/* #463 v2 (Cam: the red banner's "text/accent bar" differ, where the amber one's match; Igor's R4) — a banner's bar is its
   own ink, in both tones and both themes. */
.pt-banner.crit { background: var(--crit-fill); border-left-color: var(--crit-ink); color: var(--crit-ink); }
.pt-banner.warn { background: var(--warn-fill); border-left-color: var(--warn-ink); color: var(--warn-ink); }
.pt-banner.info { background: var(--info-fill); border-left-color: var(--info-ink); color: var(--info-ink); }
.pt-banner-glyph { font-size: 24px; line-height: 1; }
.pt-banner-n { font-size: 30px; line-height: 36px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pt-banner-text { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.pt-banner-text b { font-size: 15px; line-height: 20px; font-weight: 700; }
.pt-banner-sub { font-size: 12.5px; line-height: 18px; color: var(--muted); }
.pt-banner-chev { font-size: 14px; line-height: 1; transition: transform .2s ease; }
.pt-banner:hover { box-shadow: inset 0 0 0 1px currentColor; }
.pt-banner[aria-pressed="true"] { box-shadow: inset 0 0 0 2px currentColor; }
.pt-banner[aria-pressed="true"] .pt-banner-chev { transform: rotate(90deg); }
.pt-banner:focus-visible { outline: 3px solid var(--navy); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .pt-banner-chev { transition: none; } }
/* #463 — the line over a banner's view: what is showing, and the way back to every row. */
.pt-viewbar { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin-bottom: 12px; font-size: 13px;
    color: var(--muted); }
.pt-viewbar b { color: var(--ink); font-weight: 600; }
/* #463b — an (item, area) whose last count was off by more than the tolerance: its Último conteo says by how much, in
   Discrepancias' navy, beside the date. */
.pt-table td .pt-countoff { margin-left: 6px; color: var(--info-ink); font-weight: 600; font-variant-numeric: tabular-nums;
    white-space: nowrap; }
/* RF4d — a second, quieter line inside a table cell: a ledger note, a presentation preview ("= 40 kg · $12.00/kg"). */
.pt-table .mt { display: block; margin-top: 2px; font-size: 12px; color: var(--muted); white-space: normal; }
/* #597 (Cam: "This text size is too large") — the same quiet words outside a table: a receipt's line in its order, an
   e-mail beside its name, «Sin comprobantes…», a note beside a button. Styled only inside tables, each took the body's
   size and ink. */
.pt-admin .mt { font-size: 12px; color: var(--muted); }
/* Status tags in a column share one width, so Activo and Inactivo line up instead of ragging the column. Scoped to
   tables: a free-text pill (the recipe designer's save notice) keeps its natural width. */
.pt-table .pt-pill { min-width: 72px; }
/* #446 / #447 — a select in a table cell keeps room for "— Selecciona —" beside its chevron. The table's auto layout
   squeezed Recibir's selects to a clipped word (Cam's #447 capture), and the chevron's reserved space took 24px more. */
.pt-table .pt-select { min-width: 176px; }

/* form */
/* #444 (Cam) — no coral bar on filter / utility containers: on dense screens it is noise. Coral accent bars stay only
   on the page header bar, and on warnings (Existencias' alert banners, .pt-error). */
.pt-form { border: 1px solid var(--line); background: var(--surface); padding: 18px; margin-bottom: 16px; }
.pt-field { margin-bottom: 14px; }
.pt-field .lab { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase;
    letter-spacing: .06em; margin-bottom: 5px; display: block; }
/* #597 (Cam: "can we just put '(optional)' next to the field label? If we need to show the full text, … in smaller font
   size?") — a note on a label's line, in the label's size and ink but as plain words: not capitals, not bold. */
.pt-field .lab .pt-labnote { margin-left: 6px; font-weight: 400; text-transform: none; letter-spacing: normal; }
/* A file picked for a receipt, one per row: its name and size in the small ink of a list, and its Quitar. It was a
   .mt, which was styled only inside a table, so it took the body's size. */
.pt-filerow { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 13px; color: var(--ink); }
.pt-filerow > .pt-btn { margin-left: 4px; }
/* background-COLOR only: a shorthand here would also declare background-image: none, and .pt-select's chevron below
   would then survive only by coming later in the file.
   #593 (Cam) — the admin's field tokens (.pt-admin above), which app.css's select rule reads as well, and its field
   height: a search beside a select was 43px at Inventario's #dcdce8 beside 44px at #ced4da. */
.pt-input, .pt-select { width: 100%; border: 2px solid var(--pt-field-border); border-radius: 0; padding: 9px 12px;
    background-color: var(--pt-field-bg); color: var(--pt-field-text); font-size: 14px;
    min-height: var(--pt-field-min-height); }
/* #446 (Cam) — Chromium draws a native select's arrow against the border whatever the padding. The chevron is drawn
   here instead, 12px in from the edge, with room reserved for it so a long option never runs under it. Stroke =
   --muted, per theme (a data URI cannot read a custom property). */
.pt-select { -webkit-appearance: none; appearance: none; padding-right: 36px;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%235b5b76' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat; background-position: right 12px center; background-size: 12px 12px; }
[data-theme="dark"] .pt-admin .pt-select, .theme-dark .pt-admin .pt-select {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23a2a2bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); }
.pt-input:focus, .pt-select:focus { outline: none; border-color: var(--pt-field-focus); }
/* #605 — a textarea is inline and its baseline is its bottom edge, so the line under it kept a descender's 7px: the
   next section started 31px under Notas and 24px under any other field. On the top of its line it ends where its box
   does, and its field keeps the 14px every field has. */
.pt-admin textarea.pt-input { vertical-align: top; }
.pt-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 640px) { .pt-grid2 { grid-template-columns: 1fr; } }
/* #500 (Cam) — a field with a long explanation (Artículos' «Consumible») takes the grid's whole width, on a line of its own. */
.pt-grid2 > .pt-span2 { grid-column: 1 / -1; }
/* #487 (Cam) — Recibir's delivery note (Proveedor, Factura del proveedor, Folio de remisión) on one line once the screen
   has room, the supplier's name with twice a number's share; .pt-grid2's two columns below that, one on a phone.
   #488 (Cam: "put it last") — and «Fecha de recepción» after them, a number's share too. Four on a line need 1280px: from
   1100px, beside the admin's sidebar, «Factura del proveedor» and «Fecha de recepción» wrapped and their inputs stepped
   (measured). Each number's column keeps 11.5rem, so its label stays on one line; below 1280px the four make two even
   lines. */
@media (min-width: 1280px) { .pt-grid2.pt-docrow { grid-template-columns: 2fr repeat(3, minmax(11.5rem, 1fr)); } }
/* #494 (Cam) — Conteos: the new sheet's type, then «Incluir enviadas» and «Crear» on the select's line. The select has a
   label above it, so the actions sit on its line by the field's own bottom margin, as #481's Limpiar filtros does; a
   phone wraps them under it. */
.pt-countnew { display: flex; flex-wrap: wrap; gap: 0 14px; align-items: flex-end; }
.pt-countnew > .pt-field { flex: 0 1 280px; }
.pt-countnew > .pt-actions { min-height: 44px; margin-bottom: 14px; }
/* #494 v2 (Cam, 28 Oct) — Conteos: creating a count and printing a sheet to count on are two cards. They sit side by side
   where both fit, create a little wider as in the mock-up, and one above the other where they don't: a wrapping row
   decides it from the panel's own width, whatever the sidebar takes. Each card has a square tile (the brand's straight
   edges; the mock-up drew circles), a title with a line under it, then its row, where the select gives way before the
   controls leave its line. */
.pt-countcards { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 14px; }
.pt-countcard { flex: 1 1 460px; min-width: 0; border: 1px solid var(--line); background: var(--surface); padding: 16px 18px 2px;
    display: flex; flex-direction: column; }
/* Side by side the cards are as tall as the taller; each row sits at its card's foot, so the two rows share a line
   whatever their heads take. */
.pt-countcard > .pt-countnew { margin-top: auto; }
.pt-countcard.pt-countcard--lead { flex: 1.15 1 600px; }
.pt-countcard-head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 14px; }
.pt-countcard-tile { flex: none; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; font-size: 20px; }
.pt-countcard-tile.coral { background: var(--coral-fill); color: var(--coral-ink); }
.pt-countcard-tile.navy { background: var(--info-fill); color: var(--info-ink); }
.pt-countcard-head h5 { margin: 0; font-weight: 600; font-size: 16px; line-height: 1.3; color: var(--ink); }
.pt-countcard-head .sub { display: block; margin-top: 2px; color: var(--muted); font-size: 13px; }
.pt-countcard .pt-countnew > .pt-field { flex: 1 1 180px; }
.pt-check > .pt-checkhint { color: var(--muted); font-size: 13px; }
/* #495 (Cam) — Movimientos: a receipt's «Ver recepción» beside its label, centred on it, instead of hanging under it. */
.pt-table .pt-origin { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* #495 v2 (Cam, 28 Oct) — «Ver recepción» at the table's right edge: Origen is the last column, and the button takes
   the room to its left. Wrapped onto a line of its own (a narrow cell), it stays on the right. */
.pt-table .pt-origin > .pt-btn { margin-left: auto; }
/* C3b — what needs a look under a line read from a CFDI (an unmatched code, a unit that differs), in the warning ink. */
.pt-table .mt.pt-warntext { color: var(--warn-ink); font-weight: 600; }
/* RF4d-2 — a screen's filter row: one column per filter, as many as fit; a phone stacks them. */
.pt-filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; align-items: end; }
/* #463 (Cam) — Existencias' filters without their box ("Remove the current border box around the filters"): no border,
   fill or padding. The search leads the row with twice a select's share, on the selects' line (they carry labels
   above, it has none), its magnifier drawn inside it: the shape of the admin's search primitive (#346), built on
   Inventario's own field so it matches the selects beside it. .pt-search itself is drawn in the app's tokens, which go
   dark with the OS. (Until #593 it matched them only outside the admin's layout root: inside it, app.css drew the
   selects.) The row wraps, so a phone stacks the fields. */
.pt-filterbar { margin-bottom: 14px; }
.pt-filterbar .pt-filters { display: flex; flex-wrap: wrap; gap: 0 14px; align-items: flex-end; }
.pt-filterbar .pt-filters > .pt-field { flex: 1 1 160px; min-width: 0; }
.pt-filterbar .pt-filters > .pt-searchfield { flex: 2 1 280px; }
/* #583 (Cam: "when the filter/search fields start to stack, the search bar is put on it's own row (not the category
   filter)") — Existencias' three selects wrap as one group, its basis the three at 160px and their two gaps (508px): a
   row under 802px gives the search a row of its own and the three the next. Narrower than the three, they stack one
   per row, never two and one: a select's basis is (508px − the group's width) × 999, which is 0 while the group is
   508px or wider and more than a row under it. On a wide row nothing moves: the group grows by the three selects'
   shares, so the search and each select keep #463's widths. */
.pt-filterbar .pt-filters > .pt-filterselects { flex: 3 1 508px; min-width: 0; display: flex; flex-wrap: wrap;
    gap: 0 14px; align-items: flex-end; }
.pt-filterbar .pt-filterselects > .pt-field { flex: 1 1 calc((508px - 100%) * 999); min-width: 0; }
/* #481 (Cam's mockup) — Limpiar filtros ends the filters' row. The selects carry a label above and it does not, so it
   sits on their line by the same bottom margin, centred on them, and keeps a full 44px to touch. */
.pt-filterbar .pt-filters > .pt-clearfilters { flex: 0 0 auto; display: inline-flex; align-items: center; min-height: 44px;
    margin-bottom: 14px; }
/* #481 + #482 (Cam) — over the table's right edge: how many rows it shows, then Imprimir and PDF, as one group pushed
   past the two boxes. On a phone it wraps to a line of its own and stays right. */
.pt-filterbar-end { margin-left: auto; display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center;
    gap: 8px 12px; }
/* #655 (Cam) — regular, muted grey: not italic. */
.pt-rowcount { font-size: 12px; color: var(--muted); white-space: nowrap; }
/* #655 (Cam) — Imprimir, CSV and PDF as text links (InventoryReportActions, on every Inventario and Reportes screen): no
   border or fill, a navy glyph and word, and a short rule between two, and between the row count and the first. Each is
   still a 44px button to touch; hovered, its word is underlined. */
.pt-reportlinks { display: inline-flex; align-items: center; }
.pt-btn.pt-reportlink { background: transparent; border-color: transparent; color: var(--navy); padding: 9px 12px; position: relative; }
.pt-btn.pt-reportlink:hover:not(:disabled) { text-decoration: underline; }
.pt-btn.pt-reportlink + .pt-btn.pt-reportlink::before,
.pt-rowcount + .pt-reportlinks > .pt-btn.pt-reportlink:first-child::before {
    content: ""; position: absolute; left: 0; top: 12px; bottom: 12px; border-left: 1px solid var(--line); }
.pt-rowcount + .pt-reportlinks { margin-left: 2px; }
/* #655 (Cam: "Tighten vertical spacing between the page header, filters, toolbar, and table") — Existencias' top
   section: 12px under the title, 8px under the filters' boxes (and «Limpiar filtros», which sits on their line by the
   same margin), and 8px from the checkbox and export row to what follows. The search carries a «Buscar» label like the
   selects, so the four labels and boxes line up; it keeps twice a select's share. */
.pt-screentitle.pt-stock-title { margin-bottom: 12px; }
.pt-filterbar.pt-filterbar--stock { margin-bottom: 8px; }
.pt-filterbar--stock .pt-filters > .pt-field,
.pt-filterbar--stock .pt-filterselects > .pt-field,
.pt-filterbar.pt-filterbar--stock .pt-filters > .pt-clearfilters { margin-bottom: 8px; }
.pt-filterbar .pt-filters > .pt-stocksearch { flex: 2 1 280px; }
/* #504 (Cam: "Reduce the length of the search bar (a lot) and put it on the same row as the filter buttons but align it to
   the right side while the filter buttons are on the left") — Recetas' kind buttons and its search on one row, the search
   260px at the right with «Limpiar filtros» before it. The field's own bottom margin goes: the row keeps the gap. On a
   phone the row wraps and the search takes the whole width. */
.pt-recipe-bar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 12px 16px;
    margin-bottom: 14px; }
.pt-recipe-search { margin-left: auto; display: flex; align-items: center; gap: 8px 12px; }
.pt-recipe-search > .pt-searchfield { width: 260px; margin-bottom: 0; }
@media (max-width: 767.98px) {
    .pt-recipe-search { flex: 1 1 100%; margin-left: 0; }
    .pt-recipe-search > .pt-searchfield { flex: 1 1 auto; width: auto; }
}
.pt-searchfield { position: relative; }
.pt-searchfield > .bi { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); font-size: 15px; line-height: 1;
    color: var(--muted); pointer-events: none; }
.pt-searchfield > .pt-input { padding-left: 40px; -webkit-appearance: none; appearance: none; }
/* RF4d-2 — what the stock is worth: one figure per area, and the branch's total at the row's far end. */
.pt-totals { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: baseline; margin-top: 14px; font-size: 13px;
    color: var(--muted); }
.pt-totals b { color: var(--ink); font-weight: 600; }
/* RF4g-1 — Análisis' figures: one card per cost, as many per row as fit; a phone gets two per row. */
.pt-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 14px 0; }
.pt-kpi { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; border: 1px solid var(--line);
    background: var(--surface); }
.pt-kpi .lab { font-size: 12px; color: var(--muted); }
.pt-kpi b { font-size: 18px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
/* #497 (Cam: "give it the same styling as the navy banner design we use on the Extencias page") — a report's lead
   figure (Análisis' Costo real, Extracto's article, Compras' total) wears Discrepancias' banner: its fill, a 3px bar in
   its ink, the figure in that ink. The 2px the bar adds come off the left padding, so the text doesn't move. */
.pt-kpi.lead { background: var(--info-fill); border-color: var(--info-fill); border-left: 3px solid var(--info-ink); padding-left: 10px; }
.pt-kpi.lead b { color: var(--info-ink); }
@media (max-width: 640px) { .pt-kpis { grid-template-columns: 1fr 1fr; } }
.pt-table tfoot td { font-weight: 600; box-shadow: inset 0 2px 0 var(--line); }
/* «Reportes» → «Ventas» (Periban, #476) — a report of two tables: the second's title, with the screen's air above it, and
   the row the other sales reports add up to («Ventas») in the totals' weight. */
.pt-tabletitle { margin: 20px 0 8px; font-weight: 600; font-size: 15px; line-height: 1.3; color: var(--ink); }
.pt-table tr.pt-leadrow td { font-weight: 600; }
.pt-check { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--ink); }

/* #444 — a hint is information, not a warning: a navy bar, not coral (the same call as the editing row's navy stripe,
   Igor 7 Sep). #445 — space on BOTH sides: it used to have a top margin only, so the fields under a drawer's leading
   banner started at its edge. None above a leading hint or below a trailing one, where the container's own padding is
   the space. */
.pt-hint { font-size: 12px; color: var(--muted); margin: 14px 0; padding: 10px 12px; background: var(--surface-2);
    border-left: 3px solid var(--navy); line-height: 1.55; }
.pt-hint:first-child { margin-top: 0; }
.pt-hint:last-child { margin-bottom: 0; }
.pt-empty { padding: 28px; text-align: center; color: var(--muted); }
/* #560: what Movimientos' filters add up to, on a line of its own above the table. */
.pt-ledgertotals { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 20px; margin: 14px 0; padding: 10px 12px; font-size: 13px; color: var(--muted); background: var(--surface-2); }
.pt-ledgertotals strong { color: var(--ink); font-weight: 600; }
.pt-ledgertotals .pt-btn { margin-left: auto; }
.pt-ledgertotals .pt-btn[aria-pressed="true"] { background: var(--ink); color: var(--surface); }
/* #463 v2 (R4) — the error box follows the banners: its bar is its ink. The ink is the alert chips' darker red, since
   --coral-ink read 4.04:1 on this fill, under the 4.5 its 13.5px text needs; --crit-ink reads 5.61:1. */
.pt-error { border: 1px solid var(--crit-line); border-left: 3px solid var(--crit-ink); background: var(--crit-fill);
    color: var(--crit-ink); padding: 10px 12px; margin-bottom: 14px; font-size: 13.5px; }
