@walrosproject/ui 0.3.4 → 0.4.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/dist/index.js CHANGED
@@ -1,296 +1,6 @@
1
1
  // src/components/WalrosPortalTransition.tsx
2
- import {
3
- useEffect,
4
- useId,
5
- useLayoutEffect,
6
- useRef,
7
- useState
8
- } from "react";
9
- import { gsap } from "gsap";
10
- import { jsx, jsxs } from "react/jsx-runtime";
11
- var SILHOUETTE_VIEWBOX = 1254;
12
- var FOCUS_X = 626;
13
- var FOCUS_Y = 333;
14
- var PORTAL_RADIUS = 190;
15
- var WALROS_PORTAL_SILHOUETTE_URL = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgICAgdmlld0JveD0iMCAwIDEyNTQgMTI1NCIKICAgICB3aWR0aD0iMTI1NCIgaGVpZ2h0PSIxMjU0IgogICAgIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iU2lsdWV0YSBXYWxyT1MiPgogIDxnIGZpbGw9IiMwMDAwMDAiPgogICAgPHBhdGggZD0iTSAyNDQuMDAgMzU0LjAwIEMgMjQwLjUwIDM3Ny42NywgMjQwLjE3IDQxMy42NywgMjQyLjAwIDQzNC4wMCBDIDI0My44MyA0NTQuMzMsIDI0OS41MCA0NjQuNTAsIDI1NS4wMCA0NzYuMDAgQyAyNjAuNTAgNDg3LjUwLCAyNjguNTAgNDk2LjAwLCAyNzUuMDAgNTAzLjAwIEMgMjgxLjUwIDUxMC4wMCwgMjg3LjE3IDUxMy42NywgMjk0LjAwIDUxOC4wMCBDIDMwMC44MyA1MjIuMzMsIDMwNi4wMCA1MjUuNjcsIDMxNi4wMCA1MjkuMDAgQyAzMjYuMDAgNTMyLjMzLCAzMzYuMzMgNTM2LjY3LCAzNTQuMDAgNTM4LjAwIEMgMzcxLjY3IDUzOS4zMywgMzk3LjgzIDU0MC44MywgNDIyLjAwIDUzNy4wMCBDIDQ0Ni4xNyA1MzMuMTcsIDQ3NC4xNyA1MjUuMzMsIDQ5OS4wMCA1MTUuMDAgQyA1MjMuODMgNTA0LjY3LCA1NTAuMDAgNDg5LjMzLCA1NzEuMDAgNDc1LjAwIEMgNTkyLjAwIDQ2MC42NywgNjE1LjAwIDQzNi41MCwgNjI1LjAwIDQyOS4wMCBDIDYzNS4wMCA0MjEuNTAsIDYyMy42NyA0MjQuMDAsIDYzMS4wMCA0MzAuMDAgQyA2MzguMzMgNDM2LjAwLCA2NTYuNjcgNDU0LjgzLCA2NjkuMDAgNDY1LjAwIEMgNjgxLjMzIDQ3NS4xNywgNjkwLjAwIDQ4Mi4xNywgNzA1LjAwIDQ5MS4wMCBDIDcyMC4wMCA0OTkuODMsIDc0My42NyA1MTEuNTAsIDc1OS4wMCA1MTguMDAgQyA3NzQuMzMgNTI0LjUwLCA3ODQuMDAgNTI2LjgzLCA3OTcuMDAgNTMwLjAwIEMgODEwLjAwIDUzMy4xNywgODIwLjAwIDUzNS44MywgODM3LjAwIDUzNy4wMCBDIDg1NC4wMCA1MzguMTcsIDg4MS4xNyA1MzkuMDAsIDg5OS4wMCA1MzcuMDAgQyA5MTYuODMgNTM1LjAwLCA5MzIuMDAgNTMwLjAwLCA5NDQuMDAgNTI1LjAwIEMgOTU2LjAwIDUyMC4wMCwgOTYyLjAwIDUxNi4xNywgOTcxLjAwIDUwNy4wMCBDIDk4MC4wMCA0OTcuODMsIDk5MS42NyA0ODIuODMsIDk5OC4wMCA0NzAuMDAgQyAxMDA0LjMzIDQ1Ny4xNywgMTAwNy4xNyA0NDYuMzMsIDEwMDkuMDAgNDMwLjAwIEMgMTAxMC44MyA0MTMuNjcsIDEwMTAuODMgMzkwLjUwLCAxMDA5LjAwIDM3Mi4wMCBDIDEwMDcuMTcgMzUzLjUwLCAxMDAzLjgzIDMzNy4xNywgOTk4LjAwIDMxOS4wMCBDIDk5Mi4xNyAzMDAuODMsIDk4My42NyAyODAuMTcsIDk3NC4wMCAyNjMuMDAgQyA5NjQuMzMgMjQ1LjgzLCA5NTIuMDAgMjI5LjY3LCA5NDAuMDAgMjE2LjAwIEMgOTI4LjAwIDIwMi4zMywgOTE3LjE3IDE5Mi4wMCwgOTAyLjAwIDE4MS4wMCBDIDg4Ni44MyAxNzAuMDAsIDg2Ni41MCAxNTguMTcsIDg0OS4wMCAxNTAuMDAgQyA4MzEuNTAgMTQxLjgzLCA4MTYuNjcgMTM3LjAwLCA3OTcuMDAgMTMyLjAwIEMgNzc3LjMzIDEyNy4wMCwgNzU2LjY3IDEyMi44MywgNzMxLjAwIDEyMC4wMCBDIDcwNS4zMyAxMTcuMTcsIDY3My42NyAxMTUuMzMsIDY0My4wMCAxMTUuMDAgQyA2MTIuMzMgMTE0LjY3LCA1NzUuNjcgMTE1LjY3LCA1NDcuMDAgMTE4LjAwIEMgNTE4LjMzIDEyMC4zMywgNDk2LjE3IDEyMy4wMCwgNDcxLjAwIDEyOS4wMCBDIDQ0NS44MyAxMzUuMDAsIDQxOC4zMyAxNDMuNTAsIDM5Ni4wMCAxNTQuMDAgQyAzNzMuNjcgMTY0LjUwLCAzNTMuNTAgMTc4LjUwLCAzMzcuMDAgMTkyLjAwIEMgMzIwLjUwIDIwNS41MCwgMzA5LjMzIDIxOC4zMywgMjk3LjAwIDIzNS4wMCBDIDI4NC42NyAyNTEuNjcsIDI3MS44MyAyNzIuMTcsIDI2My4wMCAyOTIuMDAgQyAyNTQuMTcgMzExLjgzLCAyNDcuNTAgMzMwLjMzLCAyNDQuMDAgMzU0LjAwIFoiLz48cGF0aCBkPSJNIDI4OS4wMCA1MzYuMDAgQyAyODMuMTcgNTM1LjUwLCAyODQuODMgNTMzLjAwLCAyODQuMDAgNTQ0LjAwIEMgMjgzLjE3IDU1NS4wMCwgMjgyLjY3IDU3OC42NywgMjg0LjAwIDYwMi4wMCBDIDI4NS4zMyA2MjUuMzMsIDI4Ny4wMCA2NTEuODMsIDI5Mi4wMCA2ODQuMDAgQyAyOTcuMDAgNzE2LjE3LCAzMDYuMzMgNzYzLjUwLCAzMTQuMDAgNzk1LjAwIEMgMzIxLjY3IDgyNi41MCwgMzI4LjY3IDg0Ny4xNywgMzM4LjAwIDg3My4wMCBDIDM0Ny4zMyA4OTguODMsIDM1Ny4wMCA5MjMuNjcsIDM3MC4wMCA5NTAuMDAgQyAzODMuMDAgOTc2LjMzLCAzOTkuNjcgMTAwNi4wMCwgNDE2LjAwIDEwMzEuMDAgQyA0MzIuMzMgMTA1Ni4wMCwgNDU1LjMzIDEwODQuNTAsIDQ2OC4wMCAxMTAwLjAwIEMgNDgwLjY3IDExMTUuNTAsIDQ4Ni4xNyAxMTE5LjMzLCA0OTIuMDAgMTEyNC4wMCBDIDQ5Ny44MyAxMTI4LjY3LCA1MDAuNjcgMTEyNy42NywgNTAzLjAwIDExMjguMDAgQyA1MDUuMzMgMTEyOC4zMywgNTA1LjUwIDExMjcuNTAsIDUwNi4wMCAxMTI2LjAwIEMgNTA2LjUwIDExMjQuNTAsIDUxMC4zMyAxMTMyLjAwLCA1MDYuMDAgMTExOS4wMCBDIDUwMS42NyAxMTA2LjAwLCA0ODguMTcgMTA3My44MywgNDgwLjAwIDEwNDguMDAgQyA0NzEuODMgMTAyMi4xNywgNDYyLjY3IDk4Ny42NywgNDU3LjAwIDk2NC4wMCBDIDQ1MS4zMyA5NDAuMzMsIDQ0OS4xNyA5MjcuMzMsIDQ0Ni4wMCA5MDYuMDAgQyA0NDIuODMgODg0LjY3LCA0MzkuNTAgODY1LjAwLCA0MzguMDAgODM2LjAwIEMgNDM2LjUwIDgwNy4wMCwgNDM2LjUwIDc1OC4zMywgNDM3LjAwIDczMi4wMCBDIDQzNy41MCA3MDUuNjcsIDQzOS4wMCA2OTYuMzMsIDQ0MS4wMCA2NzguMDAgQyA0NDMuMDAgNjU5LjY3LCA0NDQuODMgNjQ0LjAwLCA0NDkuMDAgNjIyLjAwIEMgNDUzLjE3IDYwMC4wMCwgNDY0LjUwIDU1OS4wMCwgNDY2LjAwIDU0Ni4wMCBDIDQ2Ny41MCA1MzMuMDAsIDQ2OC42NyA1NDIuNTAsIDQ1OC4wMCA1NDQuMDAgQyA0NDcuMzMgNTQ1LjUwLCA0MTguMzMgNTUzLjE3LCA0MDIuMDAgNTU1LjAwIEMgMzg1LjY3IDU1Ni44MywgMzczLjgzIDU1Ni4zMywgMzYwLjAwIDU1NS4wMCBDIDM0Ni4xNyA1NTMuNjcsIDMzMC44MyA1NTAuMTcsIDMxOS4wMCA1NDcuMDAgQyAzMDcuMTcgNTQzLjgzLCAyOTQuODMgNTM2LjUwLCAyODkuMDAgNTM2LjAwIFoiLz48cGF0aCBkPSJNIDk2MS4wMCA1MzcuMDAgQyA5NDguNTAgNTM5LjE3LCA5MDcuODMgNTUyLjAwLCA4ODkuMDAgNTU1LjAwIEMgODcwLjE3IDU1OC4wMCwgODY0LjE3IDU1Ny4wMCwgODQ4LjAwIDU1NS4wMCBDIDgzMS44MyA1NTMuMDAsIDgwMi44MyA1NDUuMDAsIDc5Mi4wMCA1NDMuMDAgQyA3ODEuMTcgNTQxLjAwLCA3ODQuODMgNTQyLjUwLCA3ODMuMDAgNTQzLjAwIEMgNzgxLjE3IDU0My41MCwgNzc4LjY3IDUzMy44MywgNzgxLjAwIDU0Ni4wMCBDIDc4My4zMyA1NTguMTcsIDc5Mi42NyA1OTIuMzMsIDc5Ny4wMCA2MTYuMDAgQyA4MDEuMzMgNjM5LjY3LCA4MDQuODMgNjY4LjgzLCA4MDcuMDAgNjg4LjAwIEMgODA5LjE3IDcwNy4xNywgODA5LjY3IDcwOS4wMCwgODEwLjAwIDczMS4wMCBDIDgxMC4zMyA3NTMuMDAsIDgxMC41MCA3OTIuODMsIDgwOS4wMCA4MjAuMDAgQyA4MDcuNTAgODQ3LjE3LCA4MDQuNTAgODcwLjUwLCA4MDEuMDAgODk0LjAwIEMgNzk3LjUwIDkxNy41MCwgNzk0LjAwIDkzNi4xNywgNzg4LjAwIDk2MS4wMCBDIDc4Mi4wMCA5ODUuODMsIDc3My41MCAxMDE2LjY3LCA3NjUuMDAgMTA0My4wMCBDIDc1Ni41MCAxMDY5LjMzLCA3NDEuMzMgMTEwNC44MywgNzM3LjAwIDExMTkuMDAgQyA3MzIuNjcgMTEzMy4xNywgNzM2LjY3IDExMjcuMTcsIDczOS4wMCAxMTI4LjAwIEMgNzQxLjMzIDExMjguODMsIDc0NS44MyAxMTI3LjgzLCA3NTEuMDAgMTEyNC4wMCBDIDc1Ni4xNyAxMTIwLjE3LCA3NTcuMzMgMTEyMC41MCwgNzcwLjAwIDExMDUuMDAgQyA3ODIuNjcgMTA4OS41MCwgODExLjMzIDEwNTMuNTAsIDgyNy4wMCAxMDMxLjAwIEMgODQyLjY3IDEwMDguNTAsIDg1MS4xNyA5OTUuMDAsIDg2NC4wMCA5NzAuMDAgQyA4NzYuODMgOTQ1LjAwLCA4OTIuODMgOTEwLjAwLCA5MDQuMDAgODgxLjAwIEMgOTE1LjE3IDg1Mi4wMCwgOTIzLjAwIDgyNi42NywgOTMxLjAwIDc5Ni4wMCBDIDkzOS4wMCA3NjUuMzMsIDk0Ni42NyA3MjguNjcsIDk1Mi4wMCA2OTcuMDAgQyA5NTcuMzMgNjY1LjMzLCA5NjEuMDAgNjMxLjgzLCA5NjMuMDAgNjA2LjAwIEMgOTY1LjAwIDU4MC4xNywgOTY0LjMzIDU1My41MCwgOTY0LjAwIDU0Mi4wMCBDIDk2My42NyA1MzAuNTAsIDk3My41MCA1MzQuODMsIDk2MS4wMCA1MzcuMDAgWiIvPgogIDwvZz4KPC9zdmc+Cg==";
16
- function getStorage(mode) {
17
- if (typeof window === "undefined") return null;
18
- try {
19
- const storage = mode === "session" ? window.sessionStorage : window.localStorage;
20
- const testKey = "__walros_portal_test__";
21
- storage.setItem(testKey, "1");
22
- storage.removeItem(testKey);
23
- return storage;
24
- } catch {
25
- return null;
26
- }
27
- }
28
- function shouldRunTransition(enabled, storageMode, storageKey) {
29
- if (!enabled) return false;
30
- if (storageMode === "always") return true;
31
- if (typeof window === "undefined") return false;
32
- const storage = getStorage(storageMode);
33
- if (!storage) return true;
34
- try {
35
- return storage.getItem(storageKey) == null;
36
- } catch {
37
- return true;
38
- }
39
- }
40
- function WalrosPortalTransition({
41
- duration = 900,
42
- onComplete,
43
- backgroundColor = "#000000",
44
- enabled = true,
45
- storageMode = "always",
46
- storageKey = "walros-portal-seen",
47
- zIndex = 9999
48
- }) {
49
- const [shouldRun] = useState(
50
- () => shouldRunTransition(enabled, storageMode, storageKey)
51
- );
52
- const [visible, setVisible] = useState(shouldRun);
53
- const [size, setSize] = useState({ w: 0, h: 0 });
54
- const overlayRef = useRef(null);
55
- const maskScaleRef = useRef(null);
56
- const completedRef = useRef(false);
57
- const startedRef = useRef(false);
58
- const rafRef = useRef(null);
59
- const timelineRef = useRef(null);
60
- const previousOverflowRef = useRef("");
61
- const uid = useId().replace(/[:]/g, "_");
62
- const maskId = `walros-portal-mask-${uid}`;
63
- useEffect(() => {
64
- if (!visible) return;
65
- const element = overlayRef.current;
66
- if (!element) return;
67
- const measure = () => {
68
- const rect = element.getBoundingClientRect();
69
- setSize(
70
- (previous) => previous.w === rect.width && previous.h === rect.height ? previous : { w: rect.width, h: rect.height }
71
- );
72
- };
73
- measure();
74
- const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(measure) : null;
75
- observer?.observe(element);
76
- window.addEventListener("resize", measure);
77
- return () => {
78
- observer?.disconnect();
79
- window.removeEventListener("resize", measure);
80
- };
81
- }, [visible]);
82
- useLayoutEffect(() => {
83
- if (!enabled) {
84
- setVisible(false);
85
- return;
86
- }
87
- if (!visible) return;
88
- if (completedRef.current || startedRef.current) return;
89
- if (size.w <= 0 || size.h <= 0) return;
90
- const overlay = overlayRef.current;
91
- const maskScale = maskScaleRef.current;
92
- if (!overlay || !maskScale) return;
93
- startedRef.current = true;
94
- previousOverflowRef.current = document.body.style.overflow;
95
- document.body.style.overflow = "hidden";
96
- const finish = () => {
97
- if (completedRef.current) return;
98
- completedRef.current = true;
99
- document.body.style.overflow = previousOverflowRef.current;
100
- if (storageMode !== "always") {
101
- const storage = getStorage(storageMode);
102
- try {
103
- storage?.setItem(storageKey, "1");
104
- } catch {
105
- }
106
- }
107
- setVisible(false);
108
- onComplete?.();
109
- };
110
- const reducedMotion = window.matchMedia(
111
- "(prefers-reduced-motion: reduce)"
112
- ).matches;
113
- if (reducedMotion) {
114
- const timeline = gsap.timeline({ onComplete: finish });
115
- timelineRef.current = timeline;
116
- timeline.to(overlay, {
117
- opacity: 0,
118
- duration: 0.15,
119
- ease: "power1.out"
120
- });
121
- return;
122
- }
123
- rafRef.current = requestAnimationFrame(() => {
124
- rafRef.current = null;
125
- const halfDiagonal = Math.sqrt(size.w * size.w + size.h * size.h) / 2;
126
- const initialPixels2 = Math.min(
127
- 180,
128
- Math.max(120, size.w * 0.12)
129
- );
130
- const initialScale2 = initialPixels2 / SILHOUETTE_VIEWBOX;
131
- const finalScale = (halfDiagonal + 80) / PORTAL_RADIUS * 1.15;
132
- gsap.set(maskScale, {
133
- svgOrigin: "0 0",
134
- scale: initialScale2
135
- });
136
- gsap.set(overlay, { opacity: 1 });
137
- const totalSeconds = duration / 1e3;
138
- const timeline = gsap.timeline({ onComplete: finish });
139
- timelineRef.current = timeline;
140
- timeline.to(
141
- maskScale,
142
- {
143
- scale: initialScale2 * 1.4,
144
- duration: totalSeconds * 0.15,
145
- ease: "power2.out"
146
- },
147
- 0
148
- );
149
- timeline.to(
150
- maskScale,
151
- {
152
- scale: finalScale,
153
- duration: totalSeconds * 0.85,
154
- ease: "power2.in"
155
- },
156
- totalSeconds * 0.15
157
- );
158
- timeline.to(
159
- overlay,
160
- {
161
- opacity: 0,
162
- duration: totalSeconds * 0.1,
163
- ease: "power2.out"
164
- },
165
- totalSeconds * 0.9
166
- );
167
- });
168
- }, [
169
- duration,
170
- enabled,
171
- onComplete,
172
- size,
173
- storageKey,
174
- storageMode,
175
- visible
176
- ]);
177
- useEffect(() => {
178
- return () => {
179
- if (rafRef.current !== null) {
180
- cancelAnimationFrame(rafRef.current);
181
- }
182
- timelineRef.current?.kill();
183
- if (startedRef.current && !completedRef.current) {
184
- document.body.style.overflow = previousOverflowRef.current;
185
- }
186
- };
187
- }, []);
188
- if (!shouldRun || !visible) return null;
189
- const { w, h } = size;
190
- const ready = w > 0 && h > 0;
191
- const viewportCenterX = w / 2;
192
- const viewportCenterY = h / 2;
193
- const initialPixels = Math.min(
194
- 180,
195
- Math.max(120, w * 0.12)
196
- );
197
- const initialScale = initialPixels / SILHOUETTE_VIEWBOX;
198
- const visualCenter = SILHOUETTE_VIEWBOX / 2;
199
- const initialOffsetX = (visualCenter - FOCUS_X) * initialScale;
200
- const initialOffsetY = (visualCenter - FOCUS_Y) * initialScale;
201
- const portalCenterX = viewportCenterX - initialOffsetX;
202
- const portalCenterY = viewportCenterY - initialOffsetY;
203
- return /* @__PURE__ */ jsx(
204
- "div",
205
- {
206
- ref: overlayRef,
207
- "aria-hidden": "true",
208
- style: {
209
- position: "fixed",
210
- inset: 0,
211
- width: "100vw",
212
- height: "100dvh",
213
- zIndex,
214
- pointerEvents: "none",
215
- overflow: "hidden",
216
- backgroundColor: ready ? "transparent" : backgroundColor
217
- },
218
- children: ready && /* @__PURE__ */ jsxs(
219
- "svg",
220
- {
221
- width: "100%",
222
- height: "100%",
223
- viewBox: `0 0 ${w} ${h}`,
224
- preserveAspectRatio: "none",
225
- style: {
226
- position: "absolute",
227
- inset: 0,
228
- display: "block"
229
- },
230
- children: [
231
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
232
- "mask",
233
- {
234
- id: maskId,
235
- maskUnits: "userSpaceOnUse",
236
- x: "0",
237
- y: "0",
238
- width: w,
239
- height: h,
240
- children: [
241
- /* @__PURE__ */ jsx(
242
- "rect",
243
- {
244
- x: "0",
245
- y: "0",
246
- width: w,
247
- height: h,
248
- fill: "white"
249
- }
250
- ),
251
- /* @__PURE__ */ jsx(
252
- "g",
253
- {
254
- transform: `translate(${portalCenterX} ${portalCenterY})`,
255
- children: /* @__PURE__ */ jsx(
256
- "g",
257
- {
258
- ref: maskScaleRef,
259
- transform: `scale(${initialScale})`,
260
- children: /* @__PURE__ */ jsx(
261
- "image",
262
- {
263
- href: WALROS_PORTAL_SILHOUETTE_URL,
264
- x: -FOCUS_X,
265
- y: -FOCUS_Y,
266
- width: SILHOUETTE_VIEWBOX,
267
- height: SILHOUETTE_VIEWBOX
268
- }
269
- )
270
- }
271
- )
272
- }
273
- )
274
- ]
275
- }
276
- ) }),
277
- /* @__PURE__ */ jsx(
278
- "rect",
279
- {
280
- x: "0",
281
- y: "0",
282
- width: w,
283
- height: h,
284
- fill: backgroundColor,
285
- mask: `url(#${maskId})`
286
- }
287
- )
288
- ]
289
- }
290
- )
291
- }
292
- );
293
- }
2
+ import { useState as useState4 } from "react";
3
+ import { createPortal as createPortal2 } from "react-dom";
294
4
 
295
5
  // src/assets.ts
296
6
  var walrosPortalSilhouetteUrl = new URL(
@@ -314,46 +24,133 @@ var walrosThemeGlitchSoundUrl = new URL(
314
24
  import.meta.url
315
25
  ).href;
316
26
 
317
- // src/components/WalrosLogo.tsx
318
- import { jsx as jsx2 } from "react/jsx-runtime";
319
- function WalrosLogo({
320
- alt = "WalrOS",
321
- draggable = false,
322
- ...props
323
- }) {
324
- return /* @__PURE__ */ jsx2(
325
- "img",
326
- {
327
- src: walrosLogoChromeUrl,
328
- alt,
329
- draggable,
330
- ...props
331
- }
332
- );
333
- }
27
+ // src/components/WalrosThemeTransition.tsx
28
+ import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect3, useRef as useRef2, useState as useState3 } from "react";
29
+ import { createPortal } from "react-dom";
334
30
 
335
- // src/components/WalrosWordmark.tsx
336
- import { jsx as jsx3 } from "react/jsx-runtime";
337
- function WalrosWordmark({
338
- variant = "light",
339
- alt = "WalrOS",
340
- draggable = false,
341
- ...props
31
+ // src/components/Walros3DPortalTransition.tsx
32
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
33
+ import gsap from "gsap";
34
+
35
+ // src/portalSilhouette.ts
36
+ var walrosPortalPaths = [
37
+ "M 244.00 354.00 C 240.50 377.67, 240.17 413.67, 242.00 434.00 C 243.83 454.33, 249.50 464.50, 255.00 476.00 C 260.50 487.50, 268.50 496.00, 275.00 503.00 C 281.50 510.00, 287.17 513.67, 294.00 518.00 C 300.83 522.33, 306.00 525.67, 316.00 529.00 C 326.00 532.33, 336.33 536.67, 354.00 538.00 C 371.67 539.33, 397.83 540.83, 422.00 537.00 C 446.17 533.17, 474.17 525.33, 499.00 515.00 C 523.83 504.67, 550.00 489.33, 571.00 475.00 C 592.00 460.67, 615.00 436.50, 625.00 429.00 C 635.00 421.50, 623.67 424.00, 631.00 430.00 C 638.33 436.00, 656.67 454.83, 669.00 465.00 C 681.33 475.17, 690.00 482.17, 705.00 491.00 C 720.00 499.83, 743.67 511.50, 759.00 518.00 C 774.33 524.50, 784.00 526.83, 797.00 530.00 C 810.00 533.17, 820.00 535.83, 837.00 537.00 C 854.00 538.17, 881.17 539.00, 899.00 537.00 C 916.83 535.00, 932.00 530.00, 944.00 525.00 C 956.00 520.00, 962.00 516.17, 971.00 507.00 C 980.00 497.83, 991.67 482.83, 998.00 470.00 C 1004.33 457.17, 1007.17 446.33, 1009.00 430.00 C 1010.83 413.67, 1010.83 390.50, 1009.00 372.00 C 1007.17 353.50, 1003.83 337.17, 998.00 319.00 C 992.17 300.83, 983.67 280.17, 974.00 263.00 C 964.33 245.83, 952.00 229.67, 940.00 216.00 C 928.00 202.33, 917.17 192.00, 902.00 181.00 C 886.83 170.00, 866.50 158.17, 849.00 150.00 C 831.50 141.83, 816.67 137.00, 797.00 132.00 C 777.33 127.00, 756.67 122.83, 731.00 120.00 C 705.33 117.17, 673.67 115.33, 643.00 115.00 C 612.33 114.67, 575.67 115.67, 547.00 118.00 C 518.33 120.33, 496.17 123.00, 471.00 129.00 C 445.83 135.00, 418.33 143.50, 396.00 154.00 C 373.67 164.50, 353.50 178.50, 337.00 192.00 C 320.50 205.50, 309.33 218.33, 297.00 235.00 C 284.67 251.67, 271.83 272.17, 263.00 292.00 C 254.17 311.83, 247.50 330.33, 244.00 354.00 Z",
38
+ "M 289.00 536.00 C 283.17 535.50, 284.83 533.00, 284.00 544.00 C 283.17 555.00, 282.67 578.67, 284.00 602.00 C 285.33 625.33, 287.00 651.83, 292.00 684.00 C 297.00 716.17, 306.33 763.50, 314.00 795.00 C 321.67 826.50, 328.67 847.17, 338.00 873.00 C 347.33 898.83, 357.00 923.67, 370.00 950.00 C 383.00 976.33, 399.67 1006.00, 416.00 1031.00 C 432.33 1056.00, 455.33 1084.50, 468.00 1100.00 C 480.67 1115.50, 486.17 1119.33, 492.00 1124.00 C 497.83 1128.67, 500.67 1127.67, 503.00 1128.00 C 505.33 1128.33, 505.50 1127.50, 506.00 1126.00 C 506.50 1124.50, 510.33 1132.00, 506.00 1119.00 C 501.67 1106.00, 488.17 1073.83, 480.00 1048.00 C 471.83 1022.17, 462.67 987.67, 457.00 964.00 C 451.33 940.33, 449.17 927.33, 446.00 906.00 C 442.83 884.67, 439.50 865.00, 438.00 836.00 C 436.50 807.00, 436.50 758.33, 437.00 732.00 C 437.50 705.67, 439.00 696.33, 441.00 678.00 C 443.00 659.67, 444.83 644.00, 449.00 622.00 C 453.17 600.00, 464.50 559.00, 466.00 546.00 C 467.50 533.00, 468.67 542.50, 458.00 544.00 C 447.33 545.50, 418.33 553.17, 402.00 555.00 C 385.67 556.83, 373.83 556.33, 360.00 555.00 C 346.17 553.67, 330.83 550.17, 319.00 547.00 C 307.17 543.83, 294.83 536.50, 289.00 536.00 Z",
39
+ "M 961.00 537.00 C 948.50 539.17, 907.83 552.00, 889.00 555.00 C 870.17 558.00, 864.17 557.00, 848.00 555.00 C 831.83 553.00, 802.83 545.00, 792.00 543.00 C 781.17 541.00, 784.83 542.50, 783.00 543.00 C 781.17 543.50, 778.67 533.83, 781.00 546.00 C 783.33 558.17, 792.67 592.33, 797.00 616.00 C 801.33 639.67, 804.83 668.83, 807.00 688.00 C 809.17 707.17, 809.67 709.00, 810.00 731.00 C 810.33 753.00, 810.50 792.83, 809.00 820.00 C 807.50 847.17, 804.50 870.50, 801.00 894.00 C 797.50 917.50, 794.00 936.17, 788.00 961.00 C 782.00 985.83, 773.50 1016.67, 765.00 1043.00 C 756.50 1069.33, 741.33 1104.83, 737.00 1119.00 C 732.67 1133.17, 736.67 1127.17, 739.00 1128.00 C 741.33 1128.83, 745.83 1127.83, 751.00 1124.00 C 756.17 1120.17, 757.33 1120.50, 770.00 1105.00 C 782.67 1089.50, 811.33 1053.50, 827.00 1031.00 C 842.67 1008.50, 851.17 995.00, 864.00 970.00 C 876.83 945.00, 892.83 910.00, 904.00 881.00 C 915.17 852.00, 923.00 826.67, 931.00 796.00 C 939.00 765.33, 946.67 728.67, 952.00 697.00 C 957.33 665.33, 961.00 631.83, 963.00 606.00 C 965.00 580.17, 964.33 553.50, 964.00 542.00 C 963.67 530.50, 973.50 534.83, 961.00 537.00 Z"
40
+ ];
41
+
42
+ // src/components/Walros3DPortalTransition.tsx
43
+ import { jsx, jsxs } from "react/jsx-runtime";
44
+ var FOCUS_X = 626;
45
+ var FOCUS_Y = 333;
46
+ var VISIBLE_LEFT = 244;
47
+ var VISIBLE_RIGHT = 1010;
48
+ var VISIBLE_TOP = 115;
49
+ var VISIBLE_BOTTOM = 1128;
50
+ var PORTAL_RADIUS = 190;
51
+ function Walros3DPortalTransition({
52
+ origin,
53
+ onComplete,
54
+ duration = 900,
55
+ backgroundColor = "#000",
56
+ zIndex = 10001,
57
+ lockScroll = true
342
58
  }) {
343
- const src = variant === "dark" ? walrosWordmarkDarkUrl : walrosWordmarkLightUrl;
344
- return /* @__PURE__ */ jsx3(
345
- "img",
59
+ const [visible, setVisible] = useState(true);
60
+ const [viewport, setViewport] = useState(() => ({
61
+ width: typeof window === "undefined" ? 0 : window.innerWidth,
62
+ height: typeof window === "undefined" ? 0 : window.innerHeight
63
+ }));
64
+ const overlay = useRef(null);
65
+ const portal = useRef(null);
66
+ const completeRef = useRef(onComplete);
67
+ completeRef.current = onComplete;
68
+ const maskId = `walros-3d-portal-${useId().replace(/:/g, "_")}`;
69
+ useEffect(() => {
70
+ const updateViewport = () => setViewport({ width: window.innerWidth, height: window.innerHeight });
71
+ window.addEventListener("resize", updateViewport);
72
+ return () => window.removeEventListener("resize", updateViewport);
73
+ }, []);
74
+ useLayoutEffect(() => {
75
+ if (!visible || viewport.width <= 0 || viewport.height <= 0 || !overlay.current || !portal.current) return;
76
+ const previousOverflow = document.body.style.overflow;
77
+ if (lockScroll) document.body.style.overflow = "hidden";
78
+ const visibleWidth = VISIBLE_RIGHT - VISIBLE_LEFT;
79
+ const visibleHeight = VISIBLE_BOTTOM - VISIBLE_TOP;
80
+ const visualCenterX = (VISIBLE_LEFT + VISIBLE_RIGHT) / 2;
81
+ const visualCenterY = (VISIBLE_TOP + VISIBLE_BOTTOM) / 2;
82
+ const initialScale = Math.min(origin.width / visibleWidth, origin.height / visibleHeight) * 0.985;
83
+ const initialX = origin.centerX - (visualCenterX - FOCUS_X) * initialScale;
84
+ const initialY = origin.centerY - (visualCenterY - FOCUS_Y) * initialScale;
85
+ const targetX = viewport.width / 2 - (visualCenterX - FOCUS_X) * initialScale;
86
+ const targetY = viewport.height / 2 - (visualCenterY - FOCUS_Y) * initialScale;
87
+ const diagonal = Math.hypot(viewport.width, viewport.height) / 2;
88
+ const finalScale = (diagonal + 80) / PORTAL_RADIUS * 1.15;
89
+ const total = duration / 1e3;
90
+ gsap.set(portal.current, {
91
+ x: initialX,
92
+ y: initialY,
93
+ scale: initialScale,
94
+ svgOrigin: "0 0"
95
+ });
96
+ gsap.set(overlay.current, { opacity: 1 });
97
+ const finish = () => {
98
+ if (lockScroll) document.body.style.overflow = previousOverflow;
99
+ setVisible(false);
100
+ completeRef.current?.();
101
+ };
102
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
103
+ const timeline = gsap.timeline({ onComplete: finish });
104
+ if (reducedMotion) {
105
+ timeline.to(overlay.current, { opacity: 0, duration: 0.15, ease: "power1.out" });
106
+ } else {
107
+ timeline.to(portal.current, {
108
+ x: targetX,
109
+ y: targetY,
110
+ scale: finalScale,
111
+ duration: total,
112
+ ease: "power2.in"
113
+ }).to(overlay.current, {
114
+ opacity: 0,
115
+ duration: total * 0.1,
116
+ ease: "power2.out"
117
+ }, total * 0.9);
118
+ }
119
+ return () => {
120
+ timeline.kill();
121
+ if (lockScroll) document.body.style.overflow = previousOverflow;
122
+ };
123
+ }, [duration, lockScroll, origin, viewport, visible]);
124
+ if (!visible) return null;
125
+ return /* @__PURE__ */ jsx(
126
+ "div",
346
127
  {
347
- src,
348
- alt,
349
- draggable,
350
- ...props
128
+ ref: overlay,
129
+ className: "walros-3d-portal",
130
+ style: {
131
+ position: "fixed",
132
+ inset: 0,
133
+ width: "100vw",
134
+ height: "100dvh",
135
+ overflow: "hidden",
136
+ pointerEvents: "none",
137
+ zIndex,
138
+ backgroundColor: viewport.width > 0 && viewport.height > 0 ? "transparent" : backgroundColor
139
+ },
140
+ "aria-hidden": "true",
141
+ children: /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: `0 0 ${viewport.width} ${viewport.height}`, preserveAspectRatio: "none", style: { display: "block" }, children: [
142
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: viewport.width, height: viewport.height, children: [
143
+ /* @__PURE__ */ jsx("rect", { width: viewport.width, height: viewport.height, fill: "white" }),
144
+ /* @__PURE__ */ jsx("g", { ref: portal, children: /* @__PURE__ */ jsx("g", { transform: `translate(${-FOCUS_X} ${-FOCUS_Y})`, fill: "black", children: walrosPortalPaths.map((path, index) => /* @__PURE__ */ jsx("path", { d: path }, index)) }) })
145
+ ] }) }),
146
+ /* @__PURE__ */ jsx("rect", { width: viewport.width, height: viewport.height, fill: backgroundColor, mask: `url(#${maskId})` })
147
+ ] })
351
148
  }
352
149
  );
353
150
  }
354
151
 
355
152
  // src/components/WalrosThemeGlitch.tsx
356
- import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
153
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
357
154
  var GLITCH_STYLES = `
358
155
  .walros-theme-glitch-root {
359
156
  position: fixed;
@@ -371,7 +168,6 @@ var GLITCH_STYLES = `
371
168
  content: "";
372
169
  position: absolute;
373
170
  inset: 0;
374
- mix-blend-mode: screen;
375
171
  }
376
172
  .walros-theme-glitch-root::before {
377
173
  background: rgba(0, 0, 0, .82);
@@ -501,7 +297,7 @@ function WalrosThemeGlitch({
501
297
  style
502
298
  }) {
503
299
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
504
- /* @__PURE__ */ jsx4("style", { children: GLITCH_STYLES }),
300
+ /* @__PURE__ */ jsx2("style", { children: GLITCH_STYLES }),
505
301
  active ? /* @__PURE__ */ jsxs2(
506
302
  "div",
507
303
  {
@@ -509,22 +305,374 @@ function WalrosThemeGlitch({
509
305
  style: { zIndex, ...style },
510
306
  "aria-hidden": "true",
511
307
  children: [
512
- /* @__PURE__ */ jsx4("span", { className: "walros-theme-glitch-band" }),
513
- /* @__PURE__ */ jsx4("span", { className: "walros-theme-glitch-band" }),
514
- /* @__PURE__ */ jsx4("span", { className: "walros-theme-glitch-band" }),
515
- /* @__PURE__ */ jsx4("span", { className: "walros-theme-glitch-band" })
308
+ /* @__PURE__ */ jsx2("span", { className: "walros-theme-glitch-band" }),
309
+ /* @__PURE__ */ jsx2("span", { className: "walros-theme-glitch-band" }),
310
+ /* @__PURE__ */ jsx2("span", { className: "walros-theme-glitch-band" }),
311
+ /* @__PURE__ */ jsx2("span", { className: "walros-theme-glitch-band" })
516
312
  ]
517
313
  }
518
314
  ) : null
519
315
  ] });
520
316
  }
521
317
 
318
+ // src/components/useOverlayHost.ts
319
+ import { useLayoutEffect as useLayoutEffect2, useState as useState2 } from "react";
320
+ function useOverlayHost(zIndex) {
321
+ const [host] = useState2(
322
+ () => typeof document === "undefined" ? null : document.createElement("div")
323
+ );
324
+ useLayoutEffect2(() => {
325
+ if (!host) return;
326
+ host.dataset.walrosOverlayRoot = "";
327
+ host.style.position = "fixed";
328
+ host.style.inset = "0";
329
+ host.style.pointerEvents = "none";
330
+ document.documentElement.appendChild(host);
331
+ return () => host.remove();
332
+ }, [host]);
333
+ useLayoutEffect2(() => {
334
+ if (host) host.style.zIndex = String(zIndex);
335
+ }, [host, zIndex]);
336
+ return host;
337
+ }
338
+
339
+ // src/components/WalrosThemeTransition.tsx
340
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
341
+ var IMAGE_SIZE = "clamp(90px, 11vw, 150px)";
342
+ var IMAGE_WIDTH = 1254;
343
+ var VISIBLE_LEFT2 = 244;
344
+ var VISIBLE_RIGHT2 = 1010;
345
+ var VISIBLE_TOP2 = 115;
346
+ var VISIBLE_BOTTOM2 = 1128;
347
+ function WalrosPortalScene({
348
+ logoUrl,
349
+ portalDuration,
350
+ zIndex,
351
+ backgroundColor = "#000",
352
+ onComplete,
353
+ lockScroll = false
354
+ }) {
355
+ const logoRef = useRef2(null);
356
+ const [origin, setOrigin] = useState3(null);
357
+ const [portalComplete, setPortalComplete] = useState3(false);
358
+ const measureLogo = () => {
359
+ const image = logoRef.current;
360
+ if (!image) return;
361
+ const rect = image.getBoundingClientRect();
362
+ const scale = rect.width / IMAGE_WIDTH;
363
+ setOrigin({
364
+ centerX: rect.left + (VISIBLE_LEFT2 + VISIBLE_RIGHT2) / 2 * scale,
365
+ centerY: rect.top + (VISIBLE_TOP2 + VISIBLE_BOTTOM2) / 2 * scale,
366
+ width: (VISIBLE_RIGHT2 - VISIBLE_LEFT2) * scale,
367
+ height: (VISIBLE_BOTTOM2 - VISIBLE_TOP2) * scale
368
+ });
369
+ };
370
+ useLayoutEffect3(() => {
371
+ const image = logoRef.current;
372
+ if (image?.complete) measureLogo();
373
+ }, []);
374
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
375
+ !portalComplete && /* @__PURE__ */ jsx3(
376
+ "img",
377
+ {
378
+ ref: logoRef,
379
+ src: logoUrl,
380
+ onLoad: measureLogo,
381
+ onError: measureLogo,
382
+ alt: "",
383
+ "aria-hidden": "true",
384
+ style: {
385
+ position: "fixed",
386
+ left: "50%",
387
+ top: "45%",
388
+ width: IMAGE_SIZE,
389
+ height: IMAGE_SIZE,
390
+ transform: "translate(-50%, -50%)",
391
+ objectFit: "contain",
392
+ zIndex,
393
+ pointerEvents: "none"
394
+ }
395
+ }
396
+ ),
397
+ !portalComplete && !origin && /* @__PURE__ */ jsx3("div", { style: { position: "fixed", inset: 0, zIndex: zIndex + 1, background: backgroundColor, pointerEvents: "none" }, "aria-hidden": "true" }),
398
+ !portalComplete && origin && /* @__PURE__ */ jsx3(
399
+ Walros3DPortalTransition,
400
+ {
401
+ origin,
402
+ duration: portalDuration,
403
+ zIndex: zIndex + 1,
404
+ backgroundColor,
405
+ lockScroll,
406
+ onComplete: () => {
407
+ setPortalComplete(true);
408
+ onComplete?.();
409
+ }
410
+ }
411
+ )
412
+ ] });
413
+ }
414
+ function WalrosThemeTransition({
415
+ active,
416
+ portalDuration = 900,
417
+ logoUrl = walrosLogoChromeUrl,
418
+ zIndex = 1e4
419
+ }) {
420
+ const host = useOverlayHost(zIndex);
421
+ useEffect2(() => {
422
+ if (typeof window === "undefined") return;
423
+ const logo = new Image();
424
+ logo.src = logoUrl;
425
+ }, [logoUrl]);
426
+ if (!host) return null;
427
+ return createPortal(
428
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
429
+ active && /* @__PURE__ */ jsx3(WalrosPortalScene, { logoUrl, portalDuration, zIndex }),
430
+ /* @__PURE__ */ jsx3(WalrosThemeGlitch, { active, zIndex: zIndex + 2 })
431
+ ] }),
432
+ host
433
+ );
434
+ }
435
+
436
+ // src/components/WalrosPortalTransition.tsx
437
+ import { jsx as jsx4 } from "react/jsx-runtime";
438
+ function getStorage(mode) {
439
+ if (typeof window === "undefined") return null;
440
+ try {
441
+ return mode === "session" ? window.sessionStorage : window.localStorage;
442
+ } catch {
443
+ return null;
444
+ }
445
+ }
446
+ function shouldRun(enabled, mode, key) {
447
+ if (!enabled) return false;
448
+ if (mode === "always") return true;
449
+ try {
450
+ return getStorage(mode)?.getItem(key) == null;
451
+ } catch {
452
+ return true;
453
+ }
454
+ }
455
+ function WalrosPortalTransition({
456
+ duration = 900,
457
+ onComplete,
458
+ backgroundColor = "#000",
459
+ enabled = true,
460
+ storageMode = "always",
461
+ storageKey = "walros-portal-seen",
462
+ zIndex = 1e4
463
+ }) {
464
+ const [run] = useState4(() => shouldRun(enabled, storageMode, storageKey));
465
+ const [complete, setComplete] = useState4(false);
466
+ const host = useOverlayHost(zIndex);
467
+ if (!host || !enabled || !run || complete) return null;
468
+ return createPortal2(
469
+ /* @__PURE__ */ jsx4(
470
+ WalrosPortalScene,
471
+ {
472
+ logoUrl: walrosLogoChromeUrl,
473
+ portalDuration: duration,
474
+ backgroundColor,
475
+ zIndex,
476
+ lockScroll: true,
477
+ onComplete: () => {
478
+ if (storageMode !== "always") {
479
+ try {
480
+ getStorage(storageMode)?.setItem(storageKey, "1");
481
+ } catch {
482
+ }
483
+ }
484
+ setComplete(true);
485
+ onComplete?.();
486
+ }
487
+ }
488
+ ),
489
+ host
490
+ );
491
+ }
492
+
493
+ // src/components/DirectionalCursor.tsx
494
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
495
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
496
+ var DESKTOP_CURSOR_QUERY = "(min-width: 1024px) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)";
497
+ var CURSOR_STYLES = `
498
+ .directional-cursor { position: fixed; top: 0; left: 0; z-index: 9999; width: 20px; height: 22px; opacity: 0; pointer-events: none; contain: layout style; will-change: transform; transition: opacity 100ms linear; }
499
+ .directional-cursor.is-visible { opacity: 1; }
500
+ .directional-cursor-shape { width: 20px; height: 22px; transform-origin: center center; will-change: transform; }
501
+ .directional-cursor svg { display: block; width: 20px; height: 22px; }
502
+ @media (min-width: 1024px) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
503
+ html.has-directional-cursor, html.has-directional-cursor body, html.has-directional-cursor body * { cursor: none !important; }
504
+ html.has-directional-cursor :is(a, button, [role="button"], [onclick], .clickable, [data-clickable], input[type="button"], input[type="submit"], input[type="reset"], label[for]),
505
+ html.has-directional-cursor :is(a, button, [role="button"], [onclick], .clickable, [data-clickable], input[type="button"], input[type="submit"], input[type="reset"], label[for]) * { cursor: pointer !important; }
506
+ html.has-directional-cursor :is(input, textarea, select, [contenteditable="true"]) { cursor: text !important; }
507
+ }
508
+ `;
509
+ function DirectionalCursor() {
510
+ const positionLayer = useRef3(null);
511
+ const directionLayer = useRef3(null);
512
+ useEffect3(() => {
513
+ const position = positionLayer.current;
514
+ const direction = directionLayer.current;
515
+ if (!position || !direction) return;
516
+ const media = window.matchMedia(DESKTOP_CURSOR_QUERY);
517
+ let active = false;
518
+ let frame = 0;
519
+ let moveFrame = 0;
520
+ let scaleTimer = 0;
521
+ let latestEvent = null;
522
+ let currentX = window.innerWidth / 2;
523
+ let currentY = -60;
524
+ let targetX = currentX;
525
+ let targetY = currentY;
526
+ let currentAngle = 0;
527
+ let targetAngle = 0;
528
+ let hasAngle = false;
529
+ let currentScale = 1;
530
+ let targetScale = 1;
531
+ let lastX = currentX;
532
+ let lastY = 0;
533
+ const render = () => {
534
+ currentX += (targetX - currentX) * 0.4;
535
+ currentY += (targetY - currentY) * 0.4;
536
+ const angleDelta = (targetAngle - currentAngle + 540) % 360 - 180;
537
+ currentAngle += angleDelta * 0.24;
538
+ currentScale += (targetScale - currentScale) * 0.24;
539
+ position.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
540
+ direction.style.transform = `translate(-50%, -50%) rotate(${currentAngle}deg) scale(${currentScale})`;
541
+ const unsettled = Math.abs(targetX - currentX) > 0.08 || Math.abs(targetY - currentY) > 0.08 || Math.abs(angleDelta) > 0.08 || Math.abs(targetScale - currentScale) > 2e-3;
542
+ frame = unsettled ? window.requestAnimationFrame(render) : 0;
543
+ };
544
+ const requestRender = () => {
545
+ if (!frame) frame = window.requestAnimationFrame(render);
546
+ };
547
+ const processMove = () => {
548
+ moveFrame = 0;
549
+ const event = latestEvent;
550
+ if (!event) return;
551
+ const deltaX = event.clientX - lastX;
552
+ const deltaY = event.clientY - lastY;
553
+ const distance = Math.hypot(deltaX, deltaY);
554
+ targetX = event.clientX;
555
+ targetY = event.clientY;
556
+ position.classList.add("is-visible");
557
+ if (distance > 1.25) {
558
+ const rawAngle = Math.atan2(deltaY, deltaX) * (180 / Math.PI) + 90;
559
+ const normalizedAngle = (rawAngle % 360 + 360) % 360;
560
+ if (!hasAngle) {
561
+ targetAngle = normalizedAngle;
562
+ currentAngle = normalizedAngle;
563
+ hasAngle = true;
564
+ } else {
565
+ targetAngle = normalizedAngle;
566
+ }
567
+ targetScale = 0.92;
568
+ window.clearTimeout(scaleTimer);
569
+ scaleTimer = window.setTimeout(() => {
570
+ targetScale = 1;
571
+ requestRender();
572
+ }, 130);
573
+ }
574
+ lastX = event.clientX;
575
+ lastY = event.clientY;
576
+ requestRender();
577
+ };
578
+ const handleMouseMove = (event) => {
579
+ latestEvent = event;
580
+ if (!moveFrame) moveFrame = window.requestAnimationFrame(processMove);
581
+ };
582
+ const activate = () => {
583
+ if (active) return;
584
+ active = true;
585
+ document.documentElement.classList.add("has-directional-cursor");
586
+ window.addEventListener("mousemove", handleMouseMove, { passive: true });
587
+ };
588
+ const deactivate = () => {
589
+ if (!active) return;
590
+ active = false;
591
+ document.documentElement.classList.remove("has-directional-cursor");
592
+ position.classList.remove("is-visible");
593
+ window.removeEventListener("mousemove", handleMouseMove);
594
+ window.cancelAnimationFrame(frame);
595
+ window.cancelAnimationFrame(moveFrame);
596
+ window.clearTimeout(scaleTimer);
597
+ frame = 0;
598
+ moveFrame = 0;
599
+ };
600
+ const sync = () => {
601
+ if (media.matches) activate();
602
+ else deactivate();
603
+ };
604
+ media.addEventListener("change", sync);
605
+ sync();
606
+ return () => {
607
+ media.removeEventListener("change", sync);
608
+ deactivate();
609
+ };
610
+ }, []);
611
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
612
+ /* @__PURE__ */ jsx5("style", { children: CURSOR_STYLES }),
613
+ /* @__PURE__ */ jsx5("div", { ref: positionLayer, className: "directional-cursor", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("div", { ref: directionLayer, className: "directional-cursor-shape", children: /* @__PURE__ */ jsxs4("svg", { width: "50", height: "54", viewBox: "0 0 50 54", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
614
+ /* @__PURE__ */ jsxs4("g", { filter: "url(#walros-cursor-shadow)", children: [
615
+ /* @__PURE__ */ jsx5("path", { d: "M42.6817 41.1495L27.5103 6.79925C26.7269 5.02557 24.2082 5.02558 23.3927 6.79925L7.59814 41.1495C6.75833 42.9759 8.52712 44.8902 10.4125 44.1954L24.3757 39.0496C24.8829 38.8627 25.4385 38.8627 25.9422 39.0496L39.8121 44.1954C41.6849 44.8902 43.4884 42.9759 42.6817 41.1495Z", fill: "black" }),
616
+ /* @__PURE__ */ jsx5("path", { d: "M43.7146 40.6933L28.5431 6.34306C27.3556 3.65428 23.5772 3.69516 22.3668 6.32755L6.57226 40.6778C5.3134 43.4156 7.97238 46.298 10.803 45.2549L24.7662 40.109C25.0221 40.0147 25.2999 40.0156 25.5494 40.1082L39.4193 45.254C42.2261 46.2953 44.9254 43.4347 43.7146 40.6933Z", stroke: "white", strokeWidth: "2.25825" })
617
+ ] }),
618
+ /* @__PURE__ */ jsx5("defs", { children: /* @__PURE__ */ jsxs4("filter", { id: "walros-cursor-shadow", x: "0.602397", y: "0.952444", width: "49.0584", height: "52.428", filterUnits: "userSpaceOnUse", colorInterpolationFilters: "sRGB", children: [
619
+ /* @__PURE__ */ jsx5("feFlood", { floodOpacity: "0", result: "BackgroundImageFix" }),
620
+ /* @__PURE__ */ jsx5("feColorMatrix", { in: "SourceAlpha", type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0", result: "hardAlpha" }),
621
+ /* @__PURE__ */ jsx5("feOffset", { dy: "2.25825" }),
622
+ /* @__PURE__ */ jsx5("feGaussianBlur", { stdDeviation: "2.25825" }),
623
+ /* @__PURE__ */ jsx5("feComposite", { in2: "hardAlpha", operator: "out" }),
624
+ /* @__PURE__ */ jsx5("feColorMatrix", { type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0" }),
625
+ /* @__PURE__ */ jsx5("feBlend", { mode: "normal", in2: "BackgroundImageFix", result: "effect1_dropShadow" }),
626
+ /* @__PURE__ */ jsx5("feBlend", { mode: "normal", in: "SourceGraphic", in2: "effect1_dropShadow", result: "shape" })
627
+ ] }) })
628
+ ] }) }) })
629
+ ] });
630
+ }
631
+
632
+ // src/components/WalrosLogo.tsx
633
+ import { jsx as jsx6 } from "react/jsx-runtime";
634
+ function WalrosLogo({
635
+ alt = "WalrOS",
636
+ draggable = false,
637
+ ...props
638
+ }) {
639
+ return /* @__PURE__ */ jsx6(
640
+ "img",
641
+ {
642
+ src: walrosLogoChromeUrl,
643
+ alt,
644
+ draggable,
645
+ ...props
646
+ }
647
+ );
648
+ }
649
+
650
+ // src/components/WalrosWordmark.tsx
651
+ import { jsx as jsx7 } from "react/jsx-runtime";
652
+ function WalrosWordmark({
653
+ variant = "light",
654
+ alt = "WalrOS",
655
+ draggable = false,
656
+ ...props
657
+ }) {
658
+ const src = variant === "dark" ? walrosWordmarkDarkUrl : walrosWordmarkLightUrl;
659
+ return /* @__PURE__ */ jsx7(
660
+ "img",
661
+ {
662
+ src,
663
+ alt,
664
+ draggable,
665
+ ...props
666
+ }
667
+ );
668
+ }
669
+
522
670
  // src/hooks/useWalrosTheme.ts
523
671
  import {
524
672
  useCallback,
525
- useEffect as useEffect2,
526
- useRef as useRef2,
527
- useState as useState2
673
+ useEffect as useEffect4,
674
+ useRef as useRef4,
675
+ useState as useState5
528
676
  } from "react";
529
677
  var GLITCH_BASE_CLASS = "walros-theme-glitch-page";
530
678
  var GLITCH_BODY_CLASS = "walros-theme-glitch-page--body";
@@ -571,25 +719,25 @@ function useWalrosTheme({
571
719
  lockScroll,
572
720
  onAfterUnlock
573
721
  } = {}) {
574
- const [theme, setThemeState] = useState2(
722
+ const [theme, setThemeState] = useState5(
575
723
  () => readInitialTheme(storageKey, defaultTheme, useSystemPreference)
576
724
  );
577
- const [mounted, setMounted] = useState2(false);
578
- const [glitching, setGlitching] = useState2(false);
579
- const themeRef = useRef2(theme);
580
- const glitchingRef = useRef2(false);
581
- const swapTimerRef = useRef2(null);
582
- const cleanupTimerRef = useRef2(null);
583
- const applyThemeRef = useRef2(applyTheme);
584
- const modeRef = useRef2(mode);
585
- const shakeTargetsRef = useRef2(shakeTargets);
586
- const lockScrollRef = useRef2(lockScroll);
587
- const onAfterUnlockRef = useRef2(onAfterUnlock);
588
- const activeTargetsRef = useRef2([]);
589
- const viewportLockRef = useRef2(null);
590
- const soundRef = useRef2(null);
591
- const soundUrlRef = useRef2(soundUrl);
592
- const soundVolumeRef = useRef2(soundVolume);
725
+ const [mounted, setMounted] = useState5(false);
726
+ const [glitching, setGlitching] = useState5(false);
727
+ const themeRef = useRef4(theme);
728
+ const glitchingRef = useRef4(false);
729
+ const swapTimerRef = useRef4(null);
730
+ const cleanupTimerRef = useRef4(null);
731
+ const applyThemeRef = useRef4(applyTheme);
732
+ const modeRef = useRef4(mode);
733
+ const shakeTargetsRef = useRef4(shakeTargets);
734
+ const lockScrollRef = useRef4(lockScroll);
735
+ const onAfterUnlockRef = useRef4(onAfterUnlock);
736
+ const activeTargetsRef = useRef4([]);
737
+ const viewportLockRef = useRef4(null);
738
+ const soundRef = useRef4(null);
739
+ const soundUrlRef = useRef4(soundUrl);
740
+ const soundVolumeRef = useRef4(soundVolume);
593
741
  themeRef.current = theme;
594
742
  applyThemeRef.current = applyTheme;
595
743
  modeRef.current = mode;
@@ -626,7 +774,7 @@ function useWalrosTheme({
626
774
  },
627
775
  [storageKey]
628
776
  );
629
- useEffect2(() => {
777
+ useEffect4(() => {
630
778
  const initialTheme = readInitialTheme(
631
779
  storageKey,
632
780
  defaultTheme,
@@ -708,6 +856,10 @@ function useWalrosTheme({
708
856
  activeTargetsRef.current = [];
709
857
  }, []);
710
858
  const finishGlitch = useCallback(() => {
859
+ if (soundRef.current) {
860
+ soundRef.current.pause();
861
+ soundRef.current.currentTime = 0;
862
+ }
711
863
  removeShakeClasses();
712
864
  unlockViewport(true);
713
865
  glitchingRef.current = false;
@@ -757,12 +909,13 @@ function useWalrosTheme({
757
909
  finishGlitch();
758
910
  }, effectiveDuration);
759
911
  }, [finishGlitch, lockViewport, persistTheme, playThemeSound, swapDelay, totalDuration]);
760
- useEffect2(() => {
912
+ useEffect4(() => {
761
913
  return () => {
762
914
  clearTimers();
763
915
  removeShakeClasses();
764
916
  unlockViewport(false);
765
917
  soundRef.current?.pause();
918
+ if (soundRef.current) soundRef.current.currentTime = 0;
766
919
  soundRef.current = null;
767
920
  };
768
921
  }, [clearTimers, removeShakeClasses, unlockViewport]);
@@ -776,9 +929,12 @@ function useWalrosTheme({
776
929
  };
777
930
  }
778
931
  export {
932
+ DirectionalCursor,
933
+ Walros3DPortalTransition,
779
934
  WalrosLogo,
780
935
  WalrosPortalTransition,
781
936
  WalrosThemeGlitch,
937
+ WalrosThemeTransition,
782
938
  WalrosWordmark,
783
939
  useWalrosTheme,
784
940
  walrosLogoChromeUrl,