diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fd8fd4..7a14179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ at boot. Entries start at 0.3.0. ## Unreleased +### The whole nav tree shows on engines without `:has()` + +On Chromium below 105 (webOS TV 22 and 23) every nested nav entry was hidden with nothing to open +it. Now the tree folds only where `:has()` works and stays fully open elsewhere. The theme switch, +which also needs `:has()`, is hidden there. + +### Text meets 4.5:1 contrast + +The primary button and the current page button take their text colour from the new `--on-accent` +token: white in light mode, near-black in dark mode, where white on the accent was 2.7:1. The +light-mode success, warning and info badge text is darker. A plugin that overrides `--accent` or +`--l-accent` should set `--l-on-accent` and `--d-on-accent` to match. + +### Unused auth stylesheet rules are gone + +`public/css/auth.css` no longer holds the `.auth-view` screen switch, `.tpl-*` preview controls, +`.forgot-alert`, `.register-alert`, `.remember` and `.auth-foot`; no core view used them. A plugin +view that did must carry its own rules. + ### Colours render on engines without `oklch()` The theme palette in `public/css/styles.css` is hex, not `oklch()`. On an engine older than diff --git a/docs/reading-cost.md b/docs/reading-cost.md index d281e8b..6a5a028 100644 --- a/docs/reading-cost.md +++ b/docs/reading-cost.md @@ -16,3 +16,17 @@ Read most (opens and searches): - `ci.sh` 1 - `examples/plugins/scheduling/views/shifts.ejs` 1 - `src/auth/csrf.ts` 1 + +## Reading cost, chunk df5e30a4db5f, finished 2026-10-31T00:22:00Z + +80251 new input tokens before the first commit. + +Read most (opens and searches): + +- `todo.md` 5 +- `README.md` 3 +- `CHANGELOG.md` 2 +- `public/css/styles.css` 2 +- `AGENTS.md` 1 +- `src/ui/stylesheet-floor.test.ts` 1 +- `src/ui/theme-switch.test.ts` 1 diff --git a/examples/plugins/scheduling/public/scheduling.css b/examples/plugins/scheduling/public/scheduling.css index d284249..a50fe5f 100644 --- a/examples/plugins/scheduling/public/scheduling.css +++ b/examples/plugins/scheduling/public/scheduling.css @@ -4,3 +4,9 @@ .scheduling-page .table-wrap { margin-top: var(--space-3, 0.75rem); } +/* Inset the elements that sit directly in the pane like the filter bar, table and pager around them. */ +.scheduling-page > p, +.scheduling-page > .alert, +.scheduling-page > .btn { + margin-inline: var(--pad-x); +} diff --git a/public/css/auth.css b/public/css/auth.css index dbee49f..777be1e 100644 --- a/public/css/auth.css +++ b/public/css/auth.css @@ -1,9 +1,6 @@ /* ============================================================= Auth flow — login / register / forgot password - Built on the shared tokens in styles.css. Pure HTML/CSS: - - screens switch via :target (no JS) - - SSO is a config section toggled by a class/checkbox; providers - are a
under any field and gate - it on whatever state your server sets. - ============================================================= */ +/* field-level error, shown under a field the server marked .has-error */ .field-error { display: none; align-items: center; gap: 6px; margin: 0; font-size: var(--fz-xs); color: var(--neg); @@ -213,36 +134,3 @@ body:has(#forgot:target) .tpl-forgot { display: flex; } .field.has-error .field-error { display: flex; } /* server-rendered field shows its own error */ .field-error > .ico { flex: 0 0 auto; color: var(--neg); } .field-error a { color: var(--neg); font-weight: 600; text-decoration: underline; } - -/* register form-level error banner (pairs with field-level errors) */ -.register-alert { display: none; } - -/* --- state A: email already taken — field-level error only --- */ -#register.state-email-taken .err-email, -body:has(#rstate-taken:checked) #register .err-email { display: flex; } -#register.state-email-taken #reg-email, -body:has(#rstate-taken:checked) #register #reg-email { border-color: var(--neg); } - -/* --- state B: multiple errors — banner + several field errors together --- */ -#register.state-invalid .register-alert, -body:has(#rstate-combined:checked) #register .register-alert { display: flex; } -#register.state-invalid .err-email, -#register.state-invalid .err-password, -body:has(#rstate-combined:checked) #register .err-email, -body:has(#rstate-combined:checked) #register .err-password { display: flex; } -#register.state-invalid #reg-email, -#register.state-invalid #reg-password, -body:has(#rstate-combined:checked) #register #reg-email, -body:has(#rstate-combined:checked) #register #reg-password { border-color: var(--neg); } - -/* red focus ring on any invalid register input */ -#register.state-email-taken #reg-email:focus, -#register.state-invalid #reg-email:focus, -#register.state-invalid #reg-password:focus, -body:has(#rstate-taken:checked) #register #reg-email:focus, -body:has(#rstate-combined:checked) #register #reg-email:focus, -body:has(#rstate-combined:checked) #register #reg-password:focus { box-shadow: 0 0 0 2px var(--neg-bg); } - -/* the Register-state control only appears while the register view is open */ -.tpl-register { display: none; } -body:has(#register:target) .tpl-register { display: flex; } diff --git a/public/css/styles.css b/public/css/styles.css index 8074e4a..9c02c7a 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -28,18 +28,18 @@ --l-border:#e5e5e8; --l-border-2:#d6d6da; --l-text:#18181b; --l-text-muted:#6b6b73; --l-text-faint:#9a9aa2; --l-accent:#3669ac; --l-accent-bg:#e5f3ff; - --l-accent-bd:#a7c7f2; --l-focus:#2a68b7; - --l-pos:#258343; --l-pos-bg:#e1f5e4; --l-pos-bd:#a8d5b0; + --l-accent-bd:#a7c7f2; --l-focus:#2a68b7; --l-on-accent:#ffffff; + --l-pos:#1f7a3d; --l-pos-bg:#e1f5e4; --l-pos-bd:#a8d5b0; --l-neg:#c53637; --l-neg-bg:#ffebe8; --l-neg-bd:#febab4; - --l-warn:#9a6500; --l-warn-bg:#fef0d4; --l-warn-bd:#e3c78e; - --l-info:#2274af;--l-info-bg:#e2f5ff;--l-info-bd:#a2cdf2; + --l-warn:#8f5d00; --l-warn-bg:#fef0d4; --l-warn-bd:#e3c78e; + --l-info:#1d6aa5;--l-info-bg:#e2f5ff;--l-info-bd:#a2cdf2; /* ---- raw DARK palette ---- */ --d-bg:#0d0d0f; --d-surface:#161619; --d-surface-2:#1d1d22; --d-surface-3:#1a1a1e; --d-border:#2a2a31; --d-border-2:#3a3a43; --d-text:#ededf0; --d-text-muted:#9b9ba6; --d-text-faint:#6a6a74; --d-accent:#6ba0e8; --d-accent-bg:#1d3351; - --d-accent-bd:#3f5f8a; --d-focus:#72adfb; + --d-accent-bd:#3f5f8a; --d-focus:#72adfb; --d-on-accent:#0d0d0f; --d-pos:#62c37a; --d-pos-bg:#1a3520; --d-pos-bd:#376040; --d-neg:#ff7e76; --d-neg-bg:#4b1d1b; --d-neg-bd:#8a423d; --d-warn:#e4b750; --d-warn-bg:#3e2d10; --d-warn-bd:#72592c; @@ -49,7 +49,7 @@ --bg:var(--l-bg); --surface:var(--l-surface); --surface-2:var(--l-surface-2); --surface-3:var(--l-surface-3); --border:var(--l-border); --border-2:var(--l-border-2); --text:var(--l-text); --text-muted:var(--l-text-muted); --text-faint:var(--l-text-faint); - --accent:var(--l-accent); --accent-bg:var(--l-accent-bg); --accent-bd:var(--l-accent-bd); --focus:var(--l-focus); + --accent:var(--l-accent); --accent-bg:var(--l-accent-bg); --accent-bd:var(--l-accent-bd); --focus:var(--l-focus); --on-accent:var(--l-on-accent); --pos:var(--l-pos); --pos-bg:var(--l-pos-bg); --pos-bd:var(--l-pos-bd); --neg:var(--l-neg); --neg-bg:var(--l-neg-bg); --neg-bd:var(--l-neg-bd); --warn:var(--l-warn); --warn-bg:var(--l-warn-bg); --warn-bd:var(--l-warn-bd); @@ -76,7 +76,7 @@ --bg:var(--d-bg); --surface:var(--d-surface); --surface-2:var(--d-surface-2); --surface-3:var(--d-surface-3); --border:var(--d-border); --border-2:var(--d-border-2); --text:var(--d-text); --text-muted:var(--d-text-muted); --text-faint:var(--d-text-faint); - --accent:var(--d-accent); --accent-bg:var(--d-accent-bg); --accent-bd:var(--d-accent-bd); --focus:var(--d-focus); + --accent:var(--d-accent); --accent-bg:var(--d-accent-bg); --accent-bd:var(--d-accent-bd); --focus:var(--d-focus); --on-accent:var(--d-on-accent); --pos:var(--d-pos); --pos-bg:var(--d-pos-bg); --pos-bd:var(--d-pos-bd); --neg:var(--d-neg); --neg-bg:var(--d-neg-bg); --neg-bd:var(--d-neg-bd); --warn:var(--d-warn); --warn-bg:var(--d-warn-bg); --warn-bd:var(--d-warn-bd); @@ -90,7 +90,7 @@ html:has(#theme-dark:checked) { --bg:var(--d-bg); --surface:var(--d-surface); --surface-2:var(--d-surface-2); --surface-3:var(--d-surface-3); --border:var(--d-border); --border-2:var(--d-border-2); --text:var(--d-text); --text-muted:var(--d-text-muted); --text-faint:var(--d-text-faint); - --accent:var(--d-accent); --accent-bg:var(--d-accent-bg); --accent-bd:var(--d-accent-bd); --focus:var(--d-focus); + --accent:var(--d-accent); --accent-bg:var(--d-accent-bg); --accent-bd:var(--d-accent-bd); --focus:var(--d-focus); --on-accent:var(--d-on-accent); --pos:var(--d-pos); --pos-bg:var(--d-pos-bg); --pos-bd:var(--d-pos-bd); --neg:var(--d-neg); --neg-bg:var(--d-neg-bg); --neg-bd:var(--d-neg-bd); --warn:var(--d-warn); --warn-bg:var(--d-warn-bg); --warn-bd:var(--d-warn-bd); @@ -103,7 +103,7 @@ html:has(#theme-light:checked) { --bg:var(--l-bg); --surface:var(--l-surface); --surface-2:var(--l-surface-2); --surface-3:var(--l-surface-3); --border:var(--l-border); --border-2:var(--l-border-2); --text:var(--l-text); --text-muted:var(--l-text-muted); --text-faint:var(--l-text-faint); - --accent:var(--l-accent); --accent-bg:var(--l-accent-bg); --accent-bd:var(--l-accent-bd); --focus:var(--l-focus); + --accent:var(--l-accent); --accent-bg:var(--l-accent-bg); --accent-bd:var(--l-accent-bd); --focus:var(--l-focus); --on-accent:var(--l-on-accent); --pos:var(--l-pos); --pos-bg:var(--l-pos-bg); --pos-bd:var(--l-pos-bd); --neg:var(--l-neg); --neg-bg:var(--l-neg-bg); --neg-bd:var(--l-neg-bd); --warn:var(--l-warn); --warn-bg:var(--l-warn-bg); --warn-bd:var(--l-warn-bd); @@ -171,9 +171,9 @@ summary { list-style: none; cursor: pointer; } .brand-mark { width: 22px; height: 22px; border-radius: 5px; flex: 0 0 auto; background: var(--accent); - display: grid; place-items: center; color: #fff; + display: grid; place-items: center; color: var(--on-accent); } -.brand-mark .ico { stroke: #fff; } +.brand-mark .ico { stroke: var(--on-accent); } .brand-logo { width: 22px; height: 22px; border-radius: 5px; flex: 0 0 auto; object-fit: contain; } .brand-name { font-weight: 650; letter-spacing: -.01em; } .brand-sub { color: var(--text-faint); font-size: var(--fz-xs); margin-left: auto; } @@ -191,9 +191,10 @@ summary { list-style: none; cursor: pointer; } • CLICKABLE = label is → navigates on click. STATIC = label is → not a navigation target. So "header" and "clickable" are independent: any combination works. - Children sit in a sibling