@patientos/website-kit 0.1.0
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/LICENSE +201 -0
- package/README.md +30 -0
- package/dist/checkout-block-CrO7OxI9.d.ts +121 -0
- package/dist/chunk-G3U6EXJM.js +1240 -0
- package/dist/chunk-JTZ6GYA7.js +30 -0
- package/dist/chunk-LCHW6XF4.js +64 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-Q4URDZAK.js +36 -0
- package/dist/chunk-QDO3SJWR.js +72 -0
- package/dist/chunk-THMT43MV.js +61 -0
- package/dist/chunk-TOPPLOH2.js +12229 -0
- package/dist/chunk-ZOF22TJA.js +19 -0
- package/dist/consult-room.d.ts +35 -0
- package/dist/consult-room.js +31540 -0
- package/dist/contrast.d.ts +63 -0
- package/dist/contrast.js +15 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.js +484 -0
- package/dist/islands-impl.d.ts +68 -0
- package/dist/islands-impl.js +23 -0
- package/dist/islands-registry.d.ts +17 -0
- package/dist/islands-registry.js +14 -0
- package/dist/islands.d.ts +57 -0
- package/dist/islands.js +15 -0
- package/dist/patient-surface-theme.d.ts +22 -0
- package/dist/patient-surface-theme.js +7 -0
- package/dist/portal-account-EhOtLV5u.d.ts +113 -0
- package/dist/portal-account.client-CTget1-3.d.ts +169 -0
- package/dist/portal-booking-client.d.ts +232 -0
- package/dist/portal-booking-client.js +51 -0
- package/dist/script-sources.d.ts +11 -0
- package/dist/script-sources.js +17 -0
- package/dist/show-when.d.ts +70 -0
- package/dist/show-when.js +9 -0
- package/dist/website-kit.css +3580 -0
- package/package.json +120 -0
|
@@ -0,0 +1,3580 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* @patientos/website-kit — compiled stylesheet
|
|
3
|
+
*
|
|
4
|
+
* Self-contained. NO Tailwind, NO shadcn, NO dashboard app.css. Hand-authored
|
|
5
|
+
* so the generated clinic websites are fully styleable from the kit alone (D2).
|
|
6
|
+
* Every rule reads `--sk-*` tokens with inline fallbacks, so the kit looks
|
|
7
|
+
* reasonable even before <ThemeProvider>/<WebsiteShell> injects a theme.
|
|
8
|
+
* ==========================================================================*/
|
|
9
|
+
|
|
10
|
+
/* ── token slots + fallbacks ──────────────────────────────────────────────*/
|
|
11
|
+
:where([data-sk-theme]),
|
|
12
|
+
:root {
|
|
13
|
+
--sk-brand: #1f6feb;
|
|
14
|
+
--sk-brand-foreground: #ffffff;
|
|
15
|
+
--sk-neutral: #f4f6f8;
|
|
16
|
+
--sk-neutral-foreground: #1b2733;
|
|
17
|
+
--sk-accent: #0fb39a;
|
|
18
|
+
--sk-background: #ffffff;
|
|
19
|
+
--sk-foreground: #1b2733;
|
|
20
|
+
--sk-muted: #5b6b7a;
|
|
21
|
+
/* Destructive intent (cancel an appointment, a failed write). NOT clinic-supplied:
|
|
22
|
+
* `patientSurfaceTheme` allow-lists brand/accent/radius/font only, and "the colour
|
|
23
|
+
* that means danger" is not a branding decision on a patient surface. */
|
|
24
|
+
--sk-danger: #b3261e;
|
|
25
|
+
|
|
26
|
+
--sk-font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
|
|
27
|
+
sans-serif;
|
|
28
|
+
--sk-font-heading: var(--sk-font);
|
|
29
|
+
--sk-font-size-base: 1rem;
|
|
30
|
+
--sk-font-size-sm: 0.875rem;
|
|
31
|
+
--sk-font-size-lg: 1.25rem;
|
|
32
|
+
--sk-font-size-h2: 2rem;
|
|
33
|
+
--sk-font-size-h1: 3rem;
|
|
34
|
+
|
|
35
|
+
--sk-space: 0.5rem;
|
|
36
|
+
--sk-radius: 0.625rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ── base ──────────────────────────────────────────────────────────────────*/
|
|
40
|
+
[data-sk-theme] {
|
|
41
|
+
color: var(--sk-foreground);
|
|
42
|
+
background: var(--sk-background);
|
|
43
|
+
font-family: var(--sk-font);
|
|
44
|
+
font-size: var(--sk-font-size-base);
|
|
45
|
+
line-height: 1.55;
|
|
46
|
+
-webkit-font-smoothing: antialiased;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* `.sk-flow` is listed alongside the theme root because an ISLAND does not require one:
|
|
50
|
+
the funnel mounts into a clinic-authored page, and the Pocket Doc funnel page has no
|
|
51
|
+
`[data-sk-theme]` wrapper at all. It looked fine only because that site imports
|
|
52
|
+
Tailwind, whose preflight sets this globally — i.e. the island's layout depended on a
|
|
53
|
+
reset the host happened to ship. A clinic page without one had every padded input
|
|
54
|
+
overflow its grid column. An island pays for its own reset. */
|
|
55
|
+
[data-sk-theme] *,
|
|
56
|
+
[data-sk-theme] *::before,
|
|
57
|
+
[data-sk-theme] *::after,
|
|
58
|
+
.sk-flow,
|
|
59
|
+
.sk-flow *,
|
|
60
|
+
.sk-flow *::before,
|
|
61
|
+
.sk-flow *::after {
|
|
62
|
+
box-sizing: border-box;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[data-sk-theme] h1,
|
|
66
|
+
[data-sk-theme] h2,
|
|
67
|
+
[data-sk-theme] h3,
|
|
68
|
+
.sk-flow h1,
|
|
69
|
+
.sk-flow h2,
|
|
70
|
+
.sk-flow h3 {
|
|
71
|
+
font-family: var(--sk-font-heading);
|
|
72
|
+
line-height: 1.15;
|
|
73
|
+
margin: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* The rest of the island's element reset, for the same reason as `box-sizing` above:
|
|
77
|
+
`[data-sk-theme]` never reaches an island, so the funnel inherited the UA's
|
|
78
|
+
`line-height: normal` (≈1.21 against the kit's own 1.55) and every `<p>` and `<h2>`
|
|
79
|
+
kept its UA `margin-block`. That unmanaged margin — not the declared gaps — is what
|
|
80
|
+
set the spacing between the heading, its subheading and the consent paragraphs, so
|
|
81
|
+
the vertical rhythm was the browser's, not ours. */
|
|
82
|
+
.sk-flow {
|
|
83
|
+
font-family: var(--sk-font);
|
|
84
|
+
font-size: var(--sk-font-size-base);
|
|
85
|
+
line-height: 1.55;
|
|
86
|
+
}
|
|
87
|
+
/* ANCHOR the funnel's column. The step heading was landing anywhere from 203px to 389px
|
|
88
|
+
down the page depending on how tall the step happened to be — walking identity→intake
|
|
89
|
+
it dropped 186px and then climbed back, which reads as the page breaking rather than
|
|
90
|
+
advancing. `.sk-flow` never centred anything itself; the clinic's page was centring a
|
|
91
|
+
short island. The kit cannot edit clinic markup (islands are themed, not
|
|
92
|
+
restructurable), so the lever it does have is to stop being a short box.
|
|
93
|
+
SCOPED to the funnel's own mount point on purpose: `.sk-flow` is shared with
|
|
94
|
+
`<BookingBlock/>`, which is embedded mid-page and must NOT become viewport-tall. */
|
|
95
|
+
.sk-certificate-funnel .sk-flow {
|
|
96
|
+
/* All-AUTO rows, overriding the shell's `auto 1fr auto`. That `1fr` body track and the
|
|
97
|
+
`min-block-size` below were in direct conflict: an `fr` track eats the column's free
|
|
98
|
+
space during TRACK SIZING, before `align-content` ever runs, so `start` was a no-op
|
|
99
|
+
and the whole slack of a viewport-tall column opened up between the last field and
|
|
100
|
+
the CTA — ~280px on the medicare step, ~420px on the confirmation, where the primary
|
|
101
|
+
link was exiled to the bottom of an otherwise blank screen. With auto rows the tracks
|
|
102
|
+
pack to the top and the min-height goes back to doing only its stated job above.
|
|
103
|
+
THREE explicit tracks, not `none`: the railed rules below place the rail by line
|
|
104
|
+
number and need an explicit grid to resolve against. */
|
|
105
|
+
grid-template-rows: auto auto auto;
|
|
106
|
+
align-content: start;
|
|
107
|
+
min-block-size: calc(100dvh - 8rem);
|
|
108
|
+
/* 2×, not the 6× this shipped with. MEASURED: on a 390×844 phone the step heading's
|
|
109
|
+
first ink sat at 179px, of which the clinic's own 000 bar was 62 — the other 117 was
|
|
110
|
+
this inset (48), the back/progress row (36), the shell's row gap (16) and the eyebrow
|
|
111
|
+
line (17), i.e. a sixth of the fold spent before the screen says what it is asking.
|
|
112
|
+
The 000 bar directly above already carries its own 14px of air and a hairline, so 16px
|
|
113
|
+
here still reads as a gap rather than a collision, and every one of the fourteen
|
|
114
|
+
screens starts 32px higher. The BOTTOM stays 3× — that edge is against the page, not
|
|
115
|
+
against a bar. */
|
|
116
|
+
padding-block: calc(var(--sk-space) * 2) calc(var(--sk-space) * 3);
|
|
117
|
+
|
|
118
|
+
/* The HORIZONTAL twin of the `min-block-size` above, and the same root cause. This
|
|
119
|
+
column lives inside a host box that is SHRINK-TO-FIT — PocketDoc's FunnelShell
|
|
120
|
+
centres the island with `md:items-center` on a column flex box, and any wrapper in
|
|
121
|
+
between (a React root, a `.container` div) is shrink-to-fit for the same reason.
|
|
122
|
+
Against such a parent `max-width` only CAPS a width the CONTENT picks, so every step
|
|
123
|
+
sized itself to its own content. Measured at 1440, walking forward: identity 848 (reading
|
|
124
|
+
column 496) → confirm-identity 822 (470) → intake 777 (425) → slot 764 (412), then back
|
|
125
|
+
to 848 on medicare — the card's left edge sliding 296→309→332→338 and the column
|
|
126
|
+
narrowing 17% mid-intake, which reads as the page breaking rather than advancing.
|
|
127
|
+
A DEFINITE inline-size is the only lever that survives shrink-to-fit: it raises the
|
|
128
|
+
max-content contribution, so the host box resolves to this width and the step's own
|
|
129
|
+
content can no longer set it. `max-inline-size: 100%` is what keeps that safe — it
|
|
130
|
+
out-specifies the `max-width` caps below (logical and physical cascade as one
|
|
131
|
+
property), so a host column NARROWER than the cap still wins and the island shrinks
|
|
132
|
+
to fit it rather than overflowing into a horizontal scrollbar.
|
|
133
|
+
NOT on `.sk-flow` itself: `<BookingBlock/>` is embedded mid-page and must stay
|
|
134
|
+
shrink-to-fit. NOT on `.sk-certificate-funnel`: widening the mount point also widens
|
|
135
|
+
the full-bleed escalate screen, whose pixels are fixed. */
|
|
136
|
+
inline-size: 34rem;
|
|
137
|
+
max-inline-size: 100%;
|
|
138
|
+
}
|
|
139
|
+
.sk-flow p,
|
|
140
|
+
.sk-flow ul,
|
|
141
|
+
.sk-flow ol,
|
|
142
|
+
.sk-flow dl,
|
|
143
|
+
.sk-flow dd,
|
|
144
|
+
.sk-flow figure {
|
|
145
|
+
/* TRAP, read before adding a rule below: this is (0,1,1) — HIGHER than a single
|
|
146
|
+
component class, and `[data-sk-theme] p` at the same strength backs it up. Any block
|
|
147
|
+
margin authored on a <p>/<ul>/<dl> in this flow must therefore be written as
|
|
148
|
+
`.sk-flow .sk-whatever { … }` or it is silently dead. That is not hypothetical: a
|
|
149
|
+
whole round of section spacing (`.sk-identity__section`, `.sk-cf__body`, …) was
|
|
150
|
+
zeroed by this rule, which is how "Residential address" ended up captioning the
|
|
151
|
+
wrong field and the exclusion list ended up welded to the prose around it. */
|
|
152
|
+
margin: 0;
|
|
153
|
+
}
|
|
154
|
+
.sk-flow ul,
|
|
155
|
+
.sk-flow ol {
|
|
156
|
+
padding-inline-start: 0;
|
|
157
|
+
list-style: none;
|
|
158
|
+
}
|
|
159
|
+
/* `[hidden]` is only a UA `display: none`, so ANY author `display` beats it. That is not
|
|
160
|
+
hypothetical here: `.sk-cf__consent-full` sets `display: grid`, so the "Read the full
|
|
161
|
+
consent text" disclosure never hid anything — the full legal text was always on screen
|
|
162
|
+
while its own button offered to reveal it. One guard, so no future disclosure in the
|
|
163
|
+
kit can be defeated the same way. */
|
|
164
|
+
[hidden] {
|
|
165
|
+
display: none !important;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
[data-sk-theme] p {
|
|
169
|
+
margin: 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
[data-sk-theme] img {
|
|
173
|
+
max-width: 100%;
|
|
174
|
+
display: block;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
[data-sk-theme] a {
|
|
178
|
+
color: var(--sk-brand);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ── layout primitives ─────────────────────────────────────────────────────*/
|
|
182
|
+
.sk-container {
|
|
183
|
+
width: 100%;
|
|
184
|
+
max-width: 72rem;
|
|
185
|
+
margin-inline: auto;
|
|
186
|
+
padding-inline: calc(var(--sk-space) * 3);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.sk-row {
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-wrap: wrap;
|
|
192
|
+
gap: calc(var(--sk-space) * 3);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.sk-stack {
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
gap: calc(var(--sk-space) * 3);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* ── Hero ───────────────────────────────────────────────────────────────────*/
|
|
202
|
+
.sk-hero {
|
|
203
|
+
padding: calc(var(--sk-space) * 12) calc(var(--sk-space) * 3);
|
|
204
|
+
background: var(--sk-neutral);
|
|
205
|
+
color: var(--sk-neutral-foreground);
|
|
206
|
+
text-align: center;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.sk-hero--image {
|
|
210
|
+
background-image:
|
|
211
|
+
linear-gradient(rgba(10, 18, 28, 0.55), rgba(10, 18, 28, 0.55)),
|
|
212
|
+
var(--sk-hero-image);
|
|
213
|
+
background-size: cover;
|
|
214
|
+
background-position: center;
|
|
215
|
+
color: #ffffff;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.sk-hero__inner {
|
|
219
|
+
max-width: 48rem;
|
|
220
|
+
margin-inline: auto;
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
223
|
+
gap: calc(var(--sk-space) * 3);
|
|
224
|
+
align-items: center;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.sk-hero__heading {
|
|
228
|
+
font-size: var(--sk-font-size-h1);
|
|
229
|
+
font-weight: 800;
|
|
230
|
+
letter-spacing: -0.02em;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.sk-hero__subheading {
|
|
234
|
+
font-size: var(--sk-font-size-lg);
|
|
235
|
+
max-width: 38rem;
|
|
236
|
+
opacity: 0.92;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.sk-hero__cta {
|
|
240
|
+
display: flex;
|
|
241
|
+
flex-wrap: wrap;
|
|
242
|
+
gap: calc(var(--sk-space) * 2);
|
|
243
|
+
justify-content: center;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ── Section ────────────────────────────────────────────────────────────────*/
|
|
247
|
+
.sk-section {
|
|
248
|
+
padding: calc(var(--sk-space) * 10) calc(var(--sk-space) * 3);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.sk-section__inner {
|
|
252
|
+
max-width: 72rem;
|
|
253
|
+
margin-inline: auto;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.sk-section__title {
|
|
257
|
+
font-size: var(--sk-font-size-h2);
|
|
258
|
+
font-weight: 700;
|
|
259
|
+
letter-spacing: -0.01em;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.sk-section__intro {
|
|
263
|
+
color: var(--sk-muted);
|
|
264
|
+
font-size: var(--sk-font-size-lg);
|
|
265
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
266
|
+
max-width: 44rem;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.sk-section__title + .sk-service-grid,
|
|
270
|
+
.sk-section__title + .sk-team-grid,
|
|
271
|
+
.sk-section__intro + .sk-service-grid,
|
|
272
|
+
.sk-section__intro + .sk-team-grid,
|
|
273
|
+
.sk-section__intro + .sk-faq,
|
|
274
|
+
.sk-section__title + .sk-faq {
|
|
275
|
+
margin-top: calc(var(--sk-space) * 6);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* ── ServiceGrid ────────────────────────────────────────────────────────────*/
|
|
279
|
+
.sk-service-grid {
|
|
280
|
+
list-style: none;
|
|
281
|
+
margin: 0;
|
|
282
|
+
padding: 0;
|
|
283
|
+
display: grid;
|
|
284
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
285
|
+
gap: calc(var(--sk-space) * 4);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.sk-service-grid__item {
|
|
289
|
+
background: var(--sk-neutral);
|
|
290
|
+
color: var(--sk-neutral-foreground);
|
|
291
|
+
border-radius: var(--sk-radius);
|
|
292
|
+
padding: calc(var(--sk-space) * 4);
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.sk-service-grid__icon {
|
|
299
|
+
width: calc(var(--sk-space) * 6);
|
|
300
|
+
height: calc(var(--sk-space) * 6);
|
|
301
|
+
object-fit: contain;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.sk-service-grid__title {
|
|
305
|
+
font-size: var(--sk-font-size-lg);
|
|
306
|
+
font-weight: 650;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.sk-service-grid__desc {
|
|
310
|
+
color: var(--sk-muted);
|
|
311
|
+
font-size: var(--sk-font-size-sm);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* ── TeamGrid ───────────────────────────────────────────────────────────────*/
|
|
315
|
+
.sk-team-grid {
|
|
316
|
+
list-style: none;
|
|
317
|
+
margin: 0;
|
|
318
|
+
padding: 0;
|
|
319
|
+
display: grid;
|
|
320
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
321
|
+
gap: calc(var(--sk-space) * 4);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.sk-team-grid__item {
|
|
325
|
+
text-align: center;
|
|
326
|
+
display: flex;
|
|
327
|
+
flex-direction: column;
|
|
328
|
+
gap: calc(var(--sk-space) * 1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.sk-team-grid__photo {
|
|
332
|
+
width: 100%;
|
|
333
|
+
aspect-ratio: 1 / 1;
|
|
334
|
+
object-fit: cover;
|
|
335
|
+
border-radius: var(--sk-radius);
|
|
336
|
+
background: var(--sk-neutral);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.sk-team-grid__photo--placeholder {
|
|
340
|
+
background: linear-gradient(135deg, var(--sk-neutral), #e2e8ee);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.sk-team-grid__name {
|
|
344
|
+
font-weight: 650;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.sk-team-grid__role {
|
|
348
|
+
color: var(--sk-muted);
|
|
349
|
+
font-size: var(--sk-font-size-sm);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* ── Hours ──────────────────────────────────────────────────────────────────*/
|
|
353
|
+
.sk-hours {
|
|
354
|
+
margin: 0;
|
|
355
|
+
display: flex;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
max-width: 32rem;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.sk-hours__row {
|
|
361
|
+
display: flex;
|
|
362
|
+
justify-content: space-between;
|
|
363
|
+
gap: calc(var(--sk-space) * 4);
|
|
364
|
+
padding: calc(var(--sk-space) * 1.5) 0;
|
|
365
|
+
border-bottom: 1px solid color-mix(in srgb, var(--sk-muted) 20%, transparent);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.sk-hours__row:last-child {
|
|
369
|
+
border-bottom: 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.sk-hours__day {
|
|
373
|
+
font-weight: 600;
|
|
374
|
+
margin: 0;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.sk-hours__time {
|
|
378
|
+
color: var(--sk-muted);
|
|
379
|
+
margin: 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* ── MapEmbed ───────────────────────────────────────────────────────────────*/
|
|
383
|
+
.sk-map {
|
|
384
|
+
width: 100%;
|
|
385
|
+
height: var(--sk-map-height, 320px);
|
|
386
|
+
border-radius: var(--sk-radius);
|
|
387
|
+
overflow: hidden;
|
|
388
|
+
background: var(--sk-neutral);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.sk-map__frame {
|
|
392
|
+
width: 100%;
|
|
393
|
+
height: 100%;
|
|
394
|
+
border: 0;
|
|
395
|
+
display: block;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* ── FAQ ────────────────────────────────────────────────────────────────────*/
|
|
399
|
+
.sk-faq {
|
|
400
|
+
display: flex;
|
|
401
|
+
flex-direction: column;
|
|
402
|
+
max-width: 48rem;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.sk-faq__item {
|
|
406
|
+
border-bottom: 1px solid color-mix(in srgb, var(--sk-muted) 20%, transparent);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.sk-faq__q {
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
font-weight: 600;
|
|
412
|
+
padding: calc(var(--sk-space) * 2.5) 0;
|
|
413
|
+
list-style: none;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.sk-faq__q::-webkit-details-marker {
|
|
417
|
+
display: none;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.sk-faq__q::after {
|
|
421
|
+
content: "+";
|
|
422
|
+
float: right;
|
|
423
|
+
color: var(--sk-muted);
|
|
424
|
+
font-weight: 400;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.sk-faq__item[open] .sk-faq__q::after {
|
|
428
|
+
content: "\2212"; /* minus */
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.sk-faq__a {
|
|
432
|
+
color: var(--sk-muted);
|
|
433
|
+
padding: 0 0 calc(var(--sk-space) * 2.5);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* ── Button ─────────────────────────────────────────────────────────────────*/
|
|
437
|
+
.sk-button {
|
|
438
|
+
display: inline-flex;
|
|
439
|
+
align-items: center;
|
|
440
|
+
justify-content: center;
|
|
441
|
+
gap: calc(var(--sk-space) * 1);
|
|
442
|
+
padding: calc(var(--sk-space) * 1.5) calc(var(--sk-space) * 3);
|
|
443
|
+
border-radius: var(--sk-radius);
|
|
444
|
+
font: inherit;
|
|
445
|
+
font-weight: 600;
|
|
446
|
+
line-height: 1;
|
|
447
|
+
text-decoration: none;
|
|
448
|
+
cursor: pointer;
|
|
449
|
+
border: 1px solid transparent;
|
|
450
|
+
transition: filter 0.15s ease, background 0.15s ease, color 0.15s ease;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.sk-button:focus-visible {
|
|
454
|
+
outline: 2px solid var(--sk-brand);
|
|
455
|
+
outline-offset: 2px;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.sk-button--solid {
|
|
459
|
+
background: var(--sk-brand);
|
|
460
|
+
color: var(--sk-brand-foreground);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.sk-button--solid:hover {
|
|
464
|
+
filter: brightness(0.94);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.sk-button--outline {
|
|
468
|
+
background: transparent;
|
|
469
|
+
color: var(--sk-brand);
|
|
470
|
+
border-color: var(--sk-brand);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.sk-button--outline:hover {
|
|
474
|
+
background: color-mix(in srgb, var(--sk-brand) 8%, transparent);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.sk-button--ghost {
|
|
478
|
+
background: transparent;
|
|
479
|
+
color: var(--sk-foreground);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.sk-button--ghost:hover {
|
|
483
|
+
background: color-mix(in srgb, var(--sk-muted) 12%, transparent);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* ── BookingBlock placeholder (D8 seam, inert in v1) ────────────────────────*/
|
|
487
|
+
.sk-booking {
|
|
488
|
+
border: 1px dashed color-mix(in srgb, var(--sk-muted) 40%, transparent);
|
|
489
|
+
border-radius: var(--sk-radius);
|
|
490
|
+
padding: calc(var(--sk-space) * 4);
|
|
491
|
+
text-align: center;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.sk-booking__placeholder {
|
|
495
|
+
color: var(--sk-muted);
|
|
496
|
+
font-size: var(--sk-font-size-sm);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* ── responsive ─────────────────────────────────────────────────────────────*/
|
|
500
|
+
@media (max-width: 900px) {
|
|
501
|
+
.sk-service-grid {
|
|
502
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
503
|
+
}
|
|
504
|
+
.sk-team-grid {
|
|
505
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
506
|
+
}
|
|
507
|
+
:where([data-sk-theme]),
|
|
508
|
+
:root {
|
|
509
|
+
--sk-font-size-h1: 2.25rem;
|
|
510
|
+
--sk-font-size-h2: 1.625rem;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
@media (max-width: 560px) {
|
|
515
|
+
.sk-service-grid {
|
|
516
|
+
grid-template-columns: minmax(0, 1fr);
|
|
517
|
+
}
|
|
518
|
+
.sk-team-grid {
|
|
519
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
520
|
+
}
|
|
521
|
+
.sk-hero {
|
|
522
|
+
padding-block: calc(var(--sk-space) * 8);
|
|
523
|
+
}
|
|
524
|
+
.sk-section {
|
|
525
|
+
padding-block: calc(var(--sk-space) * 7);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* ── ISLAND STYLES (PAT-774) ─────────────────────────────────────────────────
|
|
530
|
+
* One section per island, so the four parallel workstreams filling in the
|
|
531
|
+
* implementations append inside their OWN block and never touch each other's.
|
|
532
|
+
* Each island's CSS reads the patient-surface theme vars off its own mount point
|
|
533
|
+
* (see `patient-surface-theme.ts`) — brand/accent/radius/font only; neutrals stay
|
|
534
|
+
* on the kit's tuned values.
|
|
535
|
+
* ------------------------------------------------------------------------- */
|
|
536
|
+
|
|
537
|
+
/* ── booking (PAT-765) ───────────────────────────────────────────────────── */
|
|
538
|
+
.sk-booking {
|
|
539
|
+
/* The inert dashed placeholder is gone — this is a real flow surface now. */
|
|
540
|
+
border: 0;
|
|
541
|
+
padding: 0;
|
|
542
|
+
text-align: left;
|
|
543
|
+
}
|
|
544
|
+
.sk-booking__heading {
|
|
545
|
+
font-size: var(--sk-font-size-h2);
|
|
546
|
+
line-height: 1.2;
|
|
547
|
+
}
|
|
548
|
+
.sk-booking__list {
|
|
549
|
+
list-style: none;
|
|
550
|
+
padding: 0;
|
|
551
|
+
margin: 0;
|
|
552
|
+
display: grid;
|
|
553
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
554
|
+
}
|
|
555
|
+
.sk-booking__option {
|
|
556
|
+
display: flex;
|
|
557
|
+
align-items: baseline;
|
|
558
|
+
justify-content: space-between;
|
|
559
|
+
gap: calc(var(--sk-space) * 2);
|
|
560
|
+
width: 100%;
|
|
561
|
+
padding: calc(var(--sk-space) * 2) calc(var(--sk-space) * 2.5);
|
|
562
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted) 30%, transparent);
|
|
563
|
+
border-radius: var(--sk-island-radius);
|
|
564
|
+
background: none;
|
|
565
|
+
/* `font: inherit` is not enough: a <button> does NOT inherit colour, so a bare
|
|
566
|
+
transparent one falls back to the UA's `buttontext` (near-black) and vanishes on
|
|
567
|
+
a dark clinic canvas. Every see-through control in this sheet must say so. */
|
|
568
|
+
color: inherit;
|
|
569
|
+
font: inherit;
|
|
570
|
+
text-align: left;
|
|
571
|
+
cursor: pointer;
|
|
572
|
+
}
|
|
573
|
+
.sk-booking__option:hover {
|
|
574
|
+
border-color: var(--sk-island-primary);
|
|
575
|
+
}
|
|
576
|
+
.sk-booking__option-label {
|
|
577
|
+
font-weight: 600;
|
|
578
|
+
}
|
|
579
|
+
.sk-booking__option-meta,
|
|
580
|
+
.sk-booking__empty,
|
|
581
|
+
.sk-booking__body,
|
|
582
|
+
.sk-booking__aside {
|
|
583
|
+
color: var(--sk-muted);
|
|
584
|
+
font-size: var(--sk-font-size-sm);
|
|
585
|
+
}
|
|
586
|
+
.sk-booking__link {
|
|
587
|
+
color: var(--sk-island-primary);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* NOTE: `.sk-booking` + `.sk-booking__placeholder` predate this split and live
|
|
591
|
+
* with the other primitives above. */
|
|
592
|
+
|
|
593
|
+
/* ── certificate-funnel (PAT-764) ────────────────────────────────────────── */
|
|
594
|
+
/* The shared FLOW frame (shell, buttons, fields) + the funnel's own screens. Neutral
|
|
595
|
+
* by default; the four allow-listed clinic values arrive as `--sk-island-*` custom
|
|
596
|
+
* properties on the island's OWN mount point (see `patient-surface-theme.ts`), so a
|
|
597
|
+
* clinic brand reaches these components without the clinic being able to restyle them. */
|
|
598
|
+
.sk-certificate-funnel,
|
|
599
|
+
.sk-booking {
|
|
600
|
+
/* The kit's OWN default, deliberately NOT `var(--sk-primary, …)`. `--sk-primary` is a
|
|
601
|
+
value the clinic's site stylesheet publishes, and reading it here handed a raw,
|
|
602
|
+
unchecked brand colour to the island under the hardcoded white below — the one route
|
|
603
|
+
in that skips `derivePrimaryPair` entirely, so the CTA's label could land at any
|
|
604
|
+
ratio at all (PocketDoc's own #e92095 is 4.11:1 under white). The two tokens are a
|
|
605
|
+
PAIR: `themeStyle()` overrides both inline together after deriving the foreground,
|
|
606
|
+
or neither moves. #0f766e is 5.47:1 under #ffffff. */
|
|
607
|
+
--sk-island-primary: #0f766e;
|
|
608
|
+
--sk-island-primary-fg: #ffffff;
|
|
609
|
+
--sk-island-accent: var(--sk-island-primary);
|
|
610
|
+
--sk-island-radius: var(--sk-radius, 10px);
|
|
611
|
+
--sk-island-font: inherit;
|
|
612
|
+
font-family: var(--sk-island-font);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/* Screen-reader-only text. Used for required markers + hidden labels. */
|
|
616
|
+
.sk-sr-only {
|
|
617
|
+
position: absolute;
|
|
618
|
+
width: 1px;
|
|
619
|
+
height: 1px;
|
|
620
|
+
padding: 0;
|
|
621
|
+
margin: -1px;
|
|
622
|
+
overflow: hidden;
|
|
623
|
+
clip: rect(0 0 0 0);
|
|
624
|
+
white-space: nowrap;
|
|
625
|
+
border: 0;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* ── flow shell ─────────────────────────────────────────────────────────────*/
|
|
629
|
+
.sk-flow {
|
|
630
|
+
display: grid;
|
|
631
|
+
grid-template-rows: auto 1fr auto;
|
|
632
|
+
gap: calc(var(--sk-space) * 2);
|
|
633
|
+
max-width: 34rem;
|
|
634
|
+
margin-inline: auto;
|
|
635
|
+
/* The island pays for its OWN inset. It used to inherit one from the host page, so a
|
|
636
|
+
site that padded its funnel canvas only from `md:` up shipped a mobile flow whose
|
|
637
|
+
heading and cards touched the left edge of the phone (PAT-853). An island that is
|
|
638
|
+
unreadable unless its host remembers to pad it is a contract nobody can keep. */
|
|
639
|
+
padding-inline: calc(var(--sk-space) * 3);
|
|
640
|
+
padding-block: calc(var(--sk-space) * 3);
|
|
641
|
+
text-align: left;
|
|
642
|
+
|
|
643
|
+
/* ── Ink DERIVED from the surface, not taken from the token bag ────────────────
|
|
644
|
+
`--sk-muted` is one absolute colour, but this island mounts on a background the
|
|
645
|
+
CLINIC chose: PocketDoc's funnel canvas is #121936, where the bridged muted
|
|
646
|
+
(#626884) lands at 3.1:1 and fails AA on every subheading, Back link and help
|
|
647
|
+
line. Mixing the surface's OWN `currentColor` instead means secondary ink is
|
|
648
|
+
always a softened version of whatever ink already reads there — ~8:1 on that dark
|
|
649
|
+
canvas, ~7:1 on white — without the kit needing to know which it is. Same rule the
|
|
650
|
+
island's foreground already follows (`deriveForeground`), same lesson as the
|
|
651
|
+
slots-confirm bar (PAT-834/838): pair the ink with the surface it sits on. */
|
|
652
|
+
--sk-flow-muted: color-mix(in srgb, currentColor 68%, transparent);
|
|
653
|
+
/* Hairlines and card edges, same derivation. A 30%-of-mid-grey border was ~1.2:1 on
|
|
654
|
+
the dark canvas — a card you can only see if you already know it is there. */
|
|
655
|
+
--sk-flow-line: color-mix(in srgb, currentColor 24%, transparent);
|
|
656
|
+
/* A barely-there raised surface for cards, again relative to the page. */
|
|
657
|
+
--sk-flow-raised: color-mix(in srgb, currentColor 6%, transparent);
|
|
658
|
+
|
|
659
|
+
/* Brand colour as TEXT ink, pulled toward the surface's own readable ink until it
|
|
660
|
+
clears AA. The raw brand is tuned to be a BUTTON FILL (where the kit derives a
|
|
661
|
+
contrasting foreground for it), not small text on a canvas the clinic also chose:
|
|
662
|
+
PocketDoc's #e92095 on #121936 is 4.14:1, which fails for the 11px uppercase
|
|
663
|
+
eyebrow and the inline policy links. Mixing `currentColor` in pulls it back toward
|
|
664
|
+
whatever the canvas is, in one declaration, without the kit knowing which it is.
|
|
665
|
+
Fills are untouched — only ink.
|
|
666
|
+
|
|
667
|
+
44%, not the 64% this shipped with. MEASURED, not estimated: the policy links on the
|
|
668
|
+
preflight screen sit inside a 6%-white card, and against that real background 64%
|
|
669
|
+
gave 4.07:1 — already under AA before the CTA was AA-floored, and 3.76:1 after. The
|
|
670
|
+
probe that reported "all passes" was comparing them to the page canvas instead of the
|
|
671
|
+
card they sit on. 44% measures 4.55:1 over the card and 5.17:1 on the bare canvas. */
|
|
672
|
+
--sk-flow-accent-ink: color-mix(in srgb, var(--sk-island-primary) 44%, currentColor);
|
|
673
|
+
|
|
674
|
+
/* Error INK, same derivation. `#b3261e` is tuned as a red on white — on PocketDoc's
|
|
675
|
+
#121936 canvas it is 2.64:1, so "Enter your date of birth" is a message the patient
|
|
676
|
+
literally cannot read on the screen that is blocking them. Pulling a brighter red
|
|
677
|
+
toward the surface's own ink gives 6.58:1 there and 5.94:1 on white: still
|
|
678
|
+
unmistakably red, readable either way. Text AND the `[aria-invalid]` field border —
|
|
679
|
+
the field no longer paints its own white fill, so that border sits on the clinic's
|
|
680
|
+
canvas too and needs the same derivation. */
|
|
681
|
+
--sk-flow-danger: color-mix(in srgb, #ef4444 70%, currentColor);
|
|
682
|
+
}
|
|
683
|
+
.sk-flow__bar {
|
|
684
|
+
display: flex;
|
|
685
|
+
align-items: center;
|
|
686
|
+
justify-content: space-between;
|
|
687
|
+
gap: calc(var(--sk-space) * 2);
|
|
688
|
+
}
|
|
689
|
+
.sk-flow__back,
|
|
690
|
+
.sk-flow__back-spacer {
|
|
691
|
+
min-height: 2.25rem;
|
|
692
|
+
}
|
|
693
|
+
.sk-flow__back {
|
|
694
|
+
background: none;
|
|
695
|
+
border: 0;
|
|
696
|
+
padding: 0;
|
|
697
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
698
|
+
font: inherit;
|
|
699
|
+
font-size: var(--sk-font-size-sm);
|
|
700
|
+
cursor: pointer;
|
|
701
|
+
}
|
|
702
|
+
.sk-flow__back:hover {
|
|
703
|
+
/* Transparent over the page → full-strength means the PAGE's text colour. */
|
|
704
|
+
color: inherit;
|
|
705
|
+
}
|
|
706
|
+
/* The position group: the words and the dots, on the bar's own row (PAT-853 fold pass).
|
|
707
|
+
`min-width: 0` + a nowrap label so a long eyebrow ("Question 6 of 6") shrinks the group
|
|
708
|
+
rather than wrapping the bar onto a second line — a bar that grows is the opposite of
|
|
709
|
+
what this move was for. */
|
|
710
|
+
.sk-flow__place {
|
|
711
|
+
display: flex;
|
|
712
|
+
align-items: center;
|
|
713
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
714
|
+
min-width: 0;
|
|
715
|
+
}
|
|
716
|
+
.sk-flow__progress {
|
|
717
|
+
display: flex;
|
|
718
|
+
align-items: center;
|
|
719
|
+
gap: 4px;
|
|
720
|
+
}
|
|
721
|
+
.sk-flow__dot {
|
|
722
|
+
width: 6px;
|
|
723
|
+
height: 6px;
|
|
724
|
+
border-radius: 999px;
|
|
725
|
+
background: var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 35%, transparent));
|
|
726
|
+
}
|
|
727
|
+
.sk-flow__dot--done {
|
|
728
|
+
background: color-mix(in srgb, var(--sk-island-primary) 45%, transparent);
|
|
729
|
+
}
|
|
730
|
+
.sk-flow__dot--current {
|
|
731
|
+
width: 20px;
|
|
732
|
+
background: var(--sk-island-primary);
|
|
733
|
+
}
|
|
734
|
+
.sk-flow__head {
|
|
735
|
+
margin-bottom: calc(var(--sk-space) * 2);
|
|
736
|
+
}
|
|
737
|
+
/* Lives in the bar beside the dots now, not above the heading — see the note in
|
|
738
|
+
`flow-shell.tsx`. `nowrap` because it shares a 342px row on a phone with the Back
|
|
739
|
+
control and up to eight dots. */
|
|
740
|
+
.sk-flow__eyebrow {
|
|
741
|
+
white-space: nowrap;
|
|
742
|
+
font-size: 0.6875rem;
|
|
743
|
+
font-weight: 600;
|
|
744
|
+
letter-spacing: 0.14em;
|
|
745
|
+
text-transform: uppercase;
|
|
746
|
+
color: var(--sk-flow-accent-ink, var(--sk-island-accent));
|
|
747
|
+
}
|
|
748
|
+
.sk-flow__heading {
|
|
749
|
+
margin-top: calc(var(--sk-space) * 0.5);
|
|
750
|
+
font-size: var(--sk-font-size-h2);
|
|
751
|
+
/* WEIGHT 500, not the UA's bold 700. The heading was never given a weight, so every
|
|
752
|
+
step title rendered at the browser's default — heavy and untracked, which is what
|
|
753
|
+
made the funnel read as an unstyled document rather than an authored one. */
|
|
754
|
+
font-weight: 500;
|
|
755
|
+
line-height: 1.1;
|
|
756
|
+
letter-spacing: -0.025em;
|
|
757
|
+
}
|
|
758
|
+
/* NO ring on the step heading. Earlier reasoning here was that "the heading is focused
|
|
759
|
+
on every step change, so a visible ring is the point" — but the heading is
|
|
760
|
+
`tabindex="-1"`, so no keyboard user can ever Tab to it. The focus is purely
|
|
761
|
+
programmatic, to make assistive tech announce the new step; the ring therefore marks
|
|
762
|
+
something nobody navigated to, and Chrome still matches `:focus-visible` after a
|
|
763
|
+
click-advance. At desktop it painted a 508×90 outlined rectangle around the question —
|
|
764
|
+
which reads as an empty text input sitting above the real one. The focus move and its
|
|
765
|
+
announcement are unchanged; only the paint is suppressed. */
|
|
766
|
+
.sk-flow__heading:focus {
|
|
767
|
+
outline: none;
|
|
768
|
+
}
|
|
769
|
+
/* READING copy, so it sits at the base size. 14px is for subordinate metadata — a
|
|
770
|
+
back link, a slot's "1 left" — not for the sentence explaining what this step is. */
|
|
771
|
+
.sk-flow__sub {
|
|
772
|
+
margin-top: calc(var(--sk-space) * 1.5);
|
|
773
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
774
|
+
}
|
|
775
|
+
/* Announced, never shown — the heading already carries it visually. */
|
|
776
|
+
.sk-flow__status {
|
|
777
|
+
position: absolute;
|
|
778
|
+
width: 1px;
|
|
779
|
+
height: 1px;
|
|
780
|
+
overflow: hidden;
|
|
781
|
+
clip: rect(0 0 0 0);
|
|
782
|
+
white-space: nowrap;
|
|
783
|
+
}
|
|
784
|
+
/* 1×, not 2×: the shell's own grid `gap` already puts 2× between the body and this row, so
|
|
785
|
+
the declared 2× here was a second helping of the same air — 32px between the last field
|
|
786
|
+
and the button the patient came to press, on every step that has one. */
|
|
787
|
+
.sk-flow__footer {
|
|
788
|
+
padding-top: calc(var(--sk-space));
|
|
789
|
+
}
|
|
790
|
+
/* NO PINNED CTA BAR (PAT-867 — this REPLACES the PAT-853/865 sticky footer).
|
|
791
|
+
The fold problem was real; bottom-sticky was the wrong answer to it. A pinned bar is a
|
|
792
|
+
floating rectangle over a scrolling column, so it needs a veil to occlude what passes
|
|
793
|
+
behind — and a veil an island is allowed to paint can only be derived from
|
|
794
|
+
`currentColor` (the clinic's canvas is unknowable here, PAT-834/838), which means it is
|
|
795
|
+
TRANSLUCENT by construction. The preflight step's own 000 link read straight through
|
|
796
|
+
the glass, under the secondary button, as one garbled line. Two sentences sharing a
|
|
797
|
+
rectangle is a worse defect than a button one scroll away.
|
|
798
|
+
So the footer sits in the column like every other row, and a step that does not fit is
|
|
799
|
+
made to FIT — see `.sk-pf__statements` for the density preflight now runs at. Where a
|
|
800
|
+
step genuinely cannot (identity's nine fields), scrolling is the honest answer.
|
|
801
|
+
The slot picker's `.sk-slots__confirm` is a different case and stays: it pins a
|
|
802
|
+
selection summary over a LIST that is unbounded by nature, and it paints no veil
|
|
803
|
+
because its content is one opaque full-width fill. */
|
|
804
|
+
.sk-flow__btn {
|
|
805
|
+
display: inline-flex;
|
|
806
|
+
align-items: center;
|
|
807
|
+
justify-content: center;
|
|
808
|
+
gap: calc(var(--sk-space));
|
|
809
|
+
width: 100%;
|
|
810
|
+
min-height: 2.75rem;
|
|
811
|
+
padding-inline: calc(var(--sk-space) * 3);
|
|
812
|
+
border: 1px solid transparent;
|
|
813
|
+
border-radius: var(--sk-island-radius);
|
|
814
|
+
font: inherit;
|
|
815
|
+
font-weight: 600;
|
|
816
|
+
cursor: pointer;
|
|
817
|
+
/* Declared HERE, not inherited: the `box-sizing` reset above is scoped to
|
|
818
|
+
`[data-sk-theme]`, and an island's mount point need not sit inside one. A `<button>`
|
|
819
|
+
gets border-box from the UA stylesheet and looked fine anyway — an `<a>` styled as a
|
|
820
|
+
button did not, and overflowed the viewport by its own padding. */
|
|
821
|
+
box-sizing: border-box;
|
|
822
|
+
text-align: center;
|
|
823
|
+
text-decoration: none;
|
|
824
|
+
}
|
|
825
|
+
/* A disabled button must not wear the accent. Two reasons, and the second is measurable:
|
|
826
|
+
the accent is the single signal telling the patient which control is the one to press,
|
|
827
|
+
so spending it on something inert lies about affordance; and `opacity: .55` over the
|
|
828
|
+
primary fill dragged the label to 3.25:1 — under AA on the very screens where the
|
|
829
|
+
patient is trying to work out why they cannot continue. Repainted as a neutral surface
|
|
830
|
+
derived from the canvas, so it tracks whatever clinic colour the island landed on. */
|
|
831
|
+
.sk-flow__btn[disabled] {
|
|
832
|
+
cursor: not-allowed;
|
|
833
|
+
background: color-mix(in srgb, currentColor 12%, transparent);
|
|
834
|
+
border-color: transparent;
|
|
835
|
+
color: color-mix(in srgb, currentColor 62%, transparent);
|
|
836
|
+
}
|
|
837
|
+
.sk-flow__btn--primary {
|
|
838
|
+
background: var(--sk-island-primary);
|
|
839
|
+
color: var(--sk-island-primary-fg);
|
|
840
|
+
}
|
|
841
|
+
.sk-flow__btn--secondary {
|
|
842
|
+
background: transparent;
|
|
843
|
+
border-color: var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 40%, transparent));
|
|
844
|
+
color: inherit;
|
|
845
|
+
}
|
|
846
|
+
.sk-flow__btn--quiet {
|
|
847
|
+
background: none;
|
|
848
|
+
border: 0;
|
|
849
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
850
|
+
font-weight: 500;
|
|
851
|
+
}
|
|
852
|
+
/* A standalone quiet action has no adjacent button to align against, so its label follows
|
|
853
|
+
the left-set reading column. Keep the full-width 44px row as the phone tap target; slot
|
|
854
|
+
cancellation and the submitted confirmation pair are not only children and stay centred. */
|
|
855
|
+
.sk-flow__footer > .sk-flow__btn--quiet:only-child,
|
|
856
|
+
.sk-cf__after > .sk-flow__btn--quiet:only-child {
|
|
857
|
+
justify-content: flex-start;
|
|
858
|
+
padding-inline: 0;
|
|
859
|
+
text-align: start;
|
|
860
|
+
}
|
|
861
|
+
.sk-flow__btn:focus-visible {
|
|
862
|
+
outline: 2px solid var(--sk-island-accent);
|
|
863
|
+
outline-offset: 2px;
|
|
864
|
+
}
|
|
865
|
+
.sk-flow__error {
|
|
866
|
+
margin-top: calc(var(--sk-space));
|
|
867
|
+
color: var(--sk-flow-danger, #b3261e);
|
|
868
|
+
font-size: var(--sk-font-size-sm);
|
|
869
|
+
}
|
|
870
|
+
.sk-flow__loading {
|
|
871
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
872
|
+
font-size: var(--sk-font-size-sm);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/* ── trust rail (PAT-853) ───────────────────────────────────────────────────
|
|
876
|
+
Standing context — who reviews the request, who regulates them, what it costs, where
|
|
877
|
+
the data lives. LAST in the DOM, so on a phone it reads after the CTA; the desktop
|
|
878
|
+
rule below moves it into the empty column beside the flow with grid placement only,
|
|
879
|
+
never by reordering the markup. */
|
|
880
|
+
.sk-flow__rail {
|
|
881
|
+
display: grid;
|
|
882
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
883
|
+
/* A grid item defaults to `min-width: auto`, so one unbreakable string (a URL a clinic
|
|
884
|
+
pastes into its policy label) would widen the whole track and give the phone a
|
|
885
|
+
horizontal scrollbar. The island's own inset cannot save a track that is too wide. */
|
|
886
|
+
min-width: 0;
|
|
887
|
+
overflow-wrap: break-word;
|
|
888
|
+
margin-top: calc(var(--sk-space) * 3);
|
|
889
|
+
padding-top: calc(var(--sk-space) * 3);
|
|
890
|
+
border-top: 1px solid var(--sk-flow-line, color-mix(in srgb, currentColor 24%, transparent));
|
|
891
|
+
font-size: var(--sk-font-size-sm);
|
|
892
|
+
}
|
|
893
|
+
/* Ink on the CHILDREN, not on the rail itself, so `currentColor` in this mix is still the
|
|
894
|
+
PAGE's ink rather than an already-softened copy of it — nesting the same mix twice is
|
|
895
|
+
how "quiet" becomes "unreadable". 74% of the surface's own ink is ~7:1 on PocketDoc's
|
|
896
|
+
near-black canvas and ~6:1 on white; below ~65% neither clears AA.
|
|
897
|
+
NOT `--sk-flow-accent-ink`: the accent marks the one thing the patient is meant to
|
|
898
|
+
press, and a rail that borrows it competes with the button for that meaning. */
|
|
899
|
+
.sk-flow__rail > * {
|
|
900
|
+
color: color-mix(in srgb, currentColor 74%, transparent);
|
|
901
|
+
}
|
|
902
|
+
.sk-flow__rail-clinic {
|
|
903
|
+
font-weight: 600;
|
|
904
|
+
}
|
|
905
|
+
.sk-flow__rail-policies {
|
|
906
|
+
display: flex;
|
|
907
|
+
flex-wrap: wrap;
|
|
908
|
+
gap: calc(var(--sk-space) * 2);
|
|
909
|
+
}
|
|
910
|
+
.sk-flow__rail-link {
|
|
911
|
+
color: inherit;
|
|
912
|
+
text-decoration: underline;
|
|
913
|
+
text-underline-offset: 3px;
|
|
914
|
+
}
|
|
915
|
+
.sk-flow__rail-link:hover {
|
|
916
|
+
text-decoration-thickness: 2px;
|
|
917
|
+
}
|
|
918
|
+
.sk-flow__rail-link:focus-visible {
|
|
919
|
+
outline: 2px solid currentColor;
|
|
920
|
+
outline-offset: 2px;
|
|
921
|
+
}
|
|
922
|
+
/* Reusing the sheet's existing breakpoint scale (900/720/600/560/480), not a new one.
|
|
923
|
+
Below this the rail stays where the markup puts it: under the step. */
|
|
924
|
+
@media (min-width: 900px) {
|
|
925
|
+
/* 53rem = the flow's own 34rem (border-box: 31rem of content inside its 1.5rem insets)
|
|
926
|
+
+ a 2rem gutter + the 17rem rail. The content column is therefore EXACTLY as wide as
|
|
927
|
+
it is today — only the empty canvas beside it is claimed. The three shell rows are
|
|
928
|
+
pinned to column 1 because grid auto-placement fills ACROSS first, which would
|
|
929
|
+
otherwise drop the step body into the rail's column. */
|
|
930
|
+
.sk-flow--railed {
|
|
931
|
+
position: relative;
|
|
932
|
+
--sk-rail-w: 17rem;
|
|
933
|
+
grid-template-columns: minmax(0, 1fr) var(--sk-rail-w);
|
|
934
|
+
column-gap: calc(var(--sk-space) * 4);
|
|
935
|
+
max-width: 53rem;
|
|
936
|
+
}
|
|
937
|
+
/* The railed width, said as a DEFINITE size for the reason spelt out on
|
|
938
|
+
`.sk-certificate-funnel .sk-flow` above: a `max-width` alone lets each step shrink to
|
|
939
|
+
its own content, so the reading column changed width and left edge between steps.
|
|
940
|
+
`max-inline-size: 100%` up there still caps this against a narrow host. */
|
|
941
|
+
.sk-certificate-funnel .sk-flow--railed {
|
|
942
|
+
inline-size: 53rem;
|
|
943
|
+
}
|
|
944
|
+
.sk-flow--railed > .sk-flow__bar,
|
|
945
|
+
.sk-flow--railed > .sk-flow__body,
|
|
946
|
+
.sk-flow--railed > .sk-flow__footer {
|
|
947
|
+
grid-column: 1;
|
|
948
|
+
}
|
|
949
|
+
/* Starts in row 2, level with the heading. A spanning in-flow grid item still feeds its
|
|
950
|
+
height into every intrinsic track it crosses, so the rail's leftover height used to
|
|
951
|
+
set the body/footer rhythm. An absolutely-positioned grid child keeps this grid area
|
|
952
|
+
as its anchor but contributes nothing to track sizing. The explicit inline start and
|
|
953
|
+
width exclude the container's inline-end padding from the rail's resolved box. */
|
|
954
|
+
.sk-flow--railed > .sk-flow__rail {
|
|
955
|
+
grid-column: 2;
|
|
956
|
+
grid-row: 2 / -1;
|
|
957
|
+
position: absolute;
|
|
958
|
+
inset-block-start: 0;
|
|
959
|
+
inset-inline-start: 0;
|
|
960
|
+
inline-size: var(--sk-rail-w);
|
|
961
|
+
margin-top: 0;
|
|
962
|
+
padding-top: 0;
|
|
963
|
+
border-top: 0;
|
|
964
|
+
padding-inline-start: calc(var(--sk-space) * 4);
|
|
965
|
+
border-inline-start: 1px solid
|
|
966
|
+
var(--sk-flow-line, color-mix(in srgb, currentColor 24%, transparent));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* ── questions ──────────────────────────────────────────────────────────────*/
|
|
971
|
+
.sk-q {
|
|
972
|
+
margin-block: calc(var(--sk-space) * 2);
|
|
973
|
+
border: 0;
|
|
974
|
+
padding: 0;
|
|
975
|
+
}
|
|
976
|
+
.sk-q__label,
|
|
977
|
+
.sk-q__legend {
|
|
978
|
+
display: block;
|
|
979
|
+
margin-bottom: calc(var(--sk-space));
|
|
980
|
+
font-size: var(--sk-font-size-sm);
|
|
981
|
+
font-weight: 600;
|
|
982
|
+
}
|
|
983
|
+
.sk-q__help {
|
|
984
|
+
margin-bottom: calc(var(--sk-space));
|
|
985
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
986
|
+
font-size: var(--sk-font-size-sm);
|
|
987
|
+
}
|
|
988
|
+
/* ── quick-pick dates (PAT-873) ──────────────────────────────────────────────
|
|
989
|
+
Chips over real date inputs. The chip row is the shortcut; the inputs below are
|
|
990
|
+
the control, and stay visible so the patient can SEE the date a chip chose and
|
|
991
|
+
adjust it. Sized for a thumb (44px min target) and wrapping, because five chips
|
|
992
|
+
never fit one phone line. Surfaces derive from `currentColor` (the clinic canvas
|
|
993
|
+
is unknowable to an island — PAT-834/838); the pressed chip is the ONE place the
|
|
994
|
+
clinic's own accent is spent, so "which did I pick" reads at a glance. */
|
|
995
|
+
.sk-qd__chips {
|
|
996
|
+
display: flex;
|
|
997
|
+
flex-wrap: wrap;
|
|
998
|
+
gap: calc(var(--sk-space));
|
|
999
|
+
margin-bottom: calc(var(--sk-space) * 1.5);
|
|
1000
|
+
}
|
|
1001
|
+
.sk-qd__chip {
|
|
1002
|
+
min-height: 2.75rem;
|
|
1003
|
+
padding-inline: calc(var(--sk-space) * 2);
|
|
1004
|
+
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
|
1005
|
+
border-radius: 999px;
|
|
1006
|
+
background: color-mix(in srgb, currentColor 6%, transparent);
|
|
1007
|
+
color: inherit;
|
|
1008
|
+
font: inherit;
|
|
1009
|
+
font-size: var(--sk-font-size-sm);
|
|
1010
|
+
font-weight: 600;
|
|
1011
|
+
cursor: pointer;
|
|
1012
|
+
}
|
|
1013
|
+
.sk-qd__chip:hover:not(:disabled) {
|
|
1014
|
+
border-color: var(--sk-island-primary);
|
|
1015
|
+
}
|
|
1016
|
+
/* `aria-pressed` IS the state — styling it means the visual and the accessible
|
|
1017
|
+
answer can never disagree, which a separate `.is-selected` class routinely does. */
|
|
1018
|
+
.sk-qd__chip[aria-pressed='true'] {
|
|
1019
|
+
border-color: var(--sk-island-primary);
|
|
1020
|
+
background: var(--sk-island-primary);
|
|
1021
|
+
color: var(--sk-island-primary-fg);
|
|
1022
|
+
}
|
|
1023
|
+
.sk-qd__chip:focus-visible {
|
|
1024
|
+
outline: 2px solid var(--sk-island-accent);
|
|
1025
|
+
outline-offset: 2px;
|
|
1026
|
+
}
|
|
1027
|
+
.sk-qd__chip:disabled {
|
|
1028
|
+
opacity: 0.55;
|
|
1029
|
+
cursor: not-allowed;
|
|
1030
|
+
}
|
|
1031
|
+
/* Two columns for a range, one for a single date — `auto-fit` does both with no
|
|
1032
|
+
modifier class, and collapses to one column on a narrow phone. */
|
|
1033
|
+
.sk-qd__fields {
|
|
1034
|
+
display: grid;
|
|
1035
|
+
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
|
1036
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1037
|
+
}
|
|
1038
|
+
.sk-flow .sk-qd__sublabel {
|
|
1039
|
+
display: block;
|
|
1040
|
+
margin-bottom: calc(var(--sk-space) * 0.5);
|
|
1041
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1042
|
+
font-size: 0.8125rem;
|
|
1043
|
+
font-weight: 500;
|
|
1044
|
+
}
|
|
1045
|
+
.sk-q__options {
|
|
1046
|
+
display: flex;
|
|
1047
|
+
flex-wrap: wrap;
|
|
1048
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1049
|
+
}
|
|
1050
|
+
.sk-q__option {
|
|
1051
|
+
display: inline-flex;
|
|
1052
|
+
align-items: center;
|
|
1053
|
+
gap: calc(var(--sk-space));
|
|
1054
|
+
padding: calc(var(--sk-space) * 1.5) calc(var(--sk-space) * 2.5);
|
|
1055
|
+
border: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 35%, transparent));
|
|
1056
|
+
border-radius: var(--sk-island-radius);
|
|
1057
|
+
cursor: pointer;
|
|
1058
|
+
}
|
|
1059
|
+
.sk-q__option:has(input:checked) {
|
|
1060
|
+
border-color: var(--sk-island-primary);
|
|
1061
|
+
background: color-mix(in srgb, var(--sk-island-primary) 8%, transparent);
|
|
1062
|
+
}
|
|
1063
|
+
/* The funnel's last two UA widgets — the intake radios and the consent tick. `accent-color`
|
|
1064
|
+
tints only the CHECKED box; unchecked, with no `color-scheme` to go on, the UA paints its
|
|
1065
|
+
light-scheme default: a solid #ffffff square with a grey hairline and a ~3px radius. On
|
|
1066
|
+
PocketDoc's #121936 that made the single brightest object on the screen where the patient
|
|
1067
|
+
signs a legal consent an unstyled browser control — a larger continuous white mass than
|
|
1068
|
+
the 32px heading's strokes, wearing a corner radius that contradicts every other box on
|
|
1069
|
+
the step. The kit CANNOT answer this with `color-scheme`: the canvas belongs to the
|
|
1070
|
+
clinic and the island must never guess it. So the same recipe the fields took at
|
|
1071
|
+
`.sk-q__input` below — derive the resting state from the SURFACE — and pay for the
|
|
1072
|
+
checked state with the `--sk-island-primary` / `-fg` PAIR the primary CTA already ships,
|
|
1073
|
+
so the tick's contrast is guaranteed by the same derivation that guarantees the button's
|
|
1074
|
+
label rather than by `accent-color`'s per-spec glyph. These stay real `<input>`s: the
|
|
1075
|
+
checked state, the label association and Space/arrow-key operation are still the
|
|
1076
|
+
platform's, and the focus ring still lives on the card (`:has(input:focus-visible)`). */
|
|
1077
|
+
.sk-cf__check input,
|
|
1078
|
+
.sk-q__option input[type='radio'] {
|
|
1079
|
+
appearance: none;
|
|
1080
|
+
-webkit-appearance: none;
|
|
1081
|
+
inline-size: 1.25rem;
|
|
1082
|
+
block-size: 1.25rem;
|
|
1083
|
+
flex: none;
|
|
1084
|
+
margin: 0;
|
|
1085
|
+
display: grid;
|
|
1086
|
+
place-content: center;
|
|
1087
|
+
/* NOT `--sk-flow-line` (currentColor 24%) — that is a hairline BETWEEN cards, and 24%
|
|
1088
|
+
measures 2.2:1 on that dark canvas, under the 3:1 a UI component's boundary owes
|
|
1089
|
+
(WCAG 1.4.11). 55% of the surface's own ink clears it on a dark canvas and on white. */
|
|
1090
|
+
border: 1.5px solid color-mix(in srgb, currentColor 55%, transparent);
|
|
1091
|
+
/* 6px, deliberately NOT `--sk-island-radius`: 10-12px on a 20px box renders as a circle,
|
|
1092
|
+
which is the radio idiom, and the two would stop being tellable apart. */
|
|
1093
|
+
border-radius: 0.375rem;
|
|
1094
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
1095
|
+
color: inherit;
|
|
1096
|
+
cursor: pointer;
|
|
1097
|
+
}
|
|
1098
|
+
.sk-q__option input[type='radio'] {
|
|
1099
|
+
border-radius: 50%;
|
|
1100
|
+
}
|
|
1101
|
+
.sk-cf__check input:checked,
|
|
1102
|
+
.sk-q__option input[type='radio']:checked {
|
|
1103
|
+
background: var(--sk-island-primary);
|
|
1104
|
+
border-color: var(--sk-island-primary);
|
|
1105
|
+
}
|
|
1106
|
+
.sk-cf__check input:checked::before {
|
|
1107
|
+
content: '';
|
|
1108
|
+
inline-size: 0.7rem;
|
|
1109
|
+
block-size: 0.7rem;
|
|
1110
|
+
background: var(--sk-island-primary-fg);
|
|
1111
|
+
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
|
1112
|
+
}
|
|
1113
|
+
.sk-q__option input[type='radio']:checked::before {
|
|
1114
|
+
content: '';
|
|
1115
|
+
inline-size: 0.5rem;
|
|
1116
|
+
block-size: 0.5rem;
|
|
1117
|
+
border-radius: 50%;
|
|
1118
|
+
background: var(--sk-island-primary-fg);
|
|
1119
|
+
}
|
|
1120
|
+
/* `appearance: none` also drops the UA's disabled greying, and both controls are disabled
|
|
1121
|
+
while a step's write is in flight. Same 0.55 the fields use. */
|
|
1122
|
+
.sk-cf__check input:disabled,
|
|
1123
|
+
.sk-q__option input[type='radio']:disabled {
|
|
1124
|
+
opacity: 0.55;
|
|
1125
|
+
}
|
|
1126
|
+
/* …and it erases the control ENTIRELY under a forced-colours OS theme, where the native
|
|
1127
|
+
widget is the only thing the user can see. Hand it straight back there: a consent tick
|
|
1128
|
+
nobody can find is a worse defect than a white one. */
|
|
1129
|
+
@media (forced-colors: active) {
|
|
1130
|
+
.sk-cf__check input,
|
|
1131
|
+
.sk-q__option input[type='radio'] {
|
|
1132
|
+
appearance: auto;
|
|
1133
|
+
}
|
|
1134
|
+
.sk-cf__check input:checked::before,
|
|
1135
|
+
.sk-q__option input[type='radio']:checked::before {
|
|
1136
|
+
content: none;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
.sk-q__input,
|
|
1140
|
+
.sk-identity__input,
|
|
1141
|
+
.sk-cf__input,
|
|
1142
|
+
.sk-pay__input {
|
|
1143
|
+
width: 100%;
|
|
1144
|
+
/* A DEFINITE box, not a `min-height`. A minimum let `<input type=text>`, `<input
|
|
1145
|
+
type=date>` and `<select>` each resolve their own intrinsic height, so one row of the
|
|
1146
|
+
identity form shipped three of them: Suburb 43px beside State 40px, Phone 43px beside
|
|
1147
|
+
Date of birth 45px — ragged bottom edges on the screen that asks for name, DOB and
|
|
1148
|
+
home address, which is exactly where a patient decides whether this is a real clinic.
|
|
1149
|
+
2.75rem rather than 2.5 also lifts every control to the 44px touch-target floor
|
|
1150
|
+
(WCAG 2.2 SC 2.5.8) on mobile. Safe as a fixed size because none of these four classes
|
|
1151
|
+
is ever on a `<textarea>` — every consumer is a single-line input or a select. The
|
|
1152
|
+
explicit `line-height` is the other half: without it a date input's UA line box, not
|
|
1153
|
+
ours, sets where the text sits inside the box. */
|
|
1154
|
+
block-size: 2.75rem;
|
|
1155
|
+
line-height: 1.2;
|
|
1156
|
+
padding: calc(var(--sk-space)) calc(var(--sk-space) * 1.5);
|
|
1157
|
+
/* Same surface-derived boundary recipe as the unchecked checkbox/radio above. Fields
|
|
1158
|
+
need a 3:1 component edge; the shared `--sk-flow-line` remains a decorative hairline. */
|
|
1159
|
+
border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
|
|
1160
|
+
border-radius: var(--sk-island-radius);
|
|
1161
|
+
/* Derived from the SURFACE, not taken from the token bag — same rule as
|
|
1162
|
+
`--sk-flow-raised` above and the slots-confirm scrim below (PAT-834/838).
|
|
1163
|
+
`--sk-background` is the kit's own LIGHT token and says nothing about the canvas the
|
|
1164
|
+
island was dropped on: painting it here laid NINE opaque white slabs down the dark
|
|
1165
|
+
identity step, so the loudest thing on the screen where the patient hands over their
|
|
1166
|
+
name, DOB and address was a wall of empty rectangles, out-competing both the question
|
|
1167
|
+
and the CTA. A translucent tint of the page's own ink darkens a light canvas and
|
|
1168
|
+
lightens a dark one, and `color: inherit` means the fill and the ink can never split. */
|
|
1169
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
1170
|
+
color: inherit;
|
|
1171
|
+
font: inherit;
|
|
1172
|
+
}
|
|
1173
|
+
/* A <select>'s options are their own painting surface on Firefox and on Windows
|
|
1174
|
+
Chrome — declaring the ink without the fill there hands the popup back to the
|
|
1175
|
+
system colours. Same token pair as the field, so they can never diverge. */
|
|
1176
|
+
.sk-q__input option,
|
|
1177
|
+
.sk-identity__input option,
|
|
1178
|
+
.sk-cf__input option,
|
|
1179
|
+
.sk-pay__input option {
|
|
1180
|
+
background: var(--sk-background, #fff);
|
|
1181
|
+
color: var(--sk-foreground, #1b2733);
|
|
1182
|
+
}
|
|
1183
|
+
.sk-q__input[aria-invalid='true'],
|
|
1184
|
+
.sk-identity__input[aria-invalid='true'],
|
|
1185
|
+
.sk-cf__input[aria-invalid='true'],
|
|
1186
|
+
.sk-pay__input[aria-invalid='true'] {
|
|
1187
|
+
/* Derived, not absolute: the field no longer paints its own white fill, so this
|
|
1188
|
+
border now sits on the clinic's canvas, where `#b3261e` is 2.64:1 on PocketDoc's
|
|
1189
|
+
#121936 — an "this one is wrong" marker you cannot see. */
|
|
1190
|
+
border-color: var(--sk-flow-danger, #b3261e);
|
|
1191
|
+
}
|
|
1192
|
+
.sk-q__input:focus-visible,
|
|
1193
|
+
.sk-identity__input:focus-visible,
|
|
1194
|
+
.sk-cf__input:focus-visible,
|
|
1195
|
+
.sk-pay__input:focus-visible {
|
|
1196
|
+
/* Same ring as `.sk-flow__btn:focus-visible` — and load-bearing now, because a
|
|
1197
|
+
see-through field has no opaque fill left to mark "you are typing here". */
|
|
1198
|
+
outline: 2px solid var(--sk-island-accent);
|
|
1199
|
+
outline-offset: 2px;
|
|
1200
|
+
border-color: var(--sk-island-accent);
|
|
1201
|
+
}
|
|
1202
|
+
.sk-q__input::placeholder,
|
|
1203
|
+
.sk-identity__input::placeholder,
|
|
1204
|
+
.sk-cf__input::placeholder,
|
|
1205
|
+
.sk-pay__input::placeholder {
|
|
1206
|
+
/* The UA's placeholder grey is tuned for a white field. Say it in the surface's own
|
|
1207
|
+
ink instead, or "MM"/"YYYY" go near-invisible on a dark canvas. */
|
|
1208
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1209
|
+
opacity: 1;
|
|
1210
|
+
}
|
|
1211
|
+
/* `::placeholder` above CANNOT reach `input[type=date]` — no engine matches it there. The
|
|
1212
|
+
UA paints the empty `dd/mm/yyyy` through its own datetime sub-fields, which inherit our
|
|
1213
|
+
`color: inherit`, so an UNANSWERED date was rendered in full-strength value ink: on the
|
|
1214
|
+
identity step the blank DOB read as already filled while the fields beside it read blank.
|
|
1215
|
+
Colour the PSEUDO, not the input — the field's fill is `currentColor 8%`, so recolouring
|
|
1216
|
+
the control itself would also re-tint the box away from its neighbours in the same grid.
|
|
1217
|
+
`:not(:focus)` hands full ink back the moment the patient starts typing, so a
|
|
1218
|
+
half-entered date is never mistaken for placeholder text. */
|
|
1219
|
+
.sk-q__input[type='date'][data-empty]:not(:focus)::-webkit-datetime-edit,
|
|
1220
|
+
.sk-identity__input[type='date'][data-empty]:not(:focus)::-webkit-datetime-edit {
|
|
1221
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1222
|
+
}
|
|
1223
|
+
/* Firefox has no `::-webkit-datetime-edit` and paints the same placeholder, so it needs the
|
|
1224
|
+
colour on the control. Scoped so Chrome/Safari never take this branch and never shift the
|
|
1225
|
+
fill; Safari without `selector()` support falls through to the pseudo rule above. */
|
|
1226
|
+
@supports not selector(::-webkit-datetime-edit) {
|
|
1227
|
+
.sk-q__input[type='date'][data-empty]:not(:focus),
|
|
1228
|
+
.sk-identity__input[type='date'][data-empty]:not(:focus) {
|
|
1229
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
/* A `<select>` still on its empty option is an UNANSWERED question, not a chosen value. */
|
|
1233
|
+
select.sk-identity__input[data-empty] {
|
|
1234
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1235
|
+
}
|
|
1236
|
+
/* The UA calendar glyph is a fixed dark SVG — legible on the old white fill, all but
|
|
1237
|
+
invisible once the field is a tint of a dark page. `invert(0.5)` flattens ANY source
|
|
1238
|
+
luminance to mid grey, so it reads on both without the island guessing the canvas. */
|
|
1239
|
+
.sk-q__input::-webkit-calendar-picker-indicator,
|
|
1240
|
+
.sk-identity__input::-webkit-calendar-picker-indicator {
|
|
1241
|
+
filter: invert(0.5);
|
|
1242
|
+
}
|
|
1243
|
+
/* Chrome repaints an autofilled field with an opaque pale slab, which would put the
|
|
1244
|
+
white rectangles straight back on the name, address and card fields. There is no
|
|
1245
|
+
opaque colour we could hand it, so defer its paint indefinitely and keep our own.
|
|
1246
|
+
|
|
1247
|
+
THE INK COMES FROM `--sk-flow-ink`, WHICH ONLY SCRIPT CAN SUPPLY. This rule shipped
|
|
1248
|
+
as `currentColor`, which is self-referential: `currentColor` resolves against the
|
|
1249
|
+
element the value lands on, and that element is the one the UA has just recoloured
|
|
1250
|
+
for the autofill paint. So the override dutifully re-read the UA's black and painted
|
|
1251
|
+
the patient's autofilled name, email, phone and street address in it, on a dark
|
|
1252
|
+
canvas. The declared colour on the field stays `rgb(255,255,255)` throughout, which
|
|
1253
|
+
is why this survived every contrast probe — nothing in the cascade is wrong, the
|
|
1254
|
+
value is just read off the wrong element.
|
|
1255
|
+
|
|
1256
|
+
Every CSS way of saying "the colour around me" fails the same way here, measured in
|
|
1257
|
+
Chromium and WebKit rather than assumed: `inherit` inherits the KEYWORD and
|
|
1258
|
+
re-resolves it, an unregistered custom property substitutes it as a token, an
|
|
1259
|
+
`@property <color>` one keeps it late-resolved, and `color-mix(… currentColor …)`
|
|
1260
|
+
yields `color(srgb 0 0 0)`. Only a concrete `rgb(…)` survives, so `useResolvedInk`
|
|
1261
|
+
in `flow-shell.tsx` reads the flow root's own computed colour and publishes it here.
|
|
1262
|
+
The fallback is the old behaviour, for a form that has not been autofilled yet.
|
|
1263
|
+
|
|
1264
|
+
Two blocks, not one selector list: `:autofill` is the standard and `:-webkit-autofill`
|
|
1265
|
+
the legacy alias, and a selector list is discarded WHOLE by any engine that fails to
|
|
1266
|
+
parse one member. Split, each engine keeps the rule it understands — which is also
|
|
1267
|
+
what finally covers Firefox, whose autofill paints its own black-on-yellow and which
|
|
1268
|
+
never matched the `-webkit-` selector at all. */
|
|
1269
|
+
.sk-q__input:-webkit-autofill,
|
|
1270
|
+
.sk-identity__input:-webkit-autofill,
|
|
1271
|
+
.sk-cf__input:-webkit-autofill,
|
|
1272
|
+
.sk-pay__input:-webkit-autofill {
|
|
1273
|
+
-webkit-text-fill-color: var(--sk-flow-ink, currentColor);
|
|
1274
|
+
caret-color: var(--sk-flow-ink, currentColor);
|
|
1275
|
+
transition: background-color 600000s 0s;
|
|
1276
|
+
}
|
|
1277
|
+
.sk-q__input:autofill,
|
|
1278
|
+
.sk-identity__input:autofill,
|
|
1279
|
+
.sk-cf__input:autofill,
|
|
1280
|
+
.sk-pay__input:autofill {
|
|
1281
|
+
-webkit-text-fill-color: var(--sk-flow-ink, currentColor);
|
|
1282
|
+
caret-color: var(--sk-flow-ink, currentColor);
|
|
1283
|
+
transition: background-color 600000s 0s;
|
|
1284
|
+
}
|
|
1285
|
+
/* Payment disables its fields while the pre-auth is in flight; with an author-set fill
|
|
1286
|
+
the UA no longer greys them out, so the disabled state has to be said explicitly. */
|
|
1287
|
+
.sk-q__input:disabled,
|
|
1288
|
+
.sk-identity__input:disabled,
|
|
1289
|
+
.sk-cf__input:disabled,
|
|
1290
|
+
.sk-pay__input:disabled {
|
|
1291
|
+
opacity: 0.55;
|
|
1292
|
+
}
|
|
1293
|
+
/* Fallback kept: these classes can render outside a `.sk-flow` subtree, where the
|
|
1294
|
+
derived token is undefined. */
|
|
1295
|
+
.sk-q__error {
|
|
1296
|
+
margin-top: calc(var(--sk-space));
|
|
1297
|
+
color: var(--sk-flow-danger, #b3261e);
|
|
1298
|
+
font-size: var(--sk-font-size-sm);
|
|
1299
|
+
}
|
|
1300
|
+
.sk-q__required {
|
|
1301
|
+
color: var(--sk-flow-danger, #b3261e);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/* ── identity ───────────────────────────────────────────────────────────────*/
|
|
1305
|
+
.sk-identity__grid {
|
|
1306
|
+
display: grid;
|
|
1307
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1308
|
+
gap: calc(var(--sk-space) * 2);
|
|
1309
|
+
}
|
|
1310
|
+
.sk-identity__field--wide {
|
|
1311
|
+
grid-column: 1 / -1;
|
|
1312
|
+
}
|
|
1313
|
+
.sk-identity__label {
|
|
1314
|
+
display: block;
|
|
1315
|
+
margin-bottom: calc(var(--sk-space) * 0.75);
|
|
1316
|
+
font-size: var(--sk-font-size-sm);
|
|
1317
|
+
font-weight: 500;
|
|
1318
|
+
}
|
|
1319
|
+
/* `.sk-flow`-prefixed, here and below, purely to out-specify the block-margin reset at
|
|
1320
|
+
the top of this sheet — see the TRAP note there. Without it these three margins were
|
|
1321
|
+
dead and identity read as one undifferentiated column: the RESIDENTIAL ADDRESS eyebrow
|
|
1322
|
+
sat flush against the date-of-birth hint above it and the "Street address" label below,
|
|
1323
|
+
so a group heading appeared to caption a field it does not belong to, and the sign-in
|
|
1324
|
+
shortcut looked like part of the first-name field. Every one of these classes only ever
|
|
1325
|
+
renders inside FlowShell's `.sk-flow` root, so the prefix always matches. */
|
|
1326
|
+
.sk-flow .sk-identity__help {
|
|
1327
|
+
margin-top: calc(var(--sk-space) * 0.5);
|
|
1328
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1329
|
+
font-size: 0.75rem;
|
|
1330
|
+
}
|
|
1331
|
+
.sk-flow .sk-identity__section {
|
|
1332
|
+
margin-top: calc(var(--sk-space) * 3);
|
|
1333
|
+
margin-bottom: calc(var(--sk-space));
|
|
1334
|
+
font-size: 0.6875rem;
|
|
1335
|
+
font-weight: 600;
|
|
1336
|
+
letter-spacing: 0.12em;
|
|
1337
|
+
text-transform: uppercase;
|
|
1338
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1339
|
+
}
|
|
1340
|
+
/* Caption for the group heading above, NOT a field hint — so it does not reuse
|
|
1341
|
+
`.sk-identity__help`, whose id is an aria-describedby contract. `.sk-flow`-prefixed for
|
|
1342
|
+
the same reason as the rules above it: the `.sk-flow p` reset near the top of this sheet
|
|
1343
|
+
is (0,1,1) and silently zeroes any bare block margin here. The negative top margin
|
|
1344
|
+
collapses against the eyebrow's 1×space bottom margin to ~0.25rem, so the note reads as
|
|
1345
|
+
the eyebrow's caption rather than the first field's hint. */
|
|
1346
|
+
.sk-flow .sk-identity__section-note {
|
|
1347
|
+
margin-top: calc(var(--sk-space) * -0.5);
|
|
1348
|
+
margin-bottom: calc(var(--sk-space) * 2);
|
|
1349
|
+
max-inline-size: 44ch;
|
|
1350
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1351
|
+
font-size: 0.8125rem;
|
|
1352
|
+
line-height: 1.45;
|
|
1353
|
+
}
|
|
1354
|
+
.sk-flow .sk-identity__ramp {
|
|
1355
|
+
margin-bottom: calc(var(--sk-space) * 2);
|
|
1356
|
+
font-size: var(--sk-font-size-sm);
|
|
1357
|
+
}
|
|
1358
|
+
.sk-identity__ramp-link {
|
|
1359
|
+
background: none;
|
|
1360
|
+
border: 0;
|
|
1361
|
+
padding: 0;
|
|
1362
|
+
color: var(--sk-flow-accent-ink, var(--sk-island-primary));
|
|
1363
|
+
font: inherit;
|
|
1364
|
+
text-decoration: underline;
|
|
1365
|
+
text-underline-offset: 3px;
|
|
1366
|
+
cursor: pointer;
|
|
1367
|
+
}
|
|
1368
|
+
.sk-identity__ramp--open {
|
|
1369
|
+
display: grid;
|
|
1370
|
+
gap: calc(var(--sk-space));
|
|
1371
|
+
padding: calc(var(--sk-space) * 2);
|
|
1372
|
+
border: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 30%, transparent));
|
|
1373
|
+
border-radius: var(--sk-island-radius);
|
|
1374
|
+
}
|
|
1375
|
+
.sk-identity__confirm {
|
|
1376
|
+
padding: calc(var(--sk-space) * 2.5);
|
|
1377
|
+
border: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 30%, transparent));
|
|
1378
|
+
border-radius: var(--sk-island-radius);
|
|
1379
|
+
}
|
|
1380
|
+
.sk-flow .sk-identity__confirm-title {
|
|
1381
|
+
font-weight: 600;
|
|
1382
|
+
margin-bottom: calc(var(--sk-space) * 1.5);
|
|
1383
|
+
}
|
|
1384
|
+
/* The RETURNING-patient screen, so the one that must feel effortless — and it was the
|
|
1385
|
+
only screen in the funnel that scrolled sideways. A `<dd>` carries a UA
|
|
1386
|
+
`margin-inline-start: 40px`, and neither cell could shrink, so an ordinary
|
|
1387
|
+
24-character email pushed the card to 442px inside a 390px phone and took the only
|
|
1388
|
+
button on the screen off the right edge with it. */
|
|
1389
|
+
.sk-identity__confirm-row {
|
|
1390
|
+
display: flex;
|
|
1391
|
+
gap: calc(var(--sk-space) * 2);
|
|
1392
|
+
padding-block: calc(var(--sk-space) * 0.5);
|
|
1393
|
+
}
|
|
1394
|
+
.sk-identity__confirm-row dt {
|
|
1395
|
+
flex: 0 0 6.5rem;
|
|
1396
|
+
min-width: 0;
|
|
1397
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1398
|
+
}
|
|
1399
|
+
.sk-identity__confirm-row dd {
|
|
1400
|
+
min-width: 0;
|
|
1401
|
+
/* An email has no break opportunity, so without this it sets the card's width. */
|
|
1402
|
+
overflow-wrap: anywhere;
|
|
1403
|
+
}
|
|
1404
|
+
.sk-identity__confirm-actions {
|
|
1405
|
+
display: grid;
|
|
1406
|
+
gap: calc(var(--sk-space));
|
|
1407
|
+
margin-top: calc(var(--sk-space) * 2.5);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/* ── funnel screens ─────────────────────────────────────────────────────────*/
|
|
1411
|
+
.sk-cf__services,
|
|
1412
|
+
.sk-cf__list,
|
|
1413
|
+
.sk-cf__links,
|
|
1414
|
+
.sk-cf__consent-summary {
|
|
1415
|
+
list-style: none;
|
|
1416
|
+
padding: 0;
|
|
1417
|
+
margin: 0;
|
|
1418
|
+
display: grid;
|
|
1419
|
+
/* 1× not 1.5×: on the confirm screen these five bullets sit between the review block
|
|
1420
|
+
and the button, and the extra half-space pushed the button below the fold. The
|
|
1421
|
+
bullets are still separated by more than their own line-height, so they read as a
|
|
1422
|
+
list rather than a paragraph. */
|
|
1423
|
+
gap: var(--sk-space);
|
|
1424
|
+
}
|
|
1425
|
+
/* These two lists FOLLOW reading copy, unlike `.sk-cf__services` / `.sk-cf__consent-summary`
|
|
1426
|
+
which lead their screens — and with the reset zeroing them they sat flush against the
|
|
1427
|
+
sentence that introduces them. On `unavailable` the tightest gap on the page fell
|
|
1428
|
+
exactly on the list's boundary, so the first exclusion read as the next line of the
|
|
1429
|
+
paragraph; on `blocked` "Nothing has been charged." ran straight into the 000 line.
|
|
1430
|
+
`.sk-flow` prefix is load-bearing — see the TRAP note on the reset. Outer air is
|
|
1431
|
+
deliberately WIDER than the 12px item gap, so the set groups against the prose. */
|
|
1432
|
+
.sk-flow .sk-cf__list,
|
|
1433
|
+
.sk-flow .sk-cf__links {
|
|
1434
|
+
margin-top: calc(var(--sk-space) * 2.5);
|
|
1435
|
+
}
|
|
1436
|
+
.sk-flow .sk-cf__list {
|
|
1437
|
+
/* Only `.sk-cf__list` has copy after it (the "see your regular GP" close), which
|
|
1438
|
+
otherwise read as a sixth exclusion. */
|
|
1439
|
+
margin-bottom: calc(var(--sk-space) * 2.5);
|
|
1440
|
+
}
|
|
1441
|
+
/* A marker, because `.sk-flow ul { list-style: none }` outranks any `list-style` we
|
|
1442
|
+
could set here — a pseudo-dot is the only route. NEVER `--sk-island-accent`: accent is
|
|
1443
|
+
a press signal and a list marker is not pressable. The consent button deliberately
|
|
1444
|
+
stays neutral until the patient ticks and signs, so its bullets must not out-shout it.
|
|
1445
|
+
Deliberately NOT on `.sk-cf__links`, whose first item is `.sk-cf__emergency` —
|
|
1446
|
+
decoration beside the 000 affordance competes with the one thing that must shout. */
|
|
1447
|
+
.sk-cf__list li {
|
|
1448
|
+
position: relative;
|
|
1449
|
+
padding-left: calc(var(--sk-space) * 2.5);
|
|
1450
|
+
}
|
|
1451
|
+
.sk-cf__list li::before {
|
|
1452
|
+
content: '';
|
|
1453
|
+
position: absolute;
|
|
1454
|
+
left: 0;
|
|
1455
|
+
top: 0.55em;
|
|
1456
|
+
width: 6px;
|
|
1457
|
+
height: 6px;
|
|
1458
|
+
border-radius: 999px;
|
|
1459
|
+
background: color-mix(in srgb, currentColor 55%, transparent);
|
|
1460
|
+
}
|
|
1461
|
+
.sk-cf__service {
|
|
1462
|
+
display: flex;
|
|
1463
|
+
align-items: center;
|
|
1464
|
+
justify-content: space-between;
|
|
1465
|
+
gap: calc(var(--sk-space) * 2);
|
|
1466
|
+
width: 100%;
|
|
1467
|
+
padding: calc(var(--sk-space) * 2) calc(var(--sk-space) * 2.5);
|
|
1468
|
+
border: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 30%, transparent));
|
|
1469
|
+
border-radius: var(--sk-island-radius);
|
|
1470
|
+
background: var(--sk-flow-raised, none);
|
|
1471
|
+
color: inherit;
|
|
1472
|
+
font: inherit;
|
|
1473
|
+
text-align: left;
|
|
1474
|
+
cursor: pointer;
|
|
1475
|
+
}
|
|
1476
|
+
.sk-cf__service:hover:not(:disabled) {
|
|
1477
|
+
border-color: var(--sk-island-primary);
|
|
1478
|
+
}
|
|
1479
|
+
.sk-cf__service:disabled {
|
|
1480
|
+
cursor: not-allowed;
|
|
1481
|
+
opacity: 0.6;
|
|
1482
|
+
}
|
|
1483
|
+
.sk-cf__service:focus-visible {
|
|
1484
|
+
outline: 2px solid var(--sk-island-accent);
|
|
1485
|
+
outline-offset: 2px;
|
|
1486
|
+
}
|
|
1487
|
+
/* WHAT was booked, on the screen that says it was booked (PAT-853). */
|
|
1488
|
+
.sk-flow .sk-cf__booked {
|
|
1489
|
+
display: grid;
|
|
1490
|
+
gap: calc(var(--sk-space) * 0.5);
|
|
1491
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
1492
|
+
padding: calc(var(--sk-space) * 2);
|
|
1493
|
+
border: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 30%, transparent));
|
|
1494
|
+
border-radius: var(--sk-island-radius);
|
|
1495
|
+
background: var(--sk-flow-raised, none);
|
|
1496
|
+
}
|
|
1497
|
+
.sk-cf__booked-label {
|
|
1498
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1499
|
+
font-size: 0.6875rem;
|
|
1500
|
+
font-weight: 600;
|
|
1501
|
+
letter-spacing: 0.14em;
|
|
1502
|
+
text-transform: uppercase;
|
|
1503
|
+
}
|
|
1504
|
+
.sk-cf__booked-when {
|
|
1505
|
+
font-size: 1.125rem;
|
|
1506
|
+
font-weight: 600;
|
|
1507
|
+
}
|
|
1508
|
+
/* The call the patient is waiting for. Same block as `.sk-cf__booked` so the two read
|
|
1509
|
+
as one pair of facts, but carrying the island accent on its leading edge — this is
|
|
1510
|
+
the next thing that HAPPENS, and it was previously losing the page to an email
|
|
1511
|
+
address in a bordered box. Accent only on the rule, never a fill: the primary button
|
|
1512
|
+
owns the filled accent, and a confirmation screen must not have two of them. */
|
|
1513
|
+
.sk-flow .sk-cf__call {
|
|
1514
|
+
margin-top: var(--sk-space);
|
|
1515
|
+
border-left: 3px solid var(--sk-island-primary);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/* The review rows on the confirm screen — label above value, PAIRED into two columns
|
|
1519
|
+
so six facts cost three rows rather than six. That is the difference between the
|
|
1520
|
+
"Agree and confirm" button sitting above the fold and 175px below it, which is the
|
|
1521
|
+
whole point of a confirmation screen: you should be able to see what you are
|
|
1522
|
+
confirming AND the button that confirms it without scrolling.
|
|
1523
|
+
|
|
1524
|
+
`auto-fit` + a 10rem floor collapses to ONE column on a narrow phone, where two
|
|
1525
|
+
columns of truncated text would be worse than a longer scroll. The natural field
|
|
1526
|
+
order already pairs well — Name|Date of birth, Email|Phone, Request|Fee — so no
|
|
1527
|
+
row needs to span and no hole opens up. `dl`/`dt`/`dd` are reset because the kit
|
|
1528
|
+
ships no UA-margin reset for them. */
|
|
1529
|
+
.sk-flow .sk-cf__review {
|
|
1530
|
+
display: grid;
|
|
1531
|
+
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
|
1532
|
+
gap: var(--sk-space) calc(var(--sk-space) * 1.5);
|
|
1533
|
+
margin: 0 0 calc(var(--sk-space) * 1.5);
|
|
1534
|
+
padding: calc(var(--sk-space) * 1.25) calc(var(--sk-space) * 1.5);
|
|
1535
|
+
border: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 30%, transparent));
|
|
1536
|
+
border-radius: var(--sk-island-radius);
|
|
1537
|
+
background: var(--sk-flow-raised, none);
|
|
1538
|
+
}
|
|
1539
|
+
.sk-flow .sk-cf__review-row {
|
|
1540
|
+
display: grid;
|
|
1541
|
+
gap: calc(var(--sk-space) * 0.25);
|
|
1542
|
+
}
|
|
1543
|
+
.sk-flow .sk-cf__review-label {
|
|
1544
|
+
margin: 0;
|
|
1545
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1546
|
+
font-size: 0.6875rem;
|
|
1547
|
+
font-weight: 600;
|
|
1548
|
+
letter-spacing: 0.14em;
|
|
1549
|
+
text-transform: uppercase;
|
|
1550
|
+
}
|
|
1551
|
+
.sk-flow .sk-cf__review-value {
|
|
1552
|
+
margin: 0;
|
|
1553
|
+
font-size: 1rem;
|
|
1554
|
+
/* An email or a long service label must wrap rather than widen the flow column. */
|
|
1555
|
+
overflow-wrap: anywhere;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
/* The sentence that says what the button means. Sits WITH the button, so it is read at
|
|
1559
|
+
the moment of pressing rather than remembered from further up the page. */
|
|
1560
|
+
.sk-flow .sk-cf__agree-note {
|
|
1561
|
+
margin: var(--sk-space) 0 0;
|
|
1562
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1563
|
+
font-size: 0.875rem;
|
|
1564
|
+
line-height: 1.45;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/* Quiet actions under the content — deliberately not a row of equal-weight buttons. */
|
|
1568
|
+
.sk-cf__after {
|
|
1569
|
+
display: grid;
|
|
1570
|
+
gap: calc(var(--sk-space) * 0.5);
|
|
1571
|
+
margin-top: calc(var(--sk-space) * 3);
|
|
1572
|
+
}
|
|
1573
|
+
.sk-cf__service-body {
|
|
1574
|
+
display: grid;
|
|
1575
|
+
gap: calc(var(--sk-space) * 0.5);
|
|
1576
|
+
min-width: 0;
|
|
1577
|
+
}
|
|
1578
|
+
.sk-cf__service-label {
|
|
1579
|
+
font-weight: 600;
|
|
1580
|
+
}
|
|
1581
|
+
/* The price on the picker (PAT-853). Muted, NOT accent-coloured: the accent is the
|
|
1582
|
+
primary action's alone, and a price that shouts reads as a sales page rather than a
|
|
1583
|
+
fact. Muted here still clears AA — it derives from the same currentColor ramp. */
|
|
1584
|
+
.sk-cf__service-delivery,
|
|
1585
|
+
.sk-cf__service-fee {
|
|
1586
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1587
|
+
font-size: var(--sk-font-size-sm);
|
|
1588
|
+
}
|
|
1589
|
+
/* THE accent on this screen, and the only one. The four cards are what the patient is
|
|
1590
|
+
meant to press, and they were painted entirely in neutral ink while the "I need a
|
|
1591
|
+
different type of certificate" escape hatch below them carried the clinic's accent and
|
|
1592
|
+
an underline — so the loudest thing on the first screen of the funnel was the way out.
|
|
1593
|
+
Marking the chevron is enough to claim the affordance without four accent-filled slabs
|
|
1594
|
+
competing with each other. Scoped INSIDE `.sk-cf__service`: the same chevron class is
|
|
1595
|
+
reused by the preflight decline list, which is a different screen's decision. Accent
|
|
1596
|
+
INK, not the raw brand — the derived mix is what clears AA on a clinic's own canvas. */
|
|
1597
|
+
.sk-cf__service .sk-cf__service-chevron {
|
|
1598
|
+
color: var(--sk-flow-accent-ink, var(--sk-island-accent));
|
|
1599
|
+
}
|
|
1600
|
+
/* The hold rule, said ONCE for the whole priced list (PAT-853). It replaces the same
|
|
1601
|
+
12-word sentence repeated verbatim on every paid row, so it belongs to the list rather
|
|
1602
|
+
than to the copy below it: 1× gap above (against the list's own 1.5× between rows) and
|
|
1603
|
+
the same muted ink as the prices it footnotes. `.sk-flow` prefix is load-bearing — see
|
|
1604
|
+
the TRAP note on the `<p>` margin reset. */
|
|
1605
|
+
.sk-flow .sk-cf__services-note {
|
|
1606
|
+
margin-top: calc(var(--sk-space) * 1.5);
|
|
1607
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1608
|
+
font-size: var(--sk-font-size-sm);
|
|
1609
|
+
}
|
|
1610
|
+
/* An emergency affordance on a NON-emergency screen (the blocked exit). Absolute
|
|
1611
|
+
colours for the same reason `.sk-pf__emergency` uses them — a way to call an
|
|
1612
|
+
ambulance must not change appearance with the clinic's brand. */
|
|
1613
|
+
.sk-cf__emergency {
|
|
1614
|
+
display: inline-flex;
|
|
1615
|
+
align-items: center;
|
|
1616
|
+
min-height: 2.75rem;
|
|
1617
|
+
color: color-mix(in srgb, #ef4444 70%, currentColor);
|
|
1618
|
+
font-weight: 700;
|
|
1619
|
+
text-decoration: underline;
|
|
1620
|
+
text-underline-offset: 3px;
|
|
1621
|
+
}
|
|
1622
|
+
.sk-cf__emergency:focus-visible {
|
|
1623
|
+
outline: 2px solid #ef4444;
|
|
1624
|
+
outline-offset: 2px;
|
|
1625
|
+
}
|
|
1626
|
+
.sk-cf__row {
|
|
1627
|
+
display: grid;
|
|
1628
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1629
|
+
gap: calc(var(--sk-space) * 2);
|
|
1630
|
+
}
|
|
1631
|
+
.sk-cf__field {
|
|
1632
|
+
margin-block: calc(var(--sk-space) * 1.5);
|
|
1633
|
+
}
|
|
1634
|
+
.sk-cf__label {
|
|
1635
|
+
display: block;
|
|
1636
|
+
margin-bottom: calc(var(--sk-space) * 0.75);
|
|
1637
|
+
font-size: var(--sk-font-size-sm);
|
|
1638
|
+
font-weight: 500;
|
|
1639
|
+
}
|
|
1640
|
+
.sk-flow .sk-cf__help {
|
|
1641
|
+
margin-top: calc(var(--sk-space) * 0.5);
|
|
1642
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1643
|
+
font-size: 0.75rem;
|
|
1644
|
+
}
|
|
1645
|
+
.sk-cf__expiry {
|
|
1646
|
+
display: flex;
|
|
1647
|
+
gap: calc(var(--sk-space));
|
|
1648
|
+
border: 0;
|
|
1649
|
+
padding: 0;
|
|
1650
|
+
margin-inline: 0;
|
|
1651
|
+
min-inline-size: 0;
|
|
1652
|
+
}
|
|
1653
|
+
.sk-cf__expiry > legend {
|
|
1654
|
+
padding-inline: 0;
|
|
1655
|
+
}
|
|
1656
|
+
.sk-cf__actions {
|
|
1657
|
+
display: grid;
|
|
1658
|
+
gap: calc(var(--sk-space));
|
|
1659
|
+
}
|
|
1660
|
+
.sk-cf__link {
|
|
1661
|
+
background: none;
|
|
1662
|
+
border: 0;
|
|
1663
|
+
padding: 0;
|
|
1664
|
+
color: var(--sk-flow-accent-ink, var(--sk-island-primary));
|
|
1665
|
+
font: inherit;
|
|
1666
|
+
text-decoration: underline;
|
|
1667
|
+
text-underline-offset: 3px;
|
|
1668
|
+
cursor: pointer;
|
|
1669
|
+
}
|
|
1670
|
+
.sk-flow .sk-cf__aside,
|
|
1671
|
+
/* Also reading copy — and on the exit screens it is the ONLY copy, telling a blocked
|
|
1672
|
+
patient what to do next. It was the smallest text on the screen. */
|
|
1673
|
+
.sk-flow .sk-cf__body,
|
|
1674
|
+
.sk-flow .sk-cf__notice {
|
|
1675
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
1676
|
+
}
|
|
1677
|
+
/* The disclosure is a <button>, so no reset ever touched it — it simply never had any
|
|
1678
|
+
spacing, and sat welded to the last consent bullet above and the "I agree" card below,
|
|
1679
|
+
reading as a fifth bullet on the screen that captures binding consent. */
|
|
1680
|
+
.sk-cf__consent-summary + .sk-cf__link {
|
|
1681
|
+
display: block;
|
|
1682
|
+
margin-block: calc(var(--sk-space) * 2);
|
|
1683
|
+
}
|
|
1684
|
+
.sk-cf__consent-summary li {
|
|
1685
|
+
padding-left: calc(var(--sk-space) * 2.5);
|
|
1686
|
+
position: relative;
|
|
1687
|
+
}
|
|
1688
|
+
.sk-cf__consent-summary li::before {
|
|
1689
|
+
content: '';
|
|
1690
|
+
position: absolute;
|
|
1691
|
+
left: 0;
|
|
1692
|
+
top: 0.55em;
|
|
1693
|
+
width: 6px;
|
|
1694
|
+
height: 6px;
|
|
1695
|
+
border-radius: 999px;
|
|
1696
|
+
background: color-mix(in srgb, currentColor 55%, transparent);
|
|
1697
|
+
}
|
|
1698
|
+
.sk-cf__consent-full {
|
|
1699
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
1700
|
+
display: grid;
|
|
1701
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1702
|
+
font-size: var(--sk-font-size-sm);
|
|
1703
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1704
|
+
}
|
|
1705
|
+
.sk-cf__consent-form {
|
|
1706
|
+
margin-top: calc(var(--sk-space) * 3);
|
|
1707
|
+
/* The tick and the signature are one act. `.sk-cf__field`'s own 12px margin left them
|
|
1708
|
+
as two loose rows under the now-taller consent card. */
|
|
1709
|
+
display: grid;
|
|
1710
|
+
gap: calc(var(--sk-space) * 2.5);
|
|
1711
|
+
}
|
|
1712
|
+
.sk-cf__consent-form .sk-cf__field {
|
|
1713
|
+
margin-block: 0;
|
|
1714
|
+
}
|
|
1715
|
+
/* The one control on this island that legally BINDS the patient, and it was the only
|
|
1716
|
+
browser-default thing on the screen: a ~13px UA checkbox with a 14px label — smaller
|
|
1717
|
+
than the bullets it agrees to — inside a ~20px hit target. Same authored card recipe
|
|
1718
|
+
as `.sk-q__option`, so the whole 44px row is the target (WCAG 2.2 SC 2.5.8). */
|
|
1719
|
+
.sk-cf__check {
|
|
1720
|
+
display: flex;
|
|
1721
|
+
align-items: center;
|
|
1722
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1723
|
+
min-height: 2.75rem;
|
|
1724
|
+
padding: calc(var(--sk-space) * 1.5) calc(var(--sk-space) * 2);
|
|
1725
|
+
border: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 35%, transparent));
|
|
1726
|
+
border-radius: var(--sk-island-radius);
|
|
1727
|
+
font-size: var(--sk-font-size-base);
|
|
1728
|
+
cursor: pointer;
|
|
1729
|
+
}
|
|
1730
|
+
/* The box itself is authored up with `.sk-q__option`'s radio — one recipe for both, so the
|
|
1731
|
+
island cannot ship two "you ticked this" idioms. */
|
|
1732
|
+
/* The same selected-state language as `.sk-q__option:has(input:checked)` — two "you
|
|
1733
|
+
picked this" idioms on one island is how an authored surface starts to look assembled. */
|
|
1734
|
+
.sk-cf__check:has(input:checked) {
|
|
1735
|
+
border-color: var(--sk-island-primary);
|
|
1736
|
+
background: color-mix(in srgb, var(--sk-island-primary) 8%, transparent);
|
|
1737
|
+
}
|
|
1738
|
+
/* The card is the control now, so the ring belongs on the card, not on a 20px box
|
|
1739
|
+
floating inside it. */
|
|
1740
|
+
.sk-cf__check:has(input:focus-visible) {
|
|
1741
|
+
outline: 2px solid var(--sk-island-accent);
|
|
1742
|
+
outline-offset: 2px;
|
|
1743
|
+
}
|
|
1744
|
+
.sk-cf__check input:focus-visible {
|
|
1745
|
+
outline: none;
|
|
1746
|
+
}
|
|
1747
|
+
.sk-cf__signature {
|
|
1748
|
+
font-style: italic;
|
|
1749
|
+
}
|
|
1750
|
+
.sk-cf__cancel {
|
|
1751
|
+
margin-top: calc(var(--sk-space) * 3);
|
|
1752
|
+
padding-top: calc(var(--sk-space) * 3);
|
|
1753
|
+
border-top: 1px solid var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 25%, transparent));
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/* ── preflight (the safety check) ───────────────────────────────────────────*/
|
|
1757
|
+
/* THE DENSITY THAT REPLACED THE PINNED BAR (PAT-867).
|
|
1758
|
+
This step's job is to be READ, and it was losing that job twice over: five cards at
|
|
1759
|
+
card-comfortable padding ran ~180px past the fold on a laptop, so the two buttons that
|
|
1760
|
+
answer them were off screen — and the fix for that (a sticky footer) put a translucent
|
|
1761
|
+
bar over the step's own 000 link. The step is the thing that had to give. Every number
|
|
1762
|
+
below is tighter than the kit's default card rhythm on purpose; the READING measures
|
|
1763
|
+
(`line-height`, font size) are untouched, because compressing those would trade a
|
|
1764
|
+
layout problem for a comprehension one on the only screen in the funnel that decides
|
|
1765
|
+
whether an online consult is safe. */
|
|
1766
|
+
.sk-pf__statements {
|
|
1767
|
+
list-style: none;
|
|
1768
|
+
padding: 0;
|
|
1769
|
+
margin: 0;
|
|
1770
|
+
display: grid;
|
|
1771
|
+
gap: var(--sk-space);
|
|
1772
|
+
}
|
|
1773
|
+
/* Each statement is a card, not a bullet: it is a thing being ATTESTED to, and it
|
|
1774
|
+
should carry the visual weight of one. */
|
|
1775
|
+
.sk-pf__statement {
|
|
1776
|
+
display: flex;
|
|
1777
|
+
align-items: flex-start;
|
|
1778
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1779
|
+
padding: calc(var(--sk-space) * 1.25) calc(var(--sk-space) * 1.5);
|
|
1780
|
+
border: 1px solid var(--sk-flow-line);
|
|
1781
|
+
border-radius: var(--sk-island-radius);
|
|
1782
|
+
background: var(--sk-flow-raised);
|
|
1783
|
+
}
|
|
1784
|
+
.sk-pf__num {
|
|
1785
|
+
flex: none;
|
|
1786
|
+
display: grid;
|
|
1787
|
+
place-items: center;
|
|
1788
|
+
width: 1.5rem;
|
|
1789
|
+
height: 1.5rem;
|
|
1790
|
+
margin-top: 0.1em;
|
|
1791
|
+
border-radius: 999px;
|
|
1792
|
+
background: color-mix(in srgb, var(--sk-island-primary) 22%, transparent);
|
|
1793
|
+
color: inherit;
|
|
1794
|
+
font-size: 0.75rem;
|
|
1795
|
+
font-weight: 700;
|
|
1796
|
+
font-variant-numeric: tabular-nums;
|
|
1797
|
+
}
|
|
1798
|
+
.sk-pf__statement-body {
|
|
1799
|
+
min-width: 0;
|
|
1800
|
+
}
|
|
1801
|
+
.sk-pf__statement-title {
|
|
1802
|
+
font-weight: 600;
|
|
1803
|
+
line-height: 1.35;
|
|
1804
|
+
}
|
|
1805
|
+
.sk-flow .sk-pf__statement-detail {
|
|
1806
|
+
margin-top: calc(var(--sk-space) * 0.375);
|
|
1807
|
+
color: var(--sk-flow-muted);
|
|
1808
|
+
font-size: var(--sk-font-size-sm);
|
|
1809
|
+
line-height: 1.5;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
.sk-pf__declines {
|
|
1813
|
+
list-style: none;
|
|
1814
|
+
padding: 0;
|
|
1815
|
+
margin: 0;
|
|
1816
|
+
display: grid;
|
|
1817
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1818
|
+
}
|
|
1819
|
+
.sk-pf__decline {
|
|
1820
|
+
display: flex;
|
|
1821
|
+
align-items: center;
|
|
1822
|
+
justify-content: space-between;
|
|
1823
|
+
gap: calc(var(--sk-space) * 2);
|
|
1824
|
+
width: 100%;
|
|
1825
|
+
padding: calc(var(--sk-space) * 2) calc(var(--sk-space) * 2.5);
|
|
1826
|
+
border: 1px solid var(--sk-flow-line);
|
|
1827
|
+
border-radius: var(--sk-island-radius);
|
|
1828
|
+
background: var(--sk-flow-raised);
|
|
1829
|
+
color: inherit;
|
|
1830
|
+
font: inherit;
|
|
1831
|
+
font-weight: 600;
|
|
1832
|
+
text-align: left;
|
|
1833
|
+
cursor: pointer;
|
|
1834
|
+
}
|
|
1835
|
+
.sk-pf__decline:hover {
|
|
1836
|
+
border-color: var(--sk-island-primary);
|
|
1837
|
+
}
|
|
1838
|
+
.sk-pf__decline:focus-visible {
|
|
1839
|
+
outline: 2px solid var(--sk-island-accent);
|
|
1840
|
+
outline-offset: 2px;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/* The 000 escape hatch. Danger colours are ABSOLUTE, never derived: an emergency
|
|
1844
|
+
affordance must look the same on every clinic's canvas, and "reads as urgent" is
|
|
1845
|
+
not something to hand to a brand token. It is a link, not a button, because the
|
|
1846
|
+
only correct outcome is a phone call. */
|
|
1847
|
+
.sk-pf__emergency {
|
|
1848
|
+
display: flex;
|
|
1849
|
+
align-items: center;
|
|
1850
|
+
justify-content: center;
|
|
1851
|
+
/* 44px — the touch-target floor, matching `.sk-flow__btn`. The one control on this
|
|
1852
|
+
screen a patient may be reaching for while unwell must not be the smallest. */
|
|
1853
|
+
min-height: 2.75rem;
|
|
1854
|
+
margin-top: calc(var(--sk-space) * 1.5);
|
|
1855
|
+
padding-inline: calc(var(--sk-space) * 2);
|
|
1856
|
+
border: 1px solid color-mix(in srgb, #ef4444 55%, transparent);
|
|
1857
|
+
border-radius: var(--sk-island-radius);
|
|
1858
|
+
background: color-mix(in srgb, #ef4444 12%, transparent);
|
|
1859
|
+
color: inherit;
|
|
1860
|
+
font-size: var(--sk-font-size-sm);
|
|
1861
|
+
font-weight: 700;
|
|
1862
|
+
text-align: center;
|
|
1863
|
+
text-decoration: none;
|
|
1864
|
+
}
|
|
1865
|
+
.sk-pf__emergency:hover {
|
|
1866
|
+
background: color-mix(in srgb, #ef4444 20%, transparent);
|
|
1867
|
+
}
|
|
1868
|
+
.sk-pf__emergency:focus-visible {
|
|
1869
|
+
outline: 2px solid #ef4444;
|
|
1870
|
+
outline-offset: 2px;
|
|
1871
|
+
}
|
|
1872
|
+
/* The prerendered no-JS fallback: real content, always visible. Never opacity-0. */
|
|
1873
|
+
.sk-certificate-funnel__services,
|
|
1874
|
+
.sk-booking__types {
|
|
1875
|
+
list-style: none;
|
|
1876
|
+
padding: 0;
|
|
1877
|
+
margin: calc(var(--sk-space) * 2) 0;
|
|
1878
|
+
display: grid;
|
|
1879
|
+
gap: calc(var(--sk-space));
|
|
1880
|
+
font-size: var(--sk-font-size-sm);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
/* ── the escalation alert — full-bleed and unmissable by design ─────────────*/
|
|
1884
|
+
.sk-cf__escalate {
|
|
1885
|
+
position: fixed;
|
|
1886
|
+
inset: 0;
|
|
1887
|
+
z-index: 9999;
|
|
1888
|
+
display: flex;
|
|
1889
|
+
flex-direction: column;
|
|
1890
|
+
align-items: center;
|
|
1891
|
+
justify-content: center;
|
|
1892
|
+
gap: calc(var(--sk-space) * 2);
|
|
1893
|
+
padding: calc(var(--sk-space) * 6);
|
|
1894
|
+
overflow-y: auto;
|
|
1895
|
+
/* Self-painted overlay: pair the ink with the surface (a dark site would
|
|
1896
|
+
otherwise inherit white onto white — unacceptable on a safety surface). */
|
|
1897
|
+
background: var(--sk-background, #fff);
|
|
1898
|
+
color: var(--sk-foreground, #1b2733);
|
|
1899
|
+
text-align: center;
|
|
1900
|
+
}
|
|
1901
|
+
/* This surface is a bare <div> OUTSIDE `.sk-flow`, so the UA heading/paragraph margins
|
|
1902
|
+
the funnel resets everywhere else survive here — the one screen where they must not.
|
|
1903
|
+
With a flat 16px gap plus an h2's 0.83em UA margin, "EMERGENCY" sat ~70px above the
|
|
1904
|
+
headline it labels, further from "Call 000" than "Call 000" was from the instruction,
|
|
1905
|
+
so three fragments of ONE instruction read as three unrelated things. Zero the UA
|
|
1906
|
+
margins, then author the rhythm: eyebrow tight to its heading, one step of air before
|
|
1907
|
+
the body. */
|
|
1908
|
+
.sk-cf__escalate > * {
|
|
1909
|
+
margin: 0;
|
|
1910
|
+
}
|
|
1911
|
+
.sk-cf__escalate-eyebrow {
|
|
1912
|
+
margin-bottom: calc(var(--sk-space) * -1);
|
|
1913
|
+
}
|
|
1914
|
+
.sk-cf__escalate-eyebrow {
|
|
1915
|
+
font-size: 0.6875rem;
|
|
1916
|
+
font-weight: 700;
|
|
1917
|
+
letter-spacing: 0.2em;
|
|
1918
|
+
text-transform: uppercase;
|
|
1919
|
+
color: #b3261e;
|
|
1920
|
+
}
|
|
1921
|
+
.sk-cf__escalate-heading {
|
|
1922
|
+
font-size: clamp(2.5rem, 8vw, 4rem);
|
|
1923
|
+
line-height: 1;
|
|
1924
|
+
}
|
|
1925
|
+
/* Same programmatic focus as the step heading, and this screen especially must never
|
|
1926
|
+
show a browser default: it was painting a flat UA-BLUE box around "Call 000" on the
|
|
1927
|
+
one canvas whose colours are absolute and authored on purpose. */
|
|
1928
|
+
.sk-cf__escalate-heading:focus {
|
|
1929
|
+
outline: none;
|
|
1930
|
+
}
|
|
1931
|
+
.sk-cf__escalate-body,
|
|
1932
|
+
.sk-cf__escalate-note {
|
|
1933
|
+
max-width: 28rem;
|
|
1934
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1935
|
+
}
|
|
1936
|
+
.sk-cf__escalate-cta {
|
|
1937
|
+
display: inline-flex;
|
|
1938
|
+
align-items: center;
|
|
1939
|
+
justify-content: center;
|
|
1940
|
+
min-height: 3.5rem;
|
|
1941
|
+
padding-inline: calc(var(--sk-space) * 5);
|
|
1942
|
+
border-radius: 999px;
|
|
1943
|
+
background: #b3261e;
|
|
1944
|
+
color: #fff;
|
|
1945
|
+
font-size: 1.25rem;
|
|
1946
|
+
font-weight: 700;
|
|
1947
|
+
text-decoration: none;
|
|
1948
|
+
}
|
|
1949
|
+
.sk-cf__escalate-alt {
|
|
1950
|
+
color: inherit;
|
|
1951
|
+
font-size: var(--sk-font-size-sm);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
/* ── payment ────────────────────────────────────────────────────────────────*/
|
|
1955
|
+
.sk-pay__form {
|
|
1956
|
+
display: grid;
|
|
1957
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1958
|
+
}
|
|
1959
|
+
.sk-pay__row {
|
|
1960
|
+
display: grid;
|
|
1961
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1962
|
+
gap: calc(var(--sk-space) * 1.5);
|
|
1963
|
+
}
|
|
1964
|
+
.sk-pay__label {
|
|
1965
|
+
display: block;
|
|
1966
|
+
margin-bottom: calc(var(--sk-space) * 0.75);
|
|
1967
|
+
font-size: var(--sk-font-size-sm);
|
|
1968
|
+
font-weight: 500;
|
|
1969
|
+
}
|
|
1970
|
+
.sk-pay__mock {
|
|
1971
|
+
padding: calc(var(--sk-space) * 2);
|
|
1972
|
+
border: 1px dashed var(--sk-flow-line, color-mix(in srgb, var(--sk-muted) 40%, transparent));
|
|
1973
|
+
border-radius: var(--sk-island-radius);
|
|
1974
|
+
font-size: var(--sk-font-size-sm);
|
|
1975
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1976
|
+
}
|
|
1977
|
+
/* The "who takes your card" line (PAT-853). No `margin-top`: it is a `.sk-pay__form`
|
|
1978
|
+
grid child and already sits on the field gap — adding one double-spaced it. Muted via
|
|
1979
|
+
`--sk-flow-muted` rather than the flat `--sk-muted` the status line uses, because this
|
|
1980
|
+
sits on the CLINIC's canvas and a fixed grey is unreadable on half of them (8.47:1 at
|
|
1981
|
+
14px on the dark canvas, measured). */
|
|
1982
|
+
/* The timezone qualifier on the slot day heading (PAT-853). Its own rule so it does not
|
|
1983
|
+
inherit the heading's 600 — the DATE is the heading, the zone is the fine print that
|
|
1984
|
+
stops a Perth patient reading "09:00" as their own 09:00. */
|
|
1985
|
+
.sk-slots__heading-tz {
|
|
1986
|
+
font-weight: 500;
|
|
1987
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1988
|
+
font-size: var(--sk-font-size-sm);
|
|
1989
|
+
}
|
|
1990
|
+
.sk-pay__secure {
|
|
1991
|
+
display: flex;
|
|
1992
|
+
align-items: flex-start;
|
|
1993
|
+
gap: calc(var(--sk-space) * 0.75);
|
|
1994
|
+
font-size: var(--sk-font-size-sm);
|
|
1995
|
+
line-height: 1.45;
|
|
1996
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
1997
|
+
}
|
|
1998
|
+
.sk-pay__secure-icon {
|
|
1999
|
+
flex: none;
|
|
2000
|
+
margin-top: 0.2em;
|
|
2001
|
+
}
|
|
2002
|
+
.sk-pay__status,
|
|
2003
|
+
.sk-pay__loading {
|
|
2004
|
+
margin-top: calc(var(--sk-space));
|
|
2005
|
+
font-size: 0.75rem;
|
|
2006
|
+
/* `--sk-muted` is a FIXED grey (#5b6b7a) — 3.14:1 on the PocketDoc canvas, i.e. the
|
|
2007
|
+
"Placing hold…" microcopy on the money screen was under AA. Derived like every other
|
|
2008
|
+
secondary ink in the flow so it tracks whatever canvas the island landed on. */
|
|
2009
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
2010
|
+
}
|
|
2011
|
+
.sk-pay__actions {
|
|
2012
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
2013
|
+
}
|
|
2014
|
+
.sk-turnstile {
|
|
2015
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
/* ── slots ──────────────────────────────────────────────────────────────────*/
|
|
2019
|
+
.sk-slots__days {
|
|
2020
|
+
display: flex;
|
|
2021
|
+
gap: calc(var(--sk-space));
|
|
2022
|
+
overflow-x: auto;
|
|
2023
|
+
padding-bottom: calc(var(--sk-space));
|
|
2024
|
+
}
|
|
2025
|
+
.sk-slots__day {
|
|
2026
|
+
flex-shrink: 0;
|
|
2027
|
+
padding: calc(var(--sk-space)) calc(var(--sk-space) * 2);
|
|
2028
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted) 35%, transparent);
|
|
2029
|
+
border-radius: 999px;
|
|
2030
|
+
background: none;
|
|
2031
|
+
/* See `.sk-booking__option` — without this the day chips render `buttontext` on the
|
|
2032
|
+
clinic's canvas, i.e. invisible on a dark site, on the screen where the patient
|
|
2033
|
+
picks their appointment. */
|
|
2034
|
+
color: inherit;
|
|
2035
|
+
font: inherit;
|
|
2036
|
+
font-size: var(--sk-font-size-sm);
|
|
2037
|
+
font-weight: 600;
|
|
2038
|
+
white-space: nowrap;
|
|
2039
|
+
cursor: pointer;
|
|
2040
|
+
}
|
|
2041
|
+
.sk-slots__day--active {
|
|
2042
|
+
border-color: var(--sk-island-primary);
|
|
2043
|
+
background: color-mix(in srgb, var(--sk-island-primary) 10%, transparent);
|
|
2044
|
+
}
|
|
2045
|
+
.sk-slots__heading {
|
|
2046
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
2047
|
+
font-size: var(--sk-font-size-sm);
|
|
2048
|
+
font-weight: 600;
|
|
2049
|
+
}
|
|
2050
|
+
.sk-slots__grid {
|
|
2051
|
+
margin-top: calc(var(--sk-space) * 1.5);
|
|
2052
|
+
display: grid;
|
|
2053
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2054
|
+
gap: calc(var(--sk-space));
|
|
2055
|
+
}
|
|
2056
|
+
.sk-slots__time {
|
|
2057
|
+
padding: calc(var(--sk-space) * 1.5);
|
|
2058
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted) 30%, transparent);
|
|
2059
|
+
border-radius: var(--sk-island-radius);
|
|
2060
|
+
background: none;
|
|
2061
|
+
/* Same as `.sk-slots__day`: every time label inherits from here. */
|
|
2062
|
+
color: inherit;
|
|
2063
|
+
font: inherit;
|
|
2064
|
+
cursor: pointer;
|
|
2065
|
+
}
|
|
2066
|
+
.sk-slots__time--selected {
|
|
2067
|
+
border-color: var(--sk-island-primary);
|
|
2068
|
+
background: color-mix(in srgb, var(--sk-island-primary) 12%, transparent);
|
|
2069
|
+
}
|
|
2070
|
+
/* An advisory hold: dimmed as a hint, still clickable — the booking call decides. */
|
|
2071
|
+
.sk-slots__time--held {
|
|
2072
|
+
opacity: 0.5;
|
|
2073
|
+
}
|
|
2074
|
+
.sk-slots__time-label {
|
|
2075
|
+
display: block;
|
|
2076
|
+
font-weight: 600;
|
|
2077
|
+
font-variant-numeric: tabular-nums;
|
|
2078
|
+
}
|
|
2079
|
+
.sk-slots__time-note {
|
|
2080
|
+
display: block;
|
|
2081
|
+
margin-top: 2px;
|
|
2082
|
+
font-size: 0.6875rem;
|
|
2083
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
2084
|
+
}
|
|
2085
|
+
.sk-slots__confirm {
|
|
2086
|
+
/* Sticky at EVERY width: a long diary (10+ times = 4+ grid rows) pushes this CTA
|
|
2087
|
+
below the fold on a 900px-tall desktop viewport too. What is width-conditional is
|
|
2088
|
+
the SCRIM, below — at 1440 the whole step fits, the bar is never stuck, and the
|
|
2089
|
+
veil painted a square-cornered slab across the column with an 8px halo round the
|
|
2090
|
+
button on the last screen before the patient commits a time and a card hold. When
|
|
2091
|
+
it IS stuck at desktop width the full-width opaque `.sk-flow__btn--primary` fill
|
|
2092
|
+
does the occluding on its own. */
|
|
2093
|
+
position: sticky;
|
|
2094
|
+
bottom: 0;
|
|
2095
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
2096
|
+
padding-block: calc(var(--sk-space) * 1.5);
|
|
2097
|
+
color: inherit;
|
|
2098
|
+
}
|
|
2099
|
+
/* Below the rail breakpoint (`@media (min-width: 900px)`, line 868) the rail is stacked
|
|
2100
|
+
under the step, so the bar owns the full column and is a genuine bottom bar: it bleeds
|
|
2101
|
+
to the flow's own 1.5rem inset (`.sk-flow { padding-inline: calc(var(--sk-space) * 3) }`,
|
|
2102
|
+
line ~628 — `.sk-flow__body` adds none, so this lands exactly on the flow's border box)
|
|
2103
|
+
and carries a hairline so its top edge reads as an edge rather than a seam. NOT above
|
|
2104
|
+
900px: the flow is railed there (`minmax(0, 1fr) 17rem` + a 2rem gap), so the same bleed
|
|
2105
|
+
would poke into the gutter and stop ~8px short of the rail's divider.
|
|
2106
|
+
The veil is still derived from `currentColor`, never from `--sk-background`: the island
|
|
2107
|
+
cannot know the clinic's canvas (PAT-834/838). */
|
|
2108
|
+
@media (max-width: 899.98px) {
|
|
2109
|
+
.sk-slots__confirm {
|
|
2110
|
+
margin-inline: calc(var(--sk-space) * -3);
|
|
2111
|
+
padding-inline: calc(var(--sk-space) * 3);
|
|
2112
|
+
border-top: 1px solid
|
|
2113
|
+
var(--sk-flow-line, color-mix(in srgb, currentColor 24%, transparent));
|
|
2114
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
2115
|
+
backdrop-filter: blur(12px);
|
|
2116
|
+
-webkit-backdrop-filter: blur(12px);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
.sk-slots--empty {
|
|
2120
|
+
padding: calc(var(--sk-space) * 4);
|
|
2121
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted) 25%, transparent);
|
|
2122
|
+
border-radius: var(--sk-island-radius);
|
|
2123
|
+
text-align: center;
|
|
2124
|
+
}
|
|
2125
|
+
/* The escapes on an empty diary (PAT-853). Stacked, not a row: on a 390px screen a row
|
|
2126
|
+
of three would each be too narrow to read, and they are not equal-weight choices. */
|
|
2127
|
+
.sk-slots__empty-actions {
|
|
2128
|
+
display: grid;
|
|
2129
|
+
gap: calc(var(--sk-space));
|
|
2130
|
+
margin-top: calc(var(--sk-space) * 2);
|
|
2131
|
+
text-align: left;
|
|
2132
|
+
}
|
|
2133
|
+
.sk-slots__empty-note {
|
|
2134
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
2135
|
+
font-size: var(--sk-font-size-sm);
|
|
2136
|
+
}
|
|
2137
|
+
.sk-slots__empty-title {
|
|
2138
|
+
font-weight: 600;
|
|
2139
|
+
}
|
|
2140
|
+
.sk-slots__empty-body {
|
|
2141
|
+
margin-top: calc(var(--sk-space));
|
|
2142
|
+
color: var(--sk-muted);
|
|
2143
|
+
font-size: var(--sk-font-size-sm);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
@media (max-width: 560px) {
|
|
2147
|
+
.sk-identity__grid,
|
|
2148
|
+
.sk-cf__row,
|
|
2149
|
+
.sk-pay__row {
|
|
2150
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2151
|
+
}
|
|
2152
|
+
.sk-identity__field--wide {
|
|
2153
|
+
grid-column: auto;
|
|
2154
|
+
}
|
|
2155
|
+
/* Stacked, the row's 16px row-gap ADDS to the 12px `margin-block` each `.sk-cf__field`
|
|
2156
|
+
already carries (grid item margins never collapse), so the Expiry group sat 40px below
|
|
2157
|
+
the Reference number group against 24px everywhere else in the form. One column needs
|
|
2158
|
+
no row-gap — the fields' own margins are the rhythm. Scoped to `.sk-cf__row`, not the
|
|
2159
|
+
group above: `.sk-identity__grid` / `.sk-pay__row` children space themselves differently. */
|
|
2160
|
+
.sk-cf__row {
|
|
2161
|
+
row-gap: 0;
|
|
2162
|
+
}
|
|
2163
|
+
/* Stack the label above the value: at phone width a 104px label column leaves too
|
|
2164
|
+
little for an address line, and a side-by-side row is what forced the overflow. */
|
|
2165
|
+
.sk-identity__confirm-row {
|
|
2166
|
+
display: grid;
|
|
2167
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2168
|
+
gap: 2px;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
|
|
2173
|
+
/* ── portal controls, shared by portal-panel + portal-account (PAT-783) ───── */
|
|
2174
|
+
/* ONE label / input / button style across both portal islands. They render the
|
|
2175
|
+
* same kinds of control on the same origin, and two sets of styles is how a
|
|
2176
|
+
* patient starts to feel the seam between "the clinic's site" and "the portal" —
|
|
2177
|
+
* which is the exact seam one-origin exists to remove. */
|
|
2178
|
+
.sk-portal-label {
|
|
2179
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2180
|
+
font-weight: 600;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
.sk-portal-input {
|
|
2184
|
+
font: inherit;
|
|
2185
|
+
width: 100%;
|
|
2186
|
+
padding: var(--sk-space, 0.5rem);
|
|
2187
|
+
border: 1px solid color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 20%, transparent);
|
|
2188
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2189
|
+
background: var(--sk-background, #ffffff);
|
|
2190
|
+
color: var(--sk-foreground, #1b2733);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
.sk-portal-button {
|
|
2194
|
+
display: inline-block;
|
|
2195
|
+
font: inherit;
|
|
2196
|
+
font-weight: 600;
|
|
2197
|
+
text-align: center;
|
|
2198
|
+
text-decoration: none;
|
|
2199
|
+
cursor: pointer;
|
|
2200
|
+
padding: var(--sk-space, 0.5rem) calc(var(--sk-space, 0.5rem) * 2);
|
|
2201
|
+
border: 1px solid transparent;
|
|
2202
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2203
|
+
/* `--sk-brand-foreground` is DERIVED by contrast (`resolveTheme`), never taken
|
|
2204
|
+
* from the clinic — see contrast.ts. */
|
|
2205
|
+
background: var(--sk-brand, #1f6feb);
|
|
2206
|
+
color: var(--sk-brand-foreground, #ffffff);
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
.sk-portal-button--ghost {
|
|
2210
|
+
background: transparent;
|
|
2211
|
+
color: var(--sk-brand, #1f6feb);
|
|
2212
|
+
border-color: color-mix(in srgb, var(--sk-brand, #1f6feb) 40%, transparent);
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
.sk-portal-button--danger {
|
|
2216
|
+
background: var(--sk-danger, #b3261e);
|
|
2217
|
+
color: #ffffff;
|
|
2218
|
+
border-color: transparent;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
.sk-portal-button[disabled] {
|
|
2222
|
+
opacity: 0.6;
|
|
2223
|
+
cursor: default;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
.sk-portal-link {
|
|
2227
|
+
color: var(--sk-brand, #1f6feb);
|
|
2228
|
+
font-weight: 600;
|
|
2229
|
+
text-decoration: underline;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
.sk-portal-error {
|
|
2233
|
+
margin: var(--sk-space, 0.5rem) 0;
|
|
2234
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2235
|
+
color: var(--sk-danger, #b3261e);
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
/* The sign-in ramp. One field + one button — anything more belongs in the portal. */
|
|
2239
|
+
.sk-portal-ramp {
|
|
2240
|
+
display: flex;
|
|
2241
|
+
flex-direction: column;
|
|
2242
|
+
gap: var(--sk-space, 0.5rem);
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
.sk-portal-ramp__intro,
|
|
2246
|
+
.sk-portal-ramp__sent {
|
|
2247
|
+
margin: 0;
|
|
2248
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2249
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/* ── portal-panel (PAT-767) ──────────────────────────────────────────────── */
|
|
2253
|
+
.sk-portal-panel {
|
|
2254
|
+
background: var(--sk-neutral, #f4f6f8);
|
|
2255
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2256
|
+
padding: calc(var(--sk-space, 0.5rem) * 3);
|
|
2257
|
+
font-family: var(--sk-font, system-ui, sans-serif);
|
|
2258
|
+
color: var(--sk-neutral-foreground, #1b2733);
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
.sk-portal-panel__heading {
|
|
2262
|
+
font-family: var(--sk-font-heading, var(--sk-font, system-ui, sans-serif));
|
|
2263
|
+
font-size: var(--sk-font-size-lg, 1.25rem);
|
|
2264
|
+
line-height: 1.3;
|
|
2265
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
.sk-portal-panel__placeholder,
|
|
2269
|
+
.sk-portal-panel__empty {
|
|
2270
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2271
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2272
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
.sk-portal-panel__link {
|
|
2276
|
+
color: var(--sk-brand, #1f6feb);
|
|
2277
|
+
font-weight: 600;
|
|
2278
|
+
text-decoration: underline;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
/* The summary list: metadata only (title · date · status). Never bytes, never
|
|
2282
|
+
* clinical narrative — the panel is a teaser that links into the real portal. */
|
|
2283
|
+
.sk-portal-panel__list {
|
|
2284
|
+
list-style: none;
|
|
2285
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2286
|
+
padding: 0;
|
|
2287
|
+
display: flex;
|
|
2288
|
+
flex-direction: column;
|
|
2289
|
+
gap: var(--sk-space, 0.5rem);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.sk-portal-panel__item {
|
|
2293
|
+
display: flex;
|
|
2294
|
+
flex-wrap: wrap;
|
|
2295
|
+
align-items: baseline;
|
|
2296
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.75);
|
|
2297
|
+
padding-bottom: var(--sk-space, 0.5rem);
|
|
2298
|
+
border-bottom: 1px solid
|
|
2299
|
+
color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 12%, transparent);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
.sk-portal-panel__item:last-child {
|
|
2303
|
+
border-bottom: 0;
|
|
2304
|
+
padding-bottom: 0;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
.sk-portal-panel__item-title {
|
|
2308
|
+
font-weight: 600;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
.sk-portal-panel__item-meta {
|
|
2312
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2313
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
.sk-portal-panel__item-status {
|
|
2317
|
+
margin-left: auto;
|
|
2318
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2319
|
+
text-transform: capitalize;
|
|
2320
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
/* ── portal-account (PAT-783) ────────────────────────────────────────────── */
|
|
2324
|
+
/* The FULL account surface, living inside the clinic's own page. Calm and dense:
|
|
2325
|
+
* this is a patient managing their care, not a marketing band, so it reads as a
|
|
2326
|
+
* quiet card rather than competing with the page around it. Controls come from
|
|
2327
|
+
* the shared `.sk-portal-*` primitives above. */
|
|
2328
|
+
.sk-portal-account {
|
|
2329
|
+
background: var(--sk-neutral, #f4f6f8);
|
|
2330
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2331
|
+
padding: calc(var(--sk-space, 0.5rem) * 3);
|
|
2332
|
+
font-family: var(--sk-font, system-ui, sans-serif);
|
|
2333
|
+
color: var(--sk-neutral-foreground, #1b2733);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
.sk-portal-account__heading {
|
|
2337
|
+
font-family: var(--sk-font-heading, var(--sk-font, system-ui, sans-serif));
|
|
2338
|
+
font-size: var(--sk-font-size-lg, 1.25rem);
|
|
2339
|
+
line-height: 1.3;
|
|
2340
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
.sk-portal-account__placeholder,
|
|
2344
|
+
.sk-portal-account__empty,
|
|
2345
|
+
.sk-portal-account__note,
|
|
2346
|
+
.sk-portal-account__hint {
|
|
2347
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2348
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2349
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
/* ── the /account/book launcher (PAT-899) ───────────────────────────────────
|
|
2353
|
+
* Rows that LEAVE for the funnel, so they are anchors and styled as such. They live in
|
|
2354
|
+
* the light account panel and must not borrow the funnel's dark canvas — the two
|
|
2355
|
+
* surfaces are deliberately different places, which is the whole reason the funnel was
|
|
2356
|
+
* not mounted here. Colours come from the same `--sk-*` slots as the rest of the panel,
|
|
2357
|
+
* so a clinic's theme reaches them without a second vocabulary. */
|
|
2358
|
+
.sk-portal-launcher {
|
|
2359
|
+
list-style: none;
|
|
2360
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2361
|
+
padding: 0;
|
|
2362
|
+
display: flex;
|
|
2363
|
+
flex-direction: column;
|
|
2364
|
+
gap: var(--sk-space, 0.5rem);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
.sk-portal-launcher__service {
|
|
2368
|
+
display: flex;
|
|
2369
|
+
align-items: center;
|
|
2370
|
+
justify-content: space-between;
|
|
2371
|
+
gap: calc(var(--sk-space, 0.5rem) * 2);
|
|
2372
|
+
width: 100%;
|
|
2373
|
+
padding: calc(var(--sk-space, 0.5rem) * 2);
|
|
2374
|
+
border: 1px solid var(--sk-border, #dce3ea);
|
|
2375
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2376
|
+
background: var(--sk-surface, #fff);
|
|
2377
|
+
color: inherit;
|
|
2378
|
+
text-decoration: none;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
.sk-portal-launcher__service:hover,
|
|
2382
|
+
.sk-portal-launcher__service:focus-visible {
|
|
2383
|
+
border-color: var(--sk-primary, #0f766e);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
.sk-portal-launcher__body {
|
|
2387
|
+
display: flex;
|
|
2388
|
+
flex-direction: column;
|
|
2389
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.5);
|
|
2390
|
+
/* The row can hold a long service name; let it wrap rather than push the chevron out. */
|
|
2391
|
+
min-width: 0;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
.sk-portal-launcher__label {
|
|
2395
|
+
font-weight: 600;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
.sk-portal-launcher__fee {
|
|
2399
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2400
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
.sk-portal-launcher__chevron {
|
|
2404
|
+
flex: none;
|
|
2405
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
/* The "already signed in on the last page" placeholder (PAT-797) — inert bars in the
|
|
2409
|
+
* shape of the rows about to land, shown INSTEAD of "Checking your sign-in…" when this
|
|
2410
|
+
* tab already knows. Deliberately UNANIMATED: a pulse would need a reduced-motion guard
|
|
2411
|
+
* to earn its keep, and it buys nothing here — this is on screen for a few hundred ms.
|
|
2412
|
+
* This is NOT the no-JS fallback below; that one is real content and must stay real. */
|
|
2413
|
+
.sk-portal-account__skeleton {
|
|
2414
|
+
display: flex;
|
|
2415
|
+
flex-direction: column;
|
|
2416
|
+
gap: var(--sk-space, 0.5rem);
|
|
2417
|
+
margin-bottom: calc(var(--sk-space, 0.5rem) * 2);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
.sk-portal-account__skeleton-row {
|
|
2421
|
+
height: 2.75rem;
|
|
2422
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2423
|
+
background: color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 7%, transparent);
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
/* The build-time no-JS fallback: REAL visible content, never a hidden skeleton. */
|
|
2427
|
+
.sk-portal-account__signin-card {
|
|
2428
|
+
display: flex;
|
|
2429
|
+
flex-direction: column;
|
|
2430
|
+
align-items: flex-start;
|
|
2431
|
+
gap: var(--sk-space, 0.5rem);
|
|
2432
|
+
padding: calc(var(--sk-space, 0.5rem) * 2);
|
|
2433
|
+
border: 1px solid color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 12%, transparent);
|
|
2434
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2435
|
+
background: var(--sk-background, #ffffff);
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
.sk-portal-account__signin-card .sk-portal-account__placeholder {
|
|
2439
|
+
margin: 0;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
/* Who you are + the way out, on one line. Always visible while signed in: a
|
|
2443
|
+
* patient looking at their own records on a clinic's marketing page must never
|
|
2444
|
+
* have to wonder whose session they are in. */
|
|
2445
|
+
.sk-portal-account__header {
|
|
2446
|
+
display: flex;
|
|
2447
|
+
flex-wrap: wrap;
|
|
2448
|
+
align-items: center;
|
|
2449
|
+
justify-content: space-between;
|
|
2450
|
+
gap: var(--sk-space, 0.5rem);
|
|
2451
|
+
margin-bottom: calc(var(--sk-space, 0.5rem) * 2);
|
|
2452
|
+
padding-bottom: var(--sk-space, 0.5rem);
|
|
2453
|
+
border-bottom: 1px solid
|
|
2454
|
+
color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 12%, transparent);
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
.sk-portal-account__who {
|
|
2458
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2459
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
.sk-portal-account__group {
|
|
2463
|
+
margin-bottom: calc(var(--sk-space, 0.5rem) * 3);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
.sk-portal-account__group-title {
|
|
2467
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2468
|
+
font-weight: 700;
|
|
2469
|
+
text-transform: uppercase;
|
|
2470
|
+
letter-spacing: 0.04em;
|
|
2471
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2472
|
+
margin: 0 0 var(--sk-space, 0.5rem);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
.sk-portal-account__list {
|
|
2476
|
+
list-style: none;
|
|
2477
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2478
|
+
padding: 0;
|
|
2479
|
+
display: flex;
|
|
2480
|
+
flex-direction: column;
|
|
2481
|
+
gap: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
.sk-portal-account__item {
|
|
2485
|
+
display: flex;
|
|
2486
|
+
flex-direction: column;
|
|
2487
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.5);
|
|
2488
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
2489
|
+
border: 1px solid color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 12%, transparent);
|
|
2490
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2491
|
+
background: var(--sk-background, #ffffff);
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
.sk-portal-account__item-head {
|
|
2495
|
+
display: flex;
|
|
2496
|
+
flex-wrap: wrap;
|
|
2497
|
+
align-items: baseline;
|
|
2498
|
+
gap: var(--sk-space, 0.5rem);
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
/* An order row is a disclosure control, so it is a real <button>: keyboard and
|
|
2502
|
+
* screen-reader reachable, styled back down to look like the row it is. */
|
|
2503
|
+
.sk-portal-account__item-toggle {
|
|
2504
|
+
display: flex;
|
|
2505
|
+
flex-wrap: wrap;
|
|
2506
|
+
align-items: baseline;
|
|
2507
|
+
gap: var(--sk-space, 0.5rem);
|
|
2508
|
+
width: 100%;
|
|
2509
|
+
font: inherit;
|
|
2510
|
+
text-align: left;
|
|
2511
|
+
cursor: pointer;
|
|
2512
|
+
padding: 0;
|
|
2513
|
+
border: 0;
|
|
2514
|
+
background: none;
|
|
2515
|
+
color: inherit;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
.sk-portal-account__item-title {
|
|
2519
|
+
font-weight: 600;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
.sk-portal-account__item-meta {
|
|
2523
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2524
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
.sk-portal-account__status {
|
|
2528
|
+
margin-left: auto;
|
|
2529
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2530
|
+
text-transform: capitalize;
|
|
2531
|
+
padding: 0 calc(var(--sk-space, 0.5rem) * 0.75);
|
|
2532
|
+
border-radius: 999px;
|
|
2533
|
+
background: color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 8%, transparent);
|
|
2534
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
.sk-portal-account__actions {
|
|
2538
|
+
display: flex;
|
|
2539
|
+
flex-wrap: wrap;
|
|
2540
|
+
gap: var(--sk-space, 0.5rem);
|
|
2541
|
+
margin-top: var(--sk-space, 0.5rem);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
.sk-portal-account__actions .sk-portal-button {
|
|
2545
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2546
|
+
padding: calc(var(--sk-space, 0.5rem) * 0.5) var(--sk-space, 0.5rem);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/* The inline cancel confirm. Expands in place rather than opening a dialog: the
|
|
2550
|
+
* patient is on the clinic's own page and a modal over a marketing layout is a
|
|
2551
|
+
* shape we cannot control. */
|
|
2552
|
+
.sk-portal-account__confirm {
|
|
2553
|
+
display: flex;
|
|
2554
|
+
flex-direction: column;
|
|
2555
|
+
gap: var(--sk-space, 0.5rem);
|
|
2556
|
+
margin-top: var(--sk-space, 0.5rem);
|
|
2557
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
2558
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2559
|
+
border: 1px solid color-mix(in srgb, var(--sk-danger, #b3261e) 30%, transparent);
|
|
2560
|
+
background: color-mix(in srgb, var(--sk-danger, #b3261e) 5%, transparent);
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
/* The SAME expanding block, without the danger tint: booking a time and moving a
|
|
2564
|
+
* time are ordinary things a patient does, and a red-edged card would read as a
|
|
2565
|
+
* warning about a decision that has nothing to warn about (PAT-789). */
|
|
2566
|
+
.sk-portal-account__confirm--neutral {
|
|
2567
|
+
border-color: color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 15%, transparent);
|
|
2568
|
+
background: var(--sk-neutral, #f4f6f8);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
.sk-portal-account__confirm-title {
|
|
2572
|
+
margin: 0;
|
|
2573
|
+
font-weight: 600;
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
/* Back + what you are booking, on one line above the slot grid. The step label is
|
|
2577
|
+
* the answer to "a time for what?", which is otherwise off the top of the screen
|
|
2578
|
+
* once the grid scrolls. */
|
|
2579
|
+
.sk-portal-account__step-head {
|
|
2580
|
+
display: flex;
|
|
2581
|
+
flex-wrap: wrap;
|
|
2582
|
+
align-items: center;
|
|
2583
|
+
gap: var(--sk-space, 0.5rem);
|
|
2584
|
+
margin-bottom: var(--sk-space, 0.5rem);
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
/* "No times available" and the like: a bordered card, not a bare grey line — an
|
|
2588
|
+
* empty grid is a dead end and should look like a considered answer. */
|
|
2589
|
+
.sk-portal-account__note-card {
|
|
2590
|
+
padding: calc(var(--sk-space, 0.5rem) * 2);
|
|
2591
|
+
border: 1px solid color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 12%, transparent);
|
|
2592
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2593
|
+
background: var(--sk-background, #ffffff);
|
|
2594
|
+
text-align: center;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
.sk-portal-account__note-card .sk-portal-account__empty {
|
|
2598
|
+
margin: var(--sk-space, 0.5rem) 0 0;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
/* The booked confirmation. It carries `role="status"`, so it is announced as well
|
|
2602
|
+
* as shown — a patient using a screen reader must not have to hunt for whether
|
|
2603
|
+
* the booking actually happened. */
|
|
2604
|
+
.sk-portal-account__done {
|
|
2605
|
+
display: flex;
|
|
2606
|
+
flex-direction: column;
|
|
2607
|
+
gap: var(--sk-space, 0.5rem);
|
|
2608
|
+
padding: calc(var(--sk-space, 0.5rem) * 2);
|
|
2609
|
+
border: 1px solid color-mix(in srgb, var(--sk-brand, #1f6feb) 35%, transparent);
|
|
2610
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2611
|
+
background: color-mix(in srgb, var(--sk-brand, #1f6feb) 6%, transparent);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
.sk-portal-account__done .sk-portal-account__fields,
|
|
2615
|
+
.sk-portal-account__done .sk-portal-account__empty {
|
|
2616
|
+
margin-bottom: 0;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
/* The slot grid inside a portal card. The board's own `.sk-slots__*` rules do the
|
|
2620
|
+
* work; this only gives it room and stops the sticky confirm bar of the funnel
|
|
2621
|
+
* variant (which the portal does not render) from being missed. */
|
|
2622
|
+
.sk-portal-account__confirm .sk-slots,
|
|
2623
|
+
.sk-portal-account__body .sk-slots {
|
|
2624
|
+
margin: var(--sk-space, 0.5rem) 0;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
.sk-portal-account__reasons {
|
|
2628
|
+
display: flex;
|
|
2629
|
+
flex-direction: column;
|
|
2630
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.5);
|
|
2631
|
+
margin: 0;
|
|
2632
|
+
padding: 0;
|
|
2633
|
+
border: 0;
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
.sk-portal-account__reason {
|
|
2637
|
+
display: flex;
|
|
2638
|
+
align-items: center;
|
|
2639
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.75);
|
|
2640
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
.sk-portal-account__confirm-actions {
|
|
2644
|
+
display: flex;
|
|
2645
|
+
flex-wrap: wrap;
|
|
2646
|
+
gap: var(--sk-space, 0.5rem);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
.sk-portal-account__detail {
|
|
2650
|
+
margin-top: var(--sk-space, 0.5rem);
|
|
2651
|
+
padding-top: var(--sk-space, 0.5rem);
|
|
2652
|
+
border-top: 1px dashed
|
|
2653
|
+
color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 15%, transparent);
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
.sk-portal-account__lines {
|
|
2657
|
+
list-style: none;
|
|
2658
|
+
margin: 0 0 var(--sk-space, 0.5rem);
|
|
2659
|
+
padding: 0;
|
|
2660
|
+
display: flex;
|
|
2661
|
+
flex-direction: column;
|
|
2662
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.5);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
.sk-portal-account__line {
|
|
2666
|
+
display: flex;
|
|
2667
|
+
justify-content: space-between;
|
|
2668
|
+
gap: var(--sk-space, 0.5rem);
|
|
2669
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
.sk-portal-account__total {
|
|
2673
|
+
margin: 0;
|
|
2674
|
+
font-weight: 700;
|
|
2675
|
+
text-align: right;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
/* The profile read view. A description list, so the label→value pairing is real
|
|
2679
|
+
* structure a screen reader can use rather than two spans that look aligned. */
|
|
2680
|
+
.sk-portal-account__fields {
|
|
2681
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
2682
|
+
padding: 0;
|
|
2683
|
+
display: flex;
|
|
2684
|
+
flex-direction: column;
|
|
2685
|
+
gap: var(--sk-space, 0.5rem);
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
.sk-portal-account__field {
|
|
2689
|
+
display: grid;
|
|
2690
|
+
grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr);
|
|
2691
|
+
gap: var(--sk-space, 0.5rem);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
.sk-portal-account__field-label {
|
|
2695
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
2696
|
+
color: var(--sk-muted, #5b6b7a);
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
.sk-portal-account__field-value {
|
|
2700
|
+
margin: 0;
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
.sk-portal-account__form {
|
|
2704
|
+
display: flex;
|
|
2705
|
+
flex-direction: column;
|
|
2706
|
+
gap: var(--sk-space, 0.5rem);
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
@media (max-width: 560px) {
|
|
2710
|
+
.sk-portal-account__field {
|
|
2711
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2712
|
+
gap: 0;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
/* ── store (PAT-766) ─────────────────────────────────────────────────────── */
|
|
2717
|
+
/* The app-served store shell (`/store` · `/cart` · `/checkout`). It lives in the
|
|
2718
|
+
* kit, not the dashboard's app.css, so a shopper crossing from the clinic's own
|
|
2719
|
+
* brochure into a page we serve does not feel like they left the clinic's site. */
|
|
2720
|
+
.sk-store-shell {
|
|
2721
|
+
min-height: 100vh;
|
|
2722
|
+
padding: calc(var(--sk-space, 0.5rem) * 3);
|
|
2723
|
+
}
|
|
2724
|
+
.sk-store-shell__header {
|
|
2725
|
+
display: flex;
|
|
2726
|
+
flex-wrap: wrap;
|
|
2727
|
+
align-items: center;
|
|
2728
|
+
justify-content: space-between;
|
|
2729
|
+
gap: calc(var(--sk-space, 0.5rem) * 2);
|
|
2730
|
+
max-width: 68rem;
|
|
2731
|
+
margin: 0 auto calc(var(--sk-space, 0.5rem) * 4);
|
|
2732
|
+
padding-bottom: calc(var(--sk-space, 0.5rem) * 2);
|
|
2733
|
+
border-bottom: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
2734
|
+
}
|
|
2735
|
+
.sk-store-shell__brand {
|
|
2736
|
+
display: inline-flex;
|
|
2737
|
+
align-items: center;
|
|
2738
|
+
gap: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
2739
|
+
color: inherit;
|
|
2740
|
+
font-weight: 700;
|
|
2741
|
+
text-decoration: none;
|
|
2742
|
+
}
|
|
2743
|
+
.sk-store-shell__logo {
|
|
2744
|
+
height: 2rem;
|
|
2745
|
+
width: auto;
|
|
2746
|
+
}
|
|
2747
|
+
.sk-store-shell__nav {
|
|
2748
|
+
display: flex;
|
|
2749
|
+
gap: calc(var(--sk-space, 0.5rem) * 3);
|
|
2750
|
+
}
|
|
2751
|
+
.sk-store-shell__nav a {
|
|
2752
|
+
color: var(--sk-brand, #1f6feb);
|
|
2753
|
+
text-decoration: none;
|
|
2754
|
+
}
|
|
2755
|
+
.sk-store-shell__nav a:hover {
|
|
2756
|
+
text-decoration: underline;
|
|
2757
|
+
}
|
|
2758
|
+
.sk-store-shell__main {
|
|
2759
|
+
max-width: 68rem;
|
|
2760
|
+
margin: 0 auto;
|
|
2761
|
+
}
|
|
2762
|
+
.sk-store__breadcrumb {
|
|
2763
|
+
margin-bottom: calc(var(--sk-space, 0.5rem) * 2);
|
|
2764
|
+
font-size: 0.875rem;
|
|
2765
|
+
}
|
|
2766
|
+
.sk-store__breadcrumb a {
|
|
2767
|
+
color: var(--sk-brand, #1f6feb);
|
|
2768
|
+
text-decoration: none;
|
|
2769
|
+
}
|
|
2770
|
+
.sk-store__header {
|
|
2771
|
+
display: flex;
|
|
2772
|
+
align-items: baseline;
|
|
2773
|
+
justify-content: space-between;
|
|
2774
|
+
gap: calc(var(--sk-space, 0.5rem) * 2);
|
|
2775
|
+
margin-bottom: calc(var(--sk-space, 0.5rem) * 3);
|
|
2776
|
+
}
|
|
2777
|
+
.sk-store__cart-link {
|
|
2778
|
+
color: var(--sk-brand, #1f6feb);
|
|
2779
|
+
font-weight: 600;
|
|
2780
|
+
text-decoration: none;
|
|
2781
|
+
white-space: nowrap;
|
|
2782
|
+
}
|
|
2783
|
+
.sk-store__cart-link:hover {
|
|
2784
|
+
text-decoration: underline;
|
|
2785
|
+
}
|
|
2786
|
+
.sk-store__grid {
|
|
2787
|
+
display: grid;
|
|
2788
|
+
gap: calc(var(--sk-space, 0.5rem) * 3);
|
|
2789
|
+
grid-template-columns: repeat(var(--sk-store-columns, 3), minmax(0, 1fr));
|
|
2790
|
+
list-style: none;
|
|
2791
|
+
margin: 0;
|
|
2792
|
+
padding: 0;
|
|
2793
|
+
}
|
|
2794
|
+
.sk-store__card {
|
|
2795
|
+
display: flex;
|
|
2796
|
+
flex-direction: column;
|
|
2797
|
+
gap: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
2798
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
2799
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2800
|
+
padding: calc(var(--sk-space, 0.5rem) * 2);
|
|
2801
|
+
}
|
|
2802
|
+
.sk-store__card-link {
|
|
2803
|
+
display: flex;
|
|
2804
|
+
flex-direction: column;
|
|
2805
|
+
gap: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
2806
|
+
color: inherit;
|
|
2807
|
+
text-decoration: none;
|
|
2808
|
+
}
|
|
2809
|
+
.sk-store__image {
|
|
2810
|
+
aspect-ratio: 1 / 1;
|
|
2811
|
+
width: 100%;
|
|
2812
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2813
|
+
object-fit: cover;
|
|
2814
|
+
background: color-mix(in oklab, color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent) 40%, transparent);
|
|
2815
|
+
}
|
|
2816
|
+
.sk-store__image--empty {
|
|
2817
|
+
display: block;
|
|
2818
|
+
}
|
|
2819
|
+
.sk-store__title {
|
|
2820
|
+
font-weight: 600;
|
|
2821
|
+
line-height: 1.3;
|
|
2822
|
+
}
|
|
2823
|
+
.sk-store__price {
|
|
2824
|
+
font-variant-numeric: tabular-nums;
|
|
2825
|
+
font-weight: 600;
|
|
2826
|
+
}
|
|
2827
|
+
.sk-store__gst {
|
|
2828
|
+
font-size: 0.8125rem;
|
|
2829
|
+
font-weight: 400;
|
|
2830
|
+
opacity: 0.7;
|
|
2831
|
+
}
|
|
2832
|
+
.sk-store__variant {
|
|
2833
|
+
display: flex;
|
|
2834
|
+
flex-direction: column;
|
|
2835
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.5);
|
|
2836
|
+
font-size: 0.875rem;
|
|
2837
|
+
}
|
|
2838
|
+
.sk-store__variant select {
|
|
2839
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
2840
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2841
|
+
padding: calc(var(--sk-space, 0.5rem) * 1);
|
|
2842
|
+
font: inherit;
|
|
2843
|
+
background: transparent;
|
|
2844
|
+
/* Form controls do not inherit colour either — a transparent one must name its ink
|
|
2845
|
+
or it renders the UA's `fieldtext` over whatever the theme painted behind it. */
|
|
2846
|
+
color: inherit;
|
|
2847
|
+
}
|
|
2848
|
+
.sk-store__add {
|
|
2849
|
+
margin-top: auto;
|
|
2850
|
+
border: 0;
|
|
2851
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2852
|
+
background: var(--sk-brand, #1f6feb);
|
|
2853
|
+
color: var(--sk-brand-foreground, #fff);
|
|
2854
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.25) calc(var(--sk-space, 0.5rem) * 2);
|
|
2855
|
+
font: inherit;
|
|
2856
|
+
font-weight: 600;
|
|
2857
|
+
cursor: pointer;
|
|
2858
|
+
}
|
|
2859
|
+
.sk-store__add:disabled {
|
|
2860
|
+
cursor: not-allowed;
|
|
2861
|
+
opacity: 0.5;
|
|
2862
|
+
}
|
|
2863
|
+
@media (max-width: 720px) {
|
|
2864
|
+
.sk-store__grid {
|
|
2865
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
@media (max-width: 480px) {
|
|
2869
|
+
.sk-store__grid {
|
|
2870
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
|
|
2875
|
+
/* ── cart (PAT-766) ──────────────────────────────────────────────────────── */
|
|
2876
|
+
.sk-cart__problems {
|
|
2877
|
+
margin: 0 0 calc(var(--sk-space, 0.5rem) * 3);
|
|
2878
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.5) calc(var(--sk-space, 0.5rem) * 2)
|
|
2879
|
+
calc(var(--sk-space, 0.5rem) * 1.5) calc(var(--sk-space, 0.5rem) * 4);
|
|
2880
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
2881
|
+
border-left-width: 3px;
|
|
2882
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2883
|
+
font-size: 0.9375rem;
|
|
2884
|
+
}
|
|
2885
|
+
.sk-cart__lines {
|
|
2886
|
+
list-style: none;
|
|
2887
|
+
margin: 0;
|
|
2888
|
+
padding: 0;
|
|
2889
|
+
}
|
|
2890
|
+
.sk-cart__line {
|
|
2891
|
+
display: grid;
|
|
2892
|
+
grid-template-columns: 1fr auto auto auto;
|
|
2893
|
+
align-items: center;
|
|
2894
|
+
gap: calc(var(--sk-space, 0.5rem) * 2);
|
|
2895
|
+
padding: calc(var(--sk-space, 0.5rem) * 2) 0;
|
|
2896
|
+
border-bottom: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
2897
|
+
}
|
|
2898
|
+
.sk-cart__line-title {
|
|
2899
|
+
color: inherit;
|
|
2900
|
+
font-weight: 600;
|
|
2901
|
+
text-decoration: none;
|
|
2902
|
+
}
|
|
2903
|
+
.sk-cart__line-unit {
|
|
2904
|
+
font-size: 0.875rem;
|
|
2905
|
+
opacity: 0.75;
|
|
2906
|
+
}
|
|
2907
|
+
.sk-cart__qty {
|
|
2908
|
+
display: inline-flex;
|
|
2909
|
+
align-items: center;
|
|
2910
|
+
gap: calc(var(--sk-space, 0.5rem) * 1);
|
|
2911
|
+
}
|
|
2912
|
+
.sk-cart__qty button {
|
|
2913
|
+
min-width: 2rem;
|
|
2914
|
+
min-height: 2rem;
|
|
2915
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
2916
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2917
|
+
background: transparent;
|
|
2918
|
+
/* See `.sk-booking__option`: a see-through <button> must name its ink. */
|
|
2919
|
+
color: inherit;
|
|
2920
|
+
font: inherit;
|
|
2921
|
+
cursor: pointer;
|
|
2922
|
+
}
|
|
2923
|
+
.sk-cart__line-total {
|
|
2924
|
+
font-variant-numeric: tabular-nums;
|
|
2925
|
+
font-weight: 600;
|
|
2926
|
+
}
|
|
2927
|
+
.sk-cart__remove {
|
|
2928
|
+
border: 0;
|
|
2929
|
+
background: none;
|
|
2930
|
+
color: inherit;
|
|
2931
|
+
font: inherit;
|
|
2932
|
+
font-size: 0.875rem;
|
|
2933
|
+
opacity: 0.75;
|
|
2934
|
+
cursor: pointer;
|
|
2935
|
+
text-decoration: underline;
|
|
2936
|
+
}
|
|
2937
|
+
.sk-cart__totals {
|
|
2938
|
+
margin: calc(var(--sk-space, 0.5rem) * 3) 0 0;
|
|
2939
|
+
margin-left: auto;
|
|
2940
|
+
max-width: 22rem;
|
|
2941
|
+
}
|
|
2942
|
+
.sk-cart__totals > div {
|
|
2943
|
+
display: flex;
|
|
2944
|
+
justify-content: space-between;
|
|
2945
|
+
gap: calc(var(--sk-space, 0.5rem) * 3);
|
|
2946
|
+
padding: calc(var(--sk-space, 0.5rem) * 0.75) 0;
|
|
2947
|
+
}
|
|
2948
|
+
.sk-cart__totals dt,
|
|
2949
|
+
.sk-cart__totals dd {
|
|
2950
|
+
margin: 0;
|
|
2951
|
+
}
|
|
2952
|
+
.sk-cart__totals dd {
|
|
2953
|
+
font-variant-numeric: tabular-nums;
|
|
2954
|
+
}
|
|
2955
|
+
.sk-cart__gst-row {
|
|
2956
|
+
font-size: 0.875rem;
|
|
2957
|
+
opacity: 0.75;
|
|
2958
|
+
}
|
|
2959
|
+
.sk-cart__total-row {
|
|
2960
|
+
border-top: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
2961
|
+
font-size: 1.125rem;
|
|
2962
|
+
font-weight: 700;
|
|
2963
|
+
}
|
|
2964
|
+
.sk-cart__note {
|
|
2965
|
+
font-size: 0.875rem;
|
|
2966
|
+
opacity: 0.75;
|
|
2967
|
+
}
|
|
2968
|
+
.sk-cart__actions {
|
|
2969
|
+
display: flex;
|
|
2970
|
+
flex-wrap: wrap;
|
|
2971
|
+
align-items: center;
|
|
2972
|
+
justify-content: space-between;
|
|
2973
|
+
gap: calc(var(--sk-space, 0.5rem) * 2);
|
|
2974
|
+
margin-top: calc(var(--sk-space, 0.5rem) * 3);
|
|
2975
|
+
}
|
|
2976
|
+
.sk-cart__continue {
|
|
2977
|
+
color: var(--sk-brand, #1f6feb);
|
|
2978
|
+
text-decoration: none;
|
|
2979
|
+
}
|
|
2980
|
+
.sk-cart__continue:hover {
|
|
2981
|
+
text-decoration: underline;
|
|
2982
|
+
}
|
|
2983
|
+
.sk-cart__checkout {
|
|
2984
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
2985
|
+
background: var(--sk-brand, #1f6feb);
|
|
2986
|
+
color: var(--sk-brand-foreground, #fff);
|
|
2987
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.5) calc(var(--sk-space, 0.5rem) * 3);
|
|
2988
|
+
font-weight: 600;
|
|
2989
|
+
text-decoration: none;
|
|
2990
|
+
}
|
|
2991
|
+
@media (max-width: 600px) {
|
|
2992
|
+
.sk-cart__line {
|
|
2993
|
+
grid-template-columns: 1fr auto;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
|
|
2998
|
+
/* ── checkout (PAT-766) ──────────────────────────────────────────────────── */
|
|
2999
|
+
.sk-checkout__summary {
|
|
3000
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
3001
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3002
|
+
padding: calc(var(--sk-space, 0.5rem) * 2);
|
|
3003
|
+
margin-bottom: calc(var(--sk-space, 0.5rem) * 3);
|
|
3004
|
+
}
|
|
3005
|
+
.sk-checkout__lines {
|
|
3006
|
+
list-style: none;
|
|
3007
|
+
margin: 0;
|
|
3008
|
+
padding: 0;
|
|
3009
|
+
}
|
|
3010
|
+
.sk-checkout__lines li {
|
|
3011
|
+
display: flex;
|
|
3012
|
+
justify-content: space-between;
|
|
3013
|
+
gap: calc(var(--sk-space, 0.5rem) * 2);
|
|
3014
|
+
padding: calc(var(--sk-space, 0.5rem) * 0.75) 0;
|
|
3015
|
+
}
|
|
3016
|
+
.sk-checkout__totals {
|
|
3017
|
+
margin: calc(var(--sk-space, 0.5rem) * 2) 0 0;
|
|
3018
|
+
border-top: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
3019
|
+
padding-top: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
3020
|
+
}
|
|
3021
|
+
.sk-checkout__totals > div {
|
|
3022
|
+
display: flex;
|
|
3023
|
+
justify-content: space-between;
|
|
3024
|
+
gap: calc(var(--sk-space, 0.5rem) * 2);
|
|
3025
|
+
padding: calc(var(--sk-space, 0.5rem) * 0.5) 0;
|
|
3026
|
+
}
|
|
3027
|
+
.sk-checkout__totals dt,
|
|
3028
|
+
.sk-checkout__totals dd {
|
|
3029
|
+
margin: 0;
|
|
3030
|
+
}
|
|
3031
|
+
.sk-checkout__totals dd {
|
|
3032
|
+
font-variant-numeric: tabular-nums;
|
|
3033
|
+
}
|
|
3034
|
+
.sk-checkout__total-row {
|
|
3035
|
+
font-size: 1.125rem;
|
|
3036
|
+
font-weight: 700;
|
|
3037
|
+
}
|
|
3038
|
+
.sk-checkout__form {
|
|
3039
|
+
display: flex;
|
|
3040
|
+
flex-direction: column;
|
|
3041
|
+
gap: calc(var(--sk-space, 0.5rem) * 3);
|
|
3042
|
+
}
|
|
3043
|
+
.sk-checkout__fieldset {
|
|
3044
|
+
display: flex;
|
|
3045
|
+
flex-direction: column;
|
|
3046
|
+
gap: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
3047
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
3048
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3049
|
+
padding: calc(var(--sk-space, 0.5rem) * 2);
|
|
3050
|
+
}
|
|
3051
|
+
.sk-checkout__fieldset legend {
|
|
3052
|
+
font-weight: 600;
|
|
3053
|
+
padding-inline: calc(var(--sk-space, 0.5rem) * 0.5);
|
|
3054
|
+
}
|
|
3055
|
+
.sk-checkout__fieldset label {
|
|
3056
|
+
display: flex;
|
|
3057
|
+
flex-direction: column;
|
|
3058
|
+
gap: calc(var(--sk-space, 0.5rem) * 0.5);
|
|
3059
|
+
font-size: 0.9375rem;
|
|
3060
|
+
}
|
|
3061
|
+
.sk-checkout__fieldset input[type='text'],
|
|
3062
|
+
.sk-checkout__fieldset input:not([type]),
|
|
3063
|
+
.sk-checkout__fieldset select {
|
|
3064
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
3065
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3066
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.25);
|
|
3067
|
+
font: inherit;
|
|
3068
|
+
background: transparent;
|
|
3069
|
+
/* See `.sk-store__variant select`. */
|
|
3070
|
+
color: inherit;
|
|
3071
|
+
}
|
|
3072
|
+
.sk-checkout__ship {
|
|
3073
|
+
display: flex;
|
|
3074
|
+
flex-direction: column;
|
|
3075
|
+
gap: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
3076
|
+
padding-top: calc(var(--sk-space, 0.5rem) * 1);
|
|
3077
|
+
}
|
|
3078
|
+
.sk-checkout__pay {
|
|
3079
|
+
border: 0;
|
|
3080
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3081
|
+
background: var(--sk-brand, #1f6feb);
|
|
3082
|
+
color: var(--sk-brand-foreground, #fff);
|
|
3083
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.75) calc(var(--sk-space, 0.5rem) * 3);
|
|
3084
|
+
font: inherit;
|
|
3085
|
+
font-size: 1.0625rem;
|
|
3086
|
+
font-weight: 700;
|
|
3087
|
+
cursor: pointer;
|
|
3088
|
+
}
|
|
3089
|
+
.sk-checkout__pay:disabled {
|
|
3090
|
+
cursor: progress;
|
|
3091
|
+
opacity: 0.6;
|
|
3092
|
+
}
|
|
3093
|
+
.sk-checkout__error {
|
|
3094
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
3095
|
+
border-left: 3px solid currentColor;
|
|
3096
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3097
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.5) calc(var(--sk-space, 0.5rem) * 2);
|
|
3098
|
+
margin-bottom: calc(var(--sk-space, 0.5rem) * 2);
|
|
3099
|
+
}
|
|
3100
|
+
.sk-checkout__note {
|
|
3101
|
+
font-size: 0.875rem;
|
|
3102
|
+
opacity: 0.8;
|
|
3103
|
+
}
|
|
3104
|
+
.sk-checkout__link,
|
|
3105
|
+
.sk-checkout__signin {
|
|
3106
|
+
display: inline-block;
|
|
3107
|
+
margin-top: calc(var(--sk-space, 0.5rem) * 2);
|
|
3108
|
+
color: var(--sk-brand, #1f6feb);
|
|
3109
|
+
font-weight: 600;
|
|
3110
|
+
text-decoration: none;
|
|
3111
|
+
}
|
|
3112
|
+
.sk-checkout__link:hover,
|
|
3113
|
+
.sk-checkout__signin:hover {
|
|
3114
|
+
text-decoration: underline;
|
|
3115
|
+
}
|
|
3116
|
+
/* The 3DS challenge frame. Fixed minimum height because the issuer's page is
|
|
3117
|
+
arbitrary and cross-origin — we cannot measure it, and a collapsed frame is a
|
|
3118
|
+
payment the shopper cannot complete. */
|
|
3119
|
+
.sk-checkout__3ds {
|
|
3120
|
+
width: 100%;
|
|
3121
|
+
min-height: 26rem;
|
|
3122
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted, #5b6b7a) 30%, transparent);
|
|
3123
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
/* ── the telehealth consult room (PAT-792) ────────────────────────────────── */
|
|
3127
|
+
/* Styled HERE rather than in the lazy chunk, deliberately: the room's markup ships
|
|
3128
|
+
* in a separately-fetched bundle, but a patient must never watch an unstyled video
|
|
3129
|
+
* call assemble itself while that bundle lands. These rules are already in the
|
|
3130
|
+
* site's compiled `/app.css`, so the panel is dressed the instant it opens.
|
|
3131
|
+
*
|
|
3132
|
+
* The stage deliberately does NOT read `--sk-background`/`--sk-foreground`: it sits
|
|
3133
|
+
* over arbitrary video, and a clinic that themed its site light-on-light would make
|
|
3134
|
+
* the controls disappear. It is a fixed dark surface with white controls — the same
|
|
3135
|
+
* call the app's room makes on the dashboard's dark scrim. */
|
|
3136
|
+
.sk-consult {
|
|
3137
|
+
display: flex;
|
|
3138
|
+
flex-direction: column;
|
|
3139
|
+
gap: var(--sk-space, 0.5rem);
|
|
3140
|
+
width: 100%;
|
|
3141
|
+
margin-top: var(--sk-space, 0.5rem);
|
|
3142
|
+
padding: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
3143
|
+
border: 1px solid color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 12%, transparent);
|
|
3144
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3145
|
+
background: var(--sk-neutral, #f4f6f8);
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
.sk-consult__head {
|
|
3149
|
+
display: flex;
|
|
3150
|
+
align-items: center;
|
|
3151
|
+
justify-content: space-between;
|
|
3152
|
+
gap: var(--sk-space, 0.5rem);
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
.sk-consult__title {
|
|
3156
|
+
font-weight: 600;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
.sk-consult__head .sk-portal-button {
|
|
3160
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
3161
|
+
padding: calc(var(--sk-space, 0.5rem) * 0.5) var(--sk-space, 0.5rem);
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
/* The chunk's own React root mounts here. Sized by its contents. */
|
|
3165
|
+
.sk-consult__host:empty {
|
|
3166
|
+
display: none;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
.sk-consult__room {
|
|
3170
|
+
display: flex;
|
|
3171
|
+
flex-direction: column;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
.sk-consult__notice {
|
|
3175
|
+
display: flex;
|
|
3176
|
+
flex-direction: column;
|
|
3177
|
+
align-items: flex-start;
|
|
3178
|
+
gap: var(--sk-space, 0.5rem);
|
|
3179
|
+
padding: calc(var(--sk-space, 0.5rem) * 2) 0;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
.sk-consult__placeholder {
|
|
3183
|
+
color: var(--sk-muted, #5b6b7a);
|
|
3184
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
.sk-consult__body {
|
|
3188
|
+
position: relative;
|
|
3189
|
+
display: flex;
|
|
3190
|
+
flex-direction: column;
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
/* The video area. `aspect-ratio` (not a fixed height) so it stays sane from a phone
|
|
3194
|
+
* to a desktop; `max-height: 70vh` so a wide viewport can't push the controls below
|
|
3195
|
+
* the fold on a page whose header we do not control. */
|
|
3196
|
+
.sk-consult__stage {
|
|
3197
|
+
position: relative;
|
|
3198
|
+
width: 100%;
|
|
3199
|
+
aspect-ratio: 16 / 9;
|
|
3200
|
+
max-height: 70vh;
|
|
3201
|
+
overflow: hidden;
|
|
3202
|
+
border-radius: var(--sk-radius, 0.625rem);
|
|
3203
|
+
background: #10161d;
|
|
3204
|
+
color: #ffffff;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
.sk-consult__stage .sk-consult__placeholder {
|
|
3208
|
+
display: flex;
|
|
3209
|
+
align-items: center;
|
|
3210
|
+
justify-content: center;
|
|
3211
|
+
height: 100%;
|
|
3212
|
+
padding: 0 calc(var(--sk-space, 0.5rem) * 2);
|
|
3213
|
+
text-align: center;
|
|
3214
|
+
color: rgba(255, 255, 255, 0.75);
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
.sk-consult__video {
|
|
3218
|
+
width: 100%;
|
|
3219
|
+
height: 100%;
|
|
3220
|
+
object-fit: cover;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
.sk-consult__video--mirrored {
|
|
3224
|
+
transform: scaleX(-1);
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
.sk-consult__remotes {
|
|
3228
|
+
display: grid;
|
|
3229
|
+
grid-template-columns: 1fr;
|
|
3230
|
+
gap: 2px;
|
|
3231
|
+
width: 100%;
|
|
3232
|
+
height: 100%;
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
.sk-consult__remotes--many {
|
|
3236
|
+
grid-template-columns: 1fr 1fr;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
.sk-consult__remote {
|
|
3240
|
+
position: relative;
|
|
3241
|
+
min-height: 0;
|
|
3242
|
+
overflow: hidden;
|
|
3243
|
+
background: #10161d;
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
/* The mirrored self-view. Small, out of the way, and labelled — an unlabelled second
|
|
3247
|
+
* tile reads as another person in the room. */
|
|
3248
|
+
.sk-consult__self {
|
|
3249
|
+
position: absolute;
|
|
3250
|
+
right: calc(var(--sk-space, 0.5rem) * 1.5);
|
|
3251
|
+
bottom: calc(var(--sk-space, 0.5rem) * 7);
|
|
3252
|
+
z-index: 10;
|
|
3253
|
+
display: flex;
|
|
3254
|
+
align-items: center;
|
|
3255
|
+
justify-content: center;
|
|
3256
|
+
width: 7rem;
|
|
3257
|
+
aspect-ratio: 16 / 9;
|
|
3258
|
+
overflow: hidden;
|
|
3259
|
+
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
3260
|
+
border-radius: calc(var(--sk-radius, 0.625rem) * 0.6);
|
|
3261
|
+
background: #10161d;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
.sk-consult__self-starting {
|
|
3265
|
+
font-size: 0.6875rem;
|
|
3266
|
+
color: rgba(255, 255, 255, 0.7);
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
.sk-consult__self-label {
|
|
3270
|
+
position: absolute;
|
|
3271
|
+
left: 0.25rem;
|
|
3272
|
+
bottom: 0.25rem;
|
|
3273
|
+
padding: 0 0.25rem;
|
|
3274
|
+
border-radius: 0.1875rem;
|
|
3275
|
+
background: rgba(0, 0, 0, 0.55);
|
|
3276
|
+
font-size: 0.6875rem;
|
|
3277
|
+
font-weight: 500;
|
|
3278
|
+
line-height: 1.4;
|
|
3279
|
+
color: #ffffff;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
/* "Waiting for your clinician to join…" over the patient's own camera. */
|
|
3283
|
+
.sk-consult__pill {
|
|
3284
|
+
position: absolute;
|
|
3285
|
+
left: var(--sk-space, 0.5rem);
|
|
3286
|
+
top: var(--sk-space, 0.5rem);
|
|
3287
|
+
z-index: 10;
|
|
3288
|
+
padding: 0.25rem 0.625rem;
|
|
3289
|
+
border-radius: 999px;
|
|
3290
|
+
background: rgba(0, 0, 0, 0.55);
|
|
3291
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
3292
|
+
color: #ffffff;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
/* A fixed amber, not a token: this chip sits over arbitrary video where a themed
|
|
3296
|
+
* colour can land at any contrast. Colour is never alone — the word carries it. */
|
|
3297
|
+
.sk-consult__reconnecting {
|
|
3298
|
+
position: absolute;
|
|
3299
|
+
left: 50%;
|
|
3300
|
+
top: var(--sk-space, 0.5rem);
|
|
3301
|
+
z-index: 30;
|
|
3302
|
+
transform: translateX(-50%);
|
|
3303
|
+
padding: 0.25rem 0.625rem;
|
|
3304
|
+
border-radius: 999px;
|
|
3305
|
+
background: #f0a202;
|
|
3306
|
+
font-size: var(--sk-font-size-sm, 0.875rem);
|
|
3307
|
+
font-weight: 600;
|
|
3308
|
+
color: #1b2733;
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
/* ALWAYS visible, unlike the app's hover-reveal bar: this is a patient on a touch
|
|
3312
|
+
* device who has never seen this surface before, and a control they must discover by
|
|
3313
|
+
* hovering is a control they do not have. */
|
|
3314
|
+
.sk-consult__controls {
|
|
3315
|
+
position: absolute;
|
|
3316
|
+
inset-inline: 0;
|
|
3317
|
+
bottom: 0;
|
|
3318
|
+
z-index: 20;
|
|
3319
|
+
display: flex;
|
|
3320
|
+
align-items: center;
|
|
3321
|
+
gap: 0.25rem;
|
|
3322
|
+
padding: calc(var(--sk-space, 0.5rem) * 3) var(--sk-space, 0.5rem) var(--sk-space, 0.5rem);
|
|
3323
|
+
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.25) 45%, transparent);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
.sk-consult__control {
|
|
3327
|
+
display: inline-flex;
|
|
3328
|
+
align-items: center;
|
|
3329
|
+
justify-content: center;
|
|
3330
|
+
width: 2.5rem;
|
|
3331
|
+
height: 2.5rem;
|
|
3332
|
+
padding: 0;
|
|
3333
|
+
border: 0;
|
|
3334
|
+
border-radius: calc(var(--sk-radius, 0.625rem) * 0.6);
|
|
3335
|
+
background: rgba(255, 255, 255, 0.12);
|
|
3336
|
+
color: rgba(255, 255, 255, 0.95);
|
|
3337
|
+
cursor: pointer;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
.sk-consult__control:hover {
|
|
3341
|
+
background: rgba(255, 255, 255, 0.24);
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
.sk-consult__control:focus-visible {
|
|
3345
|
+
outline: 2px solid rgba(255, 255, 255, 0.85);
|
|
3346
|
+
outline-offset: 2px;
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
/* Muted / camera-off. The GLYPH changes too — never colour alone. */
|
|
3350
|
+
.sk-consult__control.is-off {
|
|
3351
|
+
color: #ffb4ab;
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
.sk-consult__control--leave {
|
|
3355
|
+
margin-inline-start: auto;
|
|
3356
|
+
background: var(--sk-danger, #b3261e);
|
|
3357
|
+
color: #ffffff;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
.sk-consult__control--leave:hover {
|
|
3361
|
+
background: color-mix(in srgb, var(--sk-danger, #b3261e) 85%, #000000);
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
/* The device menu — a plain absolutely-positioned panel inside the stage, never a
|
|
3365
|
+
* portal: a portal renders into the clinic page's own <body>, escaping the island's
|
|
3366
|
+
* subtree and landing under whatever z-index that template happens to use. */
|
|
3367
|
+
.sk-consult__devices {
|
|
3368
|
+
position: absolute;
|
|
3369
|
+
left: var(--sk-space, 0.5rem);
|
|
3370
|
+
bottom: calc(var(--sk-space, 0.5rem) * 7);
|
|
3371
|
+
z-index: 30;
|
|
3372
|
+
width: 15rem;
|
|
3373
|
+
max-height: 70%;
|
|
3374
|
+
overflow-y: auto;
|
|
3375
|
+
padding: 0.25rem;
|
|
3376
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
3377
|
+
border-radius: calc(var(--sk-radius, 0.625rem) * 0.6);
|
|
3378
|
+
background: rgba(0, 0, 0, 0.85);
|
|
3379
|
+
color: #ffffff;
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
.sk-consult__device-group {
|
|
3383
|
+
padding: 0.25rem 0;
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
.sk-consult__device-label {
|
|
3387
|
+
padding: 0 0.5rem 0.125rem;
|
|
3388
|
+
font-size: 0.625rem;
|
|
3389
|
+
font-weight: 700;
|
|
3390
|
+
letter-spacing: 0.04em;
|
|
3391
|
+
text-transform: uppercase;
|
|
3392
|
+
color: rgba(255, 255, 255, 0.5);
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
.sk-consult__device-empty {
|
|
3396
|
+
padding: 0.25rem 0.5rem;
|
|
3397
|
+
font-size: 0.75rem;
|
|
3398
|
+
color: rgba(255, 255, 255, 0.5);
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
.sk-consult__device {
|
|
3402
|
+
display: flex;
|
|
3403
|
+
align-items: center;
|
|
3404
|
+
gap: 0.375rem;
|
|
3405
|
+
width: 100%;
|
|
3406
|
+
padding: 0.25rem 0.5rem;
|
|
3407
|
+
border: 0;
|
|
3408
|
+
border-radius: 0.25rem;
|
|
3409
|
+
background: transparent;
|
|
3410
|
+
font: inherit;
|
|
3411
|
+
font-size: 0.75rem;
|
|
3412
|
+
text-align: start;
|
|
3413
|
+
color: rgba(255, 255, 255, 0.9);
|
|
3414
|
+
cursor: pointer;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
.sk-consult__device:hover,
|
|
3418
|
+
.sk-consult__device:focus-visible {
|
|
3419
|
+
background: rgba(255, 255, 255, 0.15);
|
|
3420
|
+
outline: none;
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
.sk-consult__device-tick {
|
|
3424
|
+
flex: none;
|
|
3425
|
+
width: 0.75rem;
|
|
3426
|
+
height: 0.75rem;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
.sk-consult__device-tick.is-off {
|
|
3430
|
+
opacity: 0;
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
.sk-consult__device-name {
|
|
3434
|
+
overflow: hidden;
|
|
3435
|
+
text-overflow: ellipsis;
|
|
3436
|
+
white-space: nowrap;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
/* Autoplay blocked the remote audio (a patient who arrived from a magic link gave the
|
|
3440
|
+
* document no gesture). One tap, centred over the stage. */
|
|
3441
|
+
.sk-consult__unblock {
|
|
3442
|
+
position: absolute;
|
|
3443
|
+
inset: 0;
|
|
3444
|
+
z-index: 30;
|
|
3445
|
+
display: flex;
|
|
3446
|
+
align-items: center;
|
|
3447
|
+
justify-content: center;
|
|
3448
|
+
pointer-events: none;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
.sk-consult__unblock .sk-portal-button {
|
|
3452
|
+
pointer-events: auto;
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
/* ── Address picker (PAT-850) ─────────────────────────────────────────────────
|
|
3456
|
+
Address entry is Google-only: the visitor searches and CHOOSES, there are no
|
|
3457
|
+
street/suburb/state/postcode inputs left to style. Deliberately built from the
|
|
3458
|
+
same tokens as .sk-identity__* so a picker sitting inside the identity grid or
|
|
3459
|
+
the checkout form reads as one of its fields rather than a bolted-on widget. */
|
|
3460
|
+
.sk-address {
|
|
3461
|
+
position: relative;
|
|
3462
|
+
grid-column: 1 / -1;
|
|
3463
|
+
}
|
|
3464
|
+
.sk-address__label {
|
|
3465
|
+
display: block;
|
|
3466
|
+
margin-bottom: calc(var(--sk-space) * 0.75);
|
|
3467
|
+
font-size: var(--sk-font-size-sm);
|
|
3468
|
+
font-weight: 500;
|
|
3469
|
+
}
|
|
3470
|
+
.sk-address__input {
|
|
3471
|
+
width: 100%;
|
|
3472
|
+
min-height: 2.5rem;
|
|
3473
|
+
padding: calc(var(--sk-space)) calc(var(--sk-space) * 1.5);
|
|
3474
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted) 40%, transparent);
|
|
3475
|
+
border-radius: var(--sk-island-radius);
|
|
3476
|
+
background: var(--sk-background, #fff);
|
|
3477
|
+
color: var(--sk-foreground, inherit);
|
|
3478
|
+
font: inherit;
|
|
3479
|
+
}
|
|
3480
|
+
.sk-address__input[aria-invalid='true'] {
|
|
3481
|
+
border-color: #b3261e;
|
|
3482
|
+
}
|
|
3483
|
+
/* The popup is positioned against the input's own box, not the field group. */
|
|
3484
|
+
.sk-address__box {
|
|
3485
|
+
position: relative;
|
|
3486
|
+
}
|
|
3487
|
+
/* The suggestion list overlays following fields rather than shoving them down —
|
|
3488
|
+
the form must not reflow under the visitor mid-type. */
|
|
3489
|
+
.sk-address__list {
|
|
3490
|
+
position: absolute;
|
|
3491
|
+
z-index: 20;
|
|
3492
|
+
inset-inline: 0;
|
|
3493
|
+
top: calc(100% + var(--sk-space) * 0.5);
|
|
3494
|
+
max-height: 15rem;
|
|
3495
|
+
overflow-y: auto;
|
|
3496
|
+
list-style: none;
|
|
3497
|
+
margin: 0;
|
|
3498
|
+
padding: calc(var(--sk-space) * 0.5);
|
|
3499
|
+
border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
|
|
3500
|
+
border-radius: var(--sk-island-radius);
|
|
3501
|
+
/* OPAQUE, in two layers, and this is the whole point of the rule (PAT-867).
|
|
3502
|
+
`--sk-flow-canvas` is the page's own backdrop, resolved to a concrete colour by
|
|
3503
|
+
`useResolvedSurface` in `flow-shell.tsx` — the island is still reading the clinic's
|
|
3504
|
+
canvas rather than inventing one, and it is NOT `--sk-background` (the kit's own
|
|
3505
|
+
light-theme token, which would paint white down a dark page — PAT-834/838). The
|
|
3506
|
+
`currentColor` tint on top is what lifts the popup off that canvas, and it is
|
|
3507
|
+
layered ONTO an opaque base rather than being the base: as the base it was
|
|
3508
|
+
translucent by construction, and the content underneath read straight through the
|
|
3509
|
+
list. A popup covers unrelated content — it may not share it. `white` is the
|
|
3510
|
+
fallback for a no-JS render, where no popup exists yet anyway. */
|
|
3511
|
+
background:
|
|
3512
|
+
linear-gradient(
|
|
3513
|
+
color-mix(in srgb, currentColor 10%, transparent),
|
|
3514
|
+
color-mix(in srgb, currentColor 10%, transparent)
|
|
3515
|
+
),
|
|
3516
|
+
var(--sk-flow-canvas, white);
|
|
3517
|
+
box-shadow: 0 8px 24px color-mix(in srgb, currentColor 25%, transparent);
|
|
3518
|
+
}
|
|
3519
|
+
.sk-address__option {
|
|
3520
|
+
padding: calc(var(--sk-space) * 1.25) calc(var(--sk-space) * 1.5);
|
|
3521
|
+
border-radius: calc(var(--sk-island-radius) * 0.75);
|
|
3522
|
+
font-size: var(--sk-font-size-sm);
|
|
3523
|
+
line-height: 1.4;
|
|
3524
|
+
cursor: pointer;
|
|
3525
|
+
}
|
|
3526
|
+
/* Keyboard and pointer land on the SAME visual state — `aria-activedescendant` moves
|
|
3527
|
+
no real focus, so without this the arrow keys would move an invisible cursor. */
|
|
3528
|
+
.sk-address__option--active,
|
|
3529
|
+
.sk-address__option:hover {
|
|
3530
|
+
background: color-mix(in srgb, currentColor 16%, transparent);
|
|
3531
|
+
}
|
|
3532
|
+
/* Reading copy, so it takes the muted ink rather than a dimmer of the field's. */
|
|
3533
|
+
.sk-flow .sk-address__status {
|
|
3534
|
+
margin-top: calc(var(--sk-space) * 0.75);
|
|
3535
|
+
min-height: 1.2em;
|
|
3536
|
+
color: var(--sk-flow-muted, var(--sk-muted));
|
|
3537
|
+
font-size: 0.8125rem;
|
|
3538
|
+
line-height: 1.2;
|
|
3539
|
+
}
|
|
3540
|
+
.sk-address__empty {
|
|
3541
|
+
margin-top: calc(var(--sk-space) * 0.75);
|
|
3542
|
+
font-size: var(--sk-font-size-sm);
|
|
3543
|
+
}
|
|
3544
|
+
.sk-address__check {
|
|
3545
|
+
margin-top: calc(var(--sk-space) * 0.5);
|
|
3546
|
+
padding: calc(var(--sk-space) * 0.75) calc(var(--sk-space) * 1.5);
|
|
3547
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted) 40%, transparent);
|
|
3548
|
+
border-radius: var(--sk-island-radius);
|
|
3549
|
+
background: transparent;
|
|
3550
|
+
color: inherit;
|
|
3551
|
+
font: inherit;
|
|
3552
|
+
cursor: pointer;
|
|
3553
|
+
}
|
|
3554
|
+
/* A picked address is settled text, not a field — the visual difference between
|
|
3555
|
+
"chosen" and "still editable" is the whole point of the control. */
|
|
3556
|
+
.sk-address--picked {
|
|
3557
|
+
padding: calc(var(--sk-space)) calc(var(--sk-space) * 1.5);
|
|
3558
|
+
border: 1px solid color-mix(in srgb, var(--sk-muted) 30%, transparent);
|
|
3559
|
+
border-radius: var(--sk-island-radius);
|
|
3560
|
+
background: color-mix(in srgb, var(--sk-muted) 8%, transparent);
|
|
3561
|
+
}
|
|
3562
|
+
.sk-address__chosen {
|
|
3563
|
+
margin: 0;
|
|
3564
|
+
overflow-wrap: anywhere;
|
|
3565
|
+
}
|
|
3566
|
+
.sk-address__change {
|
|
3567
|
+
margin-top: calc(var(--sk-space) * 0.5);
|
|
3568
|
+
padding: 0;
|
|
3569
|
+
border: 0;
|
|
3570
|
+
background: transparent;
|
|
3571
|
+
color: var(--sk-primary, inherit);
|
|
3572
|
+
font: inherit;
|
|
3573
|
+
text-decoration: underline;
|
|
3574
|
+
cursor: pointer;
|
|
3575
|
+
}
|
|
3576
|
+
.sk-address__error {
|
|
3577
|
+
margin-top: calc(var(--sk-space));
|
|
3578
|
+
color: #b3261e;
|
|
3579
|
+
font-size: var(--sk-font-size-sm);
|
|
3580
|
+
}
|