// natal-chart.jsx — animated natal chart visualization const ZODIAC_ORDER = [ 'aries','taurus','gemini','cancer','leo','virgo', 'libra','scorpio','sagittarius','capricorn','aquarius','pisces' ]; // Sample planet positions (degrees from 0° Aries, counterclockwise) const DEFAULT_PLACEMENTS = [ { planet: 'sun', deg: 98, house: 4 }, { planet: 'moon', deg: 215, house: 8 }, { planet: 'mercury', deg: 110, house: 4 }, { planet: 'venus', deg: 76, house: 3 }, { planet: 'mars', deg: 178, house: 7 }, { planet: 'jupiter', deg: 305, house: 11 }, { planet: 'saturn', deg: 260, house: 10 }, ]; // polar to cartesian: chart is centered at cx,cy with radius r // Astrology charts are drawn with 0° Aries at left (9 o'clock) going counterclockwise. const polar = (cx, cy, r, deg) => { // convert: 0° = left, going CCW const rad = ((180 - deg) * Math.PI) / 180; return { x: cx + r * Math.cos(rad), y: cy - r * Math.sin(rad) }; }; const NatalChart = ({ size = 560, placements = DEFAULT_PLACEMENTS, animated = true, showAspects = true, showHouses = true, variant = 'full', // 'full' | 'minimal' | 'tiny' glow = true, }) => { const cx = size / 2; const cy = size / 2; const rOuter = size * 0.46; const rZodiac = size * 0.42; const rHouse = size * 0.34; const rPlanet = size * 0.30; const rInner = size * 0.18; // simple aspects between placements: opposition (~180°), trine (~120°), square (~90°) const aspects = []; if (showAspects) { for (let i = 0; i < placements.length; i++) { for (let j = i + 1; j < placements.length; j++) { let d = Math.abs(placements[i].deg - placements[j].deg); if (d > 180) d = 360 - d; let type = null; if (Math.abs(d - 180) < 6) type = 'opp'; else if (Math.abs(d - 120) < 6) type = 'trine'; else if (Math.abs(d - 90) < 5) type = 'square'; else if (Math.abs(d - 60) < 4) type = 'sextile'; if (type) aspects.push({ a: placements[i], b: placements[j], type }); } } } return ( {/* Background glow */} {glow && } {/* Outer ring + zodiac wheel */} {/* 12 zodiac divisions */} {ZODIAC_ORDER.map((_, i) => { const deg = i * 30; const p1 = polar(cx, cy, rZodiac, deg); const p2 = polar(cx, cy, rOuter, deg); return ; })} {/* Zodiac glyphs */} {ZODIAC_ORDER.map((z, i) => { const center = polar(cx, cy, (rZodiac + rOuter) / 2, i * 30 + 15); return ( {Zodiac[z]} ); })} {/* Houses ring */} {showHouses && ( {Array.from({ length: 12 }).map((_, i) => { const deg = i * 30 + 12; const p1 = polar(cx, cy, rInner, deg); const p2 = polar(cx, cy, rHouse, deg); return ; })} {/* house numbers */} {Array.from({ length: 12 }).map((_, i) => { const p = polar(cx, cy, rInner + 14, i * 30 + 12 + 15); return ( {i + 1} ); })} )} {/* Aspect lines */} {showAspects && aspects.map((a, i) => { const p1 = polar(cx, cy, rPlanet, a.a.deg); const p2 = polar(cx, cy, rPlanet, a.b.deg); const colors = { opp: 'var(--rose)', square: 'var(--rose)', trine: 'var(--sage)', sextile: 'var(--celest)' }; const dash = a.type === 'opp' ? '4 3' : a.type === 'square' ? '2 3' : a.type === 'trine' ? '0' : '1 4'; return ( ); })} {/* Inner circle */} {/* Planets — slowly drift */} {placements.map((pl, i) => { const p = polar(cx, cy, rPlanet, pl.deg); const tick1 = polar(cx, cy, rZodiac - 2, pl.deg); const tick2 = polar(cx, cy, rZodiac - 8, pl.deg); return ( {/* tick on zodiac ring */} {/* planet glyph */} {Planet[pl.planet]} ); })} {/* Center mark */} {/* Scattered tiny twinkling stars */} {variant === 'full' && Array.from({ length: 18 }).map((_, i) => { const r = rOuter + 10 + (i % 5) * 8; const deg = (i * 37) % 360; const p = polar(cx, cy, r, deg); return ( ); })} ); }; // Minimal version — just orbital rings and a few planets, for backgrounds const NatalChartBg = ({ size = 800, opacity = 0.5 }) => { const cx = size / 2; const cy = size / 2; return ( {[0.18, 0.24, 0.30, 0.36, 0.42, 0.48].map((m, i) => ( ))} {Array.from({ length: 12 }).map((_, i) => { const deg = i * 30; const p1 = polar(cx, cy, size * 0.18, deg); const p2 = polar(cx, cy, size * 0.48, deg); return ; })} {/* drifting planets */} {[ { r: 0.42, deg: 45, c: 'var(--gold)' }, { r: 0.30, deg: 200, c: 'var(--rose)' }, { r: 0.36, deg: 285, c: 'var(--celest)' }, { r: 0.24, deg: 120, c: 'var(--sage)' }, ].map((p, i) => { const pt = polar(cx, cy, size * p.r, p.deg); return ; })} {/* twinkling stars */} {Array.from({ length: 40 }).map((_, i) => { const x = (i * 73) % size; const y = (i * 113) % size; return ; })} ); }; Object.assign(window, { NatalChart, NatalChartBg, polar, ZODIAC_ORDER });