@roxyapi/ui 0.6.1 → 0.8.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.
- package/AGENTS.md +18 -1
- package/README.md +31 -2
- package/THEMING.md +1 -1
- package/dist/cdn/components/ashtakavarga-grid.js +1 -1
- package/dist/cdn/components/ashtakavarga-grid.js.map +2 -2
- package/dist/cdn/components/biorhythm-chart.js +3 -3
- package/dist/cdn/components/biorhythm-chart.js.map +2 -2
- package/dist/cdn/components/bodygraph.js +54 -0
- package/dist/cdn/components/bodygraph.js.map +7 -0
- package/dist/cdn/components/choghadiya-grid.js +1 -1
- package/dist/cdn/components/choghadiya-grid.js.map +2 -2
- package/dist/cdn/components/compatibility-card.js +1 -1
- package/dist/cdn/components/compatibility-card.js.map +2 -2
- package/dist/cdn/components/dasha-timeline.js +2 -2
- package/dist/cdn/components/dasha-timeline.js.map +2 -2
- package/dist/cdn/components/divisional-chart.js +2 -2
- package/dist/cdn/components/divisional-chart.js.map +2 -2
- package/dist/cdn/components/endpoint-form.js +2 -2
- package/dist/cdn/components/endpoint-form.js.map +2 -2
- package/dist/cdn/components/forecast-timeline.js +45 -0
- package/dist/cdn/components/forecast-timeline.js.map +7 -0
- package/dist/cdn/components/guna-milan.js +1 -1
- package/dist/cdn/components/guna-milan.js.map +2 -2
- package/dist/cdn/components/hexagram.js +1 -1
- package/dist/cdn/components/hexagram.js.map +2 -2
- package/dist/cdn/components/horoscope-card.js +1 -1
- package/dist/cdn/components/horoscope-card.js.map +2 -2
- package/dist/cdn/components/kp-chart.js +1 -1
- package/dist/cdn/components/kp-chart.js.map +2 -2
- package/dist/cdn/components/kp-ruling-planets.js +1 -1
- package/dist/cdn/components/kp-ruling-planets.js.map +2 -2
- package/dist/cdn/components/location-search.js +1 -1
- package/dist/cdn/components/location-search.js.map +2 -2
- package/dist/cdn/components/nakshatra-card.js +1 -1
- package/dist/cdn/components/nakshatra-card.js.map +2 -2
- package/dist/cdn/components/natal-chart.js +4 -4
- package/dist/cdn/components/natal-chart.js.map +2 -2
- package/dist/cdn/components/numerology-card.js +2 -2
- package/dist/cdn/components/numerology-card.js.map +2 -2
- package/dist/cdn/components/panchang-table.js +3 -3
- package/dist/cdn/components/panchang-table.js.map +3 -3
- package/dist/cdn/components/shadbala-table.js +1 -1
- package/dist/cdn/components/shadbala-table.js.map +2 -2
- package/dist/cdn/components/synastry-chart.js +3 -3
- package/dist/cdn/components/synastry-chart.js.map +2 -2
- package/dist/cdn/components/transits-table.js +2 -2
- package/dist/cdn/components/transits-table.js.map +2 -2
- package/dist/cdn/components/vedic-kundli.js +10 -10
- package/dist/cdn/components/vedic-kundli.js.map +2 -2
- package/dist/cdn/components/vedic-planets-table.js +3 -3
- package/dist/cdn/components/vedic-planets-table.js.map +4 -4
- package/dist/cdn/components/yoga-list.js +2 -2
- package/dist/cdn/components/yoga-list.js.map +2 -2
- package/dist/cdn/roxy-ui.js +55 -46
- package/dist/cdn/roxy-ui.js.map +4 -4
- package/dist/components/ashtakavarga-grid.js +1 -1
- package/dist/components/ashtakavarga-grid.js.map +3 -3
- package/dist/components/biorhythm-chart.js +1 -1
- package/dist/components/biorhythm-chart.js.map +2 -2
- package/dist/components/bodygraph.d.ts +27 -0
- package/dist/components/bodygraph.d.ts.map +1 -0
- package/dist/components/bodygraph.js +11 -0
- package/dist/components/bodygraph.js.map +7 -0
- package/dist/components/choghadiya-grid.js +1 -1
- package/dist/components/choghadiya-grid.js.map +2 -2
- package/dist/components/compatibility-card.js +1 -1
- package/dist/components/compatibility-card.js.map +2 -2
- package/dist/components/dasha-timeline.js +1 -1
- package/dist/components/dasha-timeline.js.map +2 -2
- package/dist/components/divisional-chart.js +2 -2
- package/dist/components/divisional-chart.js.map +2 -2
- package/dist/components/endpoint-form.js +1 -1
- package/dist/components/endpoint-form.js.map +2 -2
- package/dist/components/forecast-timeline.d.ts +38 -0
- package/dist/components/forecast-timeline.d.ts.map +1 -0
- package/dist/components/forecast-timeline.js +2 -0
- package/dist/components/forecast-timeline.js.map +7 -0
- package/dist/components/guna-milan.js +1 -1
- package/dist/components/guna-milan.js.map +2 -2
- package/dist/components/hexagram.js +1 -1
- package/dist/components/hexagram.js.map +2 -2
- package/dist/components/horoscope-card.js +1 -1
- package/dist/components/horoscope-card.js.map +2 -2
- package/dist/components/kp-chart.js +1 -1
- package/dist/components/kp-chart.js.map +2 -2
- package/dist/components/kp-ruling-planets.js +1 -1
- package/dist/components/kp-ruling-planets.js.map +2 -2
- package/dist/components/location-search.js +1 -1
- package/dist/components/location-search.js.map +2 -2
- package/dist/components/nakshatra-card.js +1 -1
- package/dist/components/nakshatra-card.js.map +2 -2
- package/dist/components/natal-chart.js +2 -2
- package/dist/components/natal-chart.js.map +2 -2
- package/dist/components/numerology-card.js +1 -1
- package/dist/components/numerology-card.js.map +2 -2
- package/dist/components/panchang-table.d.ts +22 -1
- package/dist/components/panchang-table.d.ts.map +1 -1
- package/dist/components/panchang-table.js +1 -1
- package/dist/components/panchang-table.js.map +3 -3
- package/dist/components/shadbala-table.js +1 -1
- package/dist/components/shadbala-table.js.map +2 -2
- package/dist/components/synastry-chart.js +4 -4
- package/dist/components/synastry-chart.js.map +2 -2
- package/dist/components/transits-table.js +1 -1
- package/dist/components/transits-table.js.map +2 -2
- package/dist/components/vedic-kundli.js +2 -2
- package/dist/components/vedic-kundli.js.map +2 -2
- package/dist/components/vedic-planets-table.d.ts +14 -0
- package/dist/components/vedic-planets-table.d.ts.map +1 -1
- package/dist/components/vedic-planets-table.js +1 -1
- package/dist/components/vedic-planets-table.js.map +4 -4
- package/dist/components/yoga-list.js +1 -1
- package/dist/components/yoga-list.js.map +2 -2
- package/dist/index.cjs +53 -44
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -43
- package/dist/index.js.map +4 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.json +2 -0
- package/dist/styles/tokens.css +4 -4
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.gen.d.ts +6058 -1431
- package/dist/types/types.gen.d.ts.map +1 -1
- package/dist/utils/bodygraph-render.d.ts +105 -0
- package/dist/utils/bodygraph-render.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/ashtakavarga-grid.ts +1 -1
- package/src/components/biorhythm-chart.ts +1 -1
- package/src/components/bodygraph.ts +390 -0
- package/src/components/choghadiya-grid.ts +1 -1
- package/src/components/compatibility-card.ts +2 -2
- package/src/components/dasha-timeline.ts +3 -3
- package/src/components/endpoint-form.ts +2 -2
- package/src/components/forecast-timeline.ts +336 -0
- package/src/components/guna-milan.ts +1 -1
- package/src/components/hexagram.ts +3 -3
- package/src/components/horoscope-card.ts +1 -1
- package/src/components/kp-chart.ts +1 -1
- package/src/components/kp-ruling-planets.ts +1 -1
- package/src/components/location-search.ts +1 -1
- package/src/components/nakshatra-card.ts +1 -1
- package/src/components/natal-chart.ts +5 -5
- package/src/components/numerology-card.ts +2 -2
- package/src/components/panchang-table.ts +112 -18
- package/src/components/shadbala-table.ts +1 -1
- package/src/components/synastry-chart.ts +4 -4
- package/src/components/transits-table.ts +1 -1
- package/src/components/vedic-planets-table.ts +221 -3
- package/src/components/yoga-list.ts +1 -1
- package/src/index.ts +4 -0
- package/src/manifest.ts +26 -0
- package/src/styles/tokens.css +4 -4
- package/src/types/index.ts +1 -1
- package/src/types/types.gen.ts +5999 -1287
- package/src/utils/bodygraph-render.ts +641 -0
- package/src/utils/kundli-styles.ts +2 -2
- package/src/utils/tablist.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { ASPECT_SYMBOL } from '../tokens/index.js';
|
|
4
|
+
import type { GenerateTimelineResponse } from '../types/index.js';
|
|
5
|
+
import { baseStyles } from '../utils/base-styles.js';
|
|
6
|
+
import { ASPECT_CLASS, formatDate, formatNumber } from '../utils/format.js';
|
|
7
|
+
import { MarkupDataController } from '../utils/markup-data.js';
|
|
8
|
+
import { capitalize, humanize } from '../utils/string.js';
|
|
9
|
+
|
|
10
|
+
type ForecastEvent = GenerateTimelineResponse['events'][number];
|
|
11
|
+
type ForecastDomain = ForecastEvent['domain'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Display label per forecast domain. Keyed by the spec `domain` union so a new
|
|
15
|
+
* domain in the spec surfaces as a typecheck failure here rather than an
|
|
16
|
+
* unlabeled swatch.
|
|
17
|
+
*/
|
|
18
|
+
const DOMAIN_LABEL: Record<ForecastDomain, string> = {
|
|
19
|
+
western: 'Western',
|
|
20
|
+
vedic: 'Vedic',
|
|
21
|
+
biorhythm: 'Biorhythm',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const DOMAIN_ORDER: readonly ForecastDomain[] = [
|
|
25
|
+
'western',
|
|
26
|
+
'vedic',
|
|
27
|
+
'biorhythm',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cross-domain forecast timeline. Pass `data` from /forecast/timeline. Events
|
|
32
|
+
* are grouped by calendar date down a vertical axis, each colored by its domain
|
|
33
|
+
* and weighted by significance (0-100), showing the event type, body, target,
|
|
34
|
+
* aspect, and description. The visual language matches the dasha timeline: a
|
|
35
|
+
* left rail of dates, a significance bar per event, and a colored marker for
|
|
36
|
+
* the domain.
|
|
37
|
+
*
|
|
38
|
+
* Theming flows through `--roxy-*` custom properties on `:host`.
|
|
39
|
+
*/
|
|
40
|
+
@customElement('roxy-forecast-timeline')
|
|
41
|
+
export class RoxyForecastTimeline extends LitElement {
|
|
42
|
+
static styles = [
|
|
43
|
+
baseStyles,
|
|
44
|
+
css`
|
|
45
|
+
.wrap {
|
|
46
|
+
display: grid;
|
|
47
|
+
gap: var(--roxy-space-md, 1rem);
|
|
48
|
+
}
|
|
49
|
+
.head {
|
|
50
|
+
display: flex;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
align-items: baseline;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
gap: var(--roxy-space-sm, 0.5rem);
|
|
55
|
+
}
|
|
56
|
+
.title {
|
|
57
|
+
margin: 0;
|
|
58
|
+
font-size: var(--roxy-text-lg, 1.125rem);
|
|
59
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
60
|
+
}
|
|
61
|
+
.range {
|
|
62
|
+
color: var(--roxy-muted, #71717a);
|
|
63
|
+
font-size: var(--roxy-text-sm, 0.875rem);
|
|
64
|
+
font-variant-numeric: tabular-nums;
|
|
65
|
+
}
|
|
66
|
+
.legend {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-wrap: wrap;
|
|
69
|
+
gap: var(--roxy-space-md, 1rem);
|
|
70
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
71
|
+
color: var(--roxy-muted, #71717a);
|
|
72
|
+
}
|
|
73
|
+
.legend .swatch {
|
|
74
|
+
display: inline-block;
|
|
75
|
+
width: 10px;
|
|
76
|
+
height: 10px;
|
|
77
|
+
border-radius: 50%;
|
|
78
|
+
margin-right: 4px;
|
|
79
|
+
vertical-align: middle;
|
|
80
|
+
}
|
|
81
|
+
.swatch-western {
|
|
82
|
+
background: var(--roxy-accent, #f59e0b);
|
|
83
|
+
}
|
|
84
|
+
.swatch-vedic {
|
|
85
|
+
background: var(--roxy-info, #2563eb);
|
|
86
|
+
}
|
|
87
|
+
.swatch-biorhythm {
|
|
88
|
+
background: var(--roxy-success, #16a34a);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.day {
|
|
92
|
+
display: grid;
|
|
93
|
+
grid-template-columns: 4.5rem 1fr;
|
|
94
|
+
gap: var(--roxy-space-md, 1rem);
|
|
95
|
+
padding-block: var(--roxy-space-sm, 0.5rem);
|
|
96
|
+
border-top: 1px solid var(--roxy-border, #e4e4e7);
|
|
97
|
+
}
|
|
98
|
+
.day:first-of-type {
|
|
99
|
+
border-top: none;
|
|
100
|
+
}
|
|
101
|
+
.day-date {
|
|
102
|
+
color: var(--roxy-muted, #71717a);
|
|
103
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
104
|
+
font-variant-numeric: tabular-nums;
|
|
105
|
+
padding-top: 2px;
|
|
106
|
+
}
|
|
107
|
+
.events {
|
|
108
|
+
display: grid;
|
|
109
|
+
gap: var(--roxy-space-sm, 0.5rem);
|
|
110
|
+
}
|
|
111
|
+
.event {
|
|
112
|
+
display: grid;
|
|
113
|
+
grid-template-columns: 0.6rem 1fr;
|
|
114
|
+
gap: var(--roxy-space-sm, 0.5rem);
|
|
115
|
+
align-items: start;
|
|
116
|
+
}
|
|
117
|
+
.dot {
|
|
118
|
+
width: 0.6rem;
|
|
119
|
+
height: 0.6rem;
|
|
120
|
+
border-radius: 50%;
|
|
121
|
+
margin-top: 4px;
|
|
122
|
+
}
|
|
123
|
+
.dot-western {
|
|
124
|
+
background: var(--roxy-accent, #f59e0b);
|
|
125
|
+
}
|
|
126
|
+
.dot-vedic {
|
|
127
|
+
background: var(--roxy-info, #2563eb);
|
|
128
|
+
}
|
|
129
|
+
.dot-biorhythm {
|
|
130
|
+
background: var(--roxy-success, #16a34a);
|
|
131
|
+
}
|
|
132
|
+
.event-body {
|
|
133
|
+
min-width: 0;
|
|
134
|
+
}
|
|
135
|
+
.event-line {
|
|
136
|
+
font-size: var(--roxy-text-sm, 0.875rem);
|
|
137
|
+
color: var(--roxy-fg, #0a0a0a);
|
|
138
|
+
}
|
|
139
|
+
.event-line .aspect-trine,
|
|
140
|
+
.event-line .aspect-sextile {
|
|
141
|
+
color: var(--roxy-success-fg, #166534);
|
|
142
|
+
}
|
|
143
|
+
.event-line .aspect-square,
|
|
144
|
+
.event-line .aspect-opposition {
|
|
145
|
+
color: var(--roxy-danger-fg, #991b1b);
|
|
146
|
+
}
|
|
147
|
+
.event-line .aspect-conjunction {
|
|
148
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
149
|
+
}
|
|
150
|
+
.event-line .kind {
|
|
151
|
+
margin-left: 0.35em;
|
|
152
|
+
color: var(--roxy-muted, #71717a);
|
|
153
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
letter-spacing: 0.04em;
|
|
156
|
+
}
|
|
157
|
+
.event-desc {
|
|
158
|
+
color: var(--roxy-muted, #71717a);
|
|
159
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
160
|
+
margin: 2px 0 0;
|
|
161
|
+
}
|
|
162
|
+
.sig {
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: 0.4rem;
|
|
166
|
+
margin-top: 4px;
|
|
167
|
+
}
|
|
168
|
+
.sig-track {
|
|
169
|
+
flex: 1;
|
|
170
|
+
max-width: 8rem;
|
|
171
|
+
height: 4px;
|
|
172
|
+
background: var(--roxy-border, #e4e4e7);
|
|
173
|
+
border-radius: var(--roxy-radius-full, 9999px);
|
|
174
|
+
overflow: hidden;
|
|
175
|
+
}
|
|
176
|
+
.sig-fill {
|
|
177
|
+
display: block;
|
|
178
|
+
height: 100%;
|
|
179
|
+
border-radius: var(--roxy-radius-full, 9999px);
|
|
180
|
+
}
|
|
181
|
+
.sig-fill-western {
|
|
182
|
+
background: var(--roxy-accent, #f59e0b);
|
|
183
|
+
}
|
|
184
|
+
.sig-fill-vedic {
|
|
185
|
+
background: var(--roxy-info, #2563eb);
|
|
186
|
+
}
|
|
187
|
+
.sig-fill-biorhythm {
|
|
188
|
+
background: var(--roxy-success, #16a34a);
|
|
189
|
+
}
|
|
190
|
+
.sig-val {
|
|
191
|
+
color: var(--roxy-muted, #71717a);
|
|
192
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
193
|
+
font-variant-numeric: tabular-nums;
|
|
194
|
+
}
|
|
195
|
+
`,
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
constructor() {
|
|
199
|
+
super();
|
|
200
|
+
// Enables hydrating `data` from a direct-child
|
|
201
|
+
// <script type="application/json" class="roxy-data"> for server-rendered
|
|
202
|
+
// and cached consumers. The JavaScript `data` property still wins.
|
|
203
|
+
new MarkupDataController(this);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@property({ attribute: false })
|
|
207
|
+
data: GenerateTimelineResponse | null = null;
|
|
208
|
+
|
|
209
|
+
render() {
|
|
210
|
+
const d = this.data;
|
|
211
|
+
if (!d)
|
|
212
|
+
return html`<div class="roxy-empty" role="status">No forecast data</div>`;
|
|
213
|
+
|
|
214
|
+
const events = d.events ?? [];
|
|
215
|
+
const grouped = this.groupByDate(events);
|
|
216
|
+
const present = DOMAIN_ORDER.filter((dom) =>
|
|
217
|
+
events.some((e) => e.domain === dom),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return html`<div class="wrap" aria-label="Forecast timeline">
|
|
221
|
+
<header class="head">
|
|
222
|
+
<h2 class="title">Forecast timeline</h2>
|
|
223
|
+
${
|
|
224
|
+
d.startDate && d.endDate
|
|
225
|
+
? html`<div class="range">
|
|
226
|
+
${formatDate(d.startDate)} - ${formatDate(d.endDate)} · ${d.count ?? events.length} events
|
|
227
|
+
</div>`
|
|
228
|
+
: nothing
|
|
229
|
+
}
|
|
230
|
+
</header>
|
|
231
|
+
${
|
|
232
|
+
present.length
|
|
233
|
+
? html`<div class="legend">
|
|
234
|
+
${present.map(
|
|
235
|
+
(dom) =>
|
|
236
|
+
html`<span><span class="swatch swatch-${dom}"></span>${DOMAIN_LABEL[dom]}</span>`,
|
|
237
|
+
)}
|
|
238
|
+
</div>`
|
|
239
|
+
: nothing
|
|
240
|
+
}
|
|
241
|
+
${
|
|
242
|
+
grouped.length
|
|
243
|
+
? html`<div class="days" role="list">
|
|
244
|
+
${grouped.map(([date, dayEvents]) => this.renderDay(date, dayEvents))}
|
|
245
|
+
</div>`
|
|
246
|
+
: html`<p class="roxy-empty" role="status">No events in this window</p>`
|
|
247
|
+
}
|
|
248
|
+
</div>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Group events by their calendar date, preserving response order. */
|
|
252
|
+
private groupByDate(
|
|
253
|
+
events: ForecastEvent[],
|
|
254
|
+
): Array<[string, ForecastEvent[]]> {
|
|
255
|
+
const map = new Map<string, ForecastEvent[]>();
|
|
256
|
+
for (const e of events) {
|
|
257
|
+
const key = e.date ?? '';
|
|
258
|
+
const list = map.get(key) ?? [];
|
|
259
|
+
list.push(e);
|
|
260
|
+
map.set(key, list);
|
|
261
|
+
}
|
|
262
|
+
return [...map.entries()];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private renderDay(date: string, events: ForecastEvent[]) {
|
|
266
|
+
return html`<div class="day" role="listitem">
|
|
267
|
+
<div class="day-date">${formatDate(date)}</div>
|
|
268
|
+
<div class="events">
|
|
269
|
+
${events.map((e) => this.renderEvent(e))}
|
|
270
|
+
</div>
|
|
271
|
+
</div>`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private renderEvent(e: ForecastEvent) {
|
|
275
|
+
const sig = typeof e.significance === 'number' ? e.significance : 0;
|
|
276
|
+
const width = Math.max(0, Math.min(100, sig));
|
|
277
|
+
return html`<div class="event">
|
|
278
|
+
<span class="dot dot-${e.domain}" aria-hidden="true"></span>
|
|
279
|
+
<div class="event-body">
|
|
280
|
+
<div class="event-line">${this.renderHeadline(e)}</div>
|
|
281
|
+
${e.description ? html`<p class="event-desc">${e.description}</p>` : nothing}
|
|
282
|
+
<div class="sig" title="Significance ${width} of 100">
|
|
283
|
+
<span class="sig-track">
|
|
284
|
+
<span class="sig-fill sig-fill-${e.domain}" style="width: ${width}%"></span>
|
|
285
|
+
</span>
|
|
286
|
+
<span class="sig-val">${width}</span>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Compact event headline built from the spec fields. Transit aspects read
|
|
294
|
+
* "body aspect target"; ingresses, stations, eclipses, dasha changes, and
|
|
295
|
+
* critical days fall back to "body" plus the qualifier the spec carries for
|
|
296
|
+
* that type (station direction, eclipse kind). The aspect symbol is colored
|
|
297
|
+
* by nature, matching the chart aspect encoding.
|
|
298
|
+
*/
|
|
299
|
+
private renderHeadline(e: ForecastEvent) {
|
|
300
|
+
const body = e.body ? capitalize(e.body) : '';
|
|
301
|
+
const target = e.target ? capitalize(e.target) : '';
|
|
302
|
+
const aspect = e.aspect ? e.aspect.toLowerCase() : '';
|
|
303
|
+
const aspectClass = ASPECT_CLASS[aspect] ?? '';
|
|
304
|
+
const aspectSym = aspect ? (ASPECT_SYMBOL[aspect] ?? aspect) : '';
|
|
305
|
+
const orb = typeof e.orb === 'number' ? formatNumber(e.orb, 1) : '';
|
|
306
|
+
const qualifier = this.typeQualifier(e);
|
|
307
|
+
|
|
308
|
+
if (aspect && target) {
|
|
309
|
+
return html`<strong>${body}</strong>
|
|
310
|
+
<span class=${aspectClass}>${aspectSym}</span>
|
|
311
|
+
<strong>${target}</strong>${orb ? html` <span class="kind">orb ${orb}°</span>` : nothing}`;
|
|
312
|
+
}
|
|
313
|
+
return html`<strong>${body || humanize(e.type ?? '')}</strong>${
|
|
314
|
+
qualifier ? html` <span class="kind">${qualifier}</span>` : nothing
|
|
315
|
+
}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Type-specific qualifier text from the optional spec fields. */
|
|
319
|
+
private typeQualifier(e: ForecastEvent): string {
|
|
320
|
+
if (e.type === 'sign-ingress' && e.target)
|
|
321
|
+
return `enters ${capitalize(e.target)}`;
|
|
322
|
+
if (e.type === 'retrograde-station' && e.station) return e.station;
|
|
323
|
+
if (e.type === 'eclipse')
|
|
324
|
+
return [e.kind, 'eclipse'].filter(Boolean).join(' ');
|
|
325
|
+
if (e.type === 'critical-day') return 'critical day';
|
|
326
|
+
if (e.type === 'dasha-change' && e.target)
|
|
327
|
+
return `dasha ${capitalize(e.target)}`;
|
|
328
|
+
return humanize(e.type ?? '');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
declare global {
|
|
333
|
+
interface HTMLElementTagNameMap {
|
|
334
|
+
'roxy-forecast-timeline': RoxyForecastTimeline;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -51,7 +51,7 @@ export class RoxyGunaMilan extends LitElement {
|
|
|
51
51
|
.total {
|
|
52
52
|
font-size: 2.25rem;
|
|
53
53
|
font-weight: var(--roxy-weight-bold, 600);
|
|
54
|
-
color: var(--roxy-accent-
|
|
54
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
55
55
|
font-variant-numeric: tabular-nums;
|
|
56
56
|
line-height: 1;
|
|
57
57
|
}
|
|
@@ -53,7 +53,7 @@ export class RoxyHexagram extends LitElement {
|
|
|
53
53
|
.symbol {
|
|
54
54
|
font-size: 3rem;
|
|
55
55
|
line-height: 1;
|
|
56
|
-
color: var(--roxy-accent-
|
|
56
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
57
57
|
}
|
|
58
58
|
.lines {
|
|
59
59
|
display: grid;
|
|
@@ -105,7 +105,7 @@ export class RoxyHexagram extends LitElement {
|
|
|
105
105
|
}
|
|
106
106
|
.tri-glyph {
|
|
107
107
|
font-size: var(--roxy-text-xl, 1.5rem);
|
|
108
|
-
color: var(--roxy-accent-
|
|
108
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
109
109
|
margin-right: 4px;
|
|
110
110
|
vertical-align: middle;
|
|
111
111
|
}
|
|
@@ -131,7 +131,7 @@ export class RoxyHexagram extends LitElement {
|
|
|
131
131
|
margin-top: var(--roxy-space-md, 1rem);
|
|
132
132
|
padding-top: var(--roxy-space-md, 1rem);
|
|
133
133
|
border-top: 1px solid var(--roxy-border, #e4e4e7);
|
|
134
|
-
color: var(--roxy-accent-
|
|
134
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
135
135
|
font-size: var(--roxy-text-sm, 0.875rem);
|
|
136
136
|
}
|
|
137
137
|
`,
|
|
@@ -76,7 +76,7 @@ export class RoxyKpChart extends LitElement {
|
|
|
76
76
|
font-family: inherit;
|
|
77
77
|
}
|
|
78
78
|
.tab[aria-selected='true'] {
|
|
79
|
-
color: var(--roxy-accent-
|
|
79
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
80
80
|
border-bottom-color: var(--roxy-accent, #f59e0b);
|
|
81
81
|
font-weight: var(--roxy-weight-bold, 600);
|
|
82
82
|
}
|
|
@@ -50,7 +50,7 @@ export class RoxyLocationSearch extends LitElement {
|
|
|
50
50
|
input:focus {
|
|
51
51
|
outline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));
|
|
52
52
|
outline-offset: 2px;
|
|
53
|
-
border-color: var(--roxy-accent-
|
|
53
|
+
border-color: var(--roxy-accent-ink, #b45309);
|
|
54
54
|
}
|
|
55
55
|
.spinner {
|
|
56
56
|
position: absolute;
|
|
@@ -35,7 +35,7 @@ export class RoxyNakshatraCard extends LitElement {
|
|
|
35
35
|
font-weight: var(--roxy-weight-bold, 600);
|
|
36
36
|
}
|
|
37
37
|
.number {
|
|
38
|
-
color: var(--roxy-accent-
|
|
38
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
39
39
|
font-size: var(--roxy-text-sm, 0.875rem);
|
|
40
40
|
font-weight: var(--roxy-weight-bold, 600);
|
|
41
41
|
}
|
|
@@ -159,7 +159,7 @@ export class RoxyNatalChart extends LitElement {
|
|
|
159
159
|
stroke: var(--roxy-danger, #dc2626);
|
|
160
160
|
}
|
|
161
161
|
.aspect-conjunction {
|
|
162
|
-
stroke: var(--roxy-accent-
|
|
162
|
+
stroke: var(--roxy-accent-ink, #b45309);
|
|
163
163
|
}
|
|
164
164
|
.aspect-other {
|
|
165
165
|
stroke: var(--roxy-muted, #71717a);
|
|
@@ -167,14 +167,14 @@ export class RoxyNatalChart extends LitElement {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
.angle-marker {
|
|
170
|
-
fill: var(--roxy-accent-
|
|
170
|
+
fill: var(--roxy-accent-ink, #b45309);
|
|
171
171
|
font-size: 10px;
|
|
172
172
|
font-weight: 700;
|
|
173
173
|
font-family: var(--roxy-font-sans);
|
|
174
174
|
letter-spacing: 0.04em;
|
|
175
175
|
}
|
|
176
176
|
.angle-tick {
|
|
177
|
-
stroke: var(--roxy-accent-
|
|
177
|
+
stroke: var(--roxy-accent-ink, #b45309);
|
|
178
178
|
stroke-width: 1.5;
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -234,7 +234,7 @@ export class RoxyNatalChart extends LitElement {
|
|
|
234
234
|
color: var(--roxy-danger, #dc2626);
|
|
235
235
|
}
|
|
236
236
|
table.aspect-grid td.aspect-conjunction .asp {
|
|
237
|
-
color: var(--roxy-accent-
|
|
237
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
238
238
|
}
|
|
239
239
|
table.aspect-grid td.aspect-other .asp {
|
|
240
240
|
color: var(--roxy-muted, #71717a);
|
|
@@ -363,7 +363,7 @@ export class RoxyNatalChart extends LitElement {
|
|
|
363
363
|
padding: 1px 8px;
|
|
364
364
|
border-radius: 9999px;
|
|
365
365
|
background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
|
|
366
|
-
color: var(--roxy-accent-
|
|
366
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
367
367
|
font-size: var(--roxy-text-xs, 0.75rem);
|
|
368
368
|
}
|
|
369
369
|
`,
|
|
@@ -44,7 +44,7 @@ export class RoxyNumerologyCard extends LitElement {
|
|
|
44
44
|
font-size: 4rem;
|
|
45
45
|
line-height: 1;
|
|
46
46
|
font-weight: var(--roxy-weight-bold, 600);
|
|
47
|
-
color: var(--roxy-accent-
|
|
47
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
48
48
|
font-variant-numeric: tabular-nums;
|
|
49
49
|
}
|
|
50
50
|
.label {
|
|
@@ -106,7 +106,7 @@ export class RoxyNumerologyCard extends LitElement {
|
|
|
106
106
|
text-transform: capitalize;
|
|
107
107
|
}
|
|
108
108
|
.cores .item strong {
|
|
109
|
-
color: var(--roxy-accent-
|
|
109
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
110
110
|
font-variant-numeric: tabular-nums;
|
|
111
111
|
font-size: var(--roxy-text-base, 1rem);
|
|
112
112
|
font-weight: var(--roxy-weight-bold, 600);
|
|
@@ -11,7 +11,18 @@ import { MarkupDataController } from '../utils/markup-data.js';
|
|
|
11
11
|
type PanchangData = GetBasicPanchangResponse | GetDetailedPanchangResponse;
|
|
12
12
|
type PanchangTime = GetDetailedPanchangResponse['rahuKaal'];
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Panchang table for /vedic-astrology/panchang/{basic,detailed}.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* The main grid lists the five limbs (tithi, nakshatra, yoga, karana, vara),
|
|
19
|
+
* sun and moon timings, and, in detailed mode, the sunrise placements a reader
|
|
20
|
+
* scans first: Moon rashi, Sun rashi, Sun nakshatra, and the current hora. The
|
|
21
|
+
* detailed mode then groups every timed window into two sections, auspicious
|
|
22
|
+
* (the fixed muhurtas plus each Amrit Kalam) and inauspicious (Rahu Kaal,
|
|
23
|
+
* Yamaganda, Gulika, plus each Dur Muhurta and Varjyam), so a consumer can act
|
|
24
|
+
* on timing without parsing the raw response.
|
|
25
|
+
*/
|
|
15
26
|
@customElement('roxy-panchang-table')
|
|
16
27
|
export class RoxyPanchangTable extends LitElement {
|
|
17
28
|
static styles = [
|
|
@@ -105,6 +116,15 @@ export class RoxyPanchangTable extends LitElement {
|
|
|
105
116
|
];
|
|
106
117
|
if (detailed) fivefold.push(['Vara', this.formatPart(detailed.vara)]);
|
|
107
118
|
|
|
119
|
+
const placements: Array<[string, string]> = detailed
|
|
120
|
+
? [
|
|
121
|
+
['Moon sign', this.formatRashi(detailed.moonSign)],
|
|
122
|
+
['Sun sign', this.formatRashi(detailed.sunSign)],
|
|
123
|
+
['Sun nakshatra', this.formatSunNakshatra(detailed.sunNakshatra)],
|
|
124
|
+
['Hora', this.formatHora(detailed.hora)],
|
|
125
|
+
].filter((row): row is [string, string] => Boolean(row[1]))
|
|
126
|
+
: [];
|
|
127
|
+
|
|
108
128
|
const muhurtas: Array<[string, PanchangTime | undefined]> = detailed
|
|
109
129
|
? [
|
|
110
130
|
['Brahma Muhurta', detailed.brahmaMuhurta],
|
|
@@ -117,6 +137,10 @@ export class RoxyPanchangTable extends LitElement {
|
|
|
117
137
|
]
|
|
118
138
|
: [];
|
|
119
139
|
|
|
140
|
+
const auspiciousWindows: Array<[string, PanchangTime]> = detailed
|
|
141
|
+
? this.expandWindows('Amrit Kalam', detailed.amritKalam)
|
|
142
|
+
: [];
|
|
143
|
+
|
|
120
144
|
const inauspicious: Array<[string, PanchangTime | undefined]> = detailed
|
|
121
145
|
? [
|
|
122
146
|
['Rahu Kaal', detailed.rahuKaal],
|
|
@@ -125,6 +149,13 @@ export class RoxyPanchangTable extends LitElement {
|
|
|
125
149
|
]
|
|
126
150
|
: [];
|
|
127
151
|
|
|
152
|
+
const inauspiciousWindows: Array<[string, PanchangTime]> = detailed
|
|
153
|
+
? [
|
|
154
|
+
...this.expandWindows('Dur Muhurta', detailed.durMuhurta),
|
|
155
|
+
...this.expandWindows('Varjyam', detailed.varjyam),
|
|
156
|
+
]
|
|
157
|
+
: [];
|
|
158
|
+
|
|
128
159
|
const transitions =
|
|
129
160
|
detailed && 'transitions' in detailed ? detailed.transitions : undefined;
|
|
130
161
|
|
|
@@ -173,6 +204,12 @@ export class RoxyPanchangTable extends LitElement {
|
|
|
173
204
|
</tr>`
|
|
174
205
|
: nothing
|
|
175
206
|
}
|
|
207
|
+
${placements.map(
|
|
208
|
+
([k, v]) => html`<tr>
|
|
209
|
+
<th>${k}</th>
|
|
210
|
+
<td>${v}</td>
|
|
211
|
+
</tr>`,
|
|
212
|
+
)}
|
|
176
213
|
</tbody>
|
|
177
214
|
</table>
|
|
178
215
|
${
|
|
@@ -192,32 +229,33 @@ export class RoxyPanchangTable extends LitElement {
|
|
|
192
229
|
}
|
|
193
230
|
${
|
|
194
231
|
this.detail === 'detailed' &&
|
|
195
|
-
(
|
|
232
|
+
(
|
|
233
|
+
muhurtas.some((m) => !!m[1]) ||
|
|
234
|
+
auspiciousWindows.length > 0 ||
|
|
235
|
+
inauspicious.some((m) => !!m[1]) ||
|
|
236
|
+
inauspiciousWindows.length > 0
|
|
237
|
+
)
|
|
196
238
|
? html`
|
|
197
239
|
<div class="section">Auspicious muhurtas</div>
|
|
198
240
|
<table>
|
|
199
241
|
<tbody>
|
|
200
|
-
${
|
|
201
|
-
.filter(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
</tr>`,
|
|
207
|
-
)}
|
|
242
|
+
${this.renderPeriodRows([
|
|
243
|
+
...muhurtas.filter(
|
|
244
|
+
(m): m is [string, PanchangTime] => !!m[1],
|
|
245
|
+
),
|
|
246
|
+
...auspiciousWindows,
|
|
247
|
+
])}
|
|
208
248
|
</tbody>
|
|
209
249
|
</table>
|
|
210
250
|
<div class="section">Inauspicious periods</div>
|
|
211
251
|
<table>
|
|
212
252
|
<tbody>
|
|
213
|
-
${
|
|
214
|
-
.filter(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
</tr>`,
|
|
220
|
-
)}
|
|
253
|
+
${this.renderPeriodRows([
|
|
254
|
+
...inauspicious.filter(
|
|
255
|
+
(m): m is [string, PanchangTime] => !!m[1],
|
|
256
|
+
),
|
|
257
|
+
...inauspiciousWindows,
|
|
258
|
+
])}
|
|
221
259
|
</tbody>
|
|
222
260
|
</table>
|
|
223
261
|
`
|
|
@@ -226,6 +264,31 @@ export class RoxyPanchangTable extends LitElement {
|
|
|
226
264
|
</div>`;
|
|
227
265
|
}
|
|
228
266
|
|
|
267
|
+
/** Renders one row per [label, period] pair, dropping any with no range. */
|
|
268
|
+
private renderPeriodRows(rows: Array<[string, PanchangTime]>) {
|
|
269
|
+
return rows.map(([k, v]) => {
|
|
270
|
+
const range = formatTimeRange(v);
|
|
271
|
+
return range
|
|
272
|
+
? html`<tr>
|
|
273
|
+
<th>${k}</th>
|
|
274
|
+
<td>${range}</td>
|
|
275
|
+
</tr>`
|
|
276
|
+
: nothing;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Expands an array of periods into labeled rows, numbering when more than one. */
|
|
281
|
+
private expandWindows(
|
|
282
|
+
label: string,
|
|
283
|
+
windows: PanchangTime[] | undefined,
|
|
284
|
+
): Array<[string, PanchangTime]> {
|
|
285
|
+
if (!windows || windows.length === 0) return [];
|
|
286
|
+
return windows.map((w, i) => [
|
|
287
|
+
windows.length > 1 ? `${label} ${i + 1}` : label,
|
|
288
|
+
w,
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
|
|
229
292
|
private renderTransitionRow(
|
|
230
293
|
label: string,
|
|
231
294
|
t: { endsAt?: string; next?: string } | undefined,
|
|
@@ -260,8 +323,39 @@ export class RoxyPanchangTable extends LitElement {
|
|
|
260
323
|
}
|
|
261
324
|
return String(v);
|
|
262
325
|
}
|
|
326
|
+
|
|
327
|
+
/** "English (Sanskrit)" label for the Moon or Sun rashi at sunrise. */
|
|
328
|
+
private formatRashi(r: RashiPlacement | undefined): string {
|
|
329
|
+
if (!r?.name) return '';
|
|
330
|
+
return r.sanskritName && r.sanskritName !== r.name
|
|
331
|
+
? `${r.name} (${r.sanskritName})`
|
|
332
|
+
: r.name;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Sun nakshatra with pada and lord, the form a panchang reader expects. */
|
|
336
|
+
private formatSunNakshatra(n: SunNakshatra | undefined): string {
|
|
337
|
+
if (!n?.name) return '';
|
|
338
|
+
const parts = [
|
|
339
|
+
n.name,
|
|
340
|
+
typeof n.pada === 'number' ? `pada ${n.pada}` : '',
|
|
341
|
+
n.lord ? `· ${n.lord}` : '',
|
|
342
|
+
].filter(Boolean);
|
|
343
|
+
return parts.join(' ');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Current planetary hora with its active window. */
|
|
347
|
+
private formatHora(h: Hora | undefined): string {
|
|
348
|
+
if (!h?.current) return '';
|
|
349
|
+
const range = formatTimeRange(h);
|
|
350
|
+
return range ? `${h.current} (${range})` : h.current;
|
|
351
|
+
}
|
|
263
352
|
}
|
|
264
353
|
|
|
354
|
+
type PanchangPlacements = GetDetailedPanchangResponse;
|
|
355
|
+
type RashiPlacement = PanchangPlacements['moonSign'];
|
|
356
|
+
type SunNakshatra = PanchangPlacements['sunNakshatra'];
|
|
357
|
+
type Hora = PanchangPlacements['hora'];
|
|
358
|
+
|
|
265
359
|
declare global {
|
|
266
360
|
interface HTMLElementTagNameMap {
|
|
267
361
|
'roxy-panchang-table': RoxyPanchangTable;
|
|
@@ -149,7 +149,7 @@ export class RoxyShadbalaTable extends LitElement {
|
|
|
149
149
|
|
|
150
150
|
.rank-badge {
|
|
151
151
|
font-size: var(--roxy-text-xs, 0.75rem);
|
|
152
|
-
color: var(--roxy-accent-
|
|
152
|
+
color: var(--roxy-accent-ink, #b45309);
|
|
153
153
|
font-weight: var(--roxy-weight-bold, 600);
|
|
154
154
|
}
|
|
155
155
|
|