@roxyapi/ui 0.1.3 → 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 (161) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +9 -3
  3. package/dist/cdn/components/ashtakavarga-grid.js +349 -0
  4. package/dist/cdn/components/ashtakavarga-grid.js.map +7 -0
  5. package/dist/cdn/components/choghadiya-grid.js +239 -0
  6. package/dist/cdn/components/choghadiya-grid.js.map +7 -0
  7. package/dist/cdn/components/compatibility-card.js +6 -6
  8. package/dist/cdn/components/compatibility-card.js.map +1 -1
  9. package/dist/cdn/components/dasha-timeline.js +4 -4
  10. package/dist/cdn/components/dasha-timeline.js.map +1 -1
  11. package/dist/cdn/components/data.js +9 -9
  12. package/dist/cdn/components/data.js.map +4 -4
  13. package/dist/cdn/components/divisional-chart.js +279 -0
  14. package/dist/cdn/components/divisional-chart.js.map +7 -0
  15. package/dist/cdn/components/dosha-card.js +39 -39
  16. package/dist/cdn/components/dosha-card.js.map +3 -3
  17. package/dist/cdn/components/endpoint-form.js +8 -8
  18. package/dist/cdn/components/endpoint-form.js.map +4 -4
  19. package/dist/cdn/components/guna-milan.js +64 -22
  20. package/dist/cdn/components/guna-milan.js.map +3 -3
  21. package/dist/cdn/components/hexagram.js +9 -9
  22. package/dist/cdn/components/hexagram.js.map +3 -3
  23. package/dist/cdn/components/horoscope-card.js +28 -21
  24. package/dist/cdn/components/horoscope-card.js.map +4 -4
  25. package/dist/cdn/components/kp-planets-table.js +4 -4
  26. package/dist/cdn/components/kp-planets-table.js.map +1 -1
  27. package/dist/cdn/components/location-search.js.map +2 -2
  28. package/dist/cdn/components/moon-phase.js +13 -13
  29. package/dist/cdn/components/moon-phase.js.map +3 -3
  30. package/dist/cdn/components/natal-chart.js +196 -22
  31. package/dist/cdn/components/natal-chart.js.map +4 -4
  32. package/dist/cdn/components/numerology-card.js +6 -6
  33. package/dist/cdn/components/numerology-card.js.map +4 -4
  34. package/dist/cdn/components/panchang-table.js +9 -9
  35. package/dist/cdn/components/panchang-table.js.map +1 -1
  36. package/dist/cdn/components/shadbala-table.js +312 -0
  37. package/dist/cdn/components/shadbala-table.js.map +7 -0
  38. package/dist/cdn/components/synastry-chart.js +21 -21
  39. package/dist/cdn/components/synastry-chart.js.map +4 -4
  40. package/dist/cdn/components/transits-table.js +391 -0
  41. package/dist/cdn/components/transits-table.js.map +7 -0
  42. package/dist/cdn/components/vedic-kundli.js +51 -29
  43. package/dist/cdn/components/vedic-kundli.js.map +4 -4
  44. package/dist/cdn/components/yoga-list.js +334 -0
  45. package/dist/cdn/components/yoga-list.js.map +7 -0
  46. package/dist/cdn/roxy-ui.js +1872 -522
  47. package/dist/cdn/roxy-ui.js.map +4 -4
  48. package/dist/components/ashtakavarga-grid.d.ts +26 -0
  49. package/dist/components/ashtakavarga-grid.d.ts.map +1 -0
  50. package/dist/components/ashtakavarga-grid.js +457 -0
  51. package/dist/components/ashtakavarga-grid.js.map +7 -0
  52. package/dist/components/choghadiya-grid.d.ts +19 -0
  53. package/dist/components/choghadiya-grid.d.ts.map +1 -0
  54. package/dist/components/choghadiya-grid.js +304 -0
  55. package/dist/components/choghadiya-grid.js.map +7 -0
  56. package/dist/components/compatibility-card.js.map +1 -1
  57. package/dist/components/dasha-timeline.js.map +1 -1
  58. package/dist/components/data.d.ts +5 -7
  59. package/dist/components/data.d.ts.map +1 -1
  60. package/dist/components/data.js +7 -5
  61. package/dist/components/data.js.map +3 -3
  62. package/dist/components/divisional-chart.d.ts +20 -0
  63. package/dist/components/divisional-chart.d.ts.map +1 -0
  64. package/dist/components/divisional-chart.js +471 -0
  65. package/dist/components/divisional-chart.js.map +7 -0
  66. package/dist/components/dosha-card.d.ts.map +1 -1
  67. package/dist/components/dosha-card.js +33 -30
  68. package/dist/components/dosha-card.js.map +2 -2
  69. package/dist/components/endpoint-form.d.ts.map +1 -1
  70. package/dist/components/endpoint-form.js +5 -3
  71. package/dist/components/endpoint-form.js.map +3 -3
  72. package/dist/components/guna-milan.d.ts.map +1 -1
  73. package/dist/components/guna-milan.js +61 -12
  74. package/dist/components/guna-milan.js.map +3 -3
  75. package/dist/components/hexagram.js +17 -0
  76. package/dist/components/hexagram.js.map +2 -2
  77. package/dist/components/horoscope-card.d.ts.map +1 -1
  78. package/dist/components/horoscope-card.js +30 -3
  79. package/dist/components/horoscope-card.js.map +3 -3
  80. package/dist/components/kp-planets-table.js.map +1 -1
  81. package/dist/components/location-search.d.ts +2 -3
  82. package/dist/components/location-search.d.ts.map +1 -1
  83. package/dist/components/location-search.js.map +2 -2
  84. package/dist/components/moon-phase.js +17 -0
  85. package/dist/components/moon-phase.js.map +2 -2
  86. package/dist/components/natal-chart.d.ts +2 -0
  87. package/dist/components/natal-chart.d.ts.map +1 -1
  88. package/dist/components/natal-chart.js +243 -36
  89. package/dist/components/natal-chart.js.map +3 -3
  90. package/dist/components/numerology-card.d.ts.map +1 -1
  91. package/dist/components/numerology-card.js +5 -3
  92. package/dist/components/numerology-card.js.map +3 -3
  93. package/dist/components/panchang-table.js.map +1 -1
  94. package/dist/components/shadbala-table.d.ts +18 -0
  95. package/dist/components/shadbala-table.d.ts.map +1 -0
  96. package/dist/components/shadbala-table.js +400 -0
  97. package/dist/components/shadbala-table.js.map +7 -0
  98. package/dist/components/synastry-chart.d.ts.map +1 -1
  99. package/dist/components/synastry-chart.js +34 -29
  100. package/dist/components/synastry-chart.js.map +3 -3
  101. package/dist/components/transits-table.d.ts +21 -0
  102. package/dist/components/transits-table.d.ts.map +1 -0
  103. package/dist/components/transits-table.js +515 -0
  104. package/dist/components/transits-table.js.map +7 -0
  105. package/dist/components/vedic-kundli.d.ts +3 -6
  106. package/dist/components/vedic-kundli.d.ts.map +1 -1
  107. package/dist/components/vedic-kundli.js +132 -80
  108. package/dist/components/vedic-kundli.js.map +3 -3
  109. package/dist/components/yoga-list.d.ts +29 -0
  110. package/dist/components/yoga-list.d.ts.map +1 -0
  111. package/dist/components/yoga-list.js +389 -0
  112. package/dist/components/yoga-list.js.map +7 -0
  113. package/dist/index.cjs +2693 -971
  114. package/dist/index.cjs.map +4 -4
  115. package/dist/index.d.ts +7 -2
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +2712 -990
  118. package/dist/index.js.map +4 -4
  119. package/dist/manifest.d.ts +4 -10
  120. package/dist/manifest.d.ts.map +1 -1
  121. package/dist/manifest.json +7 -2
  122. package/dist/styles/tokens.css +26 -0
  123. package/dist/tokens/index.d.ts +6 -0
  124. package/dist/tokens/index.d.ts.map +1 -1
  125. package/dist/types/types.gen.d.ts +2 -2
  126. package/dist/utils/format.d.ts +15 -1
  127. package/dist/utils/format.d.ts.map +1 -1
  128. package/dist/utils/kundli-render.d.ts +63 -0
  129. package/dist/utils/kundli-render.d.ts.map +1 -0
  130. package/dist/utils/string.d.ts +14 -0
  131. package/dist/utils/string.d.ts.map +1 -0
  132. package/dist/version.d.ts +1 -1
  133. package/package.json +1 -1
  134. package/src/components/ashtakavarga-grid.ts +354 -0
  135. package/src/components/choghadiya-grid.ts +185 -0
  136. package/src/components/data.ts +8 -15
  137. package/src/components/divisional-chart.ts +214 -0
  138. package/src/components/dosha-card.ts +53 -36
  139. package/src/components/endpoint-form.ts +1 -7
  140. package/src/components/guna-milan.ts +74 -16
  141. package/src/components/horoscope-card.ts +8 -4
  142. package/src/components/location-search.ts +2 -3
  143. package/src/components/natal-chart.ts +251 -42
  144. package/src/components/numerology-card.ts +1 -7
  145. package/src/components/shadbala-table.ts +286 -0
  146. package/src/components/synastry-chart.ts +13 -39
  147. package/src/components/transits-table.ts +350 -0
  148. package/src/components/vedic-kundli.ts +38 -143
  149. package/src/components/yoga-list.ts +328 -0
  150. package/src/index.ts +8 -6
  151. package/src/manifest.ts +74 -100
  152. package/src/styles/tokens.css +26 -0
  153. package/src/tokens/index.ts +9 -0
  154. package/src/types/types.gen.ts +2 -2
  155. package/src/utils/format.ts +21 -3
  156. package/src/utils/kundli-render.ts +197 -0
  157. package/src/utils/string.ts +23 -0
  158. package/src/version.ts +1 -1
  159. package/dist/utils/motion.d.ts +0 -13
  160. package/dist/utils/motion.d.ts.map +0 -1
  161. package/src/utils/motion.ts +0 -18
@@ -0,0 +1,185 @@
1
+ import { css, html, LitElement, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { PLANET_GLYPH } from '../tokens/index.js';
4
+ import type { GetChoghadiyaResponse } from '../types/index.js';
5
+ import { baseStyles } from '../utils/base-styles.js';
6
+ import { capitalize } from '../utils/string.js';
7
+
8
+ type ChoghadiyaPeriod = GetChoghadiyaResponse['dayChoghadiya'][number];
9
+
10
+ /**
11
+ * Format an ISO 8601 datetime string to a short local time (HH:MM).
12
+ * Falls back to the raw string when parsing fails.
13
+ */
14
+ function fmtTime(iso: string): string {
15
+ try {
16
+ const d = new Date(iso);
17
+ if (Number.isNaN(d.getTime())) return iso;
18
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
19
+ } catch {
20
+ return iso;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Choghadiya muhurta grid. Accepts a GetChoghadiyaResponse and renders
26
+ * 8 daytime and 8 nighttime muhurta tiles in a two-column responsive layout.
27
+ * Good periods are highlighted in green, Bad periods in red.
28
+ */
29
+ @customElement('roxy-choghadiya-grid')
30
+ export class RoxyChoghadiyaGrid extends LitElement {
31
+ static styles = [
32
+ baseStyles,
33
+ css`
34
+ .wrap {
35
+ display: grid;
36
+ gap: var(--roxy-space-md, 1rem);
37
+ }
38
+ .header {
39
+ display: grid;
40
+ gap: var(--roxy-space-xs, 0.25rem);
41
+ }
42
+ .title {
43
+ font-size: var(--roxy-text-lg, 1.125rem);
44
+ font-weight: var(--roxy-weight-bold, 600);
45
+ margin: 0;
46
+ }
47
+ .subtitle {
48
+ font-size: var(--roxy-text-sm, 0.875rem);
49
+ color: var(--roxy-muted, #71717a);
50
+ margin: 0;
51
+ }
52
+ .cho-grid {
53
+ display: grid;
54
+ grid-template-columns: 1fr;
55
+ gap: var(--roxy-space-md, 1rem);
56
+ }
57
+ @media (min-width: 720px) {
58
+ .cho-grid {
59
+ grid-template-columns: 1fr 1fr;
60
+ }
61
+ }
62
+ .period-col {
63
+ display: grid;
64
+ gap: var(--roxy-space-xs, 0.25rem);
65
+ }
66
+ .period-heading {
67
+ font-size: var(--roxy-text-base, 1rem);
68
+ font-weight: var(--roxy-weight-bold, 600);
69
+ margin: 0 0 var(--roxy-space-xs, 0.25rem);
70
+ color: var(--roxy-fg, #0a0a0a);
71
+ }
72
+ .cho-tile {
73
+ display: grid;
74
+ grid-template-columns: 1fr auto;
75
+ align-items: center;
76
+ gap: 0.25em 0.75em;
77
+ padding: 0.55em 0.85em;
78
+ border: 1px solid var(--roxy-border, #e4e4e7);
79
+ border-radius: var(--roxy-radius-md, 8px);
80
+ }
81
+ .cho-tile.good {
82
+ background: color-mix(in srgb, var(--roxy-success, #22c55e) 18%, transparent);
83
+ border-color: color-mix(in srgb, var(--roxy-success, #22c55e) 45%, transparent);
84
+ color: var(--roxy-fg, #0a0a0a);
85
+ }
86
+ .cho-tile.bad {
87
+ background: color-mix(in srgb, var(--roxy-danger, #ef4444) 18%, transparent);
88
+ border-color: color-mix(in srgb, var(--roxy-danger, #ef4444) 45%, transparent);
89
+ color: var(--roxy-fg, #0a0a0a);
90
+ }
91
+ .cho-tile.neutral {
92
+ background: transparent;
93
+ color: var(--roxy-fg, #0a0a0a);
94
+ }
95
+ .tile-name {
96
+ font-size: var(--roxy-text-base, 1rem);
97
+ font-weight: var(--roxy-weight-bold, 600);
98
+ grid-column: 1;
99
+ }
100
+ .tile-time {
101
+ font-size: var(--roxy-text-xs, 0.75rem);
102
+ opacity: 0.8;
103
+ white-space: nowrap;
104
+ grid-column: 2;
105
+ grid-row: 1 / 3;
106
+ text-align: right;
107
+ align-self: center;
108
+ }
109
+ .tile-lord {
110
+ font-size: var(--roxy-text-sm, 0.875rem);
111
+ opacity: 0.85;
112
+ grid-column: 1;
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 0.25em;
116
+ }
117
+ `,
118
+ ];
119
+
120
+ @property({ attribute: false })
121
+ data: GetChoghadiyaResponse | null = null;
122
+
123
+ private renderTile(period: ChoghadiyaPeriod) {
124
+ const effectClass =
125
+ period.effect === 'Good'
126
+ ? 'good'
127
+ : period.effect === 'Bad'
128
+ ? 'bad'
129
+ : 'neutral';
130
+ const lordGlyph = PLANET_GLYPH[capitalize(period.lord)] ?? '';
131
+ const timeRange = `${fmtTime(period.start)} - ${fmtTime(period.end)}`;
132
+ return html`<div class="cho-tile ${effectClass}" role="listitem">
133
+ <span class="tile-name">${period.name}</span>
134
+ <span class="tile-time" aria-label="Time range">${timeRange}</span>
135
+ <span class="tile-lord">
136
+ ${lordGlyph ? html`<span aria-hidden="true">${lordGlyph}</span>` : nothing}
137
+ ${period.lord}
138
+ </span>
139
+ </div>`;
140
+ }
141
+
142
+ render() {
143
+ if (!this.data)
144
+ return html`<div class="roxy-empty" role="status">No choghadiya data</div>`;
145
+
146
+ const { date, dayChoghadiya, nightChoghadiya } = this.data;
147
+
148
+ return html`<div class="wrap">
149
+ <div class="header">
150
+ <h2 class="title">Choghadiya</h2>
151
+ ${date ? html`<p class="subtitle">${date}</p>` : nothing}
152
+ </div>
153
+
154
+ <div class="cho-grid">
155
+ <section class="period-col" aria-label="Day muhurta periods">
156
+ <h3 class="period-heading">Day</h3>
157
+ <div role="list" aria-label="Daytime choghadiya">
158
+ ${
159
+ dayChoghadiya && dayChoghadiya.length > 0
160
+ ? dayChoghadiya.map((p) => this.renderTile(p))
161
+ : html`<p class="roxy-empty" role="status">No daytime periods</p>`
162
+ }
163
+ </div>
164
+ </section>
165
+
166
+ <section class="period-col" aria-label="Night muhurta periods">
167
+ <h3 class="period-heading">Night</h3>
168
+ <div role="list" aria-label="Nighttime choghadiya">
169
+ ${
170
+ nightChoghadiya && nightChoghadiya.length > 0
171
+ ? nightChoghadiya.map((p) => this.renderTile(p))
172
+ : html`<p class="roxy-empty" role="status">No nighttime periods</p>`
173
+ }
174
+ </div>
175
+ </section>
176
+ </div>
177
+ </div>`;
178
+ }
179
+ }
180
+
181
+ declare global {
182
+ interface HTMLElementTagNameMap {
183
+ 'roxy-choghadiya-grid': RoxyChoghadiyaGrid;
184
+ }
185
+ }
@@ -1,11 +1,12 @@
1
1
  import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import { baseStyles } from '../utils/base-styles.js';
4
+ import { humanize } from '../utils/string.js';
4
5
 
5
6
  /**
6
- * Generic fallback renderer. Accepts ANY OpenAPI response shape and renders it
7
- * via field-name heuristics so future spec additions render reasonably without
8
- * hand-wired components. Mirrors the WordPress GenericRenderer pattern.
7
+ * Generic fallback renderer. Accepts ANY OpenAPI response shape and renders
8
+ * it via field-name heuristics so future spec additions render reasonably
9
+ * without hand-wired components.
9
10
  *
10
11
  * Heuristic order:
11
12
  * 1. Primitive (string, number, boolean) -> single line.
@@ -14,9 +15,8 @@ import { baseStyles } from '../utils/base-styles.js';
14
15
  * 4. Object with title-like field -> card with key/value rows.
15
16
  * 5. Otherwise -> definition list of all keys.
16
17
  *
17
- * Future spec hint: when the server emits `x-roxy-ui` on a schema, the
18
- * dispatcher in tool-call (Phase 2.5) will select a hand-tuned component
19
- * instead of this fallback.
18
+ * When a schema declares an `x-roxy-ui` hint, a future dispatcher can opt
19
+ * into a hand-tuned component instead of this fallback.
20
20
  */
21
21
 
22
22
  type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
@@ -196,7 +196,7 @@ export class RoxyData extends LitElement {
196
196
  return html`<table class="roxy-table" role="table">
197
197
  <thead>
198
198
  <tr>
199
- ${keys.map((k) => html`<th>${this.humanize(k)}</th>`)}
199
+ ${keys.map((k) => html`<th>${humanize(k)}</th>`)}
200
200
  </tr>
201
201
  </thead>
202
202
  <tbody>
@@ -246,7 +246,7 @@ export class RoxyData extends LitElement {
246
246
  ? html`<dl class="roxy-rows">
247
247
  ${rows.map(
248
248
  ([k, v]) => html`
249
- <dt>${this.humanize(k)}</dt>
249
+ <dt>${humanize(k)}</dt>
250
250
  <dd>${this.renderField(v)}</dd>
251
251
  `,
252
252
  )}
@@ -290,13 +290,6 @@ export class RoxyData extends LitElement {
290
290
  }
291
291
  return Array.from(seen);
292
292
  }
293
-
294
- private humanize(key: string): string {
295
- return key
296
- .replace(/[_-]+/g, ' ')
297
- .replace(/([a-z])([A-Z])/g, '$1 $2')
298
- .replace(/^\w/, (c) => c.toUpperCase());
299
- }
300
293
  }
301
294
 
302
295
  declare global {
@@ -0,0 +1,214 @@
1
+ import { css, html, LitElement, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { PLANET_GLYPH, RASHI_KEYS } from '../tokens/index.js';
4
+ import type { DivisionalChartResponse } from '../types/index.js';
5
+ import { baseStyles } from '../utils/base-styles.js';
6
+ import type { HouseDef } from '../utils/kundli-render.js';
7
+ import {
8
+ RASHI_TO_SIGN,
9
+ renderNorthFrame,
10
+ renderNorthHouseGroup,
11
+ renderSouthFrame,
12
+ renderSouthHouseGroup,
13
+ } from '../utils/kundli-render.js';
14
+
15
+ type RashiBucket = {
16
+ rashi?: string;
17
+ signs?: Array<{ graha: string; isRetrograde?: boolean }>;
18
+ };
19
+ type ChartByRashi = { [key: string]: RashiBucket | unknown };
20
+
21
+ /**
22
+ * Divisional chart renderer (D2-D60). Accepts a DivisionalChartResponse and
23
+ * renders the same south/north kundli wheel as the birth chart, plus division
24
+ * metadata and Vargottama planet pills.
25
+ */
26
+ @customElement('roxy-divisional-chart')
27
+ export class RoxyDivisionalChart extends LitElement {
28
+ static styles = [
29
+ baseStyles,
30
+ css`
31
+ .wrap {
32
+ display: grid;
33
+ gap: var(--roxy-space-md, 1rem);
34
+ }
35
+ .header {
36
+ display: grid;
37
+ gap: var(--roxy-space-xs, 0.25rem);
38
+ }
39
+ .title {
40
+ font-size: var(--roxy-text-lg, 1.125rem);
41
+ font-weight: var(--roxy-weight-bold, 600);
42
+ margin: 0;
43
+ }
44
+ .division-meta {
45
+ font-size: var(--roxy-text-sm, 0.875rem);
46
+ color: var(--roxy-muted, #71717a);
47
+ margin: 0;
48
+ }
49
+ .significance {
50
+ font-size: var(--roxy-text-sm, 0.875rem);
51
+ color: var(--roxy-muted, #71717a);
52
+ border-left: 2px solid var(--roxy-border, #e4e4e7);
53
+ padding-left: var(--roxy-space-sm, 0.5rem);
54
+ margin: 0;
55
+ }
56
+ svg {
57
+ display: block;
58
+ width: 100%;
59
+ max-width: 360px;
60
+ margin: 0 auto;
61
+ }
62
+ .line {
63
+ fill: transparent;
64
+ stroke: var(--roxy-border, #e4e4e7);
65
+ }
66
+ .sign-text {
67
+ fill: var(--roxy-muted, #71717a);
68
+ font-size: 9px;
69
+ font-weight: 500;
70
+ font-family: var(--roxy-font-sans);
71
+ }
72
+ .planet-text {
73
+ fill: var(--roxy-fg, #0a0a0a);
74
+ font-size: 11px;
75
+ font-weight: 600;
76
+ font-family: var(--roxy-font-sans);
77
+ }
78
+ .house-num {
79
+ fill: var(--roxy-muted, #71717a);
80
+ font-size: 9px;
81
+ font-weight: 400;
82
+ font-family: var(--roxy-font-sans);
83
+ }
84
+ .lagna-marker {
85
+ fill: var(--roxy-accent-fg, #b45309);
86
+ font-size: 8px;
87
+ font-weight: 700;
88
+ font-family: var(--roxy-font-sans);
89
+ letter-spacing: 0.05em;
90
+ }
91
+ .lagna-bg {
92
+ fill: color-mix(in srgb, var(--roxy-accent, #f59e0b) 12%, transparent);
93
+ stroke: color-mix(in srgb, var(--roxy-accent, #f59e0b) 45%, transparent);
94
+ stroke-width: 0.8;
95
+ }
96
+ .vargottama-row {
97
+ display: flex;
98
+ flex-wrap: wrap;
99
+ gap: var(--roxy-space-xs, 0.25rem);
100
+ align-items: center;
101
+ }
102
+ .vargottama-label {
103
+ font-size: var(--roxy-text-sm, 0.875rem);
104
+ color: var(--roxy-muted, #71717a);
105
+ font-weight: 500;
106
+ margin-right: var(--roxy-space-xs, 0.25rem);
107
+ }
108
+ .vargottama-pill {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: 0.2em;
112
+ font-size: var(--roxy-text-sm, 0.875rem);
113
+ font-weight: 600;
114
+ padding: 0.15em 0.6em;
115
+ border-radius: 999px;
116
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 22%, transparent);
117
+ color: var(--roxy-fg, #0a0a0a);
118
+ border: 1px solid color-mix(in srgb, var(--roxy-accent, #f59e0b) 45%, transparent);
119
+ }
120
+ `,
121
+ ];
122
+
123
+ @property({ attribute: false })
124
+ data: DivisionalChartResponse | null = null;
125
+
126
+ @property({ type: String, reflect: true, attribute: 'chart-style' })
127
+ chartStyle: 'south' | 'north' = 'south';
128
+
129
+ private buildHouses(): HouseDef[] {
130
+ if (!this.data) return [];
131
+ const chart = this.data.chart as ChartByRashi;
132
+ const meta =
133
+ (this.data.chart as { meta?: Record<string, { rashi?: string }> }).meta ??
134
+ {};
135
+ const lagnaSign = meta.Lagna?.rashi ?? '';
136
+ const houses: HouseDef[] = [];
137
+ for (let i = 0; i < 12; i++) {
138
+ const key = RASHI_KEYS[i];
139
+ const bucket = chart[key] as RashiBucket | undefined;
140
+ const planets = (bucket?.signs ?? []).map((p) => p.graha).filter(Boolean);
141
+ const sign = RASHI_TO_SIGN[key] ?? '';
142
+ houses.push({
143
+ number: i + 1,
144
+ sign,
145
+ planets,
146
+ isLagna: lagnaSign
147
+ ? lagnaSign.toLowerCase() === sign.toLowerCase()
148
+ : false,
149
+ });
150
+ }
151
+ return houses;
152
+ }
153
+
154
+ render() {
155
+ if (!this.data)
156
+ return html`<div class="roxy-empty" role="status">No divisional chart data</div>`;
157
+
158
+ const { division, vargottama } = this.data;
159
+ const houses = this.buildHouses();
160
+ const isNorth = this.chartStyle === 'north';
161
+
162
+ return html`<div class="wrap">
163
+ <div class="header">
164
+ <h2 class="title">
165
+ D${division.number} ${division.name}
166
+ ${
167
+ division.sanskritName && division.sanskritName !== division.name
168
+ ? html`<span class="division-meta"> · ${division.sanskritName}</span>`
169
+ : nothing
170
+ }
171
+ </h2>
172
+ ${
173
+ division.significance
174
+ ? html`<p class="significance">${division.significance}</p>`
175
+ : nothing
176
+ }
177
+ </div>
178
+
179
+ <svg
180
+ viewBox="0 0 300 300"
181
+ role="img"
182
+ aria-label="D${division.number} ${division.name} divisional chart with twelve sign houses"
183
+ >
184
+ <title>D${division.number} ${division.name}</title>
185
+ ${isNorth ? renderNorthFrame() : renderSouthFrame()}
186
+ ${
187
+ isNorth
188
+ ? houses.map((h) => renderNorthHouseGroup(h))
189
+ : houses.map((h) => renderSouthHouseGroup(h))
190
+ }
191
+ </svg>
192
+
193
+ ${
194
+ vargottama && vargottama.length > 0
195
+ ? html`<div class="vargottama-row" role="list" aria-label="Vargottama planets">
196
+ <span class="vargottama-label">Vargottama:</span>
197
+ ${vargottama.map(
198
+ (planet) =>
199
+ html`<span class="vargottama-pill" role="listitem">
200
+ ${PLANET_GLYPH[planet] ?? ''} ${planet}
201
+ </span>`,
202
+ )}
203
+ </div>`
204
+ : nothing
205
+ }
206
+ </div>`;
207
+ }
208
+ }
209
+
210
+ declare global {
211
+ interface HTMLElementTagNameMap {
212
+ 'roxy-divisional-chart': RoxyDivisionalChart;
213
+ }
214
+ }
@@ -65,25 +65,24 @@ export class RoxyDoshaCard extends LitElement {
65
65
  background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
66
66
  color: var(--roxy-danger-fg, #991b1b);
67
67
  }
68
- .severity {
69
- display: flex;
70
- align-items: center;
71
- gap: 4px;
72
- }
73
- .severity span {
74
- width: 14px;
75
- height: 4px;
76
- border-radius: 2px;
77
- background: var(--roxy-border, #e4e4e7);
78
- }
79
- .severity.mild span:nth-child(1) {
80
- background: var(--roxy-warning, #ea580c);
81
- }
82
- .severity.moderate span:nth-child(-n + 2) {
83
- background: var(--roxy-warning, #ea580c);
84
- }
85
- .severity.severe span {
86
- background: var(--roxy-danger, #dc2626);
68
+ .severity-bar {
69
+ position: relative;
70
+ width: 100%;
71
+ height: 8px;
72
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 30%, transparent);
73
+ border-radius: 4px;
74
+ overflow: hidden;
75
+ }
76
+ .severity-fill {
77
+ display: block;
78
+ height: 100%;
79
+ transition: width var(--roxy-motion-duration, 200ms) ease-out;
80
+ border-radius: 4px;
81
+ }
82
+ @media (prefers-reduced-motion: reduce) {
83
+ .severity-fill {
84
+ transition: none;
85
+ }
87
86
  }
88
87
 
89
88
  .description {
@@ -128,7 +127,24 @@ export class RoxyDoshaCard extends LitElement {
128
127
 
129
128
  const present = !!d.present;
130
129
  const label = DOSHA_LABELS[this.type] ?? this.type;
131
- const sevClass = (d.severity ?? '').toLowerCase();
130
+ const sevLower = (d.severity ?? '').toLowerCase();
131
+ const tier =
132
+ sevLower === 'severe'
133
+ ? 3
134
+ : sevLower === 'moderate'
135
+ ? 2
136
+ : sevLower === 'mild'
137
+ ? 1
138
+ : 0;
139
+ const pct = tier * 33;
140
+ const barColor =
141
+ tier === 3
142
+ ? 'var(--roxy-danger)'
143
+ : tier === 2
144
+ ? 'var(--roxy-warning)'
145
+ : tier === 1
146
+ ? 'var(--roxy-success)'
147
+ : 'transparent';
132
148
 
133
149
  return html`<article
134
150
  class="card"
@@ -136,23 +152,24 @@ export class RoxyDoshaCard extends LitElement {
136
152
  >
137
153
  <header class="head">
138
154
  <h2 class="title">${label}</h2>
139
- <div style="display:flex; gap:0.5rem; align-items:center;">
140
- <span class=${`badge ${present ? 'present' : 'absent'}`}>
141
- ${present ? 'Present' : 'Absent'}
142
- </span>
143
- ${
144
- d.severity
145
- ? html`<span
146
- class=${`severity ${sevClass}`}
147
- role="img"
148
- aria-label=${`Severity ${d.severity}`}
149
- >
150
- <span></span><span></span><span></span>
151
- </span>`
152
- : nothing
153
- }
154
- </div>
155
+ <span class=${`badge ${present ? 'present' : 'absent'}`}>
156
+ ${present ? 'Present' : 'Absent'}
157
+ </span>
155
158
  </header>
159
+ ${
160
+ d.severity
161
+ ? html`<div
162
+ class="severity-bar"
163
+ role="meter"
164
+ aria-valuemin="0"
165
+ aria-valuemax="3"
166
+ aria-valuenow="${tier}"
167
+ aria-label="Severity ${d.severity}"
168
+ >
169
+ <span class="severity-fill" style="width: ${pct}%; background: ${barColor};"></span>
170
+ </div>`
171
+ : nothing
172
+ }
156
173
  ${d.description ? html`<p class="description">${d.description}</p>` : nothing}
157
174
  ${this.renderEffects(d)}
158
175
  ${
@@ -1,6 +1,7 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import { customElement, property, state } from 'lit/decorators.js';
3
3
  import { baseStyles } from '../utils/base-styles.js';
4
+ import { humanize } from '../utils/string.js';
4
5
 
5
6
  interface OpenApiSchemaRef {
6
7
  $ref?: string;
@@ -487,13 +488,6 @@ export class RoxyEndpointForm extends LitElement {
487
488
  }
488
489
  }
489
490
 
490
- function humanize(s: string): string {
491
- return s
492
- .replace(/[_-]+/g, ' ')
493
- .replace(/([a-z])([A-Z])/g, '$1 $2')
494
- .replace(/^\w/, (c) => c.toUpperCase());
495
- }
496
-
497
491
  declare global {
498
492
  interface HTMLElementTagNameMap {
499
493
  'roxy-endpoint-form': RoxyEndpointForm;