/* Scalpia POS — native UI kit (Android / Material 3 inspired, Scalpia green). Loaded after style.css; screen sheets
   (till, checkout, shell, lists, staff, office .css) load after this one and only restyle their own screens.
   ─ COMPONENT GUIDE (read this, not the CSS) ─────────────────────────────────────────────────────────────────────────
   Screen rules by screen: /home/mathis/docs/audits/scalpia-pos-mobile-native-benchmark.md (§ 10 = general rules).
   Breakpoints: ONE phone breakpoint = max-width 699px (shell: drawer, app bar, navigation bar, snackbar position; forms,
     editors, lists). ≥ 700px = tablet/desktop (permanent sidebar, dialogs). Legacy max-width:650px rules of style.css are
     restated up to 699px by web/ui/shell.css for the chrome; screen sheets should use 699/700 too (till.mjs phoneWidth()
     still says 650: the till's own layout keeps its tablet panes between 651 and 699px under the phone chrome).
   Tokens: :root of style.css only (tests/design-tokens.test.mjs scans web/ui/*.css too: no literal colour, font size or
   radius outside :root). Kit tokens: --field-bg --tonal-bg/-fg --nav-indicator --state-hover/-press --scrim --touch(48)
   --app-bar-h(56) --field-h(56) --row-h(56) --row-h-2(72) --nav-bar-h(64) --fab-size(56) --avatar(40) --icon(24).
   Type: 16 body/values (--text-lg), 14 secondary (--text-md), 12 labels/captions (--text-xs), 20 titles (--text-xl).
   HELPERS (web/ui/kit.mjs, app.mjs re-exports them to the feature modules through the usual deps):
   field(name,label,value,type,{required,min,max,step,placeholder,inputmode,help,full,options,money,attrs,className})
     text/number/email/tel/date… → .field.tf.tf-input: filled 56px field, small label INSIDE (top), value below.
     money (auto for price/cost/amount/…; options.money true|false) → .tf-money: "Rp" prefix, value right-aligned,
       tabular figures, numeric keyboard. Inline error: .field.invalid + small.field-error-text under the field.
       LIVE SEPARATORS (.tf-live): the <input type=number> keeps the plain value ("35000": formValues(), .value reads,
       min/max validity and tests unchanged); its own text is transparent and span.tf-mirror draws "35.000" (id) /
       "35,000" (en) on top. installKit() (called once by app.mjs) re-draws the mirror on input events and on
       programmatic `input.value = …` writes; an unparsable entry (validity.badInput) shows the raw text (.tf-raw).
       A hand-built money input can opt in: class="tf-live-input" + a sibling span.tf-mirror inside a .tf-live .tf-box.
       groupDigits(raw) → the grouped text; syncMoneyField(input) → redraw one mirror.
     select → .tf-select: same filled 56px box as a text field (label inside at the top, value under it), expand icon
       right; a real <select> (native picker).
     Floating labels: deliberately NOT used. The label always sits small at the top (M3 "populated" state): a resting
       label needs placeholder=" " (forbidden on shiftCash by tests/web-ux) or JS on every value write.
     textarea → .tf-area: same as text, grows with its content.   help → small.field-help under the field.
     checkbox → .switch-field > label.check.switch-row: label (+help) left, Material switch right, row ≥56px tappable.
   switchRow({name,label,help,checked,disabled,attrs,id})   a switch outside field() (data-* driven toggles).
   fieldGroup(title,html,{help,className,attrs})  → section.field-group: subheader (green, 14px) + grouped rows.
   listRow({leading,avatar,title,subtitle,trailing,chevron,action,id,attrs,className}) → .list-row (button when action);
   listGroup(rowsHtml) → .list-group (white surface, hairline dividers).   uiIcon(name) → back close menu chevron expand
     plus cloud cloud-done cloud-off cloud-sync cloud-alert sync edit lock switch-user person store more-vert search
     backspace check globe inbox alert info print share mail chat clock qr settings barcode cart trash percent more
     (horizontal) list grid.   initialsOf(text) → "KL".   .visually-hidden (= .sr-only) → read by TalkBack, not drawn.
   appBar({leading:{icon,action,label}|html, title, subtitle, actions, className, attrs}) → header.app-bar: 56px bar,
     leading icon button, title 20px (+ small subtitle), actions right (icon buttons, or a .text-button "SIMPAN").
     For screens of their own (cart page, sub-page); modals keep their own .modal-head.
     .app-bar.is-page → the same bar at the top of a hand-built page inside .main (outside a modal): bleeds to the
     page edges on a phone and sticks under the shell's top app bar.
   .bottom-bar → sticky bottom action bar (surface, hairline top, safe area): one .primary fills it, text buttons beside.
     .bottom-bar.is-page → same bar on a hand-built page: bleeds to the edges on a phone and sits above the navigation bar.
   --text-money-hero → the big amount size (total due, change, KPI hero); an alias of --text-amount (34 px), no new size.
   statusLine(text, kind ok|warn|error|off) → span.status-line: coloured dot + text ("Online · tersimpan").
   toast(message, error=false | {error, action:{label, run}, duration}) (app.mjs) → snackbar: inverse surface, 14px,
     bottom (above the navigation bar / charge bar on a phone; top-centre on the till ≥ 651px), optional text action
     (button.toast-action, e.g. URUNGKAN = undo), 4.2 s (6 s with an action, 10 s for errors), role status|alert.
   showModal(title,body,handler,{submit,footer,wide,name,autofocus,danger, sheet|dialog|cta}) (app.mjs):
     default → phone: FULL-SCREEN PAGE, app bar = ← back (data-action=close-modal) · title · SUBMIT text action right
       (.modal.is-page.bar-action, no bottom Cancel); ≥700px: centered dialog, footer Cancel (text) + submit (filled).
     sheet:true  → bottom sheet on a phone (action lists, one quick field): handle, ✕ · title · SUBMIT in its header.
     dialog:true → Material dialog everywhere (confirmations): title, text, text buttons right-aligned;
       danger:true paints the submit red (Hapus, Batalkan). Scrim tap closes sheets and dialogs, never pages.
     cta:true    → page whose submit is THE action: full-width filled 48px button pinned at the bottom (pay, shift…).
     footer:false keeps the old contract (no submit; the body brings its own actions).
     The single button[type=submit] stays inside #modal-form; .modal-body scrolls; .modal-footer exists unless footer:false.
   RULES
   • One column on a phone: .form-grid is 1 column < 700px (2 on wider screens); .field.full spans both columns.
   • Editors are pages, not sheets; confirmations are dialogs; only short pickers are sheets. Delete in an editor =
     button.editor-delete (red text row) at the very bottom of the page, confirmed by showModal(…,{dialog,danger}).
   • Lists, not tables, on a phone: table() rows become native rows (< 700px): title 16px, meta line 14px muted,
     amount right, hairline dividers; a row with ONE action button (Edit/Open) is tapped anywhere (the button's
     ::after covers the row, its label is kept for screen readers as .row-action-label) and shows a chevron; entity
     rows (edit-entity, customer-detail) get an initials avatar. Real tables from 700px.
   • Buttons: button = tonal (green tint, no border) · .primary = filled · .ghost/.text-button = text · .danger = tonal
     red · .icon-button = 48px round icon · .fab = floating "+" (bottom right, above the navigation bar);
     .fab.extended = icon + label. A screen's single main action at the bottom: .primary.block (full width, 48px).
   • Chrome (web/ui/shell.css): .topbar = 56px app bar (menu icon, title 20px, icon actions, 40px avatar); sync state =
     .sync-status cloud icon, muted when all is well (.is-ok), coloured only on exception (.is-warn pending + count badge,
     .is-error, .is-off offline); label visually hidden < 900px; offline = thin .offline-bar strip under the app bar on
     every page; phone drawer header (.drawer-head) = operator avatar/name/role (switch), lock, store, sync status line;
     .tab-bar = navigation bar (icon in a pill indicator + label);
     .tabs/.subnav-tabs = scrollable tabs with an underline; .chip / .report-ranges button = filter chips
     (aria-pressed=true = selected); phone drawer = light navigation drawer with pill-shaped active item.
   • Surfaces: page --bg (light grey), lists/cards --surface (white); hairline dividers (--border) instead of boxes;
     --radius-lg only for cards, sheets and dialogs, never for inputs. Touch targets ≥ 48px (--touch).
   • Snackbar = toast(): see above. :hover styles live in @media(hover:hover) only.
   • Motion: pages fade in, sheets rise (no moving page boxes); everything stops under prefers-reduced-motion.
   • .modal-body padding = var(--modal-pad) (24px, 16px on a phone): offset sticky bars with calc(-1 * var(--modal-pad)).
   ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── */

/* ── 1. Buttons ───────────────────────────────────────────────────────────────────────────────────────────────────── */
button{border-color:transparent;background:var(--tonal-bg);color:var(--tonal-fg);border-radius:var(--radius-md)}
/* Variants at the specificity of a bare `button` (:where): any screen class (.pin-key, .item-card…) wins over them. */
button:where(.primary){background:var(--primary);border-color:var(--primary);color:var(--on-primary)}
button:where(.ghost,.text-button){background:transparent;border-color:transparent;color:var(--primary)}
.sidebar :where(button.ghost){color:inherit}
button:where(.danger){background:var(--danger-bg);border-color:transparent;color:var(--danger-fg)}
button:where(.block){display:flex;width:100%;min-height:var(--touch);align-items:center;justify-content:center}
.icon-button{place-items:center;width:var(--touch);height:var(--touch);min-width:var(--touch);min-height:var(--touch);padding:0;border:0;border-radius:50%;background:transparent;color:var(--text-muted);position:relative}
.icon-button svg,.ui-icon{width:var(--icon);height:var(--icon);flex:none}
@media(max-width:699px){button:where(.primary){min-height:var(--touch)}}

/* FAB: the "add" of a list screen, bottom right, above the navigation bar. */
.fab{position:fixed;right:var(--space-4);bottom:calc(var(--space-4) + env(safe-area-inset-bottom));z-index:15;display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);min-width:var(--fab-size);height:var(--fab-size);padding:0 var(--space-4);border:0;border-radius:var(--radius-lg);background:var(--primary);color:var(--on-primary);box-shadow:var(--shadow-2);font-size:var(--text-md);font-weight:var(--weight-semibold)}
.fab:not(.extended){width:var(--fab-size);padding:0}
@media(max-width:699px){html:not([data-view=pos]) .fab{bottom:calc(var(--nav-bar-h) + var(--space-4) + env(safe-area-inset-bottom))}}

/* ── 2. Fields ────────────────────────────────────────────────────────────────────────────────────────────────────── */
.form-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--space-4)}
.field{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted);font-weight:var(--weight-medium)}
.field.full,.form-grid>.field-group{grid-column:1/-1}
.field small{font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted);font-weight:var(--weight-regular)}
.field-help,.field>small{display:block;padding:0 var(--space-4);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted);font-weight:var(--weight-regular)}
/* Text field: filled container, label inside at the top, value under it. */
.tf-box{position:relative;display:flex;align-items:stretch;min-height:var(--field-h);background:var(--field-bg);border-radius:var(--radius-sm) var(--radius-sm) 0 0;box-shadow:inset 0 -1px 0 var(--field-line)}
.tf-box:focus-within{box-shadow:inset 0 -2px 0 var(--primary)}
.tf-box>label{position:absolute;top:var(--space-2);left:var(--space-4);right:var(--space-4);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;pointer-events:none;font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted);font-weight:var(--weight-medium)}
.tf-box:focus-within>label{color:var(--primary)}
.tf-box>:is(input,select,textarea){flex:1 1 auto;width:100%;min-width:0;margin:0;border:0;background:transparent;padding:calc(var(--space-2) + var(--leading-xs)) var(--space-4) var(--space-2);font-size:var(--text-lg);line-height:var(--leading-lg);color:var(--text);font-weight:var(--weight-regular);appearance:none;-webkit-appearance:none}
.tf-box>input::placeholder,.tf-box>textarea::placeholder{color:var(--text-muted)}
.tf-box>input[readonly]{color:var(--text-muted)}
.tf-box>:is(input,select,textarea):disabled{color:var(--text-muted);cursor:default}
.tf-box>input[aria-invalid=true],.tf-box:has(:user-invalid){box-shadow:inset 0 -2px 0 var(--danger)}
/* Money: currency sign in front of the value, drawn over the input's left padding (the focus ring wraps the whole field). */
.tf-money .tf-prefix{position:absolute;left:var(--space-4);top:calc(var(--space-2) + var(--leading-xs));bottom:var(--space-2);display:flex;align-items:center;pointer-events:none;font-size:var(--text-lg);line-height:var(--leading-lg);color:var(--text-muted)}
.tf-money .tf-box>input{padding-left:var(--space-10);text-align:right;font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums}
/* Live thousands separators: the input text is transparent, .tf-mirror draws the grouped value in the same place. */
.tf-mirror{position:absolute;top:calc(var(--space-2) + var(--leading-xs));bottom:var(--space-2);right:var(--space-4);left:var(--space-10);display:flex;align-items:center;justify-content:flex-end;overflow:hidden;white-space:nowrap;pointer-events:none;font-size:var(--text-lg);line-height:var(--leading-lg);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;color:var(--text)}
.tf-live .tf-box>input{-webkit-text-fill-color:transparent;caret-color:var(--text)}
.tf-live .tf-box>input::selection{-webkit-text-fill-color:transparent}
.tf-live .tf-box>input:is([readonly],:disabled)~.tf-mirror{color:var(--text-muted)}
.tf-live.tf-raw .tf-box>input{-webkit-text-fill-color:currentColor}
.tf-live.tf-raw .tf-mirror{display:none}
.tf-money .tf-box>input{-moz-appearance:textfield}
.tf-money .tf-box>input::-webkit-inner-spin-button,.tf-money .tf-box>input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
/* Select: the same filled box as a text field (label inside at the top, value under it) with an expand icon. */
.tf-select .tf-box>select{padding-right:var(--space-10);cursor:pointer;text-overflow:ellipsis}
.tf-select .tf-trailing{position:absolute;right:var(--space-3);top:50%;transform:translateY(-50%);pointer-events:none;color:var(--text-muted)}
.tf-select .tf-box:focus-within .tf-trailing{color:var(--primary)}
/* Textarea grows with its content. */
.tf-area .tf-box>textarea{min-height:var(--field-h);resize:none;field-sizing:content;max-height:240px}
/* Fields built by hand (a .field with its own <label> and controls): label above, controls filled like the kit. */
.field:not(.tf):not(.switch-field)>label{font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted);font-weight:var(--weight-medium)}
.field:not(.tf) :is(input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]):not([type=color]),select,textarea):where(:not(.tf-box>*)),select.input{width:100%;min-width:0;min-height:var(--touch);padding:var(--space-3) var(--space-4);border:0;border-radius:var(--radius-sm) var(--radius-sm) 0 0;background:var(--field-bg);box-shadow:inset 0 -1px 0 var(--field-line);color:var(--text);font-size:var(--text-lg);font-weight:var(--weight-regular)}
.field:not(.tf) textarea:where(:not(.tf-box>*)){resize:vertical;min-height:88px}
/* Switch row (checkbox): label + help left, switch right, the whole row tappable. */
.check{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);min-height:var(--row-h);padding:var(--space-2) 0;font-size:var(--text-lg);line-height:var(--leading-lg);color:var(--text);font-weight:var(--weight-regular);cursor:pointer}
.switch-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.switch-text .field-help{padding:0}
.check input[type=checkbox]{order:2;appearance:none;-webkit-appearance:none;position:relative;flex:none;width:52px;height:32px;margin:0;border-radius:var(--radius-full);background:var(--switch-off-track);box-shadow:inset 0 0 0 2px var(--switch-off-thumb);accent-color:var(--primary);cursor:pointer;transition:background var(--dur-fast),box-shadow var(--dur-fast)}
.check input[type=checkbox]::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:16px;border-radius:50%;background:var(--switch-off-thumb);transition:transform var(--dur-fast) var(--ease),width var(--dur-fast),height var(--dur-fast),top var(--dur-fast),left var(--dur-fast)}
.check input[type=checkbox]:checked{background:var(--switch-on-track);box-shadow:none}
.check input[type=checkbox]:checked::before{top:4px;left:4px;width:24px;height:24px;transform:translateX(20px);background:var(--switch-on-thumb)}
.check input[type=checkbox]:disabled{opacity:.45;cursor:default}
.check:has(input:disabled){cursor:default}
.switch-field+.switch-field .check,.form-grid>.switch-field .check{border-bottom:1px solid var(--border)}
@media(max-width:699px){
 .form-grid{grid-template-columns:minmax(0,1fr);gap:var(--space-3)}
 .form-grid>.switch-field{margin:0}
}

/* Inline validation: .field.invalid (or aria-invalid on the control) + .field-error-text under the field. */
.field.invalid .tf-box{box-shadow:inset 0 -2px 0 var(--danger)}
.field.invalid .tf-box>label,.field-error-text{color:var(--danger-fg)}
.field-error-text{display:block;padding:0 var(--space-4);font-size:var(--text-xs);line-height:var(--leading-xs)}
/* Delete at the very bottom of an editor page: red text row ("Hapus item"), confirmed by a danger dialog. */
.editor-delete{display:flex;align-items:center;width:100%;min-height:var(--touch);margin-top:var(--space-6);padding:0 var(--space-4);border:0;border-top:1px solid var(--border);border-radius:0;background:transparent;color:var(--danger-fg);font-size:var(--text-md);font-weight:var(--weight-semibold)}

/* ── 3. Groups and lists ──────────────────────────────────────────────────────────────────────────────────────────── */
.field-group{display:flex;flex-direction:column;margin:0 0 var(--space-2)}
.field-group-title{padding:var(--space-4) 0 var(--space-2);font-size:var(--text-md);line-height:var(--leading-md);font-weight:var(--weight-semibold);letter-spacing:0;color:var(--primary)}
.field-group-help{padding:0 0 var(--space-2);font-size:var(--text-md);line-height:var(--leading-md);color:var(--text-muted)}
.field-group-body{display:flex;flex-direction:column;gap:var(--space-3)}
.field-group+.field-group{border-top:1px solid var(--border)}
.list-group{display:flex;flex-direction:column;background:var(--surface)}
.list-row{display:flex;align-items:center;gap:var(--space-4);width:100%;min-height:var(--row-h);padding:var(--space-2) var(--space-4);border:0;border-radius:0;background:transparent;color:var(--text);text-align:left;font-weight:var(--weight-regular);box-shadow:inset 0 -1px 0 var(--border)}
.list-row:last-child{box-shadow:none}
button.list-row:active{background:var(--state-press);transform:none}
.list-leading{flex:none;display:grid;place-items:center;width:var(--avatar);height:var(--avatar);color:var(--text-muted)}
.list-leading img{width:100%;height:100%;object-fit:cover;border-radius:var(--radius-sm)}
.list-avatar{border-radius:50%;background:var(--tonal-bg);color:var(--tonal-fg);font-size:var(--text-md);font-weight:var(--weight-semibold)}
.list-text{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}
.list-title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--text-lg);line-height:var(--leading-lg);font-weight:var(--weight-medium)}
.list-subtitle{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--text-md);line-height:var(--leading-md);color:var(--text-muted)}
.list-trailing{flex:none;font-size:var(--text-md);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;color:var(--text)}
.list-chevron{flex:none;color:var(--text-muted);margin-right:calc(-1 * var(--space-1))}

/* ── 4. Modals: centered dialog ≥ 700px; page / sheet / dialog / cta on a phone ───────────────────────────────────── */
.modal-backdrop{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-6);background:var(--scrim)}
.modal{--modal-pad:var(--space-6);display:flex;flex-direction:column;width:560px;max-width:100%;max-height:92vh;overflow:hidden;border:0;border-radius:var(--radius-lg);background:var(--surface);box-shadow:var(--shadow-3)}
.modal.wide{width:850px}
.sheet-handle{display:none}
.modal-head{flex-shrink:0;display:flex;align-items:center;gap:var(--space-2);min-height:var(--app-bar-h);padding:var(--space-3) var(--space-3) var(--space-3) var(--space-6);border-bottom:1px solid var(--border)}
.modal-head h2{order:1;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:var(--text-xl);line-height:var(--leading-xl);font-weight:var(--weight-semibold);letter-spacing:0}
.modal-close{order:2;flex:none;display:grid;place-items:center;width:var(--touch);height:var(--touch);min-width:var(--touch);min-height:var(--touch);padding:0;border:0;border-radius:50%;background:transparent;color:var(--text-muted)}
.modal-back-icon{display:none}
.modal-body{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain;padding:var(--modal-pad)}
.modal-footer{flex-shrink:0;display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-6);border-top:1px solid var(--border);background:var(--surface)}
.modal-footer button{min-height:var(--touch);padding:0 var(--space-6)}
/* Dialog (confirmations): no dividers, text actions right-aligned. */
.modal.is-dialog{width:420px}
.modal.is-dialog .modal-head{border-bottom:0;padding:var(--space-4) var(--space-3) 0 var(--space-6)}
.modal.is-dialog .modal-body{padding:var(--space-2) var(--space-6) var(--space-4);color:var(--text-muted)}
.modal.is-dialog .modal-footer{border-top:0;padding:0 var(--space-4) var(--space-4)}
.modal.is-dialog .modal-footer button{padding:0 var(--space-4);background:transparent;color:var(--primary)}
.modal.is-dialog .modal-footer button.danger{color:var(--danger-fg)}
@keyframes page-in{from{opacity:0}to{opacity:1}}
@keyframes sheet-in{from{transform:translateY(24px);opacity:0}to{transform:none;opacity:1}}
@media(max-width:699px){
 .modal-backdrop{padding:0;align-items:stretch}
 .modal,.modal.wide{--modal-pad:var(--space-4);width:100%;max-width:none;height:100dvh;max-height:none;border-radius:0;box-shadow:none}
 .modal.is-page{animation:page-in var(--dur) var(--ease)}
 .modal-head{gap:var(--space-1);padding:var(--space-1) var(--space-2);border-bottom:1px solid var(--border)}
 .modal-head h2{font-size:var(--text-xl)}
 .modal.is-page .modal-close{order:0}
 .modal.is-page .modal-back-icon{display:block}
 .modal.is-page .modal-close-icon{display:none}
 .modal.is-page .modal-head h2{padding-left:var(--space-1)}
 .modal-body:last-child{padding-bottom:calc(var(--modal-pad) + env(safe-area-inset-bottom))}
 .modal-footer{padding:var(--space-3) var(--space-4) calc(var(--space-3) + env(safe-area-inset-bottom))}
 .modal-footer button{flex:1 1 0;min-height:var(--touch)}
 .modal.is-page .modal-cancel{display:none}
 /* Page with its submit in the app bar: the form is flattened into the modal grid so the footer sits on row 1. */
 .modal.bar-action{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-rows:auto minmax(0,1fr)}
 .modal.bar-action #modal-form{display:contents}
 .modal.bar-action .modal-head{grid-column:1;grid-row:1}
 .modal.bar-action .modal-body{grid-column:1/-1;grid-row:2}
 .modal.bar-action .modal-footer{grid-column:2;grid-row:1;padding:0 var(--space-2) 0 0;border-top:0;border-bottom:1px solid var(--border)}
 .modal.bar-action .modal-footer button[type=submit]{flex:none;max-width:50vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0 var(--space-3);border:0;background:transparent;color:var(--primary);font-size:var(--text-md);font-weight:var(--weight-bold);letter-spacing:.5px;text-transform:uppercase}
 .modal.bar-action .modal-footer button[type=submit]:hover{background:var(--state-hover)}
 /* Bottom sheet. */
 .modal-backdrop.is-sheet{align-items:flex-end}
 .modal.is-sheet{height:auto;max-height:calc(100dvh - var(--space-4));border-radius:var(--radius-lg) var(--radius-lg) 0 0;animation:sheet-in var(--dur) var(--ease)}
 .modal.is-sheet .sheet-handle{display:block;flex-shrink:0;width:32px;height:4px;margin:var(--space-2) auto 0;border-radius:var(--radius-sm);background:var(--border-strong)}
 .modal.is-sheet .modal-head{border-bottom:0;min-height:var(--app-bar-h);padding:0 var(--space-2)}
 .modal.is-sheet .modal-close{order:0}
 .modal.is-sheet .modal-cancel{display:none}
 .modal.is-sheet.bar-action{grid-template-rows:auto auto minmax(0,1fr)}
 .modal.is-sheet.bar-action .sheet-handle{grid-column:1/-1;grid-row:1}
 .modal.is-sheet.bar-action .modal-head{grid-row:2}
 .modal.is-sheet.bar-action .modal-footer{grid-row:2;border-bottom:0}
 .modal.is-sheet.bar-action .modal-body{grid-row:3}
 /* Dialog. */
 .modal-backdrop.is-dialog{align-items:center;padding:var(--space-6)}
 .modal.is-dialog{height:auto;max-height:calc(100dvh - 2 * var(--space-6));border-radius:var(--radius-lg)}
 .modal.is-dialog .modal-head{padding:var(--space-3) var(--space-2) 0 var(--space-6)}
 .modal.is-dialog .modal-footer button{flex:none}
 /* Page whose submit is the action (cta): full-width filled button at the bottom, no Cancel. */
 .modal.cta .modal-footer{border-top:1px solid var(--border)}
 .modal.cta .modal-footer>button[type=submit]{grid-column:1/-1}
}

/* ── 5. Tables become native lists on a phone ─────────────────────────────────────────────────────────────────────── */
@media(max-width:699px){
 .panel:has(>.as-cards){border-left:0;border-right:0;border-radius:0;margin-left:calc(-1 * var(--space-4));margin-right:calc(-1 * var(--space-4))}
 .as-cards{background:var(--surface)}
 .as-cards table,.as-cards tbody{display:block}
 .as-cards thead,.as-cards td[data-cell=bar]{display:none}
 .as-cards tr{position:relative;display:flex;flex-wrap:wrap;align-items:center;column-gap:var(--space-3);row-gap:2px;min-height:var(--row-h-2);padding:var(--space-3) var(--space-4);border:0;box-shadow:inset 0 -1px 0 var(--border)}
 .as-cards tr:last-child{box-shadow:none}
 .as-cards tr:after{content:'';order:3;flex-basis:100%;height:0}
 .as-cards td,.as-cards tbody th[data-cell]{padding:0;border:0;white-space:normal;min-width:0;background:transparent;position:static;box-shadow:none;font-size:var(--text-md);line-height:var(--leading-md)}
 .as-cards [data-cell=select]{order:0;display:flex;align-items:center;justify-content:center;min-width:var(--touch);min-height:var(--touch);margin:calc(-1 * var(--space-2)) 0 calc(-1 * var(--space-2)) calc(-1 * var(--space-3));position:relative;z-index:1}
 .as-cards [data-cell=title]{order:1;flex:1 1 0;overflow-wrap:anywhere;font-size:var(--text-lg);line-height:var(--leading-lg);font-weight:var(--weight-medium);color:var(--text)}
 .as-cards [data-cell=title] small{display:block;margin-top:2px;font-size:var(--text-md);line-height:var(--leading-md);font-weight:var(--weight-regular);color:var(--text-muted)}
 .as-cards [data-cell=title][data-initial]{display:grid;grid-template-columns:var(--avatar) minmax(0,1fr);column-gap:var(--space-4);align-items:center}
 .as-cards [data-cell=title][data-initial]::before{content:attr(data-initial);grid-row:1/span 2;display:grid;place-items:center;width:var(--avatar);height:var(--avatar);border-radius:50%;background:var(--tonal-bg);color:var(--tonal-fg);font-size:var(--text-md);font-weight:var(--weight-semibold)}
 .as-cards [data-cell=title][data-initial] small{grid-column:2;margin-top:0}
 .as-cards [data-cell=amount]{order:2;text-align:right;font-size:var(--text-lg);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;color:var(--text)}
 .as-cards [data-cell=action]{order:2;display:flex;align-items:center}
 .as-cards [data-cell=action] button{min-height:40px;position:relative;z-index:1}
 .as-cards [data-cell=meta]{order:4;color:var(--text-muted);font-size:var(--text-md)}
 .as-cards [data-cell=title][data-initial]~[data-cell=meta]:not([data-cell=meta]~[data-cell=meta]){margin-left:calc(var(--avatar) + var(--space-4))}
 .as-cards [data-cell=select]~[data-cell=title]~[data-cell=meta]:not([data-cell=meta]~[data-cell=meta]){margin-left:calc(var(--touch) - var(--space-3) + var(--space-3))}
 .as-cards [data-cell=select]~[data-cell=title][data-initial]~[data-cell=meta]:not([data-cell=meta]~[data-cell=meta]){margin-left:calc(var(--touch) + var(--avatar) + var(--space-4))}
 .as-cards [data-cell=meta] small{display:inline;margin:0}
 .as-cards [data-cell=meta]:not([data-plain]):before{content:attr(data-label) ' '}
 .as-cards [data-cell=meta]~[data-cell=meta]{margin-left:calc(var(--space-1) - var(--space-3))}
 .as-cards [data-cell=meta]~[data-cell=meta][data-plain]:before{content:'· '}
 .as-cards [data-cell=meta]~[data-cell=meta]:not([data-plain]):before{content:'· ' attr(data-label) ' '}
 /* The row's only action (Edit, Open) covers the row: tap anywhere; a chevron replaces its label. */
 .as-cards [data-cell=action]>button:only-child{position:static;display:grid;place-items:center;width:var(--icon);min-width:var(--icon);min-height:var(--icon);height:var(--icon);padding:0;border:0;border-radius:0;background:transparent;color:var(--text-muted)}
 .as-cards [data-cell=action]>button:only-child::before{content:'';width:var(--icon);height:var(--icon);background:currentColor;-webkit-mask:var(--mask-chevron) center/contain no-repeat;mask:var(--mask-chevron) center/contain no-repeat}
 .as-cards [data-cell=action]>button:only-child::after{content:'';position:absolute;inset:0;z-index:0}
 .as-cards tr:has([data-cell=action]>button:only-child):active{background:var(--state-press)}
 .as-cards .row-action-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
 .as-cards [data-cell] :is(a,input,label,select,.tag):not([data-cell=action] *){position:relative;z-index:1}
}

/* ── 6. Chrome: top app bar, navigation bar, drawer, tabs, chips ──────────────────────────────────────────────────── */
.sync-status{display:inline-flex;align-items:center;gap:var(--space-2);width:auto;padding:0 var(--space-3);border-radius:var(--radius-full);color:var(--text-muted);font-size:var(--text-sm);font-weight:var(--weight-semibold);white-space:nowrap}
.sync-status .dot{display:none;position:absolute;top:10px;left:30px;width:8px;height:8px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 2px var(--surface)}
.sync-status .dot.offline{display:block}
.sync-status.offline-pill{background:var(--warning-bg);color:var(--warning-fg)}
.sync-status.is-syncing svg{color:var(--primary)}
.sync-button{display:inline-grid}
.operator-pill .avatar{width:var(--avatar);height:var(--avatar);font-size:var(--text-sm)}
@media(max-width:899px){
 .sync-status{width:var(--touch);padding:0;justify-content:center}
 .sync-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
 .sync-status .dot{left:auto;top:10px;right:10px}
}
/* Phone chrome (app bar, navigation bar, drawer): web/ui/shell.css. */
/* Tabs: scrollable, underline indicator. */
.tabs,.subnav-tabs{display:flex;gap:0;overflow-x:auto;scrollbar-width:none;padding:0;border-radius:0;background:transparent;box-shadow:inset 0 -1px 0 var(--border)}
.tabs::-webkit-scrollbar,.subnav-tabs::-webkit-scrollbar{display:none}
.tabs button,.subnav-tabs button{position:relative;flex:none;min-height:var(--touch);padding:0 var(--space-4);border:0;border-radius:0;background:transparent;box-shadow:none;color:var(--text-muted);font-size:var(--text-md);font-weight:var(--weight-semibold);white-space:nowrap}
.tabs button.active,.subnav-tabs button.active{background:transparent;box-shadow:none;color:var(--primary)}
.tabs button.active:after,.subnav-tabs button.active:after{content:'';position:absolute;left:var(--space-4);right:var(--space-4);bottom:0;height:3px;background:var(--primary)}
@media(max-width:699px){.subnav-tabs{width:auto;margin:0 calc(-1 * var(--space-4))}.subnav{padding:0 var(--space-4);background:var(--surface)}.subnav+.main{padding-top:var(--space-4)}}
/* Filter chips. */
.chip,.report-ranges button{display:inline-flex;align-items:center;gap:var(--space-2);min-height:40px;padding:0 var(--space-4);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);font-size:var(--text-md);font-weight:var(--weight-medium);white-space:nowrap}
.chip[aria-pressed=true],.chip.active,.report-ranges button[aria-pressed=true]{background:var(--tonal-bg);border-color:var(--tonal-bg);color:var(--tonal-fg);font-weight:var(--weight-semibold)}
.report-ranges{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
@media(max-width:699px){.dashboard-filters .report-ranges button{padding:0 var(--space-3)}}

/* Snackbar (toast()): inverse surface, 14px, one or two lines, optional text action; positions in web/ui/shell.css. */
.toast{display:flex;align-items:center;gap:var(--space-2);min-height:var(--touch);padding:var(--space-3) var(--space-4);border-radius:var(--radius-sm);background:var(--snackbar-bg);color:var(--snackbar-fg);box-shadow:var(--shadow-2);font-size:var(--text-md);line-height:var(--leading-md)}
.toast.error-toast{background:var(--snackbar-error-bg)}
.toast-text{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.toast.has-action{pointer-events:auto;padding-right:var(--space-2)}
.toast-action{flex:none;min-height:var(--touch);margin:calc(-1 * var(--space-2)) 0;padding:0 var(--space-3);border:0;border-radius:var(--radius-sm);background:transparent;color:var(--snackbar-action);font-size:var(--text-md);font-weight:var(--weight-semibold)}
/* App bar of a screen of its own (appBar()), sticky bottom action bar (.bottom-bar), status line (statusLine()). */
.app-bar{position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:var(--space-1);min-height:var(--app-bar-h);padding:0 var(--space-1);background:var(--surface);border-bottom:1px solid var(--border)}
.app-bar-leading{color:var(--text)}
.app-bar-title{flex:1 1 auto;min-width:0;padding:0 var(--space-3)}
.app-bar-leading+.app-bar-title{padding-left:var(--space-1)}
.app-bar-title h2{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--text-xl);line-height:var(--leading-xl);font-weight:var(--weight-semibold);letter-spacing:0}
.app-bar-title small{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted)}
.app-bar-actions{flex:none;display:flex;align-items:center}
.app-bar-actions .text-button{min-height:var(--touch);padding:0 var(--space-3);font-size:var(--text-md);font-weight:var(--weight-bold);letter-spacing:.5px;text-transform:uppercase}
.bottom-bar{position:sticky;bottom:0;z-index:2;display:flex;align-items:center;gap:var(--space-2);padding:var(--space-3) var(--space-4) calc(var(--space-3) + env(safe-area-inset-bottom));background:var(--surface);border-top:1px solid var(--border)}
.bottom-bar>.primary{flex:1 1 auto;min-height:var(--touch)}
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.app-bar.is-page{margin:0 0 var(--space-2)}
.bottom-bar.is-page{margin-top:var(--space-4)}
@media(max-width:699px){
 .app-bar.is-page{top:var(--app-bar-h);margin:calc(-1 * var(--space-4)) calc(-1 * var(--space-4)) var(--space-2)}
 .bottom-bar.is-page{margin:var(--space-4) calc(-1 * var(--space-4)) 0}
 html:not([data-view=pos]) .bottom-bar.is-page{bottom:calc(var(--nav-bar-h) + env(safe-area-inset-bottom));padding-bottom:var(--space-3)}
}
.status-line{display:inline-flex;align-items:center;gap:var(--space-2);min-width:0;font-size:var(--text-md);line-height:var(--leading-md);color:var(--text-muted)}
.status-line>span:last-child{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.status-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--status-ok)}
.status-line.is-warn .status-dot{background:var(--accent)}
.status-line.is-error .status-dot{background:var(--danger)}
.status-line.is-off .status-dot{background:var(--warning-fg)}

/* ── 7. Surfaces and typography on a phone ────────────────────────────────────────────────────────────────────────── */
@media(max-width:699px){
 .panel{border-radius:var(--radius-lg)}
 .search{min-height:var(--touch);border:0;border-radius:var(--radius-full);background:var(--field-bg);font-size:var(--text-lg)}
}

/* Hover only where a pointer hovers: on a touch screen it would stick after the tap. */
@media(hover:hover){
 button:hover{background:color-mix(in srgb,var(--tonal-bg),var(--text) 6%)}
 button:where(.primary):hover{background:var(--primary-hover)}
 button:where(.ghost,.text-button):hover{background:var(--state-hover)}
 .icon-button:hover{background:var(--state-hover)}
 .fab:hover{background:var(--primary-hover)}
 .editor-delete:hover{background:var(--state-hover)}
 button.list-row:hover{background:var(--state-hover)}
 .modal-close:hover{background:var(--state-hover)}
 .modal.is-dialog .modal-footer button:hover{background:var(--state-hover)}
 .tabs button:hover,.subnav-tabs button:hover{background:var(--state-hover)}
 .chip:hover,.report-ranges button:hover{background:var(--state-hover)}
 .toast-action:hover{background:var(--snackbar-hover)}
}
