Tina Tarighian commited on
Commit
065d164
·
1 Parent(s): cb7c145
.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
README.md CHANGED
@@ -1,11 +1,79 @@
1
- ---
2
- title: Handspew
3
- emoji: 🌖
4
- colorFrom: gray
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- short_description: Using Gemini 2.0 and Mediapipe to power a handpuppet
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HandSpew
2
+
3
+ HandSpew is a simple web application that uses MediaPipe for hand landmark detection and Gemini 2.0 Flash for generating thoughts based on hand gestures. When you open your hand like a puppet mouth (thumb not touching other fingers), the app generates a thought related to what the camera sees.
4
+
5
+ ## Features
6
+
7
+ - Real-time hand landmark detection using MediaPipe
8
+ - Thought generation using Gemini 2.0 Flash
9
+ - Simple and intuitive UI
10
+ - Responsive design
11
+
12
+ ## Getting Started
13
+
14
+ ### Prerequisites
15
+
16
+ - Node.js 18.x or higher
17
+ - A Gemini API key from [Google AI Studio](https://ai.google.dev/)
18
+
19
+ ### Installation
20
+
21
+ 1. Clone the repository:
22
+
23
+ ```bash
24
+ git clone https://github.com/yourusername/handspew.git
25
+ cd handspew
26
+ ```
27
+
28
+ 2. Install dependencies:
29
+
30
+ ```bash
31
+ npm install
32
+ ```
33
+
34
+ 3. Create a `.env.local` file in the root directory and add your Gemini API key:
35
+
36
+ ```
37
+ GEMINI_API_KEY=your_gemini_api_key_here
38
+ ```
39
+
40
+ 4. Start the development server:
41
+
42
+ ```bash
43
+ npm run dev
44
+ ```
45
+
46
+ 5. Open [http://localhost:3000](http://localhost:3000) in your browser.
47
+
48
+ ## How to Use
49
+
50
+ 1. Allow camera access when prompted
51
+ 2. Position your hand in front of the camera
52
+ 3. Open and close your hand like a puppet mouth:
53
+ - When your thumb is touching another finger (closed mouth), no thoughts are generated
54
+ - When your thumb is not touching any finger (open mouth), a thought is generated based on what the camera sees
55
+
56
+ ## Deployment
57
+
58
+ ### Deploying to Hugging Face Spaces
59
+
60
+ 1. Create a new Space on Hugging Face
61
+ 2. Connect your GitHub repository
62
+ 3. Add your Gemini API key as a secret in the Space settings
63
+ 4. Deploy the app
64
+
65
+ ## Technologies Used
66
+
67
+ - [Next.js](https://nextjs.org/) - React framework
68
+ - [MediaPipe](https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker) - Hand landmark detection
69
+ - [Gemini 2.0 Flash](https://ai.google.dev/gemini-api/docs/vision) - Vision-based thought generation
70
+ - [Tailwind CSS](https://tailwindcss.com/) - Styling
71
+
72
+ ## License
73
+
74
+ This project is licensed under the MIT License - see the LICENSE file for details.
75
+
76
+ ## Acknowledgments
77
+
78
+ - Google for providing the MediaPipe and Gemini APIs
79
+ - The Next.js team for the amazing framework
components/CameraSetup.js ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ const CameraSetup = ({
4
+ videoRef,
5
+ canvasRef,
6
+ containerRef,
7
+ facingMode,
8
+ setFacingMode,
9
+ setCameraError,
10
+ setVideoAspectRatio,
11
+ updateCanvasSize,
12
+ isMobile
13
+ }) => {
14
+ // Track if component is mounted to prevent state updates after unmount
15
+ const isMounted = useRef(true);
16
+ // Track if camera is currently being set up
17
+ const isSettingUpCamera = useRef(false);
18
+
19
+ // Set up the webcam
20
+ useEffect(() => {
21
+ // Set mounted flag
22
+ isMounted.current = true;
23
+
24
+ const setupCamera = async () => {
25
+ if (!videoRef.current || !canvasRef.current || !containerRef.current) return;
26
+ // Prevent multiple simultaneous setup attempts
27
+ if (isSettingUpCamera.current) return;
28
+
29
+ isSettingUpCamera.current = true;
30
+
31
+ try {
32
+ // Stop any existing stream first
33
+ if (videoRef.current.srcObject) {
34
+ const tracks = videoRef.current.srcObject.getTracks();
35
+ tracks.forEach(track => track.stop());
36
+ }
37
+
38
+ // Get camera stream
39
+ const stream = await navigator.mediaDevices.getUserMedia({
40
+ video: {
41
+ facingMode: facingMode,
42
+ width: { ideal: 1920 },
43
+ height: { ideal: 1080 }
44
+ },
45
+ audio: false
46
+ });
47
+
48
+ // Check if component is still mounted before continuing
49
+ if (!isMounted.current) {
50
+ stream.getTracks().forEach(track => track.stop());
51
+ return;
52
+ }
53
+
54
+ videoRef.current.srcObject = stream;
55
+
56
+ // Use a try-catch block for the play() call
57
+ try {
58
+ await videoRef.current.play();
59
+ } catch (playError) {
60
+ console.log("Play interrupted, this is normal if component remounted:", playError);
61
+ // Don't treat play interruptions as fatal errors
62
+ if (playError.name !== "AbortError") {
63
+ throw playError;
64
+ }
65
+ }
66
+
67
+ // Get the actual video dimensions once metadata is loaded
68
+ videoRef.current.onloadedmetadata = () => {
69
+ if (!isMounted.current) return;
70
+
71
+ const videoWidth = videoRef.current.videoWidth;
72
+ const videoHeight = videoRef.current.videoHeight;
73
+ const aspectRatio = videoWidth / videoHeight;
74
+ setVideoAspectRatio(aspectRatio);
75
+
76
+ // Update canvas size with the correct aspect ratio
77
+ updateCanvasSize(aspectRatio);
78
+ };
79
+
80
+ if (isMounted.current) {
81
+ setCameraError(false);
82
+ console.log("Camera set up successfully");
83
+ }
84
+
85
+ } catch (error) {
86
+ console.error('Error accessing webcam:', error);
87
+ if (isMounted.current) {
88
+ setCameraError(true);
89
+ }
90
+ } finally {
91
+ isSettingUpCamera.current = false;
92
+ }
93
+ };
94
+
95
+ setupCamera();
96
+
97
+ return () => {
98
+ // Set mounted flag to false to prevent state updates after unmount
99
+ isMounted.current = false;
100
+
101
+ if (videoRef.current && videoRef.current.srcObject) {
102
+ const tracks = videoRef.current.srcObject.getTracks();
103
+ tracks.forEach(track => track.stop());
104
+ }
105
+ };
106
+ }, [videoRef, canvasRef, containerRef, facingMode, setCameraError, setVideoAspectRatio, updateCanvasSize]);
107
+
108
+ // Function to switch camera
109
+ const switchCamera = async () => {
110
+ if (!videoRef.current || isSettingUpCamera.current) return;
111
+
112
+ isSettingUpCamera.current = true;
113
+
114
+ // Stop current stream
115
+ if (videoRef.current.srcObject) {
116
+ const tracks = videoRef.current.srcObject.getTracks();
117
+ tracks.forEach(track => track.stop());
118
+ }
119
+
120
+ // Toggle facing mode
121
+ const newFacingMode = facingMode === 'user' ? 'environment' : 'user';
122
+ setFacingMode(newFacingMode);
123
+
124
+ try {
125
+ // Get new camera stream with updated facing mode
126
+ const stream = await navigator.mediaDevices.getUserMedia({
127
+ video: {
128
+ facingMode: newFacingMode,
129
+ width: { ideal: 1920 },
130
+ height: { ideal: 1080 }
131
+ },
132
+ audio: false
133
+ });
134
+
135
+ if (!isMounted.current) {
136
+ stream.getTracks().forEach(track => track.stop());
137
+ return;
138
+ }
139
+
140
+ videoRef.current.srcObject = stream;
141
+
142
+ try {
143
+ await videoRef.current.play();
144
+ } catch (playError) {
145
+ console.log("Play interrupted during camera switch:", playError);
146
+ // Don't treat play interruptions as fatal errors
147
+ if (playError.name !== "AbortError") {
148
+ throw playError;
149
+ }
150
+ }
151
+
152
+ if (isMounted.current) {
153
+ setCameraError(false);
154
+ console.log(`Camera switched to ${newFacingMode === 'user' ? 'front' : 'back'} camera`);
155
+ }
156
+ } catch (error) {
157
+ console.error('Error switching camera:', error);
158
+ if (isMounted.current) {
159
+ setCameraError(true);
160
+ }
161
+ } finally {
162
+ isSettingUpCamera.current = false;
163
+ }
164
+ };
165
+
166
+ return (
167
+ <>
168
+ <video
169
+ ref={videoRef}
170
+ className="hidden"
171
+ width="1280"
172
+ height="720"
173
+ autoPlay
174
+ playsInline
175
+ muted
176
+ />
177
+
178
+ {/* Camera switch button - only shown on mobile */}
179
+ {isMobile && (
180
+ <button
181
+ onClick={switchCamera}
182
+ className="absolute top-4 right-4 bg-white bg-opacity-70 p-2 rounded-full shadow-md z-10 hover:bg-opacity-90 transition-all"
183
+ aria-label={`Switch to ${facingMode === 'user' ? 'back' : 'front'} camera`}
184
+ >
185
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor">
186
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
187
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
188
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.5 7.5L9.5 12.5" />
189
+ </svg>
190
+ </button>
191
+ )}
192
+ </>
193
+ );
194
+ };
195
+
196
+ export default CameraSetup;
components/HandDetector.js ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useState, useEffect } from 'react';
2
+ import ParticleEffects, { useParticleEffects } from './ParticleEffects'
3
+ import MainContent from './MainContent';
4
+ import { AnimationStyles, AudioEffects } from './UIUtils';
5
+ import useDeviceAndCanvas from '../hooks/useDeviceAndCanvas';
6
+ import useHandDetection from '../hooks/useHandDetection';
7
+ import useThoughtGeneration from '../hooks/useThoughtGeneration';
8
+
9
+ const HandDetector = () => {
10
+ // Refs
11
+ const videoRef = useRef(null);
12
+ const canvasRef = useRef(null);
13
+ const containerRef = useRef(null);
14
+
15
+ // Camera state
16
+ const [facingMode, setFacingMode] = useState('user'); // 'user' for front camera, 'environment' for back camera
17
+ const [cameraError, setCameraError] = useState(false);
18
+
19
+ // Prompt state
20
+ const [customPrompt, setCustomPrompt] = useState(
21
+ "Look at this image and identify the main object or scene (ignoring any hands or fingers that might be visible). Generate a single, short, insightful thought (maximum 5 words) about this object or scene. Focus on something interesting, philosophical, or unexpected about it. DO NOT mention hands, fingers, pose estimation, motion tracking, or any computer vision technology in your response. Respond with just the thought, no additional text."
22
+ );
23
+
24
+ // Custom hooks
25
+ const {
26
+ isMobile,
27
+ isMac,
28
+ canvasWidth,
29
+ canvasHeight,
30
+ videoAspectRatio,
31
+ setVideoAspectRatio,
32
+ updateCanvasSize
33
+ } = useDeviceAndCanvas();
34
+
35
+ const {
36
+ handDetected,
37
+ isMouthOpen,
38
+ isLeftHand,
39
+ thumbPosition,
40
+ isFirstLoad
41
+ } = useHandDetection(videoRef, canvasRef, isMobile);
42
+
43
+ const {
44
+ thought,
45
+ isThinking,
46
+ animateThinking
47
+ } = useThoughtGeneration(canvasRef, isMouthOpen, customPrompt);
48
+
49
+ const {
50
+ particles,
51
+ popParticles,
52
+ createSparkleParticles,
53
+ createPopParticles
54
+ } = useParticleEffects(containerRef, isMobile);
55
+
56
+ // Clean up all component mounted refs on unmount
57
+ useEffect(() => {
58
+ return () => {
59
+ // This will run when the component unmounts
60
+ console.log("HandDetector component unmounting");
61
+ };
62
+ }, []);
63
+
64
+ return (
65
+ <div className="relative flex flex-col items-center w-full">
66
+ {/* Add animations and styles */}
67
+ <AnimationStyles />
68
+
69
+ {/* Add audio effects */}
70
+ <AudioEffects isThinking={isThinking} />
71
+
72
+ {cameraError ? (
73
+ <div className="bg-red-500 text-white p-4 rounded-lg mb-4 w-full max-w-md">
74
+ <p className="font-medium">Camera access denied. Please allow camera access to use this app.</p>
75
+ </div>
76
+ ) : (
77
+ <MainContent
78
+ videoRef={videoRef}
79
+ canvasRef={canvasRef}
80
+ containerRef={containerRef}
81
+ facingMode={facingMode}
82
+ setFacingMode={setFacingMode}
83
+ setCameraError={setCameraError}
84
+ customPrompt={customPrompt}
85
+ setCustomPrompt={setCustomPrompt}
86
+ isMac={isMac}
87
+ isMobile={isMobile}
88
+ canvasWidth={canvasWidth}
89
+ canvasHeight={canvasHeight}
90
+ setVideoAspectRatio={setVideoAspectRatio}
91
+ updateCanvasSize={updateCanvasSize}
92
+ handDetected={handDetected}
93
+ isMouthOpen={isMouthOpen}
94
+ isLeftHand={isLeftHand}
95
+ thumbPosition={thumbPosition}
96
+ isFirstLoad={isFirstLoad}
97
+ thought={thought}
98
+ isThinking={isThinking}
99
+ animateThinking={animateThinking}
100
+ particles={particles}
101
+ popParticles={popParticles}
102
+ createSparkleParticles={createSparkleParticles}
103
+ createPopParticles={createPopParticles}
104
+ />
105
+ )}
106
+ </div>
107
+ );
108
+ };
109
+
110
+ export default HandDetector;
components/MainContent.js ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ThoughtBubble from './ThoughtBubble';
3
+ import ParticleEffects from './ParticleEffects';
4
+ import PromptEditor from './PromptEditor';
5
+ import CameraSetup from './CameraSetup';
6
+ import { StatusIndicator } from './UIUtils';
7
+
8
+ const MainContent = ({
9
+ videoRef,
10
+ canvasRef,
11
+ containerRef,
12
+ facingMode,
13
+ setFacingMode,
14
+ setCameraError,
15
+ customPrompt,
16
+ setCustomPrompt,
17
+ isMac,
18
+ isMobile,
19
+ canvasWidth,
20
+ canvasHeight,
21
+ setVideoAspectRatio,
22
+ updateCanvasSize,
23
+ handDetected,
24
+ isMouthOpen,
25
+ isLeftHand,
26
+ thumbPosition,
27
+ isFirstLoad,
28
+ thought,
29
+ isThinking,
30
+ animateThinking,
31
+ particles,
32
+ popParticles,
33
+ createSparkleParticles,
34
+ createPopParticles
35
+ }) => {
36
+ return (
37
+ <>
38
+ {/* Prompt Editor */}
39
+ <PromptEditor
40
+ customPrompt={customPrompt}
41
+ setCustomPrompt={setCustomPrompt}
42
+ isMac={isMac}
43
+ />
44
+
45
+ <div ref={containerRef} className="relative w-full max-w-4xl canvas-container">
46
+ {/* Camera Setup */}
47
+ <CameraSetup
48
+ videoRef={videoRef}
49
+ canvasRef={canvasRef}
50
+ containerRef={containerRef}
51
+ facingMode={facingMode}
52
+ setFacingMode={setFacingMode}
53
+ setCameraError={setCameraError}
54
+ setVideoAspectRatio={setVideoAspectRatio}
55
+ updateCanvasSize={updateCanvasSize}
56
+ isMobile={isMobile}
57
+ />
58
+
59
+ {/* Canvas for hand detection */}
60
+ <canvas
61
+ ref={canvasRef}
62
+ className="rounded-lg shadow-lg w-full"
63
+ style={{ height: `${canvasHeight}px` }}
64
+ width={canvasWidth}
65
+ height={canvasHeight}
66
+ />
67
+
68
+ {/* Status Indicator */}
69
+ <StatusIndicator handDetected={handDetected} />
70
+
71
+ {/* Particle Effects */}
72
+ <ParticleEffects
73
+ particles={particles}
74
+ popParticles={popParticles}
75
+ />
76
+
77
+ {/* Thought Bubble */}
78
+ <ThoughtBubble
79
+ isFirstLoad={isFirstLoad}
80
+ isThinking={isThinking}
81
+ thought={thought}
82
+ isMouthOpen={isMouthOpen}
83
+ handDetected={handDetected}
84
+ isLeftHand={isLeftHand}
85
+ thumbPosition={thumbPosition}
86
+ canvasWidth={canvasWidth}
87
+ isMobile={isMobile}
88
+ animateThinking={animateThinking}
89
+ createSparkleParticles={createSparkleParticles}
90
+ createPopParticles={createPopParticles}
91
+ />
92
+ </div>
93
+ </>
94
+ );
95
+ };
96
+
97
+ export default MainContent;
components/ParticleEffects.js ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useCallback } from 'react';
2
+
3
+ /**
4
+ * Hook for managing particle effects
5
+ * @param {React.RefObject} containerRef - Reference to container element
6
+ * @param {boolean} isMobile - Whether the device is mobile
7
+ * @returns {Object} Particle effects state and functions
8
+ */
9
+ export const useParticleEffects = (containerRef, isMobile) => {
10
+ const [particles, setParticles] = useState([]);
11
+ const [popParticles, setPopParticles] = useState([]);
12
+ const particleId = useRef(0);
13
+ const isComponentMounted = useRef(true);
14
+
15
+ // Handler for sparkle particles
16
+ const handleCreateSparkleParticles = useCallback((x, y) => {
17
+ const newParticles = createSparkleParticles(x, y, containerRef, isMobile, particleId);
18
+ setParticles(prev => [...prev, ...newParticles]);
19
+ setTimeout(() => {
20
+ if (isComponentMounted.current) {
21
+ setParticles(prev => prev.filter(p => !newParticles.find(np => np.id === p.id)));
22
+ }
23
+ }, 1000);
24
+ }, [isMobile, containerRef]);
25
+
26
+ // Handler for pop particles
27
+ const handleCreatePopParticles = useCallback((x, y) => {
28
+ const newParticles = createPopParticles(x, y, containerRef, isMobile, particleId);
29
+ setPopParticles(prev => [...prev, ...newParticles]);
30
+ setTimeout(() => {
31
+ if (isComponentMounted.current) {
32
+ setPopParticles(prev => prev.filter(p => !newParticles.find(np => np.id === p.id)));
33
+ }
34
+ }, 600);
35
+ }, [isMobile, containerRef]);
36
+
37
+ return {
38
+ particles,
39
+ popParticles,
40
+ createSparkleParticles: handleCreateSparkleParticles,
41
+ createPopParticles: handleCreatePopParticles,
42
+ isComponentMounted
43
+ };
44
+ };
45
+
46
+ /**
47
+ * Particle effects component
48
+ */
49
+ const ParticleEffects = ({ particles, popParticles }) => {
50
+ return (
51
+ <div className="absolute inset-0 pointer-events-none overflow-hidden">
52
+ {/* Render sparkle particles */}
53
+ {particles.map(particle => (
54
+ <div
55
+ key={particle.id}
56
+ className="particle"
57
+ style={particle.style}
58
+ />
59
+ ))}
60
+
61
+ {/* Render pop particles */}
62
+ {popParticles.map(particle => (
63
+ <div
64
+ key={particle.id}
65
+ className="pop-particle"
66
+ style={particle.style}
67
+ />
68
+ ))}
69
+ </div>
70
+ );
71
+ };
72
+
73
+ /**
74
+ * Create sparkle particles
75
+ * @param {number} x - X position
76
+ * @param {number} y - Y position
77
+ * @param {React.RefObject} containerRef - Reference to container element
78
+ * @param {boolean} isMobile - Whether the device is mobile
79
+ * @param {React.MutableRefObject} particleId - Reference to particle ID counter
80
+ * @returns {Array} - Array of particle objects
81
+ */
82
+ export const createSparkleParticles = (x, y, containerRef, isMobile, particleId) => {
83
+ const newParticles = [];
84
+ const numParticles = isMobile ? 8 : 12;
85
+
86
+ // Get container position for relative positioning
87
+ const containerRect = containerRef.current.getBoundingClientRect();
88
+ const relativeX = x - containerRect.left;
89
+ const relativeY = y - containerRect.top;
90
+
91
+ for (let i = 0; i < numParticles; i++) {
92
+ const angle = (Math.PI * 2 * i) / numParticles;
93
+ const velocity = 2 + Math.random() * 2;
94
+ const size = isMobile ? 3 + Math.random() * 2 : 4 + Math.random() * 3;
95
+ const tx = Math.cos(angle) * (30 + Math.random() * 20);
96
+ const ty = Math.sin(angle) * (30 + Math.random() * 20);
97
+ const rotation = Math.random() * 360;
98
+
99
+ newParticles.push({
100
+ id: particleId.current++,
101
+ x: relativeX,
102
+ y: relativeY,
103
+ size,
104
+ style: {
105
+ '--tx': `${tx}px`,
106
+ '--ty': `${ty}px`,
107
+ '--r': `${rotation}deg`,
108
+ width: `${size}px`,
109
+ height: `${size}px`,
110
+ left: `${relativeX}px`,
111
+ top: `${relativeY}px`
112
+ }
113
+ });
114
+ }
115
+
116
+ return newParticles;
117
+ };
118
+
119
+ /**
120
+ * Create pop particles
121
+ * @param {number} x - X position
122
+ * @param {number} y - Y position
123
+ * @param {React.RefObject} containerRef - Reference to container element
124
+ * @param {boolean} isMobile - Whether the device is mobile
125
+ * @param {React.MutableRefObject} particleId - Reference to particle ID counter
126
+ * @returns {Array} - Array of particle objects
127
+ */
128
+ export const createPopParticles = (x, y, containerRef, isMobile, particleId) => {
129
+ const newParticles = [];
130
+ const numParticles = isMobile ? 6 : 8;
131
+
132
+ // Get container position for relative positioning
133
+ const containerRect = containerRef.current.getBoundingClientRect();
134
+ const relativeX = x - containerRect.left;
135
+ const relativeY = y - containerRect.top;
136
+
137
+ for (let i = 0; i < numParticles; i++) {
138
+ const angle = (Math.PI * 2 * i) / numParticles;
139
+ const velocity = 3 + Math.random() * 2;
140
+ const size = isMobile ? 4 + Math.random() * 3 : 6 + Math.random() * 4;
141
+ const tx = Math.cos(angle) * (40 + Math.random() * 20);
142
+ const ty = Math.sin(angle) * (40 + Math.random() * 20);
143
+ const rotation = Math.random() * 360;
144
+
145
+ newParticles.push({
146
+ id: particleId.current++,
147
+ x: relativeX,
148
+ y: relativeY,
149
+ size,
150
+ style: {
151
+ '--tx': `${tx}px`,
152
+ '--ty': `${ty}px`,
153
+ '--r': `${rotation}deg`,
154
+ width: `${size}px`,
155
+ height: `${size}px`,
156
+ left: `${relativeX}px`,
157
+ top: `${relativeY}px`
158
+ }
159
+ });
160
+ }
161
+
162
+ return newParticles;
163
+ };
164
+
165
+ export default ParticleEffects;
components/PromptEditor.js ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+
3
+ const PromptEditor = ({ customPrompt, setCustomPrompt, isMac }) => {
4
+ const [isEditingPrompt, setIsEditingPrompt] = useState(false);
5
+ const [tempPrompt, setTempPrompt] = useState("");
6
+
7
+ return (
8
+ <div className="w-full max-w-4xl mb-4">
9
+ <div className="flex justify-between items-center mb-1">
10
+ <label htmlFor="system-prompt" className="block text-sm font-medium text-gray-700">
11
+ System Prompt
12
+ </label>
13
+ {!isEditingPrompt ? (
14
+ <button
15
+ onClick={() => {
16
+ setTempPrompt(customPrompt);
17
+ setIsEditingPrompt(true);
18
+ }}
19
+ className="text-sm text-blue-600 hover:text-blue-800 flex items-center"
20
+ >
21
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
22
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
23
+ </svg>
24
+ Edit
25
+ </button>
26
+ ) : null}
27
+ </div>
28
+
29
+ {isEditingPrompt ? (
30
+ <div>
31
+ <textarea
32
+ id="system-prompt"
33
+ value={tempPrompt}
34
+ onChange={(e) => setTempPrompt(e.target.value)}
35
+ onKeyDown={(e) => {
36
+ // Save on Ctrl+Enter or Cmd+Enter
37
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
38
+ e.preventDefault();
39
+ setCustomPrompt(tempPrompt);
40
+ setIsEditingPrompt(false);
41
+ }
42
+ }}
43
+ className="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm"
44
+ rows={4}
45
+ placeholder="Enter your custom prompt for Gemini..."
46
+ />
47
+ <div className="flex justify-end mt-2 space-x-2">
48
+ <span className="text-xs text-gray-500 self-center mr-auto">
49
+ Tip: Press {isMac ? '⌘' : 'Ctrl'}+Enter to save
50
+ </span>
51
+ <button
52
+ onClick={() => setIsEditingPrompt(false)}
53
+ className="px-3 py-1 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-100"
54
+ >
55
+ Cancel
56
+ </button>
57
+ <button
58
+ onClick={() => {
59
+ setCustomPrompt(tempPrompt);
60
+ setIsEditingPrompt(false);
61
+ }}
62
+ className="px-3 py-1 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700"
63
+ >
64
+ Save
65
+ </button>
66
+ </div>
67
+ </div>
68
+ ) : (
69
+ <div className="p-3 bg-gray-50 border border-gray-200 rounded-lg text-sm text-gray-800 whitespace-pre-wrap">
70
+ {customPrompt}
71
+ </div>
72
+ )}
73
+ </div>
74
+ );
75
+ };
76
+
77
+ export default PromptEditor;
components/ThoughtBubble.js ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ const ThoughtBubble = ({
4
+ isFirstLoad,
5
+ isThinking,
6
+ thought,
7
+ isMouthOpen,
8
+ handDetected,
9
+ isLeftHand,
10
+ thumbPosition,
11
+ canvasWidth,
12
+ isMobile,
13
+ animateThinking,
14
+ createSparkleParticles,
15
+ createPopParticles
16
+ }) => {
17
+ const thoughtBubbleRef = useRef(null);
18
+
19
+ // Add particle effects when thought appears
20
+ useEffect(() => {
21
+ if (thought && isMouthOpen && thoughtBubbleRef.current) {
22
+ const bubbleRect = thoughtBubbleRef.current.getBoundingClientRect();
23
+ const x = bubbleRect.left + bubbleRect.width / 2;
24
+ const y = bubbleRect.top + bubbleRect.height / 2;
25
+ createSparkleParticles(x, y);
26
+ }
27
+ }, [thought, isMouthOpen, createSparkleParticles]);
28
+
29
+ // Add pop effect when thought disappears
30
+ useEffect(() => {
31
+ if (!isMouthOpen && thought && thoughtBubbleRef.current) {
32
+ const bubbleRect = thoughtBubbleRef.current.getBoundingClientRect();
33
+ const x = bubbleRect.left + bubbleRect.width / 2;
34
+ const y = bubbleRect.top + bubbleRect.height / 2;
35
+ createPopParticles(x, y);
36
+ }
37
+ }, [isMouthOpen, thought, createPopParticles]);
38
+
39
+ // Get bubble content based on state
40
+ const getBubbleContent = () => {
41
+ if (isFirstLoad) {
42
+ return <p className={`${isMobile ? 'text-sm' : 'text-lg'} font-medium text-gray-500 italic`}>Open and close your hand to generate thoughts...</p>;
43
+ }
44
+
45
+ if (isThinking) {
46
+ // Much larger emoji size with blinking animation
47
+ return (
48
+ <span
49
+ style={{
50
+ fontSize: isMobile ? '28px' : '36px',
51
+ animation: 'thinking-blink 1.5s ease-in-out infinite'
52
+ }}
53
+ >
54
+ 🤔
55
+ </span>
56
+ );
57
+ }
58
+
59
+ if (thought && isMouthOpen) {
60
+ // Much larger text size
61
+ return <p style={{ fontSize: isMobile ? '18px' : '22px' }} className="font-medium text-gray-800">{thought}</p>;
62
+ }
63
+
64
+ return <p className={`${isMobile ? 'text-sm' : 'text-lg'} font-medium text-gray-400 italic`}>Waiting for hand gesture...</p>;
65
+ };
66
+
67
+ // Determine if the bubble should be visible
68
+ const shouldShowBubble = () => {
69
+ // Only show during first load, when thinking, or when mouth is open
70
+ return isFirstLoad || isThinking || isMouthOpen;
71
+ };
72
+
73
+ // Calculate opacity based on state
74
+ const getOpacity = () => {
75
+ return isMouthOpen || isThinking ? 1 : 0.7;
76
+ };
77
+
78
+ // Get appropriate padding based on content
79
+ const getBubblePadding = () => {
80
+ if (isThinking) {
81
+ // More padding for larger emoji
82
+ return isMobile ? '10px' : '12px';
83
+ }
84
+ // More padding for larger text
85
+ return isMobile ? '12px 16px' : '16px 20px';
86
+ };
87
+
88
+ // Get appropriate border radius based on content
89
+ const getBubbleBorderRadius = () => {
90
+ if (isThinking) {
91
+ // More circular for emoji
92
+ return isMobile ? '35px' : '40px';
93
+ }
94
+ // Normal border radius for text content
95
+ return isMobile ? '16px' : '20px';
96
+ };
97
+
98
+ // Get appropriate width based on content
99
+ const getBubbleWidth = () => {
100
+ if (!handDetected) {
101
+ return isMobile ? '90%' : `${canvasWidth * 0.8}px`;
102
+ }
103
+
104
+ if (isThinking) {
105
+ // Much wider to accommodate larger emoji
106
+ return isMobile ? '70px' : '80px';
107
+ }
108
+
109
+ // Wider for larger text content
110
+ const bubbleWidth = isMobile
111
+ ? Math.min(220, canvasWidth * 0.7)
112
+ : Math.min(300, canvasWidth * 0.6);
113
+
114
+ return `${bubbleWidth}px`;
115
+ };
116
+
117
+ // Calculate thought bubble position
118
+ const getBubbleStyle = () => {
119
+ if (!handDetected) {
120
+ // Default position when no hand is detected
121
+ return {
122
+ position: 'absolute',
123
+ bottom: '20px',
124
+ left: '50%',
125
+ transform: animateThinking ? undefined : 'translateX(-50%)',
126
+ width: getBubbleWidth(),
127
+ };
128
+ }
129
+
130
+ const offset = isMobile ? 12 : 20; // Space between thumb and bubble
131
+
132
+ if (isLeftHand) {
133
+ // For left hand, position to the right of thumb
134
+ return {
135
+ position: 'absolute',
136
+ top: `${thumbPosition.y - (isMobile ? 20 : 30)}px`,
137
+ left: `${thumbPosition.x + offset}px`,
138
+ width: getBubbleWidth(),
139
+ maxWidth: isThinking ? 'none' : `${canvasWidth - thumbPosition.x - (offset * 2)}px` // Prevent overflow
140
+ };
141
+ } else {
142
+ // For right hand, position to the left of thumb
143
+ return {
144
+ position: 'absolute',
145
+ top: `${thumbPosition.y - (isMobile ? 20 : 30)}px`,
146
+ right: `${canvasWidth - thumbPosition.x + offset}px`,
147
+ width: getBubbleWidth(),
148
+ maxWidth: isThinking ? 'none' : `${thumbPosition.x - (offset * 2)}px` // Prevent overflow
149
+ };
150
+ }
151
+ };
152
+
153
+ if (!shouldShowBubble()) {
154
+ return null;
155
+ }
156
+
157
+ return (
158
+ <div
159
+ ref={thoughtBubbleRef}
160
+ className="thought-bubble"
161
+ style={{
162
+ ...getBubbleStyle(),
163
+ backgroundColor: 'rgba(255, 255, 255, 0.8)',
164
+ backdropFilter: 'blur(8px)',
165
+ border: 'none',
166
+ boxShadow: '0 4px 30px rgba(0, 0, 0, 0.1)',
167
+ padding: getBubblePadding(),
168
+ borderRadius: getBubbleBorderRadius(),
169
+ textAlign: isThinking ? 'center' : 'left',
170
+ zIndex: 50,
171
+ fontFamily: 'Google Sans, sans-serif',
172
+ transition: animateThinking ? 'none' : 'all 0.3s ease-in-out',
173
+ opacity: getOpacity(),
174
+ display: 'flex',
175
+ justifyContent: isThinking ? 'center' : 'flex-start',
176
+ alignItems: 'center',
177
+ animation: !isMouthOpen && thought ? 'pop-out 0.3s ease-out forwards' :
178
+ animateThinking ? 'spring-wiggle 1.2s cubic-bezier(0.2, 0.9, 0.3, 1.5)' :
179
+ 'none',
180
+ transformOrigin: isLeftHand ? 'left center' : 'right center',
181
+ willChange: 'transform, opacity'
182
+ }}
183
+ >
184
+ {getBubbleContent()}
185
+ </div>
186
+ );
187
+ };
188
+
189
+ export default ThoughtBubble;
components/UIUtils.js ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useEffect } from 'react';
2
+
3
+ /**
4
+ * Status indicator component that shows hand detection status
5
+ */
6
+ export const StatusIndicator = ({ handDetected }) => {
7
+ return (
8
+ <div className="absolute bottom-4 left-4 flex items-center">
9
+ <div className={`w-3 h-3 rounded-full ${handDetected ? 'bg-[#217BFE]' : 'bg-gray-400'}`}></div>
10
+ <span className={`ml-2 text-xs ${handDetected ? 'text-[#217BFE] font-medium' : 'text-gray-500'}`}>
11
+ {handDetected ? 'Hand detected' : 'No hand detected'}
12
+ </span>
13
+ </div>
14
+ );
15
+ };
16
+
17
+ /**
18
+ * Audio effects component that plays sounds based on app state
19
+ */
20
+ export const AudioEffects = ({ isThinking }) => {
21
+ const bloopSoundRef = useRef(null);
22
+
23
+ // Play sound effect when thinking state changes
24
+ useEffect(() => {
25
+ if (isThinking && bloopSoundRef.current) {
26
+ bloopSoundRef.current.currentTime = 0;
27
+ bloopSoundRef.current.play().catch(err => {
28
+ // Handle any autoplay restrictions
29
+ console.log("Could not play sound effect:", err);
30
+ });
31
+ }
32
+ }, [isThinking]);
33
+
34
+ return (
35
+ <audio
36
+ ref={bloopSoundRef}
37
+ src="/sounds/bloop.mp3"
38
+ preload="auto"
39
+ />
40
+ );
41
+ };
42
+
43
+ /**
44
+ * Animation styles component that defines global animations
45
+ */
46
+ export const AnimationStyles = () => {
47
+ return (
48
+ <>
49
+ <style jsx global>{`
50
+ @keyframes thinking-blink {
51
+ 0%, 100% { opacity: 1; transform: scale(1); }
52
+ 50% { opacity: 0.7; transform: scale(0.95); }
53
+ }
54
+
55
+ @keyframes spring-out {
56
+ 0% {
57
+ transform: scale(0) translateY(0);
58
+ opacity: 0;
59
+ }
60
+ 20% {
61
+ transform: scale(0.3) translateY(-5px);
62
+ opacity: 0.7;
63
+ }
64
+ 40% {
65
+ transform: scale(1.5) translateY(-30px);
66
+ }
67
+ 60% {
68
+ transform: scale(0.8) translateY(15px);
69
+ }
70
+ 75% {
71
+ transform: scale(1.2) translateY(-10px);
72
+ }
73
+ 90% {
74
+ transform: scale(0.95) translateY(5px);
75
+ }
76
+ 100% {
77
+ transform: scale(1) translateY(0);
78
+ opacity: 1;
79
+ }
80
+ }
81
+
82
+ @keyframes wiggle {
83
+ 0% { transform: rotate(0deg); }
84
+ 15% { transform: rotate(-15deg); }
85
+ 30% { transform: rotate(12deg); }
86
+ 45% { transform: rotate(-8deg); }
87
+ 60% { transform: rotate(5deg); }
88
+ 75% { transform: rotate(-2deg); }
89
+ 100% { transform: rotate(0deg); }
90
+ }
91
+
92
+ /* Combined animation that handles both scale and rotation */
93
+ @keyframes spring-wiggle {
94
+ 0% {
95
+ transform: scale(0) rotate(0deg) translateY(0);
96
+ opacity: 0;
97
+ }
98
+ 15% {
99
+ transform: scale(0.2) rotate(-5deg) translateY(-5px);
100
+ opacity: 0.5;
101
+ }
102
+ 30% {
103
+ transform: scale(1.5) rotate(12deg) translateY(-30px);
104
+ opacity: 1;
105
+ }
106
+ 45% {
107
+ transform: scale(0.8) rotate(-8deg) translateY(15px);
108
+ }
109
+ 60% {
110
+ transform: scale(1.2) rotate(5deg) translateY(-10px);
111
+ }
112
+ 75% {
113
+ transform: scale(0.95) rotate(-2deg) translateY(5px);
114
+ }
115
+ 90% {
116
+ transform: scale(1.05) rotate(1deg) translateY(-2px);
117
+ }
118
+ 100% {
119
+ transform: scale(1) rotate(0deg) translateY(0);
120
+ }
121
+ }
122
+
123
+ /* Add new animations for particles and popping */
124
+ @keyframes float-particle {
125
+ 0% {
126
+ transform: translate(0, 0) rotate(0deg);
127
+ opacity: 1;
128
+ }
129
+ 100% {
130
+ transform: translate(var(--tx), var(--ty)) rotate(var(--r));
131
+ opacity: 0;
132
+ }
133
+ }
134
+
135
+ @keyframes pop-out {
136
+ 0% {
137
+ transform: scale(1);
138
+ opacity: 1;
139
+ }
140
+ 50% {
141
+ transform: scale(1.2);
142
+ }
143
+ 100% {
144
+ transform: scale(0);
145
+ opacity: 0;
146
+ }
147
+ }
148
+
149
+ /* Particle styles */
150
+ .particle {
151
+ position: absolute;
152
+ pointer-events: none;
153
+ background: linear-gradient(135deg, #217BFE, #AC87EB);
154
+ border-radius: 50%;
155
+ animation: float-particle 1s ease-out forwards;
156
+ }
157
+
158
+ .pop-particle {
159
+ position: absolute;
160
+ pointer-events: none;
161
+ background: linear-gradient(135deg, #217BFE, #AC87EB);
162
+ border-radius: 50%;
163
+ animation: float-particle 0.6s ease-out forwards;
164
+ }
165
+ `}</style>
166
+ </>
167
+ );
168
+ };
hooks/useDeviceAndCanvas.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+
3
+ /**
4
+ * Combined hook for device detection and canvas management
5
+ * @returns {Object} Device and canvas management utilities
6
+ */
7
+ const useDeviceAndCanvas = () => {
8
+ // Device detection state
9
+ const [isMobile, setIsMobile] = useState(false);
10
+ const [isMac, setIsMac] = useState(false);
11
+
12
+ // Canvas management state
13
+ const [canvasWidth, setCanvasWidth] = useState(640);
14
+ const [canvasHeight, setCanvasHeight] = useState(480);
15
+ const [videoAspectRatio, setVideoAspectRatio] = useState(4/3);
16
+ const isComponentMounted = useRef(true);
17
+
18
+ // Device detection effect
19
+ useEffect(() => {
20
+ isComponentMounted.current = true;
21
+
22
+ const checkMobile = () => {
23
+ const mobile = window.innerWidth < 768;
24
+ setIsMobile(mobile);
25
+ };
26
+
27
+ checkMobile();
28
+ window.addEventListener('resize', checkMobile);
29
+
30
+ // Check if user is on Mac
31
+ if (typeof navigator !== 'undefined') {
32
+ setIsMac(navigator.platform.includes('Mac'));
33
+ }
34
+
35
+ return () => {
36
+ isComponentMounted.current = false;
37
+ window.removeEventListener('resize', checkMobile);
38
+ };
39
+ }, []);
40
+
41
+ // Function to update canvas size based on container width and video aspect ratio
42
+ const updateCanvasSize = useCallback((aspectRatio) => {
43
+ if (!isComponentMounted.current) return;
44
+
45
+ const containerWidth = document.querySelector('.canvas-container')?.clientWidth || window.innerWidth;
46
+ // Set maximum width for the canvas - increased for desktop
47
+ const maxWidth = Math.min(containerWidth, isMobile ? 640 : 960);
48
+ const height = maxWidth / aspectRatio;
49
+
50
+ setCanvasWidth(maxWidth);
51
+ setCanvasHeight(height);
52
+ }, [isMobile]);
53
+
54
+ return {
55
+ // Device detection
56
+ isMobile,
57
+ isMac,
58
+
59
+ // Canvas management
60
+ canvasWidth,
61
+ canvasHeight,
62
+ videoAspectRatio,
63
+ setVideoAspectRatio,
64
+ updateCanvasSize,
65
+ isComponentMounted
66
+ };
67
+ };
68
+
69
+ export default useDeviceAndCanvas;
hooks/useHandDetection.js ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { HandLandmarker, FilesetResolver } from '@mediapipe/tasks-vision';
3
+ import { drawLandmarks, analyzeHandGesture } from '../utils/handUtils';
4
+
5
+ const useHandDetection = (videoRef, canvasRef, isMobile) => {
6
+ const [handLandmarker, setHandLandmarker] = useState(null);
7
+ const [handDetected, setHandDetected] = useState(false);
8
+ const [isMouthOpen, setIsMouthOpen] = useState(false);
9
+ const [isLeftHand, setIsLeftHand] = useState(true);
10
+ const [thumbPosition, setThumbPosition] = useState({ x: 0, y: 0 });
11
+ const [isFirstLoad, setIsFirstLoad] = useState(true);
12
+
13
+ const requestRef = useRef(null);
14
+ const lastDetectionTimeRef = useRef(0);
15
+ const isComponentMounted = useRef(true);
16
+
17
+ // Initialize the HandLandmarker
18
+ useEffect(() => {
19
+ isComponentMounted.current = true;
20
+
21
+ const initializeHandLandmarker = async () => {
22
+ try {
23
+ const vision = await FilesetResolver.forVisionTasks(
24
+ "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
25
+ );
26
+
27
+ if (!isComponentMounted.current) return;
28
+
29
+ const landmarker = await HandLandmarker.createFromOptions(vision, {
30
+ baseOptions: {
31
+ modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task",
32
+ delegate: "GPU"
33
+ },
34
+ runningMode: "VIDEO",
35
+ numHands: 1,
36
+ minHandDetectionConfidence: 0.5,
37
+ minHandPresenceConfidence: 0.5,
38
+ minTrackingConfidence: 0.5
39
+ });
40
+
41
+ if (!isComponentMounted.current) return;
42
+
43
+ setHandLandmarker(landmarker);
44
+ console.log("Hand landmarker initialized successfully");
45
+
46
+ // Set first load to false after initialization
47
+ setTimeout(() => {
48
+ if (isComponentMounted.current) {
49
+ setIsFirstLoad(false);
50
+ }
51
+ }, 3000);
52
+ } catch (error) {
53
+ console.error("Error initializing hand landmarker:", error);
54
+ }
55
+ };
56
+
57
+ initializeHandLandmarker();
58
+
59
+ return () => {
60
+ isComponentMounted.current = false;
61
+ if (requestRef.current) {
62
+ cancelAnimationFrame(requestRef.current);
63
+ requestRef.current = null;
64
+ }
65
+ };
66
+ }, []);
67
+
68
+ // Process video frames and detect hand gestures
69
+ useEffect(() => {
70
+ if (!handLandmarker || !videoRef.current || !canvasRef.current) return;
71
+
72
+ const video = videoRef.current;
73
+ const canvas = canvasRef.current;
74
+ const ctx = canvas.getContext('2d');
75
+
76
+ const detectHands = async (now) => {
77
+ if (!isComponentMounted.current) return;
78
+
79
+ if (video.readyState < 2) {
80
+ requestRef.current = requestAnimationFrame(detectHands);
81
+ return;
82
+ }
83
+
84
+ // Only run detection every 100ms for performance
85
+ if (now - lastDetectionTimeRef.current > 100) {
86
+ lastDetectionTimeRef.current = now;
87
+
88
+ // Process the frame with HandLandmarker
89
+ const results = handLandmarker.detectForVideo(video, now);
90
+
91
+ // Clear the canvas
92
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
93
+
94
+ // Draw the video frame on the canvas, maintaining aspect ratio
95
+ const videoWidth = video.videoWidth;
96
+ const videoHeight = video.videoHeight;
97
+
98
+ // Calculate dimensions to maintain aspect ratio
99
+ let drawWidth = canvas.width;
100
+ let drawHeight = canvas.height;
101
+ let offsetX = 0;
102
+ let offsetY = 0;
103
+
104
+ // Center the video in the canvas
105
+ ctx.drawImage(video, offsetX, offsetY, drawWidth, drawHeight);
106
+
107
+ // Check if hands are detected
108
+ if (results.landmarks && results.landmarks.length > 0) {
109
+ const landmarks = results.landmarks[0];
110
+ setHandDetected(true);
111
+
112
+ // Draw hand landmarks
113
+ drawLandmarks(ctx, landmarks, canvas, isMobile);
114
+
115
+ // Analyze hand gesture
116
+ const { isOpen, isLeftHand: isLeft, thumbPosition: thumbPos } = analyzeHandGesture(landmarks);
117
+
118
+ // Update state with hand information
119
+ setIsLeftHand(isLeft);
120
+ setThumbPosition({
121
+ x: thumbPos.x * canvas.width,
122
+ y: thumbPos.y * canvas.height
123
+ });
124
+
125
+ // Update UI based on hand state
126
+ if (isOpen !== isMouthOpen) {
127
+ setIsMouthOpen(isOpen);
128
+ }
129
+ } else {
130
+ // No hands detected
131
+ setHandDetected(false);
132
+ if (isMouthOpen) {
133
+ setIsMouthOpen(false);
134
+ }
135
+ }
136
+ }
137
+
138
+ requestRef.current = requestAnimationFrame(detectHands);
139
+ };
140
+
141
+ requestRef.current = requestAnimationFrame(detectHands);
142
+
143
+ return () => {
144
+ if (requestRef.current) {
145
+ cancelAnimationFrame(requestRef.current);
146
+ requestRef.current = null;
147
+ }
148
+ };
149
+ }, [handLandmarker, isMouthOpen, isMobile, videoRef, canvasRef]);
150
+
151
+ return {
152
+ handDetected,
153
+ isMouthOpen,
154
+ isLeftHand,
155
+ thumbPosition,
156
+ isFirstLoad,
157
+ isComponentMounted
158
+ };
159
+ };
160
+
161
+ export default useHandDetection;
hooks/useThoughtGeneration.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useEffect } from 'react';
2
+
3
+ const useThoughtGeneration = (canvasRef, isMouthOpen, customPrompt) => {
4
+ const [thought, setThought] = useState('');
5
+ const [isThinking, setIsThinking] = useState(false);
6
+ const [animateThinking, setAnimateThinking] = useState(false);
7
+ const lastGenerationTime = useRef(0);
8
+ const isComponentMounted = useRef(true);
9
+
10
+ // Set up component mounted ref
11
+ useEffect(() => {
12
+ isComponentMounted.current = true;
13
+ return () => {
14
+ isComponentMounted.current = false;
15
+ };
16
+ }, []);
17
+
18
+ // Watch for changes in isMouthOpen to trigger thought generation
19
+ useEffect(() => {
20
+ if (isMouthOpen) {
21
+ // Only generate a new thought if we're not already thinking
22
+ // and enough time has passed since the last generation
23
+ const now = Date.now();
24
+ if (!isThinking && now - lastGenerationTime.current > 1000) {
25
+ generateThought();
26
+ lastGenerationTime.current = now;
27
+ }
28
+ }
29
+ }, [isMouthOpen]);
30
+
31
+ // Watch for changes in isThinking to trigger animation
32
+ useEffect(() => {
33
+ if (isThinking) {
34
+ setAnimateThinking(true);
35
+
36
+ // Reset animation state after animation completes
37
+ const timer = setTimeout(() => {
38
+ if (isComponentMounted.current) {
39
+ setAnimateThinking(false);
40
+ }
41
+ }, 1200); // Slightly longer to accommodate the enhanced animation
42
+ return () => clearTimeout(timer);
43
+ }
44
+ }, [isThinking]);
45
+
46
+ // Generate a thought using Gemini
47
+ const generateThought = async () => {
48
+ if (isThinking || !isComponentMounted.current) return;
49
+
50
+ setIsThinking(true);
51
+ console.log("Generating thought...");
52
+
53
+ try {
54
+ // Capture the current frame from the canvas
55
+ const canvas = canvasRef.current;
56
+ if (!canvas) return;
57
+
58
+ const imageData = canvas.toDataURL('image/jpeg', 0.8);
59
+
60
+ // Send the image to the Gemini API
61
+ const response = await fetch('/api/gemini', {
62
+ method: 'POST',
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ },
66
+ body: JSON.stringify({
67
+ image: imageData,
68
+ prompt: customPrompt
69
+ }),
70
+ });
71
+
72
+ if (!isComponentMounted.current) return;
73
+
74
+ if (!response.ok) {
75
+ throw new Error('Failed to generate thought');
76
+ }
77
+
78
+ const data = await response.json();
79
+
80
+ if (!isComponentMounted.current) return;
81
+
82
+ setThought(data.thought);
83
+ console.log("Thought generated:", data.thought);
84
+ } catch (error) {
85
+ console.error('Error generating thought:', error);
86
+ if (isComponentMounted.current) {
87
+ setThought('Hmm, I lost my train of thought...');
88
+ }
89
+ } finally {
90
+ if (isComponentMounted.current) {
91
+ setIsThinking(false);
92
+ }
93
+ }
94
+ };
95
+
96
+ return {
97
+ thought,
98
+ isThinking,
99
+ animateThinking,
100
+ generateThought
101
+ };
102
+ };
103
+
104
+ export default useThoughtGeneration;
jsconfig.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "@/*": ["./*"]
5
+ }
6
+ }
7
+ }
next.config.mjs ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ env: {
5
+ GEMINI_API_KEY: process.env.GEMINI_API_KEY,
6
+ },
7
+ };
8
+
9
+ export default nextConfig;
package-lock.json ADDED
@@ -0,0 +1,2324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "handspew",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "handspew",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "@google/generative-ai": "^0.2.1",
12
+ "@mediapipe/tasks-vision": "^0.10.12",
13
+ "next": "15.1.7",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "postcss": "^8",
19
+ "tailwindcss": "^3.4.1"
20
+ }
21
+ },
22
+ "node_modules/@alloc/quick-lru": {
23
+ "version": "5.2.0",
24
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
25
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
26
+ "dev": true,
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=10"
30
+ },
31
+ "funding": {
32
+ "url": "https://github.com/sponsors/sindresorhus"
33
+ }
34
+ },
35
+ "node_modules/@emnapi/runtime": {
36
+ "version": "1.3.1",
37
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
38
+ "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
39
+ "license": "MIT",
40
+ "optional": true,
41
+ "dependencies": {
42
+ "tslib": "^2.4.0"
43
+ }
44
+ },
45
+ "node_modules/@google/generative-ai": {
46
+ "version": "0.2.1",
47
+ "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.2.1.tgz",
48
+ "integrity": "sha512-gNmMFadfwi7qf/6M9gImgyGJXY1jKQ/de8vGOqgJ0PPYgQ7WwzZDavbKrIuXS2zdqZZaYtxW3EFN6aG9x5wtFw==",
49
+ "license": "Apache-2.0",
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ }
53
+ },
54
+ "node_modules/@img/sharp-darwin-arm64": {
55
+ "version": "0.33.5",
56
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
57
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
58
+ "cpu": [
59
+ "arm64"
60
+ ],
61
+ "license": "Apache-2.0",
62
+ "optional": true,
63
+ "os": [
64
+ "darwin"
65
+ ],
66
+ "engines": {
67
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
68
+ },
69
+ "funding": {
70
+ "url": "https://opencollective.com/libvips"
71
+ },
72
+ "optionalDependencies": {
73
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
74
+ }
75
+ },
76
+ "node_modules/@img/sharp-darwin-x64": {
77
+ "version": "0.33.5",
78
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
79
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
80
+ "cpu": [
81
+ "x64"
82
+ ],
83
+ "license": "Apache-2.0",
84
+ "optional": true,
85
+ "os": [
86
+ "darwin"
87
+ ],
88
+ "engines": {
89
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
90
+ },
91
+ "funding": {
92
+ "url": "https://opencollective.com/libvips"
93
+ },
94
+ "optionalDependencies": {
95
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
96
+ }
97
+ },
98
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
99
+ "version": "1.0.4",
100
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
101
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
102
+ "cpu": [
103
+ "arm64"
104
+ ],
105
+ "license": "LGPL-3.0-or-later",
106
+ "optional": true,
107
+ "os": [
108
+ "darwin"
109
+ ],
110
+ "funding": {
111
+ "url": "https://opencollective.com/libvips"
112
+ }
113
+ },
114
+ "node_modules/@img/sharp-libvips-darwin-x64": {
115
+ "version": "1.0.4",
116
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
117
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
118
+ "cpu": [
119
+ "x64"
120
+ ],
121
+ "license": "LGPL-3.0-or-later",
122
+ "optional": true,
123
+ "os": [
124
+ "darwin"
125
+ ],
126
+ "funding": {
127
+ "url": "https://opencollective.com/libvips"
128
+ }
129
+ },
130
+ "node_modules/@img/sharp-libvips-linux-arm": {
131
+ "version": "1.0.5",
132
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
133
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
134
+ "cpu": [
135
+ "arm"
136
+ ],
137
+ "license": "LGPL-3.0-or-later",
138
+ "optional": true,
139
+ "os": [
140
+ "linux"
141
+ ],
142
+ "funding": {
143
+ "url": "https://opencollective.com/libvips"
144
+ }
145
+ },
146
+ "node_modules/@img/sharp-libvips-linux-arm64": {
147
+ "version": "1.0.4",
148
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
149
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
150
+ "cpu": [
151
+ "arm64"
152
+ ],
153
+ "license": "LGPL-3.0-or-later",
154
+ "optional": true,
155
+ "os": [
156
+ "linux"
157
+ ],
158
+ "funding": {
159
+ "url": "https://opencollective.com/libvips"
160
+ }
161
+ },
162
+ "node_modules/@img/sharp-libvips-linux-s390x": {
163
+ "version": "1.0.4",
164
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
165
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
166
+ "cpu": [
167
+ "s390x"
168
+ ],
169
+ "license": "LGPL-3.0-or-later",
170
+ "optional": true,
171
+ "os": [
172
+ "linux"
173
+ ],
174
+ "funding": {
175
+ "url": "https://opencollective.com/libvips"
176
+ }
177
+ },
178
+ "node_modules/@img/sharp-libvips-linux-x64": {
179
+ "version": "1.0.4",
180
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
181
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
182
+ "cpu": [
183
+ "x64"
184
+ ],
185
+ "license": "LGPL-3.0-or-later",
186
+ "optional": true,
187
+ "os": [
188
+ "linux"
189
+ ],
190
+ "funding": {
191
+ "url": "https://opencollective.com/libvips"
192
+ }
193
+ },
194
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
195
+ "version": "1.0.4",
196
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
197
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
198
+ "cpu": [
199
+ "arm64"
200
+ ],
201
+ "license": "LGPL-3.0-or-later",
202
+ "optional": true,
203
+ "os": [
204
+ "linux"
205
+ ],
206
+ "funding": {
207
+ "url": "https://opencollective.com/libvips"
208
+ }
209
+ },
210
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
211
+ "version": "1.0.4",
212
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
213
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
214
+ "cpu": [
215
+ "x64"
216
+ ],
217
+ "license": "LGPL-3.0-or-later",
218
+ "optional": true,
219
+ "os": [
220
+ "linux"
221
+ ],
222
+ "funding": {
223
+ "url": "https://opencollective.com/libvips"
224
+ }
225
+ },
226
+ "node_modules/@img/sharp-linux-arm": {
227
+ "version": "0.33.5",
228
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
229
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
230
+ "cpu": [
231
+ "arm"
232
+ ],
233
+ "license": "Apache-2.0",
234
+ "optional": true,
235
+ "os": [
236
+ "linux"
237
+ ],
238
+ "engines": {
239
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
240
+ },
241
+ "funding": {
242
+ "url": "https://opencollective.com/libvips"
243
+ },
244
+ "optionalDependencies": {
245
+ "@img/sharp-libvips-linux-arm": "1.0.5"
246
+ }
247
+ },
248
+ "node_modules/@img/sharp-linux-arm64": {
249
+ "version": "0.33.5",
250
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
251
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
252
+ "cpu": [
253
+ "arm64"
254
+ ],
255
+ "license": "Apache-2.0",
256
+ "optional": true,
257
+ "os": [
258
+ "linux"
259
+ ],
260
+ "engines": {
261
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
262
+ },
263
+ "funding": {
264
+ "url": "https://opencollective.com/libvips"
265
+ },
266
+ "optionalDependencies": {
267
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
268
+ }
269
+ },
270
+ "node_modules/@img/sharp-linux-s390x": {
271
+ "version": "0.33.5",
272
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
273
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
274
+ "cpu": [
275
+ "s390x"
276
+ ],
277
+ "license": "Apache-2.0",
278
+ "optional": true,
279
+ "os": [
280
+ "linux"
281
+ ],
282
+ "engines": {
283
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
284
+ },
285
+ "funding": {
286
+ "url": "https://opencollective.com/libvips"
287
+ },
288
+ "optionalDependencies": {
289
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
290
+ }
291
+ },
292
+ "node_modules/@img/sharp-linux-x64": {
293
+ "version": "0.33.5",
294
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
295
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
296
+ "cpu": [
297
+ "x64"
298
+ ],
299
+ "license": "Apache-2.0",
300
+ "optional": true,
301
+ "os": [
302
+ "linux"
303
+ ],
304
+ "engines": {
305
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
306
+ },
307
+ "funding": {
308
+ "url": "https://opencollective.com/libvips"
309
+ },
310
+ "optionalDependencies": {
311
+ "@img/sharp-libvips-linux-x64": "1.0.4"
312
+ }
313
+ },
314
+ "node_modules/@img/sharp-linuxmusl-arm64": {
315
+ "version": "0.33.5",
316
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
317
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
318
+ "cpu": [
319
+ "arm64"
320
+ ],
321
+ "license": "Apache-2.0",
322
+ "optional": true,
323
+ "os": [
324
+ "linux"
325
+ ],
326
+ "engines": {
327
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
328
+ },
329
+ "funding": {
330
+ "url": "https://opencollective.com/libvips"
331
+ },
332
+ "optionalDependencies": {
333
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
334
+ }
335
+ },
336
+ "node_modules/@img/sharp-linuxmusl-x64": {
337
+ "version": "0.33.5",
338
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
339
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
340
+ "cpu": [
341
+ "x64"
342
+ ],
343
+ "license": "Apache-2.0",
344
+ "optional": true,
345
+ "os": [
346
+ "linux"
347
+ ],
348
+ "engines": {
349
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
350
+ },
351
+ "funding": {
352
+ "url": "https://opencollective.com/libvips"
353
+ },
354
+ "optionalDependencies": {
355
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
356
+ }
357
+ },
358
+ "node_modules/@img/sharp-wasm32": {
359
+ "version": "0.33.5",
360
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
361
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
362
+ "cpu": [
363
+ "wasm32"
364
+ ],
365
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
366
+ "optional": true,
367
+ "dependencies": {
368
+ "@emnapi/runtime": "^1.2.0"
369
+ },
370
+ "engines": {
371
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
372
+ },
373
+ "funding": {
374
+ "url": "https://opencollective.com/libvips"
375
+ }
376
+ },
377
+ "node_modules/@img/sharp-win32-ia32": {
378
+ "version": "0.33.5",
379
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
380
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
381
+ "cpu": [
382
+ "ia32"
383
+ ],
384
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
385
+ "optional": true,
386
+ "os": [
387
+ "win32"
388
+ ],
389
+ "engines": {
390
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
391
+ },
392
+ "funding": {
393
+ "url": "https://opencollective.com/libvips"
394
+ }
395
+ },
396
+ "node_modules/@img/sharp-win32-x64": {
397
+ "version": "0.33.5",
398
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
399
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
400
+ "cpu": [
401
+ "x64"
402
+ ],
403
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
404
+ "optional": true,
405
+ "os": [
406
+ "win32"
407
+ ],
408
+ "engines": {
409
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
410
+ },
411
+ "funding": {
412
+ "url": "https://opencollective.com/libvips"
413
+ }
414
+ },
415
+ "node_modules/@isaacs/cliui": {
416
+ "version": "8.0.2",
417
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
418
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
419
+ "dev": true,
420
+ "license": "ISC",
421
+ "dependencies": {
422
+ "string-width": "^5.1.2",
423
+ "string-width-cjs": "npm:string-width@^4.2.0",
424
+ "strip-ansi": "^7.0.1",
425
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
426
+ "wrap-ansi": "^8.1.0",
427
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
428
+ },
429
+ "engines": {
430
+ "node": ">=12"
431
+ }
432
+ },
433
+ "node_modules/@jridgewell/gen-mapping": {
434
+ "version": "0.3.8",
435
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
436
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
437
+ "dev": true,
438
+ "license": "MIT",
439
+ "dependencies": {
440
+ "@jridgewell/set-array": "^1.2.1",
441
+ "@jridgewell/sourcemap-codec": "^1.4.10",
442
+ "@jridgewell/trace-mapping": "^0.3.24"
443
+ },
444
+ "engines": {
445
+ "node": ">=6.0.0"
446
+ }
447
+ },
448
+ "node_modules/@jridgewell/resolve-uri": {
449
+ "version": "3.1.2",
450
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
451
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
452
+ "dev": true,
453
+ "license": "MIT",
454
+ "engines": {
455
+ "node": ">=6.0.0"
456
+ }
457
+ },
458
+ "node_modules/@jridgewell/set-array": {
459
+ "version": "1.2.1",
460
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
461
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
462
+ "dev": true,
463
+ "license": "MIT",
464
+ "engines": {
465
+ "node": ">=6.0.0"
466
+ }
467
+ },
468
+ "node_modules/@jridgewell/sourcemap-codec": {
469
+ "version": "1.5.0",
470
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
471
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
472
+ "dev": true,
473
+ "license": "MIT"
474
+ },
475
+ "node_modules/@jridgewell/trace-mapping": {
476
+ "version": "0.3.25",
477
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
478
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
479
+ "dev": true,
480
+ "license": "MIT",
481
+ "dependencies": {
482
+ "@jridgewell/resolve-uri": "^3.1.0",
483
+ "@jridgewell/sourcemap-codec": "^1.4.14"
484
+ }
485
+ },
486
+ "node_modules/@mediapipe/tasks-vision": {
487
+ "version": "0.10.21",
488
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.21.tgz",
489
+ "integrity": "sha512-TuhKH+credq4zLksGbYrnvJ1aLIWMc5r0UHwzxzql4BHECJwIAoBR61ZrqwGOW6ZmSBIzU1t4VtKj8hbxFaKeA==",
490
+ "license": "Apache-2.0"
491
+ },
492
+ "node_modules/@next/env": {
493
+ "version": "15.1.7",
494
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.7.tgz",
495
+ "integrity": "sha512-d9jnRrkuOH7Mhi+LHav2XW91HOgTAWHxjMPkXMGBc9B2b7614P7kjt8tAplRvJpbSt4nbO1lugcT/kAaWzjlLQ==",
496
+ "license": "MIT"
497
+ },
498
+ "node_modules/@next/swc-darwin-arm64": {
499
+ "version": "15.1.7",
500
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.7.tgz",
501
+ "integrity": "sha512-hPFwzPJDpA8FGj7IKV3Yf1web3oz2YsR8du4amKw8d+jAOHfYHYFpMkoF6vgSY4W6vB29RtZEklK9ayinGiCmQ==",
502
+ "cpu": [
503
+ "arm64"
504
+ ],
505
+ "license": "MIT",
506
+ "optional": true,
507
+ "os": [
508
+ "darwin"
509
+ ],
510
+ "engines": {
511
+ "node": ">= 10"
512
+ }
513
+ },
514
+ "node_modules/@next/swc-darwin-x64": {
515
+ "version": "15.1.7",
516
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.7.tgz",
517
+ "integrity": "sha512-2qoas+fO3OQKkU0PBUfwTiw/EYpN+kdAx62cePRyY1LqKtP09Vp5UcUntfZYajop5fDFTjSxCHfZVRxzi+9FYQ==",
518
+ "cpu": [
519
+ "x64"
520
+ ],
521
+ "license": "MIT",
522
+ "optional": true,
523
+ "os": [
524
+ "darwin"
525
+ ],
526
+ "engines": {
527
+ "node": ">= 10"
528
+ }
529
+ },
530
+ "node_modules/@next/swc-linux-arm64-gnu": {
531
+ "version": "15.1.7",
532
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.7.tgz",
533
+ "integrity": "sha512-sKLLwDX709mPdzxMnRIXLIT9zaX2w0GUlkLYQnKGoXeWUhcvpCrK+yevcwCJPdTdxZEUA0mOXGLdPsGkudGdnA==",
534
+ "cpu": [
535
+ "arm64"
536
+ ],
537
+ "license": "MIT",
538
+ "optional": true,
539
+ "os": [
540
+ "linux"
541
+ ],
542
+ "engines": {
543
+ "node": ">= 10"
544
+ }
545
+ },
546
+ "node_modules/@next/swc-linux-arm64-musl": {
547
+ "version": "15.1.7",
548
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.7.tgz",
549
+ "integrity": "sha512-zblK1OQbQWdC8fxdX4fpsHDw+VSpBPGEUX4PhSE9hkaWPrWoeIJn+baX53vbsbDRaDKd7bBNcXRovY1hEhFd7w==",
550
+ "cpu": [
551
+ "arm64"
552
+ ],
553
+ "license": "MIT",
554
+ "optional": true,
555
+ "os": [
556
+ "linux"
557
+ ],
558
+ "engines": {
559
+ "node": ">= 10"
560
+ }
561
+ },
562
+ "node_modules/@next/swc-linux-x64-gnu": {
563
+ "version": "15.1.7",
564
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.7.tgz",
565
+ "integrity": "sha512-GOzXutxuLvLHFDAPsMP2zDBMl1vfUHHpdNpFGhxu90jEzH6nNIgmtw/s1MDwpTOiM+MT5V8+I1hmVFeAUhkbgQ==",
566
+ "cpu": [
567
+ "x64"
568
+ ],
569
+ "license": "MIT",
570
+ "optional": true,
571
+ "os": [
572
+ "linux"
573
+ ],
574
+ "engines": {
575
+ "node": ">= 10"
576
+ }
577
+ },
578
+ "node_modules/@next/swc-linux-x64-musl": {
579
+ "version": "15.1.7",
580
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.7.tgz",
581
+ "integrity": "sha512-WrZ7jBhR7ATW1z5iEQ0ZJfE2twCNSXbpCSaAunF3BKcVeHFADSI/AW1y5Xt3DzTqPF1FzQlwQTewqetAABhZRQ==",
582
+ "cpu": [
583
+ "x64"
584
+ ],
585
+ "license": "MIT",
586
+ "optional": true,
587
+ "os": [
588
+ "linux"
589
+ ],
590
+ "engines": {
591
+ "node": ">= 10"
592
+ }
593
+ },
594
+ "node_modules/@next/swc-win32-arm64-msvc": {
595
+ "version": "15.1.7",
596
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.7.tgz",
597
+ "integrity": "sha512-LDnj1f3OVbou1BqvvXVqouJZKcwq++mV2F+oFHptToZtScIEnhNRJAhJzqAtTE2dB31qDYL45xJwrc+bLeKM2Q==",
598
+ "cpu": [
599
+ "arm64"
600
+ ],
601
+ "license": "MIT",
602
+ "optional": true,
603
+ "os": [
604
+ "win32"
605
+ ],
606
+ "engines": {
607
+ "node": ">= 10"
608
+ }
609
+ },
610
+ "node_modules/@next/swc-win32-x64-msvc": {
611
+ "version": "15.1.7",
612
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.7.tgz",
613
+ "integrity": "sha512-dC01f1quuf97viOfW05/K8XYv2iuBgAxJZl7mbCKEjMgdQl5JjAKJ0D2qMKZCgPWDeFbFT0Q0nYWwytEW0DWTQ==",
614
+ "cpu": [
615
+ "x64"
616
+ ],
617
+ "license": "MIT",
618
+ "optional": true,
619
+ "os": [
620
+ "win32"
621
+ ],
622
+ "engines": {
623
+ "node": ">= 10"
624
+ }
625
+ },
626
+ "node_modules/@nodelib/fs.scandir": {
627
+ "version": "2.1.5",
628
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
629
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
630
+ "dev": true,
631
+ "license": "MIT",
632
+ "dependencies": {
633
+ "@nodelib/fs.stat": "2.0.5",
634
+ "run-parallel": "^1.1.9"
635
+ },
636
+ "engines": {
637
+ "node": ">= 8"
638
+ }
639
+ },
640
+ "node_modules/@nodelib/fs.stat": {
641
+ "version": "2.0.5",
642
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
643
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
644
+ "dev": true,
645
+ "license": "MIT",
646
+ "engines": {
647
+ "node": ">= 8"
648
+ }
649
+ },
650
+ "node_modules/@nodelib/fs.walk": {
651
+ "version": "1.2.8",
652
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
653
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
654
+ "dev": true,
655
+ "license": "MIT",
656
+ "dependencies": {
657
+ "@nodelib/fs.scandir": "2.1.5",
658
+ "fastq": "^1.6.0"
659
+ },
660
+ "engines": {
661
+ "node": ">= 8"
662
+ }
663
+ },
664
+ "node_modules/@pkgjs/parseargs": {
665
+ "version": "0.11.0",
666
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
667
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
668
+ "dev": true,
669
+ "license": "MIT",
670
+ "optional": true,
671
+ "engines": {
672
+ "node": ">=14"
673
+ }
674
+ },
675
+ "node_modules/@swc/counter": {
676
+ "version": "0.1.3",
677
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
678
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
679
+ "license": "Apache-2.0"
680
+ },
681
+ "node_modules/@swc/helpers": {
682
+ "version": "0.5.15",
683
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
684
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
685
+ "license": "Apache-2.0",
686
+ "dependencies": {
687
+ "tslib": "^2.8.0"
688
+ }
689
+ },
690
+ "node_modules/ansi-regex": {
691
+ "version": "6.1.0",
692
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
693
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
694
+ "dev": true,
695
+ "license": "MIT",
696
+ "engines": {
697
+ "node": ">=12"
698
+ },
699
+ "funding": {
700
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
701
+ }
702
+ },
703
+ "node_modules/ansi-styles": {
704
+ "version": "6.2.1",
705
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
706
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
707
+ "dev": true,
708
+ "license": "MIT",
709
+ "engines": {
710
+ "node": ">=12"
711
+ },
712
+ "funding": {
713
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
714
+ }
715
+ },
716
+ "node_modules/any-promise": {
717
+ "version": "1.3.0",
718
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
719
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
720
+ "dev": true,
721
+ "license": "MIT"
722
+ },
723
+ "node_modules/anymatch": {
724
+ "version": "3.1.3",
725
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
726
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
727
+ "dev": true,
728
+ "license": "ISC",
729
+ "dependencies": {
730
+ "normalize-path": "^3.0.0",
731
+ "picomatch": "^2.0.4"
732
+ },
733
+ "engines": {
734
+ "node": ">= 8"
735
+ }
736
+ },
737
+ "node_modules/arg": {
738
+ "version": "5.0.2",
739
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
740
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
741
+ "dev": true,
742
+ "license": "MIT"
743
+ },
744
+ "node_modules/balanced-match": {
745
+ "version": "1.0.2",
746
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
747
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
748
+ "dev": true,
749
+ "license": "MIT"
750
+ },
751
+ "node_modules/binary-extensions": {
752
+ "version": "2.3.0",
753
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
754
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
755
+ "dev": true,
756
+ "license": "MIT",
757
+ "engines": {
758
+ "node": ">=8"
759
+ },
760
+ "funding": {
761
+ "url": "https://github.com/sponsors/sindresorhus"
762
+ }
763
+ },
764
+ "node_modules/brace-expansion": {
765
+ "version": "2.0.1",
766
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
767
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
768
+ "dev": true,
769
+ "license": "MIT",
770
+ "dependencies": {
771
+ "balanced-match": "^1.0.0"
772
+ }
773
+ },
774
+ "node_modules/braces": {
775
+ "version": "3.0.3",
776
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
777
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
778
+ "dev": true,
779
+ "license": "MIT",
780
+ "dependencies": {
781
+ "fill-range": "^7.1.1"
782
+ },
783
+ "engines": {
784
+ "node": ">=8"
785
+ }
786
+ },
787
+ "node_modules/busboy": {
788
+ "version": "1.6.0",
789
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
790
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
791
+ "dependencies": {
792
+ "streamsearch": "^1.1.0"
793
+ },
794
+ "engines": {
795
+ "node": ">=10.16.0"
796
+ }
797
+ },
798
+ "node_modules/camelcase-css": {
799
+ "version": "2.0.1",
800
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
801
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
802
+ "dev": true,
803
+ "license": "MIT",
804
+ "engines": {
805
+ "node": ">= 6"
806
+ }
807
+ },
808
+ "node_modules/caniuse-lite": {
809
+ "version": "1.0.30001700",
810
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
811
+ "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
812
+ "funding": [
813
+ {
814
+ "type": "opencollective",
815
+ "url": "https://opencollective.com/browserslist"
816
+ },
817
+ {
818
+ "type": "tidelift",
819
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
820
+ },
821
+ {
822
+ "type": "github",
823
+ "url": "https://github.com/sponsors/ai"
824
+ }
825
+ ],
826
+ "license": "CC-BY-4.0"
827
+ },
828
+ "node_modules/chokidar": {
829
+ "version": "3.6.0",
830
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
831
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
832
+ "dev": true,
833
+ "license": "MIT",
834
+ "dependencies": {
835
+ "anymatch": "~3.1.2",
836
+ "braces": "~3.0.2",
837
+ "glob-parent": "~5.1.2",
838
+ "is-binary-path": "~2.1.0",
839
+ "is-glob": "~4.0.1",
840
+ "normalize-path": "~3.0.0",
841
+ "readdirp": "~3.6.0"
842
+ },
843
+ "engines": {
844
+ "node": ">= 8.10.0"
845
+ },
846
+ "funding": {
847
+ "url": "https://paulmillr.com/funding/"
848
+ },
849
+ "optionalDependencies": {
850
+ "fsevents": "~2.3.2"
851
+ }
852
+ },
853
+ "node_modules/chokidar/node_modules/glob-parent": {
854
+ "version": "5.1.2",
855
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
856
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
857
+ "dev": true,
858
+ "license": "ISC",
859
+ "dependencies": {
860
+ "is-glob": "^4.0.1"
861
+ },
862
+ "engines": {
863
+ "node": ">= 6"
864
+ }
865
+ },
866
+ "node_modules/client-only": {
867
+ "version": "0.0.1",
868
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
869
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
870
+ "license": "MIT"
871
+ },
872
+ "node_modules/color": {
873
+ "version": "4.2.3",
874
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
875
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
876
+ "license": "MIT",
877
+ "optional": true,
878
+ "dependencies": {
879
+ "color-convert": "^2.0.1",
880
+ "color-string": "^1.9.0"
881
+ },
882
+ "engines": {
883
+ "node": ">=12.5.0"
884
+ }
885
+ },
886
+ "node_modules/color-convert": {
887
+ "version": "2.0.1",
888
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
889
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
890
+ "devOptional": true,
891
+ "license": "MIT",
892
+ "dependencies": {
893
+ "color-name": "~1.1.4"
894
+ },
895
+ "engines": {
896
+ "node": ">=7.0.0"
897
+ }
898
+ },
899
+ "node_modules/color-name": {
900
+ "version": "1.1.4",
901
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
902
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
903
+ "devOptional": true,
904
+ "license": "MIT"
905
+ },
906
+ "node_modules/color-string": {
907
+ "version": "1.9.1",
908
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
909
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
910
+ "license": "MIT",
911
+ "optional": true,
912
+ "dependencies": {
913
+ "color-name": "^1.0.0",
914
+ "simple-swizzle": "^0.2.2"
915
+ }
916
+ },
917
+ "node_modules/commander": {
918
+ "version": "4.1.1",
919
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
920
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
921
+ "dev": true,
922
+ "license": "MIT",
923
+ "engines": {
924
+ "node": ">= 6"
925
+ }
926
+ },
927
+ "node_modules/cross-spawn": {
928
+ "version": "7.0.6",
929
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
930
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
931
+ "dev": true,
932
+ "license": "MIT",
933
+ "dependencies": {
934
+ "path-key": "^3.1.0",
935
+ "shebang-command": "^2.0.0",
936
+ "which": "^2.0.1"
937
+ },
938
+ "engines": {
939
+ "node": ">= 8"
940
+ }
941
+ },
942
+ "node_modules/cssesc": {
943
+ "version": "3.0.0",
944
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
945
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
946
+ "dev": true,
947
+ "license": "MIT",
948
+ "bin": {
949
+ "cssesc": "bin/cssesc"
950
+ },
951
+ "engines": {
952
+ "node": ">=4"
953
+ }
954
+ },
955
+ "node_modules/detect-libc": {
956
+ "version": "2.0.3",
957
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
958
+ "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
959
+ "license": "Apache-2.0",
960
+ "optional": true,
961
+ "engines": {
962
+ "node": ">=8"
963
+ }
964
+ },
965
+ "node_modules/didyoumean": {
966
+ "version": "1.2.2",
967
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
968
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
969
+ "dev": true,
970
+ "license": "Apache-2.0"
971
+ },
972
+ "node_modules/dlv": {
973
+ "version": "1.1.3",
974
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
975
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
976
+ "dev": true,
977
+ "license": "MIT"
978
+ },
979
+ "node_modules/eastasianwidth": {
980
+ "version": "0.2.0",
981
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
982
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
983
+ "dev": true,
984
+ "license": "MIT"
985
+ },
986
+ "node_modules/emoji-regex": {
987
+ "version": "9.2.2",
988
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
989
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
990
+ "dev": true,
991
+ "license": "MIT"
992
+ },
993
+ "node_modules/fast-glob": {
994
+ "version": "3.3.3",
995
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
996
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
997
+ "dev": true,
998
+ "license": "MIT",
999
+ "dependencies": {
1000
+ "@nodelib/fs.stat": "^2.0.2",
1001
+ "@nodelib/fs.walk": "^1.2.3",
1002
+ "glob-parent": "^5.1.2",
1003
+ "merge2": "^1.3.0",
1004
+ "micromatch": "^4.0.8"
1005
+ },
1006
+ "engines": {
1007
+ "node": ">=8.6.0"
1008
+ }
1009
+ },
1010
+ "node_modules/fast-glob/node_modules/glob-parent": {
1011
+ "version": "5.1.2",
1012
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
1013
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
1014
+ "dev": true,
1015
+ "license": "ISC",
1016
+ "dependencies": {
1017
+ "is-glob": "^4.0.1"
1018
+ },
1019
+ "engines": {
1020
+ "node": ">= 6"
1021
+ }
1022
+ },
1023
+ "node_modules/fastq": {
1024
+ "version": "1.19.0",
1025
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz",
1026
+ "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
1027
+ "dev": true,
1028
+ "license": "ISC",
1029
+ "dependencies": {
1030
+ "reusify": "^1.0.4"
1031
+ }
1032
+ },
1033
+ "node_modules/fill-range": {
1034
+ "version": "7.1.1",
1035
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
1036
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
1037
+ "dev": true,
1038
+ "license": "MIT",
1039
+ "dependencies": {
1040
+ "to-regex-range": "^5.0.1"
1041
+ },
1042
+ "engines": {
1043
+ "node": ">=8"
1044
+ }
1045
+ },
1046
+ "node_modules/foreground-child": {
1047
+ "version": "3.3.1",
1048
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
1049
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
1050
+ "dev": true,
1051
+ "license": "ISC",
1052
+ "dependencies": {
1053
+ "cross-spawn": "^7.0.6",
1054
+ "signal-exit": "^4.0.1"
1055
+ },
1056
+ "engines": {
1057
+ "node": ">=14"
1058
+ },
1059
+ "funding": {
1060
+ "url": "https://github.com/sponsors/isaacs"
1061
+ }
1062
+ },
1063
+ "node_modules/fsevents": {
1064
+ "version": "2.3.3",
1065
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1066
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1067
+ "dev": true,
1068
+ "hasInstallScript": true,
1069
+ "license": "MIT",
1070
+ "optional": true,
1071
+ "os": [
1072
+ "darwin"
1073
+ ],
1074
+ "engines": {
1075
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1076
+ }
1077
+ },
1078
+ "node_modules/function-bind": {
1079
+ "version": "1.1.2",
1080
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1081
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1082
+ "dev": true,
1083
+ "license": "MIT",
1084
+ "funding": {
1085
+ "url": "https://github.com/sponsors/ljharb"
1086
+ }
1087
+ },
1088
+ "node_modules/glob": {
1089
+ "version": "10.4.5",
1090
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
1091
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
1092
+ "dev": true,
1093
+ "license": "ISC",
1094
+ "dependencies": {
1095
+ "foreground-child": "^3.1.0",
1096
+ "jackspeak": "^3.1.2",
1097
+ "minimatch": "^9.0.4",
1098
+ "minipass": "^7.1.2",
1099
+ "package-json-from-dist": "^1.0.0",
1100
+ "path-scurry": "^1.11.1"
1101
+ },
1102
+ "bin": {
1103
+ "glob": "dist/esm/bin.mjs"
1104
+ },
1105
+ "funding": {
1106
+ "url": "https://github.com/sponsors/isaacs"
1107
+ }
1108
+ },
1109
+ "node_modules/glob-parent": {
1110
+ "version": "6.0.2",
1111
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
1112
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
1113
+ "dev": true,
1114
+ "license": "ISC",
1115
+ "dependencies": {
1116
+ "is-glob": "^4.0.3"
1117
+ },
1118
+ "engines": {
1119
+ "node": ">=10.13.0"
1120
+ }
1121
+ },
1122
+ "node_modules/hasown": {
1123
+ "version": "2.0.2",
1124
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1125
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1126
+ "dev": true,
1127
+ "license": "MIT",
1128
+ "dependencies": {
1129
+ "function-bind": "^1.1.2"
1130
+ },
1131
+ "engines": {
1132
+ "node": ">= 0.4"
1133
+ }
1134
+ },
1135
+ "node_modules/is-arrayish": {
1136
+ "version": "0.3.2",
1137
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
1138
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
1139
+ "license": "MIT",
1140
+ "optional": true
1141
+ },
1142
+ "node_modules/is-binary-path": {
1143
+ "version": "2.1.0",
1144
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1145
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1146
+ "dev": true,
1147
+ "license": "MIT",
1148
+ "dependencies": {
1149
+ "binary-extensions": "^2.0.0"
1150
+ },
1151
+ "engines": {
1152
+ "node": ">=8"
1153
+ }
1154
+ },
1155
+ "node_modules/is-core-module": {
1156
+ "version": "2.16.1",
1157
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
1158
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
1159
+ "dev": true,
1160
+ "license": "MIT",
1161
+ "dependencies": {
1162
+ "hasown": "^2.0.2"
1163
+ },
1164
+ "engines": {
1165
+ "node": ">= 0.4"
1166
+ },
1167
+ "funding": {
1168
+ "url": "https://github.com/sponsors/ljharb"
1169
+ }
1170
+ },
1171
+ "node_modules/is-extglob": {
1172
+ "version": "2.1.1",
1173
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1174
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
1175
+ "dev": true,
1176
+ "license": "MIT",
1177
+ "engines": {
1178
+ "node": ">=0.10.0"
1179
+ }
1180
+ },
1181
+ "node_modules/is-fullwidth-code-point": {
1182
+ "version": "3.0.0",
1183
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
1184
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
1185
+ "dev": true,
1186
+ "license": "MIT",
1187
+ "engines": {
1188
+ "node": ">=8"
1189
+ }
1190
+ },
1191
+ "node_modules/is-glob": {
1192
+ "version": "4.0.3",
1193
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1194
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1195
+ "dev": true,
1196
+ "license": "MIT",
1197
+ "dependencies": {
1198
+ "is-extglob": "^2.1.1"
1199
+ },
1200
+ "engines": {
1201
+ "node": ">=0.10.0"
1202
+ }
1203
+ },
1204
+ "node_modules/is-number": {
1205
+ "version": "7.0.0",
1206
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1207
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1208
+ "dev": true,
1209
+ "license": "MIT",
1210
+ "engines": {
1211
+ "node": ">=0.12.0"
1212
+ }
1213
+ },
1214
+ "node_modules/isexe": {
1215
+ "version": "2.0.0",
1216
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
1217
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
1218
+ "dev": true,
1219
+ "license": "ISC"
1220
+ },
1221
+ "node_modules/jackspeak": {
1222
+ "version": "3.4.3",
1223
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
1224
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
1225
+ "dev": true,
1226
+ "license": "BlueOak-1.0.0",
1227
+ "dependencies": {
1228
+ "@isaacs/cliui": "^8.0.2"
1229
+ },
1230
+ "funding": {
1231
+ "url": "https://github.com/sponsors/isaacs"
1232
+ },
1233
+ "optionalDependencies": {
1234
+ "@pkgjs/parseargs": "^0.11.0"
1235
+ }
1236
+ },
1237
+ "node_modules/jiti": {
1238
+ "version": "1.21.7",
1239
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
1240
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
1241
+ "dev": true,
1242
+ "license": "MIT",
1243
+ "bin": {
1244
+ "jiti": "bin/jiti.js"
1245
+ }
1246
+ },
1247
+ "node_modules/lilconfig": {
1248
+ "version": "3.1.3",
1249
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
1250
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
1251
+ "dev": true,
1252
+ "license": "MIT",
1253
+ "engines": {
1254
+ "node": ">=14"
1255
+ },
1256
+ "funding": {
1257
+ "url": "https://github.com/sponsors/antonk52"
1258
+ }
1259
+ },
1260
+ "node_modules/lines-and-columns": {
1261
+ "version": "1.2.4",
1262
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
1263
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
1264
+ "dev": true,
1265
+ "license": "MIT"
1266
+ },
1267
+ "node_modules/lru-cache": {
1268
+ "version": "10.4.3",
1269
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
1270
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
1271
+ "dev": true,
1272
+ "license": "ISC"
1273
+ },
1274
+ "node_modules/merge2": {
1275
+ "version": "1.4.1",
1276
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
1277
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
1278
+ "dev": true,
1279
+ "license": "MIT",
1280
+ "engines": {
1281
+ "node": ">= 8"
1282
+ }
1283
+ },
1284
+ "node_modules/micromatch": {
1285
+ "version": "4.0.8",
1286
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
1287
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
1288
+ "dev": true,
1289
+ "license": "MIT",
1290
+ "dependencies": {
1291
+ "braces": "^3.0.3",
1292
+ "picomatch": "^2.3.1"
1293
+ },
1294
+ "engines": {
1295
+ "node": ">=8.6"
1296
+ }
1297
+ },
1298
+ "node_modules/minimatch": {
1299
+ "version": "9.0.5",
1300
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
1301
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
1302
+ "dev": true,
1303
+ "license": "ISC",
1304
+ "dependencies": {
1305
+ "brace-expansion": "^2.0.1"
1306
+ },
1307
+ "engines": {
1308
+ "node": ">=16 || 14 >=14.17"
1309
+ },
1310
+ "funding": {
1311
+ "url": "https://github.com/sponsors/isaacs"
1312
+ }
1313
+ },
1314
+ "node_modules/minipass": {
1315
+ "version": "7.1.2",
1316
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
1317
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
1318
+ "dev": true,
1319
+ "license": "ISC",
1320
+ "engines": {
1321
+ "node": ">=16 || 14 >=14.17"
1322
+ }
1323
+ },
1324
+ "node_modules/mz": {
1325
+ "version": "2.7.0",
1326
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
1327
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
1328
+ "dev": true,
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "any-promise": "^1.0.0",
1332
+ "object-assign": "^4.0.1",
1333
+ "thenify-all": "^1.0.0"
1334
+ }
1335
+ },
1336
+ "node_modules/nanoid": {
1337
+ "version": "3.3.8",
1338
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
1339
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
1340
+ "funding": [
1341
+ {
1342
+ "type": "github",
1343
+ "url": "https://github.com/sponsors/ai"
1344
+ }
1345
+ ],
1346
+ "license": "MIT",
1347
+ "bin": {
1348
+ "nanoid": "bin/nanoid.cjs"
1349
+ },
1350
+ "engines": {
1351
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1352
+ }
1353
+ },
1354
+ "node_modules/next": {
1355
+ "version": "15.1.7",
1356
+ "resolved": "https://registry.npmjs.org/next/-/next-15.1.7.tgz",
1357
+ "integrity": "sha512-GNeINPGS9c6OZKCvKypbL8GTsT5GhWPp4DM0fzkXJuXMilOO2EeFxuAY6JZbtk6XIl6Ws10ag3xRINDjSO5+wg==",
1358
+ "license": "MIT",
1359
+ "dependencies": {
1360
+ "@next/env": "15.1.7",
1361
+ "@swc/counter": "0.1.3",
1362
+ "@swc/helpers": "0.5.15",
1363
+ "busboy": "1.6.0",
1364
+ "caniuse-lite": "^1.0.30001579",
1365
+ "postcss": "8.4.31",
1366
+ "styled-jsx": "5.1.6"
1367
+ },
1368
+ "bin": {
1369
+ "next": "dist/bin/next"
1370
+ },
1371
+ "engines": {
1372
+ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
1373
+ },
1374
+ "optionalDependencies": {
1375
+ "@next/swc-darwin-arm64": "15.1.7",
1376
+ "@next/swc-darwin-x64": "15.1.7",
1377
+ "@next/swc-linux-arm64-gnu": "15.1.7",
1378
+ "@next/swc-linux-arm64-musl": "15.1.7",
1379
+ "@next/swc-linux-x64-gnu": "15.1.7",
1380
+ "@next/swc-linux-x64-musl": "15.1.7",
1381
+ "@next/swc-win32-arm64-msvc": "15.1.7",
1382
+ "@next/swc-win32-x64-msvc": "15.1.7",
1383
+ "sharp": "^0.33.5"
1384
+ },
1385
+ "peerDependencies": {
1386
+ "@opentelemetry/api": "^1.1.0",
1387
+ "@playwright/test": "^1.41.2",
1388
+ "babel-plugin-react-compiler": "*",
1389
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1390
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1391
+ "sass": "^1.3.0"
1392
+ },
1393
+ "peerDependenciesMeta": {
1394
+ "@opentelemetry/api": {
1395
+ "optional": true
1396
+ },
1397
+ "@playwright/test": {
1398
+ "optional": true
1399
+ },
1400
+ "babel-plugin-react-compiler": {
1401
+ "optional": true
1402
+ },
1403
+ "sass": {
1404
+ "optional": true
1405
+ }
1406
+ }
1407
+ },
1408
+ "node_modules/next/node_modules/postcss": {
1409
+ "version": "8.4.31",
1410
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
1411
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
1412
+ "funding": [
1413
+ {
1414
+ "type": "opencollective",
1415
+ "url": "https://opencollective.com/postcss/"
1416
+ },
1417
+ {
1418
+ "type": "tidelift",
1419
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1420
+ },
1421
+ {
1422
+ "type": "github",
1423
+ "url": "https://github.com/sponsors/ai"
1424
+ }
1425
+ ],
1426
+ "license": "MIT",
1427
+ "dependencies": {
1428
+ "nanoid": "^3.3.6",
1429
+ "picocolors": "^1.0.0",
1430
+ "source-map-js": "^1.0.2"
1431
+ },
1432
+ "engines": {
1433
+ "node": "^10 || ^12 || >=14"
1434
+ }
1435
+ },
1436
+ "node_modules/normalize-path": {
1437
+ "version": "3.0.0",
1438
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1439
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1440
+ "dev": true,
1441
+ "license": "MIT",
1442
+ "engines": {
1443
+ "node": ">=0.10.0"
1444
+ }
1445
+ },
1446
+ "node_modules/object-assign": {
1447
+ "version": "4.1.1",
1448
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1449
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1450
+ "dev": true,
1451
+ "license": "MIT",
1452
+ "engines": {
1453
+ "node": ">=0.10.0"
1454
+ }
1455
+ },
1456
+ "node_modules/object-hash": {
1457
+ "version": "3.0.0",
1458
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
1459
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
1460
+ "dev": true,
1461
+ "license": "MIT",
1462
+ "engines": {
1463
+ "node": ">= 6"
1464
+ }
1465
+ },
1466
+ "node_modules/package-json-from-dist": {
1467
+ "version": "1.0.1",
1468
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
1469
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
1470
+ "dev": true,
1471
+ "license": "BlueOak-1.0.0"
1472
+ },
1473
+ "node_modules/path-key": {
1474
+ "version": "3.1.1",
1475
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1476
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1477
+ "dev": true,
1478
+ "license": "MIT",
1479
+ "engines": {
1480
+ "node": ">=8"
1481
+ }
1482
+ },
1483
+ "node_modules/path-parse": {
1484
+ "version": "1.0.7",
1485
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
1486
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
1487
+ "dev": true,
1488
+ "license": "MIT"
1489
+ },
1490
+ "node_modules/path-scurry": {
1491
+ "version": "1.11.1",
1492
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
1493
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
1494
+ "dev": true,
1495
+ "license": "BlueOak-1.0.0",
1496
+ "dependencies": {
1497
+ "lru-cache": "^10.2.0",
1498
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
1499
+ },
1500
+ "engines": {
1501
+ "node": ">=16 || 14 >=14.18"
1502
+ },
1503
+ "funding": {
1504
+ "url": "https://github.com/sponsors/isaacs"
1505
+ }
1506
+ },
1507
+ "node_modules/picocolors": {
1508
+ "version": "1.1.1",
1509
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1510
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1511
+ "license": "ISC"
1512
+ },
1513
+ "node_modules/picomatch": {
1514
+ "version": "2.3.1",
1515
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1516
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1517
+ "dev": true,
1518
+ "license": "MIT",
1519
+ "engines": {
1520
+ "node": ">=8.6"
1521
+ },
1522
+ "funding": {
1523
+ "url": "https://github.com/sponsors/jonschlinkert"
1524
+ }
1525
+ },
1526
+ "node_modules/pify": {
1527
+ "version": "2.3.0",
1528
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
1529
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
1530
+ "dev": true,
1531
+ "license": "MIT",
1532
+ "engines": {
1533
+ "node": ">=0.10.0"
1534
+ }
1535
+ },
1536
+ "node_modules/pirates": {
1537
+ "version": "4.0.6",
1538
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
1539
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
1540
+ "dev": true,
1541
+ "license": "MIT",
1542
+ "engines": {
1543
+ "node": ">= 6"
1544
+ }
1545
+ },
1546
+ "node_modules/postcss": {
1547
+ "version": "8.5.3",
1548
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
1549
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
1550
+ "dev": true,
1551
+ "funding": [
1552
+ {
1553
+ "type": "opencollective",
1554
+ "url": "https://opencollective.com/postcss/"
1555
+ },
1556
+ {
1557
+ "type": "tidelift",
1558
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1559
+ },
1560
+ {
1561
+ "type": "github",
1562
+ "url": "https://github.com/sponsors/ai"
1563
+ }
1564
+ ],
1565
+ "license": "MIT",
1566
+ "dependencies": {
1567
+ "nanoid": "^3.3.8",
1568
+ "picocolors": "^1.1.1",
1569
+ "source-map-js": "^1.2.1"
1570
+ },
1571
+ "engines": {
1572
+ "node": "^10 || ^12 || >=14"
1573
+ }
1574
+ },
1575
+ "node_modules/postcss-import": {
1576
+ "version": "15.1.0",
1577
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
1578
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
1579
+ "dev": true,
1580
+ "license": "MIT",
1581
+ "dependencies": {
1582
+ "postcss-value-parser": "^4.0.0",
1583
+ "read-cache": "^1.0.0",
1584
+ "resolve": "^1.1.7"
1585
+ },
1586
+ "engines": {
1587
+ "node": ">=14.0.0"
1588
+ },
1589
+ "peerDependencies": {
1590
+ "postcss": "^8.0.0"
1591
+ }
1592
+ },
1593
+ "node_modules/postcss-js": {
1594
+ "version": "4.0.1",
1595
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
1596
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
1597
+ "dev": true,
1598
+ "license": "MIT",
1599
+ "dependencies": {
1600
+ "camelcase-css": "^2.0.1"
1601
+ },
1602
+ "engines": {
1603
+ "node": "^12 || ^14 || >= 16"
1604
+ },
1605
+ "funding": {
1606
+ "type": "opencollective",
1607
+ "url": "https://opencollective.com/postcss/"
1608
+ },
1609
+ "peerDependencies": {
1610
+ "postcss": "^8.4.21"
1611
+ }
1612
+ },
1613
+ "node_modules/postcss-load-config": {
1614
+ "version": "4.0.2",
1615
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
1616
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
1617
+ "dev": true,
1618
+ "funding": [
1619
+ {
1620
+ "type": "opencollective",
1621
+ "url": "https://opencollective.com/postcss/"
1622
+ },
1623
+ {
1624
+ "type": "github",
1625
+ "url": "https://github.com/sponsors/ai"
1626
+ }
1627
+ ],
1628
+ "license": "MIT",
1629
+ "dependencies": {
1630
+ "lilconfig": "^3.0.0",
1631
+ "yaml": "^2.3.4"
1632
+ },
1633
+ "engines": {
1634
+ "node": ">= 14"
1635
+ },
1636
+ "peerDependencies": {
1637
+ "postcss": ">=8.0.9",
1638
+ "ts-node": ">=9.0.0"
1639
+ },
1640
+ "peerDependenciesMeta": {
1641
+ "postcss": {
1642
+ "optional": true
1643
+ },
1644
+ "ts-node": {
1645
+ "optional": true
1646
+ }
1647
+ }
1648
+ },
1649
+ "node_modules/postcss-nested": {
1650
+ "version": "6.2.0",
1651
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
1652
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
1653
+ "dev": true,
1654
+ "funding": [
1655
+ {
1656
+ "type": "opencollective",
1657
+ "url": "https://opencollective.com/postcss/"
1658
+ },
1659
+ {
1660
+ "type": "github",
1661
+ "url": "https://github.com/sponsors/ai"
1662
+ }
1663
+ ],
1664
+ "license": "MIT",
1665
+ "dependencies": {
1666
+ "postcss-selector-parser": "^6.1.1"
1667
+ },
1668
+ "engines": {
1669
+ "node": ">=12.0"
1670
+ },
1671
+ "peerDependencies": {
1672
+ "postcss": "^8.2.14"
1673
+ }
1674
+ },
1675
+ "node_modules/postcss-selector-parser": {
1676
+ "version": "6.1.2",
1677
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
1678
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
1679
+ "dev": true,
1680
+ "license": "MIT",
1681
+ "dependencies": {
1682
+ "cssesc": "^3.0.0",
1683
+ "util-deprecate": "^1.0.2"
1684
+ },
1685
+ "engines": {
1686
+ "node": ">=4"
1687
+ }
1688
+ },
1689
+ "node_modules/postcss-value-parser": {
1690
+ "version": "4.2.0",
1691
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
1692
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
1693
+ "dev": true,
1694
+ "license": "MIT"
1695
+ },
1696
+ "node_modules/queue-microtask": {
1697
+ "version": "1.2.3",
1698
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1699
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1700
+ "dev": true,
1701
+ "funding": [
1702
+ {
1703
+ "type": "github",
1704
+ "url": "https://github.com/sponsors/feross"
1705
+ },
1706
+ {
1707
+ "type": "patreon",
1708
+ "url": "https://www.patreon.com/feross"
1709
+ },
1710
+ {
1711
+ "type": "consulting",
1712
+ "url": "https://feross.org/support"
1713
+ }
1714
+ ],
1715
+ "license": "MIT"
1716
+ },
1717
+ "node_modules/react": {
1718
+ "version": "19.0.0",
1719
+ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
1720
+ "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
1721
+ "license": "MIT",
1722
+ "engines": {
1723
+ "node": ">=0.10.0"
1724
+ }
1725
+ },
1726
+ "node_modules/react-dom": {
1727
+ "version": "19.0.0",
1728
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
1729
+ "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
1730
+ "license": "MIT",
1731
+ "dependencies": {
1732
+ "scheduler": "^0.25.0"
1733
+ },
1734
+ "peerDependencies": {
1735
+ "react": "^19.0.0"
1736
+ }
1737
+ },
1738
+ "node_modules/read-cache": {
1739
+ "version": "1.0.0",
1740
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
1741
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
1742
+ "dev": true,
1743
+ "license": "MIT",
1744
+ "dependencies": {
1745
+ "pify": "^2.3.0"
1746
+ }
1747
+ },
1748
+ "node_modules/readdirp": {
1749
+ "version": "3.6.0",
1750
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1751
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1752
+ "dev": true,
1753
+ "license": "MIT",
1754
+ "dependencies": {
1755
+ "picomatch": "^2.2.1"
1756
+ },
1757
+ "engines": {
1758
+ "node": ">=8.10.0"
1759
+ }
1760
+ },
1761
+ "node_modules/resolve": {
1762
+ "version": "1.22.10",
1763
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
1764
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
1765
+ "dev": true,
1766
+ "license": "MIT",
1767
+ "dependencies": {
1768
+ "is-core-module": "^2.16.0",
1769
+ "path-parse": "^1.0.7",
1770
+ "supports-preserve-symlinks-flag": "^1.0.0"
1771
+ },
1772
+ "bin": {
1773
+ "resolve": "bin/resolve"
1774
+ },
1775
+ "engines": {
1776
+ "node": ">= 0.4"
1777
+ },
1778
+ "funding": {
1779
+ "url": "https://github.com/sponsors/ljharb"
1780
+ }
1781
+ },
1782
+ "node_modules/reusify": {
1783
+ "version": "1.0.4",
1784
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
1785
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
1786
+ "dev": true,
1787
+ "license": "MIT",
1788
+ "engines": {
1789
+ "iojs": ">=1.0.0",
1790
+ "node": ">=0.10.0"
1791
+ }
1792
+ },
1793
+ "node_modules/run-parallel": {
1794
+ "version": "1.2.0",
1795
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1796
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1797
+ "dev": true,
1798
+ "funding": [
1799
+ {
1800
+ "type": "github",
1801
+ "url": "https://github.com/sponsors/feross"
1802
+ },
1803
+ {
1804
+ "type": "patreon",
1805
+ "url": "https://www.patreon.com/feross"
1806
+ },
1807
+ {
1808
+ "type": "consulting",
1809
+ "url": "https://feross.org/support"
1810
+ }
1811
+ ],
1812
+ "license": "MIT",
1813
+ "dependencies": {
1814
+ "queue-microtask": "^1.2.2"
1815
+ }
1816
+ },
1817
+ "node_modules/scheduler": {
1818
+ "version": "0.25.0",
1819
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
1820
+ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
1821
+ "license": "MIT"
1822
+ },
1823
+ "node_modules/semver": {
1824
+ "version": "7.7.1",
1825
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
1826
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
1827
+ "license": "ISC",
1828
+ "optional": true,
1829
+ "bin": {
1830
+ "semver": "bin/semver.js"
1831
+ },
1832
+ "engines": {
1833
+ "node": ">=10"
1834
+ }
1835
+ },
1836
+ "node_modules/sharp": {
1837
+ "version": "0.33.5",
1838
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
1839
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
1840
+ "hasInstallScript": true,
1841
+ "license": "Apache-2.0",
1842
+ "optional": true,
1843
+ "dependencies": {
1844
+ "color": "^4.2.3",
1845
+ "detect-libc": "^2.0.3",
1846
+ "semver": "^7.6.3"
1847
+ },
1848
+ "engines": {
1849
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1850
+ },
1851
+ "funding": {
1852
+ "url": "https://opencollective.com/libvips"
1853
+ },
1854
+ "optionalDependencies": {
1855
+ "@img/sharp-darwin-arm64": "0.33.5",
1856
+ "@img/sharp-darwin-x64": "0.33.5",
1857
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
1858
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
1859
+ "@img/sharp-libvips-linux-arm": "1.0.5",
1860
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
1861
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
1862
+ "@img/sharp-libvips-linux-x64": "1.0.4",
1863
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
1864
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
1865
+ "@img/sharp-linux-arm": "0.33.5",
1866
+ "@img/sharp-linux-arm64": "0.33.5",
1867
+ "@img/sharp-linux-s390x": "0.33.5",
1868
+ "@img/sharp-linux-x64": "0.33.5",
1869
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
1870
+ "@img/sharp-linuxmusl-x64": "0.33.5",
1871
+ "@img/sharp-wasm32": "0.33.5",
1872
+ "@img/sharp-win32-ia32": "0.33.5",
1873
+ "@img/sharp-win32-x64": "0.33.5"
1874
+ }
1875
+ },
1876
+ "node_modules/shebang-command": {
1877
+ "version": "2.0.0",
1878
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1879
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1880
+ "dev": true,
1881
+ "license": "MIT",
1882
+ "dependencies": {
1883
+ "shebang-regex": "^3.0.0"
1884
+ },
1885
+ "engines": {
1886
+ "node": ">=8"
1887
+ }
1888
+ },
1889
+ "node_modules/shebang-regex": {
1890
+ "version": "3.0.0",
1891
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1892
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1893
+ "dev": true,
1894
+ "license": "MIT",
1895
+ "engines": {
1896
+ "node": ">=8"
1897
+ }
1898
+ },
1899
+ "node_modules/signal-exit": {
1900
+ "version": "4.1.0",
1901
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
1902
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
1903
+ "dev": true,
1904
+ "license": "ISC",
1905
+ "engines": {
1906
+ "node": ">=14"
1907
+ },
1908
+ "funding": {
1909
+ "url": "https://github.com/sponsors/isaacs"
1910
+ }
1911
+ },
1912
+ "node_modules/simple-swizzle": {
1913
+ "version": "0.2.2",
1914
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
1915
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
1916
+ "license": "MIT",
1917
+ "optional": true,
1918
+ "dependencies": {
1919
+ "is-arrayish": "^0.3.1"
1920
+ }
1921
+ },
1922
+ "node_modules/source-map-js": {
1923
+ "version": "1.2.1",
1924
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1925
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1926
+ "license": "BSD-3-Clause",
1927
+ "engines": {
1928
+ "node": ">=0.10.0"
1929
+ }
1930
+ },
1931
+ "node_modules/streamsearch": {
1932
+ "version": "1.1.0",
1933
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1934
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1935
+ "engines": {
1936
+ "node": ">=10.0.0"
1937
+ }
1938
+ },
1939
+ "node_modules/string-width": {
1940
+ "version": "5.1.2",
1941
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
1942
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
1943
+ "dev": true,
1944
+ "license": "MIT",
1945
+ "dependencies": {
1946
+ "eastasianwidth": "^0.2.0",
1947
+ "emoji-regex": "^9.2.2",
1948
+ "strip-ansi": "^7.0.1"
1949
+ },
1950
+ "engines": {
1951
+ "node": ">=12"
1952
+ },
1953
+ "funding": {
1954
+ "url": "https://github.com/sponsors/sindresorhus"
1955
+ }
1956
+ },
1957
+ "node_modules/string-width-cjs": {
1958
+ "name": "string-width",
1959
+ "version": "4.2.3",
1960
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1961
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1962
+ "dev": true,
1963
+ "license": "MIT",
1964
+ "dependencies": {
1965
+ "emoji-regex": "^8.0.0",
1966
+ "is-fullwidth-code-point": "^3.0.0",
1967
+ "strip-ansi": "^6.0.1"
1968
+ },
1969
+ "engines": {
1970
+ "node": ">=8"
1971
+ }
1972
+ },
1973
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
1974
+ "version": "5.0.1",
1975
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1976
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1977
+ "dev": true,
1978
+ "license": "MIT",
1979
+ "engines": {
1980
+ "node": ">=8"
1981
+ }
1982
+ },
1983
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
1984
+ "version": "8.0.0",
1985
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
1986
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
1987
+ "dev": true,
1988
+ "license": "MIT"
1989
+ },
1990
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
1991
+ "version": "6.0.1",
1992
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1993
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1994
+ "dev": true,
1995
+ "license": "MIT",
1996
+ "dependencies": {
1997
+ "ansi-regex": "^5.0.1"
1998
+ },
1999
+ "engines": {
2000
+ "node": ">=8"
2001
+ }
2002
+ },
2003
+ "node_modules/strip-ansi": {
2004
+ "version": "7.1.0",
2005
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
2006
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
2007
+ "dev": true,
2008
+ "license": "MIT",
2009
+ "dependencies": {
2010
+ "ansi-regex": "^6.0.1"
2011
+ },
2012
+ "engines": {
2013
+ "node": ">=12"
2014
+ },
2015
+ "funding": {
2016
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
2017
+ }
2018
+ },
2019
+ "node_modules/strip-ansi-cjs": {
2020
+ "name": "strip-ansi",
2021
+ "version": "6.0.1",
2022
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
2023
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
2024
+ "dev": true,
2025
+ "license": "MIT",
2026
+ "dependencies": {
2027
+ "ansi-regex": "^5.0.1"
2028
+ },
2029
+ "engines": {
2030
+ "node": ">=8"
2031
+ }
2032
+ },
2033
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
2034
+ "version": "5.0.1",
2035
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
2036
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
2037
+ "dev": true,
2038
+ "license": "MIT",
2039
+ "engines": {
2040
+ "node": ">=8"
2041
+ }
2042
+ },
2043
+ "node_modules/styled-jsx": {
2044
+ "version": "5.1.6",
2045
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
2046
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
2047
+ "license": "MIT",
2048
+ "dependencies": {
2049
+ "client-only": "0.0.1"
2050
+ },
2051
+ "engines": {
2052
+ "node": ">= 12.0.0"
2053
+ },
2054
+ "peerDependencies": {
2055
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
2056
+ },
2057
+ "peerDependenciesMeta": {
2058
+ "@babel/core": {
2059
+ "optional": true
2060
+ },
2061
+ "babel-plugin-macros": {
2062
+ "optional": true
2063
+ }
2064
+ }
2065
+ },
2066
+ "node_modules/sucrase": {
2067
+ "version": "3.35.0",
2068
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
2069
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
2070
+ "dev": true,
2071
+ "license": "MIT",
2072
+ "dependencies": {
2073
+ "@jridgewell/gen-mapping": "^0.3.2",
2074
+ "commander": "^4.0.0",
2075
+ "glob": "^10.3.10",
2076
+ "lines-and-columns": "^1.1.6",
2077
+ "mz": "^2.7.0",
2078
+ "pirates": "^4.0.1",
2079
+ "ts-interface-checker": "^0.1.9"
2080
+ },
2081
+ "bin": {
2082
+ "sucrase": "bin/sucrase",
2083
+ "sucrase-node": "bin/sucrase-node"
2084
+ },
2085
+ "engines": {
2086
+ "node": ">=16 || 14 >=14.17"
2087
+ }
2088
+ },
2089
+ "node_modules/supports-preserve-symlinks-flag": {
2090
+ "version": "1.0.0",
2091
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
2092
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
2093
+ "dev": true,
2094
+ "license": "MIT",
2095
+ "engines": {
2096
+ "node": ">= 0.4"
2097
+ },
2098
+ "funding": {
2099
+ "url": "https://github.com/sponsors/ljharb"
2100
+ }
2101
+ },
2102
+ "node_modules/tailwindcss": {
2103
+ "version": "3.4.17",
2104
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
2105
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
2106
+ "dev": true,
2107
+ "license": "MIT",
2108
+ "dependencies": {
2109
+ "@alloc/quick-lru": "^5.2.0",
2110
+ "arg": "^5.0.2",
2111
+ "chokidar": "^3.6.0",
2112
+ "didyoumean": "^1.2.2",
2113
+ "dlv": "^1.1.3",
2114
+ "fast-glob": "^3.3.2",
2115
+ "glob-parent": "^6.0.2",
2116
+ "is-glob": "^4.0.3",
2117
+ "jiti": "^1.21.6",
2118
+ "lilconfig": "^3.1.3",
2119
+ "micromatch": "^4.0.8",
2120
+ "normalize-path": "^3.0.0",
2121
+ "object-hash": "^3.0.0",
2122
+ "picocolors": "^1.1.1",
2123
+ "postcss": "^8.4.47",
2124
+ "postcss-import": "^15.1.0",
2125
+ "postcss-js": "^4.0.1",
2126
+ "postcss-load-config": "^4.0.2",
2127
+ "postcss-nested": "^6.2.0",
2128
+ "postcss-selector-parser": "^6.1.2",
2129
+ "resolve": "^1.22.8",
2130
+ "sucrase": "^3.35.0"
2131
+ },
2132
+ "bin": {
2133
+ "tailwind": "lib/cli.js",
2134
+ "tailwindcss": "lib/cli.js"
2135
+ },
2136
+ "engines": {
2137
+ "node": ">=14.0.0"
2138
+ }
2139
+ },
2140
+ "node_modules/thenify": {
2141
+ "version": "3.3.1",
2142
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
2143
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
2144
+ "dev": true,
2145
+ "license": "MIT",
2146
+ "dependencies": {
2147
+ "any-promise": "^1.0.0"
2148
+ }
2149
+ },
2150
+ "node_modules/thenify-all": {
2151
+ "version": "1.6.0",
2152
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
2153
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
2154
+ "dev": true,
2155
+ "license": "MIT",
2156
+ "dependencies": {
2157
+ "thenify": ">= 3.1.0 < 4"
2158
+ },
2159
+ "engines": {
2160
+ "node": ">=0.8"
2161
+ }
2162
+ },
2163
+ "node_modules/to-regex-range": {
2164
+ "version": "5.0.1",
2165
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
2166
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
2167
+ "dev": true,
2168
+ "license": "MIT",
2169
+ "dependencies": {
2170
+ "is-number": "^7.0.0"
2171
+ },
2172
+ "engines": {
2173
+ "node": ">=8.0"
2174
+ }
2175
+ },
2176
+ "node_modules/ts-interface-checker": {
2177
+ "version": "0.1.13",
2178
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
2179
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
2180
+ "dev": true,
2181
+ "license": "Apache-2.0"
2182
+ },
2183
+ "node_modules/tslib": {
2184
+ "version": "2.8.1",
2185
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
2186
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
2187
+ "license": "0BSD"
2188
+ },
2189
+ "node_modules/util-deprecate": {
2190
+ "version": "1.0.2",
2191
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
2192
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
2193
+ "dev": true,
2194
+ "license": "MIT"
2195
+ },
2196
+ "node_modules/which": {
2197
+ "version": "2.0.2",
2198
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
2199
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
2200
+ "dev": true,
2201
+ "license": "ISC",
2202
+ "dependencies": {
2203
+ "isexe": "^2.0.0"
2204
+ },
2205
+ "bin": {
2206
+ "node-which": "bin/node-which"
2207
+ },
2208
+ "engines": {
2209
+ "node": ">= 8"
2210
+ }
2211
+ },
2212
+ "node_modules/wrap-ansi": {
2213
+ "version": "8.1.0",
2214
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
2215
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
2216
+ "dev": true,
2217
+ "license": "MIT",
2218
+ "dependencies": {
2219
+ "ansi-styles": "^6.1.0",
2220
+ "string-width": "^5.0.1",
2221
+ "strip-ansi": "^7.0.1"
2222
+ },
2223
+ "engines": {
2224
+ "node": ">=12"
2225
+ },
2226
+ "funding": {
2227
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
2228
+ }
2229
+ },
2230
+ "node_modules/wrap-ansi-cjs": {
2231
+ "name": "wrap-ansi",
2232
+ "version": "7.0.0",
2233
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
2234
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
2235
+ "dev": true,
2236
+ "license": "MIT",
2237
+ "dependencies": {
2238
+ "ansi-styles": "^4.0.0",
2239
+ "string-width": "^4.1.0",
2240
+ "strip-ansi": "^6.0.0"
2241
+ },
2242
+ "engines": {
2243
+ "node": ">=10"
2244
+ },
2245
+ "funding": {
2246
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
2247
+ }
2248
+ },
2249
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
2250
+ "version": "5.0.1",
2251
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
2252
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
2253
+ "dev": true,
2254
+ "license": "MIT",
2255
+ "engines": {
2256
+ "node": ">=8"
2257
+ }
2258
+ },
2259
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
2260
+ "version": "4.3.0",
2261
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
2262
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
2263
+ "dev": true,
2264
+ "license": "MIT",
2265
+ "dependencies": {
2266
+ "color-convert": "^2.0.1"
2267
+ },
2268
+ "engines": {
2269
+ "node": ">=8"
2270
+ },
2271
+ "funding": {
2272
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
2273
+ }
2274
+ },
2275
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
2276
+ "version": "8.0.0",
2277
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
2278
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
2279
+ "dev": true,
2280
+ "license": "MIT"
2281
+ },
2282
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
2283
+ "version": "4.2.3",
2284
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
2285
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
2286
+ "dev": true,
2287
+ "license": "MIT",
2288
+ "dependencies": {
2289
+ "emoji-regex": "^8.0.0",
2290
+ "is-fullwidth-code-point": "^3.0.0",
2291
+ "strip-ansi": "^6.0.1"
2292
+ },
2293
+ "engines": {
2294
+ "node": ">=8"
2295
+ }
2296
+ },
2297
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
2298
+ "version": "6.0.1",
2299
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
2300
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
2301
+ "dev": true,
2302
+ "license": "MIT",
2303
+ "dependencies": {
2304
+ "ansi-regex": "^5.0.1"
2305
+ },
2306
+ "engines": {
2307
+ "node": ">=8"
2308
+ }
2309
+ },
2310
+ "node_modules/yaml": {
2311
+ "version": "2.7.0",
2312
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
2313
+ "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
2314
+ "dev": true,
2315
+ "license": "ISC",
2316
+ "bin": {
2317
+ "yaml": "bin.mjs"
2318
+ },
2319
+ "engines": {
2320
+ "node": ">= 14"
2321
+ }
2322
+ }
2323
+ }
2324
+ }
package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "handspew",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19.0.0",
13
+ "react-dom": "^19.0.0",
14
+ "next": "15.1.7",
15
+ "@mediapipe/tasks-vision": "^0.10.12",
16
+ "@google/generative-ai": "^0.2.1"
17
+ },
18
+ "devDependencies": {
19
+ "postcss": "^8",
20
+ "tailwindcss": "^3.4.1"
21
+ }
22
+ }
pages/_app.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import "@/styles/globals.css";
2
+
3
+ export default function App({ Component, pageProps }) {
4
+ return <Component {...pageProps} />;
5
+ }
pages/_document.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Html, Head, Main, NextScript } from "next/document";
2
+
3
+ export default function Document() {
4
+ return (
5
+ <Html lang="en">
6
+ <Head />
7
+ <body className="antialiased">
8
+ <Main />
9
+ <NextScript />
10
+ </body>
11
+ </Html>
12
+ );
13
+ }
pages/api/gemini.js ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { GoogleGenerativeAI } from "@google/generative-ai";
2
+
3
+ // Initialize the Gemini API with the API key
4
+ const apiKey = process.env.GEMINI_API_KEY;
5
+ const genAI = new GoogleGenerativeAI(apiKey);
6
+
7
+ // Maximum number of retries for API calls
8
+ const MAX_RETRIES = 3;
9
+ // Delay between retries (in milliseconds)
10
+ const RETRY_DELAY = 1000;
11
+
12
+ // Helper function to wait between retries
13
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
14
+
15
+ export default async function handler(req, res) {
16
+ if (req.method !== 'POST') {
17
+ return res.status(405).json({ error: 'Method not allowed' });
18
+ }
19
+
20
+ try {
21
+ const { image, prompt } = req.body;
22
+
23
+ if (!image) {
24
+ return res.status(400).json({ error: 'Image data is required' });
25
+ }
26
+
27
+ // Remove the data URL prefix
28
+ const base64Image = image.replace(/^data:image\/(png|jpeg|jpg);base64,/, '');
29
+
30
+ // Initialize the Gemini 2.0 Flash model
31
+ const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
32
+
33
+ // Use the custom prompt if provided, otherwise use the default
34
+ const promptText = prompt || "Look at this image and identify the main object or scene (ignoring any hands or fingers that might be visible). Generate a single, short, insightful thought (maximum 5 words) about this object or scene. Focus on something interesting, philosophical, or unexpected about it. DO NOT mention hands, fingers, pose estimation, motion tracking, or any computer vision technology in your response. Respond with just the thought, no additional text.";
35
+
36
+ // Prepare the image part for the model
37
+ const imagePart = {
38
+ inlineData: {
39
+ data: base64Image,
40
+ mimeType: "image/jpeg",
41
+ },
42
+ };
43
+
44
+ // Implement retry logic with exponential backoff
45
+ let lastError = null;
46
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
47
+ try {
48
+ // Generate content with the image
49
+ const result = await model.generateContent([promptText, imagePart]);
50
+ const response = await result.response;
51
+ const text = response.text();
52
+
53
+ return res.status(200).json({ thought: text });
54
+ } catch (error) {
55
+ console.error(`Attempt ${attempt + 1} failed:`, error.message);
56
+ lastError = error;
57
+
58
+ // Check if error is related to throttling or service unavailability
59
+ if (error.message.includes('503 Service Unavailable') ||
60
+ error.message.includes('THROTTLED') ||
61
+ error.message.includes('overloaded')) {
62
+ // Wait before retrying with exponential backoff
63
+ await sleep(RETRY_DELAY * Math.pow(2, attempt));
64
+ continue;
65
+ } else {
66
+ // For other errors, don't retry
67
+ break;
68
+ }
69
+ }
70
+ }
71
+
72
+ // If we've exhausted all retries or encountered a non-retryable error
73
+ console.error('All attempts failed or non-retryable error:', lastError);
74
+
75
+ // Provide a user-friendly error message based on the error type
76
+ if (lastError.message.includes('THROTTLED') || lastError.message.includes('overloaded')) {
77
+ return res.status(503).json({
78
+ error: 'The AI service is currently busy. Please try again in a moment.',
79
+ fallbackThought: "🤔"
80
+ });
81
+ } else if (lastError.message.includes('quota')) {
82
+ return res.status(429).json({
83
+ error: 'API quota exceeded. Please try again later.',
84
+ fallbackThought: "🤔"
85
+ });
86
+ } else {
87
+ return res.status(500).json({
88
+ error: 'Something went wrong while analyzing your image.',
89
+ fallbackThought: "🤔"
90
+ });
91
+ }
92
+ } catch (error) {
93
+ console.error('Unexpected error:', error);
94
+ return res.status(500).json({
95
+ error: 'An unexpected error occurred',
96
+ fallbackThought: "🤔"
97
+ });
98
+ }
99
+ }
pages/api/hello.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2
+
3
+ export default function handler(req, res) {
4
+ res.status(200).json({ name: "John Doe" });
5
+ }
pages/index.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Head from 'next/head';
2
+ import dynamic from 'next/dynamic';
3
+ import { Inter } from 'next/font/google';
4
+
5
+ // Import the HandDetector component with dynamic import to avoid SSR issues
6
+ const HandDetector = dynamic(() => import('../components/HandDetector'), {
7
+ ssr: false
8
+ });
9
+
10
+ const inter = Inter({ subsets: ['latin'] });
11
+
12
+ const Header = () => {
13
+ return (
14
+ <div className="fixed top-0 left-0 right-0 w-full bg-white p-4 z-50 shadow-sm">
15
+ <div className="w-full flex justify-between items-center text-base max-w-7xl mx-auto">
16
+ <div className="text-gray-500">
17
+ <span className="text-black font-bold text-lg mr-2">HandSpew</span>
18
+ Built with <a
19
+ href="https://ai.google.dev"
20
+ target="_blank"
21
+ rel="noopener noreferrer"
22
+ className="underline hover:text-gray-800 transition-colors"
23
+ >
24
+ Gemini 2.0
25
+ </a> + <a
26
+ href="https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker"
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ className="underline hover:text-gray-800 transition-colors"
30
+ >
31
+ MediaPipe
32
+ </a>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ );
37
+ };
38
+
39
+ export default function Home() {
40
+ return (
41
+ <>
42
+ <Head>
43
+ <title>HandSpew</title>
44
+ <meta name="description" content="Generate thoughts based on hand gestures using MediaPipe and Gemini" />
45
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
46
+ <link rel="icon" href="/favicon.ico" />
47
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&display=swap" />
48
+ </Head>
49
+ <Header />
50
+ <main className="flex min-h-screen flex-col items-center justify-center p-4 bg-white font-['Google_Sans',sans-serif] pt-20">
51
+ <HandDetector />
52
+ </main>
53
+ </>
54
+ );
55
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
public/favicon.ico ADDED
public/file.svg ADDED
public/globe.svg ADDED
public/next.svg ADDED
public/sounds/bloop.mp3 ADDED
Binary file (27.2 kB). View file
 
public/vercel.svg ADDED
public/window.svg ADDED
styles/globals.css ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --background: #ffffff;
7
+ --foreground: #171717;
8
+ --primary: #217BFE;
9
+ --secondary: #AC87EB;
10
+ }
11
+
12
+ @media (prefers-color-scheme: dark) {
13
+ :root {
14
+ --background: #ffffff;
15
+ --foreground: #171717;
16
+ }
17
+ }
18
+
19
+ body {
20
+ color: var(--foreground);
21
+ background: var(--background);
22
+ font-family: 'Google Sans', Arial, Helvetica, sans-serif;
23
+ }
24
+
25
+ /* Minimal thought bubble styling */
26
+ .thought-bubble {
27
+ position: absolute;
28
+ animation: fadeIn 0.3s ease-in-out;
29
+ min-height: 40px;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: flex-start;
33
+ overflow-wrap: break-word;
34
+ word-wrap: break-word;
35
+ word-break: normal;
36
+ hyphens: auto;
37
+ height: auto;
38
+ pointer-events: none;
39
+ max-width: 100%;
40
+ }
41
+
42
+ .thought-bubble p {
43
+ margin: 0;
44
+ font-size: 14px;
45
+ line-height: 1.4;
46
+ }
47
+
48
+ @keyframes fadeIn {
49
+ from {
50
+ opacity: 0;
51
+ transform: scale(0.95);
52
+ }
53
+ to {
54
+ opacity: 1;
55
+ transform: scale(1);
56
+ }
57
+ }
58
+
59
+ /* Mobile-specific styles */
60
+ @media (max-width: 767px) {
61
+ .thought-bubble {
62
+ min-height: 30px;
63
+ }
64
+
65
+ .thought-bubble p {
66
+ font-size: 12px;
67
+ line-height: 1.3;
68
+ }
69
+ }
tailwind.config.mjs ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
5
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
6
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
7
+ ],
8
+ theme: {
9
+ extend: {
10
+ colors: {
11
+ background: "var(--background)",
12
+ foreground: "var(--foreground)",
13
+ },
14
+ },
15
+ },
16
+ plugins: [],
17
+ };
utils/handUtils.js ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Utility functions for hand detection and drawing
3
+ */
4
+
5
+ /**
6
+ * Draw hand landmarks on canvas
7
+ * @param {CanvasRenderingContext2D} ctx - Canvas context
8
+ * @param {Array} landmarks - Hand landmarks from MediaPipe
9
+ * @param {HTMLCanvasElement} canvas - Canvas element
10
+ * @param {boolean} isMobile - Whether the device is mobile
11
+ */
12
+ export const drawLandmarks = (ctx, landmarks, canvas, isMobile) => {
13
+ // Draw connections
14
+ const connections = [
15
+ // Thumb
16
+ [0, 1], [1, 2], [2, 3], [3, 4],
17
+ // Index finger
18
+ [0, 5], [5, 6], [6, 7], [7, 8],
19
+ // Middle finger
20
+ [9, 10], [10, 11], [11, 12], [5, 9],
21
+ // Ring finger
22
+ [9, 13], [13, 14], [14, 15], [15, 16],
23
+ // Pinky
24
+ [13, 17], [17, 18], [18, 19], [19, 20],
25
+ // Palm
26
+ [0, 17]
27
+ ];
28
+
29
+ // Use the requested colors
30
+ ctx.lineWidth = isMobile ? 2 : 3; // Thinner lines on mobile
31
+ ctx.strokeStyle = '#217BFE'; // Primary hand color
32
+
33
+ for (const [start, end] of connections) {
34
+ ctx.beginPath();
35
+ ctx.moveTo(landmarks[start].x * canvas.width, landmarks[start].y * canvas.height);
36
+ ctx.lineTo(landmarks[end].x * canvas.width, landmarks[end].y * canvas.height);
37
+ ctx.stroke();
38
+ }
39
+
40
+ // Draw landmarks with secondary color
41
+ ctx.fillStyle = '#AC87EB';
42
+ landmarks.forEach(landmark => {
43
+ ctx.beginPath();
44
+ ctx.arc(landmark.x * canvas.width, landmark.y * canvas.height, isMobile ? 3 : 4, 0, 2 * Math.PI);
45
+ ctx.fill();
46
+ });
47
+
48
+ // Highlight thumb and index finger
49
+ const thumbTip = landmarks[4];
50
+ const indexTip = landmarks[8];
51
+
52
+ ctx.fillStyle = '#217BFE';
53
+ [thumbTip, indexTip].forEach(tip => {
54
+ ctx.beginPath();
55
+ ctx.arc(tip.x * canvas.width, tip.y * canvas.height, isMobile ? 4 : 6, 0, 2 * Math.PI);
56
+ ctx.fill();
57
+ });
58
+ };
59
+
60
+ /**
61
+ * Check if hand is open (thumb away from index finger)
62
+ * @param {Array} landmarks - Hand landmarks from MediaPipe
63
+ * @returns {Object} - Object containing isOpen and other hand information
64
+ */
65
+ export const analyzeHandGesture = (landmarks) => {
66
+ const thumbTip = landmarks[4]; // Thumb tip
67
+ const indexTip = landmarks[8]; // Index finger tip
68
+ const wrist = landmarks[0]; // Wrist
69
+
70
+ // Determine if it's a left or right hand
71
+ // If thumb is to the left of the wrist, it's likely a right hand
72
+ const isLeftHand = !(thumbTip.x < wrist.x);
73
+
74
+ // Calculate distance between thumb and index finger
75
+ const distance = Math.sqrt(
76
+ Math.pow(thumbTip.x - indexTip.x, 2) +
77
+ Math.pow(thumbTip.y - indexTip.y, 2) +
78
+ Math.pow(thumbTip.z - indexTip.z, 2)
79
+ );
80
+
81
+ // Hand is open when distance is greater than threshold
82
+ const isOpen = distance > 0.1;
83
+
84
+ return {
85
+ isOpen,
86
+ isLeftHand,
87
+ thumbPosition: {
88
+ x: thumbTip.x,
89
+ y: thumbTip.y
90
+ }
91
+ };
92
+ };
93
+
94
+ /**
95
+ * Create animation keyframes for hand detection animations
96
+ * @returns {string} - CSS keyframes string
97
+ */
98
+ export const getAnimationKeyframes = () => {
99
+ return `
100
+ @keyframes thinking-blink {
101
+ 0%, 100% { opacity: 1; transform: scale(1); }
102
+ 50% { opacity: 0.7; transform: scale(0.95); }
103
+ }
104
+
105
+ @keyframes spring-out {
106
+ 0% {
107
+ transform: scale(0) translateY(0);
108
+ opacity: 0;
109
+ }
110
+ 20% {
111
+ transform: scale(0.3) translateY(-5px);
112
+ opacity: 0.7;
113
+ }
114
+ 40% {
115
+ transform: scale(1.5) translateY(-30px);
116
+ }
117
+ 60% {
118
+ transform: scale(0.8) translateY(15px);
119
+ }
120
+ 75% {
121
+ transform: scale(1.2) translateY(-10px);
122
+ }
123
+ 90% {
124
+ transform: scale(0.95) translateY(5px);
125
+ }
126
+ 100% {
127
+ transform: scale(1) translateY(0);
128
+ opacity: 1;
129
+ }
130
+ }
131
+
132
+ @keyframes wiggle {
133
+ 0% { transform: rotate(0deg); }
134
+ 15% { transform: rotate(-15deg); }
135
+ 30% { transform: rotate(12deg); }
136
+ 45% { transform: rotate(-8deg); }
137
+ 60% { transform: rotate(5deg); }
138
+ 75% { transform: rotate(-2deg); }
139
+ 100% { transform: rotate(0deg); }
140
+ }
141
+
142
+ /* Combined animation that handles both scale and rotation */
143
+ @keyframes spring-wiggle {
144
+ 0% {
145
+ transform: scale(0) rotate(0deg) translateY(0);
146
+ opacity: 0;
147
+ }
148
+ 15% {
149
+ transform: scale(0.2) rotate(-5deg) translateY(-5px);
150
+ opacity: 0.5;
151
+ }
152
+ 30% {
153
+ transform: scale(1.5) rotate(12deg) translateY(-30px);
154
+ opacity: 1;
155
+ }
156
+ 45% {
157
+ transform: scale(0.8) rotate(-8deg) translateY(15px);
158
+ }
159
+ 60% {
160
+ transform: scale(1.2) rotate(5deg) translateY(-10px);
161
+ }
162
+ 75% {
163
+ transform: scale(0.95) rotate(-2deg) translateY(5px);
164
+ }
165
+ 90% {
166
+ transform: scale(1.05) rotate(1deg) translateY(-2px);
167
+ }
168
+ 100% {
169
+ transform: scale(1) rotate(0deg) translateY(0);
170
+ }
171
+ }
172
+
173
+ /* Add new animations for particles and popping */
174
+ @keyframes float-particle {
175
+ 0% {
176
+ transform: translate(0, 0) rotate(0deg);
177
+ opacity: 1;
178
+ }
179
+ 100% {
180
+ transform: translate(var(--tx), var(--ty)) rotate(var(--r));
181
+ opacity: 0;
182
+ }
183
+ }
184
+
185
+ @keyframes pop-out {
186
+ 0% {
187
+ transform: scale(1);
188
+ opacity: 1;
189
+ }
190
+ 50% {
191
+ transform: scale(1.2);
192
+ }
193
+ 100% {
194
+ transform: scale(0);
195
+ opacity: 0;
196
+ }
197
+ }
198
+ `;
199
+ };