@cssints/reveal 0.11.0 → 0.12.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/README.md CHANGED
@@ -1,16 +1,23 @@
1
1
  # @cssints/reveal
2
2
 
3
- Scroll-driven reveal animations for [cssints](../../README.md): an element runs one of
4
- [@cssints/motion](../motion/README.md)'s entrance presets as it scrolls into view, on `animation-timeline: view()` and
5
- `animation-range`, as one chain member of `cssints/plugin` `scope()`. In the spirit of
6
- [the supersonic plugin](https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation) and
7
- [lax.js](https://github.com/alexfoxy/lax.js), with the browser doing the work. Where a target lacks scroll-driven
8
- animations, a 0.3 kB script plays the same keyframes once when the element enters.
3
+ Entrance animations for [cssints](../../README.md), on [@cssints/motion](../motion/README.md)'s presets, as two chain
4
+ members of `cssints/plugin` `scope()`:
5
+
6
+ - **`reveal()`** is scroll-_driven_: an element runs the entrance as it scrolls into view, on `animation-timeline: view()`
7
+ and `animation-range`, so the scroll scrubs it. In the spirit of
8
+ [the supersonic plugin](https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation) and
9
+ [lax.js](https://github.com/alexfoxy/lax.js), with the browser doing the work.
10
+ - **`appear()`** is scroll-_triggered_: the scroll only decides _when_ a normal, time-based animation plays, on
11
+ `timeline-trigger` + `animation-trigger`. It keeps its own duration and easing, so a slow scroll cannot stretch the
12
+ entrance. The case is [utilitybend's article on CSS animation triggers](https://utilitybend.com/blog/scroll-triggered-animation-as-the-missing-piece-for-scrollytelling-on-the-web).
13
+
14
+ Where a target lacks the feature a member runs on, a 0.4 kB script plays the same animation once when the element enters.
9
15
 
10
16
  ```ts
11
17
  import { cn, hover } from "cssints" with { type: "cssints" };
12
- import { reveal } from "@cssints/reveal" with { type: "cssints" };
18
+ import { appear, reveal } from "@cssints/reveal" with { type: "cssints" };
13
19
 
20
+ // scroll-driven: the scroll scrubs it
14
21
  export const fade = reveal();
15
22
  export const card = reveal()
16
23
  .preset("slide-in-up")
@@ -18,8 +25,14 @@ export const card = reveal()
18
25
  .duration("500ms")
19
26
  .easing("linear");
20
27
  export const tile = cn(reveal().preset("blur-in"), hover(reveal().duration("1s")));
28
+
29
+ // scroll-triggered: it plays once on entry, with its own time
30
+ export const lede = appear().preset("slide-in-up").duration("600ms").easing("linear");
31
+ export const band = appear().preset("fade-in").inOut();
21
32
  ```
22
33
 
34
+ The CSS of `reveal()` (`fade` and `card` above):
35
+
23
36
  ```css
24
37
  @layer _.k {
25
38
  @keyframes kf-7f473efd {
@@ -76,61 +89,146 @@ export const tile = cn(reveal().preset("blur-in"), hover(reveal().duration("1s")
76
89
  }
77
90
  ```
78
91
 
92
+ The CSS of `appear()` (`lede` and `band` above):
93
+
94
+ ```css
95
+ @layer _.k {
96
+ /* motion's keyframes, the two rules reveal() names too (one pair for the page) */
97
+ ._reveal-enter {
98
+ animation: var(--reveal-enter-k) var(--reveal-enter-d) var(--reveal-enter-e) both;
99
+ timeline-trigger: --reveal-t view() var(--reveal-enter-g);
100
+ --reveal-enter-k: kf-7f473efd;
101
+ --reveal-enter-rk: kf-4f766ef7;
102
+ --reveal-enter-d: 300ms;
103
+ --reveal-enter-e: ease-out;
104
+ --reveal-enter-g: entry 100% exit 0%;
105
+ --reveal-enter: play-once;
106
+ --reveal-leave: none;
107
+ --motion-o: 0;
108
+ --motion-x: 0;
109
+ --motion-y: 0;
110
+ --motion-s: 1;
111
+ --motion-b: 0;
112
+ animation-trigger: --reveal-t var(--reveal-enter) var(--reveal-leave);
113
+ trigger-scope: --reveal-t;
114
+ }
115
+ @media (prefers-reduced-motion: reduce) {
116
+ ._reveal-enter {
117
+ animation-name: var(--reveal-enter-rk);
118
+ }
119
+ }
120
+ /* only when a target lacks animation triggers */
121
+ @supports not (animation-trigger: none) {
122
+ [data-reveal] ._reveal-enter:not([data-reveal-in]) {
123
+ animation-play-state: paused;
124
+ }
125
+ }
126
+ }
127
+ @layer _.a {
128
+ ._5mnsmm {
129
+ --reveal-enter: play-forwards;
130
+ }
131
+ ._67iv7r {
132
+ --reveal-leave: play-backwards;
133
+ }
134
+ ._1a9p5a9 {
135
+ --motion-y: 25%;
136
+ }
137
+ ._knj5bv {
138
+ --motion-o: 0;
139
+ }
140
+ ._oflqj4 {
141
+ --reveal-enter-e: linear;
142
+ }
143
+ ._rsl9nu {
144
+ --reveal-enter-d: 600ms;
145
+ }
146
+ }
147
+ ```
148
+
79
149
  ## Rules
80
150
 
81
151
  - **Methods**, each once, in any order, all optional: `preset(name)` (`fade-in`), `range(<'animation-range'>)`
82
- (`entry 0% cover 40%`), `duration(<time [0s,∞]>)` (300ms) and `easing(<easing-function>)` (`ease-out`). Each is one
83
- class that sets a custom property, so a method works under a condition. Where the timeline is native, the range
84
- decides when the animation runs and the scroll drives it (back and forth); the duration and the easing shape the
85
- fallback's one play (the easing applies to the scrubbed progress too).
152
+ (`entry 0% cover 40%` for `reveal()`, `entry 100% exit 0%` for `appear()`), `duration(<time [0s,∞]>)` (300ms) and
153
+ `easing(<easing-function>)` (`ease-out`). Each is one class that sets a custom property, so a method works under a
154
+ condition.
155
+ - **`reveal()`.** Where the timeline is native, the range decides when the animation runs and the scroll drives it (back
156
+ and forth); the duration and the easing shape the fallback's one play (the easing applies to the scrubbed progress too).
157
+ - **`appear()`.** The range is the _activation_ range of the trigger: the element is fully in at `entry 100%`, so the
158
+ animation plays there and not while it is entering. The animation then runs over `duration()` with `easing()`, whatever
159
+ the scroll does. The default actions are `play-once none`: it plays once and stays, which is what an entrance wants —
160
+ and exactly what the fallback does. `inOut()` sets `play-forwards play-backwards`, so it plays back out when the
161
+ element leaves (where the triggers are native).
86
162
  - **Presets** are motion's entrance presets: `fade-in`, `slide-in-up`, `slide-in-down`, `slide-in-left`,
87
163
  `slide-in-right`, `scale-in`, `blur-in`. The `-out` ones are not: the fallback can only play when an element enters.
88
- - **Reuse of motion.** The kernel names motion's `motionKeyframes.in` and `motionKeyframes.reducedIn`, which carries
89
- their rules (shared with `motion()` on the same page: one rule each), and a preset sets the values those keyframes
90
- read, from motion's `presets` table under motion's names (`motionVars`). Nothing is copied, not even a name.
91
- - **Reduced motion.** Under `prefers-reduced-motion: reduce` the kernel runs the opacity-only keyframes (motion's rule):
92
- a slide, scale or blur becomes a fade over the same range.
93
- - **One kernel, written as styles.** The `animation` shorthand resets `animation-timeline` and `animation-range`; a
94
- kernel prints a shorthand before its longhands, so the timeline set beside it wins.
95
- - **Conflicts.** `reveal()` sets the animation: a `cn()` that holds it and an `animation*` property is a build error,
96
- derived from the kernel: `cssints: reveal: kernel core sets animation, animation-timeline, animation-range, animation-name (this style also sets animation-duration)`.
164
+ - **Reuse of motion.** Both members name motion's `motionKeyframes.in` and `motionKeyframes.reducedIn`, so that rule is
165
+ motion's kernel, printed once however many styles use it, and a preset sets the values those keyframes read from
166
+ motion's `presets` table under motion's names (`motionVars`). Only each member's own properties are its own names:
167
+ `appear()`'s are `--reveal-enter-*`, because `reveal()`'s kernel declares the scope's short names (`--reveal-k` and
168
+ the rest) and two kernels of one scope may not share one of them. A scope may reuse a name another scope owns — that
169
+ is how both of them set `--motion-*` — so `motion()`, `reveal()` and `appear()` can sit on one page.
170
+ - **`trigger-scope` is required.** The trigger name is the kernel's (`--reveal-t`) and every element of the class shares
171
+ it; without `trigger-scope: --reveal-t`, the last element in the document would drive every card. With it, two boxes
172
+ of the same class trigger independently (checked in Chrome 156). Two different styles on one page do not collide
173
+ either: each kernel has its own name.
174
+ - **Reduced motion.** Under `prefers-reduced-motion: reduce` each kernel runs the opacity-only keyframes (motion's rule):
175
+ a slide, scale or blur becomes a fade over the same range or trigger.
176
+ - **One kernel, written as styles.** The `animation` shorthand resets `animation-timeline` and `animation-trigger`; a
177
+ kernel prints a shorthand before its longhands, so the declarations beside it win.
178
+ - **Conflicts.** `reveal()` and `appear()` set the animation: a `cn()` that holds one and an `animation*` property is a
179
+ build error, derived from the kernel:
180
+ `cssints: reveal: kernel enter sets animation, timeline-trigger, animation-trigger, trigger-scope, animation-name (this style also sets animation-duration)`.
97
181
  - **Errors** are the grammar's, in the types and at build time: `reveal.preset: unexpected "fade-out" in "fade-out",
98
- expected fade-in | ...`, `reveal.range: unexpected "sideways" in "entry 0% sideways"`.
182
+ expected fade-in | ...`, `appear.range: unexpected "sideways" in "entry 0% sideways"`.
99
183
 
100
184
  ## Browsers
101
185
 
102
- - **Native**: Chrome 115, Safari 26 (scroll-driven animations, `css.properties.animation-timeline` and
103
- `animation-range`). Firefox has them behind a flag only.
104
- - **The fallback.** When a browserslist target lacks them, the build adds the `@supports not` rule above to the sheet
105
- (it stays out of the sheet for targets that all have them), and the kernel's `animation-timeline` and `animation-range` give no warning: the runtime polyfills both. The
106
- script, `@cssints/reveal/runtime`, comes with it: the runtime entry names it (`script`), so the module that uses
107
- `reveal()` imports it through `virtual:cssints/runtime/reveal.io`, which is empty when every target has the timeline.
108
- Nothing to import by hand. In the browser it does nothing where `CSS.supports("animation-timeline:view()")`; elsewhere it
109
- marks `<html data-reveal>` (the rule then holds each reveal at its first frame) and an `IntersectionObserver` gives an
110
- element `data-reveal-in` when it enters the viewport, which plays the keyframes once, over `duration()`. A
111
- `MutationObserver` picks up elements added later. 536 bytes minified, 319 gzip; no imports.
186
+ - **Native.** `reveal()`: Chrome 115, Safari 26 (scroll-driven animations, `css.properties.animation-timeline` and
187
+ `animation-range`); Firefox has them behind a flag only. `appear()`: Chrome 146, Edge 146, Opera 130
188
+ (`css.properties.animation-trigger`, `timeline-trigger` and `trigger-scope`); Firefox and Safari have no animation
189
+ triggers today. CSS animation triggers are a candidate for Interop 2027.
190
+ - **The fallback.** When a browserslist target lacks the feature a member runs on, the build adds that member's
191
+ `@supports not` rule above to the sheet (it stays out of the sheet for targets that all have it), and the kernel's
192
+ declarations give no support warning: the runtime polyfills them. The script, `@cssints/reveal/runtime`, comes with
193
+ it: each runtime entry names it (`script`), so a module that uses `reveal()` imports it through
194
+ `virtual:cssints/runtime/reveal.io` and one that uses `appear()` through `virtual:cssints/runtime/reveal.io-appear`;
195
+ each is empty when every target has its feature. Nothing to import by hand.
196
+ - **One script, two gates.** In the browser the script does nothing where both features work. It gates on
197
+ `CSS.supports("animation-timeline:view()")` for `reveal()` and `CSS.supports("animation-trigger:none")` for
198
+ `appear()`: it marks `<html data-reveal>` (the rule then holds each fallback animation at its first frame) and an
199
+ `IntersectionObserver` gives an element `data-reveal-in` when it enters the viewport, which plays the keyframes once,
200
+ over `duration()`. A `MutationObserver` picks up elements added later. 605 bytes minified, 369 gzip; no imports.
201
+ - **The fallback equals `appear()`'s default.** Playing once on entry is exactly `play-once none`, so on Firefox and
202
+ Safari `appear()` needs no extra behaviour; it cannot play back out (`inOut()`), follow the range, or react to the
203
+ scroll.
112
204
  - **Why not a polyfill.** [flackr's scroll-timeline polyfill](https://github.com/flackr/scroll-timeline) scrubs the
113
205
  animation as the native timeline does, but it is about 61 kB minified (17 kB gzip), rewrites stylesheets and runs on
114
- every scroll. A reveal is an entrance: playing it once on entry is what most sites want, costs 1/50 of the bytes and
206
+ every scroll. An entrance is a one-shot: playing it once on entry is what most sites want, costs 1/50 of the bytes and
115
207
  nothing per scroll. The price is that the fallback is not scrubbed and ignores `range()`.
116
- - Without the script, a target that lacks the timeline plays the animation on load, as `motion()` would: nothing stays
208
+ - Without the script, a target that lacks the feature plays the animation on load, as `motion()` would: nothing stays
117
209
  hidden.
118
- - Tested in Chrome 154 and WebKit 26.6 (native, and the fallback forced) and Firefox 155 (the fallback):
119
- `bun run test:browsers`.
210
+ - Tested in Chrome 156 (native triggers and the fallback forced), Firefox 157 (the fallback) and WebKit 27.2 (the
211
+ reveal timeline native, the fallback for `appear()`): `bun run test:browsers`.
120
212
 
121
213
  ## Limits
122
214
 
123
215
  - With targets that mix both (the default browserslist), the script ships to every browser and only probes where the
124
- timeline is native.
216
+ feature is native.
125
217
  - The fallback does not follow the range, the scroll direction or an element that leaves the viewport: it plays once. An
126
218
  element that gets the class after it is in the document (not added with it) is not seen.
127
- - Until the script runs, a reveal below the fold may start playing; it is paused where it is and resumes on entry. The
128
- script runs with the first module that uses `reveal()`.
219
+ - Until the script runs, an animation below the fold may start playing; it is paused where it is and resumes on entry.
220
+ The script runs with the first module that uses `reveal()` or `appear()`.
221
+ - None of `motion()`, `reveal()` and `appear()` may be put on one element: each sets the `animation`, which two
222
+ kernels of one style refuse (`kernels ... both set animation`). On a page they share motion's keyframes and custom
223
+ properties.
224
+ - The trigger is on the animated element itself. A tall section that triggers its children is written by hand:
225
+ `css.timelineTrigger()` and `css.animationTrigger()` on the section, and `css.animationTrigger()` on the child. A
226
+ separate trigger element and a `.from("--name")` method are deferred.
129
227
  - Entrance presets only, one per element; for an exit on scroll write the animation with `animationTimeline("view()")`
130
228
  and `animationRange("exit ...")` yourself.
131
229
 
132
230
  ```bash
133
- cd packages/reveal && bun run test # fixtures through the engine (output by targets, runtime module, conflicts, errors)
231
+ cd packages/reveal && bun run test # fixtures through the engine (output by targets, runtime modules, conflicts, errors)
134
232
  cd packages/reveal && bun run test:browsers # Chrome, Firefox and WebKit through playwright-core
135
233
  ```
136
234
 
package/dist/index.d.mts CHANGED
@@ -1,13 +1,21 @@
1
- /** The scope of `reveal()`, for a scope that embeds it (its kernel `core`). */
1
+ /** The scope of `reveal()` and `appear()`, for a scope that embeds it (their kernels `core` and `enter`). */
2
2
  export declare const revealScope: import("cssints/plugin").Scope<"reveal", {
3
3
  readonly "#core": {
4
4
  readonly css: (css: import("cssints/plugin").Css) => import("cssints").Style[];
5
5
  };
6
+ readonly "#enter": {
7
+ readonly css: (css: import("cssints/plugin").Css) => import("cssints").Style[];
8
+ };
6
9
  readonly "#io": {
7
10
  readonly polyfills: ["css.properties.animation-timeline", "css.properties.animation-range"];
8
11
  readonly script: "@cssints/reveal/runtime";
9
12
  readonly css: "@supports not (animation-timeline:view()){[data-reveal] &:not([data-reveal-in]){animation-play-state:paused}}";
10
13
  };
14
+ readonly "#io-appear": {
15
+ readonly polyfills: ["css.properties.animation-trigger", "css.properties.timeline-trigger", "css.properties.trigger-scope"];
16
+ readonly script: "@cssints/reveal/runtime";
17
+ readonly css: "@supports not (animation-trigger:none){[data-reveal] &:not([data-reveal-in]){animation-play-state:paused}}";
18
+ };
11
19
  readonly reveal: {
12
20
  readonly uses: readonly ["core", "io"];
13
21
  readonly chain: {
@@ -29,12 +37,44 @@ export declare const revealScope: import("cssints/plugin").Scope<"reveal", {
29
37
  };
30
38
  };
31
39
  };
40
+ readonly appear: {
41
+ readonly uses: readonly ["enter", "io-appear"];
42
+ readonly chain: {
43
+ readonly preset: {
44
+ readonly args: "fade-in | slide-in-up | slide-in-down | slide-in-left | slide-in-right | scale-in | blur-in";
45
+ readonly emit: (name: string) => (readonly ["--motion-o" | "--motion-x" | "--motion-y" | "--motion-s" | "--motion-b", string])[];
46
+ };
47
+ readonly range: {
48
+ readonly args: "<'animation-range'>";
49
+ readonly emit: (v: string) => [string, string][];
50
+ };
51
+ readonly duration: {
52
+ readonly args: "<time [0s,∞]>";
53
+ readonly emit: (v: string) => [string, string][];
54
+ };
55
+ readonly easing: {
56
+ readonly args: "<easing-function>";
57
+ readonly emit: (v: string) => [string, string][];
58
+ };
59
+ readonly inOut: {
60
+ readonly args: "";
61
+ readonly emit: () => [string, string][];
62
+ };
63
+ };
64
+ };
32
65
  }>;
33
66
  /**
34
67
  * `reveal().preset("slide-in-up").range("entry 0% entry 100%").duration("500ms").easing("linear")`: motion's entrance
35
68
  * preset driven by the element's scroll into view, each method once and optional (`fade-in`, `entry 0% cover 40%`,
36
69
  * 300ms and `ease-out` for the fallback, which plays once on entry). Under `prefers-reduced-motion: reduce` only the
37
70
  * opacity moves. It sets the animation, so an `animation*` property in the same `cn()` is a build error.
71
+ *
72
+ * `appear().preset("slide-in-up").duration("600ms").easing("linear")`: motion's entrance preset triggered by the
73
+ * element's scroll into view, each method once and optional (`fade-in`, `entry 100% exit 0%`, 300ms and `ease-out`).
74
+ * The scroll only starts the animation: it keeps its own duration and easing. `inOut()` plays it forwards on entry and
75
+ * backwards on exit, where the triggers are native; the default plays it once and stays. Under
76
+ * `prefers-reduced-motion: reduce` only the opacity moves. It sets the animation, so an `animation*` property in the
77
+ * same `cn()` is a build error.
38
78
  */
39
79
  export declare const reveal: () => import("cssints/plugin").Chain<"reveal", {
40
80
  readonly preset: {
@@ -53,4 +93,25 @@ export declare const reveal: () => import("cssints/plugin").Chain<"reveal", {
53
93
  readonly args: "<easing-function>";
54
94
  readonly emit: (v: string) => [string, string][];
55
95
  };
96
+ }, {}>, appear: () => import("cssints/plugin").Chain<"appear", {
97
+ readonly preset: {
98
+ readonly args: "fade-in | slide-in-up | slide-in-down | slide-in-left | slide-in-right | scale-in | blur-in";
99
+ readonly emit: (name: string) => (readonly ["--motion-o" | "--motion-x" | "--motion-y" | "--motion-s" | "--motion-b", string])[];
100
+ };
101
+ readonly range: {
102
+ readonly args: "<'animation-range'>";
103
+ readonly emit: (v: string) => [string, string][];
104
+ };
105
+ readonly duration: {
106
+ readonly args: "<time [0s,∞]>";
107
+ readonly emit: (v: string) => [string, string][];
108
+ };
109
+ readonly easing: {
110
+ readonly args: "<easing-function>";
111
+ readonly emit: (v: string) => [string, string][];
112
+ };
113
+ readonly inOut: {
114
+ readonly args: "";
115
+ readonly emit: () => [string, string][];
116
+ };
56
117
  }, {}>;
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ const emitPreset = (name) => {
10
10
  return v === void 0 ? [] : [[motionVars[f], v]];
11
11
  });
12
12
  };
13
- /** The scope of `reveal()`, for a scope that embeds it (its kernel `core`). */
13
+ /** The scope of `reveal()` and `appear()`, for a scope that embeds it (their kernels `core` and `enter`). */
14
14
  const revealScope = scope("reveal", {
15
15
  "#core": { css: (css) => [
16
16
  css.vars({
@@ -30,11 +30,41 @@ const revealScope = scope("reveal", {
30
30
  css.animationRange("var(--_g)"),
31
31
  css.media("(prefers-reduced-motion: reduce)")(css.animationName("var(--_rk)"))
32
32
  ] },
33
+ "#enter": { css: (css) => [
34
+ css.vars({
35
+ "--_enter-k": motionKeyframes.in,
36
+ "--_enter-rk": motionKeyframes.reducedIn,
37
+ "--_enter-d": "300ms",
38
+ "--_enter-e": "ease-out",
39
+ "--_enter-g": "entry 100% exit 0%",
40
+ "--_enter": "play-once",
41
+ "--_leave": "none",
42
+ [motionVars.opacity]: 0,
43
+ [motionVars.x]: 0,
44
+ [motionVars.y]: 0,
45
+ [motionVars.scale]: 1,
46
+ [motionVars.blur]: 0
47
+ }),
48
+ css.animation("var(--_enter-k) var(--_enter-d) var(--_enter-e) both"),
49
+ css.timelineTrigger("--_t view() var(--_enter-g)"),
50
+ css.animationTrigger("--_t var(--_enter) var(--_leave)"),
51
+ css.triggerScope("--_t"),
52
+ css.media("(prefers-reduced-motion: reduce)")(css.animationName("var(--_enter-rk)"))
53
+ ] },
33
54
  "#io": {
34
55
  polyfills: ["css.properties.animation-timeline", "css.properties.animation-range"],
35
56
  script: "@cssints/reveal/runtime",
36
57
  css: `@supports not (animation-timeline:view()){[data-reveal] &:not([data-reveal-in]){animation-play-state:paused}}`
37
58
  },
59
+ "#io-appear": {
60
+ polyfills: [
61
+ "css.properties.animation-trigger",
62
+ "css.properties.timeline-trigger",
63
+ "css.properties.trigger-scope"
64
+ ],
65
+ script: "@cssints/reveal/runtime",
66
+ css: `@supports not (animation-trigger:none){[data-reveal] &:not([data-reveal-in]){animation-play-state:paused}}`
67
+ },
38
68
  reveal: {
39
69
  uses: ["core", "io"],
40
70
  chain: {
@@ -55,6 +85,31 @@ const revealScope = scope("reveal", {
55
85
  emit: (v) => [["--_e", v]]
56
86
  }
57
87
  }
88
+ },
89
+ appear: {
90
+ uses: ["enter", "io-appear"],
91
+ chain: {
92
+ preset: {
93
+ args: names,
94
+ emit: emitPreset
95
+ },
96
+ range: {
97
+ args: "<'animation-range'>",
98
+ emit: (v) => [["--_enter-g", v]]
99
+ },
100
+ duration: {
101
+ args: "<time [0s,∞]>",
102
+ emit: (v) => [["--_enter-d", v]]
103
+ },
104
+ easing: {
105
+ args: "<easing-function>",
106
+ emit: (v) => [["--_enter-e", v]]
107
+ },
108
+ inOut: {
109
+ args: "",
110
+ emit: () => [["--_enter", "play-forwards"], ["--_leave", "play-backwards"]]
111
+ }
112
+ }
58
113
  }
59
114
  });
60
115
  /**
@@ -62,6 +117,13 @@ const revealScope = scope("reveal", {
62
117
  * preset driven by the element's scroll into view, each method once and optional (`fade-in`, `entry 0% cover 40%`,
63
118
  * 300ms and `ease-out` for the fallback, which plays once on entry). Under `prefers-reduced-motion: reduce` only the
64
119
  * opacity moves. It sets the animation, so an `animation*` property in the same `cn()` is a build error.
120
+ *
121
+ * `appear().preset("slide-in-up").duration("600ms").easing("linear")`: motion's entrance preset triggered by the
122
+ * element's scroll into view, each method once and optional (`fade-in`, `entry 100% exit 0%`, 300ms and `ease-out`).
123
+ * The scroll only starts the animation: it keeps its own duration and easing. `inOut()` plays it forwards on entry and
124
+ * backwards on exit, where the triggers are native; the default plays it once and stays. Under
125
+ * `prefers-reduced-motion: reduce` only the opacity moves. It sets the animation, so an `animation*` property in the
126
+ * same `cn()` is a build error.
65
127
  */
66
- const { reveal } = revealScope.export();
67
- export { reveal, revealScope };
128
+ const { reveal, appear } = revealScope.export();
129
+ export { appear, reveal, revealScope };
package/dist/runtime.mjs CHANGED
@@ -1 +1 @@
1
- if(typeof CSS<`u`&&!CSS.supports(`animation-timeline:view()`)){let e=document.documentElement,t=`._reveal-core`,n=new IntersectionObserver(e=>{for(let t of e)t.isIntersecting&&(t.target.setAttribute(`data-reveal-in`,``),n.unobserve(t.target))}),r=e=>{e.matches(t)&&n.observe(e);for(let r of e.querySelectorAll(t))n.observe(r)};e.setAttribute(`data-reveal`,``),r(e),new MutationObserver(e=>{for(let t of e)for(let e of t.addedNodes)e instanceof Element&&r(e)}).observe(e,{childList:!0,subtree:!0})}
1
+ if(typeof CSS<`u`){let e=!CSS.supports(`animation-timeline:view()`),t=!CSS.supports(`animation-trigger:none`);if(e||t){let n=document.documentElement,r=[e&&`._reveal-core`,t&&`._reveal-enter`].filter(Boolean).join(`,`),i=new IntersectionObserver(e=>{for(let t of e)t.isIntersecting&&(t.target.setAttribute(`data-reveal-in`,``),i.unobserve(t.target))}),a=e=>{e.matches(r)&&i.observe(e);for(let t of e.querySelectorAll(r))i.observe(t)};n.setAttribute(`data-reveal`,``),a(n),new MutationObserver(e=>{for(let t of e)for(let e of t.addedNodes)e instanceof Element&&a(e)}).observe(n,{childList:!0,subtree:!0})}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cssints/reveal",
3
- "version": "0.11.0",
4
- "description": "Scroll-driven reveal animations for cssints: motion's presets on animation-timeline: view(), with a tiny IntersectionObserver fallback",
3
+ "version": "0.12.0",
4
+ "description": "Scroll-driven and scroll-triggered entrance animations for cssints: motion's presets on animation-timeline: view() (reveal) or on CSS animation triggers (appear), with a tiny IntersectionObserver fallback",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -28,10 +28,10 @@
28
28
  }
29
29
  },
30
30
  "dependencies": {
31
- "@cssints/motion": "^0.11.0"
31
+ "@cssints/motion": "^0.12.0"
32
32
  },
33
33
  "peerDependencies": {
34
- "cssints": "^0.11.0"
34
+ "cssints": "^0.12.0"
35
35
  },
36
36
  "engines": {
37
37
  "node": ">=22"