@windrun-huaiin/third-ui 29.2.1 → 30.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/dist/fuma/fuma-page-genarator.d.ts +2 -6
- package/dist/fuma/fuma-page-genarator.js +3 -2
- package/dist/fuma/fuma-page-genarator.mjs +3 -2
- package/dist/fuma/mdx/cheet-table.d.ts +13 -0
- package/dist/fuma/mdx/cheet-table.js +295 -0
- package/dist/fuma/mdx/cheet-table.mjs +293 -0
- package/dist/fuma/mdx/index.d.ts +1 -0
- package/dist/fuma/mdx/index.js +2 -0
- package/dist/fuma/mdx/index.mjs +1 -0
- package/dist/fuma/server/features/widgets.js +2 -0
- package/dist/fuma/server/features/widgets.mjs +2 -0
- package/dist/lib/fuma-schema-check-util.d.ts +1 -1
- package/dist/main/404-page.d.ts +12 -0
- package/dist/main/404-page.js +66 -0
- package/dist/main/404-page.mjs +64 -0
- package/dist/main/alert-dialog/confirm-dialog.js +1 -1
- package/dist/main/alert-dialog/confirm-dialog.mjs +2 -2
- package/dist/main/alert-dialog/dialog-loading-action.js +5 -2
- package/dist/main/alert-dialog/dialog-loading-action.mjs +5 -2
- package/dist/main/alert-dialog/dialog-styles.d.ts +4 -2
- package/dist/main/alert-dialog/dialog-styles.js +8 -4
- package/dist/main/alert-dialog/dialog-styles.mjs +7 -5
- package/dist/main/alert-dialog/high-priority-confirm-dialog.js +5 -5
- package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +6 -6
- package/dist/main/alert-dialog/info-dialog.js +1 -1
- package/dist/main/alert-dialog/info-dialog.mjs +2 -2
- package/dist/main/alert-dialog/undoable-confirm-dialog.js +2 -2
- package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +3 -3
- package/dist/main/anime/anime-404-page.d.ts +14 -0
- package/dist/main/anime/anime-404-page.js +197 -0
- package/dist/main/anime/anime-404-page.mjs +195 -0
- package/dist/main/anime/anime-beam-frame.d.ts +3 -0
- package/dist/main/anime/anime-beam-frame.js +63 -0
- package/dist/main/anime/anime-beam-frame.mjs +61 -0
- package/dist/main/anime/anime-not-found-page.d.ts +7 -0
- package/dist/main/anime/anime-not-found-page.js +142 -0
- package/dist/main/anime/anime-not-found-page.mjs +140 -0
- package/dist/main/anime/anime-spiral-loading.d.ts +10 -0
- package/dist/main/anime/anime-spiral-loading.js +77 -0
- package/dist/main/anime/anime-spiral-loading.mjs +75 -0
- package/dist/main/anime/index.d.ts +3 -0
- package/dist/main/anime/index.js +12 -0
- package/dist/main/anime/index.mjs +4 -0
- package/dist/main/beam-frame/animate.d.ts +3 -0
- package/dist/main/beam-frame/animate.js +63 -0
- package/dist/main/beam-frame/animate.mjs +61 -0
- package/dist/main/beam-frame/beam-frame.d.ts +4 -0
- package/dist/main/beam-frame/beam-frame.js +262 -0
- package/dist/main/beam-frame/beam-frame.mjs +258 -0
- package/dist/main/beam-frame/index.d.ts +4 -0
- package/dist/main/beam-frame/index.js +11 -0
- package/dist/main/beam-frame/index.mjs +3 -0
- package/dist/main/beam-frame/motion.d.ts +3 -0
- package/dist/main/beam-frame/motion.js +61 -0
- package/dist/main/beam-frame/motion.mjs +59 -0
- package/dist/main/beam-frame/share-config.d.ts +54 -0
- package/dist/main/beam-frame/share-config.js +161 -0
- package/dist/main/beam-frame/share-config.mjs +152 -0
- package/dist/main/beam-frame-config.d.ts +54 -0
- package/dist/main/beam-frame-config.js +161 -0
- package/dist/main/beam-frame-config.mjs +152 -0
- package/dist/main/calendar/random-date-range-dialog.js +177 -51
- package/dist/main/calendar/random-date-range-dialog.mjs +178 -52
- package/dist/main/cta.js +17 -1
- package/dist/main/cta.mjs +18 -2
- package/dist/main/delayed-img.d.ts +1 -1
- package/dist/main/delayed-img.js +8 -5
- package/dist/main/delayed-img.mjs +8 -5
- package/dist/main/index.d.ts +1 -0
- package/dist/main/index.js +2 -0
- package/dist/main/index.mjs +1 -0
- package/dist/main/info-tooltip.js +70 -9
- package/dist/main/info-tooltip.mjs +70 -9
- package/dist/main/loading-frame/index.d.ts +1 -0
- package/dist/main/loading.d.ts +2 -1
- package/dist/main/loading.js +64 -26
- package/dist/main/loading.mjs +64 -26
- package/dist/main/motion/creative-left-panel.d.ts +7 -0
- package/dist/main/motion/creative-left-panel.js +11 -0
- package/dist/main/motion/creative-left-panel.mjs +9 -0
- package/dist/main/motion/creative-right-panel.d.ts +7 -0
- package/dist/main/motion/creative-right-panel.js +11 -0
- package/dist/main/motion/creative-right-panel.mjs +9 -0
- package/dist/main/motion/index.d.ts +1 -0
- package/dist/main/motion/index.js +9 -0
- package/dist/main/motion/index.mjs +2 -0
- package/dist/main/motion/motion-beam-frame.d.ts +3 -0
- package/dist/main/motion/motion-beam-frame.js +61 -0
- package/dist/main/motion/motion-beam-frame.mjs +59 -0
- package/dist/main/snake-loading-frame.d.ts +7 -3
- package/dist/main/snake-loading-frame.js +45 -252
- package/dist/main/snake-loading-frame.mjs +47 -254
- package/package.json +16 -5
- package/src/fuma/fuma-page-genarator.tsx +2 -22
- package/src/fuma/mdx/cheet-table.tsx +650 -0
- package/src/fuma/mdx/index.ts +1 -0
- package/src/fuma/server/features/widgets.tsx +2 -0
- package/src/main/404-page.tsx +162 -0
- package/src/main/alert-dialog/confirm-dialog.tsx +2 -1
- package/src/main/alert-dialog/dialog-loading-action.tsx +7 -5
- package/src/main/alert-dialog/dialog-styles.ts +13 -3
- package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +26 -23
- package/src/main/alert-dialog/info-dialog.tsx +2 -1
- package/src/main/alert-dialog/undoable-confirm-dialog.tsx +18 -17
- package/src/main/anime/anime-404-page.tsx +344 -0
- package/src/main/anime/anime-beam-frame.tsx +128 -0
- package/src/main/anime/anime-spiral-loading.tsx +123 -0
- package/src/main/anime/index.ts +10 -0
- package/src/main/beam-frame-config.tsx +341 -0
- package/src/main/calendar/random-date-range-dialog.tsx +225 -69
- package/src/main/cta.tsx +50 -21
- package/src/main/delayed-img.tsx +9 -4
- package/src/main/index.ts +1 -0
- package/src/main/info-tooltip.tsx +116 -20
- package/src/main/loading-frame/index.ts +4 -0
- package/src/main/loading.tsx +75 -24
- package/src/main/motion/index.ts +8 -0
- package/src/main/motion/motion-beam-frame.tsx +137 -0
- package/src/main/snake-loading-frame.tsx +95 -496
- package/src/styles/cta.css +21 -4
- package/src/styles/third-ui.css +0 -20
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
THEME_BUTTON_GRADIENT_CLASS_MAP,
|
|
5
|
+
themeBgColor,
|
|
6
|
+
themeButtonGradientClass,
|
|
7
|
+
themeIconColor,
|
|
8
|
+
themeSvgIconColor,
|
|
9
|
+
type SupportedThemeColor,
|
|
10
|
+
} from '@windrun-huaiin/base-ui/lib';
|
|
11
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
12
|
+
import { animate, createTimeline, stagger, type JSAnimation, type Timeline } from 'animejs';
|
|
13
|
+
import { useReducedMotion } from 'motion/react';
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react';
|
|
15
|
+
|
|
16
|
+
export interface AnimeNotFoundPageProps {
|
|
17
|
+
siteIcon: ReactNode;
|
|
18
|
+
homeUrl?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
compact?: boolean;
|
|
21
|
+
themeClass?: SupportedThemeColor;
|
|
22
|
+
themeColor?: string;
|
|
23
|
+
ambientAnimated?: boolean;
|
|
24
|
+
doorOpen?: boolean;
|
|
25
|
+
onDoorOpenChange?: (open: boolean) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const THEME_BG_CLASS_MAP: Record<SupportedThemeColor, string> = {
|
|
29
|
+
'text-purple-500': 'bg-purple-500/20',
|
|
30
|
+
'text-orange-500': 'bg-orange-500/20',
|
|
31
|
+
'text-indigo-500': 'bg-indigo-500/20',
|
|
32
|
+
'text-emerald-500': 'bg-emerald-500/20',
|
|
33
|
+
'text-rose-500': 'bg-rose-500/20',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const dust = Array.from({ length: 10 }, (_, index) => ({
|
|
37
|
+
id: index,
|
|
38
|
+
left: `${12 + index * 8}%`,
|
|
39
|
+
top: `${18 + (index % 5) * 13}%`,
|
|
40
|
+
size: 3 + (index % 3),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
export function AnimeNotFoundPage({
|
|
44
|
+
siteIcon,
|
|
45
|
+
homeUrl = process.env.NEXT_PUBLIC_BASE_URL || '/',
|
|
46
|
+
className,
|
|
47
|
+
compact = false,
|
|
48
|
+
themeClass,
|
|
49
|
+
themeColor,
|
|
50
|
+
ambientAnimated = true,
|
|
51
|
+
doorOpen,
|
|
52
|
+
onDoorOpenChange,
|
|
53
|
+
}: AnimeNotFoundPageProps) {
|
|
54
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
55
|
+
const timelineRef = useRef<Timeline | null>(null);
|
|
56
|
+
const shimmerRef = useRef<JSAnimation | null>(null);
|
|
57
|
+
const doorAnimationRef = useRef<JSAnimation | null>(null);
|
|
58
|
+
const lightAnimationRef = useRef<JSAnimation | null>(null);
|
|
59
|
+
const handleAnimationRef = useRef<JSAnimation | null>(null);
|
|
60
|
+
const messageAnimationRef = useRef<JSAnimation | null>(null);
|
|
61
|
+
const isDoorOpenRef = useRef(doorOpen ?? false);
|
|
62
|
+
const prefersReducedMotion = useReducedMotion();
|
|
63
|
+
const activeThemeClass = themeClass ?? themeIconColor;
|
|
64
|
+
const activeGradientClass = themeClass ? THEME_BUTTON_GRADIENT_CLASS_MAP[themeClass] : themeButtonGradientClass;
|
|
65
|
+
const activeBgClass = themeClass ? THEME_BG_CLASS_MAP[themeClass] : themeBgColor;
|
|
66
|
+
const doorStyle = useMemo(
|
|
67
|
+
() => ({
|
|
68
|
+
'--not-found-theme': themeColor ?? themeSvgIconColor,
|
|
69
|
+
}) as React.CSSProperties,
|
|
70
|
+
[themeColor]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const root = rootRef.current;
|
|
75
|
+
|
|
76
|
+
if (!root || prefersReducedMotion) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const door = root.querySelector<HTMLElement>('[data-not-found-door]');
|
|
81
|
+
const light = root.querySelector<HTMLElement>('[data-not-found-light]');
|
|
82
|
+
const message = root.querySelector<HTMLElement>('[data-not-found-message]');
|
|
83
|
+
const plate = root.querySelector<HTMLElement>('[data-not-found-plate]');
|
|
84
|
+
const handle = root.querySelector<HTMLElement>('[data-not-found-handle]');
|
|
85
|
+
const dustNodes = Array.from(root.querySelectorAll<HTMLElement>('[data-not-found-dust]'));
|
|
86
|
+
|
|
87
|
+
if (!door || !light || !plate || !handle) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
door.style.transform = 'rotateY(-2deg) translateX(0)';
|
|
92
|
+
light.style.opacity = '0.2';
|
|
93
|
+
light.style.transform = 'scaleX(0.78)';
|
|
94
|
+
if (message) {
|
|
95
|
+
message.style.opacity = '0';
|
|
96
|
+
message.style.transform = 'translateY(8px)';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
timelineRef.current?.revert();
|
|
100
|
+
shimmerRef.current?.revert();
|
|
101
|
+
timelineRef.current = null;
|
|
102
|
+
shimmerRef.current = null;
|
|
103
|
+
|
|
104
|
+
if (ambientAnimated) {
|
|
105
|
+
timelineRef.current = createTimeline({ loop: true })
|
|
106
|
+
.add(plate, {
|
|
107
|
+
translateY: [0, -3, 0],
|
|
108
|
+
scale: [1, 1.025, 1],
|
|
109
|
+
duration: 1400,
|
|
110
|
+
ease: 'inOutSine',
|
|
111
|
+
})
|
|
112
|
+
.add(plate, {
|
|
113
|
+
translateY: [0, -3, 0],
|
|
114
|
+
scale: [1, 1.025, 1],
|
|
115
|
+
duration: 1400,
|
|
116
|
+
ease: 'inOutSine',
|
|
117
|
+
}, '+=900')
|
|
118
|
+
.add(dustNodes, {
|
|
119
|
+
opacity: [0, 0.72, 0],
|
|
120
|
+
translateY: [14, -18],
|
|
121
|
+
translateX: (_target: unknown, index: number) => (index % 2 === 0 ? 10 : -10),
|
|
122
|
+
scale: [0.4, 1, 0.6],
|
|
123
|
+
duration: 1800,
|
|
124
|
+
delay: stagger(80),
|
|
125
|
+
ease: 'outSine',
|
|
126
|
+
}, '<+=200');
|
|
127
|
+
|
|
128
|
+
shimmerRef.current = animate(root.querySelectorAll<HTMLElement>('[data-not-found-shimmer]'), {
|
|
129
|
+
translateX: ['-120%', '120%'],
|
|
130
|
+
opacity: [0, 0.8, 0],
|
|
131
|
+
duration: 2400,
|
|
132
|
+
delay: stagger(160),
|
|
133
|
+
ease: 'inOutSine',
|
|
134
|
+
loop: true,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return () => {
|
|
139
|
+
timelineRef.current?.revert();
|
|
140
|
+
timelineRef.current = null;
|
|
141
|
+
shimmerRef.current?.revert();
|
|
142
|
+
shimmerRef.current = null;
|
|
143
|
+
doorAnimationRef.current?.revert();
|
|
144
|
+
doorAnimationRef.current = null;
|
|
145
|
+
lightAnimationRef.current?.revert();
|
|
146
|
+
lightAnimationRef.current = null;
|
|
147
|
+
handleAnimationRef.current?.revert();
|
|
148
|
+
handleAnimationRef.current = null;
|
|
149
|
+
messageAnimationRef.current?.revert();
|
|
150
|
+
messageAnimationRef.current = null;
|
|
151
|
+
};
|
|
152
|
+
}, [ambientAnimated, prefersReducedMotion]);
|
|
153
|
+
|
|
154
|
+
const setDoorOpen = useCallback((nextOpen: boolean) => {
|
|
155
|
+
const root = rootRef.current;
|
|
156
|
+
|
|
157
|
+
if (!root) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const door = root.querySelector<HTMLElement>('[data-not-found-door]');
|
|
162
|
+
const light = root.querySelector<HTMLElement>('[data-not-found-light]');
|
|
163
|
+
const handle = root.querySelector<HTMLElement>('[data-not-found-handle]');
|
|
164
|
+
const message = root.querySelector<HTMLElement>('[data-not-found-message]');
|
|
165
|
+
|
|
166
|
+
if (!door || !light || !handle) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
isDoorOpenRef.current = nextOpen;
|
|
171
|
+
|
|
172
|
+
doorAnimationRef.current?.pause();
|
|
173
|
+
lightAnimationRef.current?.pause();
|
|
174
|
+
handleAnimationRef.current?.pause();
|
|
175
|
+
messageAnimationRef.current?.pause();
|
|
176
|
+
doorAnimationRef.current = null;
|
|
177
|
+
lightAnimationRef.current = null;
|
|
178
|
+
handleAnimationRef.current = null;
|
|
179
|
+
messageAnimationRef.current = null;
|
|
180
|
+
|
|
181
|
+
if (prefersReducedMotion) {
|
|
182
|
+
door.style.transform = nextOpen ? 'rotateY(-72deg) translateX(-12px)' : 'rotateY(-2deg) translateX(0)';
|
|
183
|
+
light.style.opacity = nextOpen ? '0.8' : '0.2';
|
|
184
|
+
light.style.transform = nextOpen ? 'scaleX(1.28)' : 'scaleX(0.78)';
|
|
185
|
+
if (message) {
|
|
186
|
+
message.style.opacity = nextOpen ? '1' : '0';
|
|
187
|
+
message.style.transform = nextOpen ? 'translateY(0)' : 'translateY(8px)';
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
doorAnimationRef.current = animate(door, {
|
|
193
|
+
rotateY: nextOpen ? -72 : -2,
|
|
194
|
+
translateX: nextOpen ? -12 : 0,
|
|
195
|
+
duration: 1050,
|
|
196
|
+
ease: 'inOutCubic',
|
|
197
|
+
});
|
|
198
|
+
lightAnimationRef.current = animate(light, {
|
|
199
|
+
opacity: nextOpen ? 0.8 : 0.2,
|
|
200
|
+
scaleX: nextOpen ? 1.28 : 0.78,
|
|
201
|
+
duration: 1050,
|
|
202
|
+
ease: 'inOutSine',
|
|
203
|
+
});
|
|
204
|
+
handleAnimationRef.current = animate(handle, {
|
|
205
|
+
scale: [1, 1.05, 1],
|
|
206
|
+
duration: 520,
|
|
207
|
+
ease: 'inOutSine',
|
|
208
|
+
});
|
|
209
|
+
if (message) {
|
|
210
|
+
messageAnimationRef.current = animate(message, {
|
|
211
|
+
opacity: nextOpen ? [0, 1] : [1, 0],
|
|
212
|
+
translateY: nextOpen ? [10, 0] : [0, 4, 10],
|
|
213
|
+
scale: nextOpen ? [0.96, 1] : [1, 0.98],
|
|
214
|
+
duration: nextOpen ? 620 : 520,
|
|
215
|
+
delay: nextOpen ? 320 : 280,
|
|
216
|
+
ease: nextOpen ? 'outCubic' : 'inOutSine',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}, [prefersReducedMotion]);
|
|
220
|
+
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
if (doorOpen === undefined || doorOpen === isDoorOpenRef.current) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setDoorOpen(doorOpen);
|
|
227
|
+
}, [doorOpen, setDoorOpen]);
|
|
228
|
+
|
|
229
|
+
const toggleDoor = () => {
|
|
230
|
+
const nextOpen = !isDoorOpenRef.current;
|
|
231
|
+
|
|
232
|
+
if (doorOpen !== undefined) {
|
|
233
|
+
onDoorOpenChange?.(nextOpen);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
setDoorOpen(nextOpen);
|
|
238
|
+
onDoorOpenChange?.(nextOpen);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div
|
|
243
|
+
ref={rootRef}
|
|
244
|
+
className={cn('relative flex w-full items-center justify-center overflow-hidden px-4 py-8', compact ? 'h-full min-h-full' : 'min-h-dvh', className)}
|
|
245
|
+
style={doorStyle}
|
|
246
|
+
>
|
|
247
|
+
<div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(circle_at_50%_20%,rgba(255,255,255,0.75),transparent_34%),linear-gradient(180deg,rgba(250,250,250,0.96),rgba(244,244,245,0.72))] dark:bg-[radial-gradient(circle_at_50%_20%,rgba(255,255,255,0.08),transparent_34%),linear-gradient(180deg,rgba(24,24,27,0.96),rgba(9,9,11,0.92))]" />
|
|
248
|
+
<div className="pointer-events-none absolute inset-x-0 bottom-0 -z-10 h-1/2 bg-[linear-gradient(180deg,transparent,rgba(0,0,0,0.05))] dark:bg-[linear-gradient(180deg,transparent,rgba(0,0,0,0.34))]" />
|
|
249
|
+
|
|
250
|
+
<div className="flex w-full max-w-3xl flex-col items-center gap-5">
|
|
251
|
+
<section className="text-center">
|
|
252
|
+
<h3 className={cn('whitespace-nowrap text-[clamp(2.15rem,8vw,3.4rem)] font-black leading-none tracking-normal bg-linear-to-r bg-clip-text text-transparent', activeGradientClass)}>
|
|
253
|
+
Page Not Found
|
|
254
|
+
</h3>
|
|
255
|
+
</section>
|
|
256
|
+
|
|
257
|
+
<section className="flex w-full justify-center">
|
|
258
|
+
<div className="relative aspect-[0.78] w-full max-w-[270px] sm:max-w-[315px] md:max-w-[330px] perspective-distant">
|
|
259
|
+
<div
|
|
260
|
+
data-not-found-light=""
|
|
261
|
+
className="absolute left-[14%] top-[7%] h-[86%] w-[72%] rounded-[28px] bg-[radial-gradient(circle_at_50%_45%,rgba(255,255,255,0.96),color-mix(in_srgb,var(--not-found-theme)_42%,transparent)_42%,transparent_72%)] opacity-25 blur-xl"
|
|
262
|
+
/>
|
|
263
|
+
<div className="absolute inset-[4%] rounded-[32px] border border-black/10 bg-neutral-950/5 shadow-2xl shadow-black/10 dark:border-white/10 dark:bg-white/5" />
|
|
264
|
+
<div className="absolute inset-[8%] rounded-[26px] bg-[linear-gradient(180deg,rgba(255,255,255,0.88),rgba(228,228,231,0.86))] shadow-[inset_0_1px_0_rgba(255,255,255,0.85)] dark:bg-[linear-gradient(180deg,rgba(39,39,42,0.92),rgba(24,24,27,0.96))]" />
|
|
265
|
+
<p
|
|
266
|
+
data-not-found-message=""
|
|
267
|
+
className="pointer-events-none absolute right-[16%] top-[29%] z-2 max-w-[46%] text-right text-sm font-medium leading-6 text-muted-foreground opacity-100"
|
|
268
|
+
>
|
|
269
|
+
<span className="block">The page</span>
|
|
270
|
+
<span className="block">you're looking for</span>
|
|
271
|
+
<span className="block">doesn't exist</span>
|
|
272
|
+
</p>
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
className="absolute inset-[8%] z-1 rounded-[26px] outline-none focus-visible:ring-2 focus-visible:ring-(--not-found-theme)"
|
|
276
|
+
aria-label="Toggle the 404 door"
|
|
277
|
+
onClick={toggleDoor}
|
|
278
|
+
/>
|
|
279
|
+
|
|
280
|
+
<div
|
|
281
|
+
data-not-found-door=""
|
|
282
|
+
className="absolute inset-[8%] z-10 origin-left rounded-[26px] border border-black/10 bg-[linear-gradient(145deg,rgba(255,255,255,0.92),rgba(212,212,216,0.9))] shadow-2xl shadow-black/20 will-change-transform dark:border-white/10 dark:bg-[linear-gradient(145deg,rgba(63,63,70,0.96),rgba(24,24,27,0.98))]"
|
|
283
|
+
>
|
|
284
|
+
<div className="absolute inset-4 overflow-hidden rounded-[20px]">
|
|
285
|
+
<div data-not-found-shimmer="" className="absolute inset-y-0 w-1/3 -skew-x-12 bg-white/35 blur-md dark:bg-white/12" />
|
|
286
|
+
<a
|
|
287
|
+
href={homeUrl}
|
|
288
|
+
className="absolute inset-x-5 bottom-5 flex h-[39%] flex-col items-center justify-center gap-2 rounded-2xl border border-black/10 bg-white/25 text-sm text-muted-foreground transition-opacity hover:opacity-80 dark:border-white/10 dark:bg-white/5"
|
|
289
|
+
>
|
|
290
|
+
<span className="inline-flex items-center gap-2">
|
|
291
|
+
{siteIcon}
|
|
292
|
+
<span>Woops!</span>
|
|
293
|
+
</span>
|
|
294
|
+
<span className={cn('text-xs font-semibold underline underline-offset-4', activeThemeClass, 'decoration-current')}>
|
|
295
|
+
Back to Homepage
|
|
296
|
+
</span>
|
|
297
|
+
</a>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<div
|
|
301
|
+
data-not-found-plate=""
|
|
302
|
+
className="absolute left-1/2 top-[18%] flex h-[88px] w-[156px] -translate-x-1/2 items-center justify-center overflow-hidden rounded-2xl border border-black/10 bg-white/86 shadow-lg shadow-black/10 dark:border-white/10 dark:bg-black/30"
|
|
303
|
+
>
|
|
304
|
+
<div data-not-found-shimmer="" className="absolute inset-y-0 w-1/2 -skew-x-12 bg-white/60 blur-md dark:bg-white/15" />
|
|
305
|
+
<span className={cn('relative text-5xl font-black tabular-nums bg-linear-to-r bg-clip-text text-transparent', activeGradientClass)}>
|
|
306
|
+
404
|
|
307
|
+
</span>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
data-not-found-handle=""
|
|
313
|
+
className="group absolute right-[1%] top-[39%] z-10 flex size-12 items-center justify-center rounded-full outline-none ring-offset-2 transition-transform hover:scale-105 focus-visible:ring-2 focus-visible:ring-(--not-found-theme)"
|
|
314
|
+
aria-label="Toggle the 404 door"
|
|
315
|
+
onClick={toggleDoor}
|
|
316
|
+
>
|
|
317
|
+
<span className="relative grid h-8 w-6 place-items-center rounded-full border border-black/10 bg-white/50 shadow-inner shadow-black/10 backdrop-blur-sm transform-[rotateY(18deg)] dark:border-white/15 dark:bg-black/25">
|
|
318
|
+
<span className="absolute size-10 rounded-full bg-(--not-found-theme) opacity-0 blur-md transition-opacity duration-300 group-hover:opacity-25" />
|
|
319
|
+
<span className="relative grid size-4 place-items-center rounded-full border border-black/10 bg-(--not-found-theme) shadow-lg shadow-black/25 dark:border-white/15">
|
|
320
|
+
<span className="absolute right-1 top-1 size-1 rounded-full bg-white/75" />
|
|
321
|
+
</span>
|
|
322
|
+
</span>
|
|
323
|
+
</button>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
{dust.map(dot => (
|
|
327
|
+
<span
|
|
328
|
+
key={dot.id}
|
|
329
|
+
data-not-found-dust=""
|
|
330
|
+
className={cn('absolute rounded-full opacity-0', activeBgClass)}
|
|
331
|
+
style={{
|
|
332
|
+
left: dot.left,
|
|
333
|
+
top: dot.top,
|
|
334
|
+
width: dot.size,
|
|
335
|
+
height: dot.size,
|
|
336
|
+
}}
|
|
337
|
+
/>
|
|
338
|
+
))}
|
|
339
|
+
</div>
|
|
340
|
+
</section>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useId, useRef } from 'react';
|
|
4
|
+
import { type WAAPIAnimation, waapi } from 'animejs';
|
|
5
|
+
import { useReducedMotion } from 'motion/react';
|
|
6
|
+
import {
|
|
7
|
+
BASE_DURATION_SECONDS,
|
|
8
|
+
BeamFrameShell,
|
|
9
|
+
BeamSvgLayer,
|
|
10
|
+
normalizeDuration,
|
|
11
|
+
useInteractiveRunning,
|
|
12
|
+
useMeasuredFrameSize,
|
|
13
|
+
type BeamFrameProps,
|
|
14
|
+
type FrameSize,
|
|
15
|
+
} from '../beam-frame-config';
|
|
16
|
+
|
|
17
|
+
export type { BeamFrameProps, BeamFrameTone } from '../beam-frame-config';
|
|
18
|
+
|
|
19
|
+
function AnimeBeamLayer({
|
|
20
|
+
isRunning,
|
|
21
|
+
duration,
|
|
22
|
+
radius,
|
|
23
|
+
size,
|
|
24
|
+
}: {
|
|
25
|
+
isRunning: boolean;
|
|
26
|
+
duration: number;
|
|
27
|
+
radius?: number;
|
|
28
|
+
size: FrameSize;
|
|
29
|
+
}) {
|
|
30
|
+
const aroundBeamRef = useRef<SVGGElement | null>(null);
|
|
31
|
+
const animationRef = useRef<WAAPIAnimation | null>(null);
|
|
32
|
+
const hasStartedRef = useRef(false);
|
|
33
|
+
const auraGradientId = useId().replace(/:/g, '');
|
|
34
|
+
const softGlowFilterId = useId().replace(/:/g, '');
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const node = aroundBeamRef.current;
|
|
38
|
+
|
|
39
|
+
if (!node) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (isRunning) {
|
|
44
|
+
hasStartedRef.current = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
node.style.opacity = isRunning || hasStartedRef.current ? 'var(--beam-frame-beam-opacity)' : '0';
|
|
48
|
+
|
|
49
|
+
if (!isRunning) {
|
|
50
|
+
animationRef.current?.pause();
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (animationRef.current) {
|
|
55
|
+
animationRef.current.speed = BASE_DURATION_SECONDS / duration;
|
|
56
|
+
animationRef.current.play();
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
animationRef.current = waapi.animate(node, {
|
|
61
|
+
strokeDashoffset: [0, -1],
|
|
62
|
+
loop: true,
|
|
63
|
+
duration: BASE_DURATION_SECONDS * 1000,
|
|
64
|
+
ease: 'linear',
|
|
65
|
+
});
|
|
66
|
+
animationRef.current.speed = BASE_DURATION_SECONDS / duration;
|
|
67
|
+
|
|
68
|
+
return undefined;
|
|
69
|
+
}, [duration, isRunning]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
return () => {
|
|
73
|
+
animationRef.current?.revert();
|
|
74
|
+
animationRef.current = null;
|
|
75
|
+
};
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<BeamSvgLayer
|
|
80
|
+
beamRef={aroundBeamRef}
|
|
81
|
+
auraGradientId={auraGradientId}
|
|
82
|
+
softGlowFilterId={softGlowFilterId}
|
|
83
|
+
radius={radius}
|
|
84
|
+
size={size}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function AnimeBeamFrame(props: BeamFrameProps) {
|
|
90
|
+
const {
|
|
91
|
+
children,
|
|
92
|
+
active = false,
|
|
93
|
+
interactive = true,
|
|
94
|
+
tone = 'theme',
|
|
95
|
+
duration = BASE_DURATION_SECONDS,
|
|
96
|
+
radius,
|
|
97
|
+
className,
|
|
98
|
+
} = props;
|
|
99
|
+
const prefersReducedMotion = useReducedMotion();
|
|
100
|
+
const { isRunning, interactionProps } = useInteractiveRunning(active, interactive);
|
|
101
|
+
const shouldRun = isRunning && !prefersReducedMotion;
|
|
102
|
+
const normalizedDuration = normalizeDuration(duration);
|
|
103
|
+
const { ref, size } = useMeasuredFrameSize<HTMLDivElement>();
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<BeamFrameShell
|
|
107
|
+
active={active}
|
|
108
|
+
interactive={interactive}
|
|
109
|
+
tone={tone}
|
|
110
|
+
duration={normalizedDuration}
|
|
111
|
+
radius={radius}
|
|
112
|
+
className={className}
|
|
113
|
+
isRunning={shouldRun}
|
|
114
|
+
interactionProps={interactionProps}
|
|
115
|
+
rootRef={ref}
|
|
116
|
+
renderBeam={() => (
|
|
117
|
+
<AnimeBeamLayer
|
|
118
|
+
isRunning={shouldRun}
|
|
119
|
+
duration={normalizedDuration}
|
|
120
|
+
radius={radius}
|
|
121
|
+
size={size}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</BeamFrameShell>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
4
|
+
import { createTimeline, stagger, type Timeline, utils } from 'animejs';
|
|
5
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_DOT_COUNT = 2024;
|
|
8
|
+
const DEFAULT_DURATION = 10000;
|
|
9
|
+
const DEFAULT_DISTANCE_REM = 20;
|
|
10
|
+
const DEFAULT_DOT_SIZE_EM = 1;
|
|
11
|
+
const DEFAULT_FONT_SIZE = 20;
|
|
12
|
+
|
|
13
|
+
export interface AnimeSpiralLoadingProps {
|
|
14
|
+
className?: string;
|
|
15
|
+
dotCount?: number;
|
|
16
|
+
duration?: number;
|
|
17
|
+
distanceRem?: number;
|
|
18
|
+
dotSizeEm?: number;
|
|
19
|
+
fontSize?: number;
|
|
20
|
+
paused?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function AnimeSpiralLoading({
|
|
24
|
+
className,
|
|
25
|
+
dotCount = DEFAULT_DOT_COUNT,
|
|
26
|
+
duration = DEFAULT_DURATION,
|
|
27
|
+
distanceRem = DEFAULT_DISTANCE_REM,
|
|
28
|
+
dotSizeEm = DEFAULT_DOT_SIZE_EM,
|
|
29
|
+
fontSize = DEFAULT_FONT_SIZE,
|
|
30
|
+
paused = false,
|
|
31
|
+
}: AnimeSpiralLoadingProps) {
|
|
32
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
33
|
+
const timelineRef = useRef<Timeline | null>(null);
|
|
34
|
+
const pausedRef = useRef(paused);
|
|
35
|
+
const safeDotCount = Math.max(1, Math.floor(dotCount));
|
|
36
|
+
const safeDuration = Math.max(1, duration);
|
|
37
|
+
const angle = useMemo(
|
|
38
|
+
() => (index: number) => utils.mapRange(index, 0, safeDotCount, 0, Math.PI * 100),
|
|
39
|
+
[safeDotCount]
|
|
40
|
+
);
|
|
41
|
+
const dots = useMemo(
|
|
42
|
+
() => Array.from({ length: safeDotCount }, (_, index) => {
|
|
43
|
+
const hue = utils.round((360 / safeDotCount) * index, 0);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
id: index,
|
|
47
|
+
background: `hsl(${hue}, 60%, 60%)`,
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
[safeDotCount]
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
pausedRef.current = paused;
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const root = rootRef.current;
|
|
57
|
+
|
|
58
|
+
if (!root) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const dotNodes = Array.from(root.querySelectorAll<HTMLElement>('[data-anime-spiral-dot]'));
|
|
63
|
+
|
|
64
|
+
timelineRef.current?.revert();
|
|
65
|
+
timelineRef.current = createTimeline()
|
|
66
|
+
.add(dotNodes, {
|
|
67
|
+
x: (_target: unknown, i: number) => `${Math.sin(angle(i)) * distanceRem}rem`,
|
|
68
|
+
y: (_target: unknown, i: number) => `${Math.cos(angle(i)) * distanceRem}rem`,
|
|
69
|
+
scale: [0, 0.4, 0.2, 0.9, 0],
|
|
70
|
+
playbackEase: 'inOutSine',
|
|
71
|
+
loop: true,
|
|
72
|
+
duration: safeDuration,
|
|
73
|
+
}, stagger([0, safeDuration]))
|
|
74
|
+
.init()
|
|
75
|
+
.seek(safeDuration);
|
|
76
|
+
|
|
77
|
+
if (pausedRef.current) {
|
|
78
|
+
timelineRef.current.pause();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return () => {
|
|
82
|
+
timelineRef.current?.revert();
|
|
83
|
+
timelineRef.current = null;
|
|
84
|
+
};
|
|
85
|
+
}, [angle, distanceRem, safeDuration, dots]);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const timeline = timelineRef.current;
|
|
89
|
+
|
|
90
|
+
if (!timeline) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (paused) {
|
|
95
|
+
timeline.pause();
|
|
96
|
+
} else {
|
|
97
|
+
timeline.play();
|
|
98
|
+
}
|
|
99
|
+
}, [paused]);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
ref={rootRef}
|
|
104
|
+
className={cn('relative h-dvh w-full overflow-hidden', className)}
|
|
105
|
+
style={{ fontSize }}
|
|
106
|
+
aria-hidden="true"
|
|
107
|
+
>
|
|
108
|
+
{dots.map(dot => (
|
|
109
|
+
<div
|
|
110
|
+
key={dot.id}
|
|
111
|
+
data-anime-spiral-dot=""
|
|
112
|
+
className="absolute left-1/2 top-1/2 rounded-full"
|
|
113
|
+
style={{
|
|
114
|
+
width: `${dotSizeEm}em`,
|
|
115
|
+
height: `${dotSizeEm}em`,
|
|
116
|
+
margin: `${dotSizeEm / -2}em 0 0 ${dotSizeEm / -2}em`,
|
|
117
|
+
backgroundColor: dot.background,
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
AnimeBeamFrame,
|
|
5
|
+
type BeamFrameProps,
|
|
6
|
+
type BeamFrameTone,
|
|
7
|
+
} from './anime-beam-frame';
|
|
8
|
+
|
|
9
|
+
export { AnimeSpiralLoading, type AnimeSpiralLoadingProps } from './anime-spiral-loading';
|
|
10
|
+
export { AnimeNotFoundPage, type AnimeNotFoundPageProps } from './anime-404-page';
|