Files
stack/assets/pfs.html
kert c81992184a
Some checks failed
CI / skinny-install (aco) (push) Successful in 54s
CI / skinny-install (api) (push) Successful in 32s
CI / skinny-install (bcda) (push) Successful in 33s
CI / skinny-install (bib) (push) Successful in 30s
CI / skinny-install (bls) (push) Successful in 26s
CI / lint-test (push) Successful in 3m19s
CI / skinny-install (ccw) (push) Successful in 27s
CI / skinny-install (cms) (push) Successful in 32s
CI / skinny-install (cli) (push) Successful in 38s
CI / skinny-install (conf) (push) Successful in 33s
CI / skinny-install (opps) (push) Successful in 43s
Infra CI / notebooks (push) Successful in 7s
Infra CI / zotero (push) Successful in 7s
Infra CI / docs (push) Failing after 10s
Infra CI / api (push) Successful in 13s
Infra CI / mc (push) Successful in 7s
CI / skinny-install (perf) (push) Successful in 31s
CI / skinny-install (pfs) (push) Successful in 30s
CI / skinny-install (rex) (push) Successful in 31s
Deploy / build-scan-report (push) Failing after 1m47s
feat: port fhirworx theme — editorial redesign based on hti5.org
Rip and replace nature/loch with fhirworx — a Federal Register / data
journalism aesthetic inspired by hti5.org.

Typography: Playfair Display for display headings, Source Serif 4 for
reading body, JetBrains Mono reserved for IDs, labels, metadata (docket
codes, commit hashes, query editors, file paths, ports). Stripped
carpet-bombed font-family: mono !important across inject.css and
per-service sheets; kept mono only where load-bearing (th, pre/code,
query editors, span IDs, badge/chip/tag, bucket names, query IDs).

Palette: warm cream (#F7F5F0) background, near-black warm foreground,
deep federal navy (#1C2B3A) primary/sidebar, indigo chart scale +
teal (support) + amber (opposition) semantic accents. PALETTE extended
to 8 colours (+ plum, gray, sepia) for categorical notebook charts.

Rename (no backwards compat):
- assets/nature.py → assets/fhirworx.py
- assets/css/loch.css → assets/css/fhirworx.css
- theme-loch.css → theme-fhirworx.css (Gitea)
- Altair theme registered as "fhirworx" via new alt.theme.register
  decorator API, eliminating the altair 5.5 deprecation warning
- Traefik inject-loch middleware → inject-fhirworx
- Gitea DEFAULT_THEME=fhirworx
- Delete empty theme-throwback.css stub

Rewrote infra/nginx/index.html and infra/gitea/custom/templates/home.tmpl
with editorial masthead (rule-line + Playfair headline + mono identifier
+ serif tagline + rule-line-thin), SERVICES eyebrow + Infrastructure h2,
tiles with .tile-eyebrow / .tile-title / .tile-desc / .tile-port
structure. Stripped letter-spacing: 2-4px tricks and text-transform:
uppercase from non-metadata elements across all per-service sheets.

Propagated import renames: src/conf/connect.py, src/aco/dag.py,
notebooks/acodb_explorer.py, tests/conf/test_connect.py,
tests/test_notebook_layout.py, docs/src/css/custom.css, README.md,
README.md.j2, compose.yml, infra/traefik/dynamic/services.yml.
2026-04-10 14:37:20 -04:00

400 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PFS Equation Explorer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Source+Serif+4:ital,wght@0,300;0,400;0,600;1,400&family=JetBrains+Mono:wght@400;500;600&display=swap">
<link rel="stylesheet" href="https://unpkg.com/katex@0.16/dist/katex.min.css">
<script src="https://unpkg.com/katex@0.16/dist/katex.min.js"></script>
<style>
:root {
/* HTI-5 palette */
--bg: #F7F5F0; --panel: #FAFAF7; --panel-hi: #EDEBE6;
--border: #D4D0C8; --text: #1A1A18; --muted: #6B6B68; --dim: #9B9B98;
/* Node-kind colours — Tableau-style but on light bg */
--c-mul: #C0392B; --c-dot: #2E8B6E; --c-add: #2E3D8F;
--c-vec: #A85800; --c-scalar: #7448C0; --c-ref: #1A7090;
--c-fn: #C8702A; --c-cond: #9B2E5A;
}
* { margin:0; padding:0; box-sizing:border-box; }
body {
background: var(--bg); color: var(--text);
font-family: "Source Serif 4", Georgia, serif;
overflow-x: hidden;
}
/* ── Header ────────────────────────── */
header {
padding: 24px 32px 0;
display: flex; align-items: baseline; gap: 16px;
}
header h1 {
font-size: 20px; font-weight: 700; color: var(--text);
font-family: "Playfair Display", Georgia, serif;
letter-spacing: -0.3px;
}
header .subtitle {
font-size: 13px; color: var(--muted);
}
/* ── Tabs ───────────────────────────── */
.tabs {
display: flex; gap: 2px; padding: 16px 32px 0;
border-bottom: 1px solid var(--border);
}
.tab {
padding: 8px 20px; font-size: 13px; font-weight: 500;
color: var(--muted); cursor: pointer; border-radius: 6px 6px 0 0;
border: 1px solid transparent; border-bottom: none;
transition: all 0.15s; user-select: none;
}
.tab:hover { color: var(--text); background: var(--panel); }
.tab.active {
color: var(--text); background: var(--panel);
border-color: var(--border);
}
/* ── Main layout ───────────────────── */
.content { padding: 0 32px 32px; }
.eq-section { display: none; }
.eq-section.active { display: block; }
/* ── Equation card ─────────────────── */
.eq-card {
background: var(--panel); border: 1px solid var(--border);
border-radius: 10px; padding: 28px 32px; margin: 20px 0;
text-align: center; overflow-x: auto;
}
.eq-card .katex { font-size: 1.3em; }
.eq-desc {
font-size: 13px; color: var(--muted); text-align: center;
margin-bottom: 16px;
}
/* ── Two-column: tree + detail ─────── */
.columns {
display: grid; grid-template-columns: 1fr 360px; gap: 20px;
align-items: start;
}
@media (max-width: 900px) {
.columns { grid-template-columns: 1fr; }
}
/* ── Tree ───────────────────────────── */
.tree {
display: flex; flex-direction: column; gap: 1px;
}
.node {
display: flex; align-items: center; gap: 8px;
padding: 5px 10px; border-radius: 5px; cursor: pointer;
transition: background 0.12s;
}
.node:hover { background: var(--panel); }
.node.selected { background: var(--panel-hi); outline: 1px solid var(--c-add); }
.node-indent { flex-shrink: 0; }
.node-icon {
width: 22px; height: 22px; border-radius: 4px;
display: inline-flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 700; flex-shrink: 0;
}
.ki-Mul { background: color-mix(in srgb, var(--c-mul) 20%, transparent); color: var(--c-mul); }
.ki-Dot { background: color-mix(in srgb, var(--c-dot) 20%, transparent); color: var(--c-dot); }
.ki-Add { background: color-mix(in srgb, var(--c-add) 20%, transparent); color: var(--c-add); }
.ki-Vector { background: color-mix(in srgb, var(--c-vec) 20%, transparent); color: var(--c-vec); }
.ki-Scalar { background: color-mix(in srgb, var(--c-scalar) 20%, transparent); color: var(--c-scalar); }
.ki-Ref { background: color-mix(in srgb, var(--c-ref) 20%, transparent); color: var(--c-ref); }
.ki-Fn { background: color-mix(in srgb, var(--c-fn) 20%, transparent); color: var(--c-fn); }
.ki-Cond { background: color-mix(in srgb, var(--c-cond) 20%, transparent); color: var(--c-cond); }
.node-label { font-size: 13px; }
.node-meta { font-size: 11px; color: var(--dim); margin-left: auto; white-space: nowrap; }
/* ── Detail panel ──────────────────── */
.detail {
background: var(--panel); border: 1px solid var(--border);
border-radius: 10px; padding: 20px; position: sticky; top: 20px;
min-height: 200px;
}
.detail-empty {
color: var(--dim); font-size: 13px; padding: 40px 0; text-align: center;
}
.detail h3 {
font-size: 14px; color: var(--text); margin-bottom: 12px;
font-weight: 600; letter-spacing: -0.2px;
}
.detail-latex {
background: var(--panel-hi); border-radius: 6px; padding: 14px;
margin-bottom: 14px; text-align: center; overflow-x: auto;
}
.detail-doc {
font-size: 13px; color: var(--muted); line-height: 1.6; margin-bottom: 12px;
}
.detail-meta { font-size: 12px; color: var(--muted); }
.detail-meta div { margin-bottom: 5px; }
.detail-meta b { color: var(--text); }
.cite-tag {
display: inline-block; background: var(--panel-hi); border-radius: 3px;
padding: 1px 7px; margin: 1px 2px; font-size: 11px; color: var(--c-ref);
}
/* ── Stats bar ─────────────────────── */
.stats {
display: flex; gap: 20px; padding: 8px 0; margin-top: 8px;
font-size: 12px; color: var(--muted); flex-wrap: wrap;
}
.stats span { display: inline-flex; align-items: center; gap: 4px; }
.stats .dot {
width: 8px; height: 8px; border-radius: 50%; display: inline-block;
}
/* ── Evaluator ─────────────────────── */
.evaluator {
background: var(--panel); border: 1px solid var(--border);
border-radius: 10px; padding: 20px; margin-top: 20px;
}
.evaluator h3 {
font-size: 14px; color: var(--text); margin-bottom: 14px; font-weight: 600;
}
.eval-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 8px; margin-bottom: 14px;
}
.eval-field {
display: flex; align-items: center; gap: 6px;
}
.eval-field label {
font-size: 12px; color: var(--muted); min-width: 72px; text-align: right;
}
.eval-field input {
width: 90px; padding: 4px 8px; border-radius: 4px;
border: 1px solid var(--border); background: var(--panel-hi);
color: var(--text); font-size: 13px; font-family: inherit;
}
.eval-field input:focus {
outline: none; border-color: var(--c-add);
}
.eval-result {
font-size: 22px; font-weight: 600; color: var(--text);
display: flex; align-items: baseline; gap: 8px;
}
.eval-result small {
font-size: 12px; font-weight: 400; color: var(--muted);
}
/* ── Legend ─────────────────────────── */
.legend {
display: flex; gap: 12px; flex-wrap: wrap; padding: 12px 0 4px;
font-size: 12px; color: var(--muted);
}
.legend span {
display: inline-flex; align-items: center; gap: 4px;
}
.legend .swatch {
width: 10px; height: 10px; border-radius: 3px; display: inline-block;
}
</style>
</head>
<body>
<header>
<h1>PFS Equation Explorer</h1>
<span class="subtitle">Physician Fee Schedule &mdash; equation tree introspection</span>
</header>
<div class="tabs" id="tabs"></div>
<div class="content" id="content"></div>
<script>
// ── Data ─────────────────────────────────────────────────
const EQS = JSON.parse(atob("W3sia2V5IjogIlBGU19QQVlNRU5UIiwgInRpdGxlIjogIlBGUyBQYXltZW50IiwgImRlc2MiOiAiUEZTIHBheW1lbnQgPSBnZW8tYWRqdXN0ZWQgUlZVIHRvdGFsIFx1MDBkNyBjb252ZXJzaW9uIGZhY3RvciIsICJsYXRleCI6ICJcXGJlZ2lue2JtYXRyaXh9IFxcdGV4dHtXb3JrXFxfUlZVfSBcXFxcIFxcYmVnaW57Y2FzZXN9IFxcdGV4dHtGXFxfUEVcXF9SVlV9ICYgXFx0ZXh0e2lmIGZhY2lsaXR5fSBcXFxcIFxcdGV4dHtORlxcX1BFXFxfUlZVfSAmIFxcdGV4dHtvdGhlcndpc2V9IFxcZW5ke2Nhc2VzfSBcXFxcIFxcdGV4dHtNUFxcX1JWVX0gXFxlbmR7Ym1hdHJpeH0gXFxjZG90IFxcYmVnaW57Ym1hdHJpeH0gXFx0ZXh0e1dvcmtcXF9HUENJfSBcXFxcIFxcdGV4dHtQRVxcX0dQQ0l9IFxcXFwgXFx0ZXh0e01QXFxfR1BDSX0gXFxlbmR7Ym1hdHJpeH0gXFx0aW1lcyBcXHRleHR7Q0Z9IiwgInRleHQiOiAiUGF5bWVudCA9IEdlby1BZGp1c3RlZCBSVlVzIFx1MDBkNyBDRlxuICBHZW8tQWRqdXN0ZWQgUlZVcyA9IFJWVXMgXHUwMGI3IEdQQ0lzXG4gICAgUlZVcyA9IFtXb3JrX1JWVSwgUEVfUlZVLCBNUF9SVlVdXG4gICAgR1BDSXMgPSBbV29ya19HUENJLCBQRV9HUENJLCBNUF9HUENJXVxuICBDRiAgKCQvUlZVKSIsICJub2RlcyI6IFt7ImlkIjogMCwgInBhcmVudCI6IG51bGwsICJsYWJlbCI6ICJQYXltZW50IiwgImtpbmQiOiAiTXVsIiwgImRvYyI6ICJQRlMgcGF5bWVudCA9IGdlby1hZGp1c3RlZCBSVlUgdG90YWwgeCBjb252ZXJzaW9uIGZhY3RvciIsICJjaXRlIjogWyJtb2R1bGU6cGZzIiwgInJ1bGU6Y21zLTE4MDctZiIsICJzb3VyY2U6ZmVkZXJhbC1yZWdpc3RlciJdLCAibGF0ZXgiOiAiXFxiZWdpbntibWF0cml4fSBcXHRleHR7V29ya1xcX1JWVX0gXFxcXCBcXGJlZ2lue2Nhc2VzfSBcXHRleHR7RlxcX1BFXFxfUlZVfSAmIFxcdGV4dHtpZiBmYWNpbGl0eX0gXFxcXCBcXHRleHR7TkZcXF9QRVxcX1JWVX0gJiBcXHRleHR7b3RoZXJ3aXNlfSBcXGVuZHtjYXNlc30gXFxcXCBcXHRleHR7TVBcXF9SVlV9IFxcZW5ke2JtYXRyaXh9IFxcY2RvdCBcXGJlZ2lue2JtYXRyaXh9IFxcdGV4dHtXb3JrXFxfR1BDSX0gXFxcXCBcXHRleHR7UEVcXF9HUENJfSBcXFxcIFxcdGV4dHtNUFxcX0dQQ0l9IFxcZW5ke2JtYXRyaXh9IFxcdGltZXMgXFx0ZXh0e0NGfSJ9LCB7ImlkIjogMSwgInBhcmVudCI6IDAsICJsYWJlbCI6ICJHZW8tQWRqdXN0ZWQgUlZVcyIsICJraW5kIjogIkRvdCIsICJkb2MiOiAiU3VtIG9mIGVhY2ggUlZVIGNvbXBvbmVudCBtdWx0aXBsaWVkIGJ5IGl0cyBnZW9ncmFwaGljIGluZGV4IiwgImNpdGUiOiBbXSwgImxhdGV4IjogIlxcYmVnaW57Ym1hdHJpeH0gXFx0ZXh0e1dvcmtcXF9SVlV9IFxcXFwgXFxiZWdpbntjYXNlc30gXFx0ZXh0e0ZcXF9QRVxcX1JWVX0gJiBcXHRleHR7aWYgZmFjaWxpdHl9IFxcXFwgXFx0ZXh0e05GXFxfUEVcXF9SVlV9ICYgXFx0ZXh0e290aGVyd2lzZX0gXFxlbmR7Y2FzZXN9IFxcXFwgXFx0ZXh0e01QXFxfUlZVfSBcXGVuZHtibWF0cml4fSBcXGNkb3QgXFxiZWdpbntibWF0cml4fSBcXHRleHR7V29ya1xcX0dQQ0l9IFxcXFwgXFx0ZXh0e1BFXFxfR1BDSX0gXFxcXCBcXHRleHR7TVBcXF9HUENJfSBcXGVuZHtibWF0cml4fSJ9LCB7ImlkIjogMiwgInBhcmVudCI6IDEsICJsYWJlbCI6ICJSVlVzIiwgImtpbmQiOiAiVmVjdG9yIiwgImRvYyI6ICIiLCAiY2l0ZSI6IFtdLCAibGF0ZXgiOiAiXFxiZWdpbntibWF0cml4fSBcXHRleHR7V29ya1xcX1JWVX0gXFxcXCBcXGJlZ2lue2Nhc2VzfSBcXHRleHR7RlxcX1BFXFxfUlZVfSAmIFxcdGV4dHtpZiBmYWNpbGl0eX0gXFxcXCBcXHRleHR7TkZcXF9QRVxcX1JWVX0gJiBcXHRleHR7b3RoZXJ3aXNlfSBcXGVuZHtjYXNlc30gXFxcXCBcXHRleHR7TVBcXF9SVlV9IFxcZW5ke2JtYXRyaXh9In0sIHsiaWQiOiAzLCAicGFyZW50IjogMiwgImxhYmVsIjogIldvcmtfUlZVIiwgImtpbmQiOiAiUmVmIiwgImRvYyI6ICJQaHlzaWNpYW4gd29yayByZWxhdGl2ZSB2YWx1ZSB1bml0cyIsICJjaXRlIjogWyJmaWxlOnJ2dSIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMucnZ1Il0sICJsYXRleCI6ICJcXHRleHR7V29ya1xcX1JWVX0iLCAidGFibGUiOiAicGZzLnJ2dSIsICJjb2x1bW4iOiAid29ya19ydnUifSwgeyJpZCI6IDQsICJwYXJlbnQiOiAyLCAibGFiZWwiOiAiUEVfUlZVIiwgImtpbmQiOiAiQ29uZCIsICJkb2MiOiAiUHJhY3RpY2UgZXhwZW5zZSBSVlVzIFx1MjAxNCBmYWNpbGl0eSBvciBub24tZmFjaWxpdHkgYnkgcGxhY2Ugb2Ygc2VydmljZSIsICJjaXRlIjogW10sICJsYXRleCI6ICJcXGJlZ2lue2Nhc2VzfSBcXHRleHR7RlxcX1BFXFxfUlZVfSAmIFxcdGV4dHtpZiBmYWNpbGl0eX0gXFxcXCBcXHRleHR7TkZcXF9QRVxcX1JWVX0gJiBcXHRleHR7b3RoZXJ3aXNlfSBcXGVuZHtjYXNlc30ifSwgeyJpZCI6IDUsICJwYXJlbnQiOiA0LCAibGFiZWwiOiAiRl9QRV9SVlUiLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIkZhY2lsaXR5IHByYWN0aWNlIGV4cGVuc2UgUlZVcyIsICJjaXRlIjogWyJmaWxlOnJ2dSIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMucnZ1Il0sICJsYXRleCI6ICJcXHRleHR7RlxcX1BFXFxfUlZVfSIsICJ0YWJsZSI6ICJwZnMucnZ1IiwgImNvbHVtbiI6ICJmYWNfcGVfcnZ1In0sIHsiaWQiOiA2LCAicGFyZW50IjogNCwgImxhYmVsIjogIk5GX1BFX1JWVSIsICJraW5kIjogIlJlZiIsICJkb2MiOiAiTm9uLWZhY2lsaXR5IHByYWN0aWNlIGV4cGVuc2UgUlZVcyIsICJjaXRlIjogWyJmaWxlOnJ2dSIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMucnZ1Il0sICJsYXRleCI6ICJcXHRleHR7TkZcXF9QRVxcX1JWVX0iLCAidGFibGUiOiAicGZzLnJ2dSIsICJjb2x1bW4iOiAibm9uX2ZhY19wZV9ydnUifSwgeyJpZCI6IDcsICJwYXJlbnQiOiAyLCAibGFiZWwiOiAiTVBfUlZVIiwgImtpbmQiOiAiUmVmIiwgImRvYyI6ICJNYWxwcmFjdGljZSBSVlVzIiwgImNpdGUiOiBbImZpbGU6cnZ1IiwgIm1vZHVsZTpwZnMiLCAic291cmNlOmNtcy13ZWJzaXRlIiwgInRhYmxlOnBmcy5ydnUiXSwgImxhdGV4IjogIlxcdGV4dHtNUFxcX1JWVX0iLCAidGFibGUiOiAicGZzLnJ2dSIsICJjb2x1bW4iOiAibXBfcnZ1In0sIHsiaWQiOiA4LCAicGFyZW50IjogMSwgImxhYmVsIjogIkdQQ0lzIiwgImtpbmQiOiAiVmVjdG9yIiwgImRvYyI6ICIiLCAiY2l0ZSI6IFtdLCAibGF0ZXgiOiAiXFxiZWdpbntibWF0cml4fSBcXHRleHR7V29ya1xcX0dQQ0l9IFxcXFwgXFx0ZXh0e1BFXFxfR1BDSX0gXFxcXCBcXHRleHR7TVBcXF9HUENJfSBcXGVuZHtibWF0cml4fSJ9LCB7ImlkIjogOSwgInBhcmVudCI6IDgsICJsYWJlbCI6ICJXb3JrX0dQQ0kiLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIldvcmsgR1BDSSBcdTIwMTQgYWRqdXN0cyBmb3IgbG9jYWwgcGh5c2ljaWFuIGNvbXBlbnNhdGlvbiIsICJjaXRlIjogWyJmaWxlOmdwY2kiLCAibW9kdWxlOnBmcyIsICJzb3VyY2U6ZmVkZXJhbC1yZWdpc3RlciIsICJ0YWJsZTpwZnMuZ3BjaSJdLCAibGF0ZXgiOiAiXFx0ZXh0e1dvcmtcXF9HUENJfSIsICJ0YWJsZSI6ICJwZnMuZ3BjaSIsICJjb2x1bW4iOiAid29ya19ncGNpIn0sIHsiaWQiOiAxMCwgInBhcmVudCI6IDgsICJsYWJlbCI6ICJQRV9HUENJIiwgImtpbmQiOiAiUmVmIiwgImRvYyI6ICJQRSBHUENJIFx1MjAxNCBhZGp1c3RzIGZvciBsb2NhbCBvZmZpY2UgcmVudCwgc3RhZmYsIGVxdWlwbWVudCIsICJjaXRlIjogWyJmaWxlOmdwY2kiLCAibW9kdWxlOnBmcyIsICJzb3VyY2U6ZmVkZXJhbC1yZWdpc3RlciIsICJ0YWJsZTpwZnMuZ3BjaSJdLCAibGF0ZXgiOiAiXFx0ZXh0e1BFXFxfR1BDSX0iLCAidGFibGUiOiAicGZzLmdwY2kiLCAiY29sdW1uIjogInBlX2dwY2kifSwgeyJpZCI6IDExLCAicGFyZW50IjogOCwgImxhYmVsIjogIk1QX0dQQ0kiLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIk1QIEdQQ0kgXHUyMDE0IGFkanVzdHMgZm9yIGxvY2FsIG1hbHByYWN0aWNlIHByZW1pdW1zIiwgImNpdGUiOiBbImZpbGU6Z3BjaSIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpmZWRlcmFsLXJlZ2lzdGVyIiwgInRhYmxlOnBmcy5ncGNpIl0sICJsYXRleCI6ICJcXHRleHR7TVBcXF9HUENJfSIsICJ0YWJsZSI6ICJwZnMuZ3BjaSIsICJjb2x1bW4iOiAibXBfZ3BjaSJ9LCB7ImlkIjogMTIsICJwYXJlbnQiOiAwLCAibGFiZWwiOiAiQ0YiLCAia2luZCI6ICJTY2FsYXIiLCAiZG9jIjogIkNvbnZlcnNpb24gZmFjdG9yIFx1MjAxNCBzZXQgYW5udWFsbHkgaW4gdGhlIEZpbmFsIFJ1bGUiLCAiY2l0ZSI6IFsibW9kdWxlOnBmcyIsICJzb3VyY2U6ZmVkZXJhbC1yZWdpc3RlciJdLCAibGF0ZXgiOiAiXFx0ZXh0e0NGfSIsICJ1bml0IjogIiQvUlZVIn1dLCAic3RhdHMiOiB7InNvdXJjZXMiOiBbInBmcy5ncGNpIiwgInBmcy5ydnUiXSwgInJlZnMiOiBbIldvcmtfUlZVIiwgIkZfUEVfUlZVIiwgIk5GX1BFX1JWVSIsICJNUF9SVlUiLCAiV29ya19HUENJIiwgIlBFX0dQQ0kiLCAiTVBfR1BDSSJdLCAic2NhbGFycyI6IFsiQ0YiXSwgImRlcHRoIjogNCwgImNpdGF0aW9ucyI6IDMzLCAibm9kZV9jb3VudCI6IDEzfX0sIHsia2V5IjogIkRJUkVDVF9QRSIsICJ0aXRsZSI6ICJEaXJlY3QgUEUiLCAiZGVzYyI6ICJEaXJlY3QgcHJhY3RpY2UgZXhwZW5zZSA9IGxhYm9yICsgc3VwcGx5ICsgZXF1aXBtZW50IiwgImxhdGV4IjogIlxcdGV4dHtMYWJvclxcX0Nvc3R9KFxcY2RvdHMpICsgXFx0ZXh0e1N1cHBseVxcX0Nvc3R9KFxcY2RvdHMpICsgXFx0ZXh0e0VxdWlwbWVudFxcX0Nvc3R9KFxcY2RvdHMpIiwgInRleHQiOiAiRGlyZWN0X1BFID0gTGFib3JfQ29zdCArIFN1cHBseV9Db3N0ICsgRXF1aXBtZW50X0Nvc3RcbiAgTGFib3JfQ29zdChORl9NaW51dGVzLCBGX01pbnV0ZXMsIFJhdGUvTWluKVxuICBTdXBwbHlfQ29zdChORl9RdHksIEZfUXR5LCBVbml0X1ByaWNlKVxuICBFcXVpcG1lbnRfQ29zdChQcmljZSwgTGlmZSwgTWlucy9ZciwgTWFpbnQpIiwgIm5vZGVzIjogW3siaWQiOiAwLCAicGFyZW50IjogbnVsbCwgImxhYmVsIjogIkRpcmVjdF9QRSIsICJraW5kIjogIkFkZCIsICJkb2MiOiAiRGlyZWN0IHByYWN0aWNlIGV4cGVuc2UgPSBsYWJvciArIHN1cHBseSArIGVxdWlwbWVudCIsICJjaXRlIjogWyJtb2R1bGU6cGZzIiwgInJ1bGU6Y21zLTE4MDctZiIsICJzb3VyY2U6ZmVkZXJhbC1yZWdpc3RlciJdLCAibGF0ZXgiOiAiXFx0ZXh0e0xhYm9yXFxfQ29zdH0oXFxjZG90cykgKyBcXHRleHR7U3VwcGx5XFxfQ29zdH0oXFxjZG90cykgKyBcXHRleHR7RXF1aXBtZW50XFxfQ29zdH0oXFxjZG90cykifSwgeyJpZCI6IDEsICJwYXJlbnQiOiAwLCAibGFiZWwiOiAiTGFib3JfQ29zdCIsICJraW5kIjogIkZuIiwgImRvYyI6ICJTdW0gb2YgKG1pbnV0ZXMgeCByYXRlX3Blcl9taW51dGUpIG92ZXIgYWxsIGNsaW5pY2FsIGxhYm9yIGFjdGl2aXRpZXMiLCAiY2l0ZSI6IFtdLCAibGF0ZXgiOiAiXFx0ZXh0e0xhYm9yXFxfQ29zdH0oXFxjZG90cykifSwgeyJpZCI6IDIsICJwYXJlbnQiOiAxLCAibGFiZWwiOiAiTkZfTWludXRlcyIsICJraW5kIjogIlJlZiIsICJkb2MiOiAiIiwgImNpdGUiOiBbImZpbGU6cGUtbGFib3IiLCAibW9kdWxlOnBmcyIsICJzb3VyY2U6Y21zLXdlYnNpdGUiLCAidGFibGU6cGZzLmNsaW5pY2FsX2xhYm9yIl0sICJsYXRleCI6ICJcXHRleHR7TkZcXF9NaW51dGVzfSIsICJ0YWJsZSI6ICJwZnMuY2xpbmljYWxfbGFib3IiLCAiY29sdW1uIjogIm5mX21pbnV0ZXMifSwgeyJpZCI6IDMsICJwYXJlbnQiOiAxLCAibGFiZWwiOiAiRl9NaW51dGVzIiwgImtpbmQiOiAiUmVmIiwgImRvYyI6ICIiLCAiY2l0ZSI6IFsiZmlsZTpwZS1sYWJvciIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMuY2xpbmljYWxfbGFib3IiXSwgImxhdGV4IjogIlxcdGV4dHtGXFxfTWludXRlc30iLCAidGFibGUiOiAicGZzLmNsaW5pY2FsX2xhYm9yIiwgImNvbHVtbiI6ICJmX21pbnV0ZXMifSwgeyJpZCI6IDQsICJwYXJlbnQiOiAxLCAibGFiZWwiOiAiUmF0ZS9NaW4iLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIiIsICJjaXRlIjogWyJmaWxlOnBlLWxhYm9yIiwgIm1vZHVsZTpwZnMiLCAic291cmNlOmNtcy13ZWJzaXRlIiwgInRhYmxlOnBmcy5jbGluaWNhbF9sYWJvciJdLCAibGF0ZXgiOiAiXFx0ZXh0e1JhdGUvTWlufSIsICJ0YWJsZSI6ICJwZnMuY2xpbmljYWxfbGFib3IiLCAiY29sdW1uIjogInJhdGVfcGVyX21pbnV0ZSJ9LCB7ImlkIjogNSwgInBhcmVudCI6IDAsICJsYWJlbCI6ICJTdXBwbHlfQ29zdCIsICJraW5kIjogIkZuIiwgImRvYyI6ICJTdW0gb2YgKHF1YW50aXR5IHggdW5pdF9wcmljZSkgb3ZlciBhbGwgc3VwcGx5IGl0ZW1zIiwgImNpdGUiOiBbXSwgImxhdGV4IjogIlxcdGV4dHtTdXBwbHlcXF9Db3N0fShcXGNkb3RzKSJ9LCB7ImlkIjogNiwgInBhcmVudCI6IDUsICJsYWJlbCI6ICJORl9RdHkiLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIiIsICJjaXRlIjogWyJmaWxlOnBlLXN1cHBseSIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMubWVkaWNhbF9zdXBwbHkiXSwgImxhdGV4IjogIlxcdGV4dHtORlxcX1F0eX0iLCAidGFibGUiOiAicGZzLm1lZGljYWxfc3VwcGx5IiwgImNvbHVtbiI6ICJuZl9xdWFudGl0eSJ9LCB7ImlkIjogNywgInBhcmVudCI6IDUsICJsYWJlbCI6ICJGX1F0eSIsICJraW5kIjogIlJlZiIsICJkb2MiOiAiIiwgImNpdGUiOiBbImZpbGU6cGUtc3VwcGx5IiwgIm1vZHVsZTpwZnMiLCAic291cmNlOmNtcy13ZWJzaXRlIiwgInRhYmxlOnBmcy5tZWRpY2FsX3N1cHBseSJdLCAibGF0ZXgiOiAiXFx0ZXh0e0ZcXF9RdHl9IiwgInRhYmxlIjogInBmcy5tZWRpY2FsX3N1cHBseSIsICJjb2x1bW4iOiAiZl9xdWFudGl0eSJ9LCB7ImlkIjogOCwgInBhcmVudCI6IDUsICJsYWJlbCI6ICJVbml0X1ByaWNlIiwgImtpbmQiOiAiUmVmIiwgImRvYyI6ICIiLCAiY2l0ZSI6IFsiZmlsZTpwZS1zdXBwbHkiLCAibW9kdWxlOnBmcyIsICJzb3VyY2U6Y21zLXdlYnNpdGUiLCAidGFibGU6cGZzLm1lZGljYWxfc3VwcGx5Il0sICJsYXRleCI6ICJcXHRleHR7VW5pdFxcX1ByaWNlfSIsICJ0YWJsZSI6ICJwZnMubWVkaWNhbF9zdXBwbHkiLCAiY29sdW1uIjogInVuaXRfcHJpY2UifSwgeyJpZCI6IDksICJwYXJlbnQiOiAwLCAibGFiZWwiOiAiRXF1aXBtZW50X0Nvc3QiLCAia2luZCI6ICJGbiIsICJkb2MiOiAiU3VtIG9mIGFtb3J0aXplZCBlcXVpcG1lbnQgY29zdDogKHByaWNlIC8gbGlmZSAvIG1pbnNfeXIpIHggKDEgKyBtYWludCkgeCB0aW1lIiwgImNpdGUiOiBbXSwgImxhdGV4IjogIlxcdGV4dHtFcXVpcG1lbnRcXF9Db3N0fShcXGNkb3RzKSJ9LCB7ImlkIjogMTAsICJwYXJlbnQiOiA5LCAibGFiZWwiOiAiUHJpY2UiLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIiIsICJjaXRlIjogWyJmaWxlOnBlLWVxdWlwbWVudCIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMubWVkaWNhbF9lcXVpcG1lbnQiXSwgImxhdGV4IjogIlxcdGV4dHtQcmljZX0iLCAidGFibGUiOiAicGZzLm1lZGljYWxfZXF1aXBtZW50IiwgImNvbHVtbiI6ICJ1bml0X3ByaWNlIn0sIHsiaWQiOiAxMSwgInBhcmVudCI6IDksICJsYWJlbCI6ICJMaWZlIiwgImtpbmQiOiAiUmVmIiwgImRvYyI6ICIiLCAiY2l0ZSI6IFsiZmlsZTpwZS1lcXVpcG1lbnQiLCAibW9kdWxlOnBmcyIsICJzb3VyY2U6Y21zLXdlYnNpdGUiLCAidGFibGU6cGZzLm1lZGljYWxfZXF1aXBtZW50Il0sICJsYXRleCI6ICJcXHRleHR7TGlmZX0iLCAidGFibGUiOiAicGZzLm1lZGljYWxfZXF1aXBtZW50IiwgImNvbHVtbiI6ICJ1c2VmdWxfbGlmZSJ9LCB7ImlkIjogMTIsICJwYXJlbnQiOiA5LCAibGFiZWwiOiAiTWlucy9ZciIsICJraW5kIjogIlJlZiIsICJkb2MiOiAiIiwgImNpdGUiOiBbImZpbGU6cGUtZXF1aXBtZW50IiwgIm1vZHVsZTpwZnMiLCAic291cmNlOmNtcy13ZWJzaXRlIiwgInRhYmxlOnBmcy5tZWRpY2FsX2VxdWlwbWVudCJdLCAibGF0ZXgiOiAiXFx0ZXh0e01pbnMvWXJ9IiwgInRhYmxlIjogInBmcy5tZWRpY2FsX2VxdWlwbWVudCIsICJjb2x1bW4iOiAibWludXRlc19wZXJfeWVhciJ9LCB7ImlkIjogMTMsICJwYXJlbnQiOiA5LCAibGFiZWwiOiAiTWFpbnQiLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIiIsICJjaXRlIjogWyJmaWxlOnBlLWVxdWlwbWVudCIsICJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMubWVkaWNhbF9lcXVpcG1lbnQiXSwgImxhdGV4IjogIlxcdGV4dHtNYWludH0iLCAidGFibGUiOiAicGZzLm1lZGljYWxfZXF1aXBtZW50IiwgImNvbHVtbiI6ICJtYWludGVuYW5jZV9mYWN0b3IifV0sICJzdGF0cyI6IHsic291cmNlcyI6IFsicGZzLmNsaW5pY2FsX2xhYm9yIiwgInBmcy5tZWRpY2FsX2VxdWlwbWVudCIsICJwZnMubWVkaWNhbF9zdXBwbHkiXSwgInJlZnMiOiBbIk5GX01pbnV0ZXMiLCAiRl9NaW51dGVzIiwgIlJhdGUvTWluIiwgIk5GX1F0eSIsICJGX1F0eSIsICJVbml0X1ByaWNlIiwgIlByaWNlIiwgIkxpZmUiLCAiTWlucy9ZciIsICJNYWludCJdLCAic2NhbGFycyI6IFtdLCAiZGVwdGgiOiAyLCAiY2l0YXRpb25zIjogNDMsICJub2RlX2NvdW50IjogMTR9fSwgeyJrZXkiOiAiTVBQUiIsICJ0aXRsZSI6ICJNUFBSIiwgImRlc2MiOiAiTXVsdGlwbGUgUHJvY2VkdXJlIFBheW1lbnQgUmVkdWN0aW9uIiwgImxhdGV4IjogIlxcYmVnaW57Y2FzZXN9IFxcdGV4dHtQRVxcX1JWVX0gJiBcXHRleHR7aWYgcmFuayA9PSAxfSBcXFxcIFxcdGV4dHtQRVxcX1JWVX0gXFx0aW1lcyBcXHVuZGVyYnJhY2V7MC41fV97XFx0ZXh0ezEtcmVkdWN0aW9ufX0gJiBcXHRleHR7b3RoZXJ3aXNlfSBcXGVuZHtjYXNlc30iLCAidGV4dCI6ICJBZGp1c3RlZF9QRSAgaWYgcmFuayA9PSAxOlxuICBQRV9SVlUgIFtwZnMucnZ1Lm5vbl9mYWNfcGVfcnZ1XVxuICBlbHNlOlxuICBSZWR1Y2VkX1BFID0gUEVfUlZVIFx1MDBkNyAxLXJlZHVjdGlvblxuICAgIFBFX1JWVSAgW3Bmcy5ydnUubm9uX2ZhY19wZV9ydnVdXG4gICAgMS1yZWR1Y3Rpb24gPSAwLjUiLCAibm9kZXMiOiBbeyJpZCI6IDAsICJwYXJlbnQiOiBudWxsLCAibGFiZWwiOiAiQWRqdXN0ZWRfUEUiLCAia2luZCI6ICJDb25kIiwgImRvYyI6ICJNdWx0aXBsZSBQcm9jZWR1cmUgUGF5bWVudCBSZWR1Y3Rpb24gXHUyMDE0IGZ1bGwgUEUgZm9yIGhpZ2hlc3QtdmFsdWVkLCByZWR1Y2VkIGZvciByZXN0IiwgImNpdGUiOiBbIm1vZHVsZTpwZnMiLCAicnVsZTpjbXMtMTgwNy1mIiwgInNvdXJjZTpmZWRlcmFsLXJlZ2lzdGVyIl0sICJsYXRleCI6ICJcXGJlZ2lue2Nhc2VzfSBcXHRleHR7UEVcXF9SVlV9ICYgXFx0ZXh0e2lmIHJhbmsgPT0gMX0gXFxcXCBcXHRleHR7UEVcXF9SVlV9IFxcdGltZXMgXFx1bmRlcmJyYWNlezAuNX1fe1xcdGV4dHsxLXJlZHVjdGlvbn19ICYgXFx0ZXh0e290aGVyd2lzZX0gXFxlbmR7Y2FzZXN9In0sIHsiaWQiOiAxLCAicGFyZW50IjogMCwgImxhYmVsIjogIlBFX1JWVSIsICJraW5kIjogIlJlZiIsICJkb2MiOiAiIiwgImNpdGUiOiBbXSwgImxhdGV4IjogIlxcdGV4dHtQRVxcX1JWVX0iLCAidGFibGUiOiAicGZzLnJ2dSIsICJjb2x1bW4iOiAibm9uX2ZhY19wZV9ydnUifSwgeyJpZCI6IDIsICJwYXJlbnQiOiAwLCAibGFiZWwiOiAiUmVkdWNlZF9QRSIsICJraW5kIjogIk11bCIsICJkb2MiOiAiIiwgImNpdGUiOiBbXSwgImxhdGV4IjogIlxcdGV4dHtQRVxcX1JWVX0gXFx0aW1lcyBcXHVuZGVyYnJhY2V7MC41fV97XFx0ZXh0ezEtcmVkdWN0aW9ufX0ifSwgeyJpZCI6IDMsICJwYXJlbnQiOiAyLCAibGFiZWwiOiAiUEVfUlZVIiwgImtpbmQiOiAiUmVmIiwgImRvYyI6ICIiLCAiY2l0ZSI6IFtdLCAibGF0ZXgiOiAiXFx0ZXh0e1BFXFxfUlZVfSIsICJ0YWJsZSI6ICJwZnMucnZ1IiwgImNvbHVtbiI6ICJub25fZmFjX3BlX3J2dSJ9LCB7ImlkIjogNCwgInBhcmVudCI6IDIsICJsYWJlbCI6ICIxLXJlZHVjdGlvbiIsICJraW5kIjogIlNjYWxhciIsICJkb2MiOiAiNTAlIFBFIHJlZHVjdGlvbiBmb3IgcmFuayA+IDEiLCAiY2l0ZSI6IFtdLCAibGF0ZXgiOiAiXFx1bmRlcmJyYWNlezAuNX1fe1xcdGV4dHsxLXJlZHVjdGlvbn19IiwgInZhbHVlIjogMC41fV0sICJzdGF0cyI6IHsic291cmNlcyI6IFsicGZzLnJ2dSJdLCAicmVmcyI6IFsiUEVfUlZVIiwgIlBFX1JWVSJdLCAic2NhbGFycyI6IFsiMS1yZWR1Y3Rpb24iXSwgImRlcHRoIjogMiwgImNpdGF0aW9ucyI6IDMsICJub2RlX2NvdW50IjogNX19LCB7ImtleSI6ICJJTlRFTlNJVFkiLCAidGl0bGUiOiAiSW50ZW5zaXR5IiwgImRlc2MiOiAiVGltZS1pbnRlbnNpdHkgcmF0aW8gPSBXb3JrX1JWVSAvIFRvdGFsX1RpbWUiLCAibGF0ZXgiOiAiXFx0ZXh0e1dvcmtcXF9SVlV9IFxcdGltZXMgXFx0ZXh0ezEvVG90YWxcXF9UaW1lfSIsICJ0ZXh0IjogIkludGVuc2l0eSA9IFdvcmtfUlZVIFx1MDBkNyAxL1RvdGFsX1RpbWVcbiAgV29ya19SVlUgIFtwZnMucGh5c2ljaWFuX3dvcmtfdGltZS53b3JrX3J2dV1cbiAgMS9Ub3RhbF9UaW1lIiwgIm5vZGVzIjogW3siaWQiOiAwLCAicGFyZW50IjogbnVsbCwgImxhYmVsIjogIkludGVuc2l0eSIsICJraW5kIjogIk11bCIsICJkb2MiOiAiVGltZS1pbnRlbnNpdHkgcmF0aW8gPSBXb3JrX1JWVSAvIFRvdGFsX1RpbWUiLCAiY2l0ZSI6IFsibW9kdWxlOnBmcyIsICJydWxlOmNtcy0xODA3LWYiLCAic291cmNlOmZlZGVyYWwtcmVnaXN0ZXIiXSwgImxhdGV4IjogIlxcdGV4dHtXb3JrXFxfUlZVfSBcXHRpbWVzIFxcdGV4dHsxL1RvdGFsXFxfVGltZX0ifSwgeyJpZCI6IDEsICJwYXJlbnQiOiAwLCAibGFiZWwiOiAiV29ya19SVlUiLCAia2luZCI6ICJSZWYiLCAiZG9jIjogIiIsICJjaXRlIjogWyJtb2R1bGU6cGZzIiwgInNvdXJjZTpjbXMtd2Vic2l0ZSIsICJ0YWJsZTpwZnMucGh5c2ljaWFuX3dvcmtfdGltZSJdLCAibGF0ZXgiOiAiXFx0ZXh0e1dvcmtcXF9SVlV9IiwgInRhYmxlIjogInBmcy5waHlzaWNpYW5fd29ya190aW1lIiwgImNvbHVtbiI6ICJ3b3JrX3J2dSJ9LCB7ImlkIjogMiwgInBhcmVudCI6IDAsICJsYWJlbCI6ICIxL1RvdGFsX1RpbWUiLCAia2luZCI6ICJTY2FsYXIiLCAiZG9jIjogIlJlY2lwcm9jYWwgb2YgdG90YWwgcGh5c2ljaWFuIHRpbWUiLCAiY2l0ZSI6IFtdLCAibGF0ZXgiOiAiXFx0ZXh0ezEvVG90YWxcXF9UaW1lfSJ9XSwgInN0YXRzIjogeyJzb3VyY2VzIjogWyJwZnMucGh5c2ljaWFuX3dvcmtfdGltZSJdLCAicmVmcyI6IFsiV29ya19SVlUiXSwgInNjYWxhcnMiOiBbIjEvVG90YWxfVGltZSJdLCAiZGVwdGgiOiAxLCAiY2l0YXRpb25zIjogNiwgIm5vZGVfY291bnQiOiAzfX1d"));
const EVAL_DEFAULTS = JSON.parse(atob("eyJXb3JrX1JWVSI6IDEuNSwgIlBFX1JWVSI6IDEuMjEsICJNUF9SVlUiOiAwLjA5LCAiV29ya19HUENJIjogMS4wMjQsICJQRV9HUENJIjogMC45ODgsICJNUF9HUENJIjogMC41NzQsICJDRiI6IDMyLjM0NjV9"));
const KIND_ICON = {
Mul: "\u00d7", Dot: "\u00b7", Add: "+", Vector: "[ ]",
Scalar: "#", Ref: "\u2192", Fn: "f()", Cond: "?"
};
const KIND_COLORS = {
Mul: "var(--c-mul)", Dot: "var(--c-dot)", Add: "var(--c-add)",
Vector: "var(--c-vec)", Scalar: "var(--c-scalar)", Ref: "var(--c-ref)",
Fn: "var(--c-fn)", Cond: "var(--c-cond)"
};
// ── Build tabs + sections ────────────────────────────────
const tabsEl = document.getElementById("tabs");
const contentEl = document.getElementById("content");
EQS.forEach((eq, i) => {
// Tab
const tab = document.createElement("div");
tab.className = "tab" + (i === 0 ? " active" : "");
tab.textContent = eq.title;
tab.dataset.idx = i;
tab.onclick = () => activate(i);
tabsEl.appendChild(tab);
// Section
const sec = document.createElement("div");
sec.className = "eq-section" + (i === 0 ? " active" : "");
sec.id = "sec-" + i;
sec.innerHTML = buildSection(eq, i);
contentEl.appendChild(sec);
});
function activate(idx) {
document.querySelectorAll(".tab").forEach(t => t.classList.toggle("active", +t.dataset.idx === idx));
document.querySelectorAll(".eq-section").forEach(s => s.classList.remove("active"));
document.getElementById("sec-" + idx).classList.add("active");
renderAllKatex();
}
// ── Section builder ──────────────────────────────────────
function buildSection(eq, idx) {
const s = eq.stats;
let html = '';
// Description
html += '<p class="eq-desc">' + esc(eq.desc) + '</p>';
// Main equation card
html += '<div class="eq-card"><div class="katex-render" data-latex="' + attr(eq.latex) + '"></div></div>';
// Legend
html += '<div class="legend">';
for (const [k, v] of Object.entries(KIND_COLORS)) {
html += '<span><span class="swatch" style="background:' + v + '"></span>' + k + '</span>';
}
html += '</div>';
// Stats
html += '<div class="stats">';
html += '<span><span class="dot" style="background:var(--c-ref)"></span>' + s.node_count + ' nodes</span>';
html += '<span><span class="dot" style="background:var(--c-dot)"></span>depth ' + s.depth + '</span>';
html += '<span><span class="dot" style="background:var(--c-scalar)"></span>' + s.sources.length + ' sources</span>';
html += '<span><span class="dot" style="background:var(--c-cond)"></span>' + s.citations + ' citations</span>';
html += '</div>';
// Columns: tree + detail
html += '<div class="columns">';
// Tree
html += '<div class="tree" id="tree-' + idx + '">';
eq.nodes.forEach(n => {
const d = getDepth(n, eq.nodes);
html += '<div class="node" data-nid="' + n.id + '" data-eq="' + idx + '" onclick="showDetail(' + idx + ',' + n.id + ')">';
html += '<span class="node-indent" style="width:' + (d * 20) + 'px"></span>';
html += '<span class="node-icon ki-' + n.kind + '">' + (KIND_ICON[n.kind] || "?") + '</span>';
html += '<span class="node-label">' + esc(n.label) + '</span>';
let meta = '';
if (n.table) meta = n.table + '.' + n.column;
else if (n.value !== undefined) meta = n.value + (n.unit ? ' ' + n.unit : '');
if (meta) html += '<span class="node-meta">' + esc(meta) + '</span>';
html += '</div>';
});
html += '</div>';
// Detail panel
html += '<div class="detail" id="detail-' + idx + '"><div class="detail-empty">Click a node to inspect</div></div>';
html += '</div>'; // .columns
// Evaluator (only for PFS_PAYMENT)
if (eq.key === "PFS_PAYMENT") {
html += '<div class="evaluator" id="evaluator">';
html += '<h3>Evaluate</h3>';
html += '<div class="eval-grid">';
for (const [k, v] of Object.entries(EVAL_DEFAULTS)) {
html += '<div class="eval-field">';
html += '<label>' + esc(k) + '</label>';
html += '<input type="number" step="any" value="' + v + '" data-var="' + esc(k) + '" oninput="recalc()">';
html += '</div>';
}
html += '</div>';
html += '<div class="eval-result" id="eval-result">$<span id="eval-value">--</span><small>Payment</small></div>';
html += '</div>';
}
return html;
}
// ── Detail panel ─────────────────────────────────────────
function showDetail(eqIdx, nodeId) {
const eq = EQS[eqIdx];
const n = eq.nodes.find(x => x.id === nodeId);
if (!n) return;
// Highlight
document.querySelectorAll("#tree-" + eqIdx + " .node").forEach(el => el.classList.remove("selected"));
document.querySelector('#tree-' + eqIdx + ' [data-nid="' + nodeId + '"]').classList.add("selected");
const panel = document.getElementById("detail-" + eqIdx);
let html = '';
html += '<h3>' + esc(n.label) + ' <span style="font-weight:400;color:var(--muted);font-size:12px">' + n.kind + '</span></h3>';
html += '<div class="detail-latex"><div class="katex-render" data-latex="' + attr(n.latex) + '"></div></div>';
if (n.doc) html += '<div class="detail-doc">' + esc(n.doc) + '</div>';
html += '<div class="detail-meta">';
if (n.table) html += '<div>Source: <b>' + esc(n.table) + '</b>.' + esc(n.column) + '</div>';
if (n.value !== undefined) html += '<div>Value: <b>' + n.value + '</b>' + (n.unit ? ' ' + n.unit : '') + '</div>';
if (n.cite && n.cite.length) {
html += '<div>Citations: ' + n.cite.map(c => '<span class="cite-tag">' + esc(c) + '</span>').join('') + '</div>';
}
html += '</div>';
panel.innerHTML = html;
renderAllKatex();
}
// ── Evaluator ────────────────────────────────────────────
function recalc() {
const vals = {};
document.querySelectorAll("#evaluator input").forEach(inp => {
vals[inp.dataset.var] = parseFloat(inp.value) || 0;
});
// PFS Payment = (Work_RVU*Work_GPCI + PE_RVU*PE_GPCI + MP_RVU*MP_GPCI) * CF
const dot = vals.Work_RVU * vals.Work_GPCI
+ vals.PE_RVU * vals.PE_GPCI
+ vals.MP_RVU * vals.MP_GPCI;
const payment = dot * vals.CF;
document.getElementById("eval-value").textContent = payment.toFixed(2);
}
// ── KaTeX rendering ──────────────────────────────────────
function renderAllKatex() {
document.querySelectorAll(".katex-render").forEach(el => {
if (el.dataset.rendered) return;
const latex = el.dataset.latex;
if (!latex) return;
try {
katex.render(latex, el, { displayMode: true, throwOnError: false });
el.dataset.rendered = "1";
} catch(e) {
el.textContent = latex;
}
});
}
// ── Utilities ────────────────────────────────────────────
function getDepth(node, nodes) {
let d = 0, cur = node;
while (cur.parent !== null) { d++; cur = nodes[cur.parent]; }
return d;
}
function esc(s) {
const d = document.createElement("div");
d.textContent = s;
return d.innerHTML;
}
function attr(s) {
return s.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
// ── Init ─────────────────────────────────────────────────
renderAllKatex();
if (document.getElementById("evaluator")) recalc();
</script>
</body>
</html>