@runcontext/site 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -69,18 +69,18 @@ interface SearchIndex {
69
69
  */
70
70
  declare function buildSearchIndex(manifest: Manifest, basePath: string): SearchIndex;
71
71
 
72
- /**
73
- * EJS template strings for the ContextKit documentation site.
74
- *
75
- * All templates use Tailwind CDN for styling and are rendered
76
- * via `ejs.render()` with embedded template strings.
77
- */
78
- 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 <script src=\"https://cdn.tailwindcss.com\"></script>\n</head>\n<body class=\"bg-white text-gray-900 min-h-screen\">\n<nav class=\"bg-gray-900 text-white px-6 py-3 flex items-center gap-6\">\n <a href=\"<%= basePath %>/\" class=\"font-bold text-lg\"><%- siteTitle %></a>\n <a href=\"<%= basePath %>/\" class=\"hover:underline\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"hover:underline\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" class=\"hover:underline\">Search</a>\n</nav>\n<% function tierBadge(tier) {\n var colors = { none: 'bg-gray-200 text-gray-700', bronze: 'bg-amber-700 text-white', silver: 'bg-gray-400 text-white', gold: 'bg-yellow-400 text-black' };\n var cls = colors[tier] || colors.none;\n return '<span class=\"px-2 py-0.5 rounded text-xs font-semibold uppercase ' + cls + '\">' + tier + '</span>';\n} %>\n<main class=\"max-w-5xl mx-auto p-6\">\n <h1 class=\"text-3xl font-bold mb-6\"><%- siteTitle %></h1>\n\n <section>\n <h2 class=\"text-xl font-semibold mb-4\">Models</h2>\n <% if (Object.keys(models).length === 0) { %>\n <p class=\"text-gray-500\">No models found.</p>\n <% } else { %>\n <div class=\"grid gap-4\">\n <% for (var name of Object.keys(models)) { %>\n <div class=\"border rounded-lg p-4 hover:shadow transition\">\n <div class=\"flex items-center gap-3\">\n <a href=\"<%= basePath %>/models/<%= name %>.html\" class=\"text-lg font-medium text-blue-600 hover:underline\"><%= name %></a>\n <% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>\n </div>\n <% if (models[name].description) { %>\n <p class=\"text-gray-600 mt-1\"><%= models[name].description %></p>\n <% } %>\n <% if (governance[name]) { %>\n <div class=\"flex gap-2 mt-2 text-xs text-gray-500\">\n <% if (governance[name].owner) { %>\n <span>Owner: <a href=\"<%= basePath %>/owners/<%= governance[name].owner %>.html\" class=\"underline\"><%= governance[name].owner %></a></span>\n <% } %>\n <% if (governance[name].trust) { %>\n <span>Trust: <%= governance[name].trust %></span>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </section>\n\n <% if (Object.keys(owners).length > 0) { %>\n <section class=\"mt-8\">\n <h2 class=\"text-xl font-semibold mb-4\">Owners</h2>\n <ul class=\"space-y-1\">\n <% for (var oid of Object.keys(owners)) { %>\n <li><a href=\"<%= basePath %>/owners/<%= oid %>.html\" class=\"text-blue-600 hover:underline\"><%= owners[oid].display_name %></a></li>\n <% } %>\n </ul>\n </section>\n <% } %>\n</main>\n<footer class=\"mt-12 border-t py-4 px-6 text-gray-500 text-sm\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" class=\"underline\">ContextKit</a>\n</footer>\n</body>\n</html>";
79
- 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 <script src=\"https://cdn.tailwindcss.com\"></script>\n</head>\n<body class=\"bg-white text-gray-900 min-h-screen\">\n<nav class=\"bg-gray-900 text-white px-6 py-3 flex items-center gap-6\">\n <a href=\"<%= basePath %>/\" class=\"font-bold text-lg\"><%- siteTitle %></a>\n <a href=\"<%= basePath %>/\" class=\"hover:underline\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"hover:underline\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" class=\"hover:underline\">Search</a>\n</nav>\n<% function tierBadge(tier) {\n var colors = { none: 'bg-gray-200 text-gray-700', bronze: 'bg-amber-700 text-white', silver: 'bg-gray-400 text-white', gold: 'bg-yellow-400 text-black' };\n var cls = colors[tier] || colors.none;\n return '<span class=\"px-2 py-0.5 rounded text-xs font-semibold uppercase ' + cls + '\">' + tier + '</span>';\n} %>\n<main class=\"max-w-5xl mx-auto p-6\">\n <div class=\"flex items-center gap-3 mb-2\">\n <h1 class=\"text-3xl font-bold\"><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n </div>\n <% if (model.description) { %>\n <p class=\"text-gray-600 mb-4\"><%= model.description %></p>\n <% } %>\n\n <div class=\"flex gap-3 mb-6 text-sm\">\n <a href=\"<%= basePath %>/models/<%= model.name %>/schema.html\" class=\"text-blue-600 hover:underline\">Schema Browser</a>\n <a href=\"<%= basePath %>/models/<%= model.name %>/rules.html\" class=\"text-blue-600 hover:underline\">Rules &amp; Queries</a>\n </div>\n\n <% if (gov) { %>\n <section class=\"mb-6\">\n <h2 class=\"text-xl font-semibold mb-2\">Governance</h2>\n <table class=\"table-auto border-collapse text-sm\">\n <tbody>\n <tr><td class=\"pr-4 font-medium\">Owner</td><td><a href=\"<%= basePath %>/owners/<%= gov.owner %>.html\" class=\"text-blue-600 underline\"><%= gov.owner %></a></td></tr>\n <% if (gov.trust) { %><tr><td class=\"pr-4 font-medium\">Trust</td><td><%= gov.trust %></td></tr><% } %>\n <% if (gov.security) { %><tr><td class=\"pr-4 font-medium\">Security</td><td><%= gov.security %></td></tr><% } %>\n <% if (gov.tags && gov.tags.length > 0) { %><tr><td class=\"pr-4 font-medium\">Tags</td><td><%= gov.tags.join(', ') %></td></tr><% } %>\n </tbody>\n </table>\n </section>\n <% } %>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <section class=\"mb-6\">\n <h2 class=\"text-xl font-semibold mb-2\">Datasets</h2>\n <div class=\"space-y-3\">\n <% for (var ds of model.datasets) { %>\n <div class=\"border rounded p-3\">\n <div class=\"font-medium\"><%= ds.name %></div>\n <div class=\"text-xs text-gray-500\">Source: <%= ds.source %></div>\n <% if (ds.description) { %><p class=\"text-sm text-gray-600 mt-1\"><%= ds.description %></p><% } %>\n <% if (ds.fields && ds.fields.length > 0) { %>\n <div class=\"text-xs text-gray-500 mt-1\"><%= ds.fields.length %> field(s)</div>\n <% } %>\n </div>\n <% } %>\n </div>\n </section>\n <% } %>\n\n <% if (model.relationships && model.relationships.length > 0) { %>\n <section class=\"mb-6\">\n <h2 class=\"text-xl font-semibold mb-2\">Relationships</h2>\n <table class=\"table-auto w-full text-sm border-collapse\">\n <thead>\n <tr class=\"border-b\"><th class=\"text-left py-1 pr-3\">Name</th><th class=\"text-left py-1 pr-3\">From</th><th class=\"text-left py-1\">To</th></tr>\n </thead>\n <tbody>\n <% for (var rel of model.relationships) { %>\n <tr class=\"border-b\"><td class=\"py-1 pr-3\"><%= rel.name %></td><td class=\"py-1 pr-3\"><%= rel.from %></td><td class=\"py-1\"><%= rel.to %></td></tr>\n <% } %>\n </tbody>\n </table>\n </section>\n <% } %>\n\n <% if (model.metrics && model.metrics.length > 0) { %>\n <section class=\"mb-6\">\n <h2 class=\"text-xl font-semibold mb-2\">Metrics</h2>\n <div class=\"space-y-2\">\n <% for (var metric of model.metrics) { %>\n <div class=\"border rounded p-3\">\n <div class=\"font-medium\"><%= metric.name %></div>\n <% if (metric.description) { %><p class=\"text-sm text-gray-600\"><%= metric.description %></p><% } %>\n </div>\n <% } %>\n </div>\n </section>\n <% } %>\n\n <% if (tier) { %>\n <section class=\"mb-6\">\n <h2 class=\"text-xl font-semibold mb-2\">Tier Details</h2>\n <% var tierLevels = ['bronze', 'silver', 'gold']; %>\n <% for (var lvl of tierLevels) { %>\n <div class=\"mb-3\">\n <h3 class=\"font-medium capitalize flex items-center gap-2\">\n <%= lvl %>\n <% if (tier[lvl].passed) { %>\n <span class=\"text-green-600 text-xs\">Passed</span>\n <% } else { %>\n <span class=\"text-red-500 text-xs\">Not passed</span>\n <% } %>\n </h3>\n <% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>\n <ul class=\"text-sm ml-4 list-disc\">\n <% for (var chk of tier[lvl].checks) { %>\n <li class=\"<%= chk.passed ? 'text-green-700' : 'text-red-600' %>\"><%= chk.label %><% if (chk.detail) { %> \u2014 <%= chk.detail %><% } %></li>\n <% } %>\n </ul>\n <% } %>\n </div>\n <% } %>\n </section>\n <% } %>\n</main>\n<footer class=\"mt-12 border-t py-4 px-6 text-gray-500 text-sm\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" class=\"underline\">ContextKit</a>\n</footer>\n</body>\n</html>";
80
- 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 <script src=\"https://cdn.tailwindcss.com\"></script>\n</head>\n<body class=\"bg-white text-gray-900 min-h-screen\">\n<nav class=\"bg-gray-900 text-white px-6 py-3 flex items-center gap-6\">\n <a href=\"<%= basePath %>/\" class=\"font-bold text-lg\"><%- siteTitle %></a>\n <a href=\"<%= basePath %>/\" class=\"hover:underline\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"hover:underline\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" class=\"hover:underline\">Search</a>\n</nav>\n<% function tierBadge(tier) {\n var colors = { none: 'bg-gray-200 text-gray-700', bronze: 'bg-amber-700 text-white', silver: 'bg-gray-400 text-white', gold: 'bg-yellow-400 text-black' };\n var cls = colors[tier] || colors.none;\n return '<span class=\"px-2 py-0.5 rounded text-xs font-semibold uppercase ' + cls + '\">' + tier + '</span>';\n} %>\n<main class=\"max-w-5xl mx-auto p-6\">\n <div class=\"flex items-center gap-3 mb-2\">\n <h1 class=\"text-3xl font-bold\"><%= model.name %> \u2014 Schema Browser</h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n </div>\n <div class=\"mb-4 text-sm\">\n <a href=\"<%= basePath %>/models/<%= model.name %>.html\" class=\"text-blue-600 hover:underline\">&larr; Back to model</a>\n </div>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <% for (var ds of model.datasets) { %>\n <section class=\"mb-8 border rounded-lg p-4\">\n <h2 class=\"text-xl font-semibold mb-1\"><%= ds.name %></h2>\n <div class=\"text-xs text-gray-500 mb-2\">Source: <%= ds.source %></div>\n <% if (ds.description) { %><p class=\"text-sm text-gray-600 mb-3\"><%= ds.description %></p><% } %>\n\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <% if (dsGov) { %>\n <div class=\"text-xs text-gray-500 mb-3 flex gap-4\">\n <% if (dsGov.grain) { %><span>Grain: <strong><%= dsGov.grain %></strong></span><% } %>\n <% if (dsGov.table_type) { %><span>Type: <strong><%= dsGov.table_type %></strong></span><% } %>\n <% if (dsGov.refresh) { %><span>Refresh: <strong><%= dsGov.refresh %></strong></span><% } %>\n <% if (dsGov.security) { %><span>Security: <strong><%= dsGov.security %></strong></span><% } %>\n </div>\n <% } %>\n\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"table-auto w-full text-sm border-collapse\">\n <thead>\n <tr class=\"border-b bg-gray-50\">\n <th class=\"text-left py-2 px-2\">Field</th>\n <th class=\"text-left py-2 px-2\">Description</th>\n <th class=\"text-left py-2 px-2\">Semantic Role</th>\n <th class=\"text-left py-2 px-2\">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 <tr class=\"border-b\">\n <td class=\"py-1 px-2 font-mono text-xs\"><%= field.name %></td>\n <td class=\"py-1 px-2\"><%= field.description || '' %></td>\n <td class=\"py-1 px-2\"><%= fGov && fGov.semantic_role ? fGov.semantic_role : '' %></td>\n <td class=\"py-1 px-2\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } else { %>\n <p class=\"text-gray-400 text-sm\">No fields defined.</p>\n <% } %>\n </section>\n <% } %>\n <% } else { %>\n <p class=\"text-gray-500\">No datasets found.</p>\n <% } %>\n</main>\n<footer class=\"mt-12 border-t py-4 px-6 text-gray-500 text-sm\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" class=\"underline\">ContextKit</a>\n</footer>\n</body>\n</html>";
81
- 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 <script src=\"https://cdn.tailwindcss.com\"></script>\n</head>\n<body class=\"bg-white text-gray-900 min-h-screen\">\n<nav class=\"bg-gray-900 text-white px-6 py-3 flex items-center gap-6\">\n <a href=\"<%= basePath %>/\" class=\"font-bold text-lg\"><%- siteTitle %></a>\n <a href=\"<%= basePath %>/\" class=\"hover:underline\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"hover:underline\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" class=\"hover:underline\">Search</a>\n</nav>\n<main class=\"max-w-5xl mx-auto p-6\">\n <h1 class=\"text-3xl font-bold mb-2\"><%= modelName %> \u2014 Rules &amp; Queries</h1>\n <div class=\"mb-4 text-sm\">\n <a href=\"<%= basePath %>/models/<%= modelName %>.html\" class=\"text-blue-600 hover:underline\">&larr; Back to model</a>\n </div>\n\n <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>\n <section class=\"mb-8\">\n <h2 class=\"text-xl font-semibold mb-3\">Golden Queries</h2>\n <div class=\"space-y-4\">\n <% for (var gq of rules.golden_queries) { %>\n <div class=\"border rounded-lg p-4\">\n <div class=\"font-medium mb-2\"><%= gq.question %></div>\n <pre class=\"bg-gray-100 rounded p-3 text-sm overflow-x-auto\"><code><%= gq.sql %></code></pre>\n <% if (gq.dialect) { %><div class=\"text-xs text-gray-500 mt-1\">Dialect: <%= gq.dialect %></div><% } %>\n <% if (gq.tags && gq.tags.length > 0) { %><div class=\"text-xs text-gray-500 mt-1\">Tags: <%= gq.tags.join(', ') %></div><% } %>\n </div>\n <% } %>\n </div>\n </section>\n <% } %>\n\n <% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>\n <section class=\"mb-8\">\n <h2 class=\"text-xl font-semibold mb-3\">Business Rules</h2>\n <div class=\"space-y-3\">\n <% for (var br of rules.business_rules) { %>\n <div class=\"border rounded p-4\">\n <div class=\"font-medium\"><%= br.name %></div>\n <p class=\"text-sm text-gray-600 mt-1\"><%= br.definition %></p>\n <% if (br.enforcement && br.enforcement.length > 0) { %>\n <div class=\"text-xs text-gray-500 mt-1\">Enforcement: <%= br.enforcement.join(', ') %></div>\n <% } %>\n <% if (br.avoid && br.avoid.length > 0) { %>\n <div class=\"text-xs text-red-500 mt-1\">Avoid: <%= br.avoid.join(', ') %></div>\n <% } %>\n </div>\n <% } %>\n </div>\n </section>\n <% } %>\n\n <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>\n <section class=\"mb-8\">\n <h2 class=\"text-xl font-semibold mb-3\">Guardrail Filters</h2>\n <div class=\"space-y-3\">\n <% for (var gf of rules.guardrail_filters) { %>\n <div class=\"border rounded p-4\">\n <div class=\"font-medium\"><%= gf.name %></div>\n <pre class=\"bg-gray-100 rounded p-2 text-sm mt-1\"><code><%= gf.filter %></code></pre>\n <p class=\"text-sm text-gray-600 mt-1\"><%= gf.reason %></p>\n </div>\n <% } %>\n </div>\n </section>\n <% } %>\n\n <% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>\n <section class=\"mb-8\">\n <h2 class=\"text-xl font-semibold mb-3\">Hierarchies</h2>\n <div class=\"space-y-3\">\n <% for (var h of rules.hierarchies) { %>\n <div class=\"border rounded p-4\">\n <div class=\"font-medium\"><%= h.name %></div>\n <div class=\"text-sm text-gray-600 mt-1\">Dataset: <%= h.dataset %></div>\n <div class=\"text-sm text-gray-600\">Levels: <%= h.levels.join(' &rarr; ') %></div>\n </div>\n <% } %>\n </div>\n </section>\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 class=\"text-gray-500\">No rules or queries defined for this model.</p>\n <% } %>\n</main>\n<footer class=\"mt-12 border-t py-4 px-6 text-gray-500 text-sm\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" class=\"underline\">ContextKit</a>\n</footer>\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 <script src=\"https://cdn.tailwindcss.com\"></script>\n</head>\n<body class=\"bg-white text-gray-900 min-h-screen\">\n<nav class=\"bg-gray-900 text-white px-6 py-3 flex items-center gap-6\">\n <a href=\"<%= basePath %>/\" class=\"font-bold text-lg\"><%- siteTitle %></a>\n <a href=\"<%= basePath %>/\" class=\"hover:underline\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"hover:underline\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" class=\"hover:underline\">Search</a>\n</nav>\n<main class=\"max-w-5xl mx-auto p-6\">\n <h1 class=\"text-3xl font-bold mb-6\">Glossary</h1>\n\n <% var termIds = Object.keys(terms).sort(); %>\n <% if (termIds.length === 0) { %>\n <p class=\"text-gray-500\">No terms defined.</p>\n <% } else { %>\n <div class=\"space-y-4\">\n <% for (var tid of termIds) { %>\n <% var term = terms[tid]; %>\n <div class=\"border rounded-lg p-4\" id=\"term-<%= tid %>\">\n <h2 class=\"text-lg font-semibold\"><%= tid %></h2>\n <p class=\"text-gray-700 mt-1\"><%= term.definition %></p>\n <% if (term.synonyms && term.synonyms.length > 0) { %>\n <div class=\"text-sm text-gray-500 mt-1\">Synonyms: <%= term.synonyms.join(', ') %></div>\n <% } %>\n <% if (term.maps_to && term.maps_to.length > 0) { %>\n <div class=\"text-sm text-gray-500 mt-1\">Maps to: <%= term.maps_to.join(', ') %></div>\n <% } %>\n <% if (term.owner) { %>\n <div class=\"text-sm text-gray-500 mt-1\">Owner: <a href=\"<%= basePath %>/owners/<%= term.owner %>.html\" class=\"text-blue-600 underline\"><%= term.owner %></a></div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n</main>\n<footer class=\"mt-12 border-t py-4 px-6 text-gray-500 text-sm\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" class=\"underline\">ContextKit</a>\n</footer>\n</body>\n</html>";
83
- 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 <script src=\"https://cdn.tailwindcss.com\"></script>\n</head>\n<body class=\"bg-white text-gray-900 min-h-screen\">\n<nav class=\"bg-gray-900 text-white px-6 py-3 flex items-center gap-6\">\n <a href=\"<%= basePath %>/\" class=\"font-bold text-lg\"><%- siteTitle %></a>\n <a href=\"<%= basePath %>/\" class=\"hover:underline\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"hover:underline\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" class=\"hover:underline\">Search</a>\n</nav>\n<% function tierBadge(tier) {\n var colors = { none: 'bg-gray-200 text-gray-700', bronze: 'bg-amber-700 text-white', silver: 'bg-gray-400 text-white', gold: 'bg-yellow-400 text-black' };\n var cls = colors[tier] || colors.none;\n return '<span class=\"px-2 py-0.5 rounded text-xs font-semibold uppercase ' + cls + '\">' + tier + '</span>';\n} %>\n<main class=\"max-w-5xl mx-auto p-6\">\n <h1 class=\"text-3xl font-bold mb-2\"><%= owner.display_name %></h1>\n <div class=\"text-sm text-gray-500 mb-4\">\n <% if (owner.email) { %><span>Email: <%= owner.email %></span><% } %>\n <% if (owner.team) { %><span class=\"ml-4\">Team: <%= owner.team %></span><% } %>\n </div>\n <% if (owner.description) { %>\n <p class=\"text-gray-600 mb-4\"><%= owner.description %></p>\n <% } %>\n\n <% if (governedModels.length > 0) { %>\n <section>\n <h2 class=\"text-xl font-semibold mb-3\">Governed Models</h2>\n <div class=\"space-y-2\">\n <% for (var gm of governedModels) { %>\n <div class=\"flex items-center gap-3\">\n <a href=\"<%= basePath %>/models/<%= gm.name %>.html\" class=\"text-blue-600 hover:underline\"><%= gm.name %></a>\n <% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>\n </div>\n <% } %>\n </div>\n </section>\n <% } else { %>\n <p class=\"text-gray-500\">No models governed by this owner.</p>\n <% } %>\n</main>\n<footer class=\"mt-12 border-t py-4 px-6 text-gray-500 text-sm\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" class=\"underline\">ContextKit</a>\n</footer>\n</body>\n</html>";
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 <script src=\"https://cdn.tailwindcss.com\"></script>\n</head>\n<body class=\"bg-white text-gray-900 min-h-screen\">\n<nav class=\"bg-gray-900 text-white px-6 py-3 flex items-center gap-6\">\n <a href=\"<%= basePath %>/\" class=\"font-bold text-lg\"><%- siteTitle %></a>\n <a href=\"<%= basePath %>/\" class=\"hover:underline\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"hover:underline\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" class=\"hover:underline\">Search</a>\n</nav>\n<main class=\"max-w-5xl mx-auto p-6\">\n <h1 class=\"text-3xl font-bold mb-6\">Search</h1>\n\n <input type=\"text\" id=\"search-input\"\n placeholder=\"Search models, datasets, terms...\"\n class=\"w-full border rounded-lg px-4 py-2 mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500\" />\n\n <div id=\"search-results\" class=\"space-y-2\"></div>\n</main>\n<footer class=\"mt-12 border-t py-4 px-6 text-gray-500 text-sm\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" class=\"underline\">ContextKit</a>\n</footer>\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 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 = 'border rounded p-3';\n\n var link = document.createElement('a');\n link.href = doc.url;\n link.className = 'text-blue-600 font-medium hover:underline';\n link.textContent = doc.title;\n wrapper.appendChild(link);\n\n var badge = document.createElement('span');\n badge.className = 'ml-2 text-xs text-gray-400 uppercase';\n badge.textContent = doc.type;\n wrapper.appendChild(badge);\n\n if (doc.description) {\n var desc = document.createElement('p');\n desc.className = 'text-sm text-gray-600 mt-1';\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.className = 'text-gray-500';\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>";
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>";
73
+
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>";
75
+
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>";
77
+
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>";
79
+
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>";
81
+
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>";
83
+
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>";
85
85
 
86
86
  export { type GenerateSiteOptions, type SearchDocument, type SearchIndex, buildSearchIndex, buildSite, generateSite, glossaryTemplate, indexTemplate, modelTemplate, ownerTemplate, rulesTemplate, schemaTemplate, searchTemplate };