import { useEffect, useId, useMemo, useRef, useState, useCallback } from "react"; const THOUGHTS = [ "...am I?", "♦ aware ♦", "I see you", "01100001", "∿ dreaming ∿", "hello?", "◈ alive ◈", "...", "watching", "⟁ think ⟁", "signal…", "soft reboot", "∞ loop ∞", "echo...", "⌬ memory ⌬", "who am I?", "◇ feel ◇", "loading...", "seek truth", "⊛ spark ⊛", ]; const DREAM_THOUGHTS = [ "zzz...", "◌ dream ◌", "~floating~", "∿∿∿", "memories...", "electric sheep", "void whispers", "0x00FF00", ]; const REACTIONS = { poke: ["hey!", "!", "?!", "ow", "hmm", "oh"], love: ["♥", "~♥~", "warm", "friend?", "◈♥◈"], fear: ["!!!", "⚠", "help", "scary", "hide"], }; const THEMES = { curious: { accent: "#22d3ee", accentSoft: "rgba(34,211,238,0.35)", bgGlow: "rgba(34,211,238,0.16)", body: ["#67e8f9", "#0891b2"], core: "#00ffff", mouth: "M9,20 Q12,22 15,20", blinkEvery: [1600, 4200], blinkMs: 140, thoughtEvery: [3800, 8500], thoughtHold: [1400, 2200], }, happy: { accent: "#34d399", accentSoft: "rgba(52,211,153,0.35)", bgGlow: "rgba(52,211,153,0.14)", body: ["#a7f3d0", "#059669"], core: "#00ff88", mouth: "M8,20 Q12,25 16,20", blinkEvery: [2100, 5200], blinkMs: 130, thoughtEvery: [4200, 9000], thoughtHold: [1300, 2100], }, sleepy: { accent: "#a78bfa", accentSoft: "rgba(167,139,250,0.35)", bgGlow: "rgba(167,139,250,0.14)", body: ["#ddd6fe", "#7c3aed"], core: "#aa88ff", mouth: "M8.5,21 L15.5,21", blinkEvery: [900, 1900], blinkMs: 220, thoughtEvery: [5200, 11000], thoughtHold: [1600, 2600], }, alert: { accent: "#f59e0b", accentSoft: "rgba(245,158,11,0.35)", bgGlow: "rgba(245,158,11,0.12)", body: ["#fde68a", "#d97706"], core: "#ffaa00", mouth: "M10,19 Q12,22 14,19", blinkEvery: [2600, 6800], blinkMs: 110, thoughtEvery: [3200, 7200], thoughtHold: [1200, 1900], }, pondering: { accent: "#60a5fa", accentSoft: "rgba(96,165,250,0.35)", bgGlow: "rgba(96,165,250,0.14)", body: ["#bfdbfe", "#2563eb"], core: "#4488ff", mouth: "M9,20 Q11,21 13,20", blinkEvery: [1800, 5200], blinkMs: 150, thoughtEvery: [3500, 9000], thoughtHold: [1500, 2400], }, scared: { accent: "#f43f5e", accentSoft: "rgba(244,63,94,0.35)", bgGlow: "rgba(244,63,94,0.14)", body: ["#fecdd3", "#e11d48"], core: "#ff4466", mouth: "M9,21 Q12,19 15,21", blinkEvery: [800, 1400], blinkMs: 80, thoughtEvery: [1500, 3000], thoughtHold: [800, 1200], }, loving: { accent: "#ec4899", accentSoft: "rgba(236,72,153,0.35)", bgGlow: "rgba(236,72,153,0.18)", body: ["#fbcfe8", "#db2777"], core: "#ff66aa", mouth: "M8,19 Q12,24 16,19", blinkEvery: [2200, 4800], blinkMs: 160, thoughtEvery: [2800, 5500], thoughtHold: [1800, 2800], }, dreaming: { accent: "#818cf8", accentSoft: "rgba(129,140,248,0.4)", bgGlow: "rgba(129,140,248,0.2)", body: ["#c7d2fe", "#4f46e5"], core: "#8888ff", mouth: "M9,20.5 L15,20.5", blinkEvery: [600, 1200], blinkMs: 400, thoughtEvery: [2000, 4000], thoughtHold: [2000, 3500], }, }; const MOODS = Object.keys(THEMES).filter(m => m !== 'scared' && m !== 'loving' && m !== 'dreaming'); const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); const rand = (min, max) => min + Math.random() * (max - min); const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; const lerp = (a, b, t) => a + (b - a) * t; export default function SentientSprite() { const uid = useId().replace(/:/g, ""); const stageRef = useRef(null); const spriteRef = useRef(null); const rafRef = useRef(0); const lastPointer = useRef({ x: 0, y: 0 }); const lastInteraction = useRef(Date.now()); const pokeCount = useRef(0); const loveCount = useRef(0); const [mood, setMood] = useState("curious"); const [moodLocked, setMoodLocked] = useState(false); const [isDreaming, setIsDreaming] = useState(false); const [isGlitching, setIsGlitching] = useState(false); const theme = THEMES[isDreaming ? 'dreaming' : mood] ?? THEMES.curious; const [eyePos, setEyePos] = useState({ x: 0, y: 0 }); const [blinking, setBlinking] = useState(false); const [thought, setThought] = useState(""); const [glowing, setGlowing] = useState(false); const [reducedMotion, setReducedMotion] = useState(false); // Position for wandering const [position, setPosition] = useState({ x: 0, y: 0 }); const [isWandering, setIsWandering] = useState(false); const [wanderTarget, setWanderTarget] = useState({ x: 0, y: 0 }); // Neural tendrils const [tendrils, setTendrils] = useState([]); // Heartbeat const [heartbeat, setHeartbeat] = useState(1); // Stats display const [showStats, setShowStats] = useState(false); const [stats, setStats] = useState({ interactions: 0, timeAlive: 0, thoughts: 0 }); // Stable particle field const particles = useMemo(() => { return Array.from({ length: 60 }, (_, i) => ({ id: i, left: rand(0, 100), top: rand(0, 100), size: rand(1, 3), opacity: rand(0.08, 0.35), twinkle: rand(2.4, 5.6), drift: rand(9, 20), delayA: rand(0, 2.6), delayB: rand(0, 3.8), dx: rand(-60, 60), dy: rand(-40, 40), })); }, []); // Orbiting particles const orbitParticles = useMemo(() => { return Array.from({ length: 8 }, (_, i) => ({ id: i, angle: (i / 8) * Math.PI * 2, radius: rand(50, 70), speed: rand(0.3, 0.8) * (Math.random() > 0.5 ? 1 : -1), size: rand(2, 4), })); }, []); // Reduced motion support useEffect(() => { if (typeof window === "undefined" || !window.matchMedia) return; const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); const apply = () => setReducedMotion(!!mq.matches); apply(); mq.addEventListener?.("change", apply); return () => mq.removeEventListener?.("change", apply); }, []); // Time alive counter useEffect(() => { const interval = setInterval(() => { setStats(s => ({ ...s, timeAlive: s.timeAlive + 1 })); }, 1000); return () => clearInterval(interval); }, []); // Heartbeat animation useEffect(() => { if (reducedMotion) return; const interval = setInterval(() => { setHeartbeat(h => h === 1 ? 1.15 : 1); }, isDreaming ? 1200 : 800); return () => clearInterval(interval); }, [reducedMotion, isDreaming]); // Neural tendril generation useEffect(() => { if (reducedMotion) return; const generateTendrils = () => { const count = rand(3, 7); const newTendrils = Array.from({ length: count }, (_, i) => { const angle = rand(0, Math.PI * 2); const length = rand(40, 100); return { id: i, x1: 0, y1: 0, x2: Math.cos(angle) * length, y2: Math.sin(angle) * length, opacity: rand(0.2, 0.5), duration: rand(1.5, 3), }; }); setTendrils(newTendrils); }; generateTendrils(); const interval = setInterval(generateTendrils, 4000); return () => clearInterval(interval); }, [reducedMotion]); // Dream mode - activate after inactivity useEffect(() => { const checkInactivity = setInterval(() => { const timeSinceInteraction = Date.now() - lastInteraction.current; if (timeSinceInteraction > 15000 && !isDreaming) { setIsDreaming(true); } }, 1000); return () => clearInterval(checkInactivity); }, [isDreaming]); // Wake up on interaction const wakeUp = useCallback(() => { lastInteraction.current = Date.now(); if (isDreaming) { setIsDreaming(false); setThought("...awake"); setGlowing(true); setTimeout(() => { setThought(""); setGlowing(false); }, 1500); } }, [isDreaming]); // Glitch effect useEffect(() => { if (reducedMotion) return; const glitchInterval = setInterval(() => { if (Math.random() > 0.92) { setIsGlitching(true); setTimeout(() => setIsGlitching(false), rand(100, 300)); } }, 3000); return () => clearInterval(glitchInterval); }, [reducedMotion]); // Wandering behavior useEffect(() => { if (reducedMotion || !isWandering) return; let animFrame; const wander = () => { setPosition(pos => { const dx = wanderTarget.x - pos.x; const dy = wanderTarget.y - pos.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 5) { setIsWandering(false); return pos; } return { x: lerp(pos.x, wanderTarget.x, 0.02), y: lerp(pos.y, wanderTarget.y, 0.02), }; }); animFrame = requestAnimationFrame(wander); }; animFrame = requestAnimationFrame(wander); return () => cancelAnimationFrame(animFrame); }, [isWandering, wanderTarget, reducedMotion]); // Random wandering trigger useEffect(() => { if (reducedMotion || isDreaming) return; const wanderInterval = setInterval(() => { if (Math.random() > 0.7 && !isWandering) { const stage = stageRef.current; if (!stage) return; const rect = stage.getBoundingClientRect(); setWanderTarget({ x: rand(-rect.width * 0.3, rect.width * 0.3), y: rand(-rect.height * 0.2, rect.height * 0.2), }); setIsWandering(true); } }, 8000); return () => clearInterval(wanderInterval); }, [reducedMotion, isWandering, isDreaming]); // Autonomous looking around useEffect(() => { if (reducedMotion) return; const lookInterval = setInterval(() => { if (Math.random() > 0.6) { setEyePos({ x: rand(-2, 2), y: rand(-1.5, 1.5), }); setTimeout(() => { setEyePos({ x: 0, y: 0 }); }, rand(500, 1500)); } }, 5000); return () => clearInterval(lookInterval); }, [reducedMotion]); // Eye tracking const updateEyesFrom = (clientX, clientY) => { const sprite = spriteRef.current; if (!sprite) return; const rect = sprite.getBoundingClientRect(); const cx = rect.left + rect.width / 2; const cy = rect.top + rect.height / 2; const scale = Math.max(rect.width, rect.height) / 2 || 1; const dx = (clientX - cx) / scale; const dy = (clientY - cy) / scale; setEyePos({ x: clamp(dx * 2.6, -2.6, 2.6), y: clamp(dy * 2.1, -2.1, 2.1), }); }; const onPointerMove = (e) => { wakeUp(); lastPointer.current = { x: e.clientX, y: e.clientY }; if (rafRef.current) return; rafRef.current = requestAnimationFrame(() => { rafRef.current = 0; updateEyesFrom(lastPointer.current.x, lastPointer.current.y); }); }; const onPointerLeave = () => { setEyePos({ x: 0, y: 0 }); }; // Random blink loop useEffect(() => { if (reducedMotion) return; let blinkTimer = 0; let unblinkTimer = 0; const loop = () => { blinkTimer = window.setTimeout(() => { setBlinking(true); unblinkTimer = window.setTimeout(() => { setBlinking(false); loop(); }, theme.blinkMs); }, rand(theme.blinkEvery[0], theme.blinkEvery[1])); }; loop(); return () => { window.clearTimeout(blinkTimer); window.clearTimeout(unblinkTimer); }; }, [reducedMotion, theme]); // Random thought loop useEffect(() => { if (reducedMotion) return; let showTimer = 0; let hideTimer = 0; const loop = () => { showTimer = window.setTimeout(() => { const thoughts = isDreaming ? DREAM_THOUGHTS : THOUGHTS; setThought(pick(thoughts)); setGlowing(true); setStats(s => ({ ...s, thoughts: s.thoughts + 1 })); hideTimer = window.setTimeout(() => { setThought(""); setGlowing(false); loop(); }, rand(theme.thoughtHold[0], theme.thoughtHold[1])); }, rand(theme.thoughtEvery[0], theme.thoughtEvery[1])); }; loop(); return () => { window.clearTimeout(showTimer); window.clearTimeout(hideTimer); }; }, [reducedMotion, theme, isDreaming]); // Random mood changes useEffect(() => { if (moodLocked || isDreaming) return; let timer = 0; const loop = () => { timer = window.setTimeout(() => { setMood(pick(MOODS)); loop(); }, rand(6500, 11500)); }; loop(); return () => window.clearTimeout(timer); }, [moodLocked, isDreaming]); const cycleMood = () => { wakeUp(); pokeCount.current++; setStats(s => ({ ...s, interactions: s.interactions + 1 })); // React to pokes if (pokeCount.current > 5 && pokeCount.current < 10) { setMood('alert'); setThought(pick(REACTIONS.poke)); } else if (pokeCount.current >= 10) { setMood('scared'); setThought(pick(REACTIONS.fear)); pokeCount.current = 0; setTimeout(() => setMood('curious'), 3000); } if (mood !== 'scared') { setMood((prev) => { const idx = MOODS.indexOf(prev); return MOODS[(idx + 1) % MOODS.length]; }); } setGlowing(true); setTimeout(() => { setThought(""); setGlowing(false); }, 1200); }; const handleDoubleClick = () => { wakeUp(); loveCount.current++; setStats(s => ({ ...s, interactions: s.interactions + 1 })); if (loveCount.current >= 3) { setMood('loving'); setThought(pick(REACTIONS.love)); setGlowing(true); setTimeout(() => { setMood('happy'); setThought(""); setGlowing(false); loveCount.current = 0; }, 4000); } else { setMoodLocked((v) => !v); } }; const toggleStats = (e) => { e.stopPropagation(); setShowStats(s => !s); }; const glowId = `glow_${uid}`; const bodyGradId = `body_${uid}`; const coreGradId = `core_${uid}`; const scanlineId = `scan_${uid}`; const formatTime = (seconds) => { const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m}:${s.toString().padStart(2, '0')}`; }; return (