import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import type { KpChartResponse } from '../types/index.js'; import { RoxyDataElement } from '../utils/base-element.js'; import { baseStyles } from '../utils/base-styles.js'; import { formatNumber } from '../utils/format.js'; type Tab = 'planets' | 'cusps'; /** A planet or node row, normalized so planets and Rahu/Ketu share a table. */ interface KpBody { name: string; sign?: string; house?: number; nakshatra?: string; starLord?: string; subLord?: string; subSubLord?: string; kpNumber?: number; retrograde?: boolean; } /** * KP (Krishnamurti Paddhati) chart. Renders /vedic-astrology/kp/chart: the * Ascendant with its full stellar hierarchy, a planets-and-nodes table, and a * Placidus cusps table. The cusp and planet sub lords are the primary * predictive surface in KP astrology, so each row carries star lord, sub lord, * sub-sub lord, and KP number (1-249). */ @customElement('roxy-kp-chart') export class RoxyKpChart extends RoxyDataElement { static styles = [ baseStyles, css` .wrap { border: 1px solid var(--roxy-border, #e4e4e7); border-radius: var(--roxy-radius-md, 8px); background: var(--roxy-surface, #fff); overflow: auto; box-shadow: var(--roxy-shadow-sm); width: 100%; } .head { padding: var(--roxy-space-md, 1rem); border-bottom: 1px solid var(--roxy-border, #e4e4e7); display: grid; gap: var(--roxy-space-xs, 0.25rem); } .title { margin: 0; font-size: var(--roxy-text-lg, 1.125rem); font-weight: var(--roxy-weight-bold, 600); } .asc, .ayan { color: var(--roxy-muted, #71717a); font-size: var(--roxy-text-sm, 0.875rem); } .asc strong { color: var(--roxy-fg, #0a0a0a); } .tablist { display: flex; gap: 2px; padding: 0 var(--roxy-space-md, 1rem); border-bottom: 2px solid var(--roxy-border, #e4e4e7); } .tab { padding: var(--roxy-space-xs, 0.25rem) var(--roxy-space-md, 1rem); font-size: var(--roxy-text-sm, 0.875rem); background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -2px; cursor: pointer; color: var(--roxy-muted, #71717a); font-family: inherit; } .tab[aria-selected='true'] { color: var(--roxy-accent-ink, #b45309); border-bottom-color: var(--roxy-accent, #f59e0b); font-weight: var(--roxy-weight-bold, 600); } .tab:hover:not([aria-selected='true']) { color: var(--roxy-fg, #0a0a0a); } table { width: 100%; border-collapse: collapse; font-size: var(--roxy-text-sm, 0.875rem); min-width: 620px; } thead { background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 20%, transparent); } th, td { padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem); text-align: left; white-space: nowrap; } th { color: var(--roxy-muted, #71717a); font-weight: var(--roxy-weight-bold, 600); text-transform: uppercase; font-size: var(--roxy-text-xs, 0.75rem); letter-spacing: 0.04em; } tbody tr { border-top: 1px solid var(--roxy-border, #e4e4e7); } td.body { font-weight: var(--roxy-weight-bold, 600); color: var(--roxy-fg, #0a0a0a); } .retro { color: var(--roxy-warning-fg, #9a3412); font-size: var(--roxy-text-xs, 0.75rem); font-weight: var(--roxy-weight-bold, 600); margin-left: 4px; } .num { font-variant-numeric: tabular-nums; } `, ]; @state() private activeTab: Tab = 'planets'; /** Merge the 7 planets and the two nodes into one ordered body list. */ private bodies(): KpBody[] { const d = this.data; if (!d) return []; const rows: KpBody[] = (d.planets ?? []).map((p) => ({ name: p.planet, sign: p.sign, house: p.house, nakshatra: p.nakshatra, starLord: p.starLord, subLord: p.subLord, subSubLord: p.subSubLord, kpNumber: p.kpNumber, retrograde: p.retrograde, })); const nodes = d.nodes; for (const [name, node] of [ ['Rahu', nodes?.rahu], ['Ketu', nodes?.ketu], ] as const) { if (node) { rows.push({ name, sign: node.sign, house: node.house, nakshatra: node.nakshatra, starLord: node.starLord, subLord: node.subLord, subSubLord: node.subSubLord, retrograde: true, }); } } return rows; } private onTabKeyDown(e: KeyboardEvent) { if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return; e.preventDefault(); this.activeTab = this.activeTab === 'planets' ? 'cusps' : 'planets'; const next = this.activeTab; requestAnimationFrame(() => { this.shadowRoot ?.querySelector(`#tab-${next}`) ?.focus(); }); } protected renderEmpty() { return html`
No KP chart data
`; } protected renderData(d: KpChartResponse) { const asc = d.ascendant; return html`

KP chart

${ asc ? html`
Ascendant: ${asc.sign ?? ''} ${asc.nakshatra ? html`· ${asc.nakshatra}` : nothing} ${asc.subLord ? html`· sub lord ${asc.subLord}` : nothing} ${typeof asc.kpNumber === 'number' ? html`· KP ${asc.kpNumber}` : nothing}
` : nothing } ${ typeof d.meta?.ayanamsa === 'number' ? html`
${d.meta.ayanamsaType ?? 'Ayanamsa'}: ${formatNumber(d.meta.ayanamsa, 4)}° ${d.meta.houseSystem ? html`· ${d.meta.houseSystem} houses` : nothing}
` : nothing }
${(['planets', 'cusps'] as const).map( (t) => html``, )}
${this.activeTab === 'planets' ? this.renderPlanets() : this.renderCusps()}
`; } private renderPlanets() { const bodies = this.bodies(); if (!bodies.length) return html`

No planets

`; return html` ${bodies.map( (b) => html``, )}
Body Sign House Nakshatra Star lord Sub lord Sub sub lord KP no.
${b.name}${b.retrograde ? html`R` : nothing} ${b.sign ?? ''} ${typeof b.house === 'number' ? b.house : ''} ${b.nakshatra ?? ''} ${b.starLord ?? ''} ${b.subLord ?? ''} ${b.subSubLord ?? ''} ${typeof b.kpNumber === 'number' ? b.kpNumber : ''}
`; } private renderCusps() { const cusps = this.data?.cusps ?? []; if (!cusps.length) return html`

No cusps

`; return html` ${cusps.map( (c) => html``, )}
House Sign Sign lord Nakshatra Star lord Sub lord Sub sub lord KP no.
${c.house} ${c.sign ?? ''} ${c.signLord ?? ''} ${c.nakshatra ?? ''} ${c.starLord ?? ''} ${c.subLord ?? ''} ${c.subSubLord ?? ''} ${typeof c.kpNumber === 'number' ? c.kpNumber : ''}
`; } } declare global { interface HTMLElementTagNameMap { 'roxy-kp-chart': RoxyKpChart; } }