/* Cosmic Bagua compass — dark, glowing, with light-beam effects */ const COSMIC_DATA = { trigrams: [ { sym: "☰", name: "乾" }, { sym: "☱", name: "兑" }, { sym: "☲", name: "离" }, { sym: "☳", name: "震" }, { sym: "☴", name: "巽" }, { sym: "☵", name: "坎" }, { sym: "☶", name: "艮" }, { sym: "☷", name: "坤" }, ], branches: ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"], solarTerms: [ "冬至","小寒","大寒","立春","雨水","惊蛰", "春分","清明","谷雨","立夏","小满","芒种", "夏至","小暑","大暑","立秋","处暑","白露", "秋分","寒露","霜降","立冬","小雪","大雪" ], mountains: [ "壬","子","癸","丑","艮","寅","甲","卯","乙","辰","巽","巳", "丙","午","丁","未","坤","申","庚","酉","辛","戌","乾","亥" ], hexagrams: [ "乾","坤","屯","蒙","需","讼","师","比", "小畜","履","泰","否","同人","大有","谦","豫", "随","蛊","临","观","噬嗑","贲","剥","复", "无妄","大畜","颐","大过","坎","离","咸","恒", "遁","大壮","晋","明夷","家人","睽","蹇","解", "损","益","夬","姤","萃","升","困","井", "革","鼎","震","艮","渐","归妹","丰","旅", "巽","兑","涣","节","中孚","小过","既济","未济" ], starSigils: [ "角","亢","氐","房","心","尾","箕", "斗","牛","女","虚","危","室","壁", "奎","娄","胃","昴","毕","觜","参", "井","鬼","柳","星","张","翼","轸" ], }; /* Cosmic Ring */ function CosmicRing({ items, innerR, outerR, rotation, onRotate, fontSize, glowColor, textColor, divider, fillEven, fillOdd, zIndex = 1, renderItem, accentIndices = [], }) { const ref = React.useRef(null); const drag = React.useRef(null); const n = items.length; const step = 360 / n; const cx = outerR, cy = outerR; const onPointerDown = (e) => { const rect = ref.current.getBoundingClientRect(); const ox = rect.left + rect.width / 2; const oy = rect.top + rect.height / 2; const a0 = Math.atan2(e.clientY - oy, e.clientX - ox) * 180 / Math.PI; drag.current = { a0, r0: rotation, ox, oy }; e.currentTarget.setPointerCapture(e.pointerId); }; const onPointerMove = (e) => { if (!drag.current) return; const { a0, r0 } = drag.current; const a = Math.atan2(e.clientY - drag.current.oy, e.clientX - drag.current.ox) * 180 / Math.PI; onRotate(r0 + (a - a0)); }; const onPointerUp = (e) => { drag.current = null; try { e.currentTarget.releasePointerCapture(e.pointerId); } catch {} }; const sectors = []; for (let i = 0; i < n; i++) { const a1 = (i * step - 90) * Math.PI / 180; const a2 = ((i + 1) * step - 90) * Math.PI / 180; const x1o = cx + outerR * Math.cos(a1), y1o = cy + outerR * Math.sin(a1); const x2o = cx + outerR * Math.cos(a2), y2o = cy + outerR * Math.sin(a2); const x1i = cx + innerR * Math.cos(a1), y1i = cy + innerR * Math.sin(a1); const x2i = cx + innerR * Math.cos(a2), y2i = cy + innerR * Math.sin(a2); const large = step > 180 ? 1 : 0; const d = `M ${x1o} ${y1o} A ${outerR} ${outerR} 0 ${large} 1 ${x2o} ${y2o} L ${x2i} ${y2i} A ${innerR} ${innerR} 0 ${large} 0 ${x1i} ${y1i} Z`; sectors.push( ); } const midR = (innerR + outerR) / 2; const labels = items.map((it, i) => { const ang = i * step + step / 2 - 90; const rad = ang * Math.PI / 180; const x = cx + midR * Math.cos(rad); const y = cy + midR * Math.sin(rad); const rot = ang + 90; const isAccent = accentIndices.includes(i); const content = renderItem ? renderItem(it, i, isAccent) : it; return ( {typeof content === "string" ? {content} : content} ); }); return ( {sectors} {labels} ); } /* Glowing Taiji */ function CosmicTaiji({ size, rotation = 0, hue = "gold" }) { const colors = hue === "cyan" ? { light: "#9ff5ff", dark: "#0a1a26", glow: "#5ed8ff" } : { light: "#ffe9a8", dark: "#1a0e05", glow: "#ffce5c" }; return ( ); } /* Vertical light beams rising and descending */ function LightBeams({ baseR, hue = "gold" }) { const beams = React.useMemo(() => { const arr = []; const N = 24; for (let i = 0; i < N; i++) { const ang = (i * 360 / N) * Math.PI / 180; const r = baseR + 20 + Math.random() * 30; arr.push({ x: r * Math.cos(ang - Math.PI / 2), y: r * Math.sin(ang - Math.PI / 2), delay: Math.random() * 4, dur: 2.5 + Math.random() * 2.5, height: 80 + Math.random() * 220, width: 1.5 + Math.random() * 2.5, }); } return arr; }, [baseR]); const beamColor = hue === "cyan" ? "#5ed8ff" : "#ffce5c"; return (
{beams.map((b, i) => (
))}
); } /* Constellation dots orbiting outside */ function OrbitingStars({ baseR, hue = "gold" }) { const dots = React.useMemo(() => { return Array.from({ length: 60 }, () => ({ ang: Math.random() * 360, r: baseR + 60 + Math.random() * 200, size: 1 + Math.random() * 2.5, blink: Math.random() * 4, blinkDur: 2 + Math.random() * 4, })); }, [baseR]); const c = hue === "cyan" ? "#9ff5ff" : "#ffe9a8"; return (
{dots.map((d, i) => { const rad = d.ang * Math.PI / 180; return (
); })}
); } /* The Cosmic Compass */ function CosmicCompass({ tweaks }) { const baseR = 360; const accentColors = tweaks.hue === "cyan" ? { glow: "#5ed8ff", text: "#cfeeff", divider: "rgba(94,216,255,0.55)", fillA: "rgba(20,60,90,0.55)", fillB: "rgba(8,28,48,0.65)" } : { glow: "#ffce5c", text: "#f1dba0", divider: "rgba(255,206,92,0.5)", fillA: "rgba(80,40,8,0.55)", fillB: "rgba(40,20,4,0.65)" }; // 4 cardinal indices for mountains: 子 卯 午 酉 → indices 1, 7, 13, 19 const cardinalsMountains = [1, 7, 13, 19]; const ringSpec = [ { key: "trigrams", inner: 80, outer: 115, items: COSMIC_DATA.trigrams.map(t => t.name), font: 18 }, { key: "branches", inner: 115, outer: 150, items: COSMIC_DATA.branches, font: 20 }, { key: "terms", inner: 150, outer: 190, items: COSMIC_DATA.solarTerms, font: 11, renderItem: (s) => ( {s[0]} {s[1]} ) }, { key: "mountains", inner: 190, outer: 232, items: COSMIC_DATA.mountains, font: 22, accentIndices: cardinalsMountains }, { key: "hexagrams", inner: 232, outer: 295, items: COSMIC_DATA.hexagrams, font: 13, renderItem: (s) => s.length === 1 ? {s} : ( {s[0]} {s[1]} ) }, { key: "stars", inner: 295, outer: baseR, items: [...COSMIC_DATA.starSigils, ...COSMIC_DATA.starSigils.slice(0, 36)].slice(0, 36), font: 16 }, ]; const [rotations, setRotations] = React.useState( Object.fromEntries(ringSpec.map(r => [r.key, 0])) ); const burstRef = React.useRef(null); React.useEffect(() => { const onBurst = (e) => { const dur = (e.detail && e.detail.duration) || 30000; burstRef.current = { start: performance.now(), duration: dur }; }; window.addEventListener('compass-burst', onBurst); return () => window.removeEventListener('compass-burst', onBurst); }, []); React.useEffect(() => { let raf, last = performance.now(); const tick = (t) => { const dt = Math.min(0.05, (t - last) / 1000); last = t; const burst = burstRef.current; let burstMult = 1; if (burst) { const elapsed = t - burst.start; if (elapsed < burst.duration) { const k = elapsed / burst.duration; // Strong easing-out: starts ~80x, decays to 1x burstMult = 1 + 80 * Math.pow(1 - k, 3.2); } else { burstRef.current = null; } } const active = tweaks.autoSpin || burstRef.current; if (active) { setRotations(prev => { const next = { ...prev }; ringSpec.forEach((r, i) => { const dir = i % 2 === 0 ? 1 : -1; const base = (tweaks.spinSpeed || 6) * (1 - i * 0.08); const speed = base * burstMult; next[r.key] = (prev[r.key] || 0) + dir * speed * dt; }); return next; }); } raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [tweaks.autoSpin, tweaks.spinSpeed]); // Pulsing aura const [pulse, setPulse] = React.useState(0); React.useEffect(() => { let raf; const tick = () => { setPulse(performance.now() / 1000); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, []); const pulseScale = 1 + Math.sin(pulse * 1.4) * 0.04; const pulseOpacity = 0.45 + Math.sin(pulse * 1.4) * 0.2; return (
{/* Pulsing aura behind everything */}
{/* Orbiting starfield */} {tweaks.showStars && } {/* Light beams rising/descending */} {tweaks.showBeams && }
{/* Outer glowing rings */}
{/* Tick marks */} {Array.from({ length: 72 }).map((_, i) => { const ang = (i * 5 - 90) * Math.PI / 180; const r1 = baseR - 4; const r2 = baseR + 12; const major = i % 9 === 0; return ( ); })} {ringSpec.map((r, i) => ( setRotations(p => ({ ...p, [r.key]: v }))} fontSize={r.font} glowColor={accentColors.glow} textColor={accentColors.text} divider={accentColors.divider} fillEven={accentColors.fillA} fillOdd={accentColors.fillB} renderItem={r.renderItem} accentIndices={r.accentIndices || []} zIndex={10 + (ringSpec.length - i)} /> ))} {/* Cardinal cross */}
); } window.CosmicCompass = CosmicCompass;