@roxyapi/ui 0.0.1 → 0.1.2

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 (168) hide show
  1. package/AGENTS.md +169 -0
  2. package/LICENSE +21 -0
  3. package/README.md +198 -0
  4. package/THEMING.md +129 -0
  5. package/dist/cdn/components/biorhythm-chart.js +261 -0
  6. package/dist/cdn/components/biorhythm-chart.js.map +7 -0
  7. package/dist/cdn/components/compatibility-card.js +257 -0
  8. package/dist/cdn/components/compatibility-card.js.map +7 -0
  9. package/dist/cdn/components/dasha-timeline.js +244 -0
  10. package/dist/cdn/components/dasha-timeline.js.map +7 -0
  11. package/dist/cdn/components/data.js +258 -0
  12. package/dist/cdn/components/data.js.map +7 -0
  13. package/dist/cdn/components/dosha-card.js +254 -0
  14. package/dist/cdn/components/dosha-card.js.map +7 -0
  15. package/dist/cdn/components/endpoint-form.js +253 -0
  16. package/dist/cdn/components/endpoint-form.js.map +7 -0
  17. package/dist/cdn/components/guna-milan.js +256 -0
  18. package/dist/cdn/components/guna-milan.js.map +7 -0
  19. package/dist/cdn/components/hexagram.js +275 -0
  20. package/dist/cdn/components/hexagram.js.map +7 -0
  21. package/dist/cdn/components/horoscope-card.js +302 -0
  22. package/dist/cdn/components/horoscope-card.js.map +7 -0
  23. package/dist/cdn/components/kp-planets-table.js +224 -0
  24. package/dist/cdn/components/kp-planets-table.js.map +7 -0
  25. package/dist/cdn/components/location-search.js +267 -0
  26. package/dist/cdn/components/location-search.js.map +7 -0
  27. package/dist/cdn/components/moon-phase.js +251 -0
  28. package/dist/cdn/components/moon-phase.js.map +7 -0
  29. package/dist/cdn/components/natal-chart.js +237 -0
  30. package/dist/cdn/components/natal-chart.js.map +7 -0
  31. package/dist/cdn/components/numerology-card.js +252 -0
  32. package/dist/cdn/components/numerology-card.js.map +7 -0
  33. package/dist/cdn/components/panchang-table.js +234 -0
  34. package/dist/cdn/components/panchang-table.js.map +7 -0
  35. package/dist/cdn/components/synastry-chart.js +303 -0
  36. package/dist/cdn/components/synastry-chart.js.map +7 -0
  37. package/dist/cdn/components/tarot-card.js +260 -0
  38. package/dist/cdn/components/tarot-card.js.map +7 -0
  39. package/dist/cdn/components/tarot-spread.js +261 -0
  40. package/dist/cdn/components/tarot-spread.js.map +7 -0
  41. package/dist/cdn/components/vedic-kundli.js +189 -0
  42. package/dist/cdn/components/vedic-kundli.js.map +7 -0
  43. package/dist/cdn/roxy-ui.js +2552 -0
  44. package/dist/cdn/roxy-ui.js.map +7 -0
  45. package/dist/cdn/widgets.js +114 -0
  46. package/dist/components/biorhythm-chart.d.ts +66 -0
  47. package/dist/components/biorhythm-chart.d.ts.map +1 -0
  48. package/dist/components/biorhythm-chart.js +318 -0
  49. package/dist/components/biorhythm-chart.js.map +7 -0
  50. package/dist/components/compatibility-card.d.ts +46 -0
  51. package/dist/components/compatibility-card.d.ts.map +1 -0
  52. package/dist/components/compatibility-card.js +279 -0
  53. package/dist/components/compatibility-card.js.map +7 -0
  54. package/dist/components/dasha-timeline.d.ts +53 -0
  55. package/dist/components/dasha-timeline.d.ts.map +1 -0
  56. package/dist/components/dasha-timeline.js +269 -0
  57. package/dist/components/dasha-timeline.js.map +7 -0
  58. package/dist/components/data.d.ts +40 -0
  59. package/dist/components/data.d.ts.map +1 -0
  60. package/dist/components/data.js +339 -0
  61. package/dist/components/data.js.map +7 -0
  62. package/dist/components/dosha-card.d.ts +35 -0
  63. package/dist/components/dosha-card.d.ts.map +1 -0
  64. package/dist/components/dosha-card.js +278 -0
  65. package/dist/components/dosha-card.js.map +7 -0
  66. package/dist/components/endpoint-form.d.ts +39 -0
  67. package/dist/components/endpoint-form.d.ts.map +1 -0
  68. package/dist/components/endpoint-form.js +432 -0
  69. package/dist/components/endpoint-form.js.map +7 -0
  70. package/dist/components/guna-milan.d.ts +35 -0
  71. package/dist/components/guna-milan.d.ts.map +1 -0
  72. package/dist/components/guna-milan.js +302 -0
  73. package/dist/components/guna-milan.js.map +7 -0
  74. package/dist/components/hexagram.d.ts +47 -0
  75. package/dist/components/hexagram.d.ts.map +1 -0
  76. package/dist/components/hexagram.js +334 -0
  77. package/dist/components/hexagram.js.map +7 -0
  78. package/dist/components/horoscope-card.d.ts +38 -0
  79. package/dist/components/horoscope-card.d.ts.map +1 -0
  80. package/dist/components/horoscope-card.js +332 -0
  81. package/dist/components/horoscope-card.js.map +7 -0
  82. package/dist/components/kp-planets-table.d.ts +36 -0
  83. package/dist/components/kp-planets-table.d.ts.map +1 -0
  84. package/dist/components/kp-planets-table.js +227 -0
  85. package/dist/components/kp-planets-table.js.map +7 -0
  86. package/dist/components/location-search.d.ts +56 -0
  87. package/dist/components/location-search.d.ts.map +1 -0
  88. package/dist/components/location-search.js +401 -0
  89. package/dist/components/location-search.js.map +7 -0
  90. package/dist/components/moon-phase.d.ts +38 -0
  91. package/dist/components/moon-phase.d.ts.map +1 -0
  92. package/dist/components/moon-phase.js +284 -0
  93. package/dist/components/moon-phase.js.map +7 -0
  94. package/dist/components/natal-chart.d.ts +65 -0
  95. package/dist/components/natal-chart.d.ts.map +1 -0
  96. package/dist/components/natal-chart.js +407 -0
  97. package/dist/components/natal-chart.js.map +7 -0
  98. package/dist/components/numerology-card.d.ts +55 -0
  99. package/dist/components/numerology-card.d.ts.map +1 -0
  100. package/dist/components/numerology-card.js +274 -0
  101. package/dist/components/numerology-card.js.map +7 -0
  102. package/dist/components/panchang-table.d.ts +77 -0
  103. package/dist/components/panchang-table.d.ts.map +1 -0
  104. package/dist/components/panchang-table.js +285 -0
  105. package/dist/components/panchang-table.js.map +7 -0
  106. package/dist/components/synastry-chart.d.ts +52 -0
  107. package/dist/components/synastry-chart.d.ts.map +1 -0
  108. package/dist/components/synastry-chart.js +415 -0
  109. package/dist/components/synastry-chart.js.map +7 -0
  110. package/dist/components/tarot-card.d.ts +47 -0
  111. package/dist/components/tarot-card.d.ts.map +1 -0
  112. package/dist/components/tarot-card.js +281 -0
  113. package/dist/components/tarot-card.js.map +7 -0
  114. package/dist/components/tarot-spread.d.ts +42 -0
  115. package/dist/components/tarot-spread.d.ts.map +1 -0
  116. package/dist/components/tarot-spread.js +271 -0
  117. package/dist/components/tarot-spread.js.map +7 -0
  118. package/dist/components/vedic-kundli.d.ts +45 -0
  119. package/dist/components/vedic-kundli.d.ts.map +1 -0
  120. package/dist/components/vedic-kundli.js +325 -0
  121. package/dist/components/vedic-kundli.js.map +7 -0
  122. package/dist/index.cjs +4174 -0
  123. package/dist/index.cjs.map +7 -0
  124. package/dist/index.d.ts +30 -0
  125. package/dist/index.d.ts.map +1 -0
  126. package/dist/index.js +4154 -0
  127. package/dist/index.js.map +7 -0
  128. package/dist/manifest.json +24 -0
  129. package/dist/styles/tokens.css +147 -0
  130. package/dist/tokens/index.d.ts +17 -0
  131. package/dist/tokens/index.d.ts.map +1 -0
  132. package/dist/utils/base-styles.d.ts +6 -0
  133. package/dist/utils/base-styles.d.ts.map +1 -0
  134. package/dist/utils/debounce.d.ts +5 -0
  135. package/dist/utils/debounce.d.ts.map +1 -0
  136. package/dist/utils/degree.d.ts +29 -0
  137. package/dist/utils/degree.d.ts.map +1 -0
  138. package/dist/utils/motion.d.ts +13 -0
  139. package/dist/utils/motion.d.ts.map +1 -0
  140. package/package.json +71 -3
  141. package/src/components/biorhythm-chart.ts +290 -0
  142. package/src/components/compatibility-card.ts +231 -0
  143. package/src/components/dasha-timeline.ts +251 -0
  144. package/src/components/data.ts +287 -0
  145. package/src/components/dosha-card.ts +215 -0
  146. package/src/components/endpoint-form.ts +433 -0
  147. package/src/components/guna-milan.ts +245 -0
  148. package/src/components/hexagram.ts +279 -0
  149. package/src/components/horoscope-card.ts +291 -0
  150. package/src/components/kp-planets-table.ts +156 -0
  151. package/src/components/location-search.ts +335 -0
  152. package/src/components/moon-phase.ts +221 -0
  153. package/src/components/natal-chart.ts +298 -0
  154. package/src/components/numerology-card.ts +243 -0
  155. package/src/components/panchang-table.ts +265 -0
  156. package/src/components/synastry-chart.ts +341 -0
  157. package/src/components/tarot-card.ts +235 -0
  158. package/src/components/tarot-spread.ts +224 -0
  159. package/src/components/vedic-kundli.ts +257 -0
  160. package/src/index.ts +61 -0
  161. package/src/styles/tokens.css +147 -0
  162. package/src/tokens/index.ts +130 -0
  163. package/src/types/index.ts +3 -0
  164. package/src/types/types.gen.ts +28526 -0
  165. package/src/utils/base-styles.ts +89 -0
  166. package/src/utils/debounce.ts +13 -0
  167. package/src/utils/degree.ts +64 -0
  168. package/src/utils/motion.ts +18 -0
package/dist/index.js ADDED
@@ -0,0 +1,4154 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // packages/ui/src/components/biorhythm-chart.ts
13
+ import { css as css2, html, LitElement, nothing, svg } from "lit";
14
+ import { customElement, property } from "lit/decorators.js";
15
+
16
+ // packages/ui/src/utils/base-styles.ts
17
+ import { css } from "lit";
18
+ var baseStyles = css`
19
+ :host {
20
+ display: block;
21
+ container-type: inline-size;
22
+ font-family: var(
23
+ --roxy-font-sans,
24
+ system-ui,
25
+ -apple-system,
26
+ BlinkMacSystemFont,
27
+ 'Segoe UI',
28
+ Roboto,
29
+ sans-serif
30
+ );
31
+ color: var(--roxy-fg, #0a0a0a);
32
+ background: transparent;
33
+ font-size: var(--roxy-text-base, 1rem);
34
+ line-height: var(--roxy-leading-normal, 1.5);
35
+ animation: roxy-fade-in var(--roxy-motion-duration, 200ms)
36
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;
37
+ }
38
+
39
+ *,
40
+ *::before,
41
+ *::after {
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ @keyframes roxy-fade-in {
46
+ from {
47
+ opacity: 0;
48
+ transform: translateY(2px);
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ transform: translateY(0);
53
+ }
54
+ }
55
+
56
+ @media (prefers-reduced-motion: reduce) {
57
+ :host {
58
+ animation: none;
59
+ }
60
+ }
61
+
62
+ .roxy-skeleton {
63
+ background: linear-gradient(
64
+ 90deg,
65
+ var(--roxy-border, #e4e4e7) 0%,
66
+ color-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,
67
+ var(--roxy-border, #e4e4e7) 100%
68
+ );
69
+ background-size: 200% 100%;
70
+ animation: roxy-shimmer 1.4s ease-in-out infinite;
71
+ border-radius: var(--roxy-radius-md, 8px);
72
+ }
73
+
74
+ @keyframes roxy-shimmer {
75
+ 0% {
76
+ background-position: 200% 0;
77
+ }
78
+ 100% {
79
+ background-position: -200% 0;
80
+ }
81
+ }
82
+
83
+ @media (prefers-reduced-motion: reduce) {
84
+ .roxy-skeleton {
85
+ animation: none;
86
+ }
87
+ }
88
+
89
+ .roxy-empty {
90
+ padding: var(--roxy-space-lg, 1.5rem);
91
+ color: var(--roxy-muted, #71717a);
92
+ text-align: center;
93
+ font-size: var(--roxy-text-sm, 0.875rem);
94
+ }
95
+
96
+ :host(:focus-within) .roxy-card {
97
+ outline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));
98
+ outline-offset: 2px;
99
+ }
100
+ `;
101
+
102
+ // packages/ui/src/components/biorhythm-chart.ts
103
+ var CYCLE_COLOR = {
104
+ physical: "#dc2626",
105
+ emotional: "#0284c7",
106
+ intellectual: "#16a34a",
107
+ intuitive: "#a855f7",
108
+ aesthetic: "#f59e0b",
109
+ awareness: "#ec4899",
110
+ spiritual: "#14b8a6",
111
+ passion: "#ef4444",
112
+ mastery: "#6366f1",
113
+ wisdom: "#475569"
114
+ };
115
+ var RoxyBiorhythmChart = class extends LitElement {
116
+ constructor() {
117
+ super(...arguments);
118
+ this.data = null;
119
+ this.mode = "daily";
120
+ }
121
+ render() {
122
+ const d = this.data;
123
+ if (!d)
124
+ return html`<div class="roxy-empty" role="status">No biorhythm data</div>`;
125
+ if (this.mode === "critical-days" && d.criticalDays?.length) {
126
+ return this.renderCritical(d);
127
+ }
128
+ if (this.mode === "forecast" && d.days?.length) {
129
+ return this.renderForecast(d);
130
+ }
131
+ return this.renderDaily(d);
132
+ }
133
+ renderDaily(d) {
134
+ const cycles = d.cycles ?? {};
135
+ const entries = Object.entries(cycles);
136
+ return html`<section class="wrap" aria-label="Daily biorhythm">
137
+ <header class="head">
138
+ <h2 class="title">Biorhythm</h2>
139
+ ${typeof d.energyRating === "number" ? html`<span class="energy">Energy ${d.energyRating}/10</span>` : nothing}
140
+ </header>
141
+ <div class="bars" role="list">
142
+ ${entries.map(([cycle, value]) => {
143
+ const v = typeof value === "number" ? value : 0;
144
+ const pct = (v + 1) / 2 * 100;
145
+ const color = CYCLE_COLOR[cycle] ?? "var(--roxy-accent, #f59e0b)";
146
+ return html`<div class="bar" role="listitem">
147
+ <span style="text-transform: capitalize">${cycle}</span>
148
+ <span class="track">
149
+ <span
150
+ class="fill"
151
+ style="width: ${pct}%; background: ${color}"
152
+ ></span>
153
+ </span>
154
+ <span class="value">${(v * 100).toFixed(0)}%</span>
155
+ </div>`;
156
+ })}
157
+ </div>
158
+ ${d.interpretation ? html`<p class="advice">${d.interpretation}</p>` : nothing}
159
+ ${d.advice ? html`<p class="advice">${d.advice}</p>` : nothing}
160
+ ${d.criticalAlerts?.length ? html`<div>
161
+ ${d.criticalAlerts.map((a) => html`<p class="alert">${a}</p>`)}
162
+ </div>` : nothing}
163
+ </section>`;
164
+ }
165
+ renderForecast(d) {
166
+ const days = d.days ?? [];
167
+ if (days.length === 0)
168
+ return html`<div class="roxy-empty" role="status">No forecast</div>`;
169
+ const w = 600;
170
+ const h = 160;
171
+ const xStep = w / Math.max(days.length - 1, 1);
172
+ const cycles = Object.keys(days[0]?.cycles ?? {});
173
+ return html`<section class="wrap" aria-label="Biorhythm forecast">
174
+ <header class="head">
175
+ <h2 class="title">Forecast</h2>
176
+ <span class="energy"
177
+ >${d.startDate ?? ""} - ${d.endDate ?? ""}</span
178
+ >
179
+ </header>
180
+ <svg
181
+ viewBox="0 0 ${w} ${h}"
182
+ role="img"
183
+ aria-label="Biorhythm cycle lines across the forecast window"
184
+ >
185
+ <title>Biorhythm forecast</title>
186
+ <line
187
+ x1="0"
188
+ y1=${h / 2}
189
+ x2=${w}
190
+ y2=${h / 2}
191
+ stroke="var(--roxy-border, #e4e4e7)"
192
+ stroke-width="1"
193
+ />
194
+ ${cycles.map((cycle) => {
195
+ const points = days.map((day, i) => {
196
+ const v = day.cycles?.[cycle] ?? 0;
197
+ const x = i * xStep;
198
+ const y = h / 2 - v * (h / 2 - 8);
199
+ return `${x.toFixed(2)},${y.toFixed(2)}`;
200
+ }).join(" ");
201
+ const color = CYCLE_COLOR[cycle] ?? "#475569";
202
+ return svg`<polyline points=${points} fill="none" stroke=${color} stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />`;
203
+ })}
204
+ </svg>
205
+ ${d.summary?.periodAdvice ? html`<p class="advice">${d.summary.periodAdvice}</p>` : nothing}
206
+ </section>`;
207
+ }
208
+ renderCritical(d) {
209
+ return html`<section class="wrap" aria-label="Critical days">
210
+ <header class="head">
211
+ <h2 class="title">Critical days</h2>
212
+ <span class="energy"
213
+ >${d.totalCriticalDays ?? d.criticalDays?.length ?? 0} total</span
214
+ >
215
+ </header>
216
+ <div>
217
+ ${(d.criticalDays ?? []).map(
218
+ (day) => html`<span class="crit"
219
+ >${day.date} · ${day.cycle ?? ""} ${day.severity ?? ""}</span
220
+ >`
221
+ )}
222
+ </div>
223
+ </section>`;
224
+ }
225
+ };
226
+ RoxyBiorhythmChart.styles = [
227
+ baseStyles,
228
+ css2`
229
+ .wrap {
230
+ display: grid;
231
+ gap: var(--roxy-space-md, 1rem);
232
+ }
233
+ .head {
234
+ display: flex;
235
+ justify-content: space-between;
236
+ align-items: center;
237
+ flex-wrap: wrap;
238
+ gap: var(--roxy-space-sm, 0.5rem);
239
+ }
240
+ .title {
241
+ margin: 0;
242
+ font-size: var(--roxy-text-lg, 1.125rem);
243
+ font-weight: var(--roxy-weight-bold, 600);
244
+ }
245
+ .energy {
246
+ font-variant-numeric: tabular-nums;
247
+ color: var(--roxy-accent-fg, #b45309);
248
+ font-weight: var(--roxy-weight-bold, 600);
249
+ }
250
+ .bars {
251
+ display: grid;
252
+ gap: var(--roxy-space-xs, 0.25rem);
253
+ }
254
+ .bar {
255
+ display: grid;
256
+ grid-template-columns: 8rem 1fr 3.5rem;
257
+ gap: var(--roxy-space-sm, 0.5rem);
258
+ align-items: center;
259
+ font-size: var(--roxy-text-sm, 0.875rem);
260
+ }
261
+ .track {
262
+ height: 14px;
263
+ background: var(--roxy-border, #e4e4e7);
264
+ border-radius: var(--roxy-radius-full, 9999px);
265
+ overflow: hidden;
266
+ position: relative;
267
+ }
268
+ .fill {
269
+ display: block;
270
+ height: 100%;
271
+ transition:
272
+ width var(--roxy-motion-duration, 200ms)
273
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
274
+ }
275
+ .value {
276
+ font-variant-numeric: tabular-nums;
277
+ text-align: right;
278
+ color: var(--roxy-muted, #71717a);
279
+ }
280
+ .advice {
281
+ color: var(--roxy-fg, #0a0a0a);
282
+ }
283
+ .alert {
284
+ background: color-mix(in srgb, var(--roxy-warning, #ea580c) 12%, transparent);
285
+ border: 1px solid color-mix(in srgb, var(--roxy-warning, #ea580c) 32%, transparent);
286
+ border-radius: var(--roxy-radius-md, 8px);
287
+ padding: var(--roxy-space-sm, 0.5rem);
288
+ font-size: var(--roxy-text-sm, 0.875rem);
289
+ margin: 0;
290
+ }
291
+ svg {
292
+ display: block;
293
+ width: 100%;
294
+ height: auto;
295
+ }
296
+ .crit {
297
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 12%, transparent);
298
+ border-radius: var(--roxy-radius-sm, 4px);
299
+ padding: 4px 8px;
300
+ font-size: var(--roxy-text-xs, 0.75rem);
301
+ display: inline-block;
302
+ margin: 2px;
303
+ }
304
+ `
305
+ ];
306
+ __decorateClass([
307
+ property({ attribute: false })
308
+ ], RoxyBiorhythmChart.prototype, "data", 2);
309
+ __decorateClass([
310
+ property({ type: String, reflect: true })
311
+ ], RoxyBiorhythmChart.prototype, "mode", 2);
312
+ RoxyBiorhythmChart = __decorateClass([
313
+ customElement("roxy-biorhythm-chart")
314
+ ], RoxyBiorhythmChart);
315
+
316
+ // packages/ui/src/components/compatibility-card.ts
317
+ import { css as css3, html as html2, LitElement as LitElement2, nothing as nothing2 } from "lit";
318
+ import { customElement as customElement2, property as property2 } from "lit/decorators.js";
319
+ var RoxyCompatibilityCard = class extends LitElement2 {
320
+ constructor() {
321
+ super(...arguments);
322
+ this.data = null;
323
+ this.mode = "astrology";
324
+ }
325
+ getBreakdown() {
326
+ const d = this.data;
327
+ if (!d) return {};
328
+ if (d.categoryScores) return d.categoryScores;
329
+ if (d.categoryBreakdown) return d.categoryBreakdown;
330
+ const inferred = {};
331
+ if (typeof d.emotional === "number") inferred.emotional = d.emotional;
332
+ if (typeof d.communication === "number")
333
+ inferred.communication = d.communication;
334
+ if (typeof d.romance === "number") inferred.romance = d.romance;
335
+ if (d.elementBalance) Object.assign(inferred, d.elementBalance);
336
+ return inferred;
337
+ }
338
+ render() {
339
+ const d = this.data;
340
+ if (!d)
341
+ return html2`<div class="roxy-empty" role="status">No compatibility data</div>`;
342
+ const score = d.overallScore ?? d.score;
343
+ const breakdown = this.getBreakdown();
344
+ return html2`<article
345
+ class="card"
346
+ aria-label=${`Compatibility (${this.mode})`}
347
+ >
348
+ <div class="head">
349
+ <h2>${this.mode} compatibility</h2>
350
+ <div>
351
+ ${typeof score === "number" ? html2`<div class="score">${score}</div>` : nothing2}
352
+ ${d.rating ? html2`<div class="rating">${d.rating}</div>` : nothing2}
353
+ </div>
354
+ </div>
355
+
356
+ ${Object.keys(breakdown).length > 0 ? html2`<div role="list">
357
+ ${Object.entries(breakdown).map(
358
+ ([k, v]) => html2`<div class="bar-row" role="listitem">
359
+ <span style="text-transform: capitalize">${k}</span>
360
+ <span class="bar"
361
+ ><span style="width: ${Math.max(0, Math.min(100, v))}%"></span
362
+ ></span>
363
+ <span>${v}</span>
364
+ </div>`
365
+ )}
366
+ </div>` : nothing2}
367
+ ${d.relationshipArchetype ? html2`<p>
368
+ <span class="archetype">${d.relationshipArchetype}</span>
369
+ </p>` : nothing2}
370
+ ${d.summary ? html2`<p>${d.summary}</p>` : nothing2}
371
+ ${d.advice ? html2`<p>${d.advice}</p>` : nothing2}
372
+ ${(d.strengths?.length ?? 0) > 0 || (d.challenges?.length ?? 0) > 0 ? html2`<div class="lists">
373
+ ${d.strengths?.length ? html2`<div>
374
+ <h3>Strengths</h3>
375
+ <ul>
376
+ ${d.strengths.map((s) => html2`<li>${s}</li>`)}
377
+ </ul>
378
+ </div>` : nothing2}
379
+ ${d.challenges?.length ? html2`<div>
380
+ <h3>Challenges</h3>
381
+ <ul>
382
+ ${d.challenges.map((s) => html2`<li>${s}</li>`)}
383
+ </ul>
384
+ </div>` : nothing2}
385
+ ${d.keyAspects?.length ? html2`<div>
386
+ <h3>Key aspects</h3>
387
+ <ul>
388
+ ${d.keyAspects.map((s) => html2`<li>${s}</li>`)}
389
+ </ul>
390
+ </div>` : nothing2}
391
+ </div>` : nothing2}
392
+ </article>`;
393
+ }
394
+ };
395
+ RoxyCompatibilityCard.styles = [
396
+ baseStyles,
397
+ css3`
398
+ .card {
399
+ background: var(--roxy-bg, #fff);
400
+ border: 1px solid var(--roxy-border, #e4e4e7);
401
+ border-radius: var(--roxy-radius-md, 8px);
402
+ padding: var(--roxy-space-lg, 1.5rem);
403
+ box-shadow: var(--roxy-shadow-sm);
404
+ display: grid;
405
+ gap: var(--roxy-space-md, 1rem);
406
+ }
407
+
408
+ .head {
409
+ display: grid;
410
+ grid-template-columns: 1fr auto;
411
+ align-items: center;
412
+ gap: var(--roxy-space-md, 1rem);
413
+ }
414
+ .head h2 {
415
+ margin: 0;
416
+ font-size: var(--roxy-text-lg, 1.125rem);
417
+ font-weight: var(--roxy-weight-bold, 600);
418
+ text-transform: capitalize;
419
+ }
420
+
421
+ .score {
422
+ font-variant-numeric: tabular-nums;
423
+ font-size: 2rem;
424
+ font-weight: var(--roxy-weight-bold, 600);
425
+ color: var(--roxy-accent-fg, #b45309);
426
+ line-height: 1;
427
+ }
428
+ .rating {
429
+ color: var(--roxy-secondary, #475569);
430
+ font-size: var(--roxy-text-sm, 0.875rem);
431
+ }
432
+
433
+ .bar-row {
434
+ display: grid;
435
+ grid-template-columns: 8rem 1fr 3.5rem;
436
+ gap: var(--roxy-space-sm, 0.5rem);
437
+ align-items: center;
438
+ font-size: var(--roxy-text-sm, 0.875rem);
439
+ }
440
+ .bar {
441
+ height: 8px;
442
+ background: var(--roxy-border, #e4e4e7);
443
+ border-radius: var(--roxy-radius-full, 9999px);
444
+ overflow: hidden;
445
+ }
446
+ .bar > span {
447
+ display: block;
448
+ height: 100%;
449
+ background: var(--roxy-accent, #f59e0b);
450
+ transition:
451
+ width var(--roxy-motion-duration, 200ms)
452
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
453
+ }
454
+ .bar-row > span:last-child {
455
+ font-variant-numeric: tabular-nums;
456
+ color: var(--roxy-muted, #71717a);
457
+ text-align: right;
458
+ }
459
+
460
+ .archetype {
461
+ color: var(--roxy-info, #0284c7);
462
+ font-weight: var(--roxy-weight-bold, 600);
463
+ }
464
+
465
+ .lists {
466
+ display: grid;
467
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
468
+ gap: var(--roxy-space-md, 1rem);
469
+ }
470
+ .lists h3 {
471
+ margin: 0 0 var(--roxy-space-xs, 0.25rem);
472
+ font-size: var(--roxy-text-xs, 0.75rem);
473
+ color: var(--roxy-muted, #71717a);
474
+ text-transform: uppercase;
475
+ letter-spacing: 0.06em;
476
+ }
477
+ .lists ul {
478
+ margin: 0;
479
+ padding-left: var(--roxy-space-md, 1rem);
480
+ }
481
+ `
482
+ ];
483
+ __decorateClass([
484
+ property2({ attribute: false })
485
+ ], RoxyCompatibilityCard.prototype, "data", 2);
486
+ __decorateClass([
487
+ property2({ type: String, reflect: true })
488
+ ], RoxyCompatibilityCard.prototype, "mode", 2);
489
+ RoxyCompatibilityCard = __decorateClass([
490
+ customElement2("roxy-compatibility-card")
491
+ ], RoxyCompatibilityCard);
492
+
493
+ // packages/ui/src/components/dasha-timeline.ts
494
+ import { css as css4, html as html3, LitElement as LitElement3, nothing as nothing3 } from "lit";
495
+ import { customElement as customElement3, property as property3 } from "lit/decorators.js";
496
+ var RoxyDashaTimeline = class extends LitElement3 {
497
+ constructor() {
498
+ super(...arguments);
499
+ this.data = null;
500
+ this.period = "current";
501
+ }
502
+ render() {
503
+ const d = this.data;
504
+ if (!d)
505
+ return html3`<div class="roxy-empty" role="status">No dasha data</div>`;
506
+ const periods = this.collectPeriods(d);
507
+ const maxYears = periods.length ? Math.max(...periods.map((p) => p.durationYears ?? p.years ?? 1)) : 0;
508
+ return html3`<div class="wrap" aria-label="Dasha timeline">
509
+ <header class="head">
510
+ <h2 class="title">
511
+ ${this.period === "major" ? "Vimshottari Mahadasha" : this.period === "sub" ? "Antardasha" : "Active dashas"}
512
+ </h2>
513
+ ${d.nakshatraName || d.moonNakshatra ? html3`<div class="nakshatra">
514
+ Moon nakshatra: ${d.nakshatraName ?? d.moonNakshatra}
515
+ ${d.nakshatraLord ? html3`(lord ${d.nakshatraLord})` : nothing3}
516
+ </div>` : nothing3}
517
+ </header>
518
+
519
+ ${this.period === "current" ? this.renderCurrent(d) : nothing3}
520
+ ${periods.length > 0 ? html3`<div class="timeline" role="list">
521
+ ${periods.map((p) => this.renderBar(p, maxYears))}
522
+ </div>` : nothing3}
523
+ </div>`;
524
+ }
525
+ renderCurrent(d) {
526
+ return html3`<div class="current">
527
+ ${d.mahadasha ? html3`<div>
528
+ <span>Mahadasha</span>
529
+ <strong>${d.mahadasha.lord ?? d.mahadasha.mahadashaLord}</strong>
530
+ ${typeof d.remainingInMahadasha === "number" ? html3`<small>${d.remainingInMahadasha.toFixed(1)} years left</small>` : nothing3}
531
+ </div>` : nothing3}
532
+ ${d.antardasha ? html3`<div>
533
+ <span>Antardasha</span>
534
+ <strong>${d.antardasha.lord ?? d.antardasha.antardashaLord}</strong>
535
+ ${typeof d.remainingInAntardasha === "number" ? html3`<small>${d.remainingInAntardasha.toFixed(1)} years left</small>` : nothing3}
536
+ </div>` : nothing3}
537
+ ${d.pratyantardasha ? html3`<div>
538
+ <span>Pratyantardasha</span>
539
+ <strong
540
+ >${d.pratyantardasha.lord ?? d.pratyantardasha.pratyantardashaLord}</strong
541
+ >
542
+ ${typeof d.remainingInPratyantardasha === "number" ? html3`<small
543
+ >${d.remainingInPratyantardasha.toFixed(2)} years left</small
544
+ >` : nothing3}
545
+ </div>` : nothing3}
546
+ </div>`;
547
+ }
548
+ collectPeriods(d) {
549
+ if (this.period === "major" && d.mahadashas?.length) return d.mahadashas;
550
+ if (this.period === "sub" && d.antardashas?.length) return d.antardashas;
551
+ return d.mahadashas ?? d.antardashas ?? [];
552
+ }
553
+ renderBar(p, max) {
554
+ const lord = p.lord ?? p.mahadashaLord ?? p.antardashaLord ?? p.planet ?? "";
555
+ const years = p.durationYears ?? p.years ?? 0;
556
+ const width = max > 0 ? years / max * 100 : 0;
557
+ return html3`<div class="bar" role="listitem">
558
+ <span>${lord}</span>
559
+ <span class="bar-track"><span style="width: ${width}%"></span></span>
560
+ <span class="dates">
561
+ ${p.startDate ? formatYear(p.startDate) : ""}
562
+ ${p.endDate ? html3`- ${formatYear(p.endDate)}` : ""}
563
+ </span>
564
+ </div>`;
565
+ }
566
+ };
567
+ RoxyDashaTimeline.styles = [
568
+ baseStyles,
569
+ css4`
570
+ .wrap {
571
+ display: grid;
572
+ gap: var(--roxy-space-md, 1rem);
573
+ }
574
+ .head {
575
+ display: flex;
576
+ justify-content: space-between;
577
+ align-items: center;
578
+ flex-wrap: wrap;
579
+ gap: var(--roxy-space-sm, 0.5rem);
580
+ }
581
+ .title {
582
+ margin: 0;
583
+ font-size: var(--roxy-text-lg, 1.125rem);
584
+ font-weight: var(--roxy-weight-bold, 600);
585
+ }
586
+ .nakshatra {
587
+ color: var(--roxy-muted, #71717a);
588
+ font-size: var(--roxy-text-sm, 0.875rem);
589
+ }
590
+
591
+ .current {
592
+ display: grid;
593
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
594
+ gap: var(--roxy-space-md, 1rem);
595
+ background: var(--roxy-bg, #fff);
596
+ border: 1px solid var(--roxy-border, #e4e4e7);
597
+ border-radius: var(--roxy-radius-md, 8px);
598
+ padding: var(--roxy-space-md, 1rem);
599
+ box-shadow: var(--roxy-shadow-sm);
600
+ }
601
+ .current div span:first-child {
602
+ display: block;
603
+ color: var(--roxy-muted, #71717a);
604
+ font-size: var(--roxy-text-xs, 0.75rem);
605
+ text-transform: uppercase;
606
+ letter-spacing: 0.06em;
607
+ }
608
+ .current div strong {
609
+ font-size: var(--roxy-text-base, 1rem);
610
+ color: var(--roxy-fg, #0a0a0a);
611
+ }
612
+
613
+ .timeline {
614
+ display: grid;
615
+ gap: var(--roxy-space-xs, 0.25rem);
616
+ }
617
+ .bar {
618
+ display: grid;
619
+ grid-template-columns: 5rem 1fr 8rem;
620
+ gap: var(--roxy-space-sm, 0.5rem);
621
+ align-items: center;
622
+ font-size: var(--roxy-text-sm, 0.875rem);
623
+ }
624
+ .bar-track {
625
+ height: 14px;
626
+ background: var(--roxy-border, #e4e4e7);
627
+ border-radius: var(--roxy-radius-full, 9999px);
628
+ overflow: hidden;
629
+ }
630
+ .bar-track > span {
631
+ display: block;
632
+ height: 100%;
633
+ background: var(--roxy-accent, #f59e0b);
634
+ transition:
635
+ width var(--roxy-motion-duration, 200ms)
636
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
637
+ }
638
+ .dates {
639
+ color: var(--roxy-muted, #71717a);
640
+ font-size: var(--roxy-text-xs, 0.75rem);
641
+ font-variant-numeric: tabular-nums;
642
+ text-align: right;
643
+ }
644
+ `
645
+ ];
646
+ __decorateClass([
647
+ property3({ attribute: false })
648
+ ], RoxyDashaTimeline.prototype, "data", 2);
649
+ __decorateClass([
650
+ property3({ type: String, reflect: true })
651
+ ], RoxyDashaTimeline.prototype, "period", 2);
652
+ RoxyDashaTimeline = __decorateClass([
653
+ customElement3("roxy-dasha-timeline")
654
+ ], RoxyDashaTimeline);
655
+ function formatYear(s) {
656
+ const m = s.match(/^(\d{4})/);
657
+ return m ? m[1] : s;
658
+ }
659
+
660
+ // packages/ui/src/components/data.ts
661
+ import { css as css5, html as html4, LitElement as LitElement4, nothing as nothing4 } from "lit";
662
+ import { customElement as customElement4, property as property4 } from "lit/decorators.js";
663
+ var TITLE_KEYS = ["title", "name", "label", "heading", "overview", "summary"];
664
+ var IMAGE_KEYS = ["imageUrl", "image", "icon", "symbol"];
665
+ var SKIP_KEYS = ["imageUrl", "image"];
666
+ var RoxyData = class extends LitElement4 {
667
+ constructor() {
668
+ super(...arguments);
669
+ this.data = null;
670
+ }
671
+ render() {
672
+ if (this.data == null) {
673
+ return html4`<div class="roxy-empty" role="status">No data</div>`;
674
+ }
675
+ return html4`<div
676
+ class="roxy-card"
677
+ aria-label="Generic data display"
678
+ >
679
+ ${this.renderValue(this.data)}
680
+ </div>`;
681
+ }
682
+ renderValue(value) {
683
+ if (value === null || value === void 0) return nothing4;
684
+ if (typeof value === "string") return html4`<p>${value}</p>`;
685
+ if (typeof value === "number" || typeof value === "boolean") {
686
+ return html4`<p>${String(value)}</p>`;
687
+ }
688
+ if (Array.isArray(value)) return this.renderArray(value);
689
+ return this.renderObject(value);
690
+ }
691
+ renderArray(arr) {
692
+ if (arr.length === 0) {
693
+ return html4`<div class="roxy-empty" role="status">Empty list</div>`;
694
+ }
695
+ const allPrimitive = arr.every(
696
+ (v) => v === null || ["string", "number", "boolean"].includes(typeof v)
697
+ );
698
+ if (allPrimitive) {
699
+ return html4`<ul class="roxy-chips">
700
+ ${arr.map((v) => html4`<li>${String(v)}</li>`)}
701
+ </ul>`;
702
+ }
703
+ const allObjects = arr.every(
704
+ (v) => v !== null && typeof v === "object" && !Array.isArray(v)
705
+ );
706
+ if (allObjects) return this.renderTable(arr);
707
+ return html4`<ol>
708
+ ${arr.map((v) => html4`<li>${this.renderValue(v)}</li>`)}
709
+ </ol>`;
710
+ }
711
+ renderTable(rows) {
712
+ const keys = this.collectKeys(rows);
713
+ return html4`<table class="roxy-table" role="table">
714
+ <thead>
715
+ <tr>
716
+ ${keys.map((k) => html4`<th>${this.humanize(k)}</th>`)}
717
+ </tr>
718
+ </thead>
719
+ <tbody>
720
+ ${rows.map(
721
+ (row) => html4`<tr>
722
+ ${keys.map((k) => html4`<td>${this.formatPrimitive(row[k])}</td>`)}
723
+ </tr>`
724
+ )}
725
+ </tbody>
726
+ </table>`;
727
+ }
728
+ renderObject(obj) {
729
+ const titleKey = TITLE_KEYS.find((k) => typeof obj[k] === "string");
730
+ const imageKey = IMAGE_KEYS.find(
731
+ (k) => typeof obj[k] === "string" && obj[k].startsWith("http")
732
+ );
733
+ const summaryKey = titleKey !== "summary" && typeof obj.summary === "string" ? "summary" : null;
734
+ const rows = Object.entries(obj).filter(
735
+ ([k, v]) => k !== titleKey && k !== summaryKey && !SKIP_KEYS.includes(k) && v !== null && v !== void 0
736
+ );
737
+ return html4`
738
+ ${imageKey ? html4`<img
739
+ class="roxy-image"
740
+ src=${String(obj[imageKey])}
741
+ alt=${titleKey ? String(obj[titleKey]) : "illustration"}
742
+ loading="lazy"
743
+ />` : nothing4}
744
+ ${titleKey ? html4`<h3 class="roxy-title">${obj[titleKey]}</h3>` : nothing4}
745
+ ${summaryKey ? html4`<p class="roxy-summary">${obj[summaryKey]}</p>` : nothing4}
746
+ ${rows.length > 0 ? html4`<dl class="roxy-rows">
747
+ ${rows.map(
748
+ ([k, v]) => html4`
749
+ <dt>${this.humanize(k)}</dt>
750
+ <dd>${this.renderField(v)}</dd>
751
+ `
752
+ )}
753
+ </dl>` : nothing4}
754
+ `;
755
+ }
756
+ renderField(value) {
757
+ if (value === null || value === void 0) return "";
758
+ if (typeof value === "string") return value;
759
+ if (typeof value === "number" || typeof value === "boolean")
760
+ return String(value);
761
+ if (Array.isArray(value)) {
762
+ const allPrimitive = value.every(
763
+ (v) => ["string", "number", "boolean"].includes(typeof v)
764
+ );
765
+ if (allPrimitive) {
766
+ return html4`<ul class="roxy-chips">
767
+ ${value.map((v) => html4`<li>${String(v)}</li>`)}
768
+ </ul>`;
769
+ }
770
+ }
771
+ return html4`<roxy-data .data=${value}></roxy-data>`;
772
+ }
773
+ formatPrimitive(value) {
774
+ if (value === null || value === void 0) return "";
775
+ if (typeof value === "string") return value;
776
+ if (typeof value === "number" || typeof value === "boolean")
777
+ return String(value);
778
+ if (Array.isArray(value)) return value.map(String).join(", ");
779
+ return JSON.stringify(value);
780
+ }
781
+ collectKeys(rows) {
782
+ const seen = /* @__PURE__ */ new Set();
783
+ for (const row of rows) {
784
+ for (const k of Object.keys(row)) seen.add(k);
785
+ }
786
+ return Array.from(seen);
787
+ }
788
+ humanize(key) {
789
+ return key.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^\w/, (c) => c.toUpperCase());
790
+ }
791
+ };
792
+ RoxyData.styles = [
793
+ baseStyles,
794
+ css5`
795
+ .roxy-card {
796
+ background: var(--roxy-bg, #fff);
797
+ border: 1px solid var(--roxy-border, #e4e4e7);
798
+ border-radius: var(--roxy-radius-md, 8px);
799
+ padding: var(--roxy-space-md, 1rem);
800
+ box-shadow: var(--roxy-shadow-sm);
801
+ }
802
+
803
+ .roxy-title {
804
+ font-size: var(--roxy-text-lg, 1.125rem);
805
+ font-weight: var(--roxy-weight-bold, 600);
806
+ margin: 0 0 var(--roxy-space-sm, 0.5rem) 0;
807
+ color: var(--roxy-primary, #0f172a);
808
+ letter-spacing: var(--roxy-tracking-tight);
809
+ }
810
+
811
+ .roxy-summary {
812
+ color: var(--roxy-secondary, #475569);
813
+ margin: 0 0 var(--roxy-space-md, 1rem) 0;
814
+ font-size: var(--roxy-text-sm, 0.875rem);
815
+ }
816
+
817
+ dl.roxy-rows {
818
+ margin: 0;
819
+ display: grid;
820
+ grid-template-columns: minmax(8ch, max-content) 1fr;
821
+ gap: var(--roxy-space-xs, 0.25rem) var(--roxy-space-md, 1rem);
822
+ }
823
+ dl.roxy-rows dt {
824
+ color: var(--roxy-muted, #71717a);
825
+ font-size: var(--roxy-text-sm, 0.875rem);
826
+ text-transform: capitalize;
827
+ }
828
+ dl.roxy-rows dd {
829
+ margin: 0;
830
+ color: var(--roxy-fg, #0a0a0a);
831
+ font-size: var(--roxy-text-sm, 0.875rem);
832
+ word-break: break-word;
833
+ }
834
+
835
+ ul.roxy-chips {
836
+ display: flex;
837
+ flex-wrap: wrap;
838
+ gap: var(--roxy-space-xs, 0.25rem);
839
+ padding: 0;
840
+ margin: 0;
841
+ list-style: none;
842
+ }
843
+ ul.roxy-chips li {
844
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
845
+ color: var(--roxy-fg, #0a0a0a);
846
+ padding: 2px 8px;
847
+ border-radius: var(--roxy-radius-full, 9999px);
848
+ font-size: var(--roxy-text-xs, 0.75rem);
849
+ }
850
+
851
+ table.roxy-table {
852
+ width: 100%;
853
+ border-collapse: collapse;
854
+ font-size: var(--roxy-text-sm, 0.875rem);
855
+ }
856
+ table.roxy-table th,
857
+ table.roxy-table td {
858
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
859
+ padding: var(--roxy-space-sm, 0.5rem);
860
+ text-align: left;
861
+ text-transform: none;
862
+ }
863
+ table.roxy-table th {
864
+ color: var(--roxy-muted, #71717a);
865
+ font-weight: var(--roxy-weight-bold, 600);
866
+ text-transform: capitalize;
867
+ font-size: var(--roxy-text-xs, 0.75rem);
868
+ letter-spacing: 0.04em;
869
+ }
870
+
871
+ .roxy-image {
872
+ max-width: 100%;
873
+ height: auto;
874
+ border-radius: var(--roxy-radius-md, 8px);
875
+ margin-bottom: var(--roxy-space-md, 1rem);
876
+ }
877
+
878
+ .roxy-section {
879
+ margin-bottom: var(--roxy-space-md, 1rem);
880
+ }
881
+ .roxy-section h4 {
882
+ font-size: var(--roxy-text-sm, 0.875rem);
883
+ font-weight: var(--roxy-weight-bold, 600);
884
+ color: var(--roxy-secondary, #475569);
885
+ margin: 0 0 var(--roxy-space-xs, 0.25rem) 0;
886
+ text-transform: capitalize;
887
+ }
888
+ `
889
+ ];
890
+ __decorateClass([
891
+ property4({ attribute: false })
892
+ ], RoxyData.prototype, "data", 2);
893
+ RoxyData = __decorateClass([
894
+ customElement4("roxy-data")
895
+ ], RoxyData);
896
+
897
+ // packages/ui/src/components/dosha-card.ts
898
+ import { css as css6, html as html5, LitElement as LitElement5, nothing as nothing5 } from "lit";
899
+ import { customElement as customElement5, property as property5 } from "lit/decorators.js";
900
+ var DOSHA_LABELS = {
901
+ manglik: "Mangal Dosha",
902
+ kalsarpa: "Kaal Sarp Dosha",
903
+ sadhesati: "Sade Sati"
904
+ };
905
+ var RoxyDoshaCard = class extends LitElement5 {
906
+ constructor() {
907
+ super(...arguments);
908
+ this.data = null;
909
+ this.type = "manglik";
910
+ }
911
+ render() {
912
+ const d = this.data;
913
+ if (!d)
914
+ return html5`<div class="roxy-empty" role="status">No dosha data</div>`;
915
+ const present = !!d.present;
916
+ const label = DOSHA_LABELS[this.type] ?? this.type;
917
+ const sevClass = (d.severity ?? "").toLowerCase();
918
+ return html5`<article
919
+ class="card"
920
+ aria-label=${label}
921
+ >
922
+ <header class="head">
923
+ <h2 class="title">${label}</h2>
924
+ <div style="display:flex; gap:0.5rem; align-items:center;">
925
+ <span class=${`badge ${present ? "present" : "absent"}`}>
926
+ ${present ? "Present" : "Absent"}
927
+ </span>
928
+ ${d.severity ? html5`<span
929
+ class=${`severity ${sevClass}`}
930
+ role="img"
931
+ aria-label=${`Severity ${d.severity}`}
932
+ >
933
+ <span></span><span></span><span></span>
934
+ </span>` : nothing5}
935
+ </div>
936
+ </header>
937
+ ${d.description ? html5`<p class="description">${d.description}</p>` : nothing5}
938
+ ${this.renderEffects(d.effects)}
939
+ ${d.remedies && d.remedies.length > 0 ? html5`<div>
940
+ <h3>Remedies</h3>
941
+ <ul>
942
+ ${d.remedies.map((r) => html5`<li>${r}</li>`)}
943
+ </ul>
944
+ </div>` : nothing5}
945
+ ${d.exceptions && d.exceptions.length > 0 ? html5`<div>
946
+ <h3>Exceptions</h3>
947
+ <ul>
948
+ ${d.exceptions.map((r) => html5`<li>${r}</li>`)}
949
+ </ul>
950
+ </div>` : nothing5}
951
+ </article>`;
952
+ }
953
+ renderEffects(e) {
954
+ if (!e) return nothing5;
955
+ if (typeof e === "string") return html5`<p>${e}</p>`;
956
+ const entries = Object.entries(e).filter(
957
+ ([, v]) => typeof v === "string" && v.length > 0
958
+ );
959
+ if (entries.length === 0) return nothing5;
960
+ return html5`<div class="effects">
961
+ ${entries.map(
962
+ ([k, v]) => html5`<div>
963
+ <h3>${k}</h3>
964
+ <p>${v}</p>
965
+ </div>`
966
+ )}
967
+ </div>`;
968
+ }
969
+ };
970
+ RoxyDoshaCard.styles = [
971
+ baseStyles,
972
+ css6`
973
+ .card {
974
+ background: var(--roxy-bg, #fff);
975
+ border: 1px solid var(--roxy-border, #e4e4e7);
976
+ border-radius: var(--roxy-radius-md, 8px);
977
+ padding: var(--roxy-space-lg, 1.5rem);
978
+ box-shadow: var(--roxy-shadow-sm);
979
+ display: grid;
980
+ gap: var(--roxy-space-md, 1rem);
981
+ }
982
+ .head {
983
+ display: flex;
984
+ align-items: center;
985
+ justify-content: space-between;
986
+ gap: var(--roxy-space-md, 1rem);
987
+ flex-wrap: wrap;
988
+ }
989
+ .title {
990
+ margin: 0;
991
+ font-size: var(--roxy-text-lg, 1.125rem);
992
+ font-weight: var(--roxy-weight-bold, 600);
993
+ text-transform: capitalize;
994
+ }
995
+ .badge {
996
+ display: inline-flex;
997
+ align-items: center;
998
+ gap: var(--roxy-space-xs, 0.25rem);
999
+ padding: 4px 10px;
1000
+ border-radius: var(--roxy-radius-full, 9999px);
1001
+ font-size: var(--roxy-text-xs, 0.75rem);
1002
+ font-weight: var(--roxy-weight-bold, 600);
1003
+ text-transform: uppercase;
1004
+ letter-spacing: 0.06em;
1005
+ }
1006
+ .badge.absent {
1007
+ background: color-mix(in srgb, var(--roxy-success, #16a34a) 16%, transparent);
1008
+ color: var(--roxy-success, #16a34a);
1009
+ }
1010
+ .badge.present {
1011
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
1012
+ color: var(--roxy-danger, #dc2626);
1013
+ }
1014
+ .severity {
1015
+ display: flex;
1016
+ align-items: center;
1017
+ gap: 4px;
1018
+ }
1019
+ .severity span {
1020
+ width: 14px;
1021
+ height: 4px;
1022
+ border-radius: 2px;
1023
+ background: var(--roxy-border, #e4e4e7);
1024
+ }
1025
+ .severity.mild span:nth-child(1) {
1026
+ background: var(--roxy-warning, #ea580c);
1027
+ }
1028
+ .severity.moderate span:nth-child(-n + 2) {
1029
+ background: var(--roxy-warning, #ea580c);
1030
+ }
1031
+ .severity.severe span {
1032
+ background: var(--roxy-danger, #dc2626);
1033
+ }
1034
+
1035
+ .description {
1036
+ margin: 0;
1037
+ color: var(--roxy-fg, #0a0a0a);
1038
+ }
1039
+
1040
+ h3 {
1041
+ margin: 0 0 var(--roxy-space-xs, 0.25rem);
1042
+ font-size: var(--roxy-text-xs, 0.75rem);
1043
+ color: var(--roxy-muted, #71717a);
1044
+ text-transform: uppercase;
1045
+ letter-spacing: 0.06em;
1046
+ }
1047
+ ul {
1048
+ margin: 0;
1049
+ padding-left: var(--roxy-space-md, 1rem);
1050
+ font-size: var(--roxy-text-sm, 0.875rem);
1051
+ }
1052
+ .effects {
1053
+ display: grid;
1054
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
1055
+ gap: var(--roxy-space-md, 1rem);
1056
+ }
1057
+ .effects p {
1058
+ margin: 0;
1059
+ font-size: var(--roxy-text-sm, 0.875rem);
1060
+ }
1061
+ `
1062
+ ];
1063
+ __decorateClass([
1064
+ property5({ attribute: false })
1065
+ ], RoxyDoshaCard.prototype, "data", 2);
1066
+ __decorateClass([
1067
+ property5({ type: String, reflect: true })
1068
+ ], RoxyDoshaCard.prototype, "type", 2);
1069
+ RoxyDoshaCard = __decorateClass([
1070
+ customElement5("roxy-dosha-card")
1071
+ ], RoxyDoshaCard);
1072
+
1073
+ // packages/ui/src/components/endpoint-form.ts
1074
+ import { css as css7, html as html6, LitElement as LitElement6, nothing as nothing6 } from "lit";
1075
+ import { customElement as customElement6, property as property6, state } from "lit/decorators.js";
1076
+ var RoxyEndpointForm = class extends LitElement6 {
1077
+ constructor() {
1078
+ super(...arguments);
1079
+ this.endpoint = "vedic-astrology/birth-chart";
1080
+ this.method = "POST";
1081
+ this.specUrl = "https://roxyapi.com/api/v2/openapi.json";
1082
+ this.submitLabel = "Submit";
1083
+ this.fields = [];
1084
+ this.values = {};
1085
+ this.hasLocation = false;
1086
+ this.loaded = false;
1087
+ this.onLocation = (e) => {
1088
+ const detail = e.detail;
1089
+ if (detail) {
1090
+ this.values = {
1091
+ ...this.values,
1092
+ latitude: detail.latitude,
1093
+ longitude: detail.longitude,
1094
+ timezone: detail.timezone ?? detail.utcOffset
1095
+ };
1096
+ }
1097
+ };
1098
+ this.onSubmit = (e) => {
1099
+ e.preventDefault();
1100
+ const missing = this.fields.filter((f) => f.required).filter(
1101
+ (f) => this.values[f.name] === void 0 || this.values[f.name] === ""
1102
+ );
1103
+ if (missing.length > 0) {
1104
+ this.dispatchEvent(
1105
+ new CustomEvent("roxy-validation-error", {
1106
+ detail: { missing: missing.map((m) => m.name) },
1107
+ bubbles: true,
1108
+ composed: true
1109
+ })
1110
+ );
1111
+ return;
1112
+ }
1113
+ this.dispatchEvent(
1114
+ new CustomEvent("roxy-submit", {
1115
+ detail: { endpoint: this.endpoint, values: this.values },
1116
+ bubbles: true,
1117
+ composed: true
1118
+ })
1119
+ );
1120
+ };
1121
+ }
1122
+ connectedCallback() {
1123
+ super.connectedCallback();
1124
+ void this.loadSchema();
1125
+ }
1126
+ async loadSchema() {
1127
+ try {
1128
+ const res = await fetch(this.specUrl);
1129
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1130
+ const spec = await res.json();
1131
+ const path = `/${this.endpoint.replace(/^\//, "")}`;
1132
+ const op = spec.paths?.[path]?.[this.method.toLowerCase()];
1133
+ if (!op) return;
1134
+ const schemas = spec.components?.schemas ?? {};
1135
+ const fields = [];
1136
+ let bodySchema;
1137
+ if (op.requestBody) {
1138
+ const ref = op.requestBody.content?.["application/json"]?.schema;
1139
+ bodySchema = this.resolve(ref, schemas);
1140
+ }
1141
+ if (bodySchema?.properties) {
1142
+ const required = new Set(bodySchema.required ?? []);
1143
+ for (const [name, sub] of Object.entries(bodySchema.properties)) {
1144
+ const resolved = this.resolve(sub, schemas) ?? {};
1145
+ fields.push({
1146
+ name,
1147
+ type: this.fieldType(resolved),
1148
+ required: required.has(name),
1149
+ description: resolved.description,
1150
+ enum: resolved.enum,
1151
+ min: resolved.minimum,
1152
+ max: resolved.maximum,
1153
+ default: resolved.default
1154
+ });
1155
+ }
1156
+ }
1157
+ for (const param of op.parameters ?? []) {
1158
+ if (param.in === "path" || param.in === "query") {
1159
+ const resolved = this.resolve(param.schema, schemas) ?? {};
1160
+ fields.push({
1161
+ name: param.name,
1162
+ type: this.fieldType(resolved),
1163
+ required: !!param.required,
1164
+ description: resolved.description,
1165
+ enum: resolved.enum,
1166
+ default: resolved.default
1167
+ });
1168
+ }
1169
+ }
1170
+ this.fields = fields;
1171
+ this.hasLocation = fields.some((f) => f.name === "latitude") && fields.some((f) => f.name === "longitude") && fields.some((f) => f.name === "timezone");
1172
+ const init = {};
1173
+ for (const f of fields) {
1174
+ if (f.default !== void 0) init[f.name] = f.default;
1175
+ }
1176
+ this.values = init;
1177
+ this.loaded = true;
1178
+ } catch (_err) {
1179
+ this.loaded = true;
1180
+ }
1181
+ }
1182
+ resolve(schema, all) {
1183
+ if (!schema) return void 0;
1184
+ if ("$ref" in schema && schema.$ref) {
1185
+ const name = schema.$ref.split("/").pop();
1186
+ return name ? all[name] : void 0;
1187
+ }
1188
+ return schema;
1189
+ }
1190
+ fieldType(s) {
1191
+ if (s.enum) return "enum";
1192
+ if (s.format === "date") return "date";
1193
+ if (s.format === "time") return "time";
1194
+ if (s.format === "date-time") return "datetime";
1195
+ if (s.type === "integer" || s.type === "number") return "number";
1196
+ return "text";
1197
+ }
1198
+ setValue(name, value) {
1199
+ this.values = { ...this.values, [name]: value };
1200
+ }
1201
+ render() {
1202
+ if (!this.loaded) {
1203
+ return html6`<form><div class="roxy-skeleton" style="height: 8rem"></div></form>`;
1204
+ }
1205
+ const renderField = (f) => {
1206
+ if (this.hasLocation && (f.name === "latitude" || f.name === "longitude" || f.name === "timezone")) {
1207
+ return nothing6;
1208
+ }
1209
+ const inputId = `roxy-form-${f.name}`;
1210
+ return html6`<div class="field">
1211
+ <label for=${inputId}>
1212
+ ${humanize(f.name)}${f.required ? html6`<span class="req" aria-hidden="true">*</span>` : nothing6}
1213
+ </label>
1214
+ ${f.enum ? html6`<select
1215
+ id=${inputId}
1216
+ ?required=${f.required}
1217
+ @change=${(e) => this.setValue(f.name, e.target.value)}
1218
+ >
1219
+ <option value="">Choose</option>
1220
+ ${f.enum.map(
1221
+ (opt) => html6`<option value=${opt} ?selected=${this.values[f.name] === opt}>
1222
+ ${opt}
1223
+ </option>`
1224
+ )}
1225
+ </select>` : html6`<input
1226
+ id=${inputId}
1227
+ type=${this.htmlType(f.type)}
1228
+ ?required=${f.required}
1229
+ min=${f.min ?? ""}
1230
+ max=${f.max ?? ""}
1231
+ step=${f.type === "number" ? "any" : ""}
1232
+ .value=${this.values[f.name] ?? ""}
1233
+ @input=${(e) => this.setValue(
1234
+ f.name,
1235
+ this.coerce(f.type, e.target.value)
1236
+ )}
1237
+ />`}
1238
+ ${f.description ? html6`<small class="help">${f.description}</small>` : nothing6}
1239
+ </div>`;
1240
+ };
1241
+ return html6`<form @submit=${this.onSubmit}>
1242
+ <h2 class="title">${humanize(this.endpoint.split("/").pop() ?? "")}</h2>
1243
+ ${this.hasLocation ? html6`<div class="location-block">
1244
+ <label>Birth location</label>
1245
+ <roxy-location-search
1246
+ @roxy-location-select=${this.onLocation}
1247
+ placeholder="City of birth"
1248
+ ></roxy-location-search>
1249
+ <small class="help">
1250
+ Required: latitude, longitude, timezone. Pick a city to autofill.
1251
+ </small>
1252
+ </div>` : nothing6}
1253
+ <div class="fields">
1254
+ ${this.fields.map((f) => renderField(f))}
1255
+ </div>
1256
+ <button class="submit" type="submit">${this.submitLabel}</button>
1257
+ </form>`;
1258
+ }
1259
+ htmlType(t) {
1260
+ switch (t) {
1261
+ case "date":
1262
+ return "date";
1263
+ case "time":
1264
+ return "time";
1265
+ case "datetime":
1266
+ return "datetime-local";
1267
+ case "number":
1268
+ return "number";
1269
+ default:
1270
+ return "text";
1271
+ }
1272
+ }
1273
+ coerce(t, v) {
1274
+ if (v === "") return void 0;
1275
+ if (t === "number") {
1276
+ const n = Number(v);
1277
+ return Number.isFinite(n) ? n : void 0;
1278
+ }
1279
+ return v;
1280
+ }
1281
+ };
1282
+ RoxyEndpointForm.styles = [
1283
+ baseStyles,
1284
+ css7`
1285
+ form {
1286
+ display: grid;
1287
+ gap: var(--roxy-space-md, 1rem);
1288
+ background: var(--roxy-bg, #fff);
1289
+ border: 1px solid var(--roxy-border, #e4e4e7);
1290
+ border-radius: var(--roxy-radius-md, 8px);
1291
+ padding: var(--roxy-space-lg, 1.5rem);
1292
+ box-shadow: var(--roxy-shadow-sm);
1293
+ }
1294
+ .title {
1295
+ margin: 0;
1296
+ font-size: var(--roxy-text-lg, 1.125rem);
1297
+ font-weight: var(--roxy-weight-bold, 600);
1298
+ }
1299
+ .fields {
1300
+ display: grid;
1301
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
1302
+ gap: var(--roxy-space-md, 1rem);
1303
+ }
1304
+ .field {
1305
+ display: grid;
1306
+ gap: var(--roxy-space-xs, 0.25rem);
1307
+ }
1308
+ label {
1309
+ font-size: var(--roxy-text-sm, 0.875rem);
1310
+ color: var(--roxy-secondary, #475569);
1311
+ }
1312
+ label .req {
1313
+ color: var(--roxy-danger, #dc2626);
1314
+ margin-left: 4px;
1315
+ }
1316
+ input,
1317
+ select {
1318
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
1319
+ font-size: var(--roxy-text-base, 1rem);
1320
+ font-family: inherit;
1321
+ color: var(--roxy-fg, #0a0a0a);
1322
+ background: var(--roxy-bg, #fff);
1323
+ border: 1px solid var(--roxy-border, #e4e4e7);
1324
+ border-radius: var(--roxy-radius-md, 8px);
1325
+ }
1326
+ input:focus,
1327
+ select:focus {
1328
+ outline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));
1329
+ outline-offset: 2px;
1330
+ border-color: var(--roxy-accent-fg, #b45309);
1331
+ }
1332
+ .help {
1333
+ color: var(--roxy-muted, #71717a);
1334
+ font-size: var(--roxy-text-xs, 0.75rem);
1335
+ }
1336
+ .location-block {
1337
+ display: grid;
1338
+ gap: var(--roxy-space-xs, 0.25rem);
1339
+ grid-column: 1 / -1;
1340
+ }
1341
+ .coords {
1342
+ display: grid;
1343
+ grid-template-columns: repeat(3, 1fr);
1344
+ gap: var(--roxy-space-sm, 0.5rem);
1345
+ }
1346
+ .coords input {
1347
+ font-size: var(--roxy-text-sm, 0.875rem);
1348
+ }
1349
+ button.submit {
1350
+ justify-self: start;
1351
+ background: var(--roxy-accent-fg, #b45309);
1352
+ color: #fff;
1353
+ border: 0;
1354
+ border-radius: var(--roxy-radius-md, 8px);
1355
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-lg, 1.5rem);
1356
+ font-size: var(--roxy-text-base, 1rem);
1357
+ font-weight: var(--roxy-weight-bold, 600);
1358
+ cursor: pointer;
1359
+ transition:
1360
+ transform var(--roxy-motion-duration, 200ms)
1361
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
1362
+ }
1363
+ button.submit:hover {
1364
+ transform: scale(1.02);
1365
+ }
1366
+ button.submit:focus-visible {
1367
+ outline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));
1368
+ outline-offset: 2px;
1369
+ }
1370
+ `
1371
+ ];
1372
+ __decorateClass([
1373
+ property6({ type: String, attribute: "data-endpoint" })
1374
+ ], RoxyEndpointForm.prototype, "endpoint", 2);
1375
+ __decorateClass([
1376
+ property6({ type: String })
1377
+ ], RoxyEndpointForm.prototype, "method", 2);
1378
+ __decorateClass([
1379
+ property6({ type: String, attribute: "spec-url" })
1380
+ ], RoxyEndpointForm.prototype, "specUrl", 2);
1381
+ __decorateClass([
1382
+ property6({ type: String, attribute: "submit-label" })
1383
+ ], RoxyEndpointForm.prototype, "submitLabel", 2);
1384
+ __decorateClass([
1385
+ state()
1386
+ ], RoxyEndpointForm.prototype, "fields", 2);
1387
+ __decorateClass([
1388
+ state()
1389
+ ], RoxyEndpointForm.prototype, "values", 2);
1390
+ __decorateClass([
1391
+ state()
1392
+ ], RoxyEndpointForm.prototype, "hasLocation", 2);
1393
+ __decorateClass([
1394
+ state()
1395
+ ], RoxyEndpointForm.prototype, "loaded", 2);
1396
+ RoxyEndpointForm = __decorateClass([
1397
+ customElement6("roxy-endpoint-form")
1398
+ ], RoxyEndpointForm);
1399
+ function humanize(s) {
1400
+ return s.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^\w/, (c) => c.toUpperCase());
1401
+ }
1402
+
1403
+ // packages/ui/src/components/guna-milan.ts
1404
+ import { css as css8, html as html7, LitElement as LitElement7, nothing as nothing7 } from "lit";
1405
+ import { customElement as customElement7, property as property7 } from "lit/decorators.js";
1406
+ var RoxyGunaMilan = class extends LitElement7 {
1407
+ constructor() {
1408
+ super(...arguments);
1409
+ this.data = null;
1410
+ }
1411
+ render() {
1412
+ const d = this.data;
1413
+ if (!d)
1414
+ return html7`<div class="roxy-empty" role="status">No Guna Milan data</div>`;
1415
+ const total = d.total ?? d.totalScore ?? 0;
1416
+ const max = d.maxScore ?? 36;
1417
+ const breakdown = (d.breakdown ?? []).filter(
1418
+ (b) => b && (b.name || b.score !== void 0)
1419
+ );
1420
+ return html7`<article class="card" aria-label="Guna Milan score">
1421
+ <div class="score-bar">
1422
+ <div>
1423
+ <span class="total">${total}</span>
1424
+ <span class="over"> / ${max}</span>
1425
+ ${typeof d.percentage === "number" ? html7`<small style="margin-left: 0.5rem; color: var(--roxy-muted)">
1426
+ ${d.percentage}%
1427
+ </small>` : nothing7}
1428
+ </div>
1429
+ ${d.recommendation ? html7`<span class="recommendation">${d.recommendation}</span>` : nothing7}
1430
+ </div>
1431
+
1432
+ ${breakdown.length > 0 ? html7`<table>
1433
+ <thead>
1434
+ <tr>
1435
+ <th>Category</th>
1436
+ <th>Progress</th>
1437
+ <th class="score">Score</th>
1438
+ </tr>
1439
+ </thead>
1440
+ <tbody>
1441
+ ${breakdown.map((b) => {
1442
+ const score = b.score ?? 0;
1443
+ const maxScore = b.max ?? b.maxScore ?? defaultMax(b.name);
1444
+ const pct = maxScore ? score / maxScore * 100 : 0;
1445
+ return html7`<tr>
1446
+ <td>${b.name ?? ""}</td>
1447
+ <td class="bar-cell">
1448
+ <div class="mini-bar">
1449
+ <span style="width: ${pct}%"></span>
1450
+ </div>
1451
+ </td>
1452
+ <td class="score">${score} / ${maxScore}</td>
1453
+ </tr>`;
1454
+ })}
1455
+ </tbody>
1456
+ </table>` : nothing7}
1457
+ ${(d.doshas?.length ?? 0) > 0 || (d.doshaCancellations?.length ?? 0) > 0 ? html7`<div class="tags">
1458
+ ${d.doshas?.map((x) => html7`<span class="dosha">${x}</span>`)}
1459
+ ${d.doshaCancellations?.map((x) => html7`<span class="cancel">${x}</span>`)}
1460
+ </div>` : nothing7}
1461
+ </article>`;
1462
+ }
1463
+ };
1464
+ RoxyGunaMilan.styles = [
1465
+ baseStyles,
1466
+ css8`
1467
+ .card {
1468
+ background: var(--roxy-bg, #fff);
1469
+ border: 1px solid var(--roxy-border, #e4e4e7);
1470
+ border-radius: var(--roxy-radius-md, 8px);
1471
+ padding: var(--roxy-space-lg, 1.5rem);
1472
+ box-shadow: var(--roxy-shadow-sm);
1473
+ display: grid;
1474
+ gap: var(--roxy-space-md, 1rem);
1475
+ }
1476
+
1477
+ .score-bar {
1478
+ display: grid;
1479
+ grid-template-columns: 1fr auto;
1480
+ align-items: center;
1481
+ gap: var(--roxy-space-md, 1rem);
1482
+ }
1483
+ .total {
1484
+ font-size: 2.25rem;
1485
+ font-weight: var(--roxy-weight-bold, 600);
1486
+ color: var(--roxy-accent-fg, #b45309);
1487
+ font-variant-numeric: tabular-nums;
1488
+ line-height: 1;
1489
+ }
1490
+ .over {
1491
+ color: var(--roxy-muted, #71717a);
1492
+ font-size: var(--roxy-text-base, 1rem);
1493
+ }
1494
+ .recommendation {
1495
+ font-size: var(--roxy-text-sm, 0.875rem);
1496
+ color: var(--roxy-secondary, #475569);
1497
+ }
1498
+
1499
+ table {
1500
+ width: 100%;
1501
+ border-collapse: collapse;
1502
+ font-size: var(--roxy-text-sm, 0.875rem);
1503
+ }
1504
+ th,
1505
+ td {
1506
+ padding: var(--roxy-space-sm, 0.5rem);
1507
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
1508
+ text-align: left;
1509
+ }
1510
+ th {
1511
+ color: var(--roxy-muted, #71717a);
1512
+ font-weight: var(--roxy-weight-bold, 600);
1513
+ text-transform: uppercase;
1514
+ font-size: var(--roxy-text-xs, 0.75rem);
1515
+ letter-spacing: 0.06em;
1516
+ }
1517
+ td.score {
1518
+ text-align: right;
1519
+ font-variant-numeric: tabular-nums;
1520
+ color: var(--roxy-fg, #0a0a0a);
1521
+ font-weight: var(--roxy-weight-bold, 600);
1522
+ }
1523
+ td.bar-cell {
1524
+ width: 30%;
1525
+ }
1526
+ .mini-bar {
1527
+ height: 8px;
1528
+ background: var(--roxy-border, #e4e4e7);
1529
+ border-radius: var(--roxy-radius-full, 9999px);
1530
+ overflow: hidden;
1531
+ }
1532
+ .mini-bar > span {
1533
+ display: block;
1534
+ height: 100%;
1535
+ background: var(--roxy-accent, #f59e0b);
1536
+ transition:
1537
+ width var(--roxy-motion-duration, 200ms)
1538
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
1539
+ }
1540
+
1541
+ .tags {
1542
+ display: flex;
1543
+ flex-wrap: wrap;
1544
+ gap: var(--roxy-space-xs, 0.25rem);
1545
+ }
1546
+ .tags span {
1547
+ padding: 2px 8px;
1548
+ border-radius: var(--roxy-radius-full, 9999px);
1549
+ font-size: var(--roxy-text-xs, 0.75rem);
1550
+ }
1551
+ .tags .dosha {
1552
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
1553
+ color: var(--roxy-danger, #dc2626);
1554
+ }
1555
+ .tags .cancel {
1556
+ background: color-mix(in srgb, var(--roxy-success, #16a34a) 18%, transparent);
1557
+ color: var(--roxy-success, #16a34a);
1558
+ }
1559
+ `
1560
+ ];
1561
+ __decorateClass([
1562
+ property7({ attribute: false })
1563
+ ], RoxyGunaMilan.prototype, "data", 2);
1564
+ RoxyGunaMilan = __decorateClass([
1565
+ customElement7("roxy-guna-milan")
1566
+ ], RoxyGunaMilan);
1567
+ function defaultMax(name) {
1568
+ if (!name) return 1;
1569
+ switch (name.toLowerCase()) {
1570
+ case "varna":
1571
+ return 1;
1572
+ case "vasya":
1573
+ return 2;
1574
+ case "tara":
1575
+ return 3;
1576
+ case "yoni":
1577
+ return 4;
1578
+ case "maitri":
1579
+ return 5;
1580
+ case "gana":
1581
+ return 6;
1582
+ case "bhakoot":
1583
+ return 7;
1584
+ case "nadi":
1585
+ return 8;
1586
+ default:
1587
+ return 1;
1588
+ }
1589
+ }
1590
+
1591
+ // packages/ui/src/components/hexagram.ts
1592
+ import { css as css9, html as html8, LitElement as LitElement8, nothing as nothing8, svg as svg2 } from "lit";
1593
+ import { customElement as customElement8, property as property8 } from "lit/decorators.js";
1594
+
1595
+ // packages/ui/src/tokens/index.ts
1596
+ var PLANET_GLYPH = {
1597
+ Sun: "\u2609",
1598
+ Moon: "\u263D",
1599
+ Mercury: "\u263F",
1600
+ Venus: "\u2640",
1601
+ Earth: "\u2641",
1602
+ Mars: "\u2642",
1603
+ Jupiter: "\u2643",
1604
+ Saturn: "\u2644",
1605
+ Uranus: "\u2645",
1606
+ Neptune: "\u2646",
1607
+ Pluto: "\u2647",
1608
+ Rahu: "\u260A",
1609
+ Ketu: "\u260B",
1610
+ Ascendant: "Asc",
1611
+ Lagna: "La",
1612
+ NorthNode: "\u260A",
1613
+ SouthNode: "\u260B"
1614
+ };
1615
+ var PLANET_ABBR = {
1616
+ Sun: "Su",
1617
+ Moon: "Mo",
1618
+ Mercury: "Me",
1619
+ Venus: "Ve",
1620
+ Mars: "Ma",
1621
+ Jupiter: "Ju",
1622
+ Saturn: "Sa",
1623
+ Uranus: "Ur",
1624
+ Neptune: "Ne",
1625
+ Pluto: "Pl",
1626
+ Rahu: "Ra",
1627
+ Ketu: "Ke",
1628
+ Ascendant: "Asc",
1629
+ Lagna: "La"
1630
+ };
1631
+ var SIGN_GLYPH = {
1632
+ Aries: "\u2648",
1633
+ Taurus: "\u2649",
1634
+ Gemini: "\u264A",
1635
+ Cancer: "\u264B",
1636
+ Leo: "\u264C",
1637
+ Virgo: "\u264D",
1638
+ Libra: "\u264E",
1639
+ Scorpio: "\u264F",
1640
+ Sagittarius: "\u2650",
1641
+ Capricorn: "\u2651",
1642
+ Aquarius: "\u2652",
1643
+ Pisces: "\u2653"
1644
+ };
1645
+ var SIGN_ABBR = {
1646
+ Aries: "Ar",
1647
+ Taurus: "Ta",
1648
+ Gemini: "Ge",
1649
+ Cancer: "Cn",
1650
+ Leo: "Le",
1651
+ Virgo: "Vi",
1652
+ Libra: "Li",
1653
+ Scorpio: "Sc",
1654
+ Sagittarius: "Sg",
1655
+ Capricorn: "Cp",
1656
+ Aquarius: "Aq",
1657
+ Pisces: "Pi"
1658
+ };
1659
+ var TRIGRAM_GLYPH = {
1660
+ heaven: "\u2630",
1661
+ lake: "\u2631",
1662
+ fire: "\u2632",
1663
+ thunder: "\u2633",
1664
+ wind: "\u2634",
1665
+ water: "\u2635",
1666
+ mountain: "\u2636",
1667
+ earth: "\u2637",
1668
+ Heaven: "\u2630",
1669
+ Lake: "\u2631",
1670
+ Fire: "\u2632",
1671
+ Thunder: "\u2633",
1672
+ Wind: "\u2634",
1673
+ Water: "\u2635",
1674
+ Mountain: "\u2636",
1675
+ Earth: "\u2637"
1676
+ };
1677
+ var MOON_PHASE_EMOJI = {
1678
+ "new moon": "\u{1F311}",
1679
+ "waxing crescent": "\u{1F312}",
1680
+ "first quarter": "\u{1F313}",
1681
+ "waxing gibbous": "\u{1F314}",
1682
+ "full moon": "\u{1F315}",
1683
+ "waning gibbous": "\u{1F316}",
1684
+ "last quarter": "\u{1F317}",
1685
+ "waning crescent": "\u{1F318}"
1686
+ };
1687
+
1688
+ // packages/ui/src/components/hexagram.ts
1689
+ var RoxyHexagram = class extends LitElement8 {
1690
+ constructor() {
1691
+ super(...arguments);
1692
+ this.data = null;
1693
+ this.mode = "lookup";
1694
+ }
1695
+ getHexagram() {
1696
+ if (!this.data) return null;
1697
+ if ("hexagram" in this.data && this.data.hexagram) {
1698
+ return {
1699
+ ...this.data.hexagram,
1700
+ lines: this.data.lines,
1701
+ changingLinePositions: this.data.changingLinePositions
1702
+ };
1703
+ }
1704
+ return this.data;
1705
+ }
1706
+ render() {
1707
+ const h = this.getHexagram();
1708
+ if (!h)
1709
+ return html8`<div class="roxy-empty" role="status">No hexagram data</div>`;
1710
+ const lines = h.lines ?? this.derivedLines(h);
1711
+ const changing = new Set(h.changingLinePositions ?? []);
1712
+ return html8`<article class="card" aria-label="I Ching hexagram">
1713
+ <div class="glyphs">
1714
+ ${h.symbol ? html8`<div class="symbol">${h.symbol}</div>` : nothing8}
1715
+ <div class="lines" aria-hidden="true">
1716
+ ${lines.slice().reverse().map((l, idx) => {
1717
+ const realIdx = lines.length - 1 - idx + 1;
1718
+ const isChanging = changing.has(realIdx);
1719
+ const broken = l === 6 || l === 8;
1720
+ const cls = `${broken ? "broken" : "solid"}${isChanging ? " changing" : ""}`;
1721
+ return html8`<div class="line ${cls}">
1722
+ ${broken ? svg2`<span class="seg"></span><span class="seg"></span>` : svg2`<span class="seg"></span>`}
1723
+ </div>`;
1724
+ })}
1725
+ </div>
1726
+ </div>
1727
+ <div>
1728
+ <h2 class="title">
1729
+ ${h.number ? html8`${h.number}. ` : nothing8}${h.english ?? h.chinese ?? "Hexagram"}
1730
+ </h2>
1731
+ <p class="subtitle">
1732
+ ${h.chinese ? html8`${h.chinese}` : nothing8}
1733
+ ${h.pinyin ? html8` · ${h.pinyin}` : nothing8}
1734
+ </p>
1735
+ <div class="trigrams">
1736
+ ${h.upperTrigram ? html8`<div>
1737
+ Upper
1738
+ <span class="tri-glyph"
1739
+ >${TRIGRAM_GLYPH[h.upperTrigram] ?? ""}</span
1740
+ >${h.upperTrigram}
1741
+ </div>` : nothing8}
1742
+ ${h.lowerTrigram ? html8`<div>
1743
+ Lower
1744
+ <span class="tri-glyph"
1745
+ >${TRIGRAM_GLYPH[h.lowerTrigram] ?? ""}</span
1746
+ >${h.lowerTrigram}
1747
+ </div>` : nothing8}
1748
+ </div>
1749
+ ${h.judgment ? html8`<p class="judgment">${h.judgment}</p>` : nothing8}
1750
+ ${h.image ? html8`<p class="image">${h.image}</p>` : nothing8}
1751
+ ${h.dailyMessage ? html8`<p class="message">${h.dailyMessage}</p>` : nothing8}
1752
+ ${h.interpretation?.general ? html8`<p>${h.interpretation.general}</p>` : nothing8}
1753
+ ${changing.size > 0 ? html8`<div class="changing">
1754
+ Changing lines: ${Array.from(changing).sort((a, b) => a - b).join(", ")}.
1755
+ ${h.resultingHexagram?.english ? html8` Becomes hexagram ${h.resultingHexagram.number}
1756
+ ${h.resultingHexagram.english}.` : nothing8}
1757
+ </div>` : nothing8}
1758
+ </div>
1759
+ </article>`;
1760
+ }
1761
+ /** When the API only ships symbol+number with no line array, render six solid yang. */
1762
+ derivedLines(h) {
1763
+ if (!h.symbol) return Array.from({ length: 6 }, () => 7);
1764
+ const cp = h.symbol.codePointAt(0) ?? 0;
1765
+ if (cp >= 19904 && cp <= 19967) {
1766
+ const offset = cp - 19904;
1767
+ const lines = [];
1768
+ for (let i = 0; i < 6; i++) {
1769
+ const broken = offset >> i & 1;
1770
+ lines.push(broken ? 8 : 7);
1771
+ }
1772
+ return lines;
1773
+ }
1774
+ return Array.from({ length: 6 }, () => 7);
1775
+ }
1776
+ };
1777
+ RoxyHexagram.styles = [
1778
+ baseStyles,
1779
+ css9`
1780
+ .card {
1781
+ background: var(--roxy-bg, #fff);
1782
+ border: 1px solid var(--roxy-border, #e4e4e7);
1783
+ border-radius: var(--roxy-radius-md, 8px);
1784
+ padding: var(--roxy-space-lg, 1.5rem);
1785
+ box-shadow: var(--roxy-shadow-sm);
1786
+ display: grid;
1787
+ grid-template-columns: 6rem 1fr;
1788
+ gap: var(--roxy-space-lg, 1.5rem);
1789
+ }
1790
+
1791
+ @container (max-width: 480px) {
1792
+ .card {
1793
+ grid-template-columns: 1fr;
1794
+ }
1795
+ }
1796
+
1797
+ .glyphs {
1798
+ display: grid;
1799
+ gap: var(--roxy-space-md, 1rem);
1800
+ justify-items: center;
1801
+ }
1802
+ .symbol {
1803
+ font-size: 3rem;
1804
+ line-height: 1;
1805
+ color: var(--roxy-accent-fg, #b45309);
1806
+ }
1807
+ .lines {
1808
+ display: grid;
1809
+ gap: 4px;
1810
+ width: 4rem;
1811
+ }
1812
+ .line {
1813
+ display: flex;
1814
+ gap: 4px;
1815
+ justify-content: center;
1816
+ align-items: center;
1817
+ height: 8px;
1818
+ }
1819
+ .seg {
1820
+ display: block;
1821
+ height: 6px;
1822
+ background: var(--roxy-fg, #0a0a0a);
1823
+ border-radius: 1px;
1824
+ }
1825
+ .line.broken .seg {
1826
+ width: 1.4rem;
1827
+ }
1828
+ .line.solid .seg {
1829
+ width: 3rem;
1830
+ }
1831
+ .line.changing .seg {
1832
+ background: var(--roxy-accent, #f59e0b);
1833
+ }
1834
+
1835
+ .title {
1836
+ margin: 0;
1837
+ font-size: var(--roxy-text-xl, 1.5rem);
1838
+ font-weight: var(--roxy-weight-bold, 600);
1839
+ letter-spacing: var(--roxy-tracking-tight);
1840
+ }
1841
+ .subtitle {
1842
+ color: var(--roxy-muted, #71717a);
1843
+ font-size: var(--roxy-text-sm, 0.875rem);
1844
+ margin: 0 0 var(--roxy-space-sm, 0.5rem);
1845
+ }
1846
+ .trigrams {
1847
+ display: flex;
1848
+ gap: var(--roxy-space-md, 1rem);
1849
+ margin-bottom: var(--roxy-space-sm, 0.5rem);
1850
+ color: var(--roxy-secondary, #475569);
1851
+ font-size: var(--roxy-text-xs, 0.75rem);
1852
+ text-transform: uppercase;
1853
+ letter-spacing: 0.06em;
1854
+ }
1855
+ .tri-glyph {
1856
+ font-size: var(--roxy-text-xl, 1.5rem);
1857
+ color: var(--roxy-accent-fg, #b45309);
1858
+ margin-right: 4px;
1859
+ vertical-align: middle;
1860
+ }
1861
+ .judgment,
1862
+ .image,
1863
+ .message {
1864
+ margin: 0 0 var(--roxy-space-sm, 0.5rem);
1865
+ font-size: var(--roxy-text-sm, 0.875rem);
1866
+ color: var(--roxy-fg, #0a0a0a);
1867
+ }
1868
+ .judgment::before {
1869
+ content: 'Judgment. ';
1870
+ font-weight: var(--roxy-weight-bold, 600);
1871
+ color: var(--roxy-secondary, #475569);
1872
+ }
1873
+ .image::before {
1874
+ content: 'Image. ';
1875
+ font-weight: var(--roxy-weight-bold, 600);
1876
+ color: var(--roxy-secondary, #475569);
1877
+ }
1878
+
1879
+ .changing {
1880
+ margin-top: var(--roxy-space-md, 1rem);
1881
+ padding-top: var(--roxy-space-md, 1rem);
1882
+ border-top: 1px solid var(--roxy-border, #e4e4e7);
1883
+ color: var(--roxy-accent-fg, #b45309);
1884
+ font-size: var(--roxy-text-sm, 0.875rem);
1885
+ }
1886
+ `
1887
+ ];
1888
+ __decorateClass([
1889
+ property8({ attribute: false })
1890
+ ], RoxyHexagram.prototype, "data", 2);
1891
+ __decorateClass([
1892
+ property8({ type: String, reflect: true })
1893
+ ], RoxyHexagram.prototype, "mode", 2);
1894
+ RoxyHexagram = __decorateClass([
1895
+ customElement8("roxy-hexagram")
1896
+ ], RoxyHexagram);
1897
+
1898
+ // packages/ui/src/components/horoscope-card.ts
1899
+ import { css as css10, html as html9, LitElement as LitElement9, nothing as nothing9 } from "lit";
1900
+ import { customElement as customElement9, property as property9 } from "lit/decorators.js";
1901
+ var RoxyHoroscopeCard = class extends LitElement9 {
1902
+ constructor() {
1903
+ super(...arguments);
1904
+ this.data = null;
1905
+ this.period = "daily";
1906
+ }
1907
+ render() {
1908
+ const d = this.data;
1909
+ if (!d)
1910
+ return html9`<div class="roxy-empty" role="status">No horoscope data</div>`;
1911
+ const sign = d.sign ?? "";
1912
+ const glyph = sign ? SIGN_GLYPH[capitalize(sign)] ?? "" : "";
1913
+ const energy = typeof d.energyRating === "number" ? d.energyRating : null;
1914
+ const dateLabel = d.date ?? d.week ?? d.month ?? "";
1915
+ return html9`<article
1916
+ class="card"
1917
+ aria-label=${`${this.period} horoscope for ${sign}`}
1918
+ >
1919
+ <header class="head">
1920
+ <span class="glyph" aria-hidden="true">${glyph}</span>
1921
+ <div>
1922
+ <h2 class="title">${sign} ${this.period}</h2>
1923
+ ${dateLabel ? html9`<div class="date">${dateLabel}</div>` : nothing9}
1924
+ </div>
1925
+ ${energy !== null ? html9`<span class="energy" aria-label=${`Energy ${energy} of 10`}>
1926
+ Energy ${energy}/10
1927
+ <span class="energy-bar"
1928
+ ><span style="width: ${energy / 10 * 100}%"></span
1929
+ ></span>
1930
+ </span>` : nothing9}
1931
+ </header>
1932
+
1933
+ ${d.overview ? html9`<p class="overview">${d.overview}</p>` : nothing9}
1934
+
1935
+ <div class="sections">
1936
+ ${d.love ? html9`<div class="section">
1937
+ <h3>Love</h3>
1938
+ <p>${d.love}</p>
1939
+ </div>` : nothing9}
1940
+ ${d.career ? html9`<div class="section">
1941
+ <h3>Career</h3>
1942
+ <p>${d.career}</p>
1943
+ </div>` : nothing9}
1944
+ ${d.health ? html9`<div class="section">
1945
+ <h3>Health</h3>
1946
+ <p>${d.health}</p>
1947
+ </div>` : nothing9}
1948
+ ${d.finance ? html9`<div class="section">
1949
+ <h3>Finance</h3>
1950
+ <p>${d.finance}</p>
1951
+ </div>` : nothing9}
1952
+ ${d.advice ? html9`<div class="section">
1953
+ <h3>Advice</h3>
1954
+ <p>${d.advice}</p>
1955
+ </div>` : nothing9}
1956
+ </div>
1957
+
1958
+ ${d.luckyNumber || d.luckyColor || (d.compatibleSigns?.length ?? 0) > 0 ? html9`<div class="lucky">
1959
+ ${d.luckyNumber !== void 0 ? html9`<span>Lucky number <strong>${d.luckyNumber}</strong></span>` : nothing9}
1960
+ ${d.luckyColor ? html9`<span>Lucky color <strong>${d.luckyColor}</strong></span>` : nothing9}
1961
+ ${d.luckyNumbers?.length ? html9`<span
1962
+ >Lucky numbers
1963
+ <strong>${d.luckyNumbers.join(", ")}</strong></span
1964
+ >` : nothing9}
1965
+ ${d.luckyDays?.length ? html9`<span
1966
+ >Lucky days <strong>${d.luckyDays.join(", ")}</strong></span
1967
+ >` : nothing9}
1968
+ ${d.compatibleSigns?.length ? html9`<span class="compat-wrap">
1969
+ Best with
1970
+ <span class="compat"
1971
+ >${d.compatibleSigns.map(
1972
+ (s) => html9`<span>${s}</span>`
1973
+ )}</span
1974
+ >
1975
+ </span>` : nothing9}
1976
+ </div>` : nothing9}
1977
+ </article>`;
1978
+ }
1979
+ };
1980
+ RoxyHoroscopeCard.styles = [
1981
+ baseStyles,
1982
+ css10`
1983
+ .card {
1984
+ background: var(--roxy-bg, #fff);
1985
+ border: 1px solid var(--roxy-border, #e4e4e7);
1986
+ border-radius: var(--roxy-radius-md, 8px);
1987
+ padding: var(--roxy-space-lg, 1.5rem);
1988
+ box-shadow: var(--roxy-shadow-sm);
1989
+ display: grid;
1990
+ gap: var(--roxy-space-md, 1rem);
1991
+ }
1992
+
1993
+ .head {
1994
+ display: flex;
1995
+ align-items: center;
1996
+ gap: var(--roxy-space-md, 1rem);
1997
+ }
1998
+
1999
+ .glyph {
2000
+ font-size: 2.25rem;
2001
+ color: var(--roxy-accent-fg, #b45309);
2002
+ line-height: 1;
2003
+ }
2004
+
2005
+ .title {
2006
+ font-size: var(--roxy-text-xl, 1.5rem);
2007
+ font-weight: var(--roxy-weight-bold, 600);
2008
+ margin: 0;
2009
+ letter-spacing: var(--roxy-tracking-tight);
2010
+ text-transform: capitalize;
2011
+ }
2012
+
2013
+ .date {
2014
+ font-size: var(--roxy-text-sm, 0.875rem);
2015
+ color: var(--roxy-muted, #71717a);
2016
+ }
2017
+
2018
+ .energy {
2019
+ margin-left: auto;
2020
+ font-variant-numeric: tabular-nums;
2021
+ font-size: var(--roxy-text-sm, 0.875rem);
2022
+ color: var(--roxy-secondary, #475569);
2023
+ }
2024
+ .energy-bar {
2025
+ display: inline-block;
2026
+ width: 6rem;
2027
+ height: 6px;
2028
+ background: var(--roxy-border, #e4e4e7);
2029
+ border-radius: var(--roxy-radius-full, 9999px);
2030
+ overflow: hidden;
2031
+ margin-left: 6px;
2032
+ vertical-align: middle;
2033
+ }
2034
+ .energy-bar > span {
2035
+ display: block;
2036
+ height: 100%;
2037
+ background: var(--roxy-accent, #f59e0b);
2038
+ transition:
2039
+ width var(--roxy-motion-duration, 200ms)
2040
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
2041
+ }
2042
+
2043
+ .overview {
2044
+ font-size: var(--roxy-text-base, 1rem);
2045
+ color: var(--roxy-fg, #0a0a0a);
2046
+ margin: 0;
2047
+ }
2048
+
2049
+ .sections {
2050
+ display: grid;
2051
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
2052
+ gap: var(--roxy-space-md, 1rem);
2053
+ }
2054
+
2055
+ .section h3 {
2056
+ margin: 0 0 var(--roxy-space-xs, 0.25rem) 0;
2057
+ font-size: var(--roxy-text-xs, 0.75rem);
2058
+ color: var(--roxy-muted, #71717a);
2059
+ font-weight: var(--roxy-weight-bold, 600);
2060
+ text-transform: uppercase;
2061
+ letter-spacing: 0.06em;
2062
+ }
2063
+ .section p {
2064
+ margin: 0;
2065
+ font-size: var(--roxy-text-sm, 0.875rem);
2066
+ color: var(--roxy-fg, #0a0a0a);
2067
+ }
2068
+
2069
+ .lucky {
2070
+ display: flex;
2071
+ flex-wrap: wrap;
2072
+ gap: var(--roxy-space-sm, 0.5rem);
2073
+ border-top: 1px solid var(--roxy-border, #e4e4e7);
2074
+ padding-top: var(--roxy-space-md, 1rem);
2075
+ font-size: var(--roxy-text-sm, 0.875rem);
2076
+ color: var(--roxy-secondary, #475569);
2077
+ }
2078
+
2079
+ .lucky strong {
2080
+ color: var(--roxy-fg, #0a0a0a);
2081
+ font-weight: var(--roxy-weight-bold, 600);
2082
+ }
2083
+
2084
+ .compat {
2085
+ display: flex;
2086
+ flex-wrap: wrap;
2087
+ gap: var(--roxy-space-xs, 0.25rem);
2088
+ }
2089
+ .compat span {
2090
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 16%, transparent);
2091
+ color: var(--roxy-fg, #0a0a0a);
2092
+ padding: 2px 8px;
2093
+ border-radius: var(--roxy-radius-full, 9999px);
2094
+ font-size: var(--roxy-text-xs, 0.75rem);
2095
+ text-transform: capitalize;
2096
+ }
2097
+ `
2098
+ ];
2099
+ __decorateClass([
2100
+ property9({ attribute: false })
2101
+ ], RoxyHoroscopeCard.prototype, "data", 2);
2102
+ __decorateClass([
2103
+ property9({ type: String, reflect: true })
2104
+ ], RoxyHoroscopeCard.prototype, "period", 2);
2105
+ RoxyHoroscopeCard = __decorateClass([
2106
+ customElement9("roxy-horoscope-card")
2107
+ ], RoxyHoroscopeCard);
2108
+ function capitalize(s) {
2109
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
2110
+ }
2111
+
2112
+ // packages/ui/src/components/kp-planets-table.ts
2113
+ import { css as css11, html as html10, LitElement as LitElement10, nothing as nothing10 } from "lit";
2114
+ import { customElement as customElement10, property as property10 } from "lit/decorators.js";
2115
+ var RoxyKpPlanetsTable = class extends LitElement10 {
2116
+ constructor() {
2117
+ super(...arguments);
2118
+ this.data = null;
2119
+ }
2120
+ render() {
2121
+ if (!this.data)
2122
+ return html10`<div class="roxy-empty" role="status">No KP data</div>`;
2123
+ const planets = this.data.planets ?? [];
2124
+ return html10`<div
2125
+ class="wrap"
2126
+ aria-label="KP planets table"
2127
+ tabindex="0"
2128
+ >
2129
+ <header class="head">
2130
+ <h2 class="title">KP planets</h2>
2131
+ ${this.data.ayanamsa ? html10`<span class="ayanamsa">Ayanamsa: ${this.data.ayanamsa}</span>` : nothing10}
2132
+ </header>
2133
+ <table role="table">
2134
+ <thead>
2135
+ <tr>
2136
+ <th scope="col">Planet</th>
2137
+ <th scope="col">Sign</th>
2138
+ <th scope="col">Sign lord</th>
2139
+ <th scope="col">Nakshatra</th>
2140
+ <th scope="col">Star lord</th>
2141
+ <th scope="col">Sub lord</th>
2142
+ <th scope="col">Sub sub lord</th>
2143
+ <th scope="col">KP no.</th>
2144
+ </tr>
2145
+ </thead>
2146
+ <tbody>
2147
+ ${planets.map(
2148
+ (p) => html10`<tr>
2149
+ <td class="planet">
2150
+ ${p.planet ?? p.name ?? ""}
2151
+ ${p.retrograde ? html10`<span class="retro">R</span>` : nothing10}
2152
+ </td>
2153
+ <td>${p.sign ?? ""}</td>
2154
+ <td>${p.signLord ?? ""}</td>
2155
+ <td>${p.nakshatra ?? ""}</td>
2156
+ <td>${p.starLord ?? p.nakshatraLord ?? ""}</td>
2157
+ <td>${p.subLord ?? ""}</td>
2158
+ <td>${p.subSubLord ?? ""}</td>
2159
+ <td>${p.kpNumber ?? ""}</td>
2160
+ </tr>`
2161
+ )}
2162
+ </tbody>
2163
+ </table>
2164
+ </div>`;
2165
+ }
2166
+ };
2167
+ RoxyKpPlanetsTable.styles = [
2168
+ baseStyles,
2169
+ css11`
2170
+ .wrap {
2171
+ border: 1px solid var(--roxy-border, #e4e4e7);
2172
+ border-radius: var(--roxy-radius-md, 8px);
2173
+ background: var(--roxy-bg, #fff);
2174
+ overflow: auto;
2175
+ box-shadow: var(--roxy-shadow-sm);
2176
+ }
2177
+ .head {
2178
+ padding: var(--roxy-space-md, 1rem);
2179
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
2180
+ display: flex;
2181
+ justify-content: space-between;
2182
+ flex-wrap: wrap;
2183
+ gap: var(--roxy-space-sm, 0.5rem);
2184
+ }
2185
+ .title {
2186
+ margin: 0;
2187
+ font-size: var(--roxy-text-lg, 1.125rem);
2188
+ font-weight: var(--roxy-weight-bold, 600);
2189
+ }
2190
+ .ayanamsa {
2191
+ color: var(--roxy-muted, #71717a);
2192
+ font-size: var(--roxy-text-sm, 0.875rem);
2193
+ }
2194
+ table {
2195
+ width: 100%;
2196
+ border-collapse: collapse;
2197
+ font-size: var(--roxy-text-sm, 0.875rem);
2198
+ min-width: 560px;
2199
+ }
2200
+ thead {
2201
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 20%, transparent);
2202
+ }
2203
+ th,
2204
+ td {
2205
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
2206
+ text-align: left;
2207
+ white-space: nowrap;
2208
+ }
2209
+ th {
2210
+ color: var(--roxy-muted, #71717a);
2211
+ font-weight: var(--roxy-weight-bold, 600);
2212
+ text-transform: uppercase;
2213
+ font-size: var(--roxy-text-xs, 0.75rem);
2214
+ letter-spacing: 0.04em;
2215
+ }
2216
+ tbody tr {
2217
+ border-top: 1px solid var(--roxy-border, #e4e4e7);
2218
+ }
2219
+ td.planet {
2220
+ font-weight: var(--roxy-weight-bold, 600);
2221
+ color: var(--roxy-fg, #0a0a0a);
2222
+ }
2223
+ .retro {
2224
+ color: var(--roxy-warning, #ea580c);
2225
+ font-size: var(--roxy-text-xs, 0.75rem);
2226
+ margin-left: 4px;
2227
+ }
2228
+ `
2229
+ ];
2230
+ __decorateClass([
2231
+ property10({ attribute: false })
2232
+ ], RoxyKpPlanetsTable.prototype, "data", 2);
2233
+ RoxyKpPlanetsTable = __decorateClass([
2234
+ customElement10("roxy-kp-planets-table")
2235
+ ], RoxyKpPlanetsTable);
2236
+
2237
+ // packages/ui/src/components/location-search.ts
2238
+ import { css as css12, html as html11, LitElement as LitElement11, nothing as nothing11 } from "lit";
2239
+ import { customElement as customElement11, property as property11, state as state2 } from "lit/decorators.js";
2240
+
2241
+ // packages/ui/src/utils/debounce.ts
2242
+ function debounce(fn, wait) {
2243
+ let timer;
2244
+ return ((...args) => {
2245
+ if (timer) clearTimeout(timer);
2246
+ timer = setTimeout(() => fn(...args), wait);
2247
+ });
2248
+ }
2249
+
2250
+ // packages/ui/src/components/location-search.ts
2251
+ var RoxyLocationSearch = class extends LitElement11 {
2252
+ constructor() {
2253
+ super(...arguments);
2254
+ this.endpoint = "https://roxyapi.com/api/v2/location/search";
2255
+ this.placeholder = "Search city";
2256
+ this.defaultValue = "";
2257
+ this.query = "";
2258
+ this.results = [];
2259
+ this.isOpen = false;
2260
+ this.isLoading = false;
2261
+ this.highlight = -1;
2262
+ this.debouncedFetch = debounce((q) => {
2263
+ void this.fetchResults(q);
2264
+ }, 300);
2265
+ this.onInput = (e) => {
2266
+ const value = e.target.value;
2267
+ this.query = value;
2268
+ if (value.length < 2) {
2269
+ this.results = [];
2270
+ this.isOpen = false;
2271
+ this.highlight = -1;
2272
+ return;
2273
+ }
2274
+ this.debouncedFetch(value);
2275
+ };
2276
+ this.onKeyDown = (e) => {
2277
+ if (!this.isOpen || this.results.length === 0) {
2278
+ if (e.key === "ArrowDown" && this.query.length >= 2) {
2279
+ void this.fetchResults(this.query);
2280
+ e.preventDefault();
2281
+ }
2282
+ return;
2283
+ }
2284
+ if (e.key === "ArrowDown") {
2285
+ e.preventDefault();
2286
+ this.highlight = (this.highlight + 1) % this.results.length;
2287
+ } else if (e.key === "ArrowUp") {
2288
+ e.preventDefault();
2289
+ this.highlight = (this.highlight - 1 + this.results.length) % this.results.length;
2290
+ } else if (e.key === "Enter") {
2291
+ e.preventDefault();
2292
+ const target = this.results[this.highlight] ?? this.results[0];
2293
+ if (target) this.select(target);
2294
+ } else if (e.key === "Escape") {
2295
+ this.isOpen = false;
2296
+ }
2297
+ };
2298
+ }
2299
+ connectedCallback() {
2300
+ super.connectedCallback();
2301
+ this.query = this.defaultValue;
2302
+ this.clickOutsideHandler = (e) => {
2303
+ const path = e.composedPath();
2304
+ if (!path.includes(this)) this.isOpen = false;
2305
+ };
2306
+ document.addEventListener("mousedown", this.clickOutsideHandler);
2307
+ }
2308
+ disconnectedCallback() {
2309
+ super.disconnectedCallback();
2310
+ if (this.clickOutsideHandler) {
2311
+ document.removeEventListener("mousedown", this.clickOutsideHandler);
2312
+ }
2313
+ }
2314
+ async fetchResults(q) {
2315
+ this.isLoading = true;
2316
+ try {
2317
+ const url = new URL(this.endpoint);
2318
+ url.searchParams.set("q", q);
2319
+ url.searchParams.set("limit", "8");
2320
+ const headers = {
2321
+ Accept: "application/json"
2322
+ };
2323
+ if (this.apiKey) headers["X-API-Key"] = this.apiKey;
2324
+ if (this.publishableKey) headers["X-API-Key"] = this.publishableKey;
2325
+ const res = await fetch(url, { headers });
2326
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
2327
+ const json = await res.json();
2328
+ this.results = json.cities ?? [];
2329
+ this.isOpen = this.results.length > 0;
2330
+ this.highlight = this.results.length > 0 ? 0 : -1;
2331
+ } catch (_err) {
2332
+ this.results = [];
2333
+ this.isOpen = false;
2334
+ } finally {
2335
+ this.isLoading = false;
2336
+ }
2337
+ }
2338
+ select(city) {
2339
+ this.query = `${city.city}${city.province ? `, ${city.province}` : ""}, ${city.country}`;
2340
+ this.isOpen = false;
2341
+ this.results = [];
2342
+ this.dispatchEvent(
2343
+ new CustomEvent("roxy-location-select", {
2344
+ detail: city,
2345
+ bubbles: true,
2346
+ composed: true
2347
+ })
2348
+ );
2349
+ }
2350
+ render() {
2351
+ return html11`<div class="field">
2352
+ <input
2353
+ type="text"
2354
+ role="combobox"
2355
+ aria-expanded=${this.isOpen ? "true" : "false"}
2356
+ aria-controls="roxy-location-listbox"
2357
+ aria-autocomplete="list"
2358
+ autocomplete="off"
2359
+ placeholder=${this.placeholder}
2360
+ .value=${this.query}
2361
+ @input=${this.onInput}
2362
+ @keydown=${this.onKeyDown}
2363
+ @focus=${() => {
2364
+ if (this.results.length > 0) this.isOpen = true;
2365
+ }}
2366
+ />
2367
+ ${this.isLoading ? html11`<span class="spinner" role="status" aria-label="Loading"></span>` : nothing11}
2368
+ ${this.isOpen ? html11`<ul
2369
+ id="roxy-location-listbox"
2370
+ class="results"
2371
+ role="listbox"
2372
+ >
2373
+ ${this.results.length === 0 ? html11`<li class="empty" role="status">No cities found</li>` : this.results.map(
2374
+ (city, idx) => html11`<li role="presentation">
2375
+ <button
2376
+ type="button"
2377
+ class="option"
2378
+ role="option"
2379
+ aria-selected=${this.highlight === idx ? "true" : "false"}
2380
+ @click=${() => this.select(city)}
2381
+ @mouseenter=${() => {
2382
+ this.highlight = idx;
2383
+ }}
2384
+ >
2385
+ <span class="city">${city.city}</span>
2386
+ <span class="where"
2387
+ >${city.province ? html11`${city.province}, ` : ""}${city.country}</span
2388
+ >
2389
+ <span class="tz"
2390
+ >UTC${city.utcOffset >= 0 ? "+" : ""}${city.utcOffset}</span
2391
+ >
2392
+ </button>
2393
+ </li>`
2394
+ )}
2395
+ </ul>` : nothing11}
2396
+ </div>`;
2397
+ }
2398
+ };
2399
+ RoxyLocationSearch.styles = [
2400
+ baseStyles,
2401
+ css12`
2402
+ :host {
2403
+ display: block;
2404
+ position: relative;
2405
+ }
2406
+ .field {
2407
+ position: relative;
2408
+ }
2409
+ input {
2410
+ width: 100%;
2411
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
2412
+ font-size: var(--roxy-text-base, 1rem);
2413
+ font-family: inherit;
2414
+ color: var(--roxy-fg, #0a0a0a);
2415
+ background: var(--roxy-bg, #fff);
2416
+ border: 1px solid var(--roxy-border, #e4e4e7);
2417
+ border-radius: var(--roxy-radius-md, 8px);
2418
+ transition:
2419
+ border-color var(--roxy-motion-duration, 200ms)
2420
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
2421
+ box-sizing: border-box;
2422
+ }
2423
+ input:focus {
2424
+ outline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));
2425
+ outline-offset: 2px;
2426
+ border-color: var(--roxy-accent-fg, #b45309);
2427
+ }
2428
+ .spinner {
2429
+ position: absolute;
2430
+ right: 12px;
2431
+ top: 50%;
2432
+ transform: translateY(-50%);
2433
+ width: 14px;
2434
+ height: 14px;
2435
+ border: 2px solid var(--roxy-muted, #71717a);
2436
+ border-top-color: transparent;
2437
+ border-radius: 50%;
2438
+ animation: roxy-spin 700ms linear infinite;
2439
+ }
2440
+ @keyframes roxy-spin {
2441
+ to {
2442
+ transform: translateY(-50%) rotate(360deg);
2443
+ }
2444
+ }
2445
+ @media (prefers-reduced-motion: reduce) {
2446
+ .spinner {
2447
+ animation: none;
2448
+ }
2449
+ }
2450
+
2451
+ .results {
2452
+ position: absolute;
2453
+ z-index: 50;
2454
+ top: calc(100% + 4px);
2455
+ left: 0;
2456
+ right: 0;
2457
+ background: var(--roxy-bg, #fff);
2458
+ border: 1px solid var(--roxy-border, #e4e4e7);
2459
+ border-radius: var(--roxy-radius-md, 8px);
2460
+ box-shadow: var(--roxy-shadow-md);
2461
+ max-height: 22rem;
2462
+ overflow-y: auto;
2463
+ animation: roxy-fade-in var(--roxy-motion-duration, 200ms)
2464
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
2465
+ }
2466
+ .option {
2467
+ display: flex;
2468
+ align-items: baseline;
2469
+ gap: var(--roxy-space-sm, 0.5rem);
2470
+ width: 100%;
2471
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
2472
+ background: transparent;
2473
+ border: 0;
2474
+ text-align: left;
2475
+ font-family: inherit;
2476
+ font-size: var(--roxy-text-sm, 0.875rem);
2477
+ color: var(--roxy-fg, #0a0a0a);
2478
+ cursor: pointer;
2479
+ transition: background-color var(--roxy-motion-duration, 200ms);
2480
+ }
2481
+ .option:hover,
2482
+ .option[aria-selected='true'] {
2483
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 10%, transparent);
2484
+ }
2485
+ .option .city {
2486
+ font-weight: var(--roxy-weight-bold, 600);
2487
+ }
2488
+ .option .where {
2489
+ color: var(--roxy-muted, #71717a);
2490
+ flex-grow: 1;
2491
+ }
2492
+ .option .tz {
2493
+ color: var(--roxy-muted, #71717a);
2494
+ font-size: var(--roxy-text-xs, 0.75rem);
2495
+ font-variant-numeric: tabular-nums;
2496
+ }
2497
+ .empty {
2498
+ padding: var(--roxy-space-md, 1rem);
2499
+ color: var(--roxy-muted, #71717a);
2500
+ font-size: var(--roxy-text-sm, 0.875rem);
2501
+ }
2502
+ `
2503
+ ];
2504
+ __decorateClass([
2505
+ property11({ type: String, attribute: "api-key" })
2506
+ ], RoxyLocationSearch.prototype, "apiKey", 2);
2507
+ __decorateClass([
2508
+ property11({ type: String, attribute: "publishable-key" })
2509
+ ], RoxyLocationSearch.prototype, "publishableKey", 2);
2510
+ __decorateClass([
2511
+ property11({ type: String })
2512
+ ], RoxyLocationSearch.prototype, "endpoint", 2);
2513
+ __decorateClass([
2514
+ property11({ type: String })
2515
+ ], RoxyLocationSearch.prototype, "placeholder", 2);
2516
+ __decorateClass([
2517
+ property11({ type: String, attribute: "default-value" })
2518
+ ], RoxyLocationSearch.prototype, "defaultValue", 2);
2519
+ __decorateClass([
2520
+ state2()
2521
+ ], RoxyLocationSearch.prototype, "query", 2);
2522
+ __decorateClass([
2523
+ state2()
2524
+ ], RoxyLocationSearch.prototype, "results", 2);
2525
+ __decorateClass([
2526
+ state2()
2527
+ ], RoxyLocationSearch.prototype, "isOpen", 2);
2528
+ __decorateClass([
2529
+ state2()
2530
+ ], RoxyLocationSearch.prototype, "isLoading", 2);
2531
+ __decorateClass([
2532
+ state2()
2533
+ ], RoxyLocationSearch.prototype, "highlight", 2);
2534
+ RoxyLocationSearch = __decorateClass([
2535
+ customElement11("roxy-location-search")
2536
+ ], RoxyLocationSearch);
2537
+
2538
+ // packages/ui/src/components/moon-phase.ts
2539
+ import { css as css13, html as html12, LitElement as LitElement12, nothing as nothing12 } from "lit";
2540
+ import { customElement as customElement12, property as property12 } from "lit/decorators.js";
2541
+ var RoxyMoonPhase = class extends LitElement12 {
2542
+ constructor() {
2543
+ super(...arguments);
2544
+ this.data = null;
2545
+ this.mode = "current";
2546
+ }
2547
+ render() {
2548
+ const d = this.data;
2549
+ if (!d)
2550
+ return html12`<div class="roxy-empty" role="status">No moon phase data</div>`;
2551
+ const list = d.phases ?? d.upcoming ?? [];
2552
+ if (this.mode !== "current" && list.length > 0) {
2553
+ return html12`<article
2554
+ class="card"
2555
+ aria-label="Moon phase calendar"
2556
+ >
2557
+ <h2 class="label">${d.month ?? "Moon phases"} ${d.year ?? ""}</h2>
2558
+ <div class="list" role="list">
2559
+ ${list.map((phase) => this.renderListItem(phase))}
2560
+ </div>
2561
+ </article>`;
2562
+ }
2563
+ return this.renderSingle(d);
2564
+ }
2565
+ renderSingle(d) {
2566
+ const emoji = phaseEmoji(d.phase);
2567
+ return html12`<article class="card" aria-label="Current moon phase">
2568
+ <div class="hero">
2569
+ <span class="emoji" aria-hidden="true">${emoji}</span>
2570
+ <div>
2571
+ <h2 class="label">${d.phase ?? "Moon"}</h2>
2572
+ ${d.date ? html12`<div class="date">${d.date}</div>` : nothing12}
2573
+ </div>
2574
+ </div>
2575
+ <div class="stats">
2576
+ ${typeof d.illumination === "number" ? html12`<div>
2577
+ <span>Illumination</span>
2578
+ <strong>${(d.illumination * 100).toFixed(0)}%</strong>
2579
+ </div>` : nothing12}
2580
+ ${typeof d.age === "number" ? html12`<div>
2581
+ <span>Age</span>
2582
+ <strong>${d.age.toFixed(1)} days</strong>
2583
+ </div>` : nothing12}
2584
+ ${d.sign ? html12`<div>
2585
+ <span>Sign</span>
2586
+ <strong>${d.sign}</strong>
2587
+ </div>` : nothing12}
2588
+ ${typeof d.distance === "number" ? html12`<div>
2589
+ <span>Distance</span>
2590
+ <strong>${(d.distance / 1e3).toFixed(0)}k km</strong>
2591
+ </div>` : nothing12}
2592
+ </div>
2593
+ ${d.meaning?.description ? html12`<p class="meaning">${d.meaning.description}</p>` : nothing12}
2594
+ ${d.meaning?.keywords?.length ? html12`<div class="keywords">
2595
+ ${d.meaning.keywords.map((k) => html12`<span>${k}</span>`)}
2596
+ </div>` : nothing12}
2597
+ </article>`;
2598
+ }
2599
+ renderListItem(p) {
2600
+ const emoji = phaseEmoji(p.phase);
2601
+ return html12`<div class="list-item" role="listitem">
2602
+ <span aria-hidden="true">${emoji}</span>
2603
+ <span>${p.phase}</span>
2604
+ <span>${p.date ?? ""}</span>
2605
+ </div>`;
2606
+ }
2607
+ };
2608
+ RoxyMoonPhase.styles = [
2609
+ baseStyles,
2610
+ css13`
2611
+ .card {
2612
+ background: var(--roxy-bg, #fff);
2613
+ border: 1px solid var(--roxy-border, #e4e4e7);
2614
+ border-radius: var(--roxy-radius-md, 8px);
2615
+ padding: var(--roxy-space-lg, 1.5rem);
2616
+ box-shadow: var(--roxy-shadow-sm);
2617
+ display: grid;
2618
+ gap: var(--roxy-space-md, 1rem);
2619
+ }
2620
+
2621
+ .hero {
2622
+ display: flex;
2623
+ align-items: center;
2624
+ gap: var(--roxy-space-md, 1rem);
2625
+ }
2626
+ .emoji {
2627
+ font-size: 3rem;
2628
+ line-height: 1;
2629
+ }
2630
+ .label {
2631
+ margin: 0;
2632
+ font-size: var(--roxy-text-lg, 1.125rem);
2633
+ font-weight: var(--roxy-weight-bold, 600);
2634
+ text-transform: capitalize;
2635
+ }
2636
+ .date {
2637
+ color: var(--roxy-muted, #71717a);
2638
+ font-size: var(--roxy-text-sm, 0.875rem);
2639
+ }
2640
+
2641
+ .stats {
2642
+ display: grid;
2643
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
2644
+ gap: var(--roxy-space-md, 1rem);
2645
+ font-size: var(--roxy-text-sm, 0.875rem);
2646
+ color: var(--roxy-secondary, #475569);
2647
+ }
2648
+ .stats div span:first-child {
2649
+ display: block;
2650
+ color: var(--roxy-muted, #71717a);
2651
+ font-size: var(--roxy-text-xs, 0.75rem);
2652
+ text-transform: uppercase;
2653
+ letter-spacing: 0.06em;
2654
+ }
2655
+ .stats strong {
2656
+ color: var(--roxy-fg, #0a0a0a);
2657
+ font-variant-numeric: tabular-nums;
2658
+ }
2659
+
2660
+ .meaning {
2661
+ color: var(--roxy-fg, #0a0a0a);
2662
+ }
2663
+ .keywords {
2664
+ display: flex;
2665
+ flex-wrap: wrap;
2666
+ gap: var(--roxy-space-xs, 0.25rem);
2667
+ margin-top: var(--roxy-space-sm, 0.5rem);
2668
+ }
2669
+ .keywords span {
2670
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
2671
+ padding: 2px 8px;
2672
+ border-radius: var(--roxy-radius-full, 9999px);
2673
+ font-size: var(--roxy-text-xs, 0.75rem);
2674
+ }
2675
+
2676
+ .list {
2677
+ display: grid;
2678
+ gap: var(--roxy-space-sm, 0.5rem);
2679
+ }
2680
+ .list-item {
2681
+ display: grid;
2682
+ grid-template-columns: 2.5rem 1fr auto;
2683
+ gap: var(--roxy-space-sm, 0.5rem);
2684
+ align-items: center;
2685
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
2686
+ padding: var(--roxy-space-sm, 0.5rem) 0;
2687
+ font-size: var(--roxy-text-sm, 0.875rem);
2688
+ }
2689
+ .list-item:last-child {
2690
+ border-bottom: none;
2691
+ }
2692
+ `
2693
+ ];
2694
+ __decorateClass([
2695
+ property12({ attribute: false })
2696
+ ], RoxyMoonPhase.prototype, "data", 2);
2697
+ __decorateClass([
2698
+ property12({ type: String, reflect: true })
2699
+ ], RoxyMoonPhase.prototype, "mode", 2);
2700
+ RoxyMoonPhase = __decorateClass([
2701
+ customElement12("roxy-moon-phase")
2702
+ ], RoxyMoonPhase);
2703
+ function phaseEmoji(phase) {
2704
+ if (!phase) return "\u{1F319}";
2705
+ return MOON_PHASE_EMOJI[phase.toLowerCase()] ?? "\u{1F319}";
2706
+ }
2707
+
2708
+ // packages/ui/src/components/natal-chart.ts
2709
+ import { css as css14, html as html13, LitElement as LitElement13, nothing as nothing13, svg as svg3 } from "lit";
2710
+ import { customElement as customElement13, property as property13 } from "lit/decorators.js";
2711
+
2712
+ // packages/ui/src/utils/degree.ts
2713
+ function polarToCartesian(cx, cy, radius, angleDeg) {
2714
+ const angleRad = angleDeg * Math.PI / 180;
2715
+ return {
2716
+ x: cx + radius * Math.cos(angleRad),
2717
+ y: cy + radius * Math.sin(angleRad)
2718
+ };
2719
+ }
2720
+
2721
+ // packages/ui/src/components/natal-chart.ts
2722
+ var SIZE = 320;
2723
+ var CENTER = SIZE / 2;
2724
+ var OUTER_R = 150;
2725
+ var SIGN_R = 134;
2726
+ var HOUSE_R = 110;
2727
+ var PLANET_R = 88;
2728
+ var RoxyNatalChart = class extends LitElement13 {
2729
+ constructor() {
2730
+ super(...arguments);
2731
+ this.data = null;
2732
+ this.houseSystem = "placidus";
2733
+ }
2734
+ getPlanets() {
2735
+ const p = this.data?.planets;
2736
+ if (!p) return [];
2737
+ if (Array.isArray(p)) return p;
2738
+ return Object.entries(p).map(([name, entry]) => ({ ...entry, name }));
2739
+ }
2740
+ render() {
2741
+ if (!this.data)
2742
+ return html13`<div class="roxy-empty" role="status">No chart data</div>`;
2743
+ const planets = this.getPlanets();
2744
+ const aspects = this.data.aspects ?? [];
2745
+ return html13`<div class="wrap">
2746
+ <header>
2747
+ <h2 class="title">Natal chart</h2>
2748
+ ${this.data.birthDetails ? html13`<div class="meta">
2749
+ ${[
2750
+ this.data.birthDetails.date,
2751
+ this.data.birthDetails.time,
2752
+ this.data.birthDetails.location
2753
+ ].filter(Boolean).join(" \xB7 ")}
2754
+ </div>` : nothing13}
2755
+ </header>
2756
+ <svg
2757
+ viewBox="0 0 ${SIZE} ${SIZE}"
2758
+ role="img"
2759
+ aria-label="Natal chart wheel with twelve houses, planets, and aspects"
2760
+ >
2761
+ <title>Natal chart wheel</title>
2762
+ <desc>
2763
+ Twelve zodiac sign segments around a circular wheel. Planet glyphs are
2764
+ placed at their ecliptic longitudes. Aspect lines connect related planets.
2765
+ </desc>
2766
+ <circle
2767
+ class="wheel-line"
2768
+ cx=${CENTER}
2769
+ cy=${CENTER}
2770
+ r=${OUTER_R}
2771
+ stroke-width="1.5"
2772
+ />
2773
+ <circle
2774
+ class="wheel-line"
2775
+ cx=${CENTER}
2776
+ cy=${CENTER}
2777
+ r=${HOUSE_R}
2778
+ stroke-width="1"
2779
+ />
2780
+ <circle
2781
+ class="wheel-line"
2782
+ cx=${CENTER}
2783
+ cy=${CENTER}
2784
+ r=${PLANET_R - 16}
2785
+ stroke-width="0.5"
2786
+ />
2787
+ ${this.renderSpokes()} ${this.renderSigns()} ${this.renderHouseNumbers()}
2788
+ ${this.renderAspects(planets, aspects)} ${this.renderPlanets(planets)}
2789
+ </svg>
2790
+ <div class="legend">
2791
+ <span>${planets.length} planets</span>
2792
+ <span>${aspects.length} aspects</span>
2793
+ <span>House system: ${this.houseSystem}</span>
2794
+ </div>
2795
+ </div>`;
2796
+ }
2797
+ renderSpokes() {
2798
+ return Array.from({ length: 12 }, (_, i) => {
2799
+ const angle = i * 30 - 90;
2800
+ const start = polarToCartesian(CENTER, CENTER, HOUSE_R, angle);
2801
+ const end = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
2802
+ return svg3`<line class="wheel-line" x1=${start.x} y1=${start.y} x2=${end.x} y2=${end.y} stroke-width="0.8" />`;
2803
+ });
2804
+ }
2805
+ renderSigns() {
2806
+ const order = [
2807
+ "Aries",
2808
+ "Taurus",
2809
+ "Gemini",
2810
+ "Cancer",
2811
+ "Leo",
2812
+ "Virgo",
2813
+ "Libra",
2814
+ "Scorpio",
2815
+ "Sagittarius",
2816
+ "Capricorn",
2817
+ "Aquarius",
2818
+ "Pisces"
2819
+ ];
2820
+ return order.map((sign, i) => {
2821
+ const angle = i * 30 + 15 - 90;
2822
+ const pos = polarToCartesian(CENTER, CENTER, SIGN_R, angle);
2823
+ return svg3`<text class="sign-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${SIGN_GLYPH[sign]}</text>`;
2824
+ });
2825
+ }
2826
+ renderHouseNumbers() {
2827
+ return Array.from({ length: 12 }, (_, i) => {
2828
+ const angle = i * 30 + 15 - 90;
2829
+ const pos = polarToCartesian(CENTER, CENTER, HOUSE_R - 12, angle);
2830
+ return svg3`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${i + 1}</text>`;
2831
+ });
2832
+ }
2833
+ renderPlanets(planets) {
2834
+ return planets.map((p) => {
2835
+ const lon = typeof p.longitude === "number" ? p.longitude : typeof p.degree === "number" ? p.degree : NaN;
2836
+ if (!Number.isFinite(lon)) return nothing13;
2837
+ const angle = lon - 90;
2838
+ const pos = polarToCartesian(CENTER, CENTER, PLANET_R, angle);
2839
+ const name = p.name ?? p.planet ?? "";
2840
+ const glyph = PLANET_GLYPH[capitalize2(name)] ?? name.slice(0, 2);
2841
+ const retro = p.retrograde || p.isRetrograde ? " R" : "";
2842
+ return svg3`<text class="planet-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central"><title>${name}${retro}</title>${glyph}</text>`;
2843
+ });
2844
+ }
2845
+ renderAspects(planets, aspects) {
2846
+ const planetMap = /* @__PURE__ */ new Map();
2847
+ for (const p of planets) {
2848
+ const lon = typeof p.longitude === "number" ? p.longitude : typeof p.degree === "number" ? p.degree : null;
2849
+ if (lon === null) continue;
2850
+ const name = capitalize2(p.name ?? p.planet ?? "");
2851
+ if (name) planetMap.set(name, lon);
2852
+ }
2853
+ return aspects.map((a) => {
2854
+ const l1 = planetMap.get(capitalize2(a.planet1 ?? ""));
2855
+ const l2 = planetMap.get(capitalize2(a.planet2 ?? ""));
2856
+ if (l1 === void 0 || l2 === void 0) return nothing13;
2857
+ const p1 = polarToCartesian(CENTER, CENTER, PLANET_R - 18, l1 - 90);
2858
+ const p2 = polarToCartesian(CENTER, CENTER, PLANET_R - 18, l2 - 90);
2859
+ return svg3`<line class="aspect" x1=${p1.x} y1=${p1.y} x2=${p2.x} y2=${p2.y} />`;
2860
+ });
2861
+ }
2862
+ };
2863
+ RoxyNatalChart.styles = [
2864
+ baseStyles,
2865
+ css14`
2866
+ .wrap {
2867
+ width: 100%;
2868
+ display: grid;
2869
+ gap: var(--roxy-space-md, 1rem);
2870
+ }
2871
+
2872
+ .title {
2873
+ font-size: var(--roxy-text-lg, 1.125rem);
2874
+ font-weight: var(--roxy-weight-bold, 600);
2875
+ margin: 0;
2876
+ color: var(--roxy-primary, #0f172a);
2877
+ }
2878
+
2879
+ .meta {
2880
+ color: var(--roxy-muted, #71717a);
2881
+ font-size: var(--roxy-text-sm, 0.875rem);
2882
+ }
2883
+
2884
+ svg {
2885
+ display: block;
2886
+ width: 100%;
2887
+ max-width: 360px;
2888
+ height: auto;
2889
+ margin: 0 auto;
2890
+ }
2891
+
2892
+ .wheel-line {
2893
+ fill: none;
2894
+ stroke: var(--roxy-border, #e4e4e7);
2895
+ }
2896
+
2897
+ .sign-glyph {
2898
+ fill: var(--roxy-secondary, #475569);
2899
+ font-size: 14px;
2900
+ font-family: var(--roxy-font-sans);
2901
+ }
2902
+
2903
+ .planet-glyph {
2904
+ fill: var(--roxy-accent, #f59e0b);
2905
+ font-size: 14px;
2906
+ font-weight: 600;
2907
+ font-family: var(--roxy-font-sans);
2908
+ }
2909
+
2910
+ .house-num {
2911
+ fill: var(--roxy-muted, #71717a);
2912
+ font-size: 9px;
2913
+ font-family: var(--roxy-font-sans);
2914
+ }
2915
+
2916
+ .aspect {
2917
+ stroke: color-mix(in srgb, var(--roxy-accent, #f59e0b) 32%, transparent);
2918
+ stroke-width: 0.6;
2919
+ fill: none;
2920
+ }
2921
+
2922
+ .legend {
2923
+ font-size: var(--roxy-text-xs, 0.75rem);
2924
+ color: var(--roxy-muted, #71717a);
2925
+ display: flex;
2926
+ flex-wrap: wrap;
2927
+ gap: var(--roxy-space-md, 1rem);
2928
+ }
2929
+ `
2930
+ ];
2931
+ __decorateClass([
2932
+ property13({ attribute: false })
2933
+ ], RoxyNatalChart.prototype, "data", 2);
2934
+ __decorateClass([
2935
+ property13({ type: String, attribute: "house-system", reflect: true })
2936
+ ], RoxyNatalChart.prototype, "houseSystem", 2);
2937
+ RoxyNatalChart = __decorateClass([
2938
+ customElement13("roxy-natal-chart")
2939
+ ], RoxyNatalChart);
2940
+ function capitalize2(s) {
2941
+ if (!s) return "";
2942
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
2943
+ }
2944
+
2945
+ // packages/ui/src/components/numerology-card.ts
2946
+ import { css as css15, html as html14, LitElement as LitElement14, nothing as nothing14 } from "lit";
2947
+ import { customElement as customElement14, property as property14 } from "lit/decorators.js";
2948
+ var RoxyNumerologyCard = class extends LitElement14 {
2949
+ constructor() {
2950
+ super(...arguments);
2951
+ this.data = null;
2952
+ this.type = "life-path";
2953
+ }
2954
+ render() {
2955
+ const d = this.data;
2956
+ if (!d)
2957
+ return html14`<div class="roxy-empty" role="status">No numerology data</div>`;
2958
+ const headerLabel = LABELS[this.type] ?? this.type;
2959
+ const number = d.personalYear ?? d.number;
2960
+ const cores = d.coreNumbers ? Object.entries(d.coreNumbers).filter(
2961
+ ([, v]) => v !== null && v !== void 0
2962
+ ) : [];
2963
+ return html14`<article
2964
+ class="card"
2965
+ aria-label=${headerLabel}
2966
+ >
2967
+ <div class="hero">
2968
+ ${typeof number === "number" ? html14`<div class="numeral">${number}</div>` : nothing14}
2969
+ <div>
2970
+ <p class="label">${headerLabel}</p>
2971
+ ${d.title ? html14`<h2 class="title">${d.title}</h2>` : d.type ? html14`<h2 class="title">
2972
+ ${d.type === "master" ? "Master number" : "Single digit"}
2973
+ </h2>` : nothing14}
2974
+ </div>
2975
+ </div>
2976
+ ${d.theme ? html14`<p><strong>Theme:</strong> ${d.theme}</p>` : nothing14}
2977
+ ${d.meaning ? html14`<p class="meaning">${d.meaning}</p>` : nothing14}
2978
+ ${d.advice ? html14`<p>${d.advice}</p>` : nothing14}
2979
+ ${d.calculation ? html14`<pre class="calc">${d.calculation}</pre>` : nothing14}
2980
+ ${d.keywords?.length ? html14`<div class="chips">
2981
+ ${d.keywords.map((k) => html14`<span>${k}</span>`)}
2982
+ </div>` : nothing14}
2983
+ ${cores.length > 0 ? html14`<div class="cores">
2984
+ ${cores.map(([k, v]) => {
2985
+ const value = typeof v === "number" ? v : v.number;
2986
+ return html14`<div class="item">
2987
+ <span>${humanize2(k)}</span>
2988
+ <strong>${value ?? ""}</strong>
2989
+ </div>`;
2990
+ })}
2991
+ </div>` : nothing14}
2992
+ ${d.hasKarmicDebt && d.karmicDebtNumber ? html14`<div class="karmic">
2993
+ Karmic debt ${d.karmicDebtNumber}.
2994
+ ${d.karmicDebtMeaning ? d.karmicDebtMeaning : ""}
2995
+ </div>` : nothing14}
2996
+ </article>`;
2997
+ }
2998
+ };
2999
+ RoxyNumerologyCard.styles = [
3000
+ baseStyles,
3001
+ css15`
3002
+ .card {
3003
+ background: var(--roxy-bg, #fff);
3004
+ border: 1px solid var(--roxy-border, #e4e4e7);
3005
+ border-radius: var(--roxy-radius-md, 8px);
3006
+ padding: var(--roxy-space-lg, 1.5rem);
3007
+ box-shadow: var(--roxy-shadow-sm);
3008
+ display: grid;
3009
+ gap: var(--roxy-space-md, 1rem);
3010
+ }
3011
+
3012
+ .hero {
3013
+ display: flex;
3014
+ align-items: center;
3015
+ gap: var(--roxy-space-md, 1rem);
3016
+ }
3017
+ .numeral {
3018
+ font-size: 4rem;
3019
+ line-height: 1;
3020
+ font-weight: var(--roxy-weight-bold, 600);
3021
+ color: var(--roxy-accent-fg, #b45309);
3022
+ font-variant-numeric: tabular-nums;
3023
+ }
3024
+ .label {
3025
+ margin: 0;
3026
+ font-size: var(--roxy-text-xs, 0.75rem);
3027
+ color: var(--roxy-muted, #71717a);
3028
+ text-transform: uppercase;
3029
+ letter-spacing: 0.06em;
3030
+ }
3031
+ .title {
3032
+ margin: 0;
3033
+ font-size: var(--roxy-text-lg, 1.125rem);
3034
+ font-weight: var(--roxy-weight-bold, 600);
3035
+ }
3036
+ .meaning {
3037
+ margin: 0;
3038
+ color: var(--roxy-fg, #0a0a0a);
3039
+ }
3040
+
3041
+ .calc {
3042
+ margin: 0;
3043
+ font-family: var(--roxy-font-mono);
3044
+ font-size: var(--roxy-text-xs, 0.75rem);
3045
+ color: var(--roxy-muted, #71717a);
3046
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 30%, transparent);
3047
+ padding: var(--roxy-space-sm, 0.5rem);
3048
+ border-radius: var(--roxy-radius-sm, 4px);
3049
+ word-break: break-all;
3050
+ }
3051
+
3052
+ .chips {
3053
+ display: flex;
3054
+ flex-wrap: wrap;
3055
+ gap: var(--roxy-space-xs, 0.25rem);
3056
+ }
3057
+ .chips span {
3058
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
3059
+ padding: 2px 8px;
3060
+ border-radius: var(--roxy-radius-full, 9999px);
3061
+ font-size: var(--roxy-text-xs, 0.75rem);
3062
+ }
3063
+
3064
+ .cores {
3065
+ display: grid;
3066
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
3067
+ gap: var(--roxy-space-sm, 0.5rem);
3068
+ border-top: 1px solid var(--roxy-border, #e4e4e7);
3069
+ padding-top: var(--roxy-space-md, 1rem);
3070
+ }
3071
+ .cores .item {
3072
+ display: flex;
3073
+ align-items: baseline;
3074
+ gap: var(--roxy-space-xs, 0.25rem);
3075
+ font-size: var(--roxy-text-sm, 0.875rem);
3076
+ }
3077
+ .cores .item span:first-child {
3078
+ color: var(--roxy-muted, #71717a);
3079
+ text-transform: capitalize;
3080
+ }
3081
+ .cores .item strong {
3082
+ color: var(--roxy-accent-fg, #b45309);
3083
+ font-variant-numeric: tabular-nums;
3084
+ font-size: var(--roxy-text-base, 1rem);
3085
+ font-weight: var(--roxy-weight-bold, 600);
3086
+ }
3087
+
3088
+ .karmic {
3089
+ background: color-mix(in srgb, var(--roxy-warning, #ea580c) 12%, transparent);
3090
+ border: 1px solid color-mix(in srgb, var(--roxy-warning, #ea580c) 32%, transparent);
3091
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
3092
+ border-radius: var(--roxy-radius-md, 8px);
3093
+ font-size: var(--roxy-text-sm, 0.875rem);
3094
+ color: var(--roxy-fg, #0a0a0a);
3095
+ }
3096
+ `
3097
+ ];
3098
+ __decorateClass([
3099
+ property14({ attribute: false })
3100
+ ], RoxyNumerologyCard.prototype, "data", 2);
3101
+ __decorateClass([
3102
+ property14({ type: String, reflect: true })
3103
+ ], RoxyNumerologyCard.prototype, "type", 2);
3104
+ RoxyNumerologyCard = __decorateClass([
3105
+ customElement14("roxy-numerology-card")
3106
+ ], RoxyNumerologyCard);
3107
+ var LABELS = {
3108
+ "life-path": "Life Path",
3109
+ expression: "Expression",
3110
+ "personal-year": "Personal Year",
3111
+ chart: "Numerology chart"
3112
+ };
3113
+ function humanize2(s) {
3114
+ return s.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^\w/, (c) => c.toUpperCase());
3115
+ }
3116
+
3117
+ // packages/ui/src/components/panchang-table.ts
3118
+ import { css as css16, html as html15, LitElement as LitElement15, nothing as nothing15 } from "lit";
3119
+ import { customElement as customElement15, property as property15 } from "lit/decorators.js";
3120
+ var RoxyPanchangTable = class extends LitElement15 {
3121
+ constructor() {
3122
+ super(...arguments);
3123
+ this.data = null;
3124
+ this.detail = "detailed";
3125
+ }
3126
+ render() {
3127
+ const d = this.data;
3128
+ if (!d)
3129
+ return html15`<div class="roxy-empty" role="status">No panchang data</div>`;
3130
+ const fivefold = [
3131
+ ["Tithi", this.formatPart(d.tithi)],
3132
+ ["Nakshatra", this.formatPart(d.nakshatra)],
3133
+ ["Yoga", this.formatPart(d.yoga)],
3134
+ ["Karana", this.formatPart(d.karana)],
3135
+ ["Vara", d.vara ?? ""]
3136
+ ];
3137
+ const muhurtas = [
3138
+ ["Brahma Muhurta", d.brahmaMuhurta],
3139
+ ["Abhijit Muhurta", d.abhijitMuhurta],
3140
+ ["Vijaya Muhurta", d.vijayaMuhurta],
3141
+ ["Godhuli Muhurta", d.godhuliMuhurta],
3142
+ ["Nishita Muhurta", d.nishitaMuhurta],
3143
+ ["Pratah Sandhya", d.pratahSandhya],
3144
+ ["Sayahna Sandhya", d.sayahnaSandhya]
3145
+ ];
3146
+ const inauspicious = [
3147
+ ["Rahu Kaal", d.rahuKaal],
3148
+ ["Yamaganda", d.yamaganda],
3149
+ ["Gulika", d.gulika]
3150
+ ];
3151
+ return html15`<div class="wrap" aria-label="Panchang">
3152
+ <header class="head">
3153
+ <h2 class="title">Panchang</h2>
3154
+ <span class="date">${d.date ?? ""}</span>
3155
+ </header>
3156
+ <table>
3157
+ <tbody>
3158
+ ${fivefold.map(
3159
+ ([k, v]) => html15`<tr>
3160
+ <th>${k}</th>
3161
+ <td>${v}</td>
3162
+ </tr>`
3163
+ )}
3164
+ ${d.sunrise ? html15`<tr>
3165
+ <th>Sunrise</th>
3166
+ <td>${d.sunrise}</td>
3167
+ </tr>` : nothing15}
3168
+ ${d.sunset ? html15`<tr>
3169
+ <th>Sunset</th>
3170
+ <td>${d.sunset}</td>
3171
+ </tr>` : nothing15}
3172
+ ${d.moonrise ? html15`<tr>
3173
+ <th>Moonrise</th>
3174
+ <td>${d.moonrise}</td>
3175
+ </tr>` : nothing15}
3176
+ ${d.moonset ? html15`<tr>
3177
+ <th>Moonset</th>
3178
+ <td>${d.moonset}</td>
3179
+ </tr>` : nothing15}
3180
+ </tbody>
3181
+ </table>
3182
+ ${this.detail === "detailed" && (muhurtas.some((m) => !!m[1]) || inauspicious.some((m) => !!m[1])) ? html15`
3183
+ <div class="section">Auspicious muhurtas</div>
3184
+ <table>
3185
+ <tbody>
3186
+ ${muhurtas.filter(([, v]) => !!v).map(
3187
+ ([k, v]) => html15`<tr>
3188
+ <th>${k}</th>
3189
+ <td>${formatRange(v)}</td>
3190
+ </tr>`
3191
+ )}
3192
+ </tbody>
3193
+ </table>
3194
+ <div class="section">Inauspicious periods</div>
3195
+ <table>
3196
+ <tbody>
3197
+ ${inauspicious.filter(([, v]) => !!v).map(
3198
+ ([k, v]) => html15`<tr>
3199
+ <th>${k}</th>
3200
+ <td>${formatRange(v)}</td>
3201
+ </tr>`
3202
+ )}
3203
+ </tbody>
3204
+ </table>
3205
+ ` : nothing15}
3206
+ </div>`;
3207
+ }
3208
+ formatPart(v) {
3209
+ if (!v) return "";
3210
+ if (typeof v === "string") return v;
3211
+ if (typeof v === "object") {
3212
+ const obj = v;
3213
+ const parts = [
3214
+ obj.name,
3215
+ obj.lord ? `(${obj.lord})` : "",
3216
+ obj.phase
3217
+ ].filter(Boolean);
3218
+ return parts.join(" ");
3219
+ }
3220
+ return String(v);
3221
+ }
3222
+ };
3223
+ RoxyPanchangTable.styles = [
3224
+ baseStyles,
3225
+ css16`
3226
+ .wrap {
3227
+ border: 1px solid var(--roxy-border, #e4e4e7);
3228
+ border-radius: var(--roxy-radius-md, 8px);
3229
+ background: var(--roxy-bg, #fff);
3230
+ overflow: hidden;
3231
+ box-shadow: var(--roxy-shadow-sm);
3232
+ }
3233
+ .head {
3234
+ padding: var(--roxy-space-md, 1rem);
3235
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
3236
+ display: flex;
3237
+ justify-content: space-between;
3238
+ flex-wrap: wrap;
3239
+ gap: var(--roxy-space-sm, 0.5rem);
3240
+ }
3241
+ .title {
3242
+ margin: 0;
3243
+ font-size: var(--roxy-text-lg, 1.125rem);
3244
+ font-weight: var(--roxy-weight-bold, 600);
3245
+ }
3246
+ .date {
3247
+ color: var(--roxy-muted, #71717a);
3248
+ font-size: var(--roxy-text-sm, 0.875rem);
3249
+ }
3250
+ table {
3251
+ width: 100%;
3252
+ border-collapse: collapse;
3253
+ font-size: var(--roxy-text-sm, 0.875rem);
3254
+ }
3255
+ tbody tr:nth-child(odd) {
3256
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 24%, transparent);
3257
+ }
3258
+ th,
3259
+ td {
3260
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
3261
+ text-align: left;
3262
+ vertical-align: top;
3263
+ }
3264
+ th {
3265
+ color: var(--roxy-muted, #71717a);
3266
+ font-weight: var(--roxy-weight-bold, 600);
3267
+ width: 38%;
3268
+ text-transform: capitalize;
3269
+ }
3270
+ td {
3271
+ color: var(--roxy-fg, #0a0a0a);
3272
+ font-variant-numeric: tabular-nums;
3273
+ }
3274
+ .section {
3275
+ border-top: 1px solid var(--roxy-border, #e4e4e7);
3276
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
3277
+ font-size: var(--roxy-text-xs, 0.75rem);
3278
+ color: var(--roxy-muted, #71717a);
3279
+ font-weight: var(--roxy-weight-bold, 600);
3280
+ text-transform: uppercase;
3281
+ letter-spacing: 0.06em;
3282
+ }
3283
+ `
3284
+ ];
3285
+ __decorateClass([
3286
+ property15({ attribute: false })
3287
+ ], RoxyPanchangTable.prototype, "data", 2);
3288
+ __decorateClass([
3289
+ property15({ type: String, reflect: true })
3290
+ ], RoxyPanchangTable.prototype, "detail", 2);
3291
+ RoxyPanchangTable = __decorateClass([
3292
+ customElement15("roxy-panchang-table")
3293
+ ], RoxyPanchangTable);
3294
+ function formatRange(t) {
3295
+ if (!t) return "";
3296
+ if (t.start && t.end) return `${t.start} - ${t.end}`;
3297
+ return t.start ?? t.end ?? "";
3298
+ }
3299
+
3300
+ // packages/ui/src/components/synastry-chart.ts
3301
+ import { css as css17, html as html16, LitElement as LitElement16, nothing as nothing16, svg as svg4 } from "lit";
3302
+ import { customElement as customElement16, property as property16 } from "lit/decorators.js";
3303
+ var SIZE2 = 360;
3304
+ var CENTER2 = SIZE2 / 2;
3305
+ var OUTER_R2 = 170;
3306
+ var SIGN_R2 = 154;
3307
+ var P1_R = 124;
3308
+ var P2_R = 96;
3309
+ var RoxySynastryChart = class extends LitElement16 {
3310
+ constructor() {
3311
+ super(...arguments);
3312
+ this.data = null;
3313
+ }
3314
+ render() {
3315
+ if (!this.data)
3316
+ return html16`<div class="roxy-empty" role="status">No synastry data</div>`;
3317
+ const {
3318
+ person1,
3319
+ person2,
3320
+ compatibilityScore,
3321
+ summary,
3322
+ interAspects = []
3323
+ } = this.data;
3324
+ const p1Planets = this.normalizePlanets(person1?.planets);
3325
+ const p2Planets = this.normalizePlanets(person2?.planets);
3326
+ return html16`<div
3327
+ class="wrap"
3328
+ aria-label="Synastry compatibility chart"
3329
+ >
3330
+ <div class="head">
3331
+ <h2 class="title">Synastry</h2>
3332
+ ${typeof compatibilityScore === "number" ? html16`<span class="score" aria-label=${`Score ${compatibilityScore} of 100`}
3333
+ >${compatibilityScore} / 100</span
3334
+ >` : nothing16}
3335
+ </div>
3336
+ <svg
3337
+ viewBox="0 0 ${SIZE2} ${SIZE2}"
3338
+ role="img"
3339
+ aria-label="Dual chart wheel comparing two natal charts"
3340
+ >
3341
+ <title>Synastry dual wheel</title>
3342
+ <circle
3343
+ class="wheel-line"
3344
+ cx=${CENTER2}
3345
+ cy=${CENTER2}
3346
+ r=${OUTER_R2}
3347
+ stroke-width="1.5"
3348
+ />
3349
+ <circle
3350
+ class="wheel-line"
3351
+ cx=${CENTER2}
3352
+ cy=${CENTER2}
3353
+ r=${P2_R + 14}
3354
+ stroke-width="0.8"
3355
+ />
3356
+ <circle
3357
+ class="wheel-line"
3358
+ cx=${CENTER2}
3359
+ cy=${CENTER2}
3360
+ r=${P2_R - 14}
3361
+ stroke-width="0.6"
3362
+ />
3363
+ ${this.renderSpokes()} ${this.renderSigns()}
3364
+ ${this.renderRing(p1Planets, P1_R, "p1")} ${this.renderRing(p2Planets, P2_R, "p2")}
3365
+ </svg>
3366
+ ${summary ? html16`<p class="summary">${summary}</p>` : nothing16}
3367
+ ${interAspects.length > 0 ? this.renderAspects(interAspects) : nothing16}
3368
+ ${(this.data.strengths?.length ?? 0) > 0 || (this.data.challenges?.length ?? 0) > 0 ? html16`<div class="lists">
3369
+ ${this.data.strengths?.length ? html16`<div>
3370
+ <h3>Strengths</h3>
3371
+ <ul>
3372
+ ${this.data.strengths.map((s) => html16`<li>${s}</li>`)}
3373
+ </ul>
3374
+ </div>` : nothing16}
3375
+ ${this.data.challenges?.length ? html16`<div>
3376
+ <h3>Challenges</h3>
3377
+ <ul>
3378
+ ${this.data.challenges.map((s) => html16`<li>${s}</li>`)}
3379
+ </ul>
3380
+ </div>` : nothing16}
3381
+ </div>` : nothing16}
3382
+ </div>`;
3383
+ }
3384
+ normalizePlanets(p) {
3385
+ if (!p) return [];
3386
+ if (Array.isArray(p)) return p;
3387
+ return Object.entries(p).map(([name, e]) => ({ ...e, name }));
3388
+ }
3389
+ renderSpokes() {
3390
+ return Array.from({ length: 12 }, (_, i) => {
3391
+ const angle = i * 30 - 90;
3392
+ const start = polarToCartesian(CENTER2, CENTER2, P2_R - 14, angle);
3393
+ const end = polarToCartesian(CENTER2, CENTER2, OUTER_R2, angle);
3394
+ return svg4`<line class="wheel-line" x1=${start.x} y1=${start.y} x2=${end.x} y2=${end.y} stroke-width="0.6" />`;
3395
+ });
3396
+ }
3397
+ renderSigns() {
3398
+ const order = [
3399
+ "Aries",
3400
+ "Taurus",
3401
+ "Gemini",
3402
+ "Cancer",
3403
+ "Leo",
3404
+ "Virgo",
3405
+ "Libra",
3406
+ "Scorpio",
3407
+ "Sagittarius",
3408
+ "Capricorn",
3409
+ "Aquarius",
3410
+ "Pisces"
3411
+ ];
3412
+ return order.map((s, i) => {
3413
+ const angle = i * 30 + 15 - 90;
3414
+ const pos = polarToCartesian(CENTER2, CENTER2, SIGN_R2, angle);
3415
+ return svg4`<text class="sign" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${SIGN_GLYPH[s]}</text>`;
3416
+ });
3417
+ }
3418
+ renderRing(planets, radius, cls) {
3419
+ return planets.map((p) => {
3420
+ const lon = typeof p.longitude === "number" ? p.longitude : typeof p.degree === "number" ? p.degree : NaN;
3421
+ if (!Number.isFinite(lon)) return nothing16;
3422
+ const pos = polarToCartesian(CENTER2, CENTER2, radius, lon - 90);
3423
+ const name = p.name ?? p.planet ?? "";
3424
+ const glyph = PLANET_GLYPH[capitalize3(name)] ?? name.slice(0, 2);
3425
+ return svg4`<text class=${cls} x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central"><title>${name}</title>${glyph}</text>`;
3426
+ });
3427
+ }
3428
+ renderAspects(aspects) {
3429
+ return html16`<table>
3430
+ <thead>
3431
+ <tr>
3432
+ <th>Planet 1</th>
3433
+ <th>Planet 2</th>
3434
+ <th>Aspect</th>
3435
+ <th>Orb</th>
3436
+ <th>Strength</th>
3437
+ </tr>
3438
+ </thead>
3439
+ <tbody>
3440
+ ${aspects.slice(0, 16).map(
3441
+ (a) => html16`<tr>
3442
+ <td>${a.planet1 ?? ""}</td>
3443
+ <td>${a.planet2 ?? ""}</td>
3444
+ <td>${a.aspect ?? ""}</td>
3445
+ <td class="orb">
3446
+ ${typeof a.orb === "number" ? a.orb.toFixed(1) : ""}
3447
+ </td>
3448
+ <td>${a.strength ?? ""}</td>
3449
+ </tr>`
3450
+ )}
3451
+ </tbody>
3452
+ </table>`;
3453
+ }
3454
+ };
3455
+ RoxySynastryChart.styles = [
3456
+ baseStyles,
3457
+ css17`
3458
+ .wrap {
3459
+ display: grid;
3460
+ gap: var(--roxy-space-md, 1rem);
3461
+ }
3462
+
3463
+ .head {
3464
+ display: flex;
3465
+ justify-content: space-between;
3466
+ align-items: center;
3467
+ gap: var(--roxy-space-md, 1rem);
3468
+ flex-wrap: wrap;
3469
+ }
3470
+
3471
+ .title {
3472
+ font-size: var(--roxy-text-lg, 1.125rem);
3473
+ font-weight: var(--roxy-weight-bold, 600);
3474
+ margin: 0;
3475
+ }
3476
+
3477
+ .score {
3478
+ font-variant-numeric: tabular-nums;
3479
+ font-weight: var(--roxy-weight-bold, 600);
3480
+ color: var(--roxy-accent-fg, #b45309);
3481
+ font-size: var(--roxy-text-xl, 1.5rem);
3482
+ }
3483
+
3484
+ svg {
3485
+ display: block;
3486
+ width: 100%;
3487
+ max-width: 400px;
3488
+ margin: 0 auto;
3489
+ }
3490
+
3491
+ .wheel-line {
3492
+ fill: none;
3493
+ stroke: var(--roxy-border, #e4e4e7);
3494
+ }
3495
+ .sign {
3496
+ fill: var(--roxy-secondary, #475569);
3497
+ font-size: 14px;
3498
+ }
3499
+ .p1 {
3500
+ fill: var(--roxy-accent, #f59e0b);
3501
+ font-weight: 600;
3502
+ font-size: 13px;
3503
+ }
3504
+ .p2 {
3505
+ fill: var(--roxy-info, #0284c7);
3506
+ font-weight: 600;
3507
+ font-size: 13px;
3508
+ }
3509
+
3510
+ .summary {
3511
+ margin: 0;
3512
+ color: var(--roxy-fg, #0a0a0a);
3513
+ font-size: var(--roxy-text-base, 1rem);
3514
+ }
3515
+
3516
+ table {
3517
+ width: 100%;
3518
+ border-collapse: collapse;
3519
+ font-size: var(--roxy-text-sm, 0.875rem);
3520
+ }
3521
+ th,
3522
+ td {
3523
+ padding: var(--roxy-space-sm, 0.5rem);
3524
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
3525
+ text-align: left;
3526
+ }
3527
+ th {
3528
+ color: var(--roxy-muted, #71717a);
3529
+ font-weight: var(--roxy-weight-bold, 600);
3530
+ text-transform: uppercase;
3531
+ font-size: var(--roxy-text-xs, 0.75rem);
3532
+ letter-spacing: 0.06em;
3533
+ }
3534
+ td.orb {
3535
+ font-variant-numeric: tabular-nums;
3536
+ color: var(--roxy-muted, #71717a);
3537
+ }
3538
+
3539
+ .lists {
3540
+ display: grid;
3541
+ grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
3542
+ gap: var(--roxy-space-md, 1rem);
3543
+ }
3544
+ .lists h3 {
3545
+ margin: 0 0 var(--roxy-space-xs, 0.25rem) 0;
3546
+ font-size: var(--roxy-text-xs, 0.75rem);
3547
+ color: var(--roxy-muted, #71717a);
3548
+ text-transform: uppercase;
3549
+ letter-spacing: 0.06em;
3550
+ }
3551
+ .lists ul {
3552
+ margin: 0;
3553
+ padding-left: var(--roxy-space-md, 1rem);
3554
+ font-size: var(--roxy-text-sm, 0.875rem);
3555
+ }
3556
+ `
3557
+ ];
3558
+ __decorateClass([
3559
+ property16({ attribute: false })
3560
+ ], RoxySynastryChart.prototype, "data", 2);
3561
+ RoxySynastryChart = __decorateClass([
3562
+ customElement16("roxy-synastry-chart")
3563
+ ], RoxySynastryChart);
3564
+ function capitalize3(s) {
3565
+ if (!s) return "";
3566
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
3567
+ }
3568
+
3569
+ // packages/ui/src/components/tarot-card.ts
3570
+ import { css as css18, html as html17, LitElement as LitElement17, nothing as nothing17 } from "lit";
3571
+ import { customElement as customElement17, property as property17, state as state3 } from "lit/decorators.js";
3572
+ var RoxyTarotCard = class extends LitElement17 {
3573
+ constructor() {
3574
+ super(...arguments);
3575
+ this.data = null;
3576
+ this.flipped = false;
3577
+ this.toggleFlip = () => {
3578
+ this.flipped = !this.flipped;
3579
+ };
3580
+ }
3581
+ getCard() {
3582
+ if (!this.data) return null;
3583
+ if ("card" in this.data && this.data.card) return this.data.card;
3584
+ return this.data;
3585
+ }
3586
+ render() {
3587
+ const card = this.getCard();
3588
+ if (!card)
3589
+ return html17`<div class="roxy-empty" role="status">No tarot data</div>`;
3590
+ const isReversed = this.flipped !== Boolean(card.reversed);
3591
+ const meaning = typeof card.meaning === "string" ? card.meaning : (isReversed ? card.meaning?.reversed : card.meaning?.upright) ?? card.meaning?.spiritual ?? card.upright?.meaning;
3592
+ const dailyMessage = this.data && "dailyMessage" in this.data ? this.data.dailyMessage : void 0;
3593
+ return html17`<article class="card" aria-label=${card.name ?? "Tarot card"}>
3594
+ <div class="image-wrap">
3595
+ ${card.imageUrl ? html17`<img
3596
+ class=${`image ${isReversed ? "reversed" : ""}`}
3597
+ src=${card.imageUrl}
3598
+ alt=${card.name ?? "Tarot card"}
3599
+ tabindex="0"
3600
+ @click=${this.toggleFlip}
3601
+ @keydown=${(e) => {
3602
+ if (e.key === "Enter" || e.key === " ") {
3603
+ e.preventDefault();
3604
+ this.toggleFlip();
3605
+ }
3606
+ }}
3607
+ />` : html17`<div
3608
+ class=${`image ${isReversed ? "reversed" : ""}`}
3609
+ style="aspect-ratio: 0.6; display: flex; align-items: center; justify-content: center; color: var(--roxy-muted)"
3610
+ >
3611
+ ${card.name ?? "?"}
3612
+ </div>`}
3613
+ </div>
3614
+ <div>
3615
+ <div class="meta">
3616
+ ${card.arcana ? html17`${card.arcana} arcana` : nothing17}
3617
+ ${card.number !== void 0 && card.number !== null ? html17` · ${card.number}` : nothing17}
3618
+ ${isReversed ? html17` · reversed` : nothing17}
3619
+ ${card.position ? html17`<span class="position">${card.position}</span>` : nothing17}
3620
+ </div>
3621
+ <h2 class="title">${card.name ?? "Tarot card"}</h2>
3622
+ ${dailyMessage ? html17`<p class="message">${dailyMessage}</p>` : nothing17}
3623
+ ${meaning ? html17`<p>${meaning}</p>` : nothing17}
3624
+ ${card.keywords?.length ? html17`<div class="chips">
3625
+ ${card.keywords.map((k) => html17`<span>${k}</span>`)}
3626
+ </div>` : nothing17}
3627
+ <button
3628
+ class="flip"
3629
+ type="button"
3630
+ @click=${this.toggleFlip}
3631
+ aria-pressed=${this.flipped ? "true" : "false"}
3632
+ >
3633
+ Flip card
3634
+ </button>
3635
+ </div>
3636
+ </article>`;
3637
+ }
3638
+ };
3639
+ RoxyTarotCard.styles = [
3640
+ baseStyles,
3641
+ css18`
3642
+ .card {
3643
+ background: var(--roxy-bg, #fff);
3644
+ border: 1px solid var(--roxy-border, #e4e4e7);
3645
+ border-radius: var(--roxy-radius-md, 8px);
3646
+ padding: var(--roxy-space-lg, 1.5rem);
3647
+ box-shadow: var(--roxy-shadow-sm);
3648
+ display: grid;
3649
+ grid-template-columns: minmax(0, 9rem) 1fr;
3650
+ gap: var(--roxy-space-lg, 1.5rem);
3651
+ align-items: start;
3652
+ }
3653
+
3654
+ @container (max-width: 480px) {
3655
+ .card {
3656
+ grid-template-columns: 1fr;
3657
+ }
3658
+ }
3659
+
3660
+ .image-wrap {
3661
+ perspective: 800px;
3662
+ }
3663
+ .image {
3664
+ display: block;
3665
+ width: 100%;
3666
+ height: auto;
3667
+ border-radius: var(--roxy-radius-md, 8px);
3668
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent);
3669
+ transition:
3670
+ transform var(--roxy-motion-duration, 200ms)
3671
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
3672
+ cursor: pointer;
3673
+ }
3674
+ .image.reversed {
3675
+ transform: rotate(180deg);
3676
+ }
3677
+ .image:focus-visible {
3678
+ outline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));
3679
+ outline-offset: 2px;
3680
+ }
3681
+
3682
+ .title {
3683
+ margin: 0;
3684
+ font-size: var(--roxy-text-xl, 1.5rem);
3685
+ font-weight: var(--roxy-weight-bold, 600);
3686
+ letter-spacing: var(--roxy-tracking-tight);
3687
+ }
3688
+ .meta {
3689
+ color: var(--roxy-muted, #71717a);
3690
+ font-size: var(--roxy-text-sm, 0.875rem);
3691
+ text-transform: uppercase;
3692
+ letter-spacing: 0.06em;
3693
+ margin-bottom: var(--roxy-space-sm, 0.5rem);
3694
+ }
3695
+ .position {
3696
+ color: var(--roxy-info, #0284c7);
3697
+ margin-left: var(--roxy-space-xs, 0.25rem);
3698
+ text-transform: capitalize;
3699
+ }
3700
+
3701
+ .message {
3702
+ color: var(--roxy-fg, #0a0a0a);
3703
+ margin: var(--roxy-space-sm, 0.5rem) 0 var(--roxy-space-md, 1rem);
3704
+ }
3705
+
3706
+ .chips {
3707
+ display: flex;
3708
+ flex-wrap: wrap;
3709
+ gap: var(--roxy-space-xs, 0.25rem);
3710
+ margin-top: var(--roxy-space-sm, 0.5rem);
3711
+ }
3712
+ .chips span {
3713
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
3714
+ padding: 2px 8px;
3715
+ border-radius: var(--roxy-radius-full, 9999px);
3716
+ font-size: var(--roxy-text-xs, 0.75rem);
3717
+ }
3718
+
3719
+ .flip {
3720
+ margin-top: var(--roxy-space-sm, 0.5rem);
3721
+ background: transparent;
3722
+ border: 1px solid var(--roxy-border, #e4e4e7);
3723
+ border-radius: var(--roxy-radius-md, 8px);
3724
+ padding: 4px 12px;
3725
+ font-family: inherit;
3726
+ font-size: var(--roxy-text-xs, 0.75rem);
3727
+ color: var(--roxy-secondary, #475569);
3728
+ cursor: pointer;
3729
+ transition:
3730
+ transform var(--roxy-motion-duration, 200ms)
3731
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
3732
+ }
3733
+ .flip:hover {
3734
+ transform: scale(1.02);
3735
+ }
3736
+ `
3737
+ ];
3738
+ __decorateClass([
3739
+ property17({ attribute: false })
3740
+ ], RoxyTarotCard.prototype, "data", 2);
3741
+ __decorateClass([
3742
+ state3()
3743
+ ], RoxyTarotCard.prototype, "flipped", 2);
3744
+ RoxyTarotCard = __decorateClass([
3745
+ customElement17("roxy-tarot-card")
3746
+ ], RoxyTarotCard);
3747
+
3748
+ // packages/ui/src/components/tarot-spread.ts
3749
+ import { css as css19, html as html18, LitElement as LitElement18, nothing as nothing18 } from "lit";
3750
+ import { customElement as customElement18, property as property18 } from "lit/decorators.js";
3751
+ var RoxyTarotSpread = class extends LitElement18 {
3752
+ constructor() {
3753
+ super(...arguments);
3754
+ this.data = null;
3755
+ this.spread = "three-card";
3756
+ }
3757
+ render() {
3758
+ const d = this.data;
3759
+ if (!d)
3760
+ return html18`<div class="roxy-empty" role="status">No tarot spread</div>`;
3761
+ const positions = d.positions ?? d.cards ?? [];
3762
+ const isYesNo = !!d.answer;
3763
+ const answerClass = isYesNo ? (d.answer ?? "").toLowerCase().replace(/[^a-z]/g, "") : "";
3764
+ return html18`<article class="wrap" aria-label="Tarot spread">
3765
+ <header class="head">
3766
+ <h2 class="title">${d.spread ?? this.spread.replace(/-/g, " ")}</h2>
3767
+ ${d.question ? html18`<span class="question">"${d.question}"</span>` : nothing18}
3768
+ </header>
3769
+ ${isYesNo ? html18`<div>
3770
+ <span class=${`answer ${answerClass}`}>${d.answer}</span>
3771
+ ${d.strength ? html18`<small> · ${d.strength}</small>` : nothing18}
3772
+ </div>` : nothing18}
3773
+ ${positions.length > 0 ? html18`<div class="grid">
3774
+ ${positions.map(
3775
+ (p) => html18`<div class="card">
3776
+ <p class="label">${p.label ?? p.name ?? p.position ?? ""}</p>
3777
+ <div class="image">
3778
+ ${p.card?.imageUrl ? html18`<img
3779
+ src=${p.card.imageUrl}
3780
+ alt=${p.card.name ?? "tarot card"}
3781
+ class=${p.card.reversed ? "reversed" : ""}
3782
+ />` : html18`${p.card?.name ?? "?"}`}
3783
+ </div>
3784
+ <p class="name">
3785
+ ${p.card?.name ?? ""}
3786
+ ${p.card?.reversed ? html18`<small>(reversed)</small>` : nothing18}
3787
+ </p>
3788
+ ${p.interpretation ? html18`<p class="interp">${p.interpretation}</p>` : nothing18}
3789
+ </div>`
3790
+ )}
3791
+ </div>` : nothing18}
3792
+ ${d.reading ? html18`<p class="reading">${d.reading}</p>` : nothing18}
3793
+ ${d.interpretation && !d.reading ? html18`<p class="reading">${d.interpretation}</p>` : nothing18}
3794
+ </article>`;
3795
+ }
3796
+ };
3797
+ RoxyTarotSpread.styles = [
3798
+ baseStyles,
3799
+ css19`
3800
+ .wrap {
3801
+ display: grid;
3802
+ gap: var(--roxy-space-md, 1rem);
3803
+ }
3804
+
3805
+ .head {
3806
+ display: flex;
3807
+ justify-content: space-between;
3808
+ gap: var(--roxy-space-md, 1rem);
3809
+ flex-wrap: wrap;
3810
+ align-items: baseline;
3811
+ }
3812
+ .title {
3813
+ margin: 0;
3814
+ font-size: var(--roxy-text-lg, 1.125rem);
3815
+ font-weight: var(--roxy-weight-bold, 600);
3816
+ text-transform: capitalize;
3817
+ }
3818
+ .question {
3819
+ color: var(--roxy-muted, #71717a);
3820
+ font-size: var(--roxy-text-sm, 0.875rem);
3821
+ font-style: italic;
3822
+ }
3823
+
3824
+ .answer {
3825
+ display: inline-block;
3826
+ padding: 4px 14px;
3827
+ border-radius: var(--roxy-radius-full, 9999px);
3828
+ font-weight: var(--roxy-weight-bold, 600);
3829
+ font-size: var(--roxy-text-base, 1rem);
3830
+ text-transform: uppercase;
3831
+ letter-spacing: 0.06em;
3832
+ }
3833
+ .answer.yes {
3834
+ background: color-mix(in srgb, var(--roxy-success, #16a34a) 16%, transparent);
3835
+ color: var(--roxy-success, #16a34a);
3836
+ }
3837
+ .answer.no {
3838
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
3839
+ color: var(--roxy-danger, #dc2626);
3840
+ }
3841
+ .answer.maybe {
3842
+ background: color-mix(in srgb, var(--roxy-warning, #ea580c) 16%, transparent);
3843
+ color: var(--roxy-warning, #ea580c);
3844
+ }
3845
+
3846
+ .grid {
3847
+ display: grid;
3848
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
3849
+ gap: var(--roxy-space-md, 1rem);
3850
+ }
3851
+
3852
+ .card {
3853
+ border: 1px solid var(--roxy-border, #e4e4e7);
3854
+ border-radius: var(--roxy-radius-md, 8px);
3855
+ padding: var(--roxy-space-sm, 0.5rem);
3856
+ background: var(--roxy-bg, #fff);
3857
+ display: grid;
3858
+ gap: var(--roxy-space-xs, 0.25rem);
3859
+ }
3860
+ .label {
3861
+ font-size: var(--roxy-text-xs, 0.75rem);
3862
+ color: var(--roxy-muted, #71717a);
3863
+ text-transform: uppercase;
3864
+ letter-spacing: 0.06em;
3865
+ margin: 0;
3866
+ }
3867
+ .image {
3868
+ width: 100%;
3869
+ aspect-ratio: 0.6;
3870
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent);
3871
+ border-radius: var(--roxy-radius-sm, 4px);
3872
+ display: flex;
3873
+ align-items: center;
3874
+ justify-content: center;
3875
+ font-size: var(--roxy-text-xs, 0.75rem);
3876
+ color: var(--roxy-muted, #71717a);
3877
+ overflow: hidden;
3878
+ }
3879
+ .image img {
3880
+ width: 100%;
3881
+ height: 100%;
3882
+ object-fit: cover;
3883
+ transition:
3884
+ transform var(--roxy-motion-duration, 200ms)
3885
+ var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));
3886
+ }
3887
+ .image img.reversed {
3888
+ transform: rotate(180deg);
3889
+ }
3890
+ .name {
3891
+ margin: 0;
3892
+ font-size: var(--roxy-text-sm, 0.875rem);
3893
+ font-weight: var(--roxy-weight-bold, 600);
3894
+ }
3895
+ .interp {
3896
+ margin: 0;
3897
+ font-size: var(--roxy-text-xs, 0.75rem);
3898
+ color: var(--roxy-secondary, #475569);
3899
+ }
3900
+
3901
+ .reading {
3902
+ margin: 0;
3903
+ color: var(--roxy-fg, #0a0a0a);
3904
+ }
3905
+ `
3906
+ ];
3907
+ __decorateClass([
3908
+ property18({ attribute: false })
3909
+ ], RoxyTarotSpread.prototype, "data", 2);
3910
+ __decorateClass([
3911
+ property18({ type: String, reflect: true })
3912
+ ], RoxyTarotSpread.prototype, "spread", 2);
3913
+ RoxyTarotSpread = __decorateClass([
3914
+ customElement18("roxy-tarot-spread")
3915
+ ], RoxyTarotSpread);
3916
+
3917
+ // packages/ui/src/components/vedic-kundli.ts
3918
+ import { css as css20, html as html19, LitElement as LitElement19, nothing as nothing19, svg as svg5 } from "lit";
3919
+ import { customElement as customElement19, property as property19 } from "lit/decorators.js";
3920
+ var SOUTH_HOUSE_CENTERS = {
3921
+ 1: { x: 150, y: 58 },
3922
+ 2: { x: 205, y: 52 },
3923
+ 3: { x: 253, y: 112 },
3924
+ 4: { x: 243, y: 150 },
3925
+ 5: { x: 253, y: 188 },
3926
+ 6: { x: 205, y: 248 },
3927
+ 7: { x: 150, y: 242 },
3928
+ 8: { x: 95, y: 248 },
3929
+ 9: { x: 47, y: 188 },
3930
+ 10: { x: 57, y: 150 },
3931
+ 11: { x: 47, y: 112 },
3932
+ 12: { x: 95, y: 52 }
3933
+ };
3934
+ var SOUTH_SIGN_POSITIONS = {
3935
+ 1: { x: 150, y: 35 },
3936
+ 2: { x: 222, y: 40 },
3937
+ 3: { x: 265, y: 100 },
3938
+ 4: { x: 265, y: 150 },
3939
+ 5: { x: 265, y: 200 },
3940
+ 6: { x: 222, y: 260 },
3941
+ 7: { x: 150, y: 265 },
3942
+ 8: { x: 78, y: 260 },
3943
+ 9: { x: 35, y: 200 },
3944
+ 10: { x: 35, y: 150 },
3945
+ 11: { x: 35, y: 100 },
3946
+ 12: { x: 78, y: 40 }
3947
+ };
3948
+ var RASHI_KEYS = [
3949
+ "aries",
3950
+ "taurus",
3951
+ "gemini",
3952
+ "cancer",
3953
+ "leo",
3954
+ "virgo",
3955
+ "libra",
3956
+ "scorpio",
3957
+ "sagittarius",
3958
+ "capricorn",
3959
+ "aquarius",
3960
+ "pisces"
3961
+ ];
3962
+ var RASHI_TO_SIGN = {
3963
+ aries: "Aries",
3964
+ taurus: "Taurus",
3965
+ gemini: "Gemini",
3966
+ cancer: "Cancer",
3967
+ leo: "Leo",
3968
+ virgo: "Virgo",
3969
+ libra: "Libra",
3970
+ scorpio: "Scorpio",
3971
+ sagittarius: "Sagittarius",
3972
+ capricorn: "Capricorn",
3973
+ aquarius: "Aquarius",
3974
+ pisces: "Pisces"
3975
+ };
3976
+ var RoxyVedicKundli = class extends LitElement19 {
3977
+ constructor() {
3978
+ super(...arguments);
3979
+ this.data = null;
3980
+ this.chartStyle = "south";
3981
+ }
3982
+ buildHouses() {
3983
+ if (!this.data) return [];
3984
+ const houses = [];
3985
+ if (Array.isArray(this.data.houses)) {
3986
+ for (const h of this.data.houses) {
3987
+ houses.push({
3988
+ house: h.house ?? h.number ?? houses.length + 1,
3989
+ sign: h.sign ?? "",
3990
+ planets: h.planets ?? []
3991
+ });
3992
+ }
3993
+ if (houses.length > 0) return houses;
3994
+ }
3995
+ for (let i = 0; i < 12; i++) {
3996
+ const key = RASHI_KEYS[i];
3997
+ const bucket = this.data[key];
3998
+ const planets = (bucket?.signs ?? []).map((p) => p.planet ?? "").filter(Boolean);
3999
+ houses.push({
4000
+ house: i + 1,
4001
+ sign: RASHI_TO_SIGN[key] ?? "",
4002
+ planets
4003
+ });
4004
+ }
4005
+ return houses;
4006
+ }
4007
+ render() {
4008
+ if (!this.data)
4009
+ return html19`<div class="roxy-empty" role="status">No kundli data</div>`;
4010
+ const houses = this.buildHouses();
4011
+ return html19`<div class="wrap">
4012
+ <h2 class="title">Vedic kundli</h2>
4013
+ <svg
4014
+ viewBox="0 0 300 300"
4015
+ role="img"
4016
+ aria-label="Vedic birth chart with twelve sign houses"
4017
+ >
4018
+ <title>Vedic kundli</title>
4019
+ <polygon class="line" points="150,10 290,150 150,290 10,150" stroke-width="1.5" />
4020
+ <polygon
4021
+ class="line"
4022
+ points="220,80 220,220 80,220 80,80"
4023
+ stroke-width="1"
4024
+ fill="none"
4025
+ />
4026
+ <line class="line" x1="150" y1="10" x2="80" y2="80" stroke-width="1" />
4027
+ <line class="line" x1="150" y1="10" x2="220" y2="80" stroke-width="1" />
4028
+ <line class="line" x1="290" y1="150" x2="220" y2="80" stroke-width="1" />
4029
+ <line class="line" x1="290" y1="150" x2="220" y2="220" stroke-width="1" />
4030
+ <line class="line" x1="150" y1="290" x2="220" y2="220" stroke-width="1" />
4031
+ <line class="line" x1="150" y1="290" x2="80" y2="220" stroke-width="1" />
4032
+ <line class="line" x1="10" y1="150" x2="80" y2="220" stroke-width="1" />
4033
+ <line class="line" x1="10" y1="150" x2="80" y2="80" stroke-width="1" />
4034
+ ${houses.map((h) => this.renderHouseGroup(h))}
4035
+ </svg>
4036
+ </div>`;
4037
+ }
4038
+ renderHouseGroup(h) {
4039
+ const center = SOUTH_HOUSE_CENTERS[h.house];
4040
+ const signPos = SOUTH_SIGN_POSITIONS[h.house];
4041
+ if (!center || !signPos) return nothing19;
4042
+ const signAbbr = SIGN_ABBR[h.sign] ?? "";
4043
+ const planets = h.planets ?? [];
4044
+ return svg5`
4045
+ <g>
4046
+ ${signAbbr ? svg5`<text class="sign-text" x=${signPos.x} y=${signPos.y} text-anchor="middle" dominant-baseline="central">${signAbbr}</text>` : nothing19}
4047
+ ${planets.map((planet, j) => {
4048
+ const abbr = PLANET_ABBR[capitalize4(planet)] ?? planet.slice(0, 2);
4049
+ const lineHeight = 13;
4050
+ const startY = center.y - (planets.length - 1) * lineHeight / 2;
4051
+ const yPos = startY + j * lineHeight;
4052
+ return svg5`<text class="planet-text" x=${center.x} y=${yPos} text-anchor="middle" dominant-baseline="central">${abbr}</text>`;
4053
+ })}
4054
+ </g>
4055
+ `;
4056
+ }
4057
+ };
4058
+ RoxyVedicKundli.styles = [
4059
+ baseStyles,
4060
+ css20`
4061
+ .wrap {
4062
+ display: grid;
4063
+ gap: var(--roxy-space-md, 1rem);
4064
+ }
4065
+ .title {
4066
+ font-size: var(--roxy-text-lg, 1.125rem);
4067
+ font-weight: var(--roxy-weight-bold, 600);
4068
+ margin: 0;
4069
+ }
4070
+ svg {
4071
+ display: block;
4072
+ width: 100%;
4073
+ max-width: 360px;
4074
+ margin: 0 auto;
4075
+ }
4076
+ .line {
4077
+ fill: transparent;
4078
+ stroke: var(--roxy-border, #e4e4e7);
4079
+ }
4080
+ .sign-text {
4081
+ fill: var(--roxy-muted, #71717a);
4082
+ font-size: 9px;
4083
+ font-weight: 500;
4084
+ font-family: var(--roxy-font-sans);
4085
+ }
4086
+ .planet-text {
4087
+ fill: var(--roxy-fg, #0a0a0a);
4088
+ font-size: 11px;
4089
+ font-weight: 600;
4090
+ font-family: var(--roxy-font-sans);
4091
+ }
4092
+ `
4093
+ ];
4094
+ __decorateClass([
4095
+ property19({ attribute: false })
4096
+ ], RoxyVedicKundli.prototype, "data", 2);
4097
+ __decorateClass([
4098
+ property19({ type: String, reflect: true, attribute: "chart-style" })
4099
+ ], RoxyVedicKundli.prototype, "chartStyle", 2);
4100
+ RoxyVedicKundli = __decorateClass([
4101
+ customElement19("roxy-vedic-kundli")
4102
+ ], RoxyVedicKundli);
4103
+ function capitalize4(s) {
4104
+ if (!s) return "";
4105
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
4106
+ }
4107
+
4108
+ // packages/ui/src/index.ts
4109
+ var ROXY_UI_VERSION = "0.1.0";
4110
+ var ROXY_UI_COMPONENTS = [
4111
+ "natal-chart",
4112
+ "horoscope-card",
4113
+ "synastry-chart",
4114
+ "compatibility-card",
4115
+ "moon-phase",
4116
+ "vedic-kundli",
4117
+ "panchang-table",
4118
+ "dasha-timeline",
4119
+ "dosha-card",
4120
+ "guna-milan",
4121
+ "kp-planets-table",
4122
+ "numerology-card",
4123
+ "tarot-card",
4124
+ "tarot-spread",
4125
+ "biorhythm-chart",
4126
+ "hexagram",
4127
+ "endpoint-form",
4128
+ "location-search",
4129
+ "data"
4130
+ ];
4131
+ export {
4132
+ ROXY_UI_COMPONENTS,
4133
+ ROXY_UI_VERSION,
4134
+ RoxyBiorhythmChart,
4135
+ RoxyCompatibilityCard,
4136
+ RoxyDashaTimeline,
4137
+ RoxyData,
4138
+ RoxyDoshaCard,
4139
+ RoxyEndpointForm,
4140
+ RoxyGunaMilan,
4141
+ RoxyHexagram,
4142
+ RoxyHoroscopeCard,
4143
+ RoxyKpPlanetsTable,
4144
+ RoxyLocationSearch,
4145
+ RoxyMoonPhase,
4146
+ RoxyNatalChart,
4147
+ RoxyNumerologyCard,
4148
+ RoxyPanchangTable,
4149
+ RoxySynastryChart,
4150
+ RoxyTarotCard,
4151
+ RoxyTarotSpread,
4152
+ RoxyVedicKundli
4153
+ };
4154
+ //# sourceMappingURL=index.js.map