@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,5 +1,13 @@
1
1
  /**
2
2
  * Lightweight debounce for input handlers. Used by location search.
3
+ *
4
+ * The returned function exposes a `.cancel()` method so callers can clear a
5
+ * pending invocation when the host element disconnects, preventing the timer
6
+ * from firing on a detached node and mutating reactive state after teardown.
3
7
  */
4
- export declare function debounce<F extends (...args: never[]) => unknown>(fn: F, wait: number): F;
8
+ export interface Debounced<F extends (...args: never[]) => unknown> {
9
+ (...args: Parameters<F>): void;
10
+ cancel: () => void;
11
+ }
12
+ export declare function debounce<F extends (...args: never[]) => unknown>(fn: F, wait: number): Debounced<F>;
5
13
  //# sourceMappingURL=debounce.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"debounce.d.ts","sourceRoot":"","sources":["../../src/utils/debounce.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,EAC/D,EAAE,EAAE,CAAC,EACL,IAAI,EAAE,MAAM,GACV,CAAC,CAMH"}
1
+ {"version":3,"file":"debounce.d.ts","sourceRoot":"","sources":["../../src/utils/debounce.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO;IACjE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,EAC/D,EAAE,EAAE,CAAC,EACL,IAAI,EAAE,MAAM,GACV,SAAS,CAAC,CAAC,CAAC,CAgBd"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Display formatters for ISO timestamps and floats coming back from the API.
3
+ * Every helper returns "" for nullish or unparseable input so it falls out of
4
+ * template literals cleanly.
5
+ */
6
+ export declare function formatTime(input: unknown): string;
7
+ export declare function formatDate(input: unknown): string;
8
+ export declare function formatTimeRange(t: {
9
+ start?: string;
10
+ end?: string;
11
+ } | undefined): string;
12
+ export declare function formatNumber(value: unknown, dp?: number): string;
13
+ export declare function formatPercent(value: unknown, dp?: number): string;
14
+ export declare function formatLongitude(value: unknown): string;
15
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYjD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAWjD;AAED,wBAAgB,eAAe,CAC9B,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC7C,MAAM,CAMR;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,SAAI,GAAG,MAAM,CAG3D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,SAAI,GAAG,MAAM,CAG5D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGtD"}
@@ -0,0 +1,2 @@
1
+ export declare const ROXY_UI_VERSION = "0.1.3";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,eAAe,UAAU,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roxyapi/ui",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Web components for the RoxyAPI catalog. Drop-in charts, tables, cards, forms for astrology, tarot, numerology, biorhythm, I Ching, crystals, dreams, angel numbers, and more. One key, beautiful in 30 minutes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -16,12 +16,17 @@
16
16
  "types": "./dist/components/*.d.ts",
17
17
  "import": "./dist/components/*.js"
18
18
  },
19
+ "./types": {
20
+ "types": "./dist/types/index.d.ts"
21
+ },
19
22
  "./styles/tokens.css": "./dist/styles/tokens.css",
20
23
  "./registry/*": "./dist/registry/*.json"
21
24
  },
22
25
  "files": [
23
26
  "dist",
24
27
  "src",
28
+ "README.md",
29
+ "LICENSE",
25
30
  "AGENTS.md",
26
31
  "THEMING.md"
27
32
  ],
@@ -61,6 +66,9 @@
61
66
  "./dist/components/*.js",
62
67
  "./dist/cdn/*.js"
63
68
  ],
69
+ "engines": {
70
+ "node": ">=18"
71
+ },
64
72
  "peerDependencies": {
65
73
  "lit": "^3.0.0"
66
74
  },
@@ -1,58 +1,16 @@
1
1
  import { css, html, LitElement, nothing, svg } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ GetCriticalDaysResponse,
5
+ GetDailyBiorhythmResponse,
6
+ GetForecastResponse,
7
+ } from '../types/index.js';
3
8
  import { baseStyles } from '../utils/base-styles.js';
4
9
 
5
- interface DailyBiorhythm {
6
- birthDate?: string;
7
- targetDate?: string;
8
- daysSinceBirth?: number;
9
- cycles?: Record<string, number>;
10
- energyRating?: number;
11
- overallPhase?: string;
12
- interpretation?: string;
13
- advice?: string;
14
- criticalAlerts?: string[];
15
- }
16
-
17
- interface BiorhythmDay {
18
- date?: string;
19
- cycles?: Record<string, number>;
20
- energyRating?: number;
21
- }
22
-
23
- interface BiorhythmForecast {
24
- birthDate?: string;
25
- startDate?: string;
26
- endDate?: string;
27
- totalDays?: number;
28
- summary?: {
29
- bestDay?: string;
30
- worstDay?: string;
31
- criticalDayCount?: number;
32
- averageEnergy?: number;
33
- periodAdvice?: string;
34
- };
35
- days?: BiorhythmDay[];
36
- }
37
-
38
- interface CriticalDay {
39
- date?: string;
40
- cycle?: string;
41
- period?: string;
42
- direction?: string;
43
- severity?: string;
44
- advisory?: string;
45
- }
46
-
47
- interface CriticalDays {
48
- birthDate?: string;
49
- startDate?: string;
50
- endDate?: string;
51
- totalCriticalDays?: number;
52
- criticalDays?: CriticalDay[];
53
- }
54
-
55
- type BiorhythmData = DailyBiorhythm & BiorhythmForecast & CriticalDays;
10
+ type BiorhythmData =
11
+ | GetDailyBiorhythmResponse
12
+ | GetForecastResponse
13
+ | GetCriticalDaysResponse;
56
14
 
57
15
  const CYCLE_COLOR: Record<string, string> = {
58
16
  physical: '#dc2626',
@@ -164,18 +122,22 @@ export class RoxyBiorhythmChart extends LitElement {
164
122
  if (!d)
165
123
  return html`<div class="roxy-empty" role="status">No biorhythm data</div>`;
166
124
 
167
- if (this.mode === 'critical-days' && d.criticalDays?.length) {
168
- return this.renderCritical(d);
125
+ if (this.mode === 'critical-days' && 'criticalDays' in d) {
126
+ return this.renderCritical(d as GetCriticalDaysResponse);
169
127
  }
170
- if (this.mode === 'forecast' && d.days?.length) {
171
- return this.renderForecast(d);
128
+ if (this.mode === 'forecast' && 'days' in d) {
129
+ return this.renderForecast(d as GetForecastResponse);
172
130
  }
173
- return this.renderDaily(d);
131
+ return this.renderDaily(d as GetDailyBiorhythmResponse);
174
132
  }
175
133
 
176
- private renderDaily(d: DailyBiorhythm) {
177
- const cycles = d.cycles ?? {};
178
- const entries = Object.entries(cycles);
134
+ private renderDaily(d: GetDailyBiorhythmResponse) {
135
+ const raw = d.quickRead ?? {};
136
+ const entries = Object.entries(raw).map(([cycle, value]) => {
137
+ const v = typeof value === 'number' ? value : 0;
138
+ const normalized = Math.abs(v) > 1 ? v / 100 : v;
139
+ return [cycle, normalized] as const;
140
+ });
179
141
  return html`<section class="wrap" aria-label="Daily biorhythm">
180
142
  <header class="head">
181
143
  <h2 class="title">Biorhythm</h2>
@@ -186,8 +148,7 @@ export class RoxyBiorhythmChart extends LitElement {
186
148
  }
187
149
  </header>
188
150
  <div class="bars" role="list">
189
- ${entries.map(([cycle, value]) => {
190
- const v = typeof value === 'number' ? value : 0;
151
+ ${entries.map(([cycle, v]) => {
191
152
  const pct = ((v + 1) / 2) * 100; // -1..1 -> 0..100
192
153
  const color = CYCLE_COLOR[cycle] ?? 'var(--roxy-accent, #f59e0b)';
193
154
  return html`<div class="bar" role="listitem">
@@ -198,36 +159,32 @@ export class RoxyBiorhythmChart extends LitElement {
198
159
  style="width: ${pct}%; background: ${color}"
199
160
  ></span>
200
161
  </span>
201
- <span class="value">${(v * 100).toFixed(0)}%</span>
162
+ <span class="value">${Math.round(v * 100)}%</span>
202
163
  </div>`;
203
164
  })}
204
165
  </div>
205
- ${d.interpretation ? html`<p class="advice">${d.interpretation}</p>` : nothing}
166
+ ${d.dailyMessage ? html`<p class="advice">${d.dailyMessage}</p>` : nothing}
206
167
  ${d.advice ? html`<p class="advice">${d.advice}</p>` : nothing}
207
- ${
208
- d.criticalAlerts?.length
209
- ? html`<div>
210
- ${d.criticalAlerts.map((a) => html`<p class="alert">${a}</p>`)}
211
- </div>`
212
- : nothing
213
- }
214
168
  </section>`;
215
169
  }
216
170
 
217
- private renderForecast(d: BiorhythmForecast) {
171
+ private renderForecast(d: GetForecastResponse) {
218
172
  const days = d.days ?? [];
219
173
  if (days.length === 0)
220
174
  return html`<div class="roxy-empty" role="status">No forecast</div>`;
221
175
  const w = 600;
222
176
  const h = 160;
223
177
  const xStep = w / Math.max(days.length - 1, 1);
224
- const cycles = Object.keys(days[0]?.cycles ?? {});
178
+ const cycleKeys = [
179
+ 'physical',
180
+ 'emotional',
181
+ 'intellectual',
182
+ 'intuitive',
183
+ ] as const;
225
184
  return html`<section class="wrap" aria-label="Biorhythm forecast">
226
185
  <header class="head">
227
186
  <h2 class="title">Forecast</h2>
228
- <span class="energy"
229
- >${d.startDate ?? ''} - ${d.endDate ?? ''}</span
230
- >
187
+ <span class="energy">${d.startDate} - ${d.endDate}</span>
231
188
  </header>
232
189
  <svg
233
190
  viewBox="0 0 ${w} ${h}"
@@ -243,12 +200,12 @@ export class RoxyBiorhythmChart extends LitElement {
243
200
  stroke="var(--roxy-border, #e4e4e7)"
244
201
  stroke-width="1"
245
202
  />
246
- ${cycles.map((cycle) => {
203
+ ${cycleKeys.map((cycle) => {
247
204
  const points = days
248
205
  .map((day, i) => {
249
- const v = day.cycles?.[cycle] ?? 0;
206
+ const v = day[cycle] ?? 0;
250
207
  const x = i * xStep;
251
- const y = h / 2 - v * (h / 2 - 8);
208
+ const y = h / 2 - (v / 100) * (h / 2 - 8);
252
209
  return `${x.toFixed(2)},${y.toFixed(2)}`;
253
210
  })
254
211
  .join(' ');
@@ -264,18 +221,16 @@ export class RoxyBiorhythmChart extends LitElement {
264
221
  </section>`;
265
222
  }
266
223
 
267
- private renderCritical(d: CriticalDays) {
224
+ private renderCritical(d: GetCriticalDaysResponse) {
268
225
  return html`<section class="wrap" aria-label="Critical days">
269
226
  <header class="head">
270
227
  <h2 class="title">Critical days</h2>
271
- <span class="energy"
272
- >${d.totalCriticalDays ?? d.criticalDays?.length ?? 0} total</span
273
- >
228
+ <span class="energy">${d.totalCriticalDays} total</span>
274
229
  </header>
275
230
  <div>
276
- ${(d.criticalDays ?? []).map(
231
+ ${d.criticalDays.map(
277
232
  (day) => html`<span class="crit"
278
- >${day.date} · ${day.cycle ?? ''} ${day.severity ?? ''}</span
233
+ >${day.date} · ${day.cycle} ${day.severity}</span
279
234
  >`,
280
235
  )}
281
236
  </div>
@@ -1,26 +1,17 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ CalculateBioCompatibilityResponse,
5
+ CalculateCompatibilityResponse,
6
+ CalculateNumCompatibilityResponse,
7
+ } from '../types/index.js';
3
8
  import { baseStyles } from '../utils/base-styles.js';
9
+ import { formatNumber } from '../utils/format.js';
4
10
 
5
- interface CompatibilityData {
6
- overallScore?: number;
7
- score?: number;
8
- rating?: string;
9
- relationshipArchetype?: string;
10
- advice?: string;
11
- summary?: string;
12
- categoryScores?: Record<string, number>;
13
- categoryBreakdown?: Record<string, number>;
14
- emotional?: number;
15
- communication?: number;
16
- romance?: number;
17
- strengths?: string[];
18
- challenges?: string[];
19
- keyAspects?: string[];
20
- elementBalance?: Record<string, number>;
21
- person1?: { name?: string; sign?: string; lifePath?: number };
22
- person2?: { name?: string; sign?: string; lifePath?: number };
23
- }
11
+ type CompatibilityData =
12
+ | CalculateCompatibilityResponse
13
+ | CalculateNumCompatibilityResponse
14
+ | CalculateBioCompatibilityResponse;
24
15
 
25
16
  /**
26
17
  * Cross-domain compatibility card. Renders /astrology/compatibility-score,
@@ -94,7 +85,7 @@ export class RoxyCompatibilityCard extends LitElement {
94
85
  }
95
86
 
96
87
  .archetype {
97
- color: var(--roxy-info, #0284c7);
88
+ color: var(--roxy-accent-fg, #b45309);
98
89
  font-weight: var(--roxy-weight-bold, 600);
99
90
  }
100
91
 
@@ -126,23 +117,49 @@ export class RoxyCompatibilityCard extends LitElement {
126
117
  private getBreakdown(): Record<string, number> {
127
118
  const d = this.data;
128
119
  if (!d) return {};
129
- if (d.categoryScores) return d.categoryScores;
130
- if (d.categoryBreakdown) return d.categoryBreakdown;
131
- const inferred: Record<string, number> = {};
132
- if (typeof d.emotional === 'number') inferred.emotional = d.emotional;
133
- if (typeof d.communication === 'number')
134
- inferred.communication = d.communication;
135
- if (typeof d.romance === 'number') inferred.romance = d.romance;
136
- if (d.elementBalance) Object.assign(inferred, d.elementBalance);
137
- return inferred;
120
+ if ('categories' in d && d.categories) {
121
+ const out: Record<string, number> = {};
122
+ for (const [k, v] of Object.entries(d.categories)) {
123
+ if (typeof v === 'number' && Number.isFinite(v)) out[k] = v;
124
+ }
125
+ return out;
126
+ }
127
+ return {};
138
128
  }
139
129
 
140
130
  render() {
141
131
  const d = this.data;
142
132
  if (!d)
143
133
  return html`<div class="roxy-empty" role="status">No compatibility data</div>`;
144
- const score = d.overallScore ?? d.score;
134
+
135
+ const score = d.overallScore;
145
136
  const breakdown = this.getBreakdown();
137
+ const rating =
138
+ 'rating' in d
139
+ ? (d as CalculateNumCompatibilityResponse).rating
140
+ : undefined;
141
+ const archetype =
142
+ 'archetype' in d
143
+ ? (d as CalculateCompatibilityResponse).archetype
144
+ : undefined;
145
+ const advice =
146
+ 'advice' in d
147
+ ? (d as CalculateNumCompatibilityResponse).advice
148
+ : undefined;
149
+ const summary =
150
+ 'summary' in d
151
+ ? (d as CalculateCompatibilityResponse).summary
152
+ : undefined;
153
+ const interpretation =
154
+ 'interpretation' in d
155
+ ? (d as CalculateCompatibilityResponse).interpretation
156
+ : undefined;
157
+ const strengths = 'strengths' in d ? d.strengths : undefined;
158
+ const challenges = 'challenges' in d ? d.challenges : undefined;
159
+ const keyAspects =
160
+ 'keyAspects' in d
161
+ ? (d as CalculateCompatibilityResponse).keyAspects
162
+ : undefined;
146
163
 
147
164
  return html`<article
148
165
  class="card"
@@ -153,10 +170,10 @@ export class RoxyCompatibilityCard extends LitElement {
153
170
  <div>
154
171
  ${
155
172
  typeof score === 'number'
156
- ? html`<div class="score">${score}</div>`
173
+ ? html`<div class="score">${formatNumber(score, 0)}</div>`
157
174
  : nothing
158
175
  }
159
- ${d.rating ? html`<div class="rating">${d.rating}</div>` : nothing}
176
+ ${rating ? html`<div class="rating">${rating}</div>` : nothing}
160
177
  </div>
161
178
  </div>
162
179
 
@@ -169,50 +186,42 @@ export class RoxyCompatibilityCard extends LitElement {
169
186
  <span class="bar"
170
187
  ><span style="width: ${Math.max(0, Math.min(100, v))}%"></span
171
188
  ></span>
172
- <span>${v}</span>
189
+ <span>${formatNumber(v, 0)}</span>
173
190
  </div>`,
174
191
  )}
175
192
  </div>`
176
193
  : nothing
177
194
  }
178
195
  ${
179
- d.relationshipArchetype
196
+ archetype
180
197
  ? html`<p>
181
- <span class="archetype">${d.relationshipArchetype}</span>
198
+ <span class="archetype">${archetype.label}</span>
199
+ ${archetype.description ? html` · ${archetype.description}` : nothing}
182
200
  </p>`
183
201
  : nothing
184
202
  }
185
- ${d.summary ? html`<p>${d.summary}</p>` : nothing}
186
- ${d.advice ? html`<p>${d.advice}</p>` : nothing}
203
+ ${summary ? html`<p>${summary}</p>` : nothing}
204
+ ${interpretation && !summary ? html`<p>${interpretation}</p>` : nothing}
205
+ ${advice ? html`<p>${advice}</p>` : nothing}
187
206
  ${
188
- (d.strengths?.length ?? 0) > 0 || (d.challenges?.length ?? 0) > 0
207
+ (strengths?.length ?? 0) > 0 || (challenges?.length ?? 0) > 0
189
208
  ? html`<div class="lists">
190
209
  ${
191
- d.strengths?.length
210
+ strengths?.length
192
211
  ? html`<div>
193
212
  <h3>Strengths</h3>
194
213
  <ul>
195
- ${d.strengths.map((s) => html`<li>${s}</li>`)}
214
+ ${strengths.map((s) => html`<li>${s}</li>`)}
196
215
  </ul>
197
216
  </div>`
198
217
  : nothing
199
218
  }
200
219
  ${
201
- d.challenges?.length
220
+ challenges?.length
202
221
  ? html`<div>
203
222
  <h3>Challenges</h3>
204
223
  <ul>
205
- ${d.challenges.map((s) => html`<li>${s}</li>`)}
206
- </ul>
207
- </div>`
208
- : nothing
209
- }
210
- ${
211
- d.keyAspects?.length
212
- ? html`<div>
213
- <h3>Key aspects</h3>
214
- <ul>
215
- ${d.keyAspects.map((s) => html`<li>${s}</li>`)}
224
+ ${challenges.map((s) => html`<li>${s}</li>`)}
216
225
  </ul>
217
226
  </div>`
218
227
  : nothing
@@ -220,10 +229,34 @@ export class RoxyCompatibilityCard extends LitElement {
220
229
  </div>`
221
230
  : nothing
222
231
  }
232
+ ${
233
+ keyAspects?.length
234
+ ? html`<div>
235
+ <h3 style="margin: 0 0 0.25rem; font-size: var(--roxy-text-xs); color: var(--roxy-muted); text-transform: uppercase; letter-spacing: 0.06em;">Key aspects</h3>
236
+ <ul style="margin: 0; padding-left: 1rem; font-size: var(--roxy-text-sm);">
237
+ ${keyAspects.slice(0, 6).map((a) => html`<li>${formatAspect(a)}</li>`)}
238
+ </ul>
239
+ </div>`
240
+ : nothing
241
+ }
223
242
  </article>`;
224
243
  }
225
244
  }
226
245
 
246
+ type KeyAspect = CalculateCompatibilityResponse extends {
247
+ keyAspects: Array<infer T>;
248
+ }
249
+ ? T
250
+ : never;
251
+
252
+ function formatAspect(a: KeyAspect): string {
253
+ const aspect = a.type.toLowerCase().replace(/_/g, '-');
254
+ const orb =
255
+ typeof a.orb === 'number' ? ` (orb ${formatNumber(a.orb, 1)}°)` : '';
256
+ const head = [a.planet1, aspect, a.planet2].filter(Boolean).join(' ');
257
+ return a.description ? `${head}${orb} · ${a.description}` : `${head}${orb}`;
258
+ }
259
+
227
260
  declare global {
228
261
  interface HTMLElementTagNameMap {
229
262
  'roxy-compatibility-card': RoxyCompatibilityCard;
@@ -1,36 +1,25 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ GetCurrentDashaResponse,
5
+ GetMajorDashasResponse,
6
+ GetSubDashasResponse,
7
+ } from '../types/index.js';
3
8
  import { baseStyles } from '../utils/base-styles.js';
9
+ import { formatNumber } from '../utils/format.js';
4
10
 
5
- interface DashaPeriod {
6
- mahadashaLord?: string;
7
- antardashaLord?: string;
8
- pratyantardashaLord?: string;
9
- lord?: string;
10
- planet?: string;
11
- startDate?: string;
12
- endDate?: string;
13
- years?: number;
14
- durationYears?: number;
15
- }
11
+ type DashaData =
12
+ | GetCurrentDashaResponse
13
+ | GetMajorDashasResponse
14
+ | GetSubDashasResponse;
16
15
 
17
- interface DashaData {
18
- moonNakshatra?: string;
19
- nakshatraName?: string;
20
- nakshatraLord?: string;
21
- mahadasha?: DashaPeriod;
22
- antardasha?: DashaPeriod;
23
- pratyantardasha?: DashaPeriod;
24
- mahadashas?: DashaPeriod[];
25
- antardashas?: DashaPeriod[];
26
- mahadashaLord?: string;
27
- mahadashaPeriod?: DashaPeriod;
28
- birthDashaBalance?: { lord?: string; years?: number };
29
- totalYears?: number;
30
- remainingInMahadasha?: number;
31
- remainingInAntardasha?: number;
32
- remainingInPratyantardasha?: number;
33
- }
16
+ type DashaPeriod = {
17
+ planet: string;
18
+ startDate: string;
19
+ endDate: string;
20
+ durationYears: number;
21
+ interpretation?: string;
22
+ };
34
23
 
35
24
  /**
36
25
  * Dasha timeline. Renders /vedic-astrology/dasha/{current,major,sub/{...}}.
@@ -132,7 +121,7 @@ export class RoxyDashaTimeline extends LitElement {
132
121
 
133
122
  const periods = this.collectPeriods(d);
134
123
  const maxYears = periods.length
135
- ? Math.max(...periods.map((p) => p.durationYears ?? p.years ?? 1))
124
+ ? Math.max(...periods.map((p) => p.durationYears))
136
125
  : 0;
137
126
 
138
127
  return html`<div class="wrap" aria-label="Dasha timeline">
@@ -147,11 +136,11 @@ export class RoxyDashaTimeline extends LitElement {
147
136
  }
148
137
  </h2>
149
138
  ${
150
- d.nakshatraName || d.moonNakshatra
139
+ 'nakshatraName' in d && d.nakshatraName
151
140
  ? html`<div class="nakshatra">
152
- Moon nakshatra: ${d.nakshatraName ?? d.moonNakshatra}
153
- ${d.nakshatraLord ? html`(lord ${d.nakshatraLord})` : nothing}
154
- </div>`
141
+ Moon nakshatra: ${d.nakshatraName}
142
+ ${'nakshatraLord' in d && d.nakshatraLord ? html`(lord ${d.nakshatraLord})` : nothing}
143
+ </div>`
155
144
  : nothing
156
145
  }
157
146
  </header>
@@ -168,68 +157,61 @@ export class RoxyDashaTimeline extends LitElement {
168
157
  }
169
158
 
170
159
  private renderCurrent(d: DashaData) {
160
+ if (!('mahadasha' in d)) return nothing;
171
161
  return html`<div class="current">
172
162
  ${
173
- d.mahadasha
163
+ 'mahadasha' in d && d.mahadasha
174
164
  ? html`<div>
175
- <span>Mahadasha</span>
176
- <strong>${d.mahadasha.lord ?? d.mahadasha.mahadashaLord}</strong>
177
- ${
178
- typeof d.remainingInMahadasha === 'number'
179
- ? html`<small>${d.remainingInMahadasha.toFixed(1)} years left</small>`
180
- : nothing
181
- }
182
- </div>`
165
+ <span>Mahadasha</span>
166
+ <strong>${d.mahadasha.planet}</strong>
167
+ ${
168
+ 'remainingInMahadasha' in d && d.remainingInMahadasha
169
+ ? html`<small>${formatNumber(d.remainingInMahadasha.years + d.remainingInMahadasha.months / 12, 1)} years left</small>`
170
+ : nothing
171
+ }
172
+ </div>`
183
173
  : nothing
184
174
  }
185
175
  ${
186
- d.antardasha
176
+ 'antardasha' in d && d.antardasha
187
177
  ? html`<div>
188
- <span>Antardasha</span>
189
- <strong>${d.antardasha.lord ?? d.antardasha.antardashaLord}</strong>
190
- ${
191
- typeof d.remainingInAntardasha === 'number'
192
- ? html`<small>${d.remainingInAntardasha.toFixed(1)} years left</small>`
193
- : nothing
194
- }
195
- </div>`
178
+ <span>Antardasha</span>
179
+ <strong>${d.antardasha.planet}</strong>
180
+ ${
181
+ 'remainingInAntardasha' in d && d.remainingInAntardasha
182
+ ? html`<small>${formatNumber(d.remainingInAntardasha.years + d.remainingInAntardasha.months / 12, 1)} years left</small>`
183
+ : nothing
184
+ }
185
+ </div>`
196
186
  : nothing
197
187
  }
198
188
  ${
199
- d.pratyantardasha
189
+ 'pratyantardasha' in d && d.pratyantardasha
200
190
  ? html`<div>
201
- <span>Pratyantardasha</span>
202
- <strong
203
- >${
204
- d.pratyantardasha.lord ?? d.pratyantardasha.pratyantardashaLord
205
- }</strong
206
- >
207
- ${
208
- typeof d.remainingInPratyantardasha === 'number'
209
- ? html`<small
210
- >${d.remainingInPratyantardasha.toFixed(2)} years left</small
211
- >`
212
- : nothing
213
- }
214
- </div>`
191
+ <span>Pratyantardasha</span>
192
+ <strong>${d.pratyantardasha.planet}</strong>
193
+ ${
194
+ 'remainingInPratyantardasha' in d && d.remainingInPratyantardasha
195
+ ? html`<small>${formatNumber(d.remainingInPratyantardasha.years + d.remainingInPratyantardasha.months / 12, 1)} years left</small>`
196
+ : nothing
197
+ }
198
+ </div>`
215
199
  : nothing
216
200
  }
217
201
  </div>`;
218
202
  }
219
203
 
220
204
  private collectPeriods(d: DashaData): DashaPeriod[] {
221
- if (this.period === 'major' && d.mahadashas?.length) return d.mahadashas;
222
- if (this.period === 'sub' && d.antardashas?.length) return d.antardashas;
223
- return d.mahadashas ?? d.antardashas ?? [];
205
+ if ('mahadashas' in d && d.mahadashas?.length) return d.mahadashas;
206
+ if ('antardashas' in d && d.antardashas?.length) return d.antardashas;
207
+ return [];
224
208
  }
225
209
 
226
210
  private renderBar(p: DashaPeriod, max: number) {
227
- const lord =
228
- p.lord ?? p.mahadashaLord ?? p.antardashaLord ?? p.planet ?? '';
229
- const years = p.durationYears ?? p.years ?? 0;
211
+ const years = p.durationYears;
230
212
  const width = max > 0 ? (years / max) * 100 : 0;
231
213
  return html`<div class="bar" role="listitem">
232
- <span>${lord}</span>
214
+ <span>${p.planet}</span>
233
215
  <span class="bar-track"><span style="width: ${width}%"></span></span>
234
216
  <span class="dates">
235
217
  ${p.startDate ? formatYear(p.startDate) : ''}