@lilydesignsystem/themes 0.6.0 → 0.6.2

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.
Files changed (66) hide show
  1. package/LICENSE.md +8 -8
  2. package/dist/abyss.css +635 -22
  3. package/dist/acid.css +634 -21
  4. package/dist/adobe-spectrum.css +636 -23
  5. package/dist/ant-design.css +634 -21
  6. package/dist/aqua.css +636 -23
  7. package/dist/atlassian.css +634 -21
  8. package/dist/australia-agriculture-design-system.css +4575 -0
  9. package/dist/autumn.css +636 -23
  10. package/dist/black.css +635 -22
  11. package/dist/brazil-padrao-digital-de-governo.css +4575 -0
  12. package/dist/bumblebee.css +637 -24
  13. package/dist/business.css +635 -22
  14. package/dist/canada-government-of-canada-design-system.css +4575 -0
  15. package/dist/cappuccino.css +634 -21
  16. package/dist/caramellatte.css +635 -22
  17. package/dist/cmyk.css +634 -21
  18. package/dist/coffee.css +634 -21
  19. package/dist/corporate.css +638 -25
  20. package/dist/cupcake.css +635 -22
  21. package/dist/cyberpunk.css +634 -21
  22. package/dist/dark.css +636 -23
  23. package/dist/dim.css +634 -21
  24. package/dist/dracula.css +634 -21
  25. package/dist/emerald.css +635 -22
  26. package/dist/european-union-europa-component-library.css +4575 -0
  27. package/dist/excel.css +634 -21
  28. package/dist/fantasy.css +635 -22
  29. package/dist/fluent.css +634 -21
  30. package/dist/forest.css +634 -21
  31. package/dist/garden.css +636 -23
  32. package/dist/github-primer.css +634 -21
  33. package/dist/halloween.css +635 -22
  34. package/dist/ibm-carbon.css +634 -21
  35. package/dist/japan-digital-agency-design-system.css +4575 -0
  36. package/dist/lemonade.css +634 -21
  37. package/dist/light.css +635 -22
  38. package/dist/linear.css +634 -21
  39. package/dist/lofi.css +634 -21
  40. package/dist/luxury.css +634 -21
  41. package/dist/mozilla-protocol.css +636 -23
  42. package/dist/night.css +634 -21
  43. package/dist/nord.css +634 -21
  44. package/dist/notion.css +634 -21
  45. package/dist/pastel.css +641 -28
  46. package/dist/retro.css +638 -25
  47. package/dist/salesforce.css +634 -21
  48. package/dist/sap-fiori.css +634 -21
  49. package/dist/silk.css +635 -22
  50. package/dist/singapore-government-design-system.css +4575 -0
  51. package/dist/sunset.css +634 -21
  52. package/dist/synthwave.css +634 -21
  53. package/dist/united-kingdom-government-digital-service.css +634 -21
  54. package/dist/united-kingdom-national-health-service-england-for-patients.css +658 -32
  55. package/dist/united-kingdom-national-health-service-england-for-practitioners.css +656 -30
  56. package/dist/united-kingdom-national-health-service-scotland-for-patients.css +669 -40
  57. package/dist/united-kingdom-national-health-service-scotland-for-practitioners.css +664 -35
  58. package/dist/united-kingdom-national-health-service-wales-for-patients.css +669 -38
  59. package/dist/united-kingdom-national-health-service-wales-for-practitioners.css +662 -31
  60. package/dist/united-states-web-design-system.css +635 -22
  61. package/dist/valentine.css +638 -25
  62. package/dist/vercel.css +634 -21
  63. package/dist/winter.css +635 -22
  64. package/dist/wireframe.css +634 -21
  65. package/index.md +8 -4
  66. package/package.json +2 -2
@@ -0,0 +1,4575 @@
1
+ /* ============================================================================
2
+ * Lily theme: brazil-padrao-digital-de-governo
3
+ *
4
+ * Standalone Lily Design System theme — declares the theme tokens and inlines
5
+ * the component CSS for all 492 Lily components, so this single file is the
6
+ * only stylesheet required to render a Lily-styled page.
7
+ *
8
+ * Credit for the theme source: Padrão Digital de Governo (gov.br Design System) (https://www.gov.br/ds/)
9
+ *
10
+ * Upstream tracked: Padrão Digital de Governo, @govbr-ds/core 3.7.0 (MIT). Independent
11
+ * implementation against Lily class hooks; values from the published tokens, no logos, crests or restricted typefaces;
12
+ * not affiliated with Governo Federal do Brasil.
13
+ * ========================================================================= */
14
+
15
+ /* Theme tokens — the colour, radius, border, and depth variables consumed
16
+ * by every Lily component rule below. The theme applies by default (`:root`)
17
+ * and also when explicitly opted into via `<html data-theme="light">`,
18
+ * so multiple themes can coexist in one document and be switched at runtime
19
+ * by updating the `data-theme` attribute. */
20
+
21
+ :where(:root, :root[data-theme="brazil-padrao-digital-de-governo"]) {
22
+ color-scheme: light;
23
+
24
+ /* Page-level surface (primary canvas). Sits behind body copy
25
+ and primary content areas. Lightest base shade in light
26
+ themes, deepest in dark themes. */
27
+ --color-base-100: #ffffff;
28
+ /* Secondary surface — section panels, card backgrounds,
29
+ sidebars, table-header fills. One step away from the page
30
+ surface. */
31
+ --color-base-200: #f8f8f8;
32
+ /* Tertiary surface — dividers, sunken wells, hover-row
33
+ backgrounds. The most muted of the three base shades. */
34
+ --color-base-300: #ededed;
35
+ /* Primary text colour. High-contrast against base-100 to meet
36
+ WCAG contrast ratios for body copy. */
37
+ --color-base-content: #333333;
38
+ /* Primary brand / action colour. Used on primary buttons,
39
+ active navigation items, selected states, and the focus
40
+ ring. */
41
+ --color-primary: #1351b4;
42
+ /* Foreground colour used over `--color-primary` fills (so
43
+ primary buttons retain a guaranteed contrast ratio). */
44
+ --color-primary-content: #ffffff;
45
+ /* Secondary brand colour. Pressed/hover state for primary
46
+ buttons and visited links; also drives the secondary chrome
47
+ family. */
48
+ --color-secondary: #555555;
49
+ /* Foreground colour used over `--color-secondary` fills. */
50
+ --color-secondary-content: #ffffff;
51
+ /* Accent colour — used sparingly for emphasis, the AI label,
52
+ illustrations, and tertiary call-outs. */
53
+ --color-accent: #0c326f;
54
+ /* Foreground colour used over `--color-accent` fills. */
55
+ --color-accent-content: #ffffff;
56
+ /* Neutral chrome surface — footer bars, code blocks,
57
+ tooltips, government-banner strips. Typically the theme's
58
+ darkest neutral. */
59
+ --color-neutral: #333333;
60
+ /* Foreground colour used over `--color-neutral` fills. */
61
+ --color-neutral-content: #ffffff;
62
+ /* Informational status colour — informational banners, links,
63
+ 'did-you-know' callouts. */
64
+ --color-info: #155bcb;
65
+ /* Foreground colour used over `--color-info` fills. */
66
+ --color-info-content: #ffffff;
67
+ /* Success / confirmation colour — used on success panels,
68
+ completed step indicators, validation pass states, and the
69
+ green RAG status. */
70
+ --color-success: #168821;
71
+ /* Foreground colour used over `--color-success` fills. */
72
+ --color-success-content: #ffffff;
73
+ /* Warning colour — yellow/amber band; used on phase banners,
74
+ in-progress states, validation warnings, and the amber RAG
75
+ status. */
76
+ --color-warning: #ffcd07;
77
+ /* Foreground colour used over `--color-warning` fills. Often
78
+ dark so text remains legible on a bright yellow background. */
79
+ --color-warning-content: #000000;
80
+ /* Error / destructive colour — used on alert dialogs, error
81
+ messages, validation failures, urgent care cards, and the
82
+ red RAG status. */
83
+ --color-error: #e52207;
84
+ /* Foreground colour used over `--color-error` fills. */
85
+ --color-error-content: #ffffff;
86
+ /* Smallest corner radius. Applied to chips, badges, switches,
87
+ and radio/checkbox containers (mapped to `--lily-radius-
88
+ sm`). */
89
+ --radius-selector: 0.25rem;
90
+ /* Medium corner radius. Applied to inputs, buttons, menu
91
+ items, tooltips, and most everyday form controls (mapped to
92
+ `--lily-radius-md`). */
93
+ --radius-field: 0.25rem;
94
+ /* Largest corner radius. Applied to cards, dialogs, drawers,
95
+ and hero blocks (mapped to `--lily-radius-lg`). */
96
+ --radius-box: 0.5rem;
97
+ /* Base size for selector-style controls (checkbox/radio glyph
98
+ size, switch thumb diameter). Currently unused by Lily's
99
+ baseline component rules but available for theme
100
+ extensions. */
101
+ --size-selector: 0.25rem;
102
+ /* Base size for field-style controls (input height
103
+ multiplier). Currently unused by Lily's baseline component
104
+ rules but available for theme extensions. */
105
+ --size-field: 0.25rem;
106
+ /* Default border width applied to inputs, buttons, dividers,
107
+ table cells, etc. Single source of truth for the system's
108
+ stroke weight. */
109
+ --border: 1px;
110
+ /* Depth flag (0 or 1). Lily's baseline rules read it
111
+ implicitly via the shadow tokens; a depth-0 theme
112
+ effectively renders flat. */
113
+ --depth: 1;
114
+ /* Noise flag (0 or 1). Reserved for theme extensions that
115
+ want to layer a subtle texture; the baseline rules do not
116
+ consume it. */
117
+ --noise: 0;
118
+ /* Fluid inline gutter for page containers and the main
119
+ region: grows with the viewport from a 1rem floor to a 2rem
120
+ ceiling. Spacing only, never text. */
121
+ --space-fluid-gutter: clamp(1rem, 0.5rem + 2vw, 2rem);
122
+ /* Fluid gap between sections (stacks and grids): grows with
123
+ the viewport from a 1rem floor to a 2rem ceiling. Spacing
124
+ only, never text. */
125
+ --space-fluid-section: clamp(1rem, 0.75rem + 1vw, 2rem);
126
+ }
127
+
128
+ /* ----------------------------------------------------------------------------
129
+ * Lily component styling — applied automatically once this theme stylesheet
130
+ * is loaded. Every Lily class hook (492 components) consumes the theme tokens
131
+ * declared above, so no other stylesheet is required for the theme to render.
132
+ *
133
+ * Rules live in @layer lily with :where() selectors so the specificity stays
134
+ * at 0 — application CSS always wins. To replace a Lily component's default
135
+ * look, declare a higher-specificity rule (or a later @layer) in your own
136
+ * stylesheet.
137
+ * -------------------------------------------------------------------------- */
138
+
139
+ @layer lily {
140
+ /* ==========================================================================
141
+ * Tokens & primitives
142
+ * --------------------------------------------------------------------------
143
+ * Lily exposes a small, stable set of derived tokens (the `--lily-*`
144
+ * family) layered on top of the upstream theme primitives (the
145
+ * `--color-*`, `--radius-*`, `--border`, `--depth`, `--noise` family).
146
+ *
147
+ * Component rules consume only the `--lily-*` tokens. That indirection
148
+ * means a consumer can swap upstream themes (or invent a new one) without
149
+ * touching component CSS — the component rules below remain stable
150
+ * because their inputs do.
151
+ * ====================================================================== */
152
+
153
+ :where(:root) {
154
+ /* --- Surfaces ------------------------------------------------------ */
155
+ /* Default page surface — the canvas behind body copy. */
156
+ --lily-surface: var(--color-base-100);
157
+ /* Secondary surface — section panels, sidebars, table headers,
158
+ muted callouts. One step away from the page background. */
159
+ --lily-surface-alt: var(--color-base-200);
160
+ /* Sunken surface — used behind progress tracks, skeletons, and
161
+ inset wells. Suggests depth without requiring a shadow. */
162
+ --lily-surface-sunken: var(--color-base-300);
163
+
164
+ /* --- Text ---------------------------------------------------------- */
165
+ /* Primary body-copy colour. Maps to the theme's high-contrast
166
+ reading colour. */
167
+ --lily-text: var(--color-base-content);
168
+ /* Error text: the theme's error colour pulled toward the theme's own reading colour (`--color-base-content`, not `--lily-text`, which a host page may override) so it
169
+ keeps WCAG contrast as text on the page and on the error-tinted
170
+ callout surface (`--color-error` itself is a fill colour). */
171
+ --lily-error-text: color-mix(
172
+ in oklch,
173
+ var(--color-error) 22%,
174
+ var(--color-base-content)
175
+ );
176
+ /* Muted / secondary text — mixed 70% with transparent to keep the
177
+ hue but lower contrast for captions, helptext, placeholders. */
178
+ --lily-text-muted: color-mix(
179
+ in oklch,
180
+ var(--color-base-content) 70%,
181
+ transparent
182
+ );
183
+
184
+ /* --- Borders ------------------------------------------------------- */
185
+ /* Default border — 20% blend with body content colour, low-contrast,
186
+ used for subtle dividers between content blocks. */
187
+ --lily-border: color-mix(
188
+ in oklch,
189
+ var(--color-base-content) 20%,
190
+ transparent
191
+ );
192
+ /* Higher-contrast border — 40% blend, used on inputs, buttons, and
193
+ anywhere a clear edge is required (WCAG non-text contrast). */
194
+ --lily-border-strong: color-mix(
195
+ in oklch,
196
+ var(--color-base-content) 40%,
197
+ transparent
198
+ );
199
+
200
+ /* --- Focus ring ---------------------------------------------------- */
201
+ /* Outline colour for `:focus-visible`. Always the primary brand
202
+ colour so focus is visually tied to interactive intent. */
203
+ --lily-focus-ring: var(--color-primary);
204
+
205
+ /* --- Radii --------------------------------------------------------- */
206
+ /* Three tiers of corner rounding. `sm` for chips/badges,
207
+ `md` for inputs/buttons/cards, `lg` for dialogs/heroes. */
208
+ --lily-radius-sm: var(--radius-selector);
209
+ --lily-radius-md: var(--radius-field);
210
+ --lily-radius-lg: var(--radius-box);
211
+
212
+ /* --- Spacing scale ------------------------------------------------- */
213
+ /* Five-step spacing scale used by every gap, padding, and margin.
214
+ Linear progression keeps rhythm consistent at any zoom level. */
215
+ --lily-space-xs: 0.25rem;
216
+ --lily-space-sm: 0.5rem;
217
+ --lily-space-md: 1rem;
218
+ --lily-space-lg: 1.5rem;
219
+ --lily-space-xl: 2rem;
220
+
221
+ /* --- Typography ---------------------------------------------------- */
222
+ /* Body font stack — system fonts first so consumers get a
223
+ native-feeling baseline without shipping a web font. */
224
+ --lily-font-body-latin:
225
+ Rawline, Raleway, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
226
+ /* Monospaced font stack — used by code, kbd, identifiers, and
227
+ anywhere alignment matters more than reading speed. */
228
+ --lily-font-mono:
229
+ ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
230
+ "Liberation Mono", monospace;
231
+
232
+ /* --- Shadows ------------------------------------------------------- */
233
+ /* Three depth tiers. The shadow colour is derived from the body
234
+ content token so it adapts to light/dark themes automatically. */
235
+ --lily-shadow-sm: 0 1px 2px
236
+ color-mix(in oklch, var(--color-base-content) 8%, transparent);
237
+ --lily-shadow-md: 0 4px 12px
238
+ color-mix(in oklch, var(--color-base-content) 12%, transparent);
239
+ --lily-shadow-lg: 0 12px 32px
240
+ color-mix(in oklch, var(--color-base-content) 16%, transparent);
241
+ }
242
+
243
+ /* Focus ring — every interactive Lily class hook gets the same visible
244
+ focus indicator. Centralising the selector means a consumer can
245
+ restyle every focus state at once by overriding `--lily-focus-ring`
246
+ (or this rule) rather than chasing 100+ component selectors. */
247
+ :where(
248
+ [class$="-input"],
249
+ [class$="-button"],
250
+ [class$="-link"],
251
+ [class$="-list-item"],
252
+ [class$="-option"],
253
+ [class$="-picker-button"],
254
+ .button,
255
+ .input,
256
+ .select,
257
+ .option,
258
+ .menu-item,
259
+ .tab-bar-button,
260
+ .switch-button,
261
+ .toggle-button,
262
+ .checkbox-input,
263
+ .radio-input,
264
+ .file-upload,
265
+ .signature-pad,
266
+ .editable
267
+ ):focus-visible {
268
+ outline: 2px solid var(--lily-focus-ring);
269
+ outline-offset: 2px;
270
+ }
271
+
272
+ /* ==========================================================================
273
+ * Inputs
274
+ * --------------------------------------------------------------------------
275
+ * Every Lily class ending in `-input` (textual, date, picker, range, file,
276
+ * national-identifier, etc.) shares the same baseline styling. The
277
+ * `[class$="-input"]` attribute selector catches single-class usage
278
+ * (`class="email-input"`); consumers needing multi-class composition can
279
+ * override with higher-specificity rules in their own stylesheet.
280
+ *
281
+ * The `:not([class$="-with-mask-input"])` exclusion exists because the
282
+ * masked-input variant carries its own typography (monospaced + letter
283
+ * spacing) and we don't want the generic baseline overriding it.
284
+ * Hidden inputs (`type="hidden"`) are also excluded — they must not be
285
+ * laid out as visible inputs.
286
+ * ====================================================================== */
287
+
288
+ /* Baseline visual treatment for every textual input. */
289
+ :where(
290
+ [class$="-input"]:not([class$="-with-mask-input"]):not([type="hidden"]),
291
+ .input
292
+ ) {
293
+ appearance: none; /* drop UA native chrome (Safari especially) */
294
+ display: inline-block;
295
+ width: 100%; /* fill parent by default; consumer caps via container */
296
+ max-width: 100%;
297
+ min-height: 2.5rem; /* WCAG 2.5.5 target-size guidance (~44px) */
298
+ padding: 0.5rem 0.75rem;
299
+ font: inherit;
300
+ font-family: var(--lily-font-body);
301
+ color: var(--lily-text);
302
+ background-color: var(--lily-surface);
303
+ border: var(--border) solid var(--lily-border-strong);
304
+ border-radius: var(--lily-radius-md);
305
+ box-sizing: border-box;
306
+ /* Smooth focus/hover edge transitions — short enough to feel
307
+ responsive, long enough to read as intentional. */
308
+ transition:
309
+ border-color 120ms ease,
310
+ box-shadow 120ms ease;
311
+ }
312
+
313
+ /* Hover affordance — switches the border to the primary brand colour so
314
+ users get a visual cue that the field is interactive. The `:not(:disabled)`
315
+ guard prevents the cue from appearing on locked-out controls. */
316
+ :where([class$="-input"], .input):hover:not(:disabled) {
317
+ border-color: var(--color-primary);
318
+ }
319
+
320
+ /* Disabled / aria-disabled — desaturate and shift to the muted surface so
321
+ the control reads as inactive without losing its layout footprint. */
322
+ :where([class$="-input"], .input):disabled,
323
+ :where([class$="-input"], .input)[aria-disabled="true"] {
324
+ color: var(--lily-text-muted);
325
+ background-color: var(--lily-surface-alt);
326
+ cursor: not-allowed;
327
+ }
328
+
329
+ /* Invalid input — red border. The `aria-invalid="true"` attribute is the
330
+ ARIA contract (server-rendered errors); `:user-invalid` is the native
331
+ state that matches only after the user has interacted, so a required
332
+ field is not styled invalid on load. Pairing a colour change with the
333
+ ARIA state keeps WCAG 1.4.1 (no colour-only meaning) honest. */
334
+ :where([class$="-input"], .input)[aria-invalid="true"],
335
+ :where([class$="-input"], .input):user-invalid {
336
+ border-color: var(--color-error);
337
+ }
338
+
339
+ /* Placeholder — match the muted text token. `opacity: 1` overrides Firefox's
340
+ legacy default placeholder opacity, which would otherwise compound on
341
+ our already-muted colour. */
342
+ :where([class$="-input"], .input)::placeholder {
343
+ color: var(--lily-text-muted);
344
+ opacity: 1;
345
+ }
346
+
347
+ /* --- Input type-specific tweaks ----------------------------------------- */
348
+
349
+ /* Hidden inputs must never render. */
350
+ :where(.hidden-input) {
351
+ display: none;
352
+ }
353
+
354
+ /* Submit / reset / image / generic button inputs render as buttons. */
355
+ :where(.button-input, .submit-input, .reset-input, .image-input) {
356
+ width: auto;
357
+ min-width: 2.5rem;
358
+ background-color: var(--color-primary);
359
+ color: var(--color-primary-content);
360
+ border-color: transparent;
361
+ cursor: pointer;
362
+ font-weight: 600;
363
+ }
364
+
365
+ /* Reset input is the secondary-action variant — muted surface, not the
366
+ primary brand colour, because "reset" is a destructive op. */
367
+ :where(.reset-input) {
368
+ background-color: var(--lily-surface-alt);
369
+ color: var(--lily-text);
370
+ border-color: var(--lily-border-strong);
371
+ }
372
+
373
+ /* Color input — narrow swatch with internal padding so the colour chip
374
+ reads cleanly inside the input frame. */
375
+ :where(.color-input) {
376
+ min-height: 2.5rem;
377
+ padding: 0.25rem;
378
+ width: 3rem;
379
+ cursor: pointer;
380
+ }
381
+
382
+ /* Range / angle slider — strip the input chrome so the consumer can
383
+ restyle the track. `accent-color` provides a minimal styled fallback. */
384
+ :where(.range-input, .angle-slider-range-input) {
385
+ appearance: none;
386
+ background: transparent;
387
+ border: 0;
388
+ padding: 0;
389
+ height: 1.5rem;
390
+ accent-color: var(--color-primary);
391
+ }
392
+
393
+ /* File inputs — slightly tighter padding because the UA-rendered
394
+ "Choose file" button consumes most of the visual space. */
395
+ :where(.file-input, .image-file-input) {
396
+ padding: 0.375rem 0.5rem;
397
+ cursor: pointer;
398
+ }
399
+
400
+ /* Checkbox / radio — small square/round controls with the brand accent. */
401
+ :where(.checkbox-input, .radio-input) {
402
+ width: 1.125rem;
403
+ height: 1.125rem;
404
+ min-height: 0; /* override the 2.5rem input baseline */
405
+ padding: 0;
406
+ accent-color: var(--color-primary);
407
+ cursor: pointer;
408
+ }
409
+ :where(.radio-input) {
410
+ border-radius: 50%;
411
+ }
412
+ :where(.checkbox-input) {
413
+ border-radius: var(--lily-radius-sm);
414
+ }
415
+
416
+ /* Textarea variants — taller minimum, line-height tuned for reading,
417
+ vertical-only resize so layout columns aren't disturbed. */
418
+ :where(.text-area-input, .text-area-input-with-character-counter) {
419
+ min-height: 6rem;
420
+ line-height: 1.5;
421
+ resize: vertical;
422
+ }
423
+ /* Character-counter variant stacks the textarea and the counter span. */
424
+ :where(.text-area-input-with-character-counter) {
425
+ display: flex;
426
+ flex-direction: column;
427
+ }
428
+
429
+ /* --- Composite inputs with built-in DOM structure ----------------------- */
430
+
431
+ /* PIN input — N small slots side-by-side. The wrapper is invisible;
432
+ the children carry the styling. */
433
+ :where(.pin-input-div) {
434
+ display: inline-flex;
435
+ gap: 0.5rem;
436
+ padding: 0;
437
+ background: transparent;
438
+ border: 0;
439
+ width: auto;
440
+ min-height: 0;
441
+ }
442
+ :where(.pin-input-div > input) {
443
+ width: 2.5rem;
444
+ height: 3rem;
445
+ padding: 0;
446
+ text-align: center;
447
+ font-size: 1.125rem;
448
+ font-weight: 600;
449
+ background-color: var(--lily-surface);
450
+ border: var(--border) solid var(--lily-border-strong);
451
+ border-radius: var(--lily-radius-md);
452
+ color: var(--lily-text);
453
+ }
454
+
455
+ /* Password-toggle composite — text input + reveal button, sharing
456
+ a single visual frame via flush radii. */
457
+ :where(.password-input-or-text-input-div) {
458
+ display: inline-flex;
459
+ align-items: stretch;
460
+ width: 100%;
461
+ background: transparent;
462
+ border: 0;
463
+ padding: 0;
464
+ min-height: 0;
465
+ }
466
+ :where(.password-input-or-text-input-div > input) {
467
+ flex: 1;
468
+ min-height: 2.5rem;
469
+ padding: 0.5rem 0.75rem;
470
+ background-color: var(--lily-surface);
471
+ color: var(--lily-text);
472
+ border: var(--border) solid var(--lily-border-strong);
473
+ border-right: 0; /* drop the seam where input meets button */
474
+ border-radius: var(--lily-radius-md) 0 0 var(--lily-radius-md);
475
+ }
476
+ :where(.password-input-or-text-input-div > button) {
477
+ padding: 0 0.75rem;
478
+ background-color: var(--lily-surface-alt);
479
+ color: var(--lily-text);
480
+ border: var(--border) solid var(--lily-border-strong);
481
+ border-radius: 0 var(--lily-radius-md) var(--lily-radius-md) 0;
482
+ cursor: pointer;
483
+ }
484
+
485
+ /* --- Input compositions ------------------------------------------------- */
486
+
487
+ /* Input group — flush-joined controls (input + button, etc.). The negative
488
+ margin collapses overlapping borders into a single shared edge. */
489
+ :where(.input-group) {
490
+ display: inline-flex;
491
+ align-items: stretch;
492
+ width: 100%;
493
+ }
494
+ :where(.input-group > *) {
495
+ border-radius: 0;
496
+ margin-left: calc(-1 * var(--border));
497
+ }
498
+ :where(.input-group > *:first-child) {
499
+ border-top-left-radius: var(--lily-radius-md);
500
+ border-bottom-left-radius: var(--lily-radius-md);
501
+ margin-left: 0;
502
+ }
503
+ :where(.input-group > *:last-child) {
504
+ border-top-right-radius: var(--lily-radius-md);
505
+ border-bottom-right-radius: var(--lily-radius-md);
506
+ }
507
+
508
+ /* Masked input — monospaced font + slight letter-spacing so the mask
509
+ template (phone, SSN, etc.) aligns over the typed characters. */
510
+ :where(.input-with-mask) {
511
+ font-family: var(--lily-font-mono);
512
+ letter-spacing: 0.05em;
513
+ }
514
+
515
+ /* Search input — reserve space on the inline-start for the magnifier
516
+ glyph. The `linear-gradient(transparent, transparent)` is a no-op
517
+ background-image; it ensures the property exists so consumers can
518
+ layer an SVG icon over it by overriding `background-image`. */
519
+ :where(.text-input-with-search, .search-input) {
520
+ background-image: linear-gradient(transparent, transparent);
521
+ padding-inline-start: 2.25rem;
522
+ }
523
+
524
+ /* ==========================================================================
525
+ * Views
526
+ * --------------------------------------------------------------------------
527
+ * Read-only display chips for identifiers, dates, ratings, and other
528
+ * pre-formatted data. Every Lily class ending in `-view` is a span/div
529
+ * that renders a value the user shouldn't edit.
530
+ *
531
+ * The exclusions (`theme-view`, `split-view`)
532
+ * are larger containers that don't fit the chip pattern — they're
533
+ * styled separately further down.
534
+ * ====================================================================== */
535
+
536
+ :where(
537
+ [class$="-view"]:not(.theme-view):not(
538
+ .split-view
539
+ )
540
+ ) {
541
+ display: inline-block;
542
+ padding: 0.125rem 0.5rem;
543
+ font-family: var(--lily-font-mono); /* fixed-width for alignment */
544
+ color: var(--lily-text);
545
+ background-color: var(--lily-surface-alt);
546
+ border: var(--border) solid var(--lily-border);
547
+ border-radius: var(--lily-radius-md);
548
+ line-height: 1.4;
549
+ }
550
+
551
+ /* Dates / ranges are human-readable, not codes — switch back to body font. */
552
+ :where(.date-time-view, .date-range, .review-date) {
553
+ font-family: var(--lily-font-body);
554
+ }
555
+
556
+ /* Theme-view — small label + colour swatch composition. */
557
+ :where(.theme-view) {
558
+ display: inline-flex;
559
+ align-items: center;
560
+ gap: 0.5rem;
561
+ padding: 0.25rem 0.5rem;
562
+ color: var(--lily-text);
563
+ }
564
+
565
+ /* ==========================================================================
566
+ * Buttons
567
+ * --------------------------------------------------------------------------
568
+ * Every class ending in `-button` and the bare `.button` share one
569
+ * baseline. Hamburger-menu is excluded because it's an icon-only square
570
+ * with its own minimal frame styling (further down).
571
+ * ====================================================================== */
572
+
573
+ /* Baseline button — primary-coloured pill. Variants override colour or
574
+ shape below. */
575
+ :where(.button, [class$="-button"]:not(.hamburger-menu-button)) {
576
+ appearance: none;
577
+ display: inline-flex;
578
+ align-items: center;
579
+ justify-content: center;
580
+ gap: 0.5rem; /* leave room for an icon + label */
581
+ min-height: 2.5rem; /* WCAG target-size guidance */
582
+ padding: 0.5rem 1rem;
583
+ font: inherit;
584
+ font-family: var(--lily-font-body);
585
+ font-weight: 600;
586
+ line-height: 1.2;
587
+ color: var(--color-primary-content);
588
+ background-color: var(--color-primary);
589
+ border: var(--border) solid transparent;
590
+ border-radius: var(--lily-radius-md);
591
+ cursor: pointer;
592
+ user-select: none; /* prevent text drag-select on double-click */
593
+ text-decoration: none;
594
+ transition:
595
+ background-color 120ms ease,
596
+ color 120ms ease,
597
+ border-color 120ms ease;
598
+ }
599
+
600
+ /* Hover — shift to the secondary token for a subtle change of state. */
601
+ :where(.button, [class$="-button"]):hover:not(:disabled):not(
602
+ [aria-disabled="true"]
603
+ ) {
604
+ background-color: var(--color-secondary);
605
+ color: var(--color-secondary-content);
606
+ }
607
+
608
+ /* Disabled — half opacity, not-allowed cursor. */
609
+ :where(.button, [class$="-button"]):disabled,
610
+ :where(.button, [class$="-button"])[aria-disabled="true"] {
611
+ opacity: 0.5;
612
+ cursor: not-allowed;
613
+ }
614
+
615
+ /* Pressed (toggle/switch active state) — re-use the secondary colour. */
616
+ :where(.button, [class$="-button"])[aria-pressed="true"] {
617
+ background-color: var(--color-secondary);
618
+ color: var(--color-secondary-content);
619
+ }
620
+
621
+ /* --- Button variants ---------------------------------------------------- */
622
+
623
+ /* Icon button — square; aspect-ratio keeps it from squashing when the
624
+ icon font changes size. */
625
+ :where(.icon-button) {
626
+ padding: 0.5rem;
627
+ min-width: 2.5rem;
628
+ aspect-ratio: 1;
629
+ }
630
+
631
+ /* Floating action button — pinned to the bottom-right of the viewport.
632
+ Uses logical inset properties so it flips correctly under RTL. */
633
+ :where(.float-button) {
634
+ position: fixed;
635
+ inset-block-end: 1rem;
636
+ inset-inline-end: 1rem;
637
+ box-shadow: var(--lily-shadow-lg);
638
+ border-radius: 999px;
639
+ width: 3.5rem;
640
+ height: 3.5rem;
641
+ padding: 0;
642
+ }
643
+
644
+ /* Split button — extra padding-end for the dropdown caret. */
645
+ :where(.split-button) {
646
+ padding-inline-end: 0.5rem;
647
+ }
648
+
649
+ /* Download button — stacks the action label over a hint (filesize, etc.). */
650
+ :where(.download-button) {
651
+ flex-direction: column;
652
+ align-items: flex-start;
653
+ text-align: start;
654
+ }
655
+
656
+ /* Clipboard-copy — muted style because it's auxiliary, not primary. */
657
+ :where(.clipboard-copy-button) {
658
+ background-color: var(--lily-surface-alt);
659
+ color: var(--lily-text);
660
+ border-color: var(--lily-border-strong);
661
+ }
662
+
663
+ /* Switch button — iOS-style toggle pill. The track is the sunken
664
+ surface; the thumb is drawn by the consumer (or via ::before in
665
+ higher-specificity overrides). */
666
+ :where(.switch-button) {
667
+ position: relative;
668
+ width: 2.75rem;
669
+ min-width: 2.75rem;
670
+ height: 1.5rem;
671
+ min-height: 1.5rem;
672
+ padding: 0;
673
+ background-color: var(--lily-surface-sunken);
674
+ border-radius: 999px;
675
+ }
676
+ :where(
677
+ .switch-button[aria-pressed="true"],
678
+ .switch-button[aria-checked="true"]
679
+ ) {
680
+ background-color: var(--color-primary);
681
+ }
682
+
683
+ /* Toggle button — when un-pressed, render as a muted secondary chip. */
684
+ :where(.toggle-button[aria-pressed="false"]) {
685
+ background-color: var(--lily-surface-alt);
686
+ color: var(--lily-text);
687
+ border-color: var(--lily-border-strong);
688
+ }
689
+
690
+ /* Link-style "button" — for back/skip/go-to controls that look like
691
+ text links but use the button machinery for keyboard semantics. */
692
+ :where(
693
+ .back-link,
694
+ .skip-link,
695
+ .go-to-top,
696
+ .go-to-next-section,
697
+ .go-to-previous-section
698
+ ) {
699
+ background-color: transparent;
700
+ color: var(--color-primary);
701
+ border-color: transparent;
702
+ text-decoration: underline;
703
+ text-underline-offset: 0.2em;
704
+ }
705
+
706
+ /* Skip-link — visually hidden off-screen until focused, then snaps to the
707
+ top-left of the page. Required pattern for keyboard accessibility
708
+ (WCAG 2.4.1 bypass blocks). */
709
+ :where(.skip-link) {
710
+ position: absolute;
711
+ top: -3rem;
712
+ left: 0.5rem;
713
+ padding: 0.5rem 1rem;
714
+ background-color: var(--color-primary);
715
+ color: var(--color-primary-content);
716
+ text-decoration: none;
717
+ border-radius: var(--lily-radius-md);
718
+ z-index: 9999;
719
+ }
720
+ :where(.skip-link:focus) {
721
+ top: 0.5rem;
722
+ }
723
+
724
+ /* Button group — horizontal cluster of related actions. */
725
+ :where(.button-group) {
726
+ display: inline-flex;
727
+ gap: 0.25rem;
728
+ }
729
+
730
+ /* --- Bars of buttons (action / menu / tab / task / tool) ---------------- */
731
+
732
+ :where(.action-bar, .menu-bar, .tab-bar, .task-bar, .tool-bar) {
733
+ display: flex;
734
+ align-items: center;
735
+ gap: 0.25rem;
736
+ padding: 0.5rem;
737
+ background-color: var(--lily-surface-alt);
738
+ border: var(--border) solid var(--lily-border);
739
+ border-radius: var(--lily-radius-md);
740
+ }
741
+ /* Children of bars render flush with the bar — transparent background,
742
+ no border, so the bar's surface acts as the visual frame. */
743
+ :where(
744
+ .action-bar-button,
745
+ .menu-bar-button,
746
+ .tab-bar-button,
747
+ .task-bar-button,
748
+ .tool-bar-button
749
+ ) {
750
+ background-color: transparent;
751
+ color: var(--lily-text);
752
+ border-color: transparent;
753
+ }
754
+ /* Selected tab — explicit primary colour. */
755
+ :where(.tab-bar-button[aria-selected="true"]) {
756
+ background-color: var(--color-primary);
757
+ color: var(--color-primary-content);
758
+ }
759
+ /* Panel revealed by the active tab. */
760
+ :where(.tab-panel) {
761
+ padding: 1rem;
762
+ background-color: var(--lily-surface);
763
+ border: var(--border) solid var(--lily-border);
764
+ border-radius: var(--lily-radius-md);
765
+ }
766
+
767
+ /* ==========================================================================
768
+ * Links, anchors, link-like
769
+ * --------------------------------------------------------------------------
770
+ * Generic `-link` styling — primary-coloured underlined text. The
771
+ * exclusions (`skip-link`, `back-link`) are styled as buttons above.
772
+ * ====================================================================== */
773
+
774
+ :where([class$="-link"]:not(.skip-link):not(.back-link)) {
775
+ color: var(--color-primary);
776
+ text-decoration: underline;
777
+ text-underline-offset: 0.15em;
778
+ }
779
+ /* Hover — thicker underline for a subtle affordance cue. */
780
+ :where([class$="-link"]):hover {
781
+ text-decoration-thickness: 2px;
782
+ }
783
+ /* Visited — secondary token. */
784
+ :where([class$="-link"]):visited {
785
+ color: var(--color-secondary);
786
+ }
787
+
788
+ /* ==========================================================================
789
+ * Lists & list items
790
+ * --------------------------------------------------------------------------
791
+ * Lily wraps `<ol>` / `<ul>` in role-specific classes (CheckList, DoList,
792
+ * etc.). The baseline kills the default browser bullet styling and zeroes
793
+ * margins so consumers can compose lists inside other components.
794
+ * ====================================================================== */
795
+
796
+ /* Baseline — kill bullets and the indent gutter on every list class. */
797
+ :where(
798
+ .accordion-list,
799
+ .breadcrumb-list,
800
+ .chat-list,
801
+ .check-list,
802
+ .collection-list,
803
+ .contents-list,
804
+ .description-list,
805
+ .document-list,
806
+ .do-list,
807
+ .dont-list,
808
+ .icon-list,
809
+ .pagination-list,
810
+ .section-list,
811
+ .step-list,
812
+ .summary-list,
813
+ .task-list,
814
+ .timeline-list,
815
+ .tour-list,
816
+ .tree-list,
817
+ .validation-list
818
+ ) {
819
+ list-style: none;
820
+ margin: 0;
821
+ padding: 0;
822
+ }
823
+
824
+ /* Baseline — every list-item gets a thin hairline rule between rows. */
825
+ :where(
826
+ .accordion-list-item,
827
+ .breadcrumb-list-item,
828
+ .chat-list-item,
829
+ .check-list-item,
830
+ .collection-list-item,
831
+ .contents-list-item,
832
+ .description-list-item,
833
+ .document-list-item,
834
+ .do-list-item,
835
+ .dont-list-item,
836
+ .icon-list-item,
837
+ .pagination-list-item,
838
+ .section-list-item,
839
+ .step-list-item,
840
+ .summary-list-item,
841
+ .task-list-item,
842
+ .timeline-list-item,
843
+ .tour-list-item,
844
+ .tree-list-item,
845
+ .validation-list-item
846
+ ) {
847
+ padding: 0.5rem 0;
848
+ border-bottom: var(--border) solid var(--lily-border);
849
+ }
850
+
851
+ /* --- Breadcrumb --------------------------------------------------------- */
852
+ /* Horizontal list separated by a "/" pseudo-element between items. */
853
+ :where(.breadcrumb-list) {
854
+ display: flex;
855
+ flex-wrap: wrap;
856
+ gap: 0.5rem;
857
+ align-items: center;
858
+ }
859
+ :where(.breadcrumb-list-item) {
860
+ border: 0; /* override the generic list-item bottom-border */
861
+ padding: 0;
862
+ display: inline-flex;
863
+ align-items: center;
864
+ }
865
+ /* Slash separator generated between adjacent items, not before the first. */
866
+ :where(.breadcrumb-list-item + .breadcrumb-list-item)::before {
867
+ content: "/";
868
+ margin-inline-end: 0.5rem;
869
+ color: var(--lily-text-muted);
870
+ }
871
+ /* Final crumb (the current page) — muted because it isn't a link. */
872
+ :where(.breadcrumb-list-item[data-current], .breadcrumb-list-item[aria-current="page"]) {
873
+ color: var(--lily-text-muted);
874
+ }
875
+
876
+ /* --- Pagination --------------------------------------------------------- */
877
+ :where(.pagination-list) {
878
+ display: flex;
879
+ flex-wrap: wrap;
880
+ gap: 0.25rem;
881
+ align-items: center;
882
+ }
883
+ :where(.pagination-list-item) {
884
+ border: 0;
885
+ padding: 0;
886
+ }
887
+ /* Pagination link — square-ish chip with a generous tap target. */
888
+ :where(.pagination-link) {
889
+ display: inline-flex;
890
+ align-items: center;
891
+ justify-content: center;
892
+ min-width: 2.5rem;
893
+ padding: 0.5rem 0.75rem;
894
+ border-radius: var(--lily-radius-md);
895
+ text-decoration: none;
896
+ }
897
+ /* Current page — filled with the primary brand colour. */
898
+ :where(.pagination-link[aria-current="page"]) {
899
+ background-color: var(--color-primary);
900
+ color: var(--color-primary-content);
901
+ }
902
+
903
+ /* --- Marker glyphs ------------------------------------------------------ */
904
+ /* Check / do — green ✓; dont — red ✗. The `::marker` pseudo-element
905
+ only works on list-items with the default list-style display, so the
906
+ generic baseline above intentionally kept that. */
907
+ :where(.check-list-item)::marker {
908
+ content: "✓ ";
909
+ color: var(--color-success);
910
+ }
911
+ :where(.do-list-item)::marker {
912
+ content: "✓ ";
913
+ color: var(--color-success);
914
+ }
915
+ :where(.dont-list-item)::marker {
916
+ content: "✗ ";
917
+ color: var(--lily-error-text);
918
+ }
919
+
920
+ /* --- Tree --------------------------------------------------------------- */
921
+ /* Nested tree lists indent their children. */
922
+ :where(.tree-list .tree-list) {
923
+ padding-inline-start: 1.25rem;
924
+ }
925
+ :where(.tree-list-item) {
926
+ border: 0;
927
+ }
928
+
929
+ /* --- Step list ---------------------------------------------------------- */
930
+ /* CSS counter draws the step number in a circular badge. */
931
+ :where(.step-list) {
932
+ counter-reset: lily-step;
933
+ }
934
+ :where(.step-list-item) {
935
+ counter-increment: lily-step;
936
+ display: flex;
937
+ align-items: flex-start;
938
+ gap: 0.75rem;
939
+ }
940
+ :where(.step-list-item)::before {
941
+ content: counter(lily-step);
942
+ flex: 0 0 auto;
943
+ display: inline-flex;
944
+ align-items: center;
945
+ justify-content: center;
946
+ width: 1.75rem;
947
+ height: 1.75rem;
948
+ border-radius: 50%;
949
+ background-color: var(--lily-surface-sunken);
950
+ color: var(--lily-text);
951
+ font-weight: 600;
952
+ }
953
+ /* Step status variants — colour-coded badge fill. */
954
+ :where(.step-list-item[data-status="finished"])::before {
955
+ background-color: var(--color-success);
956
+ color: var(--color-success-content);
957
+ }
958
+ :where(.step-list-item[data-status="in-progress"])::before {
959
+ background-color: var(--color-primary);
960
+ color: var(--color-primary-content);
961
+ }
962
+ :where(.step-list-item[data-status="error"])::before {
963
+ background-color: var(--color-error);
964
+ color: var(--color-error-content);
965
+ }
966
+
967
+ /* --- Task list ---------------------------------------------------------- */
968
+ /* Flex row so the checkbox aligns with its label. */
969
+ :where(.task-list-item) {
970
+ display: flex;
971
+ align-items: center;
972
+ gap: 0.5rem;
973
+ }
974
+
975
+ /* --- Timeline ----------------------------------------------------------- */
976
+ /* Vertical rule down the left, with a dot per event. */
977
+ :where(.timeline-list-item) {
978
+ position: relative;
979
+ padding-inline-start: 1.5rem;
980
+ border-left: var(--border) solid var(--lily-border-strong);
981
+ margin-inline-start: 0.5rem;
982
+ }
983
+ :where(.timeline-list-item)::before {
984
+ content: "";
985
+ position: absolute;
986
+ inset-inline-start: -0.4rem;
987
+ inset-block-start: 0.65rem;
988
+ width: 0.7rem;
989
+ height: 0.7rem;
990
+ border-radius: 50%;
991
+ background-color: var(--color-primary);
992
+ }
993
+
994
+ /* --- Validation list — pass / fail / pending status colours ------------ */
995
+ :where(.validation-list-item[data-status="passed"]) {
996
+ color: var(--color-success);
997
+ }
998
+ :where(.validation-list-item[data-status="failed"]) {
999
+ color: var(--lily-error-text);
1000
+ }
1001
+ :where(.validation-list-item[data-status="pending"]) {
1002
+ color: var(--lily-text-muted);
1003
+ }
1004
+
1005
+ /* --- Description list (semantic <dl>) ----------------------------------- */
1006
+ /* Two-column grid: term in the left column, description in the right. */
1007
+ :where(.description-list) {
1008
+ display: grid;
1009
+ grid-template-columns: minmax(min-content, max-content) 1fr;
1010
+ column-gap: 1rem;
1011
+ row-gap: 0.5rem;
1012
+ }
1013
+ /* `display: contents` lets the dt/dd children participate directly in
1014
+ the parent grid without an intermediate row wrapper. */
1015
+ :where(.description-list-item) {
1016
+ display: contents;
1017
+ border: 0;
1018
+ }
1019
+ :where(.description-list dt) {
1020
+ font-weight: 600;
1021
+ }
1022
+
1023
+ /* --- Summary list (three-column key/value/action grid) ------------------ */
1024
+ :where(.summary-list) {
1025
+ display: grid;
1026
+ grid-template-columns: minmax(0, max-content) 1fr auto;
1027
+ column-gap: 1rem;
1028
+ row-gap: 0.5rem;
1029
+ }
1030
+ :where(.summary-list-item) {
1031
+ display: contents;
1032
+ border: 0;
1033
+ }
1034
+
1035
+ /* --- Icon / collection / document lists — flex rows with leading icon -- */
1036
+ :where(.icon-list-item, .collection-list-item, .document-list-item) {
1037
+ display: flex;
1038
+ align-items: flex-start;
1039
+ gap: 0.75rem;
1040
+ }
1041
+
1042
+ /* --- Navs --------------------------------------------------------------- */
1043
+ /* Nav wrappers are layout-neutral by default — the inner list handles
1044
+ layout, the nav just provides the landmark semantics. */
1045
+ :where(
1046
+ .accordion-nav,
1047
+ .breadcrumb-nav,
1048
+ .chat-nav,
1049
+ .contents-nav,
1050
+ .pagination-nav,
1051
+ .section-nav,
1052
+ .tree-nav,
1053
+ .navigation-menu
1054
+ ) {
1055
+ display: block;
1056
+ padding: 0;
1057
+ }
1058
+
1059
+ /* ==========================================================================
1060
+ * Tables (plain, data, calendar, kanban, gantt)
1061
+ * --------------------------------------------------------------------------
1062
+ * Every Lily table variant shares the same baseline: full-width, collapsed
1063
+ * borders, body font. Variants override below for specialised behaviour
1064
+ * (kanban columns, gantt bars, calendar selection).
1065
+ * ====================================================================== */
1066
+
1067
+ :where(.table, .data-table, .calendar-table, .kanban-table, .gantt-table) {
1068
+ width: 100%;
1069
+ border-collapse: collapse; /* shared edges between cells */
1070
+ background-color: var(--lily-surface);
1071
+ color: var(--lily-text);
1072
+ font-family: var(--lily-font-body);
1073
+ }
1074
+
1075
+ /* Thead gets the alt surface so it visually anchors above the body. */
1076
+ :where(
1077
+ .table-head,
1078
+ .data-table-head,
1079
+ .calendar-table-head,
1080
+ .kanban-table-head,
1081
+ .gantt-table-thead
1082
+ ) {
1083
+ background-color: var(--lily-surface-alt);
1084
+ }
1085
+
1086
+ /* Header cells — bold, slightly tighter padding, with a 2px under-rule. */
1087
+ :where(
1088
+ .table-th,
1089
+ .data-table-th,
1090
+ .calendar-table-th,
1091
+ .kanban-table-th,
1092
+ .gantt-table-th
1093
+ ) {
1094
+ padding: 0.625rem 0.75rem;
1095
+ text-align: start;
1096
+ font-weight: 600;
1097
+ border-bottom: 2px solid var(--lily-border-strong);
1098
+ }
1099
+
1100
+ /* Body cells — top-aligned (multi-line content reads better when the
1101
+ baseline is at the top, not centred). */
1102
+ :where(
1103
+ .table-td,
1104
+ .data-table-td,
1105
+ .calendar-table-td,
1106
+ .kanban-table-td,
1107
+ .gantt-table-td
1108
+ ) {
1109
+ padding: 0.625rem 0.75rem;
1110
+ border-bottom: var(--border) solid var(--lily-border);
1111
+ vertical-align: top;
1112
+ }
1113
+
1114
+ /* Row hover — subtle highlight for scanning long tables. */
1115
+ :where(
1116
+ .table-row,
1117
+ .data-table-row,
1118
+ .calendar-table-row,
1119
+ .kanban-table-row,
1120
+ .gantt-table-tr
1121
+ ):hover {
1122
+ background-color: var(--lily-surface-alt);
1123
+ }
1124
+
1125
+ /* Data-table filter form — chips that sit above the table. */
1126
+ :where(.data-filter-form) {
1127
+ display: flex;
1128
+ flex-wrap: wrap;
1129
+ gap: 0.75rem;
1130
+ padding: 1rem;
1131
+ background-color: var(--lily-surface-alt);
1132
+ border-radius: var(--lily-radius-md);
1133
+ }
1134
+
1135
+ /* --- Calendar specifics ------------------------------------------------- */
1136
+ /* Selected date — filled with the primary brand colour. */
1137
+ :where(.calendar-table-td[aria-selected="true"]) {
1138
+ background-color: var(--color-primary);
1139
+ color: var(--color-primary-content);
1140
+ }
1141
+ /* Today — outlined and bold, never filled, so a selected non-today date still
1142
+ reads as "selected". The ring takes the cell's own text colour (currentColor),
1143
+ so it holds its contrast on a light page, a dark page and a selected cell
1144
+ alike, and the bold number says it without colour. */
1145
+ :where(.calendar-table-td[aria-current="date"]) {
1146
+ outline: 2px solid currentColor;
1147
+ outline-offset: -3px;
1148
+ font-weight: 700;
1149
+ }
1150
+ /* Fit a phone: day cells shrink and wrap instead of forcing the grid wider. */
1151
+ :where(.calendar-table) {
1152
+ table-layout: fixed;
1153
+ }
1154
+ :where(.calendar-table-td, .calendar-table-th) {
1155
+ min-width: 0;
1156
+ overflow-wrap: anywhere;
1157
+ }
1158
+
1159
+ /* --- Data grid: toolbar, sorting, pagination, column visibility, pinned columns ----- */
1160
+ :where(.data-grid-toolbar) {
1161
+ display: flex;
1162
+ flex-wrap: wrap;
1163
+ gap: var(--lily-space-sm);
1164
+ align-items: center;
1165
+ margin-block-end: var(--lily-space-sm);
1166
+ }
1167
+ :where(.data-grid-search, .data-grid-search-field) {
1168
+ padding: var(--lily-space-xs) var(--lily-space-sm);
1169
+ border: var(--border) solid var(--lily-border);
1170
+ border-radius: var(--lily-radius-sm);
1171
+ background-color: var(--lily-surface);
1172
+ color: var(--lily-text);
1173
+ font: inherit;
1174
+ }
1175
+ /* A sortable header is a button that looks like its text; the arrow follows aria-sort,
1176
+ so the state is in the markup for assistive technology and on screen for everyone. */
1177
+ :where(.data-grid-sort-button) {
1178
+ padding: 0;
1179
+ border: 0;
1180
+ background: none;
1181
+ color: inherit;
1182
+ font: inherit;
1183
+ text-align: start;
1184
+ cursor: pointer;
1185
+ }
1186
+ :where(.data-grid-sort-button:focus-visible) {
1187
+ outline: 2px solid var(--color-accent);
1188
+ outline-offset: 2px;
1189
+ }
1190
+ :where([aria-sort="none"] .data-grid-sort-button)::after {
1191
+ content: " \21C5";
1192
+ opacity: 0.5;
1193
+ }
1194
+ :where([aria-sort="ascending"] .data-grid-sort-button)::after {
1195
+ content: " \25B2";
1196
+ }
1197
+ :where([aria-sort="descending"] .data-grid-sort-button)::after {
1198
+ content: " \25BC";
1199
+ }
1200
+ :where(.data-grid-pagination) {
1201
+ display: flex;
1202
+ flex-wrap: wrap;
1203
+ gap: var(--lily-space-sm);
1204
+ align-items: center;
1205
+ justify-content: flex-end;
1206
+ margin-block-start: var(--lily-space-sm);
1207
+ }
1208
+ :where(.data-grid-page-previous, .data-grid-page-next, .data-grid-export-button) {
1209
+ padding: var(--lily-space-xs) var(--lily-space-md);
1210
+ border: var(--border) solid var(--lily-border);
1211
+ border-radius: var(--lily-radius-sm);
1212
+ background-color: var(--lily-surface);
1213
+ color: var(--lily-text);
1214
+ font: inherit;
1215
+ cursor: pointer;
1216
+ }
1217
+ :where(.data-grid-page-previous:disabled, .data-grid-page-next:disabled, .data-grid-export-button:disabled) {
1218
+ opacity: 0.5;
1219
+ cursor: not-allowed;
1220
+ }
1221
+ :where(.data-grid-column-visibility) {
1222
+ display: flex;
1223
+ flex-wrap: wrap;
1224
+ gap: var(--lily-space-sm);
1225
+ margin: 0;
1226
+ padding: var(--lily-space-xs) var(--lily-space-sm);
1227
+ border: var(--border) solid var(--lily-border);
1228
+ border-radius: var(--lily-radius-sm);
1229
+ }
1230
+ /* Pinned columns stay put while the rest scroll; the offset is logical, so it flips in right-to-left. */
1231
+ :where([data-pinned="start"]) {
1232
+ position: sticky;
1233
+ inset-inline-start: 0;
1234
+ z-index: 1;
1235
+ background-color: var(--lily-surface);
1236
+ }
1237
+ :where([data-pinned="end"]) {
1238
+ position: sticky;
1239
+ inset-inline-end: 0;
1240
+ z-index: 1;
1241
+ background-color: var(--lily-surface);
1242
+ }
1243
+ :where(th[data-pinned]) {
1244
+ z-index: 2;
1245
+ }
1246
+ /* A missing value: dimmed, and the grid's emptyValue text (for example a dash) reads as absence. */
1247
+ :where(.data-table-td[data-empty]) {
1248
+ opacity: 0.7;
1249
+ }
1250
+
1251
+ /* --- Kanban specifics --------------------------------------------------- */
1252
+ /* Each column renders as a `<td>`; widen the cells so cards have room. */
1253
+ :where(.kanban-table) {
1254
+ display: table;
1255
+ }
1256
+ :where(.kanban-table-td) {
1257
+ vertical-align: top;
1258
+ background-color: var(--lily-surface);
1259
+ min-width: 14rem;
1260
+ }
1261
+
1262
+ /* Dragging a card: the column it would land in is outlined; one that refuses it
1263
+ (canMove, allowedColumns, a full strict WIP limit) is striped and shows the
1264
+ not-allowed cursor, so "you can't drop here" is visible before the drop. */
1265
+ :where(.kanban-table-td[data-drop-target]) {
1266
+ outline: 2px dashed var(--color-accent);
1267
+ outline-offset: -3px;
1268
+ }
1269
+ :where(.kanban-table-td[data-drop-blocked]) {
1270
+ outline: 2px solid var(--color-error);
1271
+ outline-offset: -3px;
1272
+ cursor: not-allowed;
1273
+ background-image: repeating-linear-gradient(135deg, transparent 0 6px, color-mix(in srgb, var(--color-error) 18%, transparent) 6px 8px);
1274
+ }
1275
+
1276
+ /* --- Gantt specifics ---------------------------------------------------- */
1277
+ /* Cells marked as bars are filled with the primary colour to render the
1278
+ schedule bar inline within the grid. */
1279
+ :where(.gantt-table-td[data-state="bar"]) {
1280
+ background-color: var(--color-primary);
1281
+ }
1282
+
1283
+ /* Weekend, holiday and time-off shading (Gantt chart and calendar). A weekend
1284
+ is tinted; a day marked as a holiday or time off (data-unavailable /
1285
+ data-range) is striped as well, so no day is told apart by colour alone;
1286
+ a column only partly unavailable gets a bar under its header. */
1287
+ :where(.gantt-table-th[data-weekend], .gantt-table-td[data-weekend], .calendar-table-th[data-weekend], .calendar-table-td[data-weekend]) {
1288
+ background-color: color-mix(in srgb, var(--lily-surface) 92%, var(--lily-text));
1289
+ }
1290
+ :where(.gantt-table-th[data-unavailable], .gantt-table-td[data-unavailable], .calendar-table-td[data-range]) {
1291
+ background-image: repeating-linear-gradient(135deg, transparent 0 4px, color-mix(in srgb, var(--lily-text) 14%, transparent) 4px 5px);
1292
+ }
1293
+ :where(.gantt-table-th[data-partly-unavailable]) {
1294
+ box-shadow: inset 0 -3px 0 color-mix(in srgb, var(--lily-text) 45%, transparent);
1295
+ }
1296
+
1297
+ /* ==========================================================================
1298
+ * Cards, panels, surfaces
1299
+ * --------------------------------------------------------------------------
1300
+ * Family of generic content containers. Differ mostly in elevation
1301
+ * (shadow depth) and accent colour.
1302
+ * ====================================================================== */
1303
+
1304
+ :where(
1305
+ .card,
1306
+ .feature-card,
1307
+ .product-card,
1308
+ .care-card,
1309
+ .hover-card,
1310
+ .panel,
1311
+ .floating-panel,
1312
+ .success-panel
1313
+ ) {
1314
+ display: block;
1315
+ padding: 1rem;
1316
+ background-color: var(--lily-surface);
1317
+ color: var(--lily-text);
1318
+ border: var(--border) solid var(--lily-border);
1319
+ border-radius: var(--lily-radius-lg);
1320
+ box-shadow: var(--lily-shadow-sm);
1321
+ }
1322
+ /* Feature card — generous padding because it leads the page. */
1323
+ :where(.feature-card) {
1324
+ padding: 1.5rem;
1325
+ }
1326
+ /* Hover card — popover-like; lift it with the medium shadow. */
1327
+ :where(.hover-card) {
1328
+ box-shadow: var(--lily-shadow-md);
1329
+ }
1330
+ /* Floating panel — pinned overlay; largest shadow. */
1331
+ :where(.floating-panel) {
1332
+ box-shadow: var(--lily-shadow-lg);
1333
+ }
1334
+ /* Success panel — green border + faint green tint. */
1335
+ :where(.success-panel) {
1336
+ border-color: var(--color-success);
1337
+ background-color: color-mix(
1338
+ in oklch,
1339
+ var(--color-success) 8%,
1340
+ var(--lily-surface)
1341
+ );
1342
+ }
1343
+ /* Care card urgency variants — coloured left rail (urgent/non-urgent/
1344
+ primary). Pattern adapted from the NHS care-card guidance. */
1345
+ :where(.care-card[data-urgency="urgent"]) {
1346
+ border-color: var(--color-error);
1347
+ border-left-width: 0.375rem;
1348
+ }
1349
+ :where(.care-card[data-urgency="non-urgent"]) {
1350
+ border-color: var(--color-info);
1351
+ border-left-width: 0.375rem;
1352
+ }
1353
+ :where(.care-card[data-urgency="primary"]) {
1354
+ border-color: var(--color-primary);
1355
+ border-left-width: 0.375rem;
1356
+ }
1357
+
1358
+ /* ==========================================================================
1359
+ * Banners, alerts, callouts, notifications
1360
+ * --------------------------------------------------------------------------
1361
+ * Family of messages with a coloured left rail. The rail colour is set
1362
+ * per severity so the message reads at a glance.
1363
+ * ====================================================================== */
1364
+
1365
+ /* Baseline — alert/banner/callout/notification all share the same frame. */
1366
+ :where(
1367
+ .alert,
1368
+ .banner,
1369
+ .announcement-banner,
1370
+ .phase-banner,
1371
+ .government-banner,
1372
+ .medical-banner,
1373
+ .super-banner,
1374
+ .information-callout,
1375
+ .warning-callout,
1376
+ .summary-box,
1377
+ .notification,
1378
+ .toast,
1379
+ .sonner
1380
+ ) {
1381
+ display: flex;
1382
+ align-items: flex-start;
1383
+ gap: 0.75rem;
1384
+ padding: 1rem;
1385
+ background-color: var(--lily-surface-alt);
1386
+ color: var(--lily-text);
1387
+ border: var(--border) solid var(--lily-border);
1388
+ border-inline-start-width: 0.375rem; /* the coloured rail */
1389
+ border-radius: var(--lily-radius-md);
1390
+ }
1391
+
1392
+ /* Severity variants — rail + faint background tint. */
1393
+ :where(
1394
+ .alert[data-severity="info"],
1395
+ .information-callout,
1396
+ .announcement-banner
1397
+ ) {
1398
+ border-inline-start-color: var(--color-info);
1399
+ background-color: color-mix(
1400
+ in oklch,
1401
+ var(--color-info) 8%,
1402
+ var(--lily-surface)
1403
+ );
1404
+ }
1405
+ :where(.alert[data-severity="success"]) {
1406
+ border-inline-start-color: var(--color-success);
1407
+ background-color: color-mix(
1408
+ in oklch,
1409
+ var(--color-success) 8%,
1410
+ var(--lily-surface)
1411
+ );
1412
+ }
1413
+ :where(.alert[data-severity="warning"], .warning-callout, .phase-banner) {
1414
+ border-inline-start-color: var(--color-warning);
1415
+ background-color: color-mix(
1416
+ in oklch,
1417
+ var(--color-warning) 12%,
1418
+ var(--lily-surface)
1419
+ );
1420
+ }
1421
+ :where(
1422
+ .alert[data-severity="error"],
1423
+ .error-message,
1424
+ .error-summary,
1425
+ .super-banner
1426
+ ) {
1427
+ border-inline-start-color: var(--color-error);
1428
+ background-color: color-mix(
1429
+ in oklch,
1430
+ var(--color-error) 8%,
1431
+ var(--lily-surface)
1432
+ );
1433
+ }
1434
+ /* Medical banner — primary brand colour. */
1435
+ :where(.medical-banner) {
1436
+ border-inline-start-color: var(--color-primary);
1437
+ }
1438
+ /* Medical banner inner boxes — danger / advice variants. */
1439
+ :where(.medical-banner-box-for-danger) {
1440
+ border-color: var(--color-error);
1441
+ background-color: color-mix(
1442
+ in oklch,
1443
+ var(--color-error) 8%,
1444
+ var(--lily-surface)
1445
+ );
1446
+ }
1447
+ :where(.medical-banner-box-for-advice) {
1448
+ border-color: var(--color-info);
1449
+ background-color: color-mix(
1450
+ in oklch,
1451
+ var(--color-info) 8%,
1452
+ var(--lily-surface)
1453
+ );
1454
+ }
1455
+ /* Government banner — solid neutral surface (US-government / GOV.UK
1456
+ style: a chrome bar at the top of the page identifying the site
1457
+ as an official property). No coloured rail. */
1458
+ :where(.government-banner) {
1459
+ background-color: var(--color-neutral);
1460
+ color: var(--color-neutral-content);
1461
+ border-color: transparent;
1462
+ border-inline-start-color: transparent;
1463
+ }
1464
+ /* Summary box — primary-coloured rail; used for "answer at a glance"
1465
+ callouts on long forms (e.g. cost summary). */
1466
+ :where(.summary-box) {
1467
+ border-color: var(--color-primary);
1468
+ border-inline-start-width: 0.375rem;
1469
+ }
1470
+
1471
+ /* Banner boxes — chrome strip variants used inside header areas. */
1472
+ :where(.banner-box, .medical-banner-box) {
1473
+ display: flex;
1474
+ align-items: center;
1475
+ gap: 0.75rem;
1476
+ padding: 0.75rem 1rem;
1477
+ }
1478
+
1479
+ /* Sticky promo — stays at one edge of the viewport on scroll. A bottom banner must not also pin its top edge: with
1480
+ the component's own bottom: 0 it would stretch over the whole page and take every click. */
1481
+ :where(.sticky-promo-banner) {
1482
+ position: sticky;
1483
+ z-index: 10;
1484
+ }
1485
+
1486
+ :where(.sticky-promo-banner:not([data-position="bottom"])) {
1487
+ inset-block-start: 0;
1488
+ }
1489
+
1490
+ /* Error message — inline form-validation message. */
1491
+ :where(.error-message) {
1492
+ color: var(--lily-error-text);
1493
+ font-weight: 600;
1494
+ }
1495
+ /* Error summary — heavyweight 4px border (GOV.UK pattern) so users
1496
+ scrolling back to the top of a form see it immediately. */
1497
+ :where(.error-summary) {
1498
+ padding: 1rem;
1499
+ border: 0.25rem solid var(--color-error);
1500
+ }
1501
+
1502
+ /* Toast / Sonner — floating notifications; medium elevation. */
1503
+ :where(.toast, .sonner) {
1504
+ box-shadow: var(--lily-shadow-md);
1505
+ }
1506
+ /* In-page notification — small elevation. */
1507
+ :where(.notification) {
1508
+ box-shadow: var(--lily-shadow-sm);
1509
+ }
1510
+
1511
+ /* ==========================================================================
1512
+ * Forms
1513
+ * --------------------------------------------------------------------------
1514
+ * Form / field / fieldset layout primitives. Stack vertically with a
1515
+ * consistent gap so consumer markup stays brief.
1516
+ * ====================================================================== */
1517
+
1518
+ :where(.form, .editable-form) {
1519
+ display: flex;
1520
+ flex-direction: column;
1521
+ gap: 1rem;
1522
+ }
1523
+ /* Field — wraps a single label + input + hint/error trio. */
1524
+ :where(.field) {
1525
+ display: flex;
1526
+ flex-direction: column;
1527
+ gap: 0.375rem;
1528
+ }
1529
+ /* Fieldset — semantic <fieldset>; border + padded inset, legend bold. */
1530
+ :where(.fieldset) {
1531
+ border: var(--border) solid var(--lily-border);
1532
+ border-radius: var(--lily-radius-md);
1533
+ padding: 1rem;
1534
+ }
1535
+ :where(.fieldset > legend) {
1536
+ padding-inline: 0.5rem;
1537
+ font-weight: 600;
1538
+ }
1539
+ /* Label — bold and dark; consumers can override per-form. */
1540
+ :where(.label) {
1541
+ font-weight: 600;
1542
+ color: var(--lily-text);
1543
+ }
1544
+ /* Hint — small muted helptext under the input. */
1545
+ :where(.hint) {
1546
+ color: var(--lily-text-muted);
1547
+ font-size: 0.875rem;
1548
+ }
1549
+ /* Character counter — same colour/size as hint but right-aligned. */
1550
+ :where(.character-counter) {
1551
+ color: var(--lily-text-muted);
1552
+ font-size: 0.875rem;
1553
+ text-align: end;
1554
+ }
1555
+
1556
+ /* ==========================================================================
1557
+ * Layout & containers
1558
+ * ====================================================================== */
1559
+
1560
+ /* Page-width containers. */
1561
+ :where(
1562
+ .container,
1563
+ .container-with-fixed-width,
1564
+ .container-with-fluid-width
1565
+ ) {
1566
+ display: block;
1567
+ width: 100%;
1568
+ margin-inline: auto;
1569
+ padding-inline: var(--space-fluid-gutter);
1570
+ box-sizing: border-box;
1571
+ }
1572
+ /* Fixed-width — caps at a comfortable reading width (~1152px). */
1573
+ :where(.container-with-fixed-width) {
1574
+ max-width: 72rem;
1575
+ }
1576
+ /* Fluid-width — fills the viewport edge-to-edge. */
1577
+ :where(.container-with-fluid-width) {
1578
+ max-width: none;
1579
+ }
1580
+ /* Reset the inline padding when a container should butt against the edge. */
1581
+ :where(.padding-reset) {
1582
+ padding-inline: 0;
1583
+ }
1584
+
1585
+ /* Flex / grid / masonry primitives. */
1586
+ :where(.flex-stack) {
1587
+ display: flex;
1588
+ flex-direction: column;
1589
+ gap: var(--space-fluid-section);
1590
+ }
1591
+ :where(.grid) {
1592
+ display: grid;
1593
+ gap: var(--space-fluid-section);
1594
+ }
1595
+ :where(.masonry) {
1596
+ column-count: 3;
1597
+ column-gap: 1rem;
1598
+ }
1599
+ /* Horizontal-rule replacement that respects theme borders. */
1600
+ :where(.separator) {
1601
+ border: 0;
1602
+ border-top: var(--border) solid var(--lily-border);
1603
+ margin: 1rem 0;
1604
+ }
1605
+ /* Generic content tile. */
1606
+ :where(.tile) {
1607
+ display: block;
1608
+ padding: 1rem;
1609
+ background-color: var(--lily-surface);
1610
+ border: var(--border) solid var(--lily-border);
1611
+ border-radius: var(--lily-radius-md);
1612
+ }
1613
+ /* Sidebar — secondary-surface panel; sits beside main content. */
1614
+ :where(.sidebar) {
1615
+ display: block;
1616
+ background-color: var(--lily-surface-alt);
1617
+ padding: 1rem;
1618
+ border-radius: var(--lily-radius-md);
1619
+ }
1620
+ /* Aspect-ratio box — 16:9 default; consumers override `aspect-ratio`. */
1621
+ :where(.aspect-ratio-container) {
1622
+ position: relative;
1623
+ display: block;
1624
+ aspect-ratio: 16 / 9;
1625
+ overflow: hidden;
1626
+ border-radius: var(--lily-radius-md);
1627
+ }
1628
+ /* Framer — bordered box for embeds / illustrations. */
1629
+ :where(.framer) {
1630
+ padding: 1rem;
1631
+ border: var(--border) solid var(--lily-border);
1632
+ border-radius: var(--lily-radius-md);
1633
+ }
1634
+ /* Article-layout — declares column-width tokens that content-block reads.
1635
+ Pattern lifted from the Reuters Graphics components system. */
1636
+ :where(.article-layout) {
1637
+ --content-width-narrower: 20.625rem; /* 330px */
1638
+ --content-width-narrow: 31.875rem; /* 510px */
1639
+ --content-width-normal: 41.25rem; /* 660px (default reading width) */
1640
+ --content-width-wide: 58.125rem; /* 930px (chart escape) */
1641
+ --content-width-wider: 75rem; /* 1200px (full-bleed escape) */
1642
+ display: block;
1643
+ }
1644
+ /* Content-block — reads the column-width tokens; switch via data-width. */
1645
+ :where(.content-block) {
1646
+ max-width: var(--content-width-normal);
1647
+ margin-inline: auto;
1648
+ padding-inline: var(--space-fluid-gutter);
1649
+ box-sizing: border-box;
1650
+ }
1651
+ :where(.content-block[data-width="narrower"]) {
1652
+ max-width: var(--content-width-narrower);
1653
+ }
1654
+ :where(.content-block[data-width="narrow"]) {
1655
+ max-width: var(--content-width-narrow);
1656
+ }
1657
+ :where(.content-block[data-width="wide"]) {
1658
+ max-width: var(--content-width-wide);
1659
+ }
1660
+ :where(.content-block[data-width="wider"]) {
1661
+ max-width: var(--content-width-wider);
1662
+ }
1663
+
1664
+ /* --- Grail layout (5-region responsive page shell) --------------------- */
1665
+ /* Header spans the top; left-aside / main / right-aside fill the middle;
1666
+ footer spans the bottom. The collapse as the window narrows is in the shared
1667
+ container section at the end of this file. */
1668
+ :where(.grail-layout) {
1669
+ display: grid;
1670
+ min-height: 100vh;
1671
+ grid-template-areas:
1672
+ "header header header"
1673
+ "left main right"
1674
+ "footer footer footer";
1675
+ grid-template-rows: auto 1fr auto;
1676
+ grid-template-columns: minmax(0, 1fr) minmax(0, 3fr) minmax(0, 1fr);
1677
+ background-color: var(--lily-surface);
1678
+ color: var(--lily-text);
1679
+ }
1680
+ :where(.grail-layout-top-header) {
1681
+ grid-area: header;
1682
+ }
1683
+ :where(.grail-layout-left-aside) {
1684
+ grid-area: left;
1685
+ }
1686
+ :where(.grail-layout-center-main) {
1687
+ grid-area: main;
1688
+ }
1689
+ :where(.grail-layout-right-aside) {
1690
+ grid-area: right;
1691
+ }
1692
+ :where(.grail-layout-bottom-footer) {
1693
+ grid-area: footer;
1694
+ }
1695
+ /* Header / footer chrome — secondary surface, padded. */
1696
+ :where(.grail-layout-top-header, .grail-layout-bottom-footer) {
1697
+ background-color: var(--lily-surface-alt);
1698
+ padding: 1rem;
1699
+ }
1700
+ /* Asides — secondary surface, padded. */
1701
+ :where(.grail-layout-left-aside, .grail-layout-right-aside) {
1702
+ padding: 1rem;
1703
+ background-color: var(--lily-surface-alt);
1704
+ }
1705
+ /* Main content area — primary surface, more generous padding. */
1706
+ :where(.grail-layout-center-main) {
1707
+ padding: var(--space-fluid-gutter);
1708
+ }
1709
+
1710
+ /* Standalone semantic header / footer landmarks. */
1711
+ :where(.header) {
1712
+ display: block;
1713
+ padding: 1rem;
1714
+ background-color: var(--lily-surface);
1715
+ border-bottom: var(--border) solid var(--lily-border);
1716
+ }
1717
+ :where(.footer) {
1718
+ display: block;
1719
+ padding: 1rem;
1720
+ background-color: var(--lily-surface-alt);
1721
+ border-top: var(--border) solid var(--lily-border);
1722
+ }
1723
+
1724
+ /* ==========================================================================
1725
+ * Dialogs, popovers, overlays
1726
+ * --------------------------------------------------------------------------
1727
+ * Modal and floating UI. Backdrop is a blur + 50% black overlay; dialog
1728
+ * frames are rounded with the largest shadow.
1729
+ * ====================================================================== */
1730
+
1731
+ /* Native <dialog> + variant types. */
1732
+ :where(
1733
+ .dialog,
1734
+ .alert-dialog,
1735
+ .timeout-dialog,
1736
+ .popconfirm-dialog,
1737
+ .file-dialog
1738
+ ) {
1739
+ background-color: var(--lily-surface);
1740
+ color: var(--lily-text);
1741
+ border: var(--border) solid var(--lily-border);
1742
+ border-radius: var(--lily-radius-lg);
1743
+ padding: 1.5rem;
1744
+ box-shadow: var(--lily-shadow-lg);
1745
+ max-width: min(40rem, 90vw); /* cap on big screens, fit on small */
1746
+ }
1747
+ /* Backdrop — dim the page and blur slightly so focus stays on the dialog. */
1748
+ :where(
1749
+ .dialog::backdrop,
1750
+ .alert-dialog::backdrop,
1751
+ .timeout-dialog::backdrop,
1752
+ .popconfirm-dialog::backdrop,
1753
+ .file-dialog::backdrop
1754
+ ) {
1755
+ background-color: color-mix(in oklch, black 50%, transparent);
1756
+ backdrop-filter: blur(2px);
1757
+ }
1758
+ /* Alert dialog — red rail so the user knows it's destructive. */
1759
+ :where(.alert-dialog) {
1760
+ border-color: var(--color-error);
1761
+ border-inline-start-width: 0.375rem;
1762
+ }
1763
+
1764
+ /* Popover / tooltip family — small floating labels. */
1765
+ :where(.popover, .popup, .tooltip, .coachmark, .contextual-help) {
1766
+ display: block;
1767
+ padding: 0.5rem 0.75rem;
1768
+ background-color: var(--color-neutral);
1769
+ color: var(--color-neutral-content);
1770
+ border-radius: var(--lily-radius-md);
1771
+ box-shadow: var(--lily-shadow-md);
1772
+ font-size: 0.875rem;
1773
+ max-width: 20rem;
1774
+ }
1775
+ /* Popover / popup — surface-coloured (more substantial than a tooltip). */
1776
+ :where(.popover, .popup) {
1777
+ background-color: var(--lily-surface);
1778
+ color: var(--lily-text);
1779
+ border: var(--border) solid var(--lily-border);
1780
+ max-width: 24rem;
1781
+ }
1782
+ /* Coachmark — primary-coloured border so it reads as a tutorial hint. */
1783
+ :where(.coachmark) {
1784
+ border: 2px solid var(--color-primary);
1785
+ }
1786
+
1787
+ /* Full-page overlay container — dims everything beneath a high-z child. */
1788
+ :where(.overlay-container) {
1789
+ position: fixed;
1790
+ inset: 0;
1791
+ background-color: color-mix(in oklch, black 50%, transparent);
1792
+ z-index: 100;
1793
+ }
1794
+
1795
+ /* Drawers — side panels (right edge by default; RTL-aware via logical
1796
+ properties). Sit above the overlay container (z-index 101). */
1797
+ :where(.drawer, .slide-out-drawer, .sheet) {
1798
+ /* These are <dialog> elements opened with showModal() (P18-T6): undo the
1799
+ browser's centred-box defaults so the panel docks to the edge. */
1800
+ margin: 0;
1801
+ border: 0;
1802
+ inset-inline-start: auto;
1803
+ max-block-size: none;
1804
+ max-inline-size: none;
1805
+ block-size: 100%;
1806
+ box-sizing: border-box;
1807
+ position: fixed;
1808
+ inset-block: 0;
1809
+ inset-inline-end: 0;
1810
+ width: min(28rem, 90vw);
1811
+ background-color: var(--lily-surface);
1812
+ color: var(--lily-text);
1813
+ border-inline-start: var(--border) solid var(--lily-border);
1814
+ box-shadow: var(--lily-shadow-lg);
1815
+ padding: 1rem;
1816
+ z-index: 101;
1817
+ }
1818
+
1819
+ :where(.drawer, .slide-out-drawer, .sheet)::backdrop {
1820
+ background-color: color-mix(in oklch, black 50%, transparent);
1821
+ }
1822
+
1823
+ /* ==========================================================================
1824
+ * Menus
1825
+ * ====================================================================== */
1826
+
1827
+ /* Menu containers — surface-coloured popover with thin border + sm shadow. */
1828
+ :where(.menu, .context-menu, .dropdown-menu, .tree-menu, .command) {
1829
+ display: block;
1830
+ background-color: var(--lily-surface);
1831
+ color: var(--lily-text);
1832
+ border: var(--border) solid var(--lily-border);
1833
+ border-radius: var(--lily-radius-md);
1834
+ padding: 0.25rem;
1835
+ box-shadow: var(--lily-shadow-sm);
1836
+ list-style: none;
1837
+ margin: 0;
1838
+ }
1839
+ /* Menu items — flex row with icon + label slot. */
1840
+ :where(.menu-item, .context-menu-item) {
1841
+ display: flex;
1842
+ align-items: center;
1843
+ gap: 0.5rem;
1844
+ padding: 0.5rem 0.75rem;
1845
+ border-radius: var(--lily-radius-md);
1846
+ cursor: pointer;
1847
+ }
1848
+ /* Highlight on hover / aria-selected — alt surface. */
1849
+ :where(
1850
+ .menu-item:hover,
1851
+ .context-menu-item:hover,
1852
+ .menu-item[aria-selected="true"]
1853
+ ) {
1854
+ background-color: var(--lily-surface-alt);
1855
+ }
1856
+ /* Menu group — section within a menu. The aria-label becomes a header. */
1857
+ :where(.menu-group) {
1858
+ padding: 0.25rem 0;
1859
+ }
1860
+ :where(.menu-group::before) {
1861
+ content: attr(aria-label);
1862
+ display: block;
1863
+ padding: 0.25rem 0.75rem;
1864
+ color: var(--lily-text-muted);
1865
+ font-size: 0.75rem;
1866
+ text-transform: uppercase;
1867
+ letter-spacing: 0.05em;
1868
+ }
1869
+ /* Cascader / navigation-menu — horizontal flex row of menu columns. */
1870
+ :where(.cascader, .navigation-menu) {
1871
+ display: flex;
1872
+ gap: 0.25rem;
1873
+ }
1874
+ /* Hamburger — bordered icon-button. */
1875
+ :where(.hamburger-menu > button) {
1876
+ display: inline-flex;
1877
+ align-items: center;
1878
+ justify-content: center;
1879
+ padding: 0.5rem;
1880
+ background: transparent;
1881
+ border: var(--border) solid var(--lily-border-strong);
1882
+ border-radius: var(--lily-radius-md);
1883
+ color: var(--lily-text);
1884
+ cursor: pointer;
1885
+ }
1886
+
1887
+ /* ==========================================================================
1888
+ * Segments, toggles, groups
1889
+ * ====================================================================== */
1890
+
1891
+ /* Generic group containers — inline-flex, wrap, small gap. */
1892
+ :where(
1893
+ .segment-group,
1894
+ .toggle-group,
1895
+ .checkbox-group,
1896
+ .radio-group,
1897
+ .dial-group,
1898
+ .tag-group,
1899
+ .avatar-group,
1900
+ .mutually-exclusive
1901
+ ) {
1902
+ display: inline-flex;
1903
+ gap: 0.25rem;
1904
+ flex-wrap: wrap;
1905
+ }
1906
+ /* Segment group / toggle group get an inset background (iOS-segmented
1907
+ control aesthetic). */
1908
+ :where(.segment-group, .toggle-group) {
1909
+ padding: 0.25rem;
1910
+ background-color: var(--lily-surface-alt);
1911
+ border-radius: var(--lily-radius-md);
1912
+ }
1913
+ /* Segment items — transparent until pressed; then primary fill. */
1914
+ :where(.segment-group-item) {
1915
+ padding: 0.375rem 0.75rem;
1916
+ background: transparent;
1917
+ border: 0;
1918
+ border-radius: var(--lily-radius-md);
1919
+ color: var(--lily-text);
1920
+ cursor: pointer;
1921
+ }
1922
+ :where(
1923
+ .segment-group-item[aria-pressed="true"],
1924
+ .segment-group-item[aria-selected="true"]
1925
+ ) {
1926
+ background-color: var(--color-primary);
1927
+ color: var(--color-primary-content);
1928
+ }
1929
+ /* Avatar group — overlap each subsequent avatar by 0.5rem, with a
1930
+ contrast ring for separation. */
1931
+ :where(.avatar-group > *) {
1932
+ margin-inline-start: -0.5rem;
1933
+ border: 2px solid var(--lily-surface);
1934
+ }
1935
+
1936
+ /* ==========================================================================
1937
+ * Progress, loading
1938
+ * ====================================================================== */
1939
+
1940
+ /* Native <progress> / <meter> / bar variant — short, pill-shaped track. */
1941
+ :where(.progress, .progress-bar, .meter) {
1942
+ display: block;
1943
+ width: 100%;
1944
+ height: 0.5rem;
1945
+ appearance: none;
1946
+ background-color: var(--lily-surface-sunken);
1947
+ border: 0;
1948
+ border-radius: 999px;
1949
+ overflow: hidden;
1950
+ }
1951
+ /* Vendor pseudo-elements: track + value. */
1952
+ :where(.progress::-webkit-progress-bar, .meter::-webkit-meter-bar) {
1953
+ background-color: var(--lily-surface-sunken);
1954
+ }
1955
+ :where(
1956
+ .progress::-webkit-progress-value,
1957
+ .meter::-webkit-meter-optimum-value
1958
+ ) {
1959
+ background-color: var(--color-primary);
1960
+ }
1961
+ :where(.progress::-moz-progress-bar) {
1962
+ background-color: var(--color-primary);
1963
+ }
1964
+
1965
+ /* Progress circle — conic-gradient renders the sweep based on the
1966
+ --progress custom property (0–100). */
1967
+ :where(.progress-circle) {
1968
+ display: inline-block;
1969
+ width: 2rem;
1970
+ height: 2rem;
1971
+ border-radius: 50%;
1972
+ background: conic-gradient(
1973
+ var(--color-primary) calc(var(--progress, 0) * 1%),
1974
+ var(--lily-surface-sunken) 0
1975
+ );
1976
+ }
1977
+ /* Spinner / loading — classic CSS rotating partial-ring. */
1978
+ :where(.progress-spinner, .loading) {
1979
+ display: inline-block;
1980
+ width: 1.5rem;
1981
+ height: 1.5rem;
1982
+ border: 3px solid var(--lily-surface-sunken);
1983
+ border-top-color: var(--color-primary);
1984
+ border-radius: 50%;
1985
+ animation: lily-spin 0.8s linear infinite;
1986
+ }
1987
+ @keyframes lily-spin {
1988
+ to {
1989
+ transform: rotate(360deg);
1990
+ }
1991
+ }
1992
+ /* Skeleton — shimmering placeholder while content is loading. */
1993
+ :where(.skeleton) {
1994
+ display: block;
1995
+ background: linear-gradient(
1996
+ 90deg,
1997
+ var(--lily-surface-alt) 0%,
1998
+ var(--lily-surface-sunken) 50%,
1999
+ var(--lily-surface-alt) 100%
2000
+ );
2001
+ background-size: 200% 100%;
2002
+ animation: lily-skeleton 1.4s ease-in-out infinite;
2003
+ border-radius: var(--lily-radius-md);
2004
+ min-height: 1rem;
2005
+ }
2006
+ @keyframes lily-skeleton {
2007
+ 0% {
2008
+ background-position: 200% 0;
2009
+ }
2010
+ 100% {
2011
+ background-position: -200% 0;
2012
+ }
2013
+ }
2014
+ /* Respect prefers-reduced-motion — kill the perpetual animations.
2015
+ (WCAG 2.3.3 Animation from interactions.) */
2016
+ @media (prefers-reduced-motion: reduce) {
2017
+ :where(.progress-spinner, .loading, .skeleton, .beach-ball) {
2018
+ animation: none;
2019
+ }
2020
+ }
2021
+
2022
+ /* ==========================================================================
2023
+ * Avatars, badges, tags, status
2024
+ * ====================================================================== */
2025
+
2026
+ /* Avatar — circular tile (2.5rem) with sunken background as the
2027
+ fallback colour when no image is provided. */
2028
+ :where(.avatar) {
2029
+ display: inline-flex;
2030
+ align-items: center;
2031
+ justify-content: center;
2032
+ width: 2.5rem;
2033
+ height: 2.5rem;
2034
+ border-radius: 50%;
2035
+ background-color: var(--lily-surface-sunken);
2036
+ color: var(--lily-text);
2037
+ overflow: hidden;
2038
+ font-weight: 600;
2039
+ }
2040
+ :where(.avatar-image) {
2041
+ width: 100%;
2042
+ height: 100%;
2043
+ object-fit: cover;
2044
+ display: block;
2045
+ }
2046
+ /* Avatar fallback text — uppercase initials. */
2047
+ :where(.avatar-text) {
2048
+ text-transform: uppercase;
2049
+ }
2050
+
2051
+ /* Badge / flair / tag family — small pill-shaped labels. */
2052
+ :where(.badge, .flair, .status-tag, .tag, .ai-label) {
2053
+ display: inline-flex;
2054
+ align-items: center;
2055
+ gap: 0.25rem;
2056
+ padding: 0.125rem 0.5rem;
2057
+ font-size: 0.75rem;
2058
+ font-weight: 600;
2059
+ line-height: 1.5;
2060
+ background-color: var(--color-neutral);
2061
+ color: var(--color-neutral-content);
2062
+ border-radius: 999px;
2063
+ }
2064
+ /* Tag — neutral chip with a visible border (so it's not confused with
2065
+ a status badge). */
2066
+ :where(.tag) {
2067
+ background-color: var(--lily-surface-alt);
2068
+ color: var(--lily-text);
2069
+ border: var(--border) solid var(--lily-border);
2070
+ }
2071
+ /* AI label — accent colour so AI-generated content is visually marked. */
2072
+ :where(.ai-label) {
2073
+ background-color: var(--color-accent);
2074
+ color: var(--color-accent-content);
2075
+ }
2076
+ /* Status tags — pre-baked colour for each status. */
2077
+ :where(.status-tag[data-status="success"]) {
2078
+ background-color: var(--color-success);
2079
+ color: var(--color-success-content);
2080
+ }
2081
+ :where(.status-tag[data-status="warning"]) {
2082
+ background-color: var(--color-warning);
2083
+ color: var(--color-warning-content);
2084
+ }
2085
+ :where(.status-tag[data-status="error"]) {
2086
+ background-color: var(--color-error);
2087
+ color: var(--color-error-content);
2088
+ }
2089
+ :where(.status-tag[data-status="info"]) {
2090
+ background-color: var(--color-info);
2091
+ color: var(--color-info-content);
2092
+ }
2093
+
2094
+ /* Status light — a coloured dot followed by a label. */
2095
+ :where(.status-light) {
2096
+ display: inline-flex;
2097
+ align-items: center;
2098
+ gap: 0.375rem;
2099
+ }
2100
+ :where(.status-light)::before {
2101
+ content: "";
2102
+ display: inline-block;
2103
+ width: 0.625rem;
2104
+ height: 0.625rem;
2105
+ border-radius: 50%;
2106
+ background-color: var(--color-neutral);
2107
+ }
2108
+ :where(.status-light[data-status="success"])::before {
2109
+ background-color: var(--color-success);
2110
+ }
2111
+ :where(.status-light[data-status="warning"])::before {
2112
+ background-color: var(--color-warning);
2113
+ }
2114
+ :where(.status-light[data-status="error"])::before {
2115
+ background-color: var(--color-error);
2116
+ }
2117
+ :where(.status-light[data-status="info"])::before {
2118
+ background-color: var(--color-info);
2119
+ }
2120
+
2121
+ /* ==========================================================================
2122
+ * Pickers, ratings, theme
2123
+ * ====================================================================== */
2124
+
2125
+ /* Picker baseline — inline row of selectable chips/buttons. */
2126
+ :where(
2127
+ .color-picker,
2128
+ .five-face-rating-picker,
2129
+ .five-star-rating-picker,
2130
+ .net-promoter-score-picker,
2131
+ .red-amber-green-picker,
2132
+ .red-orange-yellow-green-blue-picker,
2133
+ .emoji-character-picker,
2134
+ .calendar-range-picker
2135
+ ) {
2136
+ display: inline-flex;
2137
+ align-items: center;
2138
+ gap: 0.25rem;
2139
+ padding: 0.25rem;
2140
+ background-color: var(--lily-surface);
2141
+ border: var(--border) solid var(--lily-border);
2142
+ border-radius: var(--lily-radius-md);
2143
+ }
2144
+ /* Picker buttons — small alt-surface chips. */
2145
+ :where(
2146
+ .color-picker-button,
2147
+ .five-face-rating-picker-button,
2148
+ .five-star-rating-picker-button,
2149
+ .net-promoter-score-picker-button,
2150
+ .red-amber-green-picker-button,
2151
+ .red-orange-yellow-green-blue-picker-button
2152
+ ) {
2153
+ background-color: var(--lily-surface-alt);
2154
+ color: var(--lily-text);
2155
+ border: var(--border) solid var(--lily-border);
2156
+ min-width: 2rem;
2157
+ min-height: 2rem;
2158
+ padding: 0.25rem 0.5rem;
2159
+ }
2160
+ /* Color picker — circular swatch. */
2161
+ :where(.color-picker-button) {
2162
+ width: 1.75rem;
2163
+ height: 1.75rem;
2164
+ padding: 0;
2165
+ border-radius: 50%;
2166
+ }
2167
+ /* Filled stars — warning (gold) colour for both viewer and pressed state. */
2168
+ :where(
2169
+ .five-star-rating-view,
2170
+ .five-star-rating-picker-button[aria-pressed="true"]
2171
+ ) {
2172
+ color: var(--color-warning);
2173
+ }
2174
+ /* RAG (red/amber/green) — semantic colour per status. */
2175
+ :where(
2176
+ .red-amber-green-view[data-status="red"],
2177
+ .red-amber-green-picker-button[data-status="red"]
2178
+ ) {
2179
+ background-color: var(--color-error);
2180
+ color: var(--color-error-content);
2181
+ }
2182
+ :where(
2183
+ .red-amber-green-view[data-status="amber"],
2184
+ .red-amber-green-picker-button[data-status="amber"]
2185
+ ) {
2186
+ background-color: var(--color-warning);
2187
+ color: var(--color-warning-content);
2188
+ }
2189
+ :where(
2190
+ .red-amber-green-view[data-status="green"],
2191
+ .red-amber-green-picker-button[data-status="green"]
2192
+ ) {
2193
+ background-color: var(--color-success);
2194
+ color: var(--color-success-content);
2195
+ }
2196
+
2197
+ /* Native <select> styling — drop the UA chrome and draw our own caret
2198
+ via two stacked linear-gradients. */
2199
+ :where(select.theme-select, .select, .select-with-extras, .tree-select) {
2200
+ width: 100%;
2201
+ min-height: 2.5rem;
2202
+ padding: 0.5rem 2rem 0.5rem 0.75rem; /* extra right padding for caret */
2203
+ font: inherit;
2204
+ background-color: var(--lily-surface);
2205
+ color: var(--lily-text);
2206
+ border: var(--border) solid var(--lily-border-strong);
2207
+ border-radius: var(--lily-radius-md);
2208
+ appearance: none;
2209
+ background-image:
2210
+ linear-gradient(45deg, transparent 50%, var(--lily-text) 50%),
2211
+ linear-gradient(135deg, var(--lily-text) 50%, transparent 50%);
2212
+ background-position:
2213
+ calc(100% - 1.1rem) 1.1rem,
2214
+ calc(100% - 0.7rem) 1.1rem;
2215
+ background-size: 0.4rem 0.4rem;
2216
+ background-repeat: no-repeat;
2217
+ }
2218
+ /* Options inherit the select colours so the dropdown matches the input. */
2219
+ :where(.option, .theme-select-option) {
2220
+ background-color: var(--lily-surface);
2221
+ color: var(--lily-text);
2222
+ }
2223
+
2224
+ /* The four *-picker helpers are an icon button that opens a popup,
2225
+ so they are styled as a popup rather than a form field. No :has()
2226
+ guard is needed any more: before the July 2026 rename these shared
2227
+ the `.theme-select` hook with the catalog theme-select component
2228
+ and had to be told apart. Distinct names, no collision. */
2229
+ :where(
2230
+ .theme-picker,
2231
+ .locale-picker,
2232
+ .text-size-picker,
2233
+ .time-zone-picker,
2234
+ .search-picker,
2235
+ .link-picker,
2236
+ .menu-picker,
2237
+ .settings-picker,
2238
+ .share-picker,
2239
+ .date-time-picker
2240
+ ) {
2241
+ position: relative;
2242
+ display: inline-block;
2243
+ }
2244
+
2245
+ :where(
2246
+ .theme-picker-button,
2247
+ .locale-picker-button,
2248
+ .text-size-picker-button,
2249
+ .time-zone-picker-button,
2250
+ .search-picker-button,
2251
+ .link-picker-button,
2252
+ .menu-picker-button,
2253
+ .settings-picker-button,
2254
+ .share-picker-button,
2255
+ .date-time-picker-button
2256
+ ) {
2257
+ display: inline-flex;
2258
+ align-items: center;
2259
+ justify-content: center;
2260
+ min-width: 2.5rem;
2261
+ min-height: 2.5rem;
2262
+ padding: var(--lily-space-xs);
2263
+ font: inherit;
2264
+ line-height: 1;
2265
+ color: var(--lily-text);
2266
+ background-color: var(--lily-surface);
2267
+ border: var(--border) solid var(--lily-border-strong);
2268
+ border-radius: var(--lily-radius-md);
2269
+ cursor: pointer;
2270
+ }
2271
+
2272
+ /* The icon is decorative (aria-hidden); the button's aria-label
2273
+ carries the accessible name. Reversed 2026-09-16: five of the
2274
+ six picker icons (theme, locale, text-size, motion, share) are
2275
+ now bundled SVGs, not Unicode glyphs. Each ships its own
2276
+ explicit width="1.05rem" height="1.05rem" on the <svg> element,
2277
+ so unlike a font-dependent character glyph -- where different
2278
+ glyphs ink different fractions of their em box, needing a
2279
+ per-glyph optical correction -- every icon in this family
2280
+ already inks the same visual size by construction (the icon's
2281
+ own author controls its viewBox). This rule only supplies flex
2282
+ centering; override the icon's own width/height if you need a
2283
+ different size. */
2284
+ :where(
2285
+ .theme-picker-icon,
2286
+ .locale-picker-icon,
2287
+ .text-size-picker-icon,
2288
+ .time-zone-picker-icon,
2289
+ .motion-picker-icon,
2290
+ .link-picker-icon,
2291
+ .menu-picker-icon,
2292
+ .settings-picker-icon,
2293
+ .share-picker-icon,
2294
+ .search-picker-icon
2295
+ ) {
2296
+ display: inline-flex;
2297
+ align-items: center;
2298
+ justify-content: center;
2299
+ }
2300
+
2301
+ /* date-time-picker's calendar icon is still a Unicode glyph
2302
+ (U+1F4C5 + U+FE0E) -- out of scope for the 2026-09-16 SVG-icon
2303
+ reversal, since date-time-picker was not part of that change
2304
+ (see AGENTS/helpers.md) -- so it keeps the font-size-based
2305
+ sizing the other five used to share.
2306
+
2307
+ U+1F4C5 calendar inks 1.000 of its em box (max extent) against
2308
+ U+25D1 half-circle's reference 0.850 (the pre-reversal
2309
+ baseline, still valid here since this is the same glyph
2310
+ measurement, unchanged): 0.850/1.000 ≈ 0.85, shipped as 0.845.
2311
+ A colour-emoji font paints every one of its glyphs into one
2312
+ identical em square, so globe, calendar, rocket and heart all
2313
+ measure the same ink box -- any future emoji helper icon can
2314
+ reuse this factor without re-measuring.
2315
+
2316
+ (Measuring also confirms the U+FE0E text-presentation selector is
2317
+ ignored here: the bare and VS-15 forms are metrically identical. It
2318
+ stays in the source as a hint to the platforms that do honour it.) */
2319
+ :where(.date-time-picker-icon) {
2320
+ display: inline-flex;
2321
+ align-items: center;
2322
+ justify-content: center;
2323
+ font-size: calc(1.25em * var(--lily-picker-icon-scale, 1));
2324
+ line-height: 1;
2325
+ --lily-picker-icon-scale: 0.845;
2326
+ }
2327
+
2328
+ /* The share list holds real links and a button rather than options,
2329
+ so its items get the same padding without the option selectors. */
2330
+ :where(.share-picker-list) > :where(.share-picker-list-item) {
2331
+ list-style: none;
2332
+ }
2333
+
2334
+ :where(.share-picker-target, .share-picker-copy) {
2335
+ display: block;
2336
+ width: 100%;
2337
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2338
+ font: inherit;
2339
+ text-align: start;
2340
+ color: var(--lily-text);
2341
+ background: none;
2342
+ border: 0;
2343
+ border-radius: var(--lily-radius-sm);
2344
+ cursor: pointer;
2345
+ }
2346
+
2347
+ :where(.share-picker-target:hover, .share-picker-copy:hover) {
2348
+ background-color: var(--lily-surface-alt);
2349
+ }
2350
+
2351
+ /* link-picker's list holds real page links (a disclosure, not a menu),
2352
+ so its items get the share list's treatment; the current page is
2353
+ marked by aria-current and shown in bold. */
2354
+ :where(.link-picker-list) > :where(.link-picker-list-item) {
2355
+ list-style: none;
2356
+ }
2357
+
2358
+ :where(.link-picker-link) {
2359
+ display: block;
2360
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2361
+ color: var(--lily-text);
2362
+ text-decoration: none;
2363
+ border-radius: var(--lily-radius-sm);
2364
+ cursor: pointer;
2365
+ }
2366
+
2367
+ :where(.link-picker-link:hover) {
2368
+ background-color: var(--lily-surface-alt);
2369
+ }
2370
+
2371
+ :where(.link-picker-link[aria-current="page"]) {
2372
+ font-weight: 600;
2373
+ background-color: var(--lily-surface-sunken);
2374
+ }
2375
+
2376
+ /* Copy feedback. Never display:none this — that silences the live
2377
+ region for screen-reader users. */
2378
+ :where(.share-picker-status) {
2379
+ margin: var(--lily-space-xs) 0 0;
2380
+ font-size: 0.875em;
2381
+ color: var(--lily-text-muted);
2382
+ }
2383
+
2384
+ /* The status paragraph is always rendered so the aria-live region
2385
+ exists before the first announcement (see above); empty, it still
2386
+ generates a line box and carries the margin-top set above, both of
2387
+ which push the button row out of alignment in a flex layout like
2388
+ picker-bar (found independently by two downstream consumers, each
2389
+ patching around it locally). :empty here matches a Svelte-rendered
2390
+ {status} of "" (a zero-length text node doesn't count as a child
2391
+ per the CSS spec), so this only applies while there is nothing to
2392
+ announce, and normal spacing returns the moment status has text. */
2393
+ :where(.share-picker-status:empty) {
2394
+ margin: 0;
2395
+ line-height: 0;
2396
+ }
2397
+
2398
+ /* Components added 2026-10-05: kbd-shortcut, show-more, thinking,
2399
+ empty-state, file-tree, one-time-password-input, multi-select, and
2400
+ the four calendar views. The calendar views reuse the
2401
+ calendar-table-* sub-element hooks, so they only need the table
2402
+ root rule. show-more's clamp is keyed on data-expanded (the
2403
+ component never sets an inline style); file-tree hides a closed
2404
+ folder's group via aria-expanded. */
2405
+ :where(.calendar-year-table, .calendar-month-table, .calendar-week-table, .calendar-day-table) {
2406
+ width: 100%;
2407
+ border-collapse: collapse;
2408
+ }
2409
+
2410
+ :where(.kbd-shortcut) {
2411
+ display: inline-flex;
2412
+ align-items: center;
2413
+ gap: var(--lily-space-xs);
2414
+ }
2415
+
2416
+ :where(.kbd-shortcut-key) {
2417
+ display: inline-block;
2418
+ padding: 0.125rem 0.4rem;
2419
+ font-family: var(--lily-font-mono);
2420
+ font-size: 0.85em;
2421
+ background-color: var(--lily-surface-alt);
2422
+ border: var(--border) solid var(--lily-border-strong);
2423
+ border-bottom-width: 2px;
2424
+ border-radius: var(--lily-radius-sm);
2425
+ }
2426
+
2427
+ :where(.show-more-content[data-expanded="false"]) {
2428
+ max-height: 6em;
2429
+ overflow: hidden;
2430
+ }
2431
+
2432
+ :where(.show-more-button) {
2433
+ margin-block-start: var(--lily-space-xs);
2434
+ padding: var(--lily-space-xs);
2435
+ font: inherit;
2436
+ color: inherit;
2437
+ text-decoration: underline;
2438
+ background: none;
2439
+ border: 0;
2440
+ cursor: pointer;
2441
+ }
2442
+
2443
+ :where(.thinking) {
2444
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2445
+ color: var(--lily-text-muted);
2446
+ border: var(--border) solid var(--lily-border-strong);
2447
+ border-radius: var(--lily-radius-md);
2448
+ }
2449
+
2450
+ :where(.thinking-summary) {
2451
+ cursor: pointer;
2452
+ }
2453
+
2454
+ :where(.thinking[data-streaming] .thinking-summary) {
2455
+ font-style: italic;
2456
+ }
2457
+
2458
+ :where(.empty-state) {
2459
+ padding: var(--lily-space-lg);
2460
+ text-align: center;
2461
+ color: var(--lily-text-muted);
2462
+ }
2463
+
2464
+ :where(.file-tree, .file-tree [role="group"]) {
2465
+ margin: 0;
2466
+ padding-inline-start: 0;
2467
+ list-style: none;
2468
+ }
2469
+
2470
+ :where(.file-tree [role="group"]) {
2471
+ padding-inline-start: var(--lily-space-md);
2472
+ }
2473
+
2474
+ :where(.file-tree [role="treeitem"]) {
2475
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2476
+ cursor: pointer;
2477
+ }
2478
+
2479
+ :where(.file-tree [role="treeitem"][aria-expanded="false"] > [role="group"]) {
2480
+ display: none;
2481
+ }
2482
+
2483
+ :where(.file-tree [role="treeitem"]:focus-visible) {
2484
+ outline: var(--lily-focus-ring);
2485
+ outline-offset: -2px;
2486
+ }
2487
+
2488
+ :where(.one-time-password-input, .multi-select, .multi-select-with-extras select) {
2489
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2490
+ font: inherit;
2491
+ color: var(--lily-text);
2492
+ background-color: var(--lily-surface);
2493
+ border: var(--border) solid var(--lily-border-strong);
2494
+ border-radius: var(--lily-radius-sm);
2495
+ }
2496
+
2497
+ :where(.one-time-password-input) {
2498
+ font-family: var(--lily-font-mono);
2499
+ letter-spacing: 0.25em;
2500
+ }
2501
+
2502
+ /* Chart containers added 2026-10-05 (gauge, heatmap, radar, sankey, pie,
2503
+ ring, funnel, candlestick, composed, choropleth, sunburst) join the
2504
+ existing five as block figures. Every chart's optional data-table
2505
+ wrapper sits below the graphic; the table itself inherits the
2506
+ catalog's table styling. */
2507
+ :where(
2508
+ .gauge-chart,
2509
+ .heatmap-chart,
2510
+ .radar-chart,
2511
+ .sankey-chart,
2512
+ .pie-chart,
2513
+ .ring-chart,
2514
+ .funnel-chart,
2515
+ .candlestick-chart,
2516
+ .composed-chart,
2517
+ .choropleth-chart,
2518
+ .sunburst-chart
2519
+ ) {
2520
+ display: block;
2521
+ margin: 0;
2522
+ }
2523
+
2524
+ :where(
2525
+ .area-chart-data-table,
2526
+ .bar-chart-data-table,
2527
+ .column-chart-data-table,
2528
+ .line-chart-data-table,
2529
+ .scatter-chart-data-table,
2530
+ .gauge-chart-data-table,
2531
+ .heatmap-chart-data-table,
2532
+ .radar-chart-data-table,
2533
+ .sankey-chart-data-table,
2534
+ .pie-chart-data-table,
2535
+ .ring-chart-data-table,
2536
+ .funnel-chart-data-table,
2537
+ .candlestick-chart-data-table,
2538
+ .composed-chart-data-table,
2539
+ .choropleth-chart-data-table,
2540
+ .sunburst-chart-data-table
2541
+ ) {
2542
+ margin-block-start: var(--lily-space-sm);
2543
+ overflow-x: auto;
2544
+ }
2545
+
2546
+ /* tool-call (2026-10-05): a bordered disclosure; the status word sits next
2547
+ to the name in the summary and is never the only cue. [data-status] is
2548
+ the consumer hook for a spinner or icon. */
2549
+ :where(.tool-call) {
2550
+ border: var(--border) solid var(--lily-border-strong);
2551
+ border-radius: var(--lily-radius-md);
2552
+ background-color: var(--lily-surface);
2553
+ color: var(--lily-text);
2554
+ }
2555
+
2556
+ :where(.tool-call-summary) {
2557
+ display: flex;
2558
+ align-items: center;
2559
+ gap: var(--lily-space-sm);
2560
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2561
+ cursor: pointer;
2562
+ }
2563
+
2564
+ :where(.tool-call-content) {
2565
+ display: grid;
2566
+ gap: var(--lily-space-xs);
2567
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2568
+ border-block-start: var(--border) solid var(--lily-border-strong);
2569
+ }
2570
+
2571
+ :where(.tool-call-name) {
2572
+ font-family: var(--lily-font-mono);
2573
+ }
2574
+
2575
+ :where(.tool-call-status) {
2576
+ color: var(--lily-text-muted);
2577
+ }
2578
+
2579
+ :where(.tool-call-input pre, .tool-call-output pre) {
2580
+ margin: 0;
2581
+ overflow-x: auto;
2582
+ font-family: var(--lily-font-mono);
2583
+ }
2584
+
2585
+ :where(.tool-call-error) {
2586
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2587
+ border: var(--border) solid var(--lily-border-strong);
2588
+ border-radius: var(--lily-radius-sm);
2589
+ font-weight: 600;
2590
+ }
2591
+
2592
+ /* mark (2026-10-05): the native <mark> highlight. The UA default is a
2593
+ fixed yellow; this uses the theme's own surface and text tokens so the
2594
+ pair keeps its contrast in every theme (including dark ones). */
2595
+ :where(.mark) {
2596
+ padding-inline: 0.15em;
2597
+ color: var(--lily-text);
2598
+ background-color: var(--lily-surface-alt);
2599
+ border-radius: var(--lily-radius-sm);
2600
+ box-shadow: 0 0 0 1px var(--lily-border-strong);
2601
+ }
2602
+
2603
+ /* chat-composer (2026-10-05): one row, textarea growing, one button. The
2604
+ button's visible word is its accessible name — hide it visually, never
2605
+ with display:none, if you replace it with an icon. */
2606
+ :where(.chat-composer) {
2607
+ display: flex;
2608
+ align-items: flex-end;
2609
+ gap: var(--lily-space-xs);
2610
+ }
2611
+
2612
+ :where(.chat-composer-input) {
2613
+ flex: 1;
2614
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2615
+ font: inherit;
2616
+ color: var(--lily-text);
2617
+ background-color: var(--lily-surface);
2618
+ border: var(--border) solid var(--lily-border-strong);
2619
+ border-radius: var(--lily-radius-md);
2620
+ resize: none;
2621
+ }
2622
+
2623
+ :where(.chat-composer-button) {
2624
+ min-width: 2.5rem;
2625
+ min-height: 2.5rem;
2626
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2627
+ font: inherit;
2628
+ color: var(--lily-text);
2629
+ background-color: var(--lily-surface);
2630
+ border: var(--border) solid var(--lily-border-strong);
2631
+ border-radius: var(--lily-radius-md);
2632
+ cursor: pointer;
2633
+ }
2634
+
2635
+ :where(.chat-composer-button:disabled) {
2636
+ cursor: not-allowed;
2637
+ opacity: 0.6;
2638
+ }
2639
+
2640
+ /* Picker tooltips (added 2026-10-04): a visual-only role="tooltip"
2641
+ element beside each picker's icon button, holding the same text as
2642
+ its aria-label. The component toggles [hidden]; position and look
2643
+ are here. It sits just below the button, in the inverse of the
2644
+ page colours so it never disappears into the surface. A ::before
2645
+ strip bridges the gap to the button so the pointer can travel onto
2646
+ the tooltip without it closing (WCAG 1.4.13, hoverable). */
2647
+ :where(.motion-picker) {
2648
+ position: relative;
2649
+ display: inline-block;
2650
+ }
2651
+
2652
+ :where(
2653
+ .theme-picker-tooltip,
2654
+ .locale-picker-tooltip,
2655
+ .text-size-picker-tooltip,
2656
+ .time-zone-picker-tooltip,
2657
+ .motion-picker-tooltip,
2658
+ .link-picker-tooltip,
2659
+ .menu-picker-tooltip,
2660
+ .settings-picker-tooltip,
2661
+ .share-picker-tooltip,
2662
+ .search-picker-tooltip,
2663
+ .date-time-picker-tooltip
2664
+ ) {
2665
+ position: absolute;
2666
+ z-index: 11;
2667
+ inset-inline-start: 0;
2668
+ inset-block-start: 100%;
2669
+ margin-block-start: var(--lily-space-xs);
2670
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2671
+ font-size: 0.875em;
2672
+ line-height: 1.3;
2673
+ white-space: nowrap;
2674
+ color: var(--lily-surface);
2675
+ background-color: var(--lily-text);
2676
+ border-radius: var(--lily-radius-sm);
2677
+ box-shadow: var(--lily-shadow-md);
2678
+ }
2679
+
2680
+ :where(
2681
+ .theme-picker-tooltip,
2682
+ .locale-picker-tooltip,
2683
+ .text-size-picker-tooltip,
2684
+ .time-zone-picker-tooltip,
2685
+ .motion-picker-tooltip,
2686
+ .link-picker-tooltip,
2687
+ .menu-picker-tooltip,
2688
+ .settings-picker-tooltip,
2689
+ .share-picker-tooltip,
2690
+ .search-picker-tooltip,
2691
+ .date-time-picker-tooltip
2692
+ )::before {
2693
+ content: "";
2694
+ position: absolute;
2695
+ inset-inline: 0;
2696
+ inset-block-end: 100%;
2697
+ height: var(--lily-space-xs);
2698
+ }
2699
+
2700
+ :where(
2701
+ .theme-picker-tooltip,
2702
+ .locale-picker-tooltip,
2703
+ .text-size-picker-tooltip,
2704
+ .time-zone-picker-tooltip,
2705
+ .motion-picker-tooltip,
2706
+ .link-picker-tooltip,
2707
+ .menu-picker-tooltip,
2708
+ .settings-picker-tooltip,
2709
+ .share-picker-tooltip,
2710
+ .search-picker-tooltip,
2711
+ .date-time-picker-tooltip
2712
+ )[hidden] {
2713
+ display: none;
2714
+ }
2715
+
2716
+ /* search-picker's panel holds a search form rather than a list: the
2717
+ field and the ⏎ submit button sit in one row, the button at the
2718
+ field's inline end (right in LTR, left under dir="rtl"). The ⏎
2719
+ button matches the picker trigger buttons' size and border. */
2720
+ :where(.search-picker-form) {
2721
+ display: flex;
2722
+ align-items: stretch;
2723
+ gap: var(--lily-space-xs);
2724
+ margin: 0;
2725
+ }
2726
+
2727
+ :where(.search-picker-input) {
2728
+ min-width: 14rem;
2729
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2730
+ font: inherit;
2731
+ color: var(--lily-text);
2732
+ background-color: var(--lily-surface);
2733
+ border: var(--border) solid var(--lily-border-strong);
2734
+ border-radius: var(--lily-radius-sm);
2735
+ }
2736
+
2737
+ :where(.search-picker-submit) {
2738
+ display: inline-flex;
2739
+ align-items: center;
2740
+ justify-content: center;
2741
+ min-width: 2.5rem;
2742
+ min-height: 2.5rem;
2743
+ padding: var(--lily-space-xs);
2744
+ font: inherit;
2745
+ line-height: 1;
2746
+ color: var(--lily-text);
2747
+ background-color: var(--lily-surface);
2748
+ border: var(--border) solid var(--lily-border-strong);
2749
+ border-radius: var(--lily-radius-md);
2750
+ cursor: pointer;
2751
+ }
2752
+
2753
+ :where(.search-picker-submit:hover) {
2754
+ background-color: var(--lily-surface-alt);
2755
+ }
2756
+
2757
+ :where(
2758
+ .theme-picker-list,
2759
+ .locale-picker-list,
2760
+ .text-size-picker-list,
2761
+ .time-zone-picker-list,
2762
+ .motion-picker-list,
2763
+ .link-picker-list,
2764
+ .menu-picker-panel,
2765
+ .settings-picker-panel,
2766
+ .share-picker-list,
2767
+ .search-picker-panel
2768
+ ) {
2769
+ position: absolute;
2770
+ z-index: 10;
2771
+ inset-inline-start: 0;
2772
+ min-width: max-content;
2773
+ max-height: 60vh;
2774
+ margin-block-start: var(--lily-space-xs);
2775
+ padding: var(--lily-space-xs);
2776
+ overflow-y: auto;
2777
+ list-style: none;
2778
+ color: var(--lily-text);
2779
+ background-color: var(--lily-surface);
2780
+ border: var(--border) solid var(--lily-border-strong);
2781
+ border-radius: var(--lily-radius-md);
2782
+ box-shadow: var(--lily-shadow-md);
2783
+ }
2784
+
2785
+ /* Explicit, because the rules above would otherwise beat the UA
2786
+ stylesheet's [hidden] { display: none }. */
2787
+ :where(
2788
+ .theme-picker-list,
2789
+ .locale-picker-list,
2790
+ .text-size-picker-list,
2791
+ .time-zone-picker-list,
2792
+ .motion-picker-list,
2793
+ .link-picker-list,
2794
+ .menu-picker-panel,
2795
+ .settings-picker-panel,
2796
+ .share-picker-list,
2797
+ .search-picker-panel
2798
+ )[hidden] {
2799
+ display: none;
2800
+ }
2801
+
2802
+ /* Popups open under their button, aligned to its start edge, and flip to
2803
+ its end edge (or above it) when they would run off the viewport. A header
2804
+ picker near the right edge otherwise overflows, and focusing the popup
2805
+ scrolls the whole page sideways. `position: fixed` makes the viewport the
2806
+ containing block that the flip fallbacks are checked against; browsers
2807
+ without anchor positioning keep the absolute placement above. */
2808
+ @supports (position-area: block-end) {
2809
+ :where(.theme-picker) { anchor-name: --lily-theme-picker; }
2810
+ :where(.locale-picker) { anchor-name: --lily-locale-picker; }
2811
+ :where(.text-size-picker) { anchor-name: --lily-text-size-picker; }
2812
+ :where(.time-zone-picker) { anchor-name: --lily-time-zone-picker; }
2813
+ :where(.motion-picker) { anchor-name: --lily-motion-picker; }
2814
+ :where(.link-picker) { anchor-name: --lily-link-picker; }
2815
+ :where(.menu-picker) { anchor-name: --lily-menu-picker; }
2816
+ :where(.settings-picker) { anchor-name: --lily-settings-picker; }
2817
+ :where(.share-picker) { anchor-name: --lily-share-picker; }
2818
+ :where(.search-picker) { anchor-name: --lily-search-picker; }
2819
+ :where(.theme-picker-list) { position-anchor: --lily-theme-picker; }
2820
+ :where(.locale-picker-list) { position-anchor: --lily-locale-picker; }
2821
+ :where(.text-size-picker-list) { position-anchor: --lily-text-size-picker; }
2822
+ :where(.time-zone-picker-list) { position-anchor: --lily-time-zone-picker; }
2823
+ :where(.motion-picker-list) { position-anchor: --lily-motion-picker; }
2824
+ :where(.link-picker-list) { position-anchor: --lily-link-picker; }
2825
+ :where(.menu-picker-panel) { position-anchor: --lily-menu-picker; }
2826
+ :where(.settings-picker-panel) { position-anchor: --lily-settings-picker; }
2827
+ :where(.share-picker-list) { position-anchor: --lily-share-picker; }
2828
+ :where(.search-picker-panel) { position-anchor: --lily-search-picker; }
2829
+ :where(
2830
+ .theme-picker-list,
2831
+ .locale-picker-list,
2832
+ .text-size-picker-list,
2833
+ .time-zone-picker-list,
2834
+ .motion-picker-list,
2835
+ .link-picker-list,
2836
+ .menu-picker-panel,
2837
+ .settings-picker-panel,
2838
+ .share-picker-list,
2839
+ .search-picker-panel
2840
+ ) {
2841
+ position: fixed;
2842
+ inset: auto;
2843
+ position-area: block-end span-inline-end;
2844
+ position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
2845
+ min-width: 0;
2846
+ width: max-content;
2847
+ max-width: calc(100vw - 1rem);
2848
+ }
2849
+ }
2850
+
2851
+ /* A closed popover stays closed. Author `display` beats the browser's own rule for a closed popover, so the
2852
+ menu rule above would otherwise leave every closed dropdown-menu and context-menu on the page. */
2853
+ :where([popover]:not(:popover-open)) {
2854
+ display: none;
2855
+ }
2856
+
2857
+ /* Anchored popups (P16-T4). These nine render `popover="manual"`, which puts them in the top layer; the
2858
+ browser's own popover style would centre them in the viewport. Closed, they are display: none (or `hidden`
2859
+ where the Popover API is missing). Where anchor positioning exists, the four that live inside a component
2860
+ open under that component's trigger or input, as wide as it at least; the five that stand alone (tooltip,
2861
+ hover card, floating panel, coachmark, popconfirm) open under the element the consumer names with
2862
+ `position-anchor`, or the invoker of a `popovertarget`. Those five use anchor() with a fallback, not
2863
+ position-area: with no anchor, position-area does nothing and a static position can be off screen. */
2864
+ :where(.cascader-panel, .tree-select-panel, .mentions-input-suggestions, .combobox > [role="listbox"], .tooltip, .hover-card, .floating-panel, .coachmark, .popconfirm-dialog)[hidden] {
2865
+ display: none;
2866
+ }
2867
+
2868
+ :where(.cascader-panel, .tree-select-panel, .mentions-input-suggestions, .combobox > [role="listbox"], .tooltip, .hover-card, .floating-panel, .coachmark, .popconfirm-dialog)[popover]:not(:popover-open) {
2869
+ display: none;
2870
+ }
2871
+
2872
+ @supports (position-area: block-end) {
2873
+ :where(.cascader-trigger) { anchor-name: --lily-cascader; }
2874
+ :where(.tree-select-trigger) { anchor-name: --lily-tree-select; }
2875
+ :where(.mentions-input-control) { anchor-name: --lily-mentions-input; }
2876
+ :where(.combobox > input) { anchor-name: --lily-combobox; }
2877
+ :where(.cascader-panel) { position-anchor: --lily-cascader; }
2878
+ :where(.tree-select-panel) { position-anchor: --lily-tree-select; }
2879
+ :where(.mentions-input-suggestions) { position-anchor: --lily-mentions-input; }
2880
+ :where(.combobox > [role="listbox"]) { position-anchor: --lily-combobox; }
2881
+ :where(.cascader-panel, .tree-select-panel, .mentions-input-suggestions, .combobox > [role="listbox"], .tooltip, .hover-card, .floating-panel, .coachmark, .popconfirm-dialog)[popover] {
2882
+ position: fixed;
2883
+ inset: auto;
2884
+ margin: 0;
2885
+ position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
2886
+ width: max-content;
2887
+ max-width: calc(100vw - 1rem);
2888
+ }
2889
+ :where(.tooltip, .hover-card, .floating-panel, .coachmark, .popconfirm-dialog)[popover] {
2890
+ inset-block-start: anchor(end, 25%);
2891
+ inset-inline-start: anchor(start, 25%);
2892
+ }
2893
+ :where(.cascader-panel, .tree-select-panel, .mentions-input-suggestions, .combobox > [role="listbox"])[popover] {
2894
+ position-area: block-end span-inline-end;
2895
+ }
2896
+ :where(.cascader-panel, .tree-select-panel, .mentions-input-suggestions, .combobox > [role="listbox"])[popover] {
2897
+ min-inline-size: anchor-size(inline);
2898
+ }
2899
+ }
2900
+
2901
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .time-zone-picker-list, .motion-picker-list)
2902
+ > :where(
2903
+ .theme-picker-option,
2904
+ .locale-picker-option,
2905
+ .text-size-picker-option,
2906
+ .time-zone-picker-option,
2907
+ .motion-picker-option
2908
+ ) {
2909
+ padding: var(--lily-space-xs) var(--lily-space-sm);
2910
+ border-radius: var(--lily-radius-sm);
2911
+ cursor: pointer;
2912
+ }
2913
+
2914
+ /* data-active is the keyboard cursor (aria-activedescendant); focus
2915
+ stays on the listbox, so this is the only visible cue for it. */
2916
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .time-zone-picker-list, .motion-picker-list)
2917
+ > :where([data-active]) {
2918
+ background-color: var(--lily-surface-alt);
2919
+ outline: var(--lily-focus-ring);
2920
+ outline-offset: -2px;
2921
+ }
2922
+
2923
+ /* aria-selected marks the applied theme/locale, which is not the
2924
+ same thing as the keyboard cursor. Both can be visible at once. */
2925
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .time-zone-picker-list, .motion-picker-list)
2926
+ > :where([aria-selected="true"]) {
2927
+ font-weight: 600;
2928
+ background-color: var(--lily-surface-sunken);
2929
+ }
2930
+
2931
+ /* Locale options inherit the select colours, same as theme options. */
2932
+ :where(.locale-picker-option) {
2933
+ background-color: var(--lily-surface);
2934
+ color: var(--lily-text);
2935
+ }
2936
+
2937
+ /* ----------------------------------------------------------------------
2938
+ * date-time-picker
2939
+ *
2940
+ * The one helper that is a form control rather than a page-header
2941
+ * control, so it is the only one with a text field beside its trigger
2942
+ * and a dialog rather than a listbox. The field itself needs no rules
2943
+ * here: `.date-time-picker-input` already matches the generic
2944
+ * [class$="-input"] baseline further up.
2945
+ * -------------------------------------------------------------------- */
2946
+
2947
+ :where(.date-time-picker-field) {
2948
+ display: flex;
2949
+ align-items: center;
2950
+ gap: var(--lily-space-xs);
2951
+ }
2952
+
2953
+ :where(.date-time-picker-dialog) {
2954
+ position: absolute;
2955
+ z-index: 10;
2956
+ inset-inline-start: 0;
2957
+ margin-block-start: var(--lily-space-xs);
2958
+ padding: var(--lily-space-sm);
2959
+ color: var(--lily-text);
2960
+ background-color: var(--lily-surface);
2961
+ border: var(--border) solid var(--lily-border-strong);
2962
+ border-radius: var(--lily-radius-md);
2963
+ box-shadow: var(--lily-shadow-md);
2964
+ }
2965
+
2966
+ /* Explicit, because the rule above would otherwise beat the UA
2967
+ stylesheet's [hidden] { display: none } — the same trap the
2968
+ listbox popups have to step around. */
2969
+ :where(.date-time-picker-dialog)[hidden] {
2970
+ display: none;
2971
+ }
2972
+
2973
+ :where(.date-time-picker-header) {
2974
+ display: flex;
2975
+ align-items: center;
2976
+ justify-content: space-between;
2977
+ gap: var(--lily-space-xs);
2978
+ margin-block-end: var(--lily-space-xs);
2979
+ }
2980
+
2981
+ /* The month/year heading is a live region, so it must keep its box
2982
+ even while empty — never display:none it. */
2983
+ :where(.date-time-picker-period) {
2984
+ flex: 1;
2985
+ font-weight: 600;
2986
+ text-align: center;
2987
+ }
2988
+
2989
+ /* The four navigation buttons and the footer/shortcut buttons do not
2990
+ end in `-button`, so the generic button family never reaches them. */
2991
+ :where(
2992
+ .date-time-picker-previous-year,
2993
+ .date-time-picker-previous-month,
2994
+ .date-time-picker-previous-week,
2995
+ .date-time-picker-previous-day,
2996
+ .date-time-picker-next-day,
2997
+ .date-time-picker-next-week,
2998
+ .date-time-picker-next-month,
2999
+ .date-time-picker-next-year,
3000
+ .date-time-picker-shortcut,
3001
+ .date-time-picker-clear,
3002
+ .date-time-picker-cancel,
3003
+ .date-time-picker-confirm
3004
+ ) {
3005
+ padding: var(--lily-space-xs) var(--lily-space-sm);
3006
+ font: inherit;
3007
+ color: var(--lily-text);
3008
+ background-color: var(--lily-surface);
3009
+ border: var(--border) solid var(--lily-border-strong);
3010
+ border-radius: var(--lily-radius-sm);
3011
+ cursor: pointer;
3012
+ }
3013
+
3014
+ :where(
3015
+ .date-time-picker-previous-year:hover,
3016
+ .date-time-picker-previous-month:hover,
3017
+ .date-time-picker-previous-week:hover,
3018
+ .date-time-picker-previous-day:hover,
3019
+ .date-time-picker-next-day:hover,
3020
+ .date-time-picker-next-week:hover,
3021
+ .date-time-picker-next-month:hover,
3022
+ .date-time-picker-next-year:hover,
3023
+ .date-time-picker-shortcut:hover,
3024
+ .date-time-picker-clear:hover,
3025
+ .date-time-picker-cancel:hover,
3026
+ .date-time-picker-confirm:hover
3027
+ ) {
3028
+ background-color: var(--lily-surface-alt);
3029
+ }
3030
+
3031
+ /* The commit action is the primary one on the dialog. */
3032
+ :where(.date-time-picker-confirm) {
3033
+ color: var(--color-primary-content);
3034
+ background-color: var(--color-primary);
3035
+ border-color: var(--color-primary);
3036
+ }
3037
+
3038
+ :where(.date-time-picker-calendar) {
3039
+ border-collapse: collapse;
3040
+ table-layout: fixed;
3041
+ }
3042
+
3043
+ :where(.date-time-picker-weekday, .date-time-picker-week-heading) {
3044
+ padding: var(--lily-space-xs);
3045
+ font-size: 0.8125em;
3046
+ font-weight: 600;
3047
+ color: var(--lily-text-muted);
3048
+ }
3049
+
3050
+ /* Week numbers are reference data, not navigation: muted, and set in
3051
+ tabular figures so the column does not shimmer as months change. */
3052
+ :where(.date-time-picker-week) {
3053
+ padding-inline-end: var(--lily-space-xs);
3054
+ font-size: 0.8125em;
3055
+ font-weight: 400;
3056
+ font-variant-numeric: tabular-nums;
3057
+ color: var(--lily-text-muted);
3058
+ text-align: end;
3059
+ }
3060
+
3061
+ :where(.date-time-picker-day) {
3062
+ display: flex;
3063
+ align-items: center;
3064
+ justify-content: center;
3065
+ /* 2rem is a compromise: WCAG 2.5.8 (AA) wants 24px, 2.5.5 (AAA)
3066
+ wants 44px, and seven 44px columns do not fit a 320px viewport
3067
+ alongside dialog chrome. Consumers targeting AAA should enlarge
3068
+ these and let the dialog go full-width on small screens. */
3069
+ min-width: 2rem;
3070
+ min-height: 2rem;
3071
+ padding: 0;
3072
+ font: inherit;
3073
+ font-variant-numeric: tabular-nums;
3074
+ color: var(--lily-text);
3075
+ background: none;
3076
+ border: var(--border) solid transparent;
3077
+ border-radius: var(--lily-radius-sm);
3078
+ cursor: pointer;
3079
+ }
3080
+
3081
+ :where(.date-time-picker-day:hover:not([data-disabled])) {
3082
+ background-color: var(--lily-surface-alt);
3083
+ }
3084
+
3085
+ /* Adjacent-month days stay legible rather than being hidden: they are
3086
+ real, selectable dates, and blanking them makes the grid harder to
3087
+ scan across a month boundary. */
3088
+ :where(.date-time-picker-day[data-outside]) {
3089
+ color: var(--lily-text-muted);
3090
+ }
3091
+
3092
+ /* Today is marked with a border, the selection with a fill. Two
3093
+ different channels, so a day that is both still reads as both. */
3094
+ :where(.date-time-picker-day[data-today]) {
3095
+ border-color: var(--lily-border-strong);
3096
+ font-weight: 600;
3097
+ }
3098
+
3099
+ :where(.date-time-picker-day[data-selected]) {
3100
+ color: var(--color-primary-content);
3101
+ background-color: var(--color-primary);
3102
+ border-color: var(--color-primary);
3103
+ }
3104
+
3105
+ :where(.date-time-picker-day[data-disabled]) {
3106
+ color: var(--lily-text-muted);
3107
+ cursor: not-allowed;
3108
+ /* Struck through as well as dimmed: "unavailable" must not rest on
3109
+ contrast alone (WCAG 1.4.1). */
3110
+ text-decoration: line-through;
3111
+ }
3112
+
3113
+ :where(.date-time-picker-time) {
3114
+ display: flex;
3115
+ flex-wrap: wrap;
3116
+ align-items: center;
3117
+ gap: var(--lily-space-xs);
3118
+ margin-block-start: var(--lily-space-sm);
3119
+ }
3120
+
3121
+ :where(.date-time-picker-time-label) {
3122
+ font-size: 0.8125em;
3123
+ color: var(--lily-text-muted);
3124
+ }
3125
+
3126
+ :where(.date-time-picker-time-zone) {
3127
+ display: flex;
3128
+ flex-wrap: wrap;
3129
+ align-items: center;
3130
+ gap: var(--lily-space-xs);
3131
+ margin-block-end: var(--lily-space-sm);
3132
+ }
3133
+
3134
+ :where(.date-time-picker-time-zone-label) {
3135
+ font-size: 0.8125em;
3136
+ color: var(--lily-text-muted);
3137
+ }
3138
+
3139
+ :where(
3140
+ .date-time-picker-hour,
3141
+ .date-time-picker-minute,
3142
+ .date-time-picker-meridiem,
3143
+ .date-time-picker-time-zone-select
3144
+ ) {
3145
+ padding: var(--lily-space-xs);
3146
+ font: inherit;
3147
+ color: var(--lily-text);
3148
+ background-color: var(--lily-surface);
3149
+ border: var(--border) solid var(--lily-border-strong);
3150
+ border-radius: var(--lily-radius-sm);
3151
+ }
3152
+
3153
+ :where(.date-time-picker-shortcuts) {
3154
+ display: flex;
3155
+ flex-wrap: wrap;
3156
+ gap: var(--lily-space-xs);
3157
+ margin-block-start: var(--lily-space-sm);
3158
+ }
3159
+
3160
+ :where(.date-time-picker-footer) {
3161
+ display: flex;
3162
+ justify-content: flex-end;
3163
+ gap: var(--lily-space-xs);
3164
+ margin-block-start: var(--lily-space-sm);
3165
+ }
3166
+
3167
+ /* Combobox / listbox / autosuggest — non-native variant of <select>. */
3168
+ :where(.combobox, .listbox, .autosuggest) {
3169
+ background-color: var(--lily-surface);
3170
+ border: var(--border) solid var(--lily-border-strong);
3171
+ border-radius: var(--lily-radius-md);
3172
+ }
3173
+
3174
+ /* ==========================================================================
3175
+ * Editorial typography
3176
+ * --------------------------------------------------------------------------
3177
+ * Headlines, body text, byline, blockquote, code — the long-form reading
3178
+ * primitives. Headlines use clamp() so they shrink on narrow viewports
3179
+ * without media queries.
3180
+ * ====================================================================== */
3181
+
3182
+ :where(.headline, .hero-headline, .section-heading) {
3183
+ color: var(--lily-text);
3184
+ font-family: var(--lily-font-body);
3185
+ font-weight: 700;
3186
+ line-height: 1.15;
3187
+ margin: 0 0 0.5rem 0;
3188
+ }
3189
+ :where(.headline) {
3190
+ font-size: clamp(1.5rem, 3vw, 2.25rem);
3191
+ }
3192
+ :where(.hero-headline) {
3193
+ font-size: clamp(2rem, 5vw, 3.5rem);
3194
+ }
3195
+ :where(.section-heading) {
3196
+ font-size: clamp(1.25rem, 2vw, 1.75rem);
3197
+ }
3198
+ /* Body text — 1.6 line-height (sweet spot for reading). */
3199
+ :where(.body-text) {
3200
+ line-height: 1.6;
3201
+ color: var(--lily-text);
3202
+ }
3203
+ /* Byline — author / date line under a headline. */
3204
+ :where(.byline) {
3205
+ color: var(--lily-text-muted);
3206
+ font-size: 0.875rem;
3207
+ }
3208
+ /* Blockquote — primary-coloured rail + italic body. */
3209
+ :where(.blockquote) {
3210
+ margin: 1rem 0;
3211
+ padding: 0.5rem 1rem;
3212
+ border-inline-start: 0.25rem solid var(--color-primary);
3213
+ color: var(--lily-text);
3214
+ font-style: italic;
3215
+ }
3216
+ /* End-notes / related content — top-bordered section at the foot. */
3217
+ :where(.end-notes, .related-content) {
3218
+ margin-top: 2rem;
3219
+ padding-top: 1rem;
3220
+ border-top: var(--border) solid var(--lily-border);
3221
+ }
3222
+ /* Caption / footnote / citation — small muted text. */
3223
+ :where(.caption, .footnote, .citation) {
3224
+ font-size: 0.875rem;
3225
+ color: var(--lily-text-muted);
3226
+ }
3227
+ /* KBD — keyboard key glyph; thicker bottom border simulates the keytop. */
3228
+ :where(.kbd) {
3229
+ display: inline-block;
3230
+ padding: 0.125rem 0.4rem;
3231
+ font-family: var(--lily-font-mono);
3232
+ font-size: 0.85em;
3233
+ background-color: var(--lily-surface-alt);
3234
+ border: var(--border) solid var(--lily-border-strong);
3235
+ border-bottom-width: 2px;
3236
+ border-radius: var(--lily-radius-sm);
3237
+ }
3238
+ /* Inline code — slight background tint + monospaced font. */
3239
+ :where(.code) {
3240
+ font-family: var(--lily-font-mono);
3241
+ padding: 0.1em 0.3em;
3242
+ background-color: var(--lily-surface-alt);
3243
+ border-radius: var(--lily-radius-sm);
3244
+ font-size: 0.9em;
3245
+ }
3246
+ /* Code block — full-width block with horizontal scroll. */
3247
+ :where(.code-block) {
3248
+ display: block;
3249
+ padding: 1rem;
3250
+ background-color: var(--color-neutral);
3251
+ color: var(--color-neutral-content);
3252
+ font-family: var(--lily-font-mono);
3253
+ border-radius: var(--lily-radius-md);
3254
+ overflow-x: auto;
3255
+ white-space: pre;
3256
+ }
3257
+ /* Inset text — left-railed callout for "Important" content
3258
+ (GOV.UK / NHS pattern). */
3259
+ :where(.inset-text) {
3260
+ padding: 0.75rem 1rem;
3261
+ border-inline-start: 0.25rem solid var(--color-primary);
3262
+ background-color: var(--lily-surface-alt);
3263
+ }
3264
+ /* Generic character — monospaced single-glyph display. */
3265
+ :where(.character) {
3266
+ font-family: var(--lily-font-mono);
3267
+ }
3268
+ /* Clamp-text — truncate to N lines (controlled by --lily-clamp-lines). */
3269
+ :where(.clamp-text) {
3270
+ display: -webkit-box;
3271
+ -webkit-line-clamp: var(--lily-clamp-lines, 3);
3272
+ line-clamp: var(--lily-clamp-lines, 3);
3273
+ -webkit-box-orient: vertical;
3274
+ overflow: hidden;
3275
+ }
3276
+ /* Screen-reader-only — visually hidden but available to AT. The
3277
+ classic clip-rect pattern (works in every screen reader). */
3278
+ :where(.screen-reader-span, .middle-truncation-full) {
3279
+ position: absolute;
3280
+ width: 1px;
3281
+ height: 1px;
3282
+ padding: 0;
3283
+ margin: -1px;
3284
+ overflow: hidden;
3285
+ clip: rect(0, 0, 0, 0);
3286
+ white-space: nowrap;
3287
+ border: 0;
3288
+ }
3289
+ /* Middle truncation — a bounded one-line block; the shortened string is aria-hidden and the full
3290
+ text (hidden above) stays in the accessibility tree. */
3291
+ :where(.middle-truncation) {
3292
+ display: block;
3293
+ position: relative;
3294
+ overflow: hidden;
3295
+ white-space: nowrap;
3296
+ }
3297
+ /* Wheel picker — a scrolling listbox that snaps the chosen option to the centre. The selected option is filled with the
3298
+ primary colour (aria-selected, not colour alone: it is also the activedescendant and gets a bold weight). */
3299
+ :where(.wheel-picker-list) {
3300
+ max-block-size: 10em;
3301
+ overflow-y: auto;
3302
+ overscroll-behavior: contain;
3303
+ scroll-snap-type: y mandatory;
3304
+ border: 1px solid var(--color-base-300);
3305
+ border-radius: var(--lily-radius-md);
3306
+ }
3307
+ :where(.wheel-picker-list:focus-visible) {
3308
+ outline: 2px solid var(--lily-focus-ring);
3309
+ outline-offset: 2px;
3310
+ }
3311
+ :where(.wheel-picker-option) {
3312
+ padding: 0.5rem 1rem;
3313
+ scroll-snap-align: center;
3314
+ cursor: pointer;
3315
+ }
3316
+ :where(.wheel-picker-option[aria-selected="true"]) {
3317
+ background-color: var(--color-primary);
3318
+ color: var(--color-primary-content);
3319
+ font-weight: 600;
3320
+ }
3321
+ :where(.wheel-picker-option[aria-disabled="true"]) {
3322
+ opacity: 0.5;
3323
+ cursor: not-allowed;
3324
+ }
3325
+
3326
+ /* ==========================================================================
3327
+ * Hero, figure, photo, media
3328
+ * ====================================================================== */
3329
+
3330
+ /* Hero block — primary-filled banner with centred content. clamp() pad
3331
+ scales vertically across viewport sizes. */
3332
+ :where(.hero) {
3333
+ display: block;
3334
+ padding: clamp(2rem, 5vw, 4rem) 1rem;
3335
+ background-color: var(--color-primary);
3336
+ color: var(--color-primary-content);
3337
+ border-radius: var(--lily-radius-lg);
3338
+ text-align: center;
3339
+ }
3340
+ /* Figure — centred block with vertical margin. */
3341
+ :where(.figure) {
3342
+ margin: 1rem 0;
3343
+ text-align: center;
3344
+ }
3345
+ /* Feature photo — responsive lead image. */
3346
+ :where(.feature-photo) {
3347
+ display: block;
3348
+ width: 100%;
3349
+ height: auto;
3350
+ border-radius: var(--lily-radius-md);
3351
+ }
3352
+ /* Photo pack — responsive grid of thumbnails. */
3353
+ :where(.photo-pack) {
3354
+ display: grid;
3355
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
3356
+ gap: 0.5rem;
3357
+ }
3358
+ /* Generic image — responsive block image. */
3359
+ :where(.image) {
3360
+ max-width: 100%;
3361
+ height: auto;
3362
+ display: block;
3363
+ }
3364
+ /* QR / barcode / signature — frame them as a labelled tile. */
3365
+ :where(.qr-code-image, .barcode-image, .signature-pad) {
3366
+ display: inline-block;
3367
+ background-color: var(--lily-surface);
3368
+ border: var(--border) solid var(--lily-border);
3369
+ border-radius: var(--lily-radius-md);
3370
+ padding: 0.5rem;
3371
+ }
3372
+ /* Signature pad — drawable surface; crosshair cursor signals input. */
3373
+ :where(.signature-pad) {
3374
+ min-height: 8rem;
3375
+ cursor: crosshair;
3376
+ }
3377
+ /* Video player — responsive black-letterboxed frame. */
3378
+ :where(.video-player) {
3379
+ display: block;
3380
+ width: 100%;
3381
+ max-width: 100%;
3382
+ border-radius: var(--lily-radius-md);
3383
+ /* Video chrome is black in every theme; pair the text colour with
3384
+ it here rather than inheriting the theme's (possibly dark)
3385
+ content colour onto a black surface. */
3386
+ background-color: black;
3387
+ color: white;
3388
+ }
3389
+
3390
+ /* ==========================================================================
3391
+ * Mockups & pictograms
3392
+ * --------------------------------------------------------------------------
3393
+ * Device frames for screenshots / illustrations — browser, phone, tablet,
3394
+ * laptop, watch, shell window. All share the same rounded-outline frame
3395
+ * baseline; specific frames override the radius / border-width.
3396
+ * ====================================================================== */
3397
+
3398
+ :where(
3399
+ .mockup-browser,
3400
+ .mockup-window,
3401
+ .mockup-shell,
3402
+ .mockup-phone-portrait,
3403
+ .mockup-phone-landscape,
3404
+ .mockup-tablet-portrait,
3405
+ .mockup-tablet-landscape,
3406
+ .mockup-laptop,
3407
+ .mockup-watch
3408
+ ) {
3409
+ display: block;
3410
+ background-color: var(--lily-surface);
3411
+ border: 2px solid var(--color-neutral);
3412
+ border-radius: var(--lily-radius-lg);
3413
+ overflow: hidden;
3414
+ box-shadow: var(--lily-shadow-md);
3415
+ }
3416
+ /* Mockup shell — terminal window; dark neutral fill + mono font. */
3417
+ :where(.mockup-shell) {
3418
+ background-color: var(--color-neutral);
3419
+ color: var(--color-neutral-content);
3420
+ font-family: var(--lily-font-mono);
3421
+ padding: 1rem;
3422
+ }
3423
+ /* Phones — heavier bezel (6px) and more rounded corners. */
3424
+ :where(.mockup-phone-portrait, .mockup-phone-landscape) {
3425
+ border-radius: 1.5rem;
3426
+ border-width: 6px;
3427
+ }
3428
+ /* Smartwatch — chunky bezel (4px) and pill radius. */
3429
+ :where(.mockup-watch) {
3430
+ border-radius: 1rem;
3431
+ border-width: 4px;
3432
+ }
3433
+ /* Pictogram — stacked icon + caption, centred. */
3434
+ :where(.pictogram) {
3435
+ display: inline-flex;
3436
+ flex-direction: column;
3437
+ align-items: center;
3438
+ gap: 0.5rem;
3439
+ padding: 1rem;
3440
+ text-align: center;
3441
+ }
3442
+
3443
+ /* ==========================================================================
3444
+ * Charts (containers only — content supplied by consumer)
3445
+ * --------------------------------------------------------------------------
3446
+ * Headless: Lily provides the frame + a 6-colour palette as custom
3447
+ * properties. The consumer's chart library (D3, Chart.js, ECharts, etc.)
3448
+ * reads `var(--lily-chart-color-1..6)` for its series colours.
3449
+ * ====================================================================== */
3450
+
3451
+ :where(
3452
+ .area-chart,
3453
+ .bar-chart,
3454
+ .column-chart,
3455
+ .line-chart,
3456
+ .scatter-chart,
3457
+ .sparkline,
3458
+ .tile-map,
3459
+ .graphic-block
3460
+ ) {
3461
+ display: block;
3462
+ background-color: var(--lily-surface);
3463
+ border-radius: var(--lily-radius-md);
3464
+ padding: 1rem;
3465
+ color: var(--lily-text);
3466
+ /* Six-colour palette — the consumer's chart library picks these up. */
3467
+ --lily-chart-color-1: var(--color-primary);
3468
+ --lily-chart-color-2: var(--color-secondary);
3469
+ --lily-chart-color-3: var(--color-accent);
3470
+ --lily-chart-color-4: var(--color-info);
3471
+ --lily-chart-color-5: var(--color-success);
3472
+ --lily-chart-color-6: var(--color-warning);
3473
+ }
3474
+ /* Sparkline — small inline chart; no padding. */
3475
+ :where(.sparkline) {
3476
+ padding: 0;
3477
+ height: 1.5rem;
3478
+ width: 6rem;
3479
+ display: inline-block;
3480
+ }
3481
+ /* Graphic block — bordered chart container. */
3482
+ :where(.graphic-block) {
3483
+ border: var(--border) solid var(--lily-border);
3484
+ }
3485
+
3486
+ /* ==========================================================================
3487
+ * Scrolling, splits, resize
3488
+ * ====================================================================== */
3489
+
3490
+ /* Scroll area — capped at 20rem, auto-overflow. */
3491
+ :where(.scroll-area) {
3492
+ overflow: auto;
3493
+ max-height: 20rem;
3494
+ }
3495
+ /* Horizontal scroller — flex row that overflows on x. */
3496
+ :where(.horizontal-scroller) {
3497
+ overflow-x: auto;
3498
+ display: flex;
3499
+ gap: 0.5rem;
3500
+ }
3501
+ /* Scroller (Reuters-pattern scrollytelling) — positioning anchor. */
3502
+ :where(.scroller, .scroller-base, .scroller-video) {
3503
+ position: relative;
3504
+ display: block;
3505
+ }
3506
+ /* Custom scrollbar track. */
3507
+ :where(.scroll-bar) {
3508
+ background-color: var(--lily-surface-sunken);
3509
+ border-radius: 999px;
3510
+ height: 0.5rem;
3511
+ }
3512
+ /* Affix — stick to the top of the viewport on scroll. */
3513
+ :where(.affix) {
3514
+ position: sticky;
3515
+ inset-block-start: 0;
3516
+ }
3517
+ /* Visible — IntersectionObserver target; baseline is a visible block. */
3518
+ :where(.visible) {
3519
+ display: block;
3520
+ }
3521
+ /* Resizable — native CSS resize handle (corner). */
3522
+ :where(.resizable) {
3523
+ resize: both;
3524
+ overflow: auto;
3525
+ }
3526
+ /* Split-view + splitter — two-pane horizontal layout. */
3527
+ :where(.split-view) {
3528
+ display: flex;
3529
+ align-items: stretch;
3530
+ gap: 0;
3531
+ }
3532
+ :where(.splitter) {
3533
+ flex: 0 0 0.375rem;
3534
+ background-color: var(--lily-border);
3535
+ cursor: col-resize;
3536
+ }
3537
+
3538
+ /* ==========================================================================
3539
+ * Misc unique components
3540
+ * ====================================================================== */
3541
+
3542
+ /* Statistic — stacked label + value; value is large and primary-coloured. */
3543
+ :where(.statistic) {
3544
+ display: inline-flex;
3545
+ flex-direction: column;
3546
+ gap: 0.25rem;
3547
+ }
3548
+ :where(.statistic > [data-part="value"]) {
3549
+ font-size: 1.5rem;
3550
+ font-weight: 700;
3551
+ color: var(--color-primary);
3552
+ }
3553
+ /* Watermark — diagonal repeating-line pattern at 8% opacity behind content. */
3554
+ :where(.watermark) {
3555
+ position: absolute;
3556
+ inset: 0;
3557
+ pointer-events: none;
3558
+ opacity: 0.08;
3559
+ background: repeating-linear-gradient(
3560
+ -45deg,
3561
+ var(--lily-text) 0 1px,
3562
+ transparent 1px 8rem
3563
+ );
3564
+ }
3565
+ /* Beach ball (macOS "waiting" indicator) — six-colour conic wheel. */
3566
+ :where(.beach-ball) {
3567
+ display: inline-block;
3568
+ width: 2rem;
3569
+ height: 2rem;
3570
+ border-radius: 50%;
3571
+ background: conic-gradient(
3572
+ var(--color-error),
3573
+ var(--color-warning),
3574
+ var(--color-success),
3575
+ var(--color-info),
3576
+ var(--color-primary),
3577
+ var(--color-error)
3578
+ );
3579
+ animation: lily-spin 2s linear infinite;
3580
+ }
3581
+ /* Diff — side-by-side two-column comparison. */
3582
+ :where(.diff) {
3583
+ display: grid;
3584
+ grid-template-columns: 1fr 1fr;
3585
+ gap: 0.5rem;
3586
+ }
3587
+ /* Addressograph box — paper-form ID label (NHS / hospital pattern). */
3588
+ :where(.addressograph-box) {
3589
+ display: block;
3590
+ padding: 0.75rem 1rem;
3591
+ background-color: var(--lily-surface-alt);
3592
+ border: 2px solid var(--lily-text);
3593
+ font-family: var(--lily-font-mono);
3594
+ line-height: 1.4;
3595
+ }
3596
+ /* Government identifier — dark chrome bar containing an ID number. */
3597
+ :where(.government-identifier) {
3598
+ display: flex;
3599
+ align-items: center;
3600
+ gap: 0.75rem;
3601
+ padding: 0.75rem 1rem;
3602
+ background-color: var(--color-neutral);
3603
+ color: var(--color-neutral-content);
3604
+ }
3605
+ /* Share / signup / CTA panels — alt-surface promo blocks. */
3606
+ :where(.share-page, .newsletter-signup, .call-to-action) {
3607
+ padding: 1rem;
3608
+ background-color: var(--lily-surface-alt);
3609
+ border-radius: var(--lily-radius-md);
3610
+ }
3611
+ /* CTA — override with primary fill so it reads as the page action. */
3612
+ :where(.call-to-action) {
3613
+ background-color: var(--color-primary);
3614
+ color: var(--color-primary-content);
3615
+ }
3616
+ /* Links inside the call-to-action inherit its content colour —
3617
+ otherwise the UA default link blue lands on the primary fill. */
3618
+ :where(.call-to-action) :where(a) {
3619
+ color: inherit;
3620
+ }
3621
+ /* Expander / collapsible / details — boxed disclosure controls. */
3622
+ :where(.expander, .collapsible, .details) {
3623
+ display: block;
3624
+ border: var(--border) solid var(--lily-border);
3625
+ border-radius: var(--lily-radius-md);
3626
+ padding: 0.5rem 1rem;
3627
+ }
3628
+ :where(.details > summary, .expander > summary, .collapsible > summary) {
3629
+ cursor: pointer;
3630
+ font-weight: 600;
3631
+ padding: 0.25rem 0;
3632
+ }
3633
+ /* Carousel — horizontal scroller with snap-points so each slide locks
3634
+ into view as the user swipes. */
3635
+ :where(.carousel) {
3636
+ display: flex;
3637
+ overflow-x: auto;
3638
+ scroll-snap-type: x mandatory;
3639
+ gap: 0.5rem;
3640
+ }
3641
+ :where(.carousel > *) {
3642
+ scroll-snap-align: start;
3643
+ flex: 0 0 auto;
3644
+ }
3645
+
3646
+ /* --- Semantic entities — neutral display blocks. ----------------------- */
3647
+ :where(
3648
+ .person,
3649
+ .organization,
3650
+ .place,
3651
+ .event,
3652
+ .comment,
3653
+ .question,
3654
+ .answer
3655
+ ) {
3656
+ display: block;
3657
+ padding: 0.75rem 0;
3658
+ }
3659
+ /* Draft wrapper — dashed border + optional "Draft" pill prefix. */
3660
+ :where(.draft) {
3661
+ border: 1px dashed var(--lily-border-strong);
3662
+ background-color: var(--lily-surface-alt);
3663
+ padding: 1rem;
3664
+ border-radius: var(--lily-radius-md);
3665
+ }
3666
+ :where(.draft[data-status="draft"])::before {
3667
+ content: "Draft";
3668
+ display: inline-block;
3669
+ margin-inline-end: 0.5rem;
3670
+ padding: 0.125rem 0.5rem;
3671
+ background-color: var(--color-warning);
3672
+ color: var(--color-warning-content);
3673
+ border-radius: 999px;
3674
+ font-size: 0.75rem;
3675
+ font-weight: 600;
3676
+ }
3677
+
3678
+ /* Dial — circular gauge driven by --dial-value (0–100). */
3679
+ :where(.dial) {
3680
+ display: inline-block;
3681
+ width: 4rem;
3682
+ height: 4rem;
3683
+ border-radius: 50%;
3684
+ background: conic-gradient(
3685
+ var(--color-primary) calc(var(--dial-value, 0) * 1%),
3686
+ var(--lily-surface-sunken) 0
3687
+ );
3688
+ }
3689
+
3690
+ /* File upload — drop-zone aesthetic: dashed border, alt surface. */
3691
+ :where(.file-upload) {
3692
+ display: block;
3693
+ padding: 1.5rem;
3694
+ background-color: var(--lily-surface-alt);
3695
+ border: 2px dashed var(--lily-border-strong);
3696
+ border-radius: var(--lily-radius-md);
3697
+ text-align: center;
3698
+ cursor: pointer;
3699
+ }
3700
+ /* File manager — surface-coloured frame holding a file list. */
3701
+ :where(.file-manager) {
3702
+ display: block;
3703
+ background-color: var(--lily-surface);
3704
+ border: var(--border) solid var(--lily-border);
3705
+ border-radius: var(--lily-radius-md);
3706
+ padding: 0.5rem;
3707
+ }
3708
+ /* Transfer list — two columns + central button stack. */
3709
+ :where(.transfer-list) {
3710
+ display: grid;
3711
+ grid-template-columns: 1fr auto 1fr;
3712
+ gap: 0.5rem;
3713
+ align-items: center;
3714
+ }
3715
+
3716
+ /* Tour — onboarding card with medium elevation. */
3717
+ :where(.tour) {
3718
+ display: block;
3719
+ background-color: var(--lily-surface);
3720
+ border: var(--border) solid var(--lily-border);
3721
+ border-radius: var(--lily-radius-lg);
3722
+ padding: 1rem;
3723
+ box-shadow: var(--lily-shadow-md);
3724
+ }
3725
+
3726
+ /* Editable text — dashed underline indicates click-to-edit. */
3727
+ :where(.editable) {
3728
+ display: inline-block;
3729
+ padding: 0.125rem 0.25rem;
3730
+ border-bottom: 1px dashed var(--lily-border-strong);
3731
+ cursor: text;
3732
+ }
3733
+ /* Editing state — alt surface + primary underline. */
3734
+ :where(.editable[aria-expanded="true"]) {
3735
+ background-color: var(--lily-surface-alt);
3736
+ border-bottom-color: var(--color-primary);
3737
+ }
3738
+
3739
+ /* Accordion-checkbox — a checkbox that toggles a panel via sibling
3740
+ combinator (no JS needed). */
3741
+ :where(.accordion-checkbox) {
3742
+ display: block;
3743
+ border: var(--border) solid var(--lily-border);
3744
+ border-radius: var(--lily-radius-md);
3745
+ padding: 0.5rem 0.75rem;
3746
+ }
3747
+ :where(.accordion-checkbox-input) {
3748
+ margin-inline-end: 0.5rem;
3749
+ }
3750
+ :where(.accordion-checkbox-panel) {
3751
+ display: none;
3752
+ padding-block-start: 0.5rem;
3753
+ }
3754
+ /* Reveal the panel when the input is checked. */
3755
+ :where(.accordion-checkbox-input:checked ~ .accordion-checkbox-panel) {
3756
+ display: block;
3757
+ }
3758
+
3759
+ /* Icon — 1em square container; sizes with surrounding text. */
3760
+ :where(.icon) {
3761
+ display: inline-flex;
3762
+ align-items: center;
3763
+ justify-content: center;
3764
+ width: 1em;
3765
+ height: 1em;
3766
+ flex: 0 0 auto;
3767
+ }
3768
+
3769
+ /* Emoji — inline glyph; not italicised. */
3770
+ :where(.emoji) {
3771
+ display: inline-block;
3772
+ font-style: normal;
3773
+ }
3774
+
3775
+ /* Slider — primary-filled track based on --slider-value (0–100). */
3776
+ :where(.slider) {
3777
+ display: block;
3778
+ width: 100%;
3779
+ height: 1.5rem;
3780
+ background: linear-gradient(
3781
+ to right,
3782
+ var(--color-primary) calc(var(--slider-value, 50) * 1%),
3783
+ var(--lily-surface-sunken) 0
3784
+ );
3785
+ border-radius: 999px;
3786
+ }
3787
+
3788
+ /* Timer — fixed-width digits so the display doesn't jiggle as it ticks. */
3789
+ :where(.timer) {
3790
+ display: inline-block;
3791
+ font-family: var(--lily-font-mono);
3792
+ font-variant-numeric: tabular-nums;
3793
+ font-weight: 600;
3794
+ }
3795
+
3796
+ /* Medical banner inner boxes — spacing-only override. */
3797
+ :where(.medical-banner-box-for-advice, .medical-banner-box-for-danger) {
3798
+ padding: 0.75rem 1rem;
3799
+ border-radius: var(--lily-radius-md);
3800
+ margin: 0.25rem 0;
3801
+ }
3802
+
3803
+ /* ==========================================================================
3804
+ * Print
3805
+ * --------------------------------------------------------------------------
3806
+ * On paper, transient/floating chrome (skip-link, FAB, toast, tooltip)
3807
+ * is meaningless — hide it. Card-like surfaces drop their drop-shadow
3808
+ * and shift to a high-contrast outline so they're readable in monochrome.
3809
+ * ====================================================================== */
3810
+ @media print {
3811
+ :where(
3812
+ .skip-link,
3813
+ .float-button,
3814
+ .toast,
3815
+ .sonner,
3816
+ .tooltip,
3817
+ .coachmark
3818
+ ) {
3819
+ display: none !important;
3820
+ }
3821
+ :where(.card, .panel, .dialog, .alert) {
3822
+ box-shadow: none;
3823
+ border-color: var(--lily-text);
3824
+ }
3825
+ }
3826
+ }
3827
+
3828
+ /* ============================================================================
3829
+ * WCAG 2.2 target-size (minimum) floor — 2.5.8
3830
+ *
3831
+ * Every interactive Lily hook offers a >=24x24 CSS-pixel target. Links
3832
+ * that render as short inline text (a pagination "1", a breadcrumb crumb)
3833
+ * fail the 24px floor under typographic sizing alone, so the link-shaped
3834
+ * hooks become inline-block with a minimum box. :where() keeps specificity
3835
+ * at zero, so any consumer rule still wins. Shared verbatim by all themes.
3836
+ * ========================================================================= */
3837
+
3838
+ :where(.action-link,
3839
+ .back-link,
3840
+ .breadcrumb-list-item > a,
3841
+ .pagination-list-item > a,
3842
+ .contents-list-item > a,
3843
+ .section-list-item > a) {
3844
+ display: inline-block;
3845
+ min-inline-size: 24px;
3846
+ min-block-size: 24px;
3847
+ }
3848
+
3849
+ :where(.pagination-list-item > a) {
3850
+ text-align: center;
3851
+ }
3852
+
3853
+ :where(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
3854
+ min-block-size: 24px;
3855
+ }
3856
+
3857
+ /* ============================================================================
3858
+ * Coarse pointer targets — WCAG 2.2 2.5.5 (AAA), shared verbatim by all themes.
3859
+ *
3860
+ * Where the primary pointer is coarse (a finger), interactive hooks are at least
3861
+ * 44x44 CSS pixels: buttons, tabs, options, menu items, the page-header picker
3862
+ * buttons, form fields, checkboxes and radios, and the link-shaped navigation
3863
+ * hooks. Links inside running text are exempt, so bare `a` is not listed. The
3864
+ * query is the capability, never a viewport width. :where() keeps specificity
3865
+ * at zero, so any consumer rule still wins.
3866
+ * ========================================================================= */
3867
+
3868
+ @media (pointer: coarse) {
3869
+ :where(button,
3870
+ [role="button"],
3871
+ [role="tab"],
3872
+ [role="option"],
3873
+ [role="menuitem"],
3874
+ [class*="-picker-button"],
3875
+ summary,
3876
+ input:not([type="hidden"]),
3877
+ select,
3878
+ textarea,
3879
+ .action-link,
3880
+ .back-link,
3881
+ .breadcrumb-list-item > a,
3882
+ .pagination-list-item > a,
3883
+ .contents-list-item > a,
3884
+ .section-list-item > a) {
3885
+ min-inline-size: 44px;
3886
+ min-block-size: 44px;
3887
+ }
3888
+ }
3889
+
3890
+ /* ============================================================================
3891
+ * Brand colour — shared verbatim by all themes (spec/visual-identity, P21-T3).
3892
+ *
3893
+ * --lily-brand is the identity's decorative colour: a header band, a rule, a
3894
+ * logo background. It is NEVER a text colour or a control's boundary, because a
3895
+ * brand colour need not reach 4.5:1 (WCAG 1.4.3, 1.4.11). --lily-on-brand is the
3896
+ * text drawn on it. By default they follow the theme's primary pair, which every
3897
+ * theme keeps readable; an adopter whose brand colour is lighter or darker sets
3898
+ * both in their own CSS, with a pair of at least 4.5:1, and text keeps using the
3899
+ * content pairs (--color-base-content, --lily-error-text and so on).
3900
+ * ========================================================================== */
3901
+
3902
+ :where(:root) {
3903
+ --lily-brand: var(--color-primary);
3904
+ --lily-on-brand: var(--color-primary-content);
3905
+ }
3906
+
3907
+ /* ============================================================================
3908
+ * Logo slot — shared verbatim by all themes (spec/visual-identity, P21-T4).
3909
+ *
3910
+ * The adopter's logo link carries data-lily-logo: an <a href="/"> holding the
3911
+ * adopter's own <img alt> or <svg role="img" aria-label>. Lily ships no logo
3912
+ * and no clear-space rule, because national and local guidelines disagree: a
3913
+ * theme or the adopter sets --lily-logo-clear-space (padding around the mark)
3914
+ * and --lily-logo-min-size (its smallest inline size). Both are unset by
3915
+ * default, so nothing changes until one is set.
3916
+ * ========================================================================== */
3917
+
3918
+ @layer lily {
3919
+ :where([data-lily-logo]) {
3920
+ display: inline-block;
3921
+ padding: var(--lily-logo-clear-space, 0);
3922
+ }
3923
+
3924
+ :where([data-lily-logo] > img, [data-lily-logo] > svg) {
3925
+ display: block;
3926
+ min-inline-size: var(--lily-logo-min-size, 0);
3927
+ max-inline-size: 100%;
3928
+ block-size: auto;
3929
+ }
3930
+ }
3931
+
3932
+ /* ============================================================================
3933
+ * Overflow guards — narrow-viewport safety, shared verbatim by all themes.
3934
+ *
3935
+ * Content that is intrinsically wider than the viewport scrolls or wraps
3936
+ * within its own box instead of pushing the page wider. Deliberately NOT
3937
+ * `display: block` on <table> — that strips implicit table semantics for
3938
+ * assistive technology; capping width and letting cell content wrap keeps
3939
+ * both the layout and the roles. Proven in the example apps' own
3940
+ * stylesheets before the theme layer took over component styling.
3941
+ * ========================================================================= */
3942
+
3943
+ :where(pre) {
3944
+ max-width: 100%;
3945
+ overflow-x: auto;
3946
+ }
3947
+
3948
+ :where(table) {
3949
+ max-width: 100%;
3950
+ }
3951
+
3952
+ :where(th, td) {
3953
+ overflow-wrap: anywhere;
3954
+ }
3955
+
3956
+ :where(a, code) {
3957
+ overflow-wrap: anywhere;
3958
+ }
3959
+
3960
+ /* ============================================================================
3961
+ * Scoped overflow — shared verbatim by all themes.
3962
+ *
3963
+ * Long unbroken strings (a national identifier, an email address, a URL) and
3964
+ * grid/flex tracks that refuse to shrink are the usual causes of horizontal
3965
+ * page scroll (WCAG 1.4.10). Both fixes are scoped, never put on `body` or `*`:
3966
+ * `overflow-wrap: anywhere` on the places a long value lands (table cells are
3967
+ * covered by Overflow guards above), and `min-inline-size: 0` on the direct
3968
+ * children of Lily's grid and flex containers so a child can shrink below its
3969
+ * content. The identifier `-view` list below is the catalog's 70 national
3970
+ * identifier views (components-categories.tsv); bin/check-theme fails when it
3971
+ * drifts from the catalog. :where() keeps specificity at zero.
3972
+ * ========================================================================= */
3973
+
3974
+ :where(
3975
+ .alba-community-health-index-view,
3976
+ .australia-individual-healthcare-identifier-view,
3977
+ .belgique-numero-de-registre-national-view,
3978
+ .bulgaria-edinen-grazhdanski-nomer-view,
3979
+ .cesko-cestovni-pas-view,
3980
+ .cesko-rodne-cislo-view,
3981
+ .cymru-rhif-y-gwasanaeth-iechyd-gwladol-view,
3982
+ .kypros-national-passport-number-view,
3983
+ .danmark-personnummer-view,
3984
+ .deutschland-krankenversichertennummer-view,
3985
+ .eesti-isikukood-view,
3986
+ .ellada-dematerialised-securities-system-view,
3987
+ .england-national-health-service-number-view,
3988
+ .espana-codigo-de-identificacion-fiscal-view,
3989
+ .espana-tarjeta-sanitaria-individual-view,
3990
+ .france-numero-d-identification-au-repertoire-view,
3991
+ .hrvatska-osobni-identifikacijski-broj-view,
3992
+ .eire-individual-health-identifier-view,
3993
+ .island-kennitala-view,
3994
+ .italia-codice-fiscale-view,
3995
+ .latvija-personas-kods-view,
3996
+ .liechtenstein-national-identity-card-number-view,
3997
+ .liechtenstein-passport-number-view,
3998
+ .lietuva-asmens-kodas-view,
3999
+ .lietuva-pasas-view,
4000
+ .malta-national-identification-number-view,
4001
+ .malta-passport-number-view,
4002
+ .nederland-burgerservice-nummer-view,
4003
+ .nederland-identiteitskaart-nummer-view,
4004
+ .nederland-paspoort-nummer-view,
4005
+ .norge-fodselsnummer-view,
4006
+ .tuaisceart-eireann-health-and-care-number-view,
4007
+ .polska-numer-identyfikacji-podatkowej-view,
4008
+ .polska-pesel-view,
4009
+ .portugal-numero-de-identificacao-fiscal-view,
4010
+ .portugal-passaporte-view,
4011
+ .romania-cod-numeric-personal-view,
4012
+ .romania-pasaport-view,
4013
+ .slovenija-emso-view,
4014
+ .slovensko-pas-view,
4015
+ .slovensko-rodne-cislo-view,
4016
+ .suomi-henkilotunnus-view,
4017
+ .sverige-personnummer-view,
4018
+ .iban-view,
4019
+ .united-kingdom-national-health-service-number-view,
4020
+ .united-kingdom-national-insurance-number-view,
4021
+ .united-states-social-security-number-view,
4022
+ .aotearoa-national-health-index-view,
4023
+ .pilipinas-philhealth-identification-number-view,
4024
+ .brasil-cartao-nacional-de-saude-view,
4025
+ .schweiz-ahv-nummer-view,
4026
+ .canada-social-insurance-number-view,
4027
+ .hanguk-jumin-deungnok-beonho-view,
4028
+ .nihon-kojin-bango-view,
4029
+ .bharat-aadhaar-view,
4030
+ .yisrael-teudat-zehut-view,
4031
+ .south-africa-identity-number-view,
4032
+ .mexico-clave-unica-de-registro-de-poblacion-view,
4033
+ .singapore-national-registration-identity-card-view,
4034
+ .osterreich-sozialversicherungsnummer-view,
4035
+ .magyarorszag-taj-szam-view,
4036
+ .luxembourg-matricule-view,
4037
+ .zhongguo-jumin-shenfenzheng-haoma-view,
4038
+ .rossiya-snils-view,
4039
+ .turkiye-tc-kimlik-numarasi-view,
4040
+ .argentina-codigo-unico-de-identificacion-laboral-view,
4041
+ .ukrayina-reyestratsiyniy-nomer-oblikovoyi-kartky-platnyka-podatkiv-view,
4042
+ .indonesia-nomor-induk-kependudukan-view,
4043
+ .prathet-thai-lek-prajam-tua-prachachon-view,
4044
+ .chile-rol-unico-nacional-view,
4045
+ .taiwan-guomin-shenfenzheng-tongyi-bianhao-view,
4046
+ .hong-kong-identity-card-view,
4047
+ .iran-kod-e-melli-view,
4048
+ .as-saudiyah-raqm-al-huwiyah-al-wataniyah-view,
4049
+ .malaysia-kad-pengenalan-view,
4050
+ .viet-nam-can-cuoc-cong-dan-view,
4051
+ .pakistan-computerized-national-identity-card-view,
4052
+ .uruguay-cedula-de-identidad-view,
4053
+ .ecuador-cedula-de-identidad-view,
4054
+ .colombia-cedula-de-ciudadania-view,
4055
+ .australia-tax-file-number-view,
4056
+ .nigeria-national-identification-number-view,
4057
+ .misr-al-raqm-al-qawmi-view
4058
+ ) {
4059
+ overflow-wrap: anywhere;
4060
+ }
4061
+
4062
+ /* overflow-wrap is inherited, so this reaches whatever the consumer puts in an item (dd, span, strong). */
4063
+ :where(.summary-list, .summary-list-item, .description-list) {
4064
+ overflow-wrap: anywhere;
4065
+ }
4066
+
4067
+ :where(
4068
+ .grid,
4069
+ .flex-stack,
4070
+ .grail-layout,
4071
+ .summary-list,
4072
+ .summary-list-item,
4073
+ .description-list,
4074
+ .form,
4075
+ .editable-form,
4076
+ .field,
4077
+ .input-group,
4078
+ .button-group,
4079
+ .radio-group,
4080
+ .checkbox-group,
4081
+ .toggle-group,
4082
+ .tag-group,
4083
+ .split-view,
4084
+ .photo-pack,
4085
+ .data-filter-form,
4086
+ .action-bar,
4087
+ .tool-bar,
4088
+ .menu-bar,
4089
+ .tab-bar
4090
+ ) > :where(*) {
4091
+ min-inline-size: 0;
4092
+ }
4093
+
4094
+ /* ============================================================================
4095
+ * Helper packages — layout and look, shared verbatim by all themes.
4096
+ *
4097
+ * The data grid, kanban board, Gantt chart and calendar view are wide by nature.
4098
+ * Each root scrolls sideways inside its own box and its table sizes to its content,
4099
+ * so a narrow screen scrolls the table instead of wrapping every cell to a letter
4100
+ * (the overflow guard above lets cells break anywhere, which is right for prose and
4101
+ * wrong for a table that has a scroller). The toolbar controls of all four helpers
4102
+ * share the data grid's look, every button has a >=24px target (WCAG 2.2 2.5.8), and
4103
+ * the Gantt chart draws its bars from `data-in-range`, a milestone as a diamond and
4104
+ * today as a rule. :where() keeps specificity at zero, so any consumer rule wins.
4105
+ * ========================================================================= */
4106
+
4107
+ :where(.data-grid, .kanban-board, .gantt-chart, .calendar-view) {
4108
+ max-inline-size: 100%;
4109
+ overflow-x: auto;
4110
+ }
4111
+
4112
+ :where(.data-grid table, .kanban-board table, .gantt-chart table, .calendar-view table) {
4113
+ inline-size: max-content;
4114
+ min-inline-size: 100%;
4115
+ }
4116
+
4117
+ :where(.kanban-board-export, .gantt-chart-export, .gantt-chart-zoom, .calendar-view-export,
4118
+ .calendar-view-toolbar, .calendar-view-nav, .calendar-view-switcher) {
4119
+ display: flex;
4120
+ flex-wrap: wrap;
4121
+ gap: var(--lily-space-sm);
4122
+ align-items: center;
4123
+ margin-block-end: var(--lily-space-sm);
4124
+ }
4125
+
4126
+ :where(.kanban-board-search, .gantt-chart-search, .calendar-view-search) {
4127
+ padding: var(--lily-space-xs) var(--lily-space-sm);
4128
+ border: var(--border) solid var(--lily-border);
4129
+ border-radius: var(--lily-radius-sm);
4130
+ background-color: var(--lily-surface);
4131
+ color: var(--lily-text);
4132
+ font: inherit;
4133
+ min-block-size: 24px;
4134
+ }
4135
+
4136
+ :where(.kanban-board-export-button,
4137
+ .kanban-board-add-button,
4138
+ .kanban-board-undo-button,
4139
+ .kanban-board-collapse-button,
4140
+ .kanban-board-lane-toggle,
4141
+ .kanban-board-move-button,
4142
+ .gantt-chart-export-button,
4143
+ .gantt-chart-zoom-in-button,
4144
+ .gantt-chart-zoom-out-button,
4145
+ .gantt-chart-collapse-button,
4146
+ .gantt-chart-collapse-all-button,
4147
+ .gantt-chart-expand-all-button,
4148
+ .gantt-chart-save-button,
4149
+ .gantt-chart-cancel-button,
4150
+ .calendar-view-export-button,
4151
+ .calendar-view-previous-button,
4152
+ .calendar-view-next-button,
4153
+ .calendar-view-today-button,
4154
+ .calendar-view-switcher-button) {
4155
+ padding: var(--lily-space-xs) var(--lily-space-md);
4156
+ border: var(--border) solid var(--lily-border);
4157
+ border-radius: var(--lily-radius-sm);
4158
+ background-color: var(--lily-surface);
4159
+ color: var(--lily-text);
4160
+ font: inherit;
4161
+ cursor: pointer;
4162
+ min-inline-size: 24px;
4163
+ min-block-size: 24px;
4164
+ }
4165
+
4166
+ :where(.kanban-board-export-button:disabled,
4167
+ .kanban-board-add-button:disabled,
4168
+ .kanban-board-undo-button:disabled,
4169
+ .gantt-chart-export-button:disabled,
4170
+ .gantt-chart-zoom-in-button:disabled,
4171
+ .gantt-chart-zoom-out-button:disabled,
4172
+ .calendar-view-export-button:disabled,
4173
+ .calendar-view-previous-button[aria-disabled="true"],
4174
+ .calendar-view-next-button[aria-disabled="true"]) {
4175
+ opacity: 0.5;
4176
+ cursor: not-allowed;
4177
+ }
4178
+
4179
+ :where(.calendar-view-switcher-button[aria-pressed="true"]) {
4180
+ background-color: var(--color-base-300);
4181
+ font-weight: 600;
4182
+ }
4183
+
4184
+ :where(.kanban-board-status, .gantt-chart-status, .calendar-view-status) {
4185
+ margin-block-start: var(--lily-space-sm);
4186
+ font-size: 0.875em;
4187
+ opacity: 0.8;
4188
+ }
4189
+
4190
+ :where(.calendar-view-event) {
4191
+ display: block;
4192
+ padding: 0 var(--lily-space-xs);
4193
+ border-radius: var(--lily-radius-sm);
4194
+ background-color: color-mix(in srgb, var(--color-primary) 18%, transparent);
4195
+ font-size: 0.75rem;
4196
+ }
4197
+
4198
+ :where(.gantt-table-th) {
4199
+ min-inline-size: 2rem;
4200
+ padding-inline: 0.25rem;
4201
+ font-size: 0.75rem;
4202
+ text-align: center;
4203
+ white-space: nowrap;
4204
+ }
4205
+
4206
+ :where(.gantt-table-th[scope="row"], .gantt-table-th:first-child) {
4207
+ min-inline-size: 10rem;
4208
+ padding-inline: var(--lily-space-sm);
4209
+ font-size: inherit;
4210
+ text-align: start;
4211
+ white-space: normal;
4212
+ }
4213
+
4214
+ :where(.gantt-table-td) {
4215
+ min-block-size: 1.7rem;
4216
+ padding: 0;
4217
+ }
4218
+
4219
+ :where(.gantt-table-td[data-in-range]) {
4220
+ background-color: var(--color-primary);
4221
+ /* Text inside a bar (the Alpine port's milestone label, say) sits on the primary fill. */
4222
+ color: var(--color-primary-content);
4223
+ }
4224
+
4225
+ :where(.gantt-table-td[data-in-range][data-milestone]) {
4226
+ clip-path: polygon(50% 8%, 92% 50%, 50% 92%, 8% 50%);
4227
+ }
4228
+
4229
+ :where(.gantt-table-td[data-today]) {
4230
+ border-inline-start: 2px solid var(--color-accent);
4231
+ }
4232
+
4233
+ /* The time zone picker's options are long ("UTC+10:30 – Lord Howe Daylight Time"): size the list to its widest
4234
+ option up to a cap, then wrap, rather than clipping the name or running off a phone. */
4235
+ :where(.time-zone-picker-list) {
4236
+ inline-size: max-content;
4237
+ max-inline-size: min(26rem, 90vw);
4238
+ max-block-size: min(24rem, 70vh);
4239
+ overflow-y: auto;
4240
+ }
4241
+
4242
+ :where(.time-zone-picker-option) {
4243
+ overflow-wrap: anywhere;
4244
+ }
4245
+
4246
+ /* ============================================================================
4247
+ * Overflow navigation — shared verbatim by all themes.
4248
+ *
4249
+ * The `overflow` mode of `navigation-menu` (contract: components/navigation-menu/index.md).
4250
+ * With no script the list simply wraps, so every link stays reachable at 320 CSS pixels
4251
+ * (WCAG 1.4.10) and the "More" item stays `hidden`. With the script a ResizeObserver moves
4252
+ * the links that do not fit into the More list. That list is in normal flow (position
4253
+ * static, never absolute), so opening it pushes the content below down and never covers it
4254
+ * (WCAG 2.4.11). Every target is at least 24 x 24 CSS pixels (WCAG 2.5.8). :where() keeps
4255
+ * specificity at zero, so any consumer rule wins.
4256
+ * ========================================================================= */
4257
+
4258
+ :where(.navigation-menu-list) {
4259
+ display: flex;
4260
+ flex: 1 1 auto;
4261
+ flex-wrap: wrap;
4262
+ align-items: flex-start;
4263
+ gap: 0.25rem 0.5rem;
4264
+ min-inline-size: 0;
4265
+ margin: 0;
4266
+ padding: 0;
4267
+ list-style: none;
4268
+ }
4269
+
4270
+ :where(.navigation-menu-list > li > a, .navigation-menu-more-list > li > a) {
4271
+ display: inline-block;
4272
+ min-inline-size: 24px;
4273
+ min-block-size: 24px;
4274
+ }
4275
+
4276
+ :where(.navigation-menu-more) {
4277
+ position: static;
4278
+ }
4279
+
4280
+ :where(.navigation-menu-more[hidden], .navigation-menu-more-list[hidden]) {
4281
+ display: none;
4282
+ }
4283
+
4284
+ :where(.navigation-menu-more-button) {
4285
+ min-inline-size: 24px;
4286
+ min-block-size: 24px;
4287
+ padding: 0.25rem 0.5rem;
4288
+ font: inherit;
4289
+ color: inherit;
4290
+ background: transparent;
4291
+ border: 1px solid currentcolor;
4292
+ cursor: pointer;
4293
+ }
4294
+
4295
+ :where(.navigation-menu-more-list) {
4296
+ position: static;
4297
+ margin: 0.25rem 0 0;
4298
+ padding: 0;
4299
+ list-style: none;
4300
+ }
4301
+
4302
+ /* ============================================================================
4303
+ * Container queries — shared verbatim by all themes.
4304
+ *
4305
+ * A component answers to the space it is given, not to the window: a summary
4306
+ * list in a narrow aside on a wide screen needs the narrow layout, which the
4307
+ * viewport query above cannot know. The three grail-layout regions are the
4308
+ * containers because their width comes from a grid track (`minmax(0, 1fr)`), so
4309
+ * inline-size containment cannot collapse them. `.card` is deliberately not a
4310
+ * container: in a shrink-to-fit parent (a flex row, an inline grid) containment
4311
+ * would collapse it to nothing. A component outside a grail region has no
4312
+ * container and keeps its wide layout. :where() keeps specificity at zero, so
4313
+ * any consumer rule wins.
4314
+ * ========================================================================= */
4315
+
4316
+ :where(.grail-layout-left-aside, .grail-layout-center-main, .grail-layout-right-aside) {
4317
+ container-type: inline-size;
4318
+ }
4319
+
4320
+ /* grail-layout collapse (contract: components/grail-layout/AGENTS.md). The page shell answers to the
4321
+ window, so these are viewport queries: the grail-layout root is not a container, only its regions are.
4322
+ Priority: the right aside collapses first (at lg it drops below main, full width), then the left aside
4323
+ (at md everything is one column). Every step keeps DOM order = visual order (header, left aside, main,
4324
+ right aside, footer), so reading and tab order match what is seen (WCAG 1.3.2, 2.4.3); nothing is
4325
+ hidden, so all content stays reachable. */
4326
+ @media (max-width: 64rem) {
4327
+ :where(.grail-layout) {
4328
+ grid-template-areas:
4329
+ "header header"
4330
+ "left main"
4331
+ "right right"
4332
+ "footer footer";
4333
+ grid-template-columns: minmax(0, 1fr) minmax(0, 3fr);
4334
+ grid-template-rows: auto auto auto auto;
4335
+ }
4336
+ }
4337
+
4338
+ @media (max-width: 48rem) {
4339
+ :where(.grail-layout) {
4340
+ grid-template-areas:
4341
+ "header"
4342
+ "left"
4343
+ "main"
4344
+ "right"
4345
+ "footer";
4346
+ grid-template-columns: minmax(0, 1fr);
4347
+ grid-template-rows: auto auto auto auto auto;
4348
+ }
4349
+ }
4350
+
4351
+ @container (max-width: 30rem) {
4352
+ :where(.summary-list) {
4353
+ grid-template-columns: minmax(0, 1fr);
4354
+ row-gap: 0.25rem;
4355
+ }
4356
+
4357
+ :where(.kanban-table-td) {
4358
+ min-width: 10rem;
4359
+ }
4360
+
4361
+ :where(.data-table, .calendar-table, .kanban-table, .gantt-table) :where(th, td) {
4362
+ padding-inline: 0.5rem;
4363
+ }
4364
+ }
4365
+
4366
+ /* ============================================================================
4367
+ * Focus not obscured — shared verbatim by all themes.
4368
+ *
4369
+ * WCAG 2.2 SC 2.4.11 (Focus Not Obscured, Minimum): a focused control is never
4370
+ * entirely hidden by author-created content, such as a bar fixed to the top or
4371
+ * bottom of the viewport. The browser scrolls a focused control into view, but
4372
+ * only as far as the edge of the viewport, which is where a fixed bar sits.
4373
+ * `scroll-padding` moves that edge in by the bar's height.
4374
+ *
4375
+ * Contract: whatever fixes a bar to the viewport (sticky-promo-banner, a
4376
+ * theme's or an app's own fixed header, a mobile bottom navigation) sets the
4377
+ * bar's height on the root element:
4378
+ *
4379
+ * --lily-fixed-bar-block-start: 4rem; (a bar fixed at the top)
4380
+ * --lily-fixed-bar-block-end: 3rem; (a bar fixed at the bottom)
4381
+ *
4382
+ * With both bars, set both. The defaults are 0px, so a page without a fixed bar
4383
+ * is unchanged. Specificity is zero (`:where`), so an app's own
4384
+ * `scroll-padding` wins.
4385
+ * ========================================================================= */
4386
+
4387
+ :where(html) {
4388
+ scroll-padding-block: var(--lily-fixed-bar-block-start, 0px) var(--lily-fixed-bar-block-end, 0px);
4389
+ }
4390
+
4391
+ /* ============================================================================
4392
+ * Text size and motion preferences — shared verbatim by all themes.
4393
+ *
4394
+ * The text-size-picker sets `data-text-size` on the root and the motion-picker
4395
+ * sets `data-motion`; neither styles anything itself. These rules are what make
4396
+ * the choice visible.
4397
+ *
4398
+ * Text size: the seven steps scale the root font size, and every theme sizes in
4399
+ * `rem`, so the whole page follows (WCAG 1.4.4). `normal` is 100% (the viewer's
4400
+ * own browser setting), and the scale is percentages of that, never pixels, so
4401
+ * a viewer who raised their browser font size keeps it. The steps below
4402
+ * `normal` are the viewer's explicit choice.
4403
+ *
4404
+ * Motion: `data-motion="reduce"` shortens every animation and transition to
4405
+ * 0.01ms instead of removing it. The end state still arrives and `animationend`
4406
+ * / `transitionend` still fire, so nothing that depends on them breaks and no
4407
+ * information is lost: a spinner's `role="status"` text, a disclosure's open
4408
+ * state and a toast's content are untouched. Scrolling is instant.
4409
+ * ========================================================================= */
4410
+
4411
+ :where(html[data-text-size="smallest"]) { font-size: 80%; }
4412
+ :where(html[data-text-size="smaller"]) { font-size: 90%; }
4413
+ :where(html[data-text-size="small"]) { font-size: 95%; }
4414
+ :where(html[data-text-size="normal"]) { font-size: 100%; }
4415
+ :where(html[data-text-size="large"]) { font-size: 110%; }
4416
+ :where(html[data-text-size="larger"]) { font-size: 125%; }
4417
+ :where(html[data-text-size="largest"]) { font-size: 150%; }
4418
+
4419
+ :where(html[data-motion="reduce"]) {
4420
+ scroll-behavior: auto;
4421
+ }
4422
+ :where(html[data-motion="reduce"], html[data-motion="reduce"] *, html[data-motion="reduce"] *::before, html[data-motion="reduce"] *::after) {
4423
+ animation-duration: 0.01ms !important;
4424
+ animation-iteration-count: 1 !important;
4425
+ animation-delay: 0s !important;
4426
+ transition-duration: 0.01ms !important;
4427
+ transition-delay: 0s !important;
4428
+ scroll-behavior: auto !important;
4429
+ }
4430
+
4431
+ /* ============================================================================
4432
+ * Forced colours and contrast preference — shared verbatim by all themes.
4433
+ *
4434
+ * Forced colours (Windows High Contrast, `forced-colors: active`): the browser
4435
+ * replaces every colour with a system colour, which erases anything a control
4436
+ * told apart by background alone. These rules give such controls a border,
4437
+ * keep the focus ring, and mark selected and current items with `Highlight`
4438
+ * and `SelectedItem` rather than a tint (WCAG 1.4.1, 2.4.7, 1.4.11).
4439
+ *
4440
+ * Contrast preference (`prefers-contrast: more`): a heavier border on controls
4441
+ * and a thicker focus ring. Colours are not changed here; a theme's colours are
4442
+ * its author's decision.
4443
+ * ========================================================================= */
4444
+
4445
+ @media (forced-colors: active) {
4446
+ :where(button, input, select, textarea, summary, [role="button"], [role="option"], [role="tab"], [role="menuitem"], [role="switch"], [role="checkbox"], [role="radio"], [class$="-button"]) {
4447
+ border: 1px solid ButtonBorder;
4448
+ }
4449
+ :where(a, button, input, select, textarea, summary, [tabindex], [role="button"], [role="option"], [role="tab"], [class$="-button"]):focus-visible {
4450
+ outline: 3px solid Highlight;
4451
+ outline-offset: 2px;
4452
+ }
4453
+ :where([aria-selected="true"], [aria-pressed="true"], [aria-checked="true"], [data-active]) {
4454
+ background: SelectedItem;
4455
+ color: SelectedItemText;
4456
+ forced-color-adjust: none;
4457
+ }
4458
+ :where([aria-current]:not([aria-current="false"])) {
4459
+ outline: 2px solid Highlight;
4460
+ outline-offset: -2px;
4461
+ }
4462
+ :where(:disabled, [aria-disabled="true"]) {
4463
+ border-color: GrayText;
4464
+ color: GrayText;
4465
+ }
4466
+ }
4467
+
4468
+ @media (prefers-contrast: more) {
4469
+ :where(button, input, select, textarea, [role="button"], [class$="-button"]) {
4470
+ border-width: 2px;
4471
+ border-style: solid;
4472
+ border-color: currentColor;
4473
+ }
4474
+ :where(a, button, input, select, textarea, summary, [tabindex], [role="button"], [role="option"], [role="tab"], [class$="-button"]):focus-visible {
4475
+ outline-width: 3px;
4476
+ outline-offset: 2px;
4477
+ }
4478
+ }
4479
+
4480
+ /* ============================================================================
4481
+ * Writing systems — shared verbatim by all themes.
4482
+ *
4483
+ * A theme names its Latin font stack (`--lily-font-body-latin`). This section
4484
+ * builds `--lily-font-body` from it, with the system fonts of the element's
4485
+ * script in front: Chinese (Simplified, Traditional, Hong Kong), Japanese,
4486
+ * Korean, Arabic script (Arabic, Persian, Urdu in Nastaliq), Hebrew, the Indic
4487
+ * scripts the site's locales use (Devanagari, Bengali, Gujarati, Gurmukhi,
4488
+ * Kannada, Tamil, Telugu), Thai, Lao and Khmer. Han characters are shared by
4489
+ * Chinese and Japanese but drawn differently, so the language picks the font,
4490
+ * not the script.
4491
+ *
4492
+ * `--lily-font-body` is re-declared on every element with a `lang`, so a
4493
+ * `<p lang="ja">` inside an English page gets Japanese fonts and a
4494
+ * `<span lang="en">` inside it gets none. A language not listed here keeps the
4495
+ * Latin stack, whose final generic family the browser resolves per language.
4496
+ *
4497
+ * `--lily-line-height-body` is 1.5, and 1.75 for scripts with tall marks above
4498
+ * and below the line (Arabic script, the Indic scripts, Thai, Lao, Khmer).
4499
+ * Elements with a `lang` use it; the app's body text should too.
4500
+ *
4501
+ * To change a theme's font, set `--lily-font-body-latin`; to change a script's,
4502
+ * set `--lily-font-script` under `:lang(...)`. Overriding `--lily-font-body`
4503
+ * itself still works on the element you set it on.
4504
+ * ========================================================================= */
4505
+
4506
+ :where(:root, [lang]) {
4507
+ --lily-font-body: var(--lily-font-script,) var(--lily-font-body-latin);
4508
+ --lily-line-height-body: 1.5;
4509
+ }
4510
+
4511
+ :where([lang]) {
4512
+ --lily-font-script: initial;
4513
+ line-height: var(--lily-line-height-body);
4514
+ }
4515
+
4516
+ :where([lang|="zh"], [lang|="wuu"]) {
4517
+ --lily-font-script: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", "Noto Sans CJK SC", "Source Han Sans SC",;
4518
+ }
4519
+ :where([lang|="zh-Hant"], [lang="zh-TW"], [lang|="zh-Hant-TW"]) {
4520
+ --lily-font-script: "PingFang TC", "Microsoft JhengHei", "Noto Sans TC", "Noto Sans CJK TC", "Source Han Sans TC",;
4521
+ }
4522
+ :where([lang="zh-HK"], [lang="zh-MO"], [lang|="zh-Hant-HK"], [lang|="yue"]) {
4523
+ --lily-font-script: "PingFang HK", "Microsoft JhengHei", "Noto Sans HK", "Noto Sans CJK HK", "Source Han Sans HC",;
4524
+ }
4525
+ :where([lang|="ja"]) {
4526
+ --lily-font-script: "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", "Yu Gothic", Meiryo, "Noto Sans JP", "Noto Sans CJK JP",;
4527
+ }
4528
+ :where([lang|="ko"]) {
4529
+ --lily-font-script: "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", "Noto Sans CJK KR",;
4530
+ }
4531
+ :where([lang|="ar"], [lang|="fa"], [lang|="ps"], [lang|="ku"], [lang|="sd"], [lang|="ug"]) {
4532
+ --lily-font-script: "Segoe UI", "Geeza Pro", "Noto Sans Arabic", "Noto Naskh Arabic", Tahoma,;
4533
+ }
4534
+ :where([lang|="ur"]) {
4535
+ --lily-font-script: "Noto Nastaliq Urdu", "Jameel Noori Nastaleeq", "Segoe UI", "Geeza Pro", "Noto Sans Arabic", Tahoma,;
4536
+ }
4537
+ :where([lang|="he"], [lang|="yi"]) {
4538
+ --lily-font-script: "Segoe UI", "Arial Hebrew", "Noto Sans Hebrew",;
4539
+ }
4540
+ :where([lang|="hi"], [lang|="mr"], [lang|="ne"], [lang|="bh"], [lang|="sa"], [lang|="mai"]) {
4541
+ --lily-font-script: "Nirmala UI", "Kohinoor Devanagari", "Devanagari Sangam MN", "Noto Sans Devanagari",;
4542
+ }
4543
+ :where([lang|="bn"], [lang|="as"]) {
4544
+ --lily-font-script: "Nirmala UI", "Kohinoor Bangla", "Bangla Sangam MN", "Noto Sans Bengali",;
4545
+ }
4546
+ :where([lang|="gu"]) {
4547
+ --lily-font-script: "Nirmala UI", "Kohinoor Gujarati", "Gujarati Sangam MN", "Noto Sans Gujarati",;
4548
+ }
4549
+ :where([lang|="pa"]) {
4550
+ --lily-font-script: "Nirmala UI", "Gurmukhi Sangam MN", "Gurmukhi MN", "Noto Sans Gurmukhi",;
4551
+ }
4552
+ :where([lang|="kn"]) {
4553
+ --lily-font-script: "Nirmala UI", "Kannada Sangam MN", "Noto Sans Kannada",;
4554
+ }
4555
+ :where([lang|="ta"]) {
4556
+ --lily-font-script: "Nirmala UI", "Tamil Sangam MN", "Noto Sans Tamil",;
4557
+ }
4558
+ :where([lang|="te"]) {
4559
+ --lily-font-script: "Nirmala UI", "Kohinoor Telugu", "Telugu Sangam MN", "Noto Sans Telugu",;
4560
+ }
4561
+ :where([lang|="th"]) {
4562
+ --lily-font-script: "Leelawadee UI", Thonburi, "Noto Sans Thai",;
4563
+ }
4564
+ :where([lang|="lo"]) {
4565
+ --lily-font-script: "Leelawadee UI", "Lao Sangam MN", "Noto Sans Lao",;
4566
+ }
4567
+ :where([lang|="km"]) {
4568
+ --lily-font-script: "Khmer UI", "Khmer Sangam MN", "Noto Sans Khmer",;
4569
+ }
4570
+ :where([lang|="ar"], [lang|="fa"], [lang|="ps"], [lang|="ku"], [lang|="sd"], [lang|="ug"], [lang|="ur"],
4571
+ [lang|="hi"], [lang|="mr"], [lang|="ne"], [lang|="bh"], [lang|="sa"], [lang|="mai"], [lang|="bn"],
4572
+ [lang|="as"], [lang|="gu"], [lang|="pa"], [lang|="kn"], [lang|="ta"], [lang|="te"], [lang|="th"],
4573
+ [lang|="lo"], [lang|="km"]) {
4574
+ --lily-line-height-body: 1.75;
4575
+ }