@ponchia/ui 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -175
- package/behaviors/index.d.ts +27 -0
- package/behaviors/index.js +305 -0
- package/classes/index.d.ts +41 -0
- package/classes/index.js +41 -0
- package/css/disclosure.css +180 -0
- package/css/dots.css +42 -4
- package/css/feedback.css +111 -0
- package/css/forms.css +44 -0
- package/css/overlay.css +48 -0
- package/css/primitives.css +60 -0
- package/css/site.css +33 -0
- package/dist/bronto.css +1 -1
- package/dist/css/disclosure.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/forms.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/primitives.css +1 -1
- package/dist/css/site.css +1 -1
- package/docs/reference.md +73 -1
- package/docs/usage.md +110 -0
- package/fonts/OFL.txt +93 -0
- package/glyphs/glyphs.d.ts +102 -0
- package/glyphs/glyphs.js +922 -0
- package/llms.txt +11 -0
- package/package.json +28 -4
package/css/disclosure.css
CHANGED
|
@@ -321,3 +321,183 @@
|
|
|
321
321
|
.ui-avatar-group .ui-avatar:not(:first-child) {
|
|
322
322
|
margin-inline-start: -0.5rem;
|
|
323
323
|
}
|
|
324
|
+
|
|
325
|
+
/* --- Carousel — a scroll-snap track of slides with an optional thumbnail
|
|
326
|
+
strip. Native horizontal scroll gives touch/trackpad swipe + momentum
|
|
327
|
+
for free; initCarousel adds prev/next, keyboard, thumbnail sync and the
|
|
328
|
+
position counter. A full-screen gallery is this same carousel inside a
|
|
329
|
+
native <dialog class="ui-lightbox"> (see overlay.css) — the dialog
|
|
330
|
+
brings the top layer, focus-trap and Esc; nothing is re-implemented. --- */
|
|
331
|
+
|
|
332
|
+
.ui-carousel {
|
|
333
|
+
display: grid;
|
|
334
|
+
gap: var(--space-sm);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* Positioned frame for the viewport + overlay controls. The arrows can't
|
|
338
|
+
live inside the scroll viewport (absolute children of a scroll container
|
|
339
|
+
scroll away with the content), so they anchor to this stage instead. */
|
|
340
|
+
.ui-carousel__stage {
|
|
341
|
+
position: relative;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.ui-carousel__viewport {
|
|
345
|
+
display: flex;
|
|
346
|
+
overflow-x: auto;
|
|
347
|
+
scroll-behavior: smooth;
|
|
348
|
+
scroll-snap-type: x mandatory;
|
|
349
|
+
scrollbar-width: none;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.ui-carousel__viewport::-webkit-scrollbar {
|
|
353
|
+
display: none;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.ui-carousel__viewport:focus-visible {
|
|
357
|
+
outline: 2px solid var(--focus-ring);
|
|
358
|
+
outline-offset: -2px;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.ui-carousel__slide {
|
|
362
|
+
flex: 0 0 100%;
|
|
363
|
+
scroll-snap-align: center;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.ui-carousel__slide > :is(img, picture, .ui-ratio) {
|
|
367
|
+
display: block;
|
|
368
|
+
inline-size: 100%;
|
|
369
|
+
object-fit: cover;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Prev / next — hairline round controls over the stage edges. */
|
|
373
|
+
.ui-carousel__prev,
|
|
374
|
+
.ui-carousel__next {
|
|
375
|
+
align-items: center;
|
|
376
|
+
background: color-mix(in srgb, var(--panel-strong) 80%, transparent);
|
|
377
|
+
border: 1px solid var(--line-strong);
|
|
378
|
+
border-radius: var(--radius-pill);
|
|
379
|
+
color: var(--text);
|
|
380
|
+
cursor: pointer;
|
|
381
|
+
display: inline-flex;
|
|
382
|
+
block-size: 2.2rem;
|
|
383
|
+
inline-size: 2.2rem;
|
|
384
|
+
inset-block-start: 50%;
|
|
385
|
+
justify-content: center;
|
|
386
|
+
position: absolute;
|
|
387
|
+
transform: translateY(-50%);
|
|
388
|
+
z-index: var(--z-raised);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.ui-carousel__prev {
|
|
392
|
+
inset-inline-start: var(--space-xs);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.ui-carousel__next {
|
|
396
|
+
inset-inline-end: var(--space-xs);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Chevron glyph, mirrors for RTL via the logical border edges. */
|
|
400
|
+
.ui-carousel__prev::before,
|
|
401
|
+
.ui-carousel__next::before {
|
|
402
|
+
block-size: 0.5rem;
|
|
403
|
+
border-block-start: 1.5px solid currentColor;
|
|
404
|
+
border-inline-end: 1.5px solid currentColor;
|
|
405
|
+
content: '';
|
|
406
|
+
inline-size: 0.5rem;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.ui-carousel__prev::before {
|
|
410
|
+
margin-inline-start: 0.2rem;
|
|
411
|
+
transform: rotate(-135deg);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.ui-carousel__next::before {
|
|
415
|
+
margin-inline-end: 0.2rem;
|
|
416
|
+
transform: rotate(45deg);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* The chevron direction is a physical rotation, so flip it for RTL (the
|
|
420
|
+
buttons already swap sides via the logical insets above). */
|
|
421
|
+
[dir='rtl'] .ui-carousel__prev::before {
|
|
422
|
+
transform: rotate(45deg);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
[dir='rtl'] .ui-carousel__next::before {
|
|
426
|
+
transform: rotate(-135deg);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.ui-carousel__prev:disabled,
|
|
430
|
+
.ui-carousel__next:disabled {
|
|
431
|
+
cursor: not-allowed;
|
|
432
|
+
opacity: 0.35;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.ui-carousel__prev:focus-visible,
|
|
436
|
+
.ui-carousel__next:focus-visible {
|
|
437
|
+
outline: 2px solid var(--focus-ring);
|
|
438
|
+
outline-offset: 2px;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Position counter ("3 / 12") — initCarousel fills the text; aria-live. */
|
|
442
|
+
.ui-carousel__status {
|
|
443
|
+
background: color-mix(in srgb, var(--panel-strong) 75%, transparent);
|
|
444
|
+
border-radius: var(--radius-pill);
|
|
445
|
+
color: var(--text);
|
|
446
|
+
font-family: var(--mono);
|
|
447
|
+
font-size: var(--text-2xs);
|
|
448
|
+
inset-block-end: var(--space-xs);
|
|
449
|
+
inset-inline-start: var(--space-xs);
|
|
450
|
+
letter-spacing: var(--tracking-wide);
|
|
451
|
+
padding: 0.2rem 0.5rem;
|
|
452
|
+
position: absolute;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* Thumbnail strip — clicking a thumb jumps the track; active = accent. */
|
|
456
|
+
.ui-carousel__thumbs {
|
|
457
|
+
display: flex;
|
|
458
|
+
gap: 0.4rem;
|
|
459
|
+
list-style: none;
|
|
460
|
+
margin: 0;
|
|
461
|
+
overflow-x: auto;
|
|
462
|
+
padding: 0;
|
|
463
|
+
scroll-behavior: smooth;
|
|
464
|
+
scrollbar-width: none;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.ui-carousel__thumbs::-webkit-scrollbar {
|
|
468
|
+
display: none;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.ui-carousel__thumb {
|
|
472
|
+
background: transparent;
|
|
473
|
+
border: 1px solid var(--line);
|
|
474
|
+
border-radius: var(--radius-sm);
|
|
475
|
+
cursor: pointer;
|
|
476
|
+
flex: 0 0 auto;
|
|
477
|
+
block-size: 3rem;
|
|
478
|
+
inline-size: 4rem;
|
|
479
|
+
overflow: hidden;
|
|
480
|
+
padding: 0;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.ui-carousel__thumb > img {
|
|
484
|
+
block-size: 100%;
|
|
485
|
+
inline-size: 100%;
|
|
486
|
+
object-fit: cover;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.ui-carousel__thumb[aria-current='true'] {
|
|
490
|
+
border-color: var(--accent);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.ui-carousel__thumb:focus-visible {
|
|
494
|
+
outline: 2px solid var(--focus-ring);
|
|
495
|
+
outline-offset: 2px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
@media (prefers-reduced-motion: reduce) {
|
|
499
|
+
.ui-carousel__viewport,
|
|
500
|
+
.ui-carousel__thumbs {
|
|
501
|
+
scroll-behavior: auto;
|
|
502
|
+
}
|
|
503
|
+
}
|
package/css/dots.css
CHANGED
|
@@ -47,17 +47,21 @@
|
|
|
47
47
|
/* Data-bound dot matrix — a grid of state cells (activity / heatmap),
|
|
48
48
|
the on-brand counterpart to the decorative .ui-dotgrid background.
|
|
49
49
|
This is the grid + cell tones only; the data→cell mapping is the
|
|
50
|
-
consumer's. Tune density via --dotmatrix-cols / --dotmatrix-gap
|
|
50
|
+
consumer's. Tune density via --dotmatrix-cols / --dotmatrix-gap; set
|
|
51
|
+
--dotmatrix-dot to a fixed dot length (intrinsic sizing, e.g. for the
|
|
52
|
+
display glyphs in @ponchia/ui/glyphs) instead of stretching to fit;
|
|
53
|
+
set --dotmatrix-dot-radius: 0 (with --dotmatrix-gap: 0) to fuse the dots
|
|
54
|
+
into a crisp pixel glyph that stays legible at small/inline sizes. */
|
|
51
55
|
.ui-dotmatrix {
|
|
52
56
|
display: grid;
|
|
53
57
|
gap: var(--dotmatrix-gap, 0.5rem);
|
|
54
|
-
grid-template-columns: repeat(var(--dotmatrix-cols, 12), minmax(0, 1fr));
|
|
58
|
+
grid-template-columns: repeat(var(--dotmatrix-cols, 12), var(--dotmatrix-dot, minmax(0, 1fr)));
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
.ui-dotmatrix__cell {
|
|
58
62
|
aspect-ratio: 1;
|
|
59
63
|
background: var(--field-dot);
|
|
60
|
-
border-radius: 50
|
|
64
|
+
border-radius: var(--dotmatrix-dot-radius, 50%);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
.ui-dotmatrix__cell--hot {
|
|
@@ -68,6 +72,38 @@
|
|
|
68
72
|
background: var(--field-dot-accent);
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
/* Animation (opt-in, decorative): a dot-matrix that "plays". Meaning stays
|
|
76
|
+
in the static cells + the element's label — motion is enhancement only,
|
|
77
|
+
and the reduced-motion block below switches it off. */
|
|
78
|
+
|
|
79
|
+
/* --reveal: cells power on in a left→right, top→bottom scan. The per-cell
|
|
80
|
+
delay reads `--i` (the row-major index) which the renderer sets. */
|
|
81
|
+
.ui-dotmatrix--reveal .ui-dotmatrix__cell {
|
|
82
|
+
animation: dotmatrixCellOn var(--duration-base) var(--ease-out) both;
|
|
83
|
+
animation-delay: calc(var(--i, 0) * var(--dotmatrix-reveal-step, 3ms));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* `from`-only on purpose: when reduced-motion kills the animation the cells
|
|
87
|
+
stay at their natural opacity:1 — i.e. the settled end-state still shows. */
|
|
88
|
+
@keyframes dotmatrixCellOn {
|
|
89
|
+
from {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
transform: scale(0.35);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* --pulse: the whole glyph breathes, for a "live"/attention state. */
|
|
96
|
+
.ui-dotmatrix--pulse {
|
|
97
|
+
animation: dotmatrixPulse 1.8s var(--ease-standard) infinite;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes dotmatrixPulse {
|
|
101
|
+
50% {
|
|
102
|
+
opacity: 0.55;
|
|
103
|
+
transform: scale(0.94);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
71
107
|
/* Status dot — the glyph-style state indicator. */
|
|
72
108
|
.ui-dot {
|
|
73
109
|
background: var(--text-dim);
|
|
@@ -289,7 +325,9 @@
|
|
|
289
325
|
.ui-dot--live::after,
|
|
290
326
|
.ui-dotloader span,
|
|
291
327
|
.ui-dotspinner,
|
|
292
|
-
.ui-dotbar--indeterminate i
|
|
328
|
+
.ui-dotbar--indeterminate i,
|
|
329
|
+
.ui-dotmatrix--reveal .ui-dotmatrix__cell,
|
|
330
|
+
.ui-dotmatrix--pulse {
|
|
293
331
|
animation: none;
|
|
294
332
|
}
|
|
295
333
|
|
package/css/feedback.css
CHANGED
|
@@ -338,3 +338,114 @@
|
|
|
338
338
|
animation: none;
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
+
|
|
342
|
+
/* --- Meter — a labelled proportion / threshold bar. Unlike ui-progress
|
|
343
|
+
(task progress, can be indeterminate), a meter shows a measured static
|
|
344
|
+
value for data display (coverage, capacity, a KPI against a target).
|
|
345
|
+
Drive the fill with the same --value knob as progress; tone the fill by
|
|
346
|
+
threshold. Author role="meter" + aria-valuenow/min/max for AT. --- */
|
|
347
|
+
|
|
348
|
+
.ui-meter {
|
|
349
|
+
background: var(--panel-soft);
|
|
350
|
+
border: 1px solid var(--line);
|
|
351
|
+
border-radius: var(--radius-pill);
|
|
352
|
+
block-size: 0.5rem;
|
|
353
|
+
overflow: hidden;
|
|
354
|
+
inline-size: 100%;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Default (untoned) fill is --text-dim, not --line-strong: the fill
|
|
358
|
+
conveys the measured value, so it must clear the 3:1 non-text floor
|
|
359
|
+
against the --panel-soft track (--line-strong is only ~2:1). --text-dim
|
|
360
|
+
on --surface-muted is already a gated contrast pairing (≥4.5:1). */
|
|
361
|
+
.ui-meter__fill {
|
|
362
|
+
background: var(--text-dim);
|
|
363
|
+
block-size: 100%;
|
|
364
|
+
transition: inline-size var(--duration-base) var(--ease-out);
|
|
365
|
+
inline-size: calc(clamp(0, var(--value, 0), 100) * 1%);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.ui-meter--accent .ui-meter__fill {
|
|
369
|
+
background: var(--accent);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.ui-meter--success .ui-meter__fill {
|
|
373
|
+
background: var(--success);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.ui-meter--warning .ui-meter__fill {
|
|
377
|
+
background: var(--warning);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.ui-meter--danger .ui-meter__fill {
|
|
381
|
+
background: var(--danger);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* --- Steps — progress through a multi-step flow. Use an <ol>; the
|
|
385
|
+
current step is aria-current="step" (no class), completed steps take
|
|
386
|
+
--done. Counter-numbered markers, hairline connectors. --- */
|
|
387
|
+
|
|
388
|
+
.ui-steps {
|
|
389
|
+
counter-reset: ui-step;
|
|
390
|
+
display: flex;
|
|
391
|
+
flex-wrap: wrap;
|
|
392
|
+
gap: var(--space-sm);
|
|
393
|
+
list-style: none;
|
|
394
|
+
margin: 0;
|
|
395
|
+
padding: 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.ui-steps__item {
|
|
399
|
+
align-items: center;
|
|
400
|
+
color: var(--text-dim);
|
|
401
|
+
counter-increment: ui-step;
|
|
402
|
+
display: inline-flex;
|
|
403
|
+
flex: 1 1 auto;
|
|
404
|
+
font-family: var(--mono);
|
|
405
|
+
font-size: var(--text-xs);
|
|
406
|
+
gap: 0.5rem;
|
|
407
|
+
letter-spacing: var(--tracking-wide);
|
|
408
|
+
min-inline-size: max-content;
|
|
409
|
+
text-transform: uppercase;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.ui-steps__item::before {
|
|
413
|
+
align-items: center;
|
|
414
|
+
border: 1px solid var(--line-strong);
|
|
415
|
+
border-radius: 50%;
|
|
416
|
+
color: var(--text-dim);
|
|
417
|
+
content: counter(ui-step);
|
|
418
|
+
display: inline-flex;
|
|
419
|
+
flex: 0 0 auto;
|
|
420
|
+
block-size: 1.5rem;
|
|
421
|
+
inline-size: 1.5rem;
|
|
422
|
+
justify-content: center;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Connector hairline trailing every item except the last. */
|
|
426
|
+
.ui-steps__item:not(:last-child)::after {
|
|
427
|
+
background: var(--line);
|
|
428
|
+
block-size: 1px;
|
|
429
|
+
content: '';
|
|
430
|
+
flex: 1 1 1.5rem;
|
|
431
|
+
min-inline-size: 1rem;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.ui-steps__item[aria-current='step'] {
|
|
435
|
+
color: var(--text);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.ui-steps__item[aria-current='step']::before {
|
|
439
|
+
border-color: var(--accent);
|
|
440
|
+
color: var(--accent-text);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.ui-steps__item--done {
|
|
444
|
+
color: var(--text-soft);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.ui-steps__item--done::before {
|
|
448
|
+
background: var(--accent);
|
|
449
|
+
border-color: var(--accent);
|
|
450
|
+
color: var(--button-text);
|
|
451
|
+
}
|
package/css/forms.css
CHANGED
|
@@ -131,6 +131,50 @@
|
|
|
131
131
|
white-space: nowrap;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/* --- Input icon: a leading (or trailing) glyph *inside* a single
|
|
135
|
+
control, distinct from input-group's adjacent affix. Wrap the input:
|
|
136
|
+
<span class="ui-input-icon"><svg class="ui-input-icon__icon"/>
|
|
137
|
+
<input class="ui-input"></span>. The icon is a decorative overlay; the
|
|
138
|
+
input keeps full width and gains padding on the icon side. --end moves
|
|
139
|
+
the icon to the inline-end. RTL falls out of the logical props. --- */
|
|
140
|
+
.ui-input-icon {
|
|
141
|
+
display: block;
|
|
142
|
+
position: relative;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.ui-input-icon > .ui-input {
|
|
146
|
+
padding-inline-start: 2.2rem;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.ui-input-icon--end > .ui-input {
|
|
150
|
+
padding-inline: 0.7rem 2.2rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.ui-input-icon__icon {
|
|
154
|
+
align-items: center;
|
|
155
|
+
color: var(--text-dim);
|
|
156
|
+
display: inline-flex;
|
|
157
|
+
inset-block-start: 50%;
|
|
158
|
+
inset-inline-start: 0.7rem;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
pointer-events: none;
|
|
161
|
+
position: absolute;
|
|
162
|
+
transform: translateY(-50%);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.ui-input-icon--end .ui-input-icon__icon {
|
|
166
|
+
inset-inline: auto 0.7rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.ui-input-icon__icon svg {
|
|
170
|
+
block-size: 1rem;
|
|
171
|
+
inline-size: 1rem;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.ui-input-icon:focus-within .ui-input-icon__icon {
|
|
175
|
+
color: var(--accent);
|
|
176
|
+
}
|
|
177
|
+
|
|
134
178
|
/* --- File input --- */
|
|
135
179
|
.ui-file {
|
|
136
180
|
color: var(--text-soft);
|
package/css/overlay.css
CHANGED
|
@@ -105,6 +105,54 @@
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/* --- Lightbox — a full-viewport <dialog> wrapping a .ui-carousel. The
|
|
109
|
+
native <dialog> brings the top layer, focus-trap and Escape; initDialog
|
|
110
|
+
adds open (`data-bronto-open`) + focus-return; initCarousel drives the
|
|
111
|
+
inner carousel. No bespoke overlay/focus code — that is the whole point
|
|
112
|
+
of building it on <dialog>. --- */
|
|
113
|
+
.ui-lightbox {
|
|
114
|
+
background: color-mix(in srgb, var(--bg) 92%, #000);
|
|
115
|
+
border: 0;
|
|
116
|
+
block-size: 100vh;
|
|
117
|
+
color: var(--text);
|
|
118
|
+
max-block-size: 100vh;
|
|
119
|
+
max-inline-size: 100vw;
|
|
120
|
+
padding: 0;
|
|
121
|
+
inline-size: 100vw;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.ui-lightbox::backdrop {
|
|
125
|
+
background: color-mix(in srgb, #000 80%, transparent);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.ui-lightbox[open] {
|
|
129
|
+
align-content: center;
|
|
130
|
+
display: grid;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.ui-lightbox .ui-carousel {
|
|
134
|
+
inline-size: 100%;
|
|
135
|
+
margin: auto;
|
|
136
|
+
max-inline-size: min(64rem, 100vw);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Lightbox images are shown whole (contain), not cropped like the inline
|
|
140
|
+
carousel's cover thumbnails. */
|
|
141
|
+
.ui-lightbox .ui-carousel__slide > img {
|
|
142
|
+
margin-inline: auto;
|
|
143
|
+
max-block-size: 80vh;
|
|
144
|
+
max-inline-size: 100%;
|
|
145
|
+
inline-size: auto;
|
|
146
|
+
object-fit: contain;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.ui-lightbox__close {
|
|
150
|
+
inset-block-start: var(--space-sm);
|
|
151
|
+
inset-inline-end: var(--space-sm);
|
|
152
|
+
position: fixed;
|
|
153
|
+
z-index: var(--z-raised);
|
|
154
|
+
}
|
|
155
|
+
|
|
108
156
|
/* --- Dropdown menu — pairs with native <details> --- */
|
|
109
157
|
|
|
110
158
|
/* The positioned host for a <details> dropdown: .ui-menu is absolute, so
|
package/css/primitives.css
CHANGED
|
@@ -631,3 +631,63 @@
|
|
|
631
631
|
color: var(--text-dim);
|
|
632
632
|
margin: 0.3rem 0 0;
|
|
633
633
|
}
|
|
634
|
+
|
|
635
|
+
/* --- Keyboard key — inline glyph for shortcut hints. Wrap a <kbd>. --- */
|
|
636
|
+
|
|
637
|
+
.ui-kbd {
|
|
638
|
+
align-items: center;
|
|
639
|
+
background: var(--panel-soft);
|
|
640
|
+
border: 1px solid var(--line-strong);
|
|
641
|
+
border-radius: var(--radius-sm);
|
|
642
|
+
color: var(--text-soft);
|
|
643
|
+
display: inline-flex;
|
|
644
|
+
font-family: var(--mono);
|
|
645
|
+
font-size: var(--text-2xs);
|
|
646
|
+
justify-content: center;
|
|
647
|
+
line-height: 1;
|
|
648
|
+
min-inline-size: 1.4rem;
|
|
649
|
+
padding: 0.18rem 0.4rem;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/* --- Timeline — vertical event list on a hairline spine. Use an <ol>;
|
|
653
|
+
each <li> is a __item with an optional __time label. aria-current marks
|
|
654
|
+
the live/most-recent event (accent marker). --- */
|
|
655
|
+
|
|
656
|
+
.ui-timeline {
|
|
657
|
+
display: grid;
|
|
658
|
+
gap: var(--space-md);
|
|
659
|
+
list-style: none;
|
|
660
|
+
margin: 0;
|
|
661
|
+
padding: 0;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.ui-timeline__item {
|
|
665
|
+
border-inline-start: 1px solid var(--line);
|
|
666
|
+
display: grid;
|
|
667
|
+
gap: 0.2rem;
|
|
668
|
+
padding-inline-start: var(--space-md);
|
|
669
|
+
position: relative;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.ui-timeline__item::before {
|
|
673
|
+
background: var(--line-strong);
|
|
674
|
+
border-radius: 50%;
|
|
675
|
+
block-size: 0.5rem;
|
|
676
|
+
content: '';
|
|
677
|
+
inline-size: 0.5rem;
|
|
678
|
+
inset-block-start: 0.18rem;
|
|
679
|
+
inset-inline-start: calc(-0.25rem - 0.5px);
|
|
680
|
+
position: absolute;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.ui-timeline__item[aria-current]::before {
|
|
684
|
+
background: var(--accent);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.ui-timeline__time {
|
|
688
|
+
color: var(--text-dim);
|
|
689
|
+
font-family: var(--mono);
|
|
690
|
+
font-size: var(--text-2xs);
|
|
691
|
+
letter-spacing: var(--tracking-wide);
|
|
692
|
+
text-transform: uppercase;
|
|
693
|
+
}
|
package/css/site.css
CHANGED
|
@@ -91,6 +91,39 @@
|
|
|
91
91
|
gap: var(--space-xs);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/* --- Page header — the in-page title bar every view re-hand-rolls:
|
|
95
|
+
eyebrow/breadcrumb + title on the start, actions on the end, one
|
|
96
|
+
hairline beneath. Compose the existing ui-eyebrow / ui-breadcrumb in
|
|
97
|
+
the title block (don't duplicate them); __actions is a cluster. --- */
|
|
98
|
+
|
|
99
|
+
.ui-pagehead {
|
|
100
|
+
align-items: flex-end;
|
|
101
|
+
border-block-end: 1px solid var(--line);
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-wrap: wrap;
|
|
104
|
+
gap: var(--space-md);
|
|
105
|
+
justify-content: space-between;
|
|
106
|
+
padding-block-end: var(--space-md);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ui-pagehead__title {
|
|
110
|
+
color: var(--text);
|
|
111
|
+
font-family: var(--display);
|
|
112
|
+
font-size: var(--text-xl);
|
|
113
|
+
letter-spacing: var(--tracking-wide);
|
|
114
|
+
line-height: 1.1;
|
|
115
|
+
margin: 0;
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
text-wrap: balance;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ui-pagehead__actions {
|
|
121
|
+
align-items: center;
|
|
122
|
+
display: inline-flex;
|
|
123
|
+
flex-wrap: wrap;
|
|
124
|
+
gap: var(--space-xs);
|
|
125
|
+
}
|
|
126
|
+
|
|
94
127
|
/* --- Nav — desktop inline, dot active cue via aria-current --- */
|
|
95
128
|
|
|
96
129
|
.ui-sitenav {
|