@7365admin1/layer-common 3.2.1 → 3.2.2-staging.76
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/.changeset/camera-wall-gated-endpoint.md +47 -0
- package/.changeset/camera-wall-plain-english.md +60 -0
- package/.changeset/camera-wall-selection-and-guidance.md +65 -0
- package/.changeset/dark-primary-contrast.md +39 -0
- package/.changeset/dashboard-dark-theme-contrast.md +15 -0
- package/.changeset/service-provider-invitation-actions.md +28 -0
- package/components/CameraWall.vue +1034 -0
- package/components/CameraWallTile.vue +818 -0
- package/components/DashboardMain.vue +84 -88
- package/components/DashboardPanel.vue +1 -1
- package/components/EntryPassInformation.vue +4 -4
- package/components/FeedbackMain.vue +7 -7
- package/components/HidAccessLogDashboard.vue +119 -44
- package/components/HidCameraCaptureDialog.vue +199 -0
- package/components/HidIntercomManagement.vue +808 -201
- package/components/HidQrCodeConfiguration.vue +907 -95
- package/components/HidServiceSettingsPanel.vue +20 -1
- package/components/HidUserEnrollment.vue +392 -81
- package/components/Layout/NavigationDrawer.vue +43 -2
- package/components/NavigationItem.vue +9 -0
- package/components/Nfc/NFCPatrolRouteMain.vue +52 -7
- package/components/OnlineFormConfigurationForm.vue +620 -224
- package/components/OnlineFormFill.vue +36 -8
- package/components/OnlineFormsConfiguration.vue +6 -2
- package/components/QrTemplate/CreditCardLandscape.vue +19 -12
- package/components/QrTemplate/PrintDialog.vue +209 -196
- package/components/ServiceProviderInvitationPrompt.vue +150 -0
- package/components/ServiceProviderMain.vue +258 -14
- package/components/SiteSettings.vue +4 -2
- package/components/VisitorForm.vue +201 -78
- package/components/VisitorManagement.vue +2 -3
- package/components/VisitorSocketPopUp.vue +56 -2
- package/composables/useComment.ts +3 -3
- package/composables/useHidAmico.ts +127 -0
- package/composables/useHidNavigation.ts +0 -7
- package/composables/useLocalAuth.ts +9 -36
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +311 -0
- package/composables/useSiteSettings.ts +50 -0
- package/composables/useVisitorSocket.ts +26 -0
- package/composables/useWebUsb.ts +127 -37
- package/package.json +3 -1
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +37 -9
- package/test/visitor-socket.test.mjs +36 -0
- package/types/site.d.ts +71 -0
- package/utils/camera-wall.test.ts +571 -0
- package/utils/camera-wall.ts +926 -0
- package/utils/theme.test.ts +216 -0
- package/utils/theme.ts +163 -0
- package/components/HidIdentityMapping.vue +0 -975
- package/middleware/member.ts +0 -4
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { DARK_THEME, LIGHT_THEME } from "./theme.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The dark theme was shipped once with `primary` chosen for the one place it
|
|
8
|
+
* was a fill, which left the twenty-six places it is a sentence at 1.80:1.
|
|
9
|
+
* These are the measurements that would have caught it.
|
|
10
|
+
*
|
|
11
|
+
* WCAG 2.1 relative luminance and contrast ratio, nothing else.
|
|
12
|
+
*/
|
|
13
|
+
const luminance = (hex: string): number => {
|
|
14
|
+
const channels = [0, 2, 4]
|
|
15
|
+
.map((i) => parseInt(hex.slice(1 + i, 3 + i), 16) / 255)
|
|
16
|
+
.map((c) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)));
|
|
17
|
+
|
|
18
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const contrast = (a: string, b: string): number => {
|
|
22
|
+
const [x, y] = [luminance(a), luminance(b)];
|
|
23
|
+
|
|
24
|
+
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** `fg` at `alpha` composited over `bg` - an overlay is not its raw value. */
|
|
28
|
+
const over = (fg: string, bg: string, alpha: number): string =>
|
|
29
|
+
"#" +
|
|
30
|
+
[0, 2, 4]
|
|
31
|
+
.map((i) =>
|
|
32
|
+
Math.round(
|
|
33
|
+
parseInt(fg.slice(1 + i, 3 + i), 16) * alpha +
|
|
34
|
+
parseInt(bg.slice(1 + i, 3 + i), 16) * (1 - alpha)
|
|
35
|
+
)
|
|
36
|
+
.toString(16)
|
|
37
|
+
.padStart(2, "0")
|
|
38
|
+
)
|
|
39
|
+
.join("");
|
|
40
|
+
|
|
41
|
+
const WHITE = "#FFFFFF";
|
|
42
|
+
const dark = DARK_THEME.colors;
|
|
43
|
+
const light = LIGHT_THEME.colors;
|
|
44
|
+
|
|
45
|
+
/** Sanity check on the measuring tape before it is used to judge anything. */
|
|
46
|
+
test("contrast is measured the way WCAG measures it", () => {
|
|
47
|
+
assert.equal(contrast(WHITE, "#000000").toFixed(2), "21.00");
|
|
48
|
+
assert.equal(contrast("#777777", WHITE).toFixed(2), "4.48");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("dark primary is readable as text on the page, a card and a menu", () => {
|
|
52
|
+
assert.ok(
|
|
53
|
+
contrast(dark.primary, dark.background) >= 4.5,
|
|
54
|
+
`primary on the page: ${contrast(dark.primary, dark.background).toFixed(2)}`
|
|
55
|
+
);
|
|
56
|
+
assert.ok(
|
|
57
|
+
contrast(dark.primary, dark.surface) >= 4.5,
|
|
58
|
+
`primary on a card: ${contrast(dark.primary, dark.surface).toFixed(2)}`
|
|
59
|
+
);
|
|
60
|
+
assert.ok(
|
|
61
|
+
contrast(dark.primary, dark["surface-bright"]) >= 4.5,
|
|
62
|
+
`primary on surface-bright: ${contrast(
|
|
63
|
+
dark.primary,
|
|
64
|
+
dark["surface-bright"]
|
|
65
|
+
).toFixed(2)}`
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("light primary is readable as text on white", () => {
|
|
70
|
+
assert.ok(contrast(light.primary, WHITE) >= 4.5);
|
|
71
|
+
assert.ok(contrast(light["text-primary"], WHITE) >= 4.5);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("text-primary is readable on the surface each theme puts it on", () => {
|
|
75
|
+
assert.ok(contrast(dark["text-primary"], dark.background) >= 4.5);
|
|
76
|
+
assert.ok(contrast(dark["text-primary"], dark.surface) >= 4.5);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The drawer is the reason `primary` was moved in the first place. If someone
|
|
81
|
+
* points it back at `primary` this fails, because a foreground colour cannot
|
|
82
|
+
* carry the drawer's label.
|
|
83
|
+
*/
|
|
84
|
+
test("the nav drawer fill carries its own label in both themes", () => {
|
|
85
|
+
assert.ok(
|
|
86
|
+
contrast(dark["on-brand-surface"], dark["brand-surface"]) >= 4.5,
|
|
87
|
+
`dark drawer label: ${contrast(
|
|
88
|
+
dark["on-brand-surface"],
|
|
89
|
+
dark["brand-surface"]
|
|
90
|
+
).toFixed(2)}`
|
|
91
|
+
);
|
|
92
|
+
assert.ok(
|
|
93
|
+
contrast(light["on-brand-surface"], light["brand-surface"]) >= 4.5,
|
|
94
|
+
`light drawer label: ${contrast(
|
|
95
|
+
light["on-brand-surface"],
|
|
96
|
+
light["brand-surface"]
|
|
97
|
+
).toFixed(2)}`
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The defect this replaced: `brand-surface` was the same deep navy in both
|
|
103
|
+
* themes, so the drawer was the one panel the theme switch did not reach. A
|
|
104
|
+
* light-theme surface has to be light, a dark-theme surface has to be dark,
|
|
105
|
+
* and if the two are ever within 3:1 of each other the switch is invisible on
|
|
106
|
+
* the drawer again.
|
|
107
|
+
*/
|
|
108
|
+
test("the drawer belongs to its theme, and the switch visibly changes it", () => {
|
|
109
|
+
assert.ok(
|
|
110
|
+
contrast(light["brand-surface"], WHITE) < 1.5,
|
|
111
|
+
"the light drawer must be a light surface, not a dark slab on a white page"
|
|
112
|
+
);
|
|
113
|
+
assert.ok(
|
|
114
|
+
contrast(dark["brand-surface"], dark.background) < 1.5,
|
|
115
|
+
"the dark drawer must sit with the dark page, not float above it"
|
|
116
|
+
);
|
|
117
|
+
assert.ok(
|
|
118
|
+
contrast(light["brand-surface"], dark["brand-surface"]) >= 3,
|
|
119
|
+
`light and dark drawers only ${contrast(
|
|
120
|
+
light["brand-surface"],
|
|
121
|
+
dark["brand-surface"]
|
|
122
|
+
).toFixed(2)} apart - the switch would look like it does nothing`
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The drawer's fill is deliberately close to the page, so the edge is the
|
|
128
|
+
* boundary and it is the thing that has to reach 3:1. 0.45 is the value
|
|
129
|
+
* `NavigationDrawer.vue` sets on itself; the theme-wide `border-opacity` is
|
|
130
|
+
* left alone.
|
|
131
|
+
*/
|
|
132
|
+
test("the drawer's own edge clears the 3:1 a boundary wants", () => {
|
|
133
|
+
const edge = (fg: string, bg: string) => over(fg, bg, 0.45);
|
|
134
|
+
|
|
135
|
+
assert.ok(
|
|
136
|
+
contrast(edge("#000000", light["brand-surface"]), WHITE) >= 3,
|
|
137
|
+
`light drawer edge vs page: ${contrast(
|
|
138
|
+
edge("#000000", light["brand-surface"]),
|
|
139
|
+
WHITE
|
|
140
|
+
).toFixed(2)}`
|
|
141
|
+
);
|
|
142
|
+
assert.ok(
|
|
143
|
+
contrast(edge(WHITE, dark["brand-surface"]), dark.background) >= 3,
|
|
144
|
+
`dark drawer edge vs page: ${contrast(
|
|
145
|
+
edge(WHITE, dark["brand-surface"]),
|
|
146
|
+
dark.background
|
|
147
|
+
).toFixed(2)}`
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/** The active item is named by colour, so `primary` has to be readable on it. */
|
|
152
|
+
test("the active nav item is readable on the drawer in both themes", () => {
|
|
153
|
+
assert.ok(
|
|
154
|
+
contrast(light.primary, light["brand-surface"]) >= 4.5,
|
|
155
|
+
`light: ${contrast(light.primary, light["brand-surface"]).toFixed(2)}`
|
|
156
|
+
);
|
|
157
|
+
assert.ok(
|
|
158
|
+
contrast(dark.primary, dark["brand-surface"]) >= 4.5,
|
|
159
|
+
`dark: ${contrast(dark.primary, dark["brand-surface"]).toFixed(2)}`
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* `v-list-subheader` is the one thing in the drawer that does NOT inherit the
|
|
165
|
+
* drawer's label colour - Vuetify paints it `on-surface` at medium emphasis.
|
|
166
|
+
* That is why apps used to hardcode `text-white` on it. It has to work
|
|
167
|
+
* unaided now, or those hardcodes come back.
|
|
168
|
+
*/
|
|
169
|
+
test("an unstyled subheader is readable on the drawer in both themes", () => {
|
|
170
|
+
assert.ok(
|
|
171
|
+
contrast(over("#000000", light["brand-surface"], 0.6), light["brand-surface"]) >= 4.5
|
|
172
|
+
);
|
|
173
|
+
assert.ok(
|
|
174
|
+
contrast(over(WHITE, dark["brand-surface"], 0.6), dark["brand-surface"]) >= 4.5
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("primary-button carries a white label in both themes", () => {
|
|
179
|
+
assert.ok(contrast(WHITE, dark["primary-button"]) >= 4.5);
|
|
180
|
+
assert.ok(contrast(WHITE, light["primary-button"]) >= 4.5);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* `primary` is still a fill in a v-btn, a v-chip, a v-alert and a tab bar. The
|
|
185
|
+
* comment above `primary` used to claim Vuetify derives black for `on-primary`;
|
|
186
|
+
* it derives WHITE, and white on this blue is 2.68:1. Nothing measured it, so
|
|
187
|
+
* it shipped. This is that measurement.
|
|
188
|
+
*/
|
|
189
|
+
test("a filled primary surface carries a readable label in both themes", () => {
|
|
190
|
+
assert.equal(DARK_THEME.colors["on-primary"], dark.background);
|
|
191
|
+
assert.ok(contrast(dark["on-primary"], dark.primary) >= 4.5);
|
|
192
|
+
assert.ok(contrast(WHITE, dark.primary) < 4.5, "white here is the defect");
|
|
193
|
+
|
|
194
|
+
// Light derives its own and it passes; measure it rather than assume.
|
|
195
|
+
assert.ok(contrast(WHITE, light.primary) >= 4.5);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Both themes correct the same two Vuetify defaults, and a value that drifts
|
|
200
|
+
* apart between them is a bug nobody sees until they flip the switch.
|
|
201
|
+
*/
|
|
202
|
+
test("disabled text clears 4.5:1 on the surface each theme puts it on", () => {
|
|
203
|
+
assert.equal(LIGHT_THEME.variables["disabled-opacity"], 0.55);
|
|
204
|
+
assert.equal(DARK_THEME.variables["disabled-opacity"], 0.55);
|
|
205
|
+
assert.ok(contrast(over("#000000", WHITE, 0.55), WHITE) >= 4.5);
|
|
206
|
+
assert.ok(
|
|
207
|
+
contrast(over(WHITE, dark.background, 0.55), dark.background) >= 4.5
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("the dark divider clears the 3:1 a boundary wants", () => {
|
|
212
|
+
const border = "#62666a"; // white at 0.35 over the dark page
|
|
213
|
+
|
|
214
|
+
assert.equal(DARK_THEME.variables["border-opacity"], 0.35);
|
|
215
|
+
assert.ok(contrast(border, dark.background) >= 3);
|
|
216
|
+
});
|
package/utils/theme.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE TWO THEMES THIS PRODUCT SHIPS.
|
|
3
|
+
*
|
|
4
|
+
* They live here rather than inline in `plugins/vuetify.ts` so that
|
|
5
|
+
* `utils/theme.test.ts` can measure them. Every ratio quoted below is WCAG 2.1,
|
|
6
|
+
* alpha composited against the surface the value actually sits on, and the test
|
|
7
|
+
* re-measures the ones that matter on every run - this file has already been
|
|
8
|
+
* got wrong once by moving a colour for one use and forgetting the other.
|
|
9
|
+
*
|
|
10
|
+
* `disabled-opacity`: Material's 0.38 measures 2.68:1 on white, below the 4.5:1
|
|
11
|
+
* a sentence needs, and disabled fields in this product carry sentences people
|
|
12
|
+
* are expected to read. 0.55 is 4.74:1 on white and 6.21:1 on the dark page -
|
|
13
|
+
* the same value the camera wall settled on, for the same reason.
|
|
14
|
+
*
|
|
15
|
+
* `border-opacity`: 0.12 measures 1.32:1, which is a divider you cannot see.
|
|
16
|
+
* Dark goes to 0.35 (3.22:1) and carries the boundary properly. Light goes to
|
|
17
|
+
* 0.26 (1.88:1) - visible, but short of the 3:1 a component boundary wants.
|
|
18
|
+
* Reaching it on white needs 0.42, which turns every table and card edge into a
|
|
19
|
+
* mid-grey grid across eleven applications; that is a change somebody should
|
|
20
|
+
* look at before it ships, not one to slip in behind a contrast fix.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `primary` IS BOTH A FILL AND A FOREGROUND, AND ONE VALUE CANNOT SERVE BOTH.
|
|
25
|
+
*
|
|
26
|
+
* Vuetify emits `.bg-primary` and `.text-primary` from the same token, so the
|
|
27
|
+
* colour that fills the navigation drawer is also the colour of every
|
|
28
|
+
* `class="text-primary"` sentence, every `color="primary"` icon, spinner and
|
|
29
|
+
* text button, and the focused outline on every input. On a dark page those
|
|
30
|
+
* pull in opposite directions: a fill that carries a white label wants to be
|
|
31
|
+
* dark, and a foreground that is readable on a dark card wants to be light.
|
|
32
|
+
* There is no overlap - a value light enough for 4.5:1 text on `surface`
|
|
33
|
+
* (#1B242F) can carry white at 3.48:1 at the very best.
|
|
34
|
+
*
|
|
35
|
+
* So the fill is not `primary` any more. `brand-surface` below is what the
|
|
36
|
+
* navigation drawer is painted with, and `primary` is free to be a foreground
|
|
37
|
+
* colour. The foreground uses outnumber the fill uses by roughly an order of
|
|
38
|
+
* magnitude, which is why the split falls this way round.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `brand-surface` IS A SURFACE, AND A SURFACE BELONGS TO ITS THEME.
|
|
43
|
+
*
|
|
44
|
+
* It was first set to the deep brand navy in BOTH themes, which made the
|
|
45
|
+
* navigation drawer the one part of the product the theme switch did not
|
|
46
|
+
* reach: a near-black slab bolted onto a white application in light mode, and
|
|
47
|
+
* a lighter blue block sitting 1.80:1 off the cards beside it in dark mode. It
|
|
48
|
+
* belonged to neither.
|
|
49
|
+
*
|
|
50
|
+
* It is now a real surface in each theme - a light navy-tinted rail on the
|
|
51
|
+
* light page, the same `surface` the app bar and the cards use on the dark
|
|
52
|
+
* page - so the drawer reads as part of the application it is in and the
|
|
53
|
+
* switch visibly changes it. The brand is carried by the title and by the
|
|
54
|
+
* active item, which is `primary` in both themes, rather than by painting the
|
|
55
|
+
* whole rail.
|
|
56
|
+
*
|
|
57
|
+
* The drawer is a low-contrast panel against the page by design (1.13:1 light,
|
|
58
|
+
* 1.19:1 dark, exactly like every other surface in Material), so its edge is
|
|
59
|
+
* what carries the boundary. `NavigationDrawer.vue` raises `border-opacity` on
|
|
60
|
+
* itself alone to reach 3:1 there without redrawing every table and card edge
|
|
61
|
+
* in eleven applications.
|
|
62
|
+
*
|
|
63
|
+
* `on-brand-surface` is set explicitly rather than left to Vuetify's black or
|
|
64
|
+
* white, so the label is the same near-black / near-white the rest of the
|
|
65
|
+
* theme reads in.
|
|
66
|
+
*/
|
|
67
|
+
export const LIGHT_THEME = {
|
|
68
|
+
dark: false,
|
|
69
|
+
colors: {
|
|
70
|
+
primary: "#042134",
|
|
71
|
+
/**
|
|
72
|
+
* The navigation drawer's own fill: a navy-tinted light rail, 1.13:1
|
|
73
|
+
* against the white page and carried by its own 3.27:1 edge.
|
|
74
|
+
*/
|
|
75
|
+
"brand-surface": "#EDF1F7",
|
|
76
|
+
/** The drawer's label colour. On the rail: 15.43:1. */
|
|
77
|
+
"on-brand-surface": "#0B1B27",
|
|
78
|
+
/**
|
|
79
|
+
* Vuetify leaves `warning` (#FB8C00) and `info` (#2196F3) at its stock
|
|
80
|
+
* values and DERIVES their foreground, and — as with `on-primary` — the
|
|
81
|
+
* derivation picks WHITE. Measured, that is 2.37:1 on warning and 3.12:1
|
|
82
|
+
* on info: every filled warning/info chip, alert, button and badge in the
|
|
83
|
+
* product, including the invitation status chips. Setting the label to the
|
|
84
|
+
* dark page colour takes them to 8.9:1 and 5.3:1. Same value in both
|
|
85
|
+
* themes because the two fills are the same in both themes.
|
|
86
|
+
*/
|
|
87
|
+
"on-warning": "#0E1319",
|
|
88
|
+
"on-info": "#0E1319",
|
|
89
|
+
"primary-button": "#1867C0",
|
|
90
|
+
"text-primary": "#052439",
|
|
91
|
+
},
|
|
92
|
+
variables: {
|
|
93
|
+
"disabled-opacity": 0.55,
|
|
94
|
+
"border-opacity": 0.26,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Only the keys that must differ; Vuetify merges the rest from its stock dark.
|
|
100
|
+
*/
|
|
101
|
+
export const DARK_THEME = {
|
|
102
|
+
dark: true,
|
|
103
|
+
colors: {
|
|
104
|
+
/**
|
|
105
|
+
* Stock dark's #121212 page and #212121 surface measure 1.16:1 apart, so a
|
|
106
|
+
* card, a dialog and the page behind them were one flat sheet. These are
|
|
107
|
+
* 1.19:1 - elevation on a dark screen is a genuinely small delta - but they
|
|
108
|
+
* are tinted towards the brand navy rather than neutral grey, and the
|
|
109
|
+
* divider opacity above is what actually draws the edges.
|
|
110
|
+
*/
|
|
111
|
+
background: "#0E1319",
|
|
112
|
+
surface: "#1B242F",
|
|
113
|
+
/** Stock dark ships a lavender #ccbfd6 here, which is not our product. */
|
|
114
|
+
"surface-bright": "#26313E",
|
|
115
|
+
"surface-light": "#26313E",
|
|
116
|
+
/**
|
|
117
|
+
* A FOREGROUND colour, chosen to be read: 6.40:1 on the page, 5.38:1 on a
|
|
118
|
+
* card, 4.53:1 on the bright surface. Not a new colour - it is the blue the
|
|
119
|
+
* camera wall already draws its accents in (`--vms-accent`), so the two
|
|
120
|
+
* dark surfaces in this product now agree on one blue.
|
|
121
|
+
*
|
|
122
|
+
* Where it is still used as a fill it needs a foreground, and the claim
|
|
123
|
+
* that used to sit here - that Vuetify derives black - is wrong. Measured
|
|
124
|
+
* in a browser, Vuetify picks WHITE, which reads at 2.68:1 on this fill:
|
|
125
|
+
* every filled primary button, chip, alert and tab bar in dark mode. So
|
|
126
|
+
* `on-primary` is set explicitly below rather than derived.
|
|
127
|
+
*/
|
|
128
|
+
primary: "#5B9BE0",
|
|
129
|
+
/**
|
|
130
|
+
* The label on a filled `primary` surface. Measured 6.40:1 against
|
|
131
|
+
* `primary`, against the 2.68:1 Vuetify's own derivation gives. Not a new
|
|
132
|
+
* colour - it is the dark page `background` above, so a filled button
|
|
133
|
+
* reads as a hole punched in the page rather than a fifth shade of navy.
|
|
134
|
+
*/
|
|
135
|
+
"on-primary": "#0E1319",
|
|
136
|
+
/**
|
|
137
|
+
* The navigation drawer's own fill. Deliberately the same value as
|
|
138
|
+
* `surface`: on a dark page the drawer, the app bar and the cards are the
|
|
139
|
+
* same material, which is what makes the rail read as part of the
|
|
140
|
+
* application instead of a blue block laid on top of it. The token stays
|
|
141
|
+
* separate from `surface` so the drawer can be moved on its own later.
|
|
142
|
+
*/
|
|
143
|
+
"brand-surface": "#1B242F",
|
|
144
|
+
/** The drawer's label colour. On the rail: 13.21:1. */
|
|
145
|
+
"on-brand-surface": "#E8ECF1",
|
|
146
|
+
/** See LIGHT_THEME: Vuetify derives white on these fills, at 2.37:1. */
|
|
147
|
+
"on-warning": "#0E1319",
|
|
148
|
+
"on-info": "#0E1319",
|
|
149
|
+
/**
|
|
150
|
+
* Dark's stock `error` is the pale #CF6679, and white on it is 3.60:1 —
|
|
151
|
+
* the light theme's darker #B00020 carries white fine, this one does not.
|
|
152
|
+
*/
|
|
153
|
+
"on-error": "#0E1319",
|
|
154
|
+
/** Unchanged on purpose: it already passes in both themes. */
|
|
155
|
+
"primary-button": "#1867C0",
|
|
156
|
+
/** #052439 on a dark page measures 1.18:1. This is 15.72:1. */
|
|
157
|
+
"text-primary": "#E8ECF1",
|
|
158
|
+
},
|
|
159
|
+
variables: {
|
|
160
|
+
"disabled-opacity": 0.55,
|
|
161
|
+
"border-opacity": 0.35,
|
|
162
|
+
},
|
|
163
|
+
};
|