diff --git a/src/app/components/RealityOverridePage.tsx b/src/app/components/RealityOverridePage.tsx index 75fdfec..a99f34f 100644 --- a/src/app/components/RealityOverridePage.tsx +++ b/src/app/components/RealityOverridePage.tsx @@ -5,7 +5,23 @@ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'; -interface Msg { text: string; id: number; } +interface Msg { text: string; id: number; } +interface Star { x: number; y: number; sz: number; ph: number; spd: number; col: [number, number, number]; } + +const STAR_PALETTE: [number, number, number][] = [ + [255, 255, 255], [255, 240, 210], [210, 220, 255], [255, 210, 210], [200, 255, 230], +]; + +function mkStar(W: number, H: number): Star { + return { + x: Math.random() * W | 0, + y: Math.random() * H | 0, + sz: Math.random() < 0.8 ? 1 : Math.random() < 0.6 ? 2 : 3, + ph: Math.random() * Math.PI * 2, + spd: Math.random() * 0.04 + 0.005, + col: STAR_PALETTE[Math.random() * STAR_PALETTE.length | 0], + }; +} // ── Shaders (verbatim from public/vortex/script.js) ────────────────────────── @@ -79,6 +95,8 @@ const FRAG_TRAIL = ` export default function RealityOverridePage({ onBack }: { onBack: () => void }) { const containerRef = useRef(null); + const starCanvasRef = useRef(null); + const starsRef = useRef([]); const wsRef = useRef(null); const msgIdRef = useRef(0); const fadeTimer = useRef | undefined>(undefined); @@ -231,6 +249,43 @@ export default function RealityOverridePage({ onBack }: { onBack: () => void }) }; }, []); + // ── Star field (always running, visible under Three.js when vortex is hidden) ─ + useEffect(() => { + const canvas = starCanvasRef.current; + if (!canvas) return; + let animId = 0; + let prevW = 0, prevH = 0; + + const loop = () => { + const W = canvas.clientWidth, H = canvas.clientHeight; + if (!W || !H) { animId = requestAnimationFrame(loop); return; } + if (W !== prevW || H !== prevH) { + canvas.width = W; canvas.height = H; + prevW = W; prevH = H; + starsRef.current = Array.from({ length: 200 }, () => mkStar(W, H)); + } + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = '#000010'; + ctx.fillRect(0, 0, W, H); + for (const st of starsRef.current) { + st.ph += st.spd; + const br = Math.sin(st.ph) * 0.4 + 0.6; + const [r, g, b] = st.col; + ctx.fillStyle = `rgba(${r},${g},${b},${br})`; + ctx.fillRect(st.x, st.y, st.sz, st.sz); + if (st.sz >= 3 && br > 0.85) { + ctx.fillStyle = `rgba(${r},${g},${b},${br * 0.35})`; + ctx.fillRect(st.x - 2, st.y + 1, st.sz + 4, 1); + ctx.fillRect(st.x + 1, st.y - 2, 1, st.sz + 4); + } + } + animId = requestAnimationFrame(loop); + }; + + animId = requestAnimationFrame(loop); + return () => cancelAnimationFrame(animId); + }, []); + // ── WebSocket ─────────────────────────────────────────────────────────────── useEffect(() => { let cancelled = false; @@ -266,7 +321,10 @@ export default function RealityOverridePage({ onBack }: { onBack: () => void }) onTouchEnd={handleTouchEnd} > - {/* Three.js canvas mount */} + {/* Star field — always visible beneath the vortex */} + + + {/* Three.js vortex — fades out on double-click/tap */}
{/* CSS keyframe for command flash */}