@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,53 +1,27 @@
1
1
  import { css, html, LitElement, nothing, svg } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
- import { PLANET_GLYPH, SIGN_GLYPH } from '../tokens/index.js';
3
+ import { PLANET_GLYPH, SIGN_GLYPH, SIGNS_ORDER } from '../tokens/index.js';
4
+ import type { NatalChartResponse } from '../types/index.js';
4
5
  import { baseStyles } from '../utils/base-styles.js';
5
- import { longitudeToSignPosition, polarToCartesian } from '../utils/degree.js';
6
-
7
- interface PlanetEntry {
8
- name?: string;
9
- planet?: string;
10
- longitude?: number;
11
- degree?: number;
12
- sign?: string;
13
- house?: number;
14
- retrograde?: boolean;
15
- isRetrograde?: boolean;
16
- }
17
-
18
- interface AspectEntry {
19
- planet1?: string;
20
- planet2?: string;
21
- aspect?: string;
22
- orb?: number;
23
- }
6
+ import { polarToCartesian } from '../utils/degree.js';
7
+ import {
8
+ ASPECT_CLASS,
9
+ formatNumber,
10
+ normalizeAspect,
11
+ } from '../utils/format.js';
12
+ import { capitalize } from '../utils/string.js';
24
13
 
25
- interface HouseEntry {
26
- house?: number;
27
- number?: number;
28
- cusp?: number;
29
- sign?: string;
30
- }
31
-
32
- interface NatalChartData {
33
- planets?: PlanetEntry[] | Record<string, PlanetEntry>;
34
- houses?: HouseEntry[];
35
- aspects?: AspectEntry[];
36
- ascendant?: number | { longitude?: number; sign?: string };
37
- midheaven?: number | { longitude?: number; sign?: string };
38
- birthDetails?: {
39
- date?: string;
40
- time?: string;
41
- location?: string;
42
- };
43
- }
14
+ type PlanetEntry = NatalChartResponse['planets'][number];
15
+ type AspectEntry = NatalChartResponse['aspects'][number];
44
16
 
45
- const SIZE = 320;
17
+ const SIZE = 420;
46
18
  const CENTER = SIZE / 2;
47
- const OUTER_R = 150;
48
- const SIGN_R = 134;
49
- const HOUSE_R = 110;
50
- const PLANET_R = 88;
19
+ const OUTER_R = 164;
20
+ const SIGN_R = 146;
21
+ const HOUSE_R = 120;
22
+ const PLANET_R = 96;
23
+ const ANGLE_TICK_R = 178;
24
+ const ANGLE_LABEL_R = 196;
51
25
 
52
26
  /**
53
27
  * Western natal chart wheel. Renders the 12 zodiac signs, 12 houses, planet
@@ -109,9 +83,36 @@ export class RoxyNatalChart extends LitElement {
109
83
  }
110
84
 
111
85
  .aspect {
112
- stroke: color-mix(in srgb, var(--roxy-accent, #f59e0b) 32%, transparent);
113
- stroke-width: 0.6;
86
+ stroke-width: 0.8;
114
87
  fill: none;
88
+ opacity: 0.55;
89
+ }
90
+ .aspect-trine,
91
+ .aspect-sextile {
92
+ stroke: var(--roxy-success, #16a34a);
93
+ }
94
+ .aspect-square,
95
+ .aspect-opposition {
96
+ stroke: var(--roxy-danger, #dc2626);
97
+ }
98
+ .aspect-conjunction {
99
+ stroke: var(--roxy-accent-fg, #b45309);
100
+ }
101
+ .aspect-other {
102
+ stroke: var(--roxy-muted, #71717a);
103
+ opacity: 0.4;
104
+ }
105
+
106
+ .angle-marker {
107
+ fill: var(--roxy-accent-fg, #b45309);
108
+ font-size: 10px;
109
+ font-weight: 700;
110
+ font-family: var(--roxy-font-sans);
111
+ letter-spacing: 0.04em;
112
+ }
113
+ .angle-tick {
114
+ stroke: var(--roxy-accent-fg, #b45309);
115
+ stroke-width: 1.5;
115
116
  }
116
117
 
117
118
  .legend {
@@ -121,20 +122,168 @@ export class RoxyNatalChart extends LitElement {
121
122
  flex-wrap: wrap;
122
123
  gap: var(--roxy-space-md, 1rem);
123
124
  }
125
+ .legend-swatch {
126
+ display: inline-block;
127
+ width: 8px;
128
+ height: 8px;
129
+ border-radius: 50%;
130
+ margin-right: 4px;
131
+ vertical-align: middle;
132
+ }
133
+
134
+ .details {
135
+ margin-top: var(--roxy-space-md, 1rem);
136
+ }
137
+
138
+ .pill-row {
139
+ display: flex;
140
+ flex-wrap: wrap;
141
+ gap: var(--roxy-space-xs, 0.25rem);
142
+ margin-bottom: var(--roxy-space-xs, 0.25rem);
143
+ }
144
+
145
+ .pill {
146
+ padding: 2px 8px;
147
+ border-radius: var(--roxy-radius-sm, 4px);
148
+ font-size: var(--roxy-text-xs, 0.75rem);
149
+ background: color-mix(in srgb, var(--roxy-fg, #0f172a) 8%, transparent);
150
+ color: var(--roxy-fg, #0f172a);
151
+ }
152
+
153
+ .pill--success {
154
+ background: color-mix(in srgb, var(--roxy-success, #16a34a) 15%, transparent);
155
+ color: var(--roxy-success, #16a34a);
156
+ }
157
+
158
+ .pill--danger {
159
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 15%, transparent);
160
+ color: var(--roxy-danger, #dc2626);
161
+ }
162
+
163
+ .pill--muted {
164
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent);
165
+ color: var(--roxy-fg, #0a0a0a);
166
+ }
167
+
168
+ .summary {
169
+ color: var(--roxy-fg, #0f172a);
170
+ font-size: var(--roxy-text-sm, 0.875rem);
171
+ margin: var(--roxy-space-md, 1rem) 0;
172
+ }
173
+
174
+ .dist-grid {
175
+ display: grid;
176
+ grid-template-columns: 1fr 1fr;
177
+ gap: var(--roxy-space-md, 1rem);
178
+ }
179
+
180
+ @container (max-width: 639px) {
181
+ .dist-grid {
182
+ grid-template-columns: 1fr;
183
+ }
184
+ }
185
+
186
+ .dist-section h3 {
187
+ font-size: var(--roxy-text-xs, 0.75rem);
188
+ font-weight: var(--roxy-weight-bold, 600);
189
+ color: var(--roxy-muted, #71717a);
190
+ margin: 0 0 var(--roxy-space-xs, 0.25rem);
191
+ text-transform: uppercase;
192
+ letter-spacing: 0.05em;
193
+ }
194
+
195
+ .dist-row {
196
+ display: grid;
197
+ grid-template-columns: 4rem 1fr 1.5rem;
198
+ align-items: center;
199
+ gap: var(--roxy-space-xs, 0.25rem);
200
+ font-size: var(--roxy-text-xs, 0.75rem);
201
+ color: var(--roxy-fg, #0f172a);
202
+ margin-bottom: 4px;
203
+ }
204
+
205
+ .dist-bar {
206
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 20%, transparent);
207
+ height: 6px;
208
+ border-radius: 3px;
209
+ }
210
+
211
+ .dist-bar > span {
212
+ display: block;
213
+ height: 100%;
214
+ background: var(--roxy-accent, #f59e0b);
215
+ border-radius: 3px;
216
+ }
217
+
218
+ .interpretations {
219
+ margin-top: var(--roxy-space-md, 1rem);
220
+ }
221
+ .interpretations h3 {
222
+ font-size: var(--roxy-text-sm, 0.875rem);
223
+ font-weight: 600;
224
+ color: var(--roxy-muted, #71717a);
225
+ text-transform: uppercase;
226
+ letter-spacing: 0.06em;
227
+ margin: 0 0 var(--roxy-space-sm, 0.5rem);
228
+ }
229
+ .interp-card {
230
+ border: 1px solid var(--roxy-border, #e4e4e7);
231
+ border-radius: var(--roxy-radius-md, 8px);
232
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
233
+ margin-bottom: var(--roxy-space-xs, 0.25rem);
234
+ }
235
+ .interp-card summary {
236
+ cursor: pointer;
237
+ font-weight: 500;
238
+ color: var(--roxy-fg, #0f172a);
239
+ }
240
+ .interp-card summary small {
241
+ color: var(--roxy-muted, #71717a);
242
+ margin-left: 0.5em;
243
+ font-weight: 400;
244
+ }
245
+ .interp-body {
246
+ margin-top: var(--roxy-space-xs, 0.25rem);
247
+ color: var(--roxy-fg, #0f172a);
248
+ font-size: var(--roxy-text-sm, 0.875rem);
249
+ }
250
+ .interp-keywords {
251
+ display: flex;
252
+ flex-wrap: wrap;
253
+ gap: 0.25rem;
254
+ margin-top: 0.5rem;
255
+ }
256
+ .interp-keywords .kw {
257
+ padding: 1px 8px;
258
+ border-radius: 9999px;
259
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
260
+ color: var(--roxy-accent-fg, #b45309);
261
+ font-size: var(--roxy-text-xs, 0.75rem);
262
+ }
124
263
  `,
125
264
  ];
126
265
 
127
266
  @property({ attribute: false })
128
- data: NatalChartData | null = null;
267
+ data: NatalChartResponse | null = null;
129
268
 
130
269
  @property({ type: String, attribute: 'house-system', reflect: true })
131
270
  houseSystem: 'placidus' | 'whole-sign' | 'equal' | 'koch' = 'placidus';
132
271
 
133
272
  private getPlanets(): PlanetEntry[] {
134
- const p = this.data?.planets;
135
- if (!p) return [];
136
- if (Array.isArray(p)) return p;
137
- return Object.entries(p).map(([name, entry]) => ({ ...entry, name }));
273
+ return this.data?.planets ?? [];
274
+ }
275
+
276
+ private getAscendant(): number {
277
+ return this.data?.ascendant?.longitude ?? 0;
278
+ }
279
+
280
+ private getMidheaven(): number | null {
281
+ const m = this.data?.midheaven?.longitude;
282
+ return typeof m === 'number' ? m : null;
283
+ }
284
+
285
+ private toAngle(lon: number): number {
286
+ return 180 + this.getAscendant() - lon;
138
287
  }
139
288
 
140
289
  render() {
@@ -149,11 +298,7 @@ export class RoxyNatalChart extends LitElement {
149
298
  ${
150
299
  this.data.birthDetails
151
300
  ? html`<div class="meta">
152
- ${[
153
- this.data.birthDetails.date,
154
- this.data.birthDetails.time,
155
- this.data.birthDetails.location,
156
- ]
301
+ ${[this.data.birthDetails.date, this.data.birthDetails.time]
157
302
  .filter(Boolean)
158
303
  .join(' · ')}
159
304
  </div>`
@@ -193,18 +338,43 @@ export class RoxyNatalChart extends LitElement {
193
338
  />
194
339
  ${this.renderSpokes()} ${this.renderSigns()} ${this.renderHouseNumbers()}
195
340
  ${this.renderAspects(planets, aspects)} ${this.renderPlanets(planets)}
341
+ ${this.renderAngles()}
196
342
  </svg>
197
343
  <div class="legend">
198
344
  <span>${planets.length} planets</span>
199
345
  <span>${aspects.length} aspects</span>
200
- <span>House system: ${this.houseSystem}</span>
346
+ <span><span class="legend-swatch" style="background: var(--roxy-success)"></span>harmonious</span>
347
+ <span><span class="legend-swatch" style="background: var(--roxy-danger)"></span>challenging</span>
201
348
  </div>
349
+ ${this.renderDetails()}
350
+ ${this.renderInterpretations()}
202
351
  </div>`;
203
352
  }
204
353
 
354
+ private renderAngles() {
355
+ const asc = this.getAscendant();
356
+ const mc = this.getMidheaven();
357
+ const items = [this.renderAngleMark(asc, 'ASC')];
358
+ if (mc !== null) items.push(this.renderAngleMark(mc, 'MC'));
359
+ return items;
360
+ }
361
+
362
+ private renderAngleMark(longitude: number, label: string) {
363
+ const angle = this.toAngle(longitude);
364
+ const tickInner = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
365
+ const tickOuter = polarToCartesian(CENTER, CENTER, ANGLE_TICK_R, angle);
366
+ const labelPos = polarToCartesian(CENTER, CENTER, ANGLE_LABEL_R, angle);
367
+ return svg`
368
+ <g>
369
+ <line class="angle-tick" x1=${tickInner.x} y1=${tickInner.y} x2=${tickOuter.x} y2=${tickOuter.y} />
370
+ <text class="angle-marker" x=${labelPos.x} y=${labelPos.y} text-anchor="middle" dominant-baseline="central">${label}</text>
371
+ </g>
372
+ `;
373
+ }
374
+
205
375
  private renderSpokes() {
206
376
  return Array.from({ length: 12 }, (_, i) => {
207
- const angle = i * 30 - 90;
377
+ const angle = this.toAngle(i * 30);
208
378
  const start = polarToCartesian(CENTER, CENTER, HOUSE_R, angle);
209
379
  const end = polarToCartesian(CENTER, CENTER, OUTER_R, angle);
210
380
  return svg`<line class="wheel-line" x1=${start.x} y1=${start.y} x2=${end.x} y2=${end.y} stroke-width="0.8" />`;
@@ -212,87 +382,171 @@ export class RoxyNatalChart extends LitElement {
212
382
  }
213
383
 
214
384
  private renderSigns() {
215
- const order = [
216
- 'Aries',
217
- 'Taurus',
218
- 'Gemini',
219
- 'Cancer',
220
- 'Leo',
221
- 'Virgo',
222
- 'Libra',
223
- 'Scorpio',
224
- 'Sagittarius',
225
- 'Capricorn',
226
- 'Aquarius',
227
- 'Pisces',
228
- ];
229
- return order.map((sign, i) => {
230
- const angle = i * 30 + 15 - 90;
385
+ return SIGNS_ORDER.map((sign, i) => {
386
+ const angle = this.toAngle(i * 30 + 15);
231
387
  const pos = polarToCartesian(CENTER, CENTER, SIGN_R, angle);
232
388
  return svg`<text class="sign-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${SIGN_GLYPH[sign]}</text>`;
233
389
  });
234
390
  }
235
391
 
236
392
  private renderHouseNumbers() {
393
+ const ascSignIndex = Math.floor(this.getAscendant() / 30);
237
394
  return Array.from({ length: 12 }, (_, i) => {
238
- const angle = i * 30 + 15 - 90;
395
+ const angle = this.toAngle(i * 30 + 15);
239
396
  const pos = polarToCartesian(CENTER, CENTER, HOUSE_R - 12, angle);
240
- return svg`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${i + 1}</text>`;
397
+ const houseNum = ((i - ascSignIndex + 12) % 12) + 1;
398
+ return svg`<text class="house-num" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central">${houseNum}</text>`;
241
399
  });
242
400
  }
243
401
 
244
402
  private renderPlanets(planets: PlanetEntry[]) {
245
403
  return planets.map((p) => {
246
- const lon =
247
- typeof p.longitude === 'number'
248
- ? p.longitude
249
- : typeof p.degree === 'number'
250
- ? p.degree
251
- : NaN;
252
- if (!Number.isFinite(lon)) return nothing;
253
- const angle = lon - 90;
404
+ if (!Number.isFinite(p.longitude)) return nothing;
405
+ const angle = this.toAngle(p.longitude);
254
406
  const pos = polarToCartesian(CENTER, CENTER, PLANET_R, angle);
255
- const name = p.name ?? p.planet ?? '';
256
- const glyph = PLANET_GLYPH[capitalize(name)] ?? name.slice(0, 2);
257
- const retro = p.retrograde || p.isRetrograde ? ' R' : '';
258
- return svg`<text class="planet-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central"><title>${name}${retro}</title>${glyph}</text>`;
407
+ const glyph = PLANET_GLYPH[capitalize(p.name)] ?? p.name.slice(0, 2);
408
+ const retro = p.isRetrograde ? ' R' : '';
409
+ const display = retro ? `${glyph}ᴿ` : glyph;
410
+ return svg`<text class="planet-glyph" x=${pos.x} y=${pos.y} text-anchor="middle" dominant-baseline="central"><title>${p.name}${retro}</title>${display}</text>`;
259
411
  });
260
412
  }
261
413
 
414
+ private renderDetails() {
415
+ const summary = this.data?.summary;
416
+ const ai = this.data?.aspectsInterpretation;
417
+ if (!summary && !ai) return nothing;
418
+
419
+ const retrogrades = summary?.retrogradePlanets ?? [];
420
+ const elementDist = summary?.elementDistribution ?? {};
421
+ const modalityDist = summary?.modalityDistribution ?? {};
422
+ const elementMax = Math.max(1, ...Object.values(elementDist));
423
+ const modalityMax = Math.max(1, ...Object.values(modalityDist));
424
+
425
+ return html`<div class="details">
426
+ ${
427
+ summary?.dominantElement || summary?.dominantModality
428
+ ? html`<div class="pill-row">
429
+ ${summary.dominantElement ? html`<span class="pill">Dominant element: ${summary.dominantElement}</span>` : nothing}
430
+ ${summary.dominantModality ? html`<span class="pill">Dominant modality: ${summary.dominantModality}</span>` : nothing}
431
+ </div>`
432
+ : nothing
433
+ }
434
+ ${
435
+ ai
436
+ ? html`<div class="pill-row">
437
+ <span class="pill pill--success">Harmonious ${ai.harmonious}</span>
438
+ <span class="pill pill--danger">Challenging ${ai.challenging}</span>
439
+ <span class="pill pill--muted">Neutral ${ai.neutral}</span>
440
+ </div>`
441
+ : nothing
442
+ }
443
+ ${
444
+ retrogrades.length > 0
445
+ ? html`<div class="pill-row">
446
+ ${retrogrades.map((p) => {
447
+ const glyph = PLANET_GLYPH[p] ?? p.slice(0, 2);
448
+ return html`<span class="pill pill--muted">${glyph} ${p} R</span>`;
449
+ })}
450
+ </div>`
451
+ : nothing
452
+ }
453
+ ${ai?.summary ? html`<p class="summary">${ai.summary}</p>` : nothing}
454
+ ${
455
+ Object.keys(elementDist).length > 0 ||
456
+ Object.keys(modalityDist).length > 0
457
+ ? html`<div class="dist-grid">
458
+ ${
459
+ Object.keys(elementDist).length > 0
460
+ ? html`<div class="dist-section">
461
+ <h3>Elements</h3>
462
+ ${Object.entries(elementDist).map(
463
+ ([label, count]) => html`<div class="dist-row">
464
+ <span>${label}</span>
465
+ <div class="dist-bar"><span style="width: ${Math.round((count / elementMax) * 100)}%"></span></div>
466
+ <span>${count}</span>
467
+ </div>`,
468
+ )}
469
+ </div>`
470
+ : nothing
471
+ }
472
+ ${
473
+ Object.keys(modalityDist).length > 0
474
+ ? html`<div class="dist-section">
475
+ <h3>Modalities</h3>
476
+ ${Object.entries(modalityDist).map(
477
+ ([label, count]) => html`<div class="dist-row">
478
+ <span>${label}</span>
479
+ <div class="dist-bar"><span style="width: ${Math.round((count / modalityMax) * 100)}%"></span></div>
480
+ <span>${count}</span>
481
+ </div>`,
482
+ )}
483
+ </div>`
484
+ : nothing
485
+ }
486
+ </div>`
487
+ : nothing
488
+ }
489
+ </div>`;
490
+ }
491
+
492
+ private renderInterpretations() {
493
+ const planets = this.getPlanets().filter((p) => p.interpretation);
494
+ if (planets.length === 0) return nothing;
495
+ return html`<section class="interpretations">
496
+ <h3>Planet readings</h3>
497
+ ${planets.map((p) => {
498
+ const interp = p.interpretation!;
499
+ const glyph = PLANET_GLYPH[capitalize(p.name)] ?? '';
500
+ const deg = formatNumber(p.degree ?? 0, 1);
501
+ return html`<details class="interp-card">
502
+ <summary>${glyph} ${p.name} <small>${p.sign ?? ''} ${deg}</small></summary>
503
+ <div class="interp-body">
504
+ ${interp.summary ? html`<p class="interp-summary">${interp.summary}</p>` : nothing}
505
+ ${interp.detailed ? html`<p class="interp-detail">${interp.detailed}</p>` : nothing}
506
+ ${
507
+ interp.keywords?.length
508
+ ? html`<div class="interp-keywords">${interp.keywords.map((k) => html`<span class="kw">${k}</span>`)}</div>`
509
+ : nothing
510
+ }
511
+ </div>
512
+ </details>`;
513
+ })}
514
+ </section>`;
515
+ }
516
+
262
517
  private renderAspects(planets: PlanetEntry[], aspects: AspectEntry[]) {
263
518
  const planetMap = new Map<string, number>();
264
519
  for (const p of planets) {
265
- const lon =
266
- typeof p.longitude === 'number'
267
- ? p.longitude
268
- : typeof p.degree === 'number'
269
- ? p.degree
270
- : null;
271
- if (lon === null) continue;
272
- const name = capitalize(p.name ?? p.planet ?? '');
273
- if (name) planetMap.set(name, lon);
520
+ if (typeof p.longitude !== 'number') continue;
521
+ const name = capitalize(p.name);
522
+ if (name) planetMap.set(name, p.longitude);
274
523
  }
275
524
  return aspects.map((a) => {
276
- const l1 = planetMap.get(capitalize(a.planet1 ?? ''));
277
- const l2 = planetMap.get(capitalize(a.planet2 ?? ''));
525
+ const l1 = planetMap.get(capitalize(a.planet1));
526
+ const l2 = planetMap.get(capitalize(a.planet2));
278
527
  if (l1 === undefined || l2 === undefined) return nothing;
279
- const p1 = polarToCartesian(CENTER, CENTER, PLANET_R - 18, l1 - 90);
280
- const p2 = polarToCartesian(CENTER, CENTER, PLANET_R - 18, l2 - 90);
281
- return svg`<line class="aspect" x1=${p1.x} y1=${p1.y} x2=${p2.x} y2=${p2.y} />`;
528
+ const p1 = polarToCartesian(
529
+ CENTER,
530
+ CENTER,
531
+ PLANET_R - 18,
532
+ this.toAngle(l1),
533
+ );
534
+ const p2 = polarToCartesian(
535
+ CENTER,
536
+ CENTER,
537
+ PLANET_R - 18,
538
+ this.toAngle(l2),
539
+ );
540
+ const aspectName = normalizeAspect(a);
541
+ const aspectClass = ASPECT_CLASS[aspectName] ?? 'aspect-other';
542
+ const orbLabel = formatNumber(a.orb, 1);
543
+ return svg`<line class=${`aspect ${aspectClass}`} x1=${p1.x} y1=${p1.y} x2=${p2.x} y2=${p2.y}><title>${a.planet1} ${aspectName || ''} ${a.planet2}${orbLabel ? ` (orb ${orbLabel}°)` : ''}</title></line>`;
282
544
  });
283
545
  }
284
546
  }
285
547
 
286
- function capitalize(s: string): string {
287
- if (!s) return '';
288
- return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
289
- }
290
-
291
548
  declare global {
292
549
  interface HTMLElementTagNameMap {
293
550
  'roxy-natal-chart': RoxyNatalChart;
294
551
  }
295
552
  }
296
-
297
- // Export for external use
298
- export { longitudeToSignPosition };