From 0377c516025282f1369126d0e152073b258a8742 Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 12 Oct 2026 23:17:57 +0200 Subject: [PATCH] fix(pos): dead discount toggle + stale discount figure, lift dark palette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- public/pos/cashup.html | 14 +-- public/pos/history.html | 16 +-- public/pos/index.html | 16 +-- public/pos/pos-app.js | 5 + public/pos/pos.html | 166 +++++++++++++++++------------- public/pos/receipt.html | 12 +-- public/pos/settings.html | 20 ++-- public/pos/slip-layout.html | 8 +- public/pos/sw.js | 10 +- src/lib/pos-cart-quantity.test.ts | 41 +++++++- src/styles/themes.css | 41 +++++--- 11 files changed, 218 insertions(+), 131 deletions(-) diff --git a/public/pos/cashup.html b/public/pos/cashup.html index 6216631..91b3b42 100644 --- a/public/pos/cashup.html +++ b/public/pos/cashup.html @@ -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); } diff --git a/public/pos/history.html b/public/pos/history.html index 2e9cd38..30aecba 100644 --- a/public/pos/history.html +++ b/public/pos/history.html @@ -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); } diff --git a/public/pos/index.html b/public/pos/index.html index 34629e7..c0bfe8f 100644 --- a/public/pos/index.html +++ b/public/pos/index.html @@ -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}} diff --git a/public/pos/pos-app.js b/public/pos/pos-app.js index 2556394..e0404dc 100644 --- a/public/pos/pos-app.js +++ b/public/pos/pos-app.js @@ -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 = ''; } } diff --git a/public/pos/pos.html b/public/pos/pos.html index eb6466c..c9f10dc 100644 --- a/public/pos/pos.html +++ b/public/pos/pos.html @@ -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}} diff --git a/public/pos/receipt.html b/public/pos/receipt.html index 7358102..cc5960c 100644 --- a/public/pos/receipt.html +++ b/public/pos/receipt.html @@ -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); } diff --git a/public/pos/settings.html b/public/pos/settings.html index 41aef2c..ebeed1a 100644 --- a/public/pos/settings.html +++ b/public/pos/settings.html @@ -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} diff --git a/public/pos/slip-layout.html b/public/pos/slip-layout.html index 707cd4a..b092cc9 100644 --- a/public/pos/slip-layout.html +++ b/public/pos/slip-layout.html @@ -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)} diff --git a/public/pos/sw.js b/public/pos/sw.js index 268c31f..bbb48be 100644 --- a/public/pos/sw.js +++ b/public/pos/sw.js @@ -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', diff --git a/src/lib/pos-cart-quantity.test.ts b/src/lib/pos-cart-quantity.test.ts index dfa5bd9..7ab06ac 100644 --- a/src/lib/pos-cart-quantity.test.ts +++ b/src/lib/pos-cart-quantity.test.ts @@ -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); }); -}); \ No newline at end of file +}); +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(/]*\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*''/, + ); + }); +}); diff --git a/src/styles/themes.css b/src/styles/themes.css index f44a940..51310bd 100644 --- a/src/styles/themes.css +++ b/src/styles/themes.css @@ -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); }