@roxyapi/ui 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +327 -14
  3. package/THEMING.md +24 -7
  4. package/dist/cdn/components/ashtakavarga-grid.js +349 -0
  5. package/dist/cdn/components/ashtakavarga-grid.js.map +7 -0
  6. package/dist/cdn/components/biorhythm-chart.js +15 -22
  7. package/dist/cdn/components/biorhythm-chart.js.map +3 -3
  8. package/dist/cdn/components/choghadiya-grid.js +239 -0
  9. package/dist/cdn/components/choghadiya-grid.js.map +7 -0
  10. package/dist/cdn/components/compatibility-card.js +36 -34
  11. package/dist/cdn/components/compatibility-card.js.map +4 -4
  12. package/dist/cdn/components/dasha-timeline.js +35 -39
  13. package/dist/cdn/components/dasha-timeline.js.map +4 -4
  14. package/dist/cdn/components/data.js +9 -9
  15. package/dist/cdn/components/data.js.map +4 -4
  16. package/dist/cdn/components/divisional-chart.js +279 -0
  17. package/dist/cdn/components/divisional-chart.js.map +7 -0
  18. package/dist/cdn/components/dosha-card.js +49 -49
  19. package/dist/cdn/components/dosha-card.js.map +3 -3
  20. package/dist/cdn/components/endpoint-form.js +47 -28
  21. package/dist/cdn/components/endpoint-form.js.map +4 -4
  22. package/dist/cdn/components/guna-milan.js +66 -24
  23. package/dist/cdn/components/guna-milan.js.map +4 -4
  24. package/dist/cdn/components/hexagram.js +26 -26
  25. package/dist/cdn/components/hexagram.js.map +3 -3
  26. package/dist/cdn/components/horoscope-card.js +47 -40
  27. package/dist/cdn/components/horoscope-card.js.map +4 -4
  28. package/dist/cdn/components/kp-planets-table.js +10 -10
  29. package/dist/cdn/components/kp-planets-table.js.map +4 -4
  30. package/dist/cdn/components/location-search.js +6 -6
  31. package/dist/cdn/components/location-search.js.map +3 -3
  32. package/dist/cdn/components/moon-phase.js +18 -18
  33. package/dist/cdn/components/moon-phase.js.map +4 -4
  34. package/dist/cdn/components/natal-chart.js +240 -24
  35. package/dist/cdn/components/natal-chart.js.map +4 -4
  36. package/dist/cdn/components/numerology-card.js +40 -31
  37. package/dist/cdn/components/numerology-card.js.map +4 -4
  38. package/dist/cdn/components/panchang-table.js +30 -30
  39. package/dist/cdn/components/panchang-table.js.map +4 -4
  40. package/dist/cdn/components/shadbala-table.js +312 -0
  41. package/dist/cdn/components/shadbala-table.js.map +7 -0
  42. package/dist/cdn/components/synastry-chart.js +129 -39
  43. package/dist/cdn/components/synastry-chart.js.map +4 -4
  44. package/dist/cdn/components/tarot-card.js +49 -20
  45. package/dist/cdn/components/tarot-card.js.map +3 -3
  46. package/dist/cdn/components/tarot-spread.js +43 -27
  47. package/dist/cdn/components/tarot-spread.js.map +3 -3
  48. package/dist/cdn/components/transits-table.js +391 -0
  49. package/dist/cdn/components/transits-table.js.map +7 -0
  50. package/dist/cdn/components/vedic-kundli.js +63 -27
  51. package/dist/cdn/components/vedic-kundli.js.map +4 -4
  52. package/dist/cdn/components/yoga-list.js +334 -0
  53. package/dist/cdn/components/yoga-list.js.map +7 -0
  54. package/dist/cdn/roxy-ui.js +2104 -544
  55. package/dist/cdn/roxy-ui.js.map +4 -4
  56. package/dist/components/ashtakavarga-grid.d.ts +26 -0
  57. package/dist/components/ashtakavarga-grid.d.ts.map +1 -0
  58. package/dist/components/ashtakavarga-grid.js +457 -0
  59. package/dist/components/ashtakavarga-grid.js.map +7 -0
  60. package/dist/components/biorhythm-chart.d.ts +2 -46
  61. package/dist/components/biorhythm-chart.d.ts.map +1 -1
  62. package/dist/components/biorhythm-chart.js +24 -23
  63. package/dist/components/biorhythm-chart.js.map +2 -2
  64. package/dist/components/choghadiya-grid.d.ts +19 -0
  65. package/dist/components/choghadiya-grid.d.ts.map +1 -0
  66. package/dist/components/choghadiya-grid.js +304 -0
  67. package/dist/components/choghadiya-grid.js.map +7 -0
  68. package/dist/components/compatibility-card.d.ts +2 -27
  69. package/dist/components/compatibility-card.d.ts.map +1 -1
  70. package/dist/components/compatibility-card.js +50 -29
  71. package/dist/components/compatibility-card.js.map +3 -3
  72. package/dist/components/dasha-timeline.d.ts +2 -31
  73. package/dist/components/dasha-timeline.d.ts.map +1 -1
  74. package/dist/components/dasha-timeline.js +32 -30
  75. package/dist/components/dasha-timeline.js.map +3 -3
  76. package/dist/components/data.d.ts +11 -7
  77. package/dist/components/data.d.ts.map +1 -1
  78. package/dist/components/data.js +16 -6
  79. package/dist/components/data.js.map +3 -3
  80. package/dist/components/divisional-chart.d.ts +20 -0
  81. package/dist/components/divisional-chart.d.ts.map +1 -0
  82. package/dist/components/divisional-chart.js +471 -0
  83. package/dist/components/divisional-chart.js.map +7 -0
  84. package/dist/components/dosha-card.d.ts +2 -16
  85. package/dist/components/dosha-card.d.ts.map +1 -1
  86. package/dist/components/dosha-card.js +45 -43
  87. package/dist/components/dosha-card.js.map +2 -2
  88. package/dist/components/endpoint-form.d.ts +2 -0
  89. package/dist/components/endpoint-form.d.ts.map +1 -1
  90. package/dist/components/endpoint-form.js +71 -11
  91. package/dist/components/endpoint-form.js.map +3 -3
  92. package/dist/components/guna-milan.d.ts +2 -20
  93. package/dist/components/guna-milan.d.ts.map +1 -1
  94. package/dist/components/guna-milan.js +79 -20
  95. package/dist/components/guna-milan.js.map +4 -4
  96. package/dist/components/hexagram.d.ts +3 -27
  97. package/dist/components/hexagram.d.ts.map +1 -1
  98. package/dist/components/hexagram.js +48 -15
  99. package/dist/components/hexagram.js.map +2 -2
  100. package/dist/components/horoscope-card.d.ts +2 -20
  101. package/dist/components/horoscope-card.d.ts.map +1 -1
  102. package/dist/components/horoscope-card.js +54 -18
  103. package/dist/components/horoscope-card.js.map +3 -3
  104. package/dist/components/kp-planets-table.d.ts +2 -21
  105. package/dist/components/kp-planets-table.d.ts.map +1 -1
  106. package/dist/components/kp-planets-table.js +10 -4
  107. package/dist/components/kp-planets-table.js.map +3 -3
  108. package/dist/components/location-search.d.ts +5 -14
  109. package/dist/components/location-search.d.ts.map +1 -1
  110. package/dist/components/location-search.js +45 -5
  111. package/dist/components/location-search.js.map +2 -2
  112. package/dist/components/moon-phase.d.ts +4 -21
  113. package/dist/components/moon-phase.d.ts.map +1 -1
  114. package/dist/components/moon-phase.js +34 -4
  115. package/dist/components/moon-phase.js.map +3 -3
  116. package/dist/components/natal-chart.d.ts +9 -43
  117. package/dist/components/natal-chart.d.ts.map +1 -1
  118. package/dist/components/natal-chart.js +346 -79
  119. package/dist/components/natal-chart.js.map +3 -3
  120. package/dist/components/numerology-card.d.ts +5 -37
  121. package/dist/components/numerology-card.d.ts.map +1 -1
  122. package/dist/components/numerology-card.js +58 -30
  123. package/dist/components/numerology-card.js.map +3 -3
  124. package/dist/components/panchang-table.d.ts +3 -62
  125. package/dist/components/panchang-table.d.ts.map +1 -1
  126. package/dist/components/panchang-table.js +62 -32
  127. package/dist/components/panchang-table.js.map +3 -3
  128. package/dist/components/shadbala-table.d.ts +18 -0
  129. package/dist/components/shadbala-table.d.ts.map +1 -0
  130. package/dist/components/shadbala-table.js +400 -0
  131. package/dist/components/shadbala-table.js.map +7 -0
  132. package/dist/components/synastry-chart.d.ts +9 -28
  133. package/dist/components/synastry-chart.d.ts.map +1 -1
  134. package/dist/components/synastry-chart.js +201 -56
  135. package/dist/components/synastry-chart.js.map +3 -3
  136. package/dist/components/tarot-card.d.ts +5 -29
  137. package/dist/components/tarot-card.d.ts.map +1 -1
  138. package/dist/components/tarot-card.js +59 -20
  139. package/dist/components/tarot-card.js.map +2 -2
  140. package/dist/components/tarot-spread.d.ts +2 -24
  141. package/dist/components/tarot-spread.d.ts.map +1 -1
  142. package/dist/components/tarot-spread.js +39 -13
  143. package/dist/components/tarot-spread.js.map +2 -2
  144. package/dist/components/transits-table.d.ts +21 -0
  145. package/dist/components/transits-table.d.ts.map +1 -0
  146. package/dist/components/transits-table.js +515 -0
  147. package/dist/components/transits-table.js.map +7 -0
  148. package/dist/components/vedic-kundli.d.ts +5 -28
  149. package/dist/components/vedic-kundli.d.ts.map +1 -1
  150. package/dist/components/vedic-kundli.js +147 -83
  151. package/dist/components/vedic-kundli.js.map +3 -3
  152. package/dist/components/yoga-list.d.ts +29 -0
  153. package/dist/components/yoga-list.d.ts.map +1 -0
  154. package/dist/components/yoga-list.js +389 -0
  155. package/dist/components/yoga-list.js.map +7 -0
  156. package/dist/index.cjs +3693 -1180
  157. package/dist/index.cjs.map +4 -4
  158. package/dist/index.d.ts +11 -4
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +3709 -1196
  161. package/dist/index.js.map +4 -4
  162. package/dist/manifest.d.ts +43 -0
  163. package/dist/manifest.d.ts.map +1 -0
  164. package/dist/manifest.json +7 -2
  165. package/dist/styles/tokens.css +73 -1
  166. package/dist/tokens/index.d.ts +6 -0
  167. package/dist/tokens/index.d.ts.map +1 -1
  168. package/dist/types/index.d.ts +2 -0
  169. package/dist/types/index.d.ts.map +1 -0
  170. package/dist/types/types.gen.d.ts +27811 -0
  171. package/dist/types/types.gen.d.ts.map +1 -0
  172. package/dist/utils/debounce.d.ts +9 -1
  173. package/dist/utils/debounce.d.ts.map +1 -1
  174. package/dist/utils/format.d.ts +29 -0
  175. package/dist/utils/format.d.ts.map +1 -0
  176. package/dist/utils/kundli-render.d.ts +63 -0
  177. package/dist/utils/kundli-render.d.ts.map +1 -0
  178. package/dist/utils/string.d.ts +14 -0
  179. package/dist/utils/string.d.ts.map +1 -0
  180. package/dist/version.d.ts +2 -0
  181. package/dist/version.d.ts.map +1 -0
  182. package/package.json +7 -1
  183. package/src/components/ashtakavarga-grid.ts +354 -0
  184. package/src/components/biorhythm-chart.ts +39 -84
  185. package/src/components/choghadiya-grid.ts +185 -0
  186. package/src/components/compatibility-card.ts +85 -52
  187. package/src/components/dasha-timeline.ts +55 -73
  188. package/src/components/data.ts +28 -16
  189. package/src/components/divisional-chart.ts +214 -0
  190. package/src/components/dosha-card.ts +72 -68
  191. package/src/components/endpoint-form.ts +80 -18
  192. package/src/components/guna-milan.ts +87 -47
  193. package/src/components/hexagram.ts +53 -43
  194. package/src/components/horoscope-card.ts +59 -43
  195. package/src/components/kp-planets-table.ts +8 -27
  196. package/src/components/location-search.ts +47 -23
  197. package/src/components/moon-phase.ts +28 -25
  198. package/src/components/natal-chart.ts +364 -110
  199. package/src/components/numerology-card.ts +86 -84
  200. package/src/components/panchang-table.ts +40 -78
  201. package/src/components/shadbala-table.ts +286 -0
  202. package/src/components/synastry-chart.ts +213 -97
  203. package/src/components/tarot-card.ts +76 -62
  204. package/src/components/tarot-spread.ts +72 -45
  205. package/src/components/transits-table.ts +350 -0
  206. package/src/components/vedic-kundli.ts +59 -173
  207. package/src/components/yoga-list.ts +328 -0
  208. package/src/index.ts +18 -26
  209. package/src/manifest.ts +340 -0
  210. package/src/styles/tokens.css +73 -1
  211. package/src/tokens/index.ts +14 -0
  212. package/src/types/types.gen.ts +3 -3
  213. package/src/utils/debounce.ts +23 -4
  214. package/src/utils/format.ts +75 -0
  215. package/src/utils/kundli-render.ts +197 -0
  216. package/src/utils/string.ts +23 -0
  217. package/src/version.ts +2 -0
  218. package/dist/utils/motion.d.ts +0 -13
  219. package/dist/utils/motion.d.ts.map +0 -1
  220. package/src/utils/motion.ts +0 -18
@@ -1,32 +1,22 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ CastCelticCrossResponse,
5
+ CastLoveSpreadResponse,
6
+ CastReadingResponse,
7
+ CastThreeCardResponse,
8
+ CastYesNoResponse,
9
+ DrawCardsResponse,
10
+ } from '../types/index.js';
3
11
  import { baseStyles } from '../utils/base-styles.js';
4
12
 
5
- interface TarotPosition {
6
- number?: number;
7
- label?: string;
8
- name?: string;
9
- position?: string;
10
- card?: {
11
- name?: string;
12
- imageUrl?: string;
13
- reversed?: boolean;
14
- keywords?: string[];
15
- arcana?: string;
16
- };
17
- interpretation?: string;
18
- }
19
-
20
- interface TarotSpreadData {
21
- spread?: string;
22
- positions?: TarotPosition[];
23
- cards?: TarotPosition[];
24
- reading?: string;
25
- question?: string;
26
- answer?: 'Yes' | 'No' | 'Maybe' | string;
27
- strength?: string;
28
- interpretation?: string;
29
- }
13
+ type TarotSpreadData =
14
+ | CastThreeCardResponse
15
+ | CastCelticCrossResponse
16
+ | CastLoveSpreadResponse
17
+ | CastYesNoResponse
18
+ | CastReadingResponse
19
+ | DrawCardsResponse;
30
20
 
31
21
  /**
32
22
  * Tarot spread card. Renders /tarot/spreads/{three-card,celtic-cross,love},
@@ -72,15 +62,15 @@ export class RoxyTarotSpread extends LitElement {
72
62
  }
73
63
  .answer.yes {
74
64
  background: color-mix(in srgb, var(--roxy-success, #16a34a) 16%, transparent);
75
- color: var(--roxy-success, #16a34a);
65
+ color: var(--roxy-success-fg, #166534);
76
66
  }
77
67
  .answer.no {
78
68
  background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
79
- color: var(--roxy-danger, #dc2626);
69
+ color: var(--roxy-danger-fg, #991b1b);
80
70
  }
81
71
  .answer.maybe {
82
72
  background: color-mix(in srgb, var(--roxy-warning, #ea580c) 16%, transparent);
83
- color: var(--roxy-warning, #ea580c);
73
+ color: var(--roxy-warning-fg, #9a3412);
84
74
  }
85
75
 
86
76
  .grid {
@@ -157,22 +147,41 @@ export class RoxyTarotSpread extends LitElement {
157
147
  if (!d)
158
148
  return html`<div class="roxy-empty" role="status">No tarot spread</div>`;
159
149
 
160
- const positions = d.positions ?? d.cards ?? [];
161
- const isYesNo = !!d.answer;
162
- const answerClass = isYesNo
163
- ? (d.answer ?? '').toLowerCase().replace(/[^a-z]/g, '')
150
+ const isYesNo = 'answer' in d;
151
+ const isDrawn = 'cards' in d && !('spread' in d);
152
+ const positions = isDrawn
153
+ ? []
154
+ : 'positions' in d
155
+ ? (d.positions ?? [])
156
+ : [];
157
+ const cards = isDrawn && 'cards' in d ? (d as DrawCardsResponse).cards : [];
158
+ const answer = isYesNo ? (d as CastYesNoResponse).answer : undefined;
159
+ const strength = isYesNo ? (d as CastYesNoResponse).strength : undefined;
160
+ const spreadLabel =
161
+ 'spread' in d
162
+ ? (d as CastThreeCardResponse).spread
163
+ : this.spread.replace(/-/g, ' ');
164
+ const question =
165
+ 'question' in d ? (d as CastThreeCardResponse).question : undefined;
166
+ const summary =
167
+ 'summary' in d ? (d as CastThreeCardResponse).summary : undefined;
168
+ const yesNoInterp = isYesNo
169
+ ? (d as CastYesNoResponse).interpretation
170
+ : undefined;
171
+ const answerClass = answer
172
+ ? answer.toLowerCase().replace(/[^a-z]/g, '')
164
173
  : '';
165
174
 
166
175
  return html`<article class="wrap" aria-label="Tarot spread">
167
176
  <header class="head">
168
- <h2 class="title">${d.spread ?? this.spread.replace(/-/g, ' ')}</h2>
169
- ${d.question ? html`<span class="question">"${d.question}"</span>` : nothing}
177
+ <h2 class="title">${spreadLabel}</h2>
178
+ ${question ? html`<span class="question">"${question}"</span>` : nothing}
170
179
  </header>
171
180
  ${
172
181
  isYesNo
173
182
  ? html`<div>
174
- <span class=${`answer ${answerClass}`}>${d.answer}</span>
175
- ${d.strength ? html`<small> · ${d.strength}</small>` : nothing}
183
+ <span class=${`answer ${answerClass}`}>${answer}</span>
184
+ ${strength ? html`<small> · ${strength}</small>` : nothing}
176
185
  </div>`
177
186
  : nothing
178
187
  }
@@ -181,7 +190,7 @@ export class RoxyTarotSpread extends LitElement {
181
190
  ? html`<div class="grid">
182
191
  ${positions.map(
183
192
  (p) => html`<div class="card">
184
- <p class="label">${p.label ?? p.name ?? p.position ?? ''}</p>
193
+ <p class="label">${p.name ?? ''}</p>
185
194
  <div class="image">
186
195
  ${
187
196
  p.card?.imageUrl
@@ -197,22 +206,40 @@ export class RoxyTarotSpread extends LitElement {
197
206
  ${p.card?.name ?? ''}
198
207
  ${p.card?.reversed ? html`<small>(reversed)</small>` : nothing}
199
208
  </p>
200
- ${
201
- p.interpretation
202
- ? html`<p class="interp">${p.interpretation}</p>`
203
- : nothing
204
- }
209
+ ${p.interpretation ? html`<p class="interp">${p.interpretation}</p>` : nothing}
205
210
  </div>`,
206
211
  )}
207
212
  </div>`
208
213
  : nothing
209
214
  }
210
- ${d.reading ? html`<p class="reading">${d.reading}</p>` : nothing}
211
215
  ${
212
- d.interpretation && !d.reading
213
- ? html`<p class="reading">${d.interpretation}</p>`
216
+ cards.length > 0
217
+ ? html`<div class="grid">
218
+ ${cards.map(
219
+ (c) => html`<div class="card">
220
+ <div class="image">
221
+ ${
222
+ c.imageUrl
223
+ ? html`<img
224
+ src=${c.imageUrl}
225
+ alt=${c.name ?? 'tarot card'}
226
+ class=${c.reversed ? 'reversed' : ''}
227
+ />`
228
+ : html`${c.name ?? '?'}`
229
+ }
230
+ </div>
231
+ <p class="name">
232
+ ${c.name ?? ''}
233
+ ${c.reversed ? html`<small>(reversed)</small>` : nothing}
234
+ </p>
235
+ ${c.meaning ? html`<p class="interp">${c.meaning}</p>` : nothing}
236
+ </div>`,
237
+ )}
238
+ </div>`
214
239
  : nothing
215
240
  }
241
+ ${summary ? html`<p class="reading">${summary}</p>` : nothing}
242
+ ${yesNoInterp ? html`<p class="reading">${yesNoInterp}</p>` : nothing}
216
243
  </article>`;
217
244
  }
218
245
  }
@@ -0,0 +1,350 @@
1
+ import { css, html, LitElement, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { PLANET_GLYPH, SIGN_GLYPH } from '../tokens/index.js';
4
+ import type { TransitsResponse } from '../types/index.js';
5
+ import { baseStyles } from '../utils/base-styles.js';
6
+ import { formatDate, formatNumber, formatTime } from '../utils/format.js';
7
+ import { capitalize } from '../utils/string.js';
8
+
9
+ /**
10
+ * Transit positions and aspect table. Pass `data` from /astrology/transits.
11
+ * When natalChart is included in the request, `data.transitAspects` and
12
+ * `data.summary` are present and rendered automatically.
13
+ */
14
+ @customElement('roxy-transits-table')
15
+ export class RoxyTransitsTable extends LitElement {
16
+ static styles = [
17
+ baseStyles,
18
+ css`
19
+ .wrap {
20
+ display: grid;
21
+ gap: var(--roxy-space-md, 1rem);
22
+ }
23
+
24
+ .head {
25
+ display: flex;
26
+ justify-content: space-between;
27
+ align-items: baseline;
28
+ gap: var(--roxy-space-md, 1rem);
29
+ flex-wrap: wrap;
30
+ }
31
+
32
+ .title {
33
+ font-size: var(--roxy-text-lg, 1.125rem);
34
+ font-weight: var(--roxy-weight-bold, 600);
35
+ margin: 0;
36
+ }
37
+
38
+ .subtitle {
39
+ color: var(--roxy-muted, #71717a);
40
+ font-size: var(--roxy-text-sm, 0.875rem);
41
+ margin: 0;
42
+ }
43
+
44
+ .summary-pills {
45
+ display: flex;
46
+ flex-wrap: wrap;
47
+ gap: var(--roxy-space-sm, 0.5rem);
48
+ }
49
+
50
+ .pill {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: 4px;
54
+ padding: 2px var(--roxy-space-sm, 0.5rem);
55
+ border-radius: var(--roxy-radius-full, 9999px);
56
+ font-size: var(--roxy-text-xs, 0.75rem);
57
+ font-weight: var(--roxy-weight-bold, 600);
58
+ border: 1px solid currentColor;
59
+ }
60
+
61
+ .pill--muted {
62
+ color: var(--roxy-fg, #0a0a0a);
63
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent);
64
+ }
65
+
66
+ .pill--success {
67
+ color: var(--roxy-success-fg, #166534);
68
+ background: color-mix(in srgb, var(--roxy-success, #16a34a) 10%, transparent);
69
+ }
70
+
71
+ .pill--danger {
72
+ color: var(--roxy-danger-fg, #991b1b);
73
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 10%, transparent);
74
+ }
75
+
76
+ table {
77
+ width: 100%;
78
+ border-collapse: collapse;
79
+ font-size: var(--roxy-text-sm, 0.875rem);
80
+ }
81
+
82
+ th,
83
+ td {
84
+ padding: var(--roxy-space-sm, 0.5rem);
85
+ border-bottom: 1px solid var(--roxy-border, #e4e4e7);
86
+ text-align: left;
87
+ }
88
+
89
+ th {
90
+ color: var(--roxy-muted, #71717a);
91
+ font-weight: var(--roxy-weight-bold, 600);
92
+ text-transform: uppercase;
93
+ font-size: var(--roxy-text-xs, 0.75rem);
94
+ letter-spacing: 0.06em;
95
+ }
96
+
97
+ .section-label {
98
+ font-size: var(--roxy-text-xs, 0.75rem);
99
+ color: var(--roxy-muted, #71717a);
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.06em;
102
+ font-weight: var(--roxy-weight-bold, 600);
103
+ margin: 0 0 var(--roxy-space-xs, 0.25rem) 0;
104
+ }
105
+
106
+ .glyph {
107
+ font-size: 1.1em;
108
+ margin-right: 2px;
109
+ line-height: 1;
110
+ }
111
+
112
+ .planet-cell {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 4px;
116
+ white-space: nowrap;
117
+ }
118
+
119
+ .retro-badge {
120
+ display: inline-block;
121
+ font-size: 0.7em;
122
+ padding: 1px 4px;
123
+ border-radius: var(--roxy-radius-sm, 4px);
124
+ background: color-mix(in srgb, var(--roxy-warning, #ea580c) 12%, transparent);
125
+ color: var(--roxy-warning-fg, #9a3412);
126
+ font-weight: var(--roxy-weight-bold, 600);
127
+ margin-left: 2px;
128
+ vertical-align: middle;
129
+ }
130
+
131
+ .speed {
132
+ font-variant-numeric: tabular-nums;
133
+ color: var(--roxy-muted, #71717a);
134
+ white-space: nowrap;
135
+ }
136
+
137
+ .speed-arrow {
138
+ font-size: 0.85em;
139
+ }
140
+
141
+ td.num {
142
+ font-variant-numeric: tabular-nums;
143
+ color: var(--roxy-muted, #71717a);
144
+ }
145
+
146
+ .nature-harmonious {
147
+ color: var(--roxy-success-fg, #166534);
148
+ }
149
+
150
+ .nature-challenging {
151
+ color: var(--roxy-danger-fg, #991b1b);
152
+ }
153
+
154
+ .nature-neutral {
155
+ color: var(--roxy-muted, #71717a);
156
+ }
157
+
158
+ .arrow-cell {
159
+ display: inline-flex;
160
+ align-items: center;
161
+ gap: 4px;
162
+ white-space: nowrap;
163
+ }
164
+
165
+ .interp {
166
+ color: var(--roxy-secondary, #475569);
167
+ font-size: var(--roxy-text-xs, 0.75rem);
168
+ max-width: 22rem;
169
+ white-space: nowrap;
170
+ overflow: hidden;
171
+ text-overflow: ellipsis;
172
+ }
173
+
174
+ @container (max-width: 600px) {
175
+ .interp {
176
+ display: none;
177
+ }
178
+ }
179
+
180
+ .overflow-scroll {
181
+ overflow-x: auto;
182
+ -webkit-overflow-scrolling: touch;
183
+ }
184
+ `,
185
+ ];
186
+
187
+ @property({ attribute: false })
188
+ data: TransitsResponse | null = null;
189
+
190
+ render() {
191
+ if (!this.data?.transitPlanets?.length) {
192
+ return html`<div class="roxy-empty" role="status">No transits data</div>`;
193
+ }
194
+
195
+ const {
196
+ transitDate,
197
+ transitTime,
198
+ transitPlanets,
199
+ transitAspects,
200
+ summary,
201
+ } = this.data;
202
+
203
+ const dateStr = [formatDate(transitDate), formatTime(transitTime)]
204
+ .filter(Boolean)
205
+ .join(' ');
206
+
207
+ return html`<div class="wrap" aria-label="Transit positions table">
208
+ <div class="head">
209
+ <h2 class="title">Transits</h2>
210
+ ${dateStr ? html`<p class="subtitle">${dateStr}</p>` : nothing}
211
+ </div>
212
+
213
+ ${summary ? this.renderSummaryPills(summary) : nothing}
214
+
215
+ <div>
216
+ <p class="section-label">Planet positions</p>
217
+ <div class="overflow-scroll">
218
+ ${this.renderPlanetsTable(transitPlanets)}
219
+ </div>
220
+ </div>
221
+
222
+ ${
223
+ transitAspects?.length
224
+ ? html`<div>
225
+ <p class="section-label">Transit aspects</p>
226
+ <div class="overflow-scroll">
227
+ ${this.renderAspectsTable(transitAspects)}
228
+ </div>
229
+ </div>`
230
+ : nothing
231
+ }
232
+ </div>`;
233
+ }
234
+
235
+ private renderSummaryPills(
236
+ summary: NonNullable<TransitsResponse['summary']>,
237
+ ) {
238
+ return html`<div class="summary-pills" role="region" aria-label="Aspect summary">
239
+ <span class="pill pill--muted">
240
+ Total: ${summary.totalAspects}
241
+ </span>
242
+ <span class="pill pill--success">
243
+ Harmonious: ${summary.harmonious}
244
+ </span>
245
+ <span class="pill pill--danger">
246
+ Challenging: ${summary.challenging}
247
+ </span>
248
+ <span class="pill pill--muted">
249
+ Neutral: ${summary.neutral}
250
+ </span>
251
+ </div>`;
252
+ }
253
+
254
+ private renderPlanetsTable(planets: TransitsResponse['transitPlanets']) {
255
+ return html`<table class="planets-table">
256
+ <thead>
257
+ <tr>
258
+ <th scope="col">Planet</th>
259
+ <th scope="col">Sign</th>
260
+ <th scope="col">Degree</th>
261
+ <th scope="col">Speed</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody>
265
+ ${planets.map((p) => {
266
+ const pGlyph = PLANET_GLYPH[capitalize(p.name)] ?? '';
267
+ const sGlyph = SIGN_GLYPH[capitalize(p.sign)] ?? '';
268
+ const speedArrow = p.speed >= 0 ? '↑' : '↓';
269
+ return html`<tr>
270
+ <td>
271
+ <div class="planet-cell">
272
+ <span class="glyph" aria-hidden="true">${pGlyph}</span>
273
+ ${p.name}
274
+ ${
275
+ p.isRetrograde
276
+ ? html`<span class="retro-badge" aria-label="retrograde">R</span>`
277
+ : nothing
278
+ }
279
+ </div>
280
+ </td>
281
+ <td>
282
+ <div class="planet-cell">
283
+ <span class="glyph" aria-hidden="true">${sGlyph}</span>
284
+ ${p.sign}
285
+ </div>
286
+ </td>
287
+ <td class="num">${formatNumber(p.degree, 2)}</td>
288
+ <td class="speed">
289
+ <span class="speed-arrow" aria-hidden="true">${speedArrow}</span>
290
+ ${formatNumber(Math.abs(p.speed), 4)}
291
+ </td>
292
+ </tr>`;
293
+ })}
294
+ </tbody>
295
+ </table>`;
296
+ }
297
+
298
+ private renderAspectsTable(
299
+ aspects: NonNullable<TransitsResponse['transitAspects']>,
300
+ ) {
301
+ return html`<table class="aspects-table">
302
+ <thead>
303
+ <tr>
304
+ <th scope="col">Transit Planet</th>
305
+ <th scope="col">Natal Planet</th>
306
+ <th scope="col">Type</th>
307
+ <th scope="col">Orb</th>
308
+ <th scope="col">Status</th>
309
+ <th scope="col">Strength</th>
310
+ <th scope="col" class="interp">Interpretation</th>
311
+ </tr>
312
+ </thead>
313
+ <tbody>
314
+ ${aspects.map((a) => {
315
+ const tGlyph = PLANET_GLYPH[capitalize(a.transitPlanet)] ?? '';
316
+ const nGlyph = PLANET_GLYPH[capitalize(a.natalPlanet)] ?? '';
317
+ const natureClass = `nature-${(a.nature ?? '').toLowerCase()}`;
318
+ const summary = a.interpretation?.summary ?? '';
319
+ const truncated =
320
+ summary.length > 120 ? `${summary.slice(0, 120)}...` : summary;
321
+ return html`<tr>
322
+ <td>
323
+ <div class="arrow-cell">
324
+ <span class="glyph" aria-hidden="true">${tGlyph}</span>
325
+ ${a.transitPlanet}
326
+ </div>
327
+ </td>
328
+ <td>
329
+ <div class="arrow-cell">
330
+ <span class="glyph" aria-hidden="true">${nGlyph}</span>
331
+ ${a.natalPlanet}
332
+ </div>
333
+ </td>
334
+ <td class=${natureClass}>${(a.type ?? '').toLowerCase()}</td>
335
+ <td class="num">${formatNumber(a.orb, 2)}</td>
336
+ <td>${a.isApplying ? 'Applying' : 'Separating'}</td>
337
+ <td class="num">${formatNumber(a.strength, 1)}</td>
338
+ <td class="interp" title=${summary}>${truncated}</td>
339
+ </tr>`;
340
+ })}
341
+ </tbody>
342
+ </table>`;
343
+ }
344
+ }
345
+
346
+ declare global {
347
+ interface HTMLElementTagNameMap {
348
+ 'roxy-transits-table': RoxyTransitsTable;
349
+ }
350
+ }