From db2f56af1729b33a141cabc50147357fc712dd09 Mon Sep 17 00:00:00 2001 From: Ryan AI Dev Date: Sun, 8 Feb 2026 12:14:49 -0700 Subject: [PATCH] Rebuild Emotion Explorer 3D experience --- frontend/src/App.tsx | 3 + frontend/src/components/EmotionExplorer3D.tsx | 220 +++++++++++++++++ frontend/src/components/Layout.tsx | 1 + frontend/src/components/TrajectoriesScene.tsx | 5 +- frontend/src/styles/global.css | 232 ++++++++++++++++++ 5 files changed, 458 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/EmotionExplorer3D.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7ad4ee2..493fa4c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,6 +11,7 @@ import IntentionsRipples from './components/IntentionsRipples'; import MoodCollageStudio from './components/MoodCollageStudio'; import NextStepsBridge from './components/NextStepsBridge'; import GuidedImageryPlayer from './components/GuidedImageryPlayer'; +import EmotionExplorer3D from './components/EmotionExplorer3D'; function Home() { return ( @@ -27,6 +28,7 @@ function Home() { 'Collage Studio', 'Next Steps Bridge', 'Guided Imagery', + 'Emotion Explorer 3D', ].map((item) => (

{item}

@@ -56,6 +58,7 @@ export default function App() { } /> } /> } /> + } /> ); diff --git a/frontend/src/components/EmotionExplorer3D.tsx b/frontend/src/components/EmotionExplorer3D.tsx new file mode 100644 index 0000000..aa70a5f --- /dev/null +++ b/frontend/src/components/EmotionExplorer3D.tsx @@ -0,0 +1,220 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; + +type EmotionZone = { + id: string; + name: string; + color: string; + energy: number; + description: string; +}; + +const zones: EmotionZone[] = [ + { + id: 'clarity', + name: 'Clarity Spire', + color: '#7cd4f7', + energy: 82, + description: 'A calm ridge where clarity rises like a lighthouse beam.', + }, + { + id: 'wonder', + name: 'Wonder Bloom', + color: '#b694ff', + energy: 74, + description: 'Curious sparks that lift the canopy into a soft lavender glow.', + }, + { + id: 'joy', + name: 'Joy Harbor', + color: '#f6b0f4', + energy: 91, + description: 'Playful currents and rhythmic pulses guide you forward.', + }, + { + id: 'resolve', + name: 'Resolve Gate', + color: '#8cf0a6', + energy: 69, + description: 'Steady footfalls, anchored in emerald light.', + }, + { + id: 'gratitude', + name: 'Gratitude Vale', + color: '#ffd479', + energy: 88, + description: 'Warm constellations thread between gilded arches.', + }, +]; + +function useStarfield(canvasRef: React.RefObject) { + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + let animationFrame = 0; + let stars = Array.from({ length: 180 }, () => ({ + x: Math.random(), + y: Math.random(), + z: Math.random(), + radius: 0.6 + Math.random() * 1.8, + })); + + const resize = () => { + canvas.width = canvas.clientWidth * window.devicePixelRatio; + canvas.height = canvas.clientHeight * window.devicePixelRatio; + }; + + resize(); + window.addEventListener('resize', resize); + + const render = () => { + if (!canvas) return; + const { width, height } = canvas; + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = 'rgba(8, 12, 28, 0.6)'; + ctx.fillRect(0, 0, width, height); + + stars = stars.map((star) => ({ + ...star, + y: star.y + 0.0008 + star.z * 0.0012, + })); + + stars.forEach((star) => { + const x = star.x * width; + const y = (star.y % 1) * height; + ctx.beginPath(); + ctx.fillStyle = `rgba(255, 255, 255, ${0.2 + star.z * 0.6})`; + ctx.arc(x, y, star.radius, 0, Math.PI * 2); + ctx.fill(); + }); + + animationFrame = requestAnimationFrame(render); + }; + + render(); + + return () => { + window.removeEventListener('resize', resize); + cancelAnimationFrame(animationFrame); + }; + }, [canvasRef]); +} + +export default function EmotionExplorer3D() { + const [activeZone, setActiveZone] = useState(zones[0]); + const [tilt, setTilt] = useState({ x: -8, y: 12 }); + const canvasRef = useRef(null); + const stageRef = useRef(null); + const orbitOffsets = useMemo(() => zones.map((_, index) => index * 72), []); + + useStarfield(canvasRef); + + const handleMove = (event: React.MouseEvent) => { + if (!stageRef.current) return; + const rect = stageRef.current.getBoundingClientRect(); + const x = (event.clientX - rect.left) / rect.width; + const y = (event.clientY - rect.top) / rect.height; + setTilt({ + x: -12 + y * 24, + y: -18 + x * 36, + }); + }; + + return ( +
+
+

Emotion Explorer 3D

+

+ A playable 3D-inspired vista built with layered depth, animated lighting, and responsive motion. Hover to + tilt the world and choose a zone to tune the mood journey. +

+
+ +
+