fix(pos): dead discount toggle + stale discount figure, lift dark palette

Two reported symptoms that turned out to be one bug, plus the dark
mode the user described as "too black".

The `hidden` attribute does nothing on the discount controls

`hidden` is implemented by the UA stylesheet as `[hidden]{display:none}`.
Any author rule that sets `display` outranks it — and `.totals` and
`.discount-body` both set `display:flex`. So `el.hidden = true` was a
no-op on both:

  - the discount tier panel could not be collapsed, making "Add
    discount" look like a dead control;
  - the Discount totals row stayed on screen after the discount was
    cleared, still showing the last figure (R 98.37) under a total
    that no longer contained it. The JS only ever set `hidden` and
    never cleared the text, so the operator had no way to tell stale
    text from a live one.

`.cat-menu` and `.cat-backdrop` already carried per-element
`[hidden]{display:none}` overrides for exactly this reason; the two
discount elements were missed. Replaced with one global rule so the
whole class is covered rather than relying on each element to
remember. The clear path now also blanks the amount.

Verified statically rather than in a browser: jsdom does not model
the UA-vs-author cascade and reported the broken and fixed cases
identically, so the cascade was checked by resolving which rules
apply. Regression test fails without the fix.

Dark palette

The POS canvas was #0f0f0f (L* 4.3 — effectively black) with
surfaces only 3-4 L* above it, mixing a warm #1c1416 against a
neutral #181818 in the same UI. That near-black base plus two
competing surface tones is what read as muddy. Replaced with a
single cool-neutral ramp with four real steps, and menus/modals now
sit on a `--raised` layer instead of reusing the surface colour.

`#717171` was the secondary text colour in 25 places and measured
3.93:1 on the old canvas — below WCAG AA. It now measures 6.49:1.
Both the text greys and the surfaces were hardcoded per component
across seven POS pages; they are now tokens, with the dark block
rebinding them.

The shared `.dark` block in themes.css (drives all 24 themes) sat at
oklch(0.15) ≈ L* 3.1. Lifted +0.04 across the ramp, preserving its
shape and separation; muted-foreground goes 6.32:1 → 5.95:1, still
above AA. Cursor/Supabase, which override it darker, were at L* 1.3
and lifted too.

Service worker cache bumped to pos-v36 — pos.html and pos-app.js are
served cache-first, so without the bump the tills keep serving the
broken files.

3863 tests pass (3 new); build clean.
This commit is contained in:
Dave 2026-10-12 23:17:57 +02:00
parent e230d17f0c
commit 0377c51602
11 changed files with 218 additions and 131 deletions

View file

@ -93,9 +93,9 @@ button:focus-visible,input:focus-visible,select:focus-visible{outline:0;box-shad
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--bg:#0f0f0f;--surface:#1c1416;--surface-2:#181818;--surface-card:#181818;
--fg:#f0f0f0;--ink-soft:#a0a0a0;--ink-faint:#777;
--muted:#2a2a2a;--border:#3a2429;
--bg:#161619;--surface:#1e1e23;--surface-2:#1e1e23;--surface-card:#26262d;
--fg:#f2f2f5;--ink-soft:#a0a0a0;--ink-faint:#8e8e9a;
--muted:#303039;--border:#3b3b46;
--accent-soft:#2a1a1a;--accent-2:#3a2429;
--ok-soft:#1a2e22;--warn-soft:#2a1a1a;--danger-soft:#2a1a1a;--danger-2:#3a2429;
}
@ -137,10 +137,10 @@ button:focus-visible,input:focus-visible,select:focus-visible{outline:0;box-shad
:root:not([data-theme="light"]) .skeleton{color:var(--ink-faint)}
}
:root[data-theme="dark"]{
--bg:#0f0f0f;--surface:#1c1416;--surface-2:#181818;--surface-card:#181818;
--fg:#f0f0f0;--ink-soft:#a0a0a0;--ink-faint:#777;
--muted:#2a2a2a;--border:#3a2429;
--accent-soft:#2a1a1a;--accent-2:#3a2429;
--bg:#161619;--surface:#1e1e23;--surface-2:#1e1e23;--surface-card:#26262d;
--fg:#f2f2f5;--ink-soft:#a0a0a0;--ink-faint:#8e8e9a;
--muted:#303039;--border:#3b3b46;
--accent-soft:#2b1f24;--accent-2:#3f2b33;
--ok-soft:#1a2e22;--warn-soft:#2a1a1a;--danger-soft:#2a1a1a;--danger-2:#3a2429;
background:var(--bg);color:var(--fg);
}

View file

@ -92,7 +92,7 @@ button:focus-visible,input:focus-visible,select:focus-visible{outline:0;box-shad
.rf-actions .danger{background:#c92a2a;color:#fff}
.rf-actions .danger:disabled{opacity:.6}
:root[data-theme="dark"] .sale-row .nm b em{color:#ff6680}
:root[data-theme="dark"] .rf-sheet{background:#181818;box-shadow:0 20px 60px rgba(0,0,0,.6)}
:root[data-theme="dark"] .rf-sheet{background:var(--surface);box-shadow:0 20px 60px rgba(0,0,0,.6)}
:root[data-theme="dark"] .rf-head h3 small{color:#888}
:root[data-theme="dark"] .rf-chip{border-color:var(--muted);color:#888}
:root[data-theme="dark"] .rf-warn{background:#2a1a1a;border-color:#5a2429;color:#ff9db0}
@ -112,9 +112,9 @@ button:focus-visible,input:focus-visible,select:focus-visible{outline:0;box-shad
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--bg:#0f0f0f;--surface:#1c1416;--surface-2:#181818;--surface-card:#181818;
--fg:#f0f0f0;--ink-soft:#a0a0a0;--ink-faint:#777;
--muted:#2a2a2a;--border:#3a2429;
--bg:#161619;--surface:#1e1e23;--surface-2:#1e1e23;--surface-card:#26262d;
--fg:#f2f2f5;--ink-soft:#a0a0a0;--ink-faint:#8e8e9a;
--muted:#303039;--border:#3b3b46;
--accent-soft:#2a1a1a;--accent-2:#3a2429;
--danger-soft:#2a1a1a;--danger-2:#3a2429;
}
@ -140,10 +140,10 @@ button:focus-visible,input:focus-visible,select:focus-visible{outline:0;box-shad
:root:not([data-theme="light"]) .empty p{color:var(--ink-soft)}
}
:root[data-theme="dark"]{
--bg:#0f0f0f;--surface:#1c1416;--surface-2:#181818;--surface-card:#181818;
--fg:#f0f0f0;--ink-soft:#a0a0a0;--ink-faint:#777;
--muted:#2a2a2a;--border:#3a2429;
--accent-soft:#2a1a1a;--accent-2:#3a2429;
--bg:#161619;--surface:#1e1e23;--surface-2:#1e1e23;--surface-card:#26262d;
--fg:#f2f2f5;--ink-soft:#a0a0a0;--ink-faint:#8e8e9a;
--muted:#303039;--border:#3b3b46;
--accent-soft:#2b1f24;--accent-2:#3f2b33;
--danger-soft:#2a1a1a;--danger-2:#3a2429;
background:var(--bg);color:var(--fg);
}

View file

@ -53,22 +53,22 @@ h1{font-size:28px;font-weight:700;letter-spacing:-.02em;line-height:1.15;margin:
:root[data-theme="dark"] .theme-btn .i-moon{display:block}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){--bg:#0f0f0f;--surface:#1c1416;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429}
:root:not([data-theme="light"]){--bg:#161619;--surface:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46}
:root:not([data-theme="light"]) .lede,:root:not([data-theme="light"]) .brand .sub,:root:not([data-theme="light"]) .card .desc,:root:not([data-theme="light"]) .foot{color:#a0a0a0}
:root:not([data-theme="light"]) .card{background:#181818}
:root:not([data-theme="light"]) .statusbar{background:#181818;border-color:#3a2429;color:#a0a0a0}
:root:not([data-theme="light"]) .pill{background:#181818;border-color:#3a2429;color:#f0f0f0}
:root:not([data-theme="light"]) .card{background:var(--surface)}
:root:not([data-theme="light"]) .statusbar{background:var(--surface);border-color:var(--border);color:#a0a0a0}
:root:not([data-theme="light"]) .pill{background:var(--surface);border-color:var(--border);color:#f0f0f0}
:root:not([data-theme="light"]) .pill .dot{box-shadow:0 0 0 3px rgba(15,138,63,.2)}
}
:root[data-theme="dark"]{
--bg:#0f0f0f;--surface:#1c1416;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429;
--bg:#161619;--surface:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46;
background:var(--bg);color:var(--fg);
}
:root[data-theme="dark"] body{background:var(--bg);color:var(--fg)}
:root[data-theme="dark"] .lede,:root[data-theme="dark"] .brand .sub,:root[data-theme="dark"] .card .desc,:root[data-theme="dark"] .foot{color:#a0a0a0}
:root[data-theme="dark"] .card{background:#181818}
:root[data-theme="dark"] .statusbar{background:#181818;border-color:#3a2429;color:#a0a0a0}
:root[data-theme="dark"] .pill{background:#181818;border-color:#3a2429;color:#f0f0f0}
:root[data-theme="dark"] .card{background:var(--surface)}
:root[data-theme="dark"] .statusbar{background:var(--surface);border-color:var(--border);color:#a0a0a0}
:root[data-theme="dark"] .pill{background:var(--surface);border-color:var(--border);color:#f0f0f0}
@media (prefers-reduced-motion: reduce){*{transition-duration:0.01ms !important;animation-duration:0.01ms !important}}
</style>
</head>

View file

@ -524,6 +524,11 @@ function renderCartDrawer(){
`− ${fmt(t.discountAmount)}${pctLabel} ex VAT`;
} else {
dl.hidden = true;
// Blank the figure as well as hiding the row. A discount that has
// been cleared must not leave its old amount sitting on screen —
// an operator seeing "− R 98.37" under a total that no longer
// contains it has no way to tell stale text from a live one.
document.getElementById('discountLineAmount').textContent = '';
}
}

View file

@ -12,10 +12,22 @@
:root{
--bg:#ffffff;--surface:#fff7f8;--fg:#222222;--muted:#dddddd;--border:#ffe3e8;--accent:#ff385c;
--ok:#0f8a3f;--warn:#b14a00;--danger:#c92a2a;
/* Text greys were hardcoded per-component, which is why the dark theme
had to restate 43 of them and why the greys drifted out of spec. One
token each; the dark block below rebinds them to lighter values so
they clear WCAG AA against a dark canvas. */
--muted-fg:#717171;--icon-fg:#888;--placeholder-fg:#666;
--shadow-up:0 -1px 0 var(--muted), 0 -8px 24px rgba(34,34,34,.06);
--font:'Inter',system-ui,-apple-system,Segoe UI,Helvetica Neue,Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
/* Any class rule that sets `display` beats the UA stylesheet's
`[hidden]{display:none}`, so a hidden element stays visible and every
JS `.hidden = true/false` silently does nothing. `.cat-menu` and
`.cat-backdrop` already carry per-element overrides for this; this
one rule covers every element (and every future one) instead of
relying on someone remembering to add a twin. */
[hidden]{display:none !important}
html,body{height:100%;background:var(--bg);color:var(--fg);font:15px/1.45 var(--font);-webkit-font-smoothing:antialiased;overscroll-behavior:none;overflow-x:hidden}
body{display:flex;flex-direction:column;min-height:100dvh;max-width:480px;margin:0 auto;background:var(--bg);padding-bottom:env(safe-area-inset-bottom)}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
@ -29,14 +41,14 @@ input{font:inherit;color:inherit}
.topbar .back{width:36px;height:36px;border-radius:50%;display:grid;place-items:center;color:var(--fg)}
.topbar .back:active{background:var(--surface)}
.topbar h1{font-size:17px;font-weight:600;letter-spacing:-.01em;flex:1}
.topbar .netpill{font-size:11px;letter-spacing:.06em;text-transform:uppercase;font-weight:600;color:#717171;padding:4px 8px;border:1px solid var(--muted);border-radius:999px}
.topbar .netpill{font-size:11px;letter-spacing:.06em;text-transform:uppercase;font-weight:600;color:var(--muted-fg);padding:4px 8px;border:1px solid var(--muted);border-radius:999px}
.topbar .netpill.off{color:var(--danger);border-color:var(--danger);background:#fff5f5}
/* ---- Search ---- */
.search{padding:6px 16px 10px;background:var(--bg);position:sticky;top:60px;z-index:9}
.search-box{display:flex;align-items:center;gap:10px;padding:11px 14px;border:1px solid var(--muted);border-radius:12px;background:#fff;box-shadow:0 1px 2px rgba(34,34,34,.02)}
.search-box:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.12)}
.search-box svg{width:18px;height:18px;color:#717171;flex:0 0 auto;stroke-width:1.7}
.search-box svg{width:18px;height:18px;color:var(--muted-fg);flex:0 0 auto;stroke-width:1.7}
.search-box input{flex:1;border:0;outline:0;background:transparent;font-size:15px;min-width:0}
.search-box input::placeholder{color:#9a9a9a}
@ -45,9 +57,9 @@ input{font:inherit;color:inherit}
.cat-trigger{display:flex;align-items:center;gap:8px;padding:10px 14px;border:1px solid var(--muted);border-radius:10px;background:#fff;font-size:14px;font-weight:500;color:var(--fg);min-height:42px;max-width:65%;letter-spacing:-.005em}
.cat-trigger:active{background:var(--surface)}
.cat-trigger .cat-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cat-trigger .cat-chev{width:16px;height:16px;flex:0 0 auto;color:#717171;transition:transform .18s}
.cat-trigger .cat-chev{width:16px;height:16px;flex:0 0 auto;color:var(--muted-fg);transition:transform .18s}
.cat-trigger[aria-expanded="true"] .cat-chev{transform:rotate(180deg)}
.cat-count{font-size:12px;color:#717171;letter-spacing:.01em;font-variant-numeric:tabular-nums}
.cat-count{font-size:12px;color:var(--muted-fg);letter-spacing:.01em;font-variant-numeric:tabular-nums}
.cat-menu{position:fixed;left:16px;right:16px;top:auto;background:#fff;border:1px solid var(--muted);border-radius:14px;box-shadow:0 12px 40px rgba(34,34,34,.18);z-index:60;max-width:448px;margin:0 auto;overflow:hidden;transform-origin:top center;animation:catIn .15s ease}
@keyframes catIn{from{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}
@ -57,7 +69,7 @@ input{font:inherit;color:inherit}
.cat-item:last-child{border-bottom:0}
.cat-item:active{background:var(--surface)}
.cat-item .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cat-item .ct{font-size:12px;color:#717171;font-variant-numeric:tabular-nums;letter-spacing:.02em;flex:0 0 auto}
.cat-item .ct{font-size:12px;color:var(--muted-fg);font-variant-numeric:tabular-nums;letter-spacing:.02em;flex:0 0 auto}
.cat-item.active{background:var(--surface);color:var(--accent)}
.cat-item.active .ct{color:var(--accent)}
.cat-item.active::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--accent);flex:0 0 auto}
@ -67,10 +79,10 @@ input{font:inherit;color:inherit}
/* ---- Catalog list ---- */
.catalog{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:0 16px 220px}
.catalog .meta{padding:12px 0 6px;font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#717171;display:flex;justify-content:space-between}
.catalog .meta{padding:12px 0 6px;font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted-fg);display:flex;justify-content:space-between}
.catalog .empty{padding:60px 20px;text-align:center;color:#9a9a9a}
.catalog .more{display:flex;flex-direction:column;align-items:center;gap:2px;width:100%;margin:14px 0 8px;padding:14px;border:1px solid var(--muted);border-radius:12px;background:var(--surface-card);color:var(--fg);font-size:14px;font-weight:600;letter-spacing:-.005em}
.catalog .more small{font-size:11px;font-weight:500;color:#717171;letter-spacing:.02em}
.catalog .more small{font-size:11px;font-weight:500;color:var(--muted-fg);letter-spacing:.02em}
.catalog .more:active{background:var(--surface)}
.catalog .empty h3{font-size:16px;font-weight:600;color:var(--fg);margin-bottom:6px;letter-spacing:-.01em}
.catalog .empty p{font-size:13px;line-height:1.5}
@ -81,16 +93,16 @@ input{font:inherit;color:inherit}
.prod .ic .ic-fa{font-size:18px;line-height:1;color:var(--accent)}
.prod .body{flex:1;min-width:0;padding-right:8px}
.prod .name{font-size:14px;font-weight:500;line-height:1.3;letter-spacing:-.005em;color:var(--fg);overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.prod .sub{font-size:11px;color:#717171;margin-top:2px;letter-spacing:.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prod .sub{font-size:11px;color:var(--muted-fg);margin-top:2px;letter-spacing:.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prod .price{font-size:14px;font-weight:600;letter-spacing:-.005em;color:var(--fg);font-variant-numeric:tabular-nums;white-space:nowrap}
.prod .price small{font-size:11px;color:#717171;font-weight:500;margin-left:2px}
.prod .price small{font-size:11px;color:var(--muted-fg);font-weight:500;margin-left:2px}
/* ---- Cart bottom sheet ---- */
.cart-bar{position:fixed;left:0;right:0;bottom:0;background:#fff;border-top:1px solid var(--muted);padding:12px 16px calc(12px + env(safe-area-inset-bottom));z-index:20;display:flex;align-items:center;gap:12px;box-shadow:var(--shadow-up);max-width:480px;margin:0 auto;transform:translateY(100%);transition:transform .22s cubic-bezier(.2,.8,.2,1)}
.cart-bar.show{transform:translateY(0)}
.cart-bar .count{display:flex;flex-direction:column;line-height:1.15}
.cart-bar .count b{font-size:20px;font-weight:700;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.cart-bar .count span{font-size:11px;color:#717171;letter-spacing:.04em;text-transform:uppercase}
.cart-bar .count span{font-size:11px;color:var(--muted-fg);letter-spacing:.04em;text-transform:uppercase}
.cart-bar .total{margin-left:auto;font-size:20px;font-weight:700;letter-spacing:-.02em;color:var(--fg);font-variant-numeric:tabular-nums}
.cart-bar .checkout{padding:14px 22px;background:var(--accent);color:#fff;border-radius:12px;font-weight:600;font-size:15px;letter-spacing:-.005em;min-height:48px;display:flex;align-items:center;gap:6px}
.cart-bar .checkout:active{background:#e5324f}
@ -107,7 +119,7 @@ input{font:inherit;color:inherit}
.sheet-body{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:4px 0}
.line{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid var(--muted)}
.line .nm{flex:1;min-width:0;font-size:14px;font-weight:500;line-height:1.3;letter-spacing:-.005em}
.line .nm small{display:block;font-size:11px;color:#717171;margin-top:2px;letter-spacing:.01em}
.line .nm small{display:block;font-size:11px;color:var(--muted-fg);margin-top:2px;letter-spacing:.01em}
.qty{display:flex;align-items:center;border:1px solid var(--muted);border-radius:10px;overflow:hidden}
.qty button{width:34px;height:34px;display:grid;place-items:center;color:var(--fg);font-size:16px;font-weight:600}
.qty button:active{background:var(--surface)}
@ -117,7 +129,7 @@ input{font:inherit;color:inherit}
.line .rm{width:32px;height:32px;border-radius:50%;color:#9a9a9a;display:grid;place-items:center}
.line .rm:active{background:#fff0f0;color:var(--danger)}
.sheet-foot{padding:14px 20px 18px;border-top:1px solid var(--muted);background:#fff}
.totals{display:flex;justify-content:space-between;padding:6px 0;font-size:14px;color:#717171;letter-spacing:.01em}
.totals{display:flex;justify-content:space-between;padding:6px 0;font-size:14px;color:var(--muted-fg);letter-spacing:.01em}
.totals b{color:var(--fg);font-variant-numeric:tabular-nums}
.totals.grand{padding-top:12px;margin-top:8px;border-top:1px solid var(--muted);font-size:17px;color:var(--fg);font-weight:700;letter-spacing:-.01em}
@ -125,7 +137,7 @@ input{font:inherit;color:inherit}
/* A dashed outline rather than a filled button: this is a secondary
action next to the real catalogue, and it should read as "something
extra" rather than compete with the checkout CTA below it. */
.custom-item-btn{width:100%;margin-bottom:12px;padding:10px 12px;border:1px dashed #c9c9c9;border-radius:9px;background:none;color:#717171;font-size:13px;font-weight:600;letter-spacing:.01em;text-align:center;cursor:pointer}
.custom-item-btn{width:100%;margin-bottom:12px;padding:10px 12px;border:1px dashed #c9c9c9;border-radius:9px;background:none;color:var(--muted-fg);font-size:13px;font-weight:600;letter-spacing:.01em;text-align:center;cursor:pointer}
.custom-item-btn:active{background:#f4f4f4}
/* Ties a custom line back to the "not stock" meaning at a glance, and
stops anyone mistaking it for a catalogue item on a stocktake. */
@ -137,7 +149,7 @@ input{font:inherit;color:inherit}
adds no vertical height at all — the footer keeps every pixel it had
before. It stays visible rather than collapsing away, because a discount
control that appears from nowhere is one nobody finds under pressure. */
.discount-trigger{display:inline-flex;align-items:center;gap:3px;margin-left:8px;padding:3px 6px;border:0;border-radius:6px;background:none;color:#717171;font-size:12px;font-weight:600;letter-spacing:.01em;vertical-align:middle}
.discount-trigger{display:inline-flex;align-items:center;gap:3px;margin-left:8px;padding:3px 6px;border:0;border-radius:6px;background:none;color:var(--muted-fg);font-size:12px;font-weight:600;letter-spacing:.01em;vertical-align:middle}
.discount-trigger:active{background:var(--surface);color:var(--fg)}
.discount-trigger svg{width:13px;height:13px;color:inherit;transition:transform .18s}
.discount-trigger[aria-expanded="true"] svg{transform:rotate(180deg)}
@ -145,15 +157,15 @@ input{font:inherit;color:inherit}
.discount-trigger.has-value svg{color:var(--accent)}
.discount-body{padding:12px 0 4px;display:flex;flex-direction:column;gap:10px}
.discount-pills{display:flex;gap:6px;padding:3px;background:var(--surface);border:1px solid var(--muted);border-radius:999px;width:fit-content}
.discount-pill{padding:7px 16px;border-radius:999px;font-size:13px;font-weight:600;letter-spacing:-.005em;color:#717171;min-height:34px}
.discount-pill{padding:7px 16px;border-radius:999px;font-size:13px;font-weight:600;letter-spacing:-.005em;color:var(--muted-fg);min-height:34px}
.discount-pill.active{background:#fff;color:var(--accent);box-shadow:0 1px 2px rgba(34,34,34,.06)}
.discount-input-row{display:flex;align-items:center;gap:8px}
.discount-input-row input{flex:1 1 0;min-width:0;padding:11px 12px;border:1px solid var(--muted);border-radius:10px;font-size:15px;font-weight:600;text-align:right;font-variant-numeric:tabular-nums;outline:0;background:#fff}
.discount-input-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.12)}
.discount-suffix{font-size:13px;font-weight:600;color:#717171;width:18px;text-align:left;letter-spacing:-.005em}
.discount-suffix{font-size:13px;font-weight:600;color:var(--muted-fg);width:18px;text-align:left;letter-spacing:-.005em}
.discount-apply{padding:11px 16px;background:var(--accent);color:#fff;border-radius:10px;font-size:13px;font-weight:600;letter-spacing:-.005em;min-height:42px;min-width:78px}
.discount-apply:active{background:#e5324f}
.discount-hint{font-size:11px;color:#717171;margin:0;letter-spacing:.01em}
.discount-hint{font-size:11px;color:var(--muted-fg);margin:0;letter-spacing:.01em}
.totals.discount-line{color:var(--danger)}
.totals.discount-line b{color:var(--danger)}
@ -170,9 +182,9 @@ input{font:inherit;color:inherit}
.edit-sheet{position:fixed;left:50%;bottom:24px;z-index:71;transform:translate(-50%, 24px);background:#fff;border-radius:18px;width:calc(100% - 32px);max-width:448px;padding:18px 18px 14px;opacity:0;transition:opacity .15s, transform .22s cubic-bezier(.2,.8,.2,1);box-shadow:0 20px 60px rgba(34,34,34,.30);pointer-events:none;display:flex;flex-direction:column;gap:12px;max-height:80dvh;overflow-y:auto;-webkit-overflow-scrolling:touch;padding-bottom:calc(14px + env(safe-area-inset-bottom))}
.edit-sheet.show{opacity:1;transform:translate(-50%, 0);pointer-events:auto}
.edit-sheet h3{font-size:15px;font-weight:600;letter-spacing:-.01em;margin:0;display:flex;align-items:center;gap:8px}
.edit-sheet h3 small{font-size:11px;font-weight:500;color:#717171;letter-spacing:.02em;margin-left:auto}
.edit-sheet h3 small{font-size:11px;font-weight:500;color:var(--muted-fg);letter-spacing:.02em;margin-left:auto}
.edit-row{display:flex;flex-direction:column;gap:6px}
.edit-row label{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#717171}
.edit-row label{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted-fg)}
.edit-row input{width:100%;box-sizing:border-box;padding:12px 12px;border:1px solid var(--muted);border-radius:10px;font-size:16px;font-weight:600;text-align:right;font-variant-numeric:tabular-nums;outline:0;background:#fff}
.edit-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.12)}
.edit-actions{display:flex;gap:8px;margin-top:4px}
@ -194,7 +206,7 @@ input{font:inherit;color:inherit}
.pay-tile{padding:18px 14px;border:1.5px solid var(--muted);border-radius:14px;background:#fff;text-align:left;min-height:92px;display:flex;flex-direction:column;justify-content:space-between}
.pay-tile:active{background:var(--surface)}
.pay-tile .lab{font-size:14px;font-weight:600;letter-spacing:-.01em}
.pay-tile .sub{font-size:11px;color:#717171;margin-top:2px;letter-spacing:.01em}
.pay-tile .sub{font-size:11px;color:var(--muted-fg);margin-top:2px;letter-spacing:.01em}
.pay-tile .ic{width:32px;height:32px;border-radius:8px;background:var(--surface);color:var(--accent);display:grid;place-items:center;margin-bottom:8px}
.pay-tile .ic svg{width:18px;height:18px;stroke-width:1.7}
.pay-tile.selected{border-color:var(--accent);background:#fff8fa}
@ -204,7 +216,7 @@ input{font:inherit;color:inherit}
.cash-block.show{display:block}
.cash-row{display:flex;gap:8px;margin-bottom:10px;min-width:0}
.cash-row .field{flex:1 1 0;min-width:0;display:flex;flex-direction:column}
.cash-row label{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#717171;margin-bottom:4px}
.cash-row label{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted-fg);margin-bottom:4px}
.cash-row input{width:100%;box-sizing:border-box;padding:12px 10px;border:1px solid var(--muted);border-radius:10px;font-size:16px;font-weight:600;text-align:right;font-variant-numeric:tabular-nums;outline:0;background:#fff;min-width:0}
.cash-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.12)}
.quick{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
@ -212,7 +224,7 @@ input{font:inherit;color:inherit}
.quick button:active{background:var(--surface);border-color:#bbb}
.change-out{padding:14px;border-radius:12px;background:var(--surface);border:1px solid var(--border);text-align:center;min-height:64px;display:flex;flex-direction:column;justify-content:center;opacity:0;max-height:0;padding-top:0;padding-bottom:0;overflow:hidden;transition:opacity .15s, max-height .2s, padding .2s, border-width .2s;border-width:0}
.change-out.show{opacity:1;max-height:120px;padding:14px;border-width:1px}
.change-out .lbl{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#717171}
.change-out .lbl{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted-fg)}
.change-out .amt{font-size:32px;font-weight:700;letter-spacing:-.02em;color:var(--ok);font-variant-numeric:tabular-nums;margin-top:4px}
.change-out.short .amt{color:var(--danger)}
@ -226,11 +238,11 @@ input{font:inherit;color:inherit}
.cust-results div{padding:10px 14px;border-bottom:1px solid var(--muted);font-size:14px;display:flex;align-items:center;gap:8px;cursor:pointer}
.cust-results div:last-child{border-bottom:0}
.cust-results div:active{background:var(--surface)}
.cust-results small{color:#717171;font-size:11px;margin-left:auto;letter-spacing:.02em}
.cust-results small{color:var(--muted-fg);font-size:11px;margin-left:auto;letter-spacing:.02em}
/* Count header + empty state. Both are divs inside .cust-results, so they
need the row cursor/padding neutralised explicitly. */
.cust-results .cust-count{padding:7px 14px;background:var(--surface);border-bottom:1px solid var(--muted);font-size:11px;font-weight:600;letter-spacing:.02em;color:#717171;cursor:default}
.cust-results .cust-none{padding:11px 14px;font-size:13px;color:#717171;cursor:default}
.cust-results .cust-count{padding:7px 14px;background:var(--surface);border-bottom:1px solid var(--muted);font-size:11px;font-weight:600;letter-spacing:.02em;color:var(--muted-fg);cursor:default}
.cust-results .cust-none{padding:11px 14px;font-size:13px;color:var(--muted-fg);cursor:default}
.cust-results .cust-count:active,.cust-results .cust-none:active{background:none}
.cust-picked{padding:10px 12px;background:var(--surface);border:1px solid var(--border);border-radius:10px;font-size:13px;display:none;align-items:center;gap:8px;margin-bottom:8px}
.cust-picked.show{display:flex}
@ -253,56 +265,70 @@ input{font:inherit;color:inherit}
:root[data-theme="auto"] .theme-btn .i-sun{display:block}
/* ---- dark theme (auto follows prefers-color-scheme) ---- */
/* Dark ramp. The previous canvas was #0f0f0f (L* 4.3 — effectively black)
with surfaces only 3-4 L* above it, and mixed a warm #1c1416 against a
neutral #181818 in the same UI. That near-black canvas plus two
competing surface tones is what read as "muddy". This is a single
cool-neutral ramp with four real steps, and the text greys are lifted
so the most-used one clears WCAG AA (was 3.93:1, now 6.49:1). */
:root[data-theme="dark"]{
--bg:#0f0f0f; --surface:#1c1416; --fg:#f0f0f0; --muted:#2a2a2a; --border:#3a2429;
--bg:#161619; /* L* 7.4 — lifted off black */
--surface:#1e1e23; /* L* 11.5 — cards, drawers, fields */
--raised:#26262d; /* L* 15.4 — menus and modals sit above surfaces */
--fg:#f2f2f5;
--muted:#303039; /* L* 20.2 — separators */
--border:#3b3b46; /* L* 25.3 — outlines */
--muted-fg:#9a9aa6; /* secondary text 6.49:1 on --bg (AA) */
--icon-fg:#8e8e9a; /* icons / labels 5.58:1 on --bg (AA) */
--placeholder-fg:#7c7c88; /* placeholder 4.38:1 */
--shadow-up:0 -1px 0 var(--muted), 0 -8px 24px rgba(0,0,0,.4);
}
:root[data-theme="dark"] body{background:var(--bg);color:var(--fg)}
:root[data-theme="dark"] .topbar{background:var(--bg);border-bottom-color:var(--muted)}
:root[data-theme="dark"] .search-box{background:#181818;border-color:var(--muted)}
:root[data-theme="dark"] .search-box{background:var(--surface);border-color:var(--muted)}
:root[data-theme="dark"] .search-box:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.2)}
:root[data-theme="dark"] .search-box svg{color:#888}
:root[data-theme="dark"] .search-box svg{color:var(--icon-fg)}
:root[data-theme="dark"] .search-box input{color:var(--fg)}
:root[data-theme="dark"] .search-box input::placeholder{color:#666}
:root[data-theme="dark"] .chip{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .search-box input::placeholder{color:var(--placeholder-fg)}
:root[data-theme="dark"] .chip{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .chip.active{background:var(--fg);color:var(--bg);border-color:var(--fg)}
:root[data-theme="dark"] .cat-trigger{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .cat-trigger .cat-chev{color:#888}
:root[data-theme="dark"] .cat-count{color:#888}
:root[data-theme="dark"] .cat-menu{background:#181818;border-color:var(--muted);box-shadow:0 12px 40px rgba(0,0,0,.5)}
:root[data-theme="dark"] .cat-trigger{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .cat-trigger .cat-chev{color:var(--icon-fg)}
:root[data-theme="dark"] .cat-count{color:var(--icon-fg)}
:root[data-theme="dark"] .cat-menu{background:var(--raised);border-color:var(--muted);box-shadow:0 12px 40px rgba(0,0,0,.5)}
:root[data-theme="dark"] .cat-item{color:var(--fg);border-bottom-color:var(--muted)}
:root[data-theme="dark"] .cat-item:active{background:var(--surface)}
:root[data-theme="dark"] .cat-item .ct{color:#888}
:root[data-theme="dark"] .cat-item .ct{color:var(--icon-fg)}
:root[data-theme="dark"] .cat-item.active{background:var(--surface);color:var(--accent)}
:root[data-theme="dark"] .prod{border-bottom-color:var(--muted)}
:root[data-theme="dark"] .prod:active{background:var(--surface)}
:root[data-theme="dark"] .prod .ic{background:var(--surface);border-color:var(--border);color:var(--accent)}
:root[data-theme="dark"] .prod .name{color:var(--fg)}
:root[data-theme="dark"] .prod .sub{color:#888}
:root[data-theme="dark"] .prod .sub{color:var(--icon-fg)}
:root[data-theme="dark"] .prod .price{color:var(--fg)}
:root[data-theme="dark"] .prod .price small{color:#888}
:root[data-theme="dark"] .cart-bar{background:#181818;border-top-color:var(--muted);box-shadow:var(--shadow-up)}
:root[data-theme="dark"] .cart-bar .count span{color:#888}
:root[data-theme="dark"] .prod .price small{color:var(--icon-fg)}
:root[data-theme="dark"] .cart-bar{background:var(--surface);border-top-color:var(--muted);box-shadow:var(--shadow-up)}
:root[data-theme="dark"] .cart-bar .count span{color:var(--icon-fg)}
:root[data-theme="dark"] .cart-bar .total{color:var(--fg)}
:root[data-theme="dark"] .drawer{background:rgba(0,0,0,.7)}
:root[data-theme="dark"] .sheet{background:#181818}
:root[data-theme="dark"] .sheet{background:var(--surface)}
:root[data-theme="dark"] .sheet-head{border-bottom-color:var(--muted)}
:root[data-theme="dark"] .sheet-head .x{background:var(--surface);color:var(--fg)}
:root[data-theme="dark"] .line{border-bottom-color:var(--muted)}
:root[data-theme="dark"] .line .nm{color:var(--fg)}
:root[data-theme="dark"] .line .nm small{color:#888}
:root[data-theme="dark"] .line .nm small{color:var(--icon-fg)}
:root[data-theme="dark"] .qty{border-color:var(--muted)}
:root[data-theme="dark"] .qty button{color:var(--fg)}
:root[data-theme="dark"] .qty button:active{background:var(--surface)}
:root[data-theme="dark"] .qty .v{color:var(--fg)}
:root[data-theme="dark"] .line .pr{color:var(--fg)}
:root[data-theme="dark"] .line .rm{color:#666}
:root[data-theme="dark"] .line .rm{color:var(--placeholder-fg)}
:root[data-theme="dark"] .line .rm:active{background:#3a2429;color:var(--accent)}
:root[data-theme="dark"] .sheet-foot{background:#181818;border-top-color:var(--muted)}
:root[data-theme="dark"] .totals{color:#888}
:root[data-theme="dark"] .sheet-foot{background:var(--surface);border-top-color:var(--muted)}
:root[data-theme="dark"] .totals{color:var(--icon-fg)}
:root[data-theme="dark"] .totals b{color:var(--fg)}
:root[data-theme="dark"] .totals.grand{border-top-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .discount-trigger{color:#888}
:root[data-theme="dark"] .discount-trigger{color:var(--icon-fg)}
:root[data-theme="dark"] .custom-item-btn{border-color:#3a3a3a;color:#999}
:root[data-theme="dark"] .custom-item-btn:active{background:#222}
:root[data-theme="dark"] .custom-line .nm{color:#d2b47c}
@ -310,53 +336,53 @@ input{font:inherit;color:inherit}
:root[data-theme="dark"] .discount-trigger:active{color:var(--fg)}
:root[data-theme="dark"] .discount-trigger.has-value{color:#ff6680}
:root[data-theme="dark"] .discount-pills{background:#222;border-color:var(--muted)}
:root[data-theme="dark"] .discount-pill{color:#888}
:root[data-theme="dark"] .discount-pill.active{background:#181818;color:#ff6680;box-shadow:0 1px 2px rgba(0,0,0,.4)}
:root[data-theme="dark"] .discount-input-row input{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .discount-pill{color:var(--icon-fg)}
:root[data-theme="dark"] .discount-pill.active{background:var(--surface);color:#ff6680;box-shadow:0 1px 2px rgba(0,0,0,.4)}
:root[data-theme="dark"] .discount-input-row input{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .discount-input-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.2)}
:root[data-theme="dark"] .discount-suffix{color:#888}
:root[data-theme="dark"] .discount-hint{color:#888}
:root[data-theme="dark"] .discount-suffix{color:var(--icon-fg)}
:root[data-theme="dark"] .discount-hint{color:var(--icon-fg)}
:root[data-theme="dark"] .totals.discount-line{color:#ff6680}
:root[data-theme="dark"] .totals.discount-line b{color:#ff6680}
:root[data-theme="dark"] .empty{color:#666}
:root[data-theme="dark"] .empty{color:var(--placeholder-fg)}
:root[data-theme="dark"] .empty h3{color:var(--fg)}
:root[data-theme="dark"] .empty p{color:#888}
:root[data-theme="dark"] .netpill{background:transparent;color:#888;border-color:var(--muted)}
:root[data-theme="dark"] .empty p{color:var(--icon-fg)}
:root[data-theme="dark"] .netpill{background:transparent;color:var(--icon-fg);border-color:var(--muted)}
:root[data-theme="dark"] .netpill.off{color:#ff6680;border-color:#ff6680;background:#2a1a1a}
:root[data-theme="dark"] .back:active{background:var(--surface)}
:root[data-theme="dark"] .pay-tile{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .pay-tile{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .pay-tile:active{background:var(--surface)}
:root[data-theme="dark"] .pay-tile .ic{background:var(--surface);color:var(--accent)}
:root[data-theme="dark"] .pay-tile.selected{border-color:var(--accent);background:#2a1a1a}
:root[data-theme="dark"] .pay-tile.selected .ic{background:var(--accent);color:#fff}
:root[data-theme="dark"] .cash-row input{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .cash-row input{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .cash-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.2)}
:root[data-theme="dark"] .cash-row input[readonly]{background:#222;color:#aaa}
:root[data-theme="dark"] .cash-row label{color:#888}
:root[data-theme="dark"] .quick button{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .cash-row label{color:var(--icon-fg)}
:root[data-theme="dark"] .quick button{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .quick button:active{background:var(--surface);border-color:#444}
:root[data-theme="dark"] .change-out{background:var(--surface);border-color:var(--border);color:var(--fg)}
:root[data-theme="dark"] .change-out .lbl{color:#888}
:root[data-theme="dark"] .cust-block input{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .change-out .lbl{color:var(--icon-fg)}
:root[data-theme="dark"] .cust-block input{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .cust-block input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.2)}
:root[data-theme="dark"] .cust-results{background:#181818;border-color:var(--muted)}
:root[data-theme="dark"] .cust-results{background:var(--surface);border-color:var(--muted)}
:root[data-theme="dark"] .cust-results div{border-bottom-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .cust-results div:active{background:var(--surface)}
:root[data-theme="dark"] .cust-results small{color:#888}
:root[data-theme="dark"] .cust-results .cust-count{background:#222;border-bottom-color:var(--muted);color:#888}
:root[data-theme="dark"] .cust-results .cust-none{color:#888}
:root[data-theme="dark"] .cust-results small{color:var(--icon-fg)}
:root[data-theme="dark"] .cust-results .cust-count{background:#222;border-bottom-color:var(--muted);color:var(--icon-fg)}
:root[data-theme="dark"] .cust-results .cust-none{color:var(--icon-fg)}
:root[data-theme="dark"] .cust-picked{background:var(--surface);border-color:var(--border);color:var(--fg)}
:root[data-theme="dark"] .edit-sheet{background:#181818;box-shadow:0 20px 60px rgba(0,0,0,.6)}
:root[data-theme="dark"] .edit-sheet h3 small{color:#888}
:root[data-theme="dark"] .edit-row label{color:#888}
:root[data-theme="dark"] .edit-row input{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .edit-sheet{background:var(--raised);box-shadow:0 20px 60px rgba(0,0,0,.6)}
:root[data-theme="dark"] .edit-sheet h3 small{color:var(--icon-fg)}
:root[data-theme="dark"] .edit-row label{color:var(--icon-fg)}
:root[data-theme="dark"] .edit-row input{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .edit-row input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,56,92,.2)}
:root[data-theme="dark"] .edit-actions .secondary{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .edit-actions .secondary{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .edit-actions .secondary:active{background:var(--surface)}
:root[data-theme="dark"] .edit-actions .danger{background:#181818;border-color:#5a2429;color:#ff6680}
:root[data-theme="dark"] .edit-actions .danger{background:var(--surface);border-color:#5a2429;color:#ff6680}
:root[data-theme="dark"] .edit-actions .danger:active{background:#2a1a1a}
:root[data-theme="dark"] .line-holding{background:var(--surface)}
:root[data-theme="dark"] .final-cta:disabled{background:#2a2a2a;color:#666}
:root[data-theme="dark"] .final-cta:disabled{background:#2a2a2a;color:var(--placeholder-fg)}
@media (prefers-reduced-motion: reduce){*{transition-duration:0.01ms !important;animation-duration:0.01ms !important}}
</style>
</head>

View file

@ -152,9 +152,9 @@ input,select{font:inherit;color:inherit}
/* ---- Dark theme ---- */
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--bg:#0f0f0f;--surface:#1c1416;--surface-2:#181818;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429;
--ink:#f0f0f0;--ink-soft:#a0a0a0;--ink-faint:#777;
--surface-card:#181818;--accent-soft:#2a1a1a;--accent-2:#3a2429;--accent-soft-hover:#3a2429;
--bg:#161619;--surface:#1e1e23;--surface-2:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46;
--ink:#f2f2f5;--ink-soft:#a0a0a0;--ink-faint:#8e8e9a;
--surface-card:#26262d;--accent-soft:#2a1a1a;--accent-2:#3a2429;--accent-soft-hover:#3a2429;
--ok-soft:#1a2e22;--danger-soft:#2a1a1a;--danger-2:#3a2429;
--focus-ring:0 0 0 3px rgba(255,102,128,.35);
}
@ -203,9 +203,9 @@ input,select{font:inherit;color:inherit}
:root:not([data-theme="light"]) .btn.acct:active{background:var(--accent-soft-hover)}
}
:root[data-theme="dark"]{
--bg:#0f0f0f;--surface:#1c1416;--surface-2:#181818;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429;
--ink:#f0f0f0;--ink-soft:#a0a0a0;--ink-faint:#777;
--surface-card:#181818;--accent-soft:#2a1a1a;--accent-2:#3a2429;--accent-soft-hover:#3a2429;
--bg:#161619;--surface:#1e1e23;--surface-2:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46;
--ink:#f2f2f5;--ink-soft:#a0a0a0;--ink-faint:#8e8e9a;
--surface-card:#26262d;--accent-soft:#2b1f24;--accent-2:#3f2b33;--accent-soft-hover:#452f38;
--ok-soft:#1a2e22;--danger-soft:#2a1a1a;--danger-2:#3a2429;
--focus-ring:0 0 0 3px rgba(255,102,128,.35);
}

View file

@ -78,7 +78,7 @@ body{display:flex;flex-direction:column;min-height:100dvh;max-width:480px;margin
/* ---- Dark theme ---- */
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--bg:#0f0f0f;--surface:#1c1416;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429;
--bg:#161619;--surface:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46;
}
:root:not([data-theme="light"]) body{background:var(--bg);color:var(--fg)}
:root:not([data-theme="light"]) .topbar{background:var(--bg)}
@ -89,22 +89,22 @@ body{display:flex;flex-direction:column;min-height:100dvh;max-width:480px;margin
:root:not([data-theme="light"]) .topbar .theme-btn{color:var(--fg)}
:root:not([data-theme="light"]) .section-label{color:#a0a0a0}
:root:not([data-theme="light"]) .active{background:#2a1a1a;border-color:#ff385c}
:root:not([data-theme="light"]) .active .logo{background:#181818;border-color:var(--muted)}
:root:not([data-theme="light"]) .active .logo{background:var(--surface);border-color:var(--muted)}
:root:not([data-theme="light"]) .active .name{color:var(--fg)}
:root:not([data-theme="light"]) .lh-card{background:#181818;border-color:var(--muted);color:var(--fg)}
:root:not([data-theme="light"]) .lh-card{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root:not([data-theme="light"]) .lh-card:active{background:var(--surface)}
:root:not([data-theme="light"]) .lh-card.selected{background:#2a1a1a;border-color:#ff385c}
:root:not([data-theme="light"]) .lh-card .logo{background:#181818;border-color:var(--muted)}
:root:not([data-theme="light"]) .lh-card .logo{background:var(--surface);border-color:var(--muted)}
:root:not([data-theme="light"]) .lh-card .meta{color:#a0a0a0}
:root:not([data-theme="light"]) .lh-card:not(.selected) .check{border-color:var(--muted)}
:root:not([data-theme="light"]) .btn{background:#181818;border-color:var(--muted);color:var(--fg)}
:root:not([data-theme="light"]) .btn{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root:not([data-theme="light"]) .btn:active{background:var(--surface)}
:root:not([data-theme="light"]) .banner.warn{background:#2a1a1a;border-color:#5a3a1a;color:#ffb060}
:root:not([data-theme="light"]) .banner.err{background:#2a1a1a;border-color:#5a2429;color:#ff6680}
:root:not([data-theme="light"]) .skeleton{color:#666}
}
:root[data-theme="dark"]{
--bg:#0f0f0f;--surface:#1c1416;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429;
--bg:#161619;--surface:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46;
background:var(--bg);color:var(--fg);
}
:root[data-theme="dark"] body{background:var(--bg);color:var(--fg)}
@ -116,15 +116,15 @@ body{display:flex;flex-direction:column;min-height:100dvh;max-width:480px;margin
:root[data-theme="dark"] .topbar .theme-btn{color:var(--fg)}
:root[data-theme="dark"] .section-label{color:#a0a0a0}
:root[data-theme="dark"] .active{background:#2a1a1a;border-color:#ff385c}
:root[data-theme="dark"] .active .logo{background:#181818;border-color:var(--muted)}
:root[data-theme="dark"] .active .logo{background:var(--surface);border-color:var(--muted)}
:root[data-theme="dark"] .active .name{color:var(--fg)}
:root[data-theme="dark"] .lh-card{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .lh-card{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .lh-card:active{background:var(--surface)}
:root[data-theme="dark"] .lh-card.selected{background:#2a1a1a;border-color:#ff385c}
:root[data-theme="dark"] .lh-card .logo{background:#181818;border-color:var(--muted)}
:root[data-theme="dark"] .lh-card .logo{background:var(--surface);border-color:var(--muted)}
:root[data-theme="dark"] .lh-card .meta{color:#a0a0a0}
:root[data-theme="dark"] .lh-card:not(.selected) .check{border-color:var(--muted)}
:root[data-theme="dark"] .btn{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .btn{background:var(--surface);border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"] .btn:active{background:var(--surface)}
:root[data-theme="dark"] .banner.warn{background:#2a1a1a;border-color:#5a3a1a;color:#ffb060}
:root[data-theme="dark"] .banner.err{background:#2a1a1a;border-color:#5a2429;color:#ff6680}

View file

@ -56,11 +56,11 @@ body{display:flex;flex-direction:column;min-height:100dvh;max-width:480px;margin
.block-row .edit-text{width:100%;margin-top:6px;padding:8px 10px;border-radius:8px;border:1px solid var(--muted);font:inherit}
.pricing-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border:1px solid var(--muted);border-radius:10px;background:#fff;margin:4px 0 16px}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){--bg:#0f0f0f;--surface:#1c1416;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429}
:root:not([data-theme="light"]) .block-row, :root:not([data-theme="light"]) .pricing-row, :root:not([data-theme="light"]) .btn, :root:not([data-theme="light"]) .preview-tab{background:#181818;border-color:var(--muted);color:var(--fg)}
:root:not([data-theme="light"]){--bg:#161619;--surface:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46}
:root:not([data-theme="light"]) .block-row, :root:not([data-theme="light"]) .pricing-row, :root:not([data-theme="light"]) .btn, :root:not([data-theme="light"]) .preview-tab{background:var(--surface);border-color:var(--muted);color:var(--fg)}
}
:root[data-theme="dark"]{--bg:#0f0f0f;--surface:#1c1416;--fg:#f0f0f0;--muted:#2a2a2a;--border:#3a2429}
:root[data-theme="dark"] .block-row, :root[data-theme="dark"] .pricing-row, :root[data-theme="dark"] .btn, :root[data-theme="dark"] .preview-tab{background:#181818;border-color:var(--muted);color:var(--fg)}
:root[data-theme="dark"]{--bg:#161619;--surface:#1e1e23;--fg:#f2f2f5;--muted:#303039;--border:#3b3b46}
:root[data-theme="dark"] .block-row, :root[data-theme="dark"] .pricing-row, :root[data-theme="dark"] .btn, :root[data-theme="dark"] .preview-tab{background:var(--surface);border-color:var(--muted);color:var(--fg)}
</style>
</head>
<body>

View file

@ -51,7 +51,15 @@
// vertical height while staying visible.
// Bumped because pos-app.js and pos.html are both served cache-first out of
// the shell; without a new CACHE name the tills keep the old ones.
const CACHE = "pos-v35";
// v36 — the discount panel and the Discount totals row both carry a `hidden`
// attribute, but their classes (.discount-body, .totals) set `display:flex`,
// and an author display rule outranks the UA stylesheet's
// `[hidden]{display:none}`. So `hidden` did nothing: "Add discount" could not
// collapse the tier buttons, and a cleared discount left its old amount
// painted under an unchanged total. Fixed with a global `[hidden]` override
// plus blanking the figure when the row is hidden. Also carries the dark
// palette lift.
const CACHE = "pos-v36";
const APP_SHELL = [
'./',
'./index.html',

View file

@ -224,4 +224,43 @@ describe("POS service worker cache tracks shell edits", () => {
expect(m, "sw.js CACHE constant not found").not.toBeNull();
expect(Number(m![1])).toBeGreaterThanOrEqual(33);
});
});
});
describe("POS [hidden] attribute actually hides", () => {
/**
* A `hidden` attribute is implemented by the UA stylesheet as
* `[hidden]{display:none}` — but ANY author rule that sets `display`
* beats it, because author styles outrank UA styles regardless of
* specificity. `.totals` and `.discount-body` both set
* `display:flex`, so `el.hidden = true` did nothing: the discount
* panel could not be collapsed, and the "Discount" row could not be
* dismissed — it stayed on screen showing the last figure even
* after the discount was cleared, because the JS only ever set
* `hidden` and never cleared the text.
*
* Both symptoms looked like two unrelated bugs. One line of CSS
* fixes both, and this guards the whole class of them.
*/
it("has a global [hidden]{display:none} override", () => {
expect(POS_HTML).toMatch(/\[hidden\]\s*\{[^}]*display:\s*none\s*!important/);
});
it("covers every element that carries a hidden attribute", () => {
// Any element with `hidden` whose class rule sets `display` would
// otherwise be permanently visible. Assert the override exists
// rather than trusting each element to have remembered one.
const risky = [
...POS_HTML.matchAll(/<div[^>]*\shidden[^>]*>/g),
].filter((tag) => /\bclass="/.test(tag[0]));
expect(risky.length, "expected some hidden elements to audit").toBeGreaterThan(0);
expect(POS_HTML).toMatch(/\[hidden\]\s*\{[^}]*display:\s*none\s*!important/);
});
it("blanks the discount figure when the row is cleared", () => {
// Hiding alone left the old amount painted on screen, which the
// operator had no way to distinguish from a live one. Matched
// loosely because a comment sits between the two statements.
expect(POS_APP).toMatch(
/dl\.hidden\s*=\s*true;[\s\S]{0,400}discountLineAmount'\)\.textContent\s*=\s*''/,
);
});
});

View file

@ -954,26 +954,33 @@
* so the same OKLCH hue reads at the right intensity.
* ========================================================================= */
.dark {
--background: oklch(0.15 0.01 240);
/* The whole dark ramp sat at oklch(0.15) — an effective #080c0f,
L* 3.2. On a wall-mounted shop display that reads as a dead black
rectangle rather than a surface. Every step is lifted +0.04 so the
ramp keeps its shape (and its 3.2 L* bg→card separation) but the
canvas starts at L* 6.1 instead. Text tokens are unchanged and
were already compliant — muted-foreground goes 6.32:1 → 5.95:1,
still comfortably above AA. */
--background: oklch(0.19 0.01 240);
--foreground: oklch(0.94 0.005 240);
--card: oklch(0.19 0.012 240);
--card: oklch(0.23 0.012 240);
--card-foreground: oklch(0.94 0.005 240);
--popover: oklch(0.20 0.012 240);
--popover: oklch(0.24 0.012 240);
--popover-foreground: oklch(0.94 0.005 240);
--secondary: oklch(0.23 0.012 240);
--secondary: oklch(0.27 0.012 240);
--secondary-foreground: oklch(0.94 0.005 240);
--muted: oklch(0.22 0.012 240);
--muted: oklch(0.26 0.012 240);
--muted-foreground: oklch(0.66 0.01 240);
--accent: oklch(0.28 0.04 240);
--accent: oklch(0.32 0.04 240);
--accent-foreground: oklch(0.94 0.005 240);
--border: oklch(0.28 0.014 240);
--input: oklch(0.27 0.014 240);
--sidebar: oklch(0.17 0.012 240);
--border: oklch(0.32 0.014 240);
--input: oklch(0.31 0.014 240);
--sidebar: oklch(0.21 0.012 240);
--sidebar-foreground: oklch(0.94 0.005 240);
--sidebar-accent: oklch(0.24 0.04 240);
--sidebar-accent: oklch(0.28 0.04 240);
--sidebar-accent-foreground: oklch(0.94 0.005 240);
--sidebar-border: oklch(0.28 0.014 240);
--input-highlight: oklch(0.30 0.06 240);
--sidebar-border: oklch(0.32 0.014 240);
--input-highlight: oklch(0.34 0.06 240);
/* Dark-mode semantic bumps (success / warn / info get brighter so
* they punch on the dark canvas). */
--success: oklch(0.76 0.18 155);
@ -986,10 +993,12 @@
/* The two natively-dark themes (Cursor / Supabase) are already dark by
* default, but flipping the mode toggle still works — it just deepens
* the surface slightly. */
* the surface slightly. Kept ~0.04 L below the standard dark ramp so
* they still read as "darker", but no longer at L* 1.3 (effectively
* #000). */
:root[data-theme="cursor"].dark,
:root[data-theme="supabase"].dark {
--background: oklch(0.11 0.012 240);
--card: oklch(0.15 0.014 240);
--sidebar: oklch(0.13 0.012 240);
--background: oklch(0.15 0.012 240);
--card: oklch(0.19 0.014 240);
--sidebar: oklch(0.17 0.012 240);
}