/** * Planet symbols for the North Indian chart and the chart page planet table. * * The chart used to print display text inside each house ("水 19°", "罗逆 29°"), * which stacked three labels on top of each other whenever a house held three * planets. The chart now carries symbols only; degrees, nakshatra and direction * live in the planet table under the chart. * * Colour is an identification aid, not a verdict: nothing here says benefic or * malefic. Rahu and Ketu deliberately share one neutral grey. * * ♀ and ♂ are followed by U+FE0E (VARIATION SELECTOR-15, text presentation). * Without it iOS renders both as colour emoji. */ export const PLANET_GLYPH_KEYS = [ "sun", "moon", "mars", "mercury", "jupiter", "venus", "saturn", "rahu", "ketu", ] as const; export type PlanetGlyphKey = (typeof PLANET_GLYPH_KEYS)[number]; const TEXT_PRESENTATION = "\uFE0E"; export const PLANET_GLYPH: Readonly> = { sun: "☉", moon: "☽", mars: `♂${TEXT_PRESENTATION}`, mercury: "☿", jupiter: "♃", venus: `♀${TEXT_PRESENTATION}`, saturn: "♄", rahu: "☊", ketu: "☋", }; /** One-character Chinese name, as used in the chart legend. */ export const PLANET_GLYPH_ZH: Readonly> = { sun: "日", moon: "月", mars: "火", mercury: "水", jupiter: "木", venus: "金", saturn: "土", rahu: "罗", ketu: "计", }; /** Full Chinese name, as used in the planet table. */ export const PLANET_NAME_ZH: Readonly> = { sun: "太阳", moon: "月亮", mars: "火星", mercury: "水星", jupiter: "木星", venus: "金星", saturn: "土星", rahu: "北交点", ketu: "南交点", }; export const PLANET_ABBR: Readonly> = { sun: "Su", moon: "Mo", mars: "Ma", mercury: "Me", jupiter: "Ju", venus: "Ve", saturn: "Sa", rahu: "Ra", ketu: "Ke", }; /** * Colour modifier per body; both nodes share one. The colours themselves are * `--color-planet-*` in globals.css, one value per theme. * * A class rather than an inline `style`: the personal report's markup contract * forbids a `style=` attribute anywhere in the rendered document, and that rule * is load-bearing (it is what keeps agent-authored content from carrying its * own CSS), so the chart may not be the one exception. */ export const PLANET_COLOR_CLASS: Readonly> = { sun: "is-sun", moon: "is-moon", mars: "is-mars", mercury: "is-mercury", jupiter: "is-jupiter", venus: "is-venus", saturn: "is-saturn", rahu: "is-node", ketu: "is-node", }; export const ASCENDANT_COLOR_CLASS = "is-ascendant"; /** The North Indian first house is always the ascendant, so the mark needs no data. */ export const ASCENDANT_MARK = "As"; export const ASCENDANT_NAME_ZH = "上升"; /** Chinese names the engines emit, keyed by their first character. */ const ZH_FIRST_CHARACTER: Readonly> = { 日: "sun", 太: "sun", // 太阳 月: "moon", 火: "mars", 水: "mercury", 木: "jupiter", 金: "venus", 土: "saturn", 罗: "rahu", // 罗睺 计: "ketu", // 计都 }; const EN_NAMES: Readonly> = { sun: "sun", moon: "moon", mars: "mars", mercury: "mercury", jupiter: "jupiter", venus: "venus", saturn: "saturn", rahu: "rahu", ketu: "ketu", }; const ASCENDANT_NAMES = new Set(["as", "asc", "ascendant", "lagna"]); /** 上升 / 上升点 / 上升星座 — the report's own wording for the same point. */ const ASCENDANT_ZH_PREFIX = "上升"; /** * Maps a label to a glyph key. Accepts every spelling the three call sites can * produce: the report chart block's one-character names ("水", "罗"), the * rectification house table's full Chinese names ("水星", "罗睺"), and the * report evidence bundle's English celestial names ("Mercury", "Rahu"). */ export function planetGlyphKey(name: string): PlanetGlyphKey | null { const trimmed = name.trim(); if (!trimmed) return null; const latin = trimmed.match(/^[A-Za-z]+/); if (latin) return EN_NAMES[latin[0].toLowerCase()] ?? null; return ZH_FIRST_CHARACTER[trimmed[0]!] ?? null; } export type ChartOccupant = | { kind: "planet"; key: PlanetGlyphKey; retrograde: boolean; label: string } | { kind: "ascendant"; label: string } | { kind: "text"; label: string }; /** * Reads one occupant string. The rectification board and the personal report * both receive their house tables as engine/agent display text and have no * structured planet list to join against — their contracts are golden-fixture * shaped and may not be invented here — so those two routes are parsed. * `toNorthIndianChart` instead attaches `occupantGlyphs` and never reaches this. */ export function parseChartOccupant(label: string): ChartOccupant { const trimmed = label.trim(); if (!trimmed) return { kind: "text", label }; if (ASCENDANT_NAMES.has(trimmed.toLowerCase()) || trimmed.startsWith(ASCENDANT_ZH_PREFIX)) { return { kind: "ascendant", label: trimmed }; } const key = planetGlyphKey(trimmed); if (!key) return { kind: "text", label: trimmed }; return { kind: "planet", key, retrograde: trimmed.includes("逆"), label: trimmed }; } /** Reading order for the chart legend: 日 月 水 金 火 木 土 罗 计. */ export const PLANET_LEGEND_ORDER = [ "sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "rahu", "ketu", ] as const satisfies readonly PlanetGlyphKey[];