@roxyapi/ui 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +327 -14
  3. package/THEMING.md +24 -7
  4. package/dist/cdn/components/ashtakavarga-grid.js +349 -0
  5. package/dist/cdn/components/ashtakavarga-grid.js.map +7 -0
  6. package/dist/cdn/components/biorhythm-chart.js +15 -22
  7. package/dist/cdn/components/biorhythm-chart.js.map +3 -3
  8. package/dist/cdn/components/choghadiya-grid.js +239 -0
  9. package/dist/cdn/components/choghadiya-grid.js.map +7 -0
  10. package/dist/cdn/components/compatibility-card.js +36 -34
  11. package/dist/cdn/components/compatibility-card.js.map +4 -4
  12. package/dist/cdn/components/dasha-timeline.js +35 -39
  13. package/dist/cdn/components/dasha-timeline.js.map +4 -4
  14. package/dist/cdn/components/data.js +9 -9
  15. package/dist/cdn/components/data.js.map +4 -4
  16. package/dist/cdn/components/divisional-chart.js +279 -0
  17. package/dist/cdn/components/divisional-chart.js.map +7 -0
  18. package/dist/cdn/components/dosha-card.js +49 -49
  19. package/dist/cdn/components/dosha-card.js.map +3 -3
  20. package/dist/cdn/components/endpoint-form.js +47 -28
  21. package/dist/cdn/components/endpoint-form.js.map +4 -4
  22. package/dist/cdn/components/guna-milan.js +66 -24
  23. package/dist/cdn/components/guna-milan.js.map +4 -4
  24. package/dist/cdn/components/hexagram.js +26 -26
  25. package/dist/cdn/components/hexagram.js.map +3 -3
  26. package/dist/cdn/components/horoscope-card.js +47 -40
  27. package/dist/cdn/components/horoscope-card.js.map +4 -4
  28. package/dist/cdn/components/kp-planets-table.js +10 -10
  29. package/dist/cdn/components/kp-planets-table.js.map +4 -4
  30. package/dist/cdn/components/location-search.js +6 -6
  31. package/dist/cdn/components/location-search.js.map +3 -3
  32. package/dist/cdn/components/moon-phase.js +18 -18
  33. package/dist/cdn/components/moon-phase.js.map +4 -4
  34. package/dist/cdn/components/natal-chart.js +240 -24
  35. package/dist/cdn/components/natal-chart.js.map +4 -4
  36. package/dist/cdn/components/numerology-card.js +40 -31
  37. package/dist/cdn/components/numerology-card.js.map +4 -4
  38. package/dist/cdn/components/panchang-table.js +30 -30
  39. package/dist/cdn/components/panchang-table.js.map +4 -4
  40. package/dist/cdn/components/shadbala-table.js +312 -0
  41. package/dist/cdn/components/shadbala-table.js.map +7 -0
  42. package/dist/cdn/components/synastry-chart.js +129 -39
  43. package/dist/cdn/components/synastry-chart.js.map +4 -4
  44. package/dist/cdn/components/tarot-card.js +49 -20
  45. package/dist/cdn/components/tarot-card.js.map +3 -3
  46. package/dist/cdn/components/tarot-spread.js +43 -27
  47. package/dist/cdn/components/tarot-spread.js.map +3 -3
  48. package/dist/cdn/components/transits-table.js +391 -0
  49. package/dist/cdn/components/transits-table.js.map +7 -0
  50. package/dist/cdn/components/vedic-kundli.js +63 -27
  51. package/dist/cdn/components/vedic-kundli.js.map +4 -4
  52. package/dist/cdn/components/yoga-list.js +334 -0
  53. package/dist/cdn/components/yoga-list.js.map +7 -0
  54. package/dist/cdn/roxy-ui.js +2104 -544
  55. package/dist/cdn/roxy-ui.js.map +4 -4
  56. package/dist/components/ashtakavarga-grid.d.ts +26 -0
  57. package/dist/components/ashtakavarga-grid.d.ts.map +1 -0
  58. package/dist/components/ashtakavarga-grid.js +457 -0
  59. package/dist/components/ashtakavarga-grid.js.map +7 -0
  60. package/dist/components/biorhythm-chart.d.ts +2 -46
  61. package/dist/components/biorhythm-chart.d.ts.map +1 -1
  62. package/dist/components/biorhythm-chart.js +24 -23
  63. package/dist/components/biorhythm-chart.js.map +2 -2
  64. package/dist/components/choghadiya-grid.d.ts +19 -0
  65. package/dist/components/choghadiya-grid.d.ts.map +1 -0
  66. package/dist/components/choghadiya-grid.js +304 -0
  67. package/dist/components/choghadiya-grid.js.map +7 -0
  68. package/dist/components/compatibility-card.d.ts +2 -27
  69. package/dist/components/compatibility-card.d.ts.map +1 -1
  70. package/dist/components/compatibility-card.js +50 -29
  71. package/dist/components/compatibility-card.js.map +3 -3
  72. package/dist/components/dasha-timeline.d.ts +2 -31
  73. package/dist/components/dasha-timeline.d.ts.map +1 -1
  74. package/dist/components/dasha-timeline.js +32 -30
  75. package/dist/components/dasha-timeline.js.map +3 -3
  76. package/dist/components/data.d.ts +11 -7
  77. package/dist/components/data.d.ts.map +1 -1
  78. package/dist/components/data.js +16 -6
  79. package/dist/components/data.js.map +3 -3
  80. package/dist/components/divisional-chart.d.ts +20 -0
  81. package/dist/components/divisional-chart.d.ts.map +1 -0
  82. package/dist/components/divisional-chart.js +471 -0
  83. package/dist/components/divisional-chart.js.map +7 -0
  84. package/dist/components/dosha-card.d.ts +2 -16
  85. package/dist/components/dosha-card.d.ts.map +1 -1
  86. package/dist/components/dosha-card.js +45 -43
  87. package/dist/components/dosha-card.js.map +2 -2
  88. package/dist/components/endpoint-form.d.ts +2 -0
  89. package/dist/components/endpoint-form.d.ts.map +1 -1
  90. package/dist/components/endpoint-form.js +71 -11
  91. package/dist/components/endpoint-form.js.map +3 -3
  92. package/dist/components/guna-milan.d.ts +2 -20
  93. package/dist/components/guna-milan.d.ts.map +1 -1
  94. package/dist/components/guna-milan.js +79 -20
  95. package/dist/components/guna-milan.js.map +4 -4
  96. package/dist/components/hexagram.d.ts +3 -27
  97. package/dist/components/hexagram.d.ts.map +1 -1
  98. package/dist/components/hexagram.js +48 -15
  99. package/dist/components/hexagram.js.map +2 -2
  100. package/dist/components/horoscope-card.d.ts +2 -20
  101. package/dist/components/horoscope-card.d.ts.map +1 -1
  102. package/dist/components/horoscope-card.js +54 -18
  103. package/dist/components/horoscope-card.js.map +3 -3
  104. package/dist/components/kp-planets-table.d.ts +2 -21
  105. package/dist/components/kp-planets-table.d.ts.map +1 -1
  106. package/dist/components/kp-planets-table.js +10 -4
  107. package/dist/components/kp-planets-table.js.map +3 -3
  108. package/dist/components/location-search.d.ts +5 -14
  109. package/dist/components/location-search.d.ts.map +1 -1
  110. package/dist/components/location-search.js +45 -5
  111. package/dist/components/location-search.js.map +2 -2
  112. package/dist/components/moon-phase.d.ts +4 -21
  113. package/dist/components/moon-phase.d.ts.map +1 -1
  114. package/dist/components/moon-phase.js +34 -4
  115. package/dist/components/moon-phase.js.map +3 -3
  116. package/dist/components/natal-chart.d.ts +9 -43
  117. package/dist/components/natal-chart.d.ts.map +1 -1
  118. package/dist/components/natal-chart.js +346 -79
  119. package/dist/components/natal-chart.js.map +3 -3
  120. package/dist/components/numerology-card.d.ts +5 -37
  121. package/dist/components/numerology-card.d.ts.map +1 -1
  122. package/dist/components/numerology-card.js +58 -30
  123. package/dist/components/numerology-card.js.map +3 -3
  124. package/dist/components/panchang-table.d.ts +3 -62
  125. package/dist/components/panchang-table.d.ts.map +1 -1
  126. package/dist/components/panchang-table.js +62 -32
  127. package/dist/components/panchang-table.js.map +3 -3
  128. package/dist/components/shadbala-table.d.ts +18 -0
  129. package/dist/components/shadbala-table.d.ts.map +1 -0
  130. package/dist/components/shadbala-table.js +400 -0
  131. package/dist/components/shadbala-table.js.map +7 -0
  132. package/dist/components/synastry-chart.d.ts +9 -28
  133. package/dist/components/synastry-chart.d.ts.map +1 -1
  134. package/dist/components/synastry-chart.js +201 -56
  135. package/dist/components/synastry-chart.js.map +3 -3
  136. package/dist/components/tarot-card.d.ts +5 -29
  137. package/dist/components/tarot-card.d.ts.map +1 -1
  138. package/dist/components/tarot-card.js +59 -20
  139. package/dist/components/tarot-card.js.map +2 -2
  140. package/dist/components/tarot-spread.d.ts +2 -24
  141. package/dist/components/tarot-spread.d.ts.map +1 -1
  142. package/dist/components/tarot-spread.js +39 -13
  143. package/dist/components/tarot-spread.js.map +2 -2
  144. package/dist/components/transits-table.d.ts +21 -0
  145. package/dist/components/transits-table.d.ts.map +1 -0
  146. package/dist/components/transits-table.js +515 -0
  147. package/dist/components/transits-table.js.map +7 -0
  148. package/dist/components/vedic-kundli.d.ts +5 -28
  149. package/dist/components/vedic-kundli.d.ts.map +1 -1
  150. package/dist/components/vedic-kundli.js +147 -83
  151. package/dist/components/vedic-kundli.js.map +3 -3
  152. package/dist/components/yoga-list.d.ts +29 -0
  153. package/dist/components/yoga-list.d.ts.map +1 -0
  154. package/dist/components/yoga-list.js +389 -0
  155. package/dist/components/yoga-list.js.map +7 -0
  156. package/dist/index.cjs +3693 -1180
  157. package/dist/index.cjs.map +4 -4
  158. package/dist/index.d.ts +11 -4
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +3709 -1196
  161. package/dist/index.js.map +4 -4
  162. package/dist/manifest.d.ts +43 -0
  163. package/dist/manifest.d.ts.map +1 -0
  164. package/dist/manifest.json +7 -2
  165. package/dist/styles/tokens.css +73 -1
  166. package/dist/tokens/index.d.ts +6 -0
  167. package/dist/tokens/index.d.ts.map +1 -1
  168. package/dist/types/index.d.ts +2 -0
  169. package/dist/types/index.d.ts.map +1 -0
  170. package/dist/types/types.gen.d.ts +27811 -0
  171. package/dist/types/types.gen.d.ts.map +1 -0
  172. package/dist/utils/debounce.d.ts +9 -1
  173. package/dist/utils/debounce.d.ts.map +1 -1
  174. package/dist/utils/format.d.ts +29 -0
  175. package/dist/utils/format.d.ts.map +1 -0
  176. package/dist/utils/kundli-render.d.ts +63 -0
  177. package/dist/utils/kundli-render.d.ts.map +1 -0
  178. package/dist/utils/string.d.ts +14 -0
  179. package/dist/utils/string.d.ts.map +1 -0
  180. package/dist/version.d.ts +2 -0
  181. package/dist/version.d.ts.map +1 -0
  182. package/package.json +7 -1
  183. package/src/components/ashtakavarga-grid.ts +354 -0
  184. package/src/components/biorhythm-chart.ts +39 -84
  185. package/src/components/choghadiya-grid.ts +185 -0
  186. package/src/components/compatibility-card.ts +85 -52
  187. package/src/components/dasha-timeline.ts +55 -73
  188. package/src/components/data.ts +28 -16
  189. package/src/components/divisional-chart.ts +214 -0
  190. package/src/components/dosha-card.ts +72 -68
  191. package/src/components/endpoint-form.ts +80 -18
  192. package/src/components/guna-milan.ts +87 -47
  193. package/src/components/hexagram.ts +53 -43
  194. package/src/components/horoscope-card.ts +59 -43
  195. package/src/components/kp-planets-table.ts +8 -27
  196. package/src/components/location-search.ts +47 -23
  197. package/src/components/moon-phase.ts +28 -25
  198. package/src/components/natal-chart.ts +364 -110
  199. package/src/components/numerology-card.ts +86 -84
  200. package/src/components/panchang-table.ts +40 -78
  201. package/src/components/shadbala-table.ts +286 -0
  202. package/src/components/synastry-chart.ts +213 -97
  203. package/src/components/tarot-card.ts +76 -62
  204. package/src/components/tarot-spread.ts +72 -45
  205. package/src/components/transits-table.ts +350 -0
  206. package/src/components/vedic-kundli.ts +59 -173
  207. package/src/components/yoga-list.ts +328 -0
  208. package/src/index.ts +18 -26
  209. package/src/manifest.ts +340 -0
  210. package/src/styles/tokens.css +73 -1
  211. package/src/tokens/index.ts +14 -0
  212. package/src/types/types.gen.ts +3 -3
  213. package/src/utils/debounce.ts +23 -4
  214. package/src/utils/format.ts +75 -0
  215. package/src/utils/kundli-render.ts +197 -0
  216. package/src/utils/string.ts +23 -0
  217. package/src/version.ts +2 -0
  218. package/dist/utils/motion.d.ts +0 -13
  219. package/dist/utils/motion.d.ts.map +0 -1
  220. package/src/utils/motion.ts +0 -18
@@ -99,6 +99,12 @@ var baseStyles = css`
99
99
  }
100
100
  `;
101
101
 
102
+ // packages/ui/src/utils/format.ts
103
+ function formatNumber(value, dp = 1) {
104
+ if (typeof value !== "number" || !Number.isFinite(value)) return "";
105
+ return value.toFixed(dp).replace(/\.?0+$/, "");
106
+ }
107
+
102
108
  // packages/ui/src/components/compatibility-card.ts
103
109
  var RoxyCompatibilityCard = class extends LitElement {
104
110
  constructor() {
@@ -109,22 +115,29 @@ var RoxyCompatibilityCard = class extends LitElement {
109
115
  getBreakdown() {
110
116
  const d = this.data;
111
117
  if (!d) return {};
112
- if (d.categoryScores) return d.categoryScores;
113
- if (d.categoryBreakdown) return d.categoryBreakdown;
114
- const inferred = {};
115
- if (typeof d.emotional === "number") inferred.emotional = d.emotional;
116
- if (typeof d.communication === "number")
117
- inferred.communication = d.communication;
118
- if (typeof d.romance === "number") inferred.romance = d.romance;
119
- if (d.elementBalance) Object.assign(inferred, d.elementBalance);
120
- return inferred;
118
+ if ("categories" in d && d.categories) {
119
+ const out = {};
120
+ for (const [k, v] of Object.entries(d.categories)) {
121
+ if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
122
+ }
123
+ return out;
124
+ }
125
+ return {};
121
126
  }
122
127
  render() {
123
128
  const d = this.data;
124
129
  if (!d)
125
130
  return html`<div class="roxy-empty" role="status">No compatibility data</div>`;
126
- const score = d.overallScore ?? d.score;
131
+ const score = d.overallScore;
127
132
  const breakdown = this.getBreakdown();
133
+ const rating = "rating" in d ? d.rating : void 0;
134
+ const archetype = "archetype" in d ? d.archetype : void 0;
135
+ const advice = "advice" in d ? d.advice : void 0;
136
+ const summary = "summary" in d ? d.summary : void 0;
137
+ const interpretation = "interpretation" in d ? d.interpretation : void 0;
138
+ const strengths = "strengths" in d ? d.strengths : void 0;
139
+ const challenges = "challenges" in d ? d.challenges : void 0;
140
+ const keyAspects = "keyAspects" in d ? d.keyAspects : void 0;
128
141
  return html`<article
129
142
  class="card"
130
143
  aria-label=${`Compatibility (${this.mode})`}
@@ -132,8 +145,8 @@ var RoxyCompatibilityCard = class extends LitElement {
132
145
  <div class="head">
133
146
  <h2>${this.mode} compatibility</h2>
134
147
  <div>
135
- ${typeof score === "number" ? html`<div class="score">${score}</div>` : nothing}
136
- ${d.rating ? html`<div class="rating">${d.rating}</div>` : nothing}
148
+ ${typeof score === "number" ? html`<div class="score">${formatNumber(score, 0)}</div>` : nothing}
149
+ ${rating ? html`<div class="rating">${rating}</div>` : nothing}
137
150
  </div>
138
151
  </div>
139
152
 
@@ -144,35 +157,37 @@ var RoxyCompatibilityCard = class extends LitElement {
144
157
  <span class="bar"
145
158
  ><span style="width: ${Math.max(0, Math.min(100, v))}%"></span
146
159
  ></span>
147
- <span>${v}</span>
160
+ <span>${formatNumber(v, 0)}</span>
148
161
  </div>`
149
162
  )}
150
163
  </div>` : nothing}
151
- ${d.relationshipArchetype ? html`<p>
152
- <span class="archetype">${d.relationshipArchetype}</span>
164
+ ${archetype ? html`<p>
165
+ <span class="archetype">${archetype.label}</span>
166
+ ${archetype.description ? html` · ${archetype.description}` : nothing}
153
167
  </p>` : nothing}
154
- ${d.summary ? html`<p>${d.summary}</p>` : nothing}
155
- ${d.advice ? html`<p>${d.advice}</p>` : nothing}
156
- ${(d.strengths?.length ?? 0) > 0 || (d.challenges?.length ?? 0) > 0 ? html`<div class="lists">
157
- ${d.strengths?.length ? html`<div>
168
+ ${summary ? html`<p>${summary}</p>` : nothing}
169
+ ${interpretation && !summary ? html`<p>${interpretation}</p>` : nothing}
170
+ ${advice ? html`<p>${advice}</p>` : nothing}
171
+ ${(strengths?.length ?? 0) > 0 || (challenges?.length ?? 0) > 0 ? html`<div class="lists">
172
+ ${strengths?.length ? html`<div>
158
173
  <h3>Strengths</h3>
159
174
  <ul>
160
- ${d.strengths.map((s) => html`<li>${s}</li>`)}
175
+ ${strengths.map((s) => html`<li>${s}</li>`)}
161
176
  </ul>
162
177
  </div>` : nothing}
163
- ${d.challenges?.length ? html`<div>
178
+ ${challenges?.length ? html`<div>
164
179
  <h3>Challenges</h3>
165
180
  <ul>
166
- ${d.challenges.map((s) => html`<li>${s}</li>`)}
167
- </ul>
168
- </div>` : nothing}
169
- ${d.keyAspects?.length ? html`<div>
170
- <h3>Key aspects</h3>
171
- <ul>
172
- ${d.keyAspects.map((s) => html`<li>${s}</li>`)}
181
+ ${challenges.map((s) => html`<li>${s}</li>`)}
173
182
  </ul>
174
183
  </div>` : nothing}
175
184
  </div>` : nothing}
185
+ ${keyAspects?.length ? html`<div>
186
+ <h3 style="margin: 0 0 0.25rem; font-size: var(--roxy-text-xs); color: var(--roxy-muted); text-transform: uppercase; letter-spacing: 0.06em;">Key aspects</h3>
187
+ <ul style="margin: 0; padding-left: 1rem; font-size: var(--roxy-text-sm);">
188
+ ${keyAspects.slice(0, 6).map((a) => html`<li>${formatAspect(a)}</li>`)}
189
+ </ul>
190
+ </div>` : nothing}
176
191
  </article>`;
177
192
  }
178
193
  };
@@ -242,7 +257,7 @@ RoxyCompatibilityCard.styles = [
242
257
  }
243
258
 
244
259
  .archetype {
245
- color: var(--roxy-info, #0284c7);
260
+ color: var(--roxy-accent-fg, #b45309);
246
261
  font-weight: var(--roxy-weight-bold, 600);
247
262
  }
248
263
 
@@ -273,6 +288,12 @@ __decorateClass([
273
288
  RoxyCompatibilityCard = __decorateClass([
274
289
  customElement("roxy-compatibility-card")
275
290
  ], RoxyCompatibilityCard);
291
+ function formatAspect(a) {
292
+ const aspect = a.type.toLowerCase().replace(/_/g, "-");
293
+ const orb = typeof a.orb === "number" ? ` (orb ${formatNumber(a.orb, 1)}\xB0)` : "";
294
+ const head = [a.planet1, aspect, a.planet2].filter(Boolean).join(" ");
295
+ return a.description ? `${head}${orb} \xB7 ${a.description}` : `${head}${orb}`;
296
+ }
276
297
  export {
277
298
  RoxyCompatibilityCard
278
299
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/components/compatibility-card.ts", "../../src/utils/base-styles.ts"],
4
- "sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { baseStyles } from '../utils/base-styles.js';\n\ninterface CompatibilityData {\n\toverallScore?: number;\n\tscore?: number;\n\trating?: string;\n\trelationshipArchetype?: string;\n\tadvice?: string;\n\tsummary?: string;\n\tcategoryScores?: Record<string, number>;\n\tcategoryBreakdown?: Record<string, number>;\n\temotional?: number;\n\tcommunication?: number;\n\tromance?: number;\n\tstrengths?: string[];\n\tchallenges?: string[];\n\tkeyAspects?: string[];\n\telementBalance?: Record<string, number>;\n\tperson1?: { name?: string; sign?: string; lifePath?: number };\n\tperson2?: { name?: string; sign?: string; lifePath?: number };\n}\n\n/**\n * Cross-domain compatibility card. Renders /astrology/compatibility-score,\n * /numerology/compatibility, or /biorhythm/compatibility responses.\n */\n@customElement('roxy-compatibility-card')\nexport class RoxyCompatibilityCard extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.card {\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\t\t\tbox-shadow: var(--roxy-shadow-sm);\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\n\t\t\t.head {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 1fr auto;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.head h2 {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\ttext-transform: capitalize;\n\t\t\t}\n\n\t\t\t.score {\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t\tfont-size: 2rem;\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\tcolor: var(--roxy-accent-fg, #b45309);\n\t\t\t\tline-height: 1;\n\t\t\t}\n\t\t\t.rating {\n\t\t\t\tcolor: var(--roxy-secondary, #475569);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\n\t\t\t.bar-row {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 8rem 1fr 3.5rem;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\talign-items: center;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\t.bar {\n\t\t\t\theight: 8px;\n\t\t\t\tbackground: var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-full, 9999px);\n\t\t\t\toverflow: hidden;\n\t\t\t}\n\t\t\t.bar > span {\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tbackground: var(--roxy-accent, #f59e0b);\n\t\t\t\ttransition:\n\t\t\t\t\twidth var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t}\n\t\t\t.bar-row > span:last-child {\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\ttext-align: right;\n\t\t\t}\n\n\t\t\t.archetype {\n\t\t\t\tcolor: var(--roxy-info, #0284c7);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t}\n\n\t\t\t.lists {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.lists h3 {\n\t\t\t\tmargin: 0 0 var(--roxy-space-xs, 0.25rem);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tletter-spacing: 0.06em;\n\t\t\t}\n\t\t\t.lists ul {\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding-left: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: CompatibilityData | null = null;\n\n\t@property({ type: String, reflect: true })\n\tmode: 'astrology' | 'numerology' | 'biorhythm' = 'astrology';\n\n\tprivate getBreakdown(): Record<string, number> {\n\t\tconst d = this.data;\n\t\tif (!d) return {};\n\t\tif (d.categoryScores) return d.categoryScores;\n\t\tif (d.categoryBreakdown) return d.categoryBreakdown;\n\t\tconst inferred: Record<string, number> = {};\n\t\tif (typeof d.emotional === 'number') inferred.emotional = d.emotional;\n\t\tif (typeof d.communication === 'number')\n\t\t\tinferred.communication = d.communication;\n\t\tif (typeof d.romance === 'number') inferred.romance = d.romance;\n\t\tif (d.elementBalance) Object.assign(inferred, d.elementBalance);\n\t\treturn inferred;\n\t}\n\n\trender() {\n\t\tconst d = this.data;\n\t\tif (!d)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No compatibility data</div>`;\n\t\tconst score = d.overallScore ?? d.score;\n\t\tconst breakdown = this.getBreakdown();\n\n\t\treturn html`<article\n\t\t\tclass=\"card\"\n\t\t\taria-label=${`Compatibility (${this.mode})`}\n\t\t>\n\t\t\t<div class=\"head\">\n\t\t\t\t<h2>${this.mode} compatibility</h2>\n\t\t\t\t<div>\n\t\t\t\t\t${\n\t\t\t\t\t\ttypeof score === 'number'\n\t\t\t\t\t\t\t? html`<div class=\"score\">${score}</div>`\n\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t}\n\t\t\t\t\t${d.rating ? html`<div class=\"rating\">${d.rating}</div>` : nothing}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t${\n\t\t\t\tObject.keys(breakdown).length > 0\n\t\t\t\t\t? html`<div role=\"list\">\n\t\t\t\t\t\t${Object.entries(breakdown).map(\n\t\t\t\t\t\t\t([k, v]) => html`<div class=\"bar-row\" role=\"listitem\">\n\t\t\t\t\t\t\t\t<span style=\"text-transform: capitalize\">${k}</span>\n\t\t\t\t\t\t\t\t<span class=\"bar\"\n\t\t\t\t\t\t\t\t\t><span style=\"width: ${Math.max(0, Math.min(100, v))}%\"></span\n\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t<span>${v}</span>\n\t\t\t\t\t\t\t</div>`,\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\td.relationshipArchetype\n\t\t\t\t\t? html`<p>\n\t\t\t\t\t\t<span class=\"archetype\">${d.relationshipArchetype}</span>\n\t\t\t\t\t</p>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${d.summary ? html`<p>${d.summary}</p>` : nothing}\n\t\t\t${d.advice ? html`<p>${d.advice}</p>` : nothing}\n\t\t\t${\n\t\t\t\t(d.strengths?.length ?? 0) > 0 || (d.challenges?.length ?? 0) > 0\n\t\t\t\t\t? html`<div class=\"lists\">\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\td.strengths?.length\n\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t<h3>Strengths</h3>\n\t\t\t\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t\t\t\t${d.strengths.map((s) => html`<li>${s}</li>`)}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\td.challenges?.length\n\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t<h3>Challenges</h3>\n\t\t\t\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t\t\t\t${d.challenges.map((s) => html`<li>${s}</li>`)}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\td.keyAspects?.length\n\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t<h3>Key aspects</h3>\n\t\t\t\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t\t\t\t${d.keyAspects.map((s) => html`<li>${s}</li>`)}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</article>`;\n\t}\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-compatibility-card': RoxyCompatibilityCard;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n"],
5
- "mappings": "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",
3
+ "sources": ["../../src/components/compatibility-card.ts", "../../src/utils/base-styles.ts", "../../src/utils/format.ts"],
4
+ "sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport type {\n\tCalculateBioCompatibilityResponse,\n\tCalculateCompatibilityResponse,\n\tCalculateNumCompatibilityResponse,\n} from '../types/index.js';\nimport { baseStyles } from '../utils/base-styles.js';\nimport { formatNumber } from '../utils/format.js';\n\ntype CompatibilityData =\n\t| CalculateCompatibilityResponse\n\t| CalculateNumCompatibilityResponse\n\t| CalculateBioCompatibilityResponse;\n\n/**\n * Cross-domain compatibility card. Renders /astrology/compatibility-score,\n * /numerology/compatibility, or /biorhythm/compatibility responses.\n */\n@customElement('roxy-compatibility-card')\nexport class RoxyCompatibilityCard extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.card {\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\t\t\tbox-shadow: var(--roxy-shadow-sm);\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\n\t\t\t.head {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 1fr auto;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.head h2 {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\ttext-transform: capitalize;\n\t\t\t}\n\n\t\t\t.score {\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t\tfont-size: 2rem;\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\tcolor: var(--roxy-accent-fg, #b45309);\n\t\t\t\tline-height: 1;\n\t\t\t}\n\t\t\t.rating {\n\t\t\t\tcolor: var(--roxy-secondary, #475569);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\n\t\t\t.bar-row {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 8rem 1fr 3.5rem;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\talign-items: center;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\t.bar {\n\t\t\t\theight: 8px;\n\t\t\t\tbackground: var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-full, 9999px);\n\t\t\t\toverflow: hidden;\n\t\t\t}\n\t\t\t.bar > span {\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tbackground: var(--roxy-accent, #f59e0b);\n\t\t\t\ttransition:\n\t\t\t\t\twidth var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t}\n\t\t\t.bar-row > span:last-child {\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\ttext-align: right;\n\t\t\t}\n\n\t\t\t.archetype {\n\t\t\t\tcolor: var(--roxy-accent-fg, #b45309);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t}\n\n\t\t\t.lists {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.lists h3 {\n\t\t\t\tmargin: 0 0 var(--roxy-space-xs, 0.25rem);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tletter-spacing: 0.06em;\n\t\t\t}\n\t\t\t.lists ul {\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding-left: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: CompatibilityData | null = null;\n\n\t@property({ type: String, reflect: true })\n\tmode: 'astrology' | 'numerology' | 'biorhythm' = 'astrology';\n\n\tprivate getBreakdown(): Record<string, number> {\n\t\tconst d = this.data;\n\t\tif (!d) return {};\n\t\tif ('categories' in d && d.categories) {\n\t\t\tconst out: Record<string, number> = {};\n\t\t\tfor (const [k, v] of Object.entries(d.categories)) {\n\t\t\t\tif (typeof v === 'number' && Number.isFinite(v)) out[k] = v;\n\t\t\t}\n\t\t\treturn out;\n\t\t}\n\t\treturn {};\n\t}\n\n\trender() {\n\t\tconst d = this.data;\n\t\tif (!d)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No compatibility data</div>`;\n\n\t\tconst score = d.overallScore;\n\t\tconst breakdown = this.getBreakdown();\n\t\tconst rating =\n\t\t\t'rating' in d\n\t\t\t\t? (d as CalculateNumCompatibilityResponse).rating\n\t\t\t\t: undefined;\n\t\tconst archetype =\n\t\t\t'archetype' in d\n\t\t\t\t? (d as CalculateCompatibilityResponse).archetype\n\t\t\t\t: undefined;\n\t\tconst advice =\n\t\t\t'advice' in d\n\t\t\t\t? (d as CalculateNumCompatibilityResponse).advice\n\t\t\t\t: undefined;\n\t\tconst summary =\n\t\t\t'summary' in d\n\t\t\t\t? (d as CalculateCompatibilityResponse).summary\n\t\t\t\t: undefined;\n\t\tconst interpretation =\n\t\t\t'interpretation' in d\n\t\t\t\t? (d as CalculateCompatibilityResponse).interpretation\n\t\t\t\t: undefined;\n\t\tconst strengths = 'strengths' in d ? d.strengths : undefined;\n\t\tconst challenges = 'challenges' in d ? d.challenges : undefined;\n\t\tconst keyAspects =\n\t\t\t'keyAspects' in d\n\t\t\t\t? (d as CalculateCompatibilityResponse).keyAspects\n\t\t\t\t: undefined;\n\n\t\treturn html`<article\n\t\t\tclass=\"card\"\n\t\t\taria-label=${`Compatibility (${this.mode})`}\n\t\t>\n\t\t\t<div class=\"head\">\n\t\t\t\t<h2>${this.mode} compatibility</h2>\n\t\t\t\t<div>\n\t\t\t\t\t${\n\t\t\t\t\t\ttypeof score === 'number'\n\t\t\t\t\t\t\t? html`<div class=\"score\">${formatNumber(score, 0)}</div>`\n\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t}\n\t\t\t\t\t${rating ? html`<div class=\"rating\">${rating}</div>` : nothing}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t${\n\t\t\t\tObject.keys(breakdown).length > 0\n\t\t\t\t\t? html`<div role=\"list\">\n\t\t\t\t\t\t${Object.entries(breakdown).map(\n\t\t\t\t\t\t\t([k, v]) => html`<div class=\"bar-row\" role=\"listitem\">\n\t\t\t\t\t\t\t\t<span style=\"text-transform: capitalize\">${k}</span>\n\t\t\t\t\t\t\t\t<span class=\"bar\"\n\t\t\t\t\t\t\t\t\t><span style=\"width: ${Math.max(0, Math.min(100, v))}%\"></span\n\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t<span>${formatNumber(v, 0)}</span>\n\t\t\t\t\t\t\t</div>`,\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\tarchetype\n\t\t\t\t\t? html`<p>\n\t\t\t\t\t\t<span class=\"archetype\">${archetype.label}</span>\n\t\t\t\t\t\t${archetype.description ? html` \u00B7 ${archetype.description}` : nothing}\n\t\t\t\t\t</p>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${summary ? html`<p>${summary}</p>` : nothing}\n\t\t\t${interpretation && !summary ? html`<p>${interpretation}</p>` : nothing}\n\t\t\t${advice ? html`<p>${advice}</p>` : nothing}\n\t\t\t${\n\t\t\t\t(strengths?.length ?? 0) > 0 || (challenges?.length ?? 0) > 0\n\t\t\t\t\t? html`<div class=\"lists\">\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tstrengths?.length\n\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t<h3>Strengths</h3>\n\t\t\t\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t\t\t\t${strengths.map((s) => html`<li>${s}</li>`)}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tchallenges?.length\n\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t<h3>Challenges</h3>\n\t\t\t\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t\t\t\t${challenges.map((s) => html`<li>${s}</li>`)}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\tkeyAspects?.length\n\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t<h3 style=\"margin: 0 0 0.25rem; font-size: var(--roxy-text-xs); color: var(--roxy-muted); text-transform: uppercase; letter-spacing: 0.06em;\">Key aspects</h3>\n\t\t\t\t\t\t<ul style=\"margin: 0; padding-left: 1rem; font-size: var(--roxy-text-sm);\">\n\t\t\t\t\t\t\t${keyAspects.slice(0, 6).map((a) => html`<li>${formatAspect(a)}</li>`)}\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</article>`;\n\t}\n}\n\ntype KeyAspect = CalculateCompatibilityResponse extends {\n\tkeyAspects: Array<infer T>;\n}\n\t? T\n\t: never;\n\nfunction formatAspect(a: KeyAspect): string {\n\tconst aspect = a.type.toLowerCase().replace(/_/g, '-');\n\tconst orb =\n\t\ttypeof a.orb === 'number' ? ` (orb ${formatNumber(a.orb, 1)}\u00B0)` : '';\n\tconst head = [a.planet1, aspect, a.planet2].filter(Boolean).join(' ');\n\treturn a.description ? `${head}${orb} \u00B7 ${a.description}` : `${head}${orb}`;\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-compatibility-card': RoxyCompatibilityCard;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n", "/**\n * Display formatters for ISO timestamps and floats coming back from the API.\n * Every helper returns \"\" for nullish or unparseable input so it falls out of\n * template literals cleanly.\n */\n\nexport function formatTime(input: unknown): string {\n\tif (typeof input !== 'string' || input.length === 0) return '';\n\tif (/^\\d{4}-\\d{2}-\\d{2}$/.test(input)) return '';\n\tconst bareTime = /^\\d{2}:\\d{2}(:\\d{2})?$/.test(input);\n\tconst iso = bareTime ? `1970-01-01T${input}` : input;\n\tconst d = new Date(iso);\n\tif (Number.isNaN(d.getTime())) return input;\n\treturn d.toLocaleTimeString(undefined, {\n\t\thour: 'numeric',\n\t\tminute: '2-digit',\n\t\thour12: true,\n\t});\n}\n\nexport function formatDate(input: unknown): string {\n\tif (typeof input !== 'string' || input.length === 0) return '';\n\tconst d = new Date(\n\t\t/^\\d{4}-\\d{2}-\\d{2}$/.test(input) ? `${input}T00:00:00` : input,\n\t);\n\tif (Number.isNaN(d.getTime())) return input;\n\treturn d.toLocaleDateString(undefined, {\n\t\tmonth: 'short',\n\t\tday: 'numeric',\n\t\tyear: 'numeric',\n\t});\n}\n\nexport function formatTimeRange(\n\tt: { start?: string; end?: string } | undefined,\n): string {\n\tif (!t) return '';\n\tconst start = formatTime(t.start);\n\tconst end = formatTime(t.end);\n\tif (start && end) return `${start} - ${end}`;\n\treturn start || end || '';\n}\n\nexport function formatNumber(value: unknown, dp = 1): string {\n\tif (typeof value !== 'number' || !Number.isFinite(value)) return '';\n\treturn value.toFixed(dp).replace(/\\.?0+$/, '');\n}\n\nexport function formatPercent(value: unknown, dp = 1): string {\n\tconst n = formatNumber(value, dp);\n\treturn n ? `${n}%` : '';\n}\n\n/**\n * CSS class name per aspect type. Used by natal and synastry chart aspect\n * lines so the same color encoding (harmonious vs challenging) applies in\n * both wheels. Keys are lowercase canonical names, values are CSS class\n * suffixes the chart components define in their `:host` styles.\n */\nexport const ASPECT_CLASS: Record<string, string> = {\n\tconjunction: 'aspect-conjunction',\n\tsextile: 'aspect-sextile',\n\tsquare: 'aspect-square',\n\ttrine: 'aspect-trine',\n\topposition: 'aspect-opposition',\n};\n\n/**\n * Normalize an aspect entry's `type` field to a lowercase, hyphen-separated\n * canonical name (`SEMI_SEXTILE` \u2192 `semi-sextile`). Accepts any aspect-shaped\n * object so both natal and synastry inter-aspect entries can share this.\n */\nexport function normalizeAspect(a: { type?: string }): string {\n\treturn (a.type ?? '').toLowerCase().replace(/_/g, '-');\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["css", "css"]
7
7
  }
@@ -1,35 +1,6 @@
1
1
  import { LitElement } from 'lit';
2
- interface DashaPeriod {
3
- mahadashaLord?: string;
4
- antardashaLord?: string;
5
- pratyantardashaLord?: string;
6
- lord?: string;
7
- planet?: string;
8
- startDate?: string;
9
- endDate?: string;
10
- years?: number;
11
- durationYears?: number;
12
- }
13
- interface DashaData {
14
- moonNakshatra?: string;
15
- nakshatraName?: string;
16
- nakshatraLord?: string;
17
- mahadasha?: DashaPeriod;
18
- antardasha?: DashaPeriod;
19
- pratyantardasha?: DashaPeriod;
20
- mahadashas?: DashaPeriod[];
21
- antardashas?: DashaPeriod[];
22
- mahadashaLord?: string;
23
- mahadashaPeriod?: DashaPeriod;
24
- birthDashaBalance?: {
25
- lord?: string;
26
- years?: number;
27
- };
28
- totalYears?: number;
29
- remainingInMahadasha?: number;
30
- remainingInAntardasha?: number;
31
- remainingInPratyantardasha?: number;
32
- }
2
+ import type { GetCurrentDashaResponse, GetMajorDashasResponse, GetSubDashasResponse } from '../types/index.js';
3
+ type DashaData = GetCurrentDashaResponse | GetMajorDashasResponse | GetSubDashasResponse;
33
4
  /**
34
5
  * Dasha timeline. Renders /vedic-astrology/dasha/{current,major,sub/{...}}.
35
6
  * Default mode shows the active mahadasha + antardasha + pratyantardasha.
@@ -1 +1 @@
1
- {"version":3,"file":"dasha-timeline.d.ts","sourceRoot":"","sources":["../../src/components/dasha-timeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAW,MAAM,KAAK,CAAC;AAIrD,UAAU,WAAW;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,SAAS;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,iBAAiB,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;GAIG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAChD,MAAM,CAAC,MAAM,4BA8EX;IAGF,IAAI,EAAE,SAAS,GAAG,IAAI,CAAQ;IAG9B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,CAAa;IAEhD,MAAM;IA0CN,OAAO,CAAC,aAAa;IAkDrB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,SAAS;CAcjB;AAOD,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,qBAAqB,EAAE,iBAAiB,CAAC;KACzC;CACD"}
1
+ {"version":3,"file":"dasha-timeline.d.ts","sourceRoot":"","sources":["../../src/components/dasha-timeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAW,MAAM,KAAK,CAAC;AAErD,OAAO,KAAK,EACX,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,mBAAmB,CAAC;AAI3B,KAAK,SAAS,GACX,uBAAuB,GACvB,sBAAsB,GACtB,oBAAoB,CAAC;AAUxB;;;;GAIG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAChD,MAAM,CAAC,MAAM,4BA8EX;IAGF,IAAI,EAAE,SAAS,GAAG,IAAI,CAAQ;IAG9B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,CAAa;IAEhD,MAAM;IA0CN,OAAO,CAAC,aAAa;IA6CrB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,SAAS;CAYjB;AAOD,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,qBAAqB,EAAE,iBAAiB,CAAC;KACzC;CACD"}
@@ -99,6 +99,12 @@ var baseStyles = css`
99
99
  }
100
100
  `;
101
101
 
102
+ // packages/ui/src/utils/format.ts
103
+ function formatNumber(value, dp = 1) {
104
+ if (typeof value !== "number" || !Number.isFinite(value)) return "";
105
+ return value.toFixed(dp).replace(/\.?0+$/, "");
106
+ }
107
+
102
108
  // packages/ui/src/components/dasha-timeline.ts
103
109
  var RoxyDashaTimeline = class extends LitElement {
104
110
  constructor() {
@@ -111,16 +117,16 @@ var RoxyDashaTimeline = class extends LitElement {
111
117
  if (!d)
112
118
  return html`<div class="roxy-empty" role="status">No dasha data</div>`;
113
119
  const periods = this.collectPeriods(d);
114
- const maxYears = periods.length ? Math.max(...periods.map((p) => p.durationYears ?? p.years ?? 1)) : 0;
120
+ const maxYears = periods.length ? Math.max(...periods.map((p) => p.durationYears)) : 0;
115
121
  return html`<div class="wrap" aria-label="Dasha timeline">
116
122
  <header class="head">
117
123
  <h2 class="title">
118
124
  ${this.period === "major" ? "Vimshottari Mahadasha" : this.period === "sub" ? "Antardasha" : "Active dashas"}
119
125
  </h2>
120
- ${d.nakshatraName || d.moonNakshatra ? html`<div class="nakshatra">
121
- Moon nakshatra: ${d.nakshatraName ?? d.moonNakshatra}
122
- ${d.nakshatraLord ? html`(lord ${d.nakshatraLord})` : nothing}
123
- </div>` : nothing}
126
+ ${"nakshatraName" in d && d.nakshatraName ? html`<div class="nakshatra">
127
+ Moon nakshatra: ${d.nakshatraName}
128
+ ${"nakshatraLord" in d && d.nakshatraLord ? html`(lord ${d.nakshatraLord})` : nothing}
129
+ </div>` : nothing}
124
130
  </header>
125
131
 
126
132
  ${this.period === "current" ? this.renderCurrent(d) : nothing}
@@ -130,39 +136,35 @@ var RoxyDashaTimeline = class extends LitElement {
130
136
  </div>`;
131
137
  }
132
138
  renderCurrent(d) {
139
+ if (!("mahadasha" in d)) return nothing;
133
140
  return html`<div class="current">
134
- ${d.mahadasha ? html`<div>
135
- <span>Mahadasha</span>
136
- <strong>${d.mahadasha.lord ?? d.mahadasha.mahadashaLord}</strong>
137
- ${typeof d.remainingInMahadasha === "number" ? html`<small>${d.remainingInMahadasha.toFixed(1)} years left</small>` : nothing}
138
- </div>` : nothing}
139
- ${d.antardasha ? html`<div>
140
- <span>Antardasha</span>
141
- <strong>${d.antardasha.lord ?? d.antardasha.antardashaLord}</strong>
142
- ${typeof d.remainingInAntardasha === "number" ? html`<small>${d.remainingInAntardasha.toFixed(1)} years left</small>` : nothing}
143
- </div>` : nothing}
144
- ${d.pratyantardasha ? html`<div>
145
- <span>Pratyantardasha</span>
146
- <strong
147
- >${d.pratyantardasha.lord ?? d.pratyantardasha.pratyantardashaLord}</strong
148
- >
149
- ${typeof d.remainingInPratyantardasha === "number" ? html`<small
150
- >${d.remainingInPratyantardasha.toFixed(2)} years left</small
151
- >` : nothing}
152
- </div>` : nothing}
141
+ ${"mahadasha" in d && d.mahadasha ? html`<div>
142
+ <span>Mahadasha</span>
143
+ <strong>${d.mahadasha.planet}</strong>
144
+ ${"remainingInMahadasha" in d && d.remainingInMahadasha ? html`<small>${formatNumber(d.remainingInMahadasha.years + d.remainingInMahadasha.months / 12, 1)} years left</small>` : nothing}
145
+ </div>` : nothing}
146
+ ${"antardasha" in d && d.antardasha ? html`<div>
147
+ <span>Antardasha</span>
148
+ <strong>${d.antardasha.planet}</strong>
149
+ ${"remainingInAntardasha" in d && d.remainingInAntardasha ? html`<small>${formatNumber(d.remainingInAntardasha.years + d.remainingInAntardasha.months / 12, 1)} years left</small>` : nothing}
150
+ </div>` : nothing}
151
+ ${"pratyantardasha" in d && d.pratyantardasha ? html`<div>
152
+ <span>Pratyantardasha</span>
153
+ <strong>${d.pratyantardasha.planet}</strong>
154
+ ${"remainingInPratyantardasha" in d && d.remainingInPratyantardasha ? html`<small>${formatNumber(d.remainingInPratyantardasha.years + d.remainingInPratyantardasha.months / 12, 1)} years left</small>` : nothing}
155
+ </div>` : nothing}
153
156
  </div>`;
154
157
  }
155
158
  collectPeriods(d) {
156
- if (this.period === "major" && d.mahadashas?.length) return d.mahadashas;
157
- if (this.period === "sub" && d.antardashas?.length) return d.antardashas;
158
- return d.mahadashas ?? d.antardashas ?? [];
159
+ if ("mahadashas" in d && d.mahadashas?.length) return d.mahadashas;
160
+ if ("antardashas" in d && d.antardashas?.length) return d.antardashas;
161
+ return [];
159
162
  }
160
163
  renderBar(p, max) {
161
- const lord = p.lord ?? p.mahadashaLord ?? p.antardashaLord ?? p.planet ?? "";
162
- const years = p.durationYears ?? p.years ?? 0;
164
+ const years = p.durationYears;
163
165
  const width = max > 0 ? years / max * 100 : 0;
164
166
  return html`<div class="bar" role="listitem">
165
- <span>${lord}</span>
167
+ <span>${p.planet}</span>
166
168
  <span class="bar-track"><span style="width: ${width}%"></span></span>
167
169
  <span class="dates">
168
170
  ${p.startDate ? formatYear(p.startDate) : ""}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/components/dasha-timeline.ts", "../../src/utils/base-styles.ts"],
4
- "sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { baseStyles } from '../utils/base-styles.js';\n\ninterface DashaPeriod {\n\tmahadashaLord?: string;\n\tantardashaLord?: string;\n\tpratyantardashaLord?: string;\n\tlord?: string;\n\tplanet?: string;\n\tstartDate?: string;\n\tendDate?: string;\n\tyears?: number;\n\tdurationYears?: number;\n}\n\ninterface DashaData {\n\tmoonNakshatra?: string;\n\tnakshatraName?: string;\n\tnakshatraLord?: string;\n\tmahadasha?: DashaPeriod;\n\tantardasha?: DashaPeriod;\n\tpratyantardasha?: DashaPeriod;\n\tmahadashas?: DashaPeriod[];\n\tantardashas?: DashaPeriod[];\n\tmahadashaLord?: string;\n\tmahadashaPeriod?: DashaPeriod;\n\tbirthDashaBalance?: { lord?: string; years?: number };\n\ttotalYears?: number;\n\tremainingInMahadasha?: number;\n\tremainingInAntardasha?: number;\n\tremainingInPratyantardasha?: number;\n}\n\n/**\n * Dasha timeline. Renders /vedic-astrology/dasha/{current,major,sub/{...}}.\n * Default mode shows the active mahadasha + antardasha + pratyantardasha.\n * Switch to period=\"major\" for the full 120-year Vimshottari timeline.\n */\n@customElement('roxy-dasha-timeline')\nexport class RoxyDashaTimeline extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.wrap {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.head {\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: space-between;\n\t\t\t\talign-items: center;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.title {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t}\n\t\t\t.nakshatra {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\n\t\t\t.current {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-md, 1rem);\n\t\t\t\tbox-shadow: var(--roxy-shadow-sm);\n\t\t\t}\n\t\t\t.current div span:first-child {\n\t\t\t\tdisplay: block;\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tletter-spacing: 0.06em;\n\t\t\t}\n\t\t\t.current div strong {\n\t\t\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t}\n\n\t\t\t.timeline {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-xs, 0.25rem);\n\t\t\t}\n\t\t\t.bar {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 5rem 1fr 8rem;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\talign-items: center;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\t.bar-track {\n\t\t\t\theight: 14px;\n\t\t\t\tbackground: var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-full, 9999px);\n\t\t\t\toverflow: hidden;\n\t\t\t}\n\t\t\t.bar-track > span {\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tbackground: var(--roxy-accent, #f59e0b);\n\t\t\t\ttransition:\n\t\t\t\t\twidth var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t}\n\t\t\t.dates {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t\ttext-align: right;\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: DashaData | null = null;\n\n\t@property({ type: String, reflect: true })\n\tperiod: 'current' | 'major' | 'sub' = 'current';\n\n\trender() {\n\t\tconst d = this.data;\n\t\tif (!d)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No dasha data</div>`;\n\n\t\tconst periods = this.collectPeriods(d);\n\t\tconst maxYears = periods.length\n\t\t\t? Math.max(...periods.map((p) => p.durationYears ?? p.years ?? 1))\n\t\t\t: 0;\n\n\t\treturn html`<div class=\"wrap\" aria-label=\"Dasha timeline\">\n\t\t\t<header class=\"head\">\n\t\t\t\t<h2 class=\"title\">\n\t\t\t\t\t${\n\t\t\t\t\t\tthis.period === 'major'\n\t\t\t\t\t\t\t? 'Vimshottari Mahadasha'\n\t\t\t\t\t\t\t: this.period === 'sub'\n\t\t\t\t\t\t\t\t? 'Antardasha'\n\t\t\t\t\t\t\t\t: 'Active dashas'\n\t\t\t\t\t}\n\t\t\t\t</h2>\n\t\t\t\t${\n\t\t\t\t\td.nakshatraName || d.moonNakshatra\n\t\t\t\t\t\t? html`<div class=\"nakshatra\">\n\t\t\t\t\t\t\tMoon nakshatra: ${d.nakshatraName ?? d.moonNakshatra}\n\t\t\t\t\t\t\t${d.nakshatraLord ? html`(lord ${d.nakshatraLord})` : nothing}\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t</header>\n\n\t\t\t${this.period === 'current' ? this.renderCurrent(d) : nothing}\n\t\t\t${\n\t\t\t\tperiods.length > 0\n\t\t\t\t\t? html`<div class=\"timeline\" role=\"list\">\n\t\t\t\t\t\t${periods.map((p) => this.renderBar(p, maxYears))}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n\n\tprivate renderCurrent(d: DashaData) {\n\t\treturn html`<div class=\"current\">\n\t\t\t${\n\t\t\t\td.mahadasha\n\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t<span>Mahadasha</span>\n\t\t\t\t\t\t<strong>${d.mahadasha.lord ?? d.mahadasha.mahadashaLord}</strong>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\ttypeof d.remainingInMahadasha === 'number'\n\t\t\t\t\t\t\t\t? html`<small>${d.remainingInMahadasha.toFixed(1)} years left</small>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\td.antardasha\n\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t<span>Antardasha</span>\n\t\t\t\t\t\t<strong>${d.antardasha.lord ?? d.antardasha.antardashaLord}</strong>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\ttypeof d.remainingInAntardasha === 'number'\n\t\t\t\t\t\t\t\t? html`<small>${d.remainingInAntardasha.toFixed(1)} years left</small>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\td.pratyantardasha\n\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t<span>Pratyantardasha</span>\n\t\t\t\t\t\t<strong\n\t\t\t\t\t\t\t>${\n\t\t\t\t\t\t\t\td.pratyantardasha.lord ?? d.pratyantardasha.pratyantardashaLord\n\t\t\t\t\t\t\t}</strong\n\t\t\t\t\t\t>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\ttypeof d.remainingInPratyantardasha === 'number'\n\t\t\t\t\t\t\t\t? html`<small\n\t\t\t\t\t\t\t\t\t>${d.remainingInPratyantardasha.toFixed(2)} years left</small\n\t\t\t\t\t\t\t\t>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n\n\tprivate collectPeriods(d: DashaData): DashaPeriod[] {\n\t\tif (this.period === 'major' && d.mahadashas?.length) return d.mahadashas;\n\t\tif (this.period === 'sub' && d.antardashas?.length) return d.antardashas;\n\t\treturn d.mahadashas ?? d.antardashas ?? [];\n\t}\n\n\tprivate renderBar(p: DashaPeriod, max: number) {\n\t\tconst lord =\n\t\t\tp.lord ?? p.mahadashaLord ?? p.antardashaLord ?? p.planet ?? '';\n\t\tconst years = p.durationYears ?? p.years ?? 0;\n\t\tconst width = max > 0 ? (years / max) * 100 : 0;\n\t\treturn html`<div class=\"bar\" role=\"listitem\">\n\t\t\t<span>${lord}</span>\n\t\t\t<span class=\"bar-track\"><span style=\"width: ${width}%\"></span></span>\n\t\t\t<span class=\"dates\">\n\t\t\t\t${p.startDate ? formatYear(p.startDate) : ''}\n\t\t\t\t${p.endDate ? html`- ${formatYear(p.endDate)}` : ''}\n\t\t\t</span>\n\t\t</div>`;\n\t}\n}\n\nfunction formatYear(s: string): string {\n\tconst m = s.match(/^(\\d{4})/);\n\treturn m ? m[1] : s;\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-dasha-timeline': RoxyDashaTimeline;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n"],
5
- "mappings": "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",
3
+ "sources": ["../../src/components/dasha-timeline.ts", "../../src/utils/base-styles.ts", "../../src/utils/format.ts"],
4
+ "sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport type {\n\tGetCurrentDashaResponse,\n\tGetMajorDashasResponse,\n\tGetSubDashasResponse,\n} from '../types/index.js';\nimport { baseStyles } from '../utils/base-styles.js';\nimport { formatNumber } from '../utils/format.js';\n\ntype DashaData =\n\t| GetCurrentDashaResponse\n\t| GetMajorDashasResponse\n\t| GetSubDashasResponse;\n\ntype DashaPeriod = {\n\tplanet: string;\n\tstartDate: string;\n\tendDate: string;\n\tdurationYears: number;\n\tinterpretation?: string;\n};\n\n/**\n * Dasha timeline. Renders /vedic-astrology/dasha/{current,major,sub/{...}}.\n * Default mode shows the active mahadasha + antardasha + pratyantardasha.\n * Switch to period=\"major\" for the full 120-year Vimshottari timeline.\n */\n@customElement('roxy-dasha-timeline')\nexport class RoxyDashaTimeline extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.wrap {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.head {\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: space-between;\n\t\t\t\talign-items: center;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.title {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t}\n\t\t\t.nakshatra {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\n\t\t\t.current {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-md, 1rem);\n\t\t\t\tbox-shadow: var(--roxy-shadow-sm);\n\t\t\t}\n\t\t\t.current div span:first-child {\n\t\t\t\tdisplay: block;\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tletter-spacing: 0.06em;\n\t\t\t}\n\t\t\t.current div strong {\n\t\t\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t}\n\n\t\t\t.timeline {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-xs, 0.25rem);\n\t\t\t}\n\t\t\t.bar {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 5rem 1fr 8rem;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\talign-items: center;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\t.bar-track {\n\t\t\t\theight: 14px;\n\t\t\t\tbackground: var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-full, 9999px);\n\t\t\t\toverflow: hidden;\n\t\t\t}\n\t\t\t.bar-track > span {\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tbackground: var(--roxy-accent, #f59e0b);\n\t\t\t\ttransition:\n\t\t\t\t\twidth var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t}\n\t\t\t.dates {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t\ttext-align: right;\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: DashaData | null = null;\n\n\t@property({ type: String, reflect: true })\n\tperiod: 'current' | 'major' | 'sub' = 'current';\n\n\trender() {\n\t\tconst d = this.data;\n\t\tif (!d)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No dasha data</div>`;\n\n\t\tconst periods = this.collectPeriods(d);\n\t\tconst maxYears = periods.length\n\t\t\t? Math.max(...periods.map((p) => p.durationYears))\n\t\t\t: 0;\n\n\t\treturn html`<div class=\"wrap\" aria-label=\"Dasha timeline\">\n\t\t\t<header class=\"head\">\n\t\t\t\t<h2 class=\"title\">\n\t\t\t\t\t${\n\t\t\t\t\t\tthis.period === 'major'\n\t\t\t\t\t\t\t? 'Vimshottari Mahadasha'\n\t\t\t\t\t\t\t: this.period === 'sub'\n\t\t\t\t\t\t\t\t? 'Antardasha'\n\t\t\t\t\t\t\t\t: 'Active dashas'\n\t\t\t\t\t}\n\t\t\t\t</h2>\n\t\t\t\t${\n\t\t\t\t\t'nakshatraName' in d && d.nakshatraName\n\t\t\t\t\t\t? html`<div class=\"nakshatra\">\n\t\t\t\t\t\tMoon nakshatra: ${d.nakshatraName}\n\t\t\t\t\t\t${'nakshatraLord' in d && d.nakshatraLord ? html`(lord ${d.nakshatraLord})` : nothing}\n\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t</header>\n\n\t\t\t${this.period === 'current' ? this.renderCurrent(d) : nothing}\n\t\t\t${\n\t\t\t\tperiods.length > 0\n\t\t\t\t\t? html`<div class=\"timeline\" role=\"list\">\n\t\t\t\t\t\t${periods.map((p) => this.renderBar(p, maxYears))}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n\n\tprivate renderCurrent(d: DashaData) {\n\t\tif (!('mahadasha' in d)) return nothing;\n\t\treturn html`<div class=\"current\">\n\t\t\t${\n\t\t\t\t'mahadasha' in d && d.mahadasha\n\t\t\t\t\t? html`<div>\n\t\t\t\t\t<span>Mahadasha</span>\n\t\t\t\t\t<strong>${d.mahadasha.planet}</strong>\n\t\t\t\t\t${\n\t\t\t\t\t\t'remainingInMahadasha' in d && d.remainingInMahadasha\n\t\t\t\t\t\t\t? html`<small>${formatNumber(d.remainingInMahadasha.years + d.remainingInMahadasha.months / 12, 1)} years left</small>`\n\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t}\n\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\t'antardasha' in d && d.antardasha\n\t\t\t\t\t? html`<div>\n\t\t\t\t\t<span>Antardasha</span>\n\t\t\t\t\t<strong>${d.antardasha.planet}</strong>\n\t\t\t\t\t${\n\t\t\t\t\t\t'remainingInAntardasha' in d && d.remainingInAntardasha\n\t\t\t\t\t\t\t? html`<small>${formatNumber(d.remainingInAntardasha.years + d.remainingInAntardasha.months / 12, 1)} years left</small>`\n\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t}\n\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\t'pratyantardasha' in d && d.pratyantardasha\n\t\t\t\t\t? html`<div>\n\t\t\t\t\t<span>Pratyantardasha</span>\n\t\t\t\t\t<strong>${d.pratyantardasha.planet}</strong>\n\t\t\t\t\t${\n\t\t\t\t\t\t'remainingInPratyantardasha' in d && d.remainingInPratyantardasha\n\t\t\t\t\t\t\t? html`<small>${formatNumber(d.remainingInPratyantardasha.years + d.remainingInPratyantardasha.months / 12, 1)} years left</small>`\n\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t}\n\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n\n\tprivate collectPeriods(d: DashaData): DashaPeriod[] {\n\t\tif ('mahadashas' in d && d.mahadashas?.length) return d.mahadashas;\n\t\tif ('antardashas' in d && d.antardashas?.length) return d.antardashas;\n\t\treturn [];\n\t}\n\n\tprivate renderBar(p: DashaPeriod, max: number) {\n\t\tconst years = p.durationYears;\n\t\tconst width = max > 0 ? (years / max) * 100 : 0;\n\t\treturn html`<div class=\"bar\" role=\"listitem\">\n\t\t\t<span>${p.planet}</span>\n\t\t\t<span class=\"bar-track\"><span style=\"width: ${width}%\"></span></span>\n\t\t\t<span class=\"dates\">\n\t\t\t\t${p.startDate ? formatYear(p.startDate) : ''}\n\t\t\t\t${p.endDate ? html`- ${formatYear(p.endDate)}` : ''}\n\t\t\t</span>\n\t\t</div>`;\n\t}\n}\n\nfunction formatYear(s: string): string {\n\tconst m = s.match(/^(\\d{4})/);\n\treturn m ? m[1] : s;\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-dasha-timeline': RoxyDashaTimeline;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n", "/**\n * Display formatters for ISO timestamps and floats coming back from the API.\n * Every helper returns \"\" for nullish or unparseable input so it falls out of\n * template literals cleanly.\n */\n\nexport function formatTime(input: unknown): string {\n\tif (typeof input !== 'string' || input.length === 0) return '';\n\tif (/^\\d{4}-\\d{2}-\\d{2}$/.test(input)) return '';\n\tconst bareTime = /^\\d{2}:\\d{2}(:\\d{2})?$/.test(input);\n\tconst iso = bareTime ? `1970-01-01T${input}` : input;\n\tconst d = new Date(iso);\n\tif (Number.isNaN(d.getTime())) return input;\n\treturn d.toLocaleTimeString(undefined, {\n\t\thour: 'numeric',\n\t\tminute: '2-digit',\n\t\thour12: true,\n\t});\n}\n\nexport function formatDate(input: unknown): string {\n\tif (typeof input !== 'string' || input.length === 0) return '';\n\tconst d = new Date(\n\t\t/^\\d{4}-\\d{2}-\\d{2}$/.test(input) ? `${input}T00:00:00` : input,\n\t);\n\tif (Number.isNaN(d.getTime())) return input;\n\treturn d.toLocaleDateString(undefined, {\n\t\tmonth: 'short',\n\t\tday: 'numeric',\n\t\tyear: 'numeric',\n\t});\n}\n\nexport function formatTimeRange(\n\tt: { start?: string; end?: string } | undefined,\n): string {\n\tif (!t) return '';\n\tconst start = formatTime(t.start);\n\tconst end = formatTime(t.end);\n\tif (start && end) return `${start} - ${end}`;\n\treturn start || end || '';\n}\n\nexport function formatNumber(value: unknown, dp = 1): string {\n\tif (typeof value !== 'number' || !Number.isFinite(value)) return '';\n\treturn value.toFixed(dp).replace(/\\.?0+$/, '');\n}\n\nexport function formatPercent(value: unknown, dp = 1): string {\n\tconst n = formatNumber(value, dp);\n\treturn n ? `${n}%` : '';\n}\n\n/**\n * CSS class name per aspect type. Used by natal and synastry chart aspect\n * lines so the same color encoding (harmonious vs challenging) applies in\n * both wheels. Keys are lowercase canonical names, values are CSS class\n * suffixes the chart components define in their `:host` styles.\n */\nexport const ASPECT_CLASS: Record<string, string> = {\n\tconjunction: 'aspect-conjunction',\n\tsextile: 'aspect-sextile',\n\tsquare: 'aspect-square',\n\ttrine: 'aspect-trine',\n\topposition: 'aspect-opposition',\n};\n\n/**\n * Normalize an aspect entry's `type` field to a lowercase, hyphen-separated\n * canonical name (`SEMI_SEXTILE` \u2192 `semi-sextile`). Accepts any aspect-shaped\n * object so both natal and synastry inter-aspect entries can share this.\n */\nexport function normalizeAspect(a: { type?: string }): string {\n\treturn (a.type ?? '').toLowerCase().replace(/_/g, '-');\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["css", "css"]
7
7
  }
@@ -1,8 +1,8 @@
1
1
  import { LitElement, type TemplateResult } from 'lit';
2
2
  /**
3
- * Generic fallback renderer. Accepts ANY OpenAPI response shape and renders it
4
- * via field-name heuristics so future spec additions render reasonably without
5
- * hand-wired components. Mirrors the WordPress GenericRenderer pattern.
3
+ * Generic fallback renderer. Accepts ANY OpenAPI response shape and renders
4
+ * it via field-name heuristics so future spec additions render reasonably
5
+ * without hand-wired components.
6
6
  *
7
7
  * Heuristic order:
8
8
  * 1. Primitive (string, number, boolean) -> single line.
@@ -11,9 +11,8 @@ import { LitElement, type TemplateResult } from 'lit';
11
11
  * 4. Object with title-like field -> card with key/value rows.
12
12
  * 5. Otherwise -> definition list of all keys.
13
13
  *
14
- * Future spec hint: when the server emits `x-roxy-ui` on a schema, the
15
- * dispatcher in tool-call (Phase 2.5) will select a hand-tuned component
16
- * instead of this fallback.
14
+ * When a schema declares an `x-roxy-ui` hint, a future dispatcher can opt
15
+ * into a hand-tuned component instead of this fallback.
17
16
  */
18
17
  type Json = string | number | boolean | null | Json[] | {
19
18
  [key: string]: Json;
@@ -21,6 +20,12 @@ type Json = string | number | boolean | null | Json[] | {
21
20
  export declare class RoxyData extends LitElement {
22
21
  static styles: import("lit").CSSResult[];
23
22
  data: Json;
23
+ /**
24
+ * Internal recursion depth. Nested <roxy-data> instances inherit this from
25
+ * the parent and increment to guard against circular references in the
26
+ * input. Not part of the public API; do not set from consumer code.
27
+ */
28
+ depth: number;
24
29
  render(): TemplateResult<1>;
25
30
  private renderValue;
26
31
  private renderArray;
@@ -29,7 +34,6 @@ export declare class RoxyData extends LitElement {
29
34
  private renderField;
30
35
  private formatPrimitive;
31
36
  private collectKeys;
32
- private humanize;
33
37
  }
34
38
  declare global {
35
39
  interface HTMLElementTagNameMap {
@@ -1 +1 @@
1
- {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/components/data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAW,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAI1E;;;;;;;;;;;;;;;GAeG;AAEH,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAMhF,qBACa,QAAS,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,4BAiGX;IAGF,IAAI,EAAE,IAAI,CAAQ;IAElB,MAAM;IAYN,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,YAAY;IA+CpB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,QAAQ;CAMhB;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,WAAW,EAAE,QAAQ,CAAC;KACtB;CACD"}
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/components/data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAW,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAK1E;;;;;;;;;;;;;;GAcG;AAEH,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAchF,qBACa,QAAS,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,4BAiGX;IAGF,IAAI,EAAE,IAAI,CAAQ;IAElB;;;;OAIG;IAEH,KAAK,SAAK;IAEV,MAAM;IAeN,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,YAAY;IA+CpB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,WAAW;CAOnB;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,WAAW,EAAE,QAAQ,CAAC;KACtB;CACD"}
@@ -99,19 +99,29 @@ var baseStyles = css`
99
99
  }
100
100
  `;
101
101
 
102
+ // packages/ui/src/utils/string.ts
103
+ function humanize(s) {
104
+ return s.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^\w/, (c) => c.toUpperCase());
105
+ }
106
+
102
107
  // packages/ui/src/components/data.ts
103
108
  var TITLE_KEYS = ["title", "name", "label", "heading", "overview", "summary"];
104
109
  var IMAGE_KEYS = ["imageUrl", "image", "icon", "symbol"];
105
110
  var SKIP_KEYS = ["imageUrl", "image"];
111
+ var MAX_DEPTH = 6;
106
112
  var RoxyData = class extends LitElement {
107
113
  constructor() {
108
114
  super(...arguments);
109
115
  this.data = null;
116
+ this.depth = 0;
110
117
  }
111
118
  render() {
112
119
  if (this.data == null) {
113
120
  return html`<div class="roxy-empty" role="status">No data</div>`;
114
121
  }
122
+ if (this.depth >= MAX_DEPTH) {
123
+ return html`<div class="roxy-empty" role="status">…</div>`;
124
+ }
115
125
  return html`<div
116
126
  class="roxy-card"
117
127
  aria-label="Generic data display"
@@ -153,7 +163,7 @@ var RoxyData = class extends LitElement {
153
163
  return html`<table class="roxy-table" role="table">
154
164
  <thead>
155
165
  <tr>
156
- ${keys.map((k) => html`<th>${this.humanize(k)}</th>`)}
166
+ ${keys.map((k) => html`<th>${humanize(k)}</th>`)}
157
167
  </tr>
158
168
  </thead>
159
169
  <tbody>
@@ -186,7 +196,7 @@ var RoxyData = class extends LitElement {
186
196
  ${rows.length > 0 ? html`<dl class="roxy-rows">
187
197
  ${rows.map(
188
198
  ([k, v]) => html`
189
- <dt>${this.humanize(k)}</dt>
199
+ <dt>${humanize(k)}</dt>
190
200
  <dd>${this.renderField(v)}</dd>
191
201
  `
192
202
  )}
@@ -208,7 +218,7 @@ var RoxyData = class extends LitElement {
208
218
  </ul>`;
209
219
  }
210
220
  }
211
- return html`<roxy-data .data=${value}></roxy-data>`;
221
+ return html`<roxy-data .data=${value} .depth=${this.depth + 1}></roxy-data>`;
212
222
  }
213
223
  formatPrimitive(value) {
214
224
  if (value === null || value === void 0) return "";
@@ -225,9 +235,6 @@ var RoxyData = class extends LitElement {
225
235
  }
226
236
  return Array.from(seen);
227
237
  }
228
- humanize(key) {
229
- return key.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^\w/, (c) => c.toUpperCase());
230
- }
231
238
  };
232
239
  RoxyData.styles = [
233
240
  baseStyles,
@@ -330,6 +337,9 @@ RoxyData.styles = [
330
337
  __decorateClass([
331
338
  property({ attribute: false })
332
339
  ], RoxyData.prototype, "data", 2);
340
+ __decorateClass([
341
+ property({ attribute: false })
342
+ ], RoxyData.prototype, "depth", 2);
333
343
  RoxyData = __decorateClass([
334
344
  customElement("roxy-data")
335
345
  ], RoxyData);