@runcontext/site 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -21,7 +21,9 @@ interface GenerateSiteOptions {
21
21
  * @param config - Optional site configuration (title, base_path)
22
22
  * @returns Map of relative file paths to HTML content
23
23
  */
24
- declare function generateSite(manifest: Manifest, config?: SiteConfig): Map<string, string>;
24
+ declare function generateSite(manifest: Manifest, config?: SiteConfig, options?: {
25
+ studioMode?: boolean;
26
+ }): Map<string, string>;
25
27
  /**
26
28
  * Build the documentation site and write files to disk.
27
29
  *
@@ -69,18 +71,18 @@ interface SearchIndex {
69
71
  */
70
72
  declare function buildSearchIndex(manifest: Manifest, basePath: string): SearchIndex;
71
73
 
72
- declare const indexTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <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\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n 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\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>\n<body>\n<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;\">\n <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>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: 'tag-none', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || cls.none;\n return '<span class=\"tag ' + c + '\">' + tier + '</span>';\n} %>\n\n<section class=\"hero\">\n <div class=\"hero-eyebrow\"><%- siteTitle %></div>\n <h1>Metadata<br><em>Catalog</em></h1>\n <p class=\"hero-sub\">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>\n</section>\n\n<div class=\"divider\"></div>\n\n<main class=\"page\">\n <%\n var modelNames = Object.keys(models);\n var totalDatasets = 0;\n var totalFields = 0;\n for (var mn of modelNames) {\n var m = models[mn];\n if (m.datasets) {\n totalDatasets += m.datasets.length;\n for (var ds of m.datasets) {\n if (ds.fields) totalFields += ds.fields.length;\n }\n }\n }\n var totalTerms = Object.keys(terms).length;\n var totalOwners = Object.keys(owners).length;\n %>\n\n <div class=\"stats reveal\">\n <div class=\"stat\">\n <div class=\"stat-value\"><%= modelNames.length %></div>\n <div class=\"stat-label\">Models</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalDatasets %></div>\n <div class=\"stat-label\">Datasets</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalFields %></div>\n <div class=\"stat-label\">Fields</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalTerms %></div>\n <div class=\"stat-label\">Terms</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalOwners %></div>\n <div class=\"stat-label\">Owners</div>\n </div>\n </div>\n\n <div class=\"section reveal\">\n <div class=\"section-label\">Semantic Models</div>\n <h2 class=\"section-title\">Models</h2>\n <% if (modelNames.length === 0) { %>\n <p style=\"color:var(--text-muted);\">No models found.</p>\n <% } else { %>\n <div class=\"card-grid\">\n <% for (var name of modelNames) { %>\n <div class=\"card\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;margin-bottom:0.6rem;\">\n <a href=\"<%= basePath %>/models/<%= name %>.html\" style=\"font-family:var(--mono);font-size:0.95rem;color:var(--gold-light);text-decoration:none;\">\n <%= name %>\n </a>\n <% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>\n </div>\n <% if (models[name].description) { %>\n <p style=\"color:var(--text-muted);font-size:0.85rem;margin-bottom:0.75rem;font-weight:300;\"><%= models[name].description %></p>\n <% } %>\n <% if (governance[name]) { %>\n <div style=\"display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;\">\n <% if (governance[name].owner) { %>\n <a href=\"<%= basePath %>/owners/<%= governance[name].owner %>.html\" class=\"tag tag-nav\" style=\"text-decoration:none;\"><%= governance[name].owner %></a>\n <% } %>\n <% if (governance[name].trust) { %>\n <span class=\"tag tag-green\"><%= governance[name].trust %></span>\n <% } %>\n <% if (governance[name].tags) { for (var t of governance[name].tags) { %>\n <span class=\"tag\"><%= t %></span>\n <% } } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n\n <% if (Object.keys(owners).length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Stewardship</div>\n <h2 class=\"section-title\">Owners</h2>\n <div class=\"card-grid-sm\">\n <% for (var oid of Object.keys(owners)) { %>\n <a href=\"<%= basePath %>/owners/<%= oid %>.html\" class=\"card\" style=\"text-decoration:none;\">\n <div style=\"font-size:1rem;font-weight:500;color:var(--text);margin-bottom:0.25rem;\"><%= owners[oid].display_name %></div>\n <% if (owners[oid].team) { %>\n <div style=\"font-size:0.78rem;color:var(--text-dim);\"><%= owners[oid].team %></div>\n <% } %>\n </a>\n <% } %>\n </div>\n </div>\n <% } %>\n</main>\n\n<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;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>\n<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n var num = /\\b(\\d+\\.?\\d*)\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n</body>\n</html>";
74
+ declare const indexTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>\n<body>\n<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">&#9776;</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs &nearr;</a>\n </div>\n</div>\n<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>\n<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: '', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || '';\n return '<span class=\"tag ' + c + '\">' + (tier || 'none') + '</span>';\n} %>\n\n<div class=\"main\">\n <div class=\"page-header\">\n <h1>Metadata Catalog</h1>\n <p class=\"subtitle\">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>\n </div>\n\n <%\n var modelNames = Object.keys(models);\n var totalDatasets = 0;\n var totalFields = 0;\n for (var mn of modelNames) {\n var m = models[mn];\n if (m.datasets) {\n totalDatasets += m.datasets.length;\n for (var ds of m.datasets) {\n if (ds.fields) totalFields += ds.fields.length;\n }\n }\n }\n var totalTerms = Object.keys(terms).length;\n var totalOwners = Object.keys(owners).length;\n %>\n\n <div class=\"stats-row\">\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= modelNames.length %></div>\n <div class=\"stat-lbl\">Models</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalDatasets %></div>\n <div class=\"stat-lbl\">Datasets</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalFields %></div>\n <div class=\"stat-lbl\">Fields</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalTerms %></div>\n <div class=\"stat-lbl\">Terms</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalOwners %></div>\n <div class=\"stat-lbl\">Owners</div>\n </div>\n </div>\n\n <div class=\"section\">\n <div class=\"section-label\">Semantic Models</div>\n <h2 class=\"section-title\">Models</h2>\n <% if (modelNames.length === 0) { %>\n <p style=\"color:var(--text-secondary);\">No models found. Run <code style=\"font-family:var(--mono);background:var(--bg-card);padding:0.15rem 0.4rem;border-radius:3px;\">context introspect</code> to get started.</p>\n <% } else { %>\n <div class=\"card-grid\">\n <% for (var name of modelNames) { %>\n <a href=\"<%= basePath %>/models/<%= name %>.html\" class=\"card-link\">\n <div class=\"card\">\n <div style=\"display:flex;align-items:center;gap:0.5rem;margin-bottom:0.4rem;\">\n <span class=\"mono\" style=\"font-size:0.88rem;color:var(--accent-light);\"><%= name %></span>\n <% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>\n </div>\n <% if (models[name].description) { %>\n <p style=\"color:var(--text-secondary);font-size:0.82rem;margin-bottom:0.5rem;font-weight:300;\"><%= models[name].description %></p>\n <% } %>\n <% if (governance[name]) { %>\n <div style=\"display:flex;gap:0.35rem;flex-wrap:wrap;align-items:center;\">\n <% if (governance[name].owner) { %>\n <span class=\"tag\"><%= governance[name].owner %></span>\n <% } %>\n <% if (governance[name].trust) { %>\n <span class=\"tag tag-green\"><%= governance[name].trust %></span>\n <% } %>\n <% if (governance[name].tags) { for (var t of governance[name].tags) { %>\n <span class=\"tag\"><%= t %></span>\n <% } } %>\n </div>\n <% } %>\n </div>\n </a>\n <% } %>\n </div>\n <% } %>\n </div>\n\n <% if (Object.keys(owners).length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Stewardship</div>\n <h2 class=\"section-title\">Owners</h2>\n <div class=\"card-grid\">\n <% for (var oid of Object.keys(owners)) { %>\n <a href=\"<%= basePath %>/owners/<%= oid %>.html\" class=\"card-link\">\n <div class=\"card\">\n <div style=\"font-size:0.9rem;font-weight:500;color:var(--text);margin-bottom:0.15rem;\"><%= owners[oid].display_name %></div>\n <% if (owners[oid].team) { %>\n <div style=\"font-size:0.75rem;color:var(--text-dim);\"><%= owners[oid].team %></div>\n <% } %>\n </div>\n </a>\n <% } %>\n </div>\n </div>\n <% } %>\n</div>\n\n<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n &nbsp;&middot;&nbsp;\n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>\n<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview &amp; Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>\n</body>\n</html>";
73
75
 
74
- declare const modelTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <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\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n 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\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>\n<body>\n<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;\">\n <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>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: 'tag-none', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || cls.none;\n return '<span class=\"tag ' + c + '\">' + tier + '</span>';\n} %>\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/\" class=\"back-link\">&larr; All Models</a>\n\n <div style=\"display:flex;align-items:center;gap:0.75rem;margin-bottom:0.5rem;\">\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);\"><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n </div>\n <% if (model.description) { %>\n <p style=\"color:var(--text-muted);font-size:1rem;margin-bottom:1.5rem;max-width:700px;font-weight:300;\"><%= model.description %></p>\n <% } %>\n\n <div style=\"display:flex;gap:0.6rem;margin-bottom:2.5rem;\">\n <a href=\"<%= basePath %>/models/<%= model.name %>/schema.html\" class=\"tag tag-nav tag-blue\">Schema Browser</a>\n <a href=\"<%= basePath %>/models/<%= model.name %>/rules.html\" class=\"tag tag-nav tag-gold\">Rules &amp; Queries</a>\n </div>\n\n <% if (gov) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Governance</div>\n <div class=\"gov-grid\">\n <div class=\"gov-item\">\n <div class=\"gov-label\">Owner</div>\n <div class=\"gov-value\"><a href=\"<%= basePath %>/owners/<%= gov.owner %>.html\"><%= gov.owner %></a></div>\n </div>\n <% if (gov.trust) { %>\n <div class=\"gov-item\">\n <div class=\"gov-label\">Trust</div>\n <div class=\"gov-value\"><%= gov.trust %></div>\n </div>\n <% } %>\n <% if (gov.security) { %>\n <div class=\"gov-item\">\n <div class=\"gov-label\">Security</div>\n <div class=\"gov-value\"><%= gov.security %></div>\n </div>\n <% } %>\n <% if (gov.tags && gov.tags.length > 0) { %>\n <div class=\"gov-item\">\n <div class=\"gov-label\">Tags</div>\n <div class=\"gov-value\" style=\"display:flex;gap:0.4rem;flex-wrap:wrap;\">\n <% for (var t of gov.tags) { %><span class=\"tag\"><%= t %></span><% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Explorer</div>\n <h2 class=\"section-title\">Datasets</h2>\n <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>\n <div style=\"display:flex;flex-direction:column;gap:1rem;\">\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"card\">\n <div class=\"expandable-header\" onclick=\"toggleExpand('ds-<%= i %>')\">\n <div>\n <div style=\"display:flex;align-items:center;gap:0.6rem;margin-bottom:0.25rem;\">\n <span class=\"mono\" style=\"color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"ds-type ds-type-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n <% if (ds.fields) { %><span class=\"tag\"><%= ds.fields.length %> fields</span><% } %>\n </div>\n <div style=\"font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);\">\n <%= ds.source %>\n <% if (dsGov && dsGov.grain) { %> &middot; <span style=\"color:var(--text-muted);font-family:var(--sans);\"><%= dsGov.grain %></span><% } %>\n <% if (dsGov && dsGov.refresh) { %> &middot; <span style=\"color:var(--text-muted);font-family:var(--sans);\"><%= dsGov.refresh %></span><% } %>\n </div>\n <% if (ds.description) { %>\n <p style=\"font-size:0.82rem;color:var(--text-muted);margin-top:0.4rem;font-weight:300;\"><%= ds.description %></p>\n <% } %>\n </div>\n <span class=\"expand-icon\" id=\"ds-<%= i %>-icon\">+</span>\n </div>\n <div class=\"expandable-content\" id=\"ds-<%= i %>\">\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"table-dark\" style=\"margin-top:1rem;\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Description</th>\n <th>Role</th>\n <th>Aggregation</th>\n </tr>\n </thead>\n <tbody>\n <% for (var field of ds.fields) { %>\n <% var fKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-muted);font-size:0.82rem;\"><%= field.description || '' %></td>\n <td><% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %></td>\n <td style=\"font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.relationships && model.relationships.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Model</div>\n <h2 class=\"section-title\">Relationships</h2>\n <table class=\"table-dark\">\n <thead>\n <tr><th>Name</th><th>From</th><th></th><th>To</th></tr>\n </thead>\n <tbody>\n <% for (var rel of model.relationships) { %>\n <tr>\n <td style=\"color:var(--text-muted);font-size:0.82rem;\"><%= rel.name %></td>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= rel.from %></td>\n <td style=\"color:var(--gold-dark);text-align:center;\">&rarr;</td>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= rel.to %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n </div>\n <% } %>\n\n <% if (model.metrics && model.metrics.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Computed Metrics</div>\n <h2 class=\"section-title\">Metrics</h2>\n <div class=\"card-grid\">\n <% for (var metric of model.metrics) { %>\n <div class=\"card\">\n <div class=\"metric-name\"><%= metric.name %></div>\n <% if (metric.description) { %><div class=\"metric-desc\"><%= metric.description %></div><% } %>\n <% if (metric.expression && metric.expression.dialects && metric.expression.dialects.length > 0) { %>\n <div class=\"metric-formula\"><%= metric.expression.dialects[0].expression %></div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0)) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Lineage</div>\n <h2 class=\"section-title\">Lineage</h2>\n <div class=\"lineage-flow\">\n <% if (lineage.upstream && lineage.upstream.length > 0) { %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Upstream</div>\n <% for (var u of lineage.upstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= u.source %></div>\n <div class=\"lineage-node-detail\"><%= u.type || '' %><% if (u.tool) { %> via <%= u.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <div class=\"lineage-arrow\">&rarr;</div>\n <% } %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">This Model</div>\n <div class=\"lineage-node\" style=\"border-color:var(--gold-dark);\">\n <div class=\"lineage-node-name\" style=\"color:var(--gold);\"><%= model.name %></div>\n </div>\n </div>\n <% if (lineage.downstream && lineage.downstream.length > 0) { %>\n <div class=\"lineage-arrow\">&rarr;</div>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Downstream</div>\n <% for (var d of lineage.downstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= d.target %></div>\n <div class=\"lineage-node-detail\"><%= d.type || '' %><% if (d.tool) { %> via <%= d.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (tier) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Quality</div>\n <h2 class=\"section-title\">Tier Scorecard</h2>\n <div class=\"scorecard\">\n <% var tierLevels = ['bronze', 'silver', 'gold']; %>\n <% for (var lvl of tierLevels) { %>\n <div class=\"scorecard-tier\">\n <div class=\"scorecard-tier-header\">\n <span class=\"scorecard-tier-name <%= lvl %>\"><%= lvl %></span>\n <% if (tier[lvl].passed) { %>\n <span class=\"scorecard-pass passed\">Passed</span>\n <% } else { %>\n <span class=\"scorecard-pass failed\">Not passed</span>\n <% } %>\n </div>\n <% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>\n <ul class=\"check-list\">\n <% for (var chk of tier[lvl].checks) { %>\n <li class=\"check-item\">\n <span class=\"check-icon <%= chk.passed ? 'pass' : 'fail' %>\"><%= chk.passed ? '\\u2713' : '\\u2717' %></span>\n <span><%= chk.label %><% if (chk.detail) { %> &mdash; <span style=\"color:var(--text-dim);\"><%= chk.detail %></span><% } %></span>\n </li>\n <% } %>\n </ul>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n</main>\n\n<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;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>\n<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n var num = /\\b(\\d+\\.?\\d*)\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n</body>\n</html>";
76
+ declare const modelTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>\n<body>\n<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">&#9776;</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs &nearr;</a>\n </div>\n</div>\n<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>\n<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: '', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || '';\n return '<span class=\"tag ' + c + '\">' + (tier || 'none') + '</span>';\n} %>\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <span><%= model.name %></span>\n </div>\n\n <div class=\"page-header\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;\">\n <h1><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n </div>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <p class=\"subtitle\"><span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.description\" data-label=\"Model description\"><%= model.description || 'Add description...' %></span></p>\n <% } else { %>\n <% if (model.description) { %>\n <p class=\"subtitle\"><%= model.description %></p>\n <% } %>\n <% } %>\n </div>\n\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\">\n <div class=\"section-label\">AI Context</div>\n <div class=\"card\" style=\"margin-bottom:1rem;\">\n <span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.ai_context\" data-label=\"AI context\"><%= (typeof model.ai_context === 'string' ? model.ai_context : (model.ai_context ? JSON.stringify(model.ai_context) : 'Add AI context...')) %></span>\n </div>\n </div>\n <% } else { %>\n <% if (model.ai_context) { %>\n <div class=\"section\">\n <div class=\"section-label\">AI Context</div>\n <div class=\"card\" style=\"margin-bottom:1rem;\">\n <p style=\"font-size:0.85rem;color:var(--text-secondary);font-weight:300;line-height:1.6;\"><%= typeof model.ai_context === 'string' ? model.ai_context : JSON.stringify(model.ai_context) %></p>\n </div>\n </div>\n <% } %>\n <% } %>\n\n <div style=\"display:flex;gap:0.5rem;margin-bottom:2rem;\">\n <a href=\"<%= basePath %>/models/<%= model.name %>/schema.html\" class=\"tag tag-blue\" style=\"padding:0.3rem 0.6rem;font-size:0.65rem;text-decoration:none;\">Schema Browser</a>\n <a href=\"<%= basePath %>/models/<%= model.name %>/rules.html\" class=\"tag tag-gold\" style=\"padding:0.3rem 0.6rem;font-size:0.65rem;text-decoration:none;\">Rules &amp; Queries</a>\n </div>\n\n <% if (gov) { %>\n <div class=\"section\">\n <div class=\"section-label\">Governance</div>\n <div class=\"gov-grid\">\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Owner</div>\n <div class=\"gov-value\"><a href=\"<%= basePath %>/owners/<%= gov.owner %>.html\"><%= gov.owner %></a></div>\n </div>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Trust</div>\n <div class=\"gov-value\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"dropdown-editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"trust\" data-label=\"Trust\" data-options=\"draft,reviewed,endorsed,certified\"><%= gov.trust || 'Select...' %></span>\n <% } else { %>\n <%= gov.trust || '' %>\n <% } %>\n </div>\n </div>\n <% if (gov.security) { %>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Security</div>\n <div class=\"gov-value\"><%= gov.security %></div>\n </div>\n <% } %>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Refresh Cadence</div>\n <div class=\"gov-value\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"refresh\" data-label=\"Refresh cadence\"><%= gov.refresh || 'Add refresh cadence...' %></span>\n <% } else { %>\n <%= gov.refresh || '' %>\n <% } %>\n </div>\n </div>\n <% if (gov.tags && gov.tags.length > 0) { %>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Tags</div>\n <div class=\"gov-value\" style=\"display:flex;gap:0.3rem;flex-wrap:wrap;\">\n <% for (var t of gov.tags) { %><span class=\"tag\"><%= t %></span><% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Explorer</div>\n <h2 class=\"section-title\">Datasets</h2>\n <div style=\"display:flex;flex-direction:column;gap:0.75rem;\">\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"card\">\n <div class=\"expandable-header\" onclick=\"toggleExpand('ds-<%= i %>')\">\n <div>\n <div style=\"display:flex;align-items:center;gap:0.5rem;margin-bottom:0.15rem;\">\n <span class=\"mono\" style=\"font-size:0.85rem;color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"tag ds-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n <% if (ds.fields) { %><span class=\"tag\"><%= ds.fields.length %> fields</span><% } %>\n </div>\n <div style=\"font-size:0.72rem;color:var(--text-dim);font-family:var(--mono);\">\n <%= ds.source %>\n <% if (dsGov && dsGov.grain) { %> &middot; <span style=\"font-family:var(--sans);color:var(--text-secondary);\"><%= dsGov.grain %></span><% } %>\n </div>\n <% if (ds.description) { %>\n <p style=\"font-size:0.8rem;color:var(--text-secondary);margin-top:0.3rem;font-weight:300;\"><%= ds.description %></p>\n <% } %>\n </div>\n <span class=\"expand-icon\" id=\"ds-<%= i %>-icon\">+</span>\n </div>\n <div class=\"expandable-content\" id=\"ds-<%= i %>\">\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"data-table\" style=\"margin-top:0.75rem;\">\n <thead>\n <tr><th>Field</th><th>Description</th><th>Role</th><th>Aggregation</th></tr>\n </thead>\n <tbody>\n <% for (var field of ds.fields) { %>\n <% var fKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-secondary);font-size:0.8rem;\"><%= field.description || '' %></td>\n <td><% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %></td>\n <td class=\"mono\" style=\"font-size:0.75rem;color:var(--text-dim);\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.relationships && model.relationships.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Model</div>\n <h2 class=\"section-title\">Relationships</h2>\n <table class=\"data-table\">\n <thead>\n <tr><th>Name</th><th>From</th><th></th><th>To</th></tr>\n </thead>\n <tbody>\n <% for (var rel of model.relationships) { %>\n <tr>\n <td style=\"color:var(--text-secondary);font-size:0.8rem;\"><%= rel.name %></td>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= rel.from %></td>\n <td style=\"color:var(--text-dim);text-align:center;\">&rarr;</td>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= rel.to %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n </div>\n <% } %>\n\n <% if (model.metrics && model.metrics.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Computed Metrics</div>\n <h2 class=\"section-title\">Metrics</h2>\n <div class=\"card-grid\">\n <% for (var metric of model.metrics) { %>\n <div class=\"card\">\n <div class=\"metric-name\"><%= metric.name %></div>\n <% if (metric.description) { %><div class=\"metric-desc\"><%= metric.description %></div><% } %>\n <% if (metric.expression && metric.expression.dialects && metric.expression.dialects.length > 0) { %>\n <div class=\"metric-formula\"><%= metric.expression.dialects[0].expression %></div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated Queries</h2>\n <% for (var gq of rules.golden_queries) { %>\n <div class=\"query-card\">\n <div class=\"query-q\">\n <span class=\"query-q-badge\">Q</span>\n <span><%= gq.question %></span>\n </div>\n <div class=\"query-sql sql-highlight\"><%= gq.sql %></div>\n <% if (gq.description) { %>\n <div class=\"query-meta\"><span><%= gq.description %></span></div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\" id=\"golden-queries-studio\">\n <% if (!(rules && rules.golden_queries && rules.golden_queries.length > 0)) { %>\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated Queries</h2>\n <% } %>\n <div id=\"golden-query-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addGoldenQuery('<%= model.name %>')\">+ Add Golden Query</button>\n </div>\n <script>\n function addGoldenQuery(modelName) {\n var container = document.getElementById('golden-query-forms');\n var card = document.createElement('div');\n card.className = 'card';\n card.style.marginBottom = '1rem';\n\n var questionLabel = document.createElement('label');\n questionLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n questionLabel.textContent = 'Question';\n var questionInput = document.createElement('input');\n questionInput.type = 'text';\n questionInput.className = 'search-input gq-question';\n questionInput.placeholder = 'e.g. What is the total revenue by region?';\n questionInput.style.fontSize = '0.85rem';\n var questionDiv = document.createElement('div');\n questionDiv.style.marginBottom = '0.75rem';\n questionDiv.appendChild(questionLabel);\n questionDiv.appendChild(questionInput);\n\n var sqlLabel = document.createElement('label');\n sqlLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n sqlLabel.textContent = 'SQL';\n var sqlInput = document.createElement('textarea');\n sqlInput.className = 'search-input gq-sql';\n sqlInput.rows = 4;\n sqlInput.placeholder = 'SELECT ...';\n sqlInput.style.cssText = 'font-family:var(--mono);font-size:0.8rem;resize:vertical;';\n var sqlDiv = document.createElement('div');\n sqlDiv.style.marginBottom = '0.75rem';\n sqlDiv.appendChild(sqlLabel);\n sqlDiv.appendChild(sqlInput);\n\n var descLabel = document.createElement('label');\n descLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n descLabel.textContent = 'Description';\n var descInput = document.createElement('input');\n descInput.type = 'text';\n descInput.className = 'search-input gq-desc';\n descInput.placeholder = 'Optional description';\n descInput.style.fontSize = '0.85rem';\n var descDiv = document.createElement('div');\n descDiv.style.marginBottom = '0.75rem';\n descDiv.appendChild(descLabel);\n descDiv.appendChild(descInput);\n\n var stageBtn = document.createElement('button');\n stageBtn.className = 'staged-btn primary';\n stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n stageBtn.textContent = 'Stage';\n stageBtn.addEventListener('click', function() { stageGoldenQuery(stageBtn, modelName); });\n\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'staged-btn secondary';\n cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n cancelBtn.textContent = 'Cancel';\n cancelBtn.addEventListener('click', function() { card.remove(); });\n\n var btnDiv = document.createElement('div');\n btnDiv.style.cssText = 'display:flex;gap:0.5rem;';\n btnDiv.appendChild(stageBtn);\n btnDiv.appendChild(cancelBtn);\n\n card.appendChild(questionDiv);\n card.appendChild(sqlDiv);\n card.appendChild(descDiv);\n card.appendChild(btnDiv);\n container.appendChild(card);\n }\n\n function stageGoldenQuery(btn, modelName) {\n var card = btn.closest('.card');\n var question = card.querySelector('.gq-question').value.trim();\n var sql = card.querySelector('.gq-sql').value.trim();\n var desc = card.querySelector('.gq-desc').value.trim();\n if (!question || !sql) { showToast('Question and SQL are required'); return; }\n var entry = { question: question, sql: sql };\n if (desc) entry.description = desc;\n stageEdit('context/rules/' + modelName + '.rules.yaml', 'golden_queries.+', entry, 'Add golden query: ' + question);\n card.style.borderColor = '#4ade80';\n btn.textContent = 'Staged';\n btn.disabled = true;\n }\n </script>\n <% } %>\n\n <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% for (var gf of rules.guardrail_filters) { %>\n <div class=\"guardrail\">\n <div class=\"guardrail-name\"><%= gf.name %></div>\n <div class=\"guardrail-filter\"><%= gf.filter %></div>\n <div class=\"guardrail-reason\"><%= gf.reason %></div>\n </div>\n <% } %>\n </div>\n <% } %>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\" id=\"guardrails-studio\">\n <% if (!(rules && rules.guardrail_filters && rules.guardrail_filters.length > 0)) { %>\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% } %>\n <div id=\"guardrail-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addGuardrail('<%= model.name %>')\">+ Add Guardrail</button>\n </div>\n <script>\n function addGuardrail(modelName) {\n var container = document.getElementById('guardrail-forms');\n var card = document.createElement('div');\n card.className = 'card';\n card.style.marginBottom = '1rem';\n\n var nameLabel = document.createElement('label');\n nameLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n nameLabel.textContent = 'Name';\n var nameInput = document.createElement('input');\n nameInput.type = 'text';\n nameInput.className = 'search-input gr-name';\n nameInput.placeholder = 'e.g. pii_filter';\n nameInput.style.fontSize = '0.85rem';\n var nameDiv = document.createElement('div');\n nameDiv.style.marginBottom = '0.75rem';\n nameDiv.appendChild(nameLabel);\n nameDiv.appendChild(nameInput);\n\n var filterLabel = document.createElement('label');\n filterLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n filterLabel.textContent = 'Filter Expression';\n var filterInput = document.createElement('input');\n filterInput.type = 'text';\n filterInput.className = 'search-input gr-filter';\n filterInput.placeholder = 'e.g. WHERE email IS NOT NULL';\n filterInput.style.cssText = 'font-family:var(--mono);font-size:0.85rem;';\n var filterDiv = document.createElement('div');\n filterDiv.style.marginBottom = '0.75rem';\n filterDiv.appendChild(filterLabel);\n filterDiv.appendChild(filterInput);\n\n var reasonLabel = document.createElement('label');\n reasonLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n reasonLabel.textContent = 'Reason';\n var reasonInput = document.createElement('input');\n reasonInput.type = 'text';\n reasonInput.className = 'search-input gr-reason';\n reasonInput.placeholder = 'Why this guardrail exists';\n reasonInput.style.fontSize = '0.85rem';\n var reasonDiv = document.createElement('div');\n reasonDiv.style.marginBottom = '0.75rem';\n reasonDiv.appendChild(reasonLabel);\n reasonDiv.appendChild(reasonInput);\n\n var stageBtn = document.createElement('button');\n stageBtn.className = 'staged-btn primary';\n stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n stageBtn.textContent = 'Stage';\n stageBtn.addEventListener('click', function() { stageGuardrail(stageBtn, modelName); });\n\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'staged-btn secondary';\n cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n cancelBtn.textContent = 'Cancel';\n cancelBtn.addEventListener('click', function() { card.remove(); });\n\n var btnDiv = document.createElement('div');\n btnDiv.style.cssText = 'display:flex;gap:0.5rem;';\n btnDiv.appendChild(stageBtn);\n btnDiv.appendChild(cancelBtn);\n\n card.appendChild(nameDiv);\n card.appendChild(filterDiv);\n card.appendChild(reasonDiv);\n card.appendChild(btnDiv);\n container.appendChild(card);\n }\n\n function stageGuardrail(btn, modelName) {\n var card = btn.closest('.card');\n var name = card.querySelector('.gr-name').value.trim();\n var filter = card.querySelector('.gr-filter').value.trim();\n var reason = card.querySelector('.gr-reason').value.trim();\n if (!name || !filter) { showToast('Name and filter expression are required'); return; }\n var entry = { name: name, filter: filter };\n if (reason) entry.reason = reason;\n stageEdit('context/rules/' + modelName + '.rules.yaml', 'guardrail_filters.+', entry, 'Add guardrail: ' + name);\n card.style.borderColor = '#4ade80';\n btn.textContent = 'Staged';\n btn.disabled = true;\n }\n </script>\n <% } %>\n\n <% if (lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0)) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Lineage</div>\n <h2 class=\"section-title\">Lineage</h2>\n <div class=\"lineage-flow\">\n <% if (lineage.upstream && lineage.upstream.length > 0) { %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Upstream</div>\n <% for (var u of lineage.upstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= u.source %></div>\n <div class=\"lineage-node-detail\"><%= u.type || '' %><% if (u.tool) { %> via <%= u.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <div class=\"lineage-arrow\">&rarr;</div>\n <% } %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">This Model</div>\n <div class=\"lineage-node\" style=\"border-color:var(--accent-border);\">\n <div class=\"lineage-node-name\" style=\"color:var(--accent);\"><%= model.name %></div>\n </div>\n </div>\n <% if (lineage.downstream && lineage.downstream.length > 0) { %>\n <div class=\"lineage-arrow\">&rarr;</div>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Downstream</div>\n <% for (var d of lineage.downstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= d.target %></div>\n <div class=\"lineage-node-detail\"><%= d.type || '' %><% if (d.tool) { %> via <%= d.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\" id=\"lineage-studio\">\n <% if (!(lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0))) { %>\n <div class=\"section-label\">Data Lineage</div>\n <h2 class=\"section-title\">Lineage</h2>\n <% } %>\n <div id=\"upstream-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addUpstreamSource('<%= model.name %>')\" style=\"margin-bottom:0.75rem;\">+ Add Upstream Source</button>\n <div id=\"downstream-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addDownstreamTarget('<%= model.name %>')\">+ Add Downstream Target</button>\n </div>\n <script>\n function createLineageForm(container, direction, modelName) {\n var card = document.createElement('div');\n card.className = 'card';\n card.style.marginBottom = '1rem';\n\n var nameLabel = document.createElement('label');\n nameLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n nameLabel.textContent = direction === 'upstream' ? 'Source Name' : 'Target Name';\n var nameInput = document.createElement('input');\n nameInput.type = 'text';\n nameInput.className = 'search-input lin-name';\n nameInput.placeholder = direction === 'upstream' ? 'e.g. raw_orders_db' : 'e.g. revenue_dashboard';\n nameInput.style.fontSize = '0.85rem';\n var nameDiv = document.createElement('div');\n nameDiv.style.marginBottom = '0.75rem';\n nameDiv.appendChild(nameLabel);\n nameDiv.appendChild(nameInput);\n\n var typeLabel = document.createElement('label');\n typeLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n typeLabel.textContent = 'Type';\n var typeSelect = document.createElement('select');\n typeSelect.className = 'search-input lin-type';\n typeSelect.style.fontSize = '0.85rem';\n ['pipeline', 'dashboard', 'api', 'file', 'derived'].forEach(function(opt) {\n var option = document.createElement('option');\n option.value = opt;\n option.textContent = opt;\n typeSelect.appendChild(option);\n });\n var typeDiv = document.createElement('div');\n typeDiv.style.marginBottom = '0.75rem';\n typeDiv.appendChild(typeLabel);\n typeDiv.appendChild(typeSelect);\n\n var notesLabel = document.createElement('label');\n notesLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n notesLabel.textContent = 'Notes';\n var notesInput = document.createElement('input');\n notesInput.type = 'text';\n notesInput.className = 'search-input lin-notes';\n notesInput.placeholder = 'Optional notes';\n notesInput.style.fontSize = '0.85rem';\n var notesDiv = document.createElement('div');\n notesDiv.style.marginBottom = '0.75rem';\n notesDiv.appendChild(notesLabel);\n notesDiv.appendChild(notesInput);\n\n var stageBtn = document.createElement('button');\n stageBtn.className = 'staged-btn primary';\n stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n stageBtn.textContent = 'Stage';\n stageBtn.addEventListener('click', function() {\n var entryName = card.querySelector('.lin-name').value.trim();\n var entryType = card.querySelector('.lin-type').value;\n var entryNotes = card.querySelector('.lin-notes').value.trim();\n if (!entryName) { showToast((direction === 'upstream' ? 'Source' : 'Target') + ' name is required'); return; }\n var entry = {};\n if (direction === 'upstream') { entry.source = entryName; } else { entry.target = entryName; }\n entry.type = entryType;\n if (entryNotes) entry.notes = entryNotes;\n stageEdit('context/lineage/' + modelName + '.lineage.yaml', direction + '.+', entry, 'Add ' + direction + ': ' + entryName);\n card.style.borderColor = '#4ade80';\n stageBtn.textContent = 'Staged';\n stageBtn.disabled = true;\n });\n\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'staged-btn secondary';\n cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n cancelBtn.textContent = 'Cancel';\n cancelBtn.addEventListener('click', function() { card.remove(); });\n\n var btnDiv = document.createElement('div');\n btnDiv.style.cssText = 'display:flex;gap:0.5rem;';\n btnDiv.appendChild(stageBtn);\n btnDiv.appendChild(cancelBtn);\n\n card.appendChild(nameDiv);\n card.appendChild(typeDiv);\n card.appendChild(notesDiv);\n card.appendChild(btnDiv);\n container.appendChild(card);\n }\n\n function addUpstreamSource(modelName) {\n createLineageForm(document.getElementById('upstream-forms'), 'upstream', modelName);\n }\n\n function addDownstreamTarget(modelName) {\n createLineageForm(document.getElementById('downstream-forms'), 'downstream', modelName);\n }\n </script>\n <% } %>\n\n <% if (tier) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Quality</div>\n <h2 class=\"section-title\">Tier Scorecard</h2>\n <div class=\"scorecard\">\n <% var tierLevels = ['bronze', 'silver', 'gold']; %>\n <% for (var lvl of tierLevels) { %>\n <div class=\"sc-tier\">\n <div class=\"sc-tier-head\">\n <span class=\"sc-tier-name <%= lvl %>\"><%= lvl %></span>\n <% if (tier[lvl].passed) { %>\n <span class=\"sc-status pass\">Passed</span>\n <% } else { %>\n <span class=\"sc-status fail\">Not passed</span>\n <% } %>\n </div>\n <% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>\n <ul class=\"check-list\">\n <% for (var chk of tier[lvl].checks) { %>\n <li class=\"check-item\">\n <span class=\"check-icon <%= chk.passed ? 'pass' : 'fail' %>\"><%= chk.passed ? '\\u2713' : '\\u2717' %></span>\n <span><%= chk.label %><% if (chk.detail) { %> &mdash; <span style=\"color:var(--text-dim);\"><%= chk.detail %></span><% } %></span>\n </li>\n <% } %>\n </ul>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n</div>\n\n<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n &nbsp;&middot;&nbsp;\n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>\n<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview &amp; Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>\n</body>\n</html>";
75
77
 
76
- declare const schemaTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <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\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n 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\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>\n<body>\n<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;\">\n <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>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: 'tag-none', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || cls.none;\n return '<span class=\"tag ' + c + '\">' + tier + '</span>';\n} %>\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/models/<%= model.name %>.html\" class=\"back-link\">&larr; Back to <%= model.name %></a>\n\n <div style=\"display:flex;align-items:center;gap:0.75rem;margin-bottom:2rem;\">\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);\"><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n <span style=\"font-size:0.85rem;color:var(--text-dim);\">Schema Browser</span>\n </div>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"section reveal\">\n <div class=\"card\" style=\"padding:0;overflow:hidden;\">\n <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;\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;\">\n <span class=\"mono\" style=\"font-size:1rem;color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"ds-type ds-type-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n </div>\n <div style=\"display:flex;gap:0.75rem;flex-wrap:wrap;\">\n <% if (dsGov && dsGov.grain) { %><span class=\"tag\"><%= dsGov.grain %></span><% } %>\n <% if (dsGov && dsGov.refresh) { %><span class=\"tag\"><%= dsGov.refresh %></span><% } %>\n <% if (dsGov && dsGov.security) { %><span class=\"tag\"><%= dsGov.security %></span><% } %>\n </div>\n </div>\n <div style=\"padding:0 1.5rem 0.5rem;font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);padding-top:0.75rem;\">\n Source: <%= ds.source %>\n </div>\n <% if (ds.description) { %>\n <div style=\"padding:0 1.5rem 1rem;font-size:0.85rem;color:var(--text-muted);font-weight:300;\"><%= ds.description %></div>\n <% } %>\n\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"table-dark\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Description</th>\n <th>Semantic Role</th>\n <th>Aggregation</th>\n </tr>\n </thead>\n <tbody>\n <% for (var field of ds.fields) { %>\n <% var fieldKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-muted);font-size:0.82rem;\"><%= field.description || '' %></td>\n <td><% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %></td>\n <td style=\"font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } else { %>\n <p style=\"padding:1.5rem;color:var(--text-dim);font-size:0.85rem;\">No fields defined.</p>\n <% } %>\n </div>\n </div>\n <% } %>\n <% } else { %>\n <p style=\"color:var(--text-muted);\">No datasets found.</p>\n <% } %>\n</main>\n\n<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;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>\n<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n var num = /\\b(\\d+\\.?\\d*)\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n</body>\n</html>";
78
+ declare const schemaTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>\n<body>\n<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">&#9776;</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs &nearr;</a>\n </div>\n</div>\n<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>\n<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: '', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || '';\n return '<span class=\"tag ' + c + '\">' + (tier || 'none') + '</span>';\n} %>\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <a href=\"<%= basePath %>/models/<%= model.name %>.html\"><%= model.name %></a>\n <span class=\"breadcrumb-sep\">/</span>\n <span>Schema</span>\n </div>\n\n <div class=\"page-header\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;\">\n <h1><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n <span style=\"font-size:0.8rem;color:var(--text-dim);\">Schema Browser</span>\n </div>\n </div>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"section\">\n <div class=\"card\" style=\"padding:0;overflow:hidden;\">\n <div style=\"padding:1rem 1.25rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.4rem;\">\n <div style=\"display:flex;align-items:center;gap:0.5rem;\">\n <span class=\"mono\" style=\"font-size:0.9rem;color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"tag ds-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n </div>\n <div style=\"display:flex;gap:0.5rem;flex-wrap:wrap;\">\n <% if (dsGov && dsGov.grain) { %><span class=\"tag\"><%= dsGov.grain %></span><% } %>\n <% if (dsGov && dsGov.refresh) { %><span class=\"tag\"><%= dsGov.refresh %></span><% } %>\n <% if (dsGov && dsGov.security) { %><span class=\"tag\"><%= dsGov.security %></span><% } %>\n </div>\n </div>\n <div style=\"padding:0.5rem 1.25rem;font-size:0.72rem;color:var(--text-dim);font-family:var(--mono);\">\n Source: <%= ds.source %>\n </div>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div style=\"padding:0 1.25rem 0.75rem;font-size:0.82rem;color:var(--text-secondary);font-weight:300;\"><span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.datasets.<%= i %>.description\" data-label=\"<%= ds.name %> description\"><%= ds.description || 'Add description' %></span></div>\n <% } else if (ds.description) { %>\n <div style=\"padding:0 1.25rem 0.75rem;font-size:0.82rem;color:var(--text-secondary);font-weight:300;\"><%= ds.description %></div>\n <% } %>\n\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"data-table\">\n <thead>\n <tr><th>Field</th><th>Description</th><th>Semantic Role</th><th>Aggregation</th></tr>\n </thead>\n <tbody>\n <% for (var j = 0; j < ds.fields.length; j++) { var field = ds.fields[j]; %>\n <% var fieldKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-secondary);font-size:0.8rem;\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.datasets.<%= i %>.fields.<%= j %>.description\" data-label=\"<%= field.name %> description\"><%= field.description || 'Add description' %></span>\n <% } else { %>\n <%= field.description || '' %>\n <% } %>\n </td>\n <td>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"dropdown-editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"datasets.<%= ds.name %>.fields.<%= field.name %>.semantic_role\" data-options=\"identifier,metric,dimension,date,attribute\" data-label=\"<%= field.name %> semantic role\"><%= role || 'Select role' %></span>\n <% } else { %>\n <% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %>\n <% } %>\n </td>\n <td class=\"mono\" style=\"font-size:0.75rem;color:var(--text-dim);\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"dropdown-editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"datasets.<%= ds.name %>.fields.<%= field.name %>.default_aggregation\" data-options=\"SUM,AVG,COUNT,MIN,MAX,NONE\" data-label=\"<%= field.name %> aggregation\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : 'Select' %></span>\n <% } else { %>\n <%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %>\n <% } %>\n </td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } else { %>\n <p style=\"padding:1.25rem;color:var(--text-dim);font-size:0.82rem;\">No fields defined.</p>\n <% } %>\n </div>\n </div>\n <% } %>\n <% } else { %>\n <p style=\"color:var(--text-secondary);\">No datasets found.</p>\n <% } %>\n</div>\n\n<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n &nbsp;&middot;&nbsp;\n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>\n<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview &amp; Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>\n</body>\n</html>";
77
79
 
78
- declare const rulesTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <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\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n 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\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>\n<body>\n<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;\">\n <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>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/models/<%= modelName %>.html\" class=\"back-link\">&larr; Back to <%= modelName %></a>\n\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);margin-bottom:2rem;\">\n <%= modelName %> <span style=\"color:var(--text-dim);font-weight:300;\">&mdash; Rules &amp; Queries</span>\n </h1>\n\n <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated questions</h2>\n <% for (var gq of rules.golden_queries) { %>\n <div class=\"query-card\">\n <div class=\"query-question\"><%= gq.question %></div>\n <div class=\"query-sql sql-highlight\"><%= gq.sql %></div>\n <% if (gq.dialect || (gq.tags && gq.tags.length > 0)) { %>\n <div class=\"query-meta\">\n <% if (gq.dialect) { %><span>Dialect: <%= gq.dialect %></span><% } %>\n <% if (gq.tags && gq.tags.length > 0) { %><span>Tags: <%= gq.tags.join(', ') %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Business Rules</div>\n <h2 class=\"section-title\">Business Rules</h2>\n <div style=\"display:flex;flex-direction:column;gap:1rem;\">\n <% for (var br of rules.business_rules) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;\"><%= br.name %></div>\n <p style=\"font-size:0.85rem;color:var(--text-muted);line-height:1.6;font-weight:300;\"><%= br.definition %></p>\n <% if (br.enforcement && br.enforcement.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.6rem;flex-wrap:wrap;\">\n <% for (var e of br.enforcement) { %><span class=\"tag tag-green\"><%= e %></span><% } %>\n </div>\n <% } %>\n <% if (br.avoid && br.avoid.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.4rem;flex-wrap:wrap;\">\n <% for (var a of br.avoid) { %><span class=\"tag tag-red\"><%= a %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% for (var gf of rules.guardrail_filters) { %>\n <div class=\"guardrail\">\n <div class=\"guardrail-name\"><%= gf.name %></div>\n <div class=\"guardrail-filter\"><%= gf.filter %></div>\n <div class=\"guardrail-reason\"><%= gf.reason %></div>\n <% if (gf.tables && gf.tables.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.6rem;\">\n <% for (var tb of gf.tables) { %><span class=\"tag\"><%= tb %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Drill Paths</div>\n <h2 class=\"section-title\">Hierarchies</h2>\n <div style=\"display:flex;flex-direction:column;gap:1rem;\">\n <% for (var h of rules.hierarchies) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;\"><%= h.name %></div>\n <div style=\"font-size:0.82rem;color:var(--text-muted);margin-bottom:0.4rem;\">Dataset: <span class=\"mono\"><%= h.dataset %></span></div>\n <div style=\"display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;\">\n <% for (var li = 0; li < h.levels.length; li++) { %>\n <span class=\"tag tag-blue\"><%= h.levels[li] %></span>\n <% if (li < h.levels.length - 1) { %><span style=\"color:var(--gold-dark);\">&rarr;</span><% } %>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% 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))) { %>\n <p style=\"color:var(--text-muted);\">No rules or queries defined for this model.</p>\n <% } %>\n</main>\n\n<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;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>\n<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n var num = /\\b(\\d+\\.?\\d*)\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n</body>\n</html>";
80
+ declare const rulesTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>\n<body>\n<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">&#9776;</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs &nearr;</a>\n </div>\n</div>\n<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>\n<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <a href=\"<%= basePath %>/models/<%= modelName %>.html\"><%= modelName %></a>\n <span class=\"breadcrumb-sep\">/</span>\n <span>Rules</span>\n </div>\n\n <div class=\"page-header\">\n <h1><%= modelName %> <span style=\"color:var(--text-dim);font-weight:300;\">&mdash; Rules &amp; Queries</span></h1>\n </div>\n\n <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated questions</h2>\n <% for (var gq of rules.golden_queries) { %>\n <div class=\"query-card\">\n <div class=\"query-q\">\n <span class=\"query-q-badge\">Q</span>\n <span><%= gq.question %></span>\n </div>\n <div class=\"query-sql sql-highlight\"><%= gq.sql %></div>\n <% if (gq.dialect || (gq.tags && gq.tags.length > 0)) { %>\n <div class=\"query-meta\">\n <% if (gq.dialect) { %><span>Dialect: <%= gq.dialect %></span><% } %>\n <% if (gq.tags && gq.tags.length > 0) { %><span>Tags: <%= gq.tags.join(', ') %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Business Rules</div>\n <h2 class=\"section-title\">Business Rules</h2>\n <div style=\"display:flex;flex-direction:column;gap:0.75rem;\">\n <% for (var br of rules.business_rules) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.82rem;color:var(--accent-light);margin-bottom:0.3rem;font-weight:500;\"><%= br.name %></div>\n <p style=\"font-size:0.82rem;color:var(--text-secondary);line-height:1.6;font-weight:300;\"><%= br.definition %></p>\n <% if (br.enforcement && br.enforcement.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <% for (var e of br.enforcement) { %><span class=\"tag tag-green\"><%= e %></span><% } %>\n </div>\n <% } %>\n <% if (br.avoid && br.avoid.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.3rem;flex-wrap:wrap;\">\n <% for (var a of br.avoid) { %><span class=\"tag tag-red\"><%= a %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% for (var gf of rules.guardrail_filters) { %>\n <div class=\"guardrail\">\n <div class=\"guardrail-name\"><%= gf.name %></div>\n <div class=\"guardrail-filter\"><%= gf.filter %></div>\n <div class=\"guardrail-reason\"><%= gf.reason %></div>\n <% if (gf.tables && gf.tables.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.4rem;\">\n <% for (var tb of gf.tables) { %><span class=\"tag\"><%= tb %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Drill Paths</div>\n <h2 class=\"section-title\">Hierarchies</h2>\n <div style=\"display:flex;flex-direction:column;gap:0.75rem;\">\n <% for (var h of rules.hierarchies) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.82rem;color:var(--accent-light);margin-bottom:0.3rem;font-weight:500;\"><%= h.name %></div>\n <div style=\"font-size:0.78rem;color:var(--text-secondary);margin-bottom:0.3rem;\">Dataset: <span class=\"mono\"><%= h.dataset %></span></div>\n <div style=\"display:flex;align-items:center;gap:0.4rem;flex-wrap:wrap;\">\n <% for (var li = 0; li < h.levels.length; li++) { %>\n <span class=\"tag tag-blue\"><%= h.levels[li] %></span>\n <% if (li < h.levels.length - 1) { %><span style=\"color:var(--text-dim);\">&rarr;</span><% } %>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% 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))) { %>\n <p style=\"color:var(--text-secondary);\">No rules or queries defined for this model.</p>\n <% } %>\n</div>\n\n<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n &nbsp;&middot;&nbsp;\n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>\n<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview &amp; Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>\n</body>\n</html>";
79
81
 
80
- declare const glossaryTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <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\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n 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\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>\n<body>\n<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;\">\n <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>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>\n\n<main class=\"page\">\n <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>\n <p style=\"color:var(--text-muted);font-size:1rem;margin-bottom:2rem;font-weight:300;\">Business term definitions and mappings.</p>\n\n <% var termIds = Object.keys(terms).sort(); %>\n <% if (termIds.length === 0) { %>\n <p style=\"color:var(--text-muted);\">No terms defined.</p>\n <% } else { %>\n <input type=\"text\" id=\"glossary-filter\"\n placeholder=\"Filter terms...\"\n class=\"search-input\"\n style=\"margin-bottom:2rem;max-width:400px;\" />\n\n <div class=\"glossary-grid\" id=\"glossary-grid\">\n <% for (var tid of termIds) { %>\n <% var term = terms[tid]; %>\n <div class=\"glossary-card\" id=\"term-<%= tid %>\" data-term=\"<%= tid %>\">\n <div class=\"glossary-term\"><%= tid %></div>\n <div class=\"glossary-def\"><%= term.definition %></div>\n <% if (term.synonyms && term.synonyms.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.75rem;flex-wrap:wrap;\">\n <% for (var s of term.synonyms) { %><span class=\"tag\"><%= s %></span><% } %>\n </div>\n <% } %>\n <% if (term.maps_to && term.maps_to.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <% for (var m of term.maps_to) { %><span class=\"tag tag-blue\"><%= m %></span><% } %>\n </div>\n <% } %>\n <% if (term.owner) { %>\n <div style=\"font-size:0.78rem;color:var(--text-dim);margin-top:0.6rem;\">\n Owner: <a href=\"<%= basePath %>/owners/<%= term.owner %>.html\"><%= term.owner %></a>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n</main>\n\n<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;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>\n<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n var num = /\\b(\\d+\\.?\\d*)\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<script>\n(function() {\n var input = document.getElementById('glossary-filter');\n if (!input) return;\n var cards = document.querySelectorAll('.glossary-card');\n input.addEventListener('input', function() {\n var q = input.value.toLowerCase();\n cards.forEach(function(card) {\n var text = card.textContent.toLowerCase();\n card.style.display = text.indexOf(q) !== -1 ? '' : 'none';\n });\n });\n})();\n</script>\n</body>\n</html>";
82
+ declare const glossaryTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>\n<body>\n<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">&#9776;</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs &nearr;</a>\n </div>\n</div>\n<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>\n<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>\n\n<div class=\"main\">\n <div class=\"page-header\">\n <h1>Glossary</h1>\n <p class=\"subtitle\">Business term definitions and mappings.</p>\n </div>\n\n <% var termIds = Object.keys(terms).sort(); %>\n <% if (termIds.length === 0) { %>\n <p style=\"color:var(--text-secondary);\">No terms defined.</p>\n <% } else { %>\n <input type=\"text\" id=\"glossary-filter\"\n placeholder=\"Filter terms...\"\n class=\"search-input\"\n style=\"margin-bottom:1.5rem;max-width:360px;\" />\n\n <div class=\"glossary-grid\" id=\"glossary-grid\">\n <% for (var tid of termIds) { %>\n <% var term = terms[tid]; %>\n <div class=\"glossary-card card\" id=\"term-<%= tid %>\" data-term=\"<%= tid %>\">\n <div class=\"glossary-term\"><%= tid %></div>\n <div class=\"glossary-def\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/glossary/<%= tid %>.term.yaml\" data-path=\"definition\" data-label=\"<%= tid %> definition\"><%= term.definition || 'Add definition' %></span>\n <% } else { %>\n <%= term.definition || '' %>\n <% } %>\n </div>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <span class=\"editable\" data-file=\"context/glossary/<%= tid %>.term.yaml\" data-path=\"synonyms\" data-label=\"<%= tid %> synonyms\" style=\"font-size:0.78rem;color:var(--text-dim);\"><%= (term.synonyms && term.synonyms.length > 0) ? term.synonyms.join(', ') : 'Add synonyms (comma-separated)' %></span>\n </div>\n <% } else if (term.synonyms && term.synonyms.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <% for (var s of term.synonyms) { %><span class=\"tag\"><%= s %></span><% } %>\n </div>\n <% } %>\n <% if (term.maps_to && term.maps_to.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.35rem;flex-wrap:wrap;\">\n <% for (var m of term.maps_to) { %><span class=\"tag tag-blue\"><%= m %></span><% } %>\n </div>\n <% } %>\n <% if (term.owner) { %>\n <div style=\"font-size:0.72rem;color:var(--text-dim);margin-top:0.4rem;\">\n Owner: <a href=\"<%= basePath %>/owners/<%= term.owner %>.html\"><%= term.owner %></a>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div id=\"add-term-container\" style=\"margin-top:1.5rem;\">\n <button class=\"studio-add-btn\" id=\"add-term-btn\" onclick=\"document.getElementById('add-term-form').style.display='block';this.style.display='none';\">+ Add Term</button>\n <div id=\"add-term-form\" class=\"card\" style=\"display:none;padding:1.25rem;margin-top:0.5rem;\">\n <div style=\"font-size:0.85rem;color:#c9a55a;margin-bottom:1rem;font-weight:500;\">New Glossary Term</div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Term ID (slug)</label>\n <input type=\"text\" id=\"new-term-id\" placeholder=\"e.g. churn_rate\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Definition</label>\n <input type=\"text\" id=\"new-term-def\" placeholder=\"What this term means\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Synonyms (comma-separated)</label>\n <input type=\"text\" id=\"new-term-synonyms\" placeholder=\"e.g. attrition, turnover\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Tags (comma-separated)</label>\n <input type=\"text\" id=\"new-term-tags\" placeholder=\"e.g. finance, metrics\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"display:flex;gap:0.5rem;justify-content:flex-end;\">\n <button onclick=\"document.getElementById('add-term-form').style.display='none';document.getElementById('add-term-btn').style.display='';\" style=\"background:none;border:1px solid #555;color:var(--text-secondary);border-radius:6px;padding:6px 14px;cursor:pointer;font-size:0.82rem;\">Cancel</button>\n <button onclick=\"submitNewTerm()\" style=\"background:#c9a55a;border:none;color:#000;border-radius:6px;padding:6px 14px;cursor:pointer;font-size:0.82rem;font-weight:500;\">Stage Term</button>\n </div>\n </div>\n </div>\n <% } %>\n <% } %>\n</div>\n\n<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n &nbsp;&middot;&nbsp;\n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>\n<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview &amp; Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>\n<script>\n(function() {\n var input = document.getElementById('glossary-filter');\n if (!input) return;\n var cards = document.querySelectorAll('.glossary-card');\n input.addEventListener('input', function() {\n var q = input.value.toLowerCase();\n cards.forEach(function(card) {\n var text = card.textContent.toLowerCase();\n card.style.display = text.indexOf(q) !== -1 ? '' : 'none';\n });\n });\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<script>\nfunction submitNewTerm() {\n var id = document.getElementById('new-term-id').value.trim();\n var def = document.getElementById('new-term-def').value.trim();\n var syns = document.getElementById('new-term-synonyms').value.trim();\n var tags = document.getElementById('new-term-tags').value.trim();\n if (!id) { alert('Term ID is required.'); return; }\n var file = 'context/glossary/' + id + '.term.yaml';\n if (def) stageEdit(file, 'definition', def, id + ' definition');\n if (syns) {\n var synList = syns.split(',').map(function(s){ return s.trim(); }).filter(Boolean);\n stageEdit(file, 'synonyms', synList, id + ' synonyms');\n }\n if (tags) {\n var tagList = tags.split(',').map(function(t){ return t.trim(); }).filter(Boolean);\n stageEdit(file, 'tags', tagList, id + ' tags');\n }\n document.getElementById('add-term-form').style.display = 'none';\n document.getElementById('add-term-btn').style.display = '';\n document.getElementById('new-term-id').value = '';\n document.getElementById('new-term-def').value = '';\n document.getElementById('new-term-synonyms').value = '';\n document.getElementById('new-term-tags').value = '';\n}\n</script>\n<% } %>\n</body>\n</html>";
81
83
 
82
- declare const ownerTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <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\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n 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\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>\n<body>\n<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;\">\n <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>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: 'tag-none', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || cls.none;\n return '<span class=\"tag ' + c + '\">' + tier + '</span>';\n} %>\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/\" class=\"back-link\">&larr; All Models</a>\n\n <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>\n\n <div style=\"display:flex;gap:1.5rem;font-size:0.85rem;color:var(--text-dim);margin-bottom:1.5rem;\">\n <% if (owner.email) { %><span>Email: <span style=\"color:var(--text-muted);\"><%= owner.email %></span></span><% } %>\n <% if (owner.team) { %><span>Team: <span style=\"color:var(--text-muted);\"><%= owner.team %></span></span><% } %>\n </div>\n\n <% if (owner.description) { %>\n <p style=\"color:var(--text-muted);font-size:0.95rem;margin-bottom:2rem;font-weight:300;max-width:600px;\"><%= owner.description %></p>\n <% } %>\n\n <% if (governedModels.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Stewardship</div>\n <h2 class=\"section-title\">Governed Models</h2>\n <div class=\"card-grid-sm\">\n <% for (var gm of governedModels) { %>\n <a href=\"<%= basePath %>/models/<%= gm.name %>.html\" class=\"card\" style=\"text-decoration:none;\">\n <div style=\"display:flex;align-items:center;gap:0.5rem;\">\n <span class=\"mono\" style=\"color:var(--gold-light);\"><%= gm.name %></span>\n <% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>\n </div>\n </a>\n <% } %>\n </div>\n </div>\n <% } else { %>\n <p style=\"color:var(--text-muted);\">No models governed by this owner.</p>\n <% } %>\n</main>\n\n<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;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>\n<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n var num = /\\b(\\d+\\.?\\d*)\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n</body>\n</html>";
84
+ declare const ownerTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>\n<body>\n<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">&#9776;</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs &nearr;</a>\n </div>\n</div>\n<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>\n<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>\n<% function tierBadge(tier) {\n var cls = { none: '', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || '';\n return '<span class=\"tag ' + c + '\">' + (tier || 'none') + '</span>';\n} %>\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <span><%= owner.display_name %></span>\n </div>\n\n <div class=\"page-header\">\n <h1>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/owners/<%= owner.id %>.owner.yaml\" data-path=\"display_name\" data-label=\"<%= owner.id %> display name\"><%= owner.display_name || 'Add display name' %></span>\n <% } else { %>\n <%= owner.display_name %>\n <% } %>\n </h1>\n </div>\n\n <div style=\"display:flex;gap:1.25rem;font-size:0.82rem;color:var(--text-dim);margin-bottom:1.5rem;\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span>Email: <span class=\"editable\" data-file=\"context/owners/<%= owner.id %>.owner.yaml\" data-path=\"email\" data-label=\"<%= owner.id %> email\" style=\"color:var(--text-secondary);\"><%= owner.email || 'Add email' %></span></span>\n <span>Team: <span class=\"editable\" data-file=\"context/owners/<%= owner.id %>.owner.yaml\" data-path=\"team\" data-label=\"<%= owner.id %> team\" style=\"color:var(--text-secondary);\"><%= owner.team || 'Add team' %></span></span>\n <% } else { %>\n <% if (owner.email) { %><span>Email: <span style=\"color:var(--text-secondary);\"><%= owner.email %></span></span><% } %>\n <% if (owner.team) { %><span>Team: <span style=\"color:var(--text-secondary);\"><%= owner.team %></span></span><% } %>\n <% } %>\n </div>\n\n <% if (owner.description) { %>\n <p style=\"color:var(--text-secondary);font-size:0.9rem;margin-bottom:2rem;font-weight:300;max-width:560px;\"><%= owner.description %></p>\n <% } %>\n\n <% if (governedModels.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Stewardship</div>\n <h2 class=\"section-title\">Governed Models</h2>\n <div class=\"card-grid\">\n <% for (var gm of governedModels) { %>\n <a href=\"<%= basePath %>/models/<%= gm.name %>.html\" class=\"card-link\">\n <div class=\"card\">\n <div style=\"display:flex;align-items:center;gap:0.4rem;\">\n <span class=\"mono\" style=\"font-size:0.85rem;color:var(--accent-light);\"><%= gm.name %></span>\n <% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>\n </div>\n </div>\n </a>\n <% } %>\n </div>\n </div>\n <% } else { %>\n <p style=\"color:var(--text-secondary);\">No models governed by this owner.</p>\n <% } %>\n</div>\n\n<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n &nbsp;&middot;&nbsp;\n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>\n<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview &amp; Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>\n</body>\n</html>";
83
85
 
84
- declare const searchTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <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\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n 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\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>\n<body>\n<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;\">\n <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>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>\n\n<main class=\"page\">\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:2rem;\">Search</h1>\n\n <input type=\"text\" id=\"search-input\"\n placeholder=\"Search models, datasets, terms...\"\n class=\"search-input\"\n style=\"margin-bottom:2rem;\" />\n\n <div id=\"search-results\" style=\"display:flex;flex-direction:column;gap:0.75rem;\"></div>\n</main>\n\n<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;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>\n<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n var num = /\\b(\\d+\\.?\\d*)\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n\n<script src=\"https://cdn.jsdelivr.net/npm/minisearch@7.1.0/dist/umd/index.min.js\"></script>\n<script>\n(function() {\n var indexData = <%- searchIndexJson %>;\n var miniSearch = MiniSearch.loadJSON(JSON.stringify(indexData.index), indexData.options);\n\n var input = document.getElementById('search-input');\n var resultsContainer = document.getElementById('search-results');\n var docs = indexData.documents;\n\n var typeColors = {\n model: 'tag-gold',\n dataset: 'tag-blue',\n term: 'tag-green',\n owner: 'tag-bronze'\n };\n\n function clearResults() {\n while (resultsContainer.firstChild) {\n resultsContainer.removeChild(resultsContainer.firstChild);\n }\n }\n\n function createResultElement(doc) {\n var wrapper = document.createElement('div');\n wrapper.className = 'card';\n wrapper.style.padding = '1rem 1.25rem';\n\n var top = document.createElement('div');\n top.style.display = 'flex';\n top.style.alignItems = 'center';\n top.style.gap = '0.5rem';\n\n var link = document.createElement('a');\n link.href = doc.url;\n link.style.fontFamily = 'var(--mono)';\n link.style.fontSize = '0.88rem';\n link.style.color = 'var(--gold-light)';\n link.textContent = doc.title;\n top.appendChild(link);\n\n var badge = document.createElement('span');\n badge.className = 'tag ' + (typeColors[doc.type] || '');\n badge.textContent = doc.type;\n top.appendChild(badge);\n\n wrapper.appendChild(top);\n\n if (doc.description) {\n var desc = document.createElement('p');\n desc.style.fontSize = '0.82rem';\n desc.style.color = 'var(--text-muted)';\n desc.style.marginTop = '0.3rem';\n desc.style.fontWeight = '300';\n desc.textContent = doc.description;\n wrapper.appendChild(desc);\n }\n\n return wrapper;\n }\n\n input.addEventListener('input', function() {\n var query = input.value.trim();\n clearResults();\n if (!query) return;\n var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });\n if (hits.length === 0) {\n var noResults = document.createElement('p');\n noResults.style.color = 'var(--text-muted)';\n noResults.textContent = 'No results found.';\n resultsContainer.appendChild(noResults);\n return;\n }\n hits.slice(0, 20).forEach(function(hit) {\n var doc = docs[hit.id];\n if (doc) {\n resultsContainer.appendChild(createResultElement(doc));\n }\n });\n });\n})();\n</script>\n</body>\n</html>";
86
+ declare const searchTemplate = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> \u2014 <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>\n<body>\n<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">&#9776;</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs &nearr;</a>\n </div>\n</div>\n<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>\n<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>\n\n<div class=\"main\">\n <div class=\"page-header\">\n <h1>Search</h1>\n </div>\n\n <input type=\"text\" id=\"search-input\"\n placeholder=\"Search models, datasets, fields, terms...\"\n class=\"search-input\"\n style=\"margin-bottom:1.5rem;\" />\n\n <div id=\"search-results\" style=\"display:flex;flex-direction:column;gap:0.5rem;\"></div>\n</div>\n\n<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n &nbsp;&middot;&nbsp;\n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>\n<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\u2212';\n }\n };\n\n // SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n 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;\n 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;\n var str = /('(?:[^'\\\\]|\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview &amp; Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>\n\n<script src=\"https://cdn.jsdelivr.net/npm/minisearch@7.1.0/dist/umd/index.min.js\"></script>\n<script>\n(function() {\n var indexData = <%- searchIndexJson %>;\n var miniSearch = MiniSearch.loadJSON(JSON.stringify(indexData.index), indexData.options);\n\n var input = document.getElementById('search-input');\n var resultsContainer = document.getElementById('search-results');\n var docs = indexData.documents;\n\n var typeColors = {\n model: 'tag-gold',\n dataset: 'tag-blue',\n term: 'tag-green',\n owner: 'tag-bronze'\n };\n\n function clearResults() {\n while (resultsContainer.firstChild) {\n resultsContainer.removeChild(resultsContainer.firstChild);\n }\n }\n\n function createResultElement(doc) {\n var wrapper = document.createElement('div');\n wrapper.className = 'card';\n wrapper.style.padding = '0.75rem 1rem';\n\n var top = document.createElement('div');\n top.style.display = 'flex';\n top.style.alignItems = 'center';\n top.style.gap = '0.4rem';\n\n var link = document.createElement('a');\n link.href = doc.url;\n link.style.fontFamily = 'var(--mono)';\n link.style.fontSize = '0.82rem';\n link.style.color = 'var(--accent-light)';\n link.textContent = doc.title;\n top.appendChild(link);\n\n var badge = document.createElement('span');\n badge.className = 'tag ' + (typeColors[doc.type] || '');\n badge.textContent = doc.type;\n top.appendChild(badge);\n\n wrapper.appendChild(top);\n\n if (doc.description) {\n var desc = document.createElement('p');\n desc.style.fontSize = '0.78rem';\n desc.style.color = 'var(--text-secondary)';\n desc.style.marginTop = '0.2rem';\n desc.style.fontWeight = '300';\n desc.textContent = doc.description;\n wrapper.appendChild(desc);\n }\n\n return wrapper;\n }\n\n input.addEventListener('input', function() {\n var query = input.value.trim();\n clearResults();\n if (!query) return;\n var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });\n if (hits.length === 0) {\n var noResults = document.createElement('p');\n noResults.style.color = 'var(--text-secondary)';\n noResults.textContent = 'No results found.';\n resultsContainer.appendChild(noResults);\n return;\n }\n hits.slice(0, 20).forEach(function(hit) {\n var doc = docs[hit.id];\n if (doc) {\n resultsContainer.appendChild(createResultElement(doc));\n }\n });\n });\n})();\n</script>\n</body>\n</html>";
85
87
 
86
88
  export { type GenerateSiteOptions, type SearchDocument, type SearchIndex, buildSearchIndex, buildSite, generateSite, glossaryTemplate, indexTemplate, modelTemplate, ownerTemplate, rulesTemplate, schemaTemplate, searchTemplate };