@roxyapi/ui 0.1.1 → 0.1.3

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.
Files changed (169) hide show
  1. package/AGENTS.md +2 -2
  2. package/LICENSE +21 -0
  3. package/README.md +505 -0
  4. package/THEMING.md +24 -7
  5. package/dist/cdn/components/biorhythm-chart.js +15 -22
  6. package/dist/cdn/components/biorhythm-chart.js.map +3 -3
  7. package/dist/cdn/components/compatibility-card.js +36 -34
  8. package/dist/cdn/components/compatibility-card.js.map +4 -4
  9. package/dist/cdn/components/dasha-timeline.js +35 -39
  10. package/dist/cdn/components/dasha-timeline.js.map +4 -4
  11. package/dist/cdn/components/data.js +6 -6
  12. package/dist/cdn/components/data.js.map +3 -3
  13. package/dist/cdn/components/dosha-card.js +13 -13
  14. package/dist/cdn/components/dosha-card.js.map +2 -2
  15. package/dist/cdn/components/endpoint-form.js +47 -28
  16. package/dist/cdn/components/endpoint-form.js.map +3 -3
  17. package/dist/cdn/components/guna-milan.js +18 -18
  18. package/dist/cdn/components/guna-milan.js.map +4 -4
  19. package/dist/cdn/components/hexagram.js +26 -26
  20. package/dist/cdn/components/hexagram.js.map +3 -3
  21. package/dist/cdn/components/horoscope-card.js +38 -38
  22. package/dist/cdn/components/horoscope-card.js.map +3 -3
  23. package/dist/cdn/components/kp-planets-table.js +10 -10
  24. package/dist/cdn/components/kp-planets-table.js.map +4 -4
  25. package/dist/cdn/components/location-search.js +6 -6
  26. package/dist/cdn/components/location-search.js.map +3 -3
  27. package/dist/cdn/components/moon-phase.js +21 -21
  28. package/dist/cdn/components/moon-phase.js.map +4 -4
  29. package/dist/cdn/components/natal-chart.js +61 -19
  30. package/dist/cdn/components/natal-chart.js.map +4 -4
  31. package/dist/cdn/components/numerology-card.js +40 -31
  32. package/dist/cdn/components/numerology-card.js.map +3 -3
  33. package/dist/cdn/components/panchang-table.js +25 -25
  34. package/dist/cdn/components/panchang-table.js.map +4 -4
  35. package/dist/cdn/components/synastry-chart.js +129 -39
  36. package/dist/cdn/components/synastry-chart.js.map +4 -4
  37. package/dist/cdn/components/tarot-card.js +49 -20
  38. package/dist/cdn/components/tarot-card.js.map +3 -3
  39. package/dist/cdn/components/tarot-spread.js +43 -27
  40. package/dist/cdn/components/tarot-spread.js.map +3 -3
  41. package/dist/cdn/components/vedic-kundli.js +23 -9
  42. package/dist/cdn/components/vedic-kundli.js.map +3 -3
  43. package/dist/cdn/roxy-ui.js +560 -350
  44. package/dist/cdn/roxy-ui.js.map +4 -4
  45. package/dist/components/biorhythm-chart.d.ts +2 -46
  46. package/dist/components/biorhythm-chart.d.ts.map +1 -1
  47. package/dist/components/biorhythm-chart.js +24 -23
  48. package/dist/components/biorhythm-chart.js.map +2 -2
  49. package/dist/components/compatibility-card.d.ts +2 -27
  50. package/dist/components/compatibility-card.d.ts.map +1 -1
  51. package/dist/components/compatibility-card.js +50 -29
  52. package/dist/components/compatibility-card.js.map +3 -3
  53. package/dist/components/dasha-timeline.d.ts +2 -31
  54. package/dist/components/dasha-timeline.d.ts.map +1 -1
  55. package/dist/components/dasha-timeline.js +32 -30
  56. package/dist/components/dasha-timeline.js.map +3 -3
  57. package/dist/components/data.d.ts +6 -0
  58. package/dist/components/data.d.ts.map +1 -1
  59. package/dist/components/data.js +9 -1
  60. package/dist/components/data.js.map +2 -2
  61. package/dist/components/dosha-card.d.ts +2 -16
  62. package/dist/components/dosha-card.d.ts.map +1 -1
  63. package/dist/components/dosha-card.js +12 -13
  64. package/dist/components/dosha-card.js.map +2 -2
  65. package/dist/components/endpoint-form.d.ts +2 -0
  66. package/dist/components/endpoint-form.d.ts.map +1 -1
  67. package/dist/components/endpoint-form.js +66 -8
  68. package/dist/components/endpoint-form.js.map +2 -2
  69. package/dist/components/guna-milan.d.ts +2 -20
  70. package/dist/components/guna-milan.d.ts.map +1 -1
  71. package/dist/components/guna-milan.js +22 -12
  72. package/dist/components/guna-milan.js.map +3 -3
  73. package/dist/components/hexagram.d.ts +3 -27
  74. package/dist/components/hexagram.d.ts.map +1 -1
  75. package/dist/components/hexagram.js +31 -15
  76. package/dist/components/hexagram.js.map +2 -2
  77. package/dist/components/horoscope-card.d.ts +2 -20
  78. package/dist/components/horoscope-card.d.ts.map +1 -1
  79. package/dist/components/horoscope-card.js +24 -15
  80. package/dist/components/horoscope-card.js.map +2 -2
  81. package/dist/components/kp-planets-table.d.ts +2 -21
  82. package/dist/components/kp-planets-table.d.ts.map +1 -1
  83. package/dist/components/kp-planets-table.js +10 -4
  84. package/dist/components/kp-planets-table.js.map +3 -3
  85. package/dist/components/location-search.d.ts +3 -11
  86. package/dist/components/location-search.d.ts.map +1 -1
  87. package/dist/components/location-search.js +45 -5
  88. package/dist/components/location-search.js.map +2 -2
  89. package/dist/components/moon-phase.d.ts +4 -21
  90. package/dist/components/moon-phase.d.ts.map +1 -1
  91. package/dist/components/moon-phase.js +17 -4
  92. package/dist/components/moon-phase.js.map +3 -3
  93. package/dist/components/natal-chart.d.ts +7 -43
  94. package/dist/components/natal-chart.d.ts.map +1 -1
  95. package/dist/components/natal-chart.js +130 -70
  96. package/dist/components/natal-chart.js.map +3 -3
  97. package/dist/components/numerology-card.d.ts +5 -37
  98. package/dist/components/numerology-card.d.ts.map +1 -1
  99. package/dist/components/numerology-card.js +54 -28
  100. package/dist/components/numerology-card.js.map +2 -2
  101. package/dist/components/panchang-table.d.ts +3 -62
  102. package/dist/components/panchang-table.d.ts.map +1 -1
  103. package/dist/components/panchang-table.js +62 -32
  104. package/dist/components/panchang-table.js.map +3 -3
  105. package/dist/components/synastry-chart.d.ts +9 -28
  106. package/dist/components/synastry-chart.d.ts.map +1 -1
  107. package/dist/components/synastry-chart.js +178 -38
  108. package/dist/components/synastry-chart.js.map +3 -3
  109. package/dist/components/tarot-card.d.ts +5 -29
  110. package/dist/components/tarot-card.d.ts.map +1 -1
  111. package/dist/components/tarot-card.js +59 -20
  112. package/dist/components/tarot-card.js.map +2 -2
  113. package/dist/components/tarot-spread.d.ts +2 -24
  114. package/dist/components/tarot-spread.d.ts.map +1 -1
  115. package/dist/components/tarot-spread.js +39 -13
  116. package/dist/components/tarot-spread.js.map +2 -2
  117. package/dist/components/vedic-kundli.d.ts +3 -23
  118. package/dist/components/vedic-kundli.d.ts.map +1 -1
  119. package/dist/components/vedic-kundli.js +25 -13
  120. package/dist/components/vedic-kundli.js.map +2 -2
  121. package/dist/index.cjs +1149 -358
  122. package/dist/index.cjs.map +4 -4
  123. package/dist/index.d.ts +6 -4
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +1149 -358
  126. package/dist/index.js.map +4 -4
  127. package/dist/manifest.d.ts +49 -0
  128. package/dist/manifest.d.ts.map +1 -0
  129. package/dist/manifest.json +1 -1
  130. package/dist/styles/tokens.css +47 -1
  131. package/dist/tokens/index.d.ts.map +1 -1
  132. package/dist/types/index.d.ts +2 -0
  133. package/dist/types/index.d.ts.map +1 -0
  134. package/dist/types/types.gen.d.ts +27811 -0
  135. package/dist/types/types.gen.d.ts.map +1 -0
  136. package/dist/utils/debounce.d.ts +9 -1
  137. package/dist/utils/debounce.d.ts.map +1 -1
  138. package/dist/utils/format.d.ts +15 -0
  139. package/dist/utils/format.d.ts.map +1 -0
  140. package/dist/version.d.ts +2 -0
  141. package/dist/version.d.ts.map +1 -0
  142. package/package.json +9 -1
  143. package/src/components/biorhythm-chart.ts +39 -84
  144. package/src/components/compatibility-card.ts +85 -52
  145. package/src/components/dasha-timeline.ts +55 -73
  146. package/src/components/data.ts +20 -1
  147. package/src/components/dosha-card.ts +18 -31
  148. package/src/components/endpoint-form.ts +79 -11
  149. package/src/components/guna-milan.ts +16 -34
  150. package/src/components/hexagram.ts +53 -43
  151. package/src/components/horoscope-card.ts +51 -39
  152. package/src/components/kp-planets-table.ts +8 -27
  153. package/src/components/location-search.ts +45 -20
  154. package/src/components/moon-phase.ts +28 -25
  155. package/src/components/natal-chart.ts +129 -84
  156. package/src/components/numerology-card.ts +87 -79
  157. package/src/components/panchang-table.ts +40 -78
  158. package/src/components/synastry-chart.ts +220 -78
  159. package/src/components/tarot-card.ts +76 -62
  160. package/src/components/tarot-spread.ts +72 -45
  161. package/src/components/vedic-kundli.ts +42 -51
  162. package/src/index.ts +14 -24
  163. package/src/manifest.ts +366 -0
  164. package/src/styles/tokens.css +47 -1
  165. package/src/tokens/index.ts +5 -0
  166. package/src/types/types.gen.ts +1 -1
  167. package/src/utils/debounce.ts +23 -4
  168. package/src/utils/format.ts +57 -0
  169. package/src/version.ts +2 -0
@@ -1,53 +1,22 @@
1
1
  import { css, html, LitElement, nothing, svg } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import { PLANET_GLYPH, SIGN_GLYPH } from '../tokens/index.js';
4
+ import type { NatalChartResponse } from '../types/index.js';
4
5
  import { baseStyles } from '../utils/base-styles.js';
5
6
  import { longitudeToSignPosition, polarToCartesian } from '../utils/degree.js';
7
+ import { formatNumber } from '../utils/format.js';
6
8
 
7
- interface PlanetEntry {
8
- name?: string;
9
- planet?: string;
10
- longitude?: number;
11
- degree?: number;
12
- sign?: string;
13
- house?: number;
14
- retrograde?: boolean;
15
- isRetrograde?: boolean;
16
- }
17
-
18
- interface AspectEntry {
19
- planet1?: string;
20
- planet2?: string;
21
- aspect?: string;
22
- orb?: number;
23
- }
24
-
25
- interface HouseEntry {
26
- house?: number;
27
- number?: number;
28
- cusp?: number;
29
- sign?: string;
30
- }
31
-
32
- interface NatalChartData {
33
- planets?: PlanetEntry[] | Record<string, PlanetEntry>;
34
- houses?: HouseEntry[];
35
- aspects?: AspectEntry[];
36
- ascendant?: number | { longitude?: number; sign?: string };
37
- midheaven?: number | { longitude?: number; sign?: string };
38
- birthDetails?: {
39
- date?: string;
40
- time?: string;
41
- location?: string;
42
- };
43
- }
9
+ type PlanetEntry = NatalChartResponse['planets'][number];
10
+ type AspectEntry = NatalChartResponse['aspects'][number];
44
11
 
45
- const SIZE = 320;
12
+ const SIZE = 384;
46
13
  const CENTER = SIZE / 2;
47
14
  const OUTER_R = 150;
48
15
  const SIGN_R = 134;
49
16
  const HOUSE_R = 110;
50
17
  const PLANET_R = 88;
18
+ const ANGLE_TICK_R = 162;
19
+ const ANGLE_LABEL_R = 176;
51
20
 
52
21
  /**
53
22
  * Western natal chart wheel. Renders the 12 zodiac signs, 12 houses, planet
@@ -109,9 +78,36 @@ export class RoxyNatalChart extends LitElement {
109
78
  }
110
79
 
111
80
  .aspect {
112
- stroke: color-mix(in srgb, var(--roxy-accent, #f59e0b) 32%, transparent);
113
- stroke-width: 0.6;
81
+ stroke-width: 0.8;
114
82
  fill: none;
83
+ opacity: 0.55;
84
+ }
85
+ .aspect-trine,
86
+ .aspect-sextile {
87
+ stroke: var(--roxy-success, #16a34a);
88
+ }
89
+ .aspect-square,
90
+ .aspect-opposition {
91
+ stroke: var(--roxy-danger, #dc2626);
92
+ }
93
+ .aspect-conjunction {
94
+ stroke: var(--roxy-accent-fg, #b45309);
95
+ }
96
+ .aspect-other {
97
+ stroke: var(--roxy-muted, #71717a);
98
+ opacity: 0.4;
99
+ }
100
+
101
+ .angle-marker {
102
+ fill: var(--roxy-accent-fg, #b45309);
103
+ font-size: 10px;
104
+ font-weight: 700;
105
+ font-family: var(--roxy-font-sans);
106
+ letter-spacing: 0.04em;
107
+ }
108
+ .angle-tick {
109
+ stroke: var(--roxy-accent-fg, #b45309);
110
+ stroke-width: 1.5;
115
111
  }
116
112
 
117
113
  .legend {
@@ -121,20 +117,38 @@ export class RoxyNatalChart extends LitElement {
121
117
  flex-wrap: wrap;
122
118
  gap: var(--roxy-space-md, 1rem);
123
119
  }
120
+ .legend-swatch {
121
+ display: inline-block;
122
+ width: 8px;
123
+ height: 8px;
124
+ border-radius: 50%;
125
+ margin-right: 4px;
126
+ vertical-align: middle;
127
+ }
124
128
  `,
125
129
  ];
126
130
 
127
131
  @property({ attribute: false })
128
- data: NatalChartData | null = null;
132
+ data: NatalChartResponse | null = null;
129
133
 
130
134
  @property({ type: String, attribute: 'house-system', reflect: true })
131
135
  houseSystem: 'placidus' | 'whole-sign' | 'equal' | 'koch' = 'placidus';
132
136
 
133
137
  private getPlanets(): PlanetEntry[] {
134
- const p = this.data?.planets;
135
- if (!p) return [];
136
- if (Array.isArray(p)) return p;
137
- return Object.entries(p).map(([name, entry]) => ({ ...entry, name }));
138
+ return this.data?.planets ?? [];
139
+ }
140
+
141
+ private getAscendant(): number {
142
+ return this.data?.ascendant?.longitude ?? 0;
143
+ }
144
+
145
+ private getMidheaven(): number | null {
146
+ const m = this.data?.midheaven?.longitude;
147
+ return typeof m === 'number' ? m : null;
148
+ }
149
+
150
+ private toAngle(lon: number): number {
151
+ return 180 + this.getAscendant() - lon;
138
152
  }
139
153
 
140
154
  render() {
@@ -149,11 +163,7 @@ export class RoxyNatalChart extends LitElement {
149
163
  ${
150
164
  this.data.birthDetails
151
165
  ? html`<div class="meta">
152
- ${[
153
- this.data.birthDetails.date,
154
- this.data.birthDetails.time,
155
- this.data.birthDetails.location,
156
- ]
166
+ ${[this.data.birthDetails.date, this.data.birthDetails.time]
157
167
  .filter(Boolean)
158
168
  .join(' · ')}
159
169
  </div>`
@@ -193,18 +203,41 @@ export class RoxyNatalChart extends LitElement {
193
203
  />
194
204
  ${this.renderSpokes()} ${this.renderSigns()} ${this.renderHouseNumbers()}
195
205
  ${this.renderAspects(planets, aspects)} ${this.renderPlanets(planets)}
206
+ ${this.renderAngles()}
196
207
  </svg>
197
208
  <div class="legend">
198
209
  <span>${planets.length} planets</span>
199
210
  <span>${aspects.length} aspects</span>
200
- <span>House system: ${this.houseSystem}</span>
211
+ <span><span class="legend-swatch" style="background: var(--roxy-success)"></span>harmonious</span>
212
+ <span><span class="legend-swatch" style="background: var(--roxy-danger)"></span>challenging</span>
201
213
  </div>
202
214
  </div>`;
203
215
  }
204
216
 
217
+ private renderAngles() {
218
+ const asc = this.getAscendant();
219
+ const mc = this.getMidheaven();
220
+ const items = [this.renderAngleMark(asc, 'ASC')];
221
+ if (mc !== null) items.push(this.renderAngleMark(mc, 'MC'));
222
+ return items;
223
+ }
224
+
225
+ private renderAngleMark(longitude: number, label: string) {
226
+ const angle = this.toAngle(longitude);
227
+ const tickInner = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
228
+ const tickOuter = polarToCartesian(CENTER, CENTER, ANGLE_TICK_R, angle);
229
+ const labelPos = polarToCartesian(CENTER, CENTER, ANGLE_LABEL_R, angle);
230
+ return svg`
231
+ <g>
232
+ <line class="angle-tick" x1=${tickInner.x} y1=${tickInner.y} x2=${tickOuter.x} y2=${tickOuter.y} />
233
+ <text class="angle-marker" x=${labelPos.x} y=${labelPos.y} text-anchor="middle" dominant-baseline="central">${label}</text>
234
+ </g>
235
+ `;
236
+ }
237
+
205
238
  private renderSpokes() {
206
239
  return Array.from({ length: 12 }, (_, i) => {
207
- const angle = i * 30 - 90;
240
+ const angle = this.toAngle(i * 30);
208
241
  const start = polarToCartesian(CENTER, CENTER, HOUSE_R, angle);
209
242
  const end = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
210
243
  return svg`<line class="wheel-line" x1=${start.x} y1=${start.y} x2=${end.x} y2=${end.y} stroke-width="0.8" />`;
@@ -227,58 +260,61 @@ export class RoxyNatalChart extends LitElement {
227
260
  'Pisces',
228
261
  ];
229
262
  return order.map((sign, i) => {
230
- const angle = i * 30 + 15 - 90;
263
+ const angle = this.toAngle(i * 30 + 15);
231
264
  const pos = polarToCartesian(CENTER, CENTER, SIGN_R, angle);
232
265
  return svg`<text class="sign-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${SIGN_GLYPH[sign]}</text>`;
233
266
  });
234
267
  }
235
268
 
236
269
  private renderHouseNumbers() {
270
+ const ascSignIndex = Math.floor(this.getAscendant() / 30);
237
271
  return Array.from({ length: 12 }, (_, i) => {
238
- const angle = i * 30 + 15 - 90;
272
+ const angle = this.toAngle(i * 30 + 15);
239
273
  const pos = polarToCartesian(CENTER, CENTER, HOUSE_R - 12, angle);
240
- return svg`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${i + 1}</text>`;
274
+ const houseNum = ((i - ascSignIndex + 12) % 12) + 1;
275
+ return svg`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${houseNum}</text>`;
241
276
  });
242
277
  }
243
278
 
244
279
  private renderPlanets(planets: PlanetEntry[]) {
245
280
  return planets.map((p) => {
246
- const lon =
247
- typeof p.longitude === 'number'
248
- ? p.longitude
249
- : typeof p.degree === 'number'
250
- ? p.degree
251
- : NaN;
252
- if (!Number.isFinite(lon)) return nothing;
253
- const angle = lon - 90;
281
+ if (!Number.isFinite(p.longitude)) return nothing;
282
+ const angle = this.toAngle(p.longitude);
254
283
  const pos = polarToCartesian(CENTER, CENTER, PLANET_R, angle);
255
- const name = p.name ?? p.planet ?? '';
256
- const glyph = PLANET_GLYPH[capitalize(name)] ?? name.slice(0, 2);
257
- const retro = p.retrograde || p.isRetrograde ? ' R' : '';
258
- return svg`<text class="planet-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central"><title>${name}${retro}</title>${glyph}</text>`;
284
+ const glyph = PLANET_GLYPH[capitalize(p.name)] ?? p.name.slice(0, 2);
285
+ const retro = p.isRetrograde ? ' R' : '';
286
+ const display = retro ? `${glyph}ᴿ` : glyph;
287
+ return svg`<text class="planet-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central"><title>${p.name}${retro}</title>${display}</text>`;
259
288
  });
260
289
  }
261
290
 
262
291
  private renderAspects(planets: PlanetEntry[], aspects: AspectEntry[]) {
263
292
  const planetMap = new Map<string, number>();
264
293
  for (const p of planets) {
265
- const lon =
266
- typeof p.longitude === 'number'
267
- ? p.longitude
268
- : typeof p.degree === 'number'
269
- ? p.degree
270
- : null;
271
- if (lon === null) continue;
272
- const name = capitalize(p.name ?? p.planet ?? '');
273
- if (name) planetMap.set(name, lon);
294
+ if (typeof p.longitude !== 'number') continue;
295
+ const name = capitalize(p.name);
296
+ if (name) planetMap.set(name, p.longitude);
274
297
  }
275
298
  return aspects.map((a) => {
276
- const l1 = planetMap.get(capitalize(a.planet1 ?? ''));
277
- const l2 = planetMap.get(capitalize(a.planet2 ?? ''));
299
+ const l1 = planetMap.get(capitalize(a.planet1));
300
+ const l2 = planetMap.get(capitalize(a.planet2));
278
301
  if (l1 === undefined || l2 === undefined) return nothing;
279
- const p1 = polarToCartesian(CENTER, CENTER, PLANET_R - 18, l1 - 90);
280
- const p2 = polarToCartesian(CENTER, CENTER, PLANET_R - 18, l2 - 90);
281
- return svg`<line class="aspect" x1=${p1.x} y1=${p1.y} x2=${p2.x} y2=${p2.y} />`;
302
+ const p1 = polarToCartesian(
303
+ CENTER,
304
+ CENTER,
305
+ PLANET_R - 18,
306
+ this.toAngle(l1),
307
+ );
308
+ const p2 = polarToCartesian(
309
+ CENTER,
310
+ CENTER,
311
+ PLANET_R - 18,
312
+ this.toAngle(l2),
313
+ );
314
+ const aspectName = normalizeAspect(a);
315
+ const aspectClass = ASPECT_CLASS[aspectName] ?? 'aspect-other';
316
+ const orbLabel = formatNumber(a.orb, 1);
317
+ return svg`<line class=${`aspect ${aspectClass}`} x1=${p1.x} y1=${p1.y} x2=${p2.x} y2=${p2.y}><title>${a.planet1} ${aspectName || ''} ${a.planet2}${orbLabel ? ` (orb ${orbLabel}°)` : ''}</title></line>`;
282
318
  });
283
319
  }
284
320
  }
@@ -288,11 +324,20 @@ function capitalize(s: string): string {
288
324
  return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
289
325
  }
290
326
 
327
+ const ASPECT_CLASS: Record<string, string> = {
328
+ conjunction: 'aspect-conjunction',
329
+ sextile: 'aspect-sextile',
330
+ square: 'aspect-square',
331
+ trine: 'aspect-trine',
332
+ opposition: 'aspect-opposition',
333
+ };
334
+
335
+ function normalizeAspect(a: AspectEntry): string {
336
+ return (a.type ?? '').toLowerCase().replace(/_/g, '-');
337
+ }
338
+
291
339
  declare global {
292
340
  interface HTMLElementTagNameMap {
293
341
  'roxy-natal-chart': RoxyNatalChart;
294
342
  }
295
343
  }
296
-
297
- // Export for external use
298
- export { longitudeToSignPosition };
@@ -1,45 +1,18 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ CalculateExpressionResponse,
5
+ CalculateLifePathResponse,
6
+ CalculatePersonalYearResponse,
7
+ GenerateNumerologyChartResponse,
8
+ } from '../types/index.js';
3
9
  import { baseStyles } from '../utils/base-styles.js';
4
10
 
5
- interface NumerologyCommon {
6
- number?: number;
7
- calculation?: string;
8
- type?: 'single' | 'master' | string;
9
- hasKarmicDebt?: boolean;
10
- karmicDebtNumber?: number;
11
- karmicDebtMeaning?: string;
12
- meaning?: string;
13
- }
14
-
15
- interface CoreNumber {
16
- number?: number;
17
- type?: string;
18
- meaning?: string;
19
- calculation?: string;
20
- }
21
-
22
- interface FullChart {
23
- profile?: { fullName?: string; birthDate?: string };
24
- coreNumbers?: Record<string, CoreNumber | number>;
25
- additionalInsights?: Record<string, unknown>;
26
- birthDayProfile?: Record<string, unknown>;
27
- maturityStatus?: string;
28
- luckyAssociations?: Record<string, unknown>;
29
- summary?: string;
30
- }
31
-
32
- interface PersonalYear {
33
- year?: number;
34
- personalYear?: number;
35
- title?: string;
36
- theme?: string;
37
- keywords?: string[];
38
- meaning?: string;
39
- advice?: string;
40
- }
41
-
42
- type NumerologyData = NumerologyCommon & FullChart & PersonalYear;
11
+ type NumerologyData =
12
+ | CalculateLifePathResponse
13
+ | CalculateExpressionResponse
14
+ | CalculatePersonalYearResponse
15
+ | GenerateNumerologyChartResponse;
43
16
 
44
17
  /**
45
18
  * Numerology card. Renders /numerology/{life-path,expression,personal-year,chart}.
@@ -97,7 +70,8 @@ export class RoxyNumerologyCard extends LitElement {
97
70
  background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 30%, transparent);
98
71
  padding: var(--roxy-space-sm, 0.5rem);
99
72
  border-radius: var(--roxy-radius-sm, 4px);
100
- word-break: break-all;
73
+ white-space: pre-wrap;
74
+ overflow-wrap: anywhere;
101
75
  }
102
76
 
103
77
  .chips {
@@ -159,62 +133,83 @@ export class RoxyNumerologyCard extends LitElement {
159
133
  return html`<div class="roxy-empty" role="status">No numerology data</div>`;
160
134
 
161
135
  const headerLabel = LABELS[this.type] ?? this.type;
162
- const number = d.personalYear ?? d.number;
163
- const cores = d.coreNumbers
164
- ? Object.entries(d.coreNumbers).filter(
165
- ([, v]) => v !== null && v !== undefined,
166
- )
167
- : [];
168
-
169
- return html`<article
170
- class="card"
171
- aria-label=${headerLabel}
172
- >
136
+
137
+ if ('coreNumbers' in d) return this.renderChart(d, headerLabel);
138
+ if ('personalYear' in d) return this.renderPersonalYear(d, headerLabel);
139
+ return this.renderNumberCard(
140
+ d as CalculateLifePathResponse | CalculateExpressionResponse,
141
+ headerLabel,
142
+ );
143
+ }
144
+
145
+ private renderNumberCard(
146
+ d: CalculateLifePathResponse | CalculateExpressionResponse,
147
+ headerLabel: string,
148
+ ) {
149
+ const keywords = d.meaning?.keywords ?? [];
150
+ return html`<article class="card" aria-label=${headerLabel}>
173
151
  <div class="hero">
174
- ${typeof number === 'number' ? html`<div class="numeral">${number}</div>` : nothing}
152
+ ${typeof d.number === 'number' ? html`<div class="numeral">${d.number}</div>` : nothing}
175
153
  <div>
176
154
  <p class="label">${headerLabel}</p>
177
- ${
178
- d.title
179
- ? html`<h2 class="title">${d.title}</h2>`
180
- : d.type
181
- ? html`<h2 class="title">
182
- ${d.type === 'master' ? 'Master number' : 'Single digit'}
183
- </h2>`
184
- : nothing
185
- }
155
+ ${d.meaning?.title ? html`<h2 class="title">${d.meaning.title}</h2>` : nothing}
186
156
  </div>
187
157
  </div>
188
- ${d.theme ? html`<p><strong>Theme:</strong> ${d.theme}</p>` : nothing}
189
- ${d.meaning ? html`<p class="meaning">${d.meaning}</p>` : nothing}
190
- ${d.advice ? html`<p>${d.advice}</p>` : nothing}
158
+ ${d.meaning?.description ? html`<p class="meaning">${d.meaning.description}</p>` : nothing}
191
159
  ${d.calculation ? html`<pre class="calc">${d.calculation}</pre>` : nothing}
192
160
  ${
193
- d.keywords?.length
161
+ keywords.length > 0
194
162
  ? html`<div class="chips">
195
- ${d.keywords.map((k) => html`<span>${k}</span>`)}
163
+ ${keywords.map((k) => html`<span>${k}</span>`)}
196
164
  </div>`
197
165
  : nothing
198
166
  }
199
167
  ${
200
- cores.length > 0
201
- ? html`<div class="cores">
202
- ${cores.map(([k, v]) => {
203
- const value =
204
- typeof v === 'number' ? v : (v as CoreNumber).number;
205
- return html`<div class="item">
206
- <span>${humanize(k)}</span>
207
- <strong>${value ?? ''}</strong>
208
- </div>`;
209
- })}
168
+ d.hasKarmicDebt && d.karmicDebtNumber
169
+ ? html`<div class="karmic">
170
+ Karmic debt ${d.karmicDebtNumber}.
171
+ ${karmicDebtText(d.karmicDebtMeaning)}
210
172
  </div>`
211
173
  : nothing
212
174
  }
175
+ </article>`;
176
+ }
177
+
178
+ private renderPersonalYear(
179
+ d: CalculatePersonalYearResponse,
180
+ headerLabel: string,
181
+ ) {
182
+ return html`<article class="card" aria-label=${headerLabel}>
183
+ <div class="hero">
184
+ ${typeof d.personalYear === 'number' ? html`<div class="numeral">${d.personalYear}</div>` : nothing}
185
+ <div>
186
+ <p class="label">${headerLabel}</p>
187
+ ${d.theme ? html`<h2 class="title">${d.theme}</h2>` : nothing}
188
+ </div>
189
+ </div>
190
+ ${d.forecast ? html`<p class="meaning">${d.forecast}</p>` : nothing}
191
+ ${d.advice ? html`<p>${d.advice}</p>` : nothing}
192
+ </article>`;
193
+ }
194
+
195
+ private renderChart(d: GenerateNumerologyChartResponse, headerLabel: string) {
196
+ const cores = Object.entries(d.coreNumbers).filter(
197
+ ([, v]) => v !== null && v !== undefined,
198
+ );
199
+ return html`<article class="card" aria-label=${headerLabel}>
200
+ <div>
201
+ <p class="label">${headerLabel}</p>
202
+ ${d.profile?.name ? html`<h2 class="title">${d.profile.name}</h2>` : nothing}
203
+ </div>
213
204
  ${
214
- d.hasKarmicDebt && d.karmicDebtNumber
215
- ? html`<div class="karmic">
216
- Karmic debt ${d.karmicDebtNumber}.
217
- ${d.karmicDebtMeaning ? d.karmicDebtMeaning : ''}
205
+ cores.length > 0
206
+ ? html`<div class="cores">
207
+ ${cores.map(
208
+ ([k, v]) => html`<div class="item">
209
+ <span>${humanize(k)}</span>
210
+ <strong>${v.number ?? ''}</strong>
211
+ </div>`,
212
+ )}
218
213
  </div>`
219
214
  : nothing
220
215
  }
@@ -229,6 +224,19 @@ const LABELS: Record<string, string> = {
229
224
  chart: 'Numerology chart',
230
225
  };
231
226
 
227
+ type KarmicDebtMeaning = {
228
+ description: string;
229
+ challenge: string;
230
+ resolution: string;
231
+ };
232
+
233
+ function karmicDebtText(value: KarmicDebtMeaning | undefined): string {
234
+ if (!value) return '';
235
+ return [value.description, value.challenge, value.resolution]
236
+ .filter(Boolean)
237
+ .join(' ');
238
+ }
239
+
232
240
  function humanize(s: string): string {
233
241
  return s
234
242
  .replace(/[_-]+/g, ' ')