htmlineage 0.0.0 → 0.1.0-beta.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 (49) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +109 -3
  3. package/index.js +4 -0
  4. package/package.json +53 -3
  5. package/skills/htmlineage/SKILL.md +173 -0
  6. package/skills/htmlineage/references/example/.lineage/august-retention.json +167 -0
  7. package/skills/htmlineage/references/example/README.md +51 -0
  8. package/skills/htmlineage/references/example/august-retention.html +111 -0
  9. package/skills/htmlineage/references/format.md +218 -0
  10. package/skills/htmlineage/references/review.md +79 -0
  11. package/src/check/rules.js +421 -0
  12. package/src/check/summary.js +154 -0
  13. package/src/cli.js +99 -0
  14. package/src/commands/check.js +74 -0
  15. package/src/commands/index.js +7 -0
  16. package/src/commands/sql.js +65 -0
  17. package/src/commands/view.js +82 -0
  18. package/src/errors.js +39 -0
  19. package/src/graph/duplicates.js +78 -0
  20. package/src/graph/expr.js +56 -0
  21. package/src/graph/ids.js +24 -0
  22. package/src/graph/numbers.js +61 -0
  23. package/src/graph/parse.js +196 -0
  24. package/src/graph/schema.js +491 -0
  25. package/src/graph/trace.js +45 -0
  26. package/src/help.js +25 -0
  27. package/src/html/embed.js +142 -0
  28. package/src/html/page.js +101 -0
  29. package/src/meta.js +13 -0
  30. package/src/sql/compose.js +215 -0
  31. package/src/sql/style.js +193 -0
  32. package/src/viewer/client/bridge.js +143 -0
  33. package/src/viewer/client/favicon.svg +6 -0
  34. package/src/viewer/client/fonts/JetBrainsMono-Bold.woff2 +0 -0
  35. package/src/viewer/client/fonts/JetBrainsMono-Medium.woff2 +0 -0
  36. package/src/viewer/client/fonts/JetBrainsMono-Regular.woff2 +0 -0
  37. package/src/viewer/client/fonts/JetBrainsMono-SemiBold.woff2 +0 -0
  38. package/src/viewer/client/fonts/OFL-JetBrainsMono.txt +93 -0
  39. package/src/viewer/client/fonts/OFL-SourceSerif4.txt +93 -0
  40. package/src/viewer/client/fonts/SourceSerif4-Medium.woff2 +0 -0
  41. package/src/viewer/client/fonts/SourceSerif4-Regular.woff2 +0 -0
  42. package/src/viewer/client/fonts/SourceSerif4-SemiBold.woff2 +0 -0
  43. package/src/viewer/client/index.html +95 -0
  44. package/src/viewer/client/lineage.js +674 -0
  45. package/src/viewer/client/themes.css +76 -0
  46. package/src/viewer/client/viewer.css +1420 -0
  47. package/src/viewer/client/viewer.js +765 -0
  48. package/src/viewer/payload.js +45 -0
  49. package/src/viewer/server.js +188 -0
@@ -0,0 +1,1420 @@
1
+ /* htmlineage viewer: every rule for the rail and the lineage panel. Colours
2
+ come from the Ledger tokens in themes.css, so both modes share one
3
+ rulebook. The report pane is never styled from here.
4
+
5
+ Borders and space separate things; colour is kept for the few marks that
6
+ mean something (the accent for "you are here", amber and red for trouble),
7
+ and those always come with words or a shape as well. */
8
+
9
+ /* ---------- fonts: shipped with the package, never fetched ---------- */
10
+ @font-face {
11
+ font-family: "Source Serif 4";
12
+ font-weight: 400;
13
+ font-style: normal;
14
+ font-display: swap;
15
+ src: url("/assets/fonts/SourceSerif4-Regular.woff2") format("woff2");
16
+ }
17
+ @font-face {
18
+ font-family: "Source Serif 4";
19
+ font-weight: 500;
20
+ font-style: normal;
21
+ font-display: swap;
22
+ src: url("/assets/fonts/SourceSerif4-Medium.woff2") format("woff2");
23
+ }
24
+ @font-face {
25
+ font-family: "Source Serif 4";
26
+ font-weight: 600;
27
+ font-style: normal;
28
+ font-display: swap;
29
+ src: url("/assets/fonts/SourceSerif4-SemiBold.woff2") format("woff2");
30
+ }
31
+ @font-face {
32
+ font-family: "JetBrains Mono";
33
+ font-weight: 400;
34
+ font-style: normal;
35
+ font-display: swap;
36
+ src: url("/assets/fonts/JetBrainsMono-Regular.woff2") format("woff2");
37
+ }
38
+ @font-face {
39
+ font-family: "JetBrains Mono";
40
+ font-weight: 500;
41
+ font-style: normal;
42
+ font-display: swap;
43
+ src: url("/assets/fonts/JetBrainsMono-Medium.woff2") format("woff2");
44
+ }
45
+ @font-face {
46
+ font-family: "JetBrains Mono";
47
+ font-weight: 600;
48
+ font-style: normal;
49
+ font-display: swap;
50
+ src: url("/assets/fonts/JetBrainsMono-SemiBold.woff2") format("woff2");
51
+ }
52
+ @font-face {
53
+ font-family: "JetBrains Mono";
54
+ font-weight: 700;
55
+ font-style: normal;
56
+ font-display: swap;
57
+ src: url("/assets/fonts/JetBrainsMono-Bold.woff2") format("woff2");
58
+ }
59
+
60
+ :root {
61
+ --f-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
62
+ --f-serif: "Source Serif 4", "Iowan Old Style", Georgia, serif;
63
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
64
+ --rail-h: 52px;
65
+ --nav-h: 48px;
66
+ --panel-w: 580px;
67
+ --gut: 28px; /* panel gutter */
68
+ --r-card: 8px;
69
+ --r-ctl: 6px;
70
+ }
71
+
72
+ /* ---------- base ---------- */
73
+ *,
74
+ *::before,
75
+ *::after {
76
+ box-sizing: border-box;
77
+ }
78
+ html,
79
+ body {
80
+ height: 100%;
81
+ }
82
+ body {
83
+ margin: 0;
84
+ background: var(--paper);
85
+ color: var(--ink);
86
+ font: 400 13px/1.5 var(--f-mono);
87
+ font-variant-numeric: tabular-nums lining-nums;
88
+ -webkit-font-smoothing: antialiased;
89
+ -moz-osx-font-smoothing: grayscale;
90
+ text-rendering: optimizeLegibility;
91
+ }
92
+ ::selection {
93
+ background: var(--sel);
94
+ color: var(--sel-ink);
95
+ }
96
+ button,
97
+ select {
98
+ font: inherit;
99
+ color: inherit;
100
+ }
101
+ button {
102
+ cursor: pointer;
103
+ }
104
+ code {
105
+ font-family: var(--f-mono);
106
+ font-size: 0.9em;
107
+ color: var(--ink);
108
+ overflow-wrap: anywhere;
109
+ }
110
+ p,
111
+ ul,
112
+ h2 {
113
+ margin: 0;
114
+ }
115
+ ul {
116
+ padding: 0;
117
+ list-style: none;
118
+ }
119
+ svg {
120
+ fill: none;
121
+ stroke: currentColor;
122
+ stroke-width: 1.5;
123
+ stroke-linecap: round;
124
+ stroke-linejoin: round;
125
+ flex: none;
126
+ }
127
+ :focus {
128
+ outline: none;
129
+ }
130
+ :focus-visible {
131
+ outline: 2px solid var(--accent);
132
+ outline-offset: 2px;
133
+ border-radius: 4px;
134
+ }
135
+ .muted {
136
+ color: var(--ink-3);
137
+ }
138
+ .gap {
139
+ flex: 1;
140
+ }
141
+
142
+ /* ---------- app frame ---------- */
143
+ .app {
144
+ display: grid;
145
+ grid-template-columns: minmax(0, 1fr);
146
+ grid-template-rows: var(--rail-h) minmax(0, 1fr) auto;
147
+ height: 100vh;
148
+ height: 100dvh;
149
+ }
150
+
151
+ .rail {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 12px;
155
+ padding: 0 16px 0 20px;
156
+ background: var(--rail-bg);
157
+ border-bottom: 1px solid var(--rule);
158
+ min-width: 0;
159
+ }
160
+ .brand {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ color: var(--ink);
164
+ text-decoration: none;
165
+ font: 700 13px/1 var(--f-mono);
166
+ text-transform: lowercase;
167
+ padding: 6px 4px;
168
+ margin-left: -4px;
169
+ border-radius: 4px;
170
+ }
171
+ /* the l is drawn in one mono cell (0.6em wide) and sits on the baseline */
172
+ .brand svg {
173
+ width: 0.6em;
174
+ height: 1em;
175
+ margin-top: 0.18em;
176
+ overflow: visible;
177
+ stroke: var(--act);
178
+ stroke-width: 19;
179
+ stroke-linejoin: round;
180
+ }
181
+ .brand .open {
182
+ stroke-width: 8;
183
+ }
184
+ .brand .end {
185
+ fill: var(--act);
186
+ stroke: none;
187
+ }
188
+ .rail-report {
189
+ color: var(--ink-3);
190
+ padding-left: 12px;
191
+ border-left: 1px solid var(--rule);
192
+ white-space: nowrap;
193
+ overflow: hidden;
194
+ text-overflow: ellipsis;
195
+ min-width: 0;
196
+ }
197
+ .rail-gap {
198
+ flex: 1;
199
+ }
200
+
201
+ .rail-btn {
202
+ display: inline-flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ gap: 8px;
206
+ height: 32px;
207
+ padding: 0 12px;
208
+ border: 1px solid var(--rule);
209
+ border-radius: var(--r-ctl);
210
+ background: var(--rail-btn-bg);
211
+ color: var(--ink);
212
+ font: 500 12px/1 var(--f-mono);
213
+ white-space: nowrap;
214
+ transition:
215
+ background-color 0.15s,
216
+ color 0.15s,
217
+ border-color 0.15s;
218
+ }
219
+ .rail-btn svg {
220
+ width: 14px;
221
+ height: 14px;
222
+ }
223
+ .rail-btn:hover {
224
+ border-color: var(--ink);
225
+ }
226
+ .rail-btn.done {
227
+ border-color: var(--ink);
228
+ }
229
+ .rail-btn:disabled {
230
+ color: var(--ink-3);
231
+ cursor: default;
232
+ }
233
+ .rail-btn:disabled:hover {
234
+ background: var(--rail-btn-bg);
235
+ border-color: var(--rule);
236
+ color: var(--ink-3);
237
+ }
238
+ .mode-btn {
239
+ width: 32px;
240
+ padding: 0;
241
+ }
242
+ .mode-btn .sun {
243
+ display: none;
244
+ }
245
+ [data-theme="console"] .mode-btn .sun {
246
+ display: block;
247
+ }
248
+ [data-theme="console"] .mode-btn .moon {
249
+ display: none;
250
+ }
251
+
252
+ .panes {
253
+ display: grid;
254
+ grid-template-columns: minmax(0, 1fr) var(--panel-w);
255
+ min-height: 0;
256
+ }
257
+ .report {
258
+ display: block;
259
+ width: 100%;
260
+ height: 100%;
261
+ border: 0;
262
+ background: #fff;
263
+ }
264
+ .lineage {
265
+ position: relative;
266
+ overflow-y: auto;
267
+ overscroll-behavior: contain;
268
+ background: var(--paper);
269
+ border-left: 1px solid var(--rule);
270
+ scrollbar-width: thin;
271
+ scrollbar-color: var(--rule) transparent;
272
+ }
273
+ .lineage::-webkit-scrollbar {
274
+ width: 10px;
275
+ }
276
+ .lineage::-webkit-scrollbar-thumb {
277
+ background: var(--rule);
278
+ border: 3px solid var(--paper);
279
+ border-radius: 4px;
280
+ }
281
+ .lineage:focus-visible {
282
+ outline: none;
283
+ }
284
+
285
+ /* ---------- shared pieces ---------- */
286
+ .h {
287
+ display: flex;
288
+ align-items: baseline;
289
+ gap: 6px;
290
+ font: 500 12px/1 var(--f-mono);
291
+ color: var(--ink-3);
292
+ margin-bottom: 14px;
293
+ }
294
+ .h::before {
295
+ content: "--";
296
+ }
297
+ .h small {
298
+ font: inherit;
299
+ color: var(--ink-2);
300
+ }
301
+
302
+ .kind {
303
+ font: 500 12px/1 var(--f-mono);
304
+ text-transform: lowercase;
305
+ color: var(--ink-2);
306
+ white-space: nowrap;
307
+ }
308
+
309
+ /* an id is a small outlined chip: the border says it can be pressed */
310
+ .id {
311
+ display: inline-block;
312
+ vertical-align: baseline;
313
+ border: 1px solid var(--rule);
314
+ border-radius: 4px;
315
+ padding: 0 5px;
316
+ background: transparent;
317
+ color: var(--ink);
318
+ font: 500 12px/1.45 var(--f-mono);
319
+ white-space: nowrap;
320
+ transition:
321
+ background-color 0.15s,
322
+ border-color 0.15s;
323
+ }
324
+ button.id:hover {
325
+ border-color: var(--ink);
326
+ background: var(--band);
327
+ }
328
+ .id.static {
329
+ cursor: default;
330
+ }
331
+ .id.missing {
332
+ border-style: dashed;
333
+ border-color: var(--stop);
334
+ color: var(--stop);
335
+ }
336
+
337
+ .sw {
338
+ display: inline-block;
339
+ flex: none;
340
+ width: 9px;
341
+ height: 9px;
342
+ border-radius: 50%;
343
+ vertical-align: 0;
344
+ }
345
+ .sw.full {
346
+ background: var(--node);
347
+ }
348
+ .sw.partial {
349
+ border: 1.5px solid var(--warn);
350
+ }
351
+ .sw.ungrounded {
352
+ border: 1.5px dashed var(--stop);
353
+ }
354
+ .sw.warn {
355
+ background: var(--warn);
356
+ }
357
+ .sw.stopw {
358
+ background: var(--stop);
359
+ }
360
+
361
+ .btn {
362
+ display: inline-flex;
363
+ align-items: center;
364
+ gap: 8px;
365
+ height: 32px;
366
+ padding: 0 12px;
367
+ border: 1px solid var(--rule);
368
+ border-radius: var(--r-ctl);
369
+ background: transparent;
370
+ color: var(--ink);
371
+ font: 500 12px/1 var(--f-mono);
372
+ white-space: nowrap;
373
+ transition:
374
+ background-color 0.15s,
375
+ color 0.15s,
376
+ border-color 0.15s;
377
+ }
378
+ .btn svg {
379
+ width: 14px;
380
+ height: 14px;
381
+ }
382
+ .btn:hover:not(:disabled) {
383
+ border-color: var(--ink);
384
+ }
385
+ /* the one filled button: the head's first action */
386
+ .head-act .btn:first-child:not(:disabled) {
387
+ background: var(--ink);
388
+ border-color: var(--ink);
389
+ color: var(--on-act);
390
+ }
391
+ .head-act .btn:first-child:hover:not(:disabled) {
392
+ background: var(--ink-2);
393
+ border-color: var(--ink-2);
394
+ }
395
+ .btn:disabled {
396
+ color: var(--ink-3);
397
+ border-color: var(--rule);
398
+ cursor: default;
399
+ }
400
+ .btn.done,
401
+ .btn.done:hover:not(:disabled),
402
+ .head-act .btn.done:first-child:not(:disabled) {
403
+ background: transparent;
404
+ color: var(--ink);
405
+ border-color: var(--ink);
406
+ }
407
+
408
+ .link {
409
+ border: 0;
410
+ padding: 0;
411
+ background: none;
412
+ color: var(--ink);
413
+ text-decoration: underline;
414
+ text-decoration-thickness: 1px;
415
+ text-underline-offset: 3px;
416
+ text-decoration-color: var(--rule-strong);
417
+ transition:
418
+ color 0.15s,
419
+ text-decoration-color 0.15s;
420
+ }
421
+ .link:hover {
422
+ color: var(--ink);
423
+ text-decoration-color: currentColor;
424
+ }
425
+
426
+ /* ---------- nav ---------- */
427
+ .nav {
428
+ position: sticky;
429
+ top: 0;
430
+ z-index: 3;
431
+ display: flex;
432
+ align-items: center;
433
+ gap: 12px;
434
+ height: var(--nav-h);
435
+ padding: 0 var(--gut) 0 calc(var(--gut) - 8px);
436
+ background: var(--paper);
437
+ border-bottom: 1px solid var(--rule);
438
+ }
439
+ .back {
440
+ display: inline-flex;
441
+ align-items: center;
442
+ gap: 4px;
443
+ height: 30px;
444
+ padding: 0 8px 0 4px;
445
+ border: 0;
446
+ border-radius: var(--r-ctl);
447
+ background: transparent;
448
+ color: var(--ink-2);
449
+ font: 500 12px/1 var(--f-mono);
450
+ transition: color 0.15s;
451
+ }
452
+ .back svg {
453
+ width: 14px;
454
+ height: 14px;
455
+ stroke-width: 1.8;
456
+ }
457
+ .back:hover {
458
+ color: var(--ink);
459
+ text-decoration: underline;
460
+ text-underline-offset: 3px;
461
+ }
462
+ .nav .link {
463
+ font-size: 12px;
464
+ }
465
+
466
+ /* ---------- summary ---------- */
467
+ .sec {
468
+ padding: 32px var(--gut) 28px;
469
+ }
470
+ .sec + .sec {
471
+ border-top: 1px solid var(--rule);
472
+ }
473
+ .lede-sub {
474
+ font: 400 24px/1.3 var(--f-serif);
475
+ letter-spacing: -0.01em;
476
+ text-wrap: balance;
477
+ }
478
+ .lede-sub + .lede-sub {
479
+ margin-top: 16px;
480
+ }
481
+ .lede-more {
482
+ display: block;
483
+ margin-top: 8px;
484
+ color: var(--ink-2);
485
+ font: 400 15px/1.5 var(--f-serif);
486
+ letter-spacing: 0;
487
+ }
488
+ .built {
489
+ border: 1px solid var(--rule);
490
+ border-radius: var(--r-card);
491
+ }
492
+ .built li {
493
+ display: flex;
494
+ flex-wrap: wrap;
495
+ align-items: baseline;
496
+ gap: 4px 12px;
497
+ padding: 12px 16px;
498
+ }
499
+ .built .what {
500
+ flex: 1 1 auto;
501
+ min-width: 0;
502
+ }
503
+ .built li + li {
504
+ border-top: 1px solid var(--rule-soft);
505
+ }
506
+ .built .when {
507
+ margin-left: auto;
508
+ color: var(--ink-2);
509
+ font-size: 12.5px;
510
+ text-align: right;
511
+ }
512
+ .built .when.warn {
513
+ color: var(--warn);
514
+ }
515
+
516
+ /* needs attention */
517
+ .attn {
518
+ border: 1px solid var(--rule);
519
+ border-radius: var(--r-card);
520
+ overflow: hidden;
521
+ }
522
+ .attn li + li {
523
+ border-top: 1px solid var(--rule-soft);
524
+ }
525
+ .attn button {
526
+ display: grid;
527
+ grid-template-columns: 9px auto minmax(0, 1fr);
528
+ align-items: start;
529
+ column-gap: 12px;
530
+ width: 100%;
531
+ padding: 14px 16px;
532
+ border: 0;
533
+ background: transparent;
534
+ text-align: left;
535
+ line-height: 1.55;
536
+ transition: background-color 0.12s;
537
+ }
538
+ .attn .plain {
539
+ display: grid;
540
+ grid-template-columns: 9px minmax(0, 1fr);
541
+ column-gap: 12px;
542
+ padding: 14px 16px;
543
+ line-height: 1.55;
544
+ }
545
+ .attn .sw {
546
+ margin-top: 6px;
547
+ }
548
+ .attn .id {
549
+ margin-top: 1px;
550
+ }
551
+ .attn .what {
552
+ color: var(--ink);
553
+ font: 400 14.5px/1.5 var(--f-serif);
554
+ text-wrap: pretty;
555
+ }
556
+ .attn .what b {
557
+ color: var(--ink);
558
+ }
559
+ .attn .what code {
560
+ color: var(--ink);
561
+ }
562
+ .attn .tag {
563
+ font-weight: 600;
564
+ }
565
+ .attn .tag.stop {
566
+ color: var(--stop);
567
+ }
568
+ .attn .tag.warn,
569
+ .attn .tag.ext {
570
+ color: var(--warn);
571
+ }
572
+ .attn button:hover {
573
+ background: var(--band-soft);
574
+ }
575
+ .attn button:hover .what {
576
+ text-decoration: underline;
577
+ text-decoration-color: var(--rule-strong);
578
+ text-underline-offset: 3px;
579
+ }
580
+ .attn button:hover .id {
581
+ border-color: var(--ink);
582
+ }
583
+ .prompt {
584
+ display: flex;
585
+ align-items: center;
586
+ gap: 12px;
587
+ margin: 0;
588
+ padding: 24px var(--gut) 36px;
589
+ border-top: 1px solid var(--rule);
590
+ color: var(--ink-3);
591
+ font-size: 12.5px;
592
+ text-wrap: pretty;
593
+ }
594
+ .prompt svg {
595
+ width: 26px;
596
+ height: 26px;
597
+ stroke: var(--ink-3);
598
+ }
599
+
600
+ /* ---------- selected: head ---------- */
601
+ .head {
602
+ padding: 32px var(--gut) 28px;
603
+ border-bottom: 1px solid var(--rule);
604
+ }
605
+ .head .value {
606
+ font: 500 52px/1 var(--f-mono);
607
+ letter-spacing: -0.04em;
608
+ color: var(--ink);
609
+ font-feature-settings: "tnum", "lnum";
610
+ overflow-wrap: anywhere;
611
+ }
612
+ .head .label {
613
+ margin-top: 14px;
614
+ font: 400 18px/1.4 var(--f-serif);
615
+ text-wrap: pretty;
616
+ }
617
+ .head .claim {
618
+ font: 400 21px/1.4 var(--f-serif);
619
+ letter-spacing: -0.005em;
620
+ text-wrap: pretty;
621
+ }
622
+ .head .claim code {
623
+ color: inherit;
624
+ }
625
+ .head .claim + .label {
626
+ margin-top: 8px;
627
+ font: 400 12.5px/1.5 var(--f-mono);
628
+ }
629
+ .head .claim mark {
630
+ background: none;
631
+ color: var(--ink);
632
+ font-weight: 600;
633
+ text-decoration: underline;
634
+ text-decoration-color: var(--accent);
635
+ text-decoration-thickness: 1.5px;
636
+ text-underline-offset: 4px;
637
+ }
638
+ .head-meta {
639
+ display: flex;
640
+ flex-wrap: wrap;
641
+ align-items: center;
642
+ gap: 8px 12px;
643
+ margin-top: 18px;
644
+ }
645
+ .head-meta .state {
646
+ display: inline-flex;
647
+ align-items: center;
648
+ gap: 6px;
649
+ color: var(--ink-2);
650
+ font-size: 12.5px;
651
+ }
652
+ .head-act {
653
+ display: flex;
654
+ flex-wrap: wrap;
655
+ gap: 10px;
656
+ margin-top: 24px;
657
+ }
658
+
659
+ /* ---------- selected: rests on ---------- */
660
+ .rests {
661
+ padding: 28px var(--gut) 28px;
662
+ border-bottom: 1px solid var(--rule);
663
+ }
664
+ .rests ul {
665
+ border: 1px solid var(--rule);
666
+ border-radius: var(--r-card);
667
+ }
668
+ .rests li {
669
+ display: grid;
670
+ grid-template-columns: 9px minmax(0, 1fr) auto;
671
+ align-items: baseline;
672
+ column-gap: 12px;
673
+ padding: 12px 16px;
674
+ }
675
+ .rests li + li {
676
+ border-top: 1px solid var(--rule-soft);
677
+ }
678
+ .rests li .sw {
679
+ align-self: center;
680
+ }
681
+ .rests li .what {
682
+ min-width: 0;
683
+ }
684
+ .rests li .what code {
685
+ font-weight: 500;
686
+ }
687
+ .rests li .when {
688
+ color: var(--ink-2);
689
+ font-size: 12.5px;
690
+ white-space: nowrap;
691
+ text-align: right;
692
+ }
693
+ .rests li .when.warn {
694
+ color: var(--warn);
695
+ }
696
+ .rests li .when.stop {
697
+ color: var(--stop);
698
+ }
699
+ .rests li[data-jump] {
700
+ cursor: pointer;
701
+ }
702
+ .rests li[data-jump]:hover .what code {
703
+ text-decoration: underline;
704
+ text-decoration-thickness: 1px;
705
+ text-underline-offset: 3px;
706
+ }
707
+ /* ---------- used by ---------- */
708
+ .uses {
709
+ padding: 28px var(--gut) 0;
710
+ }
711
+ .uses .btn svg {
712
+ width: 12px;
713
+ height: 12px;
714
+ stroke-width: 1.8;
715
+ }
716
+ /* ---------- how it was computed ---------- */
717
+ .score-h {
718
+ display: flex;
719
+ align-items: baseline;
720
+ justify-content: space-between;
721
+ gap: 12px;
722
+ padding: 32px var(--gut) 16px;
723
+ }
724
+ .score-h .h {
725
+ margin: 0;
726
+ }
727
+ .card {
728
+ padding: 18px 20px;
729
+ border: 1px solid var(--rule);
730
+ border-radius: var(--r-card);
731
+ background: var(--paper);
732
+ transition: border-color 0.15s;
733
+ }
734
+ .step-h {
735
+ display: flex;
736
+ align-items: baseline;
737
+ gap: 10px;
738
+ min-width: 0;
739
+ }
740
+ .step-h .id {
741
+ position: relative;
742
+ top: -1px;
743
+ }
744
+ .step-h .ttl {
745
+ flex: 1;
746
+ min-width: 0;
747
+ font: 400 15.5px/1.45 var(--f-serif);
748
+ text-wrap: pretty;
749
+ }
750
+ .step-h .ttl.clamp {
751
+ display: -webkit-box;
752
+ -webkit-box-orient: vertical;
753
+ -webkit-line-clamp: 2;
754
+ overflow: hidden;
755
+ }
756
+ .step-h .fig-link {
757
+ flex: none;
758
+ margin-left: 8px;
759
+ font: 400 12px/1.2 var(--f-mono);
760
+ }
761
+ .step-h .ttl code {
762
+ font-size: 13.5px;
763
+ font-weight: 500;
764
+ }
765
+ .step-h .fig {
766
+ flex: none;
767
+ margin-left: 12px;
768
+ font: 600 15px/1 var(--f-mono);
769
+ color: var(--ink);
770
+ white-space: nowrap;
771
+ }
772
+ .step-h .fig.small {
773
+ font: 400 12px/1.2 var(--f-mono);
774
+ color: var(--ink-3);
775
+ }
776
+ .step-h .fig:empty {
777
+ display: none;
778
+ }
779
+
780
+ /* a source's desc, under its mart */
781
+ .sub {
782
+ margin-top: 6px;
783
+ color: var(--ink-2);
784
+ font: 400 14.5px/1.5 var(--f-serif);
785
+ display: -webkit-box;
786
+ -webkit-box-orient: vertical;
787
+ -webkit-line-clamp: 2;
788
+ overflow: hidden;
789
+ }
790
+ /* kind · facts · toggle, one quiet line under the title */
791
+ .meta {
792
+ display: flex;
793
+ flex-wrap: wrap;
794
+ align-items: baseline;
795
+ gap: 4px 8px;
796
+ margin-top: 10px;
797
+ color: var(--ink-3);
798
+ font: 400 12px/1.6 var(--f-mono);
799
+ }
800
+ .card > .meta:first-child {
801
+ margin-top: 0;
802
+ }
803
+ .meta .sep {
804
+ color: var(--graph);
805
+ }
806
+ .meta .id {
807
+ font-size: 11.5px;
808
+ }
809
+ .meta b {
810
+ color: var(--ink);
811
+ font-weight: 600;
812
+ }
813
+ .meta .warn {
814
+ color: var(--warn);
815
+ }
816
+ .meta .stop {
817
+ color: var(--stop);
818
+ }
819
+ .tog {
820
+ display: inline-flex;
821
+ align-items: center;
822
+ gap: 4px;
823
+ padding: 0;
824
+ border: 0;
825
+ background: none;
826
+ color: var(--ink-2);
827
+ font: 500 12px/1.4 var(--f-mono);
828
+ }
829
+ .tog:hover {
830
+ color: var(--ink);
831
+ text-decoration: underline;
832
+ text-underline-offset: 3px;
833
+ }
834
+ .tog svg {
835
+ width: 12px;
836
+ height: 12px;
837
+ stroke-width: 1.6;
838
+ transition: rotate 0.2s var(--ease-out);
839
+ }
840
+ .tog[aria-expanded="true"] svg {
841
+ rotate: 90deg;
842
+ }
843
+
844
+ /* what opens below the meta line sits under a hairline */
845
+ .card > .meta + :is(.reads, .formula) {
846
+ margin-top: 16px;
847
+ padding-top: 14px;
848
+ border-top: 1px solid var(--rule-soft);
849
+ }
850
+
851
+ .reads {
852
+ margin-top: 10px;
853
+ color: var(--ink-2);
854
+ font: 400 14px/1.6 var(--f-serif);
855
+ text-wrap: pretty;
856
+ }
857
+ .card > .reads:first-child {
858
+ margin-top: 0;
859
+ }
860
+ .reads b {
861
+ color: var(--ink);
862
+ font-weight: 600;
863
+ }
864
+ .reads .id {
865
+ font-size: 12px;
866
+ }
867
+ .reads.cols {
868
+ color: var(--ink-3);
869
+ font: 400 12px/1.7 var(--f-mono);
870
+ }
871
+ .reads.cols code {
872
+ font-size: inherit;
873
+ color: var(--ink-2);
874
+ }
875
+
876
+ /* formula: the expression with ids, then with values, then the result */
877
+ .formula {
878
+ display: grid;
879
+ grid-template-columns: 18px minmax(0, 1fr);
880
+ row-gap: 4px;
881
+ margin-top: 12px;
882
+ font: 500 14px/1.6 var(--f-mono);
883
+ font-variant-numeric: tabular-nums;
884
+ }
885
+ .formula .eq {
886
+ color: var(--ink-3);
887
+ }
888
+ .formula .id {
889
+ font-size: 11.5px;
890
+ padding: 0 4px;
891
+ }
892
+ .formula > :nth-last-child(-n + 2) {
893
+ margin-top: 6px;
894
+ padding-top: 8px;
895
+ border-top: 1px solid var(--rule-soft);
896
+ }
897
+ .formula .res {
898
+ font-size: 16px;
899
+ font-weight: 600;
900
+ color: var(--ink);
901
+ }
902
+
903
+ /* sql snippet: the bottom of its card, full width under a hairline, on the
904
+ one recessed tone the panel keeps */
905
+ .snip {
906
+ margin: 16px -20px -18px;
907
+ background: var(--field);
908
+ border-top: 1px solid var(--rule);
909
+ border-radius: 0 0 calc(var(--r-card) - 1px) calc(var(--r-card) - 1px);
910
+ overflow: hidden;
911
+ }
912
+ .reads.cols + .snip {
913
+ margin-top: 14px;
914
+ }
915
+ .snip pre {
916
+ margin: 0;
917
+ padding: 14px 20px;
918
+ font: 400 12.5px/1.6 var(--f-mono);
919
+ color: var(--ink);
920
+ white-space: pre;
921
+ overflow-x: auto;
922
+ tab-size: 2;
923
+ scrollbar-width: thin;
924
+ scrollbar-color: var(--rule) transparent;
925
+ }
926
+ .snip pre .kw {
927
+ color: var(--kw);
928
+ font-weight: 500;
929
+ }
930
+ .snip pre .str {
931
+ color: var(--str);
932
+ }
933
+ .snip pre .num {
934
+ color: var(--num);
935
+ }
936
+ .snip pre .cm {
937
+ color: var(--ink-3);
938
+ font-style: italic;
939
+ }
940
+ .snip-bar {
941
+ display: flex;
942
+ align-items: center;
943
+ gap: 8px;
944
+ padding: 6px 8px 6px 20px;
945
+ border-top: 1px solid var(--rule-soft);
946
+ }
947
+ .snip-bar .muted {
948
+ font-size: 12px !important;
949
+ }
950
+ .snip-bar .link {
951
+ font-size: 12px;
952
+ }
953
+ .snip-bar .btn {
954
+ height: 26px;
955
+ padding: 0 10px;
956
+ font-size: 11.5px;
957
+ }
958
+ .snip-bar .btn svg {
959
+ width: 12px;
960
+ height: 12px;
961
+ }
962
+
963
+ /* read out to screen readers, never seen */
964
+ .vh {
965
+ position: absolute;
966
+ width: 1px;
967
+ height: 1px;
968
+ overflow: hidden;
969
+ clip-path: inset(50%);
970
+ white-space: nowrap;
971
+ }
972
+
973
+ @media (prefers-reduced-motion: reduce) {
974
+ *,
975
+ *::before,
976
+ *::after {
977
+ transition-duration: 0.01ms !important;
978
+ }
979
+ }
980
+
981
+ /* ---------- the status bar ---------- */
982
+ .status {
983
+ position: relative;
984
+ z-index: 5;
985
+ background: var(--paper);
986
+ border-top: 1px solid var(--rule);
987
+ }
988
+
989
+ /* ---------- narrower screens ---------- */
990
+ @media (max-width: 980px) {
991
+ :root {
992
+ --panel-w: 460px;
993
+ }
994
+ }
995
+
996
+ @media (max-width: 760px) {
997
+ :root {
998
+ --gut: 18px;
999
+ --status-h: 40px;
1000
+ }
1001
+ .app {
1002
+ display: block;
1003
+ height: auto;
1004
+ padding-bottom: var(--status-h);
1005
+ }
1006
+ .status {
1007
+ position: fixed;
1008
+ left: 0;
1009
+ right: 0;
1010
+ bottom: 0;
1011
+ }
1012
+ .rail {
1013
+ flex-wrap: wrap;
1014
+ row-gap: 0;
1015
+ padding: 8px 12px 0 16px;
1016
+ }
1017
+ .rail .brand {
1018
+ height: 32px;
1019
+ }
1020
+ .rail-report {
1021
+ order: 10;
1022
+ flex-basis: 100%;
1023
+ padding: 6px 0 8px;
1024
+ border-left: 0;
1025
+ font-size: 12px;
1026
+ }
1027
+ .rail-btn span {
1028
+ display: none;
1029
+ }
1030
+ #copyReport {
1031
+ width: 32px;
1032
+ padding: 0;
1033
+ }
1034
+ .panes {
1035
+ display: block;
1036
+ }
1037
+ .report {
1038
+ height: 46vh;
1039
+ height: 46svh;
1040
+ }
1041
+ .lineage {
1042
+ overflow: visible;
1043
+ border-left: 0;
1044
+ border-top: 1px solid var(--rule);
1045
+ }
1046
+ .head .value {
1047
+ font-size: 48px;
1048
+ }
1049
+ .card {
1050
+ padding: 18px 16px 16px;
1051
+ }
1052
+ .snip {
1053
+ margin: 14px -16px -16px;
1054
+ }
1055
+ .snip pre {
1056
+ padding: 12px 16px;
1057
+ }
1058
+ .rests li {
1059
+ grid-template-columns: 9px minmax(0, 1fr);
1060
+ row-gap: 2px;
1061
+ }
1062
+ .rests li .when {
1063
+ grid-column: 2;
1064
+ text-align: left;
1065
+ white-space: normal;
1066
+ }
1067
+ .built .when {
1068
+ flex-basis: 100%;
1069
+ margin-left: 0;
1070
+ padding-left: 21px;
1071
+ text-align: left;
1072
+ }
1073
+ .step-h {
1074
+ flex-wrap: wrap;
1075
+ row-gap: 6px;
1076
+ }
1077
+ .step-h .ttl {
1078
+ flex: 1 1 24ch;
1079
+ }
1080
+ .step-h .fig {
1081
+ margin-left: auto;
1082
+ }
1083
+ }
1084
+
1085
+ /* ---------- the status bar ---------- */
1086
+ .statusbar {
1087
+ font-size: 12px;
1088
+ color: var(--ink-3);
1089
+ }
1090
+ .statusbar .bar-line {
1091
+ display: flex;
1092
+ flex-wrap: nowrap;
1093
+ align-items: center;
1094
+ gap: 4px 16px;
1095
+ padding: 10px var(--gut);
1096
+ }
1097
+ .statusbar .link {
1098
+ display: inline-flex;
1099
+ align-items: center;
1100
+ gap: 6px;
1101
+ font-size: 12px;
1102
+ }
1103
+ .statusbar .link svg {
1104
+ width: 12px;
1105
+ height: 12px;
1106
+ transition: rotate 0.2s var(--ease-out);
1107
+ }
1108
+ .statusbar .link[aria-expanded="true"] svg {
1109
+ rotate: -90deg;
1110
+ }
1111
+ .statusbar .bar-line > * {
1112
+ flex: none;
1113
+ }
1114
+ .statusbar .reason {
1115
+ flex: 0 1 auto;
1116
+ min-width: 0;
1117
+ overflow: hidden;
1118
+ color: var(--stop);
1119
+ text-overflow: ellipsis;
1120
+ white-space: nowrap;
1121
+ }
1122
+ .statusbar .holds {
1123
+ padding-left: 16px;
1124
+ border-left: 1px solid var(--rule);
1125
+ }
1126
+ .statusbar .bar-line > .bar-r {
1127
+ flex: 0 1 auto;
1128
+ }
1129
+ .statusbar .bar-r {
1130
+ display: flex;
1131
+ align-items: center;
1132
+ gap: 14px;
1133
+ min-width: 0;
1134
+ max-width: 50%;
1135
+ margin-left: auto;
1136
+ }
1137
+ .statusbar .here {
1138
+ display: flex;
1139
+ align-items: baseline;
1140
+ gap: 8px;
1141
+ min-width: 0;
1142
+ }
1143
+ .statusbar .here-id {
1144
+ padding: 0 5px;
1145
+ border: 1px solid var(--rule);
1146
+ border-radius: 4px;
1147
+ color: var(--ink);
1148
+ font-weight: 500;
1149
+ }
1150
+ .statusbar .here-what {
1151
+ overflow: hidden;
1152
+ text-overflow: ellipsis;
1153
+ white-space: nowrap;
1154
+ }
1155
+ .statusbar .here-what b {
1156
+ color: var(--ink);
1157
+ font-weight: 600;
1158
+ }
1159
+ .statusbar .key {
1160
+ flex: none;
1161
+ white-space: nowrap;
1162
+ }
1163
+ .statusbar kbd {
1164
+ padding: 0 4px;
1165
+ border: 1px solid var(--rule);
1166
+ border-radius: 4px;
1167
+ font: inherit;
1168
+ font-size: 11px;
1169
+ }
1170
+ .statusbar .flash {
1171
+ overflow: hidden;
1172
+ color: var(--ink);
1173
+ font-weight: 500;
1174
+ text-overflow: ellipsis;
1175
+ white-space: nowrap;
1176
+ }
1177
+ .statusbar .flash.warn {
1178
+ color: var(--warn);
1179
+ }
1180
+ .statusbar .drawer {
1181
+ position: absolute;
1182
+ left: 0;
1183
+ right: 0;
1184
+ bottom: 100%;
1185
+ max-height: 40vh;
1186
+ overflow-y: auto;
1187
+ padding: 8px var(--gut);
1188
+ background: var(--paper);
1189
+ border-top: 1px solid var(--rule);
1190
+ }
1191
+ .statusbar .drawer li {
1192
+ display: flex;
1193
+ align-items: baseline;
1194
+ gap: 12px;
1195
+ padding: 10px 0;
1196
+ line-height: 1.55;
1197
+ }
1198
+ .statusbar .drawer li + li {
1199
+ border-top: 1px solid var(--rule-soft);
1200
+ }
1201
+ .statusbar .drawer b {
1202
+ color: var(--ink-2);
1203
+ }
1204
+ /* a callout is drawn, not filled: a border in the warning colour, words in ink */
1205
+ .c3 {
1206
+ margin-top: 18px;
1207
+ padding: 10px 14px;
1208
+ border: 1px solid var(--warn);
1209
+ border-radius: var(--r-ctl);
1210
+ color: var(--ink);
1211
+ font-size: 12.5px;
1212
+ line-height: 1.55;
1213
+ }
1214
+ .c3 b {
1215
+ color: var(--warn);
1216
+ }
1217
+
1218
+ /* after the base status bar rules, so these win at phone width */
1219
+ @media (max-width: 760px) {
1220
+ .statusbar .holds,
1221
+ .statusbar .key {
1222
+ display: none;
1223
+ }
1224
+ .statusbar .bar-line {
1225
+ flex-wrap: wrap;
1226
+ }
1227
+ .statusbar .bar-r {
1228
+ max-width: none;
1229
+ }
1230
+ }
1231
+
1232
+ /* ---------- the outline ----------
1233
+ Closed rows are a hairline list. An open row becomes a bordered box: its
1234
+ head on a band, its card and its own inputs inside, joined to the head by
1235
+ a light guide line under the chevron. */
1236
+ .ol-sec {
1237
+ padding: 0 var(--gut) 36px;
1238
+ }
1239
+ .uses + .ol-sec {
1240
+ padding-top: 14px;
1241
+ padding-bottom: 0;
1242
+ }
1243
+ .ol-own {
1244
+ margin-bottom: 4px;
1245
+ }
1246
+ .ol-cap {
1247
+ margin: 20px 0 8px;
1248
+ font: 500 12px/1.4 var(--f-mono);
1249
+ color: var(--ink-3);
1250
+ }
1251
+ .ol {
1252
+ list-style: none;
1253
+ margin: 0;
1254
+ padding: 0;
1255
+ }
1256
+ .ol-item {
1257
+ border-bottom: 1px solid var(--rule-soft);
1258
+ }
1259
+
1260
+ /* the row: chevron, swatch, then the title over a quiet meta line, value right */
1261
+ .ol-row {
1262
+ all: unset;
1263
+ box-sizing: border-box;
1264
+ width: 100%;
1265
+ cursor: pointer;
1266
+ display: grid;
1267
+ grid-template-columns: 16px 8px minmax(0, 1fr) auto;
1268
+ gap: 0 10px;
1269
+ align-items: start;
1270
+ padding: 11px 12px 11px 8px;
1271
+ border-radius: 6px;
1272
+ transition: background-color 0.15s;
1273
+ }
1274
+ .ol-row:hover {
1275
+ background: var(--band-soft);
1276
+ }
1277
+ .ol-row:focus-visible {
1278
+ outline: 2px solid var(--accent);
1279
+ outline-offset: -2px;
1280
+ }
1281
+ .ol-row[disabled] {
1282
+ cursor: default;
1283
+ }
1284
+ .ol-row[disabled]:hover {
1285
+ background: none;
1286
+ }
1287
+ .ol-chev {
1288
+ display: grid;
1289
+ place-items: center;
1290
+ height: 21px;
1291
+ color: var(--ink-3);
1292
+ }
1293
+ .ol-chev svg {
1294
+ width: 12px;
1295
+ height: 12px;
1296
+ stroke-width: 1.8;
1297
+ transition: rotate 0.2s var(--ease-out);
1298
+ }
1299
+ .ol-row:hover .ol-chev {
1300
+ color: var(--ink);
1301
+ }
1302
+ .ol-row[aria-expanded="true"] .ol-chev svg {
1303
+ rotate: 90deg;
1304
+ }
1305
+ .ol-row .sw {
1306
+ margin-top: 7px;
1307
+ }
1308
+ .ol-main {
1309
+ display: flex;
1310
+ flex-direction: column;
1311
+ gap: 4px;
1312
+ min-width: 0;
1313
+ }
1314
+ .ol-t {
1315
+ font: 400 15px/1.4 var(--f-serif);
1316
+ color: var(--ink);
1317
+ text-wrap: pretty;
1318
+ }
1319
+ .ol-t .id {
1320
+ display: inline-block;
1321
+ margin-right: 6px;
1322
+ padding: 0 5px;
1323
+ border: 1px solid var(--rule);
1324
+ border-radius: 4px;
1325
+ font: 500 11.5px/1.6 var(--f-mono);
1326
+ color: var(--ink-2);
1327
+ vertical-align: 1px;
1328
+ }
1329
+ .ol-t code {
1330
+ font-size: 13px;
1331
+ overflow-wrap: anywhere;
1332
+ }
1333
+ .ol-meta {
1334
+ font: 400 11.5px/1.5 var(--f-mono);
1335
+ color: var(--ink-3);
1336
+ overflow-wrap: break-word;
1337
+ }
1338
+ .ol-meta .sep {
1339
+ margin: 0 6px;
1340
+ }
1341
+ .ol-meta .onpage {
1342
+ color: var(--accent);
1343
+ }
1344
+ .ol-meta .stop {
1345
+ color: var(--stop);
1346
+ }
1347
+ .ol-row .fig {
1348
+ padding-left: 12px;
1349
+ font: 500 13px/21px var(--f-mono);
1350
+ font-variant-numeric: tabular-nums;
1351
+ color: var(--ink);
1352
+ }
1353
+
1354
+ /* open: one bordered box holding the head, the card and the inputs */
1355
+ .ol-item.is-open {
1356
+ margin: 8px 0;
1357
+ border: 1px solid var(--rule);
1358
+ border-radius: var(--r-card);
1359
+ background: var(--paper);
1360
+ }
1361
+ .ol-item.is-open > .ol-row {
1362
+ border-radius: calc(var(--r-card) - 1px) calc(var(--r-card) - 1px) 0 0;
1363
+ background: var(--band-soft);
1364
+ border-bottom: 1px solid var(--rule-soft);
1365
+ }
1366
+ .ol-item.is-open > .ol-row:hover {
1367
+ background: var(--band);
1368
+ }
1369
+ .ol-body {
1370
+ position: relative;
1371
+ padding: 14px 14px 14px 42px;
1372
+ }
1373
+ /* the guide: from under the chevron down the body, in the lightest rule */
1374
+ .ol-body::before {
1375
+ content: "";
1376
+ position: absolute;
1377
+ left: 15.5px;
1378
+ top: 0;
1379
+ bottom: 14px;
1380
+ border-left: 1px solid var(--rule);
1381
+ }
1382
+ .ol-body .card.bare {
1383
+ padding: 0;
1384
+ border: 0;
1385
+ background: none;
1386
+ }
1387
+ /* a bare card has no padding to bleed into: the SQL sits as its own framed
1388
+ block inside the open row */
1389
+ .ol-body .card.bare .snip {
1390
+ margin: 14px 0 0;
1391
+ border: 1px solid var(--rule);
1392
+ border-radius: 6px;
1393
+ }
1394
+ .ol-acts {
1395
+ display: flex;
1396
+ gap: 18px;
1397
+ margin-top: 16px;
1398
+ font-size: 12px;
1399
+ }
1400
+ .ol-body .ol-cap {
1401
+ margin-top: 18px;
1402
+ }
1403
+ .ol-body > .ol {
1404
+ border-top: 1px solid var(--rule-soft);
1405
+ }
1406
+ .ol-body > .ol > .ol-item:last-child {
1407
+ border-bottom: 0;
1408
+ }
1409
+ .ol-body > .ol > .ol-item.is-open:last-child {
1410
+ margin-bottom: 0;
1411
+ }
1412
+
1413
+ @media (max-width: 600px) {
1414
+ .ol-body {
1415
+ padding-left: 16px;
1416
+ }
1417
+ .ol-body::before {
1418
+ display: none;
1419
+ }
1420
+ }