/* ============================================================================
   k2.css — KESA / MBG v2 design system. Obsidian Mission Control.
   Plain CSS. Custom properties. No preprocessor, no build step.
   Loaded relatively by every page: <link rel="stylesheet" href="./k2.css">

   TABLE OF CONTENTS
     00  TOKENS — dark, the default
     01  TOKENS — light
     02  TOKENS — scale: type, space, radius, motion, z, layout
     03  RESET AND BASE
     04  THE FIVE ANTI OVERFLOW LAWS
     05  UTILITIES
     06  SUBSTRATE
     07  APP SHELL — topbar, rail, main, botnav, fab
     08  LANGUAGE AND THEME CONTROLS
     09  SECTION CADENCE
     10  HERO
     11  KPI TILES
     12  PANEL AND CARD
     13  BUTTONS AND TWO STEP CONFIRM
     14  PILLS AND BADGES
     15  FORM CONTROLS
     16  PICKER, FILE DROP, IMPORT PREVIEW
     17  TABLE
     18  PAGINATION
     19  TABS AND SEGMENTED
     20  DRAWER AND BOTTOM SHEET
     21  TOAST
     22  BANNER
     23  STATES — skeleton, empty, error, offline, stale
     24  CHARTS
     25  SPECIMEN HELPERS (kitchen sink only)
     26  PRINT DOCUMENTS — delivery bill, packing list
     27  RESPONSIVE SHELL — 390 to 2560
     28  PRINT
     29  REDUCED MOTION AND FORCED COLOURS

   THREE DELIBERATE REFINEMENTS TO THE TOKEN TABLE IN DESIGN-LANGUAGE.md §2,
   made because harness/audit.mjs holds every text node under 24px to AAA 7:1
   and DESIGN-LANGUAGE §13.2 requires the audit to report zero failures. The
   published table rated labels, meta and axis text at AA 4.5 which the harness
   fails. Nothing else moved.
     1. --k-ink-3 lifted: dark #8AA5A3 -> #9CB7B4, light #4F6769 -> #3E5456.
        ink-3 now clears 7:1 on bg, surface-1, surface-2, panel glass and the
        chart surface. It is FORBIDDEN on surface-3 and surface-4, where the
        components below switch to ink-2.
     2. --k-link added, because the light link at #0A6B64 measured 5.97:1.
        Dark #3FD0BE (8.62:1 on surface-2), light #06534D (8.93:1 on white).
     3. Status colours are never text. good / warn / crit / info / mute paint
        icons, dots, borders and washes; the words beside them are ink-1 or
        ink-2. Coloured status text on its own wash measured 4.8 to 6.8:1 in
        both themes and could not be lifted without abandoning the hue. This
        also strengthens the no-colour-only rule: every status is icon + word.
   ============================================================================ */


/* ════════════════════════════════════════════════════════════════════════════
   00  TOKENS — dark, the default
   Six depth planes, glass over them, three inks, teal for the system and gold
   for the one thing a human must act on.
   ════════════════════════════════════════════════════════════════════════════ */
:root{
  color-scheme: dark;

  --k-void:      #03090A;
  --k-bg:        #050D0E;
  --k-surface-1: #0B1A1B;
  --k-surface-2: #0E2224;
  --k-surface-3: #14312F;
  --k-surface-4: #163330;

  --k-glass:    rgba(14,34,36,.74);
  --k-glass-hi: rgba(21,50,48,.82);
  --k-glass-lo: rgba(8,20,21,.62);
  /* The two chrome bars sit over content, not over the canvas, so they get
     their own near opaque glass. Anything under .92 lets a KPI figure read
     through the words of the bottom nav. */
  --k-botnav-glass: rgba(11,26,27,.94);
  --k-topbar-glass: rgba(5,13,14,.92);
  --k-blur:     18px;
  --k-blur-sm:  10px;

  --k-line:        rgba(158,213,206,.14);
  --k-line-soft:   rgba(158,213,206,.08);
  --k-line-strong: rgba(158,213,206,.28);
  --k-line-ctl:    rgba(158,213,206,.46);
  --k-edge:        rgba(200,255,246,.26);
  --k-sh-1: 0 1px 2px rgba(0,0,0,.55);
  --k-sh-2: 0 12px 32px -14px rgba(0,0,0,.80);
  --k-sh-3: 0 36px 80px -34px rgba(0,0,0,.92);

  --k-ink-1:   #E9F3F1;
  --k-ink-2:   #A7C2BF;
  --k-ink-3:   #9CB7B4;
  --k-ink-inv: #04140F;

  --k-teal:        #14A99B;
  --k-teal-bright: #3FD0BE;
  --k-teal-deep:   #0F3D3E;
  --k-teal-wash:   rgba(20,169,155,.12);
  --k-gold:        #D9A441;
  --k-gold-bright: #E4B75F;
  --k-gold-text:   #E4B75F;
  --k-gold-wash:   rgba(217,164,65,.12);
  --k-action-bg:   #D9A441;
  --k-action-ink:  #1A1205;
  --k-action-ring: rgba(217,164,65,.30);
  /* The button FILL is Marc's gold in both themes, so its hover and pressed
     steps are tokens of their own. Deriving them from --k-gold was a light
     theme bug: there --k-gold is the brown TEXT gold, and the hover state
     measured 4.78:1 against the action ink where the rest state measures
     8.24. Hover 9.92:1, pressed 7.34:1. */
  --k-action-bg-hover:  #E4B75F;
  --k-action-bg-active: #CE9A3E;
  --k-link:        #3FD0BE;

  --k-focus:      #3FD0BE;
  --k-focus-halo: #04140F;

  /* --k-warn is a true orange, not a second gold. It used to be #E4B75F,
     which sits at OKLCh hue 82 against gold's 80: at a glance every warning
     WAS the call to action colour, which is the one thing this direction is
     not allowed to blur. Now hue 52, so 28 degrees off gold and 38 off crit.
     Measured 7.65:1 on surface-2 and 6.11:1 on its own wash. */
  --k-good:#42C88E; --k-good-bg:rgba(66,200,142,.13);  --k-good-line:rgba(66,200,142,.34);
  --k-warn:#F79A5E; --k-warn-bg:rgba(247,154,94,.13);  --k-warn-line:rgba(247,154,94,.36);
  --k-crit:#F4808D; --k-crit-bg:rgba(244,128,141,.13); --k-crit-line:rgba(244,128,141,.34);
  --k-info:#79A6F5; --k-info-bg:rgba(121,166,245,.13); --k-info-line:rgba(121,166,245,.34);
  --k-mute:#9CB7B4; --k-mute-bg:rgba(138,165,163,.13); --k-mute-line:rgba(138,165,163,.30);

  --k-c1:#14A99B; --k-c2:#8D49CA; --k-c3:#AC5102; --k-c4:#6D8EE5; --k-c5:#BC3577;
  --k-c6:#8AA5A3;
  --k-seq-1:#A9EFE6; --k-seq-2:#5FD5C7; --k-seq-3:#22B0A2; --k-seq-4:#0C7E74;
  --k-div-1:#E0806F; --k-div-2:#C9A79C; --k-div-3:#7E8F8D; --k-div-4:#5FB9AC; --k-div-5:#0C9A8B;
  --k-chart-surface:#0E2224;
  --k-grid:  rgba(158,213,206,.13);
  --k-spark: rgba(167,194,191,.42);

  /* Opaque equivalents. A translucent wash sitting on translucent glass is a
     colour nobody can predict and the audit composites it against white, so
     anything that stacks a wash gets an opaque base underneath it. */
  --k-panel-solid:#0C1D1E;   /* glass over bg, resolved */
  --k-zebra:      #112527;
  --k-sel-row:    #0D2E2D;
  --k-mono-bg:    #5FD5C7;   /* the monogram chip is brand, identical in both themes */
  --k-mono-ink:   #04140F;
  /* White on a filled teal mark is correct in both themes, so it is one token
     rather than a hex literal repeated at four call sites. */
  --k-on-teal:    #FFFFFF;

  /* Which nav item you are on. Deliberately NOT surface-3, because that is
     what hover uses and the two were indistinguishable. */
  --k-nav-on-bg: color-mix(in srgb, var(--k-teal-bright) 16%, transparent);
  --k-nav-on-bg-hi: color-mix(in srgb, var(--k-teal-bright) 24%, transparent);
  --k-nav-on-edge: color-mix(in srgb, var(--k-teal-bright) 34%, transparent);
  --k-nav-on-ink: var(--k-ink-1);
}

/* ════════════════════════════════════════════════════════════════════════════
   01  TOKENS — light
   Not an inversion. A full re selection. Suda prints the delivery bill every
   day and Naveen reads reports on paper, so light must come out ink on paper.
   ════════════════════════════════════════════════════════════════════════════ */
[data-theme="light"]{
  color-scheme: light;
  --k-void:      #D6E1DF;
  --k-bg:        #EDF2F0;
  --k-surface-1: #FFFFFF;
  --k-surface-2: #F6F8F7;
  --k-surface-3: #EBF1EF;
  --k-surface-4: #E7EFEC;

  --k-glass:    rgba(255,255,255,.80);
  --k-glass-hi: rgba(255,255,255,.92);
  --k-glass-lo: rgba(255,255,255,.62);
  --k-botnav-glass: rgba(255,255,255,.95);
  --k-topbar-glass: rgba(237,242,240,.93);
  --k-blur:     14px;
  --k-blur-sm:  8px;

  --k-line:        rgba(10,42,44,.13);
  --k-line-soft:   rgba(10,42,44,.07);
  --k-line-strong: rgba(10,42,44,.26);
  --k-line-ctl:    rgba(10,42,44,.54);
  --k-edge:        rgba(255,255,255,.95);
  --k-sh-1: 0 1px 2px rgba(9,38,40,.08);
  --k-sh-2: 0 10px 26px -14px rgba(9,38,40,.28);
  --k-sh-3: 0 30px 64px -32px rgba(9,38,40,.34);

  --k-ink-1:   #08191B;
  --k-ink-2:   #3B5254;
  --k-ink-3:   #3E5456;
  --k-ink-inv: #FFFFFF;

  --k-teal:        #0A6B64;
  --k-teal-bright: #0F8C82;
  --k-teal-deep:   #0F3D3E;
  --k-teal-wash:   rgba(10,107,100,.10);
  --k-gold:        #8A6410;
  --k-gold-bright: #A8791A;
  --k-gold-text:   #5A410A;
  --k-gold-wash:   rgba(138,100,16,.10);
  --k-action-bg:   #D9A441;
  --k-action-ink:  #1A1205;
  --k-action-ring: rgba(138,100,16,.28);
  --k-action-bg-hover:  #E4B75F;
  --k-action-bg-active: #CE9A3E;
  --k-link:        #06534D;

  --k-focus:      #0A6B64;
  --k-focus-halo: #FFFFFF;

  --k-good:#0C7047; --k-good-bg:rgba(12,112,71,.10);  --k-good-line:rgba(12,112,71,.30);
  --k-warn:#9A4A10; --k-warn-bg:rgba(154,74,16,.10);  --k-warn-line:rgba(154,74,16,.32);
  --k-crit:#B32636; --k-crit-bg:rgba(179,38,54,.09);  --k-crit-line:rgba(179,38,54,.28);
  --k-info:#1F55B0; --k-info-bg:rgba(31,85,176,.09);  --k-info-line:rgba(31,85,176,.28);
  --k-mute:#3E5456; --k-mute-bg:rgba(79,103,105,.10); --k-mute-line:rgba(79,103,105,.26);

  --k-c1:#009488; --k-c2:#6D23A6; --k-c3:#873D00; --k-c4:#517BFA; --k-c5:#A41A63;
  --k-c6:#4F6769;
  --k-seq-1:#8CB9AF; --k-seq-2:#4A9E91; --k-seq-3:#1B8375; --k-seq-4:#00675A;
  --k-div-1:#A5292B; --k-div-2:#C08A84; --k-div-3:#8A9998; --k-div-4:#4E9E92; --k-div-5:#00786A;
  --k-chart-surface:#F6F8F7;
  --k-grid:  rgba(10,42,44,.12);
  /* Light sparklines were .34 alpha and read as smudges where the dark ones
     read as marks. Light is the same system in light, not a flatter one. */
  --k-spark: rgba(59,82,84,.58);

  --k-panel-solid:#FBFCFC;
  --k-zebra:      #F2F4F3;
  --k-sel-row:    #E4EEED;
  --k-mono-bg:    #5FD5C7;
  --k-mono-ink:   #04140F;
  --k-on-teal:    #FFFFFF;

  /* The light theme showed nothing at all before: surface-3 on a near white
     bar is invisible. A teal wash with a teal edge reads at a glance. */
  --k-nav-on-bg: color-mix(in srgb, var(--k-teal) 13%, #fff);
  --k-nav-on-bg-hi: color-mix(in srgb, var(--k-teal) 19%, #fff);
  --k-nav-on-edge: color-mix(in srgb, var(--k-teal) 40%, #fff);
  --k-nav-on-ink: var(--k-teal-deep, var(--k-teal));
}

/* ════════════════════════════════════════════════════════════════════════════
   02  TOKENS — scale
   Nine type steps so a page cannot invent a tenth. A 4 px space base so
   optical alignment is arithmetic. One z ladder so nobody writes 9999.
   ════════════════════════════════════════════════════════════════════════════ */
:root{
  --k-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
            "Noto Sans Thai", "Sarabun", "Loma", "IBM Plex Sans Thai",
            "Noto Sans SC", "Noto Sans CJK SC", "PingFang SC", "Hiragino Sans GB",
            "Microsoft YaHei", "DejaVu Sans", Arial, sans-serif;
  --k-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas,
            "DejaVu Sans Mono", monospace;

  --k-t-micro:  0.6875rem;
  --k-t-label:  0.75rem;
  --k-t-small:  0.8125rem;
  --k-t-body:   0.9375rem;
  --k-t-lede:   1.125rem;
  --k-t-h3:     1.375rem;
  --k-t-h2:     1.75rem;
  --k-t-h1:     2.25rem;
  --k-t-display:3.5rem;

  --k-lh-tight:1.10; --k-lh-snug:1.35; --k-lh-body:1.60;
  --k-lh-tight-th:1.30; --k-lh-snug-th:1.55; --k-lh-body-th:1.80;
  --k-tr-display:-0.022em; --k-tr-tight:-0.014em; --k-tr-label:0.08em;

  --k-1:4px;  --k-2:8px;  --k-3:12px; --k-4:16px; --k-5:24px;
  --k-6:32px; --k-7:48px; --k-8:64px; --k-9:96px;

  --k-r1:6px; --k-r2:10px; --k-r3:14px; --k-r4:20px; --k-rpill:999px;

  --k-d1:120ms; --k-d2:240ms; --k-d3:420ms; --k-d4:900ms;
  --k-ease:     cubic-bezier(.2,.7,.2,1);
  --k-ease-out: cubic-bezier(.16,1,.3,1);

  --k-z-substrate:0;
  --k-z-content:1;
  --k-z-sticky-col:2;
  --k-z-sticky-hd:3;
  --k-z-sticky-hdcol:4;
  --k-z-rail:30;
  --k-z-topbar:60;
  --k-z-botnav:80;
  --k-z-fab:81;
  --k-z-menu:120;
  --k-z-scrim:150;
  --k-z-drawer:160;
  --k-z-tooltip:200;
  --k-z-toast:300;

  --k-rail:232px;
  --k-topbar:56px;
  --k-botnav:64px;
  --k-max:1560px;
  --k-tap:44px;
  --k-gutter:var(--k-4);

  --k-bp-sm:480px; --k-bp-md:720px; --k-bp-lg:1080px; --k-bp-xl:1360px; --k-bp-2xl:1560px;
}

/* ════════════════════════════════════════════════════════════════════════════
   THE STICKY TOPBAR OFFSET
   The topbar is 56 px, position:sticky, and scroll-margin appeared zero times
   in this file. Every in page jump on every page therefore parked the heading
   you jumped to underneath the bar: Accounts sliced "Cash, dues and the gap"
   in half, Online clipped the word "Stock", and Online's five rail anchors all
   landed 56 px low. One declaration, applied to everything a link can aim at.
   ════════════════════════════════════════════════════════════════════════════ */
:target,
[id],
.sec, .sec-hd, .panel, .panel-hd, details > summary,
h1[id], h2[id], h3[id], h4[id]{
  scroll-margin-top: calc(var(--k-topbar) + var(--k-3, 12px));
}
/* Below 1080 the topbar is still sticky and the bottom nav eats the last
   64 px, so a jump target near the end of a page needs room under it too. */
@media (max-width:1080px){
  [id]{ scroll-margin-bottom: calc(var(--k-botnav) + var(--k-4, 16px)); }
}


/* Thai stacks four levels of marks. At 1.60 they touch the line above. */
:root[data-lang="th"], :lang(th){
  --k-lh-tight: var(--k-lh-tight-th);
  --k-lh-snug:  var(--k-lh-snug-th);
  --k-lh-body:  var(--k-lh-body-th);
}
/* Chinese reads optically smaller at the same px. */
:root[data-lang="zh"]{ --k-t-body:1rem; --k-t-small:0.875rem; }


/* ════════════════════════════════════════════════════════════════════════════
   03  RESET AND BASE
   ════════════════════════════════════════════════════════════════════════════ */
*,*::before,*::after{ box-sizing:border-box; }
html{
  -webkit-text-size-adjust:100%;
  background:var(--k-bg);
  scroll-behavior:smooth;
}
body{
  margin:0;
  min-height:100vh;
  font-family:var(--k-sans);
  font-size:var(--k-t-body);
  line-height:var(--k-lh-body);
  color:var(--k-ink-1);
  background:var(--k-bg);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  /* Law 5. overflow-x:clip used to live here as a "seatbelt". It was removed:
     it hid the symptom from the eye, made the spilled columns permanently
     unreachable (window.scrollX stayed 0), and let every fixed element
     restretch to the overflowed width. Nothing is clipped now, so anything
     that overflows is visible, reachable and measurable. */
}
h1,h2,h3,h4,p,figure,dl,dd,ul,ol{ margin:0; }
ul,ol{ padding:0; list-style:none; }
/* THE HIDDEN ATTRIBUTE WINS, ALWAYS.
   The UA sheet says [hidden]{display:none} at the lowest specificity, so any
   component that sets its own display beats it: .pill{display:inline-flex}
   left a permanent empty chip beside Undo and an empty outline beside
   "Today's meetings" for everybody with no meetings, because hiding by
   attribute silently did nothing. This is the reset, not a per component
   patch, so no class written later can defeat it either. */
[hidden]{ display:none !important; }
img,svg,canvas,video{ display:block; max-width:100%; }
button,input,select,textarea{ font:inherit; color:inherit; margin:0; }
button{ background:none; border:0; padding:0; cursor:pointer; }
table{ border-collapse:separate; border-spacing:0; }
a{ color:var(--k-link); text-underline-offset:3px; }
a:hover{ text-decoration-thickness:2px; }
hr{ border:0; border-top:1px solid var(--k-line); margin:0; }
:focus-visible{
  outline:2px solid var(--k-focus);
  outline-offset:2px;
  box-shadow:0 0 0 2px var(--k-focus-halo);
  border-radius:var(--k-r1);
}
::selection{ background:var(--k-teal); color:var(--k-on-teal); }

/* Thai must never be tracked; tracking detaches the tone mark from its base. */
:lang(th), :lang(zh){ letter-spacing:0 !important; }
:lang(th) .eyebrow, :lang(zh) .eyebrow,
:lang(th) th, :lang(zh) th,
:lang(th) .k-caps, :lang(zh) .k-caps{ text-transform:none; }
:lang(th){ word-break:normal; line-break:loose; overflow-wrap:break-word; }
:lang(zh){ line-break:strict; overflow-wrap:break-word; }


/* ════════════════════════════════════════════════════════════════════════════
   04  THE FIVE ANTI OVERFLOW LAWS
   Measured on the specimen: a 390 px viewport produced a 534 px document.
   Two causes, both classes of bug. Both are closed structurally here.
   ════════════════════════════════════════════════════════════════════════════ */

/* Law 2. min-width:0 relaxes the main axis, max-width:100% the cross axis.
   A column flex parent with align-items:flex-start sizes the cross axis to
   max-content, which is how a nowrap span became 493 px inside a 308 px box. */
.main :where(div,section,article,aside,header,footer,li,dd,dt,td,th,p,span,label,figure,details,summary),
.drawer :where(div,section,li,dd,dt,p,span,label,figure),
.topbar :where(div,span,label,b,a),
.botnav :where(div,span,a),
.toaster :where(div,span,p,b){
  min-width:0;
  max-width:100%;
}

/* Law 3. nowrap is a contract. Either it ellipses, or it is flex:none inside a
   container guaranteed wider than its max-content. .k-nw is the first form. */
.k-nw{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }

/* Law 4. Long unbreakable tokens wrap. Never break-all: it shatters Thai. */
.wrap-any,
.mark, .kv dd, .toast p, .banner p, .state p, .filerow .grow,
.picker-opt .meta b, .import-diff li{
  overflow-wrap:anywhere;
  word-break:normal;
}

/* Law 1 is a discipline, not a rule you can write once: every grid track in
   this file is minmax(0, ...). Law 5 is the seatbelt on body above. */


/* ════════════════════════════════════════════════════════════════════════════
   05  UTILITIES
   ════════════════════════════════════════════════════════════════════════════ */
.sr{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
/* The skip link is off screen until focused, and it is a keyboard affordance
   rather than the thing a human must act on, so it wears the focus colour
   and not the action gold. */
.skip{
  position:absolute; left:var(--k-3); top:-100px; z-index:calc(var(--k-z-toast) + 1);
  padding:10px 16px; border-radius:var(--k-r2);
  background:var(--k-surface-1); color:var(--k-ink-1); font-weight:700;
  border:2px solid var(--k-focus); box-shadow:var(--k-sh-2);
  transition:top var(--k-d2) var(--k-ease);
}
.skip:focus{ top:var(--k-3); }

.num,
table.k td.n, table.k th.n,
.kpi .val, .kpi .delta,
.chart text.axis, .chart text.value,
.kv dd, .pager button, time{
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}
.mono{ font-family:var(--k-mono); }
.grow{ flex:1 1 auto; min-width:0; }
.k-caps{
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase;
}
.muted{ color:var(--k-ink-2); }
.quiet{ color:var(--k-ink-3); }
.k-hide{ display:none !important; }

/* The lazy icon slot k2.js creates before k2-icons has loaded. It is a span,
   so Law 2 gives it min-width:0, and as a bare flex item it collapses under
   any long neighbour: measured at 4.5 px on a phone banner. It is a fixed
   size box holding one svg, never a text run, so it never shrinks. */
.k2-ic{ display:inline-flex; align-items:center; justify-content:center; flex:none; }

/* Layout primitives. Every track is minmax(0, ...). */
.stack{ display:flex; flex-direction:column; gap:var(--k-4); min-width:0; }
.stack.tight{ gap:var(--k-2); }
.stack.loose{ gap:var(--k-6); }
.row{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-3); min-width:0; }
.row.tight{ gap:var(--k-2); }
.row.end{ justify-content:flex-end; }
/* Paired panels are equal height. Six ragged holes down a two column grid is
   what you get when the cell hugs its content instead of stretching, and the
   panel then has to fill the cell for the stretch to be visible. */
.g12{ display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:var(--k-4); align-items:stretch; }
.g12 > [class^="c-"],.g12 > [class*=" c-"]{ display:flex; min-width:0; }
.g12 > [class^="c-"] > .panel,.g12 > [class*=" c-"] > .panel{
  flex:1 1 auto; display:flex; flex-direction:column; min-width:0;
}
.g12 > [class^="c-"] > .panel > .panel-bd,
.g12 > [class*=" c-"] > .panel > .panel-bd{ flex:1 1 auto; }
.c-1{grid-column:span 1}  .c-2{grid-column:span 2}  .c-3{grid-column:span 3}
.c-4{grid-column:span 4}  .c-5{grid-column:span 5}  .c-6{grid-column:span 6}
.c-7{grid-column:span 7}  .c-8{grid-column:span 8}  .c-9{grid-column:span 9}
.c-10{grid-column:span 10}.c-11{grid-column:span 11}.c-12{grid-column:span 12}
.auto-grid{
  display:grid; gap:var(--k-4);
  grid-template-columns:repeat(auto-fill,minmax(min(240px,100%),1fr));
}


/* ════════════════════════════════════════════════════════════════════════════
   06  SUBSTRATE
   A static weave behind everything. It does not move, ever (MOTION §11).
   ════════════════════════════════════════════════════════════════════════════ */
body::before{
  content:""; position:fixed; inset:0; z-index:var(--k-z-substrate); pointer-events:none;
  background:
    radial-gradient(1200px 620px at 18% -8%, rgba(20,169,155,.13), transparent 62%),
    radial-gradient(900px 520px at 92% 4%, rgba(217,164,65,.055), transparent 60%),
    radial-gradient(1100px 800px at 50% 108%, rgba(20,169,155,.07), transparent 66%),
    repeating-linear-gradient(90deg, var(--k-line-soft) 0 1px, transparent 1px 13px),
    repeating-linear-gradient(0deg,  var(--k-line-soft) 0 1px, transparent 1px 13px);
  opacity:.9;
}
[data-theme="light"] body::before{
  background:
    radial-gradient(1200px 620px at 18% -8%, rgba(10,107,100,.09), transparent 62%),
    radial-gradient(900px 520px at 92% 4%, rgba(138,100,16,.05), transparent 60%),
    radial-gradient(1100px 800px at 50% 108%, rgba(10,107,100,.05), transparent 66%),
    repeating-linear-gradient(90deg, var(--k-line-soft) 0 1px, transparent 1px 13px),
    repeating-linear-gradient(0deg,  var(--k-line-soft) 0 1px, transparent 1px 13px);
  opacity:.75;
}


/* ════════════════════════════════════════════════════════════════════════════
   07  APP SHELL
   ════════════════════════════════════════════════════════════════════════════ */
.app{ position:relative; z-index:var(--k-z-content); min-height:100vh; }

/* The topbar is a three column grid whose middle column is the only flexible
   one, so the ends can never be pushed out of the box. This is the whole of
   overflow Cause A. */
.topbar{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;
  gap:var(--k-3);
  min-height:var(--k-topbar);
  padding-inline: max(var(--k-4), env(safe-area-inset-left)) max(var(--k-4), env(safe-area-inset-right));
  position:sticky; top:0; z-index:var(--k-z-topbar);
  /* Opaque base, same reason as the bottom nav: the topbar sits over the KPI
     row and a figure must never read through a control label. */
  background:var(--k-surface-1);
  border-bottom:1px solid var(--k-line-strong);
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .topbar{
    background:var(--k-topbar-glass);
    -webkit-backdrop-filter:blur(22px) saturate(150%);
    backdrop-filter:blur(22px) saturate(150%);
  }
}
.brand{ display:flex; align-items:center; gap:10px; flex:none; min-height:var(--k-tap); text-decoration:none; color:inherit; }
.brand-mark{ width:30px; height:30px; flex:none; color:var(--k-teal-bright); }
[data-theme="light"] .brand-mark{ color:var(--k-teal); }
.brand-txt{ display:flex; flex-direction:column; line-height:1.05; min-width:0; }
/* The two lines are set to the same measure. MEHARBAN GLOBAL is the longer word
   at looser tracking, so it came out wider than KESADHARI and the lockup read
   bottom heavy and accidental. The sub line is tracked tighter to match. */
.brand-txt b{ font-size:var(--k-t-small); font-weight:700; letter-spacing:.145em; text-transform:uppercase; }
.brand-txt span{
  font-size:var(--k-t-micro); letter-spacing:.055em; text-transform:uppercase;
  color:var(--k-ink-3); white-space:nowrap;
}
.nav{ display:flex; align-items:center; gap:2px; min-width:0; overflow:hidden; }
.nav a{
  display:inline-flex; align-items:center; gap:7px; height:36px; padding:0 12px;
  border-radius:var(--k-r2); font-size:var(--k-t-small); font-weight:550;
  color:var(--k-ink-2); text-decoration:none; white-space:nowrap; flex:none;
  transition:background var(--k-d2) var(--k-ease), color var(--k-d2) var(--k-ease);
}
.nav a svg{ width:15px; height:15px; opacity:.8; }
.nav a:hover{ background:var(--k-surface-3); color:var(--k-ink-1); }
/* The current item is marked the same way everywhere: a 2 px teal spine, a
   surface step and weight 600. The topbar used to use a bordered filled pill
   and the rail a spine, which is two idioms 60 px apart in one viewport. */
/* The current item must not look like the hovered item. They both used
   surface-3, so the highlight simply followed the mouse and never told you
   where you were, and in the light theme surface-3 sits so close to the bar
   that nothing appeared at all. Current now gets a teal wash and a teal ring
   that hover does not have, in both themes. */
.nav a[aria-current="page"], .nav a[aria-current="true"]{
  background:var(--k-nav-on-bg); color:var(--k-nav-on-ink); font-weight:650;
  box-shadow:inset 0 0 0 1px var(--k-nav-on-edge);
  position:relative;
}
.nav a[aria-current="page"]:hover, .nav a[aria-current="true"]:hover{
  background:var(--k-nav-on-bg-hi);
}
/* A page inside a section gets a quieter mark than the page itself. */
.nav a[aria-current="true"]{ font-weight:600; }
.nav a[aria-current="true"]::before{ opacity:.55; }
.nav a[aria-current="page"]::before{
  content:""; position:absolute; left:2px; top:5px; bottom:5px; width:2px;
  border-radius:2px; background:var(--k-teal-bright);
}
[data-theme="light"] .nav a[aria-current="page"]::before{ background:var(--k-teal); }
.nav a[aria-current="page"]{ padding-left:16px; }
.nav a[aria-current="page"] svg{ opacity:1; color:var(--k-teal-bright); }
[data-theme="light"] .nav a[aria-current="page"] svg{ color:var(--k-teal); }
.topbar-tools{ display:flex; align-items:center; gap:var(--k-1); justify-self:end; }
.topbar-tools > *{ flex:none; }
/* The installed build, top corner of the sign in page and the home page
   (2026-09-28). Small, mono, never louder than the name beside it. */
.buildtag{ font-family:var(--k-mono); font-size:var(--k-t-micro); color:var(--k-ink-3); white-space:nowrap; margin-right:var(--k-2); }
@media (max-width:720px){ .buildtag{ display:none; } }

.user{
  display:flex; align-items:center; gap:9px; height:var(--k-tap); min-width:var(--k-tap);
  padding:0 10px 0 5px; justify-content:center;
  border-radius:var(--k-rpill); border:1px solid var(--k-line); background:var(--k-surface-2);
}
.user:hover{ background:var(--k-surface-3); }
/* The avatar was a mint to white gradient: the brightest object in the whole
   chrome, brighter than the gold call to action, on the least important
   control, and the only gradient in the shell. It is now a quiet chip. */
.avatar{
  width:30px; height:30px; border-radius:50%; flex:none; display:grid; place-items:center;
  font-size:var(--k-t-micro); font-weight:700; color:var(--k-ink-2);
  background:var(--k-surface-3);
  box-shadow:inset 0 0 0 1px var(--k-line-ctl);
}
.user .who{ display:flex; flex-direction:column; line-height:1.2; text-align:left; min-width:0; }
.user .who b{ font-size:var(--k-t-label); font-weight:650; }
.user .who span{
  font-size:var(--k-t-micro); letter-spacing:.12em; text-transform:uppercase; color:var(--k-ink-3);
}

.shell{
  display:grid;
  grid-template-columns: var(--k-rail) minmax(0,1fr);   /* never a bare 1fr */
  max-width:var(--k-max); margin-inline:auto;
}
.rail{
  position:sticky; top:var(--k-topbar); align-self:start; z-index:var(--k-z-rail);
  height:calc(100vh - var(--k-topbar));
  overflow-y:auto; overscroll-behavior:contain;
  padding:var(--k-5) var(--k-3) var(--k-8);
  border-right:1px solid var(--k-line-strong);
  min-width:0;
}
/* In light the hairline alone did not read as a rail, so the rail is also a
   half step of surface away from the canvas. Same system, different cue. */
[data-theme="light"] .rail{ background:var(--k-surface-2); }
.rail-grp{ margin-bottom:var(--k-5); }
.rail-grp > .eyebrow{ padding-inline:var(--k-3); }
.rail a{
  display:flex; align-items:center; gap:10px; min-height:34px; padding:0 var(--k-3);
  border-radius:var(--k-r2); color:var(--k-ink-2); text-decoration:none;
  font-size:var(--k-t-small); font-weight:500; position:relative; min-width:0;
  transition:background var(--k-d2) var(--k-ease), color var(--k-d2) var(--k-ease);
}
.rail a > span:not(.count){ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rail a svg{ width:16px; height:16px; flex:none; opacity:.75; }
.rail a:hover{ background:var(--k-surface-3); color:var(--k-ink-1); }
.rail a:hover svg{ opacity:1; }
.rail a[aria-current="page"]{
  background:var(--k-nav-on-bg); color:var(--k-nav-on-ink); font-weight:650;
  box-shadow:inset 0 0 0 1px var(--k-nav-on-edge);
}
.rail a[aria-current="page"]:hover{ background:var(--k-nav-on-bg-hi); }
/* The spine is inset INSIDE the rounded fill. It used to sit at left:0 on a
   rounded row, which left a wedge of canvas between the marker and the thing
   it marks. */
.rail a[aria-current="page"]::before{
  content:""; position:absolute; left:2px; top:4px; bottom:4px; width:2px;
  border-radius:2px; background:var(--k-teal-bright);
}
[data-theme="light"] .rail a[aria-current="page"]::before{ background:var(--k-teal); }
.rail a[aria-current="page"] svg{ opacity:1; color:var(--k-teal-bright); }
[data-theme="light"] .rail a[aria-current="page"] svg{ color:var(--k-teal); }
/* The count is the number this role may see. Zero is absent, because 0 and
   "not permitted" must never look alike. */
.rail .count{ margin-left:auto; flex:none; font-size:var(--k-t-micro); color:var(--k-ink-3); }
/* The count on the CURRENT row sits on the teal nav fill, not on the rail, and
   ink-2 measured 6.88:1 there against the 7:1 the audit holds every text node
   under 24 px to. It failed on every page in the product at once. The row's
   own label already uses --k-nav-on-ink, which is the ink chosen for that
   fill in each theme; the count is text on the same fill, so it takes it too. */
.rail a[aria-current="page"] .count{ color:var(--k-nav-on-ink); }

.main{
  min-width:0;
  padding:var(--k-6) var(--k-5) var(--k-9);
  container-type:inline-size; container-name:main;
}

/* The bottom nav is the most tapped control in the product and it is used
   one handed in a warehouse in sunlight. It was --k-glass at .74 alpha, and a
   KPI tile behind it read straight through the words. Opaque is the base;
   glass is applied only where the browser will actually composite the blur,
   and even then at .94. §6.5. */
.botnav{
  display:none; position:fixed; left:0; right:0; bottom:0; z-index:var(--k-z-botnav);
  min-height:var(--k-botnav); align-items:stretch;
  padding:6px max(var(--k-2), env(safe-area-inset-left)) max(6px, env(safe-area-inset-bottom));
  background:var(--k-surface-1); border-top:1px solid var(--k-line-strong);
  box-shadow:0 -8px 24px -18px rgba(0,0,0,.9);
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .botnav{
    background:var(--k-botnav-glass);
    -webkit-backdrop-filter:blur(22px) saturate(150%);
    backdrop-filter:blur(22px) saturate(150%);
  }
}
.botnav a{
  flex:1 1 0; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:3px; border-radius:var(--k-r2); color:var(--k-ink-3); text-decoration:none;
  font-size:var(--k-t-micro); font-weight:650; min-height:var(--k-tap); min-width:0;
}
.botnav a span{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%; }
.botnav a svg{ width:20px; height:20px; }
.botnav a[aria-current="page"]{ color:var(--k-ink-1); background:var(--k-teal-wash); }
.botnav a[aria-current="page"] svg{ color:var(--k-teal-bright); }
[data-theme="light"] .botnav a[aria-current="page"] svg{ color:var(--k-teal); }

/* More. Every page's bottom nav ends in it, so it is declared once here rather
   than on the one page that happened to have it. It is a button, not a link,
   because it opens a sheet. */
.botnav-more{
  flex:1 1 0; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:3px; border-radius:var(--k-r2); color:var(--k-ink-3);
  font-size:var(--k-t-micro); font-weight:650; min-height:var(--k-tap); min-width:0;
  background:none; border:0; cursor:pointer; font-family:inherit;
}
.botnav-more span{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%; }
.botnav-more svg{ width:20px; height:20px; }
.botnav-more:hover{ color:var(--k-ink-1); }

/* THE MORE SHEET — the whole rail, on a phone.
   Below 1080 the rail is out of the flow. Without this, most of a person's
   destinations have no route at all on the device they actually hold. */
.moresheet{ position:fixed; inset:0; z-index:var(--k-z-drawer, 90); }
.moresheet[hidden]{ display:none; }
.moresheet-scrim{
  position:absolute; inset:0; border:0; padding:0; cursor:pointer;
  background:var(--k-scrim, rgba(0,0,0,.55)); opacity:0; transition:opacity .18s ease;
}
.moresheet[data-open="1"] .moresheet-scrim{ opacity:1; }
.moresheet-panel{
  position:absolute; left:0; right:0; bottom:0; max-height:80dvh; overflow:auto;
  padding:var(--k-3) var(--k-4) calc(var(--k-4) + env(safe-area-inset-bottom));
  background:var(--k-surface-1); border-top:1px solid var(--k-line-strong);
  border-radius:var(--k-r3, 16px) var(--k-r3, 16px) 0 0;
  transform:translateY(12px); opacity:0; transition:transform .2s ease, opacity .2s ease;
}
.moresheet[data-open="1"] .moresheet-panel{ transform:none; opacity:1; }
.moresheet-hd{ display:flex; align-items:center; gap:var(--k-3); margin-bottom:var(--k-4); }
.moresheet-hd h3{ flex:1 1 auto; min-width:0; margin:0; font-size:var(--k-t-h3, 1rem); }
@media (prefers-reduced-motion: reduce){
  .moresheet-scrim, .moresheet-panel{ transition:none; }
}

/* The grouped list used by the More sheet and by any page drawer that shows
   navigation. Rows are 48 px because they are tapped with a thumb. */
.dnav{ display:flex; flex-direction:column; gap:var(--k-5); }
.dnav-grp{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.dnav-grp .eyebrow{ margin:0 0 var(--k-1); }
.dnav a{
  display:flex; align-items:center; gap:var(--k-3); min-height:48px;
  padding:0 var(--k-3); border-radius:var(--k-r2);
  color:var(--k-ink-2); text-decoration:none; font-size:var(--k-t-small); min-width:0;
}
.dnav a svg{ flex:none; color:var(--k-ink-3); }
.dnav a:hover{ background:var(--k-surface-3); color:var(--k-ink-1); }
.dnav a[aria-current="page"]{
  background:var(--k-surface-3); color:var(--k-ink-1); font-weight:600;
  box-shadow:inset 2px 0 0 var(--k-teal-bright);
}
[data-theme="light"] .dnav a[aria-current="page"]{ box-shadow:inset 2px 0 0 var(--k-teal); }
.dnav a > span:not(.count){ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dnav .dnav-lab{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dnav .count{
  flex:none; font-size:var(--k-t-micro); color:var(--k-ink-3);
  font-variant-numeric:tabular-nums lining-nums;
}

/* WHERE YOU ARE, when the topbar has hidden its links.
   Below 1080 .nav a is hidden and the section name went with it, so the only
   thing naming the current page was the browser tab. The static label takes
   the same grid cell. */
.nav-here{ display:none; align-items:center; gap:8px; min-width:0; color:var(--k-ink-1); }
.nav-here svg{ width:16px; height:16px; flex:none; color:var(--k-teal-bright); }
[data-theme="light"] .nav-here svg{ color:var(--k-teal); }
.nav-here b{
  font-size:var(--k-t-small); font-weight:650; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* WHY THIS PAGE IS NOT IN ITS DEFAULT STATE.
   Dropped by K2.deepLink when the launcher opened a panel or set a filter. A
   filtered list that does not say it is filtered is a support call. */
.dl-chip{
  display:flex; align-items:center; gap:var(--k-3); flex-wrap:wrap;
  margin-bottom:var(--k-4); padding:10px var(--k-3);
  border:1px solid var(--k-line-strong); border-left:3px solid var(--k-teal-bright);
  border-radius:var(--k-r2); background:var(--k-surface-2);
  font-size:var(--k-t-small); color:var(--k-ink-2);
}
[data-theme="light"] .dl-chip{ border-left-color:var(--k-teal); }
.dl-chip > span{ flex:1 1 auto; min-width:0; }
.dl-chip .btn{ flex:none; }
@media print{ .dl-chip{ display:none; } }

/* In ไทย and 中文 the hero buttons grow, and the fixed FAB landed on top of
   "Open the drawer": a real action physically covered by a floating control,
   in the two non English languages only. Any row of actions therefore
   reserves the FAB's corner while the FAB is on screen. */
.fab{
  position:fixed; right:16px; bottom:calc(var(--k-botnav) + 14px); z-index:var(--k-z-fab);
  width:56px; height:56px; border-radius:50%; display:none; place-content:center;
  background:var(--k-action-bg); color:var(--k-action-ink);
  box-shadow:0 12px 28px -8px rgba(0,0,0,.6);
}
@media (max-width:1080px){
  .hero-acts{ padding-bottom:var(--k-2); }
  .hero-acts::after{ content:""; display:block; flex:1 0 88px; height:1px; }
}
.fab svg{ width:24px; height:24px; }


/* ════════════════════════════════════════════════════════════════════════════
   08  LANGUAGE AND THEME CONTROLS
   Three chips at 720 and up. Below that, one 44 px button and a bottom sheet,
   because three chips is exactly what broke: 中文 wrapped into two stacked
   glyphs and shoved the avatar off the layout.
   ════════════════════════════════════════════════════════════════════════════ */
/* The language switcher is an INSTANCE of .seg, not a second segmented
   control with its own anatomy. Everything it needs beyond .seg is the
   narrower cell, because EN, ไทย and 中文 are short. */
.langsw{ padding-inline:3px; }
.langsw button{ min-width:var(--k-tap); padding-inline:10px; font-size:var(--k-t-label); }
.btn.lang-one{ display:none; }
.lang-one .lang-code{ font-size:var(--k-t-label); font-weight:700; }

.langsheet{
  position:fixed; left:0; right:0; bottom:0; z-index:var(--k-z-drawer);
  background:var(--k-surface-1); border-top:1px solid var(--k-line-strong);
  border-radius:var(--k-r4) var(--k-r4) 0 0; box-shadow:var(--k-sh-3);
  padding:var(--k-3) var(--k-3) max(var(--k-4), env(safe-area-inset-bottom));
  transform:translateY(102%); visibility:hidden;
  transition:transform var(--k-d3) var(--k-ease-out), visibility 0s linear var(--k-d3);
}
.langsheet[data-open="1"]{ transform:none; visibility:visible; transition:transform var(--k-d3) var(--k-ease-out); }
@media (min-width:721px){ .langsheet{ display:none; } }
.langsheet button{
  display:flex; align-items:center; gap:var(--k-3); width:100%; min-height:48px;
  padding:0 var(--k-3); border-radius:var(--k-r2); text-align:left; color:var(--k-ink-1);
  font-size:var(--k-t-body);
}
.langsheet button:hover{ background:var(--k-surface-3); }
.langsheet button[aria-selected="true"]{ background:var(--k-teal-wash); font-weight:650; }
.langsheet button em{ font-style:normal; color:var(--k-ink-2); font-size:var(--k-t-small); margin-left:auto; }


/* ════════════════════════════════════════════════════════════════════════════
   09  SECTION CADENCE   (graft: Atelier, BRIEF 2.1.3)
   Eyebrow, rule, heading, lede. It is what gives a 4,000 px page a spine.
   ════════════════════════════════════════════════════════════════════════════ */
.sec{ margin-bottom:var(--k-7); }
.sec-hd{ margin:0 0 var(--k-4); min-width:0; }
.eyebrow{
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3);
  display:flex; align-items:center; gap:9px; margin:0 0 7px; min-width:0;
}
.selvedge{
  width:34px; height:2px; border-radius:2px; flex:none;
  background:linear-gradient(90deg,var(--k-teal-bright),var(--k-gold));
}
.sec-rule{ border:0; border-top:1px solid var(--k-line); margin:0 0 var(--k-3); }
.sec-hd h2{
  margin:0; font-size:var(--k-t-h2); font-weight:640; letter-spacing:var(--k-tr-tight);
  line-height:var(--k-lh-snug);
}
.sec-hd .lede{
  margin:var(--k-2) 0 0; font-size:var(--k-t-small); color:var(--k-ink-2); max-width:64ch;
  /* No last line orphans. A lede that ends on one word reads as a mistake. */
  text-wrap:pretty;
}
.sec-bd{ min-width:0; }

/* Progressive disclosure. A <details> is native, keyboard accessible for free
   and survives JS failing. The open attribute is set once from the container
   width and after that it belongs to the user. */
details.sec > summary{
  list-style:none; cursor:pointer; display:block; min-height:var(--k-tap);
  position:relative; padding-right:var(--k-6);
}
details.sec > summary::-webkit-details-marker{ display:none; }
/* The marker is drawn at EVERY width. It used to be display:none above 720 px
   while the summary still carried cursor:pointer and still toggled, so one
   click on a heading — or one stray click while selecting text — deleted the
   whole body of the section with no marker, no state cue and no visible way
   back. The summary already reserves the space for it at every width
   (padding-right below), which is the clearest evidence it was meant to show. */
.sec-mark{
  position:absolute; right:0; top:2px; flex:none; color:var(--k-ink-3);
  width:var(--k-tap); height:var(--k-tap); display:grid; place-content:center;
  transition:transform var(--k-d2) var(--k-ease);
}
details.sec > summary:hover .sec-mark{ color:var(--k-ink-1); }
details.sec[open] .sec-mark{ transform:rotate(180deg); }
.sec-count{ margin-left:auto; }

/* 2026-08-18 — a section under tab control is not a disclosure widget any
   more: the .k2-sectabs bar above it is what chooses visibility, so the
   chevron and the pointer cursor would be promising a click that does
   nothing. The heading itself stays — Align Books' own sub-pages keep a
   title at the top of the panel, and removing it would leave the tab label
   as the only place the section's name exists at all. */
details.sec.k2-tabbed > summary.sec-hd{ cursor:default; padding-right:0; }
details.sec.k2-tabbed > summary.sec-hd .sec-mark{ display:none; }

/* The admin-only escape hatch every page carries now: never a wall of rules
   text, just one line, never shown to anyone but the signed-in admin. */
.k2-admin-link{
  display:inline-flex; align-items:center; gap:6px; margin-top:var(--k-6);
  font-size:var(--k-t-small); font-weight:600; color:var(--k-ink-2);
  min-height:var(--k-tap); padding:0 2px;
}
.k2-admin-link:hover{ color:var(--k-teal-bright); }
[data-theme="light"] .k2-admin-link:hover{ color:var(--k-teal); }


/* ════════════════════════════════════════════════════════════════════════════
   10  HERO
   Mandatory on every page. It states the situation in plain language and
   offers the next action, before it decorates anything.
   ════════════════════════════════════════════════════════════════════════════ */
.hero{
  position:relative; border-radius:var(--k-r4); overflow:hidden;
  border:1px solid var(--k-line); box-shadow:var(--k-sh-3);
  background-color:var(--k-bg);
  background-image:linear-gradient(160deg,var(--k-surface-2),var(--k-bg) 70%);
  min-height:496px; display:flex; flex-direction:column; justify-content:flex-end;
  isolation:isolate;
}
[data-theme="light"] .hero{ background-image:linear-gradient(160deg,var(--k-surface-1),var(--k-void) 78%); }
.hero-canvas,.hero-fallback{ position:absolute; inset:0; width:100%; height:100%; z-index:0; }
.hero-canvas{ opacity:0; transition:opacity var(--k-d4) var(--k-ease); }
.hero.gl-on .hero-canvas{ opacity:1; }
.hero.gl-on .hero-fallback{ opacity:0; transition:opacity var(--k-d4) var(--k-ease); }
/* Two scrims: one so the copy sits on ink, one so the weave keeps the right
   third of the hero to itself. */
.hero::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(100deg, rgba(5,13,14,.94) 0%, rgba(5,13,14,.80) 34%, rgba(5,13,14,.20) 62%, transparent 88%),
    linear-gradient(to top, var(--k-bg) 1%, rgba(5,13,14,.62) 26%, rgba(5,13,14,.06) 58%, transparent 82%);
}
[data-theme="light"] .hero::after{
  background:
    linear-gradient(100deg, rgba(255,255,255,.94) 0%, rgba(252,254,253,.82) 34%, rgba(252,254,253,.22) 62%, transparent 88%),
    linear-gradient(to top, #EDF2F0 1%, rgba(237,242,240,.72) 26%, rgba(237,242,240,.12) 58%, transparent 82%);
}
.hero-body{ position:relative; z-index:2; padding:var(--k-7) var(--k-7) var(--k-6); max-width:840px; min-width:0; }
.hero-kicker{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-2); margin-bottom:var(--k-3); }
/* text-wrap:balance so the most important line in the product does not orphan
   the numeral: "Good morning Marc. 3" / "things need you before" read for a
   beat as a version number. The copy also carries a no break space between a
   count and its noun, which is the other half of the same fix. */
.hero h1{
  margin:0; font-size:var(--k-t-h1); font-weight:640;
  letter-spacing:var(--k-tr-display); line-height:var(--k-lh-tight); max-width:19ch;
  text-wrap:balance;
}
/* A hero carrying two lines of copy and no figures does not need 496 px. It
   drops to the same 420 px floor the phone uses rather than opening with
   400 px of empty panel above the sentence. */
.hero.short{ min-height:420px; }
.hero .lede{
  margin:var(--k-3) 0 0; font-size:var(--k-t-lede); color:var(--k-ink-2);
  max-width:56ch; line-height:1.5; text-wrap:pretty;
}
/* THE FIGURE GROUP IS A TWO ROW GRID, NOT A BOTTOM ALIGNED FLEX ROW.
   As a flex row it aligned on the value, and because the display figure is
   56 px and the two h2 figures are 28 px, the three captions landed on two
   baselines 17 px apart. A grid puts every caption in row 1 and every value
   in row 2, so the captions share a line whatever size the figures are. */
.hero-figs{
  display:grid; grid-auto-flow:column; grid-auto-columns:max-content;
  grid-template-rows:auto auto; justify-content:start;
  column-gap:var(--k-7); row-gap:6px; margin-top:var(--k-6);
}
.hero-fig{ display:contents; }
.hero-fig .lab{
  grid-row:1; align-self:end;
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; max-width:100%;
}
.hero-fig .val{
  grid-row:2; align-self:last baseline;
  font-size:var(--k-t-display); font-weight:600; letter-spacing:var(--k-tr-display);
  line-height:1; min-width:0; max-width:100%;
}
/* The unit slot carries a unit, never a prose fragment. It gets one gap on
   the 4 px scale so 6 containers does not glue itself to the 6. */
.hero-fig .val small{
  font-size:var(--k-t-h3); font-weight:600; color:var(--k-ink-2);
  letter-spacing:0; margin-inline-start:var(--k-2);
}
.hero-fig.sm .val{ font-size:var(--k-t-h2); }
.hero-acts{ display:flex; flex-wrap:wrap; gap:var(--k-3); margin-top:var(--k-6); }
.glbadge{
  position:absolute; z-index:3; right:var(--k-4); top:var(--k-4);
  display:flex; align-items:center; gap:7px; min-height:26px; padding:0 11px;
  border-radius:var(--k-rpill); font-size:var(--k-t-micro); font-weight:650;
  letter-spacing:.06em; text-transform:uppercase; color:var(--k-ink-2);
  border:1px solid var(--k-line); background:var(--k-glass-lo);
  -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
}
.glbadge svg{ width:12px; height:12px; flex:none; }
/* An author display beats the user agent's [hidden] rule, so hidden has to be
   said again. The badge is a fallback badge; when the scene runs it is gone. */
.glbadge[hidden]{ display:none; }

/* Scene band — the shipping lane, and any other 174 px substrate strip. */
.band{
  position:relative; height:174px; border-radius:var(--k-r3); overflow:hidden;
  border:1px solid var(--k-line); background:var(--k-surface-1); box-shadow:var(--k-sh-2);
}
[data-theme="light"] .band{ background:var(--k-surface-2); }
.band-canvas,.band-fallback{ position:absolute; inset:0; width:100%; height:100%; }
.band-canvas{ opacity:0; transition:opacity var(--k-d4) var(--k-ease); }
.band.gl-on .band-canvas{ opacity:1; }
.band.gl-on .band-fallback{ opacity:0; transition:opacity var(--k-d4) var(--k-ease); }
.band-ovl{
  position:absolute; inset:0; display:flex; align-items:center; gap:var(--k-5);
  padding-inline:var(--k-5); pointer-events:none;
  background:linear-gradient(90deg, var(--k-surface-1) 0%, rgba(11,26,27,.86) 16%, rgba(11,26,27,.30) 34%, transparent 48%);
}
[data-theme="light"] .band-ovl{
  background:linear-gradient(90deg, var(--k-surface-2) 0%, rgba(246,248,247,.88) 16%, rgba(246,248,247,.34) 34%, transparent 48%);
}
.band-lab{ flex:none; min-width:0; }
.band-lab .t{
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3);
}
.band-lab .v{ font-size:var(--k-t-h2); font-weight:600; letter-spacing:var(--k-tr-tight); line-height:1.2; }


/* ════════════════════════════════════════════════════════════════════════════
   11  KPI TILES
   The five tile row is what beats the AlignBooks reference. Three encodings
   on every delta: an arrow, a sign and a colour. Never colour alone.
   ════════════════════════════════════════════════════════════════════════════ */
.kpis{
  display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:var(--k-3);
  margin-top:calc(var(--k-6) * -1); position:relative; z-index:5;
}
.kpi{
  padding:var(--k-4) var(--k-4) var(--k-3);
  display:flex; flex-direction:column; gap:var(--k-2); min-width:0;
  container-type:inline-size; container-name:tile;
}
.kpi .top{ display:flex; align-items:center; gap:8px; min-width:0; }
.kpi .ic{
  width:26px; height:26px; border-radius:8px; display:grid; place-items:center; flex:none;
  background:var(--k-teal-wash); color:var(--k-teal-bright); border:1px solid var(--k-line);
}
[data-theme="light"] .kpi .ic{ color:var(--k-teal); }
.kpi .ic svg{ width:14px; height:14px; }
/* A label wraps to two lines rather than truncating. "Need you tod…" on the
   amber tile is the single worst place in the product to lose a word, and
   §4.3 already requires every label slot to survive two lines because one
   English word is often three in Thai. */
.kpi .lab{
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3);
  min-width:0; overflow-wrap:anywhere; line-height:1.25;
  /* Two lines of room, always. A one line label beside a two line one put the
     two values on different baselines, and a row of figures that does not
     line up is not a row. */
  min-height:2.5em;
}
.kpi .top{ align-items:flex-start; }
.kpi .ic,.kpi .cur{ margin-top:1px; }
.kpi .cur{
  margin-left:auto; flex:none; font-size:var(--k-t-micro); letter-spacing:.12em;
  color:var(--k-ink-3); border:1px solid var(--k-line); border-radius:var(--k-r1); padding:0 5px;
}
.kpi .val{
  font-size:var(--k-t-h2); font-weight:620; letter-spacing:var(--k-tr-display); line-height:1.1;
}
.kpi .val .u{ font-size:var(--k-t-small); color:var(--k-ink-3); font-weight:600; margin-left:3px; letter-spacing:0; }
.kpi .bot{ display:flex; align-items:flex-end; gap:var(--k-3); margin-top:auto; min-width:0; flex-wrap:wrap; }
/* The figure never breaks. "+12.4%" wrapping to "+12.4" and "%" is a number
   split across two lines, which is the same class of mistake as ellipsising
   one. The BASIS may wrap, because it is prose. */
.kpi .delta{
  display:inline-flex; align-items:baseline; flex-wrap:wrap; gap:2px 4px;
  font-size:var(--k-t-label); font-weight:650; color:var(--k-ink-1); min-width:0;
}
.kpi .delta > span{ white-space:nowrap; flex:none; }
.kpi .delta svg{ align-self:center; }
.kpi .delta svg{ width:12px; height:12px; flex:none; }
/* THE ARROW ENCODES DIRECTION. THE COLOUR ENCODES WHETHER IT IS GOOD NEWS.
   These used to be the same thing, so NEED YOU TODAY going from one to two
   rendered "up +1 since 09:00" in green with an up arrow: an increase in the
   count of things that need you, painted as healthy. And IN WAREHOUSE -4.2%
   was painted crit red, although a fall in stock is not automatically bad.
   Colour is now driven by polarity x direction, and `neutral` wears ink so a
   number with no target is not scored at all. §6.4. */
.kpi .delta[data-tone="good"] svg{ color:var(--k-good); }
.kpi .delta[data-tone="bad"] svg{ color:var(--k-crit); }
.kpi .delta[data-tone="neutral"] svg{ color:var(--k-ink-3); }
.kpi .delta[data-tone="neutral"]{ color:var(--k-ink-2); }
.kpi .delta em{ font-style:normal; color:var(--k-ink-3); font-weight:400; font-size:var(--k-t-micro); }
/* The basis of a tile that has no delta: the denominator the value came out
   of. Same size and colour as a basis, because it does the same job. */
.kpi .note{ color:var(--k-ink-3); font-weight:400; font-size:var(--k-t-micro); min-width:0; line-height:1.35; }
.kpi .spark{ margin-left:auto; flex:none; }
.kpi.act{ border-color:var(--k-action-ring); }
.kpi.act .ic{ background:var(--k-gold-wash); color:var(--k-gold-text); border-color:var(--k-action-ring); }

/* A tile decides its own layout from its own width, not the screen's. */
@container tile (max-width: 220px){
  .kpi .spark{ display:none; }
  .kpi .bot{ flex-direction:column; align-items:flex-start; gap:var(--k-1); }
  /* The currency chip is the last thing that should be squeezing a label:
     with it on the same line, IN WAREHOUSE broke as "IN WAREHO / USE" at
     390 px. It drops under the icon and the label gets the whole width. */
  .kpi .top{ flex-wrap:wrap; }
  .kpi .lab{ flex:1 1 100%; order:3; }
  .kpi .cur{ margin-left:auto; }
}


/* ════════════════════════════════════════════════════════════════════════════
   12  PANEL AND CARD
   .panel is the glass primitive. The luminous top hairline is the direction's
   whole trick: a panel catching light rather than a box with a border.
   ════════════════════════════════════════════════════════════════════════════ */
.panel{
  position:relative;
  background:var(--k-glass);
  -webkit-backdrop-filter:blur(var(--k-blur)) saturate(140%);
  backdrop-filter:blur(var(--k-blur)) saturate(140%);
  border:1px solid var(--k-line);
  border-radius:var(--k-r3);
  box-shadow:var(--k-sh-2);
  min-width:0;
  container-type:inline-size; container-name:panel;
}
/* A KPI TILE IS ALSO A PANEL, AND THE PANEL RULE CAME SECOND.
   Both rules set container-name at the same specificity, so every `.kpi`
   ended up named `panel` and the `@container tile` block below it had never
   matched anywhere in the product: at 390 the label kept sharing its line
   with the icon and the currency chip and broke as PURCHAS / E VALUE inside
   62 px. Named again, more specifically, after the rule that overrode it. */
.panel.kpi{ container-name:tile; }
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .panel{ background:var(--k-surface-2); }
}
.panel::after{
  content:""; position:absolute; left:14px; right:14px; top:0; height:1px;
  background:linear-gradient(90deg,transparent,var(--k-edge),transparent);
  pointer-events:none; border-radius:1px;
}
/* LIGHT IS THE SAME SYSTEM, NOT A FLATTER ONE.
   The luminous top edge is white, and white on a white panel is nothing, so
   in light the panel floats on a real shadow and a top border instead. The
   canvas is also pushed a full step away from surface-1 so a panel has
   something to float over. Dark keeps the edge; light keeps the depth. */
[data-theme="light"] .panel{
  border-top-color:var(--k-line-strong);
  box-shadow:var(--k-sh-2), 0 1px 0 rgba(255,255,255,.9) inset;
}
[data-theme="light"] .panel::after{ display:none; }
[data-theme="light"] .hero{
  border-color:var(--k-line-strong);
  box-shadow:var(--k-sh-3);
}
/* THE HEADER SLOT IS FIXED, NOT NEGOTIATED BY WRAPPING.
   As a wrapping flex row the Chart | Table control landed in three different
   places across one grid: inline right on Turnover, its own line left aligned
   on Small multiples, a second line right aligned on Container pipeline. A
   three column grid puts the heading, the sub caption and the control in the
   same slot on every panel, whatever the caption length. */
.panel-hd{
  display:grid; grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center; gap:var(--k-2) var(--k-3);
  padding:var(--k-4) var(--k-5); border-bottom:1px solid var(--k-line-soft); min-width:0;
}
.panel-hd > h2,.panel-hd > h3{ grid-column:1; }
.panel-hd > .sub{ grid-column:2; }
.panel-hd > .grow{ display:none; }
.panel-hd > .seg,.panel-hd > .btn,.panel-hd > .pill,.panel-hd > .row{
  grid-column:3; grid-row:1; justify-self:end;
}
/* Below 420 px of panel the caption and the control each take a full row,
   because three tracks in 400 px is a squeeze and not a layout. */
@container panel (max-width:420px){
  .panel-hd{ grid-template-columns:minmax(0,1fr); }
  .panel-hd > h2,.panel-hd > h3,.panel-hd > .sub{ grid-column:1; }
  .panel-hd > .seg,.panel-hd > .btn,.panel-hd > .pill,.panel-hd > .row{
    grid-column:1; grid-row:auto; justify-self:start;
  }
}
.panel-hd h2,.panel-hd h3{
  margin:0; font-size:var(--k-t-small); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-2); min-width:0;
}
.panel-hd .sub{
  font-size:var(--k-t-micro); color:var(--k-ink-3); letter-spacing:.02em;
  text-transform:none; font-weight:400; min-width:0;
}
.panel-bd{ padding:var(--k-5); min-width:0; }
.panel-bd.tight{ padding:var(--k-4); }
.panel-bd.flush{ padding:0; }
.panel-ft{
  padding:var(--k-3) var(--k-5); border-top:1px solid var(--k-line-soft);
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-3);
  font-size:var(--k-t-label); color:var(--k-ink-3);
}
.hairline{ height:1px; background:var(--k-line); border:0; margin:var(--k-5) 0; }


/* ════════════════════════════════════════════════════════════════════════════
   13  BUTTONS AND TWO STEP CONFIRM
   Gold is only ever the one thing a human must do on this screen.
   confirm() returns undefined in a sandbox and once broke seven controls, so
   the only confirm mechanism in this system is arm then confirm.
   ════════════════════════════════════════════════════════════════════════════ */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--k-2);
  min-height:38px; min-width:38px; padding:0 var(--k-4); border-radius:var(--k-r2);
  font-size:var(--k-t-small); font-weight:600; letter-spacing:-.005em;
  border:1px solid var(--k-line-strong); background:var(--k-surface-3); color:var(--k-ink-1);
  white-space:nowrap;
  transition:background var(--k-d2) var(--k-ease), border-color var(--k-d2) var(--k-ease),
             transform var(--k-d1) var(--k-ease), box-shadow var(--k-d2) var(--k-ease);
}
/* §6.10 promised "every one has a 44 px minimum hit area" and there was no
   overlay anywhere, so a desktop .btn probed at 37 x 37. The overlay grows
   the target without growing the control, which is the whole point of
   WCAG 2.5.8, and it is pointer-events:none so it cannot eat a sibling's
   click, only extend its own. */
.btn{ position:relative; }
.btn::after{
  content:""; position:absolute; left:50%; top:50%;
  width:max(100%, var(--k-tap)); height:max(100%, var(--k-tap));
  transform:translate(-50%,-50%);
  border-radius:inherit;
}
.btn:hover{ background:var(--k-surface-4); }
.btn:active{ transform:translateY(1px); }
.btn svg{ width:16px; height:16px; flex:none; }
.btn-ghost{ background:transparent; border-color:var(--k-line-ctl); }
.btn-ghost:hover{ background:var(--k-surface-3); }
/* btn-primary was used by seven files and declared by none, so the way out of
   every stub page rendered as plain underlined text. It is the same control as
   btn-action; the two names are kept in step here rather than in seven files. */
.btn-primary{ }
.btn-action,.btn-primary{
  background:var(--k-action-bg); color:var(--k-action-ink); border-color:transparent;
  box-shadow:0 0 0 0 var(--k-action-ring), var(--k-sh-1);
}
.btn-action:hover,.btn-primary:hover{ background:var(--k-action-bg-hover); box-shadow:0 0 0 4px var(--k-action-ring), var(--k-sh-1); }
.btn-quiet{
  min-height:30px; padding:0 var(--k-3); font-size:var(--k-t-label);
  background:transparent; border-color:var(--k-line-ctl); color:var(--k-ink-1);
}
.btn-quiet:hover{ background:var(--k-surface-3); }
.btn-danger{ border-color:var(--k-crit-line); color:var(--k-ink-1); background:var(--k-crit-bg); }
.btn-danger svg{ color:var(--k-crit); }
.btn-danger:hover{ border-color:var(--k-crit); background:var(--k-crit-bg); }
.btn-icon{ width:38px; min-width:38px; padding:0; flex:none; }
.btn[disabled],.btn[aria-disabled="true"]{ opacity:.45; pointer-events:none; }
/* Armed is ONE appearance, whichever variant was armed. A crit wash at .13
   alpha painted over a gold fill left the action ink on a translucent nothing
   and measured 1.37:1, so the label stating the consequence was the least
   readable text on the page at the exact moment it matters most. */
.btn[data-armed="1"],
.btn-action[data-armed="1"],
.btn-danger[data-armed="1"]{
  background:var(--k-surface-3); color:var(--k-ink-1);
  border-color:var(--k-crit); font-weight:700;
  box-shadow:inset 0 0 0 1px var(--k-crit-line);
}

/* FORCED STATE CLASSES.
   A page cannot screenshot :hover, :focus-visible or :active, so the specimen
   drives them from classes and the real pseudo classes point at the same
   declarations. These are the ONLY place .is-* classes exist; a product page
   never sets them. */
.btn:hover,.btn.is-hover{ background:var(--k-surface-4); }
.btn-action:hover,.btn-action.is-hover{ background:var(--k-action-bg-hover); box-shadow:0 0 0 4px var(--k-action-ring), var(--k-sh-1); }
.btn-ghost:hover,.btn-ghost.is-hover{ background:var(--k-surface-3); }
.btn-quiet:hover,.btn-quiet.is-hover{ background:var(--k-surface-3); }
.btn-danger:hover,.btn-danger.is-hover{ border-color:var(--k-crit); background:var(--k-crit-bg); }
.btn.is-active{ transform:translateY(1px); background:var(--k-surface-3); }
.btn-action.is-active,.btn-action:active{ background:var(--k-action-bg-active); }
.btn.is-focus{
  outline:2px solid var(--k-focus); outline-offset:2px;
  box-shadow:0 0 0 2px var(--k-focus-halo);
}
.matrix-scroll{
  overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain; max-width:100%;
}
.matrix{ width:100%; min-width:640px; font-size:var(--k-t-small); }
.matrix th{
  text-align:left; padding:10px var(--k-3); color:var(--k-ink-3);
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  border-bottom:1px solid var(--k-line-strong); white-space:nowrap;
}
.matrix th[scope="row"]{ text-transform:none; letter-spacing:0; color:var(--k-ink-2); }
.matrix td{ padding:var(--k-3); border-bottom:1px solid var(--k-line-soft); vertical-align:middle; }
.matrix code{ font-family:var(--k-mono); font-size:var(--k-t-micro); }
/* Frozen at half, so a still frame shows what the drain bar looks like. */
.arm-bar.frozen i{ animation:none; transform:scaleX(.5); }

.arm-note{ display:inline-flex; align-items:center; gap:6px; font-size:var(--k-t-micro); color:var(--k-ink-2); }
/* The drain bar is painted inside the armed button, so it sits under the
   thumb on the confirming tap. It is decoration and must never be the hit
   target: pointer-events:none keeps the second tap on the button itself.
   Shared on purpose — every two step control in the system embeds this bar. */
.arm-bar{ width:44px; height:3px; border-radius:2px; background:var(--k-line-strong); overflow:hidden; flex:none; pointer-events:none; }
.arm-bar i{ display:block; height:100%; background:var(--k-crit); transform-origin:left; animation:k-armDrain 4s linear both; }
@keyframes k-armDrain{ from{ transform:scaleX(1); } to{ transform:scaleX(0); } }

.kbd{
  font-family:var(--k-mono); font-size:var(--k-t-micro); padding:2px 6px;
  border-radius:var(--k-r1); border:1px solid var(--k-line);
  background:var(--k-surface-3); color:var(--k-ink-2);
}


/* ════════════════════════════════════════════════════════════════════════════
   14  PILLS AND BADGES
   A badge answers "do I need to care". The note beside it answers "why".
   Never a paragraph inside a pill. Never colour alone: always dot or icon plus
   a word, and the word is in ink so it is AAA in both themes.
   ════════════════════════════════════════════════════════════════════════════ */
.pill{
  display:inline-flex; align-items:center; gap:6px; min-height:22px; padding:0 9px;
  border-radius:var(--k-rpill); font-size:var(--k-t-micro); font-weight:650;
  letter-spacing:.02em; white-space:nowrap; color:var(--k-ink-1);
  border:1px solid var(--k-mute-line); background:var(--k-mute-bg);
}
.pill .dot{ width:6px; height:6px; border-radius:50%; background:var(--k-mute); flex:none; }
.pill svg{ width:12px; height:12px; flex:none; color:var(--k-mute); }
/* A dot is FILLED, an icon is STROKED. These used to set `background` on both,
   which turned every icon-bearing pill into a filled coloured square with a
   word next to it: the gold "Needs you" pill read as a solid amber block, not
   as a mark. Background on the dot, colour on the glyph. */
.pill.good{ border-color:var(--k-good-line); background:var(--k-good-bg); }
.pill.good .dot{ background:var(--k-good); }
.pill.good svg{ color:var(--k-good); }
.pill.warn{ border-color:var(--k-warn-line); background:var(--k-warn-bg); }
.pill.warn .dot{ background:var(--k-warn); }
.pill.warn svg{ color:var(--k-warn); }
.pill.crit{ border-color:var(--k-crit-line); background:var(--k-crit-bg); }
.pill.crit .dot{ background:var(--k-crit); }
.pill.crit svg{ color:var(--k-crit); }
.pill.info{ border-color:var(--k-info-line); background:var(--k-info-bg); }
.pill.info .dot{ background:var(--k-info); }
.pill.info svg{ color:var(--k-info); }
.pill.gold{ border-color:var(--k-action-ring); background:var(--k-gold-wash); }
.pill.gold .dot{ background:var(--k-gold-text); }
.pill.gold svg{ color:var(--k-gold-text); }
.pill.plain{ background:transparent; border-color:var(--k-line); }


/* ════════════════════════════════════════════════════════════════════════════
   15  FORM CONTROLS
   Labels sit above the control, never inside it as a placeholder: a
   placeholder disappears on the first keystroke, which is exactly when a
   warehouse user looks up. Required is the word "required", never an asterisk,
   which means nothing in Thai.
   ════════════════════════════════════════════════════════════════════════════ */
.form-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--k-5) var(--k-4); }
.field{ display:flex; flex-direction:column; gap:6px; min-width:0; }
.field.wide{ grid-column:1 / -1; }
.field > .seg{ align-self:flex-start; }
.field > label,.field .lbl{
  font-size:var(--k-t-label); font-weight:650; color:var(--k-ink-2);
  display:flex; align-items:flex-start; gap:6px; min-height:18px; flex-wrap:wrap;
}
.field .req{ color:var(--k-gold-text); font-size:var(--k-t-micro); font-weight:650; }
.field .hint{ font-size:var(--k-t-micro); color:var(--k-ink-3); }

.ctl{
  display:flex; align-items:center; gap:8px; min-height:42px; padding:0 12px;
  border-radius:var(--k-r2); border:1px solid var(--k-line-ctl); background:var(--k-surface-2);
  min-width:0;
  transition:border-color var(--k-d2) var(--k-ease), box-shadow var(--k-d2) var(--k-ease),
             background var(--k-d2) var(--k-ease);
}
.ctl:focus-within{ border-color:var(--k-focus); box-shadow:0 0 0 3px var(--k-teal-wash); background:var(--k-surface-1); }
.ctl input,.ctl select,.ctl textarea{
  border:0; background:none; outline:none; width:100%; min-width:0;
  font-size:var(--k-t-body); color:var(--k-ink-1); padding:0;
}
/* §6.8: field-sizing:content, min 3 rows, max 12 then scroll. The UA resize
   grabber is off because the field sizes itself. */
.ctl textarea{
  padding-block:10px; resize:none;
  field-sizing:content;
  min-height:calc(3lh + 20px); max-height:calc(12lh + 20px); overflow-y:auto;
}
@supports not (field-sizing:content){
  .ctl textarea{ resize:vertical; min-height:76px; }
}
.ctl input::placeholder,.ctl textarea::placeholder{ color:var(--k-ink-3); }
/* THE MISSING CHEVRON.
   appearance:none removed the native arrow and reserved 18 px for a
   replacement that was never drawn, so every dropdown in the system looked
   like a text field, while the datalist input beside it on the same delivery
   form kept its native arrow and looked like the only dropdown on the page.
   The mark is drawn here, once, in the space that was already reserved. */
.ctl select{ appearance:none; cursor:pointer; padding-right:22px; }
.ctl:has(select){ position:relative; }
.ctl:has(select)::after{
  content:""; position:absolute; right:12px; top:50%; width:8px; height:8px;
  transform:translateY(-65%) rotate(45deg);
  border-right:1.6px solid var(--k-ink-3); border-bottom:1.6px solid var(--k-ink-3);
  border-bottom-right-radius:2px;
  pointer-events:none;
}
.ctl:has(select):focus-within::after{ border-color:var(--k-ink-1); }
/* A datalist input is not a dropdown and must not be dressed as one, but it
   must not be the ONLY thing on the form wearing a native arrow either. */
.ctl input[list]{ appearance:none; }
@supports not selector(:has(*)){
  /* No :has: keep the native arrow rather than ship a field that lies. */
  .ctl select{ appearance:auto; padding-right:2px; }
}
.ctl .suffix{
  font-size:var(--k-t-label); font-weight:650; color:var(--k-ink-3); flex:none;
  padding-left:9px; border-left:1px solid var(--k-line); letter-spacing:.06em;
}
.ctl .prefix{ font-size:var(--k-t-label); font-weight:650; color:var(--k-ink-3); flex:none; }
.ctl > svg{ width:15px; height:15px; color:var(--k-ink-3); flex:none; }
.ctl.num input{ text-align:right; font-variant-numeric:tabular-nums; }
.ctl.err{ border-color:var(--k-crit); background:var(--k-surface-2); box-shadow:inset 0 0 0 1px var(--k-crit-line); }
.ctl.err:focus-within{ box-shadow:0 0 0 3px var(--k-crit-bg); }
.ctl.ok{ border-color:var(--k-good); }
.ctl.textarea{ align-items:stretch; }

.err-msg,.ok-msg{
  display:flex; align-items:flex-start; gap:6px;
  font-size:var(--k-t-label); color:var(--k-ink-1); font-weight:600; min-width:0;
}
.err-msg svg{ width:14px; height:14px; flex:none; color:var(--k-crit); margin-top:2px; }
.ok-msg svg{ width:14px; height:14px; flex:none; color:var(--k-good); margin-top:2px; }

.search{
  display:flex; align-items:center; gap:8px; min-height:38px; padding:0 11px;
  border-radius:var(--k-r2); border:1px solid var(--k-line-ctl); background:var(--k-surface-2);
  min-width:0; max-width:100%;
  /* flex-basis 200px is a WIDTH only while the parent is a row. Inside a
     column .field it became a 200 px tall empty box in Online's product
     picker. The basis is set by the toolbars that actually want it. */
  flex:1 1 auto;
}
.toolbar > .search, .panel-hd > .search, .t-flexhd > .search, .filters > .search{ flex-basis:200px; }
.search svg{ width:14px; height:14px; color:var(--k-ink-3); flex:none; }
.search input{ border:0; background:none; outline:none; width:100%; min-width:0; font-size:var(--k-t-small); }
.search input::placeholder{ color:var(--k-ink-3); }
.search:focus-within{ border-color:var(--k-focus); box-shadow:0 0 0 3px var(--k-teal-wash); }

/* Checkbox. 18 px drawn box, a drawn tick and not a font glyph.
   Hit area: 24 px under a pointer, 44 px under a thumb, per §5.7 as amended
   and WCAG 2.5.8, which exempts a pointer target of 24 px.

   The tick used to be position:absolute with no offsets inside a
   place-content:center grid, so it resolved to its STATIC position as the
   second grid item and floated above and to the left of the box in both
   themes. It is now anchored to the centre of the control, and indeterminate
   has its own mark so it can never be mistaken for checked. */
.chk{
  appearance:none; -webkit-appearance:none;
  width:24px; height:24px; flex:none; cursor:pointer;
  display:grid; place-content:center; border:0; background:none; position:relative;
}
.chk::before{
  content:""; width:18px; height:18px; border-radius:5px;
  border:1px solid var(--k-line-ctl); background:var(--k-surface-2);
  transition:background var(--k-d1) var(--k-ease), border-color var(--k-d1) var(--k-ease);
}
.chk::after{
  content:""; position:absolute; left:50%; top:50%;
  width:9px; height:5px;
  border-left:2px solid var(--k-ink-inv); border-bottom:2px solid var(--k-ink-inv);
  transform:translate(-50%,-60%) rotate(-45deg);
  opacity:0;
}
.chk:checked::before,
.chk:indeterminate::before{ background:var(--k-teal); border-color:var(--k-teal); }
.chk:checked::after{ opacity:1; }
/* Indeterminate is a bar, not a tick: "some of these" is not "all of these". */
.chk:indeterminate::after{
  opacity:1; width:9px; height:0; border-left:0;
  transform:translate(-50%,-50%);
}

/* A checkbox and its word are one component, on the 4 px scale. Hand rolling
   this row with inline styles is how fifteen pages diverge. */
.chk-row{ display:flex; align-items:flex-start; flex-wrap:wrap; gap:var(--k-3); min-width:0; }
.chk-label{
  display:flex; align-items:center; gap:var(--k-1); min-width:0;
  font-size:var(--k-t-small); color:var(--k-ink-1); cursor:pointer;
  min-height:var(--k-tap);
}

/* Toggle. Only for a state that takes effect immediately. Anything that needs
   Save is a checkbox. The input itself is 44 px so the hit area is real. */
.toggle{ display:inline-flex; align-items:center; gap:11px; cursor:pointer; min-width:0; }
.toggle input{
  appearance:none; -webkit-appearance:none; position:relative; flex:none;
  width:var(--k-tap); height:var(--k-tap); border:0; background:none; cursor:pointer;
}
.toggle input::before{
  content:""; position:absolute; left:0; top:9px; width:44px; height:26px;
  border-radius:var(--k-rpill); background:var(--k-surface-4);
  border:1px solid var(--k-line-ctl);
  transition:background var(--k-d2) var(--k-ease), border-color var(--k-d2) var(--k-ease);
}
.toggle input::after{
  content:""; position:absolute; left:3px; top:12px; width:20px; height:20px;
  border-radius:50%; background:var(--k-ink-2);
  transition:transform var(--k-d2) var(--k-ease-out), background var(--k-d2) var(--k-ease);
}
.toggle input:checked::before{ background:var(--k-teal); border-color:var(--k-teal); }
.toggle input:checked::after{ transform:translateX(18px); background:var(--k-on-teal); }
.toggle .tx{ font-size:var(--k-t-small); color:var(--k-ink-2); min-width:0; }

/* Segmented. 2 to 4 options, aria-pressed, in a padded rail. */
/* THE ONE SEGMENTED CONTROL. 3 px padded rail, filled active cell,
   aria-pressed, no dividers. Two to four options. Chart | Table, Yard basis
   and the language switcher are all this component. */
.seg,.langsw{
  display:inline-flex; align-items:center; padding:3px; gap:3px; border-radius:var(--k-r2);
  border:1px solid var(--k-line); background:var(--k-surface-2); max-width:100%;
}
.seg button,.langsw button{
  min-height:32px; padding:0 14px; border-radius:7px; white-space:nowrap; flex:none;
  font-size:var(--k-t-small); font-weight:650; color:var(--k-ink-3);
  transition:background var(--k-d2) var(--k-ease), color var(--k-d2) var(--k-ease);
}
.seg button:hover,.langsw button:hover{ color:var(--k-ink-1); }
.seg button[aria-pressed="true"],.langsw button[aria-pressed="true"]{
  background:var(--k-surface-4); color:var(--k-ink-1); box-shadow:var(--k-sh-1);
}
[data-theme="light"] .seg button[aria-pressed="true"],
[data-theme="light"] .langsw button[aria-pressed="true"]{ background:var(--k-surface-1); }
/* The control always sits inline right in a panel header, after .grow, so it
   is in the same place on every panel in a grid. */
.panel-hd > .seg{ margin-left:auto; }


/* ════════════════════════════════════════════════════════════════════════════
   16  PICKER, FILE DROP, IMPORT PREVIEW
   Photos come through k2.js asset(), which fetches with an Authorization
   header and caches a blob URL. A token never appears in an <img src>.
   ════════════════════════════════════════════════════════════════════════════ */
.picker{ position:relative; min-width:0; }
.picker-list{
  margin-top:6px; border:1px solid var(--k-line); border-radius:var(--k-r2);
  background:var(--k-surface-1); max-height:260px; overflow:auto; box-shadow:var(--k-sh-2);
}
.picker-opt{
  display:flex; align-items:center; gap:11px; padding:8px 11px; width:100%;
  min-height:var(--k-tap); text-align:left; border-bottom:1px solid var(--k-line-soft);
  color:var(--k-ink-1); min-width:0;
}
.picker-opt:last-child{ border-bottom:0; }
.picker-opt:hover,.picker-opt[data-hi="1"]{ background:var(--k-surface-3); }
.picker-opt:hover .meta span,.picker-opt[data-hi="1"] .meta span{ color:var(--k-ink-2); }
.thumb{
  width:38px; height:38px; border-radius:9px; flex:none; border:1px solid var(--k-line);
  display:grid; place-content:center; font-size:var(--k-t-micro); font-weight:700;
  letter-spacing:.04em; color:var(--k-mono-ink); overflow:hidden; position:relative;
  background-color:var(--k-mono-bg);
  background-image:linear-gradient(150deg, rgba(169,239,230,.92), rgba(228,183,95,.55));
}
.thumb img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.picker-opt .meta{ min-width:0; flex:1 1 auto; }
.picker-opt .meta b{ display:block; font-size:var(--k-t-small); font-weight:600; }
.picker-opt .meta span{ font-size:var(--k-t-micro); color:var(--k-ink-3); font-family:var(--k-mono); }
.picker-opt .price{ font-size:var(--k-t-small); font-weight:650; flex:none; }
.picked{
  display:flex; align-items:center; gap:11px; padding:8px; border-radius:var(--k-r2);
  border:1px solid var(--k-teal); background:var(--k-teal-wash); min-width:0;
}
.picked .meta{ display:flex; flex-direction:column; min-width:0; line-height:1.3; }
.picked .meta b{ font-size:var(--k-t-small); font-weight:650; }
.picked .meta span{ font-size:var(--k-t-micro); color:var(--k-ink-2); }

.drop{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:9px;
  min-height:132px; border-radius:var(--k-r3); border:1px dashed var(--k-line-ctl);
  background:var(--k-surface-2); text-align:center; padding:var(--k-4); cursor:pointer;
  transition:border-color var(--k-d2) var(--k-ease), background var(--k-d2) var(--k-ease);
}
.drop:hover,.drop[data-over="1"]{ border-color:var(--k-teal); background:var(--k-teal-wash); }
.drop svg{ width:24px; height:24px; color:var(--k-ink-3); }
.drop b{ font-size:var(--k-t-small); font-weight:650; }
.drop span{ font-size:var(--k-t-micro); color:var(--k-ink-3); }
.filerow{
  display:flex; align-items:center; gap:10px; padding:8px 11px; border-radius:var(--k-r2);
  border:1px solid var(--k-line); background:var(--k-surface-2); margin-top:8px;
  font-size:var(--k-t-small); min-width:0;
}
.filerow > svg{ width:15px; height:15px; color:var(--k-ink-3); flex:none; }
.filerow .sz{ font-size:var(--k-t-micro); color:var(--k-ink-3); flex:none; }

/* The dropped row counter. Never a silent drop, and the arithmetic must
   reconcile before the UI renders it: read = imported + dropped + excluded. */
.import-diff{
  border:1px solid var(--k-line-strong); border-radius:var(--k-r2);
  background:var(--k-surface-2); padding:var(--k-4); min-width:0;
}
.import-diff .tally{
  display:flex; flex-wrap:wrap; gap:var(--k-2) var(--k-4);
  font-size:var(--k-t-small); color:var(--k-ink-1); margin-bottom:var(--k-3);
}
.import-diff .tally b{ font-variant-numeric:tabular-nums; }
.import-diff ul{ display:flex; flex-direction:column; gap:6px; margin:0 0 var(--k-3); }
.import-diff li{
  display:flex; gap:var(--k-2); font-size:var(--k-t-label); color:var(--k-ink-2);
  font-family:var(--k-mono); min-width:0;
}
.import-diff li svg{ width:13px; height:13px; flex:none; color:var(--k-crit); margin-top:2px; }


/* ════════════════════════════════════════════════════════════════════════════
   17  TABLE
   The densest and most dangerous component. Every leak, every mis parse and
   every phone failure in this system's history happened here.
   ════════════════════════════════════════════════════════════════════════════ */
.tbl-tools{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-2);
  padding:var(--k-3) var(--k-4); border-bottom:1px solid var(--k-line-soft); min-width:0;
}
.tbl-skel:empty{ display:none; }
.tbl-skel{ padding:var(--k-3) var(--k-4); }
.tbl-scroll{
  overflow:auto; max-height:560px; position:relative; scrollbar-gutter:stable;
  scrollbar-width:thin; scrollbar-color:var(--k-line-strong) transparent;
}
.tbl-scroll::-webkit-scrollbar{ width:10px; height:10px; }
.tbl-scroll::-webkit-scrollbar-thumb{
  background:var(--k-line-strong); border-radius:var(--k-rpill);
  border:3px solid transparent; background-clip:content-box;
}
table.k{ width:100%; min-width:1080px; font-size:var(--k-t-small); }
/* A sticky header MUST be opaque, never glass, or the rows show through. */
table.k thead th{
  position:sticky; top:0; z-index:var(--k-z-sticky-hd);
  background:var(--k-surface-1); text-align:left; white-space:nowrap;
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3);
  padding:10px 9px; border-bottom:1px solid var(--k-line-strong);
}
[data-theme="light"] table.k thead th{ background:var(--k-surface-3); }
table.k thead th.n{ text-align:right; }
table.k thead th button{
  display:inline-flex; align-items:center; gap:5px; min-height:24px;
  color:inherit; font:inherit; letter-spacing:inherit; text-transform:inherit;
}
table.k thead th button:hover{ color:var(--k-ink-1); }
table.k thead th svg{ width:11px; height:11px; opacity:.45; }
table.k thead th[aria-sort] button{ color:var(--k-ink-1); }
table.k thead th[aria-sort] svg{ opacity:1; color:var(--k-teal-bright); }
[data-theme="light"] table.k thead th[aria-sort] svg{ color:var(--k-teal); }

/* 34 px, which is the number §5.7 fixes and the number this section's own
   lede asserts. It measured 41 because a two line cell, a 22 px pill and a
   27 px inline edit control all stacked on 12 px of padding. All three are
   sized to the row now instead of the row being sized to them. */
table.k tbody td{
  padding:4px 9px; height:34px; border-bottom:1px solid var(--k-line-soft); vertical-align:middle;
  color:var(--k-ink-1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
table.k tbody td .pill{ min-height:20px; }
table.k tbody td .inline-edit{ min-height:20px; padding:0 6px; }
/* A number is never ellipsised. An editable yardage cell carries its value,
   its edit affordance and its padding, so it is given the width for all
   three rather than being handed whatever auto layout has left. */
table.k .c-edit{ min-width:112px; }
table.k th.c-edit{ min-width:112px; }
table.k tbody td.n{ text-align:right; font-variant-numeric:tabular-nums; }
table.k tbody td.dim{ color:var(--k-ink-3); }
table.k tbody tr{ background:var(--k-panel-solid); transition:background var(--k-d1) linear; }
/* content-visibility is a scroll optimisation and it is also a rendering lie:
   a row outside the viewport is not painted, so a full page screenshot of it
   is empty. It is switched on only above 50 rows on the page, where the
   scrolling cost is real, and the page turns it on by adding .virtual. */
table.k.virtual tbody tr{ content-visibility:auto; contain-intrinsic-size:auto 34px; }
table.k tbody tr:hover{ background:var(--k-surface-3); }
table.k tbody tr:hover td.dim{ color:var(--k-ink-2); }
table.k tbody tr.zebra{ background:var(--k-zebra); }
table.k tbody tr.zebra:hover{ background:var(--k-surface-3); }
table.k tbody tr[data-sel="1"]{ background:var(--k-sel-row); }
table.k tbody tr[data-sel="1"] td:first-child{ box-shadow:inset 2px 0 0 var(--k-teal-bright); }
[data-theme="light"] table.k tbody tr[data-sel="1"] td:first-child{ box-shadow:inset 2px 0 0 var(--k-teal); }

table.k th.stick-l,table.k td.stick-l{ position:sticky; left:0; z-index:var(--k-z-sticky-col); background:var(--k-surface-2); }
table.k tbody td.stick-l,table.k tbody td.stick-l2,table.k tbody td.stick-r{ background:inherit; }
table.k th.stick-l2,table.k td.stick-l2{ position:sticky; left:44px; z-index:var(--k-z-sticky-col); background:var(--k-surface-2); }
table.k th.stick-r,table.k td.stick-r{
  position:sticky; right:0; z-index:var(--k-z-sticky-col); background:var(--k-surface-2);
  box-shadow:-10px 0 12px -10px rgba(0,0,0,.55);
}
table.k td.stick-l2{ box-shadow:10px 0 12px -10px rgba(0,0,0,.55); }
[data-theme="light"] table.k td.stick-r{ box-shadow:-10px 0 12px -10px rgba(9,38,40,.16); }
[data-theme="light"] table.k td.stick-l2{ box-shadow:10px 0 12px -10px rgba(9,38,40,.16); }
table.k thead th.stick-l,table.k thead th.stick-l2,table.k thead th.stick-r{
  z-index:var(--k-z-sticky-hdcol); background:var(--k-surface-1);
}
[data-theme="light"] table.k thead th.stick-l,
[data-theme="light"] table.k thead th.stick-l2,
[data-theme="light"] table.k thead th.stick-r{ background:var(--k-surface-3); }
table.k tbody tr:hover td.stick-l,table.k tbody tr:hover td.stick-l2,table.k tbody tr:hover td.stick-r{ background:var(--k-surface-3); }
table.k th.stick-l,table.k td.stick-l{ width:44px; padding-left:6px; padding-right:0; }

.mark{ font-family:var(--k-mono); font-size:var(--k-t-label); letter-spacing:.02em; font-weight:600; }
/* A two line cell is what took the desktop row from the 34 px §5.7 specifies
   to a measured 73 px, on the same page whose lede asserts "200 rows in the
   DOM at 34 px". The code and the name sit on ONE line under a pointer and
   stack only in card mode, where the row is a 132 px card anyway. */
.co{ display:flex; align-items:baseline; gap:6px; line-height:1.24; min-width:0; max-width:100%; }
.co b,.co span{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
.co b{ font-weight:550; font-size:var(--k-t-small); flex:none; }
.co span{ font-size:var(--k-t-micro); color:var(--k-ink-3); min-width:0; }
.co span::before{ content:"· "; }

.pair{ display:inline-flex; align-items:baseline; gap:5px; justify-content:flex-end; }
.pair i{ font-style:normal; color:var(--k-ink-3); font-size:var(--k-t-micro); }
/* A field absent from the payload is not permitted or not present. Never 0,
   never blank, and never counted in a total. */
.na{ color:var(--k-ink-3); }

/* ── A VALUE THAT CAME FROM THE TOP OF THE BLOCK ────────────────────────────
   2026-08-17. On a shipping schedule the mark is written once and the lines
   beneath belong to it. The screen shows it on every line, and shows it
   QUIETER, because "this line carries the mark" and "this line belongs to the
   block above" are different facts and only one of them can be edited here.
   A dotted underline rather than a colour alone: the difference has to survive
   a black-and-white print and a reader who does not see colour well. */
.inh{ color:var(--k-ink-2); border-bottom:1px dotted var(--k-line-strong); cursor:help; }
@media print{ .inh{ color:inherit; border-bottom:0; } }

.inline-edit{
  display:inline-flex; align-items:center; gap:6px; min-width:0; max-width:100%;
  min-height:22px; padding:2px 7px; border-radius:var(--k-r1);
  border:1px dashed transparent; cursor:text; color:var(--k-ink-1); text-align:inherit;
  transition:border-color var(--k-d1) var(--k-ease), background var(--k-d1) var(--k-ease);
}
.inline-edit:hover{ border-color:var(--k-line-ctl); background:var(--k-surface-2); }
.inline-edit svg{ width:11px; height:11px; opacity:0; color:var(--k-ink-3); flex:none; }
.inline-edit:hover svg{ opacity:1; }
.inline-edit.editing{ border-style:solid; border-color:var(--k-teal); background:var(--k-surface-1); }
.inline-edit input{ border:0; background:none; outline:none; width:100%; min-width:70px; font-size:var(--k-t-small); }
.inline-edit .v{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.inline-edit.saved{ border-color:var(--k-good); background:var(--k-good-bg); }

/* 24 px drawn, 44 px hit. At 28 it was the tallest thing in the row and set
   the row height on its own; the WCAG 2.5.8 pointer floor is 24. */
.rowmenu{ position:relative; }
.rowmenu > button{
  width:24px; height:24px; border-radius:var(--k-r1); display:grid; place-content:center;
  color:var(--k-ink-3); position:relative;
}
.rowmenu > button::after{
  content:""; position:absolute; left:50%; top:50%;
  width:var(--k-tap); height:var(--k-tap); transform:translate(-50%,-50%);
}
.rowmenu > button:hover{ background:var(--k-surface-4); color:var(--k-ink-1); }
.rowmenu > button svg{ width:16px; height:16px; }
/* THE ACCOUNT MENU'S POSITIONING CONTEXT.
   `.menu` is position:absolute and only v2-home.html ever declared a
   containing block for it. On the other sixteen pages the menu resolved
   against whatever ancestor happened to be positioned, so it opened away from
   the button that opened it — which is why Marc reported that clicking his
   own name and signing out "only worked on some of the pages". A shell rule
   belongs in the shell, not in one page's private stylesheet. */
.usermenu{ position:relative; }

.menu{
  position:absolute; right:0; top:38px; z-index:var(--k-z-menu); min-width:200px; padding:5px;
  background:var(--k-surface-1); border:1px solid var(--k-line-strong); border-radius:var(--k-r2);
  box-shadow:var(--k-sh-3); display:none;
}
.menu[data-open="1"]{ display:block; }
.menu button{
  display:flex; align-items:center; gap:9px; width:100%; min-height:var(--k-tap);
  padding:7px 9px; border-radius:var(--k-r1); font-size:var(--k-t-small);
  color:var(--k-ink-1); text-align:left;
}
.menu button svg{ width:14px; height:14px; opacity:.8; flex:none; }
.menu button:hover{ background:var(--k-surface-3); }
.menu button.danger svg{ color:var(--k-crit); }
.menu hr{ margin:4px 0; border-top-color:var(--k-line-soft); }

.selbar{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-3); padding:9px var(--k-4);
  background:var(--k-sel-row); border-bottom:1px solid var(--k-line); font-size:var(--k-t-small);
}
.tbl-foot{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-3);
  padding:var(--k-3) var(--k-4); border-top:1px solid var(--k-line-soft);
  font-size:var(--k-t-label); color:var(--k-ink-3);
}

/* Column priority. The fold order is a business decision, not an alphabetical
   one: supplier folds first because it is purchase side and the roles most
   often on a narrow screen must never see it anyway. Then note, then item. */
@container main (max-width:1400px){ table.k .c-sup{ display:none; } }
@container main (max-width:1180px){ table.k .c-note{ display:none; } }


/* ════════════════════════════════════════════════════════════════════════════
   18  PAGINATION
   The count comes from meta=filter_count and arrives as a string from
   Postgres, so k2.js coerces it before comparing.
   ════════════════════════════════════════════════════════════════════════════ */
.pager{ display:flex; align-items:center; gap:3px; flex-wrap:wrap; }
.pager button{
  min-width:30px; min-height:30px; padding:0 8px; border-radius:var(--k-r1);
  font-size:var(--k-t-label); font-weight:650; color:var(--k-ink-1);
}
.pager button:hover{ background:var(--k-surface-3); }
.pager button[aria-current="true"]{
  background:var(--k-teal-wash); color:var(--k-ink-1); box-shadow:inset 0 0 0 1px var(--k-line-strong);
}
.pager button[disabled]{ opacity:.4; pointer-events:none; }
.pager button svg{ width:14px; height:14px; }
.pager .gap{ min-width:20px; text-align:center; color:var(--k-ink-3); }


/* ════════════════════════════════════════════════════════════════════════════
   19  TABS
   ════════════════════════════════════════════════════════════════════════════ */
.tabs{
  display:flex; gap:var(--k-1); border-bottom:1px solid var(--k-line);
  overflow-x:auto; scroll-snap-type:x mandatory; scrollbar-width:none;
}
.tabs::-webkit-scrollbar{ display:none; }
.tabs [role="tab"]{
  /* min-height alone is half a target: a two glyph Thai label measured
     40.5 x 48 inside 44 px of padding. WCAG 2.5.8 is both dimensions. */
  min-height:var(--k-tap); min-width:var(--k-tap); padding:0 var(--k-4); white-space:nowrap; flex:none;
  scroll-snap-align:start; color:var(--k-ink-2); font-size:var(--k-t-small); font-weight:600;
  border-bottom:2px solid transparent; margin-bottom:-1px;
}
.tabs [role="tab"]:hover{ color:var(--k-ink-1); }
.tabs [role="tab"][aria-selected="true"]{ color:var(--k-ink-1); font-weight:700; border-bottom-color:var(--k-teal-bright); }
[data-theme="light"] .tabs [role="tab"][aria-selected="true"]{ border-bottom-color:var(--k-teal); }
.tabpanel{ padding-top:var(--k-4); }

/* ── SECTION TABS PIN TO THE TOP ──────────────────────────────────────────
   Marc, 2026-08-19: "the tabs dont open up like align books ... i dont want
   it to be one long scroll between all pages of each tab ... its still not
   visible."

   The bar was mounted correctly and the tabs were genuinely working — one
   section visible at a time, proved by verify-tabs. It just sat BELOW the
   hero, the KPI row and the warning banner, about 1,200 px down the page, so
   on first sight there was no tab bar on screen at all and the page read as
   exactly the long scroll it was built to replace.

   DISCOVERY WAS THE BUG, NOT THE TABS. This is why a passing test and a
   screenshot disagree: every assertion was about content, and the content
   was all there.

   The bar now pins directly under the topbar, so it arrives on screen as
   soon as you scroll and never leaves. `top` matches the topbar's own
   height token, so the two can never drift apart. */
.tabs.k2-sectabs{
  position:sticky;
  top:var(--k-topbar);
  z-index:var(--k-z-sticky-hd);
  /* Opaque, for the same reason the topbar is: a figure scrolling underneath
     must never read through a tab label. */
  background:var(--k-surface-1);
  border-bottom:1px solid var(--k-line-strong);
  scroll-margin-top:var(--k-topbar);
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .tabs.k2-sectabs{
    background:var(--k-topbar-glass);
    -webkit-backdrop-filter:blur(22px) saturate(150%);
    backdrop-filter:blur(22px) saturate(150%);
  }
}
/* A panel scrolled to by a deep link must clear BOTH sticky bars, or its
   first line lands underneath them. */
details.sec.k2-tabbed{ scroll-margin-top:calc(var(--k-topbar) * 2 + var(--k-2)); }


/* ════════════════════════════════════════════════════════════════════════════
   19b  RAIL FLYOUT — the sections behind a page
   ════════════════════════════════════════════════════════════════════════════
   Marc, 2026-08-19: click a rail item and a panel lists what is inside it,
   the way Align Books does. Built in k2.js §railRow/openFlyout. */
.rail-row{ display:flex; align-items:center; gap:2px; }
.rail-row > a{ flex:1 1 auto; min-width:0; }
.rail-fly-btn{
  flex:none; display:grid; place-items:center;
  width:26px; height:26px; margin-inline-end:6px; border-radius:var(--k-r-2);
  color:var(--k-ink-3); opacity:0; transition:opacity .12s ease, color .12s ease;
}
/* Shown on hover, on keyboard focus, and permanently on a touch screen —
   where there is no hover and an invisible control is no control at all. */
.rail-row:hover .rail-fly-btn,
.rail-fly-btn:focus-visible,
.rail-fly-btn[aria-expanded="true"]{ opacity:1; color:var(--k-ink-1); }
@media (hover:none){ .rail-fly-btn{ opacity:1; } }
.rail-fly-btn:hover{ background:var(--k-surface-3); color:var(--k-ink-1); }

.rail-fly{
  position:fixed; z-index:var(--k-z-menu);
  min-width:216px; max-width:290px; padding:var(--k-2);
  background:var(--k-surface-1); border:1px solid var(--k-line-strong);
  border-radius:var(--k-r-3); box-shadow:var(--k-shadow-3, 0 18px 40px rgba(0,0,0,.35));
  display:flex; flex-direction:column; gap:1px;
}
.rail-fly .eyebrow{ margin:2px 8px 6px; }
.rail-fly a{
  display:block; padding:9px 10px; border-radius:var(--k-r-2);
  color:var(--k-ink-1); font-size:var(--k-t-small); font-weight:600;
  min-height:var(--k-tap); line-height:1.5; text-decoration:none;
}
.rail-fly a:hover{ background:var(--k-surface-3); }
.rail-fly a:focus-visible{ background:var(--k-surface-3); outline:2px solid var(--k-teal-bright); outline-offset:-2px; }

/* The rail is not on screen at phone width; the bottom sheet is the menu
   there and it already lists everywhere. Nothing to draw. */
@media (max-width:719px){ .rail-fly-btn{ display:none; } }


/* ════════════════════════════════════════════════════════════════════════════
   20a  THE ACCOUNTS BOOK TREE — the third level of the rail
   ════════════════════════════════════════════════════════════════════════════
   Marc, 2026-08-20: "the left rail should change or open up below Accounts".
   Built in k2.js §booksTree. Two levels of indent, both drawn against ONE
   vertical hairline rather than by padding alone, because five books each
   holding up to eight screens is a list long enough to lose your place in.
   The caret is always visible here — unlike the flyout button, which appears
   on hover — since a tree whose carets hide is a tree nobody opens. */
.books-tree{
  display:flex; flex-direction:column; gap:1px;
  margin:2px 0 var(--k-3) 15px; padding-left:9px;
  border-left:1px solid var(--k-line-soft);
}
.books-row{ display:flex; align-items:center; gap:2px; }
.rail .books-row > a.books-book{
  flex:1 1 auto; min-width:0; min-height:30px; gap:9px;
  font-size:var(--k-t-small); font-weight:550; color:var(--k-ink-2);
}
.rail .books-book svg{ width:14px; height:14px; opacity:.65; }
/* A book with nothing built behind it yet still appears, and says so. The dot
   is one of three encodings — it also carries an aria-label, and the screen it
   opens states it in words. */
.books-soon{
  margin-left:auto; flex:none; font-size:10px; line-height:1;
  color:var(--k-ink-3); opacity:.7;
}
.books-caret{
  flex:none; display:grid; place-items:center;
  width:22px; height:22px; border-radius:var(--k-r-2);
  color:var(--k-ink-3); transition:color .12s ease, background .12s ease;
}
.books-caret:hover{ background:var(--k-surface-3); color:var(--k-ink-1); }
.books-caret:focus-visible{ outline:2px solid var(--k-teal-bright); outline-offset:-2px; }

.books-kids{
  display:flex; flex-direction:column; gap:1px;
  margin:1px 0 var(--k-2) 12px; padding-left:9px;
  border-left:1px solid var(--k-line-soft);
}
.rail .books-kids > a.books-screen{
  min-height:29px; padding-inline:var(--k-3);
  font-size:var(--k-t-micro); font-weight:500; color:var(--k-ink-3);
}
.rail .books-kids > a.books-screen:hover{ color:var(--k-ink-1); }
/* The current screen is marked the same way the rail marks a page, one step
   quieter, so two marks on screen at once read as a path and not a conflict:
   the book you are in, and the screen you are on inside it. */
.rail .books-book[aria-current="true"]{ color:var(--k-ink-1); font-weight:650; }
.rail .books-tree a[aria-current="page"]{ font-weight:650; }
.rail .books-tree a[aria-current="page"]::before{ left:-10px; top:6px; bottom:6px; }

/* Phone width has no rail at all; the bottom sheet is the menu there and the
   book page carries its own tabs across the top. */
@media (max-width:719px){ .books-tree{ display:none; } }


/* ════════════════════════════════════════════════════════════════════════════
   20  DRAWER AND BOTTOM SHEET
   Non modal for inspection is the default. Modal only for a form that must be
   finished. The drawer is its own container so its contents lay out from
   440 px, not from the viewport.
   ════════════════════════════════════════════════════════════════════════════ */
.drawer-scrim{
  position:fixed; inset:0; z-index:var(--k-z-scrim); background:rgba(3,9,10,.5);
  -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px);
  opacity:0; pointer-events:none; transition:opacity var(--k-d3) var(--k-ease);
}
[data-theme="light"] .drawer-scrim{ background:rgba(9,38,40,.34); }
.drawer-scrim{ visibility:hidden; }
.drawer-scrim[data-open="1"]{ opacity:1; pointer-events:auto; visibility:visible; }
.drawer{
  position:fixed; right:0; top:0; bottom:0; z-index:var(--k-z-drawer);
  width:min(440px,100vw); background:var(--k-surface-1);
  border-left:1px solid var(--k-line-strong); box-shadow:var(--k-sh-3);
  transform:translateX(102%); visibility:hidden;
  transition:transform var(--k-d3) var(--k-ease-out), visibility 0s linear var(--k-d3);
  display:flex; flex-direction:column;
  container-type:inline-size; container-name:drawer;
}
.drawer[data-open="1"]{ transform:none; visibility:visible; transition:transform var(--k-d3) var(--k-ease-out); }
.drawer-hd{
  display:flex; align-items:center; gap:var(--k-3); padding:var(--k-4) var(--k-5);
  border-bottom:1px solid var(--k-line); flex-wrap:wrap;
}
.drawer-hd h3{ margin:0; font-size:var(--k-t-lede); font-weight:640; letter-spacing:var(--k-tr-tight); }
.drawer-bd{ padding:var(--k-5); overflow:auto; flex:1 1 auto; }
.drawer-ft{
  padding:var(--k-4) max(var(--k-5), env(safe-area-inset-right)) max(var(--k-4), env(safe-area-inset-bottom)) var(--k-5);
  border-top:1px solid var(--k-line); display:flex; flex-wrap:wrap; gap:var(--k-2);
}
.drawer-handle{ display:none; }

.kv{ display:grid; grid-template-columns:minmax(0,112px) minmax(0,1fr); gap:9px var(--k-4); font-size:var(--k-t-small); }
.kv dt{ color:var(--k-ink-3); font-size:var(--k-t-label); }
.kv dd{ margin:0; }
.tl{ position:relative; padding-left:20px; margin-top:var(--k-4); }
.tl::before{ content:""; position:absolute; left:5px; top:6px; bottom:6px; width:1px; background:var(--k-line); }
.tl .ev{ position:relative; padding-bottom:14px; }
.tl .ev::before{
  content:""; position:absolute; left:-18px; top:5px; width:9px; height:9px; border-radius:50%;
  background:var(--k-surface-1); border:2px solid var(--k-line-strong);
}
.tl .ev.done::before{ background:var(--k-teal); border-color:var(--k-teal); }
.tl .ev b{ font-size:var(--k-t-small); font-weight:650; display:block; }
.tl .ev span{ font-size:var(--k-t-micro); color:var(--k-ink-3); }


/* ════════════════════════════════════════════════════════════════════════════
   21  TOAST
   A toast never carries information that exists nowhere else, because it
   disappears. Three on screen at most; a fourth replaces the oldest.
   ════════════════════════════════════════════════════════════════════════════ */
.toaster{
  position:fixed; right:var(--k-5);
  bottom:max(var(--k-5), env(safe-area-inset-bottom));
  z-index:var(--k-z-toast); display:flex; flex-direction:column; gap:var(--k-2);
  align-items:flex-end; max-width:min(92vw,380px); pointer-events:none;
}
.toast{
  display:flex; align-items:flex-start; gap:11px; padding:12px 14px; border-radius:var(--k-r2);
  border:1px solid var(--k-line-strong); background:var(--k-surface-1); box-shadow:var(--k-sh-3);
  min-width:min(270px,90vw); max-width:100%; pointer-events:auto;
  animation:k-toastIn var(--k-d3) var(--k-ease-out) both;
}
@keyframes k-toastIn{ from{ opacity:0; transform:translateY(12px) scale(.98); } to{ opacity:1; transform:none; } }
.toast.out{ animation:k-toastOut var(--k-d2) var(--k-ease) both; }
@keyframes k-toastOut{ to{ opacity:0; transform:translateY(6px) scale(.98); } }
.toast .ti{ width:18px; height:18px; flex:none; margin-top:1px; color:var(--k-mute); }
.toast.good .ti{ color:var(--k-good); }
.toast.crit .ti{ color:var(--k-crit); }
.toast.warn .ti{ color:var(--k-warn); }
.toast.info .ti{ color:var(--k-info); }
.toast .body{ flex:1 1 auto; min-width:0; }
.toast b{ display:block; font-size:var(--k-t-small); font-weight:650; }
.toast p{ margin:2px 0 0; font-size:var(--k-t-label); color:var(--k-ink-2); line-height:1.5; }
.toast .undo{
  flex:none; font-size:var(--k-t-label); font-weight:700; color:var(--k-gold-text);
  min-height:32px; padding:4px 9px; border-radius:var(--k-r1); border:1px solid var(--k-action-ring);
}
.toast .undo:hover{ background:var(--k-gold-wash); }
.toast .x{
  flex:none; color:var(--k-ink-3); width:28px; height:28px;
  display:grid; place-content:center; border-radius:5px;
}
.toast .x:hover{ background:var(--k-surface-3); color:var(--k-ink-1); }
.toast .x svg{ width:14px; height:14px; }


/* ════════════════════════════════════════════════════════════════════════════
   22  BANNER
   In flow, for a condition that persists. Never dismissible while the
   condition is still true.
   ════════════════════════════════════════════════════════════════════════════ */
.banner{
  display:flex; align-items:flex-start; gap:12px; padding:13px 15px; border-radius:var(--k-r2);
  border:1px solid var(--k-line-strong); background:var(--k-surface-2); min-width:0;
}
/* ui.banner() appends the tone icon inside a <span class="k2-ic"> wrapper,
   because the icon module loads lazily and the span is the slot it fills.
   Only `.banner > svg` was styled, so the span matched nothing: it became an
   ordinary flex item, Law 2 handed it min-width:0, and on a phone it squashed
   to a 4.5 px grey sliver that carried no tone colour. Every status on this
   system is an icon AND a word (§2.2b rule 3), so a squashed icon is half a
   status. Both forms are styled now, and the tone colour rides on both. */
.banner > .k2-ic, .banner > svg{
  display:flex; width:18px; height:18px; flex:none; margin-top:2px; color:var(--k-mute);
}
.banner > .k2-ic > svg{ width:18px; height:18px; flex:none; }
.banner .body{ flex:1 1 auto; min-width:0; }
/* LAW 3 (DESIGN-LANGUAGE 5.4). .btn is white-space:nowrap with no flex:none,
   and .banner is a row with a flex:1 1 auto body, so a 71 px Thai label spilled
   outside its 60 px button on the Warehouse banner. The action never shrinks,
   and under 360 it takes its own full width line rather than fighting the text. */
.banner .btn, .banner .btn-action{ flex:none; max-width:100%; }
.banner .btn > span{ overflow:hidden; text-overflow:ellipsis; }
@media (max-width:360px){
  .banner{ flex-wrap:wrap; }
  .banner .btn{ width:100%; }
}
.banner b{ font-size:var(--k-t-small); font-weight:650; display:block; }
.banner p{ margin:3px 0 0; font-size:var(--k-t-label); color:var(--k-ink-1); line-height:1.55; }
.banner.warn{ border-color:var(--k-warn-line); background:var(--k-warn-bg); }
.banner.warn > .k2-ic, .banner.warn > svg{ color:var(--k-warn); }
.banner.crit{ border-color:var(--k-crit-line); background:var(--k-crit-bg); }
.banner.crit > .k2-ic, .banner.crit > svg{ color:var(--k-crit); }
.banner.info{ border-color:var(--k-info-line); background:var(--k-info-bg); }
.banner.info > .k2-ic, .banner.info > svg{ color:var(--k-info); }
.banner.good{ border-color:var(--k-good-line); background:var(--k-good-bg); }
.banner.good > .k2-ic, .banner.good > svg{ color:var(--k-good); }

/* ---- ui.failBanner: the partial read, with the correction in it -----------
   The count on its own ("3 of the reads behind this page failed") was a dead
   end. The detail list names the collection and the reason, and carries the
   two things a person can actually do: open the source, or hand it on. It is
   inside a <details> because the banner must stay one calm sentence for the
   many people who will read it and carry on. */
.k2-faildet{ margin-top:9px; }
/* FIX 2026-08-17 (Fah): summary's native ::marker triangle only renders when
   the element's own display is the default list-item. This rule sets
   display:inline-flex on the summary (needed for the gap/align-items
   layout), which silently drops the native marker in every browser, so the
   old ::marker color rule below was styling a triangle that no longer
   existed. Result: "What failed" looked like plain, uninteractive text, with
   no visible sign it opens on click, even though the click still worked
   (Marc's Accounts/Warehouse/Online/Reports screenshots, 2026-08-17). Same
   root cause k2.css already names for .sec-mark above. Fixed the same way:
   an explicit arrow the CSS draws itself, that rotates open. */
.k2-faildet > summary{
  display:inline-flex; align-items:center; gap:6px; cursor:pointer; min-height:var(--k-tap);
  font-size:var(--k-t-label); font-weight:650; color:var(--k-ink-1);
  list-style:none;
}
.k2-faildet > summary::-webkit-details-marker{ display:none; }
.k2-faildet > summary::before{
  content:''; flex:none; width:0; height:0; margin-right:1px;
  border-top:4px solid transparent; border-bottom:4px solid transparent;
  border-left:5px solid var(--k-ink-3);
  transition:transform var(--k-d2, .15s) var(--k-ease, ease);
}
.k2-faildet[open] > summary::before{ transform:rotate(90deg); }
.k2-faildet > summary:hover::before{ border-left-color:var(--k-ink-1); }
.k2-faildet > summary:focus-visible{ outline:2px solid var(--k-focus); outline-offset:2px; border-radius:var(--k-r1); }
.k2-faillist{ list-style:none; margin:6px 0 0; padding:0; display:grid; gap:7px; }
.k2-faillist > li{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px; min-width:0;
  padding:8px 10px; border-radius:var(--k-r1);
  border:1px solid var(--k-line-soft); background:var(--k-surface-1);
}
.k2-faillist > li > b{
  font-family:var(--k-font-mono, ui-monospace, monospace);
  font-size:var(--k-t-label); font-weight:700; flex:none;
}
.k2-faillist > li > span{ font-size:var(--k-t-label); color:var(--k-ink-1); flex:1 1 220px; min-width:0; }
.k2-faillist > li > .btn{ flex:none; }
.k2-failacts{ display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; }
.k2-failreport{
  margin:8px 0 0; padding:9px 11px; border-radius:var(--k-r1); overflow:auto;
  border:1px solid var(--k-line-soft); background:var(--k-surface-1);
  font-family:var(--k-font-mono, ui-monospace, monospace); font-size:var(--k-t-micro);
  color:var(--k-ink-1); white-space:pre-wrap; word-break:break-word;
}

/* ---- the login gate --------------------------------------------------------
   Set by the small script in every page's head, before this stylesheet has
   painted a single row of the shell. A visitor with no session sees the same
   thing on Accounts as on Rolls: nothing, and then the sign in page. Marc's
   word for the requirement was "secretive", and a rail full of department
   names is not secretive even when every figure behind it is refused. */
html[data-k2-gate="out"] body{ visibility:hidden !important; }
/* The persistent offline banner k2.js pins under the topbar. */
.netbar{
  position:sticky; top:var(--k-topbar); z-index:calc(var(--k-z-topbar) - 1);
  display:none; align-items:center; gap:10px; padding:9px var(--k-5);
  background:var(--k-warn-bg); border-bottom:1px solid var(--k-warn-line);
  font-size:var(--k-t-small);
}
.netbar svg{ width:16px; height:16px; color:var(--k-warn); flex:none; }
body[data-net="offline"] .netbar{ display:flex; }


/* ════════════════════════════════════════════════════════════════════════════
   23  STATES
   Two empty states, and they are different. A page that shows the same box for
   both has failed review: it is the single most common way this system has
   looked broken while working perfectly.
   ════════════════════════════════════════════════════════════════════════════ */
/* The state cards were the only centred text on a 15,000 px page, and the
   developer key was printed BELOW the action so the eye ended on a token
   rather than on the thing to do. Left aligned, key moved to the header. */
.state{ min-height:216px; display:flex; flex-direction:column; position:relative; }
.state .panel-bd{
  flex:1 1 auto; display:flex; flex-direction:column; align-items:flex-start; justify-content:center;
  text-align:left; gap:11px;
}
.state .state-key{
  position:absolute; right:var(--k-3); top:var(--k-3); z-index:1;
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3);
  border:1px solid var(--k-line); border-radius:var(--k-r1); padding:1px 6px;
}
.state .ic{
  width:44px; height:44px; border-radius:13px; display:grid; place-content:center;
  border:1px solid var(--k-line); background:var(--k-surface-2); color:var(--k-ink-3);
}
.state .ic svg{ width:20px; height:20px; }
.state b{ font-size:var(--k-t-body); font-weight:640; letter-spacing:var(--k-tr-tight); }
.state p{ margin:0; font-size:var(--k-t-small); color:var(--k-ink-2); max-width:38ch; line-height:1.55; }
.state.crit .ic{ color:var(--k-crit); border-color:var(--k-crit-line); background:var(--k-crit-bg); }
.state.warn .ic{ color:var(--k-warn); border-color:var(--k-warn-line); background:var(--k-warn-bg); }
.state.info .ic{ color:var(--k-info); border-color:var(--k-info-line); background:var(--k-info-bg); }
.state.good .ic{ color:var(--k-good); border-color:var(--k-good-line); background:var(--k-good-bg); }

.skel{
  border-radius:var(--k-r1);
  background:linear-gradient(90deg,var(--k-surface-3) 8%,var(--k-surface-4) 22%,var(--k-surface-3) 36%);
  background-size:600px 100%; animation:k-shimmer 1.9s linear infinite;
}
@keyframes k-shimmer{ from{ background-position:-300px 0; } to{ background-position:300px 0; } }
.skel-row{ display:flex; align-items:center; gap:11px; padding:9px 0; border-bottom:1px solid var(--k-line-soft); }

.stale{
  display:inline-flex; align-items:center; gap:7px; min-height:24px; padding:0 10px;
  border-radius:var(--k-rpill); font-size:var(--k-t-micro); font-weight:650;
  border:1px solid var(--k-warn-line); background:var(--k-warn-bg); color:var(--k-ink-1);
}
.stale svg{ width:12px; height:12px; color:var(--k-warn); flex:none; }
.stale .pulse{ width:6px; height:6px; border-radius:50%; background:var(--k-warn); animation:k-pulse 2.2s var(--k-ease) infinite; }
@keyframes k-pulse{ 0%,100%{ opacity:1; } 50%{ opacity:.28; } }


/* ════════════════════════════════════════════════════════════════════════════
   24  CHARTS
   Hand rolled SVG from k2-charts.js. Colours are read from the token layer at
   draw time and redrawn on theme change, so no hex ever lives in chart code.
   ════════════════════════════════════════════════════════════════════════════ */
.chart-wrap{ position:relative; min-width:0; }
.chart-wrap svg{ width:100%; height:auto; display:block; overflow:hidden; }
.chart text{ font-family:var(--k-sans); }
.chart text.axis{ font-size:11px; font-weight:650; letter-spacing:.06em; fill:var(--k-ink-3); }
.chart text.value{ font-size:11px; font-weight:700; fill:var(--k-ink-1); }
.chart .gridline{ stroke:var(--k-grid); stroke-width:1; }
.chart .axisline{ stroke:var(--k-line-strong); stroke-width:1; }
.chart .hit:focus-visible{ outline:2px solid var(--k-focus); outline-offset:1px; }
.legend{ display:flex; flex-wrap:wrap; gap:var(--k-3) var(--k-4); margin-bottom:var(--k-4); }
.legend .li{ display:inline-flex; align-items:center; gap:7px; font-size:var(--k-t-label); color:var(--k-ink-2); }
.legend .sw{ width:10px; height:10px; border-radius:3px; flex:none; }
.legend .sw.line{ height:2px; border-radius:2px; width:14px; }

/* THE SWATCH SLOT. A legend swatch used to be built by resolving --k-c1 with
   getComputedStyle at build time and writing the answer into a style
   attribute. The chart redrew on a theme change and the swatch did not, so
   after one toggle the legend was painting #009488 next to a band painted
   #14A99B: the legend contradicted the chart on every multi series chart on
   every page. The slot number is now the only thing written into the DOM and
   the colour follows the token with no JS at all. §7.6. */
.sw[data-slot="1"]{ background:var(--k-c1); }
.sw[data-slot="2"]{ background:var(--k-c2); }
.sw[data-slot="3"]{ background:var(--k-c3); }
.sw[data-slot="4"]{ background:var(--k-c4); }
.sw[data-slot="5"]{ background:var(--k-c5); }
.sw[data-slot="6"]{ background:var(--k-c6); }
.sw[data-slot="other"]{ background:var(--k-c6); }
.sw[data-slot="seq-1"]{ background:var(--k-seq-1); }
.sw[data-slot="seq-2"]{ background:var(--k-seq-2); }
.sw[data-slot="seq-3"]{ background:var(--k-seq-3); }
.sw[data-slot="seq-4"]{ background:var(--k-seq-4); }
.chart-note{ margin:var(--k-3) 0 0; font-size:var(--k-t-micro); color:var(--k-ink-3); }
.chart-empty{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px;
  min-height:160px; text-align:center; color:var(--k-ink-2); font-size:var(--k-t-small);
}
/* Draw in. Finite, never infinite, and only transform and opacity. Under
   reduced motion §29 flattens it to nothing. */
.chart .draw{ animation:k-drawIn var(--k-d3) var(--k-ease-out) both; }
.chart .draw-y{ animation:k-drawY var(--k-d3) var(--k-ease-out) both; transform-origin:center bottom; }
.chart .draw-x{ animation:k-drawX var(--k-d3) var(--k-ease-out) both; transform-origin:left center; }
@keyframes k-drawIn{ from{ opacity:0; } to{ opacity:1; } }
@keyframes k-drawY{ from{ transform:scaleY(0); opacity:.4; } to{ transform:scaleY(1); opacity:1; } }
@keyframes k-drawX{ from{ transform:scaleX(0); opacity:.4; } to{ transform:scaleX(1); opacity:1; } }

.tt{
  position:fixed; z-index:var(--k-z-tooltip); pointer-events:none; opacity:0;
  transform:translateY(4px);
  transition:opacity var(--k-d1) var(--k-ease), transform var(--k-d1) var(--k-ease);
  background:var(--k-surface-1); border:1px solid var(--k-line-strong); border-radius:var(--k-r2);
  box-shadow:var(--k-sh-3); padding:9px 11px; min-width:140px; max-width:260px;
}
.tt[data-on="1"]{ opacity:1; transform:translateY(0); }
.tt .h{
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3); margin-bottom:5px;
}
.tt .r{ display:flex; align-items:center; gap:8px; font-size:var(--k-t-small); line-height:1.7; }
.tt .r .sw{ width:9px; height:9px; border-radius:3px; flex:none; }
.tt .r .n{ margin-left:auto; font-variant-numeric:tabular-nums; font-weight:650; }

.donut-mid{ position:absolute; inset:0; display:grid; place-content:center; text-align:center; pointer-events:none; }
.donut-mid .v{ font-size:var(--k-t-h1); font-weight:620; letter-spacing:var(--k-tr-display); line-height:1; }
.donut-mid .l{
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3); margin-top:4px;
}
/* A headline figure, where §7.3 says the form is a figure and not a chart:
   the percentage, what it is a percentage OF, and the absolute pair, because
   62% with no denominator is not a number anyone can act on. */
.big-fig{ display:flex; align-items:baseline; flex-wrap:wrap; gap:var(--k-2) var(--k-3); margin-bottom:var(--k-4); min-width:0; }
.big-fig b{ font-size:var(--k-t-display); font-weight:620; letter-spacing:var(--k-tr-display); line-height:1; }
.big-fig .cap{ font-size:var(--k-t-small); color:var(--k-ink-2); }
.big-fig .pair{ margin-left:auto; font-size:var(--k-t-small); color:var(--k-ink-3); font-variant-numeric:tabular-nums; }

.dlist{ display:flex; flex-direction:column; gap:9px; margin-top:var(--k-4); }
.dlist .row{ display:flex; align-items:center; gap:9px; font-size:var(--k-t-small); }
.dlist .sw{ width:10px; height:10px; border-radius:3px; flex:none; }
.dlist .n{ margin-left:auto; font-variant-numeric:tabular-nums; font-weight:650; }
.dlist .p{ color:var(--k-ink-3); font-variant-numeric:tabular-nums; width:44px; text-align:right; flex:none; }

/* The table twin. Strictly more informative than the chart it replaces. */
.twin{ display:none; }
.twin[data-on="1"]{ display:block; }
.chart-wrap[data-off="1"]{ display:none; }
/* The twin gets its own one dimensional scroller on a phone rather than
   spilling the document. A twin whose money column is silently cut in half
   is a lie, not a layout nit, so the extra column stays reachable and the
   edge fade says there is more to the right. */
.twin{ min-width:0; max-width:100%; }
.twin-scroll{
  overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain; scrollbar-gutter:stable; max-width:100%;
}
@container panel (max-width:560px){
  .twin-scroll{
    /* A mask reads alpha only, so the colour is irrelevant; it is written as
       a token anyway so the no-hex-outside-the-token-block gate stays honest. */
    -webkit-mask-image:linear-gradient(90deg,var(--k-ink-1) calc(100% - 22px),transparent);
    mask-image:linear-gradient(90deg,var(--k-ink-1) calc(100% - 22px),transparent);
  }
  .twin-scroll table.twin-t{ min-width:max-content; }
  .twin-scroll table.twin-t td,.twin-scroll table.twin-t th{ white-space:nowrap; }
}
table.twin-t{ width:100%; font-size:var(--k-t-small); }
table.twin-t th{
  text-align:left; font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3); padding:7px 8px;
  border-bottom:1px solid var(--k-line-strong); white-space:nowrap;
}
table.twin-t th.n,table.twin-t td.n{ text-align:right; font-variant-numeric:tabular-nums; }
table.twin-t td{ padding:6px 8px; border-bottom:1px solid var(--k-line-soft); }
table.twin-t tfoot td{ border-top:1px solid var(--k-line-strong); border-bottom:0; font-weight:700; }

/* Small multiples share one scale, stated in the note. */
.smalls{ display:grid; gap:var(--k-3); grid-template-columns:repeat(auto-fill,minmax(min(180px,100%),1fr)); }
.smalls figure{ margin:0; min-width:0; }
.smalls figcaption{
  font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
  text-transform:uppercase; color:var(--k-ink-3); margin-bottom:4px;
}


/* ════════════════════════════════════════════════════════════════════════════
   25  SPECIMEN HELPERS
   Used by the kitchen sink and the design specimen only.
   ════════════════════════════════════════════════════════════════════════════ */
.spec-note{
  border-left:2px solid var(--k-teal); padding-left:var(--k-3); margin:0 0 var(--k-4);
  font-size:var(--k-t-small); color:var(--k-ink-2); max-width:72ch;
}
.spec-grid{ display:grid; gap:var(--k-4); grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr)); }
/* The surface used to be on the ROW (a background behind a 1 px gap grid), so
   an incomplete last row painted visible empty plates: four in ACTIONS, eight
   in SORT AND TABLE, six in SHELL. The fill is on the cell now. */
.icons{
  display:grid; grid-template-columns:repeat(auto-fill,minmax(min(112px,100%),1fr));
  gap:var(--k-1); min-width:0;
}
.icons .cell{
  display:flex; flex-direction:column; align-items:center; gap:9px; padding:14px 6px;
  background:var(--k-surface-2); min-width:0;
  border:1px solid var(--k-line-soft); border-radius:var(--k-r1);
}
.icons .cell svg{ width:22px; height:22px; color:var(--k-ink-1); }
.icons .cell code{ font-size:var(--k-t-micro); color:var(--k-ink-3); font-family:var(--k-mono); text-align:center; overflow-wrap:anywhere; }
.icon-sizes{ display:flex; align-items:flex-end; gap:var(--k-5); margin-top:var(--k-4); flex-wrap:wrap; }
.icon-sizes div{ display:flex; flex-direction:column; align-items:center; gap:7px; }
.icon-sizes span{ font-size:var(--k-t-micro); color:var(--k-ink-3); font-family:var(--k-mono); }
/* Specimen type classes. These exist so the specimen page can demonstrate a
   step WITHOUT an inline style attribute: an inline style is how a page stops
   being a component and starts being a one off, and the build gate now greps
   for style= in every page file. */
.t-display{ font-size:var(--k-t-display); font-weight:600; letter-spacing:var(--k-tr-display); line-height:1.1; }
.t-display small{ font-size:var(--k-t-h3); color:var(--k-ink-3); font-weight:600; margin-inline-start:var(--k-2); }
.t-h1{ font-size:var(--k-t-h1); font-weight:640; letter-spacing:var(--k-tr-display); }
.t-h2{ font-size:var(--k-t-h2); font-weight:640; letter-spacing:var(--k-tr-tight); }
.t-h3{ font-size:var(--k-t-h3); font-weight:640; }
.t-lede{ font-size:var(--k-t-lede); color:var(--k-ink-2); }
.t-body{ font-size:var(--k-t-body); }
.t-small{ font-size:var(--k-t-small); }
.t-small-2{ font-size:var(--k-t-small); color:var(--k-ink-2); }
.t-label{ font-size:var(--k-t-label); font-weight:650; }
.t-quiet{ color:var(--k-ink-3); }
.th-snug{ line-height:var(--k-lh-snug-th); }
.th-body{ line-height:var(--k-lh-body-th); }
.page-foot{
  padding-top:var(--k-6); border-top:1px solid var(--k-line);
  color:var(--k-ink-2); font-size:var(--k-t-small);
}
.rows-per{ display:flex; align-items:center; gap:6px; font-size:var(--k-t-label); min-width:0; }
.rows-per select{ min-width:70px; }
/* Skeleton shapes, so a skeleton row is a component and not three inline
   widths repeated at every call site. */
.skel-lead{ width:38px; height:20px; flex:none; }
.skel-bar{ height:12px; }
.skel-tail{ width:72px; height:12px; flex:none; }

.type-row{
  display:flex; align-items:baseline; gap:var(--k-5); padding:11px 0;
  border-bottom:1px solid var(--k-line-soft); min-width:0;
}
.type-row .meta{ flex:none; width:150px; font-size:var(--k-t-micro); color:var(--k-ink-3); font-family:var(--k-mono); }
.type-row .samp{ min-width:0; max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.swatches{ display:grid; grid-template-columns:repeat(auto-fill,minmax(min(132px,100%),1fr)); gap:var(--k-2); }
.sw-card{ border-radius:var(--k-r2); border:1px solid var(--k-line); overflow:hidden; background:var(--k-surface-2); }
.sw-card .chip{ height:52px; box-shadow:inset 0 0 0 1px var(--k-line-strong); }
.sw-card .info{ padding:8px 10px; }
.sw-card b{ display:block; font-size:var(--k-t-micro); font-weight:650; }
.sw-card code{ font-size:var(--k-t-micro); color:var(--k-ink-3); font-family:var(--k-mono); }


/* ════════════════════════════════════════════════════════════════════════════
   26  PRINT DOCUMENTS
   The delivery bill (ใบส่งของชั่วคราว) and the packing list are the most
   consequential output in the product. Suda prints them daily and hands them
   to a driver, so they are specified as tightly as the screen.
   ════════════════════════════════════════════════════════════════════════════ */
/* Below 640 the bill scrolls inside its own box. It is a preview of A4 and
   A4 does not reflow, so the alternative is either a squeezed document that
   lies about what will print, or a document that pushes the page sideways.
   Print resets it: on paper there is nothing to scroll. */
.doc-wrap{ min-width:0; max-width:100%; }
@media (max-width:640px){
  .doc-wrap{ overflow-x:auto; overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch; }
  .doc{ min-width:340px; }
}
@media print{ .doc-wrap{ overflow:visible; } .doc{ min-width:0; } }

.doc{
  /* Square, because rounded corners are a small lie about paper. */
  background:#FFFFFF; color:#08191B; border-radius:0; padding:26px 28px;
  border:1px solid var(--k-line); box-shadow:var(--k-sh-2); max-width:820px; margin-inline:auto;
  font-size:12.5px; line-height:1.5;
}
:lang(th) .doc,.doc :lang(th){ line-height:1.72; }
.doc-hd{ display:flex; align-items:flex-start; gap:20px; border-bottom:1.5px solid #08191B; padding-bottom:12px; flex-wrap:wrap; }
.doc-hd .m{ width:40px; height:40px; flex:none; color:#0A6B64; }
.doc-hd .doc-co{ min-width:0; }
.doc-hd .doc-co h3{ margin:0; font-size:16px; font-weight:700; letter-spacing:-.01em; }
.doc-hd .doc-co p{ margin:2px 0 0; font-size:11px; color:#3B5254; line-height:1.45; }
.doc-hd .ttl{ margin-left:auto; text-align:right; min-width:0; }
.doc-hd .ttl h2{ margin:0; font-size:19px; font-weight:700; letter-spacing:-.01em; }
.doc-hd .ttl .en{ font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:#3B5254; }
.doc-hd .ttl .no{ margin-top:5px; font-size:12px; font-variant-numeric:tabular-nums; }
.doc-meta{
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:10px 18px;
  padding:12px 0; border-bottom:1px solid #C9D6D3;
}
.doc-meta .f{ min-width:0; }
.doc-meta .f .l{ font-size:9.5px; letter-spacing:.1em; text-transform:uppercase; color:#3B5254; }
.doc-meta .f .v{ font-size:12.5px; font-weight:600; font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
table.doc-t{ width:100%; border-collapse:collapse; margin-top:12px; font-size:12px; }
table.doc-t th{
  text-align:left; font-size:9.5px; letter-spacing:.09em; text-transform:uppercase; color:#3B5254;
  border-bottom:1px solid #08191B; padding:6px 8px; font-weight:700;
}
table.doc-t th.n,table.doc-t td.n{ text-align:right; font-variant-numeric:tabular-nums; }
table.doc-t td{ padding:6px 8px; border-bottom:1px solid #DCE5E3; }
table.doc-t tfoot td{ border-top:1.5pt solid #08191B; border-bottom:0; font-weight:700; padding-top:8px; }
.doc-note{ margin-top:16px; font-size:10.5px; color:#3B5254; border-left:2px solid #8A6410; padding-left:10px; }
.doc-sign{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:26px; margin-top:34px; }
.doc-sign .s{ border-top:1px solid #08191B; padding-top:6px; font-size:10.5px; color:#3B5254; text-align:center; }
.print-only{ display:none; }


/* ════════════════════════════════════════════════════════════════════════════
   27  RESPONSIVE SHELL — 390 to 2560
   Only the shell uses media queries. Every component uses @container.
   Build the phone first and let the desktop be the enhancement.
   ════════════════════════════════════════════════════════════════════════════ */

/* Very wide. The measure stops growing; the gutters do. */
@media (min-width:1800px){
  .main{ padding-inline:var(--k-7); }
}
@media (min-width:2200px){
  :root{ --k-rail:264px; }
}

@media (max-width:1360px){
  .kpis{ grid-template-columns:repeat(3,minmax(0,1fr)); }
  .c-8,.c-7{ grid-column:span 12; }
  .c-4,.c-5,.c-6{ grid-column:span 6; }
}

@media (max-width:1080px){
  :root{ --k-rail:0px; }
  .shell{ grid-template-columns:minmax(0,1fr); }
  .rail{ display:none; }
  /* The nav CELL stays, so the topbar grid keeps its three columns and
     the page you are on can still be named. Only the links go. */
  .nav a{ display:none; }
  .nav-here{ display:flex; }
  .main{ padding:var(--k-5) var(--k-4) calc(var(--k-botnav) + 72px); }
  .hero-body{ padding:var(--k-6) var(--k-5) var(--k-5); }
  .botnav{ display:flex; }
  .fab{ display:grid; }
  .doc-meta{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .kpis{ margin-top:var(--k-3); }
}

@media (max-width:720px){
  .kpis{ grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:var(--k-4); }
  .kpis .kpi:first-child{ grid-column:span 2; }
  .c-4,.c-5,.c-6,.c-3{ grid-column:span 12; }
  .form-grid{ grid-template-columns:minmax(0,1fr); }
  .hero{ min-height:420px; }
  .hero h1{ font-size:var(--k-t-h2); }
  .hero .lede{ font-size:var(--k-t-body); }
  /* THE HERO RULE OUTRANKS EVERY OTHER RULE, AND ON A PHONE IT WAS INVERTED.
     Measured at 390 x 844 the h1 first line began at y 630 and the actions at
     y 1290, so the entire first screen was a topbar, a badge on its own row,
     a canvas and three pills. Below 720 the decoration gives way: the badge
     is a developer affordance and is not user content, the figure group is
     two up rather than a ragged wrap, and the body padding comes down two
     steps. The sentence is now the first thing on the screen. */
  .glbadge{ display:none; }
  .hero-body{ padding:var(--k-4); }
  .hero-figs{
    grid-auto-flow:row; grid-template-columns:repeat(2,minmax(0,1fr));
    grid-template-rows:none; column-gap:var(--k-4); row-gap:var(--k-3);
    margin-top:var(--k-4);
  }
  .hero-fig{ display:flex; flex-direction:column; gap:2px; min-width:0; }
  .hero-fig .lab,.hero-fig .val{ grid-row:auto; align-self:auto; }
  /* The caption WRAPS on a phone rather than ellipsing. Off the two row grid
     there is nothing left to line up, and `WAITING FOR THE WAR…` on the one
     figure the sentence is about is the worst possible place to lose a word.
     Same rule the KPI label already follows. */
  .hero-fig .lab{
    white-space:normal; overflow:visible; text-overflow:clip;
    /* Two lines of room whether it needs them or not, so a one line caption
       beside a two line one does not put the two figures on different
       baselines. A row of figures that does not line up is not a row. */
    min-height:2.5em; display:flex; align-items:flex-end;
  }
  .hero-fig .val{ font-size:var(--k-t-h1); }
  .hero-fig.sm .val{ font-size:var(--k-t-h2); }
  .hero .lede{ margin-top:var(--k-2); }
  .hero-acts{ margin-top:var(--k-4); }
  /* Full width, all of them. `flex:1 1 auto` sized each button to its own
     label, so a three button hero came out 332, 332 and 192 px wide at 390:
     a ragged stack, and the odd one out is the one the eye reads as broken. */
  .hero-acts .btn{ flex:1 1 100%; }
  /* The freshness chip is meta, not part of the sentence, so it drops below
     the lede and the period pill goes entirely: the period is already in the
     lede and in the section captions. */
  .hero-kicker{ margin-bottom:var(--k-2); }
  .hero-kicker .k-period{ display:none; }
  .hero-kicker .k-fresh{ order:9; }
  .langsw{ display:none; }
  .btn.lang-one{ display:inline-flex; }
  /* Author documentation is written for someone at a desk with the code open.
     On a phone it is pure height, and height is the budget that decides
     whether the page is usable at all: in ไทย, where every line is 1.80
     leading, the design commentary alone pushed the page past 6,000 px. The
     product copy is untouched; only the prose ABOUT the product goes. */
  [data-i18n-doc].spec-note{ display:none; }
  .hero .lede.quiet{ font-size:var(--k-t-small); }
  details.sec > summary{ padding-right:var(--k-7); }
  details.sec:not([open]) > summary .lede{ display:none; }
  details.sec:not([open]) > summary .sec-rule{ margin-bottom:var(--k-2); }
  .sec{ margin-bottom:var(--k-6); }
  .type-row{ flex-direction:column; align-items:flex-start; gap:4px; }
  .type-row .meta{ width:auto; }
  .type-row .samp{ white-space:normal; }
  .doc-sign{ grid-template-columns:minmax(0,1fr); gap:20px; }
  .doc{ padding:18px 16px; }
  .tbl-scroll{ max-height:none; }
  .panel{
    -webkit-backdrop-filter:blur(var(--k-blur-sm)) saturate(140%);
    backdrop-filter:blur(var(--k-blur-sm)) saturate(140%);
  }
  /* Everything tappable clears 44 px. Not by growing the icon: by growing
     the control. WCAG 2.5.8 has no small print for a warehouse. */
  .btn{ min-height:48px; }
  .btn-icon{ width:48px; min-width:48px; }
  /* min-height alone is half a target. In ไทย, Delete is ลบ and twenty four
     of them probed 41 x 44: three pixels under, in one language only. */
  .btn-quiet{ min-height:var(--k-tap); min-width:var(--k-tap); padding-inline:var(--k-3); }
  .seg button,.langsw button{ min-height:var(--k-tap); padding-inline:var(--k-4); }
  /* In ไทย "Not in warehouse" is ยังไม่เข้าคลังสินค้า, and three cells at that
     length do not fit 360 px however small the type gets. §6.15 already allows
     a strip to scroll horizontally inside its own container below 720; a
     segmented control at a phone width in Thai is that case. One dimension,
     obvious, and inside its own box rather than spilling the document. */
  .seg{ overflow-x:auto; overflow-y:hidden; scroll-snap-type:x mandatory;
        overscroll-behavior-x:contain; max-width:100%; }
  .seg button{ scroll-snap-align:start; }
  .search{ min-height:48px; }
  .chk{ width:var(--k-tap); height:var(--k-tap); }
  .search input,.ctl input,.ctl select{ min-height:var(--k-tap); }
  .inline-edit{ min-height:var(--k-tap); }
  .toast .x{ width:var(--k-tap); height:var(--k-tap); min-width:var(--k-tap); flex:none; }
  /* Undo is the control Marc asked for by name. It measured 48x43. */
  .toast .undo{ min-height:var(--k-tap); min-width:var(--k-tap); flex:none; }
  .rowmenu > button{ width:var(--k-tap); height:var(--k-tap); }
  .pager button{ min-width:var(--k-tap); min-height:var(--k-tap); }
  .ctl{ min-height:48px; }
  /* SHARED LAYER FIX, 2026-08-08. The rule above already asks for the touch
     floor on .inline-edit, and `table.k tbody td .inline-edit{min-height:20px}`
     outranks it on specificity, so every editable cell on a phone shipped at
     20 px: less than half a target, in a table, which is the hardest place to
     hit anything. The desktop density rule keeps its 20 px; below 720 the
     floor wins, said at the same specificity so it actually does. */
  table.k tbody td .inline-edit{ min-height:var(--k-tap); min-width:var(--k-tap); }
  .tabs [role="tab"]{ min-height:48px; }
  .toaster{ left:var(--k-3); right:var(--k-3); max-width:none; align-items:stretch; }
}

/* The phone. Rows become summary cards of at most five fields, capped at
   132 px, and everything else is one tap away in the drawer. This is what
   turns a 9,061 px table into a 1,320 px one. */
@container main (max-width:640px){
  table.k{ min-width:0; }
  table.k thead{ display:none; }
  table.k,table.k tbody{ display:block; width:100%; }
  /* Five fields, two columns, capped at 132 px. Ten rows at 132 replaces
     twenty five rows at 342, which is the whole of the phone height budget. */
  table.k tbody tr{
    display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0 var(--k-3);
    align-content:start; width:100%; position:relative;
    border:1px solid var(--k-line); border-radius:var(--k-r2); margin-bottom:6px;
    padding:7px 56px 7px 12px; background:var(--k-surface-2);
    /* SHARED LAYER FIX, 2026-08-08. §5.6 makes the row itself a target and
       §5.7 puts the floor at 44. A one field summary card came out 34 px
       tall, so the target the whole phone drawer depends on was under the
       floor. The cap is still 148; this is the floor. */
    min-height:var(--k-tap);
  }
  /* THE SELECT BOX STAYS ON THE PHONE.
     It was display:none in card mode, and it is what feeds "Tick in
     warehouse", which CONTEXT PEOPLE names as the server enforced gate that
     unlocks every delivery. Suda could not reach the one control her whole
     day depends on. Pinned to the card corner at the full 44 px so it works
     one handed. */
  table.k tbody td[data-phone="sel"]{
    display:grid; place-content:center;
    position:absolute; right:6px; top:6px; width:var(--k-tap); height:var(--k-tap);
    padding:0; border:0; background:none;
  }
  table.k tbody td[data-phone="sel"]::before{ display:none; }
  table.k.virtual tbody tr{ contain-intrinsic-size:auto 130px; }
  table.k tbody tr.zebra{ background:var(--k-surface-2); }
  table.k tbody td{ display:none; }
  table.k tbody td[data-phone]{
    display:flex; flex-direction:column; align-items:flex-start; gap:0;
    border:0; padding:1px 0; min-height:0; min-width:0; max-width:100%;
    line-height:1.35;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-align:left;
  }
  table.k tbody td[data-phone]::before{
    content:attr(data-h);
    font-size:var(--k-t-micro); font-weight:650; letter-spacing:var(--k-tr-label);
    text-transform:uppercase; color:var(--k-ink-3); line-height:1.2;
  }
  table.k tbody td[data-phone="lead"],table.k tbody td[data-phone="wide"]{ grid-column:1 / -1; }
  table.k tbody td[data-phone="lead"]::before,
  table.k tbody td[data-phone="wide"]::before{ display:none; }
  table.k tbody td[data-phone="lead"]{ font-size:var(--k-t-small); font-weight:650; }
  table.k tbody td{ height:auto; }
  table.k tbody td .co{ flex-direction:row; gap:7px; align-items:baseline; max-width:100%; }
  table.k tbody td .co b,table.k tbody td .co span{ white-space:nowrap; display:inline; }
  table.k td.stick-l,table.k td.stick-l2,table.k td.stick-r{ position:static; background:none; box-shadow:none; width:auto; }
  table.k tbody tr[data-sel="1"] td:first-child{ box-shadow:none; }
  .tbl-scroll{ overflow:visible; }
}

@media (max-width:640px){
  .brand-txt span{ display:none; }
  .user .who{ display:none; }
  .user{ padding:0 4px; }
  .topbar{ padding-inline:var(--k-3); gap:var(--k-2); }
  .drawer{ top:auto; height:92vh; width:100vw; border-left:0; border-top:1px solid var(--k-line-strong);
           border-radius:var(--k-r4) var(--k-r4) 0 0; transform:translateY(102%); }
  .drawer[data-open="1"]{ transform:none; visibility:visible; }
  .drawer-handle{
    display:block; width:40px; height:4px; border-radius:2px; background:var(--k-line-strong);
    margin:10px auto 0;
  }
  .kv{ grid-template-columns:minmax(0,1fr); gap:2px var(--k-3); }
  .kv dd{ margin-bottom:8px; }
}

@media (max-width:380px){
  /* At 320 the topbar has 296 px to spend. The brand word costs 95 of them and
     the mark already says the same thing, so the word goes last, not first:
     §5.5's own budget leaves 144 px of slack at 390, so the phone header keeps
     the product name and only drops it below 380. */
  .brand-txt{ display:none; }
  .brand{ min-width:var(--k-tap); justify-content:center; }
  .topbar-tools .btn-icon{ width:var(--k-tap); min-height:var(--k-tap); }
  .topbar-tools .btn.lang-one{ min-height:var(--k-tap); padding-inline:10px; }
  .topbar{ gap:var(--k-1); }
}

@media (max-width:400px){
  .main{ padding-inline:var(--k-3); }
  .panel-bd{ padding:var(--k-4); }
  .hero-body{ padding:var(--k-5) var(--k-4) var(--k-4); }
  .hero-figs{ gap:var(--k-4); }
}


/* ════════════════════════════════════════════════════════════════════════════
   28  PRINT
   Print forces the light selection from the same token layer. There is no
   second stylesheet. Type is set in pt because the browser maps pt to
   physical size.
   ════════════════════════════════════════════════════════════════════════════ */
@media print{
  :root,[data-theme="dark"],[data-theme="light"]{
    color-scheme: light;
    --k-void:      #D6E1DF;
    --k-bg:        #EDF2F0;
    --k-surface-1: #FFFFFF;
    --k-surface-2: #F6F8F7;
    --k-surface-3: #EBF1EF;
    --k-surface-4: #E7EFEC;

    --k-glass:    rgba(255,255,255,.80);
    --k-glass-hi: rgba(255,255,255,.92);
    --k-glass-lo: rgba(255,255,255,.62);
    --k-blur:     0px;
    --k-blur-sm:  0px;

    --k-line:        rgba(10,42,44,.13);
    --k-line-soft:   rgba(10,42,44,.07);
    --k-line-strong: rgba(10,42,44,.26);
    --k-line-ctl:    rgba(10,42,44,.54);
    --k-edge:        rgba(255,255,255,.95);
    --k-sh-1: none; --k-sh-2: none; --k-sh-3: none;

    --k-ink-1:   #08191B;
    --k-ink-2:   #3B5254;
    --k-ink-3:   #3E5456;
    --k-ink-inv: #FFFFFF;

    --k-teal:        #0A6B64;
    --k-teal-bright: #0F8C82;
    --k-teal-deep:   #0F3D3E;
    --k-teal-wash:   rgba(10,107,100,.10);
    --k-gold:        #8A6410;
    --k-gold-bright: #A8791A;
    --k-gold-text:   #5A410A;
    --k-gold-wash:   rgba(138,100,16,.10);
    --k-action-bg:   #D9A441;
    --k-action-ink:  #1A1205;
    --k-action-ring: rgba(138,100,16,.28);
    --k-link:        #06534D;

    --k-focus:      #0A6B64;
    --k-focus-halo: #FFFFFF;

    --k-good:#0C7047; --k-good-bg:rgba(12,112,71,.10);  --k-good-line:rgba(12,112,71,.30);
    --k-warn:#9A4A10; --k-warn-bg:rgba(154,74,16,.10);  --k-warn-line:rgba(154,74,16,.32);
    --k-crit:#B32636; --k-crit-bg:rgba(179,38,54,.09);  --k-crit-line:rgba(179,38,54,.28);
    --k-info:#1F55B0; --k-info-bg:rgba(31,85,176,.09);  --k-info-line:rgba(31,85,176,.28);
    --k-mute:#3E5456; --k-mute-bg:rgba(79,103,105,.10); --k-mute-line:rgba(79,103,105,.26);

    --k-c1:#009488; --k-c2:#6D23A6; --k-c3:#873D00; --k-c4:#517BFA; --k-c5:#A41A63;
    --k-c6:#4F6769;
    --k-seq-1:#8CB9AF; --k-seq-2:#4A9E91; --k-seq-3:#1B8375; --k-seq-4:#00675A;
    --k-div-1:#A5292B; --k-div-2:#C08A84; --k-div-3:#8A9998; --k-div-4:#4E9E92; --k-div-5:#00786A;
    --k-chart-surface:#FFFFFF;
    --k-grid:  rgba(10,42,44,.12);
    --k-spark: rgba(59,82,84,.58);

    --k-botnav-glass: rgba(255,255,255,.95);
    --k-topbar-glass: rgba(237,242,240,.93);
    --k-action-bg-hover:  #E4B75F;
    --k-action-bg-active: #CE9A3E;
    --k-on-teal:    #FFFFFF;
    --k-panel-solid:#FFFFFF;
    --k-zebra:      #F5F7F6;
    --k-sel-row:    #FFFFFF;
    --k-mono-bg:    #FFFFFF;
    --k-mono-ink:   #08191B;
  }

  /* §10.4. A loose sheet off the printer has to be identifiable, so every page
     carries its own number and, where the page names one, the document number.
     The count is CSS and never JS: counter(page) of counter(pages) is the only
     thing that stays right when a long packing list spills. A page sets
     --k-print-doc to a QUOTED string before it prints; unset, the left box is
     empty and nothing is drawn. */
  @page{
    size:A4; margin:12mm;
    @bottom-left{ content:var(--k-print-doc, ""); font-size:8pt; color:#3B5254; }
    @bottom-right{ content:counter(page) " / " counter(pages); font-size:8pt; color:#3B5254; }
  }
  @page :first{ margin-top:10mm; }

  html,body{ background:#FFFFFF !important; }
  body::before{ display:none !important; }
  .topbar,.rail,.botnav,.fab,.toaster,.drawer,.drawer-scrim,.tt,.no-print,
  .band,.netbar,.langsheet,canvas{ display:none !important; }
  /* THE HERO PRINTS. Its SENTENCE, at least.
     The hero used to be in the display:none list with the rest of the chrome,
     because it is a 496 px panel with a canvas in it. That threw away the one
     line on the page most worth having on paper: the sheet began with the KPI
     row and never said what the page was or what the situation was. What is
     dropped is the panel, the scrim, the canvas and the buttons; what stays is
     the kicker, the sentence, the lede and the figures, as plain type. */
  .hero{
    display:block !important; min-height:0 !important; overflow:visible;
    border:0; box-shadow:none; border-radius:0;
    background:none !important; background-image:none !important;
  }
  .hero::after,.hero-canvas,.hero-fallback,.glbadge,.hero-acts{ display:none !important; }
  .hero-body{ padding:0 0 6mm; max-width:none; }
  .hero h1{ max-width:none; }
  .hero-figs{ margin-top:4mm; }
  .shell{ display:block; max-width:none; }
  .main{ padding:0; }
  /* A segmented view switcher is a control, and a control on paper is a dead
     button. The view that was on screen is the view that prints. */
  .panel-hd .seg{ display:none; }
  /* The tile row overlaps the hero by -32 px on screen. On paper the hero is
     gone, so that pull lands on whatever the page put above the row and hides
     it: the printed sheet lost its statement of situation behind the tiles. */
  .kpis{ margin-top:0; }
  .sec{ margin-bottom:8mm; }
  details.sec > summary{ padding-right:0; }
  details.sec .sec-mark{ display:none !important; }
  .panel{ box-shadow:none; background:#FFFFFF; border-color:#C9D6D3; backdrop-filter:none; -webkit-backdrop-filter:none; }
  .panel::after{ display:none; }
  .print-only{ display:block !important; }
  .doc{ box-shadow:none; border:0; padding:0; max-width:none; margin:0; }
  a{ color:inherit; text-decoration:none; }
  a[href^="http"]::after{ content:""; }

  body{ font-size:9.5pt; line-height:1.45; color:#08191B; }
  table.k,table.doc-t,table.twin-t{ font-size:9pt; }
  .k-caps,.eyebrow,.panel-hd h2,.panel-hd h3,table.k thead th{ font-size:7pt; }
  h1{ font-size:14pt; } h2{ font-size:12pt; } h3{ font-size:11pt; }
  :lang(th){ line-height:1.55; }

  thead{ display:table-header-group; }
  tfoot{ display:table-footer-group; }
  tr,.doc-sign,.doc-note,.kpi,.banner{ break-inside:avoid; }
  h2,h3{ break-after:avoid; }
  .doc{ break-inside:auto; }
  .doc-sign{ break-before:avoid; }
  table.doc-t{ border-collapse:collapse; }
  .doc-t td,.doc-t th{ border-bottom:0.5pt solid #DCE5E3; }
  /* A4 MINUS THE PAGE MARGIN LAYS OUT AT 703 CSS PX, WHICH IS INSIDE THE
     720 px PHONE BREAKPOINT. Every rule in the `max-width:720px` block
     therefore applies on paper, and one of them mattered: the three signature
     blocks §10.3 requires came out of the printer as three stacked full width
     rules, on every page that prints a bill. The print selection re-asserts
     what the phone breakpoint took away. */
  .doc-sign{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:26px; }
  /* Same bug, same cause, one line lower: the 1080 breakpoint also drops the
     meta grid to two columns, so the eight field header §10.3 specifies as
     four columns on A4 came out of the printer as four tall rows. */
  .doc-meta{ grid-template-columns:repeat(4,minmax(0,1fr)); }
  .tbl-scroll{ overflow:visible; max-height:none; }
  /* A4 minus the margin is 703 px and table.k asks for 1080, so every printed
     table ran off the right edge of the paper and sliced its last column in
     half: VAT THB printed as "VAT T". The screen minimum is a scroll floor,
     not a paper one. */
  table.k{ min-width:0; }
  table.k thead th{ position:static; }
  table.k tbody tr{ content-visibility:visible; }
  /* A control is not information. The select column, the row menu, the
     toolbar, the selection bar and the pager are all things you click, and
     none of them means anything on a sheet of paper. */
  table.k .stick-l,table.k .stick-r,
  .tbl-tools,.selbar,.pager,.tbl-foot .rows-per,.matrix-scroll{ display:none !important; }
  .twin-scroll{ overflow:visible; }
  .twin-scroll table.twin-t{ min-width:0; }

  /* Printing the bill prints the bill, not the application around it. */
  body[data-print-scope="bill"] .main > .sec:not(#sec-print),
  body[data-print-scope="bill"] .main > footer,
  body[data-print-scope="bill"] #sec-print > .sec-hd{ display:none !important; }

  /* Colour is never load bearing on paper: every series also carries a hatch. */
  .chart [data-tex="1"]{ fill:url(#k-tex1); }
  .chart [data-tex="2"]{ fill:url(#k-tex2); }
  .chart [data-tex="3"]{ fill:url(#k-tex3); }
  .chart [data-tex="4"]{ fill:url(#k-tex4); }
}


/* ════════════════════════════════════════════════════════════════════════════
   29  REDUCED MOTION AND FORCED COLOURS
   Reduced motion means reduced, not shorter. The Three.js scenes are not
   built at all; k2-three.js handles that. Here the CSS stops.
   ════════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    animation-delay:0ms !important;
    transition-duration:.001ms !important;
    transition-delay:0ms !important;
    scroll-behavior:auto !important;
  }
  html{ scroll-behavior:auto !important; }
  .skel{ animation:none !important; background:var(--k-surface-3); }
  .stale .pulse{ animation:none !important; }
  .arm-bar i{ animation:none !important; transform:scaleX(1); }
  .hero-canvas,.band-canvas{ display:none; }
}

@media (forced-colors: active){
  .panel,.btn,.ctl,.pill,.banner,.toast{ border:1px solid ButtonBorder; }
  .btn-action{ forced-color-adjust:none; background:Highlight; color:HighlightText; }
  .chart [data-tex="1"]{ fill:url(#k-tex1); }
  .chart [data-tex="2"]{ fill:url(#k-tex2); }
  .chart [data-tex="3"]{ fill:url(#k-tex3); }
  .chart [data-tex="4"]{ fill:url(#k-tex4); }
}

/* ── THE TWO SHORTAGE ROWS AND THE UNIT BESIDE A QUANTITY, 2026-09-06 ──────── */
.shortage-rows{ display:grid; gap:var(--k-3); grid-column:1 / -1; }
.shortage-row .shortage-line{ display:flex; align-items:center; gap:var(--k-2); flex-wrap:wrap; }
.shortage-row .shortage-line .ctl.num{ width:7.5rem; }
.shortage-row .shortage-line .ctl:not(.num){ min-width:9rem; }
.shortage-row .shortage-line .suffix{ font-size:var(--k-t-micro); color:var(--k-ink-3); }
.shortage-row .btn-icon{ min-width:2.25rem; font-size:1.1rem; line-height:1; }
.qty-with-unit{ display:flex; align-items:center; gap:var(--k-2); }
.qty-with-unit .ctl.num{ flex:1 1 auto; }
.qty-with-unit select{ width:auto; }
.c-edit{ width:2.5rem; text-align:right; }
.chk-line{ display:flex; align-items:center; gap:var(--k-2); font-size:var(--k-t-small); cursor:pointer; }
.chk-line input{ width:1.1rem; height:1.1rem; }

/* ── BUILD 36: MOVING BETWEEN PAGES WITHOUT THE BLINK ───────────────────────
   Marc, 2026-10-01: "when we click on any link the top bar and the bar on the
   left ... dont have to reload if possible? other websites dont reload".
   The browser keeps the old page on screen until the new one can paint, then
   swaps the content with a short fade while the top bar and the rail stay
   exactly where they are (they carry the same name on both pages). Browsers
   without cross page transitions simply load the page as before. Nobody who
   asked the system for less motion gets the fade. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
.topbar { view-transition-name: k-topbar; }
.rail { view-transition-name: k-rail; }
.botnav { view-transition-name: k-botnav; }
::view-transition-group(k-topbar), ::view-transition-group(k-rail), ::view-transition-group(k-botnav) { animation: none; }
::view-transition-old(k-topbar), ::view-transition-old(k-rail), ::view-transition-old(k-botnav) { display: none; }
::view-transition-new(k-topbar), ::view-transition-new(k-rail), ::view-transition-new(k-botnav) { animation: none; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 140ms; }
