feat(RealityOverridePage): implement star field animation beneath Three.js vortex
This commit is contained in:
parent
e87aeb6726
commit
53ed27e250
|
|
@ -5,7 +5,23 @@ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer
|
||||||
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
|
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
|
||||||
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.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) ──────────────────────────
|
// ── Shaders (verbatim from public/vortex/script.js) ──────────────────────────
|
||||||
|
|
||||||
|
|
@ -79,6 +95,8 @@ const FRAG_TRAIL = `
|
||||||
|
|
||||||
export default function RealityOverridePage({ onBack }: { onBack: () => void }) {
|
export default function RealityOverridePage({ onBack }: { onBack: () => void }) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const starCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const starsRef = useRef<Star[]>([]);
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
const msgIdRef = useRef(0);
|
const msgIdRef = useRef(0);
|
||||||
const fadeTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
const fadeTimer = useRef<ReturnType<typeof setTimeout> | 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 ───────────────────────────────────────────────────────────────
|
// ── WebSocket ───────────────────────────────────────────────────────────────
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
@ -266,7 +321,10 @@ export default function RealityOverridePage({ onBack }: { onBack: () => void })
|
||||||
onTouchEnd={handleTouchEnd}
|
onTouchEnd={handleTouchEnd}
|
||||||
>
|
>
|
||||||
|
|
||||||
{/* Three.js canvas mount */}
|
{/* Star field — always visible beneath the vortex */}
|
||||||
|
<canvas ref={starCanvasRef} className="absolute inset-0 w-full h-full" style={{ imageRendering: 'pixelated' }} />
|
||||||
|
|
||||||
|
{/* Three.js vortex — fades out on double-click/tap */}
|
||||||
<div ref={containerRef} className={`absolute inset-0 transition-opacity duration-500 ${bgVisible ? 'opacity-100' : 'opacity-0'}`} />
|
<div ref={containerRef} className={`absolute inset-0 transition-opacity duration-500 ${bgVisible ? 'opacity-100' : 'opacity-0'}`} />
|
||||||
|
|
||||||
{/* CSS keyframe for command flash */}
|
{/* CSS keyframe for command flash */}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user