/* NETTZINV — Nettz ERP house style.
   Last updated: 2026-10-13
   - 2026-10-13 (later) — tr.row-omitted: a component left out of a build, highlighted in its row.
   - 2026-10-13 — .lineno / .partcell / .linethumb: numbered recipe and build lines with each
     component's picture (Patito).
   - 2026-10-02 — .datebox: date boxes as YYYY-MM-DD text + calendar button (static/dateinput.js).
   - 2026-10-02 — .tipbox: the button tooltip of static/tooltip.js.
   - 2026-10-02 — home text sizes matched to the other pages (.homesec / .homelist; card number
     and title smaller) - Patito: the main page fonts were too big.
   - 2026-10-02 — .capgrid min card width 300 -> 230 px: as many cards per row as fit (Patito).
   - 2026-10-02 — .capgrid / .capcard: the 'Can build' section as cards (layout B).
   - 2026-09-16 — .lnote-mark / .lnotes: legacy notes carried over from InvenTree,
     a marker in tables and a block on unit pages.
   - 2026-09-16 — tr:target: after editing a unit's firmware or origin you
     come back to the row you were on instead of the top of the page. The
     editors now return to #<prefix>-<serial>; this is what makes the landing
     row visible. scroll-margin clears the sticky .appheader.
   - 2026-09-15 — .pill.st: equal width for every status badge, the same fix
     .pill.role got for the Users page. "Sent"/"Draft" and "Borrador"/"Enviado"
     differ enough in length that the Status column read as a ragged edge.
   - 2026-09-14 — .rowacts: the stock list's action column. Its leading button
     alternates (Mark lost / Count) and the labels differ wildly by language
     ("Marcar perdido" vs "Contar"), so an inline min-width:5.3rem left the
     column ragged — too narrow for the Spanish. Every text button in the cell
     now takes one width; the icon-only buttons take one square.
   - 2026-09-14 — .tile.bad no longer draws a red border; Total-units tile lost its accent border.
   - 2026-09-14 — details.cap: the collapsible 'Can build' rows on the home page.
   - 2026-09-14 — .pill.role: equal width for the Users page role badges.
   - 2026-09-13 — grid headers wrap (Patito: "make the columns narrower"). They
     had inherited the rows' white-space:nowrap, so the header string — not the
     data — set every column's width. Data cells still never wrap.
   - 2026-09-13 — tracking badges tightened (Patito): one width still, but sized
     to the longest label rather than 12px past it. min-width 4.75 -> 4.1rem and
     side padding .55 -> .45rem, so "Lote" and "Serie" stop carrying dead space.
   - 2026-09-13 — the Categories page badges were wrong (Patito): its tracking
     badge was built by hand without the `track` class, so Bulk rendered grey
     instead of its own colour and the column lost its shared width, and its
     consumable badge was a grey `pill none`. Both now come from the shared
     partials (_tracking_badge / _consumable_chip). The consumable mark's gap is
     keyed off a preceding name link, so it stays flush in a column of its own.
   - 2026-09-13 — the Consumable mark is a single violet "C" in a circle beside
     the component name (Patito), replacing the word. Five treatments got here:
     violet name, solid badge, outline chip in Kind, small chip by the name, then
     briefly nothing. A one-letter mark is the smallest thing that still marks the
     row; the tooltip carries the meaning the letter cannot, so it is required.
   - 2026-09-13 — the Consumable chip hugs the last character of the name
     (Patito). On the Components page the name cell is a flex row, so the chip
     had been a sibling flex item — parked beside the whole name BLOCK, centred,
     a 0.55rem flex gap away. Name and chip are now one flex item, and the gap is
     the chip's own margin-left, so it stays glued to the text however it wraps.
   - 2026-09-13 — the Consumable chip moves out of the Kind column to sit beside
     the component name (Patito), at .62rem — smaller than any other pill. The
     Kind column is back to one badge per row. Shared via
     catalog/_consumable_chip.html so the three tables cannot drift.
   - 2026-09-13 — the Consumable badge has no fill (Patito): an outline chip
     sized to its text, one tier below Kind. It had been sharing .pill.kind —
     same 5.5rem block, same solid fill — so only hue separated a sparse flag
     from the mandatory taxonomy, and it read as a fourth kind. Third exception
     to white-on-solid, with .nosource and .inventree.
   - 2026-09-12 — the InvenTree provenance badge has no fill (Patito): outlined
     and muted, joining .nosource as the second exception to white-on-solid.
   - 2026-09-12 — .pill.dtype: the dispatch Tipo column's Customer / Technician
     badges take one width.
   - 2026-09-12 — the Serial tracking badge takes the Customer badge's blue
     #3498db (Patito), replacing the dark gold.
   - 2026-09-12 — .btn.admin: the ⚙ fix-state button is black (--btn-admin), so
     the superuser override does not look like an ordinary row action.
   - 2026-09-12 — the Product kind badge is #EA005E (Patito) on its own class,
     no longer sharing the Available green.
   - 2026-09-12 — .btn.rowbtn: one width for buttons that alternate down a table
     column (the stock view's Calibrate / View pair).
   - 2026-09-12 — Bulk tracking is sage #647C64 (Patito), split off the
     shared grey. Scoped .pill.track.none so "Part" and link pills stay grey.
   - 2026-09-12 — state badges are one width (.pill.state, 6.25rem). The labels
     lost their parentheses — "Written off (lost/scrapped)" -> "Written off",
     "(at customer)" and "(not for sale)" likewise — and the sentence they
     carried moved to STATE_HELP, shown as a "What it means" column on the home
     page's state table. Home's two staged tiles shortened to Build / Assembly.
   - 2026-09-12 — the violet consumable name is gone from every table (17 call
     sites); the badge carries it there now. Kept only in the totals chips,
     which have no badge. Patito: "only in the totals they should be highlighted".
   - 2026-09-12 — consumable is shown as its OWN badge beside the kind, not as a
     violet name colour that reads like a fourth kind. Kind = how a thing is
     made, consumable = how it is planned; a roll of paper is both.
   - 2026-09-12 — the Kind and Tracking badges come from two shared partials
     (catalog/_kind_pill.html, _tracking_pill.html) instead of being retyped at
     four call sites, which is how /components/ drifted to "assembly"/"part" in
     lowercase and the long tracking labels while /stock/ said "Assembly"/"Part"
     and the short ones. One width for all of them inside a grid; the stock
     page's nth-child hack is gone with it.
   - 2026-09-12 — .serial is no longer the odd one out: brand yellow with dark
     text becomes --badge-serial #726500 (the brand yellow darkened; the ERP's
     own --primary-text-color) with white, 5.9:1. Added .pill.consumable. Also
     dropped the inline `style="color:…"` off four badges in inventree_import /
     build_detail — those overrode the new white text and left coloured text on
     a grey fill.
   - 2026-09-12 — badge text is WHITE sitewide (Patito), which meant converting
     the remaining soft-tint badges (info/none/other/lot/warn/tomove/unknown/
     inventree) into solid fills — white on a pale tint is invisible. .serial and
     .nosource are kept as exceptions, documented at the rule.
   - 2026-09-12 — every stock-state badge now uses the ERP's estado_visita
     palette as a solid fill: Available #2ecc71, Staged #3498db, Awaiting
     calibration #9b59b6, On pilot / Demo #f39c12, Quarantine #e74c3c, Written
     off #7f8c8d. New .pill.progress/.calib/.out/.bad/.gone classes so the
     generic info/none/other tints are left alone. Text is dark where that
     clears AA and white only on the purple; the ERP's own white-on-everything
     fails on four of the five.
   - 2026-09-12 — .btn.warn (--btn-warn burnt amber): the write-off action, for
     "Mark lost" -> WRITTEN_OFF. Slots between the blue ordinary action and the
     red defect one; quarantine can be undone, a write-off cannot.
   - 2026-09-12 — secondary and danger buttons are SOLID with white text, copying
     the ERP's real action buttons (its totem/ticket rows: .btn-view #17a2b8,
     .btn-edit #28a745, .btn-delete #dc3545) rather than its erp-btn.outline.
     New --btn-2 / --btn-danger tokens: --info/--bad go pale in the dark theme and
     white text would stop being readable on them. Patito, twice: no transparent
     buttons with a coloured contour; "in tickets there are blue buttons with
     white text inside".
   - 2026-09-11 — .pill.inventree marks records imported from InvenTree; .pill.warn
     defined (the dispatch list's "Draft" badge used it but it never existed).
   - 2026-09-11 — .btn shorter: .5rem vertical padding (was the ERP's 1rem), so
     buttons match the inputs beside them instead of stretching whole filter rows
     (Patito: "they are too tall").
   - 2026-09-11 — .combo*: every drop-down list gets an embedded filter
     (static/combo.js): type and only the matching options stay.
   - 2026-09-11 — re-based on the Nettz ERP's own source (web-ui "new-main"
     layout: styles.scss, side-nav, header, login, and the tenant palette in
     branding.config.ts / theme.service.ts): dark-grey side menu (#262626) with
     our rotating dots logo + wordmark kept, collapsible on desktop and off-canvas on phones; pale-yellow
     header bar and page frame; ERP cards, buttons, badges, forms, focus ring and
     scrollbars; 16px Nunito Sans; light theme by default (dark kept as an
     option); data tables kept compact on purpose so wide grids still fit.
   - 2026-09-11 — table headers follow their column's data alignment instead of
     all being centred: text left, numbers right, centred only via .center on a
     column whose data is centred too.
   - 2026-09-09 — .pill.nosource + .nosource-note: marks dispatch notes whose
     legacy source held no description.
   - 2026-08-19 — --consumable (violet) + .consumable-name: consumable
     component names are shown in violet in every table (keyed off any_supplier).
   - 2026-08-04 — base house style.

   Colours: the ERP derives every Nettz tint from three brand colours
   (#fde100 yellow / #1a1a1a black / #f59e0b amber); the values below are those
   derivations, recomputed with theme.service.ts's own formula. Deliberate
   deviations from the ERP: yellow is never used as text on white (the ERP's
   outline buttons and links do that for Nettz and are unreadable) — #726500,
   the ERP's own "primary text" colour, is used instead; and status text uses
   the darker Material shade where the ERP's fails contrast on white. */

:root {
  /* ---- Nettz brand (branding.config.ts, tenant "nettz") ---- */
  --brand-yellow: #fde100;   /* --primary-color: fills, highlights */
  --brand-hover:  #e6cc00;   /* the ERP's hover yellow */
  --on-brand:     #1a1a1a;   /* --on-primary-color: text on yellow */
  --brand-black:  #1a1a1a;   /* --secondary-color */
  --tertiary:     #f59e0b;   /* --tertiary-color: amber */
  --primary-text: #726500;   /* --primary-text-color: "yellow" that reads on white */

  /* ---- surfaces (theme.service.ts derivations + styles.scss) ---- */
  --frame:     #fffce6;      /* --primary-background: page frame + header bar */
  --bg:        #fffef2;      /* --secondary-background: the content area */
  --panel:     #ffffff;      /* --surface-color: cards, tables */
  --surface-2: #f1f5f9;      /* --background-color: table heads, hovers, disabled */
  --fg:        #212121;      /* --dark-primary-text-color */
  --muted:     #64748b;      /* --muted-text-color */
  --rule:      #e0e0e0;      /* --divider-color */
  --accent:      #726500;    /* links and accents */
  --accent-soft: #fffce6;    /* pale yellow hover fill */
  --focus-ring:  rgba(253, 225, 0, .5);   /* --primary-transparent-color */
  --code-bg:   #f1f5f9;

  /* ---- status (Material, as in styles.scss; text shades picked for contrast) ---- */
  --good: #2e7d32; --good-soft: #e8f5e9;   /* available / ready / done */
  --warn: #b45309; --warn-soft: #fff3e0;   /* your action / attention  */
  --bad:  #d32f2f; --bad-soft:  #ffebee;   /* short / blocked / defective */
  --info: #1976d2; --info-soft: #e3f2fd;   /* in progress / committed  */
  --consumable: #7c3aed;                   /* consumable / buy-from-anyone */
  /* ---- stock-state badges: the ERP's estado_visita palette, seeded in its own
     DB migration and rendered at visita-terreno/listar as solid .estado-badge ---- */
  --badge-avail:    #2ecc71;   /* "Terminado" — usable                    */
  --badge-progress: #3498db;   /* "Ingresado" — in a build here           */
  --badge-calib:    #9b59b6;   /* "En Curso"  — out for calibration       */
  --badge-out:      #f39c12;   /* "Asignado"  — with someone, still ours  */
  --badge-bad:      #e74c3c;   /* "Anulado"   — defective / quarantine    */
  --badge-gone:     #7f8c8d;   /* the ERP's own getEstadoColor() fallback */
  --badge-serial:     #3498db;   /* same blue as the Customer badge in dispatch (Patito) */
  --badge-consumable: #7c3aed;   /* matches --consumable, white is 5.7:1 */
  --badge-bulk:       #f1c40f;   /* yellow — bulk tracking only (Patito, trial) */
  --badge-product:    #EA005E;   /* Product kind (Patito). White 4.5:1 */

  /* ---- buttons (the ERP's solid action colours, e.g. its totem row actions:
     .btn-view #17a2b8 / .btn-edit #28a745 / .btn-delete #dc3545, all white text).
     Kept as their own tokens, not --info/--bad, because those flip pale in dark
     mode and white text would stop being readable on them. ---- */
  --btn-2:        #1976d2;  --btn-2-hover:   #1565c0;   /* secondary: action blue */
  --btn-warn:     #b45309;  --btn-warn-hover:   #92400e;  /* write-off: burnt amber */
  --btn-danger:   #d32f2f;  --btn-danger-hover: #b71c1c;
  --btn-admin:    #1a1a1a;  --btn-admin-hover:  #3a3a3a;  /* superuser override */
  --on-btn:       #ffffff;

  /* ---- side menu (side-nav.component.scss, Nettz palette) ---- */
  --nav-bg:        #262626;
  --nav-fg:        #f2f2f2;
  --nav-muted:     #d6d6d6;
  --nav-active-bg: rgba(253, 225, 0, .14);
  --nav-w:           250px;
  --nav-w-collapsed:  72px;

  /* ---- shape (styles.scss: cadence .5rem) ---- */
  --radius: 8px; --radius-sm: 4px; --radius-lg: 16px; --radius-badge: 12px; --radius-pill: 999px;
  --shadow:     0 2px 8px rgba(0,0,0,.08);
  --shadow-lg:  0 4px 16px rgba(0,0,0,.12);
  --shadow-bar: 0 2px 10px rgba(0,0,0,.1);

  --font: "Nunito Sans", -apple-system, "Segoe UI", system-ui, sans-serif;
  scrollbar-width: thin;
  scrollbar-color: var(--brand-yellow) #b8b8b8;
}

/* ---- dark: optional (the ERP itself is light-only) ---- */
:root[data-theme="dark"] {
  --frame:     #0f0f12;
  --bg:        #141418;
  --panel:     #1b1b20;
  --surface-2: #23232a;
  --fg:        #e7e7ea;
  --muted:     #9296a1;
  --rule:      #2c2c33;
  --primary-text: #fde100;   /* yellow reads perfectly on black */
  --accent:      #fde100;
  --accent-soft: #23220e;
  --code-bg:   #1b1b20;
  --good: #6fbf73; --good-soft: #16241a;
  --warn: #e0b24a; --warn-soft: #2a2212;
  --bad:  #ef8a80; --bad-soft:  #2a1615;
  --info: #6ba4f0; --info-soft: #17233f;
  --consumable: #c4b5fd;
  /* stay saturated in the dark theme so white text keeps its contrast */
  --btn-2:      #2563eb;  --btn-2-hover:      #1d4ed8;
  --btn-warn:   #c2410c;  --btn-warn-hover:   #9a3412;
  --btn-danger: #e53935;  --btn-danger-hover: #c62828;
  --btn-admin:  #4a4a55;  --btn-admin-hover:  #5c5c68;   /* #1a1a1a would vanish
                                                            into the dark panel */
  --nav-bg: #18181c;
  --shadow:     0 2px 8px rgba(0,0,0,.5);
  --shadow-lg:  0 6px 20px rgba(0,0,0,.6);
  --shadow-bar: 0 2px 10px rgba(0,0,0,.5);
}

/* Consumable components: name shown in violet wherever a component is listed
   in a table. Keyed off Item.any_supplier. Higher specificity than `a` so it
   overrides the normal link accent. */
/* Consumables are marked by their own badge in tables (see catalog/_kind_pill),
   so the violet name is reserved for the totals chips — where there is no room
   for a badge and the colour is the only cue. */
.consumable-name { color: var(--consumable); }
a.consumable-name:link, a.consumable-name:visited { color: var(--consumable); }

* { box-sizing: border-box; }
html { font-size: 16px; }
html, body { margin: 0; }
body {
  background: var(--frame); color: var(--fg);
  font: 1rem/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; font-weight: 600; }
a:hover { text-decoration: underline; }

::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #b8b8b8; border-radius: var(--radius-sm); }
::-webkit-scrollbar-thumb { background: var(--brand-yellow); border-radius: var(--radius-sm); }

/* ================= shell: side menu + header + content ================= */
.app { display: flex; min-height: 100dvh; }

.sidenav {
  position: sticky; top: 0; height: 100dvh; z-index: 20;
  width: var(--nav-w); flex: 0 0 var(--nav-w);
  display: flex; flex-direction: column;
  background: var(--nav-bg); color: var(--nav-fg);
  box-shadow: 2px 0 4px rgba(0,0,0,.1);
  transition: width .3s ease, flex-basis .3s ease;
}
.sidenav-top { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: 1.5rem 1rem 0 1.5rem; }
.sidenav-logo { display: flex; align-items: center; gap: .6rem; min-width: 0; color: #fff; font-weight: 800; font-size: 1.3rem; letter-spacing: -.01em; }
.sidenav-logo:hover { text-decoration: none; }
.sidenav-logo .wordmark span { color: var(--brand-yellow); }
/* the NETTZINV mark: the dot ring turns half a circle on hover (kept from the
   original top bar - Patito: "the logo animation we had is lovely, keep it") */
.sidenav-logo .logo, .auth-head .logo { flex-shrink: 0; transition: transform .7s cubic-bezier(.4,0,.2,1); }
.sidenav-logo:hover .logo, .auth-head:hover .logo { transform: rotate(180deg); }
.navcollapse {
  flex: 0 0 auto; width: 32px; height: 32px; border: 0; border-radius: var(--radius);
  background: none; color: var(--nav-muted); cursor: pointer; font-size: .95rem;
}
.navcollapse:hover { background: rgba(255,255,255,.08); color: var(--brand-yellow); }
.navcollapse i { transition: transform .3s ease; }

.navmenu { flex: 1; overflow-y: auto; overflow-x: hidden; margin-top: 1.5rem; padding-bottom: 1rem; }
.nav-item {
  display: flex; align-items: center; gap: 1rem; padding: 1.5rem;
  color: var(--nav-fg); font-weight: 600; white-space: nowrap;
  transition: all .3s ease;
}
.nav-item i { flex: 0 0 20px; width: 20px; text-align: center; }
.nav-item:hover, .nav-item.active {
  background: var(--nav-active-bg); color: var(--brand-yellow);
  text-decoration: none; transform: translateX(5px);
}
.logout-form { margin: 1.5rem; }
.logout-btn {
  display: flex; align-items: center; justify-content: center; gap: .6rem; width: 100%;
  padding: 1rem 1.5rem; border: 0; border-radius: var(--radius-sm);
  background: var(--brand-yellow); color: var(--on-brand);
  font: 700 1rem var(--font); cursor: pointer; transition: all .3s ease;
}
.logout-btn:hover { background: var(--brand-hover); transform: translateY(-2px); }

/* collapsed (desktop only): icons, no labels - wins the width back for wide grids */
@media (min-width: 769px) {
  :root[data-nav="collapsed"] .sidenav { width: var(--nav-w-collapsed); flex-basis: var(--nav-w-collapsed); }
  :root[data-nav="collapsed"] .sidenav-top { flex-direction: column; padding: 1.1rem .5rem 0; }
  :root[data-nav="collapsed"] .sidenav-logo .wordmark,
  :root[data-nav="collapsed"] .nav-item span,
  :root[data-nav="collapsed"] .logout-btn span { display: none; }
  :root[data-nav="collapsed"] .navcollapse i { transform: rotate(180deg); }
  :root[data-nav="collapsed"] .nav-item { justify-content: center; padding: 1.1rem 0; }
  :root[data-nav="collapsed"] .nav-item:hover, :root[data-nav="collapsed"] .nav-item.active { transform: none; }
  :root[data-nav="collapsed"] .logout-form { margin: 1rem .75rem; }
  :root[data-nav="collapsed"] .logout-btn { padding: .9rem 0; }
}

.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.appheader {
  position: sticky; top: 0; z-index: 15;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: 1rem 1.5rem; background: var(--frame); box-shadow: var(--shadow-bar);
}
.hamburger {
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px; border: 0; border-radius: var(--radius-sm);
  background: var(--panel); color: var(--primary-text); box-shadow: 0 2px 8px rgba(0,0,0,.1);
  font-size: 1.2rem; cursor: pointer;
}
.globalsearch { flex: 0 1 26rem; min-width: 12rem; margin: 0; }
.searchbox {
  display: flex; align-items: center; gap: .75rem; padding: 0 1.25rem;
  background: var(--panel); border-radius: 25px; box-shadow: 0 4px 6px rgba(0,0,0,.1);
  transition: box-shadow .3s ease;
}
.searchbox:focus-within { box-shadow: 0 0 0 3px rgba(253,225,0,.35), 0 4px 6px rgba(0,0,0,.1); }
.searchbox i { color: var(--primary-text); }
.searchbox input {
  flex: 1; min-width: 0; padding: .75rem 0; border: 0; background: transparent;
  color: var(--fg); font: inherit;
}
.searchbox input:focus { outline: none; }
.searchbox input::placeholder { color: var(--primary-text); opacity: .7; }
.header-actions { margin-left: auto; display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.header-actions form { margin: 0; }
.user-menu {
  display: flex; align-items: center; gap: .6rem; padding: .45rem 1rem;
  border-radius: var(--radius); background: var(--panel); box-shadow: 0 2px 4px rgba(0,0,0,.05);
}
.user-menu .avatar {
  width: 36px; height: 36px; border-radius: 50%; flex: 0 0 36px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-yellow); color: var(--on-brand); font-size: 20px;
}
.user-menu .user-info { display: flex; flex-direction: column; }
.user-menu .user-name { font-weight: 600; line-height: 1.2; }
.user-menu .user-role { font-size: 11px; font-weight: 500; line-height: 1.2; color: var(--primary-text); }

.messages { list-style: none; margin: 1rem 1rem 0; padding: 0; }
.content { flex: 1 0 auto; margin: 1rem; padding: 2rem; background: var(--bg); border-radius: var(--radius); }
/* Reading/form pages centre at the ERP's page width (1400px); wide data grids
   (stock, dispatch, builds) opt in to the whole width. */
.content:not(.wide) { padding-left: max(2rem, calc((100% - 87.5rem) / 2)); padding-right: max(2rem, calc((100% - 87.5rem) / 2)); }

.navoverlay { display: none; }

@media (max-width: 768px) {
  .sidenav {
    position: fixed; left: 0; top: 0; z-index: 40; width: var(--nav-w);
    transform: translateX(-100%); transition: transform .3s ease;
  }
  :root.nav-open .sidenav { transform: none; }
  .navoverlay { position: fixed; inset: 0; z-index: 35; background: rgba(0,0,0,.5); backdrop-filter: blur(4px); }
  :root.nav-open .navoverlay { display: block; }
  .hamburger { display: inline-flex; }
  .navcollapse { display: none; }
  .appheader { padding: .75rem 1rem; gap: .6rem; }
  .globalsearch { order: 3; flex: 1 1 100%; }
  .user-menu .user-info { display: none; }
  .content, .content:not(.wide) { margin: .5rem; padding: 1.25rem 1rem; }
  .messages { margin: .5rem .5rem 0; }
  .formrow { grid-template-columns: 1fr; }
}

/* ================= page elements ================= */
h1 { font-size: 1.5rem; margin: 0 0 1rem; letter-spacing: -.01em; font-weight: 700; }
h1 .sub { color: var(--muted); font-weight: 500; font-size: .95rem; }
h2 { font-size: 1.15rem; margin: 1.9rem 0 .6rem; font-weight: 700; }
.pagehead { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.25rem; flex-wrap: wrap; }
.pagehead .spacer { margin-left: auto; }

/* ---- stat tiles (erp-card) ---- */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 1rem; }
.tile {
  background: var(--panel); border: 1px solid transparent; border-radius: var(--radius-lg);
  padding: 1.25rem 1.5rem; box-shadow: var(--shadow); transition: box-shadow .2s ease;
}
.tile:hover { box-shadow: var(--shadow-lg); }
.tile .n { font-size: 1.75rem; font-weight: 700; letter-spacing: -.02em; color: var(--fg); }
.tile .l { color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
.tile.good .n { color: var(--good); }
.tile.warn .n { color: var(--warn); }
.tile.muted .n { color: var(--muted); }
.tile.bad .n { color: var(--bad); }   /* red number only - no border (Patito, 2026-09-14) */
.tile.info .n { color: var(--info); }

/* ---- tables (erp-table look, compact padding on purpose) ---- */
.tablewrap { overflow-x: auto; border-radius: var(--radius-lg); background: var(--panel); box-shadow: var(--shadow); }
table.grid { border-collapse: collapse; width: 100%; font-size: .875rem; }
.grid th, .grid td { text-align: left; padding: .6rem .8rem; border-bottom: 1px solid var(--rule); white-space: nowrap; }
.grid th { background: var(--surface-2); color: var(--muted); font-size: .75rem; text-transform: uppercase; letter-spacing: .5px; font-weight: 600; }
/* Headers wrap; data does not. The nowrap above is there so a row never breaks
   mid-value, but applied to the header it made the HEADER decide the column
   width — and a header is usually the longest string in its column
   ("UNIDADES EN ENSAMBLAJES" over a 2-digit number, "SEGUIMIENTO POR DEFECTO"
   over a 4rem badge). Every badge and number then floated in dead space, and no
   amount of tightening the badge could shrink the column. Wrapping the header
   hands sizing back to the data. Bottom-aligned so a two-line header still sits
   on the same baseline as its neighbours. */
.grid th { white-space: normal; vertical-align: bottom; }
.grid tbody tr:last-child td { border-bottom: 0; }
.grid tbody tr { transition: background .2s ease; }
.grid tbody tr:hover { background: var(--surface-2); }
/* Needs table: a component we genuinely cannot source (short > 0). The whole
   row reads red so the buy list is scannable without reading the last column.
   The left bar carries the same meaning without relying on colour alone. */
.grid tbody tr.row-short { background: var(--bad-soft); }
/* a component left out of a build on purpose: still a row, marked (2026-10-13) */
.grid tbody tr.row-omitted { background: var(--bad-soft); }
.grid tbody tr.row-omitted td.lineno { box-shadow: inset 3px 0 0 var(--bad); color: var(--bad); font-weight: 600; }
.grid tbody tr.row-short:hover { background: var(--bad-soft); }
.grid tbody tr.row-short td:first-child { box-shadow: inset 3px 0 0 var(--bad); }
.grid td.num, .grid th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* A header aligns with its own data: left by default, right on .num (above).
   Centre a column only when its data is centred too - put .center on both the
   th and its tds, never on the header alone. */
.grid th.center, .grid td.center { text-align: center; }
.grid th.sortable { user-select: none; cursor: pointer; }
.grid th.sortable:hover { color: var(--fg); }
.grid th .sort-ind { color: var(--accent); font-size: .8em; }
.grid .desc { white-space: normal; min-width: 14rem; }
/* a second identity under the primary one in the same cell (e.g. the legacy
   InvenTree number under the minted serial) - keeps wide tables one screen
   wide instead of buying a whole column for a secondary id. */
.grid .sub-id { display: block; font-size: .78rem; opacity: .8; }
/* Category: let a multi-word name wrap instead of forcing the table wider.
   7.5rem clears the longest single-word category ("Alcoholimetros", 14 chars)
   so only the multi-word ones break - which is the point. */
.grid .cat { white-space: normal; max-width: 7.5rem; }
/* Warehouse: every name in use is two words ("Bodega Nettz", "Cuarentena EBS").
   5.5rem clears the longest single word ("Cuarentena") so the second word drops
   to its own line instead of the column carrying the full name's width. */
.grid .wh { white-space: normal; max-width: 5.5rem; }

/* ---- badges / pills (erp-badge shape) ---- */
/* ---- badges ----
   Every badge is a solid fill with WHITE text (Patito, sitewide) — the ERP's own
   .estado-badge rule. The fills are its estado_visita palette plus its fallback
   grey, so the soft tints that used to live here are gone: white on a pale tint
   is invisible, so a tint had to become a fill for the white to mean anything.

   Contrast: white clears WCAG AA (4.5:1) only on --badge-calib (4.7). On the
   others it lands between 2.1 and 3.8, i.e. below AA and mostly below the 3:1
   large-text floor too. Recorded, not hidden — this is a deliberate call to
   match the ERP, whose badges have exactly the same problem.

   One deliberate exception: .nosource, which is the absence of a value, drawn
   as a dashed outline rather than a fill — white on nothing is nothing. */
.pill {
  display: inline-flex; align-items: center; font-size: .75rem; padding: 2px 8px;
  border-radius: var(--radius-badge); font-weight: 600;
  border: 1px solid transparent; color: #ffffff; background: var(--badge-gone);
}
/* stock states — the ERP's estado_visita colours */
.pill.avail    { background: var(--badge-avail); }
.pill.progress { background: var(--badge-progress); }
.pill.calib    { background: var(--badge-calib); }
.pill.out      { background: var(--badge-out); }
.pill.bad      { background: var(--badge-bad); }
.pill.gone     { background: var(--badge-gone); }
/* generic labels, mapped onto the same palette */
.pill.none     { background: var(--badge-gone); }
.pill.info     { background: var(--badge-progress); }
.pill.unknown  { background: var(--badge-bad); }
.pill.lot      { background: var(--badge-out); }
.pill.other    { background: var(--badge-out); }
.pill.warn     { background: var(--badge-out); }
.pill.tomove   { background: var(--badge-out); }
/* provenance: imported from InvenTree, not made in NETTZINV. No fill (Patito) —
   it annotates a row rather than classifying it, so it stays quiet. Like
   .nosource, that makes it an exception to the white-on-solid rule: white on
   nothing is nothing, so the text is muted instead. */
.pill.inventree { gap: .3rem; background: transparent; color: var(--muted);
                  border: 1px solid var(--rule); vertical-align: middle; }
.pill.inventree i { font-size: .65rem; }
/* a unit that came out of quarantine repaired: not new (observation 5, 2026-09-22) */
.pill.repaired   { gap: .3rem; background: var(--badge-out); vertical-align: middle; margin-left: .35rem; }
.pill.repaired i { font-size: .65rem; }
/* a unit's old number was written on it by hand, not an InvenTree serial (observation 8) */
.pill.fieldno { background: var(--badge-progress); font-size: .66rem; padding: .05rem .4rem; vertical-align: middle; }
.pill.serial     { background: var(--badge-serial); }
/* Bulk is teal. Scoped to .track on purpose: the same `none` class also marks
   "Part" in the Kind column and plain link pills, and those stay grey. */
.pill.track.none { background: var(--badge-bulk); }
/* Product had been borrowing the Available green; it is its own thing now. */
.pill.product { background: var(--badge-product); }
/* Kind and Tracking badges sit in a column of their own, so inside a grid they
   all take one width — a ragged column edge reads as a mistake. Outside a table
   (a detail-page header) they size to their text as normal.

   The width is set by the LONGEST label in either language, and nothing more:
   min-width never clips (an over-long pill simply grows), so the only symptom of
   too small a value is a ragged column, and the only cost of too large a value
   is dead space inside every badge. Tracking's longest short label is "A granel"
   (es, bulk) — 4.75rem was ~12px past what it needs, which "Lote" and "Serie"
   then had to carry as padding. Tightened to fit the word, with the side padding
   trimmed to match. Raise this ONE number if a longer label ever appears. */
.kindcell { display: flex; flex-wrap: wrap; gap: .25rem; }
.grid td .pill.track, .grid td .pill.kind { display: flex; justify-content: center; }
.grid td .pill.track { min-width: 4.1rem; padding-left: .45rem; padding-right: .45rem; }
/* State badges all take one width so the column reads as a column. Sized for
   the longest label in either language — "En ensamblaje" (13 chars). The old
   parenthetical explanations moved to the home page's state table instead. */
.pill.state { min-width: 6.25rem; justify-content: center; }
/* Dispatch "Tipo" column: Customer / Technician alternate down it, so they take
   one width. Sized for the longest label in either language ("Technician"). */
.pill.dtype { min-width: 5.5rem; justify-content: center; }
.grid td .pill.kind  { min-width: 5.5rem; }

/* EXCEPTION — not a fill: the note is empty because the legacy source held no
   description. Dashed + muted reads as "deliberately absent". */
/* EXCEPTION — not a fill, and a single letter rather than a word. Consumable is
   a sparse planning flag (4 of 29 items), not a member of the Kind taxonomy, so
   it rides beside the component NAME as the smallest mark that can carry it: a
   violet "C" in a circle. Square box + 50% radius so one letter reads as a
   deliberate mark, not a truncated word. The title attribute is the real label —
   do not remove it. Uses --consumable (not --badge-consumable) because that
   token is the theme-aware one: violet as TEXT, #7c3aed light / #c4b5fd dark. */
.pill.consumable { color: var(--consumable); border-color: var(--consumable);
                   background: transparent; font-size: .6rem; font-weight: 700;
                   padding: 0; width: 1.05rem; height: 1.05rem; line-height: 1;
                   justify-content: center; border-radius: 50%;
                   vertical-align: middle; position: relative; top: -1px; }
/* The gap exists only to separate the mark from a name it follows, and is the
   mark's own margin rather than a flex gap so it stays glued to the last
   character however the name wraps. Standing alone in a column of its own (the
   Categories page) it needs no gap, so key it off the name link. */
a + .pill.consumable { margin-left: .35rem; }

/* Returning from a unit editor (firmware / origin) lands on the row that was
   just edited - see the #u- / #bu- / #du- / #bc- / #dl- anchors in the row
   templates. Without the flash you land in the right place but cannot tell
   which line you just changed. */
tr[id]:target { scroll-margin-top: 5.5rem; }
tr[id]:target > td { background: var(--info-soft); }
tr[id]:target > td:first-child { box-shadow: inset 3px 0 0 var(--info); }

.pill.nosource { color: var(--muted); border: 1px dashed var(--rule); background: transparent; }
/* role badges on the Users page: one width for all three, text centred */
.pill.role { min-width: 7.5rem; justify-content: center; }
/* status badges: one width for every state word, so a Status column reads as a
   column rather than a ragged edge. Sized for the longest label in either
   language ("Borrador", "Received"). Only status pills take this — a tracking
   or provenance pill still hugs its text. */
.pill.st { min-width: 6.5rem; justify-content: center; }

/* "Can build" on the home page: one card per recipe (2026-10-02; rows before) */
/* 'How to read this table' on the home page (observation 7, 2026-09-22) */
details.help { font-size: .85rem; color: var(--muted); }
details.help summary { cursor: pointer; list-style: none; color: var(--brand, var(--accent, inherit)); text-decoration: underline dotted; }
details.help summary::-webkit-details-marker { display: none; }
details.help ul { margin: .4rem 0 .2rem; padding-left: 1.1rem; max-width: 46rem; }
details.help li { margin: .2rem 0; }
/* 2026-10-02: cards (Patito chose layout B). A card whose detail is open spans the
   whole row, so the 6-column table has room. */
.capgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: .9rem; margin: 0 0 1rem; }
.capcard { border: 1px solid var(--rule); border-radius: var(--radius-lg); padding: .9rem 1rem; background: var(--surface, var(--bg)); }
.capcard:has(details[open]) { grid-column: 1 / -1; }
.capcard-head { display: flex; align-items: center; gap: .8rem; }
.capcard .cap-n { font-size: 1.5rem; font-weight: 800; line-height: 1; min-width: 2.6rem; text-align: center; }
.capcard .cap-n.good { color: var(--good); }
.capcard .cap-n.bad { color: var(--bad); }
.capcard-sub { font-size: .78rem; color: var(--muted); margin-top: .6rem; }
.capcard-buy { list-style: none; margin: .35rem 0 0; padding: 0; }
.capcard-buy li { display: flex; justify-content: space-between; gap: .8rem; padding: .28rem 0; border-top: 1px solid var(--rule); font-size: .85rem; }
.capcard-buy li a { color: inherit; text-decoration: none; }
.capcard-buy li a:hover { text-decoration: underline; }
.capcard-buy li b { color: var(--bad); }
.capcard-detail { margin-top: .5rem; font-size: .82rem; }
.capcard-detail summary { cursor: pointer; list-style: none; color: var(--brand, var(--accent, inherit)); }
.capcard-detail summary::-webkit-details-marker { display: none; }
.capcard tr.cap-bind td { background: var(--warn-soft); }
/* home text sizes = the other pages (measured, Patito 2026-10-02) */
.homesec h2 .sub { font-size: .95rem; }
.homelist li { font-size: .875rem; }
.capcard strong { font-size: .875rem; }
/* button tooltips drawn by static/tooltip.js, above the button (2026-10-02) */
.tipbox { position: fixed; z-index: 9999; display: none; width: max-content; max-width: 22rem; padding: .3rem .6rem;
  border-radius: 6px; background: #1f2937; color: #fff; font: 500 .8rem/1.3 var(--font);
  pointer-events: none; box-shadow: 0 4px 14px rgba(0,0,0,.25); }
:root[data-theme="dark"] .tipbox { background: #f3f4f6; color: #111827; }
/* date boxes as YYYY-MM-DD text + calendar button (static/dateinput.js, 2026-10-02) */
.datebox { position: relative; display: inline-flex; align-items: stretch; gap: .3rem; max-width: 100%; }
.datebox > input { min-width: 8.5em; font-variant-numeric: tabular-nums; }
.datebox-pick { display: inline-flex; align-items: center; justify-content: center; padding: 0 .6rem;
  border: 1px solid var(--rule); border-radius: 8px; background: var(--surface, var(--bg)); color: var(--muted); cursor: pointer; }
.datebox-pick:hover, .datebox-pick:focus-visible { color: var(--fg); border-color: var(--brand-yellow); }
.datebox-native { position: absolute; right: 0; bottom: 0; width: 1px; height: 1px; opacity: 0; pointer-events: none; border: 0; padding: 0; }
.capkind { font-size: .75rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: .9rem 0 .4rem; }

/* Filter bar for a table inside a page (erp-filters controls). */
.filterbar { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin: .6rem 0 .3rem; }
.filterbar input, .filterbar select {
  padding: .5rem 1rem; border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--panel); color: var(--fg); font: inherit; font-size: .9rem; }
.filterbar input:focus, .filterbar select:focus { outline: none; border-color: var(--brand-yellow); box-shadow: 0 0 0 2px var(--focus-ring); }
.filterbar input[type="search"] { flex: 1; min-width: 11rem; }
.filterbar .count { color: var(--muted); font-size: .85rem; margin-left: auto; }
.nosource-note { display: flex; gap: .5rem; align-items: baseline; margin-top: -.4rem;
  padding: .5rem .7rem; border: 1px dashed var(--rule); border-radius: var(--radius);
  color: var(--muted); font-size: .85rem; background: transparent; }

/* ---- buttons (erp-btn) ----
   Every button here is FILLED. The ERP ships an .outline variant (transparent
   body, coloured contour) and uses it for most secondary actions, but for the
   Nettz tenant that contour and its text are #fde100 — yellow on white, which
   is unreadable; this file already had to swap in #726500 to make it legible.
   Patito rejected the look outright, so we follow what the ERP does whenever it
   styles a button properly instead of reaching for .outline — a solid or tinted
   fill (its own delete button: .btn-del { background:#fee2e2; color:#991b1b }).

   primary   .btn           solid brand yellow, dark text   — commits
   secondary .btn.secondary solid action blue, white text    — everything else
   write-off .btn.warn      solid burnt amber, white text    — removes for good
   danger    .btn.danger    solid red, white text            — broken / destroys */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .5rem 1.1rem; border-radius: var(--radius);
  background: var(--brand-yellow); color: var(--on-brand);
  font: 600 1rem/1.2 var(--font); border: 1px solid transparent; cursor: pointer;
  transition: all .2s ease;
}
.btn:hover { text-decoration: none; background: var(--brand-hover); }

/* Secondary: solid action blue, white text — the ERP's own row-action colour.
   `.ghost` is the old class name kept as an alias so the ~110 existing call
   sites keep working; it is no longer transparent and no longer a contour. */
.btn.secondary, .btn.ghost {
  background: var(--btn-2); color: var(--on-btn); border-color: transparent;
}
.btn.secondary:hover, .btn.ghost:hover {
  background: var(--btn-2-hover); color: var(--on-btn);
}

/* Write-off: solid burnt amber, white text. Sits between blue and red — the
   unit is being removed from inventory for good (mark lost -> WRITTEN_OFF), but
   nothing is broken, so it is not the defect red. White on #b45309 is 5.0:1;
   the dark theme uses #c2410c (5.2:1) rather than a lighter amber, which white
   text would not survive. */
.btn.warn {
  background: var(--btn-warn); color: var(--on-btn); border-color: transparent;
}
.btn.warn:hover { background: var(--btn-warn-hover); color: var(--on-btn); }

/* Destructive: solid red, white text (the ERP's .btn-delete). */
.btn.danger, .btn.ghost.danger {
  background: var(--btn-danger); color: var(--on-btn);
  border-color: transparent; font-weight: 700;
}
.btn.danger:hover, .btn.ghost.danger:hover {
  background: var(--btn-danger-hover); color: var(--on-btn);
}

/* Admin-only escape hatch (the ⚙ fix-state button): black, so it never reads as
   an ordinary action. This is the ERP's own --secondary-color for Nettz. */
.btn.admin { background: var(--btn-admin); color: var(--on-btn); border-color: transparent; }
.btn.admin:hover { background: var(--btn-admin-hover); color: var(--on-btn); }

.btn.small { padding: .5rem 1rem; font-size: .75rem; }
/* Buttons that alternate down one table column take a single width, so the
   column has a straight edge instead of a ragged one. Sized for the longest
   label in either language ("Calibrate" / "Calibrar"). */
.btn.rowbtn { min-width: 6rem; }

/* Row-action cell (stock list). One width for every text button in the column
   and one square for the icon-only ones, so the cluster lines up down the page
   whichever optional actions a row happens to offer. 8.5rem is sized to the
   longest label in either language ("Marcar perdido"), not guessed. */
.rowacts { display: flex; align-items: center; gap: .4rem; flex-wrap: nowrap; }
.rowacts .btn { min-width: 8.5rem; white-space: nowrap; }
.rowacts .btn.icon { min-width: 2.4rem; width: 2.4rem; padding-left: 0; padding-right: 0; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* ---- forms (erp modal .form-group) ---- */
form.stack { display: grid; gap: 1.25rem; max-width: 36rem; }
form.stack label { display: block; font-weight: 500; font-size: 1rem; color: var(--muted); margin-bottom: .5rem; }
form.stack .hint { color: var(--muted); font-weight: 400; font-size: .8rem; }
form.stack input, form.stack select, form.stack textarea {
  width: 100%; padding: 1rem; border: 1px solid var(--rule);
  border-radius: var(--radius); background: var(--panel); color: var(--fg); font-size: 1rem; font-family: var(--font);
}
form.stack textarea { min-height: 100px; resize: vertical; }
form.stack input:focus, form.stack select:focus, form.stack textarea:focus { outline: none; border-color: var(--brand-yellow); box-shadow: 0 0 0 2px var(--focus-ring); }
form.stack input:disabled, form.stack select:disabled { background: var(--surface-2); color: var(--muted); }
/* checkboxes/radios are not full-width text fields */
form.stack input[type=checkbox], form.stack input[type=radio] { width: auto; margin: 0; accent-color: var(--brand-yellow); }
.checkrow { display: flex; align-items: center; gap: .5rem; font-weight: 600; cursor: pointer; }
.checkrow input { flex: 0 0 auto; }
.formrow { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }

/* No spinner arrows on number inputs */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; appearance: textfield; }

/* ---- messages ---- */
.msg { padding: .75rem 1rem; border-radius: var(--radius); margin-bottom: .5rem; font-size: .95rem; font-weight: 600; }
.msg.success { background: var(--good-soft); color: var(--good); }
.msg.error { background: var(--bad-soft); color: var(--bad); }
.msg.warning { background: var(--warn-soft); color: var(--warn); }
.msg.info { background: var(--accent-soft); color: var(--primary-text); }

/* ---- misc ---- */
/* recipe / build lines: a line number and the component's picture (2026-10-13) */
.grid th.lineno, .grid td.lineno { width: 1%; white-space: nowrap; text-align: right; color: var(--muted);
  font-variant-numeric: tabular-nums; padding-right: .3rem; }
.partcell { display: flex; align-items: center; gap: .65rem; }
.linethumb { display: block; flex: 0 0 auto; width: 44px; height: 44px; object-fit: contain;
  border-radius: 6px; border: 1px solid var(--rule); background: var(--panel); }
.linethumb.none { background: var(--surface-2); }
.linethumb-a { flex: 0 0 auto; line-height: 0; }

/* image thumbnails */
.thumb { width: 34px; height: 34px; object-fit: cover; border-radius: 6px;
  border: 1px solid var(--rule); vertical-align: middle; margin-right: .5rem; background: var(--surface-2); }
.itemimg { max-width: 180px; max-height: 180px; object-fit: contain; border-radius: var(--radius);
  border: 1px solid var(--rule); background: var(--panel); padding: .3rem; }

.empty { color: var(--muted); padding: 2rem; text-align: center; }
.foot { color: var(--muted); font-size: .8rem; text-align: center; padding: 1rem 1rem 1.5rem; }
.cost-unknown { color: var(--bad); font-size: .82rem; font-weight: 600; }
.muted { color: var(--muted); }

/* Prev/next document navigation (receipt + dispatch detail pages). */
.docnav { display: inline-flex; gap: .5rem; align-items: center; margin-right: .4rem; }

/* ================= sign-in page (login.component, Nettz palette) ================= */
/* The ERP puts a stock beach photo behind this card; Nettz black is used instead. */
body.auth { background: radial-gradient(1100px 560px at 50% -12%, rgba(253,225,0,.10), transparent 62%), var(--brand-black); }
body.auth .messages { max-width: 34rem; margin: 1rem auto 0; }
.auth-wrap { min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: 1.5rem; }
.auth-card {
  width: 100%; max-width: 34rem; display: flex; flex-direction: column; align-items: center; gap: 2.5rem;
  padding: 3rem 1.5rem 2rem; border-radius: var(--radius);
  background: #262626; color: #fff; box-shadow: 2px 2px 5px rgba(0,0,0,.15);
}
.auth-head { display: flex; flex-direction: column; align-items: center; gap: .9rem; }
.auth-head .logo-text { font-size: 2rem; letter-spacing: 3px; font-weight: 800; color: #fff; }
.auth-head .logo-text span { color: var(--brand-yellow); }
.auth-form { width: 100%; max-width: 20rem; display: flex; flex-direction: column; gap: 1.5rem; }
.auth-form label { display: block; margin-bottom: .5rem; color: #cfcfcf; font-size: 1rem; font-weight: 400; }
.auth-form input[type=text], .auth-form input[type=password], .auth-form input[type=email] {
  width: 100%; padding: 1rem; border: 2px solid rgba(255,255,255,.35); border-radius: var(--radius);
  background: transparent; color: #fff; font: 500 1rem var(--font); caret-color: var(--brand-yellow);
  outline: none; transition: .3s;
}
.auth-form input:focus { border-color: var(--brand-yellow); background: rgba(255,255,255,.04); }
.login-btn {
  display: flex; align-items: center; justify-content: center; gap: .5rem; width: 100%;
  padding: 1.5rem 2rem; border: 0; border-radius: var(--radius); cursor: pointer;
  background: linear-gradient(135deg, #fde100 0%, #ffe94d 100%); color: #1a1a1a;
  font: 700 1.25rem var(--font); box-shadow: 0 4px 15px rgba(253,225,0,.35); transition: all .3s ease;
}
.login-btn:hover { background: linear-gradient(135deg, #e6cc00 0%, #fde100 100%); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(253,225,0,.45); }
.auth-error {
  display: flex; align-items: center; gap: .5rem; padding: 1rem; border-radius: var(--radius);
  background: #f44336; color: #fff; font-size: 1rem; animation: auth-shake .82s cubic-bezier(.36,.07,.19,.97) both;
}
.auth-foot { display: flex; align-items: center; gap: .75rem; color: #cfcfcf; font-size: .85rem; }
.auth-foot form { margin: 0; }
.auth-foot button { background: none; border: 1px solid rgba(255,255,255,.25); color: #cfcfcf; border-radius: var(--radius-sm); padding: .2rem .55rem; font: 600 .8rem var(--font); cursor: pointer; }
.auth-foot button:hover { color: var(--brand-yellow); border-color: var(--brand-yellow); }
@keyframes auth-shake {
  10%, 90% { transform: translate3d(-1px, 0, 0); }
  20%, 80% { transform: translate3d(2px, 0, 0); }
  30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
  40%, 60% { transform: translate3d(4px, 0, 0); }
}

/* ================= drop-down lists with an embedded filter (static/combo.js) ================= */
/* The native <select> stays inside .combo, invisible, and keeps the value; the
   text box on top shows the choice and filters the options as you type. */
.combo { position: relative; display: inline-block; vertical-align: middle; max-width: 100%; }
.combo.block { display: block; width: 100%; }
.combo > select.combo-native {
  position: absolute !important; inset: 0 !important; width: 100% !important; height: 100% !important;
  margin: 0 !important; opacity: 0 !important; pointer-events: none !important;
}
.combo-input {
  width: 100%; margin: 0; cursor: pointer; text-overflow: ellipsis;
  background: var(--panel); color: var(--fg); border-color: var(--rule);
}
.combo-input:focus { cursor: text; outline: none; border-color: var(--brand-yellow) !important; box-shadow: 0 0 0 2px var(--focus-ring); }
.combo-input::placeholder { color: var(--muted); opacity: .8; }
.combo-input:disabled { opacity: .55; cursor: not-allowed; background: var(--surface-2); }
.combo.invalid .combo-input { border-color: var(--bad) !important; }
.combo-caret {
  position: absolute; right: .7rem; top: 50%; transform: translateY(-50%);
  color: var(--muted); font-size: .7rem; pointer-events: none; transition: transform .2s ease;
}
.combo.open .combo-caret { transform: translateY(-50%) rotate(180deg); color: var(--primary-text); }
.combo-list {
  position: fixed; z-index: 1000; max-height: 18rem; overflow-y: auto;
  margin: 0; padding: .3rem; list-style: none;
  background: var(--panel); color: var(--fg); border: 1px solid var(--rule);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  font: 400 .9rem/1.35 var(--font);
}
.combo-opt { padding: .45rem .7rem; border-radius: 6px; cursor: pointer; }
.combo-opt.active { background: var(--accent-soft); }
.combo-opt[aria-selected="true"] { font-weight: 700; }
.combo-opt[aria-selected="true"]::after { content: "¹3"; float: right; margin-left: .75rem; color: var(--primary-text); }
.combo-opt.disabled { color: var(--muted); cursor: not-allowed; }
.combo-opt mark { background: rgba(253,225,0,.5); color: inherit; border-radius: 2px; padding: 0; }
:root[data-theme="dark"] .combo-opt mark { background: rgba(253,225,0,.3); }
.combo-group {
  padding: .55rem .7rem .2rem; color: var(--muted);
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
}
.combo-empty { padding: .6rem .7rem; color: var(--muted); font-style: italic; }

/* legacy notes carried over from InvenTree: a small marker in dense tables
   (hover for the text), a full block on the unit's own pages. */
.lnote-mark { display: inline-flex; align-items: baseline; gap: .1rem; margin-left: .35rem;
  font-size: .72rem; color: var(--muted); cursor: help; vertical-align: baseline; }
.lnote-mark .n { font-weight: 700; }
.lnotes { margin: .6rem 0 1.2rem; padding: .7rem .9rem; border: 1px dashed var(--rule);
  border-radius: var(--radius); background: transparent; }
.lnotes-h { font-size: .8rem; font-weight: 700; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; margin-bottom: .4rem; }
.lnotes-h .muted { text-transform: none; letter-spacing: 0; font-weight: 400; }
.lnote { padding: .35rem 0; border-top: 1px solid var(--rule); }
.lnote:first-of-type { border-top: 0; }
.lnote-meta { display: block; font-size: .74rem; color: var(--muted); }
.lnote-text { margin: .1rem 0 0; font: .86rem/1.45 inherit; white-space: pre-wrap; word-break: break-word; }
