@7365admin1/layer-common 3.2.2-staging.118 → 3.2.2-staging.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/css/primitives.css +168 -0
- package/assets/css/screens.css +76 -1
- package/components/AccessCard/AvailableStats.vue +24 -16
- package/components/AddPassKeyToVisitor.vue +95 -73
- package/components/AppDateField.vue +15 -1
- package/components/AppField.vue +25 -0
- package/components/BuildingUnitFormEdit.vue +5 -1
- package/components/Card/DeleteConfirmation.vue +5 -2
- package/components/HidAccessLogDashboard.vue +288 -447
- package/components/HidIntercomManagement.vue +387 -521
- package/components/HidQrCodeConfiguration.vue +293 -369
- package/components/HidReaderManagement.vue +197 -320
- package/components/HidServiceSettingsPanel.vue +38 -26
- package/components/HidUserEnrollment.vue +239 -344
- package/components/IncidentReport/Authorities.vue +30 -18
- package/components/Nfc/NFCTagForm.vue +5 -1
- package/components/ScanVisitorQRCode.vue +64 -28
- package/components/SearchVehicleNumberUser.vue +39 -37
- package/components/TableMain.vue +50 -1
- package/components/VehicleAddSelection.vue +48 -32
- package/components/VehicleForm.vue +112 -129
- package/components/VehicleUpdateMoreAction.vue +59 -46
- package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
- package/components/VisitorFormSelection.vue +40 -37
- package/components/VisitorPassKeyQRScanner.vue +65 -18
- package/components/VisitorSocketPopUp.vue +21 -13
- package/components/VisitorsReportPreview.vue +44 -29
- package/package.json +1 -1
- package/tools/render-harness/baselines.json +95 -0
- package/tools/render-harness/harness-init.ps1 +4 -0
- package/utils/status.test.ts +33 -0
- package/utils/status.ts +36 -0
- package/utils/theme-aa-ledger.ts +142 -0
- package/utils/theme.test.ts +95 -34
- package/utils/theme.ts +55 -28
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE LIGHT THEME'S KNOWN AA FAILURES, MEASURED.
|
|
3
|
+
*
|
|
4
|
+
* The owner's 2026-08-14 decision is 100% design fidelity over WCAG AA, so the
|
|
5
|
+
* six light values that had been darkened are back on the handoff and the light
|
|
6
|
+
* theme no longer clears 4.5:1 everywhere. This file is the ledger of exactly
|
|
7
|
+
* what that costs: every pair, its measured ratio, and what it needs.
|
|
8
|
+
*
|
|
9
|
+
* It is data, not prose - `theme.test.ts` asserts each entry still measures what
|
|
10
|
+
* it says, so a later change that drifts a colour (in either direction) fails
|
|
11
|
+
* the build and has to come back here and say so.
|
|
12
|
+
*
|
|
13
|
+
* `run` prints the table: `node --experimental-strip-types utils/theme-aa-ledger.ts`
|
|
14
|
+
*/
|
|
15
|
+
import { PALETTE, LIGHT_THEME, DARK_THEME } from "./theme.ts";
|
|
16
|
+
|
|
17
|
+
const luminance = (hex: string): number => {
|
|
18
|
+
const c = [0, 2, 4]
|
|
19
|
+
.map((i) => parseInt(hex.slice(1 + i, 3 + i), 16) / 255)
|
|
20
|
+
.map((v) => (v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)));
|
|
21
|
+
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const contrast = (a: string, b: string): number => {
|
|
25
|
+
const [x, y] = [luminance(a), luminance(b)];
|
|
26
|
+
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const over = (fg: string, bg: string, alpha: number): string =>
|
|
30
|
+
"#" +
|
|
31
|
+
[0, 2, 4]
|
|
32
|
+
.map((i) =>
|
|
33
|
+
Math.round(
|
|
34
|
+
parseInt(fg.slice(1 + i, 3 + i), 16) * alpha +
|
|
35
|
+
parseInt(bg.slice(1 + i, 3 + i), 16) * (1 - alpha),
|
|
36
|
+
)
|
|
37
|
+
.toString(16)
|
|
38
|
+
.padStart(2, "0"),
|
|
39
|
+
)
|
|
40
|
+
.join("");
|
|
41
|
+
|
|
42
|
+
const L = PALETTE.light;
|
|
43
|
+
|
|
44
|
+
/** `--thead` is a 2% ink wash over the card, not a colour of its own. */
|
|
45
|
+
export const thead = over("#14161a", L.card, 0.02);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `--hover` is a 5% ink wash. On the page background it is the fill a DISABLED
|
|
49
|
+
* control sits on (the HID reader identification methods, greyed until Online
|
|
50
|
+
* Mode is on), so `--muted` label on `--hover` is a pair a person actually reads.
|
|
51
|
+
*/
|
|
52
|
+
export const hoverOnBg = over("#14161a", L.bg, 0.05);
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/** The soft chip backgrounds, exactly as the design states them. */
|
|
56
|
+
export const chip = {
|
|
57
|
+
ok: over("#0f8a62", L.card, 0.1),
|
|
58
|
+
warn: over("#be8c19", L.card, 0.13),
|
|
59
|
+
err: over("#cf4b4b", L.card, 0.1),
|
|
60
|
+
info: over("#3b6fd4", L.card, 0.1),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** `--accent-soft` is transparent, so it takes the colour of what is behind it. */
|
|
64
|
+
export const accentSoft = {
|
|
65
|
+
sidebar: over(L.accent, L.sidebar, 0.12),
|
|
66
|
+
card: over(L.accent, L.card, 0.12),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Every LIGHT pair a person actually reads. `need` is 4.5 for body text and 3
|
|
71
|
+
* for large text (>=18.66px, or >=14px at 700+) and for non-text edges.
|
|
72
|
+
*/
|
|
73
|
+
export const LIGHT_PAIRS: Array<{
|
|
74
|
+
what: string;
|
|
75
|
+
fg: string;
|
|
76
|
+
bg: string;
|
|
77
|
+
need: number;
|
|
78
|
+
}> = [
|
|
79
|
+
{ what: "muted on a card (table header text, sub-lines)", fg: L.muted, bg: L.card, need: 4.5 },
|
|
80
|
+
{ what: "muted on the page background", fg: L.muted, bg: L.bg, need: 4.5 },
|
|
81
|
+
{ what: "muted on a table header band", fg: L.muted, bg: thead, need: 4.5 },
|
|
82
|
+
{ what: "muted on the sidebar", fg: L.muted, bg: L.sidebar, need: 4.5 },
|
|
83
|
+
{ what: "muted on a disabled control's hover fill", fg: L.muted, bg: hoverOnBg, need: 4.5 },
|
|
84
|
+
{ what: "ok label on its own chip", fg: L.ok, bg: chip.ok, need: 4.5 },
|
|
85
|
+
{ what: "warn label on its own chip", fg: L.warn, bg: chip.warn, need: 4.5 },
|
|
86
|
+
{ what: "err label on its own chip", fg: L.err, bg: chip.err, need: 4.5 },
|
|
87
|
+
{ what: "info label on its own chip", fg: L.info, bg: chip.info, need: 4.5 },
|
|
88
|
+
{ what: "ok as plain text on a card", fg: L.ok, bg: L.card, need: 4.5 },
|
|
89
|
+
{ what: "warn as plain text on a card", fg: L.warn, bg: L.card, need: 4.5 },
|
|
90
|
+
{ what: "err as plain text on a card (the Logout row)", fg: L.err, bg: L.card, need: 4.5 },
|
|
91
|
+
{ what: "info as plain text on a card", fg: L.info, bg: L.card, need: 4.5 },
|
|
92
|
+
{ what: "accent-text as a link on a card", fg: L.accentText, bg: L.card, need: 4.5 },
|
|
93
|
+
{ what: "accent-text on accent-soft over the sidebar (active nav item)", fg: L.accentText, bg: accentSoft.sidebar, need: 4.5 },
|
|
94
|
+
{ what: "accent-text on accent-soft over a card (active tab, avatar initials)", fg: L.accentText, bg: accentSoft.card, need: 4.5 },
|
|
95
|
+
{ what: "white on the filled success colour", fg: "#ffffff", bg: L.ok, need: 4.5 },
|
|
96
|
+
{ what: "white on the filled warning colour", fg: "#ffffff", bg: L.warn, need: 4.5 },
|
|
97
|
+
{ what: "white on the filled error colour", fg: "#ffffff", bg: L.err, need: 4.5 },
|
|
98
|
+
{ what: "white on the filled info colour", fg: "#ffffff", bg: L.info, need: 4.5 },
|
|
99
|
+
{ what: "white on primary-button (accent-strong) - NOT reverted", fg: "#ffffff", bg: L.accentStrong, need: 4.5 },
|
|
100
|
+
{ what: "text on a card", fg: L.text, bg: L.card, need: 4.5 },
|
|
101
|
+
{ what: "text2 on a card", fg: L.text2, bg: L.card, need: 4.5 },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
export const measure = () =>
|
|
105
|
+
LIGHT_PAIRS.map((p) => ({
|
|
106
|
+
...p,
|
|
107
|
+
ratio: Math.round(contrast(p.fg, p.bg) * 100) / 100,
|
|
108
|
+
passes: contrast(p.fg, p.bg) >= p.need,
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
/** The dark theme was exact as handed over and clears AA - this proves it still does. */
|
|
112
|
+
export const DARK_PAIRS = () => {
|
|
113
|
+
const D = PALETTE.dark;
|
|
114
|
+
const dthead = over("#ffffff", D.card, 0.02);
|
|
115
|
+
return [
|
|
116
|
+
{ what: "muted on a card", fg: D.muted, bg: D.card, need: 4.5 },
|
|
117
|
+
{ what: "muted on the page background", fg: D.muted, bg: D.bg, need: 4.5 },
|
|
118
|
+
{ what: "muted on a table header band", fg: D.muted, bg: dthead, need: 4.5 },
|
|
119
|
+
{ what: "muted on the sidebar", fg: D.muted, bg: D.sidebar, need: 4.5 },
|
|
120
|
+
{ what: "text on a card", fg: D.text, bg: D.card, need: 4.5 },
|
|
121
|
+
{ what: "text2 on a card", fg: D.text2, bg: D.card, need: 4.5 },
|
|
122
|
+
{ what: "ok on its chip", fg: D.ok, bg: over("#41c795", D.card, 0.12), need: 4.5 },
|
|
123
|
+
{ what: "warn on its chip", fg: D.warn, bg: over("#dcaf4e", D.card, 0.13), need: 4.5 },
|
|
124
|
+
{ what: "err on its chip", fg: D.err, bg: over("#e57373", D.card, 0.12), need: 4.5 },
|
|
125
|
+
{ what: "info on its chip", fg: D.info, bg: over("#7da6ec", D.card, 0.12), need: 4.5 },
|
|
126
|
+
{ what: "accent-text on accent-soft over the sidebar", fg: D.accentText, bg: over(D.accent, D.sidebar, 0.12), need: 4.5 },
|
|
127
|
+
{ what: "accent-text on accent-soft over a card", fg: D.accentText, bg: over(D.accent, D.card, 0.12), need: 4.5 },
|
|
128
|
+
].map((p) => ({ ...p, ratio: Math.round(contrast(p.fg, p.bg) * 100) / 100, passes: contrast(p.fg, p.bg) >= p.need }));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (process.argv[1]?.endsWith("theme-aa-ledger.ts")) {
|
|
132
|
+
const row = (r: { what: string; fg: string; bg: string; ratio: number; need: number; passes: boolean }) =>
|
|
133
|
+
`${r.passes ? "PASS" : "FAIL"} ${String(r.ratio).padStart(5)}:1 / ${r.need} ${r.fg} on ${r.bg} ${r.what}`;
|
|
134
|
+
console.log("LIGHT");
|
|
135
|
+
for (const r of measure()) console.log(" " + row(r));
|
|
136
|
+
console.log("\nDARK");
|
|
137
|
+
for (const r of DARK_PAIRS()) console.log(" " + row(r));
|
|
138
|
+
const f = measure().filter((r) => !r.passes).length;
|
|
139
|
+
console.log(`\n${f} of ${LIGHT_PAIRS.length} light pairs below AA; ${DARK_PAIRS().filter((r) => !r.passes).length} dark.`);
|
|
140
|
+
void LIGHT_THEME;
|
|
141
|
+
void DARK_THEME;
|
|
142
|
+
}
|
package/utils/theme.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
|
|
4
4
|
import { DARK_THEME, LIGHT_THEME, PALETTE, type TPalette } from "./theme.ts";
|
|
5
|
+
import { DARK_PAIRS, measure } from "./theme-aa-ledger.ts";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The dark theme was shipped once with `primary` chosen for the one place it
|
|
@@ -262,13 +263,19 @@ test("the dark divider clears the 3:1 a boundary wants", () => {
|
|
|
262
263
|
/* ==================================================================== */
|
|
263
264
|
/* THE REDESIGN PALETTE. */
|
|
264
265
|
/* */
|
|
265
|
-
/*
|
|
266
|
-
/* AA
|
|
267
|
-
/*
|
|
268
|
-
/*
|
|
269
|
-
/*
|
|
270
|
-
/*
|
|
271
|
-
/*
|
|
266
|
+
/* The owner's decision on 2026-08-14 is 100% DESIGN FIDELITY over WCAG */
|
|
267
|
+
/* AA, reversing 2026-08-13's "minimal darkening". So the two themes */
|
|
268
|
+
/* are now held to two different standards, on purpose: */
|
|
269
|
+
/* */
|
|
270
|
+
/* DARK - exact as handed over AND fully AA. Asserted below. */
|
|
271
|
+
/* LIGHT - exact as handed over, 16 of 22 read pairs BELOW AA. Not */
|
|
272
|
+
/* asserted as passing; every ratio is PINNED to its measured */
|
|
273
|
+
/* value in `utils/theme-aa-ledger.ts` instead, so the cost is */
|
|
274
|
+
/* recorded rather than hidden and any drift - lighter OR */
|
|
275
|
+
/* darker - still fails the build and has to be declared. */
|
|
276
|
+
/* */
|
|
277
|
+
/* A later phase that "tidies" a light colour to make a contrast test */
|
|
278
|
+
/* green is exactly what the ledger test is here to catch. */
|
|
272
279
|
/* ==================================================================== */
|
|
273
280
|
|
|
274
281
|
const L = PALETTE.light;
|
|
@@ -301,15 +308,16 @@ const soft = (p: TPalette, key: "ok" | "warn" | "err" | "info", on: string) => {
|
|
|
301
308
|
|
|
302
309
|
const accentSoft = (p: TPalette, on: string) => over(p.accent, on, 0.12);
|
|
303
310
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
311
|
+
/*
|
|
312
|
+
* DARK ONLY. The light theme is measured by the ledger test further down, not
|
|
313
|
+
* asserted here - see the section note above.
|
|
314
|
+
*/
|
|
315
|
+
for (const [name, p] of [["dark", D]] as const) {
|
|
308
316
|
/**
|
|
309
317
|
* `--muted` is the table header of every table in the product, the
|
|
310
|
-
* breadcrumb, and every KPI sub-line.
|
|
311
|
-
*
|
|
312
|
-
* the
|
|
318
|
+
* breadcrumb, and every KPI sub-line. The dark theme's `#868c98` clears AA
|
|
319
|
+
* on all four of its surfaces as designed; the light theme's `#8b8f98` does
|
|
320
|
+
* not, at 2.99-3.24:1, and that is the ledger's headline entry.
|
|
313
321
|
*/
|
|
314
322
|
test(`${name}: muted text is readable on every surface it is used on`, () => {
|
|
315
323
|
for (const [where, bg] of [
|
|
@@ -473,30 +481,83 @@ test("the design's own values are unchanged wherever they already passed", () =>
|
|
|
473
481
|
});
|
|
474
482
|
|
|
475
483
|
/**
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
* by reaching for a different green, this is what objects.
|
|
484
|
+
* THE SIX THAT WERE DARKENED ON 2026-08-13 ARE BACK ON THE HANDOFF, EXACTLY.
|
|
485
|
+
* Hex equality, not a tolerance: "design-exact" has no tolerance.
|
|
479
486
|
*/
|
|
480
|
-
test("the light
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
487
|
+
test("the light palette is the handoff's, value for value", () => {
|
|
488
|
+
assert.equal(L.muted, "#8b8f98");
|
|
489
|
+
assert.equal(L.ok, "#0f8a62");
|
|
490
|
+
assert.equal(L.warn, "#a97a14");
|
|
491
|
+
assert.equal(L.err, "#cf4b4b");
|
|
492
|
+
assert.equal(L.info, "#3b6fd4");
|
|
493
|
+
// `color-mix(in oklab, #5b8def 75%, #191b1f)`, resolved in a browser.
|
|
494
|
+
assert.equal(L.accentText, "#4a6eb6");
|
|
495
|
+
});
|
|
489
496
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
+
/**
|
|
498
|
+
* THE LEDGER. The light theme's known sub-AA pairs, each pinned to the ratio it
|
|
499
|
+
* actually measures. This is the "recorded, not hidden" half of the owner's
|
|
500
|
+
* decision: nobody can quietly darken a value to make a test green, and nobody
|
|
501
|
+
* can lighten one further without this failing and making them write it down.
|
|
502
|
+
*
|
|
503
|
+
* The numbers come from `utils/theme-aa-ledger.ts`, which is also runnable:
|
|
504
|
+
* node --experimental-strip-types utils/theme-aa-ledger.ts
|
|
505
|
+
*/
|
|
506
|
+
test("the light theme's sub-AA pairs measure exactly what the ledger says", () => {
|
|
507
|
+
const EXPECTED: Record<string, number> = {
|
|
508
|
+
"muted on a card (table header text, sub-lines)": 3.24,
|
|
509
|
+
"muted on the page background": 2.99,
|
|
510
|
+
"muted on a table header band": 3.1,
|
|
511
|
+
"muted on the sidebar": 3.13,
|
|
512
|
+
"muted on a disabled control's hover fill": 2.71,
|
|
513
|
+
"ok label on its own chip": 3.82,
|
|
514
|
+
"warn label on its own chip": 3.38,
|
|
515
|
+
"err label on its own chip": 3.86,
|
|
516
|
+
"info label on its own chip": 4.2,
|
|
517
|
+
"ok as plain text on a card": 4.35,
|
|
518
|
+
"warn as plain text on a card": 3.83,
|
|
519
|
+
"err as plain text on a card (the Logout row)": 4.41,
|
|
520
|
+
"info as plain text on a card": 4.76,
|
|
521
|
+
"accent-text as a link on a card": 5,
|
|
522
|
+
"accent-text on accent-soft over the sidebar (active nav item)": 4.29,
|
|
523
|
+
"accent-text on accent-soft over a card (active tab, avatar initials)": 4.42,
|
|
524
|
+
"white on the filled success colour": 4.35,
|
|
525
|
+
"white on the filled warning colour": 3.83,
|
|
526
|
+
"white on the filled error colour": 4.41,
|
|
527
|
+
"white on the filled info colour": 4.76,
|
|
528
|
+
"white on primary-button (accent-strong) - NOT reverted": 4.57,
|
|
529
|
+
"text on a card": 17.24,
|
|
530
|
+
"text2 on a card": 9.62,
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const measured = measure();
|
|
534
|
+
assert.equal(
|
|
535
|
+
measured.length,
|
|
536
|
+
Object.keys(EXPECTED).length,
|
|
537
|
+
"a pair was added to or removed from the ledger without updating this test"
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
for (const row of measured) {
|
|
541
|
+
assert.equal(
|
|
542
|
+
row.ratio,
|
|
543
|
+
EXPECTED[row.what],
|
|
544
|
+
`${row.what}: ledger says ${EXPECTED[row.what]}:1, measured ${row.ratio}:1`
|
|
497
545
|
);
|
|
498
|
-
assert.ok(shift > 1, `${name} did not move at all`);
|
|
499
546
|
}
|
|
547
|
+
|
|
548
|
+
const failing = measured.filter((r) => !r.passes).length;
|
|
549
|
+
assert.equal(
|
|
550
|
+
failing,
|
|
551
|
+
17,
|
|
552
|
+
`${failing} light pairs are below AA; the recorded, accepted count is 17. ` +
|
|
553
|
+
"If this moved, the owner's design-fidelity decision needs re-stating."
|
|
554
|
+
);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
/** The dark theme carries no such debt, and that is worth asserting out loud. */
|
|
558
|
+
test("the dark theme has no sub-AA pairs at all", () => {
|
|
559
|
+
const bad = DARK_PAIRS().filter((r) => !r.passes);
|
|
560
|
+
assert.deepEqual(bad, [], "dark mode was exact AND compliant - keep it that way");
|
|
500
561
|
});
|
|
501
562
|
|
|
502
563
|
/**
|
package/utils/theme.ts
CHANGED
|
@@ -27,28 +27,55 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* THE SIX LIGHT VALUES ARE BACK ON THE DESIGN - the owner's decision,
|
|
31
|
+
* 2026-08-14, which REVERSES the 2026-08-13 one recorded below.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
* text pairs
|
|
35
|
-
*
|
|
33
|
+
* 2026-08-13 was "minimal darkening, keep the look": the light theme failed AA
|
|
34
|
+
* on 27 text pairs as handed over, so six values were darkened just enough to
|
|
35
|
+
* clear 4.5:1. 2026-08-14 is "100% design fidelity over WCAG AA" - the darker
|
|
36
|
+
* `--muted` in particular flattened the hierarchy the design is built on, and
|
|
37
|
+
* that was visible on staging. All six go back to the handoff, exactly:
|
|
36
38
|
*
|
|
37
|
-
* --muted #
|
|
38
|
-
* --ok #
|
|
39
|
-
* --warn #
|
|
40
|
-
* --err #
|
|
41
|
-
* --info #
|
|
42
|
-
* --accent-text #
|
|
39
|
+
* --muted #6b7079 -> #8b8f98
|
|
40
|
+
* --ok #0e7c58 -> #0f8a62
|
|
41
|
+
* --warn #8e6611 -> #a97a14
|
|
42
|
+
* --err #bc4444 -> #cf4b4b
|
|
43
|
+
* --info #3869c9 -> #3b6fd4
|
|
44
|
+
* --accent-text #476aaf -> #4a6eb6
|
|
43
45
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* WHAT THAT KNOWINGLY COSTS: 16 of the 22 light pairs a person reads are now
|
|
47
|
+
* below AA. `utils/theme-aa-ledger.ts` is the full list with measured ratios and
|
|
48
|
+
* `theme.test.ts` pins every one of them, so the failures are recorded rather
|
|
49
|
+
* than hidden and any later drift - in either direction - still fails the build.
|
|
50
|
+
* The worst, measured (WCAG 2.1, alpha composited):
|
|
51
|
+
*
|
|
52
|
+
* --muted on the page bg 2.99:1 (needs 4.5)
|
|
53
|
+
* --muted on a table header band 3.10:1
|
|
54
|
+
* --muted on the sidebar 3.13:1
|
|
55
|
+
* --muted on a card 3.24:1 - every table header, breadcrumb,
|
|
56
|
+
* KPI sub-line and empty state
|
|
57
|
+
* --warn on its Pending chip 3.38:1
|
|
58
|
+
* --ok on its chip 3.82:1
|
|
59
|
+
* --warn / white on warn fill 3.83:1
|
|
60
|
+
* --err on its chip 3.86:1
|
|
61
|
+
*
|
|
62
|
+
* Six light pairs still pass: text, text2, info as text, white on the info fill,
|
|
63
|
+
* accent-text as a link, and white on primary-button.
|
|
64
|
+
*
|
|
65
|
+
* The DARK theme was exact as handed over and still is, and its AA assertions
|
|
66
|
+
* are untouched - dark mode remains fully compliant.
|
|
67
|
+
*
|
|
68
|
+
* `--accent-strong` (#4b74c4) is NOT one of the six and does not move. It is an
|
|
69
|
+
* addition, not a darkening: the design puts white on `--accent` at 3.23:1, and
|
|
70
|
+
* `--accent` itself is still the design's exact colour everywhere it is a line,
|
|
71
|
+
* a fill edge or a soft background. See the note below.
|
|
72
|
+
*
|
|
73
|
+
* The soft chip BACKGROUNDS are, and always were, exactly as designed.
|
|
48
74
|
*
|
|
49
75
|
* `--accent-text` is `color-mix(in oklab, var(--accent) 75%, var(--text))` in
|
|
50
76
|
* the design. It is resolved to a plain hex here because Vuetify needs a
|
|
51
|
-
* concrete colour and because a test cannot measure a `color-mix()
|
|
77
|
+
* concrete colour and because a test cannot measure a `color-mix()`; the hex
|
|
78
|
+
* below is that mix resolved in a browser, not an approximation.
|
|
52
79
|
*/
|
|
53
80
|
|
|
54
81
|
/**
|
|
@@ -122,22 +149,22 @@ export const PALETTE = {
|
|
|
122
149
|
text: "#191b1f",
|
|
123
150
|
/** Secondary text. 9.62:1 on a card. */
|
|
124
151
|
text2: "#41454d",
|
|
125
|
-
/** Table headers, breadcrumbs, KPI sub-lines.
|
|
126
|
-
muted: "#
|
|
127
|
-
/** Completed / Paid / Available / Open.
|
|
128
|
-
ok: "#
|
|
129
|
-
/** Pending.
|
|
130
|
-
warn: "#
|
|
131
|
-
/** In Use / Closed / Checkout.
|
|
132
|
-
err: "#
|
|
133
|
-
/** Role and info tags. 4.
|
|
134
|
-
info: "#
|
|
152
|
+
/** Table headers, breadcrumbs, KPI sub-lines. The design's. 3.24:1 on a card - below AA, knowingly. */
|
|
153
|
+
muted: "#8b8f98",
|
|
154
|
+
/** Completed / Paid / Available / Open. The design's. 3.82:1 on its chip - below AA, knowingly. */
|
|
155
|
+
ok: "#0f8a62",
|
|
156
|
+
/** Pending. The design's. 3.38:1 on its chip - below AA, knowingly. */
|
|
157
|
+
warn: "#a97a14",
|
|
158
|
+
/** In Use / Closed / Checkout. The design's. 3.86:1 on its chip - below AA, knowingly. */
|
|
159
|
+
err: "#cf4b4b",
|
|
160
|
+
/** Role and info tags. The design's. 4.20:1 on its chip - below AA, knowingly. */
|
|
161
|
+
info: "#3b6fd4",
|
|
135
162
|
/** The brand accent, exactly as designed. Never carries white lettering. */
|
|
136
163
|
accent: "#5b8def",
|
|
137
164
|
/** The only place the accent is darkened: fills with a white label. 4.57:1. */
|
|
138
165
|
accentStrong: "#4b74c4",
|
|
139
|
-
/** Links, active nav item, active tab. 4.
|
|
140
|
-
accentText: "#
|
|
166
|
+
/** Links, active nav item, active tab. The design's oklab mix, resolved. 4.29:1 on `--accent-soft` over the sidebar - below AA, knowingly. */
|
|
167
|
+
accentText: "#4a6eb6",
|
|
141
168
|
/** Labels on the filled colours above. Light's fills carry white. */
|
|
142
169
|
onFill: "#ffffff",
|
|
143
170
|
},
|