@roxyapi/ui 0.1.2 → 0.2.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.
Files changed (220) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +327 -14
  3. package/THEMING.md +24 -7
  4. package/dist/cdn/components/ashtakavarga-grid.js +349 -0
  5. package/dist/cdn/components/ashtakavarga-grid.js.map +7 -0
  6. package/dist/cdn/components/biorhythm-chart.js +15 -22
  7. package/dist/cdn/components/biorhythm-chart.js.map +3 -3
  8. package/dist/cdn/components/choghadiya-grid.js +239 -0
  9. package/dist/cdn/components/choghadiya-grid.js.map +7 -0
  10. package/dist/cdn/components/compatibility-card.js +36 -34
  11. package/dist/cdn/components/compatibility-card.js.map +4 -4
  12. package/dist/cdn/components/dasha-timeline.js +35 -39
  13. package/dist/cdn/components/dasha-timeline.js.map +4 -4
  14. package/dist/cdn/components/data.js +9 -9
  15. package/dist/cdn/components/data.js.map +4 -4
  16. package/dist/cdn/components/divisional-chart.js +279 -0
  17. package/dist/cdn/components/divisional-chart.js.map +7 -0
  18. package/dist/cdn/components/dosha-card.js +49 -49
  19. package/dist/cdn/components/dosha-card.js.map +3 -3
  20. package/dist/cdn/components/endpoint-form.js +47 -28
  21. package/dist/cdn/components/endpoint-form.js.map +4 -4
  22. package/dist/cdn/components/guna-milan.js +66 -24
  23. package/dist/cdn/components/guna-milan.js.map +4 -4
  24. package/dist/cdn/components/hexagram.js +26 -26
  25. package/dist/cdn/components/hexagram.js.map +3 -3
  26. package/dist/cdn/components/horoscope-card.js +47 -40
  27. package/dist/cdn/components/horoscope-card.js.map +4 -4
  28. package/dist/cdn/components/kp-planets-table.js +10 -10
  29. package/dist/cdn/components/kp-planets-table.js.map +4 -4
  30. package/dist/cdn/components/location-search.js +6 -6
  31. package/dist/cdn/components/location-search.js.map +3 -3
  32. package/dist/cdn/components/moon-phase.js +18 -18
  33. package/dist/cdn/components/moon-phase.js.map +4 -4
  34. package/dist/cdn/components/natal-chart.js +240 -24
  35. package/dist/cdn/components/natal-chart.js.map +4 -4
  36. package/dist/cdn/components/numerology-card.js +40 -31
  37. package/dist/cdn/components/numerology-card.js.map +4 -4
  38. package/dist/cdn/components/panchang-table.js +30 -30
  39. package/dist/cdn/components/panchang-table.js.map +4 -4
  40. package/dist/cdn/components/shadbala-table.js +312 -0
  41. package/dist/cdn/components/shadbala-table.js.map +7 -0
  42. package/dist/cdn/components/synastry-chart.js +129 -39
  43. package/dist/cdn/components/synastry-chart.js.map +4 -4
  44. package/dist/cdn/components/tarot-card.js +49 -20
  45. package/dist/cdn/components/tarot-card.js.map +3 -3
  46. package/dist/cdn/components/tarot-spread.js +43 -27
  47. package/dist/cdn/components/tarot-spread.js.map +3 -3
  48. package/dist/cdn/components/transits-table.js +391 -0
  49. package/dist/cdn/components/transits-table.js.map +7 -0
  50. package/dist/cdn/components/vedic-kundli.js +63 -27
  51. package/dist/cdn/components/vedic-kundli.js.map +4 -4
  52. package/dist/cdn/components/yoga-list.js +334 -0
  53. package/dist/cdn/components/yoga-list.js.map +7 -0
  54. package/dist/cdn/roxy-ui.js +2104 -544
  55. package/dist/cdn/roxy-ui.js.map +4 -4
  56. package/dist/components/ashtakavarga-grid.d.ts +26 -0
  57. package/dist/components/ashtakavarga-grid.d.ts.map +1 -0
  58. package/dist/components/ashtakavarga-grid.js +457 -0
  59. package/dist/components/ashtakavarga-grid.js.map +7 -0
  60. package/dist/components/biorhythm-chart.d.ts +2 -46
  61. package/dist/components/biorhythm-chart.d.ts.map +1 -1
  62. package/dist/components/biorhythm-chart.js +24 -23
  63. package/dist/components/biorhythm-chart.js.map +2 -2
  64. package/dist/components/choghadiya-grid.d.ts +19 -0
  65. package/dist/components/choghadiya-grid.d.ts.map +1 -0
  66. package/dist/components/choghadiya-grid.js +304 -0
  67. package/dist/components/choghadiya-grid.js.map +7 -0
  68. package/dist/components/compatibility-card.d.ts +2 -27
  69. package/dist/components/compatibility-card.d.ts.map +1 -1
  70. package/dist/components/compatibility-card.js +50 -29
  71. package/dist/components/compatibility-card.js.map +3 -3
  72. package/dist/components/dasha-timeline.d.ts +2 -31
  73. package/dist/components/dasha-timeline.d.ts.map +1 -1
  74. package/dist/components/dasha-timeline.js +32 -30
  75. package/dist/components/dasha-timeline.js.map +3 -3
  76. package/dist/components/data.d.ts +11 -7
  77. package/dist/components/data.d.ts.map +1 -1
  78. package/dist/components/data.js +16 -6
  79. package/dist/components/data.js.map +3 -3
  80. package/dist/components/divisional-chart.d.ts +20 -0
  81. package/dist/components/divisional-chart.d.ts.map +1 -0
  82. package/dist/components/divisional-chart.js +471 -0
  83. package/dist/components/divisional-chart.js.map +7 -0
  84. package/dist/components/dosha-card.d.ts +2 -16
  85. package/dist/components/dosha-card.d.ts.map +1 -1
  86. package/dist/components/dosha-card.js +45 -43
  87. package/dist/components/dosha-card.js.map +2 -2
  88. package/dist/components/endpoint-form.d.ts +2 -0
  89. package/dist/components/endpoint-form.d.ts.map +1 -1
  90. package/dist/components/endpoint-form.js +71 -11
  91. package/dist/components/endpoint-form.js.map +3 -3
  92. package/dist/components/guna-milan.d.ts +2 -20
  93. package/dist/components/guna-milan.d.ts.map +1 -1
  94. package/dist/components/guna-milan.js +79 -20
  95. package/dist/components/guna-milan.js.map +4 -4
  96. package/dist/components/hexagram.d.ts +3 -27
  97. package/dist/components/hexagram.d.ts.map +1 -1
  98. package/dist/components/hexagram.js +48 -15
  99. package/dist/components/hexagram.js.map +2 -2
  100. package/dist/components/horoscope-card.d.ts +2 -20
  101. package/dist/components/horoscope-card.d.ts.map +1 -1
  102. package/dist/components/horoscope-card.js +54 -18
  103. package/dist/components/horoscope-card.js.map +3 -3
  104. package/dist/components/kp-planets-table.d.ts +2 -21
  105. package/dist/components/kp-planets-table.d.ts.map +1 -1
  106. package/dist/components/kp-planets-table.js +10 -4
  107. package/dist/components/kp-planets-table.js.map +3 -3
  108. package/dist/components/location-search.d.ts +5 -14
  109. package/dist/components/location-search.d.ts.map +1 -1
  110. package/dist/components/location-search.js +45 -5
  111. package/dist/components/location-search.js.map +2 -2
  112. package/dist/components/moon-phase.d.ts +4 -21
  113. package/dist/components/moon-phase.d.ts.map +1 -1
  114. package/dist/components/moon-phase.js +34 -4
  115. package/dist/components/moon-phase.js.map +3 -3
  116. package/dist/components/natal-chart.d.ts +9 -43
  117. package/dist/components/natal-chart.d.ts.map +1 -1
  118. package/dist/components/natal-chart.js +346 -79
  119. package/dist/components/natal-chart.js.map +3 -3
  120. package/dist/components/numerology-card.d.ts +5 -37
  121. package/dist/components/numerology-card.d.ts.map +1 -1
  122. package/dist/components/numerology-card.js +58 -30
  123. package/dist/components/numerology-card.js.map +3 -3
  124. package/dist/components/panchang-table.d.ts +3 -62
  125. package/dist/components/panchang-table.d.ts.map +1 -1
  126. package/dist/components/panchang-table.js +62 -32
  127. package/dist/components/panchang-table.js.map +3 -3
  128. package/dist/components/shadbala-table.d.ts +18 -0
  129. package/dist/components/shadbala-table.d.ts.map +1 -0
  130. package/dist/components/shadbala-table.js +400 -0
  131. package/dist/components/shadbala-table.js.map +7 -0
  132. package/dist/components/synastry-chart.d.ts +9 -28
  133. package/dist/components/synastry-chart.d.ts.map +1 -1
  134. package/dist/components/synastry-chart.js +201 -56
  135. package/dist/components/synastry-chart.js.map +3 -3
  136. package/dist/components/tarot-card.d.ts +5 -29
  137. package/dist/components/tarot-card.d.ts.map +1 -1
  138. package/dist/components/tarot-card.js +59 -20
  139. package/dist/components/tarot-card.js.map +2 -2
  140. package/dist/components/tarot-spread.d.ts +2 -24
  141. package/dist/components/tarot-spread.d.ts.map +1 -1
  142. package/dist/components/tarot-spread.js +39 -13
  143. package/dist/components/tarot-spread.js.map +2 -2
  144. package/dist/components/transits-table.d.ts +21 -0
  145. package/dist/components/transits-table.d.ts.map +1 -0
  146. package/dist/components/transits-table.js +515 -0
  147. package/dist/components/transits-table.js.map +7 -0
  148. package/dist/components/vedic-kundli.d.ts +5 -28
  149. package/dist/components/vedic-kundli.d.ts.map +1 -1
  150. package/dist/components/vedic-kundli.js +147 -83
  151. package/dist/components/vedic-kundli.js.map +3 -3
  152. package/dist/components/yoga-list.d.ts +29 -0
  153. package/dist/components/yoga-list.d.ts.map +1 -0
  154. package/dist/components/yoga-list.js +389 -0
  155. package/dist/components/yoga-list.js.map +7 -0
  156. package/dist/index.cjs +3693 -1180
  157. package/dist/index.cjs.map +4 -4
  158. package/dist/index.d.ts +11 -4
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +3709 -1196
  161. package/dist/index.js.map +4 -4
  162. package/dist/manifest.d.ts +43 -0
  163. package/dist/manifest.d.ts.map +1 -0
  164. package/dist/manifest.json +7 -2
  165. package/dist/styles/tokens.css +73 -1
  166. package/dist/tokens/index.d.ts +6 -0
  167. package/dist/tokens/index.d.ts.map +1 -1
  168. package/dist/types/index.d.ts +2 -0
  169. package/dist/types/index.d.ts.map +1 -0
  170. package/dist/types/types.gen.d.ts +27811 -0
  171. package/dist/types/types.gen.d.ts.map +1 -0
  172. package/dist/utils/debounce.d.ts +9 -1
  173. package/dist/utils/debounce.d.ts.map +1 -1
  174. package/dist/utils/format.d.ts +29 -0
  175. package/dist/utils/format.d.ts.map +1 -0
  176. package/dist/utils/kundli-render.d.ts +63 -0
  177. package/dist/utils/kundli-render.d.ts.map +1 -0
  178. package/dist/utils/string.d.ts +14 -0
  179. package/dist/utils/string.d.ts.map +1 -0
  180. package/dist/version.d.ts +2 -0
  181. package/dist/version.d.ts.map +1 -0
  182. package/package.json +7 -1
  183. package/src/components/ashtakavarga-grid.ts +354 -0
  184. package/src/components/biorhythm-chart.ts +39 -84
  185. package/src/components/choghadiya-grid.ts +185 -0
  186. package/src/components/compatibility-card.ts +85 -52
  187. package/src/components/dasha-timeline.ts +55 -73
  188. package/src/components/data.ts +28 -16
  189. package/src/components/divisional-chart.ts +214 -0
  190. package/src/components/dosha-card.ts +72 -68
  191. package/src/components/endpoint-form.ts +80 -18
  192. package/src/components/guna-milan.ts +87 -47
  193. package/src/components/hexagram.ts +53 -43
  194. package/src/components/horoscope-card.ts +59 -43
  195. package/src/components/kp-planets-table.ts +8 -27
  196. package/src/components/location-search.ts +47 -23
  197. package/src/components/moon-phase.ts +28 -25
  198. package/src/components/natal-chart.ts +364 -110
  199. package/src/components/numerology-card.ts +86 -84
  200. package/src/components/panchang-table.ts +40 -78
  201. package/src/components/shadbala-table.ts +286 -0
  202. package/src/components/synastry-chart.ts +213 -97
  203. package/src/components/tarot-card.ts +76 -62
  204. package/src/components/tarot-spread.ts +72 -45
  205. package/src/components/transits-table.ts +350 -0
  206. package/src/components/vedic-kundli.ts +59 -173
  207. package/src/components/yoga-list.ts +328 -0
  208. package/src/index.ts +18 -26
  209. package/src/manifest.ts +340 -0
  210. package/src/styles/tokens.css +73 -1
  211. package/src/tokens/index.ts +14 -0
  212. package/src/types/types.gen.ts +3 -3
  213. package/src/utils/debounce.ts +23 -4
  214. package/src/utils/format.ts +75 -0
  215. package/src/utils/kundli-render.ts +197 -0
  216. package/src/utils/string.ts +23 -0
  217. package/src/version.ts +2 -0
  218. package/dist/utils/motion.d.ts +0 -13
  219. package/dist/utils/motion.d.ts.map +0 -1
  220. package/src/utils/motion.ts +0 -18
@@ -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) : ''}
@@ -1,11 +1,12 @@
1
1
  import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import { baseStyles } from '../utils/base-styles.js';
4
+ import { humanize } from '../utils/string.js';
4
5
 
5
6
  /**
6
- * Generic fallback renderer. Accepts ANY OpenAPI response shape and renders it
7
- * via field-name heuristics so future spec additions render reasonably without
8
- * hand-wired components. Mirrors the WordPress GenericRenderer pattern.
7
+ * Generic fallback renderer. Accepts ANY OpenAPI response shape and renders
8
+ * it via field-name heuristics so future spec additions render reasonably
9
+ * without hand-wired components.
9
10
  *
10
11
  * Heuristic order:
11
12
  * 1. Primitive (string, number, boolean) -> single line.
@@ -14,9 +15,8 @@ import { baseStyles } from '../utils/base-styles.js';
14
15
  * 4. Object with title-like field -> card with key/value rows.
15
16
  * 5. Otherwise -> definition list of all keys.
16
17
  *
17
- * Future spec hint: when the server emits `x-roxy-ui` on a schema, the
18
- * dispatcher in tool-call (Phase 2.5) will select a hand-tuned component
19
- * instead of this fallback.
18
+ * When a schema declares an `x-roxy-ui` hint, a future dispatcher can opt
19
+ * into a hand-tuned component instead of this fallback.
20
20
  */
21
21
 
22
22
  type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
@@ -25,6 +25,14 @@ const TITLE_KEYS = ['title', 'name', 'label', 'heading', 'overview', 'summary'];
25
25
  const IMAGE_KEYS = ['imageUrl', 'image', 'icon', 'symbol'];
26
26
  const SKIP_KEYS = ['imageUrl', 'image']; // rendered separately, not in body rows
27
27
 
28
+ // Hard cap on recursion. Real RoxyAPI responses nest at most 5-6 deep; anything
29
+ // deeper is either a circular reference (which would otherwise infinite-loop)
30
+ // or a payload too rich for the generic fallback to render usefully. The
31
+ // recursion is otherwise safe: <roxy-data> is registered globally by its
32
+ // `@customElement` decorator on import, so the nested template resolves to
33
+ // this same class without a separate import.
34
+ const MAX_DEPTH = 6;
35
+
28
36
  @customElement('roxy-data')
29
37
  export class RoxyData extends LitElement {
30
38
  static styles = [
@@ -129,10 +137,21 @@ export class RoxyData extends LitElement {
129
137
  @property({ attribute: false })
130
138
  data: Json = null;
131
139
 
140
+ /**
141
+ * Internal recursion depth. Nested <roxy-data> instances inherit this from
142
+ * the parent and increment to guard against circular references in the
143
+ * input. Not part of the public API; do not set from consumer code.
144
+ */
145
+ @property({ attribute: false })
146
+ depth = 0;
147
+
132
148
  render() {
133
149
  if (this.data == null) {
134
150
  return html`<div class="roxy-empty" role="status">No data</div>`;
135
151
  }
152
+ if (this.depth >= MAX_DEPTH) {
153
+ return html`<div class="roxy-empty" role="status">…</div>`;
154
+ }
136
155
  return html`<div
137
156
  class="roxy-card"
138
157
  aria-label="Generic data display"
@@ -177,7 +196,7 @@ export class RoxyData extends LitElement {
177
196
  return html`<table class="roxy-table" role="table">
178
197
  <thead>
179
198
  <tr>
180
- ${keys.map((k) => html`<th>${this.humanize(k)}</th>`)}
199
+ ${keys.map((k) => html`<th>${humanize(k)}</th>`)}
181
200
  </tr>
182
201
  </thead>
183
202
  <tbody>
@@ -227,7 +246,7 @@ export class RoxyData extends LitElement {
227
246
  ? html`<dl class="roxy-rows">
228
247
  ${rows.map(
229
248
  ([k, v]) => html`
230
- <dt>${this.humanize(k)}</dt>
249
+ <dt>${humanize(k)}</dt>
231
250
  <dd>${this.renderField(v)}</dd>
232
251
  `,
233
252
  )}
@@ -252,7 +271,7 @@ export class RoxyData extends LitElement {
252
271
  </ul>`;
253
272
  }
254
273
  }
255
- return html`<roxy-data .data=${value}></roxy-data>`;
274
+ return html`<roxy-data .data=${value} .depth=${this.depth + 1}></roxy-data>`;
256
275
  }
257
276
 
258
277
  private formatPrimitive(value: Json | undefined): string {
@@ -271,13 +290,6 @@ export class RoxyData extends LitElement {
271
290
  }
272
291
  return Array.from(seen);
273
292
  }
274
-
275
- private humanize(key: string): string {
276
- return key
277
- .replace(/[_-]+/g, ' ')
278
- .replace(/([a-z])([A-Z])/g, '$1 $2')
279
- .replace(/^\w/, (c) => c.toUpperCase());
280
- }
281
293
  }
282
294
 
283
295
  declare global {
@@ -0,0 +1,214 @@
1
+ import { css, html, LitElement, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { PLANET_GLYPH, RASHI_KEYS } from '../tokens/index.js';
4
+ import type { DivisionalChartResponse } from '../types/index.js';
5
+ import { baseStyles } from '../utils/base-styles.js';
6
+ import type { HouseDef } from '../utils/kundli-render.js';
7
+ import {
8
+ RASHI_TO_SIGN,
9
+ renderNorthFrame,
10
+ renderNorthHouseGroup,
11
+ renderSouthFrame,
12
+ renderSouthHouseGroup,
13
+ } from '../utils/kundli-render.js';
14
+
15
+ type RashiBucket = {
16
+ rashi?: string;
17
+ signs?: Array<{ graha: string; isRetrograde?: boolean }>;
18
+ };
19
+ type ChartByRashi = { [key: string]: RashiBucket | unknown };
20
+
21
+ /**
22
+ * Divisional chart renderer (D2-D60). Accepts a DivisionalChartResponse and
23
+ * renders the same south/north kundli wheel as the birth chart, plus division
24
+ * metadata and Vargottama planet pills.
25
+ */
26
+ @customElement('roxy-divisional-chart')
27
+ export class RoxyDivisionalChart extends LitElement {
28
+ static styles = [
29
+ baseStyles,
30
+ css`
31
+ .wrap {
32
+ display: grid;
33
+ gap: var(--roxy-space-md, 1rem);
34
+ }
35
+ .header {
36
+ display: grid;
37
+ gap: var(--roxy-space-xs, 0.25rem);
38
+ }
39
+ .title {
40
+ font-size: var(--roxy-text-lg, 1.125rem);
41
+ font-weight: var(--roxy-weight-bold, 600);
42
+ margin: 0;
43
+ }
44
+ .division-meta {
45
+ font-size: var(--roxy-text-sm, 0.875rem);
46
+ color: var(--roxy-muted, #71717a);
47
+ margin: 0;
48
+ }
49
+ .significance {
50
+ font-size: var(--roxy-text-sm, 0.875rem);
51
+ color: var(--roxy-muted, #71717a);
52
+ border-left: 2px solid var(--roxy-border, #e4e4e7);
53
+ padding-left: var(--roxy-space-sm, 0.5rem);
54
+ margin: 0;
55
+ }
56
+ svg {
57
+ display: block;
58
+ width: 100%;
59
+ max-width: 360px;
60
+ margin: 0 auto;
61
+ }
62
+ .line {
63
+ fill: transparent;
64
+ stroke: var(--roxy-border, #e4e4e7);
65
+ }
66
+ .sign-text {
67
+ fill: var(--roxy-muted, #71717a);
68
+ font-size: 9px;
69
+ font-weight: 500;
70
+ font-family: var(--roxy-font-sans);
71
+ }
72
+ .planet-text {
73
+ fill: var(--roxy-fg, #0a0a0a);
74
+ font-size: 11px;
75
+ font-weight: 600;
76
+ font-family: var(--roxy-font-sans);
77
+ }
78
+ .house-num {
79
+ fill: var(--roxy-muted, #71717a);
80
+ font-size: 9px;
81
+ font-weight: 400;
82
+ font-family: var(--roxy-font-sans);
83
+ }
84
+ .lagna-marker {
85
+ fill: var(--roxy-accent-fg, #b45309);
86
+ font-size: 8px;
87
+ font-weight: 700;
88
+ font-family: var(--roxy-font-sans);
89
+ letter-spacing: 0.05em;
90
+ }
91
+ .lagna-bg {
92
+ fill: color-mix(in srgb, var(--roxy-accent, #f59e0b) 12%, transparent);
93
+ stroke: color-mix(in srgb, var(--roxy-accent, #f59e0b) 45%, transparent);
94
+ stroke-width: 0.8;
95
+ }
96
+ .vargottama-row {
97
+ display: flex;
98
+ flex-wrap: wrap;
99
+ gap: var(--roxy-space-xs, 0.25rem);
100
+ align-items: center;
101
+ }
102
+ .vargottama-label {
103
+ font-size: var(--roxy-text-sm, 0.875rem);
104
+ color: var(--roxy-muted, #71717a);
105
+ font-weight: 500;
106
+ margin-right: var(--roxy-space-xs, 0.25rem);
107
+ }
108
+ .vargottama-pill {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: 0.2em;
112
+ font-size: var(--roxy-text-sm, 0.875rem);
113
+ font-weight: 600;
114
+ padding: 0.15em 0.6em;
115
+ border-radius: 999px;
116
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 22%, transparent);
117
+ color: var(--roxy-fg, #0a0a0a);
118
+ border: 1px solid color-mix(in srgb, var(--roxy-accent, #f59e0b) 45%, transparent);
119
+ }
120
+ `,
121
+ ];
122
+
123
+ @property({ attribute: false })
124
+ data: DivisionalChartResponse | null = null;
125
+
126
+ @property({ type: String, reflect: true, attribute: 'chart-style' })
127
+ chartStyle: 'south' | 'north' = 'south';
128
+
129
+ private buildHouses(): HouseDef[] {
130
+ if (!this.data) return [];
131
+ const chart = this.data.chart as ChartByRashi;
132
+ const meta =
133
+ (this.data.chart as { meta?: Record<string, { rashi?: string }> }).meta ??
134
+ {};
135
+ const lagnaSign = meta.Lagna?.rashi ?? '';
136
+ const houses: HouseDef[] = [];
137
+ for (let i = 0; i < 12; i++) {
138
+ const key = RASHI_KEYS[i];
139
+ const bucket = chart[key] as RashiBucket | undefined;
140
+ const planets = (bucket?.signs ?? []).map((p) => p.graha).filter(Boolean);
141
+ const sign = RASHI_TO_SIGN[key] ?? '';
142
+ houses.push({
143
+ number: i + 1,
144
+ sign,
145
+ planets,
146
+ isLagna: lagnaSign
147
+ ? lagnaSign.toLowerCase() === sign.toLowerCase()
148
+ : false,
149
+ });
150
+ }
151
+ return houses;
152
+ }
153
+
154
+ render() {
155
+ if (!this.data)
156
+ return html`<div class="roxy-empty" role="status">No divisional chart data</div>`;
157
+
158
+ const { division, vargottama } = this.data;
159
+ const houses = this.buildHouses();
160
+ const isNorth = this.chartStyle === 'north';
161
+
162
+ return html`<div class="wrap">
163
+ <div class="header">
164
+ <h2 class="title">
165
+ D${division.number} ${division.name}
166
+ ${
167
+ division.sanskritName && division.sanskritName !== division.name
168
+ ? html`<span class="division-meta"> · ${division.sanskritName}</span>`
169
+ : nothing
170
+ }
171
+ </h2>
172
+ ${
173
+ division.significance
174
+ ? html`<p class="significance">${division.significance}</p>`
175
+ : nothing
176
+ }
177
+ </div>
178
+
179
+ <svg
180
+ viewBox="0 0 300 300"
181
+ role="img"
182
+ aria-label="D${division.number} ${division.name} divisional chart with twelve sign houses"
183
+ >
184
+ <title>D${division.number} ${division.name}</title>
185
+ ${isNorth ? renderNorthFrame() : renderSouthFrame()}
186
+ ${
187
+ isNorth
188
+ ? houses.map((h) => renderNorthHouseGroup(h))
189
+ : houses.map((h) => renderSouthHouseGroup(h))
190
+ }
191
+ </svg>
192
+
193
+ ${
194
+ vargottama && vargottama.length > 0
195
+ ? html`<div class="vargottama-row" role="list" aria-label="Vargottama planets">
196
+ <span class="vargottama-label">Vargottama:</span>
197
+ ${vargottama.map(
198
+ (planet) =>
199
+ html`<span class="vargottama-pill" role="listitem">
200
+ ${PLANET_GLYPH[planet] ?? ''} ${planet}
201
+ </span>`,
202
+ )}
203
+ </div>`
204
+ : nothing
205
+ }
206
+ </div>`;
207
+ }
208
+ }
209
+
210
+ declare global {
211
+ interface HTMLElementTagNameMap {
212
+ 'roxy-divisional-chart': RoxyDivisionalChart;
213
+ }
214
+ }
@@ -1,25 +1,13 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ KalsarpaResponse,
5
+ ManglikResponse,
6
+ SadhesatiResponse,
7
+ } from '../types/index.js';
3
8
  import { baseStyles } from '../utils/base-styles.js';
4
9
 
5
- interface DoshaData {
6
- present?: boolean;
7
- severity?: 'Mild' | 'Moderate' | 'Severe' | string;
8
- type?: string;
9
- description?: string;
10
- remedies?: string[];
11
- exceptions?: string[];
12
- effects?:
13
- | string
14
- | {
15
- marriage?: string;
16
- personality?: string;
17
- timing?: string;
18
- relationships?: string;
19
- general?: string;
20
- phases?: Record<string, string>;
21
- };
22
- }
10
+ type DoshaData = ManglikResponse | KalsarpaResponse | SadhesatiResponse;
23
11
 
24
12
  const DOSHA_LABELS: Record<string, string> = {
25
13
  manglik: 'Mangal Dosha',
@@ -71,31 +59,30 @@ export class RoxyDoshaCard extends LitElement {
71
59
  }
72
60
  .badge.absent {
73
61
  background: color-mix(in srgb, var(--roxy-success, #16a34a) 16%, transparent);
74
- color: var(--roxy-success, #16a34a);
62
+ color: var(--roxy-success-fg, #166534);
75
63
  }
76
64
  .badge.present {
77
65
  background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
78
- color: var(--roxy-danger, #dc2626);
79
- }
80
- .severity {
81
- display: flex;
82
- align-items: center;
83
- gap: 4px;
84
- }
85
- .severity span {
86
- width: 14px;
87
- height: 4px;
88
- border-radius: 2px;
89
- background: var(--roxy-border, #e4e4e7);
90
- }
91
- .severity.mild span:nth-child(1) {
92
- background: var(--roxy-warning, #ea580c);
93
- }
94
- .severity.moderate span:nth-child(-n + 2) {
95
- background: var(--roxy-warning, #ea580c);
96
- }
97
- .severity.severe span {
98
- background: var(--roxy-danger, #dc2626);
66
+ color: var(--roxy-danger-fg, #991b1b);
67
+ }
68
+ .severity-bar {
69
+ position: relative;
70
+ width: 100%;
71
+ height: 8px;
72
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 30%, transparent);
73
+ border-radius: 4px;
74
+ overflow: hidden;
75
+ }
76
+ .severity-fill {
77
+ display: block;
78
+ height: 100%;
79
+ transition: width var(--roxy-motion-duration, 200ms) ease-out;
80
+ border-radius: 4px;
81
+ }
82
+ @media (prefers-reduced-motion: reduce) {
83
+ .severity-fill {
84
+ transition: none;
85
+ }
99
86
  }
100
87
 
101
88
  .description {
@@ -140,7 +127,24 @@ export class RoxyDoshaCard extends LitElement {
140
127
 
141
128
  const present = !!d.present;
142
129
  const label = DOSHA_LABELS[this.type] ?? this.type;
143
- const sevClass = (d.severity ?? '').toLowerCase();
130
+ const sevLower = (d.severity ?? '').toLowerCase();
131
+ const tier =
132
+ sevLower === 'severe'
133
+ ? 3
134
+ : sevLower === 'moderate'
135
+ ? 2
136
+ : sevLower === 'mild'
137
+ ? 1
138
+ : 0;
139
+ const pct = tier * 33;
140
+ const barColor =
141
+ tier === 3
142
+ ? 'var(--roxy-danger)'
143
+ : tier === 2
144
+ ? 'var(--roxy-warning)'
145
+ : tier === 1
146
+ ? 'var(--roxy-success)'
147
+ : 'transparent';
144
148
 
145
149
  return html`<article
146
150
  class="card"
@@ -148,25 +152,26 @@ export class RoxyDoshaCard extends LitElement {
148
152
  >
149
153
  <header class="head">
150
154
  <h2 class="title">${label}</h2>
151
- <div style="display:flex; gap:0.5rem; align-items:center;">
152
- <span class=${`badge ${present ? 'present' : 'absent'}`}>
153
- ${present ? 'Present' : 'Absent'}
154
- </span>
155
- ${
156
- d.severity
157
- ? html`<span
158
- class=${`severity ${sevClass}`}
159
- role="img"
160
- aria-label=${`Severity ${d.severity}`}
161
- >
162
- <span></span><span></span><span></span>
163
- </span>`
164
- : nothing
165
- }
166
- </div>
155
+ <span class=${`badge ${present ? 'present' : 'absent'}`}>
156
+ ${present ? 'Present' : 'Absent'}
157
+ </span>
167
158
  </header>
159
+ ${
160
+ d.severity
161
+ ? html`<div
162
+ class="severity-bar"
163
+ role="meter"
164
+ aria-valuemin="0"
165
+ aria-valuemax="3"
166
+ aria-valuenow="${tier}"
167
+ aria-label="Severity ${d.severity}"
168
+ >
169
+ <span class="severity-fill" style="width: ${pct}%; background: ${barColor};"></span>
170
+ </div>`
171
+ : nothing
172
+ }
168
173
  ${d.description ? html`<p class="description">${d.description}</p>` : nothing}
169
- ${this.renderEffects(d.effects)}
174
+ ${this.renderEffects(d)}
170
175
  ${
171
176
  d.remedies && d.remedies.length > 0
172
177
  ? html`<div>
@@ -178,22 +183,21 @@ export class RoxyDoshaCard extends LitElement {
178
183
  : nothing
179
184
  }
180
185
  ${
181
- d.exceptions && d.exceptions.length > 0
186
+ 'exceptions' in d && d.exceptions && d.exceptions.length > 0
182
187
  ? html`<div>
183
- <h3>Exceptions</h3>
184
- <ul>
185
- ${d.exceptions.map((r) => html`<li>${r}</li>`)}
186
- </ul>
187
- </div>`
188
+ <h3>Exceptions</h3>
189
+ <ul>
190
+ ${d.exceptions.map((r) => html`<li>${r}</li>`)}
191
+ </ul>
192
+ </div>`
188
193
  : nothing
189
194
  }
190
195
  </article>`;
191
196
  }
192
197
 
193
- private renderEffects(e: DoshaData['effects']) {
194
- if (!e) return nothing;
195
- if (typeof e === 'string') return html`<p>${e}</p>`;
196
- const entries = Object.entries(e).filter(
198
+ private renderEffects(d: DoshaData) {
199
+ if (!d.effects) return nothing;
200
+ const entries = Object.entries(d.effects).filter(
197
201
  ([, v]) => typeof v === 'string' && v.length > 0,
198
202
  );
199
203
  if (entries.length === 0) return nothing;