Single-binary self-hosted market watcher for stocks, ETFs, indexes, and futures: live charts, key stats, fundamentals, SEC filings, and SSE streaming.
axumdockerfinancerustself-hostedsqlitestocksvite
1@use "../../base/styles/variables" as *;
2@use "../../base/styles/mixins" as *;
3
4/* ---------- page intro ---------- */
5.health-lede {
6 max-width: 64ch;
7 margin: 10px 0 2px;
8 color: var(--ink-dim);
9 font-size: 0.9rem;
10}
11
12.health-asof {
13 @include eyebrow;
14 font-size: 0.6rem;
15}
16
17/* ---------- systems verdict (Phase 7) ----------
18 The distilled top read: a coloured dot + headline + one-line clause. Tones
19 reuse the semantic good/ok/bad tokens; filled live by the page script. */
20.health-verdict {
21 display: flex;
22 align-items: center;
23 gap: 14px;
24 margin: 18px 0 6px;
25 padding: 14px 16px;
26 border: 1px solid var(--rule);
27 border-radius: var(--radius);
28 background: var(--surface);
29}
30
31.health-verdict[hidden] {
32 display: none;
33}
34
35.health-verdict__dot {
36 flex: none;
37 width: 11px;
38 height: 11px;
39 border-radius: 50%;
40 background: var(--ink-faint);
41}
42
43.health-verdict__head {
44 margin: 0;
45 font-size: 1.05rem;
46 font-weight: 600;
47 letter-spacing: -0.01em;
48}
49
50.health-verdict__detail {
51 margin: 2px 0 0;
52 color: var(--ink-dim);
53 font-size: 0.85rem;
54}
55
56.health-verdict[data-tone="ok"] {
57 border-color: var(--up-soft);
58
59 .health-verdict__dot { background: var(--up); }
60}
61
62.health-verdict[data-tone="warn"] {
63 border-color: var(--warn);
64 background: var(--warn-soft);
65
66 .health-verdict__dot { background: var(--warn); animation: pulse 1.2s ease-in-out infinite; }
67}
68
69.health-verdict[data-tone="bad"] {
70 border-color: var(--down);
71 background: var(--down-soft);
72
73 .health-verdict__dot { background: var(--down); }
74}
75
76/* ---------- "fetching now" banner ---------- */
77.health-banner {
78 display: flex;
79 align-items: center;
80 gap: 10px;
81 margin: 16px 0 2px;
82 padding: 11px 14px;
83 border: 1px solid var(--warn);
84 border-radius: var(--radius);
85 background: var(--warn-soft);
86 font-weight: 500;
87 font-size: 0.9rem;
88}
89
90/* `.health-banner` sets display:flex, which would override the [hidden]
91 attribute's display:none — so re-assert it explicitly. */
92.health-banner[hidden] {
93 display: none;
94}
95
96.health-banner__dot {
97 flex: none;
98 width: 9px;
99 height: 9px;
100 border-radius: 50%;
101 background: var(--warn);
102 animation: pulse 1.2s ease-in-out infinite;
103}
104
105/* ---------- status badge ---------- */
106.badge {
107 @include eyebrow;
108 flex: none;
109 padding: 3px 8px;
110 border-radius: 999px;
111 font-size: 0.56rem;
112}
113
114.badge--ok {
115 color: var(--up);
116 background: var(--up-soft);
117}
118
119.badge--bad {
120 color: var(--down);
121 background: var(--down-soft);
122}
123
124.badge--warn {
125 color: var(--warn);
126 background: var(--warn-soft);
127}
128
129.badge--idle {
130 color: var(--ink-faint);
131 background: var(--well);
132}
133
134/* ---------- endpoints ----------
135 Phase 31: an explicit 3-up at desktop, 2-up at tablet, 1-up at phone,
136 so the SEC / Stooq / Yahoo cards always read in a tidy grid rather
137 than the previous auto-fit which dropped to 1-up at narrow desktops. */
138.endpoint-grid {
139 display: grid;
140 grid-template-columns: 1fr;
141 gap: var(--sp-3);
142}
143
144@media (min-width: $bp-sm) {
145 .endpoint-grid {
146 grid-template-columns: repeat(2, minmax(0, 1fr));
147 }
148}
149
150@media (min-width: $bp-md) {
151 .endpoint-grid {
152 grid-template-columns: repeat(3, minmax(0, 1fr));
153 }
154}
155
156.endpoint {
157 @include card;
158 padding: var(--sp-4);
159}
160
161.endpoint__head,
162.job__head {
163 display: flex;
164 align-items: baseline;
165 justify-content: space-between;
166 gap: 12px;
167}
168
169.endpoint__head h3,
170.job__id h3 {
171 font-size: 1rem;
172}
173
174/* request-budget meter */
175.meter {
176 margin: 16px 0 2px;
177}
178
179.meter__row {
180 display: flex;
181 align-items: baseline;
182 justify-content: space-between;
183 gap: 10px;
184 margin-bottom: 7px;
185}
186
187.meter__count {
188 font-size: 0.82rem;
189 font-weight: 600;
190}
191
192.meter__track {
193 height: 8px;
194}
195
196/* budget fill warms then reddens as the hourly ceiling nears */
197.track__fill--warn {
198 background: var(--warn);
199}
200
201.track__fill--bad {
202 background: var(--down);
203}
204
205.meter__note {
206 margin-top: 7px;
207 color: var(--ink-faint);
208 font-size: 0.74rem;
209}
210
211/* ---------- key-value rows (shared by endpoints and jobs) ---------- */
212.kvs {
213 margin-top: 12px;
214}
215
216.kv {
217 display: flex;
218 align-items: baseline;
219 justify-content: space-between;
220 gap: 12px;
221 padding: 7px 0;
222 border-top: 1px solid var(--rule);
223 font-size: 0.85rem;
224}
225
226.kv dt {
227 color: var(--ink-dim);
228}
229
230.kv dd {
231 font-weight: 600;
232 text-align: right;
233}
234
235.kv--err {
236 flex-direction: column;
237 align-items: stretch;
238}
239
240.kv--err dd {
241 font-weight: 400;
242 text-align: left;
243 color: var(--down);
244 word-break: break-word;
245}
246
247.kv--err .muted {
248 color: var(--ink-faint);
249 white-space: nowrap;
250}
251
252/* ---------- background jobs ---------- */
253.job-list {
254 display: flex;
255 flex-direction: column;
256 gap: 10px;
257}
258
259.job {
260 @include card;
261 padding: 14px 16px;
262}
263
264/* a job mid-fetch is rimmed in the working amber, echoing the banner */
265.job--active {
266 border-color: var(--warn);
267}
268
269.job__id p {
270 margin-top: 3px;
271 color: var(--ink-dim);
272 font-size: 0.8rem;
273}
274
275/* ---------- activity log ---------- */
276.log {
277 @include card;
278 padding: 2px 14px;
279}
280
281.logrows {
282 margin: 0;
283 padding: 0;
284 list-style: none;
285}
286
287.logrow {
288 display: flex;
289 align-items: baseline;
290 flex-wrap: wrap;
291 gap: var(--sp-1) var(--sp-3);
292 padding: 8px 0;
293 border-top: 1px solid var(--rule);
294 font-size: var(--fs-sm);
295 /* routine `ok` rows mute their entire body; the time, job, detail and
296 meta all drop a tier so the eye glides past them and lands on
297 anything coloured. */
298 color: var(--ink-faint);
299}
300
301.logrow:first-child {
302 border-top: none;
303}
304
305.logrow__time {
306 @include mono;
307 color: var(--ink-faint);
308 font-size: var(--fs-xs);
309}
310
311.logrow__job {
312 @include mono;
313 min-width: 6ch;
314 font-weight: 600;
315 color: var(--ink-dim);
316 font-size: var(--fs-xs);
317 text-transform: uppercase;
318 letter-spacing: 0.05em;
319}
320
321.logrow__detail {
322 flex: 1 1 14ch;
323 min-width: 0;
324 color: var(--ink-dim);
325}
326
327/* a failed fetch should catch the eye down the tail; the row's job pill
328 and detail both pop, the time/meta stay quiet. */
329.logrow--bad .logrow__job {
330 color: var(--down);
331}
332
333.logrow--bad .logrow__detail {
334 color: var(--down);
335 font-weight: 500;
336}
337
338.logrow__meta {
339 color: var(--ink-faint);
340 font-size: var(--fs-2xs);
341}
342
343/* ---------- empty states ---------- */
344.health-empty {
345 @include card;
346 padding: 22px;
347 color: var(--ink-dim);
348 font-size: 0.9rem;
349 text-align: center;
350}
351
352/* ---------- desktop ---------- */
353@media (min-width: $bp-md) {
354 .logrow__job {
355 min-width: 9ch;
356 }
357}