diff --git a/components/pages/storyboard-view.tsx b/components/pages/storyboard-view.tsx
index 9029a1f..eccdfd4 100644
--- a/components/pages/storyboard-view.tsx
+++ b/components/pages/storyboard-view.tsx
@@ -6,6 +6,7 @@ import { DragDropContext, DropResult } from 'react-beautiful-dnd';
import { ScriptMetaInfo } from '../script-overview/script-meta-info';
import { SceneFilmstrip } from '../script-overview/scene-filmstrip';
import { StoryboardCardList } from '../storyboard/storyboard-card-list';
+import { Scene } from '../pages/script-overview';
// 分镜场景数据结构
export interface StoryboardScene {
@@ -183,6 +184,19 @@ export default function StoryboardView() {
setScenes([...scenes, newScene]);
};
+ // 将 StoryboardScene 转换为 Scene 类型
+ const adaptScenesForFilmstrip = (storyboardScenes: StoryboardScene[]): Scene[] => {
+ return storyboardScenes.map(scene => ({
+ id: scene.id,
+ name: scene.name,
+ description: scene.description,
+ imageUrl: scene.imageUrl,
+ plot: scene.shot, // 使用 shot 作为 plot
+ dialogue: scene.frame, // 使用 frame 作为 dialogue
+ narration: scene.atmosphere // 使用 atmosphere 作为 narration
+ }));
+ };
+
return (
@@ -209,7 +223,7 @@ export default function StoryboardView() {
{/* Filmstrip Preview */}
{
setSelectedSceneId(sceneId);
diff --git a/components/parallax.tsx b/components/parallax.tsx
index 3234f3b..5c87c0e 100644
--- a/components/parallax.tsx
+++ b/components/parallax.tsx
@@ -10,23 +10,23 @@ import spirited from '@/assets/3dr_spirited.jpg';
import howlbg from '@/assets/3dr_howlbg.jpg';
const Parallax = () => {
- const pageXRef = useRef(null);
- const cardsRef = useRef(null);
+ const pageXRef = useRef(null);
+ const cardsRef = useRef(null);
useEffect(() => {
const pageX = pageXRef.current;
const cards = cardsRef.current;
- const images = document.querySelectorAll('.card__img');
- const backgrounds = document.querySelectorAll('.card__bg');
+ const images = document.querySelectorAll('.card__img') as NodeListOf;
+ const backgrounds = document.querySelectorAll('.card__bg') as NodeListOf;
- let timeout;
+ let timeout: number | undefined;
const range = 40;
// 计算旋转角度
- const calcValue = (a, b) => ((a / b) * range - range / 2).toFixed(1);
+ const calcValue = (a: number, b: number): string => ((a / b) * range - range / 2).toFixed(1);
// 视差动画函数
- const parallax = (e) => {
+ const parallax = (e: MouseEvent): void => {
const x = e.clientX;
const y = e.clientY;
@@ -39,7 +39,9 @@ const Parallax = () => {
const yValue = calcValue(y, window.innerHeight);
// 设置卡片容器的旋转角度
- cards.style.transform = `rotateX(${yValue}deg) rotateY(${xValue}deg)`;
+ if (cards) {
+ cards.style.transform = `rotateX(${yValue}deg) rotateY(${xValue}deg)`;
+ }
// 设置所有图片的位移
images.forEach(item => {
@@ -48,18 +50,20 @@ const Parallax = () => {
// 设置所有背景的位置
backgrounds.forEach(item => {
- item.style.backgroundPosition = `${xValue * 0.45}px ${-yValue * 0.45}px`;
+ item.style.backgroundPosition = `${Number(xValue) * 0.45}px ${-Number(yValue) * 0.45}px`;
});
});
};
- pageX.addEventListener('mousemove', parallax, false);
+ if (pageX) {
+ pageX.addEventListener('mousemove', parallax as EventListener, false);
+
+ return () => {
+ pageX.removeEventListener('mousemove', parallax as EventListener);
+ };
+ }
- return () => {
- if (pageX) {
- pageX.removeEventListener('mousemove', parallax);
- }
- };
+ return undefined;
}, []);
return (
@@ -70,8 +74,8 @@ const Parallax = () => {
{/* 幽灵公主 */}
-
-
+
+
Princess Mononoke
@@ -79,8 +83,8 @@ const Parallax = () => {
{/* 千与千寻 */}
-
-
+
+
Spirited Away
@@ -88,8 +92,8 @@ const Parallax = () => {
{/* 哈尔的移动城堡 */}
-
-
+
+
Howl's Moving Castle
@@ -176,7 +180,11 @@ const Card = styled.div`
}
`;
-const CardBg = styled.div`
+interface CardBgProps {
+ bgUrl: string;
+}
+
+const CardBg = styled.div`
bottom: -50px;
left: -50px;
position: absolute;
@@ -185,7 +193,7 @@ const CardBg = styled.div`
transform-origin: 50% 50%;
transform: translateZ(-50px);
z-index: 0;
- background: ${props => `url(${props.bg}) center/cover no-repeat`};
+ background: ${props => `url(${props.bgUrl}) center/cover no-repeat`};
`;
const CardImg = styled.img`
diff --git a/components/ui/glass-icon-button.tsx b/components/ui/glass-icon-button.tsx
index 3faeea4..a7742d8 100644
--- a/components/ui/glass-icon-button.tsx
+++ b/components/ui/glass-icon-button.tsx
@@ -1,15 +1,18 @@
'use client';
-import React from 'react';
+import React, { forwardRef } from 'react';
import { motion } from 'framer-motion';
import { LucideIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
-interface GlassIconButtonProps extends React.ButtonHTMLAttributes {
+// Define props without the ref
+interface GlassIconButtonProps {
icon: LucideIcon;
tooltip?: string;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
+ className?: string;
+ [key: string]: any; // To allow spreading other props
}
const variantStyles = {
@@ -30,42 +33,41 @@ const iconSizes = {
lg: 'w-6 h-6',
};
-export function GlassIconButton({
- icon: Icon,
- tooltip,
- variant = 'secondary',
- size = 'md',
- className,
- ...props
-}: GlassIconButtonProps) {
- return (
-
-
- {tooltip && (
-
- {tooltip}
-
- )}
-
- );
-}
\ No newline at end of file
+// Create a motion button with forwardRef
+const MotionButton = motion.button;
+
+export const GlassIconButton = forwardRef(
+ ({ icon: Icon, tooltip, variant = 'secondary', size = 'md', className, ...props }, ref) => {
+ return (
+
+
+ {tooltip && (
+
+ {tooltip}
+
+ )}
+
+ );
+ }
+);
\ No newline at end of file
diff --git a/components/ui/progress.tsx b/components/ui/progress.tsx
index b959918..6b51c44 100644
--- a/components/ui/progress.tsx
+++ b/components/ui/progress.tsx
@@ -1,28 +1,40 @@
'use client';
import * as React from 'react';
-import * as ProgressPrimitive from '@radix-ui/react-progress';
-
import { cn } from '@/lib/utils';
-const Progress = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, value, ...props }, ref) => (
-
-
-
-));
-Progress.displayName = ProgressPrimitive.Root.displayName;
+interface ProgressProps {
+ value?: number;
+ max?: number;
+ className?: string;
+}
+
+const Progress = React.forwardRef(
+ ({ className, value = 0, max = 100, ...props }, ref) => {
+ const percentage = Math.min(Math.max(0, value), max) / max * 100;
+
+ return (
+
+ );
+ }
+);
+
+Progress.displayName = "Progress";
export { Progress };
diff --git a/components/vanta-halo-background.tsx b/components/vanta-halo-background.tsx
index ffddbbc..db00e87 100644
--- a/components/vanta-halo-background.tsx
+++ b/components/vanta-halo-background.tsx
@@ -9,13 +9,26 @@ interface VantaHaloBackgroundProps {
onLoaded?: () => void;
}
+// Add VANTA to the window type
+declare global {
+ interface Window {
+ VANTA: any;
+ }
+}
+
// 预加载 Vanta 脚本
const preloadVantaScript = () => {
const link = document.createElement('link')
link.rel = 'preload'
link.as = 'script'
- link.href = '/lib/vanta.halo.min.js' // 确保路径正确
+ link.href = '/js/three.min.js'
document.head.appendChild(link)
+
+ const link2 = document.createElement('link')
+ link2.rel = 'preload'
+ link2.as = 'script'
+ link2.href = '/lib/vanta.halo.min.js' // 确保路径正确
+ document.head.appendChild(link2)
}
// 使用 React.memo 来避免不必要的重渲染
@@ -30,41 +43,55 @@ const VantaHaloBackground = memo(({ onLoaded }: VantaHaloBackgroundProps) => {
const loadVanta = async () => {
try {
- const VANTA = await import('../lib/vanta.halo.min.js')
+ // Dynamically load the script instead of importing
+ const threeScript = document.createElement('script')
+ threeScript.src = '/js/three.min.js'
+ document.body.appendChild(threeScript)
- if (canceled || !vantaRef.current || effectInstance.current) return
+ threeScript.onload = () => {
+ const vantaScript = document.createElement('script')
+ vantaScript.src = '/lib/vanta.halo.min.js'
+ document.body.appendChild(vantaScript)
- // 使用 requestAnimationFrame 来控制动画帧率
- const animate = () => {
- if (effectInstance.current) {
- effectInstance.current.frameRequestId = requestAnimationFrame(animate)
+ vantaScript.onload = () => {
+ if (canceled || !vantaRef.current || effectInstance.current) return
+
+ // 使用 requestAnimationFrame 来控制动画帧率
+ const animate = () => {
+ if (effectInstance.current) {
+ effectInstance.current.frameRequestId = requestAnimationFrame(animate)
+ }
+ }
+
+ // Access VANTA from the window object after script loads
+ if (window.VANTA && window.VANTA.HALO) {
+ effectInstance.current = window.VANTA.HALO({
+ el: vantaRef.current,
+ THREE,
+ mouseControls: true,
+ touchControls: true,
+ gyroControls: false,
+ scale: 1.0,
+ scaleMobile: 1.0,
+ amplitudeFactor: 1.5,
+ ringFactor: 1.3,
+ size: 1.2,
+ minHeight: 200.00,
+ minWidth: 200.00,
+ // 优化渲染性能的参数
+ fps: 30, // 限制帧率
+ renderCacheSize: 4, // 缓存大小
+ })
+
+ frameId.current = requestAnimationFrame(animate)
+
+ // 通知加载完成
+ if (onLoaded) {
+ onLoaded();
+ }
+ }
}
}
-
- effectInstance.current = VANTA.default({
- el: vantaRef.current,
- THREE,
- mouseControls: true,
- touchControls: true,
- gyroControls: false,
- scale: 1.0,
- scaleMobile: 1.0,
- amplitudeFactor: 1.5,
- ringFactor: 1.3,
- size: 1.2,
- minHeight: 200.00,
- minWidth: 200.00,
- // 优化渲染性能的参数
- fps: 30, // 限制帧率
- renderCacheSize: 4, // 缓存大小
- })
-
- frameId.current = requestAnimationFrame(animate)
-
- // 通知加载完成
- if (onLoaded) {
- onLoaded();
- }
} catch (error) {
console.error('Failed to load Vanta effect:', error)
}
diff --git a/package-lock.json b/package-lock.json
index 463a08d..934bbd4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,7 +27,7 @@
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1",
- "@radix-ui/react-progress": "^1.1.0",
+ "@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.2.5",
@@ -1457,12 +1457,13 @@
}
},
"node_modules/@radix-ui/react-progress": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz",
- "integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
+ "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
+ "license": "MIT",
"dependencies": {
- "@radix-ui/react-context": "1.1.0",
- "@radix-ui/react-primitive": "2.0.0"
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -1479,10 +1480,67 @@
}
}
},
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
- "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -3705,9 +3763,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001667",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
- "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
+ "version": "1.0.30001726",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz",
+ "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==",
"funding": [
{
"type": "opencollective",
@@ -3721,7 +3779,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "4.1.2",
diff --git a/package.json b/package.json
index 3d94999..7e51e8a 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1",
- "@radix-ui/react-progress": "^1.1.0",
+ "@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.2.5",