@roxyapi/ui 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +216 -38
- package/README.md +200 -24
- package/dist/cdn/components/compatibility-card.js.map +1 -1
- package/dist/cdn/components/dasha-timeline.js +8 -8
- package/dist/cdn/components/dasha-timeline.js.map +2 -2
- package/dist/cdn/components/divisional-chart.js +35 -23
- package/dist/cdn/components/divisional-chart.js.map +4 -4
- package/dist/cdn/components/guna-milan.js.map +1 -1
- package/dist/cdn/components/kp-chart.js +306 -0
- package/dist/cdn/components/kp-chart.js.map +7 -0
- package/dist/cdn/components/kp-planets-table.js.map +1 -1
- package/dist/cdn/components/kp-ruling-planets.js +269 -0
- package/dist/cdn/components/kp-ruling-planets.js.map +7 -0
- package/dist/cdn/components/location-search.js +7 -5
- package/dist/cdn/components/location-search.js.map +3 -3
- package/dist/cdn/components/moon-phase.js.map +1 -1
- package/dist/cdn/components/nakshatra-card.js +229 -0
- package/dist/cdn/components/nakshatra-card.js.map +7 -0
- package/dist/cdn/components/natal-chart.js +228 -115
- package/dist/cdn/components/natal-chart.js.map +4 -4
- package/dist/cdn/components/numerology-card.js +3 -3
- package/dist/cdn/components/numerology-card.js.map +2 -2
- package/dist/cdn/components/panchang-table.js.map +1 -1
- package/dist/cdn/components/shadbala-table.js.map +1 -1
- package/dist/cdn/components/synastry-chart.js +3 -3
- package/dist/cdn/components/synastry-chart.js.map +2 -2
- package/dist/cdn/components/transits-table.js.map +1 -1
- package/dist/cdn/components/vedic-kundli.js +34 -22
- package/dist/cdn/components/vedic-kundli.js.map +4 -4
- package/dist/cdn/components/vedic-planets-table.js +231 -0
- package/dist/cdn/components/vedic-planets-table.js.map +7 -0
- package/dist/cdn/components/western-planets-table.js +220 -0
- package/dist/cdn/components/western-planets-table.js.map +7 -0
- package/dist/cdn/roxy-ui.js +1078 -331
- package/dist/cdn/roxy-ui.js.map +4 -4
- package/dist/components/compatibility-card.js.map +1 -1
- package/dist/components/dasha-timeline.d.ts.map +1 -1
- package/dist/components/dasha-timeline.js.map +2 -2
- package/dist/components/divisional-chart.d.ts +5 -3
- package/dist/components/divisional-chart.d.ts.map +1 -1
- package/dist/components/divisional-chart.js +159 -38
- package/dist/components/divisional-chart.js.map +3 -3
- package/dist/components/guna-milan.js.map +1 -1
- package/dist/components/kp-chart.d.ts +26 -0
- package/dist/components/kp-chart.d.ts.map +1 -0
- package/dist/components/kp-chart.js +382 -0
- package/dist/components/kp-chart.js.map +7 -0
- package/dist/components/kp-planets-table.js.map +1 -1
- package/dist/components/kp-ruling-planets.d.ts +20 -0
- package/dist/components/kp-ruling-planets.d.ts.map +1 -0
- package/dist/components/kp-ruling-planets.js +275 -0
- package/dist/components/kp-ruling-planets.js.map +7 -0
- package/dist/components/location-search.d.ts.map +1 -1
- package/dist/components/location-search.js +9 -2
- package/dist/components/location-search.js.map +2 -2
- package/dist/components/moon-phase.js.map +1 -1
- package/dist/components/nakshatra-card.d.ts +18 -0
- package/dist/components/nakshatra-card.d.ts.map +1 -0
- package/dist/components/nakshatra-card.js +231 -0
- package/dist/components/nakshatra-card.js.map +7 -0
- package/dist/components/natal-chart.d.ts +28 -0
- package/dist/components/natal-chart.d.ts.map +1 -1
- package/dist/components/natal-chart.js +401 -104
- package/dist/components/natal-chart.js.map +2 -2
- package/dist/components/numerology-card.d.ts.map +1 -1
- package/dist/components/numerology-card.js.map +2 -2
- package/dist/components/panchang-table.js.map +1 -1
- package/dist/components/shadbala-table.js.map +1 -1
- package/dist/components/synastry-chart.js.map +2 -2
- package/dist/components/transits-table.js.map +1 -1
- package/dist/components/vedic-kundli.d.ts +7 -3
- package/dist/components/vedic-kundli.d.ts.map +1 -1
- package/dist/components/vedic-kundli.js +209 -87
- package/dist/components/vedic-kundli.js.map +3 -3
- package/dist/components/vedic-planets-table.d.ts +21 -0
- package/dist/components/vedic-planets-table.d.ts.map +1 -0
- package/dist/components/vedic-planets-table.js +355 -0
- package/dist/components/vedic-planets-table.js.map +7 -0
- package/dist/components/western-planets-table.d.ts +21 -0
- package/dist/components/western-planets-table.d.ts.map +1 -0
- package/dist/components/western-planets-table.js +350 -0
- package/dist/components/western-planets-table.js.map +7 -0
- package/dist/index.cjs +2042 -695
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2029 -682
- package/dist/index.js.map +4 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.json +5 -0
- package/dist/styles/tokens.css +4 -0
- package/dist/types/types.gen.d.ts +343 -49
- package/dist/types/types.gen.d.ts.map +1 -1
- package/dist/utils/degree.d.ts +12 -0
- package/dist/utils/degree.d.ts.map +1 -1
- package/dist/utils/format.d.ts +1 -1
- package/dist/utils/kundli-render.d.ts +85 -12
- package/dist/utils/kundli-render.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/dasha-timeline.ts +1 -7
- package/src/components/divisional-chart.ts +27 -41
- package/src/components/kp-chart.ts +313 -0
- package/src/components/kp-ruling-planets.ts +196 -0
- package/src/components/location-search.ts +16 -2
- package/src/components/nakshatra-card.ts +149 -0
- package/src/components/natal-chart.ts +408 -119
- package/src/components/numerology-card.ts +1 -5
- package/src/components/vedic-kundli.ts +30 -40
- package/src/components/vedic-planets-table.ts +184 -0
- package/src/components/western-planets-table.ts +180 -0
- package/src/index.ts +5 -0
- package/src/manifest.ts +146 -84
- package/src/styles/tokens.css +4 -0
- package/src/types/types.gen.ts +343 -49
- package/src/utils/degree.ts +21 -0
- package/src/utils/format.ts +1 -1
- package/src/utils/kundli-render.ts +234 -29
- package/src/version.ts +1 -1
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { css, html, LitElement, nothing, svg } from 'lit';
|
|
2
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
-
import {
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
3
|
+
import {
|
|
4
|
+
ASPECT_SYMBOL,
|
|
5
|
+
PLANET_GLYPH,
|
|
6
|
+
SIGN_GLYPH,
|
|
7
|
+
SIGNS_ORDER,
|
|
8
|
+
} from '../tokens/index.js';
|
|
4
9
|
import type { NatalChartResponse } from '../types/index.js';
|
|
5
10
|
import { baseStyles } from '../utils/base-styles.js';
|
|
6
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
arcMidpoint,
|
|
13
|
+
longitudeToSignPosition,
|
|
14
|
+
normalizeLongitude,
|
|
15
|
+
oppositePoint,
|
|
16
|
+
polarToCartesian,
|
|
17
|
+
} from '../utils/degree.js';
|
|
7
18
|
import {
|
|
8
19
|
ASPECT_CLASS,
|
|
9
20
|
formatNumber,
|
|
@@ -76,12 +87,35 @@ export class RoxyNatalChart extends LitElement {
|
|
|
76
87
|
font-family: var(--roxy-font-sans);
|
|
77
88
|
}
|
|
78
89
|
|
|
90
|
+
.planet-deg {
|
|
91
|
+
fill: var(--roxy-fg, #0a0a0a);
|
|
92
|
+
font-size: 7px;
|
|
93
|
+
font-family: var(--roxy-font-sans);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.planet-deg .retro {
|
|
97
|
+
fill: var(--roxy-danger, #dc2626);
|
|
98
|
+
}
|
|
99
|
+
|
|
79
100
|
.house-num {
|
|
80
101
|
fill: var(--roxy-muted, #71717a);
|
|
81
102
|
font-size: 9px;
|
|
82
103
|
font-family: var(--roxy-font-sans);
|
|
83
104
|
}
|
|
84
105
|
|
|
106
|
+
.cusp-deg {
|
|
107
|
+
fill: var(--roxy-muted, #71717a);
|
|
108
|
+
font-size: 6px;
|
|
109
|
+
font-family: var(--roxy-font-sans);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.tick {
|
|
113
|
+
stroke: var(--roxy-border, #e4e4e7);
|
|
114
|
+
}
|
|
115
|
+
.tick-major {
|
|
116
|
+
stroke: var(--roxy-secondary, #475569);
|
|
117
|
+
}
|
|
118
|
+
|
|
85
119
|
.aspect {
|
|
86
120
|
stroke-width: 0.8;
|
|
87
121
|
fill: none;
|
|
@@ -131,6 +165,78 @@ export class RoxyNatalChart extends LitElement {
|
|
|
131
165
|
vertical-align: middle;
|
|
132
166
|
}
|
|
133
167
|
|
|
168
|
+
.tablist {
|
|
169
|
+
display: flex;
|
|
170
|
+
gap: 2px;
|
|
171
|
+
border-bottom: 2px solid var(--roxy-border, #e4e4e7);
|
|
172
|
+
}
|
|
173
|
+
.tab {
|
|
174
|
+
padding: var(--roxy-space-xs, 0.25rem) var(--roxy-space-md, 1rem);
|
|
175
|
+
font-size: var(--roxy-text-sm, 0.875rem);
|
|
176
|
+
background: none;
|
|
177
|
+
border: none;
|
|
178
|
+
border-bottom: 2px solid transparent;
|
|
179
|
+
margin-bottom: -2px;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
color: var(--roxy-muted, #71717a);
|
|
182
|
+
font-family: inherit;
|
|
183
|
+
transition: color var(--roxy-motion-duration, 200ms) var(--roxy-motion-easing, ease);
|
|
184
|
+
}
|
|
185
|
+
.tab[aria-selected='true'] {
|
|
186
|
+
color: var(--roxy-accent-fg, #b45309);
|
|
187
|
+
border-bottom-color: var(--roxy-accent, #f59e0b);
|
|
188
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
189
|
+
}
|
|
190
|
+
.tab:hover:not([aria-selected='true']) {
|
|
191
|
+
color: var(--roxy-fg, #0a0a0a);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.grid-scroll {
|
|
195
|
+
overflow-x: auto;
|
|
196
|
+
-webkit-overflow-scrolling: touch;
|
|
197
|
+
}
|
|
198
|
+
table.aspect-grid {
|
|
199
|
+
border-collapse: collapse;
|
|
200
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
201
|
+
margin: 0 auto;
|
|
202
|
+
}
|
|
203
|
+
table.aspect-grid th,
|
|
204
|
+
table.aspect-grid td {
|
|
205
|
+
width: 1.6rem;
|
|
206
|
+
height: 1.6rem;
|
|
207
|
+
text-align: center;
|
|
208
|
+
border: 1px solid var(--roxy-border, #e4e4e7);
|
|
209
|
+
padding: 0;
|
|
210
|
+
}
|
|
211
|
+
table.aspect-grid th {
|
|
212
|
+
color: var(--roxy-secondary, #475569);
|
|
213
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
214
|
+
}
|
|
215
|
+
table.aspect-grid td.cell {
|
|
216
|
+
cursor: default;
|
|
217
|
+
}
|
|
218
|
+
table.aspect-grid td.empty {
|
|
219
|
+
background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 18%, transparent);
|
|
220
|
+
}
|
|
221
|
+
table.aspect-grid td .asp {
|
|
222
|
+
font-size: 0.95em;
|
|
223
|
+
line-height: 1;
|
|
224
|
+
}
|
|
225
|
+
table.aspect-grid td.aspect-trine .asp,
|
|
226
|
+
table.aspect-grid td.aspect-sextile .asp {
|
|
227
|
+
color: var(--roxy-success, #16a34a);
|
|
228
|
+
}
|
|
229
|
+
table.aspect-grid td.aspect-square .asp,
|
|
230
|
+
table.aspect-grid td.aspect-opposition .asp {
|
|
231
|
+
color: var(--roxy-danger, #dc2626);
|
|
232
|
+
}
|
|
233
|
+
table.aspect-grid td.aspect-conjunction .asp {
|
|
234
|
+
color: var(--roxy-accent-fg, #b45309);
|
|
235
|
+
}
|
|
236
|
+
table.aspect-grid td.aspect-other .asp {
|
|
237
|
+
color: var(--roxy-muted, #71717a);
|
|
238
|
+
}
|
|
239
|
+
|
|
134
240
|
.details {
|
|
135
241
|
margin-top: var(--roxy-space-md, 1rem);
|
|
136
242
|
}
|
|
@@ -171,48 +277,37 @@ export class RoxyNatalChart extends LitElement {
|
|
|
171
277
|
margin: var(--roxy-space-md, 1rem) 0;
|
|
172
278
|
}
|
|
173
279
|
|
|
174
|
-
.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
280
|
+
.em-grid {
|
|
281
|
+
border-collapse: collapse;
|
|
282
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
283
|
+
width: 100%;
|
|
178
284
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
285
|
+
.em-grid th,
|
|
286
|
+
.em-grid td {
|
|
287
|
+
border: 1px solid var(--roxy-border, #e4e4e7);
|
|
288
|
+
padding: 3px 5px;
|
|
289
|
+
text-align: center;
|
|
290
|
+
vertical-align: middle;
|
|
184
291
|
}
|
|
185
|
-
|
|
186
|
-
.dist-section h3 {
|
|
187
|
-
font-size: var(--roxy-text-xs, 0.75rem);
|
|
188
|
-
font-weight: var(--roxy-weight-bold, 600);
|
|
292
|
+
.em-grid th {
|
|
189
293
|
color: var(--roxy-muted, #71717a);
|
|
190
|
-
|
|
294
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
191
295
|
text-transform: uppercase;
|
|
192
|
-
letter-spacing: 0.
|
|
296
|
+
letter-spacing: 0.04em;
|
|
193
297
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
display: grid;
|
|
197
|
-
grid-template-columns: 4rem 1fr 1.5rem;
|
|
198
|
-
align-items: center;
|
|
199
|
-
gap: var(--roxy-space-xs, 0.25rem);
|
|
200
|
-
font-size: var(--roxy-text-xs, 0.75rem);
|
|
201
|
-
color: var(--roxy-fg, #0f172a);
|
|
202
|
-
margin-bottom: 4px;
|
|
298
|
+
.em-grid th[scope='row'] {
|
|
299
|
+
text-align: left;
|
|
203
300
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
height:
|
|
208
|
-
|
|
301
|
+
.em-grid td {
|
|
302
|
+
color: var(--roxy-accent, #f59e0b);
|
|
303
|
+
font-size: 0.95em;
|
|
304
|
+
line-height: 1.4;
|
|
305
|
+
min-width: 1.4rem;
|
|
209
306
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
background: var(--roxy-accent, #f59e0b);
|
|
215
|
-
border-radius: 3px;
|
|
307
|
+
.em-grid .em-total {
|
|
308
|
+
color: var(--roxy-fg, #0a0a0a);
|
|
309
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
310
|
+
background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 25%, transparent);
|
|
216
311
|
}
|
|
217
312
|
|
|
218
313
|
.interpretations {
|
|
@@ -269,6 +364,10 @@ export class RoxyNatalChart extends LitElement {
|
|
|
269
364
|
@property({ type: String, attribute: 'house-system', reflect: true })
|
|
270
365
|
houseSystem: 'placidus' | 'whole-sign' | 'equal' | 'koch' = 'placidus';
|
|
271
366
|
|
|
367
|
+
/** Which view is showing: the wheel or the planet-by-planet aspect grid. */
|
|
368
|
+
@state()
|
|
369
|
+
private view: 'wheel' | 'grid' = 'wheel';
|
|
370
|
+
|
|
272
371
|
private getPlanets(): PlanetEntry[] {
|
|
273
372
|
return this.data?.planets ?? [];
|
|
274
373
|
}
|
|
@@ -291,6 +390,7 @@ export class RoxyNatalChart extends LitElement {
|
|
|
291
390
|
return html`<div class="roxy-empty" role="status">No chart data</div>`;
|
|
292
391
|
const planets = this.getPlanets();
|
|
293
392
|
const aspects = this.data.aspects ?? [];
|
|
393
|
+
const view = this.view;
|
|
294
394
|
|
|
295
395
|
return html`<div class="wrap">
|
|
296
396
|
<header>
|
|
@@ -305,44 +405,39 @@ export class RoxyNatalChart extends LitElement {
|
|
|
305
405
|
: nothing
|
|
306
406
|
}
|
|
307
407
|
</header>
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
role="
|
|
311
|
-
aria-label="Natal chart
|
|
408
|
+
<div
|
|
409
|
+
class="tablist"
|
|
410
|
+
role="tablist"
|
|
411
|
+
aria-label="Natal chart views"
|
|
412
|
+
@keydown=${this.onTabKeyDown}
|
|
312
413
|
>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
<circle
|
|
333
|
-
class="wheel-line"
|
|
334
|
-
cx=${CENTER}
|
|
335
|
-
cy=${CENTER}
|
|
336
|
-
r=${PLANET_R - 16}
|
|
337
|
-
stroke-width="0.5"
|
|
338
|
-
/>
|
|
339
|
-
${this.renderSpokes()} ${this.renderSigns()} ${this.renderHouseNumbers()}
|
|
340
|
-
${this.renderAspects(planets, aspects)} ${this.renderPlanets(planets)}
|
|
341
|
-
${this.renderAngles()}
|
|
342
|
-
</svg>
|
|
414
|
+
${(['wheel', 'grid'] as const).map(
|
|
415
|
+
(t) => html`<button
|
|
416
|
+
class="tab"
|
|
417
|
+
role="tab"
|
|
418
|
+
id="tab-${t}"
|
|
419
|
+
aria-selected=${view === t ? 'true' : 'false'}
|
|
420
|
+
aria-controls="panel-${t}"
|
|
421
|
+
tabindex=${view === t ? '0' : '-1'}
|
|
422
|
+
@click=${() => {
|
|
423
|
+
this.view = t;
|
|
424
|
+
}}
|
|
425
|
+
>
|
|
426
|
+
${t === 'wheel' ? 'Wheel' : 'Aspect grid'}
|
|
427
|
+
</button>`,
|
|
428
|
+
)}
|
|
429
|
+
</div>
|
|
430
|
+
<div id="panel-${view}" role="tabpanel" aria-labelledby="tab-${view}">
|
|
431
|
+
${view === 'wheel' ? this.renderWheel(planets, aspects) : this.renderAspectGrid(planets, aspects)}
|
|
432
|
+
</div>
|
|
343
433
|
<div class="legend">
|
|
344
434
|
<span>${planets.length} planets</span>
|
|
345
435
|
<span>${aspects.length} aspects</span>
|
|
436
|
+
${
|
|
437
|
+
this.data.houseSystem
|
|
438
|
+
? html`<span>${this.data.houseSystem} houses</span>`
|
|
439
|
+
: nothing
|
|
440
|
+
}
|
|
346
441
|
<span><span class="legend-swatch" style="background: var(--roxy-success)"></span>harmonious</span>
|
|
347
442
|
<span><span class="legend-swatch" style="background: var(--roxy-danger)"></span>challenging</span>
|
|
348
443
|
</div>
|
|
@@ -351,11 +446,115 @@ export class RoxyNatalChart extends LitElement {
|
|
|
351
446
|
</div>`;
|
|
352
447
|
}
|
|
353
448
|
|
|
449
|
+
private onTabKeyDown(e: KeyboardEvent) {
|
|
450
|
+
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
|
|
451
|
+
e.preventDefault();
|
|
452
|
+
this.view = this.view === 'wheel' ? 'grid' : 'wheel';
|
|
453
|
+
const next = this.view;
|
|
454
|
+
requestAnimationFrame(() => {
|
|
455
|
+
this.shadowRoot
|
|
456
|
+
?.querySelector<HTMLButtonElement>(`#tab-${next}`)
|
|
457
|
+
?.focus();
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private renderWheel(planets: PlanetEntry[], aspects: AspectEntry[]) {
|
|
462
|
+
return html`<svg
|
|
463
|
+
viewBox="0 0 ${SIZE} ${SIZE}"
|
|
464
|
+
role="img"
|
|
465
|
+
aria-label="Natal chart wheel with twelve houses, planets, and aspects"
|
|
466
|
+
>
|
|
467
|
+
<title>Natal chart wheel</title>
|
|
468
|
+
<desc>
|
|
469
|
+
Twelve zodiac sign segments around a circular wheel. Planet glyphs are
|
|
470
|
+
placed at their ecliptic longitudes. Aspect lines connect related planets.
|
|
471
|
+
</desc>
|
|
472
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${OUTER_R} stroke-width="1.5" />
|
|
473
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${SIGN_R - 14} stroke-width="0.8" />
|
|
474
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${HOUSE_R} stroke-width="1" />
|
|
475
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${PLANET_R - 16} stroke-width="0.5" />
|
|
476
|
+
${this.renderTicks()} ${this.renderSpokes()} ${this.renderSigns()}
|
|
477
|
+
${this.renderHouseNumbers()} ${this.renderCuspDegrees()}
|
|
478
|
+
${this.renderAspects(planets, aspects)} ${this.renderPlanets(planets)}
|
|
479
|
+
${this.renderAngles()}
|
|
480
|
+
</svg>`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Planet-by-planet aspect grid: the lower-triangular matrix astrologers read
|
|
485
|
+
* alongside the wheel. Each filled cell shows the aspect glyph colored by
|
|
486
|
+
* nature, with the exact orb in the SVG-free `<title>` tooltip.
|
|
487
|
+
*/
|
|
488
|
+
private renderAspectGrid(planets: PlanetEntry[], aspects: AspectEntry[]) {
|
|
489
|
+
const names = planets.map((p) => capitalize(p.name));
|
|
490
|
+
// Lookup aspects by unordered planet pair.
|
|
491
|
+
const byPair = new Map<string, AspectEntry>();
|
|
492
|
+
for (const a of aspects) {
|
|
493
|
+
const k = [capitalize(a.planet1), capitalize(a.planet2)].sort().join('|');
|
|
494
|
+
byPair.set(k, a);
|
|
495
|
+
}
|
|
496
|
+
if (names.length === 0)
|
|
497
|
+
return html`<p class="roxy-empty" role="status">No planets to grid</p>`;
|
|
498
|
+
|
|
499
|
+
return html`<div class="grid-scroll">
|
|
500
|
+
<table class="aspect-grid" aria-label="Planet by planet aspect grid">
|
|
501
|
+
<thead>
|
|
502
|
+
<tr>
|
|
503
|
+
<th></th>
|
|
504
|
+
${names.slice(0, -1).map((n) => {
|
|
505
|
+
const g = PLANET_GLYPH[n] ?? n.slice(0, 2);
|
|
506
|
+
return html`<th scope="col" title=${n}>${g}</th>`;
|
|
507
|
+
})}
|
|
508
|
+
</tr>
|
|
509
|
+
</thead>
|
|
510
|
+
<tbody>
|
|
511
|
+
${names.slice(1).map((rowName, ri) => {
|
|
512
|
+
const rowGlyph = PLANET_GLYPH[rowName] ?? rowName.slice(0, 2);
|
|
513
|
+
// Row i (1-based) pairs with columns 0..i-1.
|
|
514
|
+
return html`<tr>
|
|
515
|
+
<th scope="row" title=${rowName}>${rowGlyph}</th>
|
|
516
|
+
${names.slice(0, ri + 1).map((colName) => {
|
|
517
|
+
const a = byPair.get([rowName, colName].sort().join('|'));
|
|
518
|
+
if (!a) return html`<td class="empty"></td>`;
|
|
519
|
+
const name = normalizeAspect(a);
|
|
520
|
+
const sym =
|
|
521
|
+
ASPECT_SYMBOL[name] ??
|
|
522
|
+
ASPECT_SYMBOL[name.replace(/-/g, '')] ??
|
|
523
|
+
name.slice(0, 3);
|
|
524
|
+
const cls = ASPECT_CLASS[name] ?? 'aspect-other';
|
|
525
|
+
const orb = formatNumber(a.orb, 1);
|
|
526
|
+
return html`<td class=${`cell ${cls}`} title=${`${rowName} ${name} ${colName}${orb ? ` (orb ${orb}°)` : ''}`}>
|
|
527
|
+
<span class="asp">${sym}</span>
|
|
528
|
+
</td>`;
|
|
529
|
+
})}
|
|
530
|
+
${names.slice(ri + 1, -1).map(() => html`<td class="empty"></td>`)}
|
|
531
|
+
</tr>`;
|
|
532
|
+
})}
|
|
533
|
+
</tbody>
|
|
534
|
+
</table>
|
|
535
|
+
</div>`;
|
|
536
|
+
}
|
|
537
|
+
|
|
354
538
|
private renderAngles() {
|
|
355
539
|
const asc = this.getAscendant();
|
|
356
540
|
const mc = this.getMidheaven();
|
|
357
|
-
|
|
358
|
-
|
|
541
|
+
// ASC/DESC and MC/IC are exact axes; DESC and IC are the opposite points.
|
|
542
|
+
const items = [
|
|
543
|
+
this.renderAngleMark(asc, 'ASC'),
|
|
544
|
+
this.renderAngleMark(oppositePoint(asc), 'DSC'),
|
|
545
|
+
];
|
|
546
|
+
if (mc !== null) {
|
|
547
|
+
items.push(this.renderAngleMark(mc, 'MC'));
|
|
548
|
+
items.push(this.renderAngleMark(oppositePoint(mc), 'IC'));
|
|
549
|
+
}
|
|
550
|
+
const pof = this.data?.partOfFortune?.longitude;
|
|
551
|
+
if (typeof pof === 'number') {
|
|
552
|
+
items.push(this.renderAngleMark(normalizeLongitude(pof), 'PoF'));
|
|
553
|
+
}
|
|
554
|
+
const vertex = this.data?.vertex?.longitude;
|
|
555
|
+
if (typeof vertex === 'number') {
|
|
556
|
+
items.push(this.renderAngleMark(normalizeLongitude(vertex), 'Vtx'));
|
|
557
|
+
}
|
|
359
558
|
return items;
|
|
360
559
|
}
|
|
361
560
|
|
|
@@ -373,8 +572,16 @@ export class RoxyNatalChart extends LitElement {
|
|
|
373
572
|
}
|
|
374
573
|
|
|
375
574
|
private renderSpokes() {
|
|
376
|
-
|
|
377
|
-
|
|
575
|
+
// Draw a spoke at each real house cusp longitude so Placidus / Koch
|
|
576
|
+
// unequal houses render correctly. Fall back to 12 equal spokes from the
|
|
577
|
+
// Ascendant only when the response carries no houses array.
|
|
578
|
+
const houses = this.data?.houses ?? [];
|
|
579
|
+
const cuspLongitudes =
|
|
580
|
+
houses.length === 12
|
|
581
|
+
? houses.map((h) => h.longitude)
|
|
582
|
+
: Array.from({ length: 12 }, (_, i) => this.getAscendant() + i * 30);
|
|
583
|
+
return cuspLongitudes.map((lon) => {
|
|
584
|
+
const angle = this.toAngle(lon);
|
|
378
585
|
const start = polarToCartesian(CENTER, CENTER, HOUSE_R, angle);
|
|
379
586
|
const end = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
|
|
380
587
|
return svg`<line class="wheel-line" x1=${start.x} y1=${start.y} x2=${end.x} y2=${end.y} stroke-width="0.8" />`;
|
|
@@ -390,6 +597,26 @@ export class RoxyNatalChart extends LitElement {
|
|
|
390
597
|
}
|
|
391
598
|
|
|
392
599
|
private renderHouseNumbers() {
|
|
600
|
+
const houses = this.data?.houses ?? [];
|
|
601
|
+
// Place each house number at the angular midpoint between its cusp and
|
|
602
|
+
// the next, so the label sits inside the house even when houses are
|
|
603
|
+
// unequal. Fall back to equal 30-degree sectors when houses are absent.
|
|
604
|
+
if (houses.length === 12) {
|
|
605
|
+
return houses.map((house, i) => {
|
|
606
|
+
const next = houses[(i + 1) % 12];
|
|
607
|
+
const mid = arcMidpoint(
|
|
608
|
+
house.longitude,
|
|
609
|
+
next ? next.longitude : house.longitude + 30,
|
|
610
|
+
);
|
|
611
|
+
const pos = polarToCartesian(
|
|
612
|
+
CENTER,
|
|
613
|
+
CENTER,
|
|
614
|
+
HOUSE_R - 12,
|
|
615
|
+
this.toAngle(mid),
|
|
616
|
+
);
|
|
617
|
+
return svg`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${house.number}</text>`;
|
|
618
|
+
});
|
|
619
|
+
}
|
|
393
620
|
const ascSignIndex = Math.floor(this.getAscendant() / 30);
|
|
394
621
|
return Array.from({ length: 12 }, (_, i) => {
|
|
395
622
|
const angle = this.toAngle(i * 30 + 15);
|
|
@@ -399,15 +626,55 @@ export class RoxyNatalChart extends LitElement {
|
|
|
399
626
|
});
|
|
400
627
|
}
|
|
401
628
|
|
|
629
|
+
/**
|
|
630
|
+
* Degree ticks on the outer zodiac band: a short mark every 5 degrees and a
|
|
631
|
+
* longer one on each 30-degree sign cusp, so the wheel reads like a
|
|
632
|
+
* reference-grade chart rather than a bare ring of glyphs.
|
|
633
|
+
*/
|
|
634
|
+
private renderTicks() {
|
|
635
|
+
const ticks = [];
|
|
636
|
+
for (let deg = 0; deg < 360; deg += 5) {
|
|
637
|
+
const angle = this.toAngle(deg);
|
|
638
|
+
const isMajor = deg % 30 === 0;
|
|
639
|
+
const inner = isMajor ? SIGN_R - 14 : OUTER_R - 5;
|
|
640
|
+
const a = polarToCartesian(CENTER, CENTER, inner, angle);
|
|
641
|
+
const b = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
|
|
642
|
+
ticks.push(
|
|
643
|
+
svg`<line class=${isMajor ? 'tick tick-major' : 'tick'} x1=${a.x} y1=${a.y} x2=${b.x} y2=${b.y} stroke-width=${isMajor ? 1 : 0.5} />`,
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
return ticks;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Degree-and-minute label printed next to each house cusp on the wheel, so
|
|
651
|
+
* the exact cusp position is readable without leaving the chart.
|
|
652
|
+
*/
|
|
653
|
+
private renderCuspDegrees() {
|
|
654
|
+
const houses = this.data?.houses ?? [];
|
|
655
|
+
if (houses.length !== 12) return nothing;
|
|
656
|
+
return houses.map((house) => {
|
|
657
|
+
const angle = this.toAngle(house.longitude);
|
|
658
|
+
const pos = polarToCartesian(CENTER, CENTER, HOUSE_R + 9, angle);
|
|
659
|
+
const sp = longitudeToSignPosition(house.longitude);
|
|
660
|
+
return svg`<text class="cusp-deg" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${sp.degree}°${String(sp.minute).padStart(2, '0')}'</text>`;
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
402
664
|
private renderPlanets(planets: PlanetEntry[]) {
|
|
403
665
|
return planets.map((p) => {
|
|
404
666
|
if (!Number.isFinite(p.longitude)) return nothing;
|
|
405
667
|
const angle = this.toAngle(p.longitude);
|
|
406
|
-
const
|
|
668
|
+
const glyphPos = polarToCartesian(CENTER, CENTER, PLANET_R, angle);
|
|
669
|
+
const degPos = polarToCartesian(CENTER, CENTER, PLANET_R - 13, angle);
|
|
407
670
|
const glyph = PLANET_GLYPH[capitalize(p.name)] ?? p.name.slice(0, 2);
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
|
|
671
|
+
const sp = longitudeToSignPosition(p.longitude);
|
|
672
|
+
const retro = p.isRetrograde === true;
|
|
673
|
+
const degLabel = `${sp.degree}°${String(sp.minute).padStart(2, '0')}'`;
|
|
674
|
+
return svg`<g>
|
|
675
|
+
<text class="planet-glyph" x=${glyphPos.x} y=${glyphPos.y} text-anchor="middle" dominant-baseline="central"><title>${p.name}${retro ? ' retrograde' : ''} - ${degLabel} ${p.sign ?? ''}</title>${glyph}</text>
|
|
676
|
+
<text class="planet-deg" x=${degPos.x} y=${degPos.y} text-anchor="middle" dominant-baseline="central">${degLabel}${retro ? svg`<tspan class="retro"> ℞</tspan>` : nothing}</text>
|
|
677
|
+
</g>`;
|
|
411
678
|
});
|
|
412
679
|
}
|
|
413
680
|
|
|
@@ -417,10 +684,6 @@ export class RoxyNatalChart extends LitElement {
|
|
|
417
684
|
if (!summary && !ai) return nothing;
|
|
418
685
|
|
|
419
686
|
const retrogrades = summary?.retrogradePlanets ?? [];
|
|
420
|
-
const elementDist = summary?.elementDistribution ?? {};
|
|
421
|
-
const modalityDist = summary?.modalityDistribution ?? {};
|
|
422
|
-
const elementMax = Math.max(1, ...Object.values(elementDist));
|
|
423
|
-
const modalityMax = Math.max(1, ...Object.values(modalityDist));
|
|
424
687
|
|
|
425
688
|
return html`<div class="details">
|
|
426
689
|
${
|
|
@@ -451,44 +714,70 @@ export class RoxyNatalChart extends LitElement {
|
|
|
451
714
|
: nothing
|
|
452
715
|
}
|
|
453
716
|
${ai?.summary ? html`<p class="summary">${ai.summary}</p>` : nothing}
|
|
454
|
-
${
|
|
455
|
-
Object.keys(elementDist).length > 0 ||
|
|
456
|
-
Object.keys(modalityDist).length > 0
|
|
457
|
-
? html`<div class="dist-grid">
|
|
458
|
-
${
|
|
459
|
-
Object.keys(elementDist).length > 0
|
|
460
|
-
? html`<div class="dist-section">
|
|
461
|
-
<h3>Elements</h3>
|
|
462
|
-
${Object.entries(elementDist).map(
|
|
463
|
-
([label, count]) => html`<div class="dist-row">
|
|
464
|
-
<span>${label}</span>
|
|
465
|
-
<div class="dist-bar"><span style="width: ${Math.round((count / elementMax) * 100)}%"></span></div>
|
|
466
|
-
<span>${count}</span>
|
|
467
|
-
</div>`,
|
|
468
|
-
)}
|
|
469
|
-
</div>`
|
|
470
|
-
: nothing
|
|
471
|
-
}
|
|
472
|
-
${
|
|
473
|
-
Object.keys(modalityDist).length > 0
|
|
474
|
-
? html`<div class="dist-section">
|
|
475
|
-
<h3>Modalities</h3>
|
|
476
|
-
${Object.entries(modalityDist).map(
|
|
477
|
-
([label, count]) => html`<div class="dist-row">
|
|
478
|
-
<span>${label}</span>
|
|
479
|
-
<div class="dist-bar"><span style="width: ${Math.round((count / modalityMax) * 100)}%"></span></div>
|
|
480
|
-
<span>${count}</span>
|
|
481
|
-
</div>`,
|
|
482
|
-
)}
|
|
483
|
-
</div>`
|
|
484
|
-
: nothing
|
|
485
|
-
}
|
|
486
|
-
</div>`
|
|
487
|
-
: nothing
|
|
488
|
-
}
|
|
717
|
+
${this.renderElementModalityGrid()}
|
|
489
718
|
</div>`;
|
|
490
719
|
}
|
|
491
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Element by modality grid: the 4x3 cross-tab astrologers read for chart
|
|
723
|
+
* balance. Each planet is placed by its sign into one cell (Fire/Earth/Air/
|
|
724
|
+
* Water row, Cardinal/Fixed/Mutable column). Derived purely from the planet
|
|
725
|
+
* signs, with row, column, and grand totals.
|
|
726
|
+
*/
|
|
727
|
+
private renderElementModalityGrid() {
|
|
728
|
+
const planets = this.getPlanets();
|
|
729
|
+
if (planets.length === 0) return nothing;
|
|
730
|
+
const ELEMENTS = ['Fire', 'Earth', 'Air', 'Water'] as const;
|
|
731
|
+
const MODALITIES = ['Cardinal', 'Fixed', 'Mutable'] as const;
|
|
732
|
+
const order = SIGNS_ORDER as readonly string[];
|
|
733
|
+
|
|
734
|
+
const cells: Record<string, Record<string, string[]>> = {};
|
|
735
|
+
for (const el of ELEMENTS)
|
|
736
|
+
cells[el] = { Cardinal: [], Fixed: [], Mutable: [] };
|
|
737
|
+
for (const p of planets) {
|
|
738
|
+
const idx = order.indexOf(capitalize(p.sign ?? ''));
|
|
739
|
+
if (idx < 0) continue;
|
|
740
|
+
const el = ELEMENTS[idx % 4];
|
|
741
|
+
const mod = MODALITIES[idx % 3];
|
|
742
|
+
const glyph =
|
|
743
|
+
PLANET_GLYPH[capitalize(p.name)] ?? capitalize(p.name).slice(0, 2);
|
|
744
|
+
cells[el]?.[mod]?.push(glyph);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return html`<table class="em-grid" aria-label="Element and modality distribution">
|
|
748
|
+
<thead>
|
|
749
|
+
<tr>
|
|
750
|
+
<th></th>
|
|
751
|
+
${MODALITIES.map((m) => html`<th scope="col">${m.slice(0, 3)}</th>`)}
|
|
752
|
+
<th scope="col">Total</th>
|
|
753
|
+
</tr>
|
|
754
|
+
</thead>
|
|
755
|
+
<tbody>
|
|
756
|
+
${ELEMENTS.map((el) => {
|
|
757
|
+
const rowTotal = MODALITIES.reduce(
|
|
758
|
+
(s, m) => s + (cells[el]?.[m]?.length ?? 0),
|
|
759
|
+
0,
|
|
760
|
+
);
|
|
761
|
+
return html`<tr>
|
|
762
|
+
<th scope="row">${el}</th>
|
|
763
|
+
${MODALITIES.map(
|
|
764
|
+
(m) => html`<td>${(cells[el]?.[m] ?? []).join(' ')}</td>`,
|
|
765
|
+
)}
|
|
766
|
+
<td class="em-total">${rowTotal}</td>
|
|
767
|
+
</tr>`;
|
|
768
|
+
})}
|
|
769
|
+
<tr>
|
|
770
|
+
<th scope="row">Total</th>
|
|
771
|
+
${MODALITIES.map(
|
|
772
|
+
(m) =>
|
|
773
|
+
html`<td class="em-total">${ELEMENTS.reduce((s, el) => s + (cells[el]?.[m]?.length ?? 0), 0)}</td>`,
|
|
774
|
+
)}
|
|
775
|
+
<td class="em-total">${planets.length}</td>
|
|
776
|
+
</tr>
|
|
777
|
+
</tbody>
|
|
778
|
+
</table>`;
|
|
779
|
+
}
|
|
780
|
+
|
|
492
781
|
private renderInterpretations() {
|
|
493
782
|
const planets = this.getPlanets().filter((p) => p.interpretation);
|
|
494
783
|
if (planets.length === 0) return nothing;
|
|
@@ -225,11 +225,7 @@ const LABELS: Record<string, string> = {
|
|
|
225
225
|
chart: 'Numerology chart',
|
|
226
226
|
};
|
|
227
227
|
|
|
228
|
-
type KarmicDebtMeaning =
|
|
229
|
-
description: string;
|
|
230
|
-
challenge: string;
|
|
231
|
-
resolution: string;
|
|
232
|
-
};
|
|
228
|
+
type KarmicDebtMeaning = CalculateLifePathResponse['karmicDebtMeaning'];
|
|
233
229
|
|
|
234
230
|
function karmicDebtText(value: KarmicDebtMeaning | undefined): string {
|
|
235
231
|
if (!value) return '';
|