/*!
 * inventory-components.css: the component kit of Docs/ui/styleguide.html (P0-16, ADR 0013) — Views/_components + Cells.
 *
 * Loaded after inventory-shell.css (Views/layouts/_head.php). Values come from var(--token) only: no hex/rgb
 * (scripts/qa-token-scan.php + DesignTokensTest), literal lengths only for 1px/2px strokes; the fixed sizes the
 * styleguide writes in px (6px dot, 10px pill padding, 32px thumb, 34px icon …) are the same sizes in rem so they do
 * not scale with the warehouse theme, exactly as in the styleguide. Motion never exceeds --dur, except the skeleton
 * shimmer, which is the styleguide's own 1.4 s loop (and stops with prefers-reduced-motion).
 *
 * Every interactive part has its hover / focus-visible / disabled / loading (.is-busy = what .htmx-request does
 * during a request) / error (.is-invalid, .is-error) look; /ui/kit shows them all in the three themes.
 */

/* ============================================================ 0. shared states */
/* a busy element rendered by the server looks exactly like one htmx is working on (O3 #8) */
.is-busy .htmx-indicator,
.is-busy.htmx-indicator{display:inline-block}
.is-busy .inv-busy-label,
.htmx-request .inv-busy-label{display:inline}
.is-busy .inv-idle-label{display:none}
.is-busy{cursor:progress}
/* a button that is working keeps its colour (styleguide "กำลังบันทึก…"), it just cannot be pressed again */
.btn.is-busy,
.btn.htmx-request{--bs-btn-disabled-opacity:1;cursor:progress}
.btn.is-busy:disabled,
.btn.htmx-request:disabled{pointer-events:none}
.btn .inv-spin{width:.875rem;height:.875rem}
.btn-link.btn-sm{min-height:0;padding:0;font-size:var(--t-xs)}

.inv-doc{font-family:var(--f-mono);font-size:var(--t-xs);color:var(--ink-2);font-variant-numeric:tabular-nums}
.inv-strong{font-weight:600}
/* P1-19 visual QA: TEXT always takes the `-fg` shade of a semantic colour, never the solid one. The solid
   --c-ok / --c-warn / --c-err are the border/fill shades; on the light surface they only reach ~3.8:1 and
   ~4.4:1, which is under AA for the 12-14px sizes these classes are used at. */
.inv-tone-ok{color:var(--c-ok-fg);font-weight:600}
.inv-tone-warn{color:var(--c-warn-fg);font-weight:600}
.inv-tone-err{color:var(--c-err-fg);font-weight:600}
.inv-tone-muted{color:var(--ink-3)}

/* inputs: a hint of the edge under the pointer (the styleguide has none at rest; focus stays the strong cue) */
.form-control:hover:not(:disabled):not(:focus):not(.is-invalid),
.form-select:hover:not(:disabled):not(:focus):not(.is-invalid){border-color:var(--ink-3)}
.form-control[aria-busy="true"]{cursor:progress}
.inv-field .form-label .inv-spin{vertical-align:middle;color:var(--ink-3)}

/* notices under a page header / in a panel */
.inv-notice{display:flex;gap:var(--s2);margin:0;padding:var(--s2) var(--s3);border:1px solid var(--c-info);border-left-width:var(--s1);
  border-radius:var(--r-sm);background:var(--c-info-bg);color:var(--ink);font-size:var(--t-sm)}
.inv-notice.is-warning{border-color:var(--c-warn);background:var(--c-warn-bg)}
.inv-notice.is-error{border-color:var(--c-err);background:var(--c-err-bg)}
.inv-inline-error{display:flex;gap:var(--s1);margin:var(--s2) 0 0;color:var(--c-err-fg);font-size:var(--t-sm);font-weight:500}

/* ============================================================ 1. PageHeader (screens.html .ph, styleguide section 8) */
.ph-main{min-width:0}
.ph h1 .pill{vertical-align:middle}
.ph h1 .inv-doc{font-weight:400;color:var(--ink-3)}
.ph-meta{margin-top:var(--s1);color:var(--ink-3);font-size:var(--t-sm)}
.ph .inv-notice{flex-basis:100%}
@media (max-width:40rem){ .ph .acts{margin-left:0} }

/* ============================================================ 2. StatusPill — the 11 st-* classes (styleguide section 3)
   P1-20: soft tinted chips — a semantic -bg for the fill, the matching -fg for the text and a 1px ring of the base
   colour at low alpha, so a pill reads on white, on slate-900 and on pure black without a new colour anywhere. */
.pill{display:inline-flex;align-items:center;gap:.375rem;padding:2px .625rem;border-radius:var(--r-pill);
  font-size:var(--t-xs);font-weight:600;white-space:nowrap;text-decoration:none;
  box-shadow:inset 0 0 0 1px color-mix(in srgb,currentColor 22%,transparent)}
.pill::before{content:"";flex:none;width:.375rem;height:.375rem;border-radius:50%;background:currentColor}
.st-draft{background:var(--c-n-100);color:var(--c-n-700)}
.st-quoted{background:var(--c-info-bg);color:var(--c-info-fg)}
.st-reserved{background:var(--c-brand-100);color:var(--c-brand-700)}
.st-picked{background:var(--c-amber-100);color:var(--c-amber-600)}
.st-returned,
.st-approved{background:var(--c-ok-bg);color:var(--c-ok-fg)}
.st-closed{background:var(--c-n-100);color:var(--c-n-800)}
.st-cancel,
.st-rejected{background:var(--c-err-bg);color:var(--c-err-fg)}
/* P3-13: --c-n-600 on --c-n-100 is 4.21:1 in the light theme — under AA for 12px text. --c-n-700 (as st-draft) is 6.64:1 */
.st-expired{background:var(--c-n-100);color:var(--c-n-700)}
.st-pending{background:var(--c-warn-bg);color:var(--c-warn-fg)}
/* dark theme: the neutral pills get the styleguide's dark pill colour (hex 2A2E29 = 41 % line-strong over surface) + ink-2.
   Applied to the chosen dark theme and to the OS-dark "auto" theme alike (the styleguide only had the former). */
:root[data-theme="dark"] .st-draft,
:root[data-theme="dark"] .st-closed,
:root[data-theme="dark"] .st-expired{background:color-mix(in srgb,var(--line-strong) 41%,var(--surface));color:var(--ink-2)}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]):not([data-theme="warehouse"]):not([data-theme="dark"]) .st-draft,
  :root:not([data-theme="light"]):not([data-theme="warehouse"]):not([data-theme="dark"]) .st-closed,
  :root:not([data-theme="light"]):not([data-theme="warehouse"]):not([data-theme="dark"]) .st-expired{background:color-mix(in srgb,var(--line-strong) 41%,var(--surface));color:var(--ink-2)}
}
a.pill{transition:box-shadow var(--dur)}
a.pill:hover{box-shadow:inset 0 0 0 1px currentColor}
a.pill:focus-visible{outline:2px solid var(--focus);outline-offset:2px}

/* ============================================================ 3. Skeleton (styleguide .skel) */
.inv-skel{display:block;height:.75rem;border-radius:var(--r-sm);
  background:linear-gradient(90deg,var(--surface-2) 25%,var(--line) 37%,var(--surface-2) 63%);background-size:400% 100%;
  animation:inv-sk 1.4s ease infinite}
@keyframes inv-sk{0%{background-position:100% 50%}100%{background-position:0 50%}}
.inv-skel-sm{height:var(--s2)}
.inv-skel-w1{width:100%}
.inv-skel-w2{width:82%}
.inv-skel-w3{width:64%}
.inv-skel-w4{width:42%}
.inv-skel-line+.inv-skel-line{margin-top:var(--s2)}
.inv-skel-pill{display:inline-block;width:4.5rem;height:1.25rem;border-radius:var(--r-pill);vertical-align:middle}
.inv-skel-stat{width:3.5rem;height:var(--t-2xl);margin:var(--s1) 0}
.inv-skel-box{width:1rem;height:1rem}
.inv-skel-thumb{flex:none;width:2rem;height:2rem}
.inv-skel-control{height:var(--control-h)}
.inv-skeleton{display:flex;flex-direction:column;gap:var(--s2)}
.inv-skeleton-item{display:flex;align-items:center;gap:var(--s3)}
.inv-skeleton-lines{display:flex;flex:1;flex-direction:column;gap:var(--s1)}
.inv-skeleton-field{display:flex;flex-direction:column;gap:var(--s2);margin-bottom:var(--s3)}
.inv-skeleton-card{display:flex;flex-direction:column;gap:var(--s1);padding:var(--s4);border:1px solid var(--line);
  border-radius:var(--r);background:var(--surface)}
.inv-skeleton-row{display:grid;grid-auto-columns:minmax(0,1fr);grid-auto-flow:column;gap:var(--s3);align-items:center;
  height:var(--row-h);padding:0 var(--s3);border-bottom:1px solid var(--line)}
.inv-skeleton-table{border:1px solid var(--line);border-radius:var(--r);background:var(--surface)}
.inv-skeleton-table .inv-skeleton-row:last-child{border-bottom:0}
@media (prefers-reduced-motion:reduce){ .inv-skel{animation:none} }

/* ============================================================ 4. EmptyState (styleguide .empty)
   P1-20: a large soft icon in a tinted disc, one sentence and ONE next action — never a bare "ไม่มีข้อมูล". */
.inv-empty{display:flex;flex-direction:column;align-items:center;padding:var(--s8) var(--s4);text-align:center;
  color:var(--ink-3)}
.inv-empty.is-compact{padding:var(--s5) var(--s3)}
.inv-empty-icon{display:grid;place-items:center;width:calc(var(--s8) + var(--s2));height:calc(var(--s8) + var(--s2));
  margin-bottom:var(--s4);border-radius:50%;background:var(--surface-2);color:var(--ink-3);font-size:2.125rem;
  box-shadow:inset 0 0 0 1px var(--line)}
.inv-empty-title{font-size:var(--t-lg);color:var(--ink)}
.inv-empty p{max-width:34em;margin:var(--s2) 0 var(--s5);font-size:var(--t-sm)}
.inv-empty-acts{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--s2)}
.inv-empty-title+.inv-empty-acts{margin-top:var(--s5)}
.inv-empty.is-search .inv-empty-icon{background:var(--primary-soft);color:var(--primary)}
.inv-empty.is-error .inv-empty-icon{background:var(--c-err-bg);color:var(--c-err-fg)}
.inv-empty.is-error .inv-empty-title{color:var(--c-err-fg)}
/* P3-10: all clear (nothing waits for anybody) · the next step is right next to it */
.inv-empty.is-done .inv-empty-icon{background:var(--c-ok-bg);color:var(--c-ok-fg)}
.inv-empty.is-info .inv-empty-icon{background:var(--c-info-bg);color:var(--c-info-fg)}
.inv-empty.is-compact .inv-empty-icon{width:var(--s7);height:var(--s7);margin-bottom:var(--s3);font-size:1.5rem}
.inv-empty.is-compact .inv-empty-icon .inv-icon{width:1.5rem;height:1.5rem}

/* ============================================================ 5. StatCard (styleguide .stat) */
.inv-stat{display:block;padding:var(--s5);border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
  color:inherit;text-decoration:none;box-shadow:var(--sh-1);
  transition:border-color var(--dur),box-shadow var(--dur),transform var(--dur)}
.inv-stat-label,
.inv-stat-delta{color:var(--ink-3);font-size:var(--t-xs)}
.inv-stat-value{display:block;font-family:var(--f-mono);font-size:var(--t-2xl);font-variant-numeric:tabular-nums;line-height:1.1}
.inv-stat-value small{font-size:1rem}
/* GATE-5 (visual QA of phase 5): six cards in a row (`reports/assets` at 1440) leave a card about 125px for its number, and
   "-40,200.00" ran out over the card's edge. A card in the stats grid is a size container (the grid gives it its width, so
   the containment changes nothing else): its number shrinks with the card, never past --t-lg, and a wide card keeps
   --t-2xl. */
.inv-stats>.inv-stat{container-type:inline-size}
.inv-stats>.inv-stat .inv-stat-value{font-size:clamp(var(--t-lg),16cqi,var(--t-2xl))}
.inv-stat-value.is-danger{color:var(--c-err-fg)}
.inv-stat-value.is-warning{color:var(--c-warn-fg)}
.inv-stat-value.is-ok{color:var(--c-ok-fg)}
.inv-stat-value.is-muted{color:var(--ink-3)}
.inv-stat-delta.is-up{color:var(--c-ok-fg)}
.inv-stat-delta.is-down{color:var(--c-err-fg)}
a.inv-stat:hover{border-color:var(--c-brand-200);box-shadow:var(--sh-2);color:inherit;transform:translateY(-1px)}
a.inv-stat:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
@media (prefers-reduced-motion:reduce){ a.inv-stat:hover{transform:none} }
.inv-stat.is-disabled{background:var(--surface-2);cursor:not-allowed}
.inv-stat.is-disabled .inv-stat-label{color:var(--ink-3)}
.inv-stat.is-error{border-color:var(--c-err)}
.inv-stat-error{display:block;color:var(--c-err-fg);font-size:var(--t-xs)}
.inv-stat-retry{margin-top:var(--s1)}
.inv-stat[aria-busy="true"]{cursor:progress}

/* ============================================================ 6. Timeline (styleguide .tl) */
.inv-tl{margin:0;padding:0;list-style:none}
.inv-tl li{position:relative;margin-left:.375rem;padding:0 0 var(--s4) var(--s5);border-left:2px solid var(--line)}
.inv-tl li:last-child{padding-bottom:0;border-left-color:transparent}
.inv-tl li::before{content:"";position:absolute;left:-.4375rem;top:var(--s1);width:.75rem;height:.75rem;border-radius:50%;
  background:var(--surface);border:2px solid var(--line-strong)}
.inv-tl li.is-done::before{background:var(--c-ok);border-color:var(--c-ok)}
.inv-tl li.is-now::before{background:var(--primary);border-color:var(--primary);box-shadow:0 0 0 var(--s1) var(--primary-soft)}
.inv-tl li.is-error::before{background:var(--c-err);border-color:var(--c-err)}
.inv-tl li.is-skipped::before{border-style:dashed}
.inv-tl-title{font-size:var(--t-sm);font-weight:700}
.inv-tl li.is-todo .inv-tl-title,
.inv-tl li.is-skipped .inv-tl-title{color:var(--ink-3)}
.inv-tl li.is-error .inv-tl-title{color:var(--c-err-fg)}
.inv-tl small{color:var(--ink-3);font-size:var(--t-xs)}
a.inv-tl-title{color:var(--ink);text-decoration:none}
a.inv-tl-title:hover{color:var(--primary);text-decoration:underline}
a.inv-tl-title:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.inv-tl.is-loading li{display:flex;flex-direction:column;gap:var(--s2)}
.inv-tl.is-loading li::before{background:var(--surface-2)}
/* P5-01: a line of what happened, small links after it (a photo, the ledger row), and the month a long history is cut in */
.inv-tl-detail{display:block;margin-top:var(--s1);color:var(--ink-2);font-size:var(--t-sm);overflow-wrap:anywhere}
.inv-tl-links{display:flex;flex-wrap:wrap;gap:var(--s1) var(--s3);margin-top:var(--s1);font-size:var(--t-xs)}
.inv-tl-links a{display:inline-flex;align-items:center;gap:var(--s1);color:var(--primary);text-decoration:none}
.inv-tl-links a:hover{text-decoration:underline}
.inv-tl-links a:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:var(--r-sm)}
.inv-tl-month{margin:var(--s4) 0 var(--s2);color:var(--ink-3);font-size:var(--t-xs);font-weight:700}
.inv-tl-month:first-child{margin-top:0}
.inv-tl-more{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s2);margin-top:var(--s3);
  color:var(--ink-3);font-size:var(--t-sm)}

/* ============================================================ 7. Stepper (screens.html #3 .steps) */
.inv-steps{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);margin:0 0 var(--s4);padding:0;list-style:none}
.inv-steps:last-child{margin-bottom:0}
.inv-steps li{display:flex;align-items:center;gap:var(--s2);color:var(--ink-3);font-size:var(--t-sm)}
.inv-steps li+li{flex:1}
.inv-steps li+li::before{content:"";flex:1;min-width:var(--s4);border-top:1px solid var(--line-strong)}
.inv-step{display:inline-flex;align-items:center;gap:var(--s2);color:inherit;text-decoration:none;border-radius:var(--r-sm)}
/* P2-15: the number is 12px on a filled circle, so it takes the ink that clears AA on that fill — `--ink-3` on
   `--c-n-100` was 4.21:1 in the light theme and white on `--c-ok` / `--c-err` was 1.9–2.7:1 in the dark ones. */
.inv-step-no{display:grid;flex:none;place-items:center;width:var(--s5);height:var(--s5);border-radius:50%;
  background:var(--c-n-100);color:var(--ink-2);font-size:var(--t-xs);font-style:normal;font-weight:600}
.inv-steps li.is-done{color:var(--ink-2)}
.inv-steps li.is-done .inv-step-no{background:var(--c-ok);color:var(--c-ok-ink)}
.inv-steps li.is-current{color:var(--ink);font-weight:600}
.inv-steps li.is-current .inv-step-no{background:var(--primary);color:var(--primary-ink)}
.inv-steps li.is-error{color:var(--c-err-fg);font-weight:600}
.inv-steps li.is-error .inv-step-no{background:var(--c-err);color:var(--c-err-ink)}
.inv-steps li.is-todo{cursor:not-allowed}
.inv-steps .inv-step-no .inv-spin{width:.75rem;height:.75rem}
a.inv-step:hover .inv-step-label{color:var(--primary);text-decoration:underline}
a.inv-step:focus-visible{outline:2px solid var(--focus);outline-offset:2px}

/* ============================================================ 8. Tabs (styleguide .tabs = .nav-underline in inventory.css) */
.inv-tabs{margin-bottom:var(--s4)}
.inv-tabs .nav-link{display:inline-flex;align-items:center;gap:var(--s1)}
.inv-tabs .nav-link.disabled{color:var(--ink-3);opacity:.45;cursor:not-allowed}
.inv-tab-count{padding:0 .375rem;border-radius:var(--r-pill);background:var(--surface-2);color:var(--ink-2);
  font-family:var(--f-mono);font-size:var(--t-xs);font-weight:500}
.inv-tab-dot{width:.375rem;height:.375rem;border-radius:50%;background:var(--c-err)}
.inv-tabs .nav-link.has-error{color:var(--c-err-fg)}
.inv-tabs .nav-link .inv-spin{width:.75rem;height:.75rem}

/* ============================================================ 9. Toast (styleguide .toast; region in inventory-shell.css) */
.inv-toast{max-width:23.75rem}
/* the styleguide's error toast also carries its .err text rule: red title, 12px line box */
.inv-toast.is-error{color:var(--c-err-fg);font-size:var(--t-xs)}

/* ============================================================ 10. Forms: QtyStepper, MoneyInput (styleguide section 5) */
.inv-qty{display:inline-flex;align-items:center;overflow:hidden;border:1px solid var(--line-strong);border-radius:var(--r-sm);
  background:var(--surface)}
.inv-qty-btn{width:var(--control-h);height:var(--control-h);padding:0;border:0;background:var(--surface-2);color:var(--ink);
  font:inherit;font-size:1.125rem;line-height:1;cursor:pointer;transition:background-color var(--dur)}
.inv-qty-btn:hover:not(:disabled){background:var(--line)}
.inv-qty-btn:active:not(:disabled){background:var(--line-strong)}
.inv-qty-btn:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}
.inv-qty-btn:disabled{opacity:.45;cursor:not-allowed}
.inv-qty-input{width:4rem;height:var(--control-h);padding:0;border:0;border-right:1px solid var(--line);border-left:1px solid var(--line);
  background:var(--surface);color:var(--ink);font-family:var(--f-mono);font-size:var(--t-md);text-align:center}
.inv-qty-input:focus{outline:2px solid var(--focus);outline-offset:-2px}
.inv-qty-input:disabled{background:var(--surface-2);color:var(--ink-3)}
.inv-qty.is-error{border-color:var(--c-err)}
.inv-qty.is-error .inv-qty-input{background:var(--c-err-bg)}
.inv-qty.is-disabled{opacity:.7}
.inv-qty>.inv-spin{margin:0 var(--s2)}
.inv-qty.is-busy .inv-qty-btn,
.inv-qty:has(.htmx-request) .inv-qty-btn{pointer-events:none;opacity:.45}
.inv-qty:has(.htmx-request) .htmx-indicator{display:inline-block}
@media (pointer:coarse){ .inv-qty-btn{width:var(--tap);height:var(--tap)} .inv-qty-input{height:var(--tap)} }

.inv-money{position:relative}
.inv-money .form-control.is-negative{color:var(--c-err-fg)}
.inv-money>.inv-spin{position:absolute;top:50%;left:var(--s3);z-index:5;margin-top:-.4375rem;color:var(--ink-3)}
.inv-money:has(.htmx-request) .htmx-indicator{display:inline-block}
.inv-money.is-busy .form-control{padding-left:calc(var(--s3) + var(--s5))}

/* ============================================================ 11. ScanInput (store screens: 48px, one hand) */
.inv-scan-row{display:flex;gap:var(--s2)}
.inv-scan-input{flex:1;min-width:0;min-height:var(--tap);font-family:var(--f-mono);font-size:var(--t-lg);letter-spacing:.02em}
.inv-scan-input:focus{outline-width:2px;box-shadow:0 0 0 var(--s1) var(--primary-soft)}
.inv-scan-input.is-invalid:focus{box-shadow:0 0 0 var(--s1) var(--c-err-bg)}
.inv-scan-result{margin-top:var(--s2)}
.inv-scan-result:empty{display:none}
.inv-scan.is-disabled .form-label{color:var(--ink-3)}
.inv-scan-camera{position:relative;display:flex;flex-direction:column;align-items:center;gap:var(--s2);margin-top:var(--s2);
  padding:var(--s3);border-radius:var(--r);background:var(--c-n-900);color:var(--c-n-000)}
.inv-scan-camera[hidden]{display:none}
.inv-scan-camera video{width:100%;max-height:40vh;border:2px solid var(--primary);border-radius:var(--r-sm);object-fit:cover}
.inv-scan-camera video[hidden]{display:none}
/* html5-qrcode draws its own <video> inside this box (P1-06 camera fallback, iOS Safari) */
/* `position:relative` is ours on purpose: html5-qrcode sets the same inline style on this container when it starts,
   and an authored rule keeps the page's own stylesheet the one that describes the box (P1-19 visual QA). */
.inv-scan-camera-box{position:relative;width:100%;border:2px solid var(--primary);border-radius:var(--r-sm);overflow:hidden}
.inv-scan-camera-box[hidden]{display:none}
.inv-scan-camera-hint{margin:0;font-size:var(--t-xs)}
.inv-scan-hit{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s2);padding:var(--s3);
  border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface-2)}
.inv-scan-hit b{font-size:var(--t-md)}
.inv-scan-hit .inv-doc{display:block}

/* ============================================================ 12. FilterBar + chips (styleguide section 6, screens.html #2) */
.inv-filterbar{display:flex;flex-direction:column;gap:var(--s3);margin-bottom:var(--s3)}
.inv-filterbar-row{display:flex;flex-wrap:wrap;gap:var(--s2)}
.inv-search{position:relative;flex:1 1 16rem;min-width:0;max-width:24rem}
.inv-search .form-control{padding-right:calc(var(--s3) + var(--s5))}
.inv-search>.inv-spin{position:absolute;top:50%;right:var(--s3);margin-top:-.4375rem;color:var(--ink-3)}
/* P1-19: `min-width:0` or a <select> whose longest option is a sentence (the SoD rule filter) refuses to shrink
   below its content and pushes the page wider than a 360px phone. */
.inv-filterbar-row .form-select{flex:0 1 12rem;width:auto;min-width:0;max-width:100%}
/* P1-09: date range in the search row — the label sits above the field so a phone keeps both on one line */
.inv-filterbar-date{display:flex;flex-direction:column;gap:var(--s1);flex:0 1 11rem;font-size:var(--t-sm);color:var(--ink-2)}
.inv-filterbar-date .form-control{width:auto;min-width:0;max-width:100%}
.inv-filterbar-error{display:flex;gap:var(--s1);margin-top:calc(var(--s1) * -1)}
.inv-filterbar>.inv-chips{margin-bottom:0}

.inv-chips{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);margin-bottom:var(--s3)}
.inv-chips>.inv-spin{color:var(--ink-3)}
.inv-chips-end{margin-left:auto}
.chip{position:relative;display:inline-flex;align-items:center;gap:.375rem;margin:0;padding:var(--s1) .625rem;
  border:1px solid var(--line-strong);border-radius:var(--r-pill);background:var(--surface);color:var(--ink-2);
  font:inherit;font-size:var(--t-xs);line-height:var(--lh-body);text-decoration:none;cursor:pointer;
  transition:background-color var(--dur),border-color var(--dur),color var(--dur)}
.chip:hover{background:var(--surface-2);color:var(--ink)}
.chip[aria-pressed="true"],
.chip:has(.inv-chip-input:checked){background:var(--primary-soft);border-color:var(--primary);color:var(--primary);font-weight:600}
.chip:focus-visible,
.chip:has(.inv-chip-input:focus-visible){outline:2px solid var(--focus);outline-offset:2px}
.chip:disabled,
.chip.is-disabled{opacity:.45;cursor:not-allowed}
.chip.is-disabled:hover{background:var(--surface);color:var(--ink-2)}
.chip.is-invalid{border-color:var(--c-err);color:var(--c-err-fg)}
.inv-chip-input{position:absolute;width:1px;height:1px;margin:0;opacity:0;pointer-events:none}
.inv-chip-clear{border-style:dashed}

/* ============================================================ 13. DataTable + bulk bar (styleguide section 6) */
.inv-dt{position:relative}
.inv-dt-bar:empty{display:none}
.inv-dt-meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);margin-bottom:var(--s2);color:var(--ink-3);font-size:var(--t-xs)}
.inv-dt-columns{margin-left:auto}
.inv-dt-columns .btn{min-height:0;padding-top:var(--s1);padding-bottom:var(--s1)}
.inv-dt-col-toggle{display:flex;align-items:center;gap:var(--s2);cursor:pointer}
.inv-dt-col-toggle .form-check-input{margin:0}
/* overflow-y hidden: the collapsed border of the last row sticks out 1px and would add a vertical scrollbar */
.inv-dt-wrap{overflow-x:auto;overflow-y:hidden;border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
  box-shadow:var(--sh-1)}
.inv-dt-table{min-width:40rem}
.inv-dt-md .inv-dt-table{min-width:32.5rem}
.inv-dt-sm .inv-dt-table{min-width:25rem}
.inv-dt-table>thead>tr>th{position:sticky;top:0;z-index:1}
.inv-dt-table>tbody>tr:last-child>*{border-bottom-width:0}
.inv-dt-check{width:2.25rem}
.inv-dt-check .form-check-input{margin:0;vertical-align:middle}
.inv-dt-table tr.is-disabled{opacity:.55}
.inv-dt-table td a{color:inherit;text-decoration:none}
.inv-dt-table td a:hover{color:var(--primary);text-decoration:underline}
.inv-dt-table td a:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:2px}
/* GATE-9: a button in a cell keeps its label on one line — Thai has no spaces, so a narrow column broke "เพิกถอน"
   into "เพิก / ถอน" (admin/support-access); the table scrolls in its frame instead */
.inv-dt-table td .btn{white-space:nowrap}
.inv-dt-grand{font-size:var(--t-lg);font-weight:600}
/* Bootstrap colours every cell (.table>:not(caption)>*>*): the decision colours must win over it */
.inv-dt-table td.inv-tone-ok{color:var(--c-ok-fg)}
.inv-dt-table td.inv-tone-warn{color:var(--c-warn-fg)}
.inv-dt-table td.inv-tone-err{color:var(--c-err-fg)}
.inv-dt-table td.inv-tone-muted{color:var(--ink-3)}
.inv-dt-skeleton>td .inv-skel{max-width:10rem}
.inv-dt-skeleton>td.ta-r .inv-skel{margin-left:auto;max-width:4rem}
.inv-sort{display:inline-flex;align-items:center;gap:var(--s1);color:inherit;text-decoration:none}
/* which way the column is sorted: the sprite's chevron, smaller than the label it follows (P1-19) */
.inv-sort .inv-sort-dir{width:var(--s3);height:var(--s3);flex:none}
.inv-sort:hover{color:var(--primary)}
.inv-sort:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:2px}
/* row actions: quiet until the row is under the pointer or holds the keyboard focus, never hidden from a
   screen reader and never hidden on a touch screen, where there is no hover (P1-20) */
.inv-row-actions{display:inline-flex;gap:var(--s1);opacity:0;transition:opacity var(--dur)}
.inv-dt-table tr:hover .inv-row-actions,
.inv-dt-table tr:focus-within .inv-row-actions{opacity:1}
@media (hover:none){ .inv-row-actions{opacity:1} }
.inv-thumb{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;margin-right:var(--s2);
  border-radius:var(--r-sm);background:var(--primary-soft);color:var(--primary);font-size:.875rem;vertical-align:middle}
.inv-thumb.is-photo{object-fit:cover;border:1px solid var(--line)}
/* phones: one line per row (touch height) and the table scrolls sideways, instead of words broken per syllable */
@media (max-width:40rem){ .inv-dt-table{min-width:35rem} .inv-dt-table td{white-space:nowrap} }
@media (pointer:coarse){ .inv-dt-table>:not(caption)>*>*{height:var(--row-h-touch)} }

.inv-bulkbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);margin-bottom:var(--s2);padding:var(--s2) var(--s3);
  border:1px solid var(--primary);border-radius:var(--r);background:var(--primary-soft);font-size:var(--t-sm)}
.inv-bulkbar .btn{min-height:1.875rem;padding-top:0;padding-bottom:0}
@media (pointer:coarse){ .inv-bulkbar .btn{min-height:var(--tap)} }
.inv-bulkbar-clear{margin-left:auto}
.inv-bulkbar-error{display:flex;gap:var(--s1);color:var(--c-err-fg);font-size:var(--t-xs);font-weight:500}

.inv-pager{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--s2);margin-top:var(--s4)}
.inv-pager-status{margin-right:auto;color:var(--ink-3);font-size:var(--t-sm)}
.inv-pager .pagination{margin:0}
.inv-pager .page-link{border-radius:var(--r-sm)}
.inv-pager .page-item+.page-item{margin-left:var(--s1)}

/* ============================================================ 14. ConfirmModal + SlideOver (Bootstrap modal / offcanvas) */
.inv-confirm .modal-title,
.inv-slide .offcanvas-title{font-size:var(--t-lg)}
.inv-confirm-message{margin:0 0 var(--s2);font-weight:500}
.inv-confirm-details{margin:0 0 var(--s3);padding-left:var(--s5);color:var(--ink-2);font-size:var(--t-sm)}
.inv-confirm-extra{margin-bottom:var(--s3)} /* P1-09: the one field a confirmed action may ask for */
.inv-confirm-error:empty{display:none}
.inv-slide{--bs-offcanvas-width:min(28rem,100vw)}
.inv-slide-lg{--bs-offcanvas-width:min(40rem,100vw)}
.inv-slide .offcanvas-header{border-bottom:1px solid var(--line)}
.inv-slide .offcanvas-body.htmx-request{opacity:.6;transition:opacity var(--dur) ease-out}
.inv-slide-actions{display:flex;justify-content:flex-end;gap:var(--s2);margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--line)}
@media (max-width:40rem){ .inv-slide-actions .btn{flex:1;min-height:var(--tap)} }

/* ============================================================ 15. touch targets (styleguide section 10: no control under 44px on a phone) */
/* GATE-0 visual QA: on a touch screen every button and field grows to --tap (48px), as the warehouse theme does with
   --control-h; the small inline parts (chips, link buttons, close crosses, sort headers, link pills) keep their look
   and get an invisible hit area of at least --tap around them instead (min(0%, …) only ever grows the area) */
@media (pointer:coarse){
  .btn,
  .form-control,
  .form-select,
  .skip-link,
  .inv-seg button,
  .inv-seg a,
  .inv-dt-columns .btn{min-height:var(--tap)}
  /* P6-15: an icon-only segment (the theme switch of the sign-in page) was 38px wide */
  .inv-seg button,
  .inv-seg a{min-width:var(--tap)}
  .nav-link,
  .dropdown-item,
  .page-link{display:flex;align-items:center;min-height:var(--tap)}
  .page-link{justify-content:center;min-width:var(--tap)}
  /* underline tabs have no side padding: a short label ("ส่งของ") reaches --tap wide through half the gap on each side,
     so the row does not wrap on a 360px phone. P1-19: the same hit area also grows to --tap DOWNWARDS — the tab
     itself is only 40px tall, and a tab is a control like any other. */
  /* P6-15: a one-syllable label ("รูป") was 16px, so even with half the gap on each side its hit area was 32px wide —
     the tab itself is at least --tap minus the gap, and the gap makes up the rest */
  .nav-underline .nav-link{position:relative;justify-content:center;min-width:calc(var(--tap) - var(--bs-nav-underline-gap))}
  .nav-underline .nav-link::after{content:"";position:absolute;
    inset:min(0%,calc((100% - var(--tap)) / 2)) calc(var(--bs-nav-underline-gap) / -2)}
  /* P1-19: a table header row is as tall as its data rows, and a checkbox in a table is big enough to hit with a
     thumb — 12px was the "select every row" box of a list on a phone. */
  .inv-dt-table>thead>tr>th{height:var(--row-h-touch)}
  .inv-dt-table .form-check-input{width:var(--s5);height:var(--s5);margin-top:0}
  /* GATE-2: the box itself stays 24px so a row still reads as a row, and — like every other small control in this
     block — it gets the invisible hit area that makes the thumb target --tap in both directions. */
  .inv-dt-table .form-check-input{position:relative}
  .inv-dt-table .form-check-input::after{content:"";position:absolute;inset:min(0%,calc((100% - var(--tap)) / 2))}
  /* P1-19: a standalone checkbox / radio ("เพิ่มอีกชิ้นต่อเลย" on the store phone) is 16px in Bootstrap. The box
     grows, and its label — which toggles it — becomes the --tap-tall target next to it. */
  .form-check{min-height:var(--tap)}
  .form-check .form-check-input{width:var(--s5);height:var(--s5);margin-top:calc((var(--tap) - var(--s5)) / 2)}
  .form-check .form-check-label{display:flex;align-items:center;min-height:var(--tap)}
  .chip,
  .btn-link.btn-sm,
  .btn-close,
  .inv-sort,
  .crumb a,
  .inv-dash-h a,
  .inv-dash-list a,
  a.inv-tl-title,
  a.inv-step,
  a.pill{position:relative}
  .chip::after,
  .btn-link.btn-sm::after,
  .btn-close::after,
  .inv-sort::after,
  .crumb a::after,
  .inv-dash-h a::after,
  .inv-dash-list a::after,
  a.inv-tl-title::after,
  a.inv-step::after,
  a.pill::after{content:"";position:absolute;inset:min(0%,calc((100% - var(--tap)) / 2))}
  /* P4-17 (visual QA of phase 4 at 360px, pointer:coarse): the rows of a dashboard list were 39px apart, so the --tap
     hit areas of two vendor links overlapped and each got less than 44px; the counts of a dashboard card ("1 PR
     รออนุมัติ") were 35px links; the arrows / bin of a repeater row were --control-h wide; a document number in a
     detail card's key-value list ("RFQ-2569-0001") was an 18px text link. */
  .inv-dash-list li{min-height:var(--tap)}
  .inv-dash-nums a{display:inline-flex;align-items:center;min-height:var(--tap)}
  .inv-rep-tools .inv-rep-tool{min-width:var(--tap);min-height:var(--tap)}
  /* two short crumbs ("ผู้ขาย / V-0001") and the "ล้างตัวกรอง" chip right above the tabs of a report had their --tap hit
     areas overlapping the neighbour's, so each kept about 42px: a little room between them gives each its own 48px */
  /* P6-15: a long crumb (an English maintenance plan or RFQ name) wrapped onto two lines as two inline boxes — like the
     timeline titles of GATE-5, the link is one box, so its hit area covers both lines */
  .crumb a{display:inline-block;margin-inline:var(--s1)}
  .inv-chips .inv-chip-clear{margin-block:var(--s1)}
  /* P6-15: a row of chips that wraps on a phone — the --tap hit area of a 29px chip reaches 10px past it on each side,
     so the rows (and two short chips side by side) keep that far apart and no chip loses its edge to the next one */
  .inv-chips,
  .inv-crop-aspects{gap:var(--s5) var(--s3)}
  .inv-meta-list li:has(a){align-items:center;min-height:var(--tap)}
  .inv-meta-list a{position:relative}
  .inv-meta-list a::after{content:"";position:absolute;inset:min(0%,calc((100% - var(--tap)) / 2))}
  /* GATE-5 (visual QA of phase 5 at 360px, pointer:coarse): a link that wraps onto two lines — a long goods name as the
     title of a timeline step, of a kanban card or in a key-value row — was two inline boxes, so a thumb between the lines
     or beside the shorter line hit the list, not the link (the kanban title was a 39px text link with no hit area at
     all); the small links under a timeline step (the ledger row "รายการ …") were 19px tall with the next step's title right below. */
  a.inv-tl-title,
  .inv-meta-list a{display:inline-block}
  .inv-kanban-title a{position:relative;display:block}
  .inv-kanban-title a::after{content:"";position:absolute;inset:min(0%,calc((100% - var(--tap)) / 2))}
  .inv-tl-links a{min-height:var(--tap)}
}

/* ============================================================ 16. Repeater (P1-02): rows added / removed / reordered in one form */
.inv-rep{min-width:0;margin:0;padding:0;border:0}
.inv-rep-legend{float:none;width:auto;margin:0 0 var(--s1);font-size:var(--t-md);font-weight:600;line-height:var(--lh-head)}
.inv-rep-legend .inv-spin{vertical-align:middle;color:var(--ink-3)}
.inv-rep-hint{margin:0 0 var(--s3)}
.inv-rep>.inv-inline-error{margin:0 0 var(--s3)}
.inv-rep-list{display:grid;gap:var(--s3);margin:0 0 var(--s3);padding:0;list-style:none}
.inv-rep-list:empty{display:none}
.inv-rep-row{display:flex;align-items:flex-start;gap:var(--s2);padding:var(--s3);border:1px solid var(--line);border-radius:var(--r);
  background:var(--surface);transition:border-color var(--dur) ease-out}
.inv-rep-row:hover{border-color:var(--line-strong)}
.inv-rep-row:focus-within{border-color:var(--focus)}
.inv-rep-fields{flex:1;min-width:0}
.inv-rep-tools{display:flex;flex:none;gap:var(--s1)}
.inv-rep-tool{min-width:var(--control-h)}
.inv-rep-tool:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.inv-rep-remove:hover:not(:disabled){color:var(--c-err-fg)}
.inv-rep-empty{margin:0 0 var(--s3)}
.inv-rep-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2)}
.inv-rep.is-error>.inv-rep-legend{color:var(--c-err-fg)}
.inv-rep.is-disabled .inv-rep-row{background:var(--surface-2)}
.inv-rep.is-disabled .inv-rep-tool:disabled{opacity:.45}
.inv-rep.is-busy .inv-rep-list{opacity:.6}
.inv-rep.is-busy{cursor:progress}
@media (max-width:40rem){ .inv-rep-row{flex-direction:column;align-items:stretch} .inv-rep-tools{align-self:flex-end} }

/* ============================================================ 17. AttributeFields (P1-02): the fields of a category's attributes */
.inv-attrs{display:grid;gap:var(--s4)}
@media (min-width:40rem){ .inv-attrs-2{grid-template-columns:repeat(2,minmax(0,1fr))} }
.inv-attrs-empty{margin:0}
.inv-attrs.is-error .inv-inline-error{margin-top:0}
.inv-attrs.is-loading{cursor:progress}

/* ============================================================ 18. Tree rows in a DataTable (category list, later locations) */
.inv-tree{display:inline-flex;align-items:baseline;gap:var(--s1)}
.inv-tree.is-d1{padding-left:var(--s5)}
.inv-tree.is-d2{padding-left:calc(var(--s5) * 2)}
.inv-tree.is-d3{padding-left:calc(var(--s5) * 3)}
.inv-tree.is-d4{padding-left:calc(var(--s5) * 4)}
.inv-tree.is-d5{padding-left:calc(var(--s5) * 5)}
.inv-tree-mark{display:inline-flex;align-self:center;color:var(--ink-3)}
.inv-tree-mark .inv-icon{width:var(--s4);height:var(--s4)}

/* ============================================================ 19. ChoiceGroup (P1-03): one choice out of a few, as cards */
.inv-choice{min-width:0;margin:0;padding:0;border:0}
.inv-choice-legend{float:none;width:auto;margin:0 0 var(--s2);font-size:var(--t-sm);font-weight:600;color:var(--ink-2);line-height:var(--lh-body)}
.inv-choice-legend .inv-spin{vertical-align:middle;color:var(--ink-3)}
.inv-choice-grid{display:grid;gap:var(--s2)}
@media (min-width:40rem){
  .inv-choice-2 .inv-choice-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .inv-choice-3 .inv-choice-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
}
.inv-choice-opt{display:flex;align-items:flex-start;gap:var(--s2);margin:0;padding:var(--s3);border:1px solid var(--line-strong);border-radius:var(--r);
  background:var(--surface);cursor:pointer;transition:border-color var(--dur) ease-out,background-color var(--dur) ease-out}
.inv-choice-opt:hover{border-color:var(--primary)}
.inv-choice-input{flex:none;width:var(--s4);height:var(--s4);margin:var(--s1) 0 0;accent-color:var(--primary)}
.inv-choice-input:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.inv-choice-opt:has(.inv-choice-input:focus-visible){border-color:var(--focus)}
.inv-choice-opt:has(.inv-choice-input:checked){border-color:var(--primary);background:var(--primary-soft)}
.inv-choice-body{display:grid;gap:var(--s1);min-width:0}
.inv-choice-title{font-weight:600;color:var(--ink);line-height:var(--lh-body)}
.inv-choice-hint{font-size:var(--t-sm);color:var(--ink-3);line-height:var(--lh-body)}
.inv-choice-opt:has(.inv-choice-input:disabled){cursor:not-allowed;background:var(--surface-2);border-color:var(--line)}
.inv-choice-opt:has(.inv-choice-input:disabled) .inv-choice-title,
.inv-choice-opt:has(.inv-choice-input:disabled) .inv-choice-hint{color:var(--ink-3)}
.inv-choice-opt:has(.inv-choice-input:disabled:checked){border-color:var(--line-strong);background:var(--surface-2)}
.inv-choice.is-disabled .inv-choice-opt:hover{border-color:var(--line)}
.inv-choice.is-busy .inv-choice-grid{opacity:.6}
.inv-choice.is-busy{cursor:progress}
.inv-choice.is-error>.inv-choice-legend{color:var(--c-err-fg)}
.inv-choice.is-error .inv-choice-opt{border-color:var(--c-err)}
.inv-choice>.inv-inline-error{margin:var(--s2) 0 0}
.inv-choice>.form-text{margin-top:var(--s2)}
@media (pointer:coarse){ .inv-choice-opt{min-height:var(--tap)} }
/* compact (P1-05 one-screen form): two cards per row on a phone as well, and tighter cards — the choice has to fit
   on the same screen as the rest of the form, so the cards carry their title only */
@media (max-width:40rem){
  .inv-choice-compact .inv-choice-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .inv-choice-compact .inv-choice-opt{padding:var(--s2)}
}

/* ============================================================ 20. Photos (P1-04): ImageUploader, ImageGallery, ImageTile, CropEditor */
/* ImageUploader: one drop zone, "เลือกรูป" (primary) + "ถ่ายรูป" on touch screens, progress while shrinking / sending */
.inv-upload{margin:0}
.inv-upload-zone{display:flex;align-items:center;gap:var(--s4);padding:var(--s4);border:2px dashed var(--line-strong);border-radius:var(--r-lg);
  background:var(--surface);transition:border-color var(--dur) ease-out,background-color var(--dur) ease-out}
.inv-upload-zone:hover,
.inv-upload:focus-within .inv-upload-zone{border-color:var(--primary)}
.inv-upload.is-over .inv-upload-zone{border-color:var(--primary);background:var(--primary-soft)}
.inv-upload-icon{flex:none;font-size:var(--t-2xl);line-height:1}
.inv-upload-body{display:grid;gap:var(--s2);min-width:0}
.inv-upload-body .form-text{margin:0}
.inv-upload-title{margin:0;font-weight:600}
.inv-upload-acts{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2)}
.inv-upload-drop{color:var(--ink-3);font-size:var(--t-sm)}
/* the real file fields stay focusable for the keyboard; their labels are the buttons people see */
.inv-upload-input{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;white-space:nowrap}
.inv-upload-pick{margin:0;cursor:pointer}
.inv-upload-input:focus-visible+.inv-upload-pick{outline:2px solid var(--focus);outline-offset:2px}
.inv-upload-input:disabled+.inv-upload-pick{opacity:.65;pointer-events:none;cursor:not-allowed}
.inv-upload-camera,
.inv-upload-input.is-camera{display:none}
.inv-upload.is-busy .inv-upload-zone{cursor:progress}
.inv-upload.is-disabled .inv-upload-zone{border-style:solid;background:var(--surface-2);color:var(--ink-3)}
.inv-upload.is-disabled .inv-upload-zone:hover{border-color:var(--line-strong)}
.inv-upload.is-error .inv-upload-zone{border-color:var(--c-err)}
.inv-upload>.inv-inline-error{margin:var(--s2) 0 0}
.inv-upload-progress{display:grid;gap:var(--s1);margin-top:var(--s2);font-size:var(--t-sm);color:var(--ink-2)}
/* How far the upload has got is data, so it is handed over as a custom property (like --inv-cap), never as an
   inline width — the look of the bar stays in this file (P1-19 visual QA). */
.inv-upload-progress .progress-bar{width:var(--inv-progress,0%)}
@media (pointer:coarse){ .inv-upload-camera{display:inline-flex} .inv-upload-input.is-camera{display:block} .inv-upload-drop{display:none} }
@media (max-width:40rem){
  .inv-upload-zone{padding:var(--s3)}
  .inv-upload-icon{display:none}
  .inv-upload-acts .inv-upload-pick{flex:1 1 auto;justify-content:center}
}
/* stage mode (P1-05): the photos wait in the form — thumbnails with a remove button, no gallery yet */
.inv-upload.is-stage .inv-upload-zone{border-style:solid}
.inv-upload-shots{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,6.5rem),1fr));gap:var(--s2);
  margin:var(--s3) 0 0;padding:0;list-style:none}
.inv-upload-shot{position:relative;overflow:hidden;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface-2)}
.inv-upload-shot img{display:block;width:100%;height:100%;aspect-ratio:1/1;object-fit:cover}
.inv-upload-shot-off{position:absolute;top:var(--s1);right:var(--s1);min-width:1.75rem;padding:0 var(--s2);border-radius:var(--r-pill);
  background:color-mix(in srgb,var(--surface) 88%,transparent);color:var(--ink-2);font-weight:700;line-height:1.75rem}
.inv-upload-shot-off:hover{background:var(--surface);color:var(--c-err-fg)}
.inv-upload-shot-off:focus-visible{outline:2px solid var(--focus);outline-offset:1px}
.inv-upload-shot-off:disabled{opacity:.5;cursor:not-allowed}
@media (pointer:coarse){ .inv-upload-shot-off{min-width:var(--tap);min-height:var(--tap);line-height:var(--tap)} }

/* ImageGallery: heading + count, the refusals of the last upload, tiles in a responsive grid */
.inv-gallery{display:grid;gap:var(--s3)}
.inv-gallery-head{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:var(--s2)}
.inv-gallery-head h2{margin:0;font-size:var(--t-md)}
.inv-gallery-count{color:var(--ink-3);font-size:var(--t-sm)}
.inv-gallery-form{margin:0}
.inv-gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,9.5rem),1fr));gap:var(--s3);margin:0;padding:0;list-style:none}
@media (max-width:40rem){ .inv-gallery-grid{grid-template-columns:repeat(2,minmax(0,1fr))} }
.inv-gallery-hint{margin:0}
.inv-gallery-errors{padding:var(--s3);border:1px solid var(--c-err);border-radius:var(--r);background:var(--c-err-bg);font-size:var(--t-sm)}
.inv-gallery-errors p{margin:0 0 var(--s1);color:var(--c-err-fg);font-weight:600}
.inv-gallery-errors ul{margin:0;padding-left:var(--s5);color:var(--ink)}
.inv-gallery-form.htmx-request .inv-gallery-grid{opacity:.6;cursor:progress}
.inv-gallery.is-loading{cursor:progress}
.inv-skel-tile{width:100%;height:100%;border-radius:0}

/* ImageTile: square picture, number + ⋯ menu; cover = brand border + badge; state strip over the picture */
.inv-tile{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--line);border-radius:var(--r);
  background:var(--surface);box-shadow:var(--sh-1);transition:box-shadow var(--dur) ease-out,border-color var(--dur) ease-out}
.inv-tile:hover{border-color:var(--line-strong);box-shadow:var(--sh-2)}
.inv-tile:focus-within{border-color:var(--focus)}
.inv-tile.is-cover{border-color:var(--primary)}
.inv-tile[draggable="true"]{cursor:grab}
.inv-tile.is-dragging{opacity:.45;cursor:grabbing}
.inv-tile-media{position:relative;aspect-ratio:1/1;overflow:hidden;border-radius:calc(var(--r) - 1px) calc(var(--r) - 1px) 0 0;background:var(--surface-2)}
.inv-tile-media img{display:block;width:100%;height:100%;object-fit:cover}
.inv-tile-empty{display:grid;place-items:center;width:100%;height:100%;font-size:var(--t-2xl);opacity:.45}
.inv-tile-badge{position:absolute;top:var(--s2);left:var(--s2);padding:0 var(--s2);border-radius:var(--r-pill);background:var(--primary);
  color:var(--primary-ink);font-size:var(--t-xs);font-weight:600;line-height:var(--lh-body)}
.inv-tile-state{position:absolute;right:0;bottom:0;left:0;display:flex;align-items:center;gap:var(--s1);padding:var(--s1) var(--s2);
  background:color-mix(in srgb,var(--surface) 90%,transparent);color:var(--ink-2);font-size:var(--t-xs);font-weight:500}
.inv-tile.is-processing .inv-tile-media img{opacity:.8}
.inv-tile.is-stalled .inv-tile-state{color:var(--c-warn-fg)}
.inv-tile.is-failed{border-color:var(--c-err)}
.inv-tile.is-failed .inv-tile-state{color:var(--c-err-fg)}
.inv-tile.is-busy{cursor:progress}
.inv-tile.is-busy .inv-tile-media img{opacity:.6}
.inv-tile.is-disabled{box-shadow:none}
.inv-tile.is-disabled:hover{border-color:var(--line)}
.inv-tile.is-cover.is-disabled:hover{border-color:var(--primary)}
.inv-tile-bar{display:flex;align-items:center;justify-content:space-between;gap:var(--s1);min-height:2.25rem;padding:0 var(--s1) 0 var(--s2);
  color:var(--ink-2);font-size:var(--t-sm)}
.inv-tile-no{font-weight:500}
.inv-tile-menu{min-width:2rem;font-weight:700;letter-spacing:.05em}
.inv-tile-menu:focus-visible{outline:2px solid var(--focus);outline-offset:1px}
/* P6-15: the "⋯" menu of a photo tile was 38px wide on a phone (section 15: no control under --tap on a touch screen) */
@media (pointer:coarse){ .inv-tile-menu{min-width:var(--tap)} }
.dropdown-item.inv-tone-err{color:var(--c-err-fg)}

/* CropEditor: the kept original with a frame; outside the frame is shaded, corners are handles */
.inv-crop{display:grid;gap:var(--s3)}
.inv-crop .form-text{margin:0}
.inv-crop-stage{position:relative;justify-self:center;max-width:100%;line-height:0;user-select:none;touch-action:none;
  border-radius:var(--r-sm);overflow:hidden;background:var(--surface-2)}
.inv-crop-img{display:block;max-width:100%;max-height:60vh;width:auto;height:auto}
.inv-crop-box{position:absolute;box-sizing:border-box;border:2px solid var(--primary-ink);outline:1px solid var(--primary);cursor:move;
  box-shadow:0 0 0 100vmax color-mix(in srgb,var(--c-n-900) 55%,transparent);touch-action:none}
.inv-crop-box:hover{outline-width:2px}
.inv-crop-box:focus-visible{outline:3px solid var(--focus);outline-offset:0}
.inv-crop-handle{position:absolute;width:var(--s4);height:var(--s4);border:2px solid var(--primary-ink);border-radius:50%;background:var(--primary);touch-action:none}
.inv-crop-handle.is-nw{top:calc(var(--s2) * -1);left:calc(var(--s2) * -1);cursor:nwse-resize}
.inv-crop-handle.is-ne{top:calc(var(--s2) * -1);right:calc(var(--s2) * -1);cursor:nesw-resize}
.inv-crop-handle.is-sw{bottom:calc(var(--s2) * -1);left:calc(var(--s2) * -1);cursor:nesw-resize}
.inv-crop-handle.is-se{right:calc(var(--s2) * -1);bottom:calc(var(--s2) * -1);cursor:nwse-resize}
@media (pointer:coarse){
  .inv-crop-handle{width:var(--s5);height:var(--s5)}
  .inv-crop-handle.is-nw,.inv-crop-handle.is-ne{top:calc(var(--s3) * -1)}
  .inv-crop-handle.is-sw,.inv-crop-handle.is-se{bottom:calc(var(--s3) * -1)}
  .inv-crop-handle.is-nw,.inv-crop-handle.is-sw{left:calc(var(--s3) * -1)}
  .inv-crop-handle.is-ne,.inv-crop-handle.is-se{right:calc(var(--s3) * -1)}
}
.inv-crop-tools{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s2)}
.inv-crop-aspects{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);min-width:0;margin:0;padding:0;border:0}
.inv-crop-label,
.inv-crop-size{color:var(--ink-3);font-size:var(--t-sm)}
.inv-crop-acts{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--s2)}
.inv-crop-waiting{display:flex;align-items:center;gap:var(--s2);margin:0;padding:var(--s4);border-radius:var(--r);background:var(--surface-2);color:var(--ink-2)}
.inv-crop.is-disabled .inv-crop-acts .btn{cursor:not-allowed}
.inv-crop.is-busy .inv-crop-stage{opacity:.6;cursor:progress}
.inv-crop.is-error .inv-crop-stage{outline:2px solid var(--c-err);outline-offset:2px}
@media (pointer:coarse){ .inv-crop-acts .btn{flex:1 1 auto;min-height:var(--tap)} }

/* CapacityBar: how full a storage place is (P1-08). Green while it fits, amber from 90% on, red when it is over —
   the figures beside the bar say the same thing in words, so colour is never the only signal. */
.inv-cap{display:grid;gap:var(--s1);min-width:0}
.inv-cap-figures{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:var(--s2);margin:0;font-size:var(--t-sm)}
.inv-cap-value{color:var(--ink);font-variant-numeric:tabular-nums}
.inv-cap-note{color:var(--ink-3)}
.inv-cap-track{display:block;overflow:hidden;height:var(--s2);border-radius:var(--r-pill);background:var(--line)}
.inv-cap-fill{display:block;width:var(--inv-cap,0%);height:100%;border-radius:inherit;background:var(--c-ok);transition:width var(--dur) ease}
.inv-cap.is-warn .inv-cap-fill{background:var(--c-warn)}
.inv-cap.is-warn .inv-cap-note{color:var(--c-warn-fg)}
.inv-cap.is-over .inv-cap-fill{background:var(--c-err)}
.inv-cap.is-over .inv-cap-note,
.inv-cap.is-over .inv-cap-value{color:var(--c-err-fg)}
.inv-cap.is-compact .inv-cap-figures{flex-wrap:nowrap;white-space:nowrap}
.inv-cap.is-loading .inv-cap-track{background:var(--line);opacity:.6}
@media (prefers-reduced-motion:reduce){ .inv-cap-fill{transition:none} }
/* Buttons inside a table row (the storage tree of P1-08: up / down / print label), right-aligned, wrapping on phones */
.inv-row-acts{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--s1)}
@media (pointer:coarse){ .inv-row-acts .btn{min-height:var(--tap)} }

/* FEFO pick suggestion inside the movement form (P1-10): one row per place that holds a lot, the one to empty first
   marked with a brand rule down its side. Every row also says in words what to take, so the mark is never the only
   signal; an expired row keeps the neutral row colour and carries its own pill. */
.inv-fefo-list li{align-items:flex-start;gap:var(--s3)}
.inv-fefo-list li>span{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);min-width:0}
.inv-fefo-list li>b{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--s2);text-align:right}
.inv-fefo-list li[data-fefo="first"]{padding-left:var(--s3);border-left:3px solid var(--c-brand-600)}
.inv-fefo-expired{color:var(--c-warn-fg)}
@media (pointer:coarse){ .inv-fefo-list li>b .btn{min-height:var(--tap)} }

/* PayQr (P2-09) — the PromptPay code with its amount beside it. The white plate and the black modules are drawn
   INSIDE the SVG, quiet zone included (PromptPayQr::QUIET_ZONE), so the contrast a camera needs survives every
   theme, every card and a printed page — the CSS here only places it. It sits side by side from 40rem up and
   stacks on a phone, where the code comes first (the customer is looking for it). */
.inv-payqr{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s5);align-items:start}
.inv-payqr-code{display:flex;align-items:center;justify-content:center;border:1px solid var(--line);
  border-radius:var(--r);overflow:hidden;box-shadow:var(--sh-1)}
.inv-payqr-code svg{display:block;width:100%;max-width:15rem;height:auto}
.inv-payqr-side{display:flex;flex-direction:column;gap:var(--s3);min-width:0}
.inv-payqr-amount{margin:0;font-family:var(--f-mono);font-size:var(--t-2xl);font-weight:600;line-height:var(--lh-head);color:var(--ink)}
.inv-payqr-amount small{margin-left:var(--s2);font-family:var(--f-sans);font-size:var(--t-sm);font-weight:400;color:var(--ink-3)}
.inv-payqr-for{margin:0;font-size:var(--t-sm);color:var(--ink-2)}
.inv-payqr-who{margin:0}
.inv-payqr-hint{margin:0;font-size:var(--t-sm);color:var(--ink-3)}
.inv-payqr-copy{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2)}
.inv-payqr-payload{flex:1 1 12rem;min-width:0;overflow:hidden;font-family:var(--f-mono);font-size:var(--t-xs);
  color:var(--ink-3);text-overflow:ellipsis;white-space:nowrap}
.inv-payqr.is-busy .inv-payqr-code{opacity:.6}
.inv-payqr.is-error{gap:var(--s3)}
@media (min-width:40rem){ .inv-payqr{grid-template-columns:auto minmax(0,1fr)} }

/* ============================================================ 21. AddressFields (P3-08, ADR 0055): four Thai address boxes that fill each other */
.inv-addr{display:grid;gap:var(--s4);min-width:0}
.inv-addr-cell{position:relative;min-width:0}
/* the box that is asking: the field's own busy look (spinner after the label), added and removed by the script */
.inv-addr .form-label .inv-addr-spin{margin-left:var(--s1)}
/* The listbox hangs under the box that asked. Where the box ends is layout, handed over by the script as a custom
   property (like --inv-cap). */
.inv-addr-list{--inv-addr-top:100%;position:absolute;top:var(--inv-addr-top);right:0;left:0;z-index:1000;max-height:min(45vh,22rem);
  overflow-y:auto;margin-top:var(--s1);padding:var(--s1);border:1px solid var(--line-strong);border-radius:var(--r-sm);
  background:var(--surface);box-shadow:var(--sh-3)}
/* a list the page renders open (the kit's state) stays in the flow instead of covering what follows */
.inv-addr-list.is-static{position:static}
.inv-addr-opt{display:flex;align-items:center;gap:var(--s3);min-height:var(--row-h);padding:var(--s2) var(--s3);
  border-radius:var(--r-sm);color:var(--ink);cursor:pointer;scroll-margin:var(--s1);transition:background var(--dur) ease-out}
.inv-addr-opt:hover,
.inv-addr-opt.is-active{background:var(--primary-soft);color:var(--primary-soft-ink)}
/* the option the keyboard is on also carries the focus ring: it is where Enter goes (styleguide rule 7) */
.inv-addr-opt.is-active{outline:2px solid var(--focus);outline-offset:-2px}
.inv-addr-opt-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0;line-height:var(--lh-head)}
.inv-addr-opt-text b{overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}
.inv-addr-opt-text small{overflow:hidden;color:var(--ink-3);font-size:var(--t-xs);text-overflow:ellipsis;white-space:nowrap}
.inv-addr-opt:hover .inv-addr-opt-text small,
.inv-addr-opt.is-active .inv-addr-opt-text small{color:var(--primary-soft-ink)}
.inv-addr-opt-zip{flex:none;color:var(--ink-2);font-family:var(--f-mono);font-size:var(--t-sm);font-variant-numeric:tabular-nums;text-align:right}
.inv-addr-opt:hover .inv-addr-opt-zip,
.inv-addr-opt.is-active .inv-addr-opt-zip{color:var(--primary-soft-ink)}
/* nothing found / the lookup failed: one sentence, the person types on */
.inv-addr-note{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);color:var(--ink-3);font-size:var(--t-sm);
  line-height:var(--lh-body)}
.inv-addr-note .inv-icon{flex:none}
.inv-addr-note.is-error{color:var(--c-err-fg)}
.inv-addr.is-disabled .inv-addr-list{display:none}
@media (pointer:coarse){
  .inv-addr-opt{min-height:var(--tap)}
}
@media (prefers-reduced-motion:reduce){
  .inv-addr-opt{transition:none}
}

/* ============================================================ P2-06 เอกสาร PDF — the print panel of an order and
   the public verification page. Two surfaces, both built from the same tokens as every other screen so the PDFs
   (Templates/document.css) and the pages that produce them read as one system (ADR 0028). */
.inv-doc-heading{margin:0 0 var(--s3);font-size:var(--t-lg)}
.inv-doc-actions{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s2)}
.inv-doc-action{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s2);
  padding:var(--s3);border:1px solid var(--line);border-radius:var(--r);background:var(--surface-2)}
.inv-doc-action-label{display:grid;gap:var(--s1);min-width:0}
.inv-doc-action .inv-row-acts{justify-content:flex-start}

/* The verification page (public layout): one centred column, the answer first and biggest. */
.inv-public{margin:0;background:var(--bg);color:var(--ink)}
.inv-public-main{max-width:34rem;margin:0 auto;padding:var(--s6) var(--s4) var(--s4)}
.inv-public-foot{max-width:34rem;margin:0 auto;padding:0 var(--s4) var(--s6);color:var(--ink-3);font-size:var(--t-sm)}
.inv-verify{display:grid;gap:var(--s4);padding:var(--s5);border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface);box-shadow:var(--sh-1)}
.inv-verify-title{margin:0;font-size:var(--t-lg);color:var(--ink-2)}
.inv-verify-subtitle{margin:0 0 var(--s1);font-size:var(--t-md)}
.inv-verify-answer{display:flex;align-items:flex-start;gap:var(--s3);padding:var(--s4);border-radius:var(--r);
  border:1px solid var(--line)}
.inv-verify-answer p{margin:var(--s1) 0 0;font-size:var(--t-sm)}
.inv-verify-answer.is-ok{background:var(--c-ok-bg);border-color:var(--c-ok);color:var(--c-ok-fg)}
.inv-verify-answer.is-warn{background:var(--c-warn-bg);border-color:var(--c-warn);color:var(--c-warn-fg)}
.inv-verify-answer.is-err{background:var(--c-err-bg);border-color:var(--c-err);color:var(--c-err-fg)}
.inv-verify-icon{flex:none;width:var(--s5);height:var(--s5)}
.inv-verify-note{margin:0;font-size:var(--t-sm);color:var(--ink-2)}
.inv-verify-note.is-warn{color:var(--c-warn-fg)}
.inv-verify-check{display:grid;gap:var(--s2);padding-top:var(--s4);border-top:1px solid var(--line)}
@media (pointer:coarse){ .inv-verify-check .btn{min-height:var(--tap)} }

/* P3-06: the public page where a customer signs the rental agreement (public layout) - the same card as /verify,
 * one column, the only primary button full width at the bottom (mobile-first like every hand-held screen). */
.inv-esign{display:grid;gap:var(--s4);padding:var(--s5);border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface);box-shadow:var(--sh-1)}
.inv-esign-title{margin:0;font-size:var(--t-lg);line-height:var(--lh-head);color:var(--ink)}
.inv-esign-lead{margin:0;color:var(--ink-2)}
.inv-esign-note{margin:0;font-size:var(--t-sm);color:var(--ink-3)}
.inv-esign-form{display:grid;gap:var(--s3);padding-top:var(--s4);border-top:1px solid var(--line)}
.inv-esign-form h2{margin:0;font-size:var(--t-md);line-height:var(--lh-head)}
.inv-esign-accept{display:flex;flex-wrap:wrap;align-items:flex-start;gap:var(--s2);padding-left:0;margin:0}
.inv-esign-accept .form-check-input{flex:none;margin:var(--s1) 0 0;float:none}
.inv-esign-accept .form-check-label{flex:1;min-width:0}
.inv-esign-accept .invalid-feedback{flex-basis:100%}
@media (pointer:coarse){
  .inv-esign .btn{min-height:var(--tap)}
  .inv-esign-accept .form-check-input{width:var(--s6);height:var(--s6)}
}
/* P3-06: the "ลายเซ็นลูกค้า" card of an order's documents screen - one row per signature, the link panel under it. */
.inv-esign-card-img{display:block;max-width:100%;height:auto;max-height:var(--s8);border:1px solid var(--line);
  border-radius:var(--r);background:var(--c-n-000)}
.inv-esign-link{display:grid;gap:var(--s3)}
/* P9-03: SecretOnce - a secret shown this once (a new API key). The value box takes the row, the copy button stays
 * beside it (under it on a narrow phone); the "cannot copy" state turns the frame to the error colour. */
.inv-secret{margin-bottom:var(--s4);border-color:var(--primary)}
.inv-secret-title{display:flex;align-items:center;gap:var(--s2);margin:0 0 var(--s2);font-size:var(--t-lg);line-height:var(--lh-head)}
.inv-secret-text{margin:0 0 var(--s3);font-size:var(--t-sm);color:var(--ink-2)}
.inv-secret-row{display:flex;flex-wrap:wrap;gap:var(--s2);margin-bottom:var(--s2)}
.inv-secret-row .inv-secret-value{flex:1 1 20rem;min-width:0;font-family:var(--f-mono);font-variant-numeric:tabular-nums}
.inv-secret-note{margin:0 0 var(--s1);color:var(--ink-3);font-size:var(--t-xs)}
.inv-secret-error{display:flex;gap:var(--s1);margin:0 0 var(--s1);color:var(--c-err-fg);font-size:var(--t-sm);font-weight:500}
.inv-secret.is-error{border-color:var(--c-err)}
@media (pointer:coarse){
  .inv-secret-row .btn{min-height:var(--tap)}
}
/* P9-10: SupportBanner - a Single Point support session (read only, ADR 0110) at the top of every staff page. A warning
 * strip, never a status colour; the last minutes turn it to the error tone. The leave button stays on the row, and takes
 * the full width under the text on a narrow phone. */
.inv-support-banner{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);padding:var(--s2) var(--s4);
  border-bottom:1px solid var(--c-warn);border-left:var(--s1) solid var(--c-warn);background:var(--c-warn-bg);color:var(--ink);font-size:var(--t-sm)}
.inv-support-banner.is-soon{border-color:var(--c-err);background:var(--c-err-bg)}
.inv-support-banner-icon{flex:none;display:inline-flex;color:var(--c-warn-fg)}
.inv-support-banner.is-soon .inv-support-banner-icon{color:var(--c-err-fg)}
.inv-support-banner-body{flex:1 1 20rem;min-width:0}
.inv-support-banner-title,.inv-support-banner-text{margin:0;overflow-wrap:anywhere}
.inv-support-banner-text{color:var(--ink-2)}
.inv-support-banner-meta{margin-left:var(--s2)}
.inv-support-banner-meta .inv-icon{vertical-align:middle}
.inv-support-banner-act{flex:none;margin:0}
@media (max-width:40rem){
  .inv-support-banner{padding:var(--s2) var(--s3)}
  .inv-support-banner-meta{display:block;margin-left:0}
  .inv-support-banner-act{flex-basis:100%}
  .inv-support-banner-act .btn{width:100%}
}
@media (pointer:coarse){
  .inv-support-banner-act .btn{min-height:var(--tap)}
}
/* P9-11: AnnouncementBanner - an announcement of Single Point (ADR 0111) under the top bar of every staff page. The tone
 * comes from the level through the semantic tokens (info / warn / err), never a status colour; `critical` has no close
 * button. The close button is the kit's ghost button (hover / focus ring of inventory.css) and a full tap target on touch. */
.inv-announce{display:flex;align-items:flex-start;gap:var(--s2) var(--s3);padding:var(--s2) var(--s4);
  border-bottom:1px solid var(--c-info);border-left:var(--s1) solid var(--c-info);background:var(--c-info-bg);color:var(--ink);font-size:var(--t-sm)}
.inv-announce.is-warning{border-color:var(--c-warn);background:var(--c-warn-bg)}
.inv-announce.is-critical{border-color:var(--c-err);background:var(--c-err-bg)}
.inv-announce-icon{flex:none;display:inline-flex;align-items:center;min-height:calc(var(--t-sm) * var(--lh-body));color:var(--c-info-fg)}
.inv-announce-body{flex:1 1 auto;min-width:0}
.inv-announce-title,.inv-announce-text{margin:0;overflow-wrap:anywhere}
.inv-announce-level{font-weight:600;color:var(--c-info-fg)}
.inv-announce-text{color:var(--ink-2)}
.inv-announce.is-warning .inv-announce-icon,.inv-announce.is-warning .inv-announce-level{color:var(--c-warn-fg)}
.inv-announce.is-critical .inv-announce-icon,.inv-announce.is-critical .inv-announce-level{color:var(--c-err-fg)}
.inv-announce-close{flex:none;margin:calc(var(--s1) * -1) calc(var(--s2) * -1) calc(var(--s1) * -1) 0;color:var(--ink-2)}
.inv-announce-close:hover{color:var(--ink);background:var(--surface)}
.inv-announce-close:focus-visible{color:var(--ink)}
.inv-announce + .inv-announce{border-top:0}
@media (max-width:40rem){
  .inv-announce{padding:var(--s2) var(--s3)}
}
@media (pointer:coarse){
  .inv-announce-close{min-width:var(--tap);min-height:var(--tap)}
}
/* P9-09: ShellNotice - a strip under the top bar of the staff layout about every page, not the page itself (the
 * organisation data is old, ADR 0102). A tone of the notice palette, never a status colour; it lines up with the
 * content's gutter. The one action is the kit button (hover / focus ring / disabled from inventory.css), 48px on a
 * touch screen, and takes the full width under the text on a narrow phone. */
.inv-shell-notice{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);padding:var(--s2) var(--s5);
  border-bottom:1px solid var(--c-info);border-left:var(--s1) solid var(--c-info);background:var(--c-info-bg);color:var(--ink);font-size:var(--t-sm)}
.inv-shell-notice.is-warning{border-color:var(--c-warn);background:var(--c-warn-bg)}
.inv-shell-notice.is-error{border-color:var(--c-err);background:var(--c-err-bg)}
.inv-shell-notice-icon{flex:none;display:inline-flex;color:var(--c-info-fg)}
.inv-shell-notice.is-warning .inv-shell-notice-icon{color:var(--c-warn-fg)}
.inv-shell-notice.is-error .inv-shell-notice-icon{color:var(--c-err-fg)}
.inv-shell-notice-text{flex:1 1 20rem;min-width:0;margin:0;overflow-wrap:anywhere}
.inv-shell-notice-text span{color:var(--ink-2)}
.inv-shell-notice-act{flex:none;margin:0}
@media (max-width:40rem){
  .inv-shell-notice{padding:var(--s2) var(--s3)}
  /* P8-23: with a 20rem basis the text wrapped under its icon on a 360px phone, leaving the icon alone on a row */
  .inv-shell-notice-text{flex-basis:0}
  .inv-shell-notice-act{flex-basis:100%}
  .inv-shell-notice-act .btn{width:100%}
}
@media (pointer:coarse){
  .inv-shell-notice-act .btn{min-height:var(--tap)}
}
/* P6-07: OfflineQueue - the strip under the phone's top bar about the scans kept on the phone (ADR 0187 §5). A tone of
 * the notice palette per state (no signal = warning, waiting / sending = info, not accepted = error, reached = ok); one
 * glyph per state, the others hidden. The kit buttons carry hover / focus / disabled / busy; 48px on a touch screen and
 * the full width under the text on a narrow phone. Codes are mono; the reasons wrap. */
.inv-offline{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);padding:var(--s2) var(--s3);
  border-bottom:1px solid var(--c-info);border-left:var(--s1) solid var(--c-info);background:var(--c-info-bg);color:var(--ink);font-size:var(--t-sm)}
.inv-offline[hidden]{display:none}
.inv-offline.is-offline{border-color:var(--c-warn);background:var(--c-warn-bg)}
.inv-offline.is-rejected{border-color:var(--c-err);background:var(--c-err-bg)}
.inv-offline.is-synced{border-color:var(--c-ok);background:var(--c-ok-bg)}
.inv-offline-icon{flex:none;display:inline-flex;color:var(--c-info-fg)}
.inv-offline.is-offline .inv-offline-icon{color:var(--c-warn-fg)}
.inv-offline.is-rejected .inv-offline-icon{color:var(--c-err-fg)}
.inv-offline.is-synced .inv-offline-icon{color:var(--c-ok-fg)}
.inv-offline-glyph{display:none}
.inv-offline.is-offline .inv-offline-glyph[data-for="offline"],
.inv-offline.is-pending .inv-offline-glyph[data-for="pending"],
.inv-offline.is-syncing .inv-offline-glyph[data-for="syncing"],
.inv-offline.is-rejected .inv-offline-glyph[data-for="rejected"],
.inv-offline.is-synced .inv-offline-glyph[data-for="synced"]{display:inline-flex}
.inv-offline-text{flex:1 1 0;min-width:0;margin:0;overflow-wrap:anywhere}
.inv-offline-text span{color:var(--ink-2)}
.inv-offline-act{flex:none;display:flex;gap:var(--s2);margin:0}
.inv-offline-act [hidden]{display:none}
.inv-offline-list{flex-basis:100%;display:grid;gap:var(--s1);margin:0;padding:0;list-style:none}
.inv-offline-list[hidden]{display:none}
.inv-offline-list li{display:flex;flex-wrap:wrap;gap:0 var(--s2);overflow-wrap:anywhere}
.inv-offline-list .num{font-family:var(--f-mono);font-weight:600}
.inv-offline-said{margin:0;color:var(--c-info-fg)}
@media (max-width:40rem){
  .inv-offline-act{flex-basis:100%}
  .inv-offline-act .btn{flex:1 1 0}
}
@media (pointer:coarse){
  .inv-offline-act .btn{min-height:var(--tap)}
}
/* P9-17: ManagedField - a value that belongs to Single Point, where a Field would be (ADR 0116). The box has the height,
 * radius and padding of .form-control so a form mixes both without a jump, but the tinted surface and the lock say it
 * cannot be typed. Hover darkens the frame and the lock; the focus ring is the shell's; `is-error` keeps the value and
 * turns the frame to the error tone; `is-disabled` (not linked) is muted and out of the Tab order. */
.inv-managed-box{display:flex;align-items:flex-start;gap:var(--s2);min-height:var(--control-h);padding:var(--s2) var(--s3);
  border:1px solid var(--line);border-radius:var(--r-sm);background:var(--bg);color:var(--ink);font-size:var(--t-sm);
  line-height:var(--lh-body);cursor:default;transition:border-color var(--dur)}
.inv-managed-box:hover{border-color:var(--line-strong)}
.inv-managed-box:focus-visible{outline:2px solid var(--focus);outline-offset:0;border-color:var(--focus)}
.inv-managed-value{flex:1 1 auto;min-width:0;overflow-wrap:anywhere;user-select:text}
.inv-managed-value.num{font-family:var(--f-mono);font-variant-numeric:tabular-nums}
.inv-managed-value.is-empty{color:var(--ink-3)}
.inv-managed-lock{flex:none;display:inline-flex;margin-top:var(--s1);color:var(--ink-3);transition:color var(--dur)}
.inv-managed-box:hover .inv-managed-lock,
.inv-managed-box:focus-visible .inv-managed-lock{color:var(--ink-2)}
.inv-managed-note{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s1);margin:var(--s1) 0 0;color:var(--ink-3);
  font-size:var(--t-xs)}
.inv-managed-note b{color:var(--ink-2);font-weight:600}
.inv-managed-note .inv-icon{color:var(--primary)}
.inv-managed-note a{color:var(--primary)}
.inv-managed.is-error .inv-managed-box{border-color:var(--c-err);background:var(--c-err-bg)}
.inv-managed.is-busy .inv-managed-box{cursor:progress}
.inv-managed.is-disabled .inv-managed-box{border-style:dashed;background:var(--surface);color:var(--ink-3)}
.inv-managed.is-disabled .inv-managed-note .inv-icon{color:var(--ink-3)}
.inv-managed-list{display:flex;flex-wrap:wrap;gap:var(--s1) var(--s2);margin:0;padding:0;list-style:none}
.inv-managed-list li{display:inline-flex;align-items:center;gap:var(--s1)}
.inv-managed-list .is-off{color:var(--ink-3);text-decoration:line-through}
/* P9-17: the settings screen groups its fields by who owns them (Form pattern cards); a line under each card title. */
.inv-form .card-body>h2+.inv-section-lead{margin:calc(var(--s2) * -1) 0 var(--s4);color:var(--ink-3);font-size:var(--t-sm)}
/* P9-17: "กลับ Single Point" in the top bar (DECISIONS #5). A 360px phone has no room left in the bar (menu, search,
 * bell, language, avatar), so there the way back is the first item of the user menu instead. */
.inv-hdr-sp{white-space:nowrap}
@media (max-width:40rem){
  .inv-hdr-sp{display:none}
}
@media (min-width:40.01rem){
  .hdr .dropdown-item.inv-hdr-sp-item,
  .hdr .dropdown-divider.inv-hdr-sp-item{display:none}
}
@media (pointer:coarse){
  .inv-hdr-sp{min-height:var(--tap)}
}

/* ============================================================ P4-16 CommentThread (ADR 0135): the questions and answers
 * of a document on its own page. A comment is a quiet card; the reader's own ones carry the primary tint on the edge. A
 * mention is a soft chip in the text. The @ list hangs under the box like a dropdown: hover and the active option share the
 * soft primary; the focus ring is the box's own. `is-disabled` (a read-only licence) mutes the thread; `is-error` rings
 * the box in the error tone; `is-loading` shows skeleton rows. */
.inv-comments-h{display:flex;align-items:center;gap:var(--s2)}
.inv-comments-count{display:inline-flex;align-items:center;justify-content:center;min-width:var(--s5);padding:0 var(--s2);
  border-radius:var(--r-pill);background:var(--surface-2);color:var(--ink-2);font-size:var(--t-xs);font-weight:600}
.inv-comments-list{display:flex;flex-direction:column;gap:var(--s3);margin:0 0 var(--s4);padding:0;list-style:none}
.inv-comment{padding:var(--s3) var(--s4);border:1px solid var(--line);border-left:3px solid var(--line-strong);
  border-radius:var(--r-sm);background:var(--surface);transition:border-color var(--dur)}
.inv-comment:hover{border-color:var(--line-strong)}
.inv-comment.is-mine{border-left-color:var(--primary)}
.inv-comment-head{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s1) var(--s2);margin-bottom:var(--s1)}
.inv-comment-author{font-size:var(--t-sm)}
.inv-comment-when{color:var(--ink-3);font-size:var(--t-xs)}
.inv-comment-delete{margin-left:auto}
.inv-comment-body{font-size:var(--t-sm);line-height:var(--lh-body);overflow-wrap:anywhere}
.inv-mention{padding:0 var(--s1);border-radius:var(--r-sm);background:var(--primary-soft);color:var(--primary-soft-ink);font-weight:600}
.inv-comments-empty{margin-bottom:var(--s3)}
.inv-mention-wrap{position:relative}
.inv-mention-list{position:absolute;z-index:5;left:0;right:0;max-height:calc(var(--row-h) * 6);margin:calc(var(--s1) * -1) 0 0;
  padding:var(--s1) 0;overflow-y:auto;list-style:none;border:1px solid var(--line-strong);border-radius:var(--r-sm);
  background:var(--surface);box-shadow:var(--sh-2)}
.inv-mention-opt{display:flex;align-items:baseline;gap:var(--s2);min-height:var(--control-h);padding:var(--s2) var(--s3);
  cursor:pointer;font-size:var(--t-sm)}
.inv-mention-opt:hover,
.inv-mention-opt.is-active{background:var(--primary-soft);color:var(--primary-soft-ink)}
.inv-mention-opt.is-active{box-shadow:inset 3px 0 0 var(--focus)}
.inv-mention-handle{color:var(--ink-3);font-family:var(--f-mono);font-size:var(--t-xs)}
.inv-mention-opt.is-active .inv-mention-handle{color:inherit}
.inv-mention-none{padding:var(--s2) var(--s3);color:var(--ink-3);font-size:var(--t-sm)}
.inv-comments-actions{display:flex;justify-content:flex-end;margin-top:var(--s2)}
.inv-comments.is-disabled .inv-comment{background:var(--surface-2)}
.inv-comments.is-disabled .inv-comments-readonly{display:flex;align-items:center;gap:var(--s1)}
.inv-comments.is-error .form-control{border-color:var(--c-err)}
.inv-comments.is-loading .inv-comments-list{display:none}
.inv-comments-form.htmx-request .form-control{cursor:progress}
@media (pointer:coarse){
  .inv-mention-opt{min-height:var(--tap)}
}

/* ============================================================ P5-06 KanbanBoard (spec O1 KanbanColumn, O2 "Board", screens.html
   screen 7): one column per status, one card per job. Columns sit side by side while they fit and stack on a phone — the
   page itself never scrolls sideways. A card moves only through its own buttons (dragging presses one), never optimistically. */
.inv-kanban{display:grid;gap:var(--s3);grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));align-items:start}
.inv-kanban-col{min-width:0;min-height:calc(var(--row-h) * 5);padding:var(--s2);border:1px solid var(--line);border-radius:var(--r);
  background:var(--surface-2);transition:border-color var(--dur),box-shadow var(--dur)}
.inv-kanban-head{display:flex;justify-content:space-between;align-items:center;gap:var(--s2);margin:0;padding:var(--s2);
  color:var(--ink-2);font-size:var(--t-sm);font-weight:600}
.inv-kanban-count{min-width:var(--s5);padding:0 var(--s1);border-radius:var(--r-pill);background:var(--surface);color:var(--ink-3);
  font-size:var(--t-xs);text-align:center}
.inv-kanban-cards{display:flex;flex-direction:column;gap:var(--s2);margin:0;padding:0;list-style:none}
.inv-kanban-card{position:relative;display:flex;flex-direction:column;gap:var(--s1);padding:var(--s3);border:1px solid var(--line);
  border-left:3px solid var(--line);border-radius:var(--r-sm);background:var(--surface);box-shadow:var(--sh-1);font-size:var(--t-sm);
  transition:border-color var(--dur),box-shadow var(--dur),opacity var(--dur)}
.inv-kanban-card:hover{border-color:var(--line-strong);box-shadow:var(--sh-2)}
.inv-kanban-card:focus-within{box-shadow:0 0 0 3px var(--focus)}
.inv-kanban-card.is-urgent{border-left-color:var(--c-err)}
.inv-kanban-card.is-high{border-left-color:var(--c-warn)}
.inv-kanban-card.is-muted{opacity:.72}
.inv-kanban-card.is-muted:hover{opacity:1}
.inv-kanban-card[draggable="true"]{cursor:grab}
.inv-kanban-card.is-dragging{opacity:.5;cursor:grabbing}
.inv-kanban-grip{position:absolute;top:var(--s2);right:var(--s2);color:var(--ink-3)}
.inv-kanban-ref{color:var(--ink-3);font-size:var(--t-xs)}
.inv-kanban-title{padding-right:var(--s4);line-height:var(--lh-head);overflow-wrap:anywhere}
.inv-kanban-title a{color:inherit;text-decoration:none}
.inv-kanban-title a:hover{color:var(--primary);text-decoration:underline}
.inv-kanban-title a:focus-visible{outline:none;box-shadow:0 0 0 3px var(--focus);border-radius:var(--r-sm)}
.inv-kanban-meta,
.inv-kanban-foot{color:var(--ink-3);font-size:var(--t-xs);overflow-wrap:anywhere}
.inv-kanban-pill{margin-top:var(--s1)}
.inv-kanban-moves{display:flex;flex-wrap:wrap;gap:var(--s1);margin-top:var(--s2)}
.inv-kanban-empty{margin:0;padding:var(--s4) var(--s2);color:var(--ink-3);font-size:var(--t-sm);text-align:center}
.inv-kanban.is-dragging .inv-kanban-col{border-style:dashed}
.inv-kanban-col.is-drop-target{border-color:var(--primary);border-style:solid;box-shadow:0 0 0 3px var(--focus)}
.inv-kanban.is-disabled .inv-kanban-card{background:var(--surface-2);box-shadow:none}
.inv-kanban.is-loading .inv-kanban-col{cursor:progress}
.inv-skel-kanban{gap:var(--s2);margin-bottom:var(--s2)}
.inv-kanban.is-error{display:block}
.inv-kanban.htmx-request{opacity:.7;cursor:progress}
@media (pointer:coarse){
  .inv-kanban-moves .btn{min-height:var(--tap)}
  .inv-kanban-grip{display:none}
}
/* a phone reads the columns one under the other: an empty one takes one line, not a tall empty box */
@media (max-width:40rem){
  .inv-kanban-col{min-height:0}
}
@media (prefers-reduced-motion:reduce){
  .inv-kanban-col,.inv-kanban-card{transition:none}
}

/* ============================================================ RouteMap (P6-04, ADR 0177) */
/* the map box of a delivery run: the Longdo map page in a frame (a skeleton behind it until it paints), or — without a
   Longdo key — why, with the Google Maps link of the whole way */
.inv-routemap{display:flex;flex-direction:column;gap:var(--s2)}
.inv-routemap-frame{position:relative;min-height:calc(var(--s8) * 6);border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;background:var(--surface-2)}
.inv-routemap-frame iframe{position:relative;display:block;width:100%;height:calc(var(--s8) * 6);border:0;background:transparent}
.inv-routemap-skel{position:absolute;inset:0;height:auto;border-radius:0}
.inv-routemap-status{position:absolute;inset:auto 0 0 0;display:flex;align-items:center;gap:var(--s2);margin:0;
  padding:var(--s2) var(--s3);color:var(--ink-2);font-size:var(--t-sm)}
.inv-routemap-box{display:flex;align-items:flex-start;gap:var(--s3);padding:var(--s3);border:1px dashed var(--line-strong);
  border-radius:var(--r);background:var(--surface-2)}
.inv-routemap-icon{display:inline-flex;color:var(--ink-3)}
.inv-routemap-title{margin:0 0 var(--s1);font-weight:600}
.inv-routemap-text{margin:0;color:var(--ink-2);font-size:var(--t-sm)}
.inv-routemap-actions{display:flex;flex-wrap:wrap;gap:var(--s2)}
.inv-routemap-actions a.btn:hover{border-color:var(--line-strong)}
.inv-routemap-actions a.btn:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.inv-routemap.is-error .inv-routemap-box{border-style:solid;border-color:var(--c-err);background:var(--c-err-bg)}
.inv-routemap.is-error .inv-routemap-icon,
.inv-routemap.is-error .inv-routemap-title{color:var(--c-err-fg)}
.inv-routemap.is-empty .inv-routemap-box{align-items:center}
.inv-routemap.is-disabled .inv-routemap-frame{opacity:.72}
.inv-routemap.is-loading .inv-routemap-frame{cursor:progress}
@media (max-width:40rem){
  .inv-routemap-frame,.inv-routemap-frame iframe{min-height:calc(var(--s8) * 4);height:calc(var(--s8) * 4)}
}
@media (pointer:coarse){
  .inv-routemap-actions .btn{min-height:var(--tap)}
}
