@getvitops/generator 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +174 -0
- package/assets/bricks/elements/copy-button.php +3 -3
- package/assets/bricks/elements/multi-field.php +3 -3
- package/assets/css/animation.css +90 -0
- package/assets/css/global.css +10 -0
- package/assets/css/index.css +5 -0
- package/assets/css/layout-utilities.css +46 -0
- package/assets/css/layout.css +87 -2
- package/assets/css/patterns/anchor-link.css +36 -0
- package/assets/css/patterns/code.css +24 -0
- package/assets/css/patterns/details.css +28 -16
- package/assets/css/patterns/dialog.css +13 -31
- package/assets/css/patterns/drawer.css +27 -66
- package/assets/css/patterns/dropdown.css +15 -1
- package/assets/css/patterns/lightbox.css +3 -3
- package/assets/css/patterns/marquee.css +33 -5
- package/assets/css/patterns/nav.css +27 -79
- package/assets/css/patterns/navbar.css +86 -0
- package/assets/css/patterns/navshell.css +383 -0
- package/assets/css/patterns/popover.css +56 -8
- package/assets/css/patterns/scroll-target.css +18 -4
- package/assets/css/patterns/sitenav.css +117 -338
- package/assets/css/patterns/split-link.css +3 -3
- package/assets/js/elements.js +3 -3
- package/{site.schema.json → config.schema.json} +2300 -2283
- package/dist/index.d.mts +882 -415
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3284 -3095
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/assets/css/layout.css
CHANGED
|
@@ -46,10 +46,82 @@
|
|
|
46
46
|
.rhythm > * + * {
|
|
47
47
|
margin-top: calc(var(--rhythm-base) * var(--rhythm-p-p));
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
/* The heading pairs are defined by what a heading IS, not by a list of what can
|
|
50
|
+
sit next to one.
|
|
51
|
+
These used to enumerate the other side (`p`, then `p, pre, blockquote, table,
|
|
52
|
+
dl, ul, ol`), and an enumeration is a list that is always one element short:
|
|
53
|
+
first a `<pre>` got paragraph-to-paragraph spacing before a heading, then a
|
|
54
|
+
`<div class="marquee">` did. Any block is content; only headings are special.
|
|
55
|
+
So the rules invert — `:not(<heading>) + <heading>` — and nothing has to be
|
|
56
|
+
added here again.
|
|
57
|
+
|
|
58
|
+
`:where()` keeps the whole selector at (0,1,0), the same weight as the generic
|
|
59
|
+
`* + *` above, so SOURCE ORDER is what makes these win. Keep them below it,
|
|
60
|
+
and keep the media pairs below these — `figure + h2` matches both, and the
|
|
61
|
+
media rule is meant to take it.
|
|
62
|
+
|
|
63
|
+
A heading is also a TYPE ROLE, not just a tag: this framework's own idiom is
|
|
64
|
+
`<p class="font-heading">` inside a card, which is a heading in every sense
|
|
65
|
+
that matters to rhythm.
|
|
66
|
+
|
|
67
|
+
Those three are matched with `[class~="…"]`, not `.font-heading`, and the
|
|
68
|
+
difference is not cosmetic. `LAYER_CONTRACT` asserts that a utility's class
|
|
69
|
+
name appears first in `vitops.utilities`; writing `.font-display` here — in a
|
|
70
|
+
partial that lands in `vitops.components` — reads to that guard exactly like a
|
|
71
|
+
utility defined in the wrong layer, and it fails the build. The attribute form
|
|
72
|
+
says what is true: this rule REFERENCES the class, it does not define it. */
|
|
73
|
+
.rhythm
|
|
74
|
+
> :where(
|
|
75
|
+
h1,
|
|
76
|
+
h2,
|
|
77
|
+
h3,
|
|
78
|
+
h4,
|
|
79
|
+
h5,
|
|
80
|
+
h6,
|
|
81
|
+
[class~='font-display'],
|
|
82
|
+
[class~='font-title'],
|
|
83
|
+
[class~='font-heading']
|
|
84
|
+
)
|
|
85
|
+
+ :not(
|
|
86
|
+
:where(
|
|
87
|
+
h1,
|
|
88
|
+
h2,
|
|
89
|
+
h3,
|
|
90
|
+
h4,
|
|
91
|
+
h5,
|
|
92
|
+
h6,
|
|
93
|
+
[class~='font-display'],
|
|
94
|
+
[class~='font-title'],
|
|
95
|
+
[class~='font-heading']
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
50
98
|
margin-top: calc(var(--rhythm-base) * var(--rhythm-h-p));
|
|
51
99
|
}
|
|
52
|
-
.rhythm
|
|
100
|
+
.rhythm
|
|
101
|
+
> :not(
|
|
102
|
+
:where(
|
|
103
|
+
h1,
|
|
104
|
+
h2,
|
|
105
|
+
h3,
|
|
106
|
+
h4,
|
|
107
|
+
h5,
|
|
108
|
+
h6,
|
|
109
|
+
[class~='font-display'],
|
|
110
|
+
[class~='font-title'],
|
|
111
|
+
[class~='font-heading']
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
+ :where(
|
|
115
|
+
h1,
|
|
116
|
+
h2,
|
|
117
|
+
h3,
|
|
118
|
+
h4,
|
|
119
|
+
h5,
|
|
120
|
+
h6,
|
|
121
|
+
[class~='font-display'],
|
|
122
|
+
[class~='font-title'],
|
|
123
|
+
[class~='font-heading']
|
|
124
|
+
) {
|
|
53
125
|
margin-top: calc(var(--rhythm-base) * var(--rhythm-p-h));
|
|
54
126
|
}
|
|
55
127
|
.rhythm > :where(h1, h2, h3, h4, h5, h6) + :where(h1, h2, h3, h4, h5, h6) {
|
|
@@ -81,6 +153,11 @@
|
|
|
81
153
|
--width-measure: 65ch;
|
|
82
154
|
--width-breakout: 90ch;
|
|
83
155
|
--width-spotlight: 120ch;
|
|
156
|
+
/* A navigation column beside content (`navshell`). In `rem`, not `ch`, unlike
|
|
157
|
+
the three above: those are reading measures and scale with the text they
|
|
158
|
+
hold, while a nav column is chrome sized in the page's own rhythm — it
|
|
159
|
+
should not get wider because the prose font has a wide `0` glyph. */
|
|
160
|
+
--width-nav: 16rem;
|
|
84
161
|
--gutter: clamp(1rem, 4cqi, 3rem);
|
|
85
162
|
}
|
|
86
163
|
|
|
@@ -110,6 +187,14 @@ body {
|
|
|
110
187
|
|
|
111
188
|
.centered > * {
|
|
112
189
|
grid-column: measure;
|
|
190
|
+
/* A grid item's automatic minimum size is its MIN-CONTENT size, and every
|
|
191
|
+
track here is definite (`--_measure` is a min() of two lengths; the rest are
|
|
192
|
+
minmax(0, …)). So a track child holding something with a large min-content —
|
|
193
|
+
a wide <pre>, a long unbreakable token — pushes past its track and the whole
|
|
194
|
+
document scrolls sideways. This caps the container; wrapping or scrolling
|
|
195
|
+
the CONTENT (see global.css and patterns/code.css) is what stops the visual
|
|
196
|
+
overflow. */
|
|
197
|
+
min-inline-size: 0;
|
|
113
198
|
}
|
|
114
199
|
|
|
115
200
|
/* Vertical rhythm region (usually on the Section) */
|
|
@@ -64,3 +64,39 @@ a[target='_blank'].link--external::after {
|
|
|
64
64
|
mask-size: contain;
|
|
65
65
|
mask-repeat: no-repeat;
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Skip link — the first focusable thing on a page, jumping past the chrome to
|
|
70
|
+
* the main landmark. Every site needs one and every site was hand-rolling it.
|
|
71
|
+
*
|
|
72
|
+
* `clip-path` + a 1px box rather than the old `inset-inline-start: -100vw`
|
|
73
|
+
* idiom: `100vw` includes the scrollbar gutter (`global.css` sets
|
|
74
|
+
* `scrollbar-gutter: stable`), and a negatively-positioned box DOES contribute
|
|
75
|
+
* scrollable overflow in RTL, where the page then scrolls to nothing. This
|
|
76
|
+
* version has no position to get wrong in either direction.
|
|
77
|
+
*
|
|
78
|
+
* Not `.sr-only` — that name is in the generator's TW_CLASH list, so the
|
|
79
|
+
* tailwind format ships no rule for it and a consumer on that format would get a
|
|
80
|
+
* permanently visible skip link.
|
|
81
|
+
*/
|
|
82
|
+
.skip-link:not(:focus-visible) {
|
|
83
|
+
position: absolute;
|
|
84
|
+
inline-size: 1px;
|
|
85
|
+
block-size: 1px;
|
|
86
|
+
padding: 0;
|
|
87
|
+
margin: -1px;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
clip-path: inset(50%);
|
|
90
|
+
white-space: nowrap;
|
|
91
|
+
border: 0;
|
|
92
|
+
}
|
|
93
|
+
.skip-link:focus-visible {
|
|
94
|
+
position: absolute;
|
|
95
|
+
inset-inline-start: 0.5rem;
|
|
96
|
+
inset-block-start: 0.5rem;
|
|
97
|
+
z-index: var(--z-tier-top);
|
|
98
|
+
padding: 0.5rem 0.75rem;
|
|
99
|
+
background: var(--color-bg-surface);
|
|
100
|
+
color: var(--color-text-surface);
|
|
101
|
+
border-radius: var(--br-control, 0.375rem);
|
|
102
|
+
}
|
|
@@ -14,4 +14,28 @@
|
|
|
14
14
|
color: var(--color-text-neutral);
|
|
15
15
|
padding: 0.1em 0.4em;
|
|
16
16
|
border-radius: var(--br-default);
|
|
17
|
+
|
|
18
|
+
/* `anywhere`, not the `break-word` global.css gives prose: an inline code span
|
|
19
|
+
is usually ONE token with no break opportunity at all — a 61-character CSS
|
|
20
|
+
selector or a package path — so `break-word` never fires and it overflows
|
|
21
|
+
the measure. Breaking mid-token is the right trade for code in running text.
|
|
22
|
+
`anywhere` does affect min-content sizing, which is the point here: it lets
|
|
23
|
+
an intrinsically sized ancestor shrink instead of being held open. */
|
|
24
|
+
overflow-wrap: anywhere;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ── Code blocks. The framework had NO `pre` rule, so every consumer re-derived
|
|
28
|
+
this — and a <pre> is the widest thing on a typical docs page.
|
|
29
|
+
|
|
30
|
+
Worth knowing, because it is the reverse of what the spec reads like: making a
|
|
31
|
+
<pre> a scroll container does NOT zero its min-content contribution in Chrome.
|
|
32
|
+
Measured at 390px on this repo's own docs site, a `pre` with `overflow-x: auto`
|
|
33
|
+
still reported a 797px min-content, its parent inherited 829px, and any
|
|
34
|
+
ancestor sized under `fit-content` resolved to THAT rather than the available
|
|
35
|
+
width. `max-inline-size: 100%` is what actually caps it. ── */
|
|
36
|
+
:where(pre) {
|
|
37
|
+
max-inline-size: 100%;
|
|
38
|
+
overflow-x: auto;
|
|
39
|
+
/* Don't chain a horizontal flick out to the page once the block ends. */
|
|
40
|
+
overscroll-behavior-x: contain;
|
|
17
41
|
}
|
|
@@ -38,30 +38,42 @@ details,
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Animated collapse — `block-size: 0 → auto`, backed by the
|
|
42
|
+
* `interpolate-size: allow-keywords` in patterns/reveal.css.
|
|
42
43
|
*
|
|
43
|
-
* This
|
|
44
|
-
* `content-visibility`
|
|
45
|
-
* `
|
|
46
|
-
*
|
|
47
|
-
* so `auto` resolves to `0`, so the transition has nothing to run — and the
|
|
48
|
-
* disclosure never opens *at all*. Not "opens without animating": every
|
|
49
|
-
* `<details>` on the page becomes inert, which is a total content-loss bug.
|
|
44
|
+
* This was previously disabled with a long note saying the transition
|
|
45
|
+
* DEADLOCKS: `content-visibility` stays `hidden`, the content never gets
|
|
46
|
+
* layout, `auto` resolves to `0`, and the disclosure never opens at all —
|
|
47
|
+
* total content loss, not merely a missing animation.
|
|
50
48
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
49
|
+
* Re-verified on Chrome 149.0.7827.200 by the standard that note itself set:
|
|
50
|
+
* a real dispatched click on a bare `<details>`, not an `@supports` query.
|
|
51
|
+
* It opens, and it animates both ways — measured 25 → 41 → 133 → 196px on
|
|
52
|
+
* open and 196 → 132 → 25px on close.
|
|
55
53
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
54
|
+
* Keep BOTH properties in the transition list. `content-visibility` with
|
|
55
|
+
* `allow-discrete` is the load-bearing half: it is precisely what lets the
|
|
56
|
+
* content take layout during the transition, and dropping it is what
|
|
57
|
+
* reproduces the deadlock. If this ever regresses, the tell is a `<details>`
|
|
58
|
+
* that will not open on the first click — check a real interaction, because
|
|
59
|
+
* the syntax stays valid either way.
|
|
60
60
|
*/
|
|
61
61
|
&::details-content {
|
|
62
62
|
display: block;
|
|
63
63
|
overflow: hidden;
|
|
64
64
|
block-size: 0;
|
|
65
|
+
|
|
66
|
+
/* Re-added and re-verified — see the note above, which this supersedes.
|
|
67
|
+
`content-visibility` MUST be in the transition list with `allow-discrete`:
|
|
68
|
+
that is the exact thing whose absence caused the deadlock, because
|
|
69
|
+
`content-visibility: hidden` leaves the content unlaid-out and `auto`
|
|
70
|
+
resolves to 0. Behind reduced-motion, so the no-preference case keeps the
|
|
71
|
+
instant open the previous version guaranteed. */
|
|
72
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
73
|
+
transition:
|
|
74
|
+
block-size var(--animation-duration) var(--custom-ease-out),
|
|
75
|
+
content-visibility var(--animation-duration) allow-discrete;
|
|
76
|
+
}
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
> summary + * {
|
|
@@ -29,16 +29,20 @@ dialog.dialog {
|
|
|
29
29
|
color: var(--color-text-surface);
|
|
30
30
|
max-block-size: calc(100vh - 2rem);
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
/* The scrim itself is one rule in popover.css; this only says how it should
|
|
33
|
+
look for a dialog. It also fades in/out from there, so the bespoke
|
|
34
|
+
`dialog-backdrop-enter` keyframe that used to run here is gone (it only ever
|
|
35
|
+
animated the entry). */
|
|
36
|
+
--scrim-filter: blur(4px);
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
/* The entry, as effect vars rather than a keyframe. Same three endpoints the
|
|
39
|
+
`dialog-enter` keyframe used, now run by the top-layer driver in
|
|
40
|
+
animation.css — which means the dialog also animates OUT, which it never did
|
|
41
|
+
(an `animation:` on `[open]` only ever plays once, on open). */
|
|
42
|
+
--opacity-from: 0;
|
|
43
|
+
--scale-x-from: 0.95;
|
|
44
|
+
--scale-y-from: 0.95;
|
|
45
|
+
--translate-y-from: -1rem;
|
|
42
46
|
|
|
43
47
|
.dialog__header {
|
|
44
48
|
display: flex;
|
|
@@ -75,25 +79,3 @@ dialog.dialog {
|
|
|
75
79
|
margin-block-start: 1.5rem;
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
|
-
|
|
79
|
-
@keyframes dialog-enter {
|
|
80
|
-
from {
|
|
81
|
-
opacity: 0;
|
|
82
|
-
scale: 0.95;
|
|
83
|
-
translate: 0 -1rem;
|
|
84
|
-
}
|
|
85
|
-
to {
|
|
86
|
-
opacity: 1;
|
|
87
|
-
scale: 1;
|
|
88
|
-
translate: 0 0;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
@keyframes dialog-backdrop-enter {
|
|
93
|
-
from {
|
|
94
|
-
opacity: 0;
|
|
95
|
-
}
|
|
96
|
-
to {
|
|
97
|
-
opacity: 1;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -21,24 +21,30 @@
|
|
|
21
21
|
max-block-size: none;
|
|
22
22
|
|
|
23
23
|
&::backdrop {
|
|
24
|
-
backdrop-filter: blur(4px);
|
|
24
|
+
backdrop-filter: var(--scrim-filter, blur(4px));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Each edge states WHERE IT STARTS in the animation system's own vocabulary
|
|
29
|
+
* (`--translate-<axis>-from`) instead of owning a keyframe. Add `.transition`
|
|
30
|
+
* and the top-layer driver in animation.css animates it — both ways.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* This replaced four `@keyframes drawer-slide-in-*` driven by `animation:`,
|
|
33
|
+
* which only ever played on OPEN: the drawer's close was instant, while
|
|
34
|
+
* sitenav's transition-based copy of the same idea animated both ways.
|
|
35
|
+
*
|
|
36
|
+
* The var is set here rather than asking authors for a `.slide-*` effect
|
|
37
|
+
* class, because both would trip them up: the effect names read backwards for
|
|
38
|
+
* an edge panel (a LEFT drawer starts off-screen left and travels right, i.e.
|
|
39
|
+
* `.slide-right`), and `--slide-distance` defaults to 2rem where a drawer
|
|
40
|
+
* needs the full 100%. An explicit effect class still overrides this.
|
|
41
|
+
*
|
|
42
|
+
* EVERY edge states BOTH axes. The unset axis does not inherit a sensible
|
|
43
|
+
* zero — it inherits whatever the bare `.drawer` default left there, and the
|
|
44
|
+
* default is the left edge's `-100%`. Setting only `--translate-y-from` on
|
|
45
|
+
* `.drawer--bottom` therefore produced `translate: -100% 100%` and the panel
|
|
46
|
+
* flew in diagonally from the bottom-left corner.
|
|
36
47
|
*/
|
|
37
|
-
&.drawer--modeless::backdrop {
|
|
38
|
-
background: none;
|
|
39
|
-
backdrop-filter: none;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
48
|
&,
|
|
43
49
|
&.drawer--left {
|
|
44
50
|
inset-block: 0;
|
|
@@ -46,11 +52,8 @@
|
|
|
46
52
|
block-size: 100dvh;
|
|
47
53
|
inline-size: min(100dvi, var(--drawer-max-inline-size, 280px));
|
|
48
54
|
border-radius: 0;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
&:popover-open {
|
|
52
|
-
animation: drawer-slide-in-left 0.3s ease;
|
|
53
|
-
}
|
|
55
|
+
--translate-x-from: -100%;
|
|
56
|
+
--translate-y-from: 0;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
&.drawer--right {
|
|
@@ -58,22 +61,16 @@
|
|
|
58
61
|
inset-inline-end: 0;
|
|
59
62
|
inset-inline-start: auto;
|
|
60
63
|
inline-size: min(100dvi, var(--drawer-max-inline-size, 280px));
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
&:popover-open {
|
|
64
|
-
animation: drawer-slide-in-right 0.3s ease;
|
|
65
|
-
}
|
|
64
|
+
--translate-x-from: 100%;
|
|
65
|
+
--translate-y-from: 0;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
&.drawer--top {
|
|
69
69
|
inset-block-start: 0;
|
|
70
70
|
inset-inline: 0;
|
|
71
71
|
block-size: min(100dvb, var(--drawer-max-block-size, 100dvb));
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
&:popover-open {
|
|
75
|
-
animation: drawer-slide-in-top 0.3s ease;
|
|
76
|
-
}
|
|
72
|
+
--translate-x-from: 0;
|
|
73
|
+
--translate-y-from: -100%;
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
&.drawer--bottom {
|
|
@@ -81,43 +78,7 @@
|
|
|
81
78
|
inset-block-start: auto;
|
|
82
79
|
inset-inline: 0;
|
|
83
80
|
block-size: min(100dvb, var(--drawer-max-block-size, 100dvb));
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
&:popover-open {
|
|
87
|
-
animation: drawer-slide-in-bottom 0.3s ease;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
@keyframes drawer-slide-in-left {
|
|
93
|
-
from {
|
|
94
|
-
translate: -100% 0;
|
|
95
|
-
}
|
|
96
|
-
to {
|
|
97
|
-
translate: 0 0;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
@keyframes drawer-slide-in-right {
|
|
101
|
-
from {
|
|
102
|
-
translate: 100% 0;
|
|
103
|
-
}
|
|
104
|
-
to {
|
|
105
|
-
translate: 0 0;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
@keyframes drawer-slide-in-top {
|
|
109
|
-
from {
|
|
110
|
-
translate: 0 -100%;
|
|
111
|
-
}
|
|
112
|
-
to {
|
|
113
|
-
translate: 0 0;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
@keyframes drawer-slide-in-bottom {
|
|
117
|
-
from {
|
|
118
|
-
translate: 0 100%;
|
|
119
|
-
}
|
|
120
|
-
to {
|
|
121
|
-
translate: 0 0;
|
|
81
|
+
--translate-x-from: 0;
|
|
82
|
+
--translate-y-from: 100%;
|
|
122
83
|
}
|
|
123
84
|
}
|
|
@@ -19,7 +19,19 @@
|
|
|
19
19
|
opacity 0.2s ease,
|
|
20
20
|
translate 0.2s ease,
|
|
21
21
|
visibility 0.2s allow-discrete;
|
|
22
|
+
/* `position: absolute`, anchored to the `position: relative` host above.
|
|
23
|
+
This is the whole reason a hover dropdown ever worked or didn't: a closed
|
|
24
|
+
popover is NOT in the top layer, but it keeps the UA's
|
|
25
|
+
`[popover] { position: fixed }`. Resetting only `inset` left it fixed to
|
|
26
|
+
the VIEWPORT at whatever static position it resolved to — measured 3300px
|
|
27
|
+
below its own trigger and off-screen, so the panel faithfully faded in
|
|
28
|
+
somewhere nobody was looking, which reads exactly like "hover does
|
|
29
|
+
nothing". Anchoring it puts it under the trigger and makes it follow on
|
|
30
|
+
scroll. */
|
|
31
|
+
position: absolute;
|
|
22
32
|
inset: auto;
|
|
33
|
+
inset-block-start: 100%;
|
|
34
|
+
inset-inline-start: 0;
|
|
23
35
|
margin: 0;
|
|
24
36
|
}
|
|
25
37
|
|
|
@@ -39,7 +51,9 @@
|
|
|
39
51
|
padding-block-start: var(--dropdown-gap, 0.5rem);
|
|
40
52
|
}
|
|
41
53
|
|
|
54
|
+
/* Was `!important`, to beat popover.css's then plain-specificity ::backdrop.
|
|
55
|
+
That rule is `:where()` (0,0,0) now, so ordinary specificity is enough. */
|
|
42
56
|
> [popover]::backdrop {
|
|
43
|
-
background: transparent
|
|
57
|
+
background: transparent;
|
|
44
58
|
}
|
|
45
59
|
}
|
|
@@ -52,9 +52,9 @@ dialog.lightbox-dialog {
|
|
|
52
52
|
display: grid;
|
|
53
53
|
place-items: center;
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/* No scrim. Stated through popover.css's token rather than overriding its
|
|
56
|
+
::backdrop rule, so it keeps the shared fade timing. */
|
|
57
|
+
--scrim: transparent;
|
|
58
58
|
|
|
59
59
|
&[open] {
|
|
60
60
|
display: grid;
|
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Marquee — CSS-only infinite scrolling row/column migrated from old-css-lib.
|
|
3
|
-
*
|
|
4
|
-
* --pause-hover
|
|
3
|
+
*
|
|
4
|
+
* <div class="marquee marquee--pause-hover">
|
|
5
|
+
* <div class="marquee__content"><span class="marquee__item">…</span></div>
|
|
6
|
+
* <div class="marquee__content" aria-hidden="true">…the same items…</div>
|
|
7
|
+
* </div>
|
|
8
|
+
*
|
|
9
|
+
* TWO `.marquee__content` blocks, not one. Each is `min-inline-size: 100%` and the
|
|
10
|
+
* keyframe travels exactly its own width, so the second copy is what occupies the
|
|
11
|
+
* space the first vacates. With a single copy the track scrolls off and leaves
|
|
12
|
+
* blank space — which reads as "the marquee isn't working" rather than as a
|
|
13
|
+
* missing duplicate.
|
|
14
|
+
*
|
|
15
|
+
* Spacing is `--marquee-gap`, and it has to be applied in BOTH places: between
|
|
16
|
+
* items inside a copy, and between the two copies. The keyframe then travels
|
|
17
|
+
* `100% + gap`, because the seam between copies is one more gap that has to be
|
|
18
|
+
* consumed for the loop to be seamless — translate by a bare `-100%` and the
|
|
19
|
+
* items visibly bunch up once per revolution.
|
|
5
20
|
*/
|
|
6
21
|
|
|
7
22
|
.marquee {
|
|
8
23
|
--_marquee-direction: var(--marquee-direction, normal);
|
|
9
24
|
|
|
25
|
+
/* `<wc-marquee>` overrides this to `auto`, releasing the 100% floor. */
|
|
26
|
+
--_marquee-min: 100%;
|
|
27
|
+
|
|
10
28
|
display: flex;
|
|
29
|
+
gap: var(--marquee-gap, 1rem);
|
|
11
30
|
overflow: hidden;
|
|
12
31
|
user-select: none;
|
|
13
32
|
mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
|
14
33
|
|
|
15
34
|
.marquee__content {
|
|
16
35
|
display: flex;
|
|
36
|
+
gap: var(--marquee-gap, 1rem);
|
|
17
37
|
flex-shrink: 0;
|
|
18
|
-
|
|
38
|
+
/* 100% pads a short copy out to the track width, which puts the leftover
|
|
39
|
+
space at the end of every copy and makes the seam gap wider than the item
|
|
40
|
+
gaps. `<wc-marquee>` sets `--_marquee-min: auto` and clones enough copies to
|
|
41
|
+
cover the track, so the copy can be its own width and every gap matches.
|
|
42
|
+
Without the element this stays 100% — the CSS-only loop still works, it
|
|
43
|
+
just needs the author to duplicate the content. */
|
|
44
|
+
min-inline-size: var(--_marquee-min, 100%);
|
|
19
45
|
animation: marquee-scroll var(--marquee-duration, 20s) linear infinite;
|
|
20
46
|
animation-direction: var(--_marquee-direction);
|
|
21
47
|
}
|
|
@@ -54,7 +80,9 @@
|
|
|
54
80
|
translate: 0 0;
|
|
55
81
|
}
|
|
56
82
|
to {
|
|
57
|
-
|
|
83
|
+
/* One copy + one gap. `--_marquee-distance` is the measured px value when
|
|
84
|
+
the element is present; the percentage is the CSS-only equivalent. */
|
|
85
|
+
translate: calc(-1 * var(--_marquee-distance, calc(100% + var(--marquee-gap, 1rem)))) 0;
|
|
58
86
|
}
|
|
59
87
|
}
|
|
60
88
|
|
|
@@ -63,6 +91,6 @@
|
|
|
63
91
|
translate: 0 0;
|
|
64
92
|
}
|
|
65
93
|
to {
|
|
66
|
-
translate: 0 -100
|
|
94
|
+
translate: 0 calc(-1 * var(--_marquee-distance, calc(100% + var(--marquee-gap, 1rem))));
|
|
67
95
|
}
|
|
68
96
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Nav —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Nav — LEGACY. Base / drawer / collapse navigation presentations migrated from
|
|
3
|
+
* old-css-lib, superseded by the three patterns below. Kept because it ships in
|
|
4
|
+
* a published major; removal is its own breaking change.
|
|
5
|
+
*
|
|
6
|
+
* This header used to say "the open/close toggle wiring arrives with the Lit nav
|
|
7
|
+
* component". No such component was ever written, and none is planned — the
|
|
8
|
+
* house pattern is native (`popovertarget` + `[popover]`, `<details>`), which
|
|
9
|
+
* needs no JS at all beyond the Popover polyfill.
|
|
10
|
+
*
|
|
11
|
+
* Which one you actually want:
|
|
12
|
+
* · `patterns/navbar.css` — a horizontal row of links, with alignment.
|
|
13
|
+
* · `patterns/sitenav.css` — navbar at width ⇄ toggle + drawer below it.
|
|
14
|
+
* · `patterns/navshell.css` — a nav ASIDE beside main content, collapsing to a
|
|
15
|
+
* toggle + drawer. Nestable (site nav + on-page nav).
|
|
6
16
|
*/
|
|
7
17
|
|
|
8
18
|
nav:has(> .nav-items) {
|
|
@@ -133,48 +143,20 @@ nav:has(> .nav-items) {
|
|
|
133
143
|
width: 100%;
|
|
134
144
|
background: var(--color-bg-surface);
|
|
135
145
|
color: var(--color-text-surface);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
|
|
147
|
+
/* Entry direction only. The transition, the `@starting-style` and the scrim
|
|
148
|
+
used to be restated here in full; they now come from the top-layer driver in
|
|
149
|
+
animation.css and the single `::backdrop` in popover.css. Add an effect
|
|
150
|
+
class to change the motion (`class="drawer-menu open-fade-in"`).
|
|
151
|
+
|
|
152
|
+
Timing moved with them: this was a hand-set 0.4s / 0.7s pair and is now the
|
|
153
|
+
framework's `--animation-duration` + `--custom-ease-out`, like every other
|
|
154
|
+
transition in the system. */
|
|
155
|
+
--translate-x-from: -100%;
|
|
144
156
|
|
|
145
157
|
&[open],
|
|
146
158
|
&:popover-open {
|
|
147
159
|
display: flex;
|
|
148
|
-
opacity: 1;
|
|
149
|
-
translate: 0%;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
@starting-style {
|
|
153
|
-
&[open],
|
|
154
|
-
&:popover-open {
|
|
155
|
-
opacity: 0;
|
|
156
|
-
translate: -100%;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
&::backdrop {
|
|
161
|
-
background-color: transparent;
|
|
162
|
-
transition:
|
|
163
|
-
display 0.7s allow-discrete,
|
|
164
|
-
overlay 0.7s allow-discrete,
|
|
165
|
-
background-color 0.7s;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
&[open]::backdrop,
|
|
169
|
-
&:popover-open::backdrop {
|
|
170
|
-
background-color: rgb(0 0 0 / 25%);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
@starting-style {
|
|
174
|
-
&[open]::backdrop,
|
|
175
|
-
&:popover-open::backdrop {
|
|
176
|
-
background-color: transparent;
|
|
177
|
-
}
|
|
178
160
|
}
|
|
179
161
|
}
|
|
180
162
|
|
|
@@ -193,43 +175,9 @@ nav:has(> .nav-items) {
|
|
|
193
175
|
overflow-y: auto;
|
|
194
176
|
}
|
|
195
177
|
|
|
196
|
-
/*
|
|
197
|
-
.navbar
|
|
198
|
-
|
|
199
|
-
align-items: center;
|
|
200
|
-
gap: 2rem;
|
|
201
|
-
|
|
202
|
-
nav {
|
|
203
|
-
flex: 1;
|
|
204
|
-
flex-direction: row;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.nav-items {
|
|
208
|
-
flex: 1;
|
|
209
|
-
flex-direction: row;
|
|
210
|
-
gap: 2.25rem;
|
|
211
|
-
border-bottom: none;
|
|
212
|
-
padding-bottom: 0;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.nav-actions {
|
|
216
|
-
flex-direction: row;
|
|
217
|
-
gap: 2.25rem;
|
|
218
|
-
border-top: none;
|
|
219
|
-
padding-top: 0;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.nav-header {
|
|
223
|
-
border-bottom: none;
|
|
224
|
-
padding-bottom: 0;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
.navbar-sticky {
|
|
229
|
-
position: sticky;
|
|
230
|
-
top: 0;
|
|
231
|
-
z-index: var(--z-tier-sticky);
|
|
232
|
-
}
|
|
178
|
+
/* Navbar presentation moved to patterns/navbar.css, where it is a primitive
|
|
179
|
+
rather than one half of this file's drawer⇄navbar pair. `.navbar-sticky`
|
|
180
|
+
is aliased there so existing markup keeps working. */
|
|
233
181
|
|
|
234
182
|
.nav-split {
|
|
235
183
|
margin-block-start: auto;
|