@roxyapi/ui 0.0.1 → 0.1.1

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