@wzrhine/effects 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,38 @@
1
+ # Changelog · @wzrhine/effects
2
+
3
+ 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。本包为 **Experimental**,0.x 期间 API 可能调整。
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.2.0] - 2026-10-14
8
+
9
+ ### Added
10
+ - **B9 · 展示动效接入补齐**:`OrbitalDiagram` 新增 `size`、节点 `tone`(accent / info / warning / success / neutral)与 `selected` / `selectedLabel`(选中态连同本地化读屏后缀);`ScanSurface` 新增 `tone`、`duration`(毫秒或 `fast` / `base` / `panel` / `showcase` 预设)与 `containerType` / `overflow`(容器副作用开关);`ArchiveReveal` 支持原生属性与 `ref` 透传、视口暂停与受控 `paused`。
11
+
12
+ ### Changed
13
+ - peer `motion` 为 `^14.0.0`,本仓开发与验证版本为 14.1.0;与其余四个包同一版本线。
14
+ - 迁移:升级到 motion 14.1.0,并确保应用内 React 与 motion 各只有一份。
15
+
16
+ ## [0.1.0] - 2026-10-12
17
+
18
+ 首次发布。
19
+
20
+ ### Added
21
+ - **M4(ADR-0004)**:`ArchiveReveal`(确定性解密、可中断、减动直接切换)、`ScanSurface`(单次扫描,减动时为静态描边)、`OrbitalDiagram`(仪器风格轨道,可选旋转)、`MotionPlayer`(Default / Slow ×0.25 / Reduced / Interrupt 四通道),以及 `EffectClock` / `useEffectTiming`。
22
+ - 页面隐藏、离开视口、完成或卸载时停止循环;不使用 Canvas 或 WebGL。
23
+ - **M5**:通过 `dist/` 发布 ESM 与 `.d.ts`;`./styles.css` 导出。
24
+ - **M10(ADR-0007)**:`OrbitalDiagram` 增加 `data-lifecycle`(`idle` / `running` / `paused` / `stopped`)与 `data-paused`(`reduced` / `hidden` / `offscreen`)。`MotionPlayer` 的各通道设置 `MotionSpeed`,`@wzrhine/react` 的 MotionPresence / RhineMotionGroup / `useRhineMotion` 也随 Slow ×0.25 放慢。
25
+
26
+ ### Changed
27
+ - **破坏性(声明层面,M9.5,ADR-0008)**:peer `react` / `react-dom` 收窄为 `^19.0.0`。
28
+ - **M9.5**:含 React 运行时的模块在产物首行带 `'use client'`;`scramble` 等纯函数不带。
29
+ - **M10**:`usePageVisible` / `useInViewport` 改为重新导出 `@wzrhine/react` 的实现,effects 与组件共用一套可见性来源;导入路径不变。
30
+ - **M10**:所有循环改用共享生命周期。`OrbitalDiagram` 暂停(页面隐藏、离开视口)时保持当前角度、恢复时从该角度接续,而不是回到 0°;`ScanSurface` 在页面隐藏时立即结束本次扫描,隐藏期间请求的扫描不运行;`ArchiveReveal` 隐藏时冻结在当前进度。
31
+
32
+ ### Fixed
33
+ - **M10**:`MotionPlayer` 的 Default / Slow / Interrupt 通道曾用 `MotionConfig reducedMotion="user"` 包裹,会把外层 `RhineProvider motion="reduce"` 或 `MotionConfig reducedMotion="always"` 重置为跟随系统,导致用户选择减少动态效果时这些通道仍有位移。现在只有 Reduced 通道强制减动,其他通道继承外层偏好。
34
+
35
+ ### Requirements
36
+ - peer:`@wzrhine/react`(同版本线)、`react` / `react-dom` ^19(M9.5 起)、`motion` ^12。
37
+
38
+ M6 未改动本包。升级步骤见 [UPGRADING.md](https://github.com/BaiZhi967/WzRhineLab-UI/blob/main/docs/development/UPGRADING.md)。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WhiteZhi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,23 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @wzrhine/effects
2
+
3
+ WzRhineLab UI 的**可选**展示动效:解密、扫描、轨道,以及用于对照的四通道动效播放器。`@wzrhine/react` 不依赖本包。
4
+
5
+ > 实验性(0.x 期间 API 可能调整);MIT 许可证。
6
+
7
+ ```sh
8
+ pnpm add @wzrhine/effects @wzrhine/react @wzrhine/tokens react react-dom motion
9
+ ```
10
+
11
+ ```tsx
12
+ import '@wzrhine/effects/styles.css';
13
+ import { ArchiveReveal, MotionPlayer } from '@wzrhine/effects';
14
+
15
+ <MotionPlayer title="ArchiveReveal" demo={({ active }) => <ArchiveReveal text="ARC-1024" revealed={active} />} />;
16
+ ```
17
+
18
+ 规则:只在用户触发时播放;内容先于动画可读;减动时有等价表现;页面隐藏时暂停。
19
+
20
+ - 使用说明:[docs/components/effects.md](https://github.com/BaiZhi967/WzRhineLab-UI/blob/main/docs/components/effects.md)
21
+ - API 参考(自动生成):[docs/api/effects.md](https://github.com/BaiZhi967/WzRhineLab-UI/blob/main/docs/api/effects.md)
22
+ - 变更记录:[CHANGELOG.md](./CHANGELOG.md)
23
+ - 许可证:[MIT](./LICENSE)
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Decrypt-style reveal for short labels (IDs, titles). Showcase effect only (MOTION.md):
4
+ * - The real text is always in the accessibility tree; the scramble is aria-hidden decoration.
5
+ * - Toggling `revealed` mid-flight reverses from the current progress, never restarting.
6
+ * - Reduced motion (OS, provider, or EffectClock) swaps instantly with no glyph cycling.
7
+ * - The loop pauses while the page is hidden (shared `usePageVisible`, ADR-0007), resumes from the
8
+ * current progress, and stops once settled or unmounted. It is short and one-shot, so it does not
9
+ * track the viewport.
10
+ * Never use it to delay business data: pass content that is already loaded.
11
+ */
12
+ export type ArchiveRevealProps = Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> & {
13
+ text: string;
14
+ /** Target state. Changing it animates from the current position. */
15
+ revealed: boolean;
16
+ /** Full reveal duration at speed 1 (ms). */
17
+ duration?: number;
18
+ /** Called once the effect settles at the requested state. */
19
+ onSettled?: (revealed: boolean) => void;
20
+ /** Accessible text while concealed, e.g. "受限内容". Defaults to the text itself. */
21
+ concealedLabel?: string;
22
+ paused?: boolean;
23
+ };
24
+ export declare const ArchiveReveal: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> & {
25
+ text: string;
26
+ /** Target state. Changing it animates from the current position. */
27
+ revealed: boolean;
28
+ /** Full reveal duration at speed 1 (ms). */
29
+ duration?: number;
30
+ /** Called once the effect settles at the requested state. */
31
+ onSettled?: (revealed: boolean) => void;
32
+ /** Accessible text while concealed, e.g. "受限内容". Defaults to the text itself. */
33
+ concealedLabel?: string;
34
+ paused?: boolean;
35
+ } & React.RefAttributes<HTMLSpanElement>>;
36
+ //# sourceMappingURL=ArchiveReveal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ArchiveReveal.d.ts","sourceRoot":"","sources":["../src/ArchiveReveal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;;;;;;GASG;AAEH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC,GAAG;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAIF,eAAO,MAAM,aAAa;UAdlB,MAAM;IACZ,oEAAoE;cAC1D,OAAO;IACjB,4CAA4C;eACjC,MAAM;IACjB,6DAA6D;gBACjD,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI;IACvC,iFAAiF;qBAChE,MAAM;aACd,OAAO;yCAiGhB,CAAC"}
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { useEffectTiming } from './clock.js';
5
+ import { scrambleCells, stepToward } from './scramble.js';
6
+ import { usePageVisible } from './usePageVisible.js';
7
+ const FRAME_MS = 48;
8
+ export const ArchiveReveal = React.forwardRef(function ArchiveReveal({ text, revealed, duration = 640, onSettled, concealedLabel, paused = false, className, ...rest }, ref) {
9
+ const timing = useEffectTiming();
10
+ const visible = usePageVisible();
11
+ const viewportRef = React.useRef(null);
12
+ const [inViewport, setInViewport] = React.useState(true);
13
+ React.useEffect(() => {
14
+ const element = viewportRef.current;
15
+ if (!element)
16
+ return undefined;
17
+ const onVisibility = () => { if (!document.hidden)
18
+ setInViewport(true); };
19
+ document.addEventListener('visibilitychange', onVisibility);
20
+ if (typeof IntersectionObserver === 'undefined')
21
+ return () => document.removeEventListener('visibilitychange', onVisibility);
22
+ const observer = new IntersectionObserver(([entry]) => setInViewport(entry?.isIntersecting ?? false), { threshold: 0 });
23
+ observer.observe(element);
24
+ return () => { observer.disconnect(); document.removeEventListener('visibilitychange', onVisibility); };
25
+ }, []);
26
+ const effectiveVisible = visible && inViewport && !paused;
27
+ const target = revealed ? 1 : 0;
28
+ const [progress, setProgress] = React.useState(target);
29
+ const [frame, setFrame] = React.useState(0);
30
+ const progressRef = React.useRef(progress);
31
+ progressRef.current = progress;
32
+ const settledRef = React.useRef(onSettled);
33
+ settledRef.current = onSettled;
34
+ React.useEffect(() => {
35
+ if (timing.reduced) {
36
+ setProgress(target);
37
+ if (progressRef.current !== target)
38
+ settledRef.current?.(revealed);
39
+ return;
40
+ }
41
+ if (progressRef.current === target)
42
+ return;
43
+ if (!effectiveVisible) {
44
+ // Paused: settle nothing now; the effect re-runs (and resumes from the current progress) on visibility.
45
+ return;
46
+ }
47
+ const total = timing.scale(duration);
48
+ // Baseline comes from the first frame's own timestamp: rAF time and performance.now()
49
+ // do not share an origin in every environment.
50
+ let last = null;
51
+ let lastFrame = 0;
52
+ let raf = 0;
53
+ const tick = (now) => {
54
+ if (last === null) {
55
+ last = now;
56
+ lastFrame = now;
57
+ raf = requestAnimationFrame(tick);
58
+ return;
59
+ }
60
+ const dt = Math.max(0, now - last);
61
+ last = now;
62
+ const next = stepToward(progressRef.current, target, dt / total);
63
+ progressRef.current = next;
64
+ setProgress(next);
65
+ if (now - lastFrame >= FRAME_MS / timing.speed) {
66
+ lastFrame = now;
67
+ setFrame((f) => f + 1);
68
+ }
69
+ if (next === target) {
70
+ settledRef.current?.(revealed);
71
+ return;
72
+ }
73
+ raf = requestAnimationFrame(tick);
74
+ };
75
+ raf = requestAnimationFrame(tick);
76
+ return () => cancelAnimationFrame(raf);
77
+ }, [target, revealed, duration, timing, effectiveVisible]);
78
+ const cells = scrambleCells(text, progress, frame);
79
+ const settled = progress === target;
80
+ const label = revealed ? text : (concealedLabel ?? text);
81
+ return (_jsxs("span", { ...rest, ref: (node) => { viewportRef.current = node; if (typeof ref === 'function')
82
+ ref(node);
83
+ else if (ref)
84
+ ref.current = node; }, className: ['rl-reveal', className].filter(Boolean).join(' '), "data-slot": "archive-reveal", "data-state": settled ? (revealed ? 'revealed' : 'concealed') : 'animating', "data-reduced": timing.reduced || undefined, "data-paused": paused || !effectiveVisible || undefined, "aria-label": rest['aria-label'] ?? label, children: [_jsx("span", { className: "rl-effect-sr", children: label }), _jsx("span", { className: "rl-reveal__glyphs", "aria-hidden": "true", children: cells.map((cell, i) => (_jsx("span", { className: "rl-reveal__cell", "data-resolved": cell.resolved || undefined, children: cell.char }, i))) })] }));
85
+ });
86
+ //# sourceMappingURL=ArchiveReveal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ArchiveReveal.js","sourceRoot":"","sources":["../src/ArchiveReveal.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Side-by-side motion comparison (MOTION.md "Showcase 每类动画提供 Default / Slow x0.25 /
4
+ * Reduced / Interrupt 四组状态"). Renders the same demo in four lanes:
5
+ * - Default: speed 1, inherits the surrounding motion preference (never re-enables motion).
6
+ * - Slow ×0.25: four times slower, for reviewing easing and choreography.
7
+ * - Reduced: forced reduced motion (MotionConfig "always" + data-rl-motion), the equivalent fallback.
8
+ * - Interrupt: speed 1; the player re-triggers the demo while it is still running, proving the effect
9
+ * retargets from its current state instead of queueing or restarting.
10
+ * One shared play control drives every lane, so the lanes stay in step. Lanes also set `MotionSpeed`, so
11
+ * @wzrhine/react motion (MotionPresence, RhineMotionGroup, useRhineMotion) slows down with effects.
12
+ */
13
+ export type MotionLane = 'default' | 'slow' | 'reduced' | 'interrupt';
14
+ export interface MotionDemoProps {
15
+ /** Monotonic play counter; each increment is one "play". */
16
+ play: number;
17
+ /** Target state toggled by each play (e.g. revealed / concealed). */
18
+ active: boolean;
19
+ lane: MotionLane;
20
+ }
21
+ export type MotionPlayerProps = {
22
+ /** Visible title of the comparison; also names the region. */
23
+ title: React.ReactNode;
24
+ /** One-line timing spec shown under the title, e.g. "scene 420ms · ease-in-out". */
25
+ spec?: React.ReactNode;
26
+ /** Renders one lane. Must be deterministic for a given (play, active). */
27
+ demo: (props: MotionDemoProps) => React.ReactNode;
28
+ /** Lanes to show; defaults to all four. */
29
+ lanes?: readonly MotionLane[];
30
+ /** How long after each play (ms) the Interrupt lane reverses; keep it shorter than the demo's duration. */
31
+ interruptAfterMs?: number;
32
+ /** Initial active state. */
33
+ defaultActive?: boolean;
34
+ className?: string;
35
+ };
36
+ export declare function MotionPlayer({ title, spec, demo, lanes, interruptAfterMs, defaultActive, className, }: MotionPlayerProps): React.JSX.Element;
37
+ //# sourceMappingURL=MotionPlayer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MotionPlayer.d.ts","sourceRoot":"","sources":["../src/MotionPlayer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,oFAAoF;IACpF,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0EAA0E;IAC1E,IAAI,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,KAAK,CAAC,SAAS,CAAC;IAClD,2CAA2C;IAC3C,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,2GAA2G;IAC3G,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4BAA4B;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAoBF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,KAAgB,EAChB,gBAAsB,EACtB,aAAqB,EACrB,SAAS,GACV,EAAE,iBAAiB,qBAwEnB"}
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { MotionConfig } from 'motion/react';
5
+ import { MotionSpeed } from '@wzrhine/react';
6
+ import { EffectClock } from './clock.js';
7
+ const laneMeta = {
8
+ default: { label: 'Default', hint: '×1 · 跟随系统偏好', speed: 1, reduced: false },
9
+ slow: { label: 'Slow ×0.25', hint: '四倍慢放,检查缓动', speed: 0.25, reduced: false },
10
+ reduced: { label: 'Reduced', hint: '强制减少动态效果', speed: 1, reduced: true },
11
+ interrupt: { label: 'Interrupt', hint: '播放中途反向触发', speed: 1, reduced: false },
12
+ };
13
+ const allLanes = ['default', 'slow', 'reduced', 'interrupt'];
14
+ /**
15
+ * The Reduced lane forces reduced motion. The other lanes inherit the surrounding preference
16
+ * (OS or RhineProvider) instead of resetting it to "user": a reader who asked for reduced motion
17
+ * must not get motion back inside a comparison.
18
+ */
19
+ function LaneMotion({ reduced, children }) {
20
+ return reduced ? _jsx(MotionConfig, { reducedMotion: "always", children: children }) : _jsx(_Fragment, { children: children });
21
+ }
22
+ export function MotionPlayer({ title, spec, demo, lanes = allLanes, interruptAfterMs = 180, defaultActive = false, className, }) {
23
+ const titleId = React.useId();
24
+ const [play, setPlay] = React.useState(0);
25
+ const [active, setActive] = React.useState(defaultActive);
26
+ // The interrupt lane gets its own state: it follows the shared play, then reverses shortly after.
27
+ const [interrupt, setInterrupt] = React.useState({ play: 0, active: defaultActive });
28
+ const trigger = () => {
29
+ const next = !active;
30
+ setActive(next);
31
+ setPlay((p) => p + 1);
32
+ setInterrupt((s) => ({ play: s.play + 1, active: next }));
33
+ };
34
+ React.useEffect(() => {
35
+ if (play === 0)
36
+ return;
37
+ const t = window.setTimeout(() => setInterrupt((s) => ({ play: s.play + 1, active: !s.active })), interruptAfterMs);
38
+ return () => window.clearTimeout(t);
39
+ }, [play, interruptAfterMs]);
40
+ const reset = () => {
41
+ setActive(defaultActive);
42
+ setPlay(0);
43
+ setInterrupt({ play: 0, active: defaultActive });
44
+ };
45
+ return (_jsxs("section", { className: ['rl-player', className].filter(Boolean).join(' '), "aria-labelledby": titleId, "data-slot": "motion-player", children: [_jsxs("header", { className: "rl-player__head", children: [_jsxs("div", { className: "rl-player__titles", children: [_jsx("h3", { id: titleId, className: "rl-player__title", children: title }), spec != null && _jsx("p", { className: "rl-player__spec", children: spec })] }), _jsxs("div", { className: "rl-player__controls", children: [_jsx("button", { type: "button", className: "rl-player__button", "data-primary": true, onClick: trigger, "aria-describedby": `${titleId}-state`, children: active ? '反向播放' : '播放' }), _jsx("button", { type: "button", className: "rl-player__button", onClick: reset, disabled: play === 0, children: "\u91CD\u7F6E" }), _jsxs("span", { id: `${titleId}-state`, className: "rl-player__state", "aria-live": "polite", children: ["\u7B2C ", play, " \u6B21 \u00B7 \u76EE\u6807\uFF1A", active ? '激活' : '初始'] })] })] }), _jsx("ol", { className: "rl-player__lanes", children: lanes.map((lane) => {
46
+ const meta = laneMeta[lane];
47
+ const laneProps = lane === 'interrupt' ? { play: interrupt.play, active: interrupt.active, lane } : { play, active, lane };
48
+ return (_jsxs("li", { className: "rl-player__lane", "data-lane": lane, children: [_jsxs("p", { className: "rl-player__lane-label", children: [_jsx("span", { children: meta.label }), _jsx("span", { className: "rl-player__lane-hint", children: meta.hint })] }), _jsx(LaneMotion, { reduced: meta.reduced, children: _jsx(MotionSpeed, { speed: meta.speed, children: _jsx(EffectClock, { speed: meta.speed, reduced: meta.reduced, children: _jsx("div", { className: "rl-player__stage", "data-rl-motion": meta.reduced ? 'reduce' : undefined, children: demo(laneProps) }) }) }) })] }, lane));
49
+ }) })] }));
50
+ }
51
+ //# sourceMappingURL=MotionPlayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MotionPlayer.js","sourceRoot":"","sources":["../src/MotionPlayer.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Instrument-style orbital diagram (DESIGN.md "Instrument Accent"): concentric rings with labelled
4
+ * nodes. Decorative structure around real labels, not a data visualisation:
5
+ * - Labels are real text in an accessible list; the SVG is aria-hidden.
6
+ * - Rotation is opt-in (`animate`), stops when off-screen, on hidden tabs and under reduced motion
7
+ * (shared lifecycle, ADR-0007); `data-lifecycle` / `data-paused` expose the current state.
8
+ * - Positions are fixed by `angle` props; nothing is random.
9
+ */
10
+ export interface OrbitNode {
11
+ id: string;
12
+ label: string;
13
+ /** Ring index, 0 = innermost. */
14
+ ring: number;
15
+ /** Position on the ring in degrees, 0 = top, clockwise. */
16
+ angle: number;
17
+ /** Highlight one node (e.g. the current subject). */
18
+ active?: boolean;
19
+ selected?: boolean;
20
+ tone?: 'accent' | 'info' | 'warning' | 'success' | 'neutral';
21
+ }
22
+ export type OrbitalDiagramProps = {
23
+ nodes: readonly OrbitNode[];
24
+ size?: number;
25
+ rings?: number;
26
+ /** Centre caption, e.g. a record ID. */
27
+ center?: string;
28
+ /** Slow ambient rotation. Off by default; disabled by reduced motion. */
29
+ animate?: boolean;
30
+ /** Seconds per revolution of the outer ring at speed 1. */
31
+ period?: number;
32
+ /** Accessible name of the node list. */
33
+ label: string;
34
+ /** Localised suffix announced for a selected node. */
35
+ selectedLabel?: string;
36
+ className?: string;
37
+ };
38
+ export declare function OrbitalDiagram({ nodes, rings, size, center, animate, period, label, selectedLabel, className }: OrbitalDiagramProps): React.JSX.Element;
39
+ //# sourceMappingURL=OrbitalDiagram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrbitalDiagram.d.ts","sourceRoot":"","sources":["../src/OrbitalDiagram.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;CAC9D;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAIF,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,KAAS,EAAE,IAAW,EAAE,MAAM,EAAE,OAAe,EAAE,MAAW,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,mBAAmB,qBA4E3J"}
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { animate as animateValue, motion, useMotionValue } from 'motion/react';
5
+ import { useMotionLifecycle, useRhineLocale } from '@wzrhine/react';
6
+ import { useEffectTiming } from './clock.js';
7
+ const SIZE = 240;
8
+ export function OrbitalDiagram({ nodes, rings = 3, size = SIZE, center, animate = false, period = 60, label, selectedLabel, className }) {
9
+ const { locale } = useRhineLocale();
10
+ const selectedText = selectedLabel ?? (locale.toLowerCase().startsWith('en') ? 'selected' : '当前');
11
+ const timing = useEffectTiming();
12
+ const ref = React.useRef(null);
13
+ const lifecycle = useMotionLifecycle({ active: animate, viewportRef: ref, reduced: timing.reduced });
14
+ const spinning = lifecycle.running;
15
+ const rotate = useMotionValue(0);
16
+ const revolution = period / timing.speed;
17
+ // Running: continue from the current angle. Paused (hidden, off-screen, reduced): hold the angle.
18
+ // Stopped or never started: back to the rest position.
19
+ React.useEffect(() => {
20
+ if (!spinning) {
21
+ if (lifecycle.state !== 'paused' || timing.reduced)
22
+ rotate.jump(0);
23
+ return;
24
+ }
25
+ const from = rotate.get() % 360;
26
+ const controls = animateValue(rotate, [from, from + 360], { duration: revolution, ease: 'linear', repeat: Infinity });
27
+ return () => controls.stop();
28
+ }, [spinning, revolution, lifecycle.state, timing.reduced, rotate]);
29
+ const viewSize = Math.max(96, size);
30
+ const centerPoint = viewSize / 2;
31
+ const radius = (ring) => 36 * (viewSize / SIZE) + (ring * (centerPoint - 48 * (viewSize / SIZE))) / Math.max(1, rings - 1);
32
+ return (_jsx("div", { className: "rl-orbit-wrap", children: _jsxs("div", { ref: ref, className: ['rl-orbit', className].filter(Boolean).join(' '), "data-slot": "orbital-diagram", "data-size": size, "data-spinning": spinning || undefined, "data-lifecycle": lifecycle.state, "data-paused": lifecycle.pauseReason ?? undefined, children: [_jsxs("svg", { className: "rl-orbit__svg", viewBox: `0 0 ${viewSize} ${viewSize}`, "aria-hidden": "true", focusable: "false", children: [_jsx("g", { className: "rl-orbit__ticks", children: Array.from({ length: 24 }, (_, i) => {
33
+ const a = (i * 15 * Math.PI) / 180;
34
+ const r1 = centerPoint - 6;
35
+ const r2 = i % 6 === 0 ? centerPoint - 14 : centerPoint - 10;
36
+ return _jsx("line", { x1: centerPoint + r1 * Math.sin(a), y1: centerPoint - r1 * Math.cos(a), x2: centerPoint + r2 * Math.sin(a), y2: centerPoint - r2 * Math.cos(a) }, i);
37
+ }) }), _jsxs(motion.g, { className: "rl-orbit__rotor", style: { transformOrigin: `${centerPoint}px ${centerPoint}px`, rotate }, children: [Array.from({ length: rings }, (_, ring) => (_jsx("circle", { className: "rl-orbit__ring", cx: centerPoint, cy: centerPoint, r: radius(ring), "data-ring": ring }, ring))), nodes.map((node) => {
38
+ const a = (node.angle * Math.PI) / 180;
39
+ const r = radius(Math.min(node.ring, rings - 1));
40
+ return (_jsx("circle", { className: "rl-orbit__node", "data-active": node.active || undefined, "data-selected": node.selected || undefined, "data-tone": node.tone ?? undefined, cx: centerPoint + r * Math.sin(a), cy: centerPoint - r * Math.cos(a), r: node.active ? 5 : 3.5 }, node.id));
41
+ })] }), _jsx("circle", { className: "rl-orbit__core", cx: centerPoint, cy: centerPoint, r: 14 * (viewSize / SIZE) }), _jsx("path", { className: "rl-orbit__crosshair", d: `M${centerPoint - 22 * (viewSize / SIZE)} ${centerPoint}h${8 * (viewSize / SIZE)}M${centerPoint + 14 * (viewSize / SIZE)} ${centerPoint}h${8 * (viewSize / SIZE)}M${centerPoint} ${centerPoint - 22 * (viewSize / SIZE)}v${8 * (viewSize / SIZE)}M${centerPoint} ${centerPoint + 14 * (viewSize / SIZE)}v${8 * (viewSize / SIZE)}` })] }), center && _jsx("span", { className: "rl-orbit__center", children: center }), _jsx("ul", { className: "rl-orbit__legend", "aria-label": label, children: nodes.map((node) => (_jsxs("li", { "data-active": node.active || undefined, "data-selected": node.selected || undefined, children: [_jsxs("span", { className: "rl-orbit__ring-index", "aria-hidden": "true", children: ["R", node.ring + 1] }), node.label, (node.active || node.selected) && _jsxs("span", { className: "rl-effect-sr", children: ["(", selectedText, ")"] })] }, node.id))) })] }) }));
42
+ }
43
+ //# sourceMappingURL=OrbitalDiagram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrbitalDiagram.js","sourceRoot":"","sources":["../src/OrbitalDiagram.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Single-pass scan line over a surface, triggered by the user (e.g. "re-scan"). Showcase effect:
4
+ * - One sweep per `scanKey` change, never an idle infinite loop (STYLE.md forbids default infinite scans).
5
+ * - Children render immediately and stay interactive; the sweep is a pointer-events:none overlay.
6
+ * - Reduced motion: no moving line; a brief static outline marks the "scanned" moment instead.
7
+ * - Stop conditions (ADR-0007): skipped while the page is hidden, cut short if the page hides mid-sweep,
8
+ * cleared on unmount. One-shot, so it does not track the viewport.
9
+ */
10
+ export type ScanSurfaceProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> & {
11
+ children: React.ReactNode;
12
+ /** Change this value to run one sweep (a counter or timestamp). */
13
+ scanKey?: number | string;
14
+ /** Sweep duration at speed 1 (ms). */
15
+ duration?: number | 'fast' | 'base' | 'panel' | 'showcase';
16
+ tone?: 'accent' | 'info' | 'warning' | 'success' | 'neutral';
17
+ overflow?: 'hidden' | 'visible';
18
+ containerType?: boolean;
19
+ onScanEnd?: () => void;
20
+ className?: string;
21
+ };
22
+ export declare function ScanSurface({ children, scanKey, duration, onScanEnd, tone, overflow, containerType, className, ...rest }: ScanSurfaceProps): React.JSX.Element;
23
+ //# sourceMappingURL=ScanSurface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScanSurface.d.ts","sourceRoot":"","sources":["../src/ScanSurface.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B;;;;;;;GAOG;AAEH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACtF,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC3D,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAIF,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAqB,EAAE,SAAS,EAAE,IAAe,EAAE,QAAmB,EAAE,aAAoB,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,gBAAgB,qBAsDpL"}
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { useEffectTiming } from './clock.js';
5
+ import { usePageVisible } from './usePageVisible.js';
6
+ const DURATIONS = { fast: 120, base: 200, panel: 280, showcase: 700 };
7
+ export function ScanSurface({ children, scanKey, duration = 'showcase', onScanEnd, tone = 'accent', overflow = 'hidden', containerType = true, className, ...rest }) {
8
+ const durationMs = typeof duration === 'number' ? duration : DURATIONS[duration];
9
+ const timing = useEffectTiming();
10
+ const visible = usePageVisible();
11
+ const [running, setRunning] = React.useState(false);
12
+ const lastKey = React.useRef(scanKey);
13
+ const timer = React.useRef(undefined);
14
+ const latest = React.useRef({ visible, reduced: timing.reduced, ms: timing.scale(durationMs), onScanEnd });
15
+ latest.current = { visible, reduced: timing.reduced, ms: timing.scale(durationMs), onScanEnd };
16
+ // Only a change of scanKey starts a sweep; other prop changes must not retrigger it.
17
+ React.useEffect(() => {
18
+ if (Object.is(lastKey.current, scanKey))
19
+ return;
20
+ lastKey.current = scanKey;
21
+ const { visible: isVisible, reduced, ms } = latest.current;
22
+ if (scanKey === undefined || !isVisible)
23
+ return;
24
+ setRunning(true);
25
+ const t = window.setTimeout(() => {
26
+ setRunning(false);
27
+ latest.current.onScanEnd?.();
28
+ }, reduced ? 160 : ms);
29
+ timer.current = t;
30
+ return () => window.clearTimeout(t);
31
+ }, [scanKey]);
32
+ // Hidden mid-sweep: end it now instead of finishing off-screen.
33
+ React.useEffect(() => {
34
+ if (visible || !running)
35
+ return;
36
+ window.clearTimeout(timer.current);
37
+ setRunning(false);
38
+ latest.current.onScanEnd?.();
39
+ }, [visible, running]);
40
+ const style = { '--rl-scan-duration': `${timing.scale(durationMs)}ms` };
41
+ return (_jsxs("div", { ...rest, className: ['rl-scan', className].filter(Boolean).join(' '), "data-slot": "scan-surface", "data-scanning": running || undefined, "data-reduced": timing.reduced || undefined, "data-tone": tone, "data-container-type": containerType ? 'inline-size' : 'none', "data-overflow": overflow, style: style, children: [children, running && _jsx("span", { className: "rl-scan__line", "aria-hidden": "true" }, String(scanKey))] }));
42
+ }
43
+ //# sourceMappingURL=ScanSurface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScanSurface.js","sourceRoot":"","sources":["../src/ScanSurface.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ export interface EffectClockProps {
3
+ speed?: number;
4
+ reduced?: boolean;
5
+ children: React.ReactNode;
6
+ }
7
+ export declare function EffectClock({ speed, reduced, children }: EffectClockProps): React.JSX.Element;
8
+ export interface EffectTiming {
9
+ speed: number;
10
+ reduced: boolean;
11
+ /** Scale a base duration (ms) by the current speed. */
12
+ scale: (ms: number) => number;
13
+ }
14
+ export declare function useEffectTiming(): EffectTiming;
15
+ //# sourceMappingURL=clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAS,EAAE,OAAe,EAAE,QAAQ,EAAE,EAAE,gBAAgB,qBAGrF;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/B;AAED,wBAAgB,eAAe,IAAI,YAAY,CAO9C"}
package/dist/clock.js ADDED
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { useRhineMotion } from '@wzrhine/react';
5
+ const EffectClockContext = React.createContext({ speed: 1, forceReduced: false });
6
+ export function EffectClock({ speed = 1, reduced = false, children }) {
7
+ const value = React.useMemo(() => ({ speed: speed > 0 ? speed : 1, forceReduced: reduced }), [speed, reduced]);
8
+ return _jsx(EffectClockContext.Provider, { value: value, children: children });
9
+ }
10
+ export function useEffectTiming() {
11
+ const { speed, forceReduced } = React.useContext(EffectClockContext);
12
+ const { reduced } = useRhineMotion();
13
+ return React.useMemo(() => ({ speed, reduced: forceReduced || reduced, scale: (ms) => ms / speed }), [speed, forceReduced, reduced]);
14
+ }
15
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAYhD,MAAM,kBAAkB,GAAG,KAAK,CAAC,aAAa,CAAmB,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;AAQpG,MAAM,UAAU,WAAW,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,QAAQ,EAAoB;IACpF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/G,OAAO,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA+B,CAAC;AAC7F,CAAC;AASD,MAAM,UAAU,eAAe;IAC7B,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;IACrE,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IACrC,OAAO,KAAK,CAAC,OAAO,CAClB,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,EACtF,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,CAC/B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,367 @@
1
+ /*
2
+ * WzRhineLab UI · optional showcase effects (M4). Tokens only; requires a `.rl-theme` ancestor.
3
+ * Every animation here has a reduced-motion equivalent (asserted by tests).
4
+ */
5
+
6
+ .rl-effect-sr {
7
+ position: absolute;
8
+ top: 0;
9
+ left: 0;
10
+ width: var(--rl-border-width);
11
+ height: var(--rl-border-width);
12
+ margin: calc(var(--rl-border-width) * -1);
13
+ padding: 0;
14
+ overflow: hidden;
15
+ clip-path: inset(50%);
16
+ white-space: nowrap;
17
+ border: 0;
18
+ }
19
+
20
+ /* ---------- ArchiveReveal ---------- */
21
+ .rl-reveal {
22
+ position: relative;
23
+ display: inline-block;
24
+ max-width: 100%;
25
+ font-family: var(--rl-font-mono);
26
+ letter-spacing: var(--rl-tracking-mono);
27
+ font-variant-numeric: tabular-nums;
28
+ }
29
+
30
+ .rl-reveal__glyphs {
31
+ white-space: pre-wrap;
32
+ overflow-wrap: anywhere;
33
+ }
34
+
35
+ .rl-reveal__cell {
36
+ color: var(--rl-fg-subtle);
37
+ transition: color var(--rl-motion-fast) var(--rl-ease-out);
38
+ }
39
+
40
+ .rl-reveal__cell[data-resolved] {
41
+ color: inherit;
42
+ }
43
+
44
+ .rl-reveal[data-state='concealed'] .rl-reveal__cell {
45
+ color: var(--rl-fg-subtle);
46
+ }
47
+
48
+ /* ---------- ScanSurface ---------- */
49
+ .rl-scan {
50
+ position: relative;
51
+ overflow: hidden;
52
+ isolation: isolate;
53
+ }
54
+
55
+ .rl-scan[data-overflow='visible'] { overflow: visible; }
56
+
57
+ .rl-scan__line {
58
+ position: absolute;
59
+ inset: 0 auto 0 0;
60
+ z-index: 1;
61
+ width: var(--rl-space-16);
62
+ pointer-events: none;
63
+ background: linear-gradient(
64
+ to right,
65
+ transparent,
66
+ color-mix(in srgb, var(--rl-accent) 14%, transparent) 70%,
67
+ var(--rl-scan-tone, var(--rl-accent)) calc(100% - var(--rl-border-width)),
68
+ transparent 100%
69
+ );
70
+ translate: -100% 0;
71
+ animation: rl-scan-sweep var(--rl-scan-duration) var(--rl-ease-in-out) 1 forwards;
72
+ }
73
+
74
+ @keyframes rl-scan-sweep {
75
+ to {
76
+ translate: calc(100% + 100cqw) 0;
77
+ }
78
+ }
79
+
80
+ .rl-scan {
81
+ container-type: inline-size;
82
+ }
83
+ .rl-scan[data-container-type='none'] { container-type: normal; }
84
+ .rl-scan[data-tone='info'] { --rl-scan-tone: var(--rl-info); }
85
+ .rl-scan[data-tone='warning'] { --rl-scan-tone: var(--rl-warning); }
86
+ .rl-scan[data-tone='success'] { --rl-scan-tone: var(--rl-success); }
87
+ .rl-scan[data-tone='neutral'] { --rl-scan-tone: var(--rl-fg-muted); }
88
+
89
+ /* Reduced motion: no travelling line; a brief static outline marks the scan instead. */
90
+ .rl-scan[data-reduced] .rl-scan__line,
91
+ .rl-theme[data-rl-motion='reduce'] .rl-scan__line {
92
+ inset: 0;
93
+ width: auto;
94
+ background: none;
95
+ box-shadow: inset 0 0 0 var(--rl-border-width-strong) var(--rl-accent);
96
+ translate: none;
97
+ animation: none;
98
+ }
99
+
100
+ @media (prefers-reduced-motion: reduce) {
101
+ .rl-scan__line {
102
+ inset: 0;
103
+ width: auto;
104
+ background: none;
105
+ box-shadow: inset 0 0 0 var(--rl-border-width-strong) var(--rl-accent);
106
+ translate: none;
107
+ animation: none;
108
+ }
109
+ }
110
+
111
+ /* ---------- OrbitalDiagram ---------- */
112
+ /* Diagram and legend sit side by side; the legend wraps below only when its column would be too
113
+ narrow to hold a label on one line (container query on the diagram's own width). */
114
+ .rl-orbit-wrap {
115
+ container-type: inline-size;
116
+ min-width: 0;
117
+ }
118
+
119
+ .rl-orbit {
120
+ display: grid;
121
+ grid-template-columns: minmax(0, 1fr);
122
+ align-items: center;
123
+ gap: var(--rl-space-6);
124
+ min-width: 0;
125
+ }
126
+
127
+ @container (min-width: 400px) {
128
+ .rl-orbit {
129
+ grid-template-columns: minmax(0, var(--rl-orbit-size)) minmax(0, 1fr);
130
+ }
131
+ }
132
+
133
+ .rl-orbit__svg {
134
+ grid-column: 1;
135
+ grid-row: 1;
136
+ justify-self: center;
137
+ width: 100%;
138
+ max-width: var(--rl-orbit-size);
139
+ height: auto;
140
+ overflow: visible;
141
+ }
142
+
143
+ .rl-orbit__center {
144
+ grid-column: 1;
145
+ grid-row: 1;
146
+ justify-self: center;
147
+ align-self: center;
148
+ translate: 0 calc(var(--rl-space-8) + var(--rl-space-2));
149
+ font: var(--rl-type-mono);
150
+ letter-spacing: var(--rl-tracking-caps);
151
+ color: var(--rl-fg-muted);
152
+ }
153
+
154
+ .rl-orbit__ticks line {
155
+ stroke: var(--rl-border-default);
156
+ stroke-width: var(--rl-border-width);
157
+ }
158
+
159
+ .rl-orbit__ring {
160
+ fill: none;
161
+ stroke: var(--rl-border-default);
162
+ stroke-width: var(--rl-border-width);
163
+ }
164
+
165
+ .rl-orbit__ring[data-ring='0'] {
166
+ stroke-dasharray: 2 4;
167
+ }
168
+
169
+ .rl-orbit__node {
170
+ fill: var(--rl-bg-surface);
171
+ stroke: var(--rl-fg-muted);
172
+ stroke-width: var(--rl-border-width-strong);
173
+ }
174
+
175
+ .rl-orbit__node[data-active] {
176
+ fill: var(--rl-accent);
177
+ stroke: var(--rl-accent);
178
+ }
179
+ .rl-orbit__node[data-selected] { stroke: var(--rl-fg-primary); stroke-width: var(--rl-border-width-strong); }
180
+ .rl-orbit__node[data-tone='info'] { fill: var(--rl-info-subtle); stroke: var(--rl-info); }
181
+ .rl-orbit__node[data-tone='warning'] { fill: var(--rl-warning-subtle); stroke: var(--rl-warning); }
182
+ .rl-orbit__node[data-tone='success'] { fill: var(--rl-success-subtle); stroke: var(--rl-success); }
183
+ .rl-orbit__node[data-tone='neutral'] { fill: var(--rl-bg-subtle); stroke: var(--rl-fg-muted); }
184
+
185
+ .rl-orbit__core {
186
+ fill: var(--rl-bg-subtle);
187
+ stroke: var(--rl-fg-primary);
188
+ stroke-width: var(--rl-border-width);
189
+ }
190
+
191
+ .rl-orbit__crosshair {
192
+ stroke: var(--rl-fg-primary);
193
+ stroke-width: var(--rl-border-width);
194
+ }
195
+
196
+ .rl-orbit__legend {
197
+ display: grid;
198
+ gap: var(--rl-space-2);
199
+ margin: 0;
200
+ padding: 0;
201
+ list-style: none;
202
+ font: var(--rl-type-body-sm);
203
+ }
204
+
205
+ .rl-orbit__legend li {
206
+ display: flex;
207
+ min-width: 0;
208
+ align-items: baseline;
209
+ gap: var(--rl-space-3);
210
+ padding-bottom: var(--rl-space-2);
211
+ border-bottom: var(--rl-border-width) solid var(--rl-border-subtle);
212
+ }
213
+
214
+ .rl-orbit__legend li[data-active] {
215
+ font-weight: 600;
216
+ }
217
+ .rl-orbit__legend li[data-selected] { text-decoration: underline; text-decoration-color: var(--rl-accent); text-underline-offset: var(--rl-space-1); }
218
+
219
+ .rl-orbit__ring-index {
220
+ font: var(--rl-type-mono);
221
+ color: var(--rl-fg-subtle);
222
+ }
223
+
224
+ .rl-orbit__legend li[data-active] .rl-orbit__ring-index {
225
+ color: var(--rl-accent-text);
226
+ }
227
+
228
+ /* ---------- MotionPlayer ---------- */
229
+ .rl-player {
230
+ display: grid;
231
+ gap: var(--rl-space-4);
232
+ min-width: 0;
233
+ }
234
+
235
+ .rl-player__head {
236
+ display: flex;
237
+ flex-wrap: wrap;
238
+ align-items: flex-end;
239
+ justify-content: space-between;
240
+ gap: var(--rl-space-3) var(--rl-space-6);
241
+ }
242
+
243
+ .rl-player__titles {
244
+ display: grid;
245
+ gap: var(--rl-space-1);
246
+ }
247
+
248
+ .rl-player__title {
249
+ margin: 0;
250
+ font: var(--rl-type-label);
251
+ }
252
+
253
+ .rl-player__spec {
254
+ margin: 0;
255
+ font: var(--rl-type-mono);
256
+ letter-spacing: var(--rl-tracking-mono);
257
+ color: var(--rl-fg-muted);
258
+ }
259
+
260
+ .rl-player__controls {
261
+ display: flex;
262
+ flex-wrap: wrap;
263
+ align-items: center;
264
+ gap: var(--rl-space-2);
265
+ }
266
+
267
+ .rl-player__button {
268
+ min-height: var(--rl-control-height-sm);
269
+ padding: 0 var(--rl-control-padding-x-sm);
270
+ appearance: none;
271
+ border: var(--rl-border-width) solid var(--rl-border-strong);
272
+ border-radius: var(--rl-radius-sm);
273
+ background: var(--rl-bg-surface);
274
+ color: var(--rl-fg-primary);
275
+ font: var(--rl-type-label-sm);
276
+ cursor: pointer;
277
+ }
278
+
279
+ .rl-player__button[data-primary] {
280
+ border-color: var(--rl-bg-inverse);
281
+ background: var(--rl-bg-inverse);
282
+ color: var(--rl-fg-inverse);
283
+ }
284
+
285
+ .rl-player__button:disabled {
286
+ border-color: var(--rl-border-default);
287
+ background: var(--rl-bg-disabled);
288
+ color: var(--rl-fg-disabled);
289
+ cursor: not-allowed;
290
+ }
291
+
292
+ .rl-player__button:focus-visible {
293
+ outline: var(--rl-focus-ring);
294
+ outline-offset: var(--rl-focus-offset);
295
+ }
296
+
297
+ .rl-player__state {
298
+ font: var(--rl-type-mono);
299
+ color: var(--rl-fg-subtle);
300
+ }
301
+
302
+ /* Lanes: 4 → 2 → 1 columns, never 3 + 1. Driven by the player's own width (container query).
303
+ Thresholds are the readable lane minimum (~200px) × columns + gaps. */
304
+ .rl-player {
305
+ container-type: inline-size;
306
+ }
307
+
308
+ .rl-player__lanes {
309
+ --rl-lane-gap: var(--rl-space-3);
310
+
311
+ display: grid;
312
+ grid-template-columns: minmax(0, 1fr);
313
+ gap: var(--rl-lane-gap);
314
+ margin: 0;
315
+ padding: 0;
316
+ list-style: none;
317
+ }
318
+
319
+ @container (min-width: 420px) {
320
+ .rl-player__lanes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
321
+ }
322
+
323
+ @container (min-width: 840px) {
324
+ .rl-player__lanes { grid-template-columns: repeat(4, minmax(0, 1fr)); }
325
+ }
326
+
327
+ .rl-player__lane {
328
+ display: grid;
329
+ grid-template-rows: auto 1fr;
330
+ gap: var(--rl-space-2);
331
+ min-width: 0;
332
+ padding: var(--rl-space-3);
333
+ border: var(--rl-border-width) solid var(--rl-border-subtle);
334
+ border-radius: var(--rl-radius-xs);
335
+ background: var(--rl-bg-canvas);
336
+ }
337
+
338
+ .rl-player__lane-label {
339
+ display: grid;
340
+ gap: var(--rl-space-1);
341
+ margin: 0;
342
+ font: var(--rl-type-mono);
343
+ letter-spacing: var(--rl-tracking-caps);
344
+ text-transform: uppercase;
345
+ color: var(--rl-accent-text);
346
+ }
347
+
348
+ .rl-player__lane-hint {
349
+ font: var(--rl-type-meta);
350
+ letter-spacing: 0;
351
+ text-transform: none;
352
+ color: var(--rl-fg-muted);
353
+ }
354
+
355
+ .rl-player__stage {
356
+ display: grid;
357
+ align-content: center;
358
+ min-height: var(--rl-space-16);
359
+ min-width: 0;
360
+ }
361
+
362
+ @media (forced-colors: active) {
363
+ .rl-scan__line { background: none; box-shadow: inset 0 0 0 var(--rl-border-width-strong) Highlight; }
364
+ .rl-orbit__ring, .rl-orbit__ticks line, .rl-orbit__crosshair { stroke: CanvasText; }
365
+ .rl-orbit__node[data-active] { fill: Highlight; stroke: Highlight; }
366
+ .rl-player__lane { border-color: CanvasText; }
367
+ }
@@ -0,0 +1,14 @@
1
+ export { ArchiveReveal } from './ArchiveReveal.js';
2
+ export type { ArchiveRevealProps } from './ArchiveReveal.js';
3
+ export { ScanSurface } from './ScanSurface.js';
4
+ export type { ScanSurfaceProps } from './ScanSurface.js';
5
+ export { OrbitalDiagram } from './OrbitalDiagram.js';
6
+ export type { OrbitalDiagramProps, OrbitNode } from './OrbitalDiagram.js';
7
+ export { MotionPlayer } from './MotionPlayer.js';
8
+ export type { MotionDemoProps, MotionLane, MotionPlayerProps } from './MotionPlayer.js';
9
+ export { EffectClock, useEffectTiming } from './clock.js';
10
+ export type { EffectClockProps, EffectTiming } from './clock.js';
11
+ export { isWide, isWideGlyph, maskText, scrambleCells, scrambleText, stepToward } from './scramble.js';
12
+ export type { ScrambleCell } from './scramble.js';
13
+ export { useInViewport, usePageVisible } from './usePageVisible.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACvG,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // Optional showcase effects. Import styles from `@wzrhine/effects/styles.css`.
2
+ // Kept out of @wzrhine/react so base components never pull in effect code (ARCHITECTURE.md).
3
+ export { ArchiveReveal } from './ArchiveReveal.js';
4
+ export { ScanSurface } from './ScanSurface.js';
5
+ export { OrbitalDiagram } from './OrbitalDiagram.js';
6
+ export { MotionPlayer } from './MotionPlayer.js';
7
+ export { EffectClock, useEffectTiming } from './clock.js';
8
+ export { isWide, isWideGlyph, maskText, scrambleCells, scrambleText, stepToward } from './scramble.js';
9
+ export { useInViewport, usePageVisible } from './usePageVisible.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6FAA6F;AAC7F,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEvG,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Deterministic glyph scramble for the decrypt-style reveal. The same (text, progress, frame)
3
+ * always yields the same output, so tests and screenshots are stable; nothing here is random.
4
+ */
5
+ /** CJK and other full-width characters get full-width glyphs so the line does not reflow. */
6
+ export declare function isWide(ch: string): boolean;
7
+ /** True for glyphs drawn from the full-width scramble set. */
8
+ export declare function isWideGlyph(ch: string): boolean;
9
+ /** Static mask shown while content is concealed. */
10
+ export declare function maskText(text: string): string;
11
+ export interface ScrambleCell {
12
+ char: string;
13
+ resolved: boolean;
14
+ }
15
+ /**
16
+ * Characters resolve left to right as progress goes 0 → 1; unresolved positions cycle through
17
+ * glyphs as `frame` advances. progress ≥ 1 returns the text; progress ≤ 0 returns the mask.
18
+ */
19
+ export declare function scrambleCells(text: string, progress: number, frame: number): ScrambleCell[];
20
+ export declare function scrambleText(text: string, progress: number, frame: number): string;
21
+ /** Move `current` toward `target` by `step` without overshooting. */
22
+ export declare function stepToward(current: number, target: number, step: number): number;
23
+ //# sourceMappingURL=scramble.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scramble.d.ts","sourceRoot":"","sources":["../src/scramble.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,6FAA6F;AAC7F,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAG1C;AAED,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE/C;AAMD,oDAAoD;AACpD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAW3F;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhF"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Deterministic glyph scramble for the decrypt-style reveal. The same (text, progress, frame)
3
+ * always yields the same output, so tests and screenshots are stable; nothing here is random.
4
+ */
5
+ const NARROW = '0123456789ABCDEFGHJKLMNPRSTUVWXYZ#/-';
6
+ const WIDE = '▓▒░▚▞';
7
+ /** CJK and other full-width characters get full-width glyphs so the line does not reflow. */
8
+ export function isWide(ch) {
9
+ const code = ch.codePointAt(0) ?? 0;
10
+ return code >= 0x2e80;
11
+ }
12
+ /** True for glyphs drawn from the full-width scramble set. */
13
+ export function isWideGlyph(ch) {
14
+ return WIDE.includes(ch);
15
+ }
16
+ function isBlank(ch) {
17
+ return /\s/.test(ch);
18
+ }
19
+ /** Static mask shown while content is concealed. */
20
+ export function maskText(text) {
21
+ return Array.from(text)
22
+ .map((ch) => (isBlank(ch) ? ch : isWide(ch) ? '▒' : '·'))
23
+ .join('');
24
+ }
25
+ /**
26
+ * Characters resolve left to right as progress goes 0 → 1; unresolved positions cycle through
27
+ * glyphs as `frame` advances. progress ≥ 1 returns the text; progress ≤ 0 returns the mask.
28
+ */
29
+ export function scrambleCells(text, progress, frame) {
30
+ const chars = Array.from(text);
31
+ const n = chars.length;
32
+ if (progress >= 1)
33
+ return chars.map((char) => ({ char, resolved: true }));
34
+ if (progress <= 0)
35
+ return Array.from(maskText(text)).map((char) => ({ char, resolved: false }));
36
+ return chars.map((ch, i) => {
37
+ if (isBlank(ch))
38
+ return { char: ch, resolved: true };
39
+ if (progress >= (i + 1) / n)
40
+ return { char: ch, resolved: true };
41
+ const set = isWide(ch) ? WIDE : NARROW;
42
+ return { char: set[(i * 7 + frame * 13) % set.length], resolved: false };
43
+ });
44
+ }
45
+ export function scrambleText(text, progress, frame) {
46
+ return scrambleCells(text, progress, frame)
47
+ .map((cell) => cell.char)
48
+ .join('');
49
+ }
50
+ /** Move `current` toward `target` by `step` without overshooting. */
51
+ export function stepToward(current, target, step) {
52
+ if (current < target)
53
+ return Math.min(target, current + step);
54
+ if (current > target)
55
+ return Math.max(target, current - step);
56
+ return current;
57
+ }
58
+ //# sourceMappingURL=scramble.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scramble.js","sourceRoot":"","sources":["../src/scramble.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,GAAG,sCAAsC,CAAC;AACtD,MAAM,IAAI,GAAG,OAAO,CAAC;AAErB,6FAA6F;AAC7F,MAAM,UAAU,MAAM,CAAC,EAAU;IAC/B,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,IAAI,IAAI,MAAM,CAAC;AACxB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,OAAO,CAAC,EAAU;IACzB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;SACpB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;SACxD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAa;IACzE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1E,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAChG,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QACzB,IAAI,OAAO,CAAC,EAAE,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACjE,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;QACvC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,CAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAa;IACxE,OAAO,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC;SACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;SACxB,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,MAAc,EAAE,IAAY;IACtE,IAAI,OAAO,GAAG,MAAM;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;IAC9D,IAAI,OAAO,GAAG,MAAM;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;IAC9D,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { useInViewport, usePageVisible } from '@wzrhine/react';
2
+ //# sourceMappingURL=usePageVisible.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePageVisible.d.ts","sourceRoot":"","sources":["../src/usePageVisible.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ // Visibility hooks live in @wzrhine/react (single source, ADR-0007); re-exported here so the
2
+ // effects public API is unchanged.
3
+ export { useInViewport, usePageVisible } from '@wzrhine/react';
4
+ //# sourceMappingURL=usePageVisible.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePageVisible.js","sourceRoot":"","sources":["../src/usePageVisible.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,mCAAmC;AACnC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,65 @@
1
1
  {
2
2
  "name": "@wzrhine/effects",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "license": "MIT",
5
+ "description": "Optional showcase effects (decrypt reveal, scan, orbit) and a four-lane motion player for WzRhineLab UI.",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/BaiZhi967/WzRhineLab-UI.git",
10
+ "directory": "packages/effects"
11
+ },
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "CHANGELOG.md"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./styles.css": "./dist/effects.css",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "peerDependencies": {
33
+ "motion": "^14.0.0",
34
+ "react": "^19.0.0",
35
+ "react-dom": "^19.0.0",
36
+ "@wzrhine/react": "^0.2.0"
37
+ },
38
+ "dependencies": {
39
+ "@wzrhine/tokens": "0.2.0"
40
+ },
41
+ "devDependencies": {
42
+ "@testing-library/dom": "10.4.2",
43
+ "@testing-library/jest-dom": "7.0.1",
44
+ "@testing-library/react": "16.3.3",
45
+ "@testing-library/user-event": "14.6.7",
46
+ "@types/node": "24.19.1",
47
+ "@types/react": "^19.0.0",
48
+ "@types/react-dom": "^19.0.0",
49
+ "jsdom": "27.4.0",
50
+ "motion": "^14.1.0",
51
+ "react": "^19.3.0",
52
+ "react-dom": "^19.3.0",
53
+ "typescript": "^5.9.0",
54
+ "vitest": "4.1.11",
55
+ "@wzrhine/react": "0.2.0"
56
+ },
57
+ "scripts": {
58
+ "typecheck": "tsc --noEmit -p tsconfig.json",
59
+ "test": "vitest run",
60
+ "build": "node ../../scripts/build-package.mjs"
61
+ },
62
+ "main": "./dist/index.js",
63
+ "module": "./dist/index.js",
64
+ "types": "./dist/index.d.ts"
6
65
  }