/* ============ Camas Factory Platform — design system ============ */
/* 🔒 LOCKED direction, 2026-08-25 — see projects/camas-webapp/DESIGN.md (+ design/mockups/).
   "An instrument, not a website." White page, grey boxes, warm neutrals, 3px controls /
   10px cards. Depth = a lit top edge + a soft halo, never a drop shadow. Colour is
   rationed: five status meanings + one accent, and the accent (ส้ม) means "needs YOU".
   Everything below reads from the token block, so editing a token shifts the whole app.
   OLD token names are kept as a compatibility layer (238 templates use them by name) —
   they now point at the new palette. Do not delete them; re-point them. */
:root{
  /* ---- surfaces: white page, grey boxes ---- */
  --canvas:#ffffff;  --surface:#f3f2f0;  --well:#e9e7e4;  --hairline:#e2e0dc;
  --muted:#6f6b68;   --ink:#151312;      --slab:#23395b;  --slab-ink:#ffffff;
  --slab-muted:#a9b6cc;

  /* ---- status: five meanings. -t = text, -f = fill ---- */
  --live:#ff5e00;   --live-t:#8d3202;   --live-f:#ffb37f;   /* accent: needs YOU */
  --ok:#35a300;     --ok-t:#245a16;     --ok-f:#a5ea74;     /* running / pass   */
  --warn:#e49100;   --warn-t:#6b4700;   --warn-f:#ffca00;   /* needs a look     */
  --danger:#de2500; --danger-t:#7d1f0c; --danger-f:#ff977d; /* stop / failed    */
  --info:#0081aa;   --info-t:#174d66;   --info-f:#8ed8fb;   /* remark / waiting */
  --idle:#87837e;   --idle-t:#4f4b47;   --idle-f:#d8d3ce;   /* empty / queued   */

  /* ---- departments: charts and legends ONLY, never a status ---- */
  --d1:#ff5e00; /* ฉีด */    --d2:#0081aa; /* รีด */    --d3:#35a300; /* ผสม */
  --d4:#8a2cd4; /* ทำเม็ด */ --d5:#dca300; /* ท่อ */    --d6:#00a192; /* บด */

  /* ---- elevation: AuthKit's recipe, flipped to light ---- */
  --dot:rgba(21,19,18,.20);
  --glow:rgba(255,94,0,.07);
  --edge-lit:inset 0 1px 0 rgba(255,255,255,.95);
  --halo:0 1px 2px rgba(21,19,18,.05), 0 10px 28px -16px rgba(21,19,18,.22);
  --halo-up:0 2px 4px rgba(21,19,18,.06), 0 18px 40px -18px rgba(21,19,18,.30);

  --r-control:3px;  --r-card:10px;

  /* ---- width scale (2026-08-26) ----
     A page no longer picks a pixel number, it says what it IS. Before this there were three
     hand-applied buckets (1120 / 1480 / 100%) that 21 of 65 pages never opted into, so they sat
     at 1120 — 58% of a 2560px screen unused. Page-local caps use these too; never a new number. */
  --w-phone:34rem;   /* a handheld page, centred on a desktop   */
  --w-form:56rem;    /* one form, one document                  */
  --w-page:1680px;   /* the default working page                */

  --font-ui:"Geist",-apple-system,BlinkMacSystemFont,"SF Pro Text","Noto Sans Thai",system-ui,sans-serif;
  --font-mono:"Geist Mono",ui-monospace,"SF Mono",SFMono-Regular,Menlo,monospace;

  /* ---- compatibility layer: OLD names → NEW palette ----
     Every template that says var(--primary) / var(--radius) / var(--ok-bg) keeps working.
     The -bg tints are deliberately PALER than the -f fills, because templates pair them
     with color:var(--ok) etc.; the true -f/-t pairs are used by the components below. */
  --bg:#ffffff;
  --surface-2:#e9e7e4; --surface-3:#ded9d4; --inset:#e9e7e4;
  --text:#151312;      --faint:#7a7570;
  --border:#e2e0dc;    --border-2:#eceae7;
  /* --primary was indigo, then the neutral near-black "slab". Since 2026-08-26 it is
     INK BLUE (#23395B) — Pokkrong picked option C from the four-way comparison because
     a page carrying many controls read as too heavy in near-black, and blue is the one
     hue this app does not already spend on a status (see DESIGN.md §11). It stays DARK
     in BOTH themes so the ~25 templates that hardcode color:#fff on it stay legible. */
  --primary:#23395b;   --primary-d:#172742; --primary-l:#e9e7e4;
  --primary-ink:#151312; --primary-bg:#e9e7e4;
  --violet:#8a2cd4; --teal:#00a192; --sky:#0081aa; --amber:#dca300; --pink:#d40073;
  --ok-bg:#daf1c9;     --ok-bd:#98dd63;
  --warn-bg:#fdeab1;   --warn-bd:#ffc400;   --warn-l:#fdeab1; --warn-ink:#6b4700;
  --danger-bg:#fdd5c9; --danger-bd:#ff8f70;
  --info-bg:#d0eaf6;   --info-bd:#82d3f5;   --info-ink:#174d66;
  --idle-bg:#ebe8e5;
  --radius:10px;       --radius-sm:3px;
  --shadow:0 1px 2px rgba(21,19,18,.05), 0 10px 28px -16px rgba(21,19,18,.22);
  --shadow-sm:0 1px 2px rgba(21,19,18,.06);
  --shadow-lg:0 2px 4px rgba(21,19,18,.06), 0 18px 40px -18px rgba(21,19,18,.30);
  --side-w:236px;
}
/* Dark theme — applied ONLY when the user toggles it (data-theme="dark"); light stays the
   default, so nobody is auto-switched by their OS. Token-only: every component flips. */
:root[data-theme="dark"]{
  --canvas:#1a1918; --surface:#111110; --well:#262322; --hairline:#332f2e;
  --muted:#8a8380;  --ink:#eeeeee;     --slab:#eeeeee; --slab-ink:#101010; --slab-muted:#5c5754;
  --live-t:#ff7831;   --live-f:#5c2600;
  --ok-t:#79e642;     --ok-f:#164a00;
  --warn-t:#fdbd00;   --warn-f:#5b4400;
  --danger-t:#ff785b; --danger-f:#5c1600;
  --info-t:#63c5f6;   --info-f:#00384f;
  --idle-t:#a8a4a0;   --idle-f:#332f2c;
  --d1:#ff6600; --d2:#37aadf; --d3:#55c826; --d4:#ac6cea; --d5:#fec300; --d6:#00d4bf;
  --dot:rgba(238,238,238,.24);
  --glow:rgba(255,94,0,.13);
  --edge-lit:inset 0 1px 0 rgba(255,255,255,.06);
  --halo:0 1px 2px rgba(0,0,0,.4), 0 12px 30px -16px rgba(0,0,0,.7);
  --halo-up:0 2px 6px rgba(0,0,0,.5), 0 20px 46px -18px rgba(0,0,0,.85);
  /* compat, dark. The bare --ok/--warn/--danger/--info are ALSO used as text colour on
     dark tints by templates, so they take the bright -t tone here (as the old file did). */
  --bg:#1a1918;
  --surface-2:#262322; --surface-3:#332f2e; --inset:#0f0e0e;
  --text:#eeeeee;      --faint:#797470;
  --border:#332f2e;    --border-2:#282524;
  --primary:#2f4c80;   --primary-d:#3d63a5; --primary-l:#262322;
  --primary-ink:#eeeeee; --primary-bg:#262322;
  --violet:#ac6cea; --teal:#00d4bf; --sky:#37aadf; --amber:#fec300; --pink:#f06aa4;
  --ok:#79e642;     --ok-bg:#1d4313;     --ok-bd:#276b09;
  --warn:#fdbd00;   --warn-bg:#523e00;   --warn-bd:#7d5f00; --warn-l:#523e00; --warn-ink:#fdbd00;
  --danger:#ff785b; --danger-bg:#4e1d14; --danger-bd:#7d2412;
  --info:#63c5f6;   --info-bg:#0a3345;   --info-bd:#0d5470; --info-ink:#63c5f6;
  --idle:#a8a4a0;   --idle-bg:#332f2c;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 12px 30px -16px rgba(0,0,0,.7);
  --shadow-sm:0 1px 2px rgba(0,0,0,.45);
  --shadow-lg:0 2px 6px rgba(0,0,0,.5), 0 20px 46px -18px rgba(0,0,0,.85);
}
:root[data-theme="dark"] .logo,:root[data-theme="dark"] .mobilebar .brand .logo{box-shadow:none}

/* ⛔ HARD RULE 1 — Thai never takes letter-spacing. Tracked / mono / UPPERCASE micro-labels
   are for Latin and numerals only; at 13px a tracked Thai word shreds into loose glyphs. */
:lang(th),[lang="th"]{letter-spacing:normal!important;text-transform:none}

*{box-sizing:border-box}
body{margin:0;font-family:var(--font-ui);
  background:var(--canvas);color:var(--ink);font-size:15px;line-height:1.5;font-weight:400;
  -webkit-font-smoothing:antialiased;display:flex;min-height:100vh}
a{color:var(--ink);text-decoration:none}
a:hover{text-decoration:underline}
/* Type scale (DESIGN.md §3), with the THAI STEP-UP applied (2026-08-25, Pokkrong: "the font
   is a little small… important information should read easy too").
   §3 already says Thai sits one step larger than English, which is why body is 15 and not 14 —
   that same step is now applied all the way down the scale: labels 14 (not 13), micro 13 (not
   12), and anything that carries MEANING rather than decoration goes up a full step.
   Weights stay 400 everywhere and 500 for labels/emphasis — never 600+. Display sizes earn
   their weight through tight tracking, not boldness. */
h1{font-size:30px;font-weight:400;letter-spacing:-.035em;line-height:1.1;margin:.1rem 0 .9rem}
h2{font-size:19px;font-weight:500;letter-spacing:-.02em}
h3{font-size:17px;font-weight:500;letter-spacing:-.01em}
strong,b{font-weight:500}
.num,td.num,.tabular{font-variant-numeric:tabular-nums}
.mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums}

/* ============ sidebar — stays WHITE, so the grey cards read as boxes on a page ============ */
.sidebar{width:var(--side-w);flex:none;background:var(--canvas);border-right:1px solid var(--hairline);
  display:flex;flex-direction:column;padding:1rem .65rem .8rem;position:sticky;top:0;height:100vh;overflow-y:auto;z-index:40}
.sidebar .brand{display:flex;align-items:center;gap:.6rem;padding:.15rem .5rem 1rem;color:var(--ink);text-decoration:none}
.sidebar .brand:hover{text-decoration:none}
.sidebar .brand .logo{width:32px;height:32px;border-radius:var(--r-control);flex:none;background:var(--slab);
  display:grid;place-items:center;font-size:.68rem;font-weight:500;color:var(--slab-ink);letter-spacing:-.01em}
.sidebar .brand .bname{display:block;font-weight:500;font-size:15px;letter-spacing:-.01em;line-height:1.2}
.sidebar .brand .bsub{display:block;font-size:13px;color:var(--muted);margin-top:2px;line-height:1.2}
/* Latin-only micro-label: the Thai guard above neutralises tracking if a Thai word lands here */
.navgrp{font-family:var(--font-mono);font-size:11px;font-weight:400;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);padding:.95rem .55rem .3rem}
/* ---- module-scoped rail (shell step 4, mockup 06-navigation option A) ----
   The rail now shows ONE module with nothing folded away, so it needs a way back out
   (ทุกโมดูล) and a name for where you are. Section headings are deliberately NOT .navgrp:
   these are Thai phrases, and .navgrp's tracking + uppercase is a Latin-only voice. */
.side-back{display:flex;align-items:center;gap:9px;padding:7px 8px;margin-bottom:2px;
  border-radius:var(--r-control);color:var(--muted);font-size:14px;text-decoration:none}
.side-back:hover{background:var(--well);color:var(--ink);text-decoration:none}
.side-back .sico{width:16px;height:16px;flex:none;stroke:currentColor;stroke-width:1.6;fill:none;
  stroke-linecap:round;stroke-linejoin:round}
.side-mod{display:flex;align-items:center;gap:10px;padding:9px 8px 12px;
  border-bottom:1px solid var(--hairline);margin-bottom:6px}
.side-mod b{font-size:17px;font-weight:500;letter-spacing:-.01em;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sm-ic{width:32px;height:32px;flex:none;border-radius:4px;display:grid;place-items:center;
  background:var(--slab);color:var(--slab-ink)}
/* the ico() macro's SVGs are line icons — without these they render as solid black blobs,
   because the stroke/fill rules live under .navlink .ico and this tile is not a navlink */
.sm-ic .ico{width:20px;height:20px;display:grid;place-items:center}
.sm-ic .ico svg{width:20px;height:20px;stroke:currentColor;stroke-width:1.5;fill:none;
  stroke-linecap:round;stroke-linejoin:round}
/* Solid, exactly like the launcher tile it came from — the rail header IS the module you
   picked, so it must look like the tile you pressed. ท่อ (--d5) keeps a dark glyph. */
.sm-ic.h1{background:var(--d1);color:#fff}
.sm-ic.h2{background:var(--d2);color:#fff}
.sm-ic.h3{background:var(--d3);color:#fff}
.sm-ic.h4{background:var(--d4);color:#fff}
.sm-ic.h5{background:var(--d5);color:#3a2a00}
.sm-ic.h6{background:var(--d6);color:#fff}
/* ---- foldable groups inside the rail (step 4b, option A "กล่อง", Pokkrong 2026-08-25) ----
   The flat list failed his test: *"everything looks so blend together, hard to find."* A heading
   in the same grey as its links is not a heading. Each group is now a pressable grey box; ONE is
   open at a time, so แผนกผสม reads as 5 boxes instead of 21 undifferentiated lines. The dot and
   the active marker take the module's own department hue, set by the hue class on .sidebar. */
.sidebar.h1{--sg-hue:var(--d1)} .sidebar.h2{--sg-hue:var(--d2)} .sidebar.h3{--sg-hue:var(--d3)}
.sidebar.h4{--sg-hue:var(--d4)} .sidebar.h5{--sg-hue:var(--d5)} .sidebar.h6{--sg-hue:var(--d6)}
.sg{border-radius:var(--r-control);margin-bottom:5px}
.sg.open{background:var(--surface)}
.sgh{display:flex;align-items:center;gap:8px;width:100%;padding:8px 9px;border:none;cursor:pointer;
  background:var(--surface);border-radius:var(--r-control);font:inherit;font-size:14px;font-weight:500;
  color:var(--ink);text-align:left;box-shadow:none;line-height:1.3}
.sg.open>.sgh{border-radius:var(--r-control) var(--r-control) 0 0}
.sgh:hover{background:var(--well);opacity:1}
.sgh:focus-visible{outline:2px solid var(--sg-hue,var(--ink));outline-offset:1px}
.sgt{min-width:0}
.sgh .dot{width:7px;height:7px;flex:none;border-radius:2px;background:var(--sg-hue,var(--ink));opacity:.45}
.sg.open>.sgh .dot{opacity:1}
.sgh .chev{margin-left:auto;flex:none;font-size:11px;color:var(--muted);transition:transform .18s ease}
.sg.open>.sgh .chev{transform:rotate(90deg)}
.sgh .sg-bd{margin-left:auto}
.sgh .sg-bd+.chev{margin-left:6px}
.sg.open>.sgh .sg-bd{display:none}      /* the count is only a stand-in for the folded links */
.sgb{display:none;overflow:hidden;padding:1px 5px 6px}
.sg.open>.sgb{display:block}
.sg .navlink{font-size:14px}
.sg.open .navlink:hover{background:var(--canvas)}
/* the base .navlink.on paints a dark left BORDER; inside a group the hue inset replaces it,
   otherwise the active row gets two left edges in two different colours */
.sg.open .navlink.on{background:var(--canvas);color:var(--ink);font-weight:500;
  border-left-color:transparent;box-shadow:inset 2px 0 0 var(--sg-hue,var(--ink))}
@media (prefers-reduced-motion:no-preference){
  .sg.open>.sgb{animation:sgopen .19s cubic-bezier(.22,1,.36,1)}
  @keyframes sgopen{from{opacity:.001;transform:translateY(-5px)}to{opacity:1;transform:none}}
}
/* nav badge — was five copies of the same inline style in base.html */
.nav-bd{margin-left:auto;flex:none;font-family:var(--font-mono);font-size:11.5px;font-weight:400;
  border-radius:var(--r-control);padding:1px 5px;background:var(--well);color:var(--muted)}
.nav-bd.danger{background:var(--danger-f);color:var(--danger-t)}
.nav-bd.warn{background:var(--warn-f);color:var(--warn-t)}
.nav-bd.ok{background:var(--ok-f);color:var(--ok-t)}
.nav-bd.info{background:var(--info-f);color:var(--info-t)}
.sidebar .navlink{white-space:normal;line-height:1.3}
.navlink{display:flex;align-items:center;gap:.55rem;padding:.42rem .55rem;border-radius:var(--r-control);
  color:var(--muted);font-weight:400;font-size:15px;white-space:nowrap;
  border-left:2px solid transparent;transition:background .14s,color .14s}
.navlink:hover{background:var(--well);color:var(--ink);text-decoration:none}
.navlink.on{background:var(--well);color:var(--ink);font-weight:500;border-left-color:var(--ink)}
.navlink .ico{width:18px;height:18px;flex:none;display:grid;place-items:center}
.navlink .ico svg{width:18px;height:18px;stroke:currentColor;stroke-width:1.5;fill:none;stroke-linecap:round;stroke-linejoin:round}
.navlink .cnt{margin-left:auto;font-family:var(--font-mono);font-size:12px;font-weight:500;color:var(--muted)}
.navlink.on .cnt{color:var(--ink)}
.navsub{display:flex;flex-direction:column;gap:1px;margin:.1rem 0 .1rem .35rem;padding-left:.5rem;border-left:1px solid var(--hairline)}
.navsub .navlink{font-size:14px;padding:.38rem .5rem}
/* SECOND-level fold: a module whose submenu covers two distinct activities folds
   again (แผนกผสม → ⚖️ การชั่ง / 🌀 การผสม), so only the one in use is open.
   Nesting works because the fold rules below are direct-child selectors and the
   toggle uses closest('.navgroup') — the inner group wins. */
.navsub .navtoggle{font-weight:500}
.navsub .navsub .navlink{font-size:13.5px;padding:.34rem .45rem}
/* collapsible department group (injection) — single item, expands on click */
.navtoggle{width:100%;background:none;border:none;cursor:pointer;font:inherit;text-align:left;box-shadow:none}
.navtoggle:hover{background:var(--well)}
.navlink .chev{margin-left:auto;font-size:.68rem;color:var(--muted);transition:transform .18s}
.navtoggle.on{background:var(--well);color:var(--ink);font-weight:500}
.navgroup>.navsub{display:none}
.navgroup.open>.navsub{display:flex}
.navgroup.open>.navtoggle .chev{transform:rotate(180deg)}
/* theme toggle (light/dark) */
.themebtn{width:100%;background:none;border:none;cursor:pointer;font:inherit;text-align:left;box-shadow:none}
.themebtn .sun{display:none}
:root[data-theme="dark"] .themebtn .moon{display:none}
:root[data-theme="dark"] .themebtn .sun{display:grid}
.themebtn .lbl-light{display:none}
:root[data-theme="dark"] .themebtn .lbl-dark{display:none}
:root[data-theme="dark"] .themebtn .lbl-light{display:inline}
.themebtn-m{background:none;border:none;cursor:pointer;color:var(--muted);padding:.25rem;border-radius:var(--r-control);
  display:grid;place-items:center;margin-left:auto;box-shadow:none}
.themebtn-m svg{width:19px;height:19px;stroke:currentColor;stroke-width:1.5;fill:none;stroke-linecap:round;stroke-linejoin:round}
.themebtn-m .sun{display:none}
:root[data-theme="dark"] .themebtn-m .moon{display:none}
:root[data-theme="dark"] .themebtn-m .sun{display:block}
.side-foot{margin-top:auto;padding-top:.6rem;border-top:1px solid var(--hairline);display:flex;flex-direction:column;gap:.1rem}
.side-user{display:flex;align-items:center;gap:.55rem;padding:.45rem .6rem}
.side-user .av{width:30px;height:30px;border-radius:var(--r-control);flex:none;display:grid;place-items:center;
  color:var(--slab-ink);font-weight:500;font-size:13px;background:var(--slab)}
.side-user .un{font-size:14px;font-weight:500;line-height:1.2}
.side-user .ur{font-size:13px;color:var(--muted)}

/* ============ content / layout ============ */
.content{flex:1;min-width:0;display:flex;flex-direction:column}
main{width:100%;max-width:var(--measure,var(--w-page));margin:1.6rem auto;
  padding-inline:clamp(14px,1.4vw,30px);flex:1}
body.wide   main{--measure:100%}
body.wide80 main{--measure:var(--w-page)}   /* one fewer bucket to choose wrong */
body.read   main{--measure:var(--w-form)}
/* A FOCUS screen: one person, one machine, one job. It gets the whole glass — the ways out are
   ⠿ โมดูล and ⌘K, both still in the top bar. */
body.focus  main{--measure:100%;margin:0;padding:12px 16px 16px}
body.focus #sidebar,body.focus .sidebar{display:none}
/* The ☰ burger renders whenever the page HAS a module (base.html `{% if nav_mod %}`), and
   toggleSide() only adds .open — which body.focus's higher specificity beats. Left in, it is a
   dead button that still drops the scrim over the screen. A focus page has no rail, so no burger. */
body.focus .cmt-burger{display:none}
.mobilebar{display:none}

/* ============ forms & buttons ============ */
/* An input must NOT look like the card it sits on — fields stay white (canvas). */
input,select,textarea{font:inherit;font-size:15px;padding:.5rem .7rem;border:1px solid var(--hairline);
  border-radius:var(--r-control);background:var(--canvas);color:var(--ink);transition:border-color .15s}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--ink);box-shadow:none}
input[disabled],select[disabled],textarea[disabled]{background:var(--well);color:var(--muted);cursor:not-allowed}
/* Primary = a neutral dark slab, never a coloured one. 3px radius, no shadow. */
button{font:inherit;font-size:14px;font-weight:500;padding:.5rem .9rem;border:1px solid var(--primary);
  border-radius:var(--r-control);background:var(--primary);color:#fff;cursor:pointer;
  transition:opacity .15s,background .15s;box-shadow:none}
button:hover{background:var(--primary);opacity:.88}
button:active{transform:translateY(1px)}
button[disabled]{opacity:.42;cursor:not-allowed}
button.ghost{background:transparent;color:var(--ink);border-color:var(--hairline);opacity:1}
button.ghost:hover{background:var(--well);opacity:1}
button.danger{background:transparent;color:var(--danger-t);border-color:var(--danger-t)}
button.danger:hover{background:var(--danger-f);opacity:1}
button.small,.small{font-size:14px} button.small{padding:.32rem .62rem}
/* Inside a data table every button is a ROW action, not the page's primary action. Left as
   slabs, a 30-row table reads as 30 primary buttons and the real primary stops standing out.
   Opt back in with class="primary" on the one button that IS the row's main action. */
.grid td button:not(.primary),.grid td .btn:not(.primary){background:transparent;color:var(--ink);border-color:var(--hairline);opacity:1}
.grid td button:not(.primary):hover{background:var(--well);opacity:1}
.grid td .del button,.grid td button.del{color:var(--danger-t);border-color:var(--danger-t)}
.grid td .del button:hover,.grid td button.del:hover{background:var(--danger-f)}
label{display:block;margin:.55rem 0;font-size:14px;color:var(--muted);font-weight:500}
:focus-visible{outline:2px solid var(--live);outline-offset:2px}

/* ============ cards — grey box, no border, lit edge + halo, four corner dots ============ */
.card{position:relative;background:var(--surface);border:1px solid transparent;border-radius:var(--r-card);
  padding:16px 18px;box-shadow:var(--edge-lit),var(--halo)}
.card::after{content:"";position:absolute;inset:7px;border-radius:inherit;pointer-events:none;
  background:
    radial-gradient(circle 1.4px at 0 0,      var(--dot) 99%,transparent 100%),
    radial-gradient(circle 1.4px at 100% 0,   var(--dot) 99%,transparent 100%),
    radial-gradient(circle 1.4px at 0 100%,   var(--dot) 99%,transparent 100%),
    radial-gradient(circle 1.4px at 100% 100%,var(--dot) 99%,transparent 100%)}
.card.narrow{max-width:400px;margin:4rem auto}
.card.narrow h1{text-align:center;margin-bottom:1.2rem}
.card.narrow label input{display:block;width:100%;margin-top:.3rem}
.card.narrow button{width:100%;margin-top:.6rem;padding:.62rem}

/* ============ flash / messages — STRUCTURE C ============
   The box is plain (canvas + hairline, black text). ALL the colour lives in one 34px
   icon tile. Six yellows were tried and rejected: a large tinted row reads like a
   highlighter at any shade; the same colour in 1/30th of the area reads as deliberate. */
.flash{display:flex;align-items:center;gap:12px;padding:11px 14px;border-radius:var(--r-control);
  margin:.6rem 0;font-size:15px;border:1px solid var(--hairline);background:var(--canvas);color:var(--ink)}
.flash::before{content:"i";width:34px;height:34px;border-radius:var(--r-control);flex:none;
  display:grid;place-items:center;font-size:16px;line-height:1;font-weight:500;
  font-family:var(--font-mono);background:var(--well);color:var(--muted)}
.flash.ok::before{content:"✓";background:var(--ok-f);color:var(--ok-t)}
.flash.error::before{content:"!";background:var(--danger-f);color:var(--danger-t)}
.flash.warn::before{content:"!";background:var(--warn-f);color:var(--warn-t)}
.flash.info::before{content:"i";background:var(--info-f);color:var(--info-t)}

/* ============ tables ============ */
table.grid{width:100%;border-collapse:separate;border-spacing:0;background:var(--surface);
  border:1px solid transparent;border-radius:var(--r-card);overflow:hidden;box-shadow:var(--edge-lit),var(--halo)}
.grid th,.grid td{text-align:left;padding:.6rem .8rem;border-bottom:1px solid var(--hairline);font-size:15px}
.grid thead th{background:var(--surface);color:var(--muted);font-weight:500;font-size:14px;letter-spacing:0;
  text-transform:none;position:sticky;top:0}
.grid tbody tr:last-child td{border-bottom:none}
.grid tbody tr{transition:background .12s}
.grid tbody tr:hover{background:var(--well)}
td.num,th.num,td.r,th.r{text-align:right;font-variant-numeric:tabular-nums}
.muted{color:var(--muted)} .faint{color:var(--faint)} .small{font-size:14px} .nowrap,td.nowrap{white-space:nowrap}
.head-row{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:.7rem}
.datepick{display:flex;align-items:center;gap:.7rem}

/* ============ pills / badges / chips ============
   3px radius, 13px, weight 500, 6px dot. เดินอยู่ (running) is deliberately CALM: a plain
   outlined chip with a green dot — the most common state must not be the loudest thing on
   screen. Only problems carry a fill, and เครื่องเสีย is the ONLY solid chip in the system. */
/* Inline status icons (the `sico()` macro in templates/_status_ico.html). They inherit
   currentColor, so a chip's own colour drives the icon — and dark mode comes free. These
   replaced the emoji that used to sit in status chips: an emoji is a full-colour glyph the
   FONT decides, so it ignores the palette, looks glossy against a flat system, and draws
   differently on every device. */
.sico{width:15px;height:15px;flex:none;stroke:currentColor;fill:none;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;vertical-align:-2px}
.sico.solid{fill:currentColor;stroke:none}   /* play / pause read better as a filled glyph */
.sico.lg{width:17px;height:17px}
.sico.sm{width:14px;height:14px;vertical-align:-2px}

.pill{display:inline-flex;align-items:center;gap:7px;background:transparent;color:var(--ink);
  padding:4px 10px;border-radius:var(--r-control);font-size:14px;font-weight:500;
  border:1px solid var(--hairline);white-space:nowrap}
.pill .d{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.pill.active,.pill.run{background:transparent;color:var(--ink);border-color:var(--hairline)}
.pill.active .d,.pill.run .d{background:var(--ok)}
.pill.pending{background:var(--warn-f);color:var(--warn-t);border-color:transparent}
.pill.disabled,.pill.done{background:var(--idle-f);color:var(--idle-t);border-color:transparent}
/* planned had NO rule (sweep 2026-08-28): กำลังผลิต and วางแผน rendered as the
   same plain chip on every CMO table — planned now reads quiet/filled */
.pill.planned{background:var(--idle-f);color:var(--idle-t);border-color:transparent}
.badge-done{color:var(--ok-t);font-weight:500} .badge-pend{color:var(--warn-t);font-weight:500}
.actions form.inline{display:inline} .actions select{margin-left:.3rem}

/* ============ status form ============ */
form.entry{display:flex;gap:.5rem;flex-wrap:wrap;background:var(--surface);border:1px solid transparent;
  padding:.9rem;border-radius:var(--r-card);margin:.9rem 0;box-shadow:var(--edge-lit),var(--halo)}
form.entry .grow{flex:1 1 260px}
table.status td{vertical-align:middle}
table.status td.namecol{white-space:normal;min-width:150px}
table.status input,table.status select{padding:.32rem .5rem;font-size:14px}
table.status input.tiny{width:80px} table.status input.mo{width:150px} table.status input.notecol{width:100%;min-width:140px}
table.status .rowact button,table.status .rowact form{margin-right:.25rem} table.status .rowact .inline{display:inline}
.edit{display:none}
table.status tr.editing .view{display:none} table.status tr.editing .edit{display:inline-block}
table.status .save-btn,table.status .cancel-btn{display:none}
table.status tr.editing .edit-btn,table.status tr.editing .del{display:none}
table.status tr.editing .save-btn,table.status tr.editing .cancel-btn{display:inline-block}
table.status tr.editing{background:var(--well)}
table.status tr.recalled{box-shadow:inset 3px 0 0 var(--warn)}
.recall-dot{color:var(--warn-t);font-weight:500;cursor:help}
table.status td.upd{font-size:13px;line-height:1.3;color:var(--muted)}
.daysum{margin:.5rem 0;color:var(--muted)}

/* ============ tabs — segmented, not pills ============ */
/* Segment look, but each tab keeps its own border — these rows WRAP on the plan page,
   and a single outer border around a wrapped row draws a box round empty space. */
.tabs{display:flex;gap:6px;margin:.7rem 0 0;flex-wrap:wrap}
.tab{padding:6px 14px;background:transparent;border:1px solid var(--hairline);
  border-radius:var(--r-control);color:var(--muted);font-weight:500;font-size:14px;transition:background .14s,color .14s}
.tab:hover{text-decoration:none;background:var(--well);color:var(--ink)}
.tab.on{background:var(--slab);color:var(--slab-ink);border-color:var(--slab)}
/* plan folder-tabs keep their attached look via .modtabs / .planwrap pairing */
.planwrap{overflow-x:auto;background:var(--surface);border:1px solid transparent;
  border-radius:var(--r-card);box-shadow:var(--edge-lit),var(--halo);margin-top:.6rem}
table.plan{font-size:13px;white-space:normal;width:100%;border:none;box-shadow:none;border-radius:0;background:none}
table.plan td{border:1px solid var(--hairline);padding:.35rem .55rem}
table.plan td:first-child{width:1%;text-align:right;color:var(--muted);white-space:nowrap}
table.plan tr.hrow td{background:var(--well);font-weight:500;position:sticky;top:0}
.plan-head{margin:.3rem 0;font-weight:500} .plan-head.sub{font-weight:400;color:var(--muted);font-size:15px}
.swatch{display:inline-block;width:10px;height:10px;border-radius:2px;border:1px solid rgba(21,19,18,.12);margin-right:6px;vertical-align:middle}
td.urg{font-weight:500}
/* urgency ladder — a warm sequential ramp, red → amber → green (severity, not category) */
.u1{background:#f3ad9b}.u2{background:#f7c39b}.u3{background:#f7d477}.u4{background:#eae09a}.u5{background:#b7e29c}.u6{background:#ded9d4}
/* "differs from Odoo" — a whole column of these on the plan page, so the flag is a ring,
   not a fill. ส้ม is the needs-YOU accent and it must stay rare enough to still mean that. */
td.odoo-diff{background:var(--well)!important;font-weight:500;box-shadow:inset 0 0 0 1px var(--live)}

/* ============ AI accents ============ */
.ai-bar{display:flex;align-items:center;gap:1rem;justify-content:space-between;
  background:var(--well);border:1px solid var(--hairline);padding:.6rem .9rem;border-radius:var(--r-control);margin:.6rem 0;flex-wrap:wrap}
.ai-note{color:var(--muted);font-size:15px}
.ai-badge{background:var(--slab);color:var(--slab-ink);font-size:11px;font-weight:500;
  padding:.14rem .45rem;border-radius:var(--r-control);vertical-align:middle;letter-spacing:.03em;font-family:var(--font-mono)}

/* ============ running / progress bars ============ */
table.running td{vertical-align:middle}
table.running td.mc{font-weight:500;white-space:nowrap;color:var(--ink);font-family:var(--font-mono)}
table.running .prog,table.asm-sub .prog{min-width:150px}
.bar{background:var(--well);border-radius:var(--r-control);height:8px;overflow:hidden;margin-bottom:2px}
.bar .fill{background:var(--ok);height:100%;border-radius:var(--r-control)}
.pnum{font-size:14px;color:var(--muted);font-variant-numeric:tabular-nums}

/* ============ reconcile ============ */
.recon-sum{display:flex;gap:.5rem;margin:.6rem 0}
.pill.flag-high{background:var(--danger);color:#fff;border-color:transparent}
.pill.flag-high .d{background:#fff}
.pill.flag-mid{background:var(--warn-f);color:var(--warn-t);border-color:transparent}
.pill.flag-low{background:var(--idle-f);color:var(--idle-t);border-color:transparent}
.flag{padding:4px 10px;border-radius:var(--r-control);font-size:13px;margin:.15rem 0;display:inline-block}
.flag.flag-high{background:var(--danger);color:#fff}
.flag.flag-mid{background:var(--warn-f);color:var(--warn-t)}
.flag.flag-low{background:var(--idle-f);color:var(--idle-t)}
table.recon .fcol{background:var(--well)} table.recon .ocol{background:var(--ok-f)} table.recon td{vertical-align:top}

/* ============ assembly ============ */
.asm-card{position:relative;background:var(--surface);border:1px solid transparent;border-radius:var(--r-card);
  box-shadow:var(--edge-lit),var(--halo);margin:.9rem 0;padding:.9rem 1.1rem}
.asm-head{display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap;margin-bottom:.5rem}
table.asm-sub{font-size:14px;border:none;box-shadow:none;background:none}

/* ============ homepage / dashboard ============ */
.dash-head{margin:.2rem 0 1.4rem}
.dash-head h1{margin:.1rem 0 .3rem}
.card.pad{padding:18px 20px}
/* KPI cards */
.kpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(214px,1fr));gap:14px;margin-bottom:14px}
.kpi{padding:15px 16px;display:flex;flex-direction:column;transition:transform .22s cubic-bezier(.22,1,.36,1),box-shadow .22s cubic-bezier(.22,1,.36,1)}
a.kpi:hover{transform:translateY(-2px);box-shadow:var(--edge-lit),var(--halo-up);text-decoration:none}
.kpi .kh{display:flex;align-items:center;gap:9px;margin-bottom:6px}
.kpi-ic{width:32px;height:32px;border-radius:var(--r-control);flex:none;display:grid;place-items:center;
  background:var(--well);color:var(--muted)}
.kpi-ic svg{width:19px;height:19px;stroke:currentColor;stroke-width:1.5;fill:none;stroke-linecap:round;stroke-linejoin:round}
/* The DEFAULT tile is neutral. A KPI that is merely a count ("งานกำลังเดินทั้งหมด",
   "คิวตรวจ Lab") is not asking for anything, so it must not wear a status colour — four
   coloured tiles in a row and none of them means anything. Only ด่วน (warn) and a real
   failure (danger) get a fill; รอปิด is ฟ้า = waiting, per DESIGN.md §5. */
.kpi-ic.i-ac{background:var(--info);color:#fff}
.kpi-ic.i-wn{background:var(--warn);color:#3a2a00} .kpi-ic.i-ok{background:var(--info);color:#fff}
.kpi-ic.i-cr{background:var(--danger);color:#fff} .kpi-ic.i-lb{background:var(--d4);color:#fff}
.kpi-ic.i-live{background:var(--live);color:#2b0f00}   /* only for "needs YOU" */
/* The label is what tells you what the number MEANS — it is important information, so it
   takes ink at body size, not muted micro-text. The sub-line underneath stays quiet. */
.kpi-l{color:var(--ink);font-size:15px;font-weight:500}
.kpi-n{font-size:34px;font-weight:400;line-height:1.1;letter-spacing:-.035em;color:var(--ink);font-variant-numeric:tabular-nums}
.kpi-sub{font-size:14px;color:var(--muted);margin-top:.45rem}
.chip{display:inline-flex;align-items:center;gap:.2rem;font-family:var(--font-mono);font-weight:400;font-size:13px;
  padding:1px 6px;border-radius:var(--r-control)}
.chip.up{background:var(--ok-f);color:var(--ok-t)} .chip.dn{background:var(--danger-f);color:var(--danger-t)}
/* chart / donut row */
.dash-row{display:grid;grid-template-columns:1.7fr 1fr;gap:14px;margin-bottom:.4rem;align-items:start}
.dash-row.solo{grid-template-columns:minmax(320px,520px)}
/* sales chart alone (factory idle → no donut): let the trend take the full width */
.dash-row.solo.wide{grid-template-columns:1fr}
@media(max-width:940px){.dash-row,.dash-row.solo{grid-template-columns:1fr}}
.ch{display:flex;align-items:center;justify-content:space-between;margin-bottom:.9rem}
.ch h3{font-size:19px;font-weight:500;margin:0;letter-spacing:-.01em}
.ch-sub{font-size:17px;font-weight:500;margin:0 0 .7rem}
.bignum{font-size:29px;font-weight:400;letter-spacing:-.035em;font-variant-numeric:tabular-nums}
.bignum small{font-size:14px;color:var(--muted);font-weight:400;letter-spacing:0}
.areachart{width:100%;height:auto;margin-top:.7rem;display:block}
.areachart .gl{stroke:var(--hairline);stroke-width:1}
/* home sales card — privacy-first: hidden on every load, revealed by click (sc- prefixed) */
.sc-head{gap:1rem;flex-wrap:wrap}
/* a class-level display: beats the UA rule for [hidden] — without this the range chips stay
   visible while the card is locked, which is exactly what must not happen on a shared screen */
.sc-tools[hidden],.sc-locked[hidden]{display:none}
.sc-tools{display:flex;align-items:center;gap:.5rem;margin-left:auto}
.sc-rngs{display:inline-flex;border:1px solid var(--hairline);border-radius:var(--r-control);overflow:hidden}
.sc-rng{border:0;border-right:1px solid var(--hairline);background:transparent;color:var(--muted);font:inherit;font-size:14px;font-weight:500;
  padding:.3rem .7rem;border-radius:0;cursor:pointer;transition:.15s;white-space:nowrap;box-shadow:none}
.sc-rng:last-child{border-right:0}
.sc-rng:hover{color:var(--ink);background:var(--well);opacity:1}
.sc-rng.on{background:var(--slab);color:var(--slab-ink);box-shadow:none;opacity:1}
.sc-eye{display:inline-flex;align-items:center;gap:.35rem;border:1px solid var(--hairline);
  background:transparent;color:var(--muted);font:inherit;font-size:14px;font-weight:500;
  padding:.3rem .7rem;border-radius:var(--r-control);cursor:pointer;transition:.15s;box-shadow:none}
.sc-eye:hover{color:var(--ink);background:var(--well);opacity:1}
.sc-eye svg{width:15px;height:15px}
.sc-locked{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:.55rem;padding:2.3rem 1rem 2.5rem;text-align:center}
.sc-reveal{display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--slab);
  background:var(--slab);color:var(--slab-ink);font:inherit;font-size:15px;font-weight:500;
  padding:.6rem 1.1rem;border-radius:var(--r-control);cursor:pointer;transition:.15s;box-shadow:none}
.sc-reveal:hover{opacity:.88}
.sc-reveal svg{width:17px;height:17px}
.sc-hint{margin:0;font-size:14px;color:var(--muted)}
.sc-ax{display:flex;justify-content:space-between;font-size:13px;color:var(--muted);
  margin-top:.3rem;font-variant-numeric:tabular-nums;font-family:var(--font-mono)}
/* hover readout (stock-chart style): crosshair + point marker + bubble, all HTML over the SVG */
/* the wrapper must box the SVG EXACTLY — hover maths converts viewBox units with its rect, so the
   chart's own top margin moves to the wrapper (a child margin would otherwise collapse out of it) */
.sc-plot{position:relative;touch-action:pan-y;margin-top:.7rem}
.sc-plot>.areachart{margin-top:0}
.sc-cross,.sc-mark,.sc-tip{pointer-events:none}
.sc-cross[hidden],.sc-mark[hidden],.sc-tip[hidden]{display:none}
.sc-cross{position:absolute;top:0;bottom:0;width:1px;background:var(--hairline)}
.sc-mark{position:absolute;width:9px;height:9px;border-radius:50%;background:var(--ok);
  border:2.5px solid var(--surface);transform:translate(-50%,-50%)}
.sc-tip{position:absolute;transform:translate(-50%,-100%);display:flex;flex-direction:column;
  gap:.05rem;background:var(--canvas);border:1px solid var(--hairline);border-radius:var(--r-control);
  padding:.35rem .6rem;box-shadow:var(--halo);white-space:nowrap;z-index:2}
.sc-tip.below{transform:translate(-50%,0)}
.sc-tip .d{font-size:13px;color:var(--muted)}
.sc-tip .v{font-size:17px;font-weight:500;letter-spacing:-.01em;font-variant-numeric:tabular-nums}
.statdiv{border-top:1px solid var(--hairline);margin:1.1rem 0 .95rem}
.donut{display:flex;align-items:center;gap:1.4rem;flex-wrap:wrap}
.donut svg{flex:none}
.dc-n{font-size:29px;font-weight:400;letter-spacing:-.035em}
.leg{display:flex;flex-direction:column;gap:.5rem;flex:1;min-width:120px}
.leg .li{display:flex;align-items:center;gap:.6rem;font-size:15px}
.leg .sw{width:10px;height:10px;border-radius:2px;flex:none}
.leg .ln{color:var(--muted)} .leg .lv{margin-left:auto;font-weight:500;color:var(--ink);font-variant-numeric:tabular-nums}
.statbar{display:flex;height:12px;border-radius:var(--r-control);overflow:hidden;margin:.2rem 0 .9rem;background:var(--well)}
.statbar span{height:100%}
.stleg{display:flex;gap:1.4rem;flex-wrap:wrap}
.stleg .i{font-size:15px;color:var(--muted)} .stleg .d{width:9px;height:9px;border-radius:2px;display:inline-block;margin-right:.4rem}
.stleg b{font-weight:400;font-size:22px;color:var(--ink);display:block;letter-spacing:-.02em;margin-top:.1rem;font-variant-numeric:tabular-nums}
/* section + department cards */
h2.sec{font-size:19px;font-weight:500;letter-spacing:-.02em;margin:1.6rem .1rem .85rem;display:flex;align-items:center;gap:.6rem}
h2.sec .more{margin-left:auto;font-size:14px;font-weight:500;color:var(--muted)}
h2.sec .more:hover{color:var(--ink)}
.dept-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(226px,1fr));gap:14px}
.dept-tile{padding:15px 16px;display:flex;flex-direction:column;gap:8px;
  transition:transform .22s cubic-bezier(.22,1,.36,1),box-shadow .22s cubic-bezier(.22,1,.36,1)}
a.dept-tile:hover{transform:translateY(-2px);box-shadow:var(--edge-lit),var(--halo-up);text-decoration:none}
.dept-tile .dh{display:flex;align-items:center;justify-content:space-between;gap:.5rem}
.dept-title{font-weight:500;font-size:17px;color:var(--ink)}
.dept-big{font-size:32px;font-weight:400;letter-spacing:-.035em;font-variant-numeric:tabular-nums}
.dept-big small{font-size:14px;font-weight:400;color:var(--muted);letter-spacing:0;margin-left:.32rem}
.dept-links{display:flex;flex-direction:column;gap:1px}
.dept-links a{color:var(--muted);padding:.26rem 0;font-size:15px;font-weight:400}
.dept-links a:hover{color:var(--ink)}
.dept-tile.soon{opacity:.62;box-shadow:var(--edge-lit)} .dept-tile.soon:hover{opacity:1;transform:none}
.hbadge{display:inline-block;font-size:13px;font-weight:500;padding:.05rem .4rem;border-radius:var(--r-control);margin-left:.25rem;
  vertical-align:middle;background:var(--well);color:var(--ink);font-family:var(--font-mono)}
.hbadge.u{background:var(--live-f);color:var(--live-t)} .hbadge.w{background:var(--warn-f);color:var(--warn-t)}

/* ============ top bar + account menu ============
   LOCKED 2026-08-25 — mockup design/mockups/07-shell.html, step 1. The left rail was carrying
   three unrelated jobs (navigation, your account, the admin view-as previewer); the last two
   moved here, top-right, and the rail keeps only menus. This bar replaces the old .mobilebar
   and renders at EVERY width, so there is one implementation instead of two.
   All classes are cmt- prefixed — style.css owns the generic names. */
.cmt-bar{display:flex;align-items:center;gap:8px;padding:9px 14px;background:var(--canvas);
  border-bottom:1px solid var(--hairline);position:sticky;top:0;z-index:36}
.cmt-gap{flex:1;min-width:0}   /* ⌘K search lands here (step 2) */
.cmt-burger{display:none;background:none;border:none;color:var(--ink);font-size:1.4rem;line-height:1;
  cursor:pointer;padding:2px 8px;border-radius:var(--r-control);box-shadow:none}
.cmt-burger:hover{background:var(--well);opacity:1}
.cmt-apps{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:var(--r-control);
  background:var(--well);color:var(--ink);font-size:14px;font-weight:500;text-decoration:none;white-space:nowrap}
.cmt-apps:hover{background:var(--hairline);text-decoration:none}
.cmt-apps .sico{width:16px;height:16px}
/* the brand shows at EVERY width now (shell step 4): on the launcher there is no rail, and the
   rail is where the brand used to live — without this the app loses its name on its own home. */
.cmt-brand{display:flex;align-items:center;gap:8px;color:var(--ink);font-size:14px;font-weight:500;
  text-decoration:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmt-brand .logo{width:24px;height:24px;border-radius:var(--r-control);background:var(--slab);
  color:var(--slab-ink);display:grid;place-items:center;font-size:9px;font-weight:500;flex:none}
.cmt-ib{background:none;border:none;color:var(--muted);cursor:pointer;width:32px;height:32px;
  border-radius:var(--r-control);display:grid;place-items:center;box-shadow:none;flex:none}
.cmt-ib:hover{background:var(--well);color:var(--ink);opacity:1}
/* ---- "needs you" bell (shell step 5) ---- */
.cmt-bell{position:relative;flex:none}
/* The count sits BESIDE the bell, never on top of it. Three digits is normal here (445 QC
   masters were awaiting approval on 2026-08-29) and an overlaid badge covered the icon
   completely at EVERY width. Static positioning also means the badge can no longer escape
   to the bar's own top-right corner when .cmt-bell drops to position:static on a phone
   (the panel-anchoring rule below) — which is exactly what he photographed. */
.cmt-bellb{width:auto;min-width:32px;height:32px;padding:0 6px;gap:5px;
  display:inline-flex;align-items:center;justify-content:center;flex:none}
.cmt-bellb .sico{width:19px;height:19px;stroke:currentColor;stroke-width:1.5;fill:none;
  stroke-linecap:round;stroke-linejoin:round;flex:none}
.cmt-bellb.open{background:var(--well);color:var(--ink)}
.cmt-bn{position:static;flex:none;min-width:16px;padding:0 4px;border-radius:var(--r-control);
  font-family:var(--font-mono);font-size:10.5px;line-height:1.5;text-align:center;
  background:var(--live);color:#2b0f00}
/* grey = a queue you own, not a decision you owe. ส้ม is rationed to "needs YOU" (DESIGN §5). */
.cmt-bn.team{background:var(--idle);color:#fff}
.cmt-nd{width:296px}
.nd-row{gap:9px;align-items:flex-start}
.nd-n{flex:none;min-width:24px;padding:1px 5px;border-radius:var(--r-control);text-align:center;
  font-family:var(--font-mono);font-size:12.5px;background:var(--live-f);color:var(--live-t)}
.nd-n.team{background:var(--well);color:var(--muted)}
.nd-l{min-width:0;line-height:1.35}
.nd-none{padding:10px;color:var(--muted);font-size:14px}
.cmt-acct{position:relative;flex:none}
.cmt-me{display:flex;align-items:center;gap:7px;padding:4px 8px 4px 4px;border-radius:var(--r-control);
  background:none;border:none;cursor:pointer;box-shadow:none;color:var(--muted)}
.cmt-me:hover,.cmt-me.open{background:var(--well);opacity:1}
.cmt-av{width:27px;height:27px;border-radius:var(--r-control);background:var(--slab);color:var(--slab-ink);
  display:grid;place-items:center;font-size:13px;font-weight:500;flex:none}
.cmt-av.lg{width:34px;height:34px;font-size:15px}
.cmt-cv{font-size:11px}
.cmt-menu{position:absolute;top:calc(100% + 6px);right:0;width:270px;max-height:78vh;overflow-y:auto;
  background:var(--canvas);border:1px solid var(--hairline);border-radius:var(--r-card);
  box-shadow:var(--edge-lit),var(--halo-up);padding:6px;z-index:60}
/* a class-level display: beats the UA rule for [hidden] — without this the menu would render
   open on every page load. Same trap the home sales card hit on 2026-08-09. */
.cmt-menu[hidden]{display:none}
.cmt-who{display:flex;gap:10px;align-items:center;padding:9px 10px 11px;
  border-bottom:1px solid var(--hairline);margin-bottom:5px}
.cmt-who b{display:block;font-size:14px;font-weight:500;line-height:1.3}
.cmt-who span span{display:block;font-size:13px;color:var(--muted)}
.cmt-it{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border:none;
  border-radius:var(--r-control);font:inherit;font-size:14px;color:var(--ink);background:none;
  text-decoration:none;cursor:pointer;text-align:left;box-shadow:none}
.cmt-it:hover{background:var(--well);text-decoration:none;opacity:1}
.cmt-it .ico,.cmt-it .sico{width:18px;height:18px;flex:none;display:grid;place-items:center}
.cmt-it .ico svg,.cmt-it .sico{width:18px;height:18px;stroke:currentColor;stroke-width:1.5;fill:none;
  stroke-linecap:round;stroke-linejoin:round}
.cmt-r{margin-left:auto;font-size:13px;color:var(--muted)}
.cmt-div{height:1px;background:var(--hairline);margin:5px 2px}
.cmt-sec{font-size:12.5px;font-weight:500;color:var(--muted);padding:7px 10px 3px}
/* the theme item shows the mode it will switch TO, so the label is the action, not the state */
.cmt-it .sun{display:none}
:root[data-theme="dark"] .cmt-it .moon{display:none}
:root[data-theme="dark"] .cmt-it .sun{display:grid}
.cmt-r .lbl-light{display:none}
:root[data-theme="dark"] .cmt-r .lbl-dark{display:none}
:root[data-theme="dark"] .cmt-r .lbl-light{display:inline}
/* the ⌘K trigger, in the middle of the bar (shell step 2). A flanking .cmt-gap on each side
   centres it; it looks like a field but is a button, because it opens a dialog. */
.cmt-find{display:inline-flex;align-items:center;gap:8px;flex:0 1 330px;min-width:0;
  padding:5px 10px;border:1px solid var(--hairline);border-radius:var(--r-control);
  background:var(--canvas);color:var(--muted);font:inherit;font-size:14px;font-weight:400;
  text-align:left;cursor:pointer;box-shadow:none}
.cmt-find:hover{background:var(--well);color:var(--ink);opacity:1}
.cmt-find .sico{width:16px;height:16px;flex:none;stroke:currentColor;stroke-width:1.6;fill:none;
  stroke-linecap:round;stroke-linejoin:round}
.cmt-find-t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmt-kbd{margin-left:auto;flex:none;font-family:var(--font-mono);font-size:11.5px;
  border:1px solid var(--hairline);border-radius:var(--r-control);padding:0 5px;color:var(--muted)}
/* The bell is NOT at the right edge of the bar — the avatar is — so a panel anchored right:0 to
   the bell hangs off-screen on a phone (measured L=-26px at 430px wide). Below 520px, drop the
   bell's own positioning context so the panel anchors to the sticky BAR and is simply inset. */
@media (max-width:520px){
  .cmt-bell{position:static}
  .cmt-nd{left:8px;right:8px;width:auto}
}
@media (max-width:860px){
  .cmt-burger{display:block}
  .cmt-brand{display:flex}
  .cmt-apps .cmt-apps-t{display:none}
  .cmt-bar{padding:9px 10px;gap:5px}
  /* on a phone the bar is full: the search collapses to its icon and the gaps stop stretching */
  .cmt-find{flex:none}
  .cmt-find .cmt-find-t,.cmt-kbd{display:none}
  .cmt-gap{flex:0 1 auto}
  /* Thumb-sized. The bar was 52px with 26-35px controls — every one of them under the 44px
     minimum a finger needs. 2026-08-29, his phone: "the top bar is a little too small".
     .cmt-bellb re-states width AFTER .cmt-ib, or the fixed 42px would crush the count. */
  .cmt-burger{padding:8px 9px;font-size:1.5rem}
  .cmt-apps{padding:11px 10px}
  .cmt-apps .sico{width:18px;height:18px}
  .cmt-ib{width:42px;height:42px}
  .cmt-bellb{width:auto;min-width:42px;height:42px}
  .cmt-bellb .sico{width:21px;height:21px}
  .cmt-find{padding:12px 10px}
  .cmt-find .sico{width:18px;height:18px}
  .cmt-me{padding:7px 6px}
  .cmt-av{width:30px;height:30px}
  .cmt-brand{font-size:15px}
  .cmt-brand .logo{width:26px;height:26px}
  /* the caret is decoration on a phone — the avatar already reads as tappable */
  .cmt-cv{display:none}
}
@media print{.cmt-bar{display:none}}

/* ============ ⌘K command palette ============
   LOCKED 2026-08-25 — shell step 2. Type a few letters, jump to any menu item. The index is
   scraped from the rendered rail, so it inherits that user's permissions and never drifts. */
.cmk{position:fixed;inset:0;z-index:80;display:flex;justify-content:center;align-items:flex-start}
.cmk[hidden]{display:none}   /* a class-level display: beats the UA [hidden] rule */
.cmk-bd{position:absolute;inset:0;background:rgba(21,19,18,.34)}
:root[data-theme="dark"] .cmk-bd{background:rgba(0,0,0,.55)}
.cmk-panel{position:relative;width:min(620px,calc(100vw - 28px));margin-top:11vh;
  background:var(--canvas);border:1px solid var(--hairline);border-radius:var(--r-card);
  box-shadow:var(--edge-lit),var(--halo-up);overflow:hidden;display:flex;flex-direction:column;
  max-height:74vh}
.cmk-in{display:flex;align-items:center;gap:10px;padding:11px 13px;border-bottom:1px solid var(--hairline)}
.cmk-in .sico{width:18px;height:18px;flex:none;stroke:var(--muted);stroke-width:1.6;fill:none;
  stroke-linecap:round;stroke-linejoin:round}
.cmk-in input{flex:1;min-width:0;border:none;background:none;box-shadow:none;padding:0;
  font:inherit;font-size:16px;color:var(--ink);outline:none}
.cmk-in input::placeholder{color:var(--muted)}
.cmk-x{flex:none;background:none;border:1px solid var(--hairline);border-radius:var(--r-control);
  color:var(--muted);font-family:var(--font-mono);font-size:11.5px;padding:2px 6px;cursor:pointer;
  box-shadow:none}
.cmk-x:hover{background:var(--well);color:var(--ink);opacity:1}
.cmk-list{overflow-y:auto;padding:6px;flex:1;min-height:0}
.cmk-sec{font-size:12.5px;font-weight:500;color:var(--muted);padding:8px 9px 4px}
.cmk-row{display:flex;align-items:baseline;gap:10px;padding:8px 9px;border-radius:var(--r-control);
  color:var(--ink);text-decoration:none;cursor:pointer}
.cmk-row:hover{background:var(--well);text-decoration:none}
.cmk-row.on{background:var(--well);box-shadow:inset 3px 0 0 var(--live)}
.cmk-l{font-size:14.5px;font-weight:500}
.cmk-p{font-size:12.5px;color:var(--muted);margin-left:auto;text-align:right;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmk-none{padding:22px 12px;text-align:center;color:var(--muted);font-size:14px}
.cmk-foot{display:flex;gap:16px;padding:8px 13px;border-top:1px solid var(--hairline);
  background:var(--surface);color:var(--muted);font-size:12.5px}
.cmk-foot b{font-family:var(--font-mono);font-weight:400;color:var(--ink)}
@media (max-width:640px){
  .cmk-panel{margin-top:0;width:100vw;max-height:100vh;height:100%;border-radius:0;border:none}
  .cmk-p{display:none}
  .cmk-foot{display:none}
}

/* ============ module grid (home) ============
   LOCKED 2026-08-25 — mockup design/mockups/06-navigation.html, option A. One tile per MODULE
   instead of one link per page: the home page is the launcher, and entering a module is where
   its menus live. Adopted from our own Odoo 19 core (zxs_entp_theme home menu + web_responsive).
   Orange badge = needs YOU · grey badge (.q) = a queue that is simply long, not an alarm. */
.mod-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(232px,1fr));gap:14px}
.mod-tile{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:12px;
  padding:18px 20px;text-decoration:none;color:var(--ink);
  transition:transform .22s cubic-bezier(.22,1,.36,1),box-shadow .22s cubic-bezier(.22,1,.36,1)}
a.mod-tile:hover{transform:translateY(-2px);box-shadow:var(--edge-lit),var(--halo-up);text-decoration:none}
.mt-ic{width:56px;height:56px;border-radius:4px;display:grid;place-items:center;
  /* a module with no department hue is a SYSTEM page — it gets weight, not a colour */
  background:var(--slab);color:var(--slab-ink)}
.mt-ic .sico{width:32px;height:32px}
.mt-nm{font-size:19px;font-weight:500;letter-spacing:-.01em;line-height:1.3}
.mt-sub{font-size:14px;color:var(--muted);line-height:1.5}
/* the badge sits in the corner, the way the app icons on the core carry their count */
.mt-bd{position:absolute;top:16px;right:16px;font-family:var(--font-mono);font-size:13px;
  padding:1px 7px;border-radius:var(--r-control);background:var(--live-f);color:var(--live-t)}
.mt-bd.q{background:var(--well);color:var(--muted)}
/* Department hues are CATEGORICAL — they identify the module, they never mean a status.
   color-mix keeps one source of truth per hue instead of a second hand-picked tint. */
.mod-tile.h1 .mt-ic{background:var(--d1);color:#fff}
.mod-tile.h2 .mt-ic{background:var(--d2);color:#fff}
.mod-tile.h3 .mt-ic{background:var(--d3);color:#fff}
.mod-tile.h4 .mt-ic{background:var(--d4);color:#fff}
.mod-tile.h5 .mt-ic{background:var(--d5);color:#3a2a00}
.mod-tile.h6 .mt-ic{background:var(--d6);color:#fff}
/* …and a whisper of the same hue across the card, so the page carries colour as AREA.
   A saturation change alone moves 0.85% of the launcher's pixels; this moves 27%. */
.mod-tile.h1{background:color-mix(in srgb,var(--d1) 8%,var(--surface))}
.mod-tile.h2{background:color-mix(in srgb,var(--d2) 8%,var(--surface))}
.mod-tile.h3{background:color-mix(in srgb,var(--d3) 8%,var(--surface))}
.mod-tile.h4{background:color-mix(in srgb,var(--d4) 8%,var(--surface))}
.mod-tile.h5{background:color-mix(in srgb,var(--d5) 8%,var(--surface))}
.mod-tile.h6{background:color-mix(in srgb,var(--d6) 8%,var(--surface))}

/* ============ mobile / responsive ============ */
.nav-toggle{background:none;border:none;color:var(--ink);font-size:1.5rem;line-height:1;cursor:pointer;
  padding:.1rem .5rem;border-radius:var(--r-control);box-shadow:none}
.nav-toggle:hover{background:var(--well);opacity:1}
.scrim{display:none}
@media (max-width:860px){
  body{display:block}
  .sidebar{position:fixed;top:0;left:0;height:100vh;transform:translateX(-100%);
    transition:transform .22s ease;box-shadow:var(--halo-up);width:min(84vw,290px)}
  .sidebar.open{transform:none}
  .content{min-height:100vh}
  .mobilebar{display:flex;align-items:center;gap:.7rem;padding:.6rem .9rem;background:var(--canvas);
    border-bottom:1px solid var(--hairline);position:sticky;top:0;z-index:30}
  .mobilebar .brand{display:flex;align-items:center;gap:.5rem;font-weight:500;color:var(--ink)}
  .mobilebar .brand .logo{width:26px;height:26px;border-radius:var(--r-control);background:var(--slab);
    display:grid;place-items:center;font-size:.56rem;font-weight:500;color:var(--slab-ink);letter-spacing:-.02em}
  .scrim.show{display:block;position:fixed;inset:0;background:rgba(21,19,18,.4);z-index:35}
  main,body.wide main,body.wide80 main{max-width:100%;padding:0 .9rem;margin-top:1rem}
  /* hard rule 2 — the PAGE never scrolls sideways. A wide table scrolls inside its own
     .tblwrap (see below); a table without one is the bug, not a case to style around. */
  /* Fixed rem widths on form controls are the whole remaining family of sideways scroll:
     a `width:22rem` label is 352px and does not care that the phone is 360. On a small screen
     nothing may be wider than its column. (min-width still wins, so a .tblwrap table keeps its
     880px and goes on scrolling inside its own box.) */
  main label,main select,main input,main textarea,main img,main .card{max-width:100%}
  h1{font-size:24px}
  .kpi-row{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
  table.grid{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .card.narrow{margin:2rem auto;padding:1.2rem}
  /* mobile-audit S1: any input under 16px makes iOS Safari zoom-on-focus — force 16px on phones */
  input,select,textarea,table.status input,table.status select{font-size:16px}
  /* wide tables in a .tblwrap keep their natural width and scroll INSIDE the wrapper on phones —
     without this, columns squeeze until Thai text wraps one character per line */
  .tblwrap>table.grid{display:table;width:auto;min-width:880px}
  .tblwrap>table.grid.guest{min-width:560px}
  /* running board: per-column minimums so no column collapses into vertical Thai text —
     the header cell's min-width governs its whole column */
  .grid.running th:nth-child(1){min-width:110px}   /* เครื่อง */
  .grid.running th:nth-child(2){min-width:125px}   /* CMO */
  .grid.running th:nth-child(3){min-width:190px}   /* สินค้า */
  .grid.running th:nth-child(4){min-width:85px}    /* ขั้นตอน */
  .grid.running th:nth-child(5){min-width:250px}   /* ความคืบหน้า + วัสดุ */
  .grid.running th:nth-child(6){min-width:130px}   /* แม่พิมพ์ */
  .grid.running th:nth-child(7){min-width:44px}    /* คน */
  .grid.running th:nth-child(8){min-width:100px}   /* เริ่มเมื่อ */
  /* the material-forecast detail block makes rows ~700px tall on phones: top-align cells so
     content doesn't float mid-row, and drop the desktop-grade detail lines (the one-line
     verdict chip + quality warnings stay) */
  .tblwrap>table.grid th,.tblwrap>table.grid td{vertical-align:top}
  .grid.running .mat .matd{display:none}
}
/* horizontal-scroll container for wide tables (running boards, admin) — inert on wide desktops */
.tblwrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
/* สิทธิ์ matrix: keep the permission-name column visible while scrolling roles sideways
   (group-header rows excluded — they span the full width and keep their tint) */
.permtable th:first-child{position:sticky;left:0;background:var(--surface);z-index:2}
.permtable tr:not(.permgrp)>td:first-child{position:sticky;left:0;background:var(--surface);z-index:2}
/* mobile-audit S2: finger-sized action buttons on touch devices (desktop mouse unaffected) */
@media (pointer:coarse){
  button,button.small,.actions a{min-height:40px}
  button.small{padding:.45rem .7rem}
}

/* ============ motion — one pass on load, then still ============
   HARD RULE 3: motion is ADDITIVE, never load-bearing. Content is fully visible in plain
   CSS; everything here lives inside prefers-reduced-motion:no-preference. (A version that
   held content at opacity:0 until an animation ran rendered a completely blank page.) */
@media (prefers-reduced-motion:no-preference){
  @keyframes camas-settle{from{transform:translateY(14px) scale(.994);opacity:.001}to{transform:none;opacity:1}}
  main>.card,main>.kpi-row>.kpi,main>.dash-row>.card,main>.dept-grid>.dept-tile,
  main>.mod-grid>.mod-tile,main>table.grid,main>.planwrap{
    animation:camas-settle .5s cubic-bezier(.22,1,.36,1) both}
  main>.kpi-row>.kpi:nth-child(2),main>.dept-grid>.dept-tile:nth-child(2),main>.mod-grid>.mod-tile:nth-child(2){animation-delay:.08s}
  main>.kpi-row>.kpi:nth-child(3),main>.dept-grid>.dept-tile:nth-child(3),main>.mod-grid>.mod-tile:nth-child(3){animation-delay:.16s}
  main>.kpi-row>.kpi:nth-child(4),main>.dept-grid>.dept-tile:nth-child(4),main>.mod-grid>.mod-tile:nth-child(4){animation-delay:.24s}
  main>.dept-grid>.dept-tile:nth-child(5),main>.mod-grid>.mod-tile:nth-child(5){animation-delay:.32s}
  main>.dept-grid>.dept-tile:nth-child(6),main>.mod-grid>.mod-tile:nth-child(6){animation-delay:.40s}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ดูในมุมมอง (view-as) picker — role AND department, because permission alone does not decide
   what a person sees: the running boards and the maintenance module are dept-scoped too. */
.vaform{display:flex;flex-direction:column;gap:.3rem;padding:.5rem .65rem .7rem}
.valbl{font-size:12px;color:var(--muted);letter-spacing:.04em;font-family:var(--font-mono);text-transform:uppercase}
.vasel{width:100%;padding:.32rem .45rem;border:1px solid var(--hairline);border-radius:var(--r-control);
  background:var(--canvas);color:var(--ink);font-size:14px;font-family:inherit}
.vahint{font-size:13px;color:var(--muted);line-height:1.35;margin-top:.15rem}

/* ── Inside the Camas handheld app ─────────────────────────────────────────
   Only applies when the page is being viewed in the app (body.inapp, set from
   its User-Agent) AND on a shop-floor page (body.kiosk). Chrome, phones and
   desktops are completely unaffected — nothing here can regress the team.

   The aim is not a different design, it is the SAME design at floor scale:
   gloved hands, a 5.5" screen held at arm's length, dust on the glass, and a
   person who is holding a 25 kg sack with the other arm. */
body.inapp.kiosk{
  -webkit-tap-highlight-color:transparent;
  -webkit-user-select:none;user-select:none;   /* nothing here is text to copy */
}
body.inapp.kiosk main{
  padding:.6rem .6rem 3.2rem;                  /* room above the nav bar so the
                                                  last action is never half-hidden */
  font-size:1.05rem;
}
/* Every interactive thing clears the 48 px accessibility target. Scoped to
   main so the slim kiosk bar keeps its own compact sizing. */
body.inapp.kiosk main button,
body.inapp.kiosk main .btn,
body.inapp.kiosk main a.btn,
body.inapp.kiosk main input[type=submit]{
  min-height:52px;padding-block:.7rem;font-size:1.05rem;
  /* A button label must never wrap. At floor font sizes a short label like
     "เปิด →" was breaking onto two lines inside a narrow flex cell, which reads
     as a broken control rather than a bigger one. */
  white-space:nowrap;
}
body.inapp.kiosk main select,
body.inapp.kiosk main input:not([type=checkbox]):not([type=radio]){
  min-height:52px;font-size:1.05rem;
}
body.inapp.kiosk main table{font-size:1rem}
/* A dense table cannot be made touch-sized, so let it scroll on its own rather
   than squeezing the page — the page itself must never scroll sideways. */
body.inapp.kiosk main .tblwrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
body.inapp.kiosk{overflow-x:hidden}
/* The "เปิด →" affordance on the PDA home cards is a chip inside the heading,
   not a button, so the rule above never reached it. At floor font sizes the
   arrow was breaking onto its own line and reading as a broken control. */
body.inapp.kiosk .pdah-st{white-space:nowrap}
/* Floor screens keep the settle-in motion OFF — an operator mid-pour should never
   see the page move under their hand. */
body.kiosk main>.card,body.kiosk main>table.grid{animation:none}

/* ═══════════════ THE WEIGH-STATION KIT (shared by the TWO twins) ═══════════════
   `mixing_weigh.html` (ชั่งสด) and `rm_weigh.html` (คิวชั่ง RM) do the same job
   against different scales and stock. They drift because the code was never
   shared — only the behaviour was, and "kept in sync" by hand has failed twice
   (ui-rules.md "the two weigh pages are TWINS"). The mixing rebuild of
   2026-08-25 is what rm_weigh never got; putting the KIT here is what stops the
   next divergence, because now there is one copy to change.

   `mixing_weigh.html` still carries its own identical copy of these rules in its
   page <style>. That is deliberate and harmless — a page <style> wins on order,
   and the declarations match, so mixing_weigh renders byte-identically today.
   Removing that copy is a separate, separately-verified change.

   SCOPING NOTE: in mixing_weigh the `[data-s="…"]` rules are UNSCOPED. Global
   they must not be — `data-s` is also used by `mixing_pda_opcards.html` and
   `mixing_mirror.html`, which would have inherited these colours by accident.
   Every rule below is scoped to a `wx-` element. */
/* ⛔ `.wx-band` must NOT appear in these five rules, however natural it looks.
   The needle reads `--s-c`, and in mixing_weigh the variable is set on the
   `.gauge` ANCESTOR (its `[data-s]` rules are unscoped). Declaring `--s-c` on
   `.wx-band` too would put an idle-grey value on an element BETWEEN .gauge and
   the needle, and the nearer ancestor wins — the live ชั่งสด needle would have
   gone permanently grey. Custom properties resolve where they are USED
   (ui-rules.md hard rule 5). The state element is the gauge; keep it there. */
.wx-gauge,.wx-stat1{--s-c:var(--idle);--s-t:var(--idle-t);--s-f:var(--idle-f)}
.wx-gauge[data-s="under"],.wx-stat1[data-s="under"]{--s-c:var(--info);--s-t:var(--info-t);--s-f:var(--info-f)}
.wx-gauge[data-s="good"], .wx-stat1[data-s="good"] {--s-c:var(--ok);--s-t:var(--ok-t);--s-f:var(--ok-f)}
.wx-gauge[data-s="over"], .wx-stat1[data-s="over"] {--s-c:var(--warn);--s-t:var(--warn-t);--s-f:var(--warn-f)}
.wx-gauge[data-s="stop"], .wx-stat1[data-s="stop"] {--s-c:var(--danger);--s-t:var(--danger-t);--s-f:var(--danger-f)}

/* ── ZONE 1 · the context strip — set once per shift, then ONE line ───────── */
.wx-ctx{display:flex;align-items:stretch;flex-wrap:wrap;background:var(--canvas);
  border:1px solid var(--hairline);border-radius:var(--r-control);overflow:hidden;margin:0 0 .6rem}
.wx-ctx .c{display:flex;align-items:center;gap:.45rem;padding:.5rem .9rem;
  border-right:1px solid var(--hairline);min-width:0}
.wx-ctx .c:last-child,.wx-ctx .c:last-of-type{border-right:0}
.wx-ctx > .wx-edit{margin-left:auto;align-self:center;margin-right:.5rem}
.wx-ctx .k{font-size:.85rem;color:var(--muted);white-space:nowrap}
.wx-ctx .v{font-size:.98rem;font-weight:500;color:var(--ink);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;max-width:24rem}
.wx-ctx .v.warn{color:var(--warn-t)}
/* The job stacks: its NAME, then the CMO right under it. Pokkrong 2026-08-25 —
   "put the CMO and mixing product next to each other, you can do double line
   instead of squeezing into a line." */
.wx-ctx .jb{display:flex;flex-direction:column;gap:.05rem;min-width:0}
.wx-ctx .c-job .v{font-weight:600;max-width:34rem}
.wx-ctx .k2{font-size:.82rem;color:var(--muted);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.wx-ctx .conn{font-size:.88rem;font-weight:500;padding:0;border-radius:0;background:none;
  color:var(--muted);max-width:30rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wx-ctx .conn.on{background:none;color:var(--ok-t)}
.wx-edit{font:inherit;font-size:.88rem;font-weight:500;padding:.32rem .85rem;
  border-radius:var(--r-control);border:1px solid var(--hairline);
  background:var(--canvas);color:var(--ink);cursor:pointer}
.wx-edit:hover{background:var(--well)}
.wx-setup{background:var(--surface);border-radius:var(--r-card);padding:.9rem 1rem;
  margin:0 0 .7rem;box-shadow:var(--edge-lit),var(--halo);
  display:flex;flex-direction:column;gap:.6rem}
.wx-setup[hidden]{display:none}
.wx-setup select{font:inherit;font-size:1rem;font-weight:500;padding:.35rem .5rem;
  border:1px solid var(--hairline);border-radius:var(--r-control);
  background:var(--canvas);color:var(--ink)}
.wx-line{display:flex;align-items:center;gap:.7rem;flex-wrap:wrap}
.wx-line > .wx-lbl-s{font-size:.88rem;color:var(--muted);min-width:6.5rem}

/* ── THE TARGET BAND, not a dial (DESIGN.md §6 + mockup 03) ────────────────
   A dial answers "how far along, 0–100%". Weighing asks "am I IN the band?".
   A track with a target line and three zones — green = save is ON, amber = over
   the tolerance (take a little out), red = past what may be recorded. */
.wx-gauge{width:100%;max-width:600px;display:flex;flex-direction:column;gap:.1rem}
/* forgot-TARE state: the gauge visibly "doesn't count" */
.wx-gauge.notare{opacity:.5;filter:grayscale(1);animation:wxblink 1s steps(2) infinite}
@keyframes wxblink{50%{opacity:.3}}
.wx-center{display:flex;align-items:baseline;justify-content:center;gap:.5rem;
  width:100%;text-align:center;padding:.1rem 0 0}
.wx-num{font-size:4.6rem;font-weight:400;line-height:1;font-variant-numeric:tabular-nums;
  white-space:nowrap;letter-spacing:-.045em;color:var(--s-t)}
.wx-unit{font-size:1.5rem;color:var(--muted)}
.wx-diff{text-align:center;font-size:1.15rem;color:var(--s-t);
  font-variant-numeric:tabular-nums;min-height:1.4rem}
.wx-diff .dvk{color:var(--muted);font-size:.85em;font-weight:400}
.wx-band{position:relative;height:56px;margin:.2rem 0 0}
.wx-track{position:absolute;inset:20px 0;background:var(--well);
  border-radius:var(--r-control);overflow:hidden}
.wx-track i{position:absolute;top:0;bottom:0;display:block}
.wx-track .z-ok{background:var(--ok-f)}
.wx-track .z-warn{background:var(--warn-f)}
.wx-track .z-stop{background:var(--danger-f)}
.wx-tgt{position:absolute;top:11px;bottom:11px;width:2px;background:var(--ink)}
.wx-tgt::after{content:"เป้า";position:absolute;top:-17px;left:50%;transform:translateX(-50%);
  font-size:.75rem;color:var(--muted);white-space:nowrap}
.wx-needle{position:absolute;top:7px;bottom:7px;width:4px;margin-left:-2px;background:var(--s-c);
  border-radius:2px;box-shadow:0 0 0 3px var(--canvas);transition:left .1s linear}
.wx-ax{position:absolute;bottom:-1px;font-size:.72rem;color:var(--muted);
  font-variant-numeric:tabular-nums}
.wx-ax.l{left:0} .wx-ax.r{right:0}

/* ── ONE status line — the icon tile carries the colour (structure C) ─────── */
.wx-stat1{display:flex;align-items:center;gap:.7rem;width:100%;max-width:600px;
  padding:.55rem .8rem;border:1px solid var(--hairline);border-radius:var(--r-control);
  background:var(--canvas)}
.wx-stat1 .mi{width:38px;height:38px;flex:none;border-radius:var(--r-control);
  display:grid;place-items:center;background:var(--s-f);color:var(--s-t)}
.wx-stat1 .bd{min-width:0;flex:1}
.wx-stat1 .tx{font-size:1.02rem;font-weight:500;color:var(--ink);line-height:1.3}
.wx-stat1 .sb{font-size:.88rem;color:var(--muted);line-height:1.3}
/* the ONE case that goes solid: nothing may be recorded (แดง is the only solid).
   --on-solid, NOT a literal #fff: in dark mode --danger flips to a LIGHT salmon
   (#ff785b) and white-on-that measures 2.6:1 — unreadable. Light 4.80, dark 7.33. */
.wx-stat1{--on-solid:#fff}
:root[data-theme="dark"] .wx-stat1{--on-solid:#101010}
.wx-stat1[data-s="stop"]{background:var(--danger);border-color:var(--danger)}
.wx-stat1[data-s="stop"] .tx{color:var(--on-solid)}
.wx-stat1[data-s="stop"] .sb{color:var(--on-solid);opacity:.82}
.wx-stat1[data-s="stop"] .mi{background:rgba(127,127,127,.28);color:var(--on-solid)}

/* ── the FACTS row — the four numbers that were scattered ─────────────────── */
.wx-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:1px;
  background:var(--hairline);border-radius:var(--r-control);overflow:hidden;
  width:100%;max-width:600px}
.wx-facts .fact{background:var(--canvas);padding:.4rem .7rem}
.wx-facts .k{font-size:.78rem;color:var(--muted)}
.wx-facts .v{font-size:1.05rem;font-variant-numeric:tabular-nums;margin-top:1px;font-weight:500}
.wx-stat{width:100%;max-width:600px;display:flex;flex-direction:column;gap:.4rem;margin-top:.4rem}
.wx-stat > div:empty{display:none}

/* ═══════════════ DUOTONE ICONS — the emoji replacement ═══════════════
   Paths + the two accessors live in `templates/_duo.html`; this is only how they
   are painted. ONE svg = a soft fill of the meaning colour at 25% + the outline
   on top, both inheriting `color`. Keeps the SHAPE and the COLOUR an emoji
   carries, which is the whole reason the previous flat one-colour sweep was
   rejected ("I don't want to just change to one single color").

   Sized in `em`, so the same icon is right in a 13px label and a 17px heading.
   Tones are only ever valid ON `.duo` — written as `.duo.t-x`, never a bare
   `.t-x`, so they can never leak into the global namespace the way `.flag` and
   `.edit` once did (ui-rules.md "prefix every class I add"). */
.duo{width:1.05em;height:1.05em;vertical-align:-.16em;flex:none;display:inline-block}
.duo .f{fill:currentColor;opacity:.25;stroke:none}
.duo .s{fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.duo.duo-lg{width:1.5em;height:1.5em;vertical-align:-.3em}
.duo.t-ink{color:var(--ink)}
.duo.t-muted{color:var(--muted)}
.duo.t-ok{color:var(--ok)}
.duo.t-warn{color:var(--warn)}
.duo.t-danger{color:var(--danger)}
.duo.t-info{color:var(--info)}
.duo.t-live{color:var(--live)}
.duo.t-violet{color:var(--d4)}
/* ON A SOLID SLAB THE TONE MUST YIELD. A palette tone is chosen against the
   page, not against a dark button: measured on the ปิดเพื่อส่ง slab, t-info
   (#0081aa) on the near-black button came out at 4.16:1 — under AA for the
   14px glyph it is. Inside a button the icon takes the button's OWN foreground,
   which is what makes it legible on the dark slab (16.4:1) and still correct on
   a ghost or outline button, where `inherit` is simply the ink.

   The `[class]` is not decoration — it is specificity arithmetic. The tone rule
   `.duo.t-info` is two classes (0,0,2,0); a plain `button .duo` is one class +
   one element (0,0,1,1) and LOSES, which is exactly what the first attempt did
   (measured: still 4.16). The attribute selector takes it to (0,0,2,1). */
button .duo[class],.rw-save .duo[class]{color:inherit}
/* same reasoning for the one panel that goes solid danger */
.wx-stat1[data-s="stop"] .duo{color:var(--on-solid)}
