@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,45 +1,19 @@
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';
10
+ import { humanize } from '../utils/string.js';
4
11
 
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;
12
+ type NumerologyData =
13
+ | CalculateLifePathResponse
14
+ | CalculateExpressionResponse
15
+ | CalculatePersonalYearResponse
16
+ | GenerateNumerologyChartResponse;
43
17
 
44
18
  /**
45
19
  * Numerology card. Renders /numerology/{life-path,expression,personal-year,chart}.
@@ -97,7 +71,8 @@ export class RoxyNumerologyCard extends LitElement {
97
71
  background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 30%, transparent);
98
72
  padding: var(--roxy-space-sm, 0.5rem);
99
73
  border-radius: var(--roxy-radius-sm, 4px);
100
- word-break: break-all;
74
+ white-space: pre-wrap;
75
+ overflow-wrap: anywhere;
101
76
  }
102
77
 
103
78
  .chips {
@@ -159,62 +134,83 @@ export class RoxyNumerologyCard extends LitElement {
159
134
  return html`<div class="roxy-empty" role="status">No numerology data</div>`;
160
135
 
161
136
  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
- >
137
+
138
+ if ('coreNumbers' in d) return this.renderChart(d, headerLabel);
139
+ if ('personalYear' in d) return this.renderPersonalYear(d, headerLabel);
140
+ return this.renderNumberCard(
141
+ d as CalculateLifePathResponse | CalculateExpressionResponse,
142
+ headerLabel,
143
+ );
144
+ }
145
+
146
+ private renderNumberCard(
147
+ d: CalculateLifePathResponse | CalculateExpressionResponse,
148
+ headerLabel: string,
149
+ ) {
150
+ const keywords = d.meaning?.keywords ?? [];
151
+ return html`<article class="card" aria-label=${headerLabel}>
173
152
  <div class="hero">
174
- ${typeof number === 'number' ? html`<div class="numeral">${number}</div>` : nothing}
153
+ ${typeof d.number === 'number' ? html`<div class="numeral">${d.number}</div>` : nothing}
175
154
  <div>
176
155
  <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
- }
156
+ ${d.meaning?.title ? html`<h2 class="title">${d.meaning.title}</h2>` : nothing}
186
157
  </div>
187
158
  </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}
159
+ ${d.meaning?.description ? html`<p class="meaning">${d.meaning.description}</p>` : nothing}
191
160
  ${d.calculation ? html`<pre class="calc">${d.calculation}</pre>` : nothing}
192
161
  ${
193
- d.keywords?.length
162
+ keywords.length > 0
194
163
  ? html`<div class="chips">
195
- ${d.keywords.map((k) => html`<span>${k}</span>`)}
164
+ ${keywords.map((k) => html`<span>${k}</span>`)}
196
165
  </div>`
197
166
  : nothing
198
167
  }
199
168
  ${
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
- })}
169
+ d.hasKarmicDebt && d.karmicDebtNumber
170
+ ? html`<div class="karmic">
171
+ Karmic debt ${d.karmicDebtNumber}.
172
+ ${karmicDebtText(d.karmicDebtMeaning)}
210
173
  </div>`
211
174
  : nothing
212
175
  }
176
+ </article>`;
177
+ }
178
+
179
+ private renderPersonalYear(
180
+ d: CalculatePersonalYearResponse,
181
+ headerLabel: string,
182
+ ) {
183
+ return html`<article class="card" aria-label=${headerLabel}>
184
+ <div class="hero">
185
+ ${typeof d.personalYear === 'number' ? html`<div class="numeral">${d.personalYear}</div>` : nothing}
186
+ <div>
187
+ <p class="label">${headerLabel}</p>
188
+ ${d.theme ? html`<h2 class="title">${d.theme}</h2>` : nothing}
189
+ </div>
190
+ </div>
191
+ ${d.forecast ? html`<p class="meaning">${d.forecast}</p>` : nothing}
192
+ ${d.advice ? html`<p>${d.advice}</p>` : nothing}
193
+ </article>`;
194
+ }
195
+
196
+ private renderChart(d: GenerateNumerologyChartResponse, headerLabel: string) {
197
+ const cores = Object.entries(d.coreNumbers).filter(
198
+ ([, v]) => v !== null && v !== undefined,
199
+ );
200
+ return html`<article class="card" aria-label=${headerLabel}>
201
+ <div>
202
+ <p class="label">${headerLabel}</p>
203
+ ${d.profile?.name ? html`<h2 class="title">${d.profile.name}</h2>` : nothing}
204
+ </div>
213
205
  ${
214
- d.hasKarmicDebt && d.karmicDebtNumber
215
- ? html`<div class="karmic">
216
- Karmic debt ${d.karmicDebtNumber}.
217
- ${d.karmicDebtMeaning ? d.karmicDebtMeaning : ''}
206
+ cores.length > 0
207
+ ? html`<div class="cores">
208
+ ${cores.map(
209
+ ([k, v]) => html`<div class="item">
210
+ <span>${humanize(k)}</span>
211
+ <strong>${v.number ?? ''}</strong>
212
+ </div>`,
213
+ )}
218
214
  </div>`
219
215
  : nothing
220
216
  }
@@ -229,11 +225,17 @@ const LABELS: Record<string, string> = {
229
225
  chart: 'Numerology chart',
230
226
  };
231
227
 
232
- function humanize(s: string): string {
233
- return s
234
- .replace(/[_-]+/g, ' ')
235
- .replace(/([a-z])([A-Z])/g, '$1 $2')
236
- .replace(/^\w/, (c) => c.toUpperCase());
228
+ type KarmicDebtMeaning = {
229
+ description: string;
230
+ challenge: string;
231
+ resolution: string;
232
+ };
233
+
234
+ function karmicDebtText(value: KarmicDebtMeaning | undefined): string {
235
+ if (!value) return '';
236
+ return [value.description, value.challenge, value.resolution]
237
+ .filter(Boolean)
238
+ .join(' ');
237
239
  }
238
240
 
239
241
  declare global {
@@ -1,53 +1,16 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ GetBasicPanchangResponse,
5
+ GetDetailedPanchangResponse,
6
+ } from '../types/index.js';
3
7
  import { baseStyles } from '../utils/base-styles.js';
8
+ import { formatDate, formatTime, formatTimeRange } from '../utils/format.js';
4
9
 
5
- interface PanchangTime {
6
- start?: string;
7
- end?: string;
8
- }
9
-
10
- interface PanchangData {
11
- date?: string;
12
- location?: { name?: string; latitude?: number; longitude?: number };
13
- vara?: string;
14
- sunrise?: string;
15
- sunset?: string;
16
- moonrise?: string;
17
- moonset?: string;
18
- sunSign?: string;
19
- moonSign?: string;
20
- sunNakshatra?: string;
21
- tithi?: string | { name?: string; phase?: string; end?: string };
22
- nakshatra?: string | { name?: string; lord?: string; end?: string };
23
- yoga?: string | { name?: string; end?: string };
24
- karana?: string | { name?: string; end?: string };
25
- hora?: string;
26
- rahuKaal?: PanchangTime;
27
- yamaganda?: PanchangTime;
28
- gulika?: PanchangTime;
29
- abhijitMuhurta?: PanchangTime;
30
- brahmaMuhurta?: PanchangTime;
31
- vijayaMuhurta?: PanchangTime;
32
- nishitaMuhurta?: PanchangTime;
33
- godhuliMuhurta?: PanchangTime;
34
- pratahSandhya?: PanchangTime;
35
- sayahnaSandhya?: PanchangTime;
36
- durMuhurta?: PanchangTime[];
37
- varjyam?: PanchangTime[];
38
- amritKalam?: PanchangTime[];
39
- chandrabalam?: string | string[];
40
- tarabalam?: string;
41
- panchaka?: string;
42
- bhadra?: string;
43
- sunLongitude?: number;
44
- moonLongitude?: number;
45
- }
10
+ type PanchangData = GetBasicPanchangResponse | GetDetailedPanchangResponse;
11
+ type PanchangTime = GetDetailedPanchangResponse['rahuKaal'];
46
12
 
47
- /**
48
- * Panchang table for /vedic-astrology/panchang/{basic,detailed}. Detailed mode
49
- * renders 15+ muhurtas. Basic mode renders the five elements only.
50
- */
13
+ /** Panchang table for /vedic-astrology/panchang/{basic,detailed}. */
51
14
  @customElement('roxy-panchang-table')
52
15
  export class RoxyPanchangTable extends LitElement {
53
16
  static styles = [
@@ -123,35 +86,40 @@ export class RoxyPanchangTable extends LitElement {
123
86
  const d = this.data;
124
87
  if (!d)
125
88
  return html`<div class="roxy-empty" role="status">No panchang data</div>`;
89
+ const detailed = 'sunrise' in d ? d : null;
126
90
 
127
- const fivefold = [
91
+ const fivefold: Array<[string, string]> = [
128
92
  ['Tithi', this.formatPart(d.tithi)],
129
93
  ['Nakshatra', this.formatPart(d.nakshatra)],
130
94
  ['Yoga', this.formatPart(d.yoga)],
131
95
  ['Karana', this.formatPart(d.karana)],
132
- ['Vara', d.vara ?? ''],
133
96
  ];
97
+ if (detailed) fivefold.push(['Vara', this.formatPart(detailed.vara)]);
134
98
 
135
- const muhurtas: Array<[string, PanchangTime | undefined]> = [
136
- ['Brahma Muhurta', d.brahmaMuhurta],
137
- ['Abhijit Muhurta', d.abhijitMuhurta],
138
- ['Vijaya Muhurta', d.vijayaMuhurta],
139
- ['Godhuli Muhurta', d.godhuliMuhurta],
140
- ['Nishita Muhurta', d.nishitaMuhurta],
141
- ['Pratah Sandhya', d.pratahSandhya],
142
- ['Sayahna Sandhya', d.sayahnaSandhya],
143
- ];
99
+ const muhurtas: Array<[string, PanchangTime | undefined]> = detailed
100
+ ? [
101
+ ['Brahma Muhurta', detailed.brahmaMuhurta],
102
+ ['Abhijit Muhurta', detailed.abhijitMuhurta],
103
+ ['Vijaya Muhurta', detailed.vijayaMuhurta],
104
+ ['Godhuli Muhurta', detailed.godhuliMuhurta],
105
+ ['Nishita Muhurta', detailed.nishitaMuhurta],
106
+ ['Pratah Sandhya', detailed.pratahSandhya],
107
+ ['Sayahna Sandhya', detailed.sayahnaSandhya],
108
+ ]
109
+ : [];
144
110
 
145
- const inauspicious: Array<[string, PanchangTime | undefined]> = [
146
- ['Rahu Kaal', d.rahuKaal],
147
- ['Yamaganda', d.yamaganda],
148
- ['Gulika', d.gulika],
149
- ];
111
+ const inauspicious: Array<[string, PanchangTime | undefined]> = detailed
112
+ ? [
113
+ ['Rahu Kaal', detailed.rahuKaal],
114
+ ['Yamaganda', detailed.yamaganda],
115
+ ['Gulika', detailed.gulika],
116
+ ]
117
+ : [];
150
118
 
151
119
  return html`<div class="wrap" aria-label="Panchang">
152
120
  <header class="head">
153
121
  <h2 class="title">Panchang</h2>
154
- <span class="date">${d.date ?? ''}</span>
122
+ <span class="date">${detailed ? formatDate(detailed.date) : ''}</span>
155
123
  </header>
156
124
  <table>
157
125
  <tbody>
@@ -162,34 +130,34 @@ export class RoxyPanchangTable extends LitElement {
162
130
  </tr>`,
163
131
  )}
164
132
  ${
165
- d.sunrise
133
+ detailed?.sunrise
166
134
  ? html`<tr>
167
135
  <th>Sunrise</th>
168
- <td>${d.sunrise}</td>
136
+ <td>${formatTime(detailed.sunrise)}</td>
169
137
  </tr>`
170
138
  : nothing
171
139
  }
172
140
  ${
173
- d.sunset
141
+ detailed?.sunset
174
142
  ? html`<tr>
175
143
  <th>Sunset</th>
176
- <td>${d.sunset}</td>
144
+ <td>${formatTime(detailed.sunset)}</td>
177
145
  </tr>`
178
146
  : nothing
179
147
  }
180
148
  ${
181
- d.moonrise
149
+ detailed?.moonrise
182
150
  ? html`<tr>
183
151
  <th>Moonrise</th>
184
- <td>${d.moonrise}</td>
152
+ <td>${formatTime(detailed.moonrise)}</td>
185
153
  </tr>`
186
154
  : nothing
187
155
  }
188
156
  ${
189
- d.moonset
157
+ detailed?.moonset
190
158
  ? html`<tr>
191
159
  <th>Moonset</th>
192
- <td>${d.moonset}</td>
160
+ <td>${formatTime(detailed.moonset)}</td>
193
161
  </tr>`
194
162
  : nothing
195
163
  }
@@ -207,7 +175,7 @@ export class RoxyPanchangTable extends LitElement {
207
175
  .map(
208
176
  ([k, v]) => html`<tr>
209
177
  <th>${k}</th>
210
- <td>${formatRange(v)}</td>
178
+ <td>${formatTimeRange(v)}</td>
211
179
  </tr>`,
212
180
  )}
213
181
  </tbody>
@@ -220,7 +188,7 @@ export class RoxyPanchangTable extends LitElement {
220
188
  .map(
221
189
  ([k, v]) => html`<tr>
222
190
  <th>${k}</th>
223
- <td>${formatRange(v)}</td>
191
+ <td>${formatTimeRange(v)}</td>
224
192
  </tr>`,
225
193
  )}
226
194
  </tbody>
@@ -252,12 +220,6 @@ export class RoxyPanchangTable extends LitElement {
252
220
  }
253
221
  }
254
222
 
255
- function formatRange(t: PanchangTime | undefined): string {
256
- if (!t) return '';
257
- if (t.start && t.end) return `${t.start} - ${t.end}`;
258
- return t.start ?? t.end ?? '';
259
- }
260
-
261
223
  declare global {
262
224
  interface HTMLElementTagNameMap {
263
225
  'roxy-panchang-table': RoxyPanchangTable;
@@ -0,0 +1,286 @@
1
+ import { css, html, LitElement, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { PLANET_GLYPH } from '../tokens/index.js';
4
+ import type { ShadbalaResponse } from '../types/index.js';
5
+ import { baseStyles } from '../utils/base-styles.js';
6
+ import { formatNumber } from '../utils/format.js';
7
+ import { capitalize } from '../utils/string.js';
8
+
9
+ type Planet = ShadbalaResponse['planets'][number];
10
+
11
+ /** CSS variable and display name for each of the 6 bala components. */
12
+ const BALA_COMPONENTS: Array<{
13
+ key: keyof Pick<
14
+ Planet,
15
+ | 'sthanaBala'
16
+ | 'digBala'
17
+ | 'kalaBala'
18
+ | 'chestaBala'
19
+ | 'naisargikaBala'
20
+ | 'drikBala'
21
+ >;
22
+ label: string;
23
+ color: string;
24
+ }> = [
25
+ { key: 'sthanaBala', label: 'Sthana', color: 'var(--roxy-info, #0284c7)' },
26
+ { key: 'digBala', label: 'Dig', color: 'var(--roxy-success, #16a34a)' },
27
+ { key: 'kalaBala', label: 'Kala', color: 'var(--roxy-warning, #ea580c)' },
28
+ { key: 'chestaBala', label: 'Chesta', color: 'var(--roxy-accent, #f59e0b)' },
29
+ {
30
+ key: 'naisargikaBala',
31
+ label: 'Naisargika',
32
+ color: 'var(--roxy-secondary, #475569)',
33
+ },
34
+ { key: 'drikBala', label: 'Drik', color: 'var(--roxy-danger, #dc2626)' },
35
+ ];
36
+
37
+ /**
38
+ * Shadbala six-fold planetary strength table with stacked bar visualization.
39
+ * Pass `data` from /vedic-astrology/shadbala.
40
+ */
41
+ @customElement('roxy-shadbala-table')
42
+ export class RoxyShadbalaTable extends LitElement {
43
+ static styles = [
44
+ baseStyles,
45
+ css`
46
+ .wrap {
47
+ display: grid;
48
+ gap: var(--roxy-space-md, 1rem);
49
+ }
50
+
51
+ .head {
52
+ display: flex;
53
+ justify-content: space-between;
54
+ align-items: baseline;
55
+ gap: var(--roxy-space-md, 1rem);
56
+ flex-wrap: wrap;
57
+ }
58
+
59
+ .title {
60
+ font-size: var(--roxy-text-lg, 1.125rem);
61
+ font-weight: var(--roxy-weight-bold, 600);
62
+ margin: 0;
63
+ }
64
+
65
+ .subtitle {
66
+ color: var(--roxy-muted, #71717a);
67
+ font-size: var(--roxy-text-sm, 0.875rem);
68
+ margin: 0;
69
+ }
70
+
71
+ .planet-row {
72
+ display: grid;
73
+ grid-template-columns: 8rem 1fr auto;
74
+ align-items: center;
75
+ gap: var(--roxy-space-sm, 0.5rem);
76
+ padding: var(--roxy-space-sm, 0.5rem) 0;
77
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
78
+ }
79
+
80
+ .planet-row:last-of-type {
81
+ border-bottom: none;
82
+ }
83
+
84
+ .planet-label {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 6px;
88
+ font-size: var(--roxy-text-sm, 0.875rem);
89
+ font-weight: var(--roxy-weight-bold, 600);
90
+ }
91
+
92
+ .glyph {
93
+ font-size: 1.2em;
94
+ line-height: 1;
95
+ }
96
+
97
+ .bar-wrap {
98
+ display: flex;
99
+ flex-direction: column;
100
+ gap: 4px;
101
+ }
102
+
103
+ .bar {
104
+ display: flex;
105
+ height: 12px;
106
+ border-radius: var(--roxy-radius-sm, 4px);
107
+ overflow: hidden;
108
+ background: var(--roxy-border, #e4e4e7);
109
+ }
110
+
111
+ .bar-segment {
112
+ height: 100%;
113
+ transition: flex-grow var(--roxy-motion-duration, 200ms)
114
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
115
+ }
116
+
117
+ .pills {
118
+ display: flex;
119
+ flex-direction: column;
120
+ align-items: flex-end;
121
+ gap: 4px;
122
+ }
123
+
124
+ .rupas-label {
125
+ font-variant-numeric: tabular-nums;
126
+ font-size: var(--roxy-text-xs, 0.75rem);
127
+ color: var(--roxy-muted, #71717a);
128
+ white-space: nowrap;
129
+ }
130
+
131
+ .adequacy-badge {
132
+ display: inline-block;
133
+ padding: 1px 6px;
134
+ border-radius: var(--roxy-radius-full, 9999px);
135
+ font-size: var(--roxy-text-xs, 0.75rem);
136
+ font-weight: var(--roxy-weight-bold, 600);
137
+ }
138
+
139
+ .adequacy-badge--adequate {
140
+ background: color-mix(in srgb, var(--roxy-success, #16a34a) 12%, transparent);
141
+ color: var(--roxy-success-fg, #166534);
142
+ }
143
+
144
+ .adequacy-badge--weak {
145
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 12%, transparent);
146
+ color: var(--roxy-danger-fg, #991b1b);
147
+ }
148
+
149
+ .rank-badge {
150
+ font-size: var(--roxy-text-xs, 0.75rem);
151
+ color: var(--roxy-accent-fg, #b45309);
152
+ font-weight: var(--roxy-weight-bold, 600);
153
+ }
154
+
155
+ .legend {
156
+ display: flex;
157
+ flex-wrap: wrap;
158
+ gap: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
159
+ border-top: 1px solid var(--roxy-border, #e4e4e7);
160
+ padding-top: var(--roxy-space-sm, 0.5rem);
161
+ }
162
+
163
+ .legend-row {
164
+ display: flex;
165
+ align-items: center;
166
+ gap: 6px;
167
+ font-size: var(--roxy-text-xs, 0.75rem);
168
+ color: var(--roxy-muted, #71717a);
169
+ }
170
+
171
+ .legend-swatch {
172
+ display: inline-block;
173
+ width: 10px;
174
+ height: 10px;
175
+ border-radius: var(--roxy-radius-sm, 4px);
176
+ flex-shrink: 0;
177
+ }
178
+
179
+ @container (max-width: 480px) {
180
+ .planet-row {
181
+ grid-template-columns: 6rem 1fr;
182
+ grid-template-rows: auto auto;
183
+ }
184
+ .pills {
185
+ grid-column: 1 / -1;
186
+ flex-direction: row;
187
+ align-items: center;
188
+ justify-content: flex-start;
189
+ }
190
+ }
191
+ `,
192
+ ];
193
+
194
+ @property({ attribute: false })
195
+ data: ShadbalaResponse | null = null;
196
+
197
+ render() {
198
+ if (!this.data?.planets?.length) {
199
+ return html`<div class="roxy-empty" role="status">No shadbala data</div>`;
200
+ }
201
+
202
+ const sorted = [...this.data.planets].sort(
203
+ (a, b) => a.relativeRank - b.relativeRank,
204
+ );
205
+
206
+ return html`<div class="wrap" aria-label="Shadbala planetary strength">
207
+ <div class="head">
208
+ <h2 class="title">Shadbala</h2>
209
+ <p class="subtitle">${sorted.length} planets ranked by strength</p>
210
+ </div>
211
+
212
+ <div role="list" aria-label="Planet strength bars">
213
+ ${sorted.map((p) => this.renderPlanetRow(p))}
214
+ </div>
215
+
216
+ <div class="legend" aria-label="Strength component legend">
217
+ ${BALA_COMPONENTS.map(
218
+ (b) => html`<div class="legend-row">
219
+ <span
220
+ class="legend-swatch"
221
+ style="background: ${b.color}"
222
+ aria-hidden="true"
223
+ ></span>
224
+ ${b.label}
225
+ </div>`,
226
+ )}
227
+ </div>
228
+ </div>`;
229
+ }
230
+
231
+ private renderPlanetRow(p: Planet) {
232
+ const glyph = PLANET_GLYPH[capitalize(p.planet)] ?? '';
233
+
234
+ // Compute positive component values (drikBala can be negative)
235
+ const values = BALA_COMPONENTS.map((b) => Math.max(0, p[b.key] as number));
236
+ const total = values.reduce((s, v) => s + v, 0);
237
+
238
+ const isAdequate =
239
+ typeof p.strengthRatio === 'number' && p.strengthRatio >= 1;
240
+ const badgeClass = isAdequate
241
+ ? 'adequacy-badge--adequate'
242
+ : 'adequacy-badge--weak';
243
+ const badgeLabel = isAdequate ? 'adequate' : 'weak';
244
+
245
+ const rupasStr =
246
+ formatNumber(p.totalRupas, 2) && formatNumber(p.minRequired, 2)
247
+ ? `${formatNumber(p.totalRupas, 2)} / ${formatNumber(p.minRequired, 2)} R`
248
+ : '';
249
+
250
+ return html`<div class="planet-row" role="listitem" aria-label="${p.planet} shadbala">
251
+ <div class="planet-label">
252
+ <span class="glyph" aria-hidden="true">${glyph}</span>
253
+ ${p.planet}
254
+ <span class="rank-badge" aria-label="rank ${p.relativeRank}">#${p.relativeRank}</span>
255
+ </div>
256
+ <div class="bar-wrap">
257
+ <div class="bar" role="img" aria-label="Strength components for ${p.planet}">
258
+ ${
259
+ total > 0
260
+ ? BALA_COMPONENTS.map((b, i) => {
261
+ const v = values[i];
262
+ if (v <= 0) return nothing;
263
+ const grow = (v / total) * 100;
264
+ return html`<div
265
+ class="bar-segment"
266
+ style="flex-grow: ${grow}; background: ${b.color};"
267
+ title="${b.label}: ${formatNumber(v, 1)}"
268
+ ></div>`;
269
+ })
270
+ : nothing
271
+ }
272
+ </div>
273
+ </div>
274
+ <div class="pills">
275
+ ${rupasStr ? html`<span class="rupas-label">${rupasStr}</span>` : nothing}
276
+ <span class="${`adequacy-badge ${badgeClass}`}">${badgeLabel}</span>
277
+ </div>
278
+ </div>`;
279
+ }
280
+ }
281
+
282
+ declare global {
283
+ interface HTMLElementTagNameMap {
284
+ 'roxy-shadbala-table': RoxyShadbalaTable;
285
+ }
286
+ }