@roxyapi/ui 0.1.1 → 0.1.3

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 (169) hide show
  1. package/AGENTS.md +2 -2
  2. package/LICENSE +21 -0
  3. package/README.md +505 -0
  4. package/THEMING.md +24 -7
  5. package/dist/cdn/components/biorhythm-chart.js +15 -22
  6. package/dist/cdn/components/biorhythm-chart.js.map +3 -3
  7. package/dist/cdn/components/compatibility-card.js +36 -34
  8. package/dist/cdn/components/compatibility-card.js.map +4 -4
  9. package/dist/cdn/components/dasha-timeline.js +35 -39
  10. package/dist/cdn/components/dasha-timeline.js.map +4 -4
  11. package/dist/cdn/components/data.js +6 -6
  12. package/dist/cdn/components/data.js.map +3 -3
  13. package/dist/cdn/components/dosha-card.js +13 -13
  14. package/dist/cdn/components/dosha-card.js.map +2 -2
  15. package/dist/cdn/components/endpoint-form.js +47 -28
  16. package/dist/cdn/components/endpoint-form.js.map +3 -3
  17. package/dist/cdn/components/guna-milan.js +18 -18
  18. package/dist/cdn/components/guna-milan.js.map +4 -4
  19. package/dist/cdn/components/hexagram.js +26 -26
  20. package/dist/cdn/components/hexagram.js.map +3 -3
  21. package/dist/cdn/components/horoscope-card.js +38 -38
  22. package/dist/cdn/components/horoscope-card.js.map +3 -3
  23. package/dist/cdn/components/kp-planets-table.js +10 -10
  24. package/dist/cdn/components/kp-planets-table.js.map +4 -4
  25. package/dist/cdn/components/location-search.js +6 -6
  26. package/dist/cdn/components/location-search.js.map +3 -3
  27. package/dist/cdn/components/moon-phase.js +21 -21
  28. package/dist/cdn/components/moon-phase.js.map +4 -4
  29. package/dist/cdn/components/natal-chart.js +61 -19
  30. package/dist/cdn/components/natal-chart.js.map +4 -4
  31. package/dist/cdn/components/numerology-card.js +40 -31
  32. package/dist/cdn/components/numerology-card.js.map +3 -3
  33. package/dist/cdn/components/panchang-table.js +25 -25
  34. package/dist/cdn/components/panchang-table.js.map +4 -4
  35. package/dist/cdn/components/synastry-chart.js +129 -39
  36. package/dist/cdn/components/synastry-chart.js.map +4 -4
  37. package/dist/cdn/components/tarot-card.js +49 -20
  38. package/dist/cdn/components/tarot-card.js.map +3 -3
  39. package/dist/cdn/components/tarot-spread.js +43 -27
  40. package/dist/cdn/components/tarot-spread.js.map +3 -3
  41. package/dist/cdn/components/vedic-kundli.js +23 -9
  42. package/dist/cdn/components/vedic-kundli.js.map +3 -3
  43. package/dist/cdn/roxy-ui.js +560 -350
  44. package/dist/cdn/roxy-ui.js.map +4 -4
  45. package/dist/components/biorhythm-chart.d.ts +2 -46
  46. package/dist/components/biorhythm-chart.d.ts.map +1 -1
  47. package/dist/components/biorhythm-chart.js +24 -23
  48. package/dist/components/biorhythm-chart.js.map +2 -2
  49. package/dist/components/compatibility-card.d.ts +2 -27
  50. package/dist/components/compatibility-card.d.ts.map +1 -1
  51. package/dist/components/compatibility-card.js +50 -29
  52. package/dist/components/compatibility-card.js.map +3 -3
  53. package/dist/components/dasha-timeline.d.ts +2 -31
  54. package/dist/components/dasha-timeline.d.ts.map +1 -1
  55. package/dist/components/dasha-timeline.js +32 -30
  56. package/dist/components/dasha-timeline.js.map +3 -3
  57. package/dist/components/data.d.ts +6 -0
  58. package/dist/components/data.d.ts.map +1 -1
  59. package/dist/components/data.js +9 -1
  60. package/dist/components/data.js.map +2 -2
  61. package/dist/components/dosha-card.d.ts +2 -16
  62. package/dist/components/dosha-card.d.ts.map +1 -1
  63. package/dist/components/dosha-card.js +12 -13
  64. package/dist/components/dosha-card.js.map +2 -2
  65. package/dist/components/endpoint-form.d.ts +2 -0
  66. package/dist/components/endpoint-form.d.ts.map +1 -1
  67. package/dist/components/endpoint-form.js +66 -8
  68. package/dist/components/endpoint-form.js.map +2 -2
  69. package/dist/components/guna-milan.d.ts +2 -20
  70. package/dist/components/guna-milan.d.ts.map +1 -1
  71. package/dist/components/guna-milan.js +22 -12
  72. package/dist/components/guna-milan.js.map +3 -3
  73. package/dist/components/hexagram.d.ts +3 -27
  74. package/dist/components/hexagram.d.ts.map +1 -1
  75. package/dist/components/hexagram.js +31 -15
  76. package/dist/components/hexagram.js.map +2 -2
  77. package/dist/components/horoscope-card.d.ts +2 -20
  78. package/dist/components/horoscope-card.d.ts.map +1 -1
  79. package/dist/components/horoscope-card.js +24 -15
  80. package/dist/components/horoscope-card.js.map +2 -2
  81. package/dist/components/kp-planets-table.d.ts +2 -21
  82. package/dist/components/kp-planets-table.d.ts.map +1 -1
  83. package/dist/components/kp-planets-table.js +10 -4
  84. package/dist/components/kp-planets-table.js.map +3 -3
  85. package/dist/components/location-search.d.ts +3 -11
  86. package/dist/components/location-search.d.ts.map +1 -1
  87. package/dist/components/location-search.js +45 -5
  88. package/dist/components/location-search.js.map +2 -2
  89. package/dist/components/moon-phase.d.ts +4 -21
  90. package/dist/components/moon-phase.d.ts.map +1 -1
  91. package/dist/components/moon-phase.js +17 -4
  92. package/dist/components/moon-phase.js.map +3 -3
  93. package/dist/components/natal-chart.d.ts +7 -43
  94. package/dist/components/natal-chart.d.ts.map +1 -1
  95. package/dist/components/natal-chart.js +130 -70
  96. package/dist/components/natal-chart.js.map +3 -3
  97. package/dist/components/numerology-card.d.ts +5 -37
  98. package/dist/components/numerology-card.d.ts.map +1 -1
  99. package/dist/components/numerology-card.js +54 -28
  100. package/dist/components/numerology-card.js.map +2 -2
  101. package/dist/components/panchang-table.d.ts +3 -62
  102. package/dist/components/panchang-table.d.ts.map +1 -1
  103. package/dist/components/panchang-table.js +62 -32
  104. package/dist/components/panchang-table.js.map +3 -3
  105. package/dist/components/synastry-chart.d.ts +9 -28
  106. package/dist/components/synastry-chart.d.ts.map +1 -1
  107. package/dist/components/synastry-chart.js +178 -38
  108. package/dist/components/synastry-chart.js.map +3 -3
  109. package/dist/components/tarot-card.d.ts +5 -29
  110. package/dist/components/tarot-card.d.ts.map +1 -1
  111. package/dist/components/tarot-card.js +59 -20
  112. package/dist/components/tarot-card.js.map +2 -2
  113. package/dist/components/tarot-spread.d.ts +2 -24
  114. package/dist/components/tarot-spread.d.ts.map +1 -1
  115. package/dist/components/tarot-spread.js +39 -13
  116. package/dist/components/tarot-spread.js.map +2 -2
  117. package/dist/components/vedic-kundli.d.ts +3 -23
  118. package/dist/components/vedic-kundli.d.ts.map +1 -1
  119. package/dist/components/vedic-kundli.js +25 -13
  120. package/dist/components/vedic-kundli.js.map +2 -2
  121. package/dist/index.cjs +1149 -358
  122. package/dist/index.cjs.map +4 -4
  123. package/dist/index.d.ts +6 -4
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +1149 -358
  126. package/dist/index.js.map +4 -4
  127. package/dist/manifest.d.ts +49 -0
  128. package/dist/manifest.d.ts.map +1 -0
  129. package/dist/manifest.json +1 -1
  130. package/dist/styles/tokens.css +47 -1
  131. package/dist/tokens/index.d.ts.map +1 -1
  132. package/dist/types/index.d.ts +2 -0
  133. package/dist/types/index.d.ts.map +1 -0
  134. package/dist/types/types.gen.d.ts +27811 -0
  135. package/dist/types/types.gen.d.ts.map +1 -0
  136. package/dist/utils/debounce.d.ts +9 -1
  137. package/dist/utils/debounce.d.ts.map +1 -1
  138. package/dist/utils/format.d.ts +15 -0
  139. package/dist/utils/format.d.ts.map +1 -0
  140. package/dist/version.d.ts +2 -0
  141. package/dist/version.d.ts.map +1 -0
  142. package/package.json +9 -1
  143. package/src/components/biorhythm-chart.ts +39 -84
  144. package/src/components/compatibility-card.ts +85 -52
  145. package/src/components/dasha-timeline.ts +55 -73
  146. package/src/components/data.ts +20 -1
  147. package/src/components/dosha-card.ts +18 -31
  148. package/src/components/endpoint-form.ts +79 -11
  149. package/src/components/guna-milan.ts +16 -34
  150. package/src/components/hexagram.ts +53 -43
  151. package/src/components/horoscope-card.ts +51 -39
  152. package/src/components/kp-planets-table.ts +8 -27
  153. package/src/components/location-search.ts +45 -20
  154. package/src/components/moon-phase.ts +28 -25
  155. package/src/components/natal-chart.ts +129 -84
  156. package/src/components/numerology-card.ts +87 -79
  157. package/src/components/panchang-table.ts +40 -78
  158. package/src/components/synastry-chart.ts +220 -78
  159. package/src/components/tarot-card.ts +76 -62
  160. package/src/components/tarot-spread.ts +72 -45
  161. package/src/components/vedic-kundli.ts +42 -51
  162. package/src/index.ts +14 -24
  163. package/src/manifest.ts +366 -0
  164. package/src/styles/tokens.css +47 -1
  165. package/src/tokens/index.ts +5 -0
  166. package/src/types/types.gen.ts +1 -1
  167. package/src/utils/debounce.ts +23 -4
  168. package/src/utils/format.ts +57 -0
  169. package/src/version.ts +2 -0
@@ -1,34 +1,9 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property, state } from 'lit/decorators.js';
3
+ import type { GetCardResponse, GetDailyCardResponse } from '../types/index.js';
3
4
  import { baseStyles } from '../utils/base-styles.js';
4
5
 
5
- interface TarotCard {
6
- id?: string;
7
- name?: string;
8
- arcana?: 'major' | 'minor' | string;
9
- number?: number | string;
10
- position?: string;
11
- reversed?: boolean;
12
- keywords?: string[];
13
- meaning?:
14
- | string
15
- | {
16
- upright?: string;
17
- reversed?: string;
18
- spiritual?: string;
19
- emotional?: string;
20
- physical?: string;
21
- };
22
- imageUrl?: string;
23
- upright?: { meaning?: string; keywords?: string[] };
24
- }
25
-
26
- interface TarotData {
27
- date?: string;
28
- seed?: string;
29
- card?: TarotCard;
30
- dailyMessage?: string;
31
- }
6
+ type TarotData = GetCardResponse | GetDailyCardResponse;
32
7
 
33
8
  /**
34
9
  * Tarot card. Renders /tarot/cards/{id} or /tarot/daily. Click to flip
@@ -92,11 +67,6 @@ export class RoxyTarotCard extends LitElement {
92
67
  letter-spacing: 0.06em;
93
68
  margin-bottom: var(--roxy-space-sm, 0.5rem);
94
69
  }
95
- .position {
96
- color: var(--roxy-info, #0284c7);
97
- margin-left: var(--roxy-space-xs, 0.25rem);
98
- text-transform: capitalize;
99
- }
100
70
 
101
71
  .message {
102
72
  color: var(--roxy-fg, #0a0a0a);
@@ -137,37 +107,28 @@ export class RoxyTarotCard extends LitElement {
137
107
  ];
138
108
 
139
109
  @property({ attribute: false })
140
- data: TarotData | TarotCard | null = null;
110
+ data: TarotData | null = null;
141
111
 
142
112
  @state()
143
113
  private flipped = false;
144
114
 
145
- private getCard(): TarotCard | null {
146
- if (!this.data) return null;
147
- if ('card' in this.data && this.data.card) return this.data.card;
148
- return this.data as TarotCard;
149
- }
150
-
151
115
  private toggleFlip = () => {
152
116
  this.flipped = !this.flipped;
153
117
  };
154
118
 
155
119
  render() {
156
- const card = this.getCard();
157
- if (!card)
120
+ const d = this.data;
121
+ if (!d)
158
122
  return html`<div class="roxy-empty" role="status">No tarot data</div>`;
159
123
 
160
- const isReversed = this.flipped !== Boolean(card.reversed); // start at server-provided orientation, toggle on click
161
- const meaning =
162
- typeof card.meaning === 'string'
163
- ? card.meaning
164
- : ((isReversed ? card.meaning?.reversed : card.meaning?.upright) ??
165
- card.meaning?.spiritual ??
166
- card.upright?.meaning);
167
- const dailyMessage =
168
- this.data && 'dailyMessage' in this.data
169
- ? this.data.dailyMessage
170
- : undefined;
124
+ if ('card' in d) return this.renderDailyCard(d);
125
+ return this.renderFullCard(d);
126
+ }
127
+
128
+ private renderDailyCard(d: GetDailyCardResponse) {
129
+ const card = d.card;
130
+ const isReversed = this.flipped !== Boolean(card.reversed);
131
+ const keywords = card.keywords ?? [];
171
132
 
172
133
  return html`<article class="card" aria-label=${card.name ?? 'Tarot card'}>
173
134
  <div class="image-wrap">
@@ -197,21 +158,74 @@ export class RoxyTarotCard extends LitElement {
197
158
  <div>
198
159
  <div class="meta">
199
160
  ${card.arcana ? html`${card.arcana} arcana` : nothing}
200
- ${card.number !== undefined && card.number !== null ? html` · ${card.number}` : nothing}
201
161
  ${isReversed ? html` · reversed` : nothing}
202
- ${
203
- card.position
204
- ? html`<span class="position">${card.position}</span>`
205
- : nothing
206
- }
207
162
  </div>
208
163
  <h2 class="title">${card.name ?? 'Tarot card'}</h2>
209
- ${dailyMessage ? html`<p class="message">${dailyMessage}</p>` : nothing}
210
- ${meaning ? html`<p>${meaning}</p>` : nothing}
164
+ ${d.dailyMessage ? html`<p class="message">${d.dailyMessage}</p>` : nothing}
165
+ ${card.meaning ? html`<p>${card.meaning}</p>` : nothing}
166
+ ${
167
+ keywords.length > 0
168
+ ? html`<div class="chips">
169
+ ${keywords.map((k) => html`<span>${k}</span>`)}
170
+ </div>`
171
+ : nothing
172
+ }
173
+ <button
174
+ class="flip"
175
+ type="button"
176
+ @click=${this.toggleFlip}
177
+ aria-pressed=${this.flipped ? 'true' : 'false'}
178
+ >
179
+ Flip card
180
+ </button>
181
+ </div>
182
+ </article>`;
183
+ }
184
+
185
+ private renderFullCard(d: GetCardResponse) {
186
+ const isReversed = this.flipped;
187
+ const orientedMeaning = isReversed ? d.reversed : d.upright;
188
+ const keywords = isReversed
189
+ ? (d.keywords?.reversed ?? [])
190
+ : (d.keywords?.upright ?? []);
191
+
192
+ return html`<article class="card" aria-label=${d.name ?? 'Tarot card'}>
193
+ <div class="image-wrap">
194
+ ${
195
+ d.imageUrl
196
+ ? html`<img
197
+ class=${`image ${isReversed ? 'reversed' : ''}`}
198
+ src=${d.imageUrl}
199
+ alt=${d.name ?? 'Tarot card'}
200
+ tabindex="0"
201
+ @click=${this.toggleFlip}
202
+ @keydown=${(e: KeyboardEvent) => {
203
+ if (e.key === 'Enter' || e.key === ' ') {
204
+ e.preventDefault();
205
+ this.toggleFlip();
206
+ }
207
+ }}
208
+ />`
209
+ : html`<div
210
+ class=${`image ${isReversed ? 'reversed' : ''}`}
211
+ style="aspect-ratio: 0.6; display: flex; align-items: center; justify-content: center; color: var(--roxy-muted)"
212
+ >
213
+ ${d.name ?? '?'}
214
+ </div>`
215
+ }
216
+ </div>
217
+ <div>
218
+ <div class="meta">
219
+ ${d.arcana ? html`${d.arcana} arcana` : nothing}
220
+ ${d.number !== undefined && d.number !== null ? html` · ${d.number}` : nothing}
221
+ ${isReversed ? html` · reversed` : nothing}
222
+ </div>
223
+ <h2 class="title">${d.name ?? 'Tarot card'}</h2>
224
+ ${orientedMeaning?.description ? html`<p>${orientedMeaning.description}</p>` : nothing}
211
225
  ${
212
- card.keywords?.length
226
+ keywords.length > 0
213
227
  ? html`<div class="chips">
214
- ${card.keywords.map((k) => html`<span>${k}</span>`)}
228
+ ${keywords.map((k) => html`<span>${k}</span>`)}
215
229
  </div>`
216
230
  : nothing
217
231
  }
@@ -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
  }
@@ -1,26 +1,16 @@
1
1
  import { css, html, LitElement, nothing, svg } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import { PLANET_ABBR, SIGN_ABBR } from '../tokens/index.js';
4
+ import type { BirthChartResponse } from '../types/index.js';
4
5
  import { baseStyles } from '../utils/base-styles.js';
5
6
 
6
- interface KundliMeta {
7
- [planet: string]: {
8
- graha?: string;
9
- rashi?: string;
10
- longitude?: number;
11
- nakshatra?: string;
12
- isRetrograde?: boolean;
13
- };
14
- }
7
+ type RashiBucket = BirthChartResponse['aries'];
15
8
 
16
- interface RashiBucket {
17
- rashi?: string;
18
- signs?: Array<{
19
- planet?: string;
20
- longitude?: number;
21
- isRetrograde?: boolean;
22
- }>;
23
- }
9
+ // TODO(spec): BirthChartResponse types only `aries` and `meta`, but the API
10
+ // returns all 12 rashi keys (taurus, gemini, ..., pisces) with the same shape
11
+ // as `aries`. Update the OpenAPI schema so the renderer can index by rashi
12
+ // without casts.
13
+ type BirthChartByRashi = BirthChartResponse & Record<string, RashiBucket>;
24
14
 
25
15
  interface KundliHouse {
26
16
  house: number;
@@ -28,19 +18,6 @@ interface KundliHouse {
28
18
  planets: string[];
29
19
  }
30
20
 
31
- interface KundliData {
32
- meta?: KundliMeta;
33
- houses?: Array<{
34
- house?: number;
35
- number?: number;
36
- sign?: string;
37
- planets?: string[];
38
- }>;
39
- combustion?: unknown[];
40
- planetaryWar?: unknown[];
41
- [rashi: string]: unknown;
42
- }
43
-
44
21
  const SOUTH_HOUSE_CENTERS: Record<number, { x: number; y: number }> = {
45
22
  1: { x: 150, y: 58 },
46
23
  2: { x: 205, y: 52 },
@@ -145,39 +122,35 @@ export class RoxyVedicKundli extends LitElement {
145
122
  font-weight: 600;
146
123
  font-family: var(--roxy-font-sans);
147
124
  }
125
+ .lagna-marker {
126
+ fill: var(--roxy-accent-fg, #b45309);
127
+ font-size: 8px;
128
+ font-weight: 700;
129
+ font-family: var(--roxy-font-sans);
130
+ letter-spacing: 0.05em;
131
+ }
132
+ .lagna-bg {
133
+ fill: color-mix(in srgb, var(--roxy-accent, #f59e0b) 12%, transparent);
134
+ stroke: color-mix(in srgb, var(--roxy-accent, #f59e0b) 45%, transparent);
135
+ stroke-width: 0.8;
136
+ }
148
137
  `,
149
138
  ];
150
139
 
151
140
  @property({ attribute: false })
152
- data: KundliData | null = null;
141
+ data: BirthChartResponse | null = null;
153
142
 
154
143
  @property({ type: String, reflect: true, attribute: 'chart-style' })
155
144
  chartStyle: 'south' | 'north' = 'south';
156
145
 
157
146
  private buildHouses(): KundliHouse[] {
158
147
  if (!this.data) return [];
148
+ const data = this.data as BirthChartByRashi;
159
149
  const houses: KundliHouse[] = [];
160
- // Prefer normalized .houses array if present
161
- if (Array.isArray(this.data.houses)) {
162
- for (const h of this.data.houses) {
163
- houses.push({
164
- house: (h.house ?? h.number ?? houses.length + 1) as number,
165
- sign: h.sign ?? '',
166
- planets: h.planets ?? [],
167
- });
168
- }
169
- if (houses.length > 0) return houses;
170
- }
171
- // Otherwise read the rashi buckets and project them as houses 1..12
172
- // keyed by sign order. Lagna-anchored ordering would require knowing
173
- // the ascendant rashi; we render rashi buckets directly which is the
174
- // canonical South Indian layout.
175
150
  for (let i = 0; i < 12; i++) {
176
151
  const key = RASHI_KEYS[i];
177
- const bucket = this.data[key] as RashiBucket | undefined;
178
- const planets = (bucket?.signs ?? [])
179
- .map((p) => p.planet ?? '')
180
- .filter(Boolean);
152
+ const bucket = data[key];
153
+ const planets = (bucket?.signs ?? []).map((p) => p.graha).filter(Boolean);
181
154
  houses.push({
182
155
  house: i + 1,
183
156
  sign: RASHI_TO_SIGN[key] ?? '',
@@ -220,23 +193,41 @@ export class RoxyVedicKundli extends LitElement {
220
193
  </div>`;
221
194
  }
222
195
 
196
+ private isLagna(h: KundliHouse): boolean {
197
+ const ascSign = this.data?.meta?.Lagna?.rashi;
198
+ if (!ascSign) return false;
199
+ return ascSign.toLowerCase() === h.sign.toLowerCase();
200
+ }
201
+
223
202
  private renderHouseGroup(h: KundliHouse) {
224
203
  const center = SOUTH_HOUSE_CENTERS[h.house];
225
204
  const signPos = SOUTH_SIGN_POSITIONS[h.house];
226
205
  if (!center || !signPos) return nothing;
227
206
  const signAbbr = SIGN_ABBR[h.sign] ?? '';
228
207
  const planets = h.planets ?? [];
208
+ const isLagna = this.isLagna(h);
229
209
  return svg`
230
210
  <g>
211
+ ${
212
+ isLagna
213
+ ? svg`<rect class="lagna-bg" x=${center.x - 30} y=${center.y - 28} width="60" height="56" rx="6" />`
214
+ : nothing
215
+ }
231
216
  ${
232
217
  signAbbr
233
218
  ? svg`<text class="sign-text" x=${signPos.x} y=${signPos.y} text-anchor="middle" dominant-baseline="central">${signAbbr}</text>`
234
219
  : nothing
235
220
  }
221
+ ${
222
+ isLagna
223
+ ? svg`<text class="lagna-marker" x=${center.x} y=${center.y - 18} text-anchor="middle" dominant-baseline="central">LAGNA</text>`
224
+ : nothing
225
+ }
236
226
  ${planets.map((planet, j) => {
237
227
  const abbr = PLANET_ABBR[capitalize(planet)] ?? planet.slice(0, 2);
238
228
  const lineHeight = 13;
239
- const startY = center.y - ((planets.length - 1) * lineHeight) / 2;
229
+ const baseY = isLagna ? center.y + 8 : center.y;
230
+ const startY = baseY - ((planets.length - 1) * lineHeight) / 2;
240
231
  const yPos = startY + j * lineHeight;
241
232
  return svg`<text class="planet-text" x=${center.x} y=${yPos} text-anchor="middle" dominant-baseline="central">${abbr}</text>`;
242
233
  })}
package/src/index.ts CHANGED
@@ -33,29 +33,19 @@ export { RoxyTarotSpread } from './components/tarot-spread.js';
33
33
  // Vedic
34
34
  export { RoxyVedicKundli } from './components/vedic-kundli.js';
35
35
 
36
- export const ROXY_UI_VERSION = '0.1.0';
36
+ import { ROXY_COMPONENTS, type RoxyComponentSlug } from './manifest.js';
37
37
 
38
- /** Component manifest used by the widgets auto-mount script and registry build. */
39
- export const ROXY_UI_COMPONENTS = [
40
- 'natal-chart',
41
- 'horoscope-card',
42
- 'synastry-chart',
43
- 'compatibility-card',
44
- 'moon-phase',
45
- 'vedic-kundli',
46
- 'panchang-table',
47
- 'dasha-timeline',
48
- 'dosha-card',
49
- 'guna-milan',
50
- 'kp-planets-table',
51
- 'numerology-card',
52
- 'tarot-card',
53
- 'tarot-spread',
54
- 'biorhythm-chart',
55
- 'hexagram',
56
- 'endpoint-form',
57
- 'location-search',
58
- 'data',
59
- ] as const;
38
+ export {
39
+ ROXY_COMPONENTS,
40
+ type RoxyComponent,
41
+ type RoxyComponentSlug,
42
+ type RoxyComponentTag,
43
+ type RoxyDomain,
44
+ } from './manifest.js';
45
+ export { ROXY_UI_VERSION } from './version.js';
60
46
 
61
- export type RoxyUIComponentName = (typeof ROXY_UI_COMPONENTS)[number];
47
+ /** Slugs in declaration order. Kept for the auto-mount widgets script and downstream codegen. */
48
+ export const ROXY_UI_COMPONENTS: readonly RoxyComponentSlug[] =
49
+ ROXY_COMPONENTS.map((c) => c.slug) as RoxyComponentSlug[];
50
+
51
+ export type RoxyUIComponentName = RoxyComponentSlug;