@rnine/nineos 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.
- package/README.md +93 -2
- package/dist/_variables.scss +199 -0
- package/dist/components/Chrome/Chrome.d.ts +27 -0
- package/dist/components/Chrome/Chrome.js +45 -0
- package/dist/components/Chrome/Chrome.js.map +1 -0
- package/dist/components/Chrome/Chrome.module.js +14 -0
- package/dist/components/Chrome/Chrome.module.js.map +1 -0
- package/dist/components/CommandLine/ChipBar.d.ts +20 -0
- package/dist/components/CommandLine/ChipBar.js +25 -0
- package/dist/components/CommandLine/ChipBar.js.map +1 -0
- package/dist/components/CommandLine/ChipBar.module.js +9 -0
- package/dist/components/CommandLine/ChipBar.module.js.map +1 -0
- package/dist/components/CommandLine/CommandLine.d.ts +38 -0
- package/dist/components/CommandLine/CommandLine.js +121 -0
- package/dist/components/CommandLine/CommandLine.js.map +1 -0
- package/dist/components/CommandLine/CommandLine.module.js +13 -0
- package/dist/components/CommandLine/CommandLine.module.js.map +1 -0
- package/dist/components/Hint/Hint.d.ts +22 -0
- package/dist/components/Hint/Hint.js +52 -0
- package/dist/components/Hint/Hint.js.map +1 -0
- package/dist/components/Hint/Hint.module.js +11 -0
- package/dist/components/Hint/Hint.module.js.map +1 -0
- package/dist/components/Panel/PanelStage.d.ts +19 -0
- package/dist/components/Panel/PanelStage.js +67 -0
- package/dist/components/Panel/PanelStage.js.map +1 -0
- package/dist/components/Panel/PanelStage.module.js +10 -0
- package/dist/components/Panel/PanelStage.module.js.map +1 -0
- package/dist/components.css +2 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/motion/DecryptText.d.ts +9 -0
- package/dist/motion/DecryptText.js +87 -0
- package/dist/motion/DecryptText.js.map +1 -0
- package/dist/motion/DecryptText.module.js +12 -0
- package/dist/motion/DecryptText.module.js.map +1 -0
- package/dist/motion/DotField.d.ts +8 -0
- package/dist/motion/DotField.js +116 -0
- package/dist/motion/DotField.js.map +1 -0
- package/dist/motion/DotField.module.js +8 -0
- package/dist/motion/DotField.module.js.map +1 -0
- package/dist/motion/Monogram.d.ts +7 -0
- package/dist/motion/Monogram.js +40 -0
- package/dist/motion/Monogram.js.map +1 -0
- package/dist/motion/Monogram.module.js +10 -0
- package/dist/motion/Monogram.module.js.map +1 -0
- package/dist/motion/PageEffects.d.ts +2 -0
- package/dist/motion/PageEffects.js +45 -0
- package/dist/motion/PageEffects.js.map +1 -0
- package/dist/motion/PageEffects.module.js +8 -0
- package/dist/motion/PageEffects.module.js.map +1 -0
- package/dist/motion/Terminal.d.ts +12 -0
- package/dist/motion/Terminal.js +57 -0
- package/dist/motion/Terminal.js.map +1 -0
- package/dist/motion/Terminal.module.js +12 -0
- package/dist/motion/Terminal.module.js.map +1 -0
- package/dist/motion/VelocityStrip.d.ts +7 -0
- package/dist/motion/VelocityStrip.js +46 -0
- package/dist/motion/VelocityStrip.js.map +1 -0
- package/dist/motion/VelocityStrip.module.js +9 -0
- package/dist/motion/VelocityStrip.module.js.map +1 -0
- package/dist/motion/signal.d.ts +15 -0
- package/dist/motion/signal.js +78 -0
- package/dist/motion/signal.js.map +1 -0
- package/dist/motion/useDecrypt.d.ts +21 -0
- package/dist/motion/useDecrypt.js +45 -0
- package/dist/motion/useDecrypt.js.map +1 -0
- package/dist/motion.d.ts +10 -0
- package/dist/motion.js +10 -0
- package/dist/nineos-base.css +54 -0
- package/dist/provider/ThemeProvider.d.ts +25 -0
- package/dist/provider/ThemeProvider.js +44 -0
- package/dist/provider/ThemeProvider.js.map +1 -0
- package/dist/styles.css +637 -0
- package/dist/tokens.css +470 -0
- package/dist/tokens.d.ts +2446 -0
- package/dist/tokens.js +2446 -0
- package/fonts.css +9 -0
- package/package.json +114 -4
- package/scss/_glitch.scss +23 -0
- package/scss/_index.scss +6 -0
- package/scss/base.scss +24 -0
- package/theme.json +9 -0
- package/tokens/color.json +46 -0
- package/tokens/layout.json +29 -0
- package/tokens/motion.json +18 -0
- package/tokens/palettes/acid.json +65 -0
- package/tokens/palettes/volt.json +65 -0
- package/tokens/shape.json +26 -0
- package/tokens/space.json +19 -0
- package/tokens/themes/dark.json +81 -0
- package/tokens/themes/light.json +106 -0
- package/tokens/typography.json +58 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import e from "./Monogram.module.js";
|
|
2
|
+
import { MarkArt as t, cx as n } from "@rnine/core/kit";
|
|
3
|
+
import { useEffect as r, useState as i } from "react";
|
|
4
|
+
import { useReducedMotion as a } from "@rnine/core/motion";
|
|
5
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
6
|
+
//#region src/motion/Monogram.tsx
|
|
7
|
+
function c({ size: c = 32, variant: l = "full", animate: u = "hover", label: d, className: f }) {
|
|
8
|
+
let p = a(), [m, h] = i(0);
|
|
9
|
+
return r(() => {
|
|
10
|
+
if (u !== "mount" || p) return;
|
|
11
|
+
let e = window.setTimeout(() => h((e) => e + 1), 400);
|
|
12
|
+
return () => window.clearTimeout(e);
|
|
13
|
+
}, [u, p]), /* @__PURE__ */ s("span", {
|
|
14
|
+
className: n(e.mono, f),
|
|
15
|
+
style: {
|
|
16
|
+
width: c,
|
|
17
|
+
height: c
|
|
18
|
+
},
|
|
19
|
+
onMouseEnter: () => {
|
|
20
|
+
u !== "none" && !p && h((e) => e + 1);
|
|
21
|
+
},
|
|
22
|
+
role: d ? "img" : void 0,
|
|
23
|
+
"aria-label": d,
|
|
24
|
+
"aria-hidden": d ? void 0 : !0,
|
|
25
|
+
children: [/* @__PURE__ */ o(t, { variant: l }), m > 0 && /* @__PURE__ */ s("span", {
|
|
26
|
+
className: e.ghosts,
|
|
27
|
+
children: [/* @__PURE__ */ o("span", {
|
|
28
|
+
className: e.g1,
|
|
29
|
+
children: /* @__PURE__ */ o(t, { variant: l })
|
|
30
|
+
}), /* @__PURE__ */ o("span", {
|
|
31
|
+
className: e.g2,
|
|
32
|
+
children: /* @__PURE__ */ o(t, { variant: l })
|
|
33
|
+
})]
|
|
34
|
+
}, m)]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { c as Mark };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=Monogram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Monogram.js","names":[],"sources":["../../src/motion/Monogram.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { cx, MarkArt } from '@rnine/core/kit';\nimport { useReducedMotion } from '@rnine/core/motion';\nimport type { MarkProps } from '@rnine/core';\nimport styles from './Monogram.module.scss';\n\n/**\n * The app's mark (ThemeProvider mark, or MarkProvider), with the NineOS slice\n * glitch in cyan and magenta (animate: on hover, on mount, or never). Until an\n * app provides one it shows a placeholder frame.\n */\nexport function Mark({ size = 32, variant = 'full', animate = 'hover', label, className }: MarkProps) {\n const reduced = useReducedMotion();\n const [run, setRun] = useState(0);\n\n useEffect(() => {\n if (animate !== 'mount' || reduced) return;\n const t = window.setTimeout(() => setRun((n) => n + 1), 400);\n return () => window.clearTimeout(t);\n }, [animate, reduced]);\n\n const play = () => {\n if (animate !== 'none' && !reduced) setRun((n) => n + 1);\n };\n\n return (\n <span\n className={cx(styles.mono, className)}\n style={{ width: size, height: size }}\n onMouseEnter={play}\n role={label ? 'img' : undefined}\n aria-label={label}\n aria-hidden={label ? undefined : true}\n >\n <MarkArt variant={variant} />\n {run > 0 && (\n <span key={run} className={styles.ghosts}>\n <span className={styles.g1}><MarkArt variant={variant} /></span>\n <span className={styles.g2}><MarkArt variant={variant} /></span>\n </span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;;AAWA,SAAgB,EAAK,EAAE,UAAO,IAAI,aAAU,QAAQ,aAAU,SAAS,UAAO,gBAAwB;CACpG,IAAM,IAAU,EAAiB,GAC3B,CAAC,GAAK,KAAU,EAAS,CAAC;CAYhC,OAVA,QAAgB;EACd,IAAI,MAAY,WAAW,GAAS;EACpC,IAAM,IAAI,OAAO,iBAAiB,GAAQ,MAAM,IAAI,CAAC,GAAG,GAAG;EAC3D,aAAa,OAAO,aAAa,CAAC;CACpC,GAAG,CAAC,GAAS,CAAO,CAAC,GAOnB,kBAAC,QAAD;EACE,WAAW,EAAG,EAAO,MAAM,CAAS;EACpC,OAAO;GAAE,OAAO;GAAM,QAAQ;EAAK;EACnC,oBARe;GACjB,AAAI,MAAY,UAAU,CAAC,KAAS,GAAQ,MAAM,IAAI,CAAC;EACzD;EAOI,MAAM,IAAQ,QAAQ,KAAA;EACtB,cAAY;EACZ,eAAa,IAAQ,KAAA,IAAY;YANnC,CAQE,kBAAC,GAAD,EAAkB,WAAU,CAAA,GAC3B,IAAM,KACL,kBAAC,QAAD;GAAgB,WAAW,EAAO;aAAlC,CACE,kBAAC,QAAD;IAAM,WAAW,EAAO;cAAI,kBAAC,GAAD,EAAkB,WAAU,CAAA;GAAO,CAAA,GAC/D,kBAAC,QAAD;IAAM,WAAW,EAAO;cAAI,kBAAC,GAAD,EAAkB,WAAU,CAAA;GAAO,CAAA,CAC3D;KAHK,CAGL,CAEJ;;AAEV"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var e = {
|
|
2
|
+
mono: "r9-Monogram-module_mono_b1gNw",
|
|
3
|
+
ghosts: "r9-Monogram-module_ghosts_1W2MQ",
|
|
4
|
+
g1: "r9-Monogram-module_g1_b6O-L",
|
|
5
|
+
g2: "r9-Monogram-module_g2_RJHLf"
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { e as default };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=Monogram.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Monogram.module.js","names":[],"sources":["../../src/motion/Monogram.module.scss"],"sourcesContent":["@use \"@rnine/nineos/scss\" as r9;\n\n.mono {\n position: relative;\n display: inline-block;\n flex: none;\n color: currentColor;\n\n svg { display: block; width: 100%; height: 100%; }\n}\n\n.ghosts { position: absolute; inset: 0; pointer-events: none; }\n\n.g1,\n.g2 {\n position: absolute;\n inset: 0;\n opacity: 0;\n}\n\n.g1 {\n color: r9.$signal;\n animation: r9.keyframes(slice-a) r9.$duration-slow steps(1) 1;\n}\n\n.g2 {\n color: r9.$accent;\n animation: r9.keyframes(slice-b) r9.$duration-slow steps(1) 1;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import e from "./PageEffects.module.js";
|
|
2
|
+
import { useEffect as t, useRef as n } from "react";
|
|
3
|
+
import { useReducedMotion as r } from "@rnine/core/motion";
|
|
4
|
+
import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
5
|
+
//#region src/motion/PageEffects.tsx
|
|
6
|
+
function s() {
|
|
7
|
+
let s = n(null), c = r();
|
|
8
|
+
return t(() => {
|
|
9
|
+
let e = s.current, t = e?.getContext("2d");
|
|
10
|
+
if (!e || !t || c) return;
|
|
11
|
+
let n = Math.min(window.devicePixelRatio || 1, 2), r = [], i = 0, a = "", o = () => {
|
|
12
|
+
e.width = Math.round(window.innerWidth * n), e.height = Math.round(window.innerHeight * n), t.setTransform(n, 0, 0, n, 0, 0);
|
|
13
|
+
}, l = (e) => {
|
|
14
|
+
t.clearRect(0, 0, window.innerWidth, window.innerHeight), r = r.filter((t) => e - t.t < 420), t.strokeStyle = a, t.lineWidth = 1.5;
|
|
15
|
+
for (let n of r) {
|
|
16
|
+
let r = (e - n.t) / 420, i = 6 + (1 - (1 - r) ** 3) * 16, a = i + 8 * (1 - r);
|
|
17
|
+
for (let e = 0; e < 8; e++) {
|
|
18
|
+
let r = Math.PI * 2 * e / 8;
|
|
19
|
+
t.beginPath(), t.moveTo(n.x + Math.cos(r) * i, n.y + Math.sin(r) * i), t.lineTo(n.x + Math.cos(r) * a, n.y + Math.sin(r) * a), t.stroke();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
i = r.length ? requestAnimationFrame(l) : 0;
|
|
23
|
+
}, u = (e) => {
|
|
24
|
+
a = getComputedStyle(document.documentElement).getPropertyValue("--r9-accent").trim(), r.push({
|
|
25
|
+
x: e.clientX,
|
|
26
|
+
y: e.clientY,
|
|
27
|
+
t: performance.now()
|
|
28
|
+
}), i ||= requestAnimationFrame(l);
|
|
29
|
+
};
|
|
30
|
+
return o(), window.addEventListener("resize", o), document.addEventListener("click", u), () => {
|
|
31
|
+
cancelAnimationFrame(i), window.removeEventListener("resize", o), document.removeEventListener("click", u);
|
|
32
|
+
};
|
|
33
|
+
}, [c]), /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a("div", {
|
|
34
|
+
className: e.scan,
|
|
35
|
+
"aria-hidden": "true"
|
|
36
|
+
}), /* @__PURE__ */ a("canvas", {
|
|
37
|
+
ref: s,
|
|
38
|
+
className: e.sparks,
|
|
39
|
+
"aria-hidden": "true"
|
|
40
|
+
})] });
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { s as PageEffects };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=PageEffects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PageEffects.js","names":[],"sources":["../../src/motion/PageEffects.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useReducedMotion } from '@rnine/core/motion';\nimport styles from './PageEffects.module.scss';\n\ntype Burst = { x: number; y: number; t: number };\n\n/** Page-wide effects: drifting scanlines (Cipher) and magenta click sparks. Mount once. */\nexport function PageEffects() {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const reduced = useReducedMotion();\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const ctx = canvas?.getContext('2d');\n if (!canvas || !ctx || reduced) return;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n let bursts: Burst[] = [];\n let raf = 0;\n let color = '';\n\n const resize = () => {\n canvas.width = Math.round(window.innerWidth * dpr);\n canvas.height = Math.round(window.innerHeight * dpr);\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n };\n const draw = (now: number) => {\n ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);\n bursts = bursts.filter((b) => now - b.t < 420);\n ctx.strokeStyle = color;\n ctx.lineWidth = 1.5;\n for (const b of bursts) {\n const p = (now - b.t) / 420;\n const eased = 1 - (1 - p) ** 3;\n const r1 = 6 + eased * 16;\n const r2 = r1 + 8 * (1 - p);\n for (let i = 0; i < 8; i++) {\n const a = (Math.PI * 2 * i) / 8;\n ctx.beginPath();\n ctx.moveTo(b.x + Math.cos(a) * r1, b.y + Math.sin(a) * r1);\n ctx.lineTo(b.x + Math.cos(a) * r2, b.y + Math.sin(a) * r2);\n ctx.stroke();\n }\n }\n raf = bursts.length ? requestAnimationFrame(draw) : 0;\n };\n const onClick = (e: MouseEvent) => {\n color = getComputedStyle(document.documentElement).getPropertyValue('--r9-accent').trim();\n bursts.push({ x: e.clientX, y: e.clientY, t: performance.now() });\n if (!raf) raf = requestAnimationFrame(draw);\n };\n\n resize();\n window.addEventListener('resize', resize);\n document.addEventListener('click', onClick);\n return () => {\n cancelAnimationFrame(raf);\n window.removeEventListener('resize', resize);\n document.removeEventListener('click', onClick);\n };\n }, [reduced]);\n\n return (\n <>\n <div className={styles.scan} aria-hidden=\"true\" />\n <canvas ref={canvasRef} className={styles.sparks} aria-hidden=\"true\" />\n </>\n );\n}\n"],"mappings":";;;;;AAOA,SAAgB,IAAc;CAC5B,IAAM,IAAY,EAA0B,IAAI,GAC1C,IAAU,EAAiB;CAoDjC,OAlDA,QAAgB;EACd,IAAM,IAAS,EAAU,SACnB,IAAM,GAAQ,WAAW,IAAI;EACnC,IAAI,CAAC,KAAU,CAAC,KAAO,GAAS;EAChC,IAAM,IAAM,KAAK,IAAI,OAAO,oBAAoB,GAAG,CAAC,GAChD,IAAkB,CAAC,GACnB,IAAM,GACN,IAAQ,IAEN,UAAe;GAGnB,AAFA,EAAO,QAAQ,KAAK,MAAM,OAAO,aAAa,CAAG,GACjD,EAAO,SAAS,KAAK,MAAM,OAAO,cAAc,CAAG,GACnD,EAAI,aAAa,GAAK,GAAG,GAAG,GAAK,GAAG,CAAC;EACvC,GACM,KAAQ,MAAgB;GAI5B,AAHA,EAAI,UAAU,GAAG,GAAG,OAAO,YAAY,OAAO,WAAW,GACzD,IAAS,EAAO,QAAQ,MAAM,IAAM,EAAE,IAAI,GAAG,GAC7C,EAAI,cAAc,GAClB,EAAI,YAAY;GAChB,KAAK,IAAM,KAAK,GAAQ;IACtB,IAAM,KAAK,IAAM,EAAE,KAAK,KAElB,IAAK,KADG,KAAK,IAAI,MAAM,KACN,IACjB,IAAK,IAAK,KAAK,IAAI;IACzB,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;KAC1B,IAAM,IAAK,KAAK,KAAK,IAAI,IAAK;KAI9B,AAHA,EAAI,UAAU,GACd,EAAI,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,GAAI,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,CAAE,GACzD,EAAI,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,GAAI,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,CAAE,GACzD,EAAI,OAAO;IACb;GACF;GACA,IAAM,EAAO,SAAS,sBAAsB,CAAI,IAAI;EACtD,GACM,KAAW,MAAkB;GAGjC,AAFA,IAAQ,iBAAiB,SAAS,eAAe,EAAE,iBAAiB,aAAa,EAAE,KAAK,GACxF,EAAO,KAAK;IAAE,GAAG,EAAE;IAAS,GAAG,EAAE;IAAS,GAAG,YAAY,IAAI;GAAE,CAAC,GAChE,AAAU,MAAM,sBAAsB,CAAI;EAC5C;EAKA,OAHA,EAAO,GACP,OAAO,iBAAiB,UAAU,CAAM,GACxC,SAAS,iBAAiB,SAAS,CAAO,SAC7B;GAGX,AAFA,qBAAqB,CAAG,GACxB,OAAO,oBAAoB,UAAU,CAAM,GAC3C,SAAS,oBAAoB,SAAS,CAAO;EAC/C;CACF,GAAG,CAAC,CAAO,CAAC,GAGV,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAM,eAAY;CAAQ,CAAA,GACjD,kBAAC,UAAD;EAAQ,KAAK;EAAW,WAAW,EAAO;EAAQ,eAAY;CAAQ,CAAA,CACtE,EAAA,CAAA;AAEN"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PageEffects.module.js","names":[],"sources":["../../src/motion/PageEffects.module.scss"],"sourcesContent":["@use \"@rnine/nineos/scss\" as r9;\n\n.scan {\n position: fixed;\n inset: 0;\n z-index: r9.$z-fx;\n pointer-events: none;\n overflow: hidden;\n\n &::before {\n content: \"\";\n position: absolute;\n inset: -4px 0 0;\n @include r9.texture(lines);\n @include r9.motion-safe { animation: r9.keyframes(drift) 9s linear infinite; }\n }\n\n // A soft signal band passes down the screen\n &::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n height: 120px;\n transform: translateY(-140px);\n background: linear-gradient(180deg, transparent, #{r9.$scan}, transparent);\n @include r9.motion-safe { animation: r9.keyframes(band) 7s r9.$easing-out infinite; }\n }\n}\n\n.sparks {\n position: fixed;\n inset: 0;\n width: 100%;\n height: 100%;\n z-index: r9.$z-fx;\n pointer-events: none;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type TerminalLine = {
|
|
2
|
+
text: string;
|
|
3
|
+
tone?: 'cmd' | 'dim' | 'ok';
|
|
4
|
+
};
|
|
5
|
+
type Props = {
|
|
6
|
+
lines: TerminalLine[];
|
|
7
|
+
label: string;
|
|
8
|
+
loopDelay?: number;
|
|
9
|
+
};
|
|
10
|
+
/** Terminal that types its first line, prints the rest, then starts over. */
|
|
11
|
+
export declare function Terminal({ lines, label, loopDelay }: Props): import("react").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import e from "./Terminal.module.js";
|
|
2
|
+
import { useEffect as t, useState as n } from "react";
|
|
3
|
+
import { useReducedMotion as r } from "@rnine/core/motion";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
//#region src/motion/Terminal.tsx
|
|
6
|
+
function o({ lines: o, label: s, loopDelay: c = 4200 }) {
|
|
7
|
+
let l = r(), [u, d] = n({
|
|
8
|
+
count: o.length,
|
|
9
|
+
typed: o[0]?.text.length ?? 0
|
|
10
|
+
});
|
|
11
|
+
t(() => {
|
|
12
|
+
if (l) return;
|
|
13
|
+
let e = !1, t = [], n = (n, r) => t.push(window.setTimeout(() => !e && n(), r)), r = () => {
|
|
14
|
+
d({
|
|
15
|
+
count: 1,
|
|
16
|
+
typed: 0
|
|
17
|
+
});
|
|
18
|
+
let e = o[0]?.text ?? "", t = 0, i = () => {
|
|
19
|
+
t++, d({
|
|
20
|
+
count: 1,
|
|
21
|
+
typed: t
|
|
22
|
+
}), t < e.length ? n(i, 45 + Math.random() * 55) : n(() => a(1), 380);
|
|
23
|
+
}, a = (t) => {
|
|
24
|
+
if (t >= o.length) {
|
|
25
|
+
n(r, c);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
d({
|
|
29
|
+
count: t + 1,
|
|
30
|
+
typed: e.length
|
|
31
|
+
}), n(() => a(t + 1), 260 + Math.random() * 240);
|
|
32
|
+
};
|
|
33
|
+
i();
|
|
34
|
+
};
|
|
35
|
+
return n(r, 1400), () => {
|
|
36
|
+
e = !0, t.forEach(clearTimeout);
|
|
37
|
+
};
|
|
38
|
+
}, [
|
|
39
|
+
o,
|
|
40
|
+
l,
|
|
41
|
+
c
|
|
42
|
+
]);
|
|
43
|
+
let f = u.typed < (o[0]?.text.length ?? 0);
|
|
44
|
+
return /* @__PURE__ */ a("div", {
|
|
45
|
+
className: e.term,
|
|
46
|
+
role: "img",
|
|
47
|
+
"aria-label": `${s}: ${o.map((e) => e.text).join(". ")}`,
|
|
48
|
+
children: [o.slice(0, u.count).map((t, n) => /* @__PURE__ */ a("div", {
|
|
49
|
+
className: [e.line, t.tone && e[t.tone]].filter(Boolean).join(" "),
|
|
50
|
+
children: [n === 0 ? t.text.slice(0, u.typed) : t.text, n === 0 && f && /* @__PURE__ */ i("span", { className: e.caret })]
|
|
51
|
+
}, n)), !f && /* @__PURE__ */ i("span", { className: e.caret })]
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { o as Terminal };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=Terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Terminal.js","names":[],"sources":["../../src/motion/Terminal.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useReducedMotion } from '@rnine/core/motion';\nimport styles from './Terminal.module.scss';\n\nexport type TerminalLine = { text: string; tone?: 'cmd' | 'dim' | 'ok' };\n\ntype Props = { lines: TerminalLine[]; label: string; loopDelay?: number };\n\n/** Terminal that types its first line, prints the rest, then starts over. */\nexport function Terminal({ lines, label, loopDelay = 4200 }: Props) {\n const reduced = useReducedMotion();\n // Starts complete, so the first frame (and reduced motion) shows the full output\n const [state, setState] = useState({ count: lines.length, typed: lines[0]?.text.length ?? 0 });\n\n useEffect(() => {\n if (reduced) return;\n let cancelled = false;\n const timers: number[] = [];\n const later = (fn: () => void, ms: number) => timers.push(window.setTimeout(() => !cancelled && fn(), ms));\n\n const run = () => {\n setState({ count: 1, typed: 0 });\n const first = lines[0]?.text ?? '';\n let k = 0;\n const type = () => {\n k++;\n setState({ count: 1, typed: k });\n if (k < first.length) later(type, 45 + Math.random() * 55);\n else later(() => print(1), 380);\n };\n const print = (n: number) => {\n if (n >= lines.length) {\n later(run, loopDelay);\n return;\n }\n setState({ count: n + 1, typed: first.length });\n later(() => print(n + 1), 260 + Math.random() * 240);\n };\n type();\n };\n later(run, 1400);\n return () => {\n cancelled = true;\n timers.forEach(clearTimeout);\n };\n }, [lines, reduced, loopDelay]);\n\n const typing = state.typed < (lines[0]?.text.length ?? 0);\n return (\n <div className={styles.term} role=\"img\" aria-label={`${label}: ${lines.map((l) => l.text).join('. ')}`}>\n {lines.slice(0, state.count).map((l, i) => (\n <div key={i} className={[styles.line, l.tone && styles[l.tone]].filter(Boolean).join(' ')}>\n {i === 0 ? l.text.slice(0, state.typed) : l.text}\n {i === 0 && typing && <span className={styles.caret} />}\n </div>\n ))}\n {!typing && <span className={styles.caret} />}\n </div>\n );\n}\n"],"mappings":";;;;;AASA,SAAgB,EAAS,EAAE,UAAO,UAAO,eAAY,QAAe;CAClE,IAAM,IAAU,EAAiB,GAE3B,CAAC,GAAO,KAAY,EAAS;EAAE,OAAO,EAAM;EAAQ,OAAO,EAAM,IAAI,KAAK,UAAU;CAAE,CAAC;CAE7F,QAAgB;EACd,IAAI,GAAS;EACb,IAAI,IAAY,IACV,IAAmB,CAAC,GACpB,KAAS,GAAgB,MAAe,EAAO,KAAK,OAAO,iBAAiB,CAAC,KAAa,EAAG,GAAG,CAAE,CAAC,GAEnG,UAAY;GAChB,EAAS;IAAE,OAAO;IAAG,OAAO;GAAE,CAAC;GAC/B,IAAM,IAAQ,EAAM,IAAI,QAAQ,IAC5B,IAAI,GACF,UAAa;IAGjB,AAFA,KACA,EAAS;KAAE,OAAO;KAAG,OAAO;IAAE,CAAC,GAC3B,IAAI,EAAM,SAAQ,EAAM,GAAM,KAAK,KAAK,OAAO,IAAI,EAAE,IACpD,QAAY,EAAM,CAAC,GAAG,GAAG;GAChC,GACM,KAAS,MAAc;IAC3B,IAAI,KAAK,EAAM,QAAQ;KACrB,EAAM,GAAK,CAAS;KACpB;IACF;IAEA,AADA,EAAS;KAAE,OAAO,IAAI;KAAG,OAAO,EAAM;IAAO,CAAC,GAC9C,QAAY,EAAM,IAAI,CAAC,GAAG,MAAM,KAAK,OAAO,IAAI,GAAG;GACrD;GACA,EAAK;EACP;EAEA,OADA,EAAM,GAAK,IAAI,SACF;GAEX,AADA,IAAY,IACZ,EAAO,QAAQ,YAAY;EAC7B;CACF,GAAG;EAAC;EAAO;EAAS;CAAS,CAAC;CAE9B,IAAM,IAAS,EAAM,SAAS,EAAM,IAAI,KAAK,UAAU;CACvD,OACE,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAM,MAAK;EAAM,cAAY,GAAG,EAAM,IAAI,EAAM,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;YAAnG,CACG,EAAM,MAAM,GAAG,EAAM,KAAK,EAAE,KAAK,GAAG,MACnC,kBAAC,OAAD;GAAa,WAAW,CAAC,EAAO,MAAM,EAAE,QAAQ,EAAO,EAAE,KAAK,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;aAAxF,CACG,MAAM,IAAI,EAAE,KAAK,MAAM,GAAG,EAAM,KAAK,IAAI,EAAE,MAC3C,MAAM,KAAK,KAAU,kBAAC,QAAD,EAAM,WAAW,EAAO,MAAQ,CAAA,CACnD;KAHK,CAGL,CACN,GACA,CAAC,KAAU,kBAAC,QAAD,EAAM,WAAW,EAAO,MAAQ,CAAA,CACzC;;AAET"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
var e = {
|
|
2
|
+
term: "r9-Terminal-module_term_MpeFz",
|
|
3
|
+
line: "r9-Terminal-module_line_moKRF",
|
|
4
|
+
cmd: "r9-Terminal-module_cmd_-e6US",
|
|
5
|
+
dim: "r9-Terminal-module_dim_8Upay",
|
|
6
|
+
ok: "r9-Terminal-module_ok_v-BTE",
|
|
7
|
+
caret: "r9-Terminal-module_caret_yU4Ba"
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { e as default };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=Terminal.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Terminal.module.js","names":[],"sources":["../../src/motion/Terminal.module.scss"],"sourcesContent":["@use \"@rnine/nineos/scss\" as r9;\n\n.term {\n @include r9.type(code);\n @include r9.corner(md);\n @include r9.hairline;\n background: r9.$surface;\n padding: r9.$space-5;\n min-height: 12.5rem;\n overflow-x: auto;\n}\n\n.line { white-space: pre; }\n\n// The command is the terminal's magenta mark\n.cmd::before { content: \"$ \"; color: r9.$accent-text; }\n.dim { color: r9.$fg-muted; }\n.ok { color: r9.$signal-text; }\n\n.caret {\n display: inline-block;\n width: 8px;\n height: 1.05em;\n vertical-align: -0.15em;\n background: r9.$accent;\n @include r9.motion-safe { animation: r9.keyframes(blink) 1s steps(1) infinite; }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import e from "./VelocityStrip.module.js";
|
|
2
|
+
import { useEffect as t, useRef as n } from "react";
|
|
3
|
+
import { useReducedMotion as r } from "@rnine/core/motion";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
//#region src/motion/VelocityStrip.tsx
|
|
6
|
+
function o({ items: o, label: s }) {
|
|
7
|
+
let c = n(null), l = r();
|
|
8
|
+
t(() => {
|
|
9
|
+
let e = c.current;
|
|
10
|
+
if (!e || l) return;
|
|
11
|
+
let t = 0, n = 0, r = 1, i = window.scrollY, a = 0, o = () => {
|
|
12
|
+
let e = window.scrollY, t = e - i;
|
|
13
|
+
i = e, t && (r = t > 0 ? 1 : -1), n = Math.min(40, n + Math.abs(t) * .6);
|
|
14
|
+
}, s = () => {
|
|
15
|
+
n *= .92, t -= (.35 + n * .25) * r;
|
|
16
|
+
let i = e.scrollWidth / 3;
|
|
17
|
+
t < -i && (t += i), t > 0 && (t -= i), e.style.transform = `translateX(${t.toFixed(1)}px)`, a = requestAnimationFrame(s);
|
|
18
|
+
};
|
|
19
|
+
return window.addEventListener("scroll", o, { passive: !0 }), a = requestAnimationFrame(s), () => {
|
|
20
|
+
window.removeEventListener("scroll", o), cancelAnimationFrame(a), e.style.transform = "";
|
|
21
|
+
};
|
|
22
|
+
}, [l]);
|
|
23
|
+
let u = o.flatMap((e) => [e, "/"]);
|
|
24
|
+
return /* @__PURE__ */ a("div", {
|
|
25
|
+
className: e.strip,
|
|
26
|
+
"aria-label": s,
|
|
27
|
+
role: "note",
|
|
28
|
+
children: [/* @__PURE__ */ i("div", {
|
|
29
|
+
ref: c,
|
|
30
|
+
className: e.track,
|
|
31
|
+
"aria-hidden": "true",
|
|
32
|
+
children: [
|
|
33
|
+
0,
|
|
34
|
+
1,
|
|
35
|
+
2
|
|
36
|
+
].flatMap((e) => u.map((t, n) => t === "/" ? /* @__PURE__ */ i("i", { children: "/" }, `${e}-${n}`) : /* @__PURE__ */ i("span", { children: t }, `${e}-${n}`)))
|
|
37
|
+
}), /* @__PURE__ */ i("span", {
|
|
38
|
+
className: e.sr,
|
|
39
|
+
children: o.join(", ")
|
|
40
|
+
})]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { o as VelocityStrip };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=VelocityStrip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VelocityStrip.js","names":[],"sources":["../../src/motion/VelocityStrip.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useReducedMotion } from '@rnine/core/motion';\nimport styles from './VelocityStrip.module.scss';\n\ntype Props = { items: string[]; label: string };\n\n/** A line of words that drifts, speeds up with scrolling and follows its direction. */\nexport function VelocityStrip({ items, label }: Props) {\n const track = useRef<HTMLDivElement>(null);\n const reduced = useReducedMotion();\n\n useEffect(() => {\n const el = track.current;\n if (!el || reduced) return;\n let offset = 0;\n let velocity = 0;\n let direction = 1;\n let lastY = window.scrollY;\n let raf = 0;\n\n const onScroll = () => {\n const y = window.scrollY;\n const dy = y - lastY;\n lastY = y;\n if (dy) direction = dy > 0 ? 1 : -1;\n velocity = Math.min(40, velocity + Math.abs(dy) * 0.6);\n };\n const loop = () => {\n velocity *= 0.92;\n offset -= (0.35 + velocity * 0.25) * direction;\n const third = el.scrollWidth / 3;\n if (offset < -third) offset += third;\n if (offset > 0) offset -= third;\n el.style.transform = `translateX(${offset.toFixed(1)}px)`;\n raf = requestAnimationFrame(loop);\n };\n window.addEventListener('scroll', onScroll, { passive: true });\n raf = requestAnimationFrame(loop);\n return () => {\n window.removeEventListener('scroll', onScroll);\n cancelAnimationFrame(raf);\n el.style.transform = '';\n };\n }, [reduced]);\n\n const run = items.flatMap((w) => [w, '/']);\n return (\n <div className={styles.strip} aria-label={label} role=\"note\">\n <div ref={track} className={styles.track} aria-hidden=\"true\">\n {[0, 1, 2].flatMap((copy) =>\n run.map((w, i) =>\n w === '/' ? (\n <i key={`${copy}-${i}`}>/</i>\n ) : (\n <span key={`${copy}-${i}`}>{w}</span>\n ),\n ),\n )}\n </div>\n <span className={styles.sr}>{items.join(', ')}</span>\n </div>\n );\n}\n"],"mappings":";;;;;AAOA,SAAgB,EAAc,EAAE,UAAO,YAAgB;CACrD,IAAM,IAAQ,EAAuB,IAAI,GACnC,IAAU,EAAiB;CAEjC,QAAgB;EACd,IAAM,IAAK,EAAM;EACjB,IAAI,CAAC,KAAM,GAAS;EACpB,IAAI,IAAS,GACT,IAAW,GACX,IAAY,GACZ,IAAQ,OAAO,SACf,IAAM,GAEJ,UAAiB;GACrB,IAAM,IAAI,OAAO,SACX,IAAK,IAAI;GAGf,AAFA,IAAQ,GACJ,MAAI,IAAY,IAAK,IAAI,IAAI,KACjC,IAAW,KAAK,IAAI,IAAI,IAAW,KAAK,IAAI,CAAE,IAAI,EAAG;EACvD,GACM,UAAa;GAEjB,AADA,KAAY,KACZ,MAAW,MAAO,IAAW,OAAQ;GACrC,IAAM,IAAQ,EAAG,cAAc;GAI/B,AAHI,IAAS,CAAC,MAAO,KAAU,IAC3B,IAAS,MAAG,KAAU,IAC1B,EAAG,MAAM,YAAY,cAAc,EAAO,QAAQ,CAAC,EAAE,MACrD,IAAM,sBAAsB,CAAI;EAClC;EAGA,OAFA,OAAO,iBAAiB,UAAU,GAAU,EAAE,SAAS,GAAK,CAAC,GAC7D,IAAM,sBAAsB,CAAI,SACnB;GAGX,AAFA,OAAO,oBAAoB,UAAU,CAAQ,GAC7C,qBAAqB,CAAG,GACxB,EAAG,MAAM,YAAY;EACvB;CACF,GAAG,CAAC,CAAO,CAAC;CAEZ,IAAM,IAAM,EAAM,SAAS,MAAM,CAAC,GAAG,GAAG,CAAC;CACzC,OACE,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAO,cAAY;EAAO,MAAK;YAAtD,CACE,kBAAC,OAAD;GAAK,KAAK;GAAO,WAAW,EAAO;GAAO,eAAY;aACnD;IAAC;IAAG;IAAG;GAAC,EAAE,SAAS,MAClB,EAAI,KAAK,GAAG,MACV,MAAM,MACJ,kBAAC,KAAD,EAAA,UAAwB,IAAI,GAApB,GAAG,EAAK,GAAG,GAAS,IAE5B,kBAAC,QAAD,EAAA,UAA4B,EAAQ,GAAzB,GAAG,EAAK,GAAG,GAAc,CAExC,CACF;EACG,CAAA,GACL,kBAAC,QAAD;GAAM,WAAW,EAAO;aAAK,EAAM,KAAK,IAAI;EAAQ,CAAA,CACjD;;AAET"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VelocityStrip.module.js","names":[],"sources":["../../src/motion/VelocityStrip.module.scss"],"sourcesContent":["@use \"@rnine/nineos/scss\" as r9;\n\n.strip {\n // Positioned, so the screen-reader text stays inside instead of at the track's far end\n position: relative;\n overflow: hidden;\n white-space: nowrap;\n padding-block: r9.$space-4;\n border-block: r9.$border-hair solid r9.$line;\n}\n\n.track {\n display: inline-flex;\n will-change: transform;\n\n span {\n font: r9.$font-weight-medium 0.9375rem / 1 r9.$font-display;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: r9.$fg-muted;\n }\n\n // Separators: the strip's magenta mark\n i {\n font-style: normal;\n color: r9.$accent-text;\n padding-inline: 1.1em;\n }\n}\n\n.sr {\n @include r9.visually-hidden;\n top: 0;\n left: 0;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Duration of the exit, in ms. The next panel enters after it. */
|
|
2
|
+
export declare const SWAP_OUT_MS = 220;
|
|
3
|
+
/** Duration of the entrance reveal, in ms. */
|
|
4
|
+
export declare const SWAP_IN_MS = 500;
|
|
5
|
+
/**
|
|
6
|
+
* Exit: a quick cyan/magenta RGB split while the element slides 6px and
|
|
7
|
+
* fades. Never scales. Resolves when it has finished (at once without WAAPI).
|
|
8
|
+
*/
|
|
9
|
+
export declare function swapOut(el: HTMLElement): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Entrance: the element is revealed top-down (its clip-path follows the
|
|
12
|
+
* NineOS card cut), its children rise 8px in a short stagger, and an
|
|
13
|
+
* optional scan bar runs down with the reveal.
|
|
14
|
+
*/
|
|
15
|
+
export declare function swapIn(el: HTMLElement, scanbar?: HTMLElement | null): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/motion/signal.ts
|
|
2
|
+
var e = "cubic-bezier(.2,.7,.2,1)", t = 220, n = 500;
|
|
3
|
+
function r(e) {
|
|
4
|
+
return getComputedStyle(document.documentElement).getPropertyValue(e).trim();
|
|
5
|
+
}
|
|
6
|
+
function i(e) {
|
|
7
|
+
return typeof e.animate == "function";
|
|
8
|
+
}
|
|
9
|
+
function a(t) {
|
|
10
|
+
if (!i(t)) return Promise.resolve();
|
|
11
|
+
let n = r("--r9-signal"), a = r("--r9-accent"), o = t.animate([
|
|
12
|
+
{
|
|
13
|
+
opacity: 1,
|
|
14
|
+
transform: "translateX(0)",
|
|
15
|
+
filter: "none"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
offset: .35,
|
|
19
|
+
opacity: 1,
|
|
20
|
+
transform: "translateX(3px)",
|
|
21
|
+
filter: `drop-shadow(-3px 0 ${n}) drop-shadow(3px 0 ${a})`
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
opacity: 0,
|
|
25
|
+
transform: "translateX(-6px)",
|
|
26
|
+
filter: `drop-shadow(-6px 0 ${n}) drop-shadow(6px 0 ${a})`
|
|
27
|
+
}
|
|
28
|
+
], {
|
|
29
|
+
duration: 220,
|
|
30
|
+
easing: e,
|
|
31
|
+
fill: "forwards"
|
|
32
|
+
});
|
|
33
|
+
return new Promise((e) => {
|
|
34
|
+
o.onfinish = () => e(), o.oncancel = () => e();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function o(t, n) {
|
|
38
|
+
if (i(t) && (t.animate([{ clipPath: "polygon(0 0, 100% 0, 100% 0, 0 0)" }, { clipPath: "polygon(0 0, 100% 0, 100% 100%, 0 100%)" }], {
|
|
39
|
+
duration: 500,
|
|
40
|
+
easing: e
|
|
41
|
+
}), Array.from(t.children).forEach((t, n) => {
|
|
42
|
+
i(t) && t.animate([{
|
|
43
|
+
opacity: 0,
|
|
44
|
+
transform: "translateY(8px)"
|
|
45
|
+
}, {
|
|
46
|
+
opacity: 1,
|
|
47
|
+
transform: "translateY(0)"
|
|
48
|
+
}], {
|
|
49
|
+
duration: 450,
|
|
50
|
+
delay: Math.min(n, 4) * 60,
|
|
51
|
+
easing: e,
|
|
52
|
+
fill: "backwards"
|
|
53
|
+
});
|
|
54
|
+
}), n && i(n))) {
|
|
55
|
+
let t = n.parentElement?.getBoundingClientRect().height ?? 0;
|
|
56
|
+
n.animate([
|
|
57
|
+
{
|
|
58
|
+
transform: "translateY(0)",
|
|
59
|
+
opacity: 1
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
offset: .9,
|
|
63
|
+
opacity: 1
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
transform: `translateY(${t}px)`,
|
|
67
|
+
opacity: 0
|
|
68
|
+
}
|
|
69
|
+
], {
|
|
70
|
+
duration: 500,
|
|
71
|
+
easing: e
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { n as SWAP_IN_MS, t as SWAP_OUT_MS, o as swapIn, a as swapOut };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.js","names":[],"sources":["../../src/motion/signal.ts"],"sourcesContent":["// \"Signal swap\": NineOS's panel swap (swapIn/swapOut). Runs with the Web\n// Animations API, so nothing (classes, inline styles) is left on the element\n// when it ends. Both helpers are no-ops where element.animate is missing.\n\nconst EASE = 'cubic-bezier(.2,.7,.2,1)';\n\n/** Duration of the exit, in ms. The next panel enters after it. */\nexport const SWAP_OUT_MS = 220;\n/** Duration of the entrance reveal, in ms. */\nexport const SWAP_IN_MS = 500;\n\nfunction token(name: string): string {\n return getComputedStyle(document.documentElement).getPropertyValue(name).trim();\n}\n\nfunction canAnimate(el: Element): boolean {\n return typeof (el as HTMLElement).animate === 'function';\n}\n\n/**\n * Exit: a quick cyan/magenta RGB split while the element slides 6px and\n * fades. Never scales. Resolves when it has finished (at once without WAAPI).\n */\nexport function swapOut(el: HTMLElement): Promise<void> {\n if (!canAnimate(el)) return Promise.resolve();\n const signal = token('--r9-signal');\n const accent = token('--r9-accent');\n const animation = el.animate(\n [\n { opacity: 1, transform: 'translateX(0)', filter: 'none' },\n { offset: 0.35, opacity: 1, transform: 'translateX(3px)', filter: `drop-shadow(-3px 0 ${signal}) drop-shadow(3px 0 ${accent})` },\n { opacity: 0, transform: 'translateX(-6px)', filter: `drop-shadow(-6px 0 ${signal}) drop-shadow(6px 0 ${accent})` },\n ],\n { duration: SWAP_OUT_MS, easing: EASE, fill: 'forwards' },\n );\n return new Promise((resolve) => {\n animation.onfinish = () => resolve();\n animation.oncancel = () => resolve();\n });\n}\n\n/**\n * Entrance: the element is revealed top-down (its clip-path follows the\n * NineOS card cut), its children rise 8px in a short stagger, and an\n * optional scan bar runs down with the reveal.\n */\nexport function swapIn(el: HTMLElement, scanbar?: HTMLElement | null): void {\n if (!canAnimate(el)) return;\n el.animate(\n [{ clipPath: 'polygon(0 0, 100% 0, 100% 0, 0 0)' }, { clipPath: 'polygon(0 0, 100% 0, 100% 100%, 0 100%)' }],\n { duration: SWAP_IN_MS, easing: EASE },\n );\n Array.from(el.children).forEach((child, i) => {\n if (!canAnimate(child)) return;\n (child as HTMLElement).animate(\n [{ opacity: 0, transform: 'translateY(8px)' }, { opacity: 1, transform: 'translateY(0)' }],\n { duration: 450, delay: Math.min(i, 4) * 60, easing: EASE, fill: 'backwards' },\n );\n });\n if (scanbar && canAnimate(scanbar)) {\n // Down the stage with transform (not top): no layout work, no layout shift\n const height = scanbar.parentElement?.getBoundingClientRect().height ?? 0;\n scanbar.animate(\n [{ transform: 'translateY(0)', opacity: 1 }, { offset: 0.9, opacity: 1 }, { transform: `translateY(${height}px)`, opacity: 0 }],\n { duration: SWAP_IN_MS, easing: EASE },\n );\n }\n}\n"],"mappings":";AAIA,IAAM,IAAO,4BAGA,IAAc,KAEd,IAAa;AAE1B,SAAS,EAAM,GAAsB;CACnC,OAAO,iBAAiB,SAAS,eAAe,EAAE,iBAAiB,CAAI,EAAE,KAAK;AAChF;AAEA,SAAS,EAAW,GAAsB;CACxC,OAAO,OAAQ,EAAmB,WAAY;AAChD;AAMA,SAAgB,EAAQ,GAAgC;CACtD,IAAI,CAAC,EAAW,CAAE,GAAG,OAAO,QAAQ,QAAQ;CAC5C,IAAM,IAAS,EAAM,aAAa,GAC5B,IAAS,EAAM,aAAa,GAC5B,IAAY,EAAG,QACnB;EACE;GAAE,SAAS;GAAG,WAAW;GAAiB,QAAQ;EAAO;EACzD;GAAE,QAAQ;GAAM,SAAS;GAAG,WAAW;GAAmB,QAAQ,sBAAsB,EAAO,sBAAsB,EAAO;EAAG;EAC/H;GAAE,SAAS;GAAG,WAAW;GAAoB,QAAQ,sBAAsB,EAAO,sBAAsB,EAAO;EAAG;CACpH,GACA;EAAE,UAAA;EAAuB,QAAQ;EAAM,MAAM;CAAW,CAC1D;CACA,OAAO,IAAI,SAAS,MAAY;EAE9B,AADA,EAAU,iBAAiB,EAAQ,GACnC,EAAU,iBAAiB,EAAQ;CACrC,CAAC;AACH;AAOA,SAAgB,EAAO,GAAiB,GAAoC;CACrE,MAAW,CAAE,MAClB,EAAG,QACD,CAAC,EAAE,UAAU,oCAAoC,GAAG,EAAE,UAAU,0CAA0C,CAAC,GAC3G;EAAE,UAAA;EAAsB,QAAQ;CAAK,CACvC,GACA,MAAM,KAAK,EAAG,QAAQ,EAAE,SAAS,GAAO,MAAM;EACvC,EAAW,CAAK,KACrB,EAAuB,QACrB,CAAC;GAAE,SAAS;GAAG,WAAW;EAAkB,GAAG;GAAE,SAAS;GAAG,WAAW;EAAgB,CAAC,GACzF;GAAE,UAAU;GAAK,OAAO,KAAK,IAAI,GAAG,CAAC,IAAI;GAAI,QAAQ;GAAM,MAAM;EAAY,CAC/E;CACF,CAAC,GACG,KAAW,EAAW,CAAO,IAAG;EAElC,IAAM,IAAS,EAAQ,eAAe,sBAAsB,EAAE,UAAU;EACxE,EAAQ,QACN;GAAC;IAAE,WAAW;IAAiB,SAAS;GAAE;GAAG;IAAE,QAAQ;IAAK,SAAS;GAAE;GAAG;IAAE,WAAW,cAAc,EAAO;IAAM,SAAS;GAAE;EAAC,GAC9H;GAAE,UAAA;GAAsB,QAAQ;EAAK,CACvC;CACF;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type TextRevealControl } from '@rnine/core/motion';
|
|
2
|
+
/** Terminal symbols the letters decode from. */
|
|
3
|
+
export declare const DECRYPT_GLYPHS = "<>/\\[]{}=+*#_:;";
|
|
4
|
+
/** How long each glyph stays before changing, in ms (each letter on its own rhythm). */
|
|
5
|
+
export declare const DECRYPT_TICK = 70;
|
|
6
|
+
/**
|
|
7
|
+
* One frame of the effect: for each character of `text`, the glyph to draw
|
|
8
|
+
* over it, or null when it shows the real character. Pure and deterministic:
|
|
9
|
+
* the same text, progress and time always give the same frame. Each letter
|
|
10
|
+
* has its own glyph sequence and changes on its own offset rhythm, so
|
|
11
|
+
* neighbours never move in step.
|
|
12
|
+
*
|
|
13
|
+
* @param progress 0 (all scrambled) to 1 (fully decoded)
|
|
14
|
+
* @param elapsed milliseconds since the effect started
|
|
15
|
+
*/
|
|
16
|
+
export declare function decryptFrame(text: string, progress: number, elapsed: number): (string | null)[];
|
|
17
|
+
/**
|
|
18
|
+
* Drives NineOS's text reveal (the decrypt) by hand. Returns the current frame (null when the text is
|
|
19
|
+
* at rest) and a function that plays it again. Does nothing with reduced motion.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useTextReveal(text: string, duration?: number): TextRevealControl;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
|
|
2
|
+
import { useReducedMotion as i } from "@rnine/core/motion";
|
|
3
|
+
//#region src/motion/useDecrypt.ts
|
|
4
|
+
var a = "<>/\\[]{}=+*#_:;", o = .12, s = /[\p{L}\p{N}]/u;
|
|
5
|
+
function c(e, t) {
|
|
6
|
+
let n = Math.imul(e + 1, 374761393) + Math.imul(t + 1, 668265263) | 0;
|
|
7
|
+
return n = Math.imul(n ^ n >>> 13, 1274126177), (n ^ n >>> 16) >>> 0;
|
|
8
|
+
}
|
|
9
|
+
function l(e, t, n) {
|
|
10
|
+
let r = Array.from(e), i = r.length;
|
|
11
|
+
return r.map((e, r) => {
|
|
12
|
+
if (t >= 1 || !s.test(e) || t >= o + r / i * (1 - o)) return null;
|
|
13
|
+
let l = c(r, 999) % 70;
|
|
14
|
+
return a[c(r, Math.floor((n + l) / 70)) % 15];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function u(a, o = 1e3) {
|
|
18
|
+
let s = i(), [c, u] = r(null), d = n(0), f = e(() => {
|
|
19
|
+
if (s) return;
|
|
20
|
+
cancelAnimationFrame(d.current);
|
|
21
|
+
let e = 0, t = "", n = (r) => {
|
|
22
|
+
e ||= r;
|
|
23
|
+
let i = r - e, s = Math.min(1, i / o), c = s >= 1 ? null : l(a, s, i), f = c ? c.join("\0") : "";
|
|
24
|
+
(f !== t || !c) && (t = f, u(c)), s < 1 && (d.current = requestAnimationFrame(n));
|
|
25
|
+
};
|
|
26
|
+
d.current = requestAnimationFrame(n);
|
|
27
|
+
}, [
|
|
28
|
+
a,
|
|
29
|
+
o,
|
|
30
|
+
s
|
|
31
|
+
]);
|
|
32
|
+
t(() => () => cancelAnimationFrame(d.current), []);
|
|
33
|
+
let [p, m] = r({
|
|
34
|
+
text: a,
|
|
35
|
+
reduced: s
|
|
36
|
+
});
|
|
37
|
+
return (p.text !== a || p.reduced !== s) && (m({
|
|
38
|
+
text: a,
|
|
39
|
+
reduced: s
|
|
40
|
+
}), u(null)), [c, f];
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { u as useTextReveal };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=useDecrypt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDecrypt.js","names":[],"sources":["../../src/motion/useDecrypt.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useReducedMotion, type TextRevealControl } from '@rnine/core/motion';\n\n/** Terminal symbols the letters decode from. */\nexport const DECRYPT_GLYPHS = '<>/\\\\[]{}=+*#_:;';\n/** How long each glyph stays before changing, in ms (each letter on its own rhythm). */\nexport const DECRYPT_TICK = 70;\n/** Share of the duration where every letter is still scrambled, before the sweep. */\nconst HOLD = 0.12;\n/** Only letters and digits scramble; spaces and punctuation keep their place. */\nconst SCRAMBLES = /[\\p{L}\\p{N}]/u;\n\n/** Small integer hash: deterministic, without the visible repeats of a simple formula. */\nfunction hash(a: number, b: number): number {\n let x = (Math.imul(a + 1, 374761393) + Math.imul(b + 1, 668265263)) | 0;\n x = Math.imul(x ^ (x >>> 13), 1274126177);\n return (x ^ (x >>> 16)) >>> 0;\n}\n\n/**\n * One frame of the effect: for each character of `text`, the glyph to draw\n * over it, or null when it shows the real character. Pure and deterministic:\n * the same text, progress and time always give the same frame. Each letter\n * has its own glyph sequence and changes on its own offset rhythm, so\n * neighbours never move in step.\n *\n * @param progress 0 (all scrambled) to 1 (fully decoded)\n * @param elapsed milliseconds since the effect started\n */\nexport function decryptFrame(text: string, progress: number, elapsed: number): (string | null)[] {\n const chars = Array.from(text);\n const n = chars.length;\n return chars.map((c, i) => {\n if (progress >= 1 || !SCRAMBLES.test(c)) return null;\n const resolvesAt = HOLD + (i / n) * (1 - HOLD);\n if (progress >= resolvesAt) return null;\n const phase = hash(i, 999) % DECRYPT_TICK;\n const tick = Math.floor((elapsed + phase) / DECRYPT_TICK);\n return DECRYPT_GLYPHS[hash(i, tick) % DECRYPT_GLYPHS.length];\n });\n}\n\n/**\n * Drives NineOS's text reveal (the decrypt) by hand. Returns the current frame (null when the text is\n * at rest) and a function that plays it again. Does nothing with reduced motion.\n */\nexport function useTextReveal(text: string, duration = 1000): TextRevealControl {\n const reduced = useReducedMotion();\n const [frame, setFrame] = useState<(string | null)[] | null>(null);\n const raf = useRef(0);\n\n const play = useCallback(() => {\n if (reduced) return;\n cancelAnimationFrame(raf.current);\n let start = 0;\n let last = '';\n const step = (t: number) => {\n if (!start) start = t;\n const elapsed = t - start;\n const progress = Math.min(1, elapsed / duration);\n const next = progress >= 1 ? null : decryptFrame(text, progress, elapsed);\n // Re-render only when a glyph actually changed\n const key = next ? next.join('\\u0000') : '';\n if (key !== last || !next) {\n last = key;\n setFrame(next);\n }\n if (progress < 1) raf.current = requestAnimationFrame(step);\n };\n raf.current = requestAnimationFrame(step);\n }, [text, duration, reduced]);\n\n useEffect(() => () => cancelAnimationFrame(raf.current), []);\n\n // A new text, or reduced motion turning on, shows the final text at once\n const [prev, setPrev] = useState({ text, reduced });\n if (prev.text !== text || prev.reduced !== reduced) {\n setPrev({ text, reduced });\n setFrame(null);\n }\n\n return [frame, play] as const;\n}\n"],"mappings":";;;AAIA,IAAa,IAAiB,oBAIxB,IAAO,KAEP,IAAY;AAGlB,SAAS,EAAK,GAAW,GAAmB;CAC1C,IAAI,IAAK,KAAK,KAAK,IAAI,GAAG,SAAS,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,IAAK;CAEtE,OADA,IAAI,KAAK,KAAK,IAAK,MAAM,IAAK,UAAU,IAChC,IAAK,MAAM,QAAS;AAC9B;AAYA,SAAgB,EAAa,GAAc,GAAkB,GAAoC;CAC/F,IAAM,IAAQ,MAAM,KAAK,CAAI,GACvB,IAAI,EAAM;CAChB,OAAO,EAAM,KAAK,GAAG,MAAM;EAGzB,IAFI,KAAY,KAAK,CAAC,EAAU,KAAK,CAAC,KAElC,KADe,IAAQ,IAAI,KAAM,IAAI,IACb,OAAO;EACnC,IAAM,IAAQ,EAAK,GAAG,GAAG,IAAA;EAEzB,OAAO,EAAe,EAAK,GADd,KAAK,OAAO,IAAU,KAAA,EACL,CAAI,IAAI;CACxC,CAAC;AACH;AAMA,SAAgB,EAAc,GAAc,IAAW,KAAyB;CAC9E,IAAM,IAAU,EAAiB,GAC3B,CAAC,GAAO,KAAY,EAAmC,IAAI,GAC3D,IAAM,EAAO,CAAC,GAEd,IAAO,QAAkB;EAC7B,IAAI,GAAS;EACb,qBAAqB,EAAI,OAAO;EAChC,IAAI,IAAQ,GACR,IAAO,IACL,KAAQ,MAAc;GAC1B,AAAY,MAAQ;GACpB,IAAM,IAAU,IAAI,GACd,IAAW,KAAK,IAAI,GAAG,IAAU,CAAQ,GACzC,IAAO,KAAY,IAAI,OAAO,EAAa,GAAM,GAAU,CAAO,GAElE,IAAM,IAAO,EAAK,KAAK,IAAQ,IAAI;GAKzC,CAJI,MAAQ,KAAQ,CAAC,OACnB,IAAO,GACP,EAAS,CAAI,IAEX,IAAW,MAAG,EAAI,UAAU,sBAAsB,CAAI;EAC5D;EACA,EAAI,UAAU,sBAAsB,CAAI;CAC1C,GAAG;EAAC;EAAM;EAAU;CAAO,CAAC;CAE5B,cAAsB,qBAAqB,EAAI,OAAO,GAAG,CAAC,CAAC;CAG3D,IAAM,CAAC,GAAM,KAAW,EAAS;EAAE;EAAM;CAAQ,CAAC;CAMlD,QALI,EAAK,SAAS,KAAQ,EAAK,YAAY,OACzC,EAAQ;EAAE;EAAM;CAAQ,CAAC,GACzB,EAAS,IAAI,IAGR,CAAC,GAAO,CAAI;AACrB"}
|
package/dist/motion.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from '@rnine/core/motion';
|
|
2
|
+
export { TextReveal } from './motion/DecryptText';
|
|
3
|
+
export { useTextReveal } from './motion/useDecrypt';
|
|
4
|
+
export { Backdrop } from './motion/DotField';
|
|
5
|
+
export { Mark } from './motion/Monogram';
|
|
6
|
+
export { PageEffects } from './motion/PageEffects';
|
|
7
|
+
export { swapIn, swapOut, SWAP_IN_MS, SWAP_OUT_MS } from './motion/signal';
|
|
8
|
+
export { Terminal } from './motion/Terminal';
|
|
9
|
+
export type { TerminalLine } from './motion/Terminal';
|
|
10
|
+
export { VelocityStrip } from './motion/VelocityStrip';
|
package/dist/motion.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useTextReveal as e } from "./motion/useDecrypt.js";
|
|
2
|
+
import { TextReveal as t } from "./motion/DecryptText.js";
|
|
3
|
+
import { PageEffects as n } from "./motion/PageEffects.js";
|
|
4
|
+
import { Mark as r } from "./motion/Monogram.js";
|
|
5
|
+
import { SWAP_IN_MS as i, SWAP_OUT_MS as a, swapIn as o, swapOut as s } from "./motion/signal.js";
|
|
6
|
+
import { Backdrop as c } from "./motion/DotField.js";
|
|
7
|
+
import { Terminal as l } from "./motion/Terminal.js";
|
|
8
|
+
import { VelocityStrip as u } from "./motion/VelocityStrip.js";
|
|
9
|
+
export * from "@rnine/core/motion";
|
|
10
|
+
export { c as Backdrop, r as Mark, n as PageEffects, i as SWAP_IN_MS, a as SWAP_OUT_MS, l as Terminal, t as TextReveal, u as VelocityStrip, o as swapIn, s as swapOut, e as useTextReveal };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* NineOS's own keyframes. Generated from scss/base.scss. */
|
|
2
|
+
@keyframes r9-slice-a {
|
|
3
|
+
0% {
|
|
4
|
+
opacity: 1;
|
|
5
|
+
clip-path: inset(12% 0 68% 0);
|
|
6
|
+
transform: translateX(-4%);
|
|
7
|
+
}
|
|
8
|
+
25% {
|
|
9
|
+
clip-path: inset(52% 0 26% 0);
|
|
10
|
+
transform: translateX(3%);
|
|
11
|
+
}
|
|
12
|
+
50% {
|
|
13
|
+
clip-path: inset(30% 0 52% 0);
|
|
14
|
+
transform: translateX(-2%);
|
|
15
|
+
}
|
|
16
|
+
75% {
|
|
17
|
+
clip-path: inset(78% 0 6% 0);
|
|
18
|
+
transform: translateX(2%);
|
|
19
|
+
}
|
|
20
|
+
100% {
|
|
21
|
+
opacity: 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
@keyframes r9-slice-b {
|
|
25
|
+
0% {
|
|
26
|
+
opacity: 1;
|
|
27
|
+
clip-path: inset(62% 0 14% 0);
|
|
28
|
+
transform: translateX(4%);
|
|
29
|
+
}
|
|
30
|
+
33% {
|
|
31
|
+
clip-path: inset(18% 0 62% 0);
|
|
32
|
+
transform: translateX(-3%);
|
|
33
|
+
}
|
|
34
|
+
66% {
|
|
35
|
+
clip-path: inset(40% 0 42% 0);
|
|
36
|
+
transform: translateX(2%);
|
|
37
|
+
}
|
|
38
|
+
100% {
|
|
39
|
+
opacity: 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
@keyframes r9-drift {
|
|
43
|
+
to {
|
|
44
|
+
transform: translateY(3px);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
@keyframes r9-band {
|
|
48
|
+
0% {
|
|
49
|
+
transform: translateY(-140px);
|
|
50
|
+
}
|
|
51
|
+
60%, 100% {
|
|
52
|
+
transform: translateY(110vh);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ThemeConfig, type ThemeProviderProps } from '@rnine/core/kit';
|
|
2
|
+
import { type BootScriptOptions } from '@rnine/core';
|
|
3
|
+
/** NineOS: its moods (Clear and Cipher), Auto rule and effects. Matches theme.json (a test checks). */
|
|
4
|
+
export declare const NINEOS_THEME: ThemeConfig;
|
|
5
|
+
/**
|
|
6
|
+
* Wrap the app once. It keeps the mood on <html data-scheme="light|dark">
|
|
7
|
+
* (Clear and Cipher): the viewer's saved choice, or Clear by day and, at
|
|
8
|
+
* night, Cipher for a dark system setting (flipping at dusk, dawn, and when
|
|
9
|
+
* the system switches). See useMood. It also applies the viewer's saved
|
|
10
|
+
* motion setting (MotionToggle), mounts the page effects (Cipher scanlines,
|
|
11
|
+
* magenta click sparks) and gives every Mark the app's artwork.
|
|
12
|
+
*
|
|
13
|
+
* <ThemeProvider mark={{ full: markSvg }} mood={{ cookieDomain: 'example.com' }}>
|
|
14
|
+
* <App />
|
|
15
|
+
* </ThemeProvider>
|
|
16
|
+
*/
|
|
17
|
+
export declare function ThemeProvider(props: ThemeProviderProps): import("react").JSX.Element;
|
|
18
|
+
/**
|
|
19
|
+
* The script to paste into each app's index.html <head> as an inline
|
|
20
|
+
* <script>, so the first paint already has the right mood. NineOS's Auto rule
|
|
21
|
+
* (the night rule) is the default; pass the same storageKey as configureMood.
|
|
22
|
+
*/
|
|
23
|
+
export declare function moodBootScript(options?: BootScriptOptions): string;
|
|
24
|
+
/** moodBootScript() with the default storage key. (Apps' tests check their copy matches.) */
|
|
25
|
+
export declare const MOOD_BOOT_SCRIPT: string;
|