/* niftyMRP UI — industrial workshop treatment.
   Tokens first: light is the source theme, dark re-derives every token.
   Type: Archivo (variable) carries the UI; IBM Plex Mono carries the data
   (part numbers, order numbers, quantities). Accent: viridian; copper is a
   rare highlight (brand, key figures). Semantic ok/warn/crit are separate. */

@font-face {
  font-family: "Archivo";
  src: url("../fonts/Archivo-var.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("../fonts/PlexMono-400.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("../fonts/PlexMono-600.woff2") format("woff2");
  font-weight: 600;
  font-display: swap;
}

:root {
  /* Tells the UA which palette to render native chrome (scrollbars, date
     pickers, focus rings) in — without it a light-forced page on a dark OS
     gets dark native controls. Re-declared in every theme block below. */
  color-scheme: light;

  --ground: #f1f3f0;
  --panel: #fcfdfc;
  --panel-raised: #ffffff;
  --ink: #1d2823;
  --ink-strong: #101815;
  --muted: #64716a;
  --line: #dfe4df;
  --line-soft: #e9ede9;

  --accent: #177355;
  --accent-strong: #0f5c43;
  --accent-ink: #ffffff;
  --accent-soft: #e2efe9;
  --copper: #af6234;

  --ok: #1c7a3d;      --ok-soft: #e2f1e6;
  --warn: #a3660a;    --warn-soft: #f8eeda;
  --crit: #b3362b;    --crit-soft: #f9e8e5;
  --info: #23617e;    --info-soft: #e3eef4;
  --violet: #5b4a9e;  --violet-soft: #ebe7f6;

  --side-bg: #15201b;
  --side-ink: #b9c6be;
  --side-dim: #6d7f75;
  --side-active: #1e2f27;
  --side-line: #24332b;

  --shadow: 0 1px 2px rgba(16, 24, 21, .05), 0 4px 14px rgba(16, 24, 21, .04);
  --shadow-pop: 0 6px 24px rgba(16, 24, 21, .16);
  --radius: 6px;
  --sidebar-w: 212px;
  /* Fluid page gutter: 16px on a phone, 28px once there's room for it. */
  --gutter: clamp(16px, 4vw, 28px);
  --tap: 44px;          /* minimum comfortable touch target */
  --tabbar-h: 0px;      /* becomes real only on phones (see Responsive) */

  --sans: "Archivo", -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Plex Mono", ui-monospace, "Cascadia Mono", monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --ground: #131816;
    --panel: #1a211d;
    --panel-raised: #1f2823;
    --ink: #d7ded9;
    --ink-strong: #f0f4f1;
    --muted: #8a968f;
    --line: #2b352f;
    --line-soft: #242d28;

    --accent: #3fae87;
    --accent-strong: #55c29b;
    --accent-ink: #0c1511;
    --accent-soft: #1d3a2f;
    --copper: #d08d5c;

    --ok: #52b475;      --ok-soft: #1c3325;
    --warn: #d9a04b;    --warn-soft: #382c17;
    --crit: #e07a6e;    --crit-soft: #3d221e;
    --info: #6aa9c4;    --info-soft: #1c2f38;
    --violet: #a394d8;  --violet-soft: #2b2540;

    --side-bg: #0e1512;
    --side-ink: #a7b4ac;
    --side-dim: #5d6b63;
    --side-active: #1a2620;
    --side-line: #1d2823;

    --shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 4px 14px rgba(0, 0, 0, .18);
    --shadow-pop: 0 8px 28px rgba(0, 0, 0, .5);
  }
}
/* The viewer's explicit toggle wins over the OS preference, both directions. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --ground: #131816; --panel: #1a211d; --panel-raised: #1f2823;
  --ink: #d7ded9; --ink-strong: #f0f4f1; --muted: #8a968f;
  --line: #2b352f; --line-soft: #242d28;
  --accent: #3fae87; --accent-strong: #55c29b; --accent-ink: #0c1511;
  --accent-soft: #1d3a2f; --copper: #d08d5c;
  --ok: #52b475; --ok-soft: #1c3325; --warn: #d9a04b; --warn-soft: #382c17;
  --crit: #e07a6e; --crit-soft: #3d221e; --info: #6aa9c4; --info-soft: #1c2f38;
  --violet: #a394d8; --violet-soft: #2b2540;
  --side-bg: #0e1512; --side-ink: #a7b4ac; --side-dim: #5d6b63;
  --side-active: #1a2620; --side-line: #1d2823;
  --shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 4px 14px rgba(0, 0, 0, .18);
  --shadow-pop: 0 8px 28px rgba(0, 0, 0, .5);
}
:root[data-theme="light"] {
  color-scheme: light;
  --ground: #f1f3f0; --panel: #fcfdfc; --panel-raised: #ffffff;
  --ink: #1d2823; --ink-strong: #101815; --muted: #64716a;
  --line: #dfe4df; --line-soft: #e9ede9;
  --accent: #177355; --accent-strong: #0f5c43; --accent-ink: #ffffff;
  --accent-soft: #e2efe9; --copper: #af6234;
  --ok: #1c7a3d; --ok-soft: #e2f1e6; --warn: #a3660a; --warn-soft: #f8eeda;
  --crit: #b3362b; --crit-soft: #f9e8e5; --info: #23617e; --info-soft: #e3eef4;
  --violet: #5b4a9e; --violet-soft: #ebe7f6;
  --side-bg: #15201b; --side-ink: #b9c6be; --side-dim: #6d7f75;
  --side-active: #1e2f27; --side-line: #24332b;
  --shadow: 0 1px 2px rgba(16, 24, 21, .05), 0 4px 14px rgba(16, 24, 21, .04);
  --shadow-pop: 0 6px 24px rgba(16, 24, 21, .16);
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--ground); color: var(--ink);
  font: 400 14px/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-strong); text-decoration: underline; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* ---- Shell ---- */
.layout { display: flex; min-height: 100vh; min-height: 100dvh; }
.sidebar {
  width: var(--sidebar-w); flex-shrink: 0; background: var(--side-bg); color: var(--side-ink);
  display: flex; flex-direction: column; position: sticky; top: 0;
  height: 100vh; height: 100dvh;
}
.sidebar .brand {
  display: block; padding: 18px 16px 14px; color: #fff; font-size: 17px; font-weight: 800;
  letter-spacing: .14em; font-family: var(--mono);
}
.sidebar .brand:hover { text-decoration: none; }
.sidebar .brand::after { content: "_"; color: var(--copper); }
.sidebar nav { flex: 1; overflow-y: auto; padding-bottom: 10px; }
.sidebar nav a {
  display: block; padding: 7px 16px 7px 14px; color: var(--side-ink);
  border-left: 2px solid transparent; font-weight: 500;
}
.sidebar nav a:hover { background: var(--side-active); color: #fff; text-decoration: none; }
.sidebar nav a.active {
  background: var(--side-active); color: #fff; border-left-color: var(--copper);
}
.sidebar .navgroup {
  padding: 16px 16px 5px; font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .16em; color: var(--side-dim);
}
.sidebar .userbox {
  padding: 12px 16px; border-top: 1px solid var(--side-line); font-size: 12px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.sidebar .userbox button {
  background: none; border: none; color: var(--side-dim); cursor: pointer;
  padding: 0; font: inherit;
}
.sidebar .userbox button:hover { color: #fff; }

.main { flex: 1; min-width: 0; }
.topbar {
  display: flex; align-items: center; gap: 16px; padding: 10px 28px;
  background: var(--panel); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 50;
}
.theme-toggle {
  border: 1px solid var(--line); background: var(--panel);
  color: var(--muted); border-radius: 999px; width: 32px; height: 32px;
  padding: 0; cursor: pointer; font-size: 15px; line-height: 1;
  flex: none;   /* never let the flex row shrink this into a sliver */
}
.theme-toggle:hover { color: var(--ink); border-color: var(--muted); }
.content {
  padding: var(--gutter) var(--gutter) calc(60px + var(--tabbar-h));
  max-width: 1280px;
}

/* ---- Global search ---- */
.gsearch { position: relative; flex: 1; max-width: 460px; }
.gsearch input {
  width: 100%; padding: 8px 12px 8px 34px; border: 1px solid var(--line);
  border-radius: 999px; background: var(--ground); color: var(--ink); font: inherit;
}
.gsearch::before {
  content: "⌕"; position: absolute; left: 12px; top: 5px;
  font-size: 17px; color: var(--muted);
}
.gsearch .results {
  position: absolute; top: 42px; left: 0; right: 0; background: var(--panel-raised);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-pop);
  max-height: 70vh; overflow-y: auto; display: none;
}
.gsearch .results.open { display: block; }
.gsearch .results .group {
  padding: 8px 12px 3px; font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .14em; color: var(--muted);
}
.gsearch .results a { display: block; padding: 7px 12px; color: var(--ink); }
.gsearch .results a:hover, .gsearch .results a.sel {
  background: var(--accent-soft); text-decoration: none;
}
.gsearch .results .pn { font-weight: 600; font-family: var(--mono); font-size: 13px; }
.gsearch .results .sub { color: var(--muted); font-size: 12px; }

/* ---- Headings, panels ---- */
h1 {
  font-size: 21px; font-weight: 700; letter-spacing: -.01em; color: var(--ink-strong);
  margin: 0 0 18px; text-wrap: balance;
}
h2 {
  font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .14em;
  color: var(--muted); margin: 0 0 12px;
}
.pagehead { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
.pagehead h1, .pagehead h2 { margin: 0; }
.pagehead .spacer { flex: 1; }
.panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px; margin-bottom: 20px; box-shadow: var(--shadow);
}
.panel > h2:first-child { margin-top: 0; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
@media (max-width: 899px) { .grid2 { grid-template-columns: 1fr; } }

/* ---- Bulk-action bar above a selectable table ---- */
.tablebar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--line-soft);
}
.tablebar .spacer { flex: 1; }
.tablebar [data-sel-count] { font-size: 13px; }
.tablebar button[disabled] { opacity: .5; cursor: not-allowed; }

/* "Select all N matching" — appears beside the count once a whole page is ticked. */
.tablebar .sel-banner:empty { display: none; }
.linkish {
  padding: 0; border: 0; background: none; color: var(--accent);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.linkish:hover { background: none; color: var(--accent-strong); text-decoration: underline; }

/* ---- List filters (listing.py, templates/_listing.html) ----
   Each column filters itself from a row under its heading: a text box, a
   dropdown of checkboxes, a date or number range. Conditions that belong to no
   one column live in the slim bar above the table, with saved views. */
/* The toolbar: bulk actions left, Filters + saved views right, and a second
   line of chips naming the active filters while the filters are folded. */
.tools-right { margin-left: auto; display: flex; gap: 8px; align-items: center; }
/* A <label> (for the toggle) dressed as its neighbour buttons, not as a field label. */
.listtools .filters-btn {
  margin: 0; display: inline-flex; align-items: center;
  font: 600 12px/1.4 var(--sans); text-transform: none; letter-spacing: 0; color: var(--ink);
}
.filters-btn .fcount { color: var(--accent); white-space: pre; }
.filters-toggle:checked ~ .listtools .filters-btn {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }
.fchips { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.fchips:not(:has(a)) { display: none; }
/* No bulk actions to sit beside: the chips take the left of the one line. */
.listtools:not(:has([data-sel-count])) .fchips { order: -1; flex: 1 1 auto; }
.filters-toggle:checked ~ .listtools .chip:not(.chip-link),
.filters-toggle:checked ~ .listtools .chip-clear { display: none; }

/* The filters themselves, folded until the toggle opens them. */
.listbar {
  display: none; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--line-soft);
}
.filters-toggle:checked ~ .listbar { display: flex; }
.filters-toggle:checked ~ .listbar:not(:has(.fin, .ftoggle, .chip-clear)) { display: none; }
#lf { display: contents; }
.js .fapply { display: none; }
.listbar .fin { width: auto; max-width: 220px; }
.listbar input.fin { width: 190px; }
.listbar .fpop { min-width: 140px; }

/* An on/off condition reads as a pill you press, not a form checkbox. */
.ftoggle {
  display: inline-flex; align-items: center; gap: 7px; margin: 0; cursor: pointer;
  padding: 5px 12px 5px 9px; border: 1px solid var(--line); border-radius: 999px;
  font-size: 13px; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink);
  background: var(--panel-raised);
}
.ftoggle.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); }

/* The filter row. Controls take their column's width rather than setting it,
   so the headings, not the inputs, decide the layout. */
/* Above the headings: the heading row's rule still closes the header, so the
   filters need no line of their own. */
thead tr.filter-row { display: none; }
.filters-toggle:checked ~ * thead tr.filter-row { display: table-row; }
thead tr.filter-row td {
  padding: 0 5px 6px; vertical-align: bottom; border-bottom: 0;
}
thead tr.filter-row td:first-child { padding-left: 10px; }
.fin {
  display: block; width: 100%; min-width: 0; height: 30px; padding: 4px 8px;
  font: inherit; font-size: 12.5px; text-transform: none; letter-spacing: 0;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel-raised); color: var(--ink);
}
input.fin::placeholder { color: var(--muted); opacity: .7; }
.fin.on { border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  background: color-mix(in srgb, var(--accent-soft) 60%, var(--panel-raised)); }
.fpop { position: relative; min-width: 0; }
summary.fsum {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 4px;
  overflow: hidden; white-space: nowrap; color: var(--muted);
}
summary.fsum::-webkit-details-marker { display: none; }
summary.fsum.on { color: var(--accent-strong); font-weight: 600; }
summary.fsum::after { content: "▾"; margin-left: auto; font-size: 10px; color: var(--muted); flex: none; }
summary.fsum.num { justify-content: flex-end; }
summary.fsum.num::after { margin-left: 4px; }
.fpop[open] > summary.fsum { border-color: var(--accent); }
.fpop-menu {
  margin-top: 4px; padding: 10px 12px; text-align: left;
  background: var(--panel-raised); border: 1px solid var(--line); border-radius: var(--radius);
  font-size: 13px; font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--ink);
}
@media (min-width: 600px) {
  .js .fpop-menu { position: fixed; z-index: 70; margin: 0; box-shadow: var(--shadow-pop);
    max-height: 60vh; overflow-y: auto; }
}
/* A checkbox with its words beside it, in body text rather than label caps. */
.check {
  display: inline-flex; align-items: center; gap: 8px; margin: 0; cursor: pointer;
  font-size: 13px; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink);
}
/* Options read as text, not as the uppercase field labels a <label> gets. */
.fpop-menu label.check {
  display: flex; gap: 8px; padding: 5px 2px; margin: 0; white-space: nowrap;
  font-size: 13px; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink);
}
.fpop-row { display: flex; gap: 10px; }
.fpop-row label { margin: 0; display: flex; flex-direction: column; gap: 4px; }
.fpop-row label .muted { text-transform: none; letter-spacing: 0; font-weight: 400; }
.fpop-row input { width: 150px; font-size: 13px; padding: 5px 8px; }
.fpop-menu p { margin: 8px 0 0; }
.fpresets { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 10px; font-size: 12.5px; }
.fclear { display: inline-block; margin-top: 8px; font-size: 12.5px; color: var(--crit); }
.fpresets .fclear { margin-top: 0; }

.chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 6px 3px 10px;
  border-radius: 999px; background: var(--accent-soft); color: var(--accent-strong);
  font-size: 12.5px; font-weight: 500; line-height: 1.5;
}
.chip > span[aria-hidden] { font-size: 14px; line-height: 1; opacity: .6; padding: 0 2px; }
.chip:hover { text-decoration: none; color: var(--accent-strong);
  background: color-mix(in srgb, var(--accent) 22%, var(--accent-soft)); }
.chip:hover > span[aria-hidden] { opacity: 1; }
.chip-clear { font-size: 12.5px; margin-left: 2px; }

.views-menu { position: relative; }
.views-menu > summary { list-style: none; white-space: nowrap; }
.views-menu > summary::-webkit-details-marker { display: none; }
.views-menu > summary::after { content: " ▾"; font-size: 10px; color: var(--muted); }
.views-menu .menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  width: min(320px, calc(100vw - 2 * var(--gutter)));
  background: var(--panel-raised); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-pop); padding: 8px;
}
.views-menu .menu-group {
  padding: 8px 8px 4px; font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .14em; color: var(--muted);
}
.views-menu .menu-row { display: flex; align-items: center; border-radius: var(--radius); }
.views-menu .menu-row > a { flex: 1; min-width: 0; padding: 7px 8px; color: var(--ink); }
.views-menu .menu-row:hover { background: var(--ground); }
.views-menu .menu-row > a:hover { text-decoration: none; }
.views-menu .menu-row.active { background: var(--accent-soft); }
.views-menu .menu-row.active > a { color: var(--accent-strong); font-weight: 600; }
.views-menu .menu-del {
  padding: 2px 8px; border: 0; background: none; color: var(--muted); font-size: 15px; line-height: 1;
}
.views-menu .menu-del:hover { color: var(--crit); background: none; }
.views-menu .menu-empty { margin: 4px 8px 8px; }
.views-menu .menu-save { border-top: 1px solid var(--line-soft); margin-top: 6px; padding: 10px 8px 2px; }
.views-menu .menu-save .formrow { flex-wrap: nowrap; gap: 8px; margin-bottom: 8px; }
.views-menu .menu-save input[name=name] { flex: 1; min-width: 0; }
.views-menu .menu-save p { margin: 6px 0 0; }

/* ---- Pager ---- */
.pager {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line-soft); font-size: 13px;
}
.pager-count { color: var(--muted); }
.pager-count b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.pager-pages, .pager-per { display: flex; align-items: center; gap: 2px; }
.pager-per { margin-left: auto; gap: 2px; }
.pager-per > .muted { margin-right: 6px; font-size: 12px; }
.pg {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 32px; padding: 0 9px; border-radius: var(--radius);
  color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap;
}
a.pg:hover { background: var(--ground); text-decoration: none; color: var(--ink); }
.pg.cur { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.pg.off { color: var(--muted); opacity: .45; }
.pg.gap { color: var(--muted); min-width: 18px; padding: 0; }
.pg.pos { display: none; color: var(--muted); }
[data-swap][aria-busy="true"] { opacity: .55; transition: opacity .15s .1s; }

/* ---- Tables ---- */
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
th {
  font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .12em;
  color: var(--muted); border-bottom-color: var(--line);
}
tr:last-child td { border-bottom: none; }

/* Sortable headings. The heading text becomes a button (so it is reachable by
   keyboard and announces itself); it inherits the <th> type so the table looks
   unchanged until you hover. The arrow is drawn from aria-sort, which keeps
   the accessible state and the visible state impossible to disagree about —
   and keeps the glyph out of th.textContent, which initResponsiveTables()
   copies onto every cell as its card label. */
th > .th-sort {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 0; margin: 0; border: 0; background: none; border-radius: 3px;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  text-align: inherit; cursor: pointer;
}
th.num > .th-sort { justify-content: flex-end; }
th > a.th-sort { text-decoration: none; }
th > .th-sort:hover { background: none; color: var(--ink); text-decoration: none; }
th > .th-sort::after {
  content: "↕"; font-size: 10px; letter-spacing: 0; opacity: .35;
  transition: opacity .12s;
}
th > .th-sort:hover::after { opacity: .7; }
th[aria-sort="ascending"] > .th-sort::after { content: "↑"; opacity: 1; }
th[aria-sort="descending"] > .th-sort::after { content: "↓"; opacity: 1; }
th[aria-sort="ascending"], th[aria-sort="descending"] { color: var(--accent); }
th > .th-sort:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* Phone-only sort control: in card mode the headings are hidden, so the same
   sort is offered above the cards. Built only for card-mode tables. */
.sortbar { display: none; align-items: center; gap: 8px; margin: 0 0 10px; }
.sortbar label { margin: 0; }
.sortbar select { flex: 1; min-width: 0; }
.sortbar .sortbar-dir { white-space: nowrap; }
tbody tr:hover td { background: color-mix(in srgb, var(--accent-soft) 34%, transparent); }
td.num, th.num { text-align: right; }
td.num { font-family: var(--mono); font-size: 13px; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-size: 13px; }
.muted { color: var(--muted); }
.small { font-size: 12px; }

/* ---- Forms ---- */
input, select, textarea {
  padding: 7px 10px; border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; font-size: 13.5px; background: var(--panel-raised); color: var(--ink);
  max-width: 100%;
}
/* Checkboxes/radios must escape the boxed text-input treatment above, and are
   drawn by us rather than the UA: a native control follows the OS scheme, so a
   light-forced page on a dark OS rendered white-on-black boxes. */
input[type="checkbox"], input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  flex: none; width: 17px; height: 17px; padding: 0; margin: 0;
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--panel-raised); box-shadow: none;
  vertical-align: -3px; position: relative; cursor: pointer;
  transition: background .12s, border-color .12s;
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]:hover:not(:disabled), input[type="radio"]:hover:not(:disabled) {
  border-color: var(--accent);
}
input[type="checkbox"]:checked, input[type="checkbox"]:indeterminate, input[type="radio"]:checked {
  background: var(--accent); border-color: var(--accent);
}
/* The tick is a rotated corner rather than a glyph, so it stays crisp at any
   size and needs no icon font. */
input[type="checkbox"]:checked::after {
  content: ""; position: absolute; left: 50%; top: 46%;
  width: 30%; height: 58%;
  border: solid var(--accent-ink); border-width: 0 2px 2px 0;
  transform: translate(-50%, -55%) rotate(45deg);
}
input[type="checkbox"]:indeterminate::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 56%; height: 2px; background: var(--accent-ink);
  transform: translate(-50%, -50%);
}
input[type="radio"]:checked::after {
  content: ""; position: absolute; inset: 3px;
  border-radius: 50%; background: var(--accent-ink);
}
input[type="checkbox"]:disabled, input[type="radio"]:disabled {
  opacity: .5; cursor: not-allowed; background: var(--line-soft);
}
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 24%, transparent);
}
input:disabled, select:disabled, textarea:disabled {
  opacity: .5; cursor: not-allowed;
  background: var(--line-soft); color: var(--muted);
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: .8; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
input.qty { width: 92px; text-align: right; font-family: var(--mono); font-size: 13px; }
input.money { width: 116px; text-align: right; font-family: var(--mono); font-size: 13px; }
label {
  display: block; font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .1em; color: var(--muted); margin-bottom: 4px;
}
/* Inline reference value in a label ("Counted qty · now 6") — the number in
   ink so it reads as data, without adding a second line that breaks formrow
   alignment. */
.label-hint { font-weight: 400; }
.label-hint [data-current-qty] { color: var(--ink); font-weight: 600; }
.field { margin-bottom: 14px; }
.formrow { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; }
.formrow .field { margin-bottom: 0; }

button, .btn {
  display: inline-block; padding: 8px 14px; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--panel-raised); color: var(--ink);
  font: 600 13px/1.4 var(--sans); cursor: pointer;
  transition: background .12s, border-color .12s;
}
button:hover, .btn:hover { background: var(--ground); text-decoration: none; color: var(--ink); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--accent-ink); }
.btn-danger { color: var(--crit); border-color: color-mix(in srgb, var(--crit) 35%, var(--line)); }
.btn-danger:hover { background: var(--crit-soft); }
.btn-sm { padding: 4px 9px; font-size: 12px; }
form.inline { display: inline; }

/* ---- Badges & state ---- */
.badge {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 600; letter-spacing: .04em;
  white-space: nowrap;   /* a badge is one token; never break it mid-word */
}
.badge.draft { background: var(--line-soft); color: var(--muted); }
.badge.ordered, .badge.open, .badge.confirmed { background: var(--info-soft); color: var(--info); }
.badge.partial { background: var(--warn-soft); color: var(--warn); }
.badge.received, .badge.shipped, .badge.complete, .badge.ok { background: var(--ok-soft); color: var(--ok); }
.badge.cancelled { background: var(--crit-soft); color: var(--crit); }
.badge.manufactured { background: var(--violet-soft); color: var(--violet); }
.badge.purchased { background: var(--info-soft); color: var(--info); }
.badge.consumable { background: var(--warn-soft); color: var(--warn); }
.shortqty { color: var(--crit); font-weight: 600; }
.okqty { color: var(--ok); }

.flash { padding: 10px 14px; border-radius: var(--radius); margin-bottom: 16px; font-weight: 500; }
.flash.ok { background: var(--ok-soft); color: var(--ok); }
.flash.error { background: var(--crit-soft); color: var(--crit); }
.flash.warn { background: var(--warn-soft); color: var(--warn); }
/* Records named in a message link to themselves; same colour as the text. */
.flash a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }

/* ---- Dashboard tiles ---- */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin-bottom: 20px; }
.tile {
  display: block; text-decoration: none;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; box-shadow: var(--shadow);
}
a.tile:hover { border-color: var(--accent); text-decoration: none; }
a.tile:hover .l { color: var(--accent); }
.tile .n {
  font-family: var(--mono); font-size: 27px; font-weight: 600;
  color: var(--copper); font-variant-numeric: tabular-nums; line-height: 1.2;
}
.tile .l {
  color: var(--muted); font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .12em; margin-top: 2px;
}

/* ---- BOM tree ---- */
.bomtree ul { list-style: none; margin: 0; padding-left: 24px; border-left: 1px solid var(--line); }
.bomtree > ul { padding-left: 0; border-left: none; }
.bomtree li { margin: 2px 0; }
.bomtree .node {
  display: flex; align-items: center; gap: 8px; padding: 5px 8px;
  border-radius: var(--radius);
}
.bomtree .node:hover { background: color-mix(in srgb, var(--accent-soft) 55%, transparent); }
.bomtree .toggle {
  width: 18px; height: 18px; line-height: 15px; text-align: center;
  border: 1px solid var(--line); border-radius: 4px; background: var(--panel-raised);
  cursor: pointer; font-size: 11px; padding: 0; flex-shrink: 0; color: var(--muted);
}
.bomtree .toggle.leaf { visibility: hidden; }
.bomtree .stock {
  margin-left: auto; display: flex; gap: 14px; font-size: 12px;
  font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap;
  color: var(--muted);
}
.bomtree .stock b { color: var(--ink); }

/* ---- Part picker & searchable dropdowns (same popup, different data source) ---- */
.ppicker, .combo { position: relative; display: inline-block; }
.ppicker input { width: min(290px, 100%); }
.combo input { width: min(240px, 100%); }
.ppicker .opts, .combo .opts {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 40;
  background: var(--panel-raised); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-pop); display: none; max-height: 260px; overflow-y: auto;
}
.ppicker .opts.open, .combo .opts.open { display: block; }
.ppicker .opts div, .combo .opts div { padding: 7px 10px; cursor: pointer; }
.ppicker .opts div:hover, .ppicker .opts div.sel,
.combo .opts div:hover, .combo .opts div.sel { background: var(--accent-soft); }
/* "+ Create ‹name›" sits under the matches, fenced off so it reads as an action
   rather than one more result. .busy/.error replace the list while creating. */
.combo .opts div.create { border-top: 1px solid var(--line); color: var(--accent); }
.combo .opts div.create:only-child { border-top: 0; }
.combo .opts div.create .note { color: var(--muted); font-size: .85em; margin-left: 6px; }
.combo .opts div.create.hint { color: var(--muted); }
.combo .opts div.create.hint:hover { background: none; }
.combo .opts div.busy, .combo .opts div.error { color: var(--muted); cursor: default; }
.combo .opts div.error { color: var(--crit); }

details summary { cursor: pointer; }

/* ---- Feedback dialog ---- */
.feedback-dialog, .create-dialog {
  border: 1px solid var(--line); border-radius: 10px; padding: 22px;
  background: var(--panel-raised); color: var(--ink); width: 440px; max-width: 92vw;
  box-shadow: var(--shadow-pop);
}
.feedback-dialog::backdrop, .create-dialog::backdrop { background: rgba(10, 16, 13, .45); }
.create-dialog { width: 560px; }
.create-dialog h2 { margin-top: 0; }
.create-dialog .flash { margin-bottom: 12px; }
.req {
  color: var(--crit); font-size: 10.5px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; margin-left: 4px;
}
.feedback-dialog textarea { width: 100%; resize: vertical; }

/* ---- Attachments ---- */
.attachments { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.attachment { position: relative; display: inline-flex; align-items: center; gap: 4px; }
.attachment img {
  height: 56px; max-width: 120px; object-fit: cover; display: block;
  border: 1px solid var(--line); border-radius: 6px;
}
.attachment .att-remove {
  position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; padding: 0;
  border-radius: 50%; border: 1px solid var(--line); background: var(--panel-raised);
  color: var(--muted); font-size: 12px; line-height: 1; cursor: pointer; display: none;
}
.attachment:hover .att-remove { display: block; }
.attachment[draggable="true"] { cursor: grab; }
.attachment.dragging { opacity: .4; cursor: grabbing; }
/* Upload affordance: a ghost thumbnail at the end of the row, same footprint
   as the images it creates. The invisible file input stretches over the whole
   tile so clicking and dropping files both hit the native control. */
.att-add {
  position: relative; overflow: hidden; cursor: pointer;
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; height: 56px; min-width: 64px; padding: 0 12px; margin: 0;
  border: 1px dashed color-mix(in srgb, var(--muted) 55%, var(--line));
  border-radius: 6px; color: var(--muted);
  transition: color .12s, border-color .12s, background .12s;
}
.att-add:hover, .att-add:focus-within {
  color: var(--accent); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent-soft) 55%, transparent);
}
.att-add:focus-within { outline: 2px solid var(--accent); outline-offset: 1px; }
.att-add .plus { font-size: 16px; line-height: 1; }
.att-add input[type="file"] {
  position: absolute; inset: 0; opacity: 0; cursor: pointer;
}

/* ---- Login ---- */
.login-wrap {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; min-height: 100dvh; padding: var(--gutter);
}
.login-box {
  width: min(340px, 100%); background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: 30px; box-shadow: var(--shadow);
}
.login-box h1 {
  text-align: center; font-family: var(--mono); letter-spacing: .14em; font-weight: 600;
}
.login-box h1::after { content: "_"; color: var(--copper); }
.login-box input { width: 100%; }
.login-box button { width: 100%; margin-top: 10px; }

/* ---- Units of measure ----
   A unit's meaning is a relationship, so the row reads as an equation:
   "1 <code> = <factor> <reference>". Keep those parts aligned across rows
   and let the mono face carry every code and number. */
.uom-intro { max-width: 62ch; margin: -8px 0 20px; line-height: 1.5; }
.uom-intro code { font-family: var(--mono); font-size: 12px; color: var(--ink); }

.uom-table td { vertical-align: middle; }
.uom-table input.uom-code { width: 76px; }
.uom-table input.uom-name { width: 100%; min-width: 120px; }

/* The definition cell: fixed lead + fixed factor + fixed select so the
   "=" columns line up down the table regardless of code length. */
.uom-eq { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.uom-eq .lead { color: var(--muted); font-size: 12.5px; }
.uom-eq .lead .mono { color: var(--ink); }
.uom-eq input.uom-factor { width: 78px; }
.uom-eq select.uom-ref { width: 108px; }

/* Resolved value against the dimension's base unit. */
.uom-base { color: var(--muted); font-family: var(--sans); font-size: 12px;
  text-transform: uppercase; letter-spacing: .08em; }

/* Right-aligned row controls: an inline active toggle beside Save. */
.uom-actions { display: flex; align-items: center; justify-content: flex-end; gap: 14px; }
.uom-toggle { display: inline-flex; align-items: center; gap: 6px; margin: 0;
  text-transform: none; letter-spacing: 0; font-size: 12px; font-weight: 500;
  color: var(--ink); cursor: pointer; }

/* Add-unit form is one horizontal band under the table, not a squeezed column. */
.uom-add .formrow { align-items: flex-end; }
.uom-add .field.grow { flex: 1; min-width: 160px; }
.uom-add .uom-eq { padding-bottom: 1px; }

/* Demand tree + build-next: badges name the demand relationship. */
.badge.shared   { background: var(--warn-soft); color: var(--warn); }
.badge.ship     { background: var(--info-soft); color: var(--info); }
.badge.unblocks { background: var(--violet-soft); color: var(--violet); }
.badge.feeds    { background: var(--line-soft); color: var(--muted); }

/* Build next — the two "why now" columns. Each relationship gets its own line
   so the badge reads as a label for the list beside it, not as one more chip in
   a run of chips. */
.reason { line-height: 1.65; }
.reason-line + .reason-line { margin-top: 2px; }

.viewtoggle a { color: var(--muted); }
.viewtoggle a.active { color: var(--ink); font-weight: 600; }

.wogroup { margin-bottom: 14px; }
.wogroup:last-child { margin-bottom: 0; }
.sohead { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.wotree { list-style: none; margin: 0; padding-left: 4px; }
.wotree ul { list-style: none; margin: 0 0 0 7px; padding-left: 18px;
  border-left: 1px solid var(--line-soft); }
.wotree li { padding: 3px 0; }

/* ==========================================================================
   Responsive
   --------------------------------------------------------------------------
   Scale:  ≤599 phone (drawer + tab bar, tables become cards)
           600–899 large phone / tablet (drawer via hamburger, tables scroll)
           900–1279 laptop (sidebar returns, grid2 splits)
           ≥1280 desktop · ≥1700 wide
   The drawer is a checkbox, so navigation works with JavaScript disabled.
   ========================================================================== */

/* Doubled class on purpose: the typed control rules above are [type="…"]
   selectors, which outrank a single class — an un-doubled .vis-hidden left the
   nav-toggle checkbox laid out as a real 17px box, a bar across the top of
   every page. */
.vis-hidden, .vis-hidden.vis-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ---- Hamburger + scrim (inert until the drawer breakpoint) ---- */
.navbtn {
  display: none; flex: none; align-items: center; justify-content: center;
  width: 38px; height: 38px; margin: 0 -4px 0 0; padding: 0;
  border-radius: var(--radius); color: var(--muted); cursor: pointer;
}
.feedback-open { display: inline-flex; align-items: center; gap: 6px; }
.feedback-open svg {
  width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.navbtn svg, .tabbar svg {
  width: 20px; height: 20px; fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.navbtn:hover { color: var(--ink); background: var(--ground); }
.navbtn:focus-visible, .tab-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.nav-scrim { display: none; }

/* ---- Phone-only bottom tab bar ---- */
.tabbar { display: none; }
.tabbar a, .tabbar .tab-more {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; padding: 7px 2px 6px; min-height: 54px; cursor: pointer;
  color: var(--muted); font-size: 10px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; text-decoration: none;
}
.tabbar a:hover { text-decoration: none; color: var(--ink); }
.tabbar a.active { color: var(--accent); }
.tabbar a.active svg { stroke-width: 1.9; }

/* ---- Tables: horizontal scroller (the universal safety net) ---- */
.table-wrap {
  overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch;
  /* Classic pure-CSS scroll shadows: the edge fades only while there's more
     table out of view, so the row silently advertises that it scrolls. */
  background:
    linear-gradient(to right, var(--panel) 30%, transparent) 0 0 / 28px 100% no-repeat,
    linear-gradient(to left,  var(--panel) 30%, transparent) 100% 0 / 28px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%,   rgba(16, 24, 21, .13), transparent) 0 0 / 11px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(16, 24, 21, .13), transparent) 100% 0 / 11px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
}
/* Only once the table has actually become cards — between 600 and 899 a
   card-eligible table is still a table, and still needs its scroller. */
@media (max-width: 599px) {
  .wrap-cards { overflow: visible; background: none; }
}
/* initResponsiveTables() collects each labelled cell's contents into .c-val.
   Outside card mode the wrapper must be invisible to layout. */
.c-val { display: contents; }

/* ==========================  ≤1279  ====================================== */
@media (max-width: 1279px) {
  .content { max-width: none; }
}

/* ==========================  ≤899 — drawer  ============================== */
@media (max-width: 899px) {
  .navbtn { display: inline-flex; }

  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; height: 100dvh; z-index: 200;
    width: min(280px, 84vw); transform: translateX(-100%);
    transition: transform .22s ease; box-shadow: var(--shadow-pop);
    padding-bottom: env(safe-area-inset-bottom);
  }
  #nav-toggle:checked ~ .layout .sidebar { transform: translateX(0); }

  .nav-scrim {
    display: block; position: fixed; inset: 0; z-index: 199;
    background: rgba(10, 16, 13, .45); opacity: 0; pointer-events: none;
    transition: opacity .22s ease;
  }
  #nav-toggle:checked ~ .layout .nav-scrim { opacity: 1; pointer-events: auto; }
  /* Freeze the page behind an open drawer. Purely a nicety — if :has() is
     unsupported the drawer still opens and closes correctly. */
  html:has(#nav-toggle:checked) { overflow: hidden; }

  .topbar { padding: 8px var(--gutter); gap: 8px; }
  .gsearch { max-width: none; min-width: 0; }
  .feedback-open .lbl { display: none; }

  /* 16px is the threshold below which iOS Safari force-zooms on focus. */
  input, select, textarea { font-size: 16px; }
  input.qty { width: 100px; }
  input.money { width: 124px; }
}

/* ==========================  ≤599 — phone  =============================== */
@media (max-width: 599px) {
  :root { --tabbar-h: calc(54px + env(safe-area-inset-bottom)); }

  /* "More" in the tab bar owns the drawer down here, so drop the hamburger. */
  .navbtn { display: none; }
  .tabbar {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 150;
    background: var(--panel); border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
  }

  h1 { font-size: clamp(18px, 4.6vw, 21px); }
  .panel { padding: 14px 12px; margin-bottom: 14px; }
  .pagehead { gap: 10px; }
  .pagehead .spacer { display: none; }
  .pagehead > h1, .pagehead > form, .pagehead > .formrow { width: 100%; }
  .pagehead > form.formrow { gap: 8px; }
  .pagehead > form.formrow > input[type="search"] { flex: 1 1 120px; min-width: 0; }
  .pagehead > form.formrow > select { flex: 1 1 100px; min-width: 0; }
  .pagehead > form.formrow > button { flex: 0 0 auto; }

  /* Unfolded on a phone, the bar's filters and the header's filter row (a
     card of its own) stack full width. */
  .listbar { flex-direction: column; align-items: stretch; }
  .listbar .fin, .listbar input.fin { width: 100%; max-width: none; }
  .listbar .ftoggle { align-self: flex-start; }
  table.cards thead { display: block; }
  table.cards thead tr:not(.filter-row) { display: none; }
  table.cards thead tr.filter-row { display: none; margin-bottom: 12px; }
  .filters-toggle:checked ~ * table.cards thead tr.filter-row { display: block; }
  /* Top-aligned: an open dropdown makes the cell tall, and a centred label
     would slide down beside it. */
  table.cards thead tr.filter-row td { padding: 5px 0; border: 0; align-items: flex-start; }
  table.cards thead tr.filter-row td::before { padding-top: 12px; }
  table.cards thead tr.filter-row td.fc-empty { display: none; }
  table.cards thead tr.filter-row td > * { flex: 1 1 60%; min-width: 0; }
  .fin { height: auto; min-height: 40px; font-size: 16px; }
  .fpop-row input { width: 100%; }
  .fpop-row label { flex: 1; }

  /* Pager: prev / "3 / 13" / next across the width; numbered links go. */
  .pager { gap: 10px; }
  .pager-count { flex: 1 1 100%; }
  .pager-pages { flex: 1 1 100%; justify-content: space-between; }
  .pager-pages .pg.num, .pager-pages .pg.gap { display: none; }
  .pager-pages .pg.pos { display: inline-flex; }
  .pager-per { margin-left: 0; }

  /* Forms stack: text fields take the row, numeric fields stay narrow so a
     qty and its unit still read as one thing. */
  .formrow > .field { flex: 1 1 100%; min-width: 0; }
  .formrow > .field:has(input.qty), .formrow > .field:has(input.money) { flex: 0 0 auto; }
  .formrow > .field input:not(.qty):not(.money),
  .formrow > .field select, .formrow > .field textarea { width: 100%; }
  .ppicker, .combo { display: block; width: 100%; }
  .ppicker input, .combo input { width: 100%; }   /* beat the min(240px,…) cap */

  /* BOM tree: give the stock figures their own line under the part name, and
     tighten the per-level indent so a deep branch still has room to read. */
  .bomtree ul { padding-left: 13px; }
  .bomtree .node { flex-wrap: wrap; align-items: flex-start; row-gap: 3px; }
  .bomtree .node > span:not(.stock) { flex: 1 1 auto; min-width: 0; }
  .bomtree .stock {
    flex-basis: 100%; margin-left: 0; padding-left: 26px;
    white-space: normal; flex-wrap: wrap; gap: 3px 12px;
  }
  .wotree ul { padding-left: 12px; }

  .uom-eq { white-space: normal; flex-wrap: wrap; }
  .uom-table input.uom-code { width: 100%; }

  /* ---- Row-per-card tables ----
     initResponsiveTables() copies each <th> onto its cells as data-label, so
     this works on every current and future table without template churn. */
  table.cards, table.cards tbody, table.cards tr, table.cards td { display: block; }
  .sortbar { display: flex; }
  table.cards tr {
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--panel-raised); padding: 10px 12px; margin-bottom: 10px;
  }
  table.cards tr:last-child { margin-bottom: 0; }
  table.cards tbody tr:hover td { background: none; }
  table.cards td {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap; gap: 2px 14px;
    padding: 4px 0; border: none; text-align: left; overflow-wrap: anywhere;
  }
  /* One flex item for the whole value: badges and links wrap inside it rather
     than each being shrunk to a single character. The flex-basis lets a long
     value drop onto its own full-width line when it can't sit beside a label. */
  table.cards td:not(.c-title) > .c-val {
    display: block; flex: 1 1 62%; min-width: 0; text-align: right;
  }
  table.cards td::before {
    content: attr(data-label); flex: none;
    font-size: 10.5px; font-weight: 600; text-transform: uppercase;
    letter-spacing: .12em; color: var(--muted);
  }
  table.cards td.c-plain::before, table.cards td.c-title::before,
  table.cards td.c-check::before { content: none; }
  table.cards tr { position: relative; }
  table.cards td.c-check {
    position: absolute; top: 9px; right: 11px; display: block; padding: 0;
  }
  table.cards td.c-empty, table.cards td.c-hide { display: none; }
  /* The identifying cell leads the card at full width, like a card heading. */
  table.cards td.c-title {
    display: block; font-size: 15px; padding: 0 44px 6px 0;
    border-bottom: 1px solid var(--line-soft); margin-bottom: 6px;
  }
  table.cards td.c-title .mono { font-size: 15px; }
  table.cards td.num { font-size: 14px; justify-content: space-between; }
  /* Action cells (buttons, checkboxes) get room rather than a cramped column. */
  table.cards td.c-actions { justify-content: flex-start; flex-wrap: wrap; gap: 8px;
    padding-top: 8px; }
  table.cards td.c-actions::before { flex-basis: 100%; }
}

/* ==========================  touch  ====================================== */
@media (pointer: coarse) {
  button, .btn, a.btn, input[type="submit"], select, summary {
    min-height: var(--tap);
  }
  /* A "small" button is only small in ink — it still gets a full target. */
  /* min-width catches the glyph-only buttons ("✕", "＋") that are otherwise
     a comfortable height and an uncomfortable 20px wide. */
  .btn-sm { min-height: var(--tap); min-width: var(--tap); padding: 10px 14px; font-size: 13px; }
  .feedback-open { min-width: var(--tap); text-align: center; }
  /* 24px is the WCAG 2.2 SC 2.5.8 minimum; a checkbox scaled to 44 looks
     broken, and the row padding around it carries the rest of the target. */
  input[type="checkbox"], input[type="radio"] {
    width: 24px; height: 24px; vertical-align: -7px;
  }
  .theme-toggle { width: var(--tap); height: var(--tap); }
  .pg { min-width: var(--tap); height: var(--tap); }
  .chip { min-height: 36px; padding: 4px 10px 4px 14px; }
  .views-menu .menu-row > a { padding: 12px 10px; }
  .views-menu .menu-del { min-width: var(--tap); }
  /* Sorting is a primary action on a tablet, so the heading gets a full
     target rather than the 15px of ink it draws. */
  th > .th-sort { min-height: var(--tap); min-width: var(--tap); }
  .uom-actions { gap: 10px; }

  .sidebar nav a { padding-top: 11px; padding-bottom: 11px; }
  .bomtree .stock { padding-left: 40px; }   /* clears the larger touch toggle */
  .sidebar .userbox button { min-height: var(--tap); }
  /* Same opt-out as .att-remove: the blanket 44px would stretch this square
     into an oval, and a 44px bordered box per row makes a deep BOM unreadable.
     Keep 30px of ink; the ::after carries the target out to 44. */
  .bomtree .toggle {
    position: relative; min-width: 0; min-height: 0;
    width: 30px; height: 30px; line-height: 27px; font-size: 13px;
  }
  .bomtree .toggle::after { content: ""; position: absolute; inset: -7px; }
  .bomtree .toggle.leaf { display: none; }
  .bomtree .node { padding: 8px; gap: 10px; }
  .ppicker .opts div, .combo .opts div { padding: 12px 12px; }
  .gsearch .results a { padding: 12px; }
  /* Hover-only reveals are unreachable without a mouse. */
  .attachment .att-remove {
    /* Explicitly opt out of the blanket 44px button sizing above — left alone
       it stretches this badge into an oval that covers the thumbnail. */
    display: block; min-width: 0; min-height: 0;
    width: 28px; height: 28px; top: -8px; right: -8px; font-size: 15px; line-height: 26px;
  }
  /* Keep the badge small but the target big: the ::after extends the hit area
     to ~46px without inflating the glyph over a 56px thumbnail. */
  .attachment .att-remove::after { content: ""; position: absolute; inset: -8px; }
}

/* ==========================  dialogs → bottom sheets  ==================== */
@media (max-width: 599px) {
  .feedback-dialog, .create-dialog {
    width: 100%; max-width: 100%; margin: 0 auto;
    position: fixed; inset: auto 0 0 0;
    border-radius: 14px 14px 0 0; border-bottom: none;
    max-height: 92dvh; overflow-y: auto; padding: 18px 16px;
    padding-bottom: calc(18px + env(safe-area-inset-bottom));
  }
  .feedback-dialog .formrow, .create-dialog .formrow { gap: 10px; }
}

/* ==========================  ≥1700 — wide desktop  ======================= */
@media (min-width: 1700px) {
  /* Data-dense tables earn the extra width; the cap only stops prose from
     running to 200 characters a line. */
  .content { max-width: 1560px; }
  .tiles { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; }
}
