@runcontext/site 0.3.3 → 0.3.5

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/dist/index.cjs CHANGED
@@ -3,416 +3,1348 @@ var _ejs = require('ejs'); var _ejs2 = _interopRequireDefault(_ejs);
3
3
  var _fs = require('fs'); var fs = _interopRequireWildcard(_fs);
4
4
  var _path = require('path'); var path = _interopRequireWildcard(_path);
5
5
 
6
- // src/templates.ts
6
+ // src/templates/shared.ts
7
7
  var HEAD = `<!DOCTYPE html>
8
8
  <html lang="en">
9
9
  <head>
10
10
  <meta charset="UTF-8">
11
11
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
12
  <title><%= pageTitle %> \u2014 <%= siteTitle %></title>
13
- <script src="https://cdn.tailwindcss.com"></script>
13
+ <link rel="preconnect" href="https://fonts.googleapis.com">
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;600;700&family=DM+Sans:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
16
+ <style>
17
+ :root {
18
+ --gold: #D4A855;
19
+ --gold-light: #E8C878;
20
+ --gold-dark: #A07D3A;
21
+ --gold-glow: rgba(212, 168, 85, 0.15);
22
+ --bg: #0A0A0C;
23
+ --bg-card: #111114;
24
+ --bg-elevated: #18181C;
25
+ --text: #E8E6E1;
26
+ --text-muted: #8A8880;
27
+ --text-dim: #5A5850;
28
+ --border: #2A2A2E;
29
+ --green: #4ADE80;
30
+ --green-dim: rgba(74, 222, 128, 0.12);
31
+ --blue: #60A5FA;
32
+ --red: #F87171;
33
+ --serif: 'Cormorant Garamond', Georgia, serif;
34
+ --sans: 'DM Sans', system-ui, sans-serif;
35
+ --mono: 'JetBrains Mono', monospace;
36
+ }
37
+ * { margin: 0; padding: 0; box-sizing: border-box; }
38
+ html { scroll-behavior: smooth; }
39
+ body {
40
+ font-family: var(--sans);
41
+ background: var(--bg);
42
+ color: var(--text);
43
+ line-height: 1.6;
44
+ overflow-x: hidden;
45
+ }
46
+ a { color: var(--gold); text-decoration: none; }
47
+ a:hover { text-decoration: underline; }
48
+
49
+ /* === GRAIN OVERLAY === */
50
+ body::before {
51
+ content: '';
52
+ position: fixed;
53
+ inset: 0;
54
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
55
+ pointer-events: none;
56
+ z-index: 9999;
57
+ }
58
+
59
+ /* === LAYOUT === */
60
+ .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }
61
+ .section { margin-bottom: 3rem; }
62
+ .section-label {
63
+ font-family: var(--mono);
64
+ font-size: 0.65rem;
65
+ letter-spacing: 0.3em;
66
+ text-transform: uppercase;
67
+ color: var(--gold-dark);
68
+ margin-bottom: 0.75rem;
69
+ }
70
+ .section h2, h2.section-title {
71
+ font-family: var(--serif);
72
+ font-weight: 400;
73
+ font-size: clamp(1.6rem, 3vw, 2.4rem);
74
+ letter-spacing: -0.01em;
75
+ margin-bottom: 1rem;
76
+ color: var(--text);
77
+ }
78
+ .section-intro {
79
+ color: var(--text-muted);
80
+ font-size: 1rem;
81
+ max-width: 600px;
82
+ margin-bottom: 2rem;
83
+ font-weight: 300;
84
+ }
85
+ .divider {
86
+ width: 100%;
87
+ height: 1px;
88
+ background: var(--border);
89
+ margin: 0 auto;
90
+ }
91
+
92
+ /* === HERO (index page) === */
93
+ .hero {
94
+ text-align: center;
95
+ padding: 5rem 2rem 4rem;
96
+ position: relative;
97
+ }
98
+ .hero::before {
99
+ content: '';
100
+ position: absolute;
101
+ top: -30%;
102
+ left: 50%;
103
+ transform: translateX(-50%);
104
+ width: 700px;
105
+ height: 700px;
106
+ background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);
107
+ pointer-events: none;
108
+ }
109
+ .hero-eyebrow {
110
+ font-family: var(--mono);
111
+ font-size: 0.7rem;
112
+ letter-spacing: 0.3em;
113
+ text-transform: uppercase;
114
+ color: var(--gold);
115
+ margin-bottom: 1.5rem;
116
+ position: relative;
117
+ }
118
+ .hero h1 {
119
+ font-family: var(--serif);
120
+ font-weight: 300;
121
+ font-size: clamp(2.5rem, 6vw, 5rem);
122
+ line-height: 1.1;
123
+ letter-spacing: -0.02em;
124
+ color: var(--text);
125
+ position: relative;
126
+ }
127
+ .hero h1 em { font-style: italic; color: var(--gold); }
128
+ .hero-sub {
129
+ font-size: 1.05rem;
130
+ color: var(--text-muted);
131
+ max-width: 520px;
132
+ margin: 1.5rem auto 0;
133
+ font-weight: 300;
134
+ position: relative;
135
+ }
136
+
137
+ /* === STATS === */
138
+ .stats {
139
+ display: grid;
140
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
141
+ gap: 1px;
142
+ background: var(--border);
143
+ border: 1px solid var(--border);
144
+ border-radius: 12px;
145
+ overflow: hidden;
146
+ margin-bottom: 3rem;
147
+ }
148
+ .stat {
149
+ background: var(--bg-card);
150
+ padding: 1.5rem;
151
+ text-align: center;
152
+ }
153
+ .stat-value {
154
+ font-family: var(--serif);
155
+ font-size: 2.4rem;
156
+ font-weight: 300;
157
+ color: var(--gold);
158
+ line-height: 1;
159
+ }
160
+ .stat-label {
161
+ font-size: 0.7rem;
162
+ color: var(--text-muted);
163
+ margin-top: 0.4rem;
164
+ text-transform: uppercase;
165
+ letter-spacing: 0.1em;
166
+ }
167
+
168
+ /* === CARDS === */
169
+ .card {
170
+ border: 1px solid var(--border);
171
+ border-radius: 12px;
172
+ padding: 1.5rem;
173
+ background: var(--bg-card);
174
+ transition: border-color 0.3s, box-shadow 0.3s;
175
+ }
176
+ .card:hover {
177
+ border-color: var(--gold-dark);
178
+ box-shadow: 0 0 40px rgba(212,168,85,0.06);
179
+ }
180
+ .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }
181
+ .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
182
+
183
+ /* === TAGS === */
184
+ .tag {
185
+ display: inline-block;
186
+ font-family: var(--mono);
187
+ font-size: 0.6rem;
188
+ letter-spacing: 0.05em;
189
+ text-transform: uppercase;
190
+ padding: 0.2rem 0.55rem;
191
+ border-radius: 4px;
192
+ background: var(--bg);
193
+ border: 1px solid var(--border);
194
+ color: var(--text-muted);
195
+ }
196
+ .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }
197
+ .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }
198
+ .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }
199
+ .tag-none { color: var(--text-dim); border-color: var(--border); }
200
+ .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }
201
+ .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }
202
+ .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }
203
+ .tag-nav {
204
+ padding: 0.35rem 0.75rem;
205
+ font-size: 0.65rem;
206
+ border-radius: 6px;
207
+ text-decoration: none;
208
+ transition: border-color 0.2s;
209
+ }
210
+ .tag-nav:hover { border-color: var(--gold); text-decoration: none; }
211
+
212
+ /* === TABLES (dark) === */
213
+ .table-dark { width: 100%; border-collapse: collapse; }
214
+ .table-dark th {
215
+ font-family: var(--mono);
216
+ font-size: 0.65rem;
217
+ letter-spacing: 0.15em;
218
+ text-transform: uppercase;
219
+ color: var(--text-dim);
220
+ text-align: left;
221
+ padding: 0.75rem 1rem;
222
+ border-bottom: 1px solid var(--border);
223
+ }
224
+ .table-dark td {
225
+ padding: 0.75rem 1rem;
226
+ border-bottom: 1px solid rgba(42,42,46,0.5);
227
+ font-size: 0.85rem;
228
+ vertical-align: top;
229
+ }
230
+ .table-dark tr:hover td { background: rgba(212,168,85,0.02); }
231
+ .mono { font-family: var(--mono); font-size: 0.82rem; }
232
+
233
+ /* === SEMANTIC ROLE PILLS === */
234
+ .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }
235
+ .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }
236
+ .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }
237
+ .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }
238
+ .role-attribute { color: var(--text-muted); border-color: var(--border); }
239
+
240
+ /* === DS TYPE PILLS === */
241
+ .ds-type {
242
+ font-family: var(--mono);
243
+ font-size: 0.65rem;
244
+ text-transform: uppercase;
245
+ letter-spacing: 0.08em;
246
+ padding: 0.15rem 0.45rem;
247
+ border-radius: 4px;
248
+ display: inline-block;
249
+ }
250
+ .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }
251
+ .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }
252
+ .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }
253
+ .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }
254
+
255
+ /* === METRIC CARDS === */
256
+ .metric-name {
257
+ font-family: var(--mono);
258
+ font-size: 0.9rem;
259
+ color: var(--gold-light);
260
+ margin-bottom: 0.5rem;
261
+ }
262
+ .metric-desc {
263
+ color: var(--text-muted);
264
+ font-size: 0.85rem;
265
+ line-height: 1.6;
266
+ margin-bottom: 1rem;
267
+ }
268
+ .metric-formula {
269
+ font-family: var(--mono);
270
+ font-size: 0.72rem;
271
+ color: var(--text-dim);
272
+ background: var(--bg);
273
+ border: 1px solid var(--border);
274
+ border-radius: 6px;
275
+ padding: 0.6rem 0.8rem;
276
+ line-height: 1.5;
277
+ overflow-x: auto;
278
+ }
279
+
280
+ /* === QUERY CARDS === */
281
+ .query-card {
282
+ border: 1px solid var(--border);
283
+ border-radius: 12px;
284
+ overflow: hidden;
285
+ margin-bottom: 1.5rem;
286
+ background: var(--bg-card);
287
+ transition: border-color 0.3s;
288
+ }
289
+ .query-card:hover { border-color: var(--gold-dark); }
290
+ .query-question {
291
+ padding: 1.25rem 1.5rem;
292
+ font-family: var(--serif);
293
+ font-size: 1.1rem;
294
+ font-weight: 400;
295
+ font-style: italic;
296
+ color: var(--text);
297
+ border-bottom: 1px solid var(--border);
298
+ display: flex;
299
+ align-items: flex-start;
300
+ gap: 0.6rem;
301
+ }
302
+ .query-question::before {
303
+ content: 'Q';
304
+ font-family: var(--mono);
305
+ font-size: 0.6rem;
306
+ font-style: normal;
307
+ letter-spacing: 0.1em;
308
+ color: var(--gold);
309
+ background: rgba(212,168,85,0.1);
310
+ border: 1px solid rgba(212,168,85,0.2);
311
+ padding: 0.15rem 0.35rem;
312
+ border-radius: 3px;
313
+ flex-shrink: 0;
314
+ margin-top: 0.3rem;
315
+ }
316
+ .query-sql {
317
+ padding: 1rem 1.5rem;
318
+ font-family: var(--mono);
319
+ font-size: 0.78rem;
320
+ color: var(--text-muted);
321
+ line-height: 1.6;
322
+ background: var(--bg);
323
+ overflow-x: auto;
324
+ white-space: pre-wrap;
325
+ }
326
+ .query-meta {
327
+ padding: 0.6rem 1.5rem;
328
+ display: flex;
329
+ gap: 1rem;
330
+ font-size: 0.7rem;
331
+ color: var(--text-dim);
332
+ border-top: 1px solid var(--border);
333
+ }
334
+
335
+ /* === GUARDRAILS === */
336
+ .guardrail {
337
+ border: 1px solid rgba(248, 113, 113, 0.2);
338
+ border-radius: 12px;
339
+ padding: 1.5rem;
340
+ background: rgba(248, 113, 113, 0.03);
341
+ margin-bottom: 1rem;
342
+ }
343
+ .guardrail-name {
344
+ font-family: var(--mono);
345
+ font-size: 0.82rem;
346
+ color: var(--red);
347
+ margin-bottom: 0.4rem;
348
+ }
349
+ .guardrail-filter {
350
+ font-family: var(--mono);
351
+ font-size: 0.78rem;
352
+ color: var(--text);
353
+ background: var(--bg);
354
+ padding: 0.4rem 0.65rem;
355
+ border-radius: 4px;
356
+ border: 1px solid var(--border);
357
+ display: inline-block;
358
+ margin-bottom: 0.6rem;
359
+ }
360
+ .guardrail-reason {
361
+ font-size: 0.82rem;
362
+ color: var(--text-muted);
363
+ font-weight: 300;
364
+ }
365
+
366
+ /* === LINEAGE === */
367
+ .lineage-flow {
368
+ display: flex;
369
+ align-items: stretch;
370
+ gap: 0;
371
+ overflow-x: auto;
372
+ padding: 1.5rem 0;
373
+ }
374
+ .lineage-col {
375
+ display: flex;
376
+ flex-direction: column;
377
+ gap: 0.75rem;
378
+ min-width: 220px;
379
+ }
380
+ .lineage-col-label {
381
+ font-family: var(--mono);
382
+ font-size: 0.6rem;
383
+ letter-spacing: 0.2em;
384
+ text-transform: uppercase;
385
+ color: var(--text-dim);
386
+ margin-bottom: 0.25rem;
387
+ padding-left: 0.5rem;
388
+ }
389
+ .lineage-node {
390
+ border: 1px solid var(--border);
391
+ border-radius: 8px;
392
+ padding: 0.75rem 1rem;
393
+ background: var(--bg-card);
394
+ transition: border-color 0.3s;
395
+ }
396
+ .lineage-node:hover { border-color: var(--gold-dark); }
397
+ .lineage-node-name {
398
+ font-family: var(--mono);
399
+ font-size: 0.78rem;
400
+ color: var(--text);
401
+ margin-bottom: 0.15rem;
402
+ }
403
+ .lineage-node-detail {
404
+ font-size: 0.7rem;
405
+ color: var(--text-dim);
406
+ }
407
+ .lineage-arrow {
408
+ display: flex;
409
+ align-items: center;
410
+ justify-content: center;
411
+ min-width: 50px;
412
+ color: var(--gold-dark);
413
+ font-size: 1.3rem;
414
+ }
415
+
416
+ /* === SCORECARD === */
417
+ .scorecard {
418
+ display: grid;
419
+ grid-template-columns: repeat(3, 1fr);
420
+ gap: 1px;
421
+ background: var(--border);
422
+ border: 1px solid var(--border);
423
+ border-radius: 12px;
424
+ overflow: hidden;
425
+ }
426
+ @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }
427
+ .scorecard-tier {
428
+ background: var(--bg-card);
429
+ padding: 1.5rem;
430
+ }
431
+ .scorecard-tier-header {
432
+ display: flex;
433
+ align-items: center;
434
+ justify-content: space-between;
435
+ margin-bottom: 1rem;
436
+ }
437
+ .scorecard-tier-name {
438
+ font-family: var(--serif);
439
+ font-size: 1.3rem;
440
+ font-weight: 500;
441
+ text-transform: capitalize;
442
+ }
443
+ .scorecard-tier-name.bronze { color: #CD7F32; }
444
+ .scorecard-tier-name.silver { color: #C0C0C0; }
445
+ .scorecard-tier-name.gold { color: var(--gold); }
446
+ .scorecard-pass {
447
+ font-family: var(--mono);
448
+ font-size: 0.6rem;
449
+ letter-spacing: 0.1em;
450
+ text-transform: uppercase;
451
+ padding: 0.15rem 0.5rem;
452
+ border-radius: 4px;
453
+ }
454
+ .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }
455
+ .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }
456
+ .check-list { list-style: none; }
457
+ .check-item {
458
+ display: flex;
459
+ align-items: flex-start;
460
+ gap: 0.4rem;
461
+ padding: 0.3rem 0;
462
+ font-size: 0.75rem;
463
+ color: var(--text-muted);
464
+ line-height: 1.4;
465
+ }
466
+ .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }
467
+ .check-icon.pass { color: var(--green); }
468
+ .check-icon.fail { color: var(--red); }
469
+
470
+ /* === GLOSSARY === */
471
+ .glossary-grid {
472
+ display: grid;
473
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
474
+ gap: 1.5rem;
475
+ }
476
+ .glossary-card {
477
+ border: 1px solid var(--border);
478
+ border-radius: 12px;
479
+ padding: 1.5rem;
480
+ background: var(--bg-card);
481
+ position: relative;
482
+ overflow: hidden;
483
+ transition: border-color 0.3s;
484
+ }
485
+ .glossary-card:hover { border-color: var(--gold-dark); }
486
+ .glossary-card::before {
487
+ content: '';
488
+ position: absolute;
489
+ top: 0; left: 0; right: 0;
490
+ height: 2px;
491
+ background: linear-gradient(90deg, var(--gold-dark), transparent);
492
+ }
493
+ .glossary-term {
494
+ font-family: var(--serif);
495
+ font-size: 1.3rem;
496
+ font-weight: 500;
497
+ margin-bottom: 0.5rem;
498
+ color: var(--text);
499
+ }
500
+ .glossary-def {
501
+ font-size: 0.85rem;
502
+ color: var(--text-muted);
503
+ line-height: 1.7;
504
+ font-weight: 300;
505
+ }
506
+
507
+ /* === EXPANDABLE === */
508
+ .expandable-header {
509
+ cursor: pointer;
510
+ display: flex;
511
+ align-items: center;
512
+ justify-content: space-between;
513
+ user-select: none;
514
+ }
515
+ .expand-icon {
516
+ font-family: var(--mono);
517
+ font-size: 0.9rem;
518
+ color: var(--text-dim);
519
+ transition: transform 0.2s;
520
+ width: 20px;
521
+ text-align: center;
522
+ }
523
+ .expandable-content {
524
+ max-height: 0;
525
+ overflow: hidden;
526
+ transition: max-height 0.3s ease;
527
+ }
528
+
529
+ /* === SEARCH === */
530
+ .search-input {
531
+ width: 100%;
532
+ background: var(--bg-card);
533
+ border: 1px solid var(--border);
534
+ border-radius: 8px;
535
+ padding: 0.8rem 1.2rem;
536
+ font-family: var(--sans);
537
+ font-size: 1rem;
538
+ color: var(--text);
539
+ outline: none;
540
+ transition: border-color 0.2s, box-shadow 0.2s;
541
+ }
542
+ .search-input::placeholder { color: var(--text-dim); }
543
+ .search-input:focus {
544
+ border-color: var(--gold-dark);
545
+ box-shadow: 0 0 0 3px var(--gold-glow);
546
+ }
547
+
548
+ /* === BACK LINK === */
549
+ .back-link {
550
+ font-size: 0.85rem;
551
+ color: var(--text-muted);
552
+ display: inline-flex;
553
+ align-items: center;
554
+ gap: 0.3rem;
555
+ margin-bottom: 1.5rem;
556
+ text-decoration: none;
557
+ }
558
+ .back-link:hover { color: var(--gold); text-decoration: none; }
559
+
560
+ /* === GOVERNANCE GRID === */
561
+ .gov-grid {
562
+ display: grid;
563
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
564
+ gap: 1px;
565
+ background: var(--border);
566
+ border: 1px solid var(--border);
567
+ border-radius: 8px;
568
+ overflow: hidden;
569
+ }
570
+ .gov-item {
571
+ background: var(--bg-card);
572
+ padding: 1rem 1.25rem;
573
+ }
574
+ .gov-label {
575
+ font-family: var(--mono);
576
+ font-size: 0.6rem;
577
+ letter-spacing: 0.15em;
578
+ text-transform: uppercase;
579
+ color: var(--text-dim);
580
+ margin-bottom: 0.3rem;
581
+ }
582
+ .gov-value {
583
+ font-size: 0.9rem;
584
+ color: var(--text);
585
+ }
586
+
587
+ /* === ANIMATIONS === */
588
+ @keyframes fadeUp {
589
+ from { opacity: 0; transform: translateY(20px); }
590
+ to { opacity: 1; transform: translateY(0); }
591
+ }
592
+ @keyframes pulse {
593
+ 0%, 100% { opacity: 1; }
594
+ 50% { opacity: 0.4; }
595
+ }
596
+ .reveal {
597
+ opacity: 0;
598
+ transform: translateY(25px);
599
+ transition: opacity 0.6s ease, transform 0.6s ease;
600
+ }
601
+ .reveal.visible {
602
+ opacity: 1;
603
+ transform: translateY(0);
604
+ }
605
+
606
+ /* === SQL HIGHLIGHT === */
607
+ .sql-kw { color: #818CF8; }
608
+ .sql-fn { color: #34D399; }
609
+ .sql-str { color: #FCD34D; }
610
+ .sql-num { color: #FB923C; }
611
+ .sql-cm { color: var(--text-dim); font-style: italic; }
612
+ </style>
14
613
  </head>`;
15
- var NAV = `<nav class="bg-gray-900 text-white px-6 py-3 flex items-center gap-6">
16
- <a href="<%= basePath %>/" class="font-bold text-lg"><%- siteTitle %></a>
17
- <a href="<%= basePath %>/" class="hover:underline">Models</a>
18
- <a href="<%= basePath %>/glossary.html" class="hover:underline">Glossary</a>
19
- <a href="<%= basePath %>/search.html" class="hover:underline">Search</a>
614
+ var NAV = `<nav style="position:sticky;top:0;z-index:100;background:var(--bg-card);border-bottom:1px solid var(--border);padding:0.75rem 2rem;display:flex;align-items:center;gap:2rem;">
615
+ <a href="<%= basePath %>/index.html" style="font-family:var(--serif);font-size:1.25rem;font-weight:600;color:var(--gold);text-decoration:none;"><%- siteTitle %></a>
616
+ <div style="display:flex;gap:1.5rem;font-size:0.82rem;">
617
+ <a href="<%= basePath %>/index.html" style="color:var(--text-muted);text-decoration:none;">Models</a>
618
+ <a href="<%= basePath %>/glossary.html" style="color:var(--text-muted);text-decoration:none;">Glossary</a>
619
+ <a href="<%= basePath %>/search.html" style="color:var(--text-muted);text-decoration:none;">Search</a>
620
+ </div>
20
621
  </nav>`;
21
- var FOOTER = `<footer class="mt-12 border-t py-4 px-6 text-gray-500 text-sm">
22
- Generated by <a href="https://github.com/erickittelson/ContextKit" class="underline">ContextKit</a>
622
+ var FOOTER = `<footer style="text-align:center;padding:3rem 2rem;color:var(--text-dim);font-size:0.78rem;border-top:1px solid var(--border);margin-top:4rem;">
623
+ Generated by <a href="https://github.com/erickittelson/ContextKit" style="color:var(--gold-dark);">ContextKit</a>
23
624
  </footer>`;
24
625
  var TIER_BADGE = `<% function tierBadge(tier) {
25
- var colors = { none: 'bg-gray-200 text-gray-700', bronze: 'bg-amber-700 text-white', silver: 'bg-gray-400 text-white', gold: 'bg-yellow-400 text-black' };
26
- var cls = colors[tier] || colors.none;
27
- return '<span class="px-2 py-0.5 rounded text-xs font-semibold uppercase ' + cls + '">' + tier + '</span>';
626
+ var cls = { none: 'tag-none', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };
627
+ var c = cls[tier] || cls.none;
628
+ return '<span class="tag ' + c + '">' + tier + '</span>';
28
629
  } %>`;
630
+ var SCRIPTS = `<script>
631
+ (function() {
632
+ // Scroll-reveal observer
633
+ var reveals = document.querySelectorAll('.reveal');
634
+ if (reveals.length > 0 && 'IntersectionObserver' in window) {
635
+ var observer = new IntersectionObserver(function(entries) {
636
+ entries.forEach(function(e) {
637
+ if (e.isIntersecting) {
638
+ e.target.classList.add('visible');
639
+ observer.unobserve(e.target);
640
+ }
641
+ });
642
+ }, { threshold: 0.1 });
643
+ reveals.forEach(function(el) { observer.observe(el); });
644
+ } else {
645
+ reveals.forEach(function(el) { el.classList.add('visible'); });
646
+ }
647
+
648
+ // Expandable sections
649
+ window.toggleExpand = function(id) {
650
+ var el = document.getElementById(id);
651
+ var icon = document.getElementById(id + '-icon');
652
+ if (!el) return;
653
+ if (el.style.maxHeight && el.style.maxHeight !== '0px') {
654
+ el.style.maxHeight = '0px';
655
+ if (icon) icon.textContent = '+';
656
+ } else {
657
+ el.style.maxHeight = el.scrollHeight + 'px';
658
+ if (icon) icon.textContent = '\\u2212';
659
+ }
660
+ };
661
+
662
+ // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content
663
+ window.highlightSQL = function() {
664
+ var blocks = document.querySelectorAll('.sql-highlight');
665
+ var kw = /\\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|CROSS|ON|AND|OR|NOT|IN|AS|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|UNION|ALL|DISTINCT|CASE|WHEN|THEN|ELSE|END|IS|NULL|BETWEEN|LIKE|EXISTS|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SET|VALUES|INTO|WITH|DESC|ASC|OVER|PARTITION|WINDOW|FILTER|LATERAL|UNNEST|TRUE|FALSE)\\b/gi;
666
+ var fn = /\\b(SUM|COUNT|AVG|MIN|MAX|ROUND|COALESCE|CAST|NULLIF|ABS|UPPER|LOWER|LENGTH|TRIM|SUBSTRING|CONCAT|ROW_NUMBER|RANK|DENSE_RANK|LAG|LEAD|FIRST_VALUE|LAST_VALUE|NTILE|PERCENTILE_CONT|STRING_AGG|ARRAY_AGG|LIST|STRUCT_PACK)\\b/gi;
667
+ var str = /('(?:[^'\\\\]|\\\\.)*')/g;
668
+ var num = /\\b(\\d+\\.?\\d*)\\b/g;
669
+
670
+ blocks.forEach(function(block) {
671
+ var text = block.textContent || '';
672
+ text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
673
+ text = text.replace(str, '<span class="sql-str">$1</span>');
674
+ text = text.replace(kw, '<span class="sql-kw">$&</span>');
675
+ text = text.replace(fn, '<span class="sql-fn">$&</span>');
676
+ block.innerHTML = text;
677
+ });
678
+ };
679
+ if (document.readyState === 'loading') {
680
+ document.addEventListener('DOMContentLoaded', window.highlightSQL);
681
+ } else {
682
+ window.highlightSQL();
683
+ }
684
+ })();
685
+ </script>`;
686
+
687
+ // src/templates/index.ts
29
688
  var indexTemplate = `${HEAD}
30
- <body class="bg-white text-gray-900 min-h-screen">
689
+ <body>
31
690
  ${NAV}
32
691
  ${TIER_BADGE}
33
- <main class="max-w-5xl mx-auto p-6">
34
- <h1 class="text-3xl font-bold mb-6"><%- siteTitle %></h1>
35
692
 
36
- <section>
37
- <h2 class="text-xl font-semibold mb-4">Models</h2>
38
- <% if (Object.keys(models).length === 0) { %>
39
- <p class="text-gray-500">No models found.</p>
693
+ <section class="hero">
694
+ <div class="hero-eyebrow"><%- siteTitle %></div>
695
+ <h1>Metadata<br><em>Catalog</em></h1>
696
+ <p class="hero-sub">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>
697
+ </section>
698
+
699
+ <div class="divider"></div>
700
+
701
+ <main class="page">
702
+ <%
703
+ var modelNames = Object.keys(models);
704
+ var totalDatasets = 0;
705
+ var totalFields = 0;
706
+ for (var mn of modelNames) {
707
+ var m = models[mn];
708
+ if (m.datasets) {
709
+ totalDatasets += m.datasets.length;
710
+ for (var ds of m.datasets) {
711
+ if (ds.fields) totalFields += ds.fields.length;
712
+ }
713
+ }
714
+ }
715
+ var totalTerms = Object.keys(terms).length;
716
+ var totalOwners = Object.keys(owners).length;
717
+ %>
718
+
719
+ <div class="stats reveal">
720
+ <div class="stat">
721
+ <div class="stat-value"><%= modelNames.length %></div>
722
+ <div class="stat-label">Models</div>
723
+ </div>
724
+ <div class="stat">
725
+ <div class="stat-value"><%= totalDatasets %></div>
726
+ <div class="stat-label">Datasets</div>
727
+ </div>
728
+ <div class="stat">
729
+ <div class="stat-value"><%= totalFields %></div>
730
+ <div class="stat-label">Fields</div>
731
+ </div>
732
+ <div class="stat">
733
+ <div class="stat-value"><%= totalTerms %></div>
734
+ <div class="stat-label">Terms</div>
735
+ </div>
736
+ <div class="stat">
737
+ <div class="stat-value"><%= totalOwners %></div>
738
+ <div class="stat-label">Owners</div>
739
+ </div>
740
+ </div>
741
+
742
+ <div class="section reveal">
743
+ <div class="section-label">Semantic Models</div>
744
+ <h2 class="section-title">Models</h2>
745
+ <% if (modelNames.length === 0) { %>
746
+ <p style="color:var(--text-muted);">No models found.</p>
40
747
  <% } else { %>
41
- <div class="grid gap-4">
42
- <% for (var name of Object.keys(models)) { %>
43
- <div class="border rounded-lg p-4 hover:shadow transition">
44
- <div class="flex items-center gap-3">
45
- <a href="<%= basePath %>/models/<%= name %>.html" class="text-lg font-medium text-blue-600 hover:underline"><%= name %></a>
748
+ <div class="card-grid">
749
+ <% for (var name of modelNames) { %>
750
+ <div class="card">
751
+ <div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:0.6rem;">
752
+ <a href="<%= basePath %>/models/<%= name %>.html" style="font-family:var(--mono);font-size:0.95rem;color:var(--gold-light);text-decoration:none;">
753
+ <%= name %>
754
+ </a>
46
755
  <% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>
47
756
  </div>
48
757
  <% if (models[name].description) { %>
49
- <p class="text-gray-600 mt-1"><%= models[name].description %></p>
758
+ <p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:0.75rem;font-weight:300;"><%= models[name].description %></p>
50
759
  <% } %>
51
760
  <% if (governance[name]) { %>
52
- <div class="flex gap-2 mt-2 text-xs text-gray-500">
761
+ <div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;">
53
762
  <% if (governance[name].owner) { %>
54
- <span>Owner: <a href="<%= basePath %>/owners/<%= governance[name].owner %>.html" class="underline"><%= governance[name].owner %></a></span>
763
+ <a href="<%= basePath %>/owners/<%= governance[name].owner %>.html" class="tag tag-nav" style="text-decoration:none;"><%= governance[name].owner %></a>
55
764
  <% } %>
56
765
  <% if (governance[name].trust) { %>
57
- <span>Trust: <%= governance[name].trust %></span>
766
+ <span class="tag tag-green"><%= governance[name].trust %></span>
58
767
  <% } %>
768
+ <% if (governance[name].tags) { for (var t of governance[name].tags) { %>
769
+ <span class="tag"><%= t %></span>
770
+ <% } } %>
59
771
  </div>
60
772
  <% } %>
61
773
  </div>
62
774
  <% } %>
63
775
  </div>
64
776
  <% } %>
65
- </section>
777
+ </div>
66
778
 
67
779
  <% if (Object.keys(owners).length > 0) { %>
68
- <section class="mt-8">
69
- <h2 class="text-xl font-semibold mb-4">Owners</h2>
70
- <ul class="space-y-1">
780
+ <div class="section reveal">
781
+ <div class="section-label">Data Stewardship</div>
782
+ <h2 class="section-title">Owners</h2>
783
+ <div class="card-grid-sm">
71
784
  <% for (var oid of Object.keys(owners)) { %>
72
- <li><a href="<%= basePath %>/owners/<%= oid %>.html" class="text-blue-600 hover:underline"><%= owners[oid].display_name %></a></li>
785
+ <a href="<%= basePath %>/owners/<%= oid %>.html" class="card" style="text-decoration:none;">
786
+ <div style="font-size:1rem;font-weight:500;color:var(--text);margin-bottom:0.25rem;"><%= owners[oid].display_name %></div>
787
+ <% if (owners[oid].team) { %>
788
+ <div style="font-size:0.78rem;color:var(--text-dim);"><%= owners[oid].team %></div>
789
+ <% } %>
790
+ </a>
73
791
  <% } %>
74
- </ul>
75
- </section>
792
+ </div>
793
+ </div>
76
794
  <% } %>
77
795
  </main>
796
+
78
797
  ${FOOTER}
798
+ ${SCRIPTS}
79
799
  </body>
80
800
  </html>`;
801
+
802
+ // src/templates/model.ts
81
803
  var modelTemplate = `${HEAD}
82
- <body class="bg-white text-gray-900 min-h-screen">
804
+ <body>
83
805
  ${NAV}
84
806
  ${TIER_BADGE}
85
- <main class="max-w-5xl mx-auto p-6">
86
- <div class="flex items-center gap-3 mb-2">
87
- <h1 class="text-3xl font-bold"><%= model.name %></h1>
807
+
808
+ <main class="page">
809
+ <a href="<%= basePath %>/" class="back-link">&larr; All Models</a>
810
+
811
+ <div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.5rem;">
812
+ <h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);"><%= model.name %></h1>
88
813
  <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
89
814
  </div>
90
815
  <% if (model.description) { %>
91
- <p class="text-gray-600 mb-4"><%= model.description %></p>
816
+ <p style="color:var(--text-muted);font-size:1rem;margin-bottom:1.5rem;max-width:700px;font-weight:300;"><%= model.description %></p>
92
817
  <% } %>
93
818
 
94
- <div class="flex gap-3 mb-6 text-sm">
95
- <a href="<%= basePath %>/models/<%= model.name %>/schema.html" class="text-blue-600 hover:underline">Schema Browser</a>
96
- <a href="<%= basePath %>/models/<%= model.name %>/rules.html" class="text-blue-600 hover:underline">Rules &amp; Queries</a>
819
+ <div style="display:flex;gap:0.6rem;margin-bottom:2.5rem;">
820
+ <a href="<%= basePath %>/models/<%= model.name %>/schema.html" class="tag tag-nav tag-blue">Schema Browser</a>
821
+ <a href="<%= basePath %>/models/<%= model.name %>/rules.html" class="tag tag-nav tag-gold">Rules &amp; Queries</a>
97
822
  </div>
98
823
 
99
824
  <% if (gov) { %>
100
- <section class="mb-6">
101
- <h2 class="text-xl font-semibold mb-2">Governance</h2>
102
- <table class="table-auto border-collapse text-sm">
103
- <tbody>
104
- <tr><td class="pr-4 font-medium">Owner</td><td><a href="<%= basePath %>/owners/<%= gov.owner %>.html" class="text-blue-600 underline"><%= gov.owner %></a></td></tr>
105
- <% if (gov.trust) { %><tr><td class="pr-4 font-medium">Trust</td><td><%= gov.trust %></td></tr><% } %>
106
- <% if (gov.security) { %><tr><td class="pr-4 font-medium">Security</td><td><%= gov.security %></td></tr><% } %>
107
- <% if (gov.tags && gov.tags.length > 0) { %><tr><td class="pr-4 font-medium">Tags</td><td><%= gov.tags.join(', ') %></td></tr><% } %>
108
- </tbody>
109
- </table>
110
- </section>
825
+ <div class="section reveal">
826
+ <div class="section-label">Governance</div>
827
+ <div class="gov-grid">
828
+ <div class="gov-item">
829
+ <div class="gov-label">Owner</div>
830
+ <div class="gov-value"><a href="<%= basePath %>/owners/<%= gov.owner %>.html"><%= gov.owner %></a></div>
831
+ </div>
832
+ <% if (gov.trust) { %>
833
+ <div class="gov-item">
834
+ <div class="gov-label">Trust</div>
835
+ <div class="gov-value"><%= gov.trust %></div>
836
+ </div>
837
+ <% } %>
838
+ <% if (gov.security) { %>
839
+ <div class="gov-item">
840
+ <div class="gov-label">Security</div>
841
+ <div class="gov-value"><%= gov.security %></div>
842
+ </div>
843
+ <% } %>
844
+ <% if (gov.tags && gov.tags.length > 0) { %>
845
+ <div class="gov-item">
846
+ <div class="gov-label">Tags</div>
847
+ <div class="gov-value" style="display:flex;gap:0.4rem;flex-wrap:wrap;">
848
+ <% for (var t of gov.tags) { %><span class="tag"><%= t %></span><% } %>
849
+ </div>
850
+ </div>
851
+ <% } %>
852
+ </div>
853
+ </div>
111
854
  <% } %>
112
855
 
113
856
  <% if (model.datasets && model.datasets.length > 0) { %>
114
- <section class="mb-6">
115
- <h2 class="text-xl font-semibold mb-2">Datasets</h2>
116
- <div class="space-y-3">
117
- <% for (var ds of model.datasets) { %>
118
- <div class="border rounded p-3">
119
- <div class="font-medium"><%= ds.name %></div>
120
- <div class="text-xs text-gray-500">Source: <%= ds.source %></div>
121
- <% if (ds.description) { %><p class="text-sm text-gray-600 mt-1"><%= ds.description %></p><% } %>
122
- <% if (ds.fields && ds.fields.length > 0) { %>
123
- <div class="text-xs text-gray-500 mt-1"><%= ds.fields.length %> field(s)</div>
124
- <% } %>
857
+ <div class="section reveal">
858
+ <div class="section-label">Data Explorer</div>
859
+ <h2 class="section-title">Datasets</h2>
860
+ <p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:1.5rem;font-weight:300;">Click a dataset to explore its fields, governance, and metadata.</p>
861
+ <div style="display:flex;flex-direction:column;gap:1rem;">
862
+ <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>
863
+ <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
864
+ <div class="card">
865
+ <div class="expandable-header" onclick="toggleExpand('ds-<%= i %>')">
866
+ <div>
867
+ <div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:0.25rem;">
868
+ <span class="mono" style="color:var(--text);"><%= ds.name %></span>
869
+ <% if (dsGov && dsGov.table_type) { %>
870
+ <span class="ds-type ds-type-<%= dsGov.table_type %>"><%= dsGov.table_type %></span>
871
+ <% } %>
872
+ <% if (ds.fields) { %><span class="tag"><%= ds.fields.length %> fields</span><% } %>
873
+ </div>
874
+ <div style="font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);">
875
+ <%= ds.source %>
876
+ <% if (dsGov && dsGov.grain) { %> &middot; <span style="color:var(--text-muted);font-family:var(--sans);"><%= dsGov.grain %></span><% } %>
877
+ <% if (dsGov && dsGov.refresh) { %> &middot; <span style="color:var(--text-muted);font-family:var(--sans);"><%= dsGov.refresh %></span><% } %>
878
+ </div>
879
+ <% if (ds.description) { %>
880
+ <p style="font-size:0.82rem;color:var(--text-muted);margin-top:0.4rem;font-weight:300;"><%= ds.description %></p>
881
+ <% } %>
882
+ </div>
883
+ <span class="expand-icon" id="ds-<%= i %>-icon">+</span>
884
+ </div>
885
+ <div class="expandable-content" id="ds-<%= i %>">
886
+ <% if (ds.fields && ds.fields.length > 0) { %>
887
+ <table class="table-dark" style="margin-top:1rem;">
888
+ <thead>
889
+ <tr>
890
+ <th>Field</th>
891
+ <th>Description</th>
892
+ <th>Role</th>
893
+ <th>Aggregation</th>
894
+ </tr>
895
+ </thead>
896
+ <tbody>
897
+ <% for (var field of ds.fields) { %>
898
+ <% var fKey = ds.name + '.' + field.name; %>
899
+ <% var fGov = gov && gov.fields && gov.fields[fKey]; %>
900
+ <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>
901
+ <tr>
902
+ <td class="mono" style="font-size:0.78rem;"><%= field.name %></td>
903
+ <td style="color:var(--text-muted);font-size:0.82rem;"><%= field.description || '' %></td>
904
+ <td><% if (role) { %><span class="tag role-<%= role %>"><%= role %></span><% } %></td>
905
+ <td style="font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
906
+ </tr>
907
+ <% } %>
908
+ </tbody>
909
+ </table>
910
+ <% } %>
911
+ </div>
125
912
  </div>
126
913
  <% } %>
127
914
  </div>
128
- </section>
915
+ </div>
129
916
  <% } %>
130
917
 
131
918
  <% if (model.relationships && model.relationships.length > 0) { %>
132
- <section class="mb-6">
133
- <h2 class="text-xl font-semibold mb-2">Relationships</h2>
134
- <table class="table-auto w-full text-sm border-collapse">
919
+ <div class="section reveal">
920
+ <div class="section-label">Data Model</div>
921
+ <h2 class="section-title">Relationships</h2>
922
+ <table class="table-dark">
135
923
  <thead>
136
- <tr class="border-b"><th class="text-left py-1 pr-3">Name</th><th class="text-left py-1 pr-3">From</th><th class="text-left py-1">To</th></tr>
924
+ <tr><th>Name</th><th>From</th><th></th><th>To</th></tr>
137
925
  </thead>
138
926
  <tbody>
139
927
  <% for (var rel of model.relationships) { %>
140
- <tr class="border-b"><td class="py-1 pr-3"><%= rel.name %></td><td class="py-1 pr-3"><%= rel.from %></td><td class="py-1"><%= rel.to %></td></tr>
928
+ <tr>
929
+ <td style="color:var(--text-muted);font-size:0.82rem;"><%= rel.name %></td>
930
+ <td class="mono" style="font-size:0.78rem;"><%= rel.from %></td>
931
+ <td style="color:var(--gold-dark);text-align:center;">&rarr;</td>
932
+ <td class="mono" style="font-size:0.78rem;"><%= rel.to %></td>
933
+ </tr>
141
934
  <% } %>
142
935
  </tbody>
143
936
  </table>
144
- </section>
937
+ </div>
145
938
  <% } %>
146
939
 
147
940
  <% if (model.metrics && model.metrics.length > 0) { %>
148
- <section class="mb-6">
149
- <h2 class="text-xl font-semibold mb-2">Metrics</h2>
150
- <div class="space-y-2">
941
+ <div class="section reveal">
942
+ <div class="section-label">Computed Metrics</div>
943
+ <h2 class="section-title">Metrics</h2>
944
+ <div class="card-grid">
151
945
  <% for (var metric of model.metrics) { %>
152
- <div class="border rounded p-3">
153
- <div class="font-medium"><%= metric.name %></div>
154
- <% if (metric.description) { %><p class="text-sm text-gray-600"><%= metric.description %></p><% } %>
946
+ <div class="card">
947
+ <div class="metric-name"><%= metric.name %></div>
948
+ <% if (metric.description) { %><div class="metric-desc"><%= metric.description %></div><% } %>
949
+ <% if (metric.expression && metric.expression.dialects && metric.expression.dialects.length > 0) { %>
950
+ <div class="metric-formula"><%= metric.expression.dialects[0].expression %></div>
951
+ <% } %>
155
952
  </div>
156
953
  <% } %>
157
954
  </div>
158
- </section>
955
+ </div>
159
956
  <% } %>
160
957
 
161
- <% if (tier) { %>
162
- <section class="mb-6">
163
- <h2 class="text-xl font-semibold mb-2">Tier Details</h2>
164
- <% var tierLevels = ['bronze', 'silver', 'gold']; %>
165
- <% for (var lvl of tierLevels) { %>
166
- <div class="mb-3">
167
- <h3 class="font-medium capitalize flex items-center gap-2">
168
- <%= lvl %>
169
- <% if (tier[lvl].passed) { %>
170
- <span class="text-green-600 text-xs">Passed</span>
171
- <% } else { %>
172
- <span class="text-red-500 text-xs">Not passed</span>
173
- <% } %>
174
- </h3>
175
- <% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>
176
- <ul class="text-sm ml-4 list-disc">
177
- <% for (var chk of tier[lvl].checks) { %>
178
- <li class="<%= chk.passed ? 'text-green-700' : 'text-red-600' %>"><%= chk.label %><% if (chk.detail) { %> \u2014 <%= chk.detail %><% } %></li>
179
- <% } %>
180
- </ul>
958
+ <% if (lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0)) { %>
959
+ <div class="section reveal">
960
+ <div class="section-label">Data Lineage</div>
961
+ <h2 class="section-title">Lineage</h2>
962
+ <div class="lineage-flow">
963
+ <% if (lineage.upstream && lineage.upstream.length > 0) { %>
964
+ <div class="lineage-col">
965
+ <div class="lineage-col-label">Upstream</div>
966
+ <% for (var u of lineage.upstream) { %>
967
+ <div class="lineage-node">
968
+ <div class="lineage-node-name"><%= u.source %></div>
969
+ <div class="lineage-node-detail"><%= u.type || '' %><% if (u.tool) { %> via <%= u.tool %><% } %></div>
970
+ </div>
181
971
  <% } %>
182
972
  </div>
183
- <% } %>
184
- </section>
973
+ <div class="lineage-arrow">&rarr;</div>
974
+ <% } %>
975
+ <div class="lineage-col">
976
+ <div class="lineage-col-label">This Model</div>
977
+ <div class="lineage-node" style="border-color:var(--gold-dark);">
978
+ <div class="lineage-node-name" style="color:var(--gold);"><%= model.name %></div>
979
+ </div>
980
+ </div>
981
+ <% if (lineage.downstream && lineage.downstream.length > 0) { %>
982
+ <div class="lineage-arrow">&rarr;</div>
983
+ <div class="lineage-col">
984
+ <div class="lineage-col-label">Downstream</div>
985
+ <% for (var d of lineage.downstream) { %>
986
+ <div class="lineage-node">
987
+ <div class="lineage-node-name"><%= d.target %></div>
988
+ <div class="lineage-node-detail"><%= d.type || '' %><% if (d.tool) { %> via <%= d.tool %><% } %></div>
989
+ </div>
990
+ <% } %>
991
+ </div>
992
+ <% } %>
993
+ </div>
994
+ </div>
995
+ <% } %>
996
+
997
+ <% if (tier) { %>
998
+ <div class="section reveal">
999
+ <div class="section-label">Data Quality</div>
1000
+ <h2 class="section-title">Tier Scorecard</h2>
1001
+ <div class="scorecard">
1002
+ <% var tierLevels = ['bronze', 'silver', 'gold']; %>
1003
+ <% for (var lvl of tierLevels) { %>
1004
+ <div class="scorecard-tier">
1005
+ <div class="scorecard-tier-header">
1006
+ <span class="scorecard-tier-name <%= lvl %>"><%= lvl %></span>
1007
+ <% if (tier[lvl].passed) { %>
1008
+ <span class="scorecard-pass passed">Passed</span>
1009
+ <% } else { %>
1010
+ <span class="scorecard-pass failed">Not passed</span>
1011
+ <% } %>
1012
+ </div>
1013
+ <% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>
1014
+ <ul class="check-list">
1015
+ <% for (var chk of tier[lvl].checks) { %>
1016
+ <li class="check-item">
1017
+ <span class="check-icon <%= chk.passed ? 'pass' : 'fail' %>"><%= chk.passed ? '\\u2713' : '\\u2717' %></span>
1018
+ <span><%= chk.label %><% if (chk.detail) { %> &mdash; <span style="color:var(--text-dim);"><%= chk.detail %></span><% } %></span>
1019
+ </li>
1020
+ <% } %>
1021
+ </ul>
1022
+ <% } %>
1023
+ </div>
1024
+ <% } %>
1025
+ </div>
1026
+ </div>
185
1027
  <% } %>
186
1028
  </main>
1029
+
187
1030
  ${FOOTER}
1031
+ ${SCRIPTS}
188
1032
  </body>
189
1033
  </html>`;
1034
+
1035
+ // src/templates/schema.ts
190
1036
  var schemaTemplate = `${HEAD}
191
- <body class="bg-white text-gray-900 min-h-screen">
1037
+ <body>
192
1038
  ${NAV}
193
1039
  ${TIER_BADGE}
194
- <main class="max-w-5xl mx-auto p-6">
195
- <div class="flex items-center gap-3 mb-2">
196
- <h1 class="text-3xl font-bold"><%= model.name %> \u2014 Schema Browser</h1>
1040
+
1041
+ <main class="page">
1042
+ <a href="<%= basePath %>/models/<%= model.name %>.html" class="back-link">&larr; Back to <%= model.name %></a>
1043
+
1044
+ <div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:2rem;">
1045
+ <h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);"><%= model.name %></h1>
197
1046
  <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
198
- </div>
199
- <div class="mb-4 text-sm">
200
- <a href="<%= basePath %>/models/<%= model.name %>.html" class="text-blue-600 hover:underline">&larr; Back to model</a>
1047
+ <span style="font-size:0.85rem;color:var(--text-dim);">Schema Browser</span>
201
1048
  </div>
202
1049
 
203
1050
  <% if (model.datasets && model.datasets.length > 0) { %>
204
- <% for (var ds of model.datasets) { %>
205
- <section class="mb-8 border rounded-lg p-4">
206
- <h2 class="text-xl font-semibold mb-1"><%= ds.name %></h2>
207
- <div class="text-xs text-gray-500 mb-2">Source: <%= ds.source %></div>
208
- <% if (ds.description) { %><p class="text-sm text-gray-600 mb-3"><%= ds.description %></p><% } %>
209
-
210
- <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
211
- <% if (dsGov) { %>
212
- <div class="text-xs text-gray-500 mb-3 flex gap-4">
213
- <% if (dsGov.grain) { %><span>Grain: <strong><%= dsGov.grain %></strong></span><% } %>
214
- <% if (dsGov.table_type) { %><span>Type: <strong><%= dsGov.table_type %></strong></span><% } %>
215
- <% if (dsGov.refresh) { %><span>Refresh: <strong><%= dsGov.refresh %></strong></span><% } %>
216
- <% if (dsGov.security) { %><span>Security: <strong><%= dsGov.security %></strong></span><% } %>
1051
+ <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>
1052
+ <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
1053
+ <div class="section reveal">
1054
+ <div class="card" style="padding:0;overflow:hidden;">
1055
+ <div style="padding:1.25rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.5rem;">
1056
+ <div style="display:flex;align-items:center;gap:0.6rem;">
1057
+ <span class="mono" style="font-size:1rem;color:var(--text);"><%= ds.name %></span>
1058
+ <% if (dsGov && dsGov.table_type) { %>
1059
+ <span class="ds-type ds-type-<%= dsGov.table_type %>"><%= dsGov.table_type %></span>
1060
+ <% } %>
1061
+ </div>
1062
+ <div style="display:flex;gap:0.75rem;flex-wrap:wrap;">
1063
+ <% if (dsGov && dsGov.grain) { %><span class="tag"><%= dsGov.grain %></span><% } %>
1064
+ <% if (dsGov && dsGov.refresh) { %><span class="tag"><%= dsGov.refresh %></span><% } %>
1065
+ <% if (dsGov && dsGov.security) { %><span class="tag"><%= dsGov.security %></span><% } %>
1066
+ </div>
217
1067
  </div>
218
- <% } %>
1068
+ <div style="padding:0 1.5rem 0.5rem;font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);padding-top:0.75rem;">
1069
+ Source: <%= ds.source %>
1070
+ </div>
1071
+ <% if (ds.description) { %>
1072
+ <div style="padding:0 1.5rem 1rem;font-size:0.85rem;color:var(--text-muted);font-weight:300;"><%= ds.description %></div>
1073
+ <% } %>
219
1074
 
220
- <% if (ds.fields && ds.fields.length > 0) { %>
221
- <table class="table-auto w-full text-sm border-collapse">
222
- <thead>
223
- <tr class="border-b bg-gray-50">
224
- <th class="text-left py-2 px-2">Field</th>
225
- <th class="text-left py-2 px-2">Description</th>
226
- <th class="text-left py-2 px-2">Semantic Role</th>
227
- <th class="text-left py-2 px-2">Aggregation</th>
228
- </tr>
229
- </thead>
230
- <tbody>
231
- <% for (var field of ds.fields) { %>
232
- <% var fieldKey = ds.name + '.' + field.name; %>
233
- <% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>
234
- <tr class="border-b">
235
- <td class="py-1 px-2 font-mono text-xs"><%= field.name %></td>
236
- <td class="py-1 px-2"><%= field.description || '' %></td>
237
- <td class="py-1 px-2"><%= fGov && fGov.semantic_role ? fGov.semantic_role : '' %></td>
238
- <td class="py-1 px-2"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
1075
+ <% if (ds.fields && ds.fields.length > 0) { %>
1076
+ <table class="table-dark">
1077
+ <thead>
1078
+ <tr>
1079
+ <th>Field</th>
1080
+ <th>Description</th>
1081
+ <th>Semantic Role</th>
1082
+ <th>Aggregation</th>
239
1083
  </tr>
240
- <% } %>
241
- </tbody>
242
- </table>
243
- <% } else { %>
244
- <p class="text-gray-400 text-sm">No fields defined.</p>
245
- <% } %>
246
- </section>
1084
+ </thead>
1085
+ <tbody>
1086
+ <% for (var field of ds.fields) { %>
1087
+ <% var fieldKey = ds.name + '.' + field.name; %>
1088
+ <% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>
1089
+ <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>
1090
+ <tr>
1091
+ <td class="mono" style="font-size:0.78rem;"><%= field.name %></td>
1092
+ <td style="color:var(--text-muted);font-size:0.82rem;"><%= field.description || '' %></td>
1093
+ <td><% if (role) { %><span class="tag role-<%= role %>"><%= role %></span><% } %></td>
1094
+ <td style="font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
1095
+ </tr>
1096
+ <% } %>
1097
+ </tbody>
1098
+ </table>
1099
+ <% } else { %>
1100
+ <p style="padding:1.5rem;color:var(--text-dim);font-size:0.85rem;">No fields defined.</p>
1101
+ <% } %>
1102
+ </div>
1103
+ </div>
247
1104
  <% } %>
248
1105
  <% } else { %>
249
- <p class="text-gray-500">No datasets found.</p>
1106
+ <p style="color:var(--text-muted);">No datasets found.</p>
250
1107
  <% } %>
251
1108
  </main>
1109
+
252
1110
  ${FOOTER}
1111
+ ${SCRIPTS}
253
1112
  </body>
254
1113
  </html>`;
1114
+
1115
+ // src/templates/rules.ts
255
1116
  var rulesTemplate = `${HEAD}
256
- <body class="bg-white text-gray-900 min-h-screen">
1117
+ <body>
257
1118
  ${NAV}
258
- <main class="max-w-5xl mx-auto p-6">
259
- <h1 class="text-3xl font-bold mb-2"><%= modelName %> \u2014 Rules &amp; Queries</h1>
260
- <div class="mb-4 text-sm">
261
- <a href="<%= basePath %>/models/<%= modelName %>.html" class="text-blue-600 hover:underline">&larr; Back to model</a>
262
- </div>
1119
+
1120
+ <main class="page">
1121
+ <a href="<%= basePath %>/models/<%= modelName %>.html" class="back-link">&larr; Back to <%= modelName %></a>
1122
+
1123
+ <h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);margin-bottom:2rem;">
1124
+ <%= modelName %> <span style="color:var(--text-dim);font-weight:300;">&mdash; Rules &amp; Queries</span>
1125
+ </h1>
263
1126
 
264
1127
  <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>
265
- <section class="mb-8">
266
- <h2 class="text-xl font-semibold mb-3">Golden Queries</h2>
267
- <div class="space-y-4">
268
- <% for (var gq of rules.golden_queries) { %>
269
- <div class="border rounded-lg p-4">
270
- <div class="font-medium mb-2"><%= gq.question %></div>
271
- <pre class="bg-gray-100 rounded p-3 text-sm overflow-x-auto"><code><%= gq.sql %></code></pre>
272
- <% if (gq.dialect) { %><div class="text-xs text-gray-500 mt-1">Dialect: <%= gq.dialect %></div><% } %>
273
- <% if (gq.tags && gq.tags.length > 0) { %><div class="text-xs text-gray-500 mt-1">Tags: <%= gq.tags.join(', ') %></div><% } %>
1128
+ <div class="section reveal">
1129
+ <div class="section-label">Golden Queries</div>
1130
+ <h2 class="section-title">Pre-validated questions</h2>
1131
+ <% for (var gq of rules.golden_queries) { %>
1132
+ <div class="query-card">
1133
+ <div class="query-question"><%= gq.question %></div>
1134
+ <div class="query-sql sql-highlight"><%= gq.sql %></div>
1135
+ <% if (gq.dialect || (gq.tags && gq.tags.length > 0)) { %>
1136
+ <div class="query-meta">
1137
+ <% if (gq.dialect) { %><span>Dialect: <%= gq.dialect %></span><% } %>
1138
+ <% if (gq.tags && gq.tags.length > 0) { %><span>Tags: <%= gq.tags.join(', ') %></span><% } %>
274
1139
  </div>
275
- <% } %>
276
- </div>
277
- </section>
1140
+ <% } %>
1141
+ </div>
1142
+ <% } %>
1143
+ </div>
278
1144
  <% } %>
279
1145
 
280
1146
  <% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>
281
- <section class="mb-8">
282
- <h2 class="text-xl font-semibold mb-3">Business Rules</h2>
283
- <div class="space-y-3">
1147
+ <div class="section reveal">
1148
+ <div class="section-label">Business Rules</div>
1149
+ <h2 class="section-title">Business Rules</h2>
1150
+ <div style="display:flex;flex-direction:column;gap:1rem;">
284
1151
  <% for (var br of rules.business_rules) { %>
285
- <div class="border rounded p-4">
286
- <div class="font-medium"><%= br.name %></div>
287
- <p class="text-sm text-gray-600 mt-1"><%= br.definition %></p>
1152
+ <div class="card">
1153
+ <div style="font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;"><%= br.name %></div>
1154
+ <p style="font-size:0.85rem;color:var(--text-muted);line-height:1.6;font-weight:300;"><%= br.definition %></p>
288
1155
  <% if (br.enforcement && br.enforcement.length > 0) { %>
289
- <div class="text-xs text-gray-500 mt-1">Enforcement: <%= br.enforcement.join(', ') %></div>
1156
+ <div style="display:flex;gap:0.4rem;margin-top:0.6rem;flex-wrap:wrap;">
1157
+ <% for (var e of br.enforcement) { %><span class="tag tag-green"><%= e %></span><% } %>
1158
+ </div>
290
1159
  <% } %>
291
1160
  <% if (br.avoid && br.avoid.length > 0) { %>
292
- <div class="text-xs text-red-500 mt-1">Avoid: <%= br.avoid.join(', ') %></div>
1161
+ <div style="display:flex;gap:0.4rem;margin-top:0.4rem;flex-wrap:wrap;">
1162
+ <% for (var a of br.avoid) { %><span class="tag tag-red"><%= a %></span><% } %>
1163
+ </div>
293
1164
  <% } %>
294
1165
  </div>
295
1166
  <% } %>
296
1167
  </div>
297
- </section>
1168
+ </div>
298
1169
  <% } %>
299
1170
 
300
1171
  <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>
301
- <section class="mb-8">
302
- <h2 class="text-xl font-semibold mb-3">Guardrail Filters</h2>
303
- <div class="space-y-3">
304
- <% for (var gf of rules.guardrail_filters) { %>
305
- <div class="border rounded p-4">
306
- <div class="font-medium"><%= gf.name %></div>
307
- <pre class="bg-gray-100 rounded p-2 text-sm mt-1"><code><%= gf.filter %></code></pre>
308
- <p class="text-sm text-gray-600 mt-1"><%= gf.reason %></p>
309
- </div>
310
- <% } %>
311
- </div>
312
- </section>
1172
+ <div class="section reveal">
1173
+ <div class="section-label">Safety</div>
1174
+ <h2 class="section-title">Guardrail Filters</h2>
1175
+ <% for (var gf of rules.guardrail_filters) { %>
1176
+ <div class="guardrail">
1177
+ <div class="guardrail-name"><%= gf.name %></div>
1178
+ <div class="guardrail-filter"><%= gf.filter %></div>
1179
+ <div class="guardrail-reason"><%= gf.reason %></div>
1180
+ <% if (gf.tables && gf.tables.length > 0) { %>
1181
+ <div style="display:flex;gap:0.4rem;margin-top:0.6rem;">
1182
+ <% for (var tb of gf.tables) { %><span class="tag"><%= tb %></span><% } %>
1183
+ </div>
1184
+ <% } %>
1185
+ </div>
1186
+ <% } %>
1187
+ </div>
313
1188
  <% } %>
314
1189
 
315
1190
  <% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>
316
- <section class="mb-8">
317
- <h2 class="text-xl font-semibold mb-3">Hierarchies</h2>
318
- <div class="space-y-3">
1191
+ <div class="section reveal">
1192
+ <div class="section-label">Drill Paths</div>
1193
+ <h2 class="section-title">Hierarchies</h2>
1194
+ <div style="display:flex;flex-direction:column;gap:1rem;">
319
1195
  <% for (var h of rules.hierarchies) { %>
320
- <div class="border rounded p-4">
321
- <div class="font-medium"><%= h.name %></div>
322
- <div class="text-sm text-gray-600 mt-1">Dataset: <%= h.dataset %></div>
323
- <div class="text-sm text-gray-600">Levels: <%= h.levels.join(' &rarr; ') %></div>
1196
+ <div class="card">
1197
+ <div style="font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;"><%= h.name %></div>
1198
+ <div style="font-size:0.82rem;color:var(--text-muted);margin-bottom:0.4rem;">Dataset: <span class="mono"><%= h.dataset %></span></div>
1199
+ <div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
1200
+ <% for (var li = 0; li < h.levels.length; li++) { %>
1201
+ <span class="tag tag-blue"><%= h.levels[li] %></span>
1202
+ <% if (li < h.levels.length - 1) { %><span style="color:var(--gold-dark);">&rarr;</span><% } %>
1203
+ <% } %>
1204
+ </div>
324
1205
  </div>
325
1206
  <% } %>
326
1207
  </div>
327
- </section>
1208
+ </div>
328
1209
  <% } %>
329
1210
 
330
1211
  <% if (!rules || ((!rules.golden_queries || rules.golden_queries.length === 0) && (!rules.business_rules || rules.business_rules.length === 0) && (!rules.guardrail_filters || rules.guardrail_filters.length === 0) && (!rules.hierarchies || rules.hierarchies.length === 0))) { %>
331
- <p class="text-gray-500">No rules or queries defined for this model.</p>
1212
+ <p style="color:var(--text-muted);">No rules or queries defined for this model.</p>
332
1213
  <% } %>
333
1214
  </main>
1215
+
334
1216
  ${FOOTER}
1217
+ ${SCRIPTS}
335
1218
  </body>
336
1219
  </html>`;
1220
+
1221
+ // src/templates/glossary.ts
337
1222
  var glossaryTemplate = `${HEAD}
338
- <body class="bg-white text-gray-900 min-h-screen">
1223
+ <body>
339
1224
  ${NAV}
340
- <main class="max-w-5xl mx-auto p-6">
341
- <h1 class="text-3xl font-bold mb-6">Glossary</h1>
1225
+
1226
+ <main class="page">
1227
+ <h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:0.5rem;">Glossary</h1>
1228
+ <p style="color:var(--text-muted);font-size:1rem;margin-bottom:2rem;font-weight:300;">Business term definitions and mappings.</p>
342
1229
 
343
1230
  <% var termIds = Object.keys(terms).sort(); %>
344
1231
  <% if (termIds.length === 0) { %>
345
- <p class="text-gray-500">No terms defined.</p>
1232
+ <p style="color:var(--text-muted);">No terms defined.</p>
346
1233
  <% } else { %>
347
- <div class="space-y-4">
1234
+ <input type="text" id="glossary-filter"
1235
+ placeholder="Filter terms..."
1236
+ class="search-input"
1237
+ style="margin-bottom:2rem;max-width:400px;" />
1238
+
1239
+ <div class="glossary-grid" id="glossary-grid">
348
1240
  <% for (var tid of termIds) { %>
349
1241
  <% var term = terms[tid]; %>
350
- <div class="border rounded-lg p-4" id="term-<%= tid %>">
351
- <h2 class="text-lg font-semibold"><%= tid %></h2>
352
- <p class="text-gray-700 mt-1"><%= term.definition %></p>
1242
+ <div class="glossary-card" id="term-<%= tid %>" data-term="<%= tid %>">
1243
+ <div class="glossary-term"><%= tid %></div>
1244
+ <div class="glossary-def"><%= term.definition %></div>
353
1245
  <% if (term.synonyms && term.synonyms.length > 0) { %>
354
- <div class="text-sm text-gray-500 mt-1">Synonyms: <%= term.synonyms.join(', ') %></div>
1246
+ <div style="display:flex;gap:0.4rem;margin-top:0.75rem;flex-wrap:wrap;">
1247
+ <% for (var s of term.synonyms) { %><span class="tag"><%= s %></span><% } %>
1248
+ </div>
355
1249
  <% } %>
356
1250
  <% if (term.maps_to && term.maps_to.length > 0) { %>
357
- <div class="text-sm text-gray-500 mt-1">Maps to: <%= term.maps_to.join(', ') %></div>
1251
+ <div style="display:flex;gap:0.4rem;margin-top:0.5rem;flex-wrap:wrap;">
1252
+ <% for (var m of term.maps_to) { %><span class="tag tag-blue"><%= m %></span><% } %>
1253
+ </div>
358
1254
  <% } %>
359
1255
  <% if (term.owner) { %>
360
- <div class="text-sm text-gray-500 mt-1">Owner: <a href="<%= basePath %>/owners/<%= term.owner %>.html" class="text-blue-600 underline"><%= term.owner %></a></div>
1256
+ <div style="font-size:0.78rem;color:var(--text-dim);margin-top:0.6rem;">
1257
+ Owner: <a href="<%= basePath %>/owners/<%= term.owner %>.html"><%= term.owner %></a>
1258
+ </div>
361
1259
  <% } %>
362
1260
  </div>
363
1261
  <% } %>
364
1262
  </div>
365
1263
  <% } %>
366
1264
  </main>
1265
+
367
1266
  ${FOOTER}
1267
+ ${SCRIPTS}
1268
+ <script>
1269
+ (function() {
1270
+ var input = document.getElementById('glossary-filter');
1271
+ if (!input) return;
1272
+ var cards = document.querySelectorAll('.glossary-card');
1273
+ input.addEventListener('input', function() {
1274
+ var q = input.value.toLowerCase();
1275
+ cards.forEach(function(card) {
1276
+ var text = card.textContent.toLowerCase();
1277
+ card.style.display = text.indexOf(q) !== -1 ? '' : 'none';
1278
+ });
1279
+ });
1280
+ })();
1281
+ </script>
368
1282
  </body>
369
1283
  </html>`;
1284
+
1285
+ // src/templates/owner.ts
370
1286
  var ownerTemplate = `${HEAD}
371
- <body class="bg-white text-gray-900 min-h-screen">
1287
+ <body>
372
1288
  ${NAV}
373
1289
  ${TIER_BADGE}
374
- <main class="max-w-5xl mx-auto p-6">
375
- <h1 class="text-3xl font-bold mb-2"><%= owner.display_name %></h1>
376
- <div class="text-sm text-gray-500 mb-4">
377
- <% if (owner.email) { %><span>Email: <%= owner.email %></span><% } %>
378
- <% if (owner.team) { %><span class="ml-4">Team: <%= owner.team %></span><% } %>
1290
+
1291
+ <main class="page">
1292
+ <a href="<%= basePath %>/" class="back-link">&larr; All Models</a>
1293
+
1294
+ <h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:0.5rem;"><%= owner.display_name %></h1>
1295
+
1296
+ <div style="display:flex;gap:1.5rem;font-size:0.85rem;color:var(--text-dim);margin-bottom:1.5rem;">
1297
+ <% if (owner.email) { %><span>Email: <span style="color:var(--text-muted);"><%= owner.email %></span></span><% } %>
1298
+ <% if (owner.team) { %><span>Team: <span style="color:var(--text-muted);"><%= owner.team %></span></span><% } %>
379
1299
  </div>
1300
+
380
1301
  <% if (owner.description) { %>
381
- <p class="text-gray-600 mb-4"><%= owner.description %></p>
1302
+ <p style="color:var(--text-muted);font-size:0.95rem;margin-bottom:2rem;font-weight:300;max-width:600px;"><%= owner.description %></p>
382
1303
  <% } %>
383
1304
 
384
1305
  <% if (governedModels.length > 0) { %>
385
- <section>
386
- <h2 class="text-xl font-semibold mb-3">Governed Models</h2>
387
- <div class="space-y-2">
1306
+ <div class="section reveal">
1307
+ <div class="section-label">Stewardship</div>
1308
+ <h2 class="section-title">Governed Models</h2>
1309
+ <div class="card-grid-sm">
388
1310
  <% for (var gm of governedModels) { %>
389
- <div class="flex items-center gap-3">
390
- <a href="<%= basePath %>/models/<%= gm.name %>.html" class="text-blue-600 hover:underline"><%= gm.name %></a>
391
- <% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>
392
- </div>
1311
+ <a href="<%= basePath %>/models/<%= gm.name %>.html" class="card" style="text-decoration:none;">
1312
+ <div style="display:flex;align-items:center;gap:0.5rem;">
1313
+ <span class="mono" style="color:var(--gold-light);"><%= gm.name %></span>
1314
+ <% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>
1315
+ </div>
1316
+ </a>
393
1317
  <% } %>
394
1318
  </div>
395
- </section>
1319
+ </div>
396
1320
  <% } else { %>
397
- <p class="text-gray-500">No models governed by this owner.</p>
1321
+ <p style="color:var(--text-muted);">No models governed by this owner.</p>
398
1322
  <% } %>
399
1323
  </main>
1324
+
400
1325
  ${FOOTER}
1326
+ ${SCRIPTS}
401
1327
  </body>
402
1328
  </html>`;
1329
+
1330
+ // src/templates/search.ts
403
1331
  var searchTemplate = `${HEAD}
404
- <body class="bg-white text-gray-900 min-h-screen">
1332
+ <body>
405
1333
  ${NAV}
406
- <main class="max-w-5xl mx-auto p-6">
407
- <h1 class="text-3xl font-bold mb-6">Search</h1>
1334
+
1335
+ <main class="page">
1336
+ <h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:2rem;">Search</h1>
408
1337
 
409
1338
  <input type="text" id="search-input"
410
1339
  placeholder="Search models, datasets, terms..."
411
- class="w-full border rounded-lg px-4 py-2 mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500" />
1340
+ class="search-input"
1341
+ style="margin-bottom:2rem;" />
412
1342
 
413
- <div id="search-results" class="space-y-2"></div>
1343
+ <div id="search-results" style="display:flex;flex-direction:column;gap:0.75rem;"></div>
414
1344
  </main>
1345
+
415
1346
  ${FOOTER}
1347
+ ${SCRIPTS}
416
1348
 
417
1349
  <script src="https://cdn.jsdelivr.net/npm/minisearch@7.1.0/dist/umd/index.min.js"></script>
418
1350
  <script>
@@ -424,6 +1356,13 @@ ${FOOTER}
424
1356
  var resultsContainer = document.getElementById('search-results');
425
1357
  var docs = indexData.documents;
426
1358
 
1359
+ var typeColors = {
1360
+ model: 'tag-gold',
1361
+ dataset: 'tag-blue',
1362
+ term: 'tag-green',
1363
+ owner: 'tag-bronze'
1364
+ };
1365
+
427
1366
  function clearResults() {
428
1367
  while (resultsContainer.firstChild) {
429
1368
  resultsContainer.removeChild(resultsContainer.firstChild);
@@ -432,22 +1371,35 @@ ${FOOTER}
432
1371
 
433
1372
  function createResultElement(doc) {
434
1373
  var wrapper = document.createElement('div');
435
- wrapper.className = 'border rounded p-3';
1374
+ wrapper.className = 'card';
1375
+ wrapper.style.padding = '1rem 1.25rem';
1376
+
1377
+ var top = document.createElement('div');
1378
+ top.style.display = 'flex';
1379
+ top.style.alignItems = 'center';
1380
+ top.style.gap = '0.5rem';
436
1381
 
437
1382
  var link = document.createElement('a');
438
1383
  link.href = doc.url;
439
- link.className = 'text-blue-600 font-medium hover:underline';
1384
+ link.style.fontFamily = 'var(--mono)';
1385
+ link.style.fontSize = '0.88rem';
1386
+ link.style.color = 'var(--gold-light)';
440
1387
  link.textContent = doc.title;
441
- wrapper.appendChild(link);
1388
+ top.appendChild(link);
442
1389
 
443
1390
  var badge = document.createElement('span');
444
- badge.className = 'ml-2 text-xs text-gray-400 uppercase';
1391
+ badge.className = 'tag ' + (typeColors[doc.type] || '');
445
1392
  badge.textContent = doc.type;
446
- wrapper.appendChild(badge);
1393
+ top.appendChild(badge);
1394
+
1395
+ wrapper.appendChild(top);
447
1396
 
448
1397
  if (doc.description) {
449
1398
  var desc = document.createElement('p');
450
- desc.className = 'text-sm text-gray-600 mt-1';
1399
+ desc.style.fontSize = '0.82rem';
1400
+ desc.style.color = 'var(--text-muted)';
1401
+ desc.style.marginTop = '0.3rem';
1402
+ desc.style.fontWeight = '300';
451
1403
  desc.textContent = doc.description;
452
1404
  wrapper.appendChild(desc);
453
1405
  }
@@ -462,7 +1414,7 @@ ${FOOTER}
462
1414
  var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });
463
1415
  if (hits.length === 0) {
464
1416
  var noResults = document.createElement('p');
465
- noResults.className = 'text-gray-500';
1417
+ noResults.style.color = 'var(--text-muted)';
466
1418
  noResults.textContent = 'No results found.';
467
1419
  resultsContainer.appendChild(noResults);
468
1420
  return;
@@ -544,7 +1496,7 @@ function buildSearchIndex(manifest, basePath) {
544
1496
  function generateSite(manifest, config) {
545
1497
  const files = /* @__PURE__ */ new Map();
546
1498
  const siteTitle = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _ => _.title]), () => ( "ContextKit"));
547
- const basePath = (_nullishCoalesce(_optionalChain([config, 'optionalAccess', _2 => _2.base_path]), () => ( ""))).replace(/\/+$/, "");
1499
+ const basePath = (_nullishCoalesce(_optionalChain([config, 'optionalAccess', _2 => _2.base_path]), () => ( ""))).replace(/\/+$/, "") || ".";
548
1500
  const commonData = {
549
1501
  siteTitle,
550
1502
  basePath
@@ -557,13 +1509,15 @@ function generateSite(manifest, config) {
557
1509
  models: manifest.models,
558
1510
  governance: manifest.governance,
559
1511
  tiers: manifest.tiers,
560
- owners: manifest.owners
1512
+ owners: manifest.owners,
1513
+ terms: manifest.terms
561
1514
  })
562
1515
  );
563
1516
  for (const [name, model] of Object.entries(manifest.models)) {
564
1517
  const gov = _nullishCoalesce(manifest.governance[name], () => ( null));
565
1518
  const tier = _nullishCoalesce(manifest.tiers[name], () => ( null));
566
1519
  const rules = _nullishCoalesce(manifest.rules[name], () => ( null));
1520
+ const lineage = _nullishCoalesce(manifest.lineage[name], () => ( null));
567
1521
  files.set(
568
1522
  `models/${name}.html`,
569
1523
  _ejs2.default.render(modelTemplate, {
@@ -572,7 +1526,8 @@ function generateSite(manifest, config) {
572
1526
  model,
573
1527
  gov,
574
1528
  tier,
575
- rules
1529
+ rules,
1530
+ lineage
576
1531
  })
577
1532
  );
578
1533
  files.set(