@lovince/motion 0.0.0-stage → 0.1.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -2
  3. package/dist/accessibility/focus-management.d.ts +9 -0
  4. package/dist/accessibility/index.d.ts +2 -0
  5. package/dist/accessibility/reduced-motion.d.ts +7 -0
  6. package/dist/core/cleanup.d.ts +10 -0
  7. package/dist/core/config.d.ts +7 -0
  8. package/dist/core/context.d.ts +40 -0
  9. package/dist/core/entrance.d.ts +22 -0
  10. package/dist/core/index.d.ts +7 -0
  11. package/dist/core/preferences.d.ts +6 -0
  12. package/dist/core/targets.d.ts +4 -0
  13. package/dist/core/types.d.ts +20 -0
  14. package/dist/css/dom.d.ts +9 -0
  15. package/dist/css/fade-in.d.ts +25 -0
  16. package/dist/css/index.d.ts +3 -0
  17. package/dist/css/reveal.d.ts +22 -0
  18. package/dist/dom-CK6FnKcV.js +74 -0
  19. package/dist/focus-management-DTJ2C7Pn.js +8 -0
  20. package/dist/gsap/engine.d.ts +17 -0
  21. package/dist/gsap/horizontal-scroll.d.ts +14 -0
  22. package/dist/gsap/index.d.ts +8 -0
  23. package/dist/gsap/parallax.d.ts +14 -0
  24. package/dist/gsap/scroll-reveal.d.ts +18 -0
  25. package/dist/gsap/split-text.d.ts +27 -0
  26. package/dist/gsap/stagger.d.ts +15 -0
  27. package/dist/gsap/timeline.d.ts +23 -0
  28. package/dist/gsap.js +96 -0
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.js +75 -0
  31. package/dist/presets/entrance.d.ts +20 -0
  32. package/dist/presets/image.d.ts +14 -0
  33. package/dist/presets/index.d.ts +5 -0
  34. package/dist/presets/navigation.d.ts +17 -0
  35. package/dist/presets/page-transition.d.ts +26 -0
  36. package/dist/presets/typography.d.ts +20 -0
  37. package/dist/presets.js +138 -0
  38. package/dist/reveal-By5zvLLc.js +39 -0
  39. package/dist/scroll/create-smooth-scroll.d.ts +37 -0
  40. package/dist/scroll/gsap-integration.d.ts +8 -0
  41. package/dist/scroll/index.d.ts +1 -0
  42. package/dist/scroll.js +89 -0
  43. package/dist/split-text-Cnl9gkUu.js +141 -0
  44. package/dist/targets-x7JiHhWI.js +49 -0
  45. package/dist/timeline-Cg3eq5ft.js +60 -0
  46. package/dist/vue/index.d.ts +27 -0
  47. package/dist/vue.js +33 -0
  48. package/package.json +97 -4
  49. package/styles/motion.css +4 -0
  50. package/styles/presets.css +58 -0
  51. package/styles/tokens.css +23 -0
  52. package/styles/utilities.css +128 -0
@@ -0,0 +1,18 @@
1
+ import { MotionContext } from '../core/context';
2
+ import { Cleanup, DistanceName, EntranceName, MotionTarget } from '../core/types';
3
+ export type ScrollRevealOptions = {
4
+ readonly context?: MotionContext;
5
+ readonly effect?: EntranceName;
6
+ readonly distance?: DistanceName;
7
+ readonly stagger?: number;
8
+ /** ScrollTrigger start, e.g. `top 85%`. */
9
+ readonly start?: string;
10
+ /** Hide again when scrolled back above, instead of revealing once. */
11
+ readonly repeat?: boolean;
12
+ };
13
+ /**
14
+ * Reveals elements in batches as they scroll in, so items entering together
15
+ * stagger as a group. For a plain one-time reveal with no GSAP at all, use
16
+ * `revealOnScroll` from the main entry. Under reduced motion nothing is hidden.
17
+ */
18
+ export declare const createScrollReveal: (targets: MotionTarget, options?: ScrollRevealOptions) => Cleanup;
@@ -0,0 +1,27 @@
1
+ import { Cleanup } from '../core/types';
2
+ export type SplitBy = 'words' | 'chars' | 'lines';
3
+ export type SplitOptions = {
4
+ readonly by?: SplitBy;
5
+ /**
6
+ * Lines only: wrap each line in a clipping box, so a line can slide up from
7
+ * behind its own edge. `lines` then holds the moving inner spans.
8
+ */
9
+ readonly mask?: boolean;
10
+ /** Lines only: called after the text re-splits for a new width, so animations can rebuild. */
11
+ readonly onResplit?: (split: SplitText) => void;
12
+ };
13
+ export type SplitText = {
14
+ readonly words: readonly HTMLElement[];
15
+ readonly chars: readonly HTMLElement[];
16
+ /** Line boxes; filled only when the element holds plain text, so no markup is ever restructured. */
17
+ readonly lines: readonly HTMLElement[];
18
+ /** Words grouped by rendered line; always available. */
19
+ readonly lineGroups: readonly (readonly HTMLElement[])[];
20
+ readonly revert: Cleanup;
21
+ };
22
+ /**
23
+ * Splits an element's text into spans for animation and puts it back on
24
+ * `revert`. Inline markup (links, emphasis) is kept. Line splits re-run when
25
+ * the element's width changes, since wrapping moves words between lines.
26
+ */
27
+ export declare const splitText: (element: HTMLElement, options?: SplitOptions) => SplitText;
@@ -0,0 +1,15 @@
1
+ import { MotionContext } from '../core/context';
2
+ import { DistanceName, EntranceName, MotionTarget } from '../core/types';
3
+ import { ManagedTimeline } from './timeline';
4
+ export type StaggerOptions = {
5
+ readonly context?: MotionContext;
6
+ readonly effect?: EntranceName;
7
+ readonly distance?: DistanceName;
8
+ /** Seconds between items; defaults to the context's stagger. */
9
+ readonly stagger?: number;
10
+ readonly duration?: number;
11
+ /** Start when the first item scrolls into view instead of at once. */
12
+ readonly onScroll?: boolean;
13
+ };
14
+ /** Brings a collection in one after another, in document order. */
15
+ export declare const createStagger: (targets: MotionTarget, options?: StaggerOptions) => ManagedTimeline;
@@ -0,0 +1,23 @@
1
+ import { MotionContext, MotionScope } from '../core/context';
2
+ import { Cleanup, MotionOutcome } from '../core/types';
3
+ export type TimelineBuild = (timeline: gsap.core.Timeline, scope: MotionScope) => void;
4
+ export type TimelineOptions = {
5
+ readonly context?: MotionContext;
6
+ readonly vars?: gsap.TimelineVars;
7
+ /** Drives the timeline from scroll. Dropped under reduced motion, where the timeline shows its end state instead. */
8
+ readonly scrollTrigger?: ScrollTrigger.Vars | ((scope: MotionScope) => ScrollTrigger.Vars | null);
9
+ };
10
+ export type ManagedTimeline = {
11
+ /** The live timeline, or null when there is none (reduced motion still builds one, at its end). */
12
+ readonly timeline: () => gsap.core.Timeline | null;
13
+ /** `finished` when it completes, `cancelled` when destroyed first. Never rejects. */
14
+ readonly finished: Promise<MotionOutcome>;
15
+ readonly destroy: Cleanup;
16
+ };
17
+ /**
18
+ * A GSAP timeline whose tweens are all reverted on destroy. Under reduced
19
+ * motion it jumps to its end state: the same final layout, with no movement.
20
+ * Its progress survives a change of preference, so a finished entrance is
21
+ * never replayed.
22
+ */
23
+ export declare const createTimeline: (build: TimelineBuild, options?: TimelineOptions) => ManagedTimeline;
package/dist/gsap.js ADDED
@@ -0,0 +1,96 @@
1
+ import { n as e } from "./targets-x7JiHhWI.js";
2
+ import { a as t } from "./dom-CK6FnKcV.js";
3
+ import { t as n } from "./split-text-Cnl9gkUu.js";
4
+ import { a as r, i, n as a, o, r as s, t as c } from "./timeline-Cg3eq5ft.js";
5
+ import { gsap as l } from "gsap";
6
+ //#region src/gsap/horizontal-scroll.ts
7
+ var u = (e) => Math.min(1, Math.max(0, e)), d = (n, r, i = {}) => t(i.context, (t) => {
8
+ let a = t.select(n)[0], s = a === void 0 ? void 0 : e(r, a)[0];
9
+ if (a === void 0 || s === void 0 || t.reduced) return;
10
+ o();
11
+ let c = l.matchMedia();
12
+ return c.add(`(min-width: ${i.minWidth ?? "48rem"})`, () => {
13
+ let e = () => Math.max(0, s.scrollWidth - a.clientWidth), t = l.to(s, {
14
+ x: () => -e(),
15
+ ease: "none",
16
+ scrollTrigger: {
17
+ trigger: a,
18
+ pin: !0,
19
+ start: "top top",
20
+ end: () => `+=${e()}`,
21
+ scrub: !0,
22
+ invalidateOnRefresh: !0
23
+ }
24
+ }), n = (n) => {
25
+ let r = t.scrollTrigger, i = n.target;
26
+ if (r === void 0 || !(i instanceof Element) || e() === 0) return;
27
+ let a = u((i.getBoundingClientRect().left - s.getBoundingClientRect().left) / e());
28
+ window.scrollTo({
29
+ top: r.start + a * (r.end - r.start),
30
+ behavior: "auto"
31
+ });
32
+ };
33
+ return s.addEventListener("focusin", n), () => s.removeEventListener("focusin", n);
34
+ }), () => c.revert();
35
+ }), f = (e, n = {}) => t(n.context, (t) => {
36
+ let i = t.select(e);
37
+ if (i.length === 0 || t.reduced) return;
38
+ o();
39
+ let a = (n.speed ?? .15) * 50, s = n.axis === "x" ? "xPercent" : "yPercent", c = l.context(() => {
40
+ for (let e of i) l.fromTo(e, { [s]: a }, {
41
+ [s]: -a,
42
+ ease: "none",
43
+ scrollTrigger: {
44
+ trigger: e,
45
+ start: "top bottom",
46
+ end: "bottom top",
47
+ scrub: !0
48
+ }
49
+ });
50
+ }, r(t));
51
+ return () => c.revert();
52
+ }), p = (e, n = {}) => t(n.context, (t) => {
53
+ let a = t.select(e);
54
+ if (a.length === 0 || t.reduced) return;
55
+ let c = n.effect ?? "fade-up", u = s(c, n.distance), d = o(), f = l.context(() => {
56
+ l.set(a, u), d.batch(a, {
57
+ start: n.start ?? "top 85%",
58
+ once: n.repeat !== !0,
59
+ onEnter: (e) => l.to(e, {
60
+ ...i(c),
61
+ duration: t.config.duration,
62
+ ease: t.config.ease,
63
+ stagger: n.stagger ?? t.config.stagger,
64
+ overwrite: !0
65
+ }),
66
+ ...n.repeat === !0 ? { onLeaveBack: (e) => l.set(e, u) } : {}
67
+ });
68
+ }, r(t));
69
+ return () => f.revert();
70
+ }), m = (e, t = {}) => c((n, r) => {
71
+ let i = r.select(e);
72
+ i.length !== 0 && a(n, i, t.effect ?? "fade-up", {
73
+ distance: t.distance,
74
+ vars: {
75
+ stagger: t.stagger ?? r.config.stagger,
76
+ ...t.duration === void 0 ? {} : { duration: t.duration }
77
+ }
78
+ });
79
+ }, {
80
+ context: t.context,
81
+ scrollTrigger: t.onScroll === !0 ? (t) => {
82
+ let n = t.select(e)[0];
83
+ return n === void 0 ? null : {
84
+ trigger: n,
85
+ start: "top 85%",
86
+ once: !0
87
+ };
88
+ } : void 0
89
+ }), h = 500, g = 33, _ = 0, v = () => ({ start: (e) => {
90
+ let t = o(), n = (t) => e.raf(t * 1e3), r = e.on("scroll", () => t.update());
91
+ return l.ticker.add(n), _ += 1, l.ticker.lagSmoothing(0), () => {
92
+ l.ticker.remove(n), r(), --_, _ === 0 && l.ticker.lagSmoothing(h, g);
93
+ };
94
+ } });
95
+ //#endregion
96
+ export { a as addEntrance, d as createHorizontalScroll, f as createParallax, p as createScrollReveal, m as createStagger, c as createTimeline, s as entranceFrom, i as entranceTo, v as gsapScrollDriver, o as scrollTrigger, n as splitText };
@@ -0,0 +1,4 @@
1
+ export * from './core';
2
+ export * from './css';
3
+ export * from './accessibility';
4
+ export { splitText, type SplitBy, type SplitOptions, type SplitText } from './gsap/split-text';
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./targets-x7JiHhWI.js";
2
+ import { a as s, c, i as l, l as u, n as d, o as f, s as p, t as m } from "./dom-CK6FnKcV.js";
3
+ import { a as h, i as g, n as _, r as v, t as y } from "./split-text-Cnl9gkUu.js";
4
+ import { n as b, r as x, t as S } from "./reveal-By5zvLLc.js";
5
+ import { t as C } from "./focus-management-DTJ2C7Pn.js";
6
+ //#region src/core/types.ts
7
+ var w = [
8
+ "fade",
9
+ "fade-up",
10
+ "fade-down",
11
+ "slide-left",
12
+ "slide-right",
13
+ "scale",
14
+ "clip"
15
+ ], T = [
16
+ "sm",
17
+ "md",
18
+ "lg"
19
+ ], E = (e, t) => {
20
+ let n = h(e, t), r = `translate3d(${n.x}px, ${n.y}px, 0) scale(${n.scale})`;
21
+ return n.clipPath === null ? [{
22
+ opacity: n.opacity,
23
+ transform: r
24
+ }, {
25
+ opacity: 1,
26
+ transform: "none"
27
+ }] : [{
28
+ opacity: n.opacity,
29
+ transform: r,
30
+ clipPath: n.clipPath
31
+ }, {
32
+ opacity: 1,
33
+ transform: "none",
34
+ clipPath: v
35
+ }];
36
+ }, D = (e, t = {}) => {
37
+ let n = m(), r = !1, i = s(t.context, (i) => {
38
+ if (n.settled()) return;
39
+ let a = i.select(e).filter((e) => typeof e.animate == "function");
40
+ if (i.reduced || a.length === 0) {
41
+ n.settle("finished");
42
+ return;
43
+ }
44
+ let o = t.duration ?? i.config.duration, s = t.stagger ?? i.config.stagger, c = E(t.effect ?? "fade-up", t.distance ?? "md"), l = a.map((e, n) => e.animate(c, {
45
+ duration: o * 1e3,
46
+ delay: ((t.delay ?? 0) + n * s) * 1e3,
47
+ easing: t.easing ?? d(i.config.ease),
48
+ fill: "backwards"
49
+ }));
50
+ return r = !0, Promise.all(l.map((e) => e.finished)).then(() => n.settle("finished"), () => void 0), () => {
51
+ r = !1;
52
+ for (let e of l) e.cancel();
53
+ queueMicrotask(() => {
54
+ r || n.settle("cancelled");
55
+ });
56
+ };
57
+ });
58
+ return {
59
+ finished: n.promise,
60
+ cancel: () => {
61
+ i(), n.settle("cancelled");
62
+ }
63
+ };
64
+ }, O = (t) => {
65
+ let n = a, r = (e) => {
66
+ n(), n = e ? t() ?? a : a;
67
+ };
68
+ r(i() === "no-preference");
69
+ let o = e((e) => r(e === "no-preference"));
70
+ return () => {
71
+ o(), n(), n = a;
72
+ };
73
+ };
74
+ //#endregion
75
+ export { _ as CLIP_HIDDEN, v as CLIP_SHOWN, T as DISTANCE_NAMES, g as ENTRANCES, w as ENTRANCE_NAMES, S as REVEAL_ATTR, b as STATE_ATTR, r as createDisposer, l as createMotion, d as cssEase, f as defaultMotionConfig, p as distancePx, D as fadeIn, C as focusTarget, i as getMotionPreference, o as isElement, c as motionDistance, a as noop, t as prefersReducedMotion, u as resolveConfig, h as resolveEntrance, n as resolveTargets, x as revealOnScroll, s as runInContext, y as splitText, e as watchMotionPreference, O as whenMotionAllowed };
@@ -0,0 +1,20 @@
1
+ import { MotionContext } from '../core/context';
2
+ import { DistanceName, MotionTarget } from '../core/types';
3
+ import { ManagedTimeline } from '../gsap/timeline';
4
+ /** The parts of a hero, each optional. Children of `actions` enter one by one. */
5
+ export type HeroParts = {
6
+ readonly label?: MotionTarget;
7
+ readonly heading?: MotionTarget;
8
+ readonly body?: MotionTarget;
9
+ readonly actions?: MotionTarget;
10
+ readonly media?: MotionTarget;
11
+ };
12
+ export type HeroOptions = {
13
+ readonly context?: MotionContext;
14
+ readonly distance?: DistanceName;
15
+ };
16
+ /**
17
+ * The hierarchy a reader takes in: label, heading, supporting text, actions,
18
+ * then media. Steps overlap so the whole hero reads as one entrance, not five.
19
+ */
20
+ export declare const heroEntrance: (parts: HeroParts, options?: HeroOptions) => ManagedTimeline;
@@ -0,0 +1,14 @@
1
+ import { MotionContext } from '../core/context';
2
+ import { Cleanup, MotionTarget } from '../core/types';
3
+ export type ImageRevealOptions = {
4
+ readonly context?: MotionContext;
5
+ /** How far the image starts zoomed in; it settles to 1 as the frame opens. Keep it subtle. */
6
+ readonly scale?: number;
7
+ readonly onScroll?: boolean;
8
+ };
9
+ /**
10
+ * Opens a frame upward while the image inside settles from a slight zoom.
11
+ * Only `clip-path` on the frame and `transform` on the image change; the
12
+ * frame's first `img`, `picture` or `video` is the image, or the frame itself.
13
+ */
14
+ export declare const revealImage: (targets: MotionTarget, options?: ImageRevealOptions) => Cleanup;
@@ -0,0 +1,5 @@
1
+ export { heroEntrance, type HeroOptions, type HeroParts } from './entrance';
2
+ export { revealImage, type ImageRevealOptions } from './image';
3
+ export { followState, type FollowStateOptions } from './navigation';
4
+ export { createPageTransition, type PageTransition, type PageTransitionOptions, type TransitionBuild } from './page-transition';
5
+ export { revealText, type TextRevealOptions } from './typography';
@@ -0,0 +1,17 @@
1
+ import { MotionContext } from '../core/context';
2
+ import { Cleanup, MotionTarget } from '../core/types';
3
+ export type FollowStateOptions = {
4
+ readonly context?: MotionContext;
5
+ /** The attribute that holds the real state, e.g. a menu's `data-state` or `aria-expanded`. */
6
+ readonly attribute?: string;
7
+ /** Where the element should be in each state. Unlisted states are left alone. */
8
+ readonly states: Readonly<Record<string, gsap.TweenVars>>;
9
+ readonly duration?: number;
10
+ };
11
+ /**
12
+ * Animates an element to match the state its attribute reports, and nothing
13
+ * else: the component owns the state, this only shows it. So a menu can never
14
+ * look open while it is closed for assistive technology. Reduced motion
15
+ * applies each state instantly.
16
+ */
17
+ export declare const followState: (target: MotionTarget, options: FollowStateOptions) => Cleanup;
@@ -0,0 +1,26 @@
1
+ import { MotionContext } from '../core/context';
2
+ import { MotionOutcome, MotionTarget } from '../core/types';
3
+ export type TransitionBuild = (timeline: gsap.core.Timeline, container: Element) => void;
4
+ export type PageTransitionOptions = {
5
+ readonly context?: MotionContext;
6
+ readonly leave?: TransitionBuild;
7
+ readonly enter?: TransitionBuild;
8
+ /** Inside the entering container: where focus lands once it is in, e.g. `h1`. */
9
+ readonly focus?: MotionTarget;
10
+ };
11
+ export type PageTransition = {
12
+ /** Plays the exit. Resolves when done or interrupted, never hangs: a missing container resolves at once. */
13
+ readonly leave: (container: MotionTarget) => Promise<MotionOutcome>;
14
+ readonly enter: (container: MotionTarget) => Promise<MotionOutcome>;
15
+ /** Stops whatever is playing; its promise resolves `cancelled`. */
16
+ readonly cancel: () => void;
17
+ /** Cancels, and releases the context if this transition created it. */
18
+ readonly destroy: () => void;
19
+ };
20
+ /**
21
+ * Exit and entrance for route changes. Routers await `leave` before swapping
22
+ * pages and call `enter` after. Navigation is never held hostage: every call
23
+ * settles, starting a new one cancels the last, and reduced motion settles at
24
+ * once. After an entrance, focus moves into the new page.
25
+ */
26
+ export declare const createPageTransition: (options?: PageTransitionOptions) => PageTransition;
@@ -0,0 +1,20 @@
1
+ import { MotionContext } from '../core/context';
2
+ import { Cleanup, DistanceName, EntranceName, MotionTarget } from '../core/types';
3
+ import { SplitBy } from '../gsap/split-text';
4
+ export type TextRevealOptions = {
5
+ readonly context?: MotionContext;
6
+ readonly by?: SplitBy;
7
+ /** Ignored for `lines`, which rise from behind their own clipping edge. */
8
+ readonly effect?: EntranceName;
9
+ readonly distance?: DistanceName;
10
+ /** Seconds between pieces; keep it short so the text reads as one entrance. */
11
+ readonly stagger?: number;
12
+ /** Start when the text scrolls into view. */
13
+ readonly onScroll?: boolean;
14
+ };
15
+ /**
16
+ * Reveals a heading or paragraph word by word, line by line, or letter by
17
+ * letter. Under reduced motion the text is never split. Line reveals rebuild
18
+ * when wrapping changes, picking up where they were.
19
+ */
20
+ export declare const revealText: (target: MotionTarget, options?: TextRevealOptions) => Cleanup;
@@ -0,0 +1,138 @@
1
+ import { n as e } from "./targets-x7JiHhWI.js";
2
+ import { a as t, i as n, s as r, t as i } from "./dom-CK6FnKcV.js";
3
+ import { n as a, r as o, t as s } from "./split-text-Cnl9gkUu.js";
4
+ import { t as c } from "./focus-management-DTJ2C7Pn.js";
5
+ import { a as l, i as u, n as d, o as f, r as p, t as m } from "./timeline-Cg3eq5ft.js";
6
+ import { duration as h, ease as g } from "@lovince/tokens";
7
+ import { gsap as _ } from "gsap";
8
+ //#region src/presets/entrance.ts
9
+ var v = (t, n = {}) => m((r, i) => {
10
+ let a = n.distance ?? "md", o = i.select(t.label), s = i.select(t.heading), c = i.select(t.body), l = i.select(t.actions).flatMap((t) => {
11
+ let n = e(t.children);
12
+ return n.length > 0 ? n : [t];
13
+ }), u = i.select(t.media), f = `-=${i.config.duration * .6}`;
14
+ o.length > 0 && d(r, o, "fade-up", { distance: "sm" }), s.length > 0 && d(r, s, "fade-up", {
15
+ distance: a,
16
+ position: o.length > 0 ? f : 0
17
+ }), c.length > 0 && d(r, c, "fade-up", {
18
+ distance: "sm",
19
+ position: f
20
+ }), l.length > 0 && d(r, l, "fade-up", {
21
+ distance: "sm",
22
+ vars: { stagger: i.config.stagger },
23
+ position: f
24
+ }), u.length > 0 && d(r, u, "scale", { position: f });
25
+ }, { context: n.context }), y = (e, n = {}) => t(n.context, (t) => {
26
+ let r = t.select(e);
27
+ if (r.length === 0 || t.reduced) return;
28
+ n.onScroll !== !1 && f();
29
+ let i = _.context(() => {
30
+ for (let e of r) {
31
+ let r = e.querySelector("img, picture, video") ?? e, i = _.timeline({
32
+ defaults: {
33
+ duration: t.config.duration * 1.4,
34
+ ease: t.config.ease,
35
+ lazy: !1
36
+ },
37
+ ...n.onScroll === !1 ? {} : { scrollTrigger: {
38
+ trigger: e,
39
+ start: "top 85%",
40
+ once: !0
41
+ } }
42
+ });
43
+ i.fromTo(e, { clipPath: a }, { clipPath: o }), r !== e && i.fromTo(r, { scale: n.scale ?? 1.12 }, { scale: 1 }, 0);
44
+ }
45
+ }, l(t));
46
+ return () => i.revert();
47
+ }), b = (e, n) => t(n.context, (t) => {
48
+ let r = t.select(e)[0];
49
+ if (r === void 0) return;
50
+ let i = n.attribute ?? "data-state", a = r.getAttribute("style"), o = (e) => {
51
+ let a = n.states[r.getAttribute(i) ?? ""];
52
+ a !== void 0 && (e || t.reduced ? _.set(r, {
53
+ ...a,
54
+ overwrite: !0
55
+ }) : _.to(r, {
56
+ duration: n.duration ?? t.config.duration,
57
+ ease: t.config.ease,
58
+ ...a,
59
+ overwrite: !0
60
+ }));
61
+ };
62
+ o(!0);
63
+ let s = new MutationObserver(() => o(!1));
64
+ return s.observe(r, {
65
+ attributes: !0,
66
+ attributeFilter: [i]
67
+ }), () => {
68
+ s.disconnect(), _.killTweensOf(r), _.set(r, { clearProps: "all" }), a === null ? r.removeAttribute("style") : r.setAttribute("style", a);
69
+ };
70
+ }), x = (e, t) => {
71
+ e.to(t, {
72
+ autoAlpha: 0,
73
+ y: -r("sm"),
74
+ duration: h.state,
75
+ ease: g.in.gsap
76
+ });
77
+ }, S = (e, t) => {
78
+ d(e, t, "fade-up", { distance: "sm" });
79
+ }, C = (t = {}) => {
80
+ let r = t.context ?? n(), a = null, o = (t, n, o) => {
81
+ a?.();
82
+ let s = i(), c = e(t)[0];
83
+ if (c === void 0 || r.destroyed()) return s.settle("finished"), s.promise;
84
+ let l = _.timeline({ defaults: {
85
+ duration: r.config.duration,
86
+ ease: r.config.ease,
87
+ lazy: !1
88
+ } });
89
+ n(l, c);
90
+ let u = !1, d = (e) => {
91
+ u || (u = !0, a = null, l.kill(), f(), e === "finished" && o?.(c), s.settle(e));
92
+ }, f = r.add(() => d("cancelled"));
93
+ return a = () => d("cancelled"), r.reduced() ? (l.progress(1), d("finished")) : l.then(() => d("finished")), s.promise;
94
+ };
95
+ return {
96
+ leave: (e) => o(e, t.leave ?? x),
97
+ enter: (n) => o(n, t.enter ?? S, (n) => {
98
+ t.focus !== void 0 && c(e(t.focus, n)[0]);
99
+ }),
100
+ cancel: () => a?.(),
101
+ destroy: () => {
102
+ a?.(), t.context === void 0 && r.destroy();
103
+ }
104
+ };
105
+ }, w = (e, t) => t === "chars" ? e.chars : t === "lines" && e.lines.length > 0 ? e.lines : e.words, T = (e, n = {}) => {
106
+ let r = 0;
107
+ return t(n.context, (t) => {
108
+ let i = t.select(e)[0];
109
+ if (!(i instanceof HTMLElement) || t.reduced) return;
110
+ let a = n.by ?? "words", o = n.stagger ?? (a === "chars" ? .02 : a === "lines" ? t.config.stagger : .04), c = null, d = (e) => {
111
+ c !== null && (r = c.progress()), c?.revert();
112
+ let s = w(e, a), l = a === "lines" && e.lines.length > 0, d = l ? { yPercent: 100 } : p(n.effect ?? "fade-up", n.distance ?? "sm"), f = l ? { yPercent: 0 } : u(n.effect ?? "fade-up");
113
+ c = _.fromTo(s, d, {
114
+ ...f,
115
+ duration: t.config.duration,
116
+ ease: t.config.ease,
117
+ stagger: o,
118
+ lazy: !1,
119
+ ...n.onScroll === !0 ? { scrollTrigger: {
120
+ trigger: i,
121
+ start: "top 85%",
122
+ once: !0
123
+ } } : {}
124
+ }), r > 0 && c.progress(r);
125
+ };
126
+ n.onScroll === !0 && f();
127
+ let m = _.context(() => void 0, l(t)), h = s(i, {
128
+ by: a,
129
+ mask: a === "lines",
130
+ onResplit: (e) => m.add(() => d(e))
131
+ });
132
+ return m.add(() => d(h)), () => {
133
+ c !== null && (r = c.progress()), m.revert(), h.revert();
134
+ };
135
+ });
136
+ };
137
+ //#endregion
138
+ export { C as createPageTransition, b as followState, v as heroEntrance, y as revealImage, T as revealText };
@@ -0,0 +1,39 @@
1
+ import { a as e, r as t } from "./dom-CK6FnKcV.js";
2
+ //#region src/css/reveal.ts
3
+ var n = "data-motion-reveal", r = "data-motion-state", i = (e, n) => {
4
+ let r = t(e);
5
+ n > 0 ? r?.setProperty("--motion-delay", `${n}s`) : r?.removeProperty("--motion-delay");
6
+ }, a = (t, a = {}) => {
7
+ let { effect: o = "fade-up", once: s = !0, threshold: c = .15, rootMargin: l = "0px 0px -10% 0px" } = a, u = /* @__PURE__ */ new WeakSet();
8
+ return e(a.context, (e) => {
9
+ let d = e.select(t);
10
+ if (d.length === 0) return;
11
+ let f = a.stagger ?? e.config.stagger, p = d.filter((e) => !e.hasAttribute(n));
12
+ for (let e of p) e.setAttribute(n, o);
13
+ let m = () => {
14
+ for (let e of d) e.removeAttribute(r), i(e, 0);
15
+ for (let e of p) e.removeAttribute(n);
16
+ };
17
+ if (e.reduced || typeof IntersectionObserver > "u") {
18
+ for (let e of d) e.setAttribute(r, "visible");
19
+ return m;
20
+ }
21
+ let h = new IntersectionObserver((e) => {
22
+ if (e.filter((e) => e.isIntersecting).map((e) => e.target).sort((e, t) => d.indexOf(e) - d.indexOf(t)).forEach((e, t) => {
23
+ i(e, t * f), e.setAttribute(r, "visible"), u.add(e), s && h.unobserve(e);
24
+ }), !s) for (let t of e) t.isIntersecting || (i(t.target, 0), t.target.setAttribute(r, "hidden"));
25
+ }, {
26
+ threshold: c,
27
+ rootMargin: l
28
+ });
29
+ for (let e of d) {
30
+ let t = s && u.has(e);
31
+ e.setAttribute(r, t ? "visible" : "hidden"), t || h.observe(e);
32
+ }
33
+ return () => {
34
+ h.disconnect(), m();
35
+ };
36
+ });
37
+ };
38
+ //#endregion
39
+ export { r as n, a as r, n as t };
@@ -0,0 +1,37 @@
1
+ import { default as Lenis, LenisOptions } from 'lenis';
2
+ import { Cleanup, MotionTarget } from '../core/types';
3
+ /** Drives Lenis's clock. Without one, Lenis runs its own requestAnimationFrame loop. */
4
+ export type SmoothScrollDriver = {
5
+ readonly start: (lenis: Lenis) => Cleanup;
6
+ };
7
+ export type SmoothScrollOptions = {
8
+ /** Passed to Lenis. `autoRaf` is decided here: on without a driver, off with one. */
9
+ readonly lenis?: Omit<LenisOptions, 'autoRaf'>;
10
+ /** Share one clock with another engine, e.g. `gsapScrollDriver()` from `@lovince/motion/gsap`. */
11
+ readonly driver?: SmoothScrollDriver;
12
+ /** Fall back to native scrolling for users who ask for reduced motion. On by default. */
13
+ readonly respectReducedMotion?: boolean;
14
+ };
15
+ export type ScrollToOptions = {
16
+ readonly offset?: number;
17
+ /** Jump without animating. Always true under reduced motion. */
18
+ readonly immediate?: boolean;
19
+ };
20
+ export type SmoothScroll = {
21
+ /** The Lenis instance, or null while scrolling is native. */
22
+ readonly lenis: () => Lenis | null;
23
+ readonly scrollTo: (target: number | MotionTarget, options?: ScrollToOptions) => void;
24
+ /** Pause and resume smooth scrolling, e.g. behind a modal. */
25
+ readonly stop: () => void;
26
+ readonly start: () => void;
27
+ /** Releases this handle; the instance is torn down when its last handle is. */
28
+ readonly destroy: Cleanup;
29
+ };
30
+ /**
31
+ * Smooth scrolling as a single, owned service. Calling it again for the same
32
+ * scroller shares the existing instance (options from the first call win);
33
+ * each handle must be destroyed, and the last one tears Lenis down. Under
34
+ * reduced motion there is no Lenis at all and the browser scrolls natively,
35
+ * switching live if the preference changes.
36
+ */
37
+ export declare const createSmoothScroll: (options?: SmoothScrollOptions) => SmoothScroll;
@@ -0,0 +1,8 @@
1
+ import { SmoothScrollDriver } from './create-smooth-scroll';
2
+ /**
3
+ * Runs Lenis on GSAP's ticker, so smooth scrolling and every tween share one
4
+ * frame loop, and keeps ScrollTrigger in step with Lenis's scroll position.
5
+ * Lag smoothing is off while driving, or a dropped frame would make scroll
6
+ * and scroll-linked animation disagree.
7
+ */
8
+ export declare const gsapScrollDriver: () => SmoothScrollDriver;
@@ -0,0 +1 @@
1
+ export { createSmoothScroll, type ScrollToOptions, type SmoothScroll, type SmoothScrollDriver, type SmoothScrollOptions, } from './create-smooth-scroll';