@roxyapi/ui 0.2.2 → 0.3.0
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 +15 -10
- package/README.md +18 -13
- 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 +23 -18
- 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
|
@@ -11,7 +11,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
11
11
|
|
|
12
12
|
// packages/ui/src/components/natal-chart.ts
|
|
13
13
|
import { css as css2, html, LitElement, nothing, svg } from "lit";
|
|
14
|
-
import { customElement, property } from "lit/decorators.js";
|
|
14
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
15
15
|
|
|
16
16
|
// packages/ui/src/tokens/index.ts
|
|
17
17
|
var PLANET_GLYPH = {
|
|
@@ -69,6 +69,15 @@ var SIGNS_ORDER = [
|
|
|
69
69
|
var RASHI_KEYS = SIGNS_ORDER.map(
|
|
70
70
|
(s) => s.toLowerCase()
|
|
71
71
|
);
|
|
72
|
+
var ASPECT_SYMBOL = {
|
|
73
|
+
conjunction: "\u260C",
|
|
74
|
+
opposition: "\u260D",
|
|
75
|
+
trine: "\u25B3",
|
|
76
|
+
square: "\u25A1",
|
|
77
|
+
sextile: "\u2731",
|
|
78
|
+
quincunx: "\u22BB",
|
|
79
|
+
semisextile: "\u22BC"
|
|
80
|
+
};
|
|
72
81
|
|
|
73
82
|
// packages/ui/src/utils/base-styles.ts
|
|
74
83
|
import { css } from "lit";
|
|
@@ -157,6 +166,35 @@ var baseStyles = css`
|
|
|
157
166
|
`;
|
|
158
167
|
|
|
159
168
|
// packages/ui/src/utils/degree.ts
|
|
169
|
+
function normalizeLongitude(lon) {
|
|
170
|
+
const wrapped = lon % 360;
|
|
171
|
+
return wrapped < 0 ? wrapped + 360 : wrapped;
|
|
172
|
+
}
|
|
173
|
+
function longitudeToSignPosition(longitude) {
|
|
174
|
+
const lon = normalizeLongitude(longitude);
|
|
175
|
+
const signIndex = Math.floor(lon / 30) % 12;
|
|
176
|
+
const within = lon % 30;
|
|
177
|
+
const degree = Math.floor(within);
|
|
178
|
+
const minuteFloat = (within - degree) * 60;
|
|
179
|
+
const minute = Math.floor(minuteFloat);
|
|
180
|
+
const second = Math.round((minuteFloat - minute) * 60);
|
|
181
|
+
return {
|
|
182
|
+
sign: SIGNS_ORDER[signIndex] ?? "Aries",
|
|
183
|
+
signIndex,
|
|
184
|
+
degree,
|
|
185
|
+
minute,
|
|
186
|
+
second
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function oppositePoint(longitude) {
|
|
190
|
+
return normalizeLongitude(longitude + 180);
|
|
191
|
+
}
|
|
192
|
+
function arcMidpoint(start, end) {
|
|
193
|
+
const s = normalizeLongitude(start);
|
|
194
|
+
let span = normalizeLongitude(end) - s;
|
|
195
|
+
if (span < 0) span += 360;
|
|
196
|
+
return normalizeLongitude(s + span / 2);
|
|
197
|
+
}
|
|
160
198
|
function polarToCartesian(cx, cy, radius, angleDeg) {
|
|
161
199
|
const angleRad = angleDeg * Math.PI / 180;
|
|
162
200
|
return {
|
|
@@ -201,6 +239,7 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
201
239
|
super(...arguments);
|
|
202
240
|
this.data = null;
|
|
203
241
|
this.houseSystem = "placidus";
|
|
242
|
+
this.view = "wheel";
|
|
204
243
|
}
|
|
205
244
|
getPlanets() {
|
|
206
245
|
return this.data?.planets ?? [];
|
|
@@ -220,6 +259,7 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
220
259
|
return html`<div class="roxy-empty" role="status">No chart data</div>`;
|
|
221
260
|
const planets = this.getPlanets();
|
|
222
261
|
const aspects = this.data.aspects ?? [];
|
|
262
|
+
const view = this.view;
|
|
223
263
|
return html`<div class="wrap">
|
|
224
264
|
<header>
|
|
225
265
|
<h2 class="title">Natal chart</h2>
|
|
@@ -227,44 +267,35 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
227
267
|
${[this.data.birthDetails.date, this.data.birthDetails.time].filter(Boolean).join(" \xB7 ")}
|
|
228
268
|
</div>` : nothing}
|
|
229
269
|
</header>
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
role="
|
|
233
|
-
aria-label="Natal chart
|
|
270
|
+
<div
|
|
271
|
+
class="tablist"
|
|
272
|
+
role="tablist"
|
|
273
|
+
aria-label="Natal chart views"
|
|
274
|
+
@keydown=${this.onTabKeyDown}
|
|
234
275
|
>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<circle
|
|
255
|
-
class="wheel-line"
|
|
256
|
-
cx=${CENTER}
|
|
257
|
-
cy=${CENTER}
|
|
258
|
-
r=${PLANET_R - 16}
|
|
259
|
-
stroke-width="0.5"
|
|
260
|
-
/>
|
|
261
|
-
${this.renderSpokes()} ${this.renderSigns()} ${this.renderHouseNumbers()}
|
|
262
|
-
${this.renderAspects(planets, aspects)} ${this.renderPlanets(planets)}
|
|
263
|
-
${this.renderAngles()}
|
|
264
|
-
</svg>
|
|
276
|
+
${["wheel", "grid"].map(
|
|
277
|
+
(t) => html`<button
|
|
278
|
+
class="tab"
|
|
279
|
+
role="tab"
|
|
280
|
+
id="tab-${t}"
|
|
281
|
+
aria-selected=${view === t ? "true" : "false"}
|
|
282
|
+
aria-controls="panel-${t}"
|
|
283
|
+
tabindex=${view === t ? "0" : "-1"}
|
|
284
|
+
@click=${() => {
|
|
285
|
+
this.view = t;
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
${t === "wheel" ? "Wheel" : "Aspect grid"}
|
|
289
|
+
</button>`
|
|
290
|
+
)}
|
|
291
|
+
</div>
|
|
292
|
+
<div id="panel-${view}" role="tabpanel" aria-labelledby="tab-${view}">
|
|
293
|
+
${view === "wheel" ? this.renderWheel(planets, aspects) : this.renderAspectGrid(planets, aspects)}
|
|
294
|
+
</div>
|
|
265
295
|
<div class="legend">
|
|
266
296
|
<span>${planets.length} planets</span>
|
|
267
297
|
<span>${aspects.length} aspects</span>
|
|
298
|
+
${this.data.houseSystem ? html`<span>${this.data.houseSystem} houses</span>` : nothing}
|
|
268
299
|
<span><span class="legend-swatch" style="background: var(--roxy-success)"></span>harmonious</span>
|
|
269
300
|
<span><span class="legend-swatch" style="background: var(--roxy-danger)"></span>challenging</span>
|
|
270
301
|
</div>
|
|
@@ -272,11 +303,103 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
272
303
|
${this.renderInterpretations()}
|
|
273
304
|
</div>`;
|
|
274
305
|
}
|
|
306
|
+
onTabKeyDown(e) {
|
|
307
|
+
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
this.view = this.view === "wheel" ? "grid" : "wheel";
|
|
310
|
+
const next = this.view;
|
|
311
|
+
requestAnimationFrame(() => {
|
|
312
|
+
this.shadowRoot?.querySelector(`#tab-${next}`)?.focus();
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
renderWheel(planets, aspects) {
|
|
316
|
+
return html`<svg
|
|
317
|
+
viewBox="0 0 ${SIZE} ${SIZE}"
|
|
318
|
+
role="img"
|
|
319
|
+
aria-label="Natal chart wheel with twelve houses, planets, and aspects"
|
|
320
|
+
>
|
|
321
|
+
<title>Natal chart wheel</title>
|
|
322
|
+
<desc>
|
|
323
|
+
Twelve zodiac sign segments around a circular wheel. Planet glyphs are
|
|
324
|
+
placed at their ecliptic longitudes. Aspect lines connect related planets.
|
|
325
|
+
</desc>
|
|
326
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${OUTER_R} stroke-width="1.5" />
|
|
327
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${SIGN_R - 14} stroke-width="0.8" />
|
|
328
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${HOUSE_R} stroke-width="1" />
|
|
329
|
+
<circle class="wheel-line" cx=${CENTER} cy=${CENTER} r=${PLANET_R - 16} stroke-width="0.5" />
|
|
330
|
+
${this.renderTicks()} ${this.renderSpokes()} ${this.renderSigns()}
|
|
331
|
+
${this.renderHouseNumbers()} ${this.renderCuspDegrees()}
|
|
332
|
+
${this.renderAspects(planets, aspects)} ${this.renderPlanets(planets)}
|
|
333
|
+
${this.renderAngles()}
|
|
334
|
+
</svg>`;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Planet-by-planet aspect grid: the lower-triangular matrix astrologers read
|
|
338
|
+
* alongside the wheel. Each filled cell shows the aspect glyph colored by
|
|
339
|
+
* nature, with the exact orb in the SVG-free `<title>` tooltip.
|
|
340
|
+
*/
|
|
341
|
+
renderAspectGrid(planets, aspects) {
|
|
342
|
+
const names = planets.map((p) => capitalize(p.name));
|
|
343
|
+
const byPair = /* @__PURE__ */ new Map();
|
|
344
|
+
for (const a of aspects) {
|
|
345
|
+
const k = [capitalize(a.planet1), capitalize(a.planet2)].sort().join("|");
|
|
346
|
+
byPair.set(k, a);
|
|
347
|
+
}
|
|
348
|
+
if (names.length === 0)
|
|
349
|
+
return html`<p class="roxy-empty" role="status">No planets to grid</p>`;
|
|
350
|
+
return html`<div class="grid-scroll">
|
|
351
|
+
<table class="aspect-grid" aria-label="Planet by planet aspect grid">
|
|
352
|
+
<thead>
|
|
353
|
+
<tr>
|
|
354
|
+
<th></th>
|
|
355
|
+
${names.slice(0, -1).map((n) => {
|
|
356
|
+
const g = PLANET_GLYPH[n] ?? n.slice(0, 2);
|
|
357
|
+
return html`<th scope="col" title=${n}>${g}</th>`;
|
|
358
|
+
})}
|
|
359
|
+
</tr>
|
|
360
|
+
</thead>
|
|
361
|
+
<tbody>
|
|
362
|
+
${names.slice(1).map((rowName, ri) => {
|
|
363
|
+
const rowGlyph = PLANET_GLYPH[rowName] ?? rowName.slice(0, 2);
|
|
364
|
+
return html`<tr>
|
|
365
|
+
<th scope="row" title=${rowName}>${rowGlyph}</th>
|
|
366
|
+
${names.slice(0, ri + 1).map((colName) => {
|
|
367
|
+
const a = byPair.get([rowName, colName].sort().join("|"));
|
|
368
|
+
if (!a) return html`<td class="empty"></td>`;
|
|
369
|
+
const name = normalizeAspect(a);
|
|
370
|
+
const sym = ASPECT_SYMBOL[name] ?? ASPECT_SYMBOL[name.replace(/-/g, "")] ?? name.slice(0, 3);
|
|
371
|
+
const cls = ASPECT_CLASS[name] ?? "aspect-other";
|
|
372
|
+
const orb = formatNumber(a.orb, 1);
|
|
373
|
+
return html`<td class=${`cell ${cls}`} title=${`${rowName} ${name} ${colName}${orb ? ` (orb ${orb}\xB0)` : ""}`}>
|
|
374
|
+
<span class="asp">${sym}</span>
|
|
375
|
+
</td>`;
|
|
376
|
+
})}
|
|
377
|
+
${names.slice(ri + 1, -1).map(() => html`<td class="empty"></td>`)}
|
|
378
|
+
</tr>`;
|
|
379
|
+
})}
|
|
380
|
+
</tbody>
|
|
381
|
+
</table>
|
|
382
|
+
</div>`;
|
|
383
|
+
}
|
|
275
384
|
renderAngles() {
|
|
276
385
|
const asc = this.getAscendant();
|
|
277
386
|
const mc = this.getMidheaven();
|
|
278
|
-
const items = [
|
|
279
|
-
|
|
387
|
+
const items = [
|
|
388
|
+
this.renderAngleMark(asc, "ASC"),
|
|
389
|
+
this.renderAngleMark(oppositePoint(asc), "DSC")
|
|
390
|
+
];
|
|
391
|
+
if (mc !== null) {
|
|
392
|
+
items.push(this.renderAngleMark(mc, "MC"));
|
|
393
|
+
items.push(this.renderAngleMark(oppositePoint(mc), "IC"));
|
|
394
|
+
}
|
|
395
|
+
const pof = this.data?.partOfFortune?.longitude;
|
|
396
|
+
if (typeof pof === "number") {
|
|
397
|
+
items.push(this.renderAngleMark(normalizeLongitude(pof), "PoF"));
|
|
398
|
+
}
|
|
399
|
+
const vertex = this.data?.vertex?.longitude;
|
|
400
|
+
if (typeof vertex === "number") {
|
|
401
|
+
items.push(this.renderAngleMark(normalizeLongitude(vertex), "Vtx"));
|
|
402
|
+
}
|
|
280
403
|
return items;
|
|
281
404
|
}
|
|
282
405
|
renderAngleMark(longitude, label) {
|
|
@@ -292,8 +415,10 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
292
415
|
`;
|
|
293
416
|
}
|
|
294
417
|
renderSpokes() {
|
|
295
|
-
|
|
296
|
-
|
|
418
|
+
const houses = this.data?.houses ?? [];
|
|
419
|
+
const cuspLongitudes = houses.length === 12 ? houses.map((h) => h.longitude) : Array.from({ length: 12 }, (_, i) => this.getAscendant() + i * 30);
|
|
420
|
+
return cuspLongitudes.map((lon) => {
|
|
421
|
+
const angle = this.toAngle(lon);
|
|
297
422
|
const start = polarToCartesian(CENTER, CENTER, HOUSE_R, angle);
|
|
298
423
|
const end = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
|
|
299
424
|
return svg`<line class="wheel-line" x1=${start.x} y1=${start.y} x2=${end.x} y2=${end.y} stroke-width="0.8" />`;
|
|
@@ -307,6 +432,23 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
307
432
|
});
|
|
308
433
|
}
|
|
309
434
|
renderHouseNumbers() {
|
|
435
|
+
const houses = this.data?.houses ?? [];
|
|
436
|
+
if (houses.length === 12) {
|
|
437
|
+
return houses.map((house, i) => {
|
|
438
|
+
const next = houses[(i + 1) % 12];
|
|
439
|
+
const mid = arcMidpoint(
|
|
440
|
+
house.longitude,
|
|
441
|
+
next ? next.longitude : house.longitude + 30
|
|
442
|
+
);
|
|
443
|
+
const pos = polarToCartesian(
|
|
444
|
+
CENTER,
|
|
445
|
+
CENTER,
|
|
446
|
+
HOUSE_R - 12,
|
|
447
|
+
this.toAngle(mid)
|
|
448
|
+
);
|
|
449
|
+
return svg`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${house.number}</text>`;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
310
452
|
const ascSignIndex = Math.floor(this.getAscendant() / 30);
|
|
311
453
|
return Array.from({ length: 12 }, (_, i) => {
|
|
312
454
|
const angle = this.toAngle(i * 30 + 15);
|
|
@@ -315,15 +457,53 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
315
457
|
return svg`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${houseNum}</text>`;
|
|
316
458
|
});
|
|
317
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Degree ticks on the outer zodiac band: a short mark every 5 degrees and a
|
|
462
|
+
* longer one on each 30-degree sign cusp, so the wheel reads like a
|
|
463
|
+
* reference-grade chart rather than a bare ring of glyphs.
|
|
464
|
+
*/
|
|
465
|
+
renderTicks() {
|
|
466
|
+
const ticks = [];
|
|
467
|
+
for (let deg = 0; deg < 360; deg += 5) {
|
|
468
|
+
const angle = this.toAngle(deg);
|
|
469
|
+
const isMajor = deg % 30 === 0;
|
|
470
|
+
const inner = isMajor ? SIGN_R - 14 : OUTER_R - 5;
|
|
471
|
+
const a = polarToCartesian(CENTER, CENTER, inner, angle);
|
|
472
|
+
const b = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
|
|
473
|
+
ticks.push(
|
|
474
|
+
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} />`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return ticks;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Degree-and-minute label printed next to each house cusp on the wheel, so
|
|
481
|
+
* the exact cusp position is readable without leaving the chart.
|
|
482
|
+
*/
|
|
483
|
+
renderCuspDegrees() {
|
|
484
|
+
const houses = this.data?.houses ?? [];
|
|
485
|
+
if (houses.length !== 12) return nothing;
|
|
486
|
+
return houses.map((house) => {
|
|
487
|
+
const angle = this.toAngle(house.longitude);
|
|
488
|
+
const pos = polarToCartesian(CENTER, CENTER, HOUSE_R + 9, angle);
|
|
489
|
+
const sp = longitudeToSignPosition(house.longitude);
|
|
490
|
+
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>`;
|
|
491
|
+
});
|
|
492
|
+
}
|
|
318
493
|
renderPlanets(planets) {
|
|
319
494
|
return planets.map((p) => {
|
|
320
495
|
if (!Number.isFinite(p.longitude)) return nothing;
|
|
321
496
|
const angle = this.toAngle(p.longitude);
|
|
322
|
-
const
|
|
497
|
+
const glyphPos = polarToCartesian(CENTER, CENTER, PLANET_R, angle);
|
|
498
|
+
const degPos = polarToCartesian(CENTER, CENTER, PLANET_R - 13, angle);
|
|
323
499
|
const glyph = PLANET_GLYPH[capitalize(p.name)] ?? p.name.slice(0, 2);
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
|
|
500
|
+
const sp = longitudeToSignPosition(p.longitude);
|
|
501
|
+
const retro = p.isRetrograde === true;
|
|
502
|
+
const degLabel = `${sp.degree}\xB0${String(sp.minute).padStart(2, "0")}'`;
|
|
503
|
+
return svg`<g>
|
|
504
|
+
<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>
|
|
505
|
+
<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>
|
|
506
|
+
</g>`;
|
|
327
507
|
});
|
|
328
508
|
}
|
|
329
509
|
renderDetails() {
|
|
@@ -331,10 +511,6 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
331
511
|
const ai = this.data?.aspectsInterpretation;
|
|
332
512
|
if (!summary && !ai) return nothing;
|
|
333
513
|
const retrogrades = summary?.retrogradePlanets ?? [];
|
|
334
|
-
const elementDist = summary?.elementDistribution ?? {};
|
|
335
|
-
const modalityDist = summary?.modalityDistribution ?? {};
|
|
336
|
-
const elementMax = Math.max(1, ...Object.values(elementDist));
|
|
337
|
-
const modalityMax = Math.max(1, ...Object.values(modalityDist));
|
|
338
514
|
return html`<div class="details">
|
|
339
515
|
${summary?.dominantElement || summary?.dominantModality ? html`<div class="pill-row">
|
|
340
516
|
${summary.dominantElement ? html`<span class="pill">Dominant element: ${summary.dominantElement}</span>` : nothing}
|
|
@@ -352,30 +528,64 @@ var RoxyNatalChart = class extends LitElement {
|
|
|
352
528
|
})}
|
|
353
529
|
</div>` : nothing}
|
|
354
530
|
${ai?.summary ? html`<p class="summary">${ai.summary}</p>` : nothing}
|
|
355
|
-
${
|
|
356
|
-
${Object.keys(elementDist).length > 0 ? html`<div class="dist-section">
|
|
357
|
-
<h3>Elements</h3>
|
|
358
|
-
${Object.entries(elementDist).map(
|
|
359
|
-
([label, count]) => html`<div class="dist-row">
|
|
360
|
-
<span>${label}</span>
|
|
361
|
-
<div class="dist-bar"><span style="width: ${Math.round(count / elementMax * 100)}%"></span></div>
|
|
362
|
-
<span>${count}</span>
|
|
363
|
-
</div>`
|
|
364
|
-
)}
|
|
365
|
-
</div>` : nothing}
|
|
366
|
-
${Object.keys(modalityDist).length > 0 ? html`<div class="dist-section">
|
|
367
|
-
<h3>Modalities</h3>
|
|
368
|
-
${Object.entries(modalityDist).map(
|
|
369
|
-
([label, count]) => html`<div class="dist-row">
|
|
370
|
-
<span>${label}</span>
|
|
371
|
-
<div class="dist-bar"><span style="width: ${Math.round(count / modalityMax * 100)}%"></span></div>
|
|
372
|
-
<span>${count}</span>
|
|
373
|
-
</div>`
|
|
374
|
-
)}
|
|
375
|
-
</div>` : nothing}
|
|
376
|
-
</div>` : nothing}
|
|
531
|
+
${this.renderElementModalityGrid()}
|
|
377
532
|
</div>`;
|
|
378
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Element by modality grid: the 4x3 cross-tab astrologers read for chart
|
|
536
|
+
* balance. Each planet is placed by its sign into one cell (Fire/Earth/Air/
|
|
537
|
+
* Water row, Cardinal/Fixed/Mutable column). Derived purely from the planet
|
|
538
|
+
* signs, with row, column, and grand totals.
|
|
539
|
+
*/
|
|
540
|
+
renderElementModalityGrid() {
|
|
541
|
+
const planets = this.getPlanets();
|
|
542
|
+
if (planets.length === 0) return nothing;
|
|
543
|
+
const ELEMENTS = ["Fire", "Earth", "Air", "Water"];
|
|
544
|
+
const MODALITIES = ["Cardinal", "Fixed", "Mutable"];
|
|
545
|
+
const order = SIGNS_ORDER;
|
|
546
|
+
const cells = {};
|
|
547
|
+
for (const el of ELEMENTS)
|
|
548
|
+
cells[el] = { Cardinal: [], Fixed: [], Mutable: [] };
|
|
549
|
+
for (const p of planets) {
|
|
550
|
+
const idx = order.indexOf(capitalize(p.sign ?? ""));
|
|
551
|
+
if (idx < 0) continue;
|
|
552
|
+
const el = ELEMENTS[idx % 4];
|
|
553
|
+
const mod = MODALITIES[idx % 3];
|
|
554
|
+
const glyph = PLANET_GLYPH[capitalize(p.name)] ?? capitalize(p.name).slice(0, 2);
|
|
555
|
+
cells[el]?.[mod]?.push(glyph);
|
|
556
|
+
}
|
|
557
|
+
return html`<table class="em-grid" aria-label="Element and modality distribution">
|
|
558
|
+
<thead>
|
|
559
|
+
<tr>
|
|
560
|
+
<th></th>
|
|
561
|
+
${MODALITIES.map((m) => html`<th scope="col">${m.slice(0, 3)}</th>`)}
|
|
562
|
+
<th scope="col">Total</th>
|
|
563
|
+
</tr>
|
|
564
|
+
</thead>
|
|
565
|
+
<tbody>
|
|
566
|
+
${ELEMENTS.map((el) => {
|
|
567
|
+
const rowTotal = MODALITIES.reduce(
|
|
568
|
+
(s, m) => s + (cells[el]?.[m]?.length ?? 0),
|
|
569
|
+
0
|
|
570
|
+
);
|
|
571
|
+
return html`<tr>
|
|
572
|
+
<th scope="row">${el}</th>
|
|
573
|
+
${MODALITIES.map(
|
|
574
|
+
(m) => html`<td>${(cells[el]?.[m] ?? []).join(" ")}</td>`
|
|
575
|
+
)}
|
|
576
|
+
<td class="em-total">${rowTotal}</td>
|
|
577
|
+
</tr>`;
|
|
578
|
+
})}
|
|
579
|
+
<tr>
|
|
580
|
+
<th scope="row">Total</th>
|
|
581
|
+
${MODALITIES.map(
|
|
582
|
+
(m) => html`<td class="em-total">${ELEMENTS.reduce((s, el) => s + (cells[el]?.[m]?.length ?? 0), 0)}</td>`
|
|
583
|
+
)}
|
|
584
|
+
<td class="em-total">${planets.length}</td>
|
|
585
|
+
</tr>
|
|
586
|
+
</tbody>
|
|
587
|
+
</table>`;
|
|
588
|
+
}
|
|
379
589
|
renderInterpretations() {
|
|
380
590
|
const planets = this.getPlanets().filter((p) => p.interpretation);
|
|
381
591
|
if (planets.length === 0) return nothing;
|
|
@@ -473,12 +683,35 @@ RoxyNatalChart.styles = [
|
|
|
473
683
|
font-family: var(--roxy-font-sans);
|
|
474
684
|
}
|
|
475
685
|
|
|
686
|
+
.planet-deg {
|
|
687
|
+
fill: var(--roxy-fg, #0a0a0a);
|
|
688
|
+
font-size: 7px;
|
|
689
|
+
font-family: var(--roxy-font-sans);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.planet-deg .retro {
|
|
693
|
+
fill: var(--roxy-danger, #dc2626);
|
|
694
|
+
}
|
|
695
|
+
|
|
476
696
|
.house-num {
|
|
477
697
|
fill: var(--roxy-muted, #71717a);
|
|
478
698
|
font-size: 9px;
|
|
479
699
|
font-family: var(--roxy-font-sans);
|
|
480
700
|
}
|
|
481
701
|
|
|
702
|
+
.cusp-deg {
|
|
703
|
+
fill: var(--roxy-muted, #71717a);
|
|
704
|
+
font-size: 6px;
|
|
705
|
+
font-family: var(--roxy-font-sans);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.tick {
|
|
709
|
+
stroke: var(--roxy-border, #e4e4e7);
|
|
710
|
+
}
|
|
711
|
+
.tick-major {
|
|
712
|
+
stroke: var(--roxy-secondary, #475569);
|
|
713
|
+
}
|
|
714
|
+
|
|
482
715
|
.aspect {
|
|
483
716
|
stroke-width: 0.8;
|
|
484
717
|
fill: none;
|
|
@@ -528,6 +761,78 @@ RoxyNatalChart.styles = [
|
|
|
528
761
|
vertical-align: middle;
|
|
529
762
|
}
|
|
530
763
|
|
|
764
|
+
.tablist {
|
|
765
|
+
display: flex;
|
|
766
|
+
gap: 2px;
|
|
767
|
+
border-bottom: 2px solid var(--roxy-border, #e4e4e7);
|
|
768
|
+
}
|
|
769
|
+
.tab {
|
|
770
|
+
padding: var(--roxy-space-xs, 0.25rem) var(--roxy-space-md, 1rem);
|
|
771
|
+
font-size: var(--roxy-text-sm, 0.875rem);
|
|
772
|
+
background: none;
|
|
773
|
+
border: none;
|
|
774
|
+
border-bottom: 2px solid transparent;
|
|
775
|
+
margin-bottom: -2px;
|
|
776
|
+
cursor: pointer;
|
|
777
|
+
color: var(--roxy-muted, #71717a);
|
|
778
|
+
font-family: inherit;
|
|
779
|
+
transition: color var(--roxy-motion-duration, 200ms) var(--roxy-motion-easing, ease);
|
|
780
|
+
}
|
|
781
|
+
.tab[aria-selected='true'] {
|
|
782
|
+
color: var(--roxy-accent-fg, #b45309);
|
|
783
|
+
border-bottom-color: var(--roxy-accent, #f59e0b);
|
|
784
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
785
|
+
}
|
|
786
|
+
.tab:hover:not([aria-selected='true']) {
|
|
787
|
+
color: var(--roxy-fg, #0a0a0a);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.grid-scroll {
|
|
791
|
+
overflow-x: auto;
|
|
792
|
+
-webkit-overflow-scrolling: touch;
|
|
793
|
+
}
|
|
794
|
+
table.aspect-grid {
|
|
795
|
+
border-collapse: collapse;
|
|
796
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
797
|
+
margin: 0 auto;
|
|
798
|
+
}
|
|
799
|
+
table.aspect-grid th,
|
|
800
|
+
table.aspect-grid td {
|
|
801
|
+
width: 1.6rem;
|
|
802
|
+
height: 1.6rem;
|
|
803
|
+
text-align: center;
|
|
804
|
+
border: 1px solid var(--roxy-border, #e4e4e7);
|
|
805
|
+
padding: 0;
|
|
806
|
+
}
|
|
807
|
+
table.aspect-grid th {
|
|
808
|
+
color: var(--roxy-secondary, #475569);
|
|
809
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
810
|
+
}
|
|
811
|
+
table.aspect-grid td.cell {
|
|
812
|
+
cursor: default;
|
|
813
|
+
}
|
|
814
|
+
table.aspect-grid td.empty {
|
|
815
|
+
background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 18%, transparent);
|
|
816
|
+
}
|
|
817
|
+
table.aspect-grid td .asp {
|
|
818
|
+
font-size: 0.95em;
|
|
819
|
+
line-height: 1;
|
|
820
|
+
}
|
|
821
|
+
table.aspect-grid td.aspect-trine .asp,
|
|
822
|
+
table.aspect-grid td.aspect-sextile .asp {
|
|
823
|
+
color: var(--roxy-success, #16a34a);
|
|
824
|
+
}
|
|
825
|
+
table.aspect-grid td.aspect-square .asp,
|
|
826
|
+
table.aspect-grid td.aspect-opposition .asp {
|
|
827
|
+
color: var(--roxy-danger, #dc2626);
|
|
828
|
+
}
|
|
829
|
+
table.aspect-grid td.aspect-conjunction .asp {
|
|
830
|
+
color: var(--roxy-accent-fg, #b45309);
|
|
831
|
+
}
|
|
832
|
+
table.aspect-grid td.aspect-other .asp {
|
|
833
|
+
color: var(--roxy-muted, #71717a);
|
|
834
|
+
}
|
|
835
|
+
|
|
531
836
|
.details {
|
|
532
837
|
margin-top: var(--roxy-space-md, 1rem);
|
|
533
838
|
}
|
|
@@ -568,48 +873,37 @@ RoxyNatalChart.styles = [
|
|
|
568
873
|
margin: var(--roxy-space-md, 1rem) 0;
|
|
569
874
|
}
|
|
570
875
|
|
|
571
|
-
.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
876
|
+
.em-grid {
|
|
877
|
+
border-collapse: collapse;
|
|
878
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
879
|
+
width: 100%;
|
|
575
880
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
881
|
+
.em-grid th,
|
|
882
|
+
.em-grid td {
|
|
883
|
+
border: 1px solid var(--roxy-border, #e4e4e7);
|
|
884
|
+
padding: 3px 5px;
|
|
885
|
+
text-align: center;
|
|
886
|
+
vertical-align: middle;
|
|
581
887
|
}
|
|
582
|
-
|
|
583
|
-
.dist-section h3 {
|
|
584
|
-
font-size: var(--roxy-text-xs, 0.75rem);
|
|
585
|
-
font-weight: var(--roxy-weight-bold, 600);
|
|
888
|
+
.em-grid th {
|
|
586
889
|
color: var(--roxy-muted, #71717a);
|
|
587
|
-
|
|
890
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
588
891
|
text-transform: uppercase;
|
|
589
|
-
letter-spacing: 0.
|
|
892
|
+
letter-spacing: 0.04em;
|
|
590
893
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
display: grid;
|
|
594
|
-
grid-template-columns: 4rem 1fr 1.5rem;
|
|
595
|
-
align-items: center;
|
|
596
|
-
gap: var(--roxy-space-xs, 0.25rem);
|
|
597
|
-
font-size: var(--roxy-text-xs, 0.75rem);
|
|
598
|
-
color: var(--roxy-fg, #0f172a);
|
|
599
|
-
margin-bottom: 4px;
|
|
894
|
+
.em-grid th[scope='row'] {
|
|
895
|
+
text-align: left;
|
|
600
896
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
height:
|
|
605
|
-
|
|
897
|
+
.em-grid td {
|
|
898
|
+
color: var(--roxy-accent, #f59e0b);
|
|
899
|
+
font-size: 0.95em;
|
|
900
|
+
line-height: 1.4;
|
|
901
|
+
min-width: 1.4rem;
|
|
606
902
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
background: var(--roxy-accent, #f59e0b);
|
|
612
|
-
border-radius: 3px;
|
|
903
|
+
.em-grid .em-total {
|
|
904
|
+
color: var(--roxy-fg, #0a0a0a);
|
|
905
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
906
|
+
background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 25%, transparent);
|
|
613
907
|
}
|
|
614
908
|
|
|
615
909
|
.interpretations {
|
|
@@ -665,6 +959,9 @@ __decorateClass([
|
|
|
665
959
|
__decorateClass([
|
|
666
960
|
property({ type: String, attribute: "house-system", reflect: true })
|
|
667
961
|
], RoxyNatalChart.prototype, "houseSystem", 2);
|
|
962
|
+
__decorateClass([
|
|
963
|
+
state()
|
|
964
|
+
], RoxyNatalChart.prototype, "view", 2);
|
|
668
965
|
RoxyNatalChart = __decorateClass([
|
|
669
966
|
customElement("roxy-natal-chart")
|
|
670
967
|
], RoxyNatalChart);
|