@getvitops/core 1.0.0 → 2.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 +460 -0
- package/README.md +18 -4
- package/css/animation.css +145 -34
- package/css/global.css +19 -3
- package/css/index.css +8 -0
- package/css/layout-utilities.css +764 -0
- package/css/layout.css +50 -584
- package/css/patterns/consent.css +117 -0
- package/css/patterns/details.css +1 -1
- package/css/patterns/reveal.css +92 -0
- package/css/patterns/subgrid.css +7 -2
- package/css/patterns/theme-editor.css +37 -0
- package/css/utilities.css +19 -98
- package/dist/consent.js +1 -0
- package/dist/deferred.js +1 -1
- package/dist/editor.js +1 -1
- package/package.json +6 -2
package/css/animation.css
CHANGED
|
@@ -11,15 +11,18 @@
|
|
|
11
11
|
* · animate-view / animate-scroll — keyframe linked to a scroll timeline
|
|
12
12
|
* · animate-trigger — keyframe, time-based; plays once when
|
|
13
13
|
* .is-active / [data-active] is set
|
|
14
|
-
* · transition
|
|
15
|
-
*
|
|
14
|
+
* · transition + hover-/focus-/ — TRANSITION (not keyframe) over the same
|
|
15
|
+
* active-<fx> from/to vars, so it reverses on state flip.
|
|
16
|
+
* Each state matches the element OR its
|
|
17
|
+
* direct parent, so an effect that clips
|
|
18
|
+
* itself away (reveal-*) is still reachable.
|
|
16
19
|
* Both driver families read the same `--<prop>-*` vars, so one effect
|
|
17
20
|
* (e.g. .fade-in) works in every mode with no redefinition.
|
|
18
21
|
*
|
|
19
22
|
* Effects by rendering stage (cheapest → most expensive):
|
|
20
23
|
* composite: fade-in/out, slide-up/down/left/right, scale-up/down, rotate-cw/ccw
|
|
21
24
|
* paint: blur-in/out, elevate-up/down, reveal-left/right/up/down
|
|
22
|
-
* layout: size-grow, size-shrink (
|
|
25
|
+
* layout: size-grow, size-shrink (auto height; needs interpolate-size)
|
|
23
26
|
* journey: fade-journey, slide-journey, … (multi-phase; view timeline)
|
|
24
27
|
* One keyframe per element: same-stage effects compose via vars (fade-in +
|
|
25
28
|
* scale-up); cross-stage (fade + blur) needs nested elements.
|
|
@@ -31,8 +34,8 @@
|
|
|
31
34
|
* Usage:
|
|
32
35
|
* <div class="fade-in slide-up animate-view"> <!-- scroll-into-view -->
|
|
33
36
|
* <div class="fade-in scale-up animate-trigger"> <!-- play once on enter -->
|
|
34
|
-
* <button class="scale-up
|
|
35
|
-
* <div class="fade-in
|
|
37
|
+
* <button class="transition hover-scale-up"> <!-- grow on hover -->
|
|
38
|
+
* <div class="transition active-fade-in"> <!-- flip via .is-active -->
|
|
36
39
|
*/
|
|
37
40
|
|
|
38
41
|
/* ===========================================
|
|
@@ -220,11 +223,20 @@
|
|
|
220
223
|
/* ===========================================
|
|
221
224
|
* Trigger Play-State
|
|
222
225
|
* Paused until activated. deferred.ts adds .is-active via IntersectionObserver,
|
|
223
|
-
* but [data-active] / .is-active can also be toggled by any state logic.
|
|
224
|
-
*
|
|
226
|
+
* but [data-active] / .is-active can also be toggled by any state logic.
|
|
227
|
+
*
|
|
228
|
+
* Gated on `scripting: enabled` because pausing is only safe when something can
|
|
229
|
+
* un-pause it: with no JS, .is-active is never added, and a paused entrance
|
|
230
|
+
* animation is `opacity: 0` — i.e. invisible content, permanently. Note which way
|
|
231
|
+
* this fails. An engine that doesn't know the media feature drops the whole block
|
|
232
|
+
* and the animation simply runs on load: an enhancement lost, nothing hidden.
|
|
233
|
+
* That is the right direction, and it is why this is a media query rather than
|
|
234
|
+
* the `:root:not(.no-js)` class it replaced — that spelling needed a class in the
|
|
235
|
+
* consumer's own <html>, which the Astro integration cannot put there, so every
|
|
236
|
+
* Astro site was silently failing the other way.
|
|
225
237
|
* =========================================== */
|
|
226
238
|
|
|
227
|
-
|
|
239
|
+
@media (scripting: enabled) {
|
|
228
240
|
.animate-trigger {
|
|
229
241
|
animation-play-state: paused;
|
|
230
242
|
}
|
|
@@ -261,6 +273,21 @@
|
|
|
261
273
|
* Stagger Support
|
|
262
274
|
* =========================================== */
|
|
263
275
|
|
|
276
|
+
/* Both are per-element positions, never something a child should inherit — an
|
|
277
|
+
unregistered custom property would leak the parent's index into every
|
|
278
|
+
descendant that happens to carry a driver. */
|
|
279
|
+
@property --_stagger-index {
|
|
280
|
+
syntax: '<number>';
|
|
281
|
+
inherits: false;
|
|
282
|
+
initial-value: 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@property --_anim-range-offset {
|
|
286
|
+
syntax: '<length-percentage>';
|
|
287
|
+
inherits: false;
|
|
288
|
+
initial-value: 0%;
|
|
289
|
+
}
|
|
290
|
+
|
|
264
291
|
.stagger {
|
|
265
292
|
--_stagger-amount: var(--stagger-amount, 0.1s);
|
|
266
293
|
}
|
|
@@ -270,15 +297,36 @@
|
|
|
270
297
|
--_stagger-index inline, which wins. Where sibling-index() is supported
|
|
271
298
|
it provides a JS-free fallback via the @supports block below. */
|
|
272
299
|
--_stagger-index: 0;
|
|
300
|
+
|
|
301
|
+
/* Time-based drivers (.animate-trigger, .float) stagger on delay. */
|
|
273
302
|
animation-delay: calc(var(--_stagger-index) * var(--_stagger-amount));
|
|
274
|
-
}
|
|
275
303
|
|
|
276
|
-
/*
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
304
|
+
/* Scroll-driven drivers do NOT: animation-delay is time-based and is ignored
|
|
305
|
+
outright on a progress-based timeline, which is why `.stagger > .animate-view`
|
|
306
|
+
used to arrive all at once. Offsetting the view RANGE by the same index is the
|
|
307
|
+
equivalent move — each child starts its entrance a little further into its own
|
|
308
|
+
entry phase. .animate-view folds this into its default range, so one .stagger
|
|
309
|
+
works under both driver families. Journeys set an explicit animation-range and
|
|
310
|
+
opt out by construction.
|
|
311
|
+
|
|
312
|
+
The step is a VIEWPORT length, matching the window it shifts: .animate-view
|
|
313
|
+
now spans 10vh → 25vh of the element's midpoint travel, so a step expressed
|
|
314
|
+
as a percentage of the element's own height (which is what a bare `%` means
|
|
315
|
+
inside the entry range) would be a few pixels against an ~80px window — a
|
|
316
|
+
stagger too small to read, and one that changed meaning with tile size. */
|
|
317
|
+
--_anim-range-offset: calc(var(--_stagger-index) * var(--stagger-range-step, 5vh));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* sibling-index() is near-baseline (lacking only Firefox as of 2026). The test
|
|
321
|
+
must be a REAL property — `@supports (--x: sibling-index())` is always true,
|
|
322
|
+
because any token stream is a valid custom-property value, so it guarded
|
|
323
|
+
nothing. Where it is unsupported the declaration is skipped and the index stays
|
|
324
|
+
0 (items animate together) rather than producing an invalid delay/range.
|
|
325
|
+
`- 1` because applyRowStagger writes a 0-based index and sibling-index() is
|
|
326
|
+
1-based; the two must agree or the JS and CSS paths differ by one step. */
|
|
327
|
+
@supports (animation-delay: calc(sibling-index() * 1s)) {
|
|
280
328
|
.stagger > * {
|
|
281
|
-
--_stagger-index: sibling-index();
|
|
329
|
+
--_stagger-index: calc(sibling-index() - 1);
|
|
282
330
|
}
|
|
283
331
|
}
|
|
284
332
|
|
|
@@ -355,11 +403,34 @@
|
|
|
355
403
|
.animate-view {
|
|
356
404
|
animation-name: var(--_anim);
|
|
357
405
|
animation-timeline: view();
|
|
358
|
-
/*
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
/* Timed off the element's MIDPOINT, measured into the viewport — the only
|
|
407
|
+
framing that behaves the same for a 4rem card and a full-bleed section.
|
|
408
|
+
|
|
409
|
+
The pivot is `entry 50%`. The entry phase runs from "leading edge at the
|
|
410
|
+
viewport's trailing edge" to "trailing edge at the viewport's trailing
|
|
411
|
+
edge", a scroll of exactly one element height — so at its halfway point the
|
|
412
|
+
element's midpoint sits precisely ON the viewport edge, whatever the element
|
|
413
|
+
measures. Everything after that is stated in viewport units: `+ 10vh` is
|
|
414
|
+
"midpoint 10% of the viewport further in".
|
|
415
|
+
|
|
416
|
+
So the default plays from midpoint 15% in to midpoint 25% in. Both stops are
|
|
417
|
+
the ELEMENT's position on screen, not a fraction of its own height, which is
|
|
418
|
+
what the previous defaults got wrong in both directions: `entry 20%` was ~17px
|
|
419
|
+
of scroll for a small tile (over before it appeared), and `entry 100%` still
|
|
420
|
+
finished with the element flush against the bottom edge.
|
|
421
|
+
|
|
422
|
+
Percentages here resolve against the entry range (the element's height) and
|
|
423
|
+
lengths against the scrollport, so the two mix in one calc() by design.
|
|
424
|
+
|
|
425
|
+
--anim-start / --anim-end move the window without restating the pivot;
|
|
426
|
+
--anim-range replaces it outright; --_anim-range-offset shifts both stops
|
|
427
|
+
together (that's how .stagger offsets scroll-driven children — see Stagger
|
|
428
|
+
Support). Journeys declare their own range and ignore all of it. */
|
|
429
|
+
animation-range: var(
|
|
430
|
+
--anim-range,
|
|
431
|
+
entry calc(50% + var(--anim-start, 10vh) + var(--_anim-range-offset, 0%)) entry
|
|
432
|
+
calc(50% + var(--anim-end, 25vh) + var(--_anim-range-offset, 0%))
|
|
433
|
+
);
|
|
363
434
|
}
|
|
364
435
|
|
|
365
436
|
.animate-scroll {
|
|
@@ -372,13 +443,21 @@
|
|
|
372
443
|
animation-duration: var(--trigger-animation-duration);
|
|
373
444
|
}
|
|
374
445
|
|
|
375
|
-
/* Fallback:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
446
|
+
/* Fallback: without a scroll-driven timeline, view()/scroll() animations never
|
|
447
|
+
advance and their elements stick at the `from` keyframe (opacity:0, clipped,
|
|
448
|
+
blurred) — an entrance animation that never runs is invisible content. Cancel
|
|
449
|
+
the animation so they render in their natural, fully-visible state. Trigger
|
|
450
|
+
animations are unaffected (JS-driven, see Trigger Play-State above).
|
|
451
|
+
|
|
452
|
+
Same probe as the `.text-reveal` block above, in its negative form — no JS, so
|
|
453
|
+
it holds with scripting disabled, which the `.no-scroll-timeline` class this
|
|
454
|
+
replaced could not (a class only JS can add cannot help a page with no JS).
|
|
455
|
+
`.animate-scroll` uses scroll() rather than view(), but no engine has shipped
|
|
456
|
+
one without the other, so one probe covers both. */
|
|
457
|
+
@supports not (animation-timeline: view()) {
|
|
458
|
+
:where(.animate-view, .animate-scroll) {
|
|
459
|
+
animation: none;
|
|
460
|
+
}
|
|
382
461
|
}
|
|
383
462
|
|
|
384
463
|
/* ===========================================
|
|
@@ -388,7 +467,8 @@
|
|
|
388
467
|
* active-<fx> in animation-effects.css) set those vars per state, so different
|
|
389
468
|
* effects bind to different states on one element:
|
|
390
469
|
* <div class="transition hover-slide-up active-elevate-up">
|
|
391
|
-
* Covers compositor
|
|
470
|
+
* Covers compositor, the full paint chain, and — where `interpolate-size` is
|
|
471
|
+
* supported — the layout stage (auto height) too.
|
|
392
472
|
* =========================================== */
|
|
393
473
|
|
|
394
474
|
.transition {
|
|
@@ -409,13 +489,44 @@
|
|
|
409
489
|
saturate(var(--t-saturate, var(--saturate-from, 1))) sepia(var(--t-sepia, var(--sepia-from, 0)))
|
|
410
490
|
hue-rotate(var(--t-hue-rotate, var(--hue-rotate-from, 0deg)));
|
|
411
491
|
clip-path: var(--t-clip, var(--clip-from, none));
|
|
412
|
-
transition:
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
492
|
+
/* Longhands rather than the `transition` shorthand: every property here shares
|
|
493
|
+
one duration and one easing, so listing them once lets the @supports block
|
|
494
|
+
below APPEND height without restating the whole list (a shorthand would
|
|
495
|
+
replace it, and transition-property has no additive form). */
|
|
496
|
+
transition-property: opacity, translate, scale, rotate, filter, clip-path;
|
|
497
|
+
transition-duration: var(--animation-duration);
|
|
498
|
+
transition-timing-function: var(--custom-ease-out);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* Layout stage — `height`, the only one that reflows, and the only one that
|
|
502
|
+
needs a feature gate.
|
|
503
|
+
|
|
504
|
+
`height: 0 → auto` is not interpolable on its own: `auto` isn't a length, so
|
|
505
|
+
the pair falls back to discrete and the transition doesn't run.
|
|
506
|
+
`interpolate-size: allow-keywords` (set on :root in utilities.css) is what
|
|
507
|
+
makes `auto` a real endpoint — NOT `transition-behavior: allow-discrete`,
|
|
508
|
+
which only permits genuinely discrete properties (display,
|
|
509
|
+
content-visibility, overlay) to transition and would at best snap at the
|
|
510
|
+
midpoint here. Gated so engines without it keep no height transition rather
|
|
511
|
+
than that snap. The same dependency applies to the `layout` KEYFRAME, so this
|
|
512
|
+
is not a weaker path than a driver — it is the same requirement, stated once.
|
|
513
|
+
|
|
514
|
+
Declaring `height: auto` on every .transition element is safe because auto is
|
|
515
|
+
the initial value and animation.css is imported second in index.css — before
|
|
516
|
+
layout.css, every pattern and utilities.css — so anything later at equal
|
|
517
|
+
specificity still wins.
|
|
518
|
+
|
|
519
|
+
Caveat worth keeping: patterns/details.css records that transitioning
|
|
520
|
+
block-size to `auto` on ::details-content DEADLOCKS in Chrome 149
|
|
521
|
+
(content-visibility: hidden → no layout → `auto` resolves to 0 → nothing
|
|
522
|
+
runs). That is specific to ::details-content, but the lesson generalises — a
|
|
523
|
+
passing @supports query is not evidence the interaction works. Verify a real
|
|
524
|
+
hover before trusting a change here. */
|
|
525
|
+
@supports (interpolate-size: allow-keywords) {
|
|
526
|
+
.transition {
|
|
527
|
+
height: var(--t-height, var(--height-from, auto));
|
|
528
|
+
transition-property: opacity, translate, scale, rotate, filter, clip-path, height;
|
|
529
|
+
}
|
|
419
530
|
}
|
|
420
531
|
|
|
421
532
|
/* Respect reduced-motion: drop keyframes, transitions, and continuous floats so
|
package/css/global.css
CHANGED
|
@@ -8,12 +8,28 @@
|
|
|
8
8
|
font-size: 100% !important;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/* Border-box sizing, everywhere.
|
|
12
|
+
The framework used to refuse this on the grounds that a global reset would
|
|
13
|
+
silently reflow consumer layouts. Two things changed. It is now LAYERED (this
|
|
14
|
+
file is `vitops.base`, the lowest of the three), so it loses to any unlayered
|
|
15
|
+
consumer rule — the opt-out is `*, *::before, *::after { box-sizing:
|
|
16
|
+
content-box }` in your own stylesheet, and a consumer who already ships a
|
|
17
|
+
border-box reset (Tailwind preflight, normalize, almost any modern base) sees
|
|
18
|
+
no change at all. And several patterns are only CORRECT under border-box: the
|
|
19
|
+
`.split` ratio is a flex basis, which sizes the border box, so under
|
|
20
|
+
content-box a padded column came out wider than its sibling by exactly its
|
|
21
|
+
padding. Stating the assumption beats each pattern re-asserting it and the
|
|
22
|
+
ones that forget being quietly wrong. */
|
|
23
|
+
*,
|
|
24
|
+
*::before,
|
|
25
|
+
*::after {
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
/* Drop the UA's 8px body margin. The framework owns page gutters via `.centered`
|
|
12
30
|
(--gutter), so the UA default just offsets every full-bleed surface — sticky
|
|
13
31
|
headers and `bg-*` bands end up inset by 8px with a sliver of canvas showing
|
|
14
|
-
around them.
|
|
15
|
-
does NOT ship a general reset (no global box-sizing change), because that
|
|
16
|
-
would silently reflow existing consumer layouts. */
|
|
32
|
+
around them. */
|
|
17
33
|
body {
|
|
18
34
|
margin: 0;
|
|
19
35
|
}
|
package/css/index.css
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
@import './generated/tokens.css';
|
|
8
8
|
@import './generated/typography.css';
|
|
9
9
|
@import './layout.css';
|
|
10
|
+
/* Must precede utilities.css: both are `vitops.utilities`, and utilities.css
|
|
11
|
+
re-emits `.<bp>-flex` AFTER its own `.hidden` so `class="hidden md-flex"`
|
|
12
|
+
reveals. That re-emit is only load-bearing because this file's `.md-flex`
|
|
13
|
+
comes first. */
|
|
14
|
+
@import './layout-utilities.css';
|
|
10
15
|
/* was layout-extra.css */
|
|
11
16
|
@import './patterns/cluster.css';
|
|
12
17
|
@import './patterns/grouped.css';
|
|
@@ -17,7 +22,9 @@
|
|
|
17
22
|
@import './patterns/sticky.css';
|
|
18
23
|
@import './patterns/stack.css';
|
|
19
24
|
@import './patterns/splitter.css';
|
|
25
|
+
@import './patterns/reveal.css';
|
|
20
26
|
@import './utilities.css';
|
|
27
|
+
@import './generated/spacing.css';
|
|
21
28
|
@import './generated/patterns.css';
|
|
22
29
|
/* was overlays.css */
|
|
23
30
|
@import './patterns/dialog.css';
|
|
@@ -38,6 +45,7 @@
|
|
|
38
45
|
@import './patterns/details.css';
|
|
39
46
|
@import './patterns/comment.css';
|
|
40
47
|
@import './patterns/banner.css';
|
|
48
|
+
@import './patterns/consent.css';
|
|
41
49
|
@import './patterns/list.css';
|
|
42
50
|
@import './patterns/tree.css';
|
|
43
51
|
/* was typographic.css */
|