@roxyapi/ui 0.9.0 → 0.11.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 (378) hide show
  1. package/AGENTS.md +53 -27
  2. package/README.md +42 -12
  3. package/components-catalog.json +1022 -0
  4. package/dist/cdn/components/angel-number-card.js +52 -0
  5. package/dist/cdn/components/angel-number-card.js.map +7 -0
  6. package/dist/cdn/components/angel-number-lookup.js +52 -0
  7. package/dist/cdn/components/angel-number-lookup.js.map +7 -0
  8. package/dist/cdn/components/ashtakavarga-grid.js +10 -3
  9. package/dist/cdn/components/ashtakavarga-grid.js.map +4 -4
  10. package/dist/cdn/components/aspects-table.js +52 -0
  11. package/dist/cdn/components/aspects-table.js.map +7 -0
  12. package/dist/cdn/components/biorhythm-chart.js +10 -3
  13. package/dist/cdn/components/biorhythm-chart.js.map +4 -4
  14. package/dist/cdn/components/bodygraph.js +17 -10
  15. package/dist/cdn/components/bodygraph.js.map +4 -4
  16. package/dist/cdn/components/choghadiya-grid.js +10 -3
  17. package/dist/cdn/components/choghadiya-grid.js.map +4 -4
  18. package/dist/cdn/components/compatibility-card.js +10 -3
  19. package/dist/cdn/components/compatibility-card.js.map +4 -4
  20. package/dist/cdn/components/crystal-card.js +52 -0
  21. package/dist/cdn/components/crystal-card.js.map +7 -0
  22. package/dist/cdn/components/crystal-grid.js +52 -0
  23. package/dist/cdn/components/crystal-grid.js.map +7 -0
  24. package/dist/cdn/components/dasha-timeline.js +10 -3
  25. package/dist/cdn/components/dasha-timeline.js.map +4 -4
  26. package/dist/cdn/components/data.js +10 -3
  27. package/dist/cdn/components/data.js.map +4 -4
  28. package/dist/cdn/components/divisional-chart.js +31 -24
  29. package/dist/cdn/components/divisional-chart.js.map +4 -4
  30. package/dist/cdn/components/dosha-card.js +10 -3
  31. package/dist/cdn/components/dosha-card.js.map +4 -4
  32. package/dist/cdn/components/dream-card.js +52 -0
  33. package/dist/cdn/components/dream-card.js.map +7 -0
  34. package/dist/cdn/components/dream-search.js +52 -0
  35. package/dist/cdn/components/dream-search.js.map +7 -0
  36. package/dist/cdn/components/endpoint-form.js +3 -3
  37. package/dist/cdn/components/endpoint-form.js.map +3 -3
  38. package/dist/cdn/components/forecast-digest.js +52 -0
  39. package/dist/cdn/components/forecast-digest.js.map +7 -0
  40. package/dist/cdn/components/forecast-timeline.js +10 -3
  41. package/dist/cdn/components/forecast-timeline.js.map +4 -4
  42. package/dist/cdn/components/guna-milan.js +10 -3
  43. package/dist/cdn/components/guna-milan.js.map +4 -4
  44. package/dist/cdn/components/hd-connection.js +52 -0
  45. package/dist/cdn/components/hd-connection.js.map +7 -0
  46. package/dist/cdn/components/hd-penta.js +52 -0
  47. package/dist/cdn/components/hd-penta.js.map +7 -0
  48. package/dist/cdn/components/hd-variables.js +52 -0
  49. package/dist/cdn/components/hd-variables.js.map +7 -0
  50. package/dist/cdn/components/hexagram.js +10 -3
  51. package/dist/cdn/components/hexagram.js.map +4 -4
  52. package/dist/cdn/components/hora-table.js +52 -0
  53. package/dist/cdn/components/hora-table.js.map +7 -0
  54. package/dist/cdn/components/horoscope-card.js +10 -3
  55. package/dist/cdn/components/horoscope-card.js.map +4 -4
  56. package/dist/cdn/components/kp-chart.js +10 -3
  57. package/dist/cdn/components/kp-chart.js.map +4 -4
  58. package/dist/cdn/components/kp-planets-table.js +10 -3
  59. package/dist/cdn/components/kp-planets-table.js.map +4 -4
  60. package/dist/cdn/components/kp-ruling-planets.js +10 -3
  61. package/dist/cdn/components/kp-ruling-planets.js.map +4 -4
  62. package/dist/cdn/components/location-search.js +3 -3
  63. package/dist/cdn/components/location-search.js.map +2 -2
  64. package/dist/cdn/components/moon-phase.js +10 -3
  65. package/dist/cdn/components/moon-phase.js.map +4 -4
  66. package/dist/cdn/components/nakshatra-card.js +10 -3
  67. package/dist/cdn/components/nakshatra-card.js.map +4 -4
  68. package/dist/cdn/components/natal-chart.js +16 -9
  69. package/dist/cdn/components/natal-chart.js.map +4 -4
  70. package/dist/cdn/components/numerology-card.js +10 -3
  71. package/dist/cdn/components/numerology-card.js.map +4 -4
  72. package/dist/cdn/components/panchang-table.js +10 -3
  73. package/dist/cdn/components/panchang-table.js.map +4 -4
  74. package/dist/cdn/components/reference-card.js +52 -0
  75. package/dist/cdn/components/reference-card.js.map +7 -0
  76. package/dist/cdn/components/shadbala-table.js +10 -3
  77. package/dist/cdn/components/shadbala-table.js.map +4 -4
  78. package/dist/cdn/components/synastry-chart.js +16 -9
  79. package/dist/cdn/components/synastry-chart.js.map +4 -4
  80. package/dist/cdn/components/tarot-card.js +10 -3
  81. package/dist/cdn/components/tarot-card.js.map +4 -4
  82. package/dist/cdn/components/tarot-catalog.js +52 -0
  83. package/dist/cdn/components/tarot-catalog.js.map +7 -0
  84. package/dist/cdn/components/tarot-spread.js +10 -3
  85. package/dist/cdn/components/tarot-spread.js.map +4 -4
  86. package/dist/cdn/components/transits-table.js +10 -3
  87. package/dist/cdn/components/transits-table.js.map +4 -4
  88. package/dist/cdn/components/vedic-aspects.js +52 -0
  89. package/dist/cdn/components/vedic-aspects.js.map +7 -0
  90. package/dist/cdn/components/vedic-kundli.js +31 -24
  91. package/dist/cdn/components/vedic-kundli.js.map +4 -4
  92. package/dist/cdn/components/vedic-planets-table.js +10 -3
  93. package/dist/cdn/components/vedic-planets-table.js.map +4 -4
  94. package/dist/cdn/components/western-planets-table.js +10 -3
  95. package/dist/cdn/components/western-planets-table.js.map +4 -4
  96. package/dist/cdn/components/yoga-list.js +10 -3
  97. package/dist/cdn/components/yoga-list.js.map +4 -4
  98. package/dist/cdn/roxy-ui.js +92 -81
  99. package/dist/cdn/roxy-ui.js.map +4 -4
  100. package/dist/components/angel-number-card.d.ts +17 -0
  101. package/dist/components/angel-number-card.d.ts.map +1 -0
  102. package/dist/components/angel-number-card.js +2 -0
  103. package/dist/components/angel-number-card.js.map +7 -0
  104. package/dist/components/angel-number-lookup.d.ts +21 -0
  105. package/dist/components/angel-number-lookup.d.ts.map +1 -0
  106. package/dist/components/angel-number-lookup.js +2 -0
  107. package/dist/components/angel-number-lookup.js.map +7 -0
  108. package/dist/components/ashtakavarga-grid.d.ts +4 -5
  109. package/dist/components/ashtakavarga-grid.d.ts.map +1 -1
  110. package/dist/components/ashtakavarga-grid.js +1 -1
  111. package/dist/components/ashtakavarga-grid.js.map +4 -4
  112. package/dist/components/aspects-table.d.ts +23 -0
  113. package/dist/components/aspects-table.d.ts.map +1 -0
  114. package/dist/components/aspects-table.js +2 -0
  115. package/dist/components/aspects-table.js.map +7 -0
  116. package/dist/components/biorhythm-chart.d.ts +4 -5
  117. package/dist/components/biorhythm-chart.d.ts.map +1 -1
  118. package/dist/components/biorhythm-chart.js +1 -1
  119. package/dist/components/biorhythm-chart.js.map +4 -4
  120. package/dist/components/bodygraph.d.ts +4 -5
  121. package/dist/components/bodygraph.d.ts.map +1 -1
  122. package/dist/components/bodygraph.js +9 -9
  123. package/dist/components/bodygraph.js.map +4 -4
  124. package/dist/components/choghadiya-grid.d.ts +4 -5
  125. package/dist/components/choghadiya-grid.d.ts.map +1 -1
  126. package/dist/components/choghadiya-grid.js +1 -1
  127. package/dist/components/choghadiya-grid.js.map +4 -4
  128. package/dist/components/compatibility-card.d.ts +4 -5
  129. package/dist/components/compatibility-card.d.ts.map +1 -1
  130. package/dist/components/compatibility-card.js +1 -1
  131. package/dist/components/compatibility-card.js.map +4 -4
  132. package/dist/components/crystal-card.d.ts +19 -0
  133. package/dist/components/crystal-card.d.ts.map +1 -0
  134. package/dist/components/crystal-card.js +2 -0
  135. package/dist/components/crystal-card.js.map +7 -0
  136. package/dist/components/crystal-grid.d.ts +26 -0
  137. package/dist/components/crystal-grid.d.ts.map +1 -0
  138. package/dist/components/crystal-grid.js +2 -0
  139. package/dist/components/crystal-grid.js.map +7 -0
  140. package/dist/components/dasha-timeline.d.ts +4 -5
  141. package/dist/components/dasha-timeline.d.ts.map +1 -1
  142. package/dist/components/dasha-timeline.js +1 -1
  143. package/dist/components/dasha-timeline.js.map +4 -4
  144. package/dist/components/data.d.ts +5 -8
  145. package/dist/components/data.d.ts.map +1 -1
  146. package/dist/components/data.js +1 -1
  147. package/dist/components/data.js.map +4 -4
  148. package/dist/components/divisional-chart.d.ts +4 -5
  149. package/dist/components/divisional-chart.d.ts.map +1 -1
  150. package/dist/components/divisional-chart.js +59 -59
  151. package/dist/components/divisional-chart.js.map +4 -4
  152. package/dist/components/dosha-card.d.ts +4 -5
  153. package/dist/components/dosha-card.d.ts.map +1 -1
  154. package/dist/components/dosha-card.js +1 -1
  155. package/dist/components/dosha-card.js.map +4 -4
  156. package/dist/components/dream-card.d.ts +16 -0
  157. package/dist/components/dream-card.d.ts.map +1 -0
  158. package/dist/components/dream-card.js +2 -0
  159. package/dist/components/dream-card.js.map +7 -0
  160. package/dist/components/dream-search.d.ts +18 -0
  161. package/dist/components/dream-search.d.ts.map +1 -0
  162. package/dist/components/dream-search.js +2 -0
  163. package/dist/components/dream-search.js.map +7 -0
  164. package/dist/components/endpoint-form.d.ts +10 -5
  165. package/dist/components/endpoint-form.d.ts.map +1 -1
  166. package/dist/components/endpoint-form.js +1 -1
  167. package/dist/components/endpoint-form.js.map +3 -3
  168. package/dist/components/forecast-digest.d.ts +19 -0
  169. package/dist/components/forecast-digest.d.ts.map +1 -0
  170. package/dist/components/forecast-digest.js +2 -0
  171. package/dist/components/forecast-digest.js.map +7 -0
  172. package/dist/components/forecast-timeline.d.ts +8 -6
  173. package/dist/components/forecast-timeline.d.ts.map +1 -1
  174. package/dist/components/forecast-timeline.js +1 -1
  175. package/dist/components/forecast-timeline.js.map +4 -4
  176. package/dist/components/guna-milan.d.ts +4 -5
  177. package/dist/components/guna-milan.d.ts.map +1 -1
  178. package/dist/components/guna-milan.js +1 -1
  179. package/dist/components/guna-milan.js.map +4 -4
  180. package/dist/components/hd-connection.d.ts +18 -0
  181. package/dist/components/hd-connection.d.ts.map +1 -0
  182. package/dist/components/hd-connection.js +2 -0
  183. package/dist/components/hd-connection.js.map +7 -0
  184. package/dist/components/hd-penta.d.ts +18 -0
  185. package/dist/components/hd-penta.d.ts.map +1 -0
  186. package/dist/components/hd-penta.js +2 -0
  187. package/dist/components/hd-penta.js.map +7 -0
  188. package/dist/components/hd-variables.d.ts +17 -0
  189. package/dist/components/hd-variables.d.ts.map +1 -0
  190. package/dist/components/hd-variables.js +2 -0
  191. package/dist/components/hd-variables.js.map +7 -0
  192. package/dist/components/hexagram.d.ts +4 -5
  193. package/dist/components/hexagram.d.ts.map +1 -1
  194. package/dist/components/hexagram.js +1 -1
  195. package/dist/components/hexagram.js.map +4 -4
  196. package/dist/components/hora-table.d.ts +17 -0
  197. package/dist/components/hora-table.d.ts.map +1 -0
  198. package/dist/components/hora-table.js +2 -0
  199. package/dist/components/hora-table.js.map +7 -0
  200. package/dist/components/horoscope-card.d.ts +4 -5
  201. package/dist/components/horoscope-card.d.ts.map +1 -1
  202. package/dist/components/horoscope-card.js +1 -1
  203. package/dist/components/horoscope-card.js.map +4 -4
  204. package/dist/components/kp-chart.d.ts +4 -5
  205. package/dist/components/kp-chart.d.ts.map +1 -1
  206. package/dist/components/kp-chart.js +1 -1
  207. package/dist/components/kp-chart.js.map +4 -4
  208. package/dist/components/kp-planets-table.d.ts +4 -5
  209. package/dist/components/kp-planets-table.d.ts.map +1 -1
  210. package/dist/components/kp-planets-table.js +1 -1
  211. package/dist/components/kp-planets-table.js.map +4 -4
  212. package/dist/components/kp-ruling-planets.d.ts +4 -5
  213. package/dist/components/kp-ruling-planets.d.ts.map +1 -1
  214. package/dist/components/kp-ruling-planets.js +1 -1
  215. package/dist/components/kp-ruling-planets.js.map +4 -4
  216. package/dist/components/location-search.js +1 -1
  217. package/dist/components/location-search.js.map +2 -2
  218. package/dist/components/moon-phase.d.ts +5 -5
  219. package/dist/components/moon-phase.d.ts.map +1 -1
  220. package/dist/components/moon-phase.js +1 -1
  221. package/dist/components/moon-phase.js.map +4 -4
  222. package/dist/components/nakshatra-card.d.ts +4 -5
  223. package/dist/components/nakshatra-card.d.ts.map +1 -1
  224. package/dist/components/nakshatra-card.js +1 -1
  225. package/dist/components/nakshatra-card.js.map +4 -4
  226. package/dist/components/natal-chart.d.ts +4 -5
  227. package/dist/components/natal-chart.d.ts.map +1 -1
  228. package/dist/components/natal-chart.js +8 -8
  229. package/dist/components/natal-chart.js.map +4 -4
  230. package/dist/components/numerology-card.d.ts +15 -10
  231. package/dist/components/numerology-card.d.ts.map +1 -1
  232. package/dist/components/numerology-card.js +1 -1
  233. package/dist/components/numerology-card.js.map +4 -4
  234. package/dist/components/panchang-table.d.ts +4 -5
  235. package/dist/components/panchang-table.d.ts.map +1 -1
  236. package/dist/components/panchang-table.js +1 -1
  237. package/dist/components/panchang-table.js.map +4 -4
  238. package/dist/components/reference-card.d.ts +19 -0
  239. package/dist/components/reference-card.d.ts.map +1 -0
  240. package/dist/components/reference-card.js +2 -0
  241. package/dist/components/reference-card.js.map +7 -0
  242. package/dist/components/shadbala-table.d.ts +4 -5
  243. package/dist/components/shadbala-table.d.ts.map +1 -1
  244. package/dist/components/shadbala-table.js +1 -1
  245. package/dist/components/shadbala-table.js.map +4 -4
  246. package/dist/components/synastry-chart.d.ts +4 -5
  247. package/dist/components/synastry-chart.d.ts.map +1 -1
  248. package/dist/components/synastry-chart.js +7 -7
  249. package/dist/components/synastry-chart.js.map +4 -4
  250. package/dist/components/tarot-card.d.ts +4 -5
  251. package/dist/components/tarot-card.d.ts.map +1 -1
  252. package/dist/components/tarot-card.js +1 -1
  253. package/dist/components/tarot-card.js.map +4 -4
  254. package/dist/components/tarot-catalog.d.ts +20 -0
  255. package/dist/components/tarot-catalog.d.ts.map +1 -0
  256. package/dist/components/tarot-catalog.js +2 -0
  257. package/dist/components/tarot-catalog.js.map +7 -0
  258. package/dist/components/tarot-spread.d.ts +7 -8
  259. package/dist/components/tarot-spread.d.ts.map +1 -1
  260. package/dist/components/tarot-spread.js +1 -1
  261. package/dist/components/tarot-spread.js.map +4 -4
  262. package/dist/components/transits-table.d.ts +4 -5
  263. package/dist/components/transits-table.d.ts.map +1 -1
  264. package/dist/components/transits-table.js +1 -1
  265. package/dist/components/transits-table.js.map +4 -4
  266. package/dist/components/vedic-aspects.d.ts +16 -0
  267. package/dist/components/vedic-aspects.d.ts.map +1 -0
  268. package/dist/components/vedic-aspects.js +2 -0
  269. package/dist/components/vedic-aspects.js.map +7 -0
  270. package/dist/components/vedic-kundli.d.ts +22 -5
  271. package/dist/components/vedic-kundli.d.ts.map +1 -1
  272. package/dist/components/vedic-kundli.js +63 -63
  273. package/dist/components/vedic-kundli.js.map +4 -4
  274. package/dist/components/vedic-planets-table.d.ts +4 -5
  275. package/dist/components/vedic-planets-table.d.ts.map +1 -1
  276. package/dist/components/vedic-planets-table.js +1 -1
  277. package/dist/components/vedic-planets-table.js.map +4 -4
  278. package/dist/components/western-planets-table.d.ts +4 -5
  279. package/dist/components/western-planets-table.d.ts.map +1 -1
  280. package/dist/components/western-planets-table.js +1 -1
  281. package/dist/components/western-planets-table.js.map +4 -4
  282. package/dist/components/yoga-list.d.ts +6 -7
  283. package/dist/components/yoga-list.d.ts.map +1 -1
  284. package/dist/components/yoga-list.js +1 -1
  285. package/dist/components/yoga-list.js.map +4 -4
  286. package/dist/generated/endpoint-bindings.d.ts +15 -0
  287. package/dist/generated/endpoint-bindings.d.ts.map +1 -0
  288. package/dist/index.cjs +73 -73
  289. package/dist/index.cjs.map +4 -4
  290. package/dist/index.d.ts +16 -0
  291. package/dist/index.d.ts.map +1 -1
  292. package/dist/index.js +71 -71
  293. package/dist/index.js.map +4 -4
  294. package/dist/manifest.d.ts.map +1 -1
  295. package/dist/manifest.json +39 -24
  296. package/dist/styles/tokens-css.d.ts +1 -1
  297. package/dist/styles/tokens-css.d.ts.map +1 -1
  298. package/dist/styles/tokens.css +4 -0
  299. package/dist/types/index.d.ts +1 -1
  300. package/dist/types/index.d.ts.map +1 -1
  301. package/dist/types/types.gen.d.ts +1103 -164
  302. package/dist/types/types.gen.d.ts.map +1 -1
  303. package/dist/utils/angel-sections.d.ts +14 -0
  304. package/dist/utils/angel-sections.d.ts.map +1 -0
  305. package/dist/utils/base-element.d.ts +73 -0
  306. package/dist/utils/base-element.d.ts.map +1 -0
  307. package/dist/utils/base-styles.d.ts.map +1 -1
  308. package/dist/utils/fetch-controller.d.ts +60 -0
  309. package/dist/utils/fetch-controller.d.ts.map +1 -0
  310. package/dist/utils/kundli-render.d.ts +2 -1
  311. package/dist/utils/kundli-render.d.ts.map +1 -1
  312. package/dist/utils/kundli-styles.d.ts.map +1 -1
  313. package/dist/utils/markup-data.d.ts +34 -0
  314. package/dist/utils/markup-data.d.ts.map +1 -1
  315. package/dist/version.d.ts +1 -1
  316. package/dist/version.d.ts.map +1 -1
  317. package/package.json +2 -1
  318. package/src/components/angel-number-card.ts +210 -0
  319. package/src/components/angel-number-lookup.ts +207 -0
  320. package/src/components/ashtakavarga-grid.ts +15 -20
  321. package/src/components/aspects-table.ts +329 -0
  322. package/src/components/biorhythm-chart.ts +14 -18
  323. package/src/components/bodygraph.ts +9 -20
  324. package/src/components/choghadiya-grid.ts +15 -19
  325. package/src/components/compatibility-card.ts +9 -19
  326. package/src/components/crystal-card.ts +242 -0
  327. package/src/components/crystal-grid.ts +182 -0
  328. package/src/components/dasha-timeline.ts +9 -20
  329. package/src/components/data.ts +8 -27
  330. package/src/components/divisional-chart.ts +10 -18
  331. package/src/components/dosha-card.ts +8 -19
  332. package/src/components/dream-card.ts +88 -0
  333. package/src/components/dream-search.ts +135 -0
  334. package/src/components/endpoint-form.ts +149 -55
  335. package/src/components/forecast-digest.ts +213 -0
  336. package/src/components/forecast-timeline.ts +24 -19
  337. package/src/components/guna-milan.ts +8 -19
  338. package/src/components/hd-connection.ts +188 -0
  339. package/src/components/hd-penta.ts +165 -0
  340. package/src/components/hd-variables.ts +128 -0
  341. package/src/components/hexagram.ts +10 -18
  342. package/src/components/hora-table.ts +149 -0
  343. package/src/components/horoscope-card.ts +9 -19
  344. package/src/components/kp-chart.ts +11 -20
  345. package/src/components/kp-planets-table.ts +11 -20
  346. package/src/components/kp-ruling-planets.ts +8 -18
  347. package/src/components/moon-phase.ts +10 -19
  348. package/src/components/nakshatra-card.ts +11 -20
  349. package/src/components/natal-chart.ts +20 -23
  350. package/src/components/numerology-card.ts +90 -31
  351. package/src/components/panchang-table.ts +9 -19
  352. package/src/components/reference-card.ts +212 -0
  353. package/src/components/shadbala-table.ts +15 -18
  354. package/src/components/synastry-chart.ts +17 -20
  355. package/src/components/tarot-card.ts +9 -20
  356. package/src/components/tarot-catalog.ts +129 -0
  357. package/src/components/tarot-spread.ts +20 -21
  358. package/src/components/transits-table.ts +17 -20
  359. package/src/components/vedic-aspects.ts +188 -0
  360. package/src/components/vedic-kundli.ts +46 -19
  361. package/src/components/vedic-planets-table.ts +11 -19
  362. package/src/components/western-planets-table.ts +11 -19
  363. package/src/components/yoga-list.ts +17 -23
  364. package/src/generated/endpoint-bindings.ts +655 -0
  365. package/src/index.ts +27 -0
  366. package/src/manifest.ts +198 -6
  367. package/src/styles/tokens-css.ts +4 -0
  368. package/src/styles/tokens.css +4 -0
  369. package/src/types/index.ts +1 -1
  370. package/src/types/types.gen.ts +1123 -164
  371. package/src/utils/angel-sections.ts +38 -0
  372. package/src/utils/base-element.ts +183 -0
  373. package/src/utils/base-styles.ts +33 -0
  374. package/src/utils/fetch-controller.ts +166 -0
  375. package/src/utils/kundli-render.ts +9 -2
  376. package/src/utils/kundli-styles.ts +7 -1
  377. package/src/utils/markup-data.ts +45 -0
  378. package/src/version.ts +1 -1
@@ -1,7 +1,8 @@
1
- import { css, html, LitElement, nothing } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
1
+ import { css, html, nothing } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
3
  import { PLANET_GLYPH } from '../tokens/index.js';
4
4
  import type { GenerateBodygraphResponse } from '../types/index.js';
5
+ import { RoxyDataElement } from '../utils/base-element.js';
5
6
  import { baseStyles } from '../utils/base-styles.js';
6
7
  import {
7
8
  BODYGRAPH_VIEWBOX,
@@ -9,7 +10,6 @@ import {
9
10
  channelKey,
10
11
  renderBodygraphSvg,
11
12
  } from '../utils/bodygraph-render.js';
12
- import { MarkupDataController } from '../utils/markup-data.js';
13
13
  import { capitalize } from '../utils/string.js';
14
14
 
15
15
  type GateActivation = GenerateBodygraphResponse['gates'][number];
@@ -26,7 +26,7 @@ type GateActivation = GenerateBodygraphResponse['gates'][number];
26
26
  * it adopts the host palette in light and dark without runtime color probing.
27
27
  */
28
28
  @customElement('roxy-bodygraph')
29
- export class RoxyBodygraph extends LitElement {
29
+ export class RoxyBodygraph extends RoxyDataElement<GenerateBodygraphResponse> {
30
30
  static styles = [
31
31
  baseStyles,
32
32
  css`
@@ -65,7 +65,7 @@ export class RoxyBodygraph extends LitElement {
65
65
  svg {
66
66
  display: block;
67
67
  width: 100%;
68
- max-width: 340px;
68
+ max-width: var(--roxy-chart-max-width, 340px);
69
69
  height: auto;
70
70
  margin: 0 auto;
71
71
  }
@@ -165,7 +165,7 @@ export class RoxyBodygraph extends LitElement {
165
165
  border: 1px solid var(--roxy-border, #e4e4e7);
166
166
  border-radius: var(--roxy-radius-md, 8px);
167
167
  padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
168
- background: var(--roxy-bg, #fff);
168
+ background: var(--roxy-surface, #fff);
169
169
  }
170
170
  .fact span {
171
171
  display: block;
@@ -251,22 +251,11 @@ export class RoxyBodygraph extends LitElement {
251
251
  `,
252
252
  ];
253
253
 
254
- constructor() {
255
- super();
256
- // Enables hydrating `data` from a direct-child
257
- // <script type="application/json" class="roxy-data"> for server-rendered
258
- // and cached consumers. The JavaScript `data` property still wins.
259
- new MarkupDataController(this);
254
+ protected renderEmpty() {
255
+ return html`<div class="roxy-empty" role="status">No bodygraph data</div>`;
260
256
  }
261
257
 
262
- @property({ attribute: false })
263
- data: GenerateBodygraphResponse | null = null;
264
-
265
- render() {
266
- const d = this.data;
267
- if (!d)
268
- return html`<div class="roxy-empty" role="status">No bodygraph data</div>`;
269
-
258
+ protected renderData(d: GenerateBodygraphResponse) {
270
259
  const definedCenters = new Set<BodygraphCenterId>(
271
260
  (d.centers ?? [])
272
261
  .filter((c) => c.defined)
@@ -1,9 +1,9 @@
1
- import { css, html, LitElement, nothing } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
1
+ import { css, html, nothing } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
3
  import { PLANET_GLYPH } from '../tokens/index.js';
4
4
  import type { GetChoghadiyaResponse } from '../types/index.js';
5
+ import { RoxyDataElement } from '../utils/base-element.js';
5
6
  import { baseStyles } from '../utils/base-styles.js';
6
- import { MarkupDataController } from '../utils/markup-data.js';
7
7
  import { capitalize } from '../utils/string.js';
8
8
 
9
9
  type ChoghadiyaPeriod = GetChoghadiyaResponse['dayChoghadiya'][number];
@@ -28,11 +28,17 @@ function fmtTime(iso: string): string {
28
28
  * Good periods are highlighted in green, Bad periods in red.
29
29
  */
30
30
  @customElement('roxy-choghadiya-grid')
31
- export class RoxyChoghadiyaGrid extends LitElement {
31
+ export class RoxyChoghadiyaGrid extends RoxyDataElement<GetChoghadiyaResponse> {
32
32
  static styles = [
33
33
  baseStyles,
34
34
  css`
35
35
  .wrap {
36
+ background: var(--roxy-surface, #fff);
37
+ color: var(--roxy-fg, #0a0a0a);
38
+ border: 1px solid var(--roxy-border, #e4e4e7);
39
+ border-radius: var(--roxy-radius-md, 8px);
40
+ padding: var(--roxy-space-lg, 1.5rem);
41
+ box-shadow: var(--roxy-shadow-sm);
36
42
  display: grid;
37
43
  gap: var(--roxy-space-md, 1rem);
38
44
  }
@@ -133,17 +139,6 @@ export class RoxyChoghadiyaGrid extends LitElement {
133
139
  `,
134
140
  ];
135
141
 
136
- constructor() {
137
- super();
138
- // Enables hydrating `data` from a direct-child
139
- // <script type="application/json" class="roxy-data"> for server-rendered
140
- // and cached consumers. The JavaScript `data` property still wins.
141
- new MarkupDataController(this);
142
- }
143
-
144
- @property({ attribute: false })
145
- data: GetChoghadiyaResponse | null = null;
146
-
147
142
  /**
148
143
  * True when the current wall-clock time falls inside this period. Both
149
144
  * `start` and `end` are ISO 8601 with timezone, so the comparison is
@@ -183,11 +178,12 @@ export class RoxyChoghadiyaGrid extends LitElement {
183
178
  </div>`;
184
179
  }
185
180
 
186
- render() {
187
- if (!this.data)
188
- return html`<div class="roxy-empty" role="status">No choghadiya data</div>`;
181
+ protected renderEmpty() {
182
+ return html`<div class="roxy-empty" role="status">No choghadiya data</div>`;
183
+ }
189
184
 
190
- const { date, dayChoghadiya, nightChoghadiya } = this.data;
185
+ protected renderData(d: GetChoghadiyaResponse) {
186
+ const { date, dayChoghadiya, nightChoghadiya } = d;
191
187
 
192
188
  return html`<div class="wrap">
193
189
  <div class="header">
@@ -1,13 +1,13 @@
1
- import { css, html, LitElement, nothing } from 'lit';
1
+ import { css, html, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import type {
4
4
  CalculateBioCompatibilityResponse,
5
5
  CalculateCompatibilityResponse,
6
6
  CalculateNumCompatibilityResponse,
7
7
  } from '../types/index.js';
8
+ import { RoxyDataElement } from '../utils/base-element.js';
8
9
  import { baseStyles } from '../utils/base-styles.js';
9
10
  import { formatNumber } from '../utils/format.js';
10
- import { MarkupDataController } from '../utils/markup-data.js';
11
11
 
12
12
  type CompatibilityData =
13
13
  | CalculateCompatibilityResponse
@@ -19,12 +19,13 @@ type CompatibilityData =
19
19
  * /numerology/compatibility, or /biorhythm/compatibility responses.
20
20
  */
21
21
  @customElement('roxy-compatibility-card')
22
- export class RoxyCompatibilityCard extends LitElement {
22
+ export class RoxyCompatibilityCard extends RoxyDataElement<CompatibilityData> {
23
23
  static styles = [
24
24
  baseStyles,
25
25
  css`
26
26
  .card {
27
- background: var(--roxy-bg, #fff);
27
+ background: var(--roxy-surface, #fff);
28
+ color: var(--roxy-fg, #0a0a0a);
28
29
  border: 1px solid var(--roxy-border, #e4e4e7);
29
30
  border-radius: var(--roxy-radius-md, 8px);
30
31
  padding: var(--roxy-space-lg, 1.5rem);
@@ -109,17 +110,6 @@ export class RoxyCompatibilityCard extends LitElement {
109
110
  `,
110
111
  ];
111
112
 
112
- constructor() {
113
- super();
114
- // Enables hydrating `data` from a direct-child
115
- // <script type="application/json" class="roxy-data"> for server-rendered
116
- // and cached consumers. The JavaScript `data` property still wins.
117
- new MarkupDataController(this);
118
- }
119
-
120
- @property({ attribute: false })
121
- data: CompatibilityData | null = null;
122
-
123
113
  @property({ type: String, reflect: true })
124
114
  mode: 'astrology' | 'numerology' | 'biorhythm' = 'astrology';
125
115
 
@@ -136,11 +126,11 @@ export class RoxyCompatibilityCard extends LitElement {
136
126
  return {};
137
127
  }
138
128
 
139
- render() {
140
- const d = this.data;
141
- if (!d)
142
- return html`<div class="roxy-empty" role="status">No compatibility data</div>`;
129
+ protected renderEmpty() {
130
+ return html`<div class="roxy-empty" role="status">No compatibility data</div>`;
131
+ }
143
132
 
133
+ protected renderData(d: CompatibilityData) {
144
134
  const score = d.overallScore;
145
135
  const breakdown = this.getBreakdown();
146
136
  const rating =
@@ -0,0 +1,242 @@
1
+ import { css, html, nothing } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+ import type { GetCrystalResponse } from '../types/index.js';
4
+ import { RoxyDataElement } from '../utils/base-element.js';
5
+ import { baseStyles } from '../utils/base-styles.js';
6
+
7
+ const MONTHS = [
8
+ 'January',
9
+ 'February',
10
+ 'March',
11
+ 'April',
12
+ 'May',
13
+ 'June',
14
+ 'July',
15
+ 'August',
16
+ 'September',
17
+ 'October',
18
+ 'November',
19
+ 'December',
20
+ ];
21
+
22
+ /**
23
+ * Single-crystal detail card. Renders /crystals/{id}: the stone's photo, description, and full metaphysical profile (spiritual / emotional / physical meaning, governing chakras, zodiac signs, planet, elements, colours, Mohs hardness, numerical vibration, birthstone month), plus its affirmation and the crystals it pairs with. This is the detail view; roxy-crystal-grid is the gallery.
24
+ */
25
+ @customElement('roxy-crystal-card')
26
+ export class RoxyCrystalCard extends RoxyDataElement<GetCrystalResponse> {
27
+ static styles = [
28
+ baseStyles,
29
+ css`
30
+ .wrap {
31
+ background: var(--roxy-surface, #fff);
32
+ color: var(--roxy-fg, #0a0a0a);
33
+ border: 1px solid var(--roxy-border, #e4e4e7);
34
+ border-radius: var(--roxy-radius-md, 8px);
35
+ padding: var(--roxy-space-lg, 1.5rem);
36
+ box-shadow: var(--roxy-shadow-sm);
37
+ display: grid;
38
+ gap: var(--roxy-space-md, 1rem);
39
+ }
40
+ .hero {
41
+ display: flex;
42
+ gap: var(--roxy-space-md, 1rem);
43
+ align-items: flex-start;
44
+ }
45
+ .photo {
46
+ width: 96px;
47
+ height: 96px;
48
+ flex: none;
49
+ border-radius: var(--roxy-radius-md, 8px);
50
+ object-fit: cover;
51
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 35%, transparent);
52
+ }
53
+ .title {
54
+ margin: 0 0 var(--roxy-space-xs, 0.25rem) 0;
55
+ font-size: var(--roxy-text-lg, 1.125rem);
56
+ font-weight: var(--roxy-weight-bold, 600);
57
+ }
58
+ .desc {
59
+ margin: 0;
60
+ line-height: 1.55;
61
+ font-size: var(--roxy-text-sm, 0.875rem);
62
+ color: var(--roxy-fg, #0a0a0a);
63
+ }
64
+ .attrs {
65
+ display: grid;
66
+ grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
67
+ gap: var(--roxy-space-sm, 0.5rem);
68
+ }
69
+ .attr {
70
+ display: grid;
71
+ gap: 2px;
72
+ }
73
+ .attr dt {
74
+ font-size: var(--roxy-text-xs, 0.75rem);
75
+ color: var(--roxy-muted, #71717a);
76
+ text-transform: uppercase;
77
+ letter-spacing: 0.05em;
78
+ }
79
+ .attr dd {
80
+ margin: 0;
81
+ font-size: var(--roxy-text-sm, 0.875rem);
82
+ font-weight: 500;
83
+ }
84
+ .colors {
85
+ display: flex;
86
+ flex-wrap: wrap;
87
+ gap: 0.4rem;
88
+ }
89
+ .color {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ gap: 0.3rem;
93
+ font-size: var(--roxy-text-xs, 0.75rem);
94
+ text-transform: capitalize;
95
+ }
96
+ .dot {
97
+ width: 0.7rem;
98
+ height: 0.7rem;
99
+ border-radius: var(--roxy-radius-full, 9999px);
100
+ border: 1px solid color-mix(in srgb, var(--roxy-fg, #0a0a0a) 18%, transparent);
101
+ }
102
+ .meaning h3 {
103
+ margin: 0 0 var(--roxy-space-xs, 0.25rem) 0;
104
+ font-size: var(--roxy-text-xs, 0.75rem);
105
+ color: var(--roxy-muted, #71717a);
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.05em;
108
+ }
109
+ .meaning p {
110
+ margin: 0 0 var(--roxy-space-sm, 0.5rem) 0;
111
+ font-size: var(--roxy-text-sm, 0.875rem);
112
+ line-height: 1.55;
113
+ }
114
+ .chips {
115
+ display: flex;
116
+ flex-wrap: wrap;
117
+ gap: 0.3rem;
118
+ }
119
+ .chip {
120
+ padding: 1px 8px;
121
+ border-radius: var(--roxy-radius-full, 9999px);
122
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
123
+ color: var(--roxy-fg, #0a0a0a);
124
+ font-size: var(--roxy-text-xs, 0.75rem);
125
+ text-transform: capitalize;
126
+ }
127
+ .section-label {
128
+ font-size: var(--roxy-text-xs, 0.75rem);
129
+ color: var(--roxy-muted, #71717a);
130
+ text-transform: uppercase;
131
+ letter-spacing: 0.06em;
132
+ font-weight: var(--roxy-weight-bold, 600);
133
+ margin: 0 0 var(--roxy-space-xs, 0.25rem) 0;
134
+ }
135
+ .affirmation {
136
+ margin: 0;
137
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
138
+ border-left: 3px solid var(--roxy-accent, #f59e0b);
139
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 8%, transparent);
140
+ font-style: italic;
141
+ font-size: var(--roxy-text-sm, 0.875rem);
142
+ border-radius: 0 var(--roxy-radius-sm, 4px) var(--roxy-radius-sm, 4px) 0;
143
+ }
144
+ `,
145
+ ];
146
+
147
+ protected renderEmpty() {
148
+ return html`<div class="roxy-empty" role="status">No crystal data</div>`;
149
+ }
150
+
151
+ protected renderData(d: GetCrystalResponse) {
152
+ const colors = d.colors ?? [];
153
+ const keywords = d.keywords ?? [];
154
+ const pairs = d.pairsWith ?? [];
155
+ const month =
156
+ typeof d.birthMonth === 'number' ? MONTHS[d.birthMonth - 1] : undefined;
157
+
158
+ return html`<article class="wrap" aria-label=${d.name ?? 'Crystal'}>
159
+ <div class="hero">
160
+ ${d.imageUrl ? html`<img class="photo" src=${d.imageUrl} alt=${d.name ?? 'Crystal'} loading="lazy" />` : nothing}
161
+ <div>
162
+ <h2 class="title">${d.name}</h2>
163
+ ${d.description ? html`<p class="desc">${d.description}</p>` : nothing}
164
+ </div>
165
+ </div>
166
+
167
+ <dl class="attrs">
168
+ ${this.attr('Planet', d.planet)}
169
+ ${this.attr('Hardness', typeof d.hardness === 'number' ? `${d.hardness} Mohs` : undefined)}
170
+ ${this.attr('Vibration', d.numericalVibration)}
171
+ ${this.attr('Birthstone', month)}
172
+ ${this.list('Chakras', d.chakras)}
173
+ ${this.list('Zodiac', d.zodiacSigns)}
174
+ ${this.list('Elements', d.elements)}
175
+ ${
176
+ colors.length
177
+ ? html`<div class="attr">
178
+ <dt>Colors</dt>
179
+ <dd>
180
+ <div class="colors">
181
+ ${colors.map((c) => html`<span class="color"><span class="dot" style="background:${c}"></span>${c}</span>`)}
182
+ </div>
183
+ </dd>
184
+ </div>`
185
+ : nothing
186
+ }
187
+ </dl>
188
+
189
+ ${this.renderMeaning(d.meaning)}
190
+
191
+ ${
192
+ keywords.length
193
+ ? html`<div>
194
+ <p class="section-label">Keywords</p>
195
+ <div class="chips">${keywords.map((k) => html`<span class="chip">${k}</span>`)}</div>
196
+ </div>`
197
+ : nothing
198
+ }
199
+
200
+ ${d.affirmation ? html`<p class="affirmation">${d.affirmation}</p>` : nothing}
201
+
202
+ ${
203
+ pairs.length
204
+ ? html`<div>
205
+ <p class="section-label">Pairs with</p>
206
+ <div class="chips">${pairs.map((p) => html`<span class="chip">${String(p).replace(/-/g, ' ')}</span>`)}</div>
207
+ </div>`
208
+ : nothing
209
+ }
210
+ </article>`;
211
+ }
212
+
213
+ private attr(label: string, value: string | number | undefined) {
214
+ if (value === undefined || value === null || value === '') return nothing;
215
+ return html`<div class="attr"><dt>${label}</dt><dd>${value}</dd></div>`;
216
+ }
217
+
218
+ private list(label: string, values: readonly string[] | undefined) {
219
+ if (!values?.length) return nothing;
220
+ return html`<div class="attr"><dt>${label}</dt><dd>${values.join(', ')}</dd></div>`;
221
+ }
222
+
223
+ private renderMeaning(m: GetCrystalResponse['meaning'] | undefined) {
224
+ if (!m) return nothing;
225
+ const rows: Array<[string, string | undefined]> = [
226
+ ['Spiritual', m.spiritual],
227
+ ['Emotional', m.emotional],
228
+ ['Physical', m.physical],
229
+ ];
230
+ const present = rows.filter(([, v]) => Boolean(v));
231
+ if (present.length === 0) return nothing;
232
+ return html`<div class="meaning">
233
+ ${present.map(([label, text]) => html`<h3>${label}</h3><p>${text}</p>`)}
234
+ </div>`;
235
+ }
236
+ }
237
+
238
+ declare global {
239
+ interface HTMLElementTagNameMap {
240
+ 'roxy-crystal-card': RoxyCrystalCard;
241
+ }
242
+ }
@@ -0,0 +1,182 @@
1
+ import { css, html, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import type {
4
+ GetBirthstonesResponse,
5
+ GetCrystalsByChakraResponse,
6
+ GetCrystalsByElementResponse,
7
+ GetCrystalsByZodiacResponse,
8
+ ListCrystalsResponse,
9
+ SearchCrystalsResponse,
10
+ } from '../types/index.js';
11
+ import { RoxyDataElement } from '../utils/base-element.js';
12
+ import { baseStyles } from '../utils/base-styles.js';
13
+
14
+ /**
15
+ * Any crystal list response that carries a `crystals` summary array. Every crystals endpoint that returns more than one stone shares the `{ name, id, imageUrl, colors }` item shape, so one grid renders them all.
16
+ */
17
+ type CrystalGridData =
18
+ | ListCrystalsResponse
19
+ | GetCrystalsByChakraResponse
20
+ | GetCrystalsByElementResponse
21
+ | GetCrystalsByZodiacResponse
22
+ | GetBirthstonesResponse
23
+ | SearchCrystalsResponse;
24
+
25
+ /**
26
+ * Month number to birthstone month name for the derived heading.
27
+ */
28
+ const MONTHS = [
29
+ 'January',
30
+ 'February',
31
+ 'March',
32
+ 'April',
33
+ 'May',
34
+ 'June',
35
+ 'July',
36
+ 'August',
37
+ 'September',
38
+ 'October',
39
+ 'November',
40
+ 'December',
41
+ ];
42
+
43
+ /**
44
+ * Crystal grid. Renders any crystals list response (/crystals, /crystals/chakra/{chakra}, /crystals/element/{element}, /crystals/zodiac/{sign}, /crystals/birthstone/{month}, /crystals/search) as a responsive gallery of crystal tiles with photo, name, and colour swatches. The heading is derived from the response filter (chakra, element, zodiac sign, or birth month) or set explicitly via the `heading` attribute.
45
+ */
46
+ @customElement('roxy-crystal-grid')
47
+ export class RoxyCrystalGrid extends RoxyDataElement<CrystalGridData> {
48
+ static styles = [
49
+ baseStyles,
50
+ css`
51
+ .wrap {
52
+ display: grid;
53
+ gap: var(--roxy-space-md, 1rem);
54
+ }
55
+ .head {
56
+ display: flex;
57
+ align-items: baseline;
58
+ justify-content: space-between;
59
+ gap: var(--roxy-space-sm, 0.5rem);
60
+ flex-wrap: wrap;
61
+ }
62
+ .title {
63
+ margin: 0;
64
+ font-size: var(--roxy-text-lg, 1.125rem);
65
+ font-weight: var(--roxy-weight-bold, 600);
66
+ color: var(--roxy-fg, #0a0a0a);
67
+ }
68
+ .count {
69
+ color: var(--roxy-muted, #71717a);
70
+ font-size: var(--roxy-text-sm, 0.875rem);
71
+ }
72
+ .grid {
73
+ display: grid;
74
+ grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
75
+ gap: var(--roxy-space-md, 1rem);
76
+ margin: 0;
77
+ padding: 0;
78
+ list-style: none;
79
+ }
80
+ .tile {
81
+ display: grid;
82
+ gap: var(--roxy-space-xs, 0.25rem);
83
+ background: var(--roxy-surface, #fff);
84
+ border: 1px solid var(--roxy-border, #e4e4e7);
85
+ border-radius: var(--roxy-radius-md, 8px);
86
+ padding: var(--roxy-space-sm, 0.5rem);
87
+ box-shadow: var(--roxy-shadow-sm);
88
+ }
89
+ .photo {
90
+ aspect-ratio: 1 / 1;
91
+ width: 100%;
92
+ border-radius: var(--roxy-radius-sm, 4px);
93
+ object-fit: cover;
94
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 35%, transparent);
95
+ }
96
+ .name {
97
+ margin: 0;
98
+ font-size: var(--roxy-text-sm, 0.875rem);
99
+ font-weight: var(--roxy-weight-bold, 600);
100
+ color: var(--roxy-fg, #0a0a0a);
101
+ }
102
+ .colors {
103
+ display: flex;
104
+ flex-wrap: wrap;
105
+ gap: 4px;
106
+ }
107
+ .swatch {
108
+ width: 10px;
109
+ height: 10px;
110
+ border-radius: var(--roxy-radius-full, 9999px);
111
+ border: 1px solid color-mix(in srgb, var(--roxy-fg, #0a0a0a) 18%, transparent);
112
+ }
113
+ `,
114
+ ];
115
+
116
+ /**
117
+ * Override the auto-derived grid heading. Empty by default, in which case the heading comes from the response filter (chakra, element, zodiac, or birth month) or falls back to "Crystals".
118
+ */
119
+ @property({ type: String, reflect: true })
120
+ heading = '';
121
+
122
+ protected renderEmpty() {
123
+ return html`<div class="roxy-empty" role="status">No crystals</div>`;
124
+ }
125
+
126
+ protected renderData(d: CrystalGridData) {
127
+ const crystals = d.crystals ?? [];
128
+ if (crystals.length === 0) return this.renderEmpty();
129
+
130
+ const title = this.heading || this.deriveHeading(d);
131
+ const total =
132
+ 'total' in d && typeof d.total === 'number' ? d.total : crystals.length;
133
+
134
+ return html`<section class="wrap" aria-label=${title}>
135
+ <header class="head">
136
+ <h2 class="title">${title}</h2>
137
+ <span class="count">${total} ${total === 1 ? 'crystal' : 'crystals'}</span>
138
+ </header>
139
+ <ul class="grid">
140
+ ${crystals.map(
141
+ (c) => html`<li class="tile">
142
+ ${
143
+ c.imageUrl
144
+ ? html`<img class="photo" src=${c.imageUrl} alt=${c.name ?? 'Crystal'} loading="lazy" />`
145
+ : html`<div class="photo" aria-hidden="true"></div>`
146
+ }
147
+ <p class="name">${c.name}</p>
148
+ ${
149
+ c.colors && c.colors.length > 0
150
+ ? html`<div class="colors" aria-label=${`Colours: ${c.colors.join(', ')}`}>
151
+ ${c.colors.map((col) => html`<span class="swatch" style=${`background:${cssColor(col)}`} title=${col}></span>`)}
152
+ </div>`
153
+ : nothing
154
+ }
155
+ </li>`,
156
+ )}
157
+ </ul>
158
+ </section>`;
159
+ }
160
+
161
+ private deriveHeading(d: CrystalGridData): string {
162
+ if ('chakra' in d && d.chakra) return `${d.chakra} chakra crystals`;
163
+ if ('element' in d && d.element) return `${d.element} element crystals`;
164
+ if ('sign' in d && d.sign) return `Crystals for ${d.sign}`;
165
+ if ('month' in d && typeof d.month === 'number')
166
+ return `${MONTHS[d.month - 1] ?? ''} birthstones`.trim();
167
+ return 'Crystals';
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Map an API colour keyword to a CSS colour. Most crystal colours (violet, purple, green, blue) are valid CSS named colours; multi-word or non-standard values (blue-green, lavender) fall back to the keyword and, if the browser cannot resolve it, the swatch border still renders. Lower-cased and space-stripped so "Blue Green" resolves to the CSS hyphen form where it exists.
173
+ */
174
+ function cssColor(name: string): string {
175
+ return name.trim().toLowerCase().replace(/\s+/g, '');
176
+ }
177
+
178
+ declare global {
179
+ interface HTMLElementTagNameMap {
180
+ 'roxy-crystal-grid': RoxyCrystalGrid;
181
+ }
182
+ }
@@ -1,13 +1,13 @@
1
- import { css, html, LitElement, nothing } from 'lit';
1
+ import { css, html, nothing } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import type {
4
4
  GetCurrentDashaResponse,
5
5
  GetMajorDashasResponse,
6
6
  GetSubDashasResponse,
7
7
  } from '../types/index.js';
8
+ import { RoxyDataElement } from '../utils/base-element.js';
8
9
  import { baseStyles } from '../utils/base-styles.js';
9
10
  import { formatNumber } from '../utils/format.js';
10
- import { MarkupDataController } from '../utils/markup-data.js';
11
11
 
12
12
  type DashaData =
13
13
  | GetCurrentDashaResponse
@@ -22,7 +22,7 @@ type DashaPeriod = GetMajorDashasResponse['mahadashas'][number];
22
22
  * Switch to period="major" for the full 120-year Vimshottari timeline.
23
23
  */
24
24
  @customElement('roxy-dasha-timeline')
25
- export class RoxyDashaTimeline extends LitElement {
25
+ export class RoxyDashaTimeline extends RoxyDataElement<DashaData> {
26
26
  static styles = [
27
27
  baseStyles,
28
28
  css`
@@ -51,7 +51,7 @@ export class RoxyDashaTimeline extends LitElement {
51
51
  display: grid;
52
52
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
53
53
  gap: var(--roxy-space-md, 1rem);
54
- background: var(--roxy-bg, #fff);
54
+ background: var(--roxy-surface, #fff);
55
55
  border: 1px solid var(--roxy-border, #e4e4e7);
56
56
  border-radius: var(--roxy-radius-md, 8px);
57
57
  padding: var(--roxy-space-md, 1rem);
@@ -138,7 +138,7 @@ export class RoxyDashaTimeline extends LitElement {
138
138
  border: 1px solid var(--roxy-border, #e4e4e7);
139
139
  border-radius: var(--roxy-radius-md, 8px);
140
140
  padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
141
- background: var(--roxy-bg, #fff);
141
+ background: var(--roxy-surface, #fff);
142
142
  }
143
143
  .interp h3 {
144
144
  margin: 0;
@@ -153,25 +153,14 @@ export class RoxyDashaTimeline extends LitElement {
153
153
  `,
154
154
  ];
155
155
 
156
- constructor() {
157
- super();
158
- // Enables hydrating `data` from a direct-child
159
- // <script type="application/json" class="roxy-data"> for server-rendered
160
- // and cached consumers. The JavaScript `data` property still wins.
161
- new MarkupDataController(this);
162
- }
163
-
164
- @property({ attribute: false })
165
- data: DashaData | null = null;
166
-
167
156
  @property({ type: String, reflect: true })
168
157
  period: 'current' | 'major' | 'sub' = 'current';
169
158
 
170
- render() {
171
- const d = this.data;
172
- if (!d)
173
- return html`<div class="roxy-empty" role="status">No dasha data</div>`;
159
+ protected renderEmpty() {
160
+ return html`<div class="roxy-empty" role="status">No dasha data</div>`;
161
+ }
174
162
 
163
+ protected renderData(d: DashaData) {
175
164
  const periods = this.collectPeriods(d);
176
165
  const maxYears = periods.length
177
166
  ? Math.max(...periods.map((p) => p.durationYears))