// 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 (
);
};
// 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 });