@windrun-huaiin/third-ui 30.0.0 → 31.0.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 +109 -143
- package/dist/ai/ai-prompt-textarea.js +5 -5
- package/dist/ai/ai-prompt-textarea.mjs +5 -5
- package/dist/clerk/clerk-auth-appearance.d.ts +13 -0
- package/dist/clerk/clerk-auth-appearance.js +19 -0
- package/dist/clerk/clerk-auth-appearance.mjs +15 -0
- package/dist/clerk/clerk-auth-modal-appearance.d.ts +12 -0
- package/dist/clerk/clerk-auth-modal-appearance.js +17 -0
- package/dist/clerk/clerk-auth-modal-appearance.mjs +14 -0
- package/dist/clerk/clerk-page-context-generator.js +3 -3
- package/dist/clerk/clerk-page-context-generator.mjs +3 -3
- package/dist/clerk/clerk-page-generator.js +4 -4
- package/dist/clerk/clerk-page-generator.mjs +4 -4
- package/dist/clerk/clerk-user-client.js +2 -1
- package/dist/clerk/clerk-user-client.mjs +2 -1
- package/dist/clerk/fingerprint/fingerprint-client.d.ts +10 -10
- package/dist/clerk/fingerprint/fingerprint-client.js +20 -20
- package/dist/clerk/fingerprint/fingerprint-client.mjs +20 -20
- package/dist/clerk/fingerprint/fingerprint-provider.d.ts +3 -3
- package/dist/clerk/fingerprint/fingerprint-provider.js +8 -8
- package/dist/clerk/fingerprint/fingerprint-provider.mjs +8 -8
- package/dist/clerk/fingerprint/fingerprint-server.d.ts +12 -12
- package/dist/clerk/fingerprint/fingerprint-server.js +17 -17
- package/dist/clerk/fingerprint/fingerprint-server.mjs +17 -17
- package/dist/clerk/fingerprint/fingerprint-shared.d.ts +3 -3
- package/dist/clerk/fingerprint/fingerprint-shared.js +10 -10
- package/dist/clerk/fingerprint/fingerprint-shared.mjs +10 -10
- package/dist/clerk/fingerprint/types.d.ts +0 -1
- package/dist/clerk/fingerprint/use-fingerprint.js +7 -7
- package/dist/clerk/fingerprint/use-fingerprint.mjs +7 -7
- package/dist/clerk/signin-with-fingerprint-client.d.ts +2 -2
- package/dist/clerk/signin-with-fingerprint-client.js +7 -6
- package/dist/clerk/signin-with-fingerprint-client.mjs +7 -6
- package/dist/clerk/signup-button-with-fingerprint-client.js +6 -4
- package/dist/clerk/signup-button-with-fingerprint-client.mjs +6 -4
- package/dist/clerk/signup-with-fingerprint-client.d.ts +2 -2
- package/dist/clerk/signup-with-fingerprint-client.js +7 -6
- package/dist/clerk/signup-with-fingerprint-client.mjs +7 -6
- 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/heavy/mermaid.js +1 -1
- package/dist/fuma/heavy/mermaid.mjs +1 -1
- package/dist/fuma/site-x.js +0 -1
- package/dist/fuma/site-x.mjs +0 -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/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-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/index.d.ts +1 -0
- package/dist/main/anime/index.js +2 -0
- package/dist/main/anime/index.mjs +1 -0
- package/dist/main/calendar/calendar-date-range-input.js +1 -1
- package/dist/main/calendar/calendar-date-range-input.mjs +1 -1
- package/dist/main/credit/types.d.ts +8 -8
- 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/money-price/index.d.ts +1 -1
- package/dist/main/money-price/money-price-button.js +10 -10
- package/dist/main/money-price/money-price-button.mjs +10 -10
- package/dist/main/money-price/money-price-config-util.d.ts +30 -30
- package/dist/main/money-price/money-price-config-util.js +48 -48
- package/dist/main/money-price/money-price-config-util.mjs +48 -48
- package/dist/main/money-price/money-price-interactive.js +30 -18
- package/dist/main/money-price/money-price-interactive.mjs +30 -18
- package/dist/main/money-price/money-price-types.d.ts +7 -1
- package/dist/main/money-price/money-price-types.js +2 -2
- package/dist/main/money-price/money-price-types.mjs +2 -2
- package/dist/main/money-price/server.d.ts +1 -1
- 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/pill-select/x-pill-select.js +2 -2
- package/dist/main/pill-select/x-pill-select.mjs +2 -2
- package/dist/main/server.d.ts +1 -1
- package/dist/main/snake-loading-frame.js +1 -0
- package/dist/main/snake-loading-frame.mjs +1 -0
- package/package.json +13 -7
- package/src/ai/ai-prompt-textarea.tsx +6 -6
- package/src/clerk/clerk-auth-appearance.ts +16 -0
- package/src/clerk/clerk-page-context-generator.tsx +3 -5
- package/src/clerk/clerk-page-generator.tsx +9 -8
- package/src/clerk/clerk-user-client.tsx +14 -5
- package/src/clerk/fingerprint/fingerprint-client.ts +20 -20
- package/src/clerk/fingerprint/fingerprint-provider.tsx +11 -11
- package/src/clerk/fingerprint/fingerprint-server.ts +17 -17
- package/src/clerk/fingerprint/fingerprint-shared.ts +10 -10
- package/src/clerk/fingerprint/types.ts +0 -1
- package/src/clerk/fingerprint/use-fingerprint.ts +7 -7
- package/src/clerk/signin-with-fingerprint-client.tsx +7 -7
- package/src/clerk/signup-button-with-fingerprint-client.tsx +7 -5
- package/src/clerk/signup-with-fingerprint-client.tsx +7 -7
- package/src/fuma/base/custom-home-layout.tsx +4 -4
- package/src/fuma/fuma-page-genarator.tsx +2 -22
- package/src/fuma/heavy/mermaid.tsx +1 -1
- package/src/fuma/site-x.tsx +0 -1
- package/src/main/404-page.tsx +162 -0
- package/src/main/anime/anime-404-page.tsx +344 -0
- package/src/main/anime/index.ts +1 -0
- package/src/main/calendar/calendar-date-range-input.tsx +1 -1
- package/src/main/credit/types.ts +8 -8
- package/src/main/gallery/gallery-mobile-swiper.tsx +0 -1
- package/src/main/gallery/gallery-server.tsx +2 -2
- package/src/main/index.ts +1 -0
- package/src/main/money-price/index.ts +2 -0
- package/src/main/money-price/money-price-button.tsx +10 -10
- package/src/main/money-price/money-price-config-util.ts +49 -49
- package/src/main/money-price/money-price-interactive.tsx +40 -20
- package/src/main/money-price/money-price-types.ts +21 -14
- package/src/main/money-price/server.ts +2 -0
- package/src/main/pill-select/x-pill-select.tsx +2 -2
- package/src/main/server.ts +3 -1
- package/src/styles/third-ui.css +8 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { NotFoundIcon } from '@windrun-huaiin/base-ui/components/shared';
|
|
4
|
+
import {
|
|
5
|
+
THEME_BUTTON_GRADIENT_CLASS_MAP,
|
|
6
|
+
themeBgColor,
|
|
7
|
+
themeButtonGradientClass,
|
|
8
|
+
themeIconColor,
|
|
9
|
+
themeViaColor,
|
|
10
|
+
type SupportedThemeColor,
|
|
11
|
+
} from '@windrun-huaiin/base-ui/lib';
|
|
12
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
13
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
14
|
+
|
|
15
|
+
interface NotFoundPageProps {
|
|
16
|
+
siteIcon: ReactNode;
|
|
17
|
+
errorIcon?: ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
compact?: boolean;
|
|
20
|
+
themeClass?: SupportedThemeColor;
|
|
21
|
+
animated?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const THEME_BG_CLASS_MAP: Record<SupportedThemeColor, string> = {
|
|
25
|
+
'text-purple-500': 'bg-purple-500/20',
|
|
26
|
+
'text-orange-500': 'bg-orange-500/20',
|
|
27
|
+
'text-indigo-500': 'bg-indigo-500/20',
|
|
28
|
+
'text-emerald-500': 'bg-emerald-500/20',
|
|
29
|
+
'text-rose-500': 'bg-rose-500/20',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const THEME_VIA_CLASS_MAP: Record<SupportedThemeColor, string> = {
|
|
33
|
+
'text-purple-500': 'via-purple-500/20',
|
|
34
|
+
'text-orange-500': 'via-orange-500/20',
|
|
35
|
+
'text-indigo-500': 'via-indigo-500/20',
|
|
36
|
+
'text-emerald-500': 'via-emerald-500/20',
|
|
37
|
+
'text-rose-500': 'via-rose-500/20',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function NotFoundPage({
|
|
41
|
+
siteIcon,
|
|
42
|
+
errorIcon,
|
|
43
|
+
className,
|
|
44
|
+
compact = false,
|
|
45
|
+
themeClass,
|
|
46
|
+
animated = true,
|
|
47
|
+
}: NotFoundPageProps) {
|
|
48
|
+
const [glitchText, setGlitchText] = useState('404');
|
|
49
|
+
const homeUrl = process.env.NEXT_PUBLIC_BASE_URL || '/';
|
|
50
|
+
const activeThemeClass = themeClass ?? themeIconColor;
|
|
51
|
+
const activeGradientClass = themeClass ? THEME_BUTTON_GRADIENT_CLASS_MAP[themeClass] : themeButtonGradientClass;
|
|
52
|
+
const activeBgClass = themeClass ? THEME_BG_CLASS_MAP[themeClass] : themeBgColor;
|
|
53
|
+
const activeViaClass = themeClass ? THEME_VIA_CLASS_MAP[themeClass] : themeViaColor;
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!animated) {
|
|
57
|
+
setGlitchText('404');
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const glitchChars = ['4', '0', '4', '?', '#', '!', '*', '&', '%', '$'];
|
|
62
|
+
|
|
63
|
+
const interval = setInterval(() => {
|
|
64
|
+
if (Math.random() < 0.5) {
|
|
65
|
+
setGlitchText('404');
|
|
66
|
+
} else {
|
|
67
|
+
const randomChars = Array.from(
|
|
68
|
+
{ length: 3 },
|
|
69
|
+
() => glitchChars[Math.floor(Math.random() * glitchChars.length)]
|
|
70
|
+
).join('');
|
|
71
|
+
setGlitchText(randomChars);
|
|
72
|
+
}
|
|
73
|
+
}, 600);
|
|
74
|
+
|
|
75
|
+
return () => clearInterval(interval);
|
|
76
|
+
}, [animated]);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className={cn('relative flex w-full flex-col items-center justify-center px-4 py-8', compact ? 'h-full min-h-full' : 'min-h-dvh', className)}>
|
|
80
|
+
<div className="text-center space-y-8 max-w-2xl">
|
|
81
|
+
<div className="relative flex justify-center">
|
|
82
|
+
<h1
|
|
83
|
+
className={cn(
|
|
84
|
+
'text-8xl md:text-9xl font-bold bg-linear-to-r bg-clip-text text-transparent select-none',
|
|
85
|
+
activeGradientClass
|
|
86
|
+
)}
|
|
87
|
+
style={{
|
|
88
|
+
fontFamily: 'Montserrat, monospace',
|
|
89
|
+
textShadow: '0 0 30px rgba(172, 98, 253, 0.3)',
|
|
90
|
+
letterSpacing: '0.1em',
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{glitchText}
|
|
94
|
+
</h1>
|
|
95
|
+
<div className="absolute inset-0 pointer-events-none">
|
|
96
|
+
<div className={cn('h-full w-full bg-linear-to-b from-transparent to-transparent', animated && 'animate-pulse', activeViaClass)} />
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="space-y-4">
|
|
101
|
+
<h2 className="text-2xl md:text-3xl font-semibold text-foreground">
|
|
102
|
+
Page Not Found
|
|
103
|
+
</h2>
|
|
104
|
+
<p className="text-lg text-muted-foreground max-w-md mx-auto leading-relaxed">
|
|
105
|
+
The page you're looking for doesn't exist
|
|
106
|
+
</p>
|
|
107
|
+
<a
|
|
108
|
+
href={homeUrl}
|
|
109
|
+
className={cn(
|
|
110
|
+
'inline-flex text-sm font-medium underline underline-offset-4 transition-opacity hover:opacity-80',
|
|
111
|
+
activeThemeClass,
|
|
112
|
+
'decoration-current'
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
Back to Homepage
|
|
116
|
+
</a>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div className="flex justify-center items-center gap-8 pt-8 opacity-60">
|
|
120
|
+
<a
|
|
121
|
+
href={homeUrl}
|
|
122
|
+
className="flex items-center gap-2 text-sm text-muted-foreground transition-opacity hover:opacity-80"
|
|
123
|
+
>
|
|
124
|
+
{siteIcon}
|
|
125
|
+
<span>Woops!</span>
|
|
126
|
+
</a>
|
|
127
|
+
<div className={cn('w-2 h-2 rounded-full', animated && 'animate-ping', activeBgClass)} />
|
|
128
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
129
|
+
{errorIcon ?? <NotFoundIcon />}
|
|
130
|
+
<span>Error Code: 404</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div className={cn('pointer-events-none inset-0 overflow-hidden -z-10', compact ? 'absolute' : 'fixed')}>
|
|
136
|
+
<div
|
|
137
|
+
className="absolute inset-0 opacity-[0.02] dark:opacity-[0.05]"
|
|
138
|
+
style={{
|
|
139
|
+
backgroundImage: `
|
|
140
|
+
linear-gradient(rgba(172, 98, 253, 0.1) 1px, transparent 1px),
|
|
141
|
+
linear-gradient(90deg, rgba(172, 98, 253, 0.1) 1px, transparent 1px)
|
|
142
|
+
`,
|
|
143
|
+
backgroundSize: '50px 50px',
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
148
|
+
<div
|
|
149
|
+
key={i}
|
|
150
|
+
className={cn('absolute w-2 h-2 rounded-full', animated && 'animate-bounce', activeBgClass)}
|
|
151
|
+
style={{
|
|
152
|
+
left: `${20 + i * 15}%`,
|
|
153
|
+
top: `${30 + (i % 3) * 20}%`,
|
|
154
|
+
animationDelay: `${i * 0.5}s`,
|
|
155
|
+
animationDuration: `${2 + i * 0.3}s`,
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -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
|
+
}
|
package/src/main/anime/index.ts
CHANGED
package/src/main/credit/types.ts
CHANGED
|
@@ -47,21 +47,21 @@ export interface CreditCTAConfig {
|
|
|
47
47
|
export type CreditBucketStatus = 'active' | 'expiringSoon' | 'expired';
|
|
48
48
|
|
|
49
49
|
export interface CreditBucket {
|
|
50
|
-
/**
|
|
50
|
+
/** Business-defined credit type identifier for translation mapping or analytics. */
|
|
51
51
|
kind: string;
|
|
52
|
-
/**
|
|
52
|
+
/** Display name override; otherwise the component uses the default translation for kind. */
|
|
53
53
|
label?: string;
|
|
54
|
-
/**
|
|
54
|
+
/** Current credit balance. */
|
|
55
55
|
balance: number;
|
|
56
|
-
/**
|
|
56
|
+
/** Credit limit for this credit type. */
|
|
57
57
|
limit: number;
|
|
58
|
-
/**
|
|
58
|
+
/** Optional status label for highlighting states such as expiration. */
|
|
59
59
|
status?: CreditBucketStatus;
|
|
60
|
-
/**
|
|
60
|
+
/** Credit expiration time as a local time string, used to derive component state. */
|
|
61
61
|
expiresAt?: string;
|
|
62
|
-
/**
|
|
62
|
+
/** Progress percentage from 0 to 100; computed from balance/limit when omitted. */
|
|
63
63
|
progressPercent?: number;
|
|
64
|
-
/**
|
|
64
|
+
/** Additional details, such as remaining days or usage limits. */
|
|
65
65
|
description?: string;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -19,7 +19,6 @@ const swiperThemeStyle = {
|
|
|
19
19
|
export function GalleryMobileSwiper({ items }: Props) {
|
|
20
20
|
return (
|
|
21
21
|
<div className="block sm:hidden px-4">
|
|
22
|
-
{/* 外层容器:强制 maxWidth,防止任何溢出 */}
|
|
23
22
|
<div
|
|
24
23
|
className="w-full overflow-hidden"
|
|
25
24
|
style={{ maxWidth: "min(calc(100vw - 48px), 350px)", margin: "0 auto" }}
|
|
@@ -33,10 +33,10 @@ export async function Gallery({ locale, sectionClassName, button }: GalleryProps
|
|
|
33
33
|
</h2>
|
|
34
34
|
<p className="text-center max-w-2xl mx-auto mb-16">{data.description}</p>
|
|
35
35
|
|
|
36
|
-
{/*
|
|
36
|
+
{/* Mobile swiper */}
|
|
37
37
|
<GalleryMobileSwiper items={data.items} />
|
|
38
38
|
|
|
39
|
-
{/*
|
|
39
|
+
{/* Desktop grid */}
|
|
40
40
|
<GalleryDesktopGrid items={data.items} />
|
|
41
41
|
|
|
42
42
|
{button && <div className="text-center mx-auto mt-12 max-w-[85vw]">{button}</div>}
|
package/src/main/index.ts
CHANGED
|
@@ -44,9 +44,9 @@ export function MoneyPriceButton({
|
|
|
44
44
|
return 0;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// Button configuration for one-time mode.
|
|
48
48
|
const getOnetimeButtonConfig = () => {
|
|
49
|
-
//
|
|
49
|
+
// Anonymous users: show the sign-in button on every card.
|
|
50
50
|
if (!isAuthenticated) {
|
|
51
51
|
return {
|
|
52
52
|
text: texts.getStarted,
|
|
@@ -57,12 +57,12 @@ export function MoneyPriceButton({
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (subscriptionStatus === UserState.Anonymous) {
|
|
60
|
-
//
|
|
60
|
+
// Signed in but status unknown: treat as FreeUser.
|
|
61
61
|
console.warn('Clerk is authed OK but user is anonymous!');
|
|
62
62
|
return { text: '', disabled: true, hidden: true };
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
//
|
|
65
|
+
// Signed-in users: show the buy-credits button on every card in one-time mode.
|
|
66
66
|
return {
|
|
67
67
|
text: texts.buyCredits || texts.upgrade,
|
|
68
68
|
onClick: () => onAction(planKey, billingType),
|
|
@@ -71,9 +71,9 @@ export function MoneyPriceButton({
|
|
|
71
71
|
};
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// Button configuration for subscription mode.
|
|
75
75
|
const getSubscriptionButtonConfig = () => {
|
|
76
|
-
//
|
|
76
|
+
// Anonymous users.
|
|
77
77
|
if (!isAuthenticated) {
|
|
78
78
|
const getButtonText = () => {
|
|
79
79
|
switch (planKey) {
|
|
@@ -96,7 +96,7 @@ export function MoneyPriceButton({
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
//
|
|
99
|
+
// Signed-in users.
|
|
100
100
|
switch (subscriptionStatus) {
|
|
101
101
|
case UserState.FreeUser: {
|
|
102
102
|
if (planTier === 'F1') {
|
|
@@ -115,7 +115,7 @@ export function MoneyPriceButton({
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
case UserState.ProUser: {
|
|
118
|
-
//
|
|
118
|
+
// Do not allow downgrades to Free.
|
|
119
119
|
if (planTier === 'F1') {
|
|
120
120
|
return { hidden: true };
|
|
121
121
|
}
|
|
@@ -209,13 +209,13 @@ export function MoneyPriceButton({
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
default:
|
|
212
|
-
//
|
|
212
|
+
// Signed in but status unknown: treat as FreeUser.
|
|
213
213
|
console.warn('Clerk is authed OK but user is anonymous!');
|
|
214
214
|
return { text: '', disabled: true, hidden: true };
|
|
215
215
|
}
|
|
216
216
|
};
|
|
217
217
|
|
|
218
|
-
//
|
|
218
|
+
// Main button configuration function.
|
|
219
219
|
const getButtonConfig = () => {
|
|
220
220
|
if (billingType === 'onetime') {
|
|
221
221
|
return getOnetimeButtonConfig();
|