feat(dossier): lex on the fhirworx design system, from its sources (S0c, R85)
Some checks failed
CI / lint (push) Successful in 1m2s
CI / notebooks-smoke (push) Successful in 1m54s
Deploy / changes (push) Successful in 18s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / bcda (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
CI / test (push) Failing after 3m44s
Deploy / report (push) Successful in 18s
Some checks failed
CI / lint (push) Successful in 1m2s
CI / notebooks-smoke (push) Successful in 1m54s
Deploy / changes (push) Successful in 18s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / bcda (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
CI / test (push) Failing after 3m44s
Deploy / report (push) Successful in 18s
build.py inlines assets/css/fhirworx.css (palette, fonts, base classes), maps the Gitea fhirworx-dark palette onto the same token names (opt-in, as in Gitea; light stays light under an OS dark setting), and inlines the logo as an SVG symbol plus the favicon set from assets/icons. The template drops its own palette and uses only fhirworx tokens: sidebar navy navbar with the logo, the dashboard masthead (rule lines, eyebrow, title, tagline) carrying the lex name text and the preview status as a health box, dashboard tiles (Tabler icon plate, eyebrow, title, description, health box, address, stripe) for the Contents and Dossier sections, the dashboard footer, inject.css tables/buttons/inputs, and the chart palette (indigo final, amber proposed; stances teal/amber/red/grey).
This commit is contained in:
@@ -17,6 +17,14 @@ document (template.html with the data inlined) to --out, by default
|
||||
infra/gitea/custom/public/assets/dossier/index.html (gitignored: the data
|
||||
is rebuilt, never committed). --json also writes the raw data.
|
||||
|
||||
Design: lex is a fhirworx (HTI-5) page built from the system's sources, so
|
||||
it follows them on every rebuild. The page inlines assets/css/fhirworx.css
|
||||
(palette, fonts, base classes), the fhirworx dark palette from the Gitea
|
||||
theme (infra/gitea/theme/theme-fhirworx-dark.css) mapped onto the same token
|
||||
names (opt-in, as in Gitea), the logo (assets/icons/logo.svg, as an SVG
|
||||
symbol) and the favicon set (assets/icons). The template uses only those
|
||||
tokens.
|
||||
|
||||
AMA license: CPT descriptors never ship. The RVU-file short descriptors
|
||||
are not read at all, and descriptor_change events carry a fixed note in
|
||||
place of the RVU-file text.
|
||||
@@ -25,6 +33,7 @@ place of the RVU-file text.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
@@ -805,12 +814,130 @@ def _fill(tpl: str, marker: str, value: str, count: int = 1) -> str:
|
||||
return tpl.replace(marker, value)
|
||||
|
||||
|
||||
# ── the fhirworx design system, from its sources ──────────────────────────
|
||||
FHIRWORX_CSS = (
|
||||
ROOT / "assets/css/fhirworx.css"
|
||||
) # single source of truth for the palette
|
||||
GITEA_DARK = (
|
||||
ROOT / "infra/gitea/theme/theme-fhirworx-dark.css"
|
||||
) # the system's dark palette
|
||||
ICONS = ROOT / "assets/icons"
|
||||
|
||||
# fhirworx.css token -> the Gitea fhirworx-dark variable that plays the same
|
||||
# role. chart-1..3 (the indigo scale) have no dark counterpart in the system,
|
||||
# so they keep their fhirworx.css values; the template picks the step that
|
||||
# reads on the dark card.
|
||||
DARK_ROLES = {
|
||||
"background": "--fw-background",
|
||||
"foreground": "--fw-foreground",
|
||||
"card": "--fw-card",
|
||||
"card-foreground": "--fw-foreground",
|
||||
"popover": "--fw-card",
|
||||
"popover-foreground": "--fw-foreground",
|
||||
"primary": "--fw-primary",
|
||||
"primary-foreground": "--fw-primary-foreground",
|
||||
"secondary": "--fw-secondary",
|
||||
"secondary-foreground": "--color-text-light",
|
||||
"muted": "--fw-secondary",
|
||||
"muted-foreground": "--fw-muted-foreground",
|
||||
"accent": "--fw-secondary",
|
||||
"accent-foreground": "--fw-foreground",
|
||||
"destructive": "--fw-destructive",
|
||||
"destructive-foreground": "--fw-primary-foreground",
|
||||
"border": "--fw-border",
|
||||
"input": "--fw-border",
|
||||
"ring": "--fw-primary",
|
||||
"sidebar": "--fw-nav-bg",
|
||||
"sidebar-foreground": "--fw-nav-text",
|
||||
"sidebar-primary": "--fw-link",
|
||||
"sidebar-primary-foreground": "--fw-primary-foreground",
|
||||
"sidebar-accent": "--fw-nav-hover-bg",
|
||||
"sidebar-accent-foreground": "--fw-nav-text",
|
||||
"sidebar-border": "--fw-nav-hover-bg",
|
||||
"sidebar-ring": "--fw-link",
|
||||
"chart-4": "--color-green",
|
||||
"chart-5": "--fw-chart-5",
|
||||
}
|
||||
|
||||
|
||||
def _root_vars(path: Path) -> dict[str, str]:
|
||||
"""Custom properties of the stylesheet's first :root block (raw values)."""
|
||||
m = re.search(r"^:root\s*\{(.*?)^\}", path.read_text(encoding="utf-8"), re.S | re.M)
|
||||
if not m:
|
||||
raise SystemExit(f"{path}: no :root block")
|
||||
body = re.sub(r"/\*.*?\*/", "", m.group(1), flags=re.S)
|
||||
return {k: v.strip() for k, v in re.findall(r"(--[\w-]+)\s*:\s*([^;]+);", body)}
|
||||
|
||||
|
||||
def _resolve(raw: dict[str, str], name: str, where: Path, depth: int = 0) -> str:
|
||||
"""The value of one custom property, following var(--x) references."""
|
||||
if depth > 8 or name not in raw:
|
||||
raise SystemExit(f"{where}: cannot resolve {name}")
|
||||
return re.sub(
|
||||
r"var\((--[\w-]+)\)",
|
||||
lambda r: _resolve(raw, r.group(1), where, depth + 1),
|
||||
raw[name],
|
||||
)
|
||||
|
||||
|
||||
def _dark_palette() -> str:
|
||||
light = _root_vars(FHIRWORX_CSS)
|
||||
dark = _root_vars(GITEA_DARK)
|
||||
lines = []
|
||||
for token, src in DARK_ROLES.items():
|
||||
if f"--{token}" not in light:
|
||||
raise SystemExit(
|
||||
f"{FHIRWORX_CSS}: token --{token} is gone; update DARK_ROLES"
|
||||
)
|
||||
value = _resolve(dark, src, GITEA_DARK)
|
||||
if not re.fullmatch(r"#[0-9A-Fa-f]{6}", value):
|
||||
raise SystemExit(f"{GITEA_DARK}: {src} is {value!r}, expected a hex colour")
|
||||
lines.append(f" --{token}: {value}; /* {src} */")
|
||||
return ':root[data-theme="dark"] {\n' + "\n".join(lines) + "\n}"
|
||||
|
||||
|
||||
def _data_uri(path: Path, mime: str) -> str:
|
||||
return f"data:{mime};base64," + base64.b64encode(path.read_bytes()).decode("ascii")
|
||||
|
||||
|
||||
def _logo_symbol() -> str:
|
||||
"""assets/icons/logo.svg as an SVG <symbol id="fw-logo">, used by <use>."""
|
||||
svg = (ICONS / "logo.svg").read_text(encoding="utf-8")
|
||||
m = re.fullmatch(r"\s*<svg\b([^>]*)>(.*)</svg>\s*", svg, re.S)
|
||||
vb = re.search(r'viewBox="([^"]+)"', m.group(1)) if m else None
|
||||
if not (m and vb):
|
||||
raise SystemExit(
|
||||
f"{ICONS / 'logo.svg'}: expected one <svg viewBox=...> element"
|
||||
)
|
||||
return f'<symbol id="fw-logo" viewBox="{vb.group(1)}">{m.group(2).strip()}</symbol>'
|
||||
|
||||
|
||||
fw_css = FHIRWORX_CSS.read_text(encoding="utf-8")
|
||||
if "</" in fw_css:
|
||||
raise SystemExit(f"{FHIRWORX_CSS}: '</' would end the inline <style>")
|
||||
|
||||
# Inline the data into the page. Every "<" in the JSON is escaped so no
|
||||
# string in it can close or confuse the <script> element.
|
||||
page = (HERE / "template.html").read_text(encoding="utf-8")
|
||||
page = _fill(page, "/*__BUILT__*/", html.escape(BUILT.strftime("%Y-%m-%d %H:%M %Z")))
|
||||
# navbar links back to Gitea (on lex.<domain>, "/" is this page)
|
||||
page = _fill(page, "/*__DOMAIN__*/", html.escape(DOMAIN), count=2)
|
||||
page = _fill(page, "/*__FHIRWORX_CSS__*/", fw_css)
|
||||
page = _fill(page, "/*__FHIRWORX_DARK__*/", _dark_palette())
|
||||
page = _fill(page, "<!--__FW_LOGO__-->", _logo_symbol())
|
||||
page = _fill(
|
||||
page, "/*__FAVICON_SVG__*/", _data_uri(ICONS / "favicon.svg", "image/svg+xml")
|
||||
)
|
||||
page = _fill(page, "/*__FAVICON_PNG__*/", _data_uri(ICONS / "fav32.png", "image/png"))
|
||||
page = _fill(
|
||||
page,
|
||||
"/*__APPLE_TOUCH_ICON__*/",
|
||||
_data_uri(ICONS / "apple-touch-icon.png", "image/png"),
|
||||
)
|
||||
# status line and footer
|
||||
page = _fill(
|
||||
page, "/*__BUILT__*/", html.escape(BUILT.strftime("%Y-%m-%d %H:%M %Z")), count=2
|
||||
)
|
||||
# navbar links back to Gitea (on lex.<domain>, "/" is this page), the
|
||||
# masthead and footer address, the dashboard link, the tiles' deep links
|
||||
page = _fill(page, "/*__DOMAIN__*/", html.escape(DOMAIN), count=6)
|
||||
page = _fill(page, "/*__DATA__*/{}", blob.replace("<", "\\u003c"))
|
||||
dst = args.out
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -5,321 +5,397 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>lex · Home or Residence Services</title>
|
||||
<!-- fhirworx favicon set (assets/icons), inlined by build.py -->
|
||||
<link rel="icon" type="image/svg+xml" href="/*__FAVICON_SVG__*/">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/*__FAVICON_PNG__*/">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/*__APPLE_TOUCH_ICON__*/">
|
||||
<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@600;700;800&family=Source+Serif+4:ital,opsz,wght@0,8..60,400;0,8..60,600;1,8..60,400&family=JetBrains+Mono:wght@400;500;600&display=swap">
|
||||
<script>
|
||||
/* Light is the default, as on the dashboard and the Gitea fhirworx theme; dark is opt-in (the
|
||||
Gitea fhirworx-dark theme is a user choice too), remembered per browser. */
|
||||
try { if (localStorage.getItem("lex-theme") === "dark") document.documentElement.setAttribute("data-theme", "dark"); } catch (e) {}
|
||||
</script>
|
||||
<!-- HTI-5 fhirworx design system: assets/css/fhirworx.css, inlined by build.py (palette, fonts, base classes) -->
|
||||
<style id="fhirworx">/*__FHIRWORX_CSS__*/</style>
|
||||
<!-- the fhirworx dark palette (infra/gitea/theme/theme-fhirworx-dark.css) on the same token names, by build.py -->
|
||||
<style id="fhirworx-dark">/*__FHIRWORX_DARK__*/</style>
|
||||
<style>
|
||||
/* Layout: the fhirworx Gitea shell (navy navbar, cream ground, 3px ink rules) around a three-pane
|
||||
workspace: the book in the left rail, the selected object with one tab per view, the evidence drawer. */
|
||||
/* lex on the fhirworx design system. Every colour, font and radius below is a fhirworx.css token
|
||||
(or a mix of two). Patterns come from the system's own pages:
|
||||
masthead, section header, tile + icon plate + health box + badge + stripe, footer: assets/css/dashboard.css
|
||||
navbar, tables, buttons, inputs, links, scrollbars: assets/css/inject.css, Gitea theme
|
||||
in-page sidebar: assets/css/marimo.css (.app-sidebar)
|
||||
Layout: the masthead, then a three-pane workspace: the book in the left rail, the selected
|
||||
object with one tab per view, and the evidence drawer. */
|
||||
:root {
|
||||
--bg: #F7F5F0; /* fhirworx cream body */
|
||||
--surface: #FFFFFF; /* card */
|
||||
--surface-2: #EDEBE6; /* box header / secondary */
|
||||
--hover: #F3F0EA;
|
||||
--fg: #1A1A18;
|
||||
--fg-2: #454542;
|
||||
--muted: #6E6E6A;
|
||||
--rule: #B8B3A4;
|
||||
--rule-soft: #E0DCD1;
|
||||
--ink-rule: #1A1A18; /* the 3px masthead rule */
|
||||
--nav-bg: #1C2B3A;
|
||||
--nav-fg: #F0F4F8;
|
||||
--nav-hover: #253748;
|
||||
--primary: #1C2B3A;
|
||||
--link: #2D5F7F;
|
||||
--link-hover: #244B63;
|
||||
--s-final: #2E6E9E; /* chart: final values (validated pair) */
|
||||
--s-prop: #C8702A; /* chart: proposed values */
|
||||
--s-ruc: #6E6E6A; /* chart: neutral reference marks */
|
||||
--s-alt: #A8802A; /* chart: second series (validated with --s-final) */
|
||||
--prop-bg: #FBF1E6;
|
||||
--ok: #247058; --ok-bg: #E8F2DD;
|
||||
--warn: #7F5E08; --warn-bg: #F8F0DC; --warn-line: #C9BA9B;
|
||||
--bad: #C0392B; --bad-bg: #F6E5E4;
|
||||
--info-bg: #E8F0F6; --info-line: #A5BCCD;
|
||||
--st-support: #247058; --st-modify: #8F6C0E; --st-oppose: #C0392B; --st-unclear: #6E6E6A;
|
||||
--chip-ink: #FFFFFF;
|
||||
--f-display: "Playfair Display", Georgia, "Times New Roman", serif;
|
||||
--f-body: "Source Serif 4", Georgia, "Times New Roman", serif;
|
||||
--f-mono: "JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0F1419; --surface: #1A2028; --surface-2: #1F262E; --hover: #232A34;
|
||||
--fg: #E8E5DD; --fg-2: #CFCCC5; --muted: #9A968E; --rule: #3A4452; --rule-soft: #2A323A; --ink-rule: #E8E5DD;
|
||||
--nav-bg: #0A0E13; --nav-fg: #E8F0F8; --nav-hover: #1C2B3A; --primary: #5684A3;
|
||||
--link: #5C9CC0; --link-hover: #85ABC2;
|
||||
--s-final: #4F9BD0; --s-prop: #D07A35; --s-ruc: #9A968E; --s-alt: #AD8838; --prop-bg: #2A2219;
|
||||
--ok: #4CB28A; --ok-bg: #1F3A2A; --warn: #E5BE5A; --warn-bg: #3A3220; --warn-line: #8A7A30;
|
||||
--bad: #E07A6E; --bad-bg: #3C1F1F; --info-bg: #1A2E44; --info-line: #3A6A8F;
|
||||
--st-support: #4CB28A; --st-modify: #C9A347; --st-oppose: #E07A6E; --st-unclear: #9A968E; --chip-ink: #0F1419;
|
||||
color-scheme: dark;
|
||||
}
|
||||
color-scheme: light only; /* as the Gitea fhirworx theme: an OS dark setting does not flip it */
|
||||
/* Roles, each one a system token. */
|
||||
--s-final: var(--chart-2); /* chart: final values (indigo) */
|
||||
--s-prop: var(--chart-5); /* chart: proposed values (amber) */
|
||||
--s-ruc: var(--muted-foreground); /* chart: neutral reference marks */
|
||||
--s-alt: var(--chart-1); /* chart: second series (indigo, lighter step) */
|
||||
--st-support: var(--chart-4); /* stance: teal */
|
||||
--st-modify: var(--chart-5); /* stance: amber */
|
||||
--st-oppose: var(--destructive);
|
||||
--st-unclear: var(--muted-foreground);
|
||||
--on-amber: var(--foreground); /* Gitea theme rule: near-black, not white, on orange */
|
||||
--nav-rule: var(--foreground); /* the 3px ink line under the navbar */
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0F1419; --surface: #1A2028; --surface-2: #1F262E; --hover: #232A34;
|
||||
--fg: #E8E5DD; --fg-2: #CFCCC5; --muted: #9A968E; --rule: #3A4452; --rule-soft: #2A323A; --ink-rule: #E8E5DD;
|
||||
--nav-bg: #0A0E13; --nav-fg: #E8F0F8; --nav-hover: #1C2B3A; --primary: #5684A3;
|
||||
--link: #5C9CC0; --link-hover: #85ABC2;
|
||||
--s-final: #4F9BD0; --s-prop: #D07A35; --s-ruc: #9A968E; --s-alt: #AD8838; --prop-bg: #2A2219;
|
||||
--ok: #4CB28A; --ok-bg: #1F3A2A; --warn: #E5BE5A; --warn-bg: #3A3220; --warn-line: #8A7A30;
|
||||
--bad: #E07A6E; --bad-bg: #3C1F1F; --info-bg: #1A2E44; --info-line: #3A6A8F;
|
||||
--st-support: #4CB28A; --st-modify: #C9A347; --st-oppose: #E07A6E; --st-unclear: #9A968E; --chip-ink: #0F1419;
|
||||
color-scheme: dark;
|
||||
--s-final: var(--chart-1); /* the system has no dark indigo scale; chart-2 is under 3:1 on the dark card */
|
||||
--s-alt: var(--muted-foreground);
|
||||
--on-amber: var(--primary-foreground);
|
||||
--nav-rule: var(--border); /* Gitea fhirworx-dark draws the navbar rule in the border colour */
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--bg); color: var(--fg); font-family: var(--f-body); font-size: 15px; line-height: 1.55; }
|
||||
a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 2px; }
|
||||
a:hover { color: var(--link-hover); }
|
||||
:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
|
||||
body { margin: 0; min-height: 100vh; overflow-x: hidden; }
|
||||
a { color: var(--primary); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); transition: color .15s ease; }
|
||||
a:hover { color: var(--foreground); text-decoration-color: currentColor; }
|
||||
::selection { background: var(--primary); color: var(--primary-foreground); }
|
||||
::-webkit-scrollbar { width: 12px; height: 12px; }
|
||||
::-webkit-scrollbar-track { background: var(--secondary); }
|
||||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 6px; border: 2px solid var(--secondary); }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--muted-foreground); }
|
||||
h1, h2, h3 { text-wrap: balance; }
|
||||
.mono { font-family: var(--f-mono); }
|
||||
.num { font-family: var(--f-mono); font-variant-numeric: tabular-nums; font-size: 12.5px; }
|
||||
.eyebrow { font-family: var(--f-mono); font-size: 11px; text-transform: uppercase; letter-spacing: .12em; color: var(--muted); }
|
||||
.muted { color: var(--muted); }
|
||||
.mono { font-family: var(--font-mono); }
|
||||
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 12.5px; }
|
||||
.muted { color: var(--muted-foreground); }
|
||||
.nowrap { white-space: nowrap; }
|
||||
.small { font-size: 13px; }
|
||||
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
|
||||
|
||||
/* navbar: the fhirworx dark bar with its 3px ink line */
|
||||
.nav { background: var(--nav-bg); color: var(--nav-fg); border-bottom: 3px solid var(--ink-rule); }
|
||||
.nav-in { max-width: 1480px; margin: 0 auto; padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px)); padding-block: 10px; display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
|
||||
.brand-group { display: inline-flex; align-items: baseline; gap: 12px; }
|
||||
.brand { font-family: var(--f-display); font-weight: 700; font-size: 20px; letter-spacing: -.01em; color: var(--nav-fg); }
|
||||
.app { font-family: var(--f-display); font-style: italic; font-weight: 600; font-size: 20px; letter-spacing: .01em; color: var(--nav-fg); padding-left: 12px; border-left: 1px solid color-mix(in srgb, var(--nav-fg) 40%, transparent); }
|
||||
.nav-item { font-family: var(--f-mono); font-size: 12px; letter-spacing: .06em; color: var(--nav-fg); opacity: .78; padding: 4px 8px; }
|
||||
.nav-item.on { opacity: 1; background: var(--nav-hover); }
|
||||
a.brand, a.app, a.nav-item { text-decoration: none; }
|
||||
a.brand:hover, a.app:hover, a.nav-item:hover { color: var(--nav-fg); opacity: 1; }
|
||||
/* ── navbar: the Gitea fhirworx bar (sidebar navy, 3px ink rule, logo first) ── */
|
||||
.nav { background: var(--sidebar); color: var(--sidebar-foreground); border-bottom: 3px solid var(--nav-rule); }
|
||||
.nav-in { max-width: 1480px; margin: 0 auto; padding: 6px 1.5rem; min-height: 54px; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
|
||||
.brand-group { display: inline-flex; align-items: center; gap: 12px; margin-right: 10px; }
|
||||
.brand { display: inline-flex; align-items: center; gap: 10px; font-family: var(--font-display); font-weight: 700; font-size: 1.2rem; letter-spacing: -.01em; color: var(--sidebar-accent-foreground); }
|
||||
.brand svg { width: 30px; height: 30px; flex: none; }
|
||||
.app { font-family: var(--font-display); font-weight: 700; font-size: 1.2rem; color: var(--sidebar-accent-foreground); padding-left: 12px; border-left: 1px solid color-mix(in srgb, var(--sidebar-foreground) 40%, transparent); }
|
||||
.nav-item { font-family: var(--font-body); font-size: 15px; color: var(--sidebar-foreground); padding: 5px 10px; border-radius: var(--radius); display: inline-flex; align-items: center; gap: 6px; }
|
||||
.nav-item.on { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
|
||||
.nav a { text-decoration: none; }
|
||||
.nav a:hover, .nav button:hover { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
|
||||
.nav a.brand:hover, .nav a.app:hover { background: transparent; color: var(--sidebar-accent-foreground); }
|
||||
.nav-sp { flex: 1; }
|
||||
.nav-note { font-size: 14px; opacity: .85; }
|
||||
.nav button.nav-item { background: transparent; border: 0; cursor: pointer; }
|
||||
.nav-item svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.nav :focus-visible { outline-color: var(--sidebar-primary) !important; }
|
||||
|
||||
/* banner: what lex is, then the build status as a smaller second line */
|
||||
.banner { background: var(--surface-2); border-bottom: 1px solid var(--rule-soft); }
|
||||
.banner-in { max-width: 1480px; margin: 0 auto; padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px)); padding-block: 10px 9px; }
|
||||
.banner-what { margin: 0; max-width: 96ch; font-size: 15px; line-height: 1.5; text-wrap: pretty; }
|
||||
.banner-what .lex-word { font-family: var(--f-display); font-weight: 700; font-size: 17px; }
|
||||
.banner-status { margin: 5px 0 0; font-size: 12.5px; color: var(--fg-2); display: flex; gap: 4px 10px; align-items: baseline; flex-wrap: wrap; }
|
||||
.status-pill { font-family: var(--f-mono); font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .12em; color: var(--warn); background: var(--warn-bg); border: 1px solid var(--warn-line); padding: 1px 6px; }
|
||||
/* ── page + masthead: dashboard.css ── */
|
||||
.page { max-width: 1480px; margin: 0 auto; padding: 3rem 1.5rem 2.5rem; }
|
||||
.masthead { margin-bottom: .5rem; }
|
||||
.masthead-row { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; }
|
||||
.masthead-brand { display: flex; align-items: center; gap: 1rem; }
|
||||
.masthead-logo { width: clamp(48px, 6vw, 80px); height: clamp(48px, 6vw, 80px); display: block; flex: 0 0 auto; }
|
||||
.masthead h1 { font-weight: 800; font-size: clamp(2.5rem, 5vw, 3.5rem); line-height: 1.1; letter-spacing: -.02em; margin: 0; }
|
||||
.masthead-eyebrow { font-family: var(--font-mono); font-size: .7rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted-foreground); align-self: flex-end; padding-bottom: .35rem; }
|
||||
.masthead-body { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 0 2.5rem; }
|
||||
.tagline { font-size: 1.125rem; color: var(--muted-foreground); max-width: 65ch; line-height: 1.65; margin: 0 0 1.5rem; text-wrap: pretty; }
|
||||
.tagline em { color: var(--foreground); }
|
||||
.masthead .health { margin: 0 0 1.5rem; max-width: 34rem; }
|
||||
.masthead .health-v { font-size: 16px; }
|
||||
.status-note { font-size: 14px; color: var(--muted-foreground); margin-top: 2px; }
|
||||
|
||||
.shell { max-width: 1480px; margin: 0 auto; padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px)); display: grid; grid-template-columns: 236px minmax(0, 1fr) 330px; align-items: start; }
|
||||
/* health box: dashboard.css (label, status word, the left edge carries the state) */
|
||||
.health { display: flex; flex-direction: column; margin: 0 0 .85rem; padding: 8px 10px; border: 1px solid var(--border); border-left-width: 4px; background: var(--background); }
|
||||
.health-k { font-family: var(--font-mono); font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted-foreground); }
|
||||
.health-v { font-size: 18px; font-weight: 600; line-height: 1.2; color: var(--foreground); }
|
||||
.health-ok { border-left-color: var(--chart-4); }
|
||||
.health-warn { border-left-color: var(--chart-5); }
|
||||
.health-bad { border-left-color: var(--destructive); }
|
||||
.health-unknown { border-left-color: var(--muted-foreground); }
|
||||
|
||||
/* left rail: the book */
|
||||
.rail { position: sticky; top: 0; max-height: 100vh; overflow: auto; padding: 22px 18px 24px 0; border-right: 1px solid var(--rule-soft); }
|
||||
.rail input { width: 100%; font: inherit; font-size: 14px; padding: 7px 10px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg); border-radius: 0; }
|
||||
.rail h2 { font-family: var(--f-display); font-size: 1.15rem; margin: 18px 0 6px; }
|
||||
/* badge: dashboard.css */
|
||||
.badge { font-family: var(--font-mono); color: var(--primary-foreground); background: var(--primary); font-size: .6rem; padding: 3px 7px; text-transform: uppercase; letter-spacing: .08em; white-space: nowrap; display: inline-block; line-height: 1.4; }
|
||||
.badge.new { background: var(--chart-5); }
|
||||
.tile > .badge { position: absolute; top: -1px; left: 1.5rem; }
|
||||
|
||||
.shell { display: grid; grid-template-columns: 236px minmax(0, 1fr) 330px; align-items: start; }
|
||||
|
||||
/* ── left rail: the book (an in-page sidebar, as marimo's .app-sidebar) ── */
|
||||
.rail { position: sticky; top: 0; max-height: 100vh; overflow: auto; padding: 1rem 1.5rem 1.5rem 0; border-right: 1px solid var(--border); }
|
||||
.rail label.eyebrow { display: block; margin-bottom: 6px; }
|
||||
.rail input { width: 100%; }
|
||||
.rail input, select.fsel { font: inherit; font-size: 15px; padding: 6px 10px; background: var(--card); color: var(--foreground); border: 1px solid var(--input); border-radius: var(--radius); }
|
||||
select.fsel { font-size: 14px; padding: 4px 8px; }
|
||||
.rail input:focus, select.fsel:focus { border-color: var(--primary); outline: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 25%, transparent); }
|
||||
.rail h2 { font-size: 1.25rem; margin: 1.25rem 0 .4rem; }
|
||||
.toc { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
|
||||
.toc li { display: flex; gap: 8px; align-items: baseline; justify-content: space-between; padding: 4px 6px; font-size: 14px; }
|
||||
.toc li.cur { background: var(--surface); border-left: 3px solid var(--ink-rule); }
|
||||
.toc li.cur a { color: var(--fg); font-weight: 600; text-decoration: none; }
|
||||
.toc .tier { font-family: var(--f-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
|
||||
.toc li.planned span:first-child { color: var(--fg-2); }
|
||||
.rail .note { font-size: 13px; color: var(--muted); margin-top: 14px; border-top: 1px solid var(--rule-soft); padding-top: 10px; }
|
||||
.toc li { display: flex; gap: 8px; align-items: baseline; justify-content: space-between; padding: 4px 8px; font-size: 15px; line-height: 1.4; color: var(--muted-foreground); }
|
||||
.toc li.cur { background: var(--card); border-left: 3px solid var(--foreground); color: var(--foreground); }
|
||||
.toc li.cur a { color: var(--foreground); font-weight: 600; text-decoration: none; }
|
||||
.toc li.cur a:hover { color: var(--primary); }
|
||||
.toc .tier { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted-foreground); white-space: nowrap; }
|
||||
.toc li.planned span:first-child { color: var(--secondary-foreground); }
|
||||
.rail .eyebrow + .toc, .rail .toc + .eyebrow { margin-top: 4px; }
|
||||
.rail .note { font-size: 14px; color: var(--muted-foreground); margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
|
||||
.rail details summary { cursor: pointer; list-style: none; }
|
||||
.rail details summary::-webkit-details-marker { display: none; }
|
||||
.rail details:not([open]) summary h2::after { content: " +"; color: var(--muted); font-family: var(--f-mono); font-size: 14px; }
|
||||
.rail details:not([open]) summary h2::after { content: " +"; color: var(--muted-foreground); font-family: var(--font-mono); font-size: 14px; }
|
||||
|
||||
/* main pane */
|
||||
.main { min-width: 0; padding: 22px 26px 64px; }
|
||||
.mast .rule { border-top: 3px solid var(--ink-rule); margin-bottom: 12px; }
|
||||
.mast-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
|
||||
.mast h1 { font-family: var(--f-display); font-weight: 800; font-size: clamp(1.9rem, 3.6vw, 2.7rem); line-height: 1.1; letter-spacing: -.02em; margin: 2px 0 6px; }
|
||||
.mast .tag { font-family: var(--f-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; padding: 3px 8px; border: 1px solid var(--ok); color: var(--ok); white-space: nowrap; }
|
||||
.mast .rule-thin { border-top: 1px solid var(--rule); margin-top: 12px; }
|
||||
/* ── main pane ── */
|
||||
.main { min-width: 0; padding: 1rem 1.5rem 4rem; }
|
||||
.section-header { margin-bottom: 1.25rem; }
|
||||
.section-row { display: flex; align-items: center; justify-content: space-between; gap: .5rem 1rem; flex-wrap: wrap; margin-bottom: .4rem; }
|
||||
.section-eyebrow { font-family: var(--font-mono); font-size: .7rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted-foreground); display: block; }
|
||||
.section-row .section-eyebrow { margin: 0; }
|
||||
.section-header h2 { font-size: 1.75rem; font-weight: 700; line-height: 1.2; letter-spacing: -.01em; margin: 0; }
|
||||
.section-sub { margin: .4rem 0 0; font-size: .95rem; color: var(--muted-foreground); line-height: 1.55; }
|
||||
|
||||
.tabs-wrap { overflow-x: auto; margin: 0 0 22px; border-bottom: 1px solid var(--rule); }
|
||||
.tabs-wrap { overflow-x: auto; margin: 0 0 1.5rem; border-bottom: 1px solid var(--border); }
|
||||
.tabs { display: flex; gap: 2px; flex-wrap: wrap; align-items: flex-end; }
|
||||
.tabs .grp { font-family: var(--f-mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); padding: 0 6px 9px 10px; }
|
||||
.tabs a { font-family: var(--f-mono); font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); text-decoration: none; padding: 9px 10px 8px; border-bottom: 3px solid transparent; white-space: nowrap; }
|
||||
.tabs a:hover { color: var(--fg); background: var(--hover); }
|
||||
.tabs a.on { color: var(--fg); border-bottom-color: var(--ink-rule); font-weight: 600; }
|
||||
.tabs .grp { font-family: var(--font-mono); font-size: .62rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted-foreground); opacity: .8; padding: 0 6px 11px 10px; }
|
||||
.tabs a { font-family: var(--font-mono); font-size: .7rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted-foreground); text-decoration: none; padding: 10px 10px 8px; border-bottom: 3px solid transparent; white-space: nowrap; }
|
||||
.tabs a:hover { color: var(--foreground); background: var(--secondary); }
|
||||
.tabs a.on { color: var(--foreground); border-bottom-color: var(--foreground); font-weight: 600; }
|
||||
.tabs a.soon { border-bottom-style: dashed; }
|
||||
.tabs a.soon::after { content: "coming"; margin-left: 6px; font-size: 9px; padding: 1px 4px; border: 1px dashed var(--rule); color: var(--muted); vertical-align: 1px; }
|
||||
.tabs a.soon::after { content: "coming"; margin-left: 6px; font-size: 9px; padding: 1px 4px; border: 1px dashed var(--border); color: var(--muted-foreground); vertical-align: 1px; }
|
||||
|
||||
.view h2 { font-family: var(--f-display); font-weight: 700; font-size: 1.55rem; letter-spacing: -.01em; margin: 0 0 4px; }
|
||||
.view h3 { font-family: var(--f-display); font-weight: 700; font-size: 1.15rem; margin: 26px 0 8px; }
|
||||
.view .lede { color: var(--fg-2); max-width: 72ch; margin: 0 0 16px; }
|
||||
.sec { margin-top: 26px; }
|
||||
.view h2 { font-size: 1.25rem; letter-spacing: -.01em; margin: 0 0 .25rem; }
|
||||
.view h3 { font-size: 1.1rem; margin: 1.75rem 0 .5rem; }
|
||||
.view .lede { color: var(--muted-foreground); max-width: 72ch; margin: 0 0 1rem; line-height: 1.6; }
|
||||
.sec { margin-top: 1.75rem; }
|
||||
.sec-head { margin: 1.75rem 0 1rem; }
|
||||
.sec-head h3 { margin: 0 !important; }
|
||||
|
||||
/* tiles, as on the homelab landing */
|
||||
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 16px; }
|
||||
.tile { background: var(--surface); border: 1px solid var(--rule); border-top: 3px solid var(--ink-rule); padding: 14px 16px 16px; min-width: 0; }
|
||||
.tile .t-title { font-family: var(--f-display); font-weight: 700; font-size: 1.15rem; margin: 2px 0 8px; }
|
||||
.tile .t-title a { color: var(--fg); text-decoration: none; }
|
||||
.tile .t-title a:hover { color: var(--link); }
|
||||
.facts { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
|
||||
.facts li { font-size: 14px; line-height: 1.45; }
|
||||
/* ── tiles: dashboard.css (icon plate, eyebrow, title, description, health, port, stripe) ── */
|
||||
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.25rem; }
|
||||
.tile { background: var(--card); border: 1px solid var(--border); border-top: 3px solid var(--foreground); padding: 1.5rem; position: relative; overflow: hidden; transition: border-color .15s ease, background .15s ease; min-width: 0; display: flex; flex-direction: column; }
|
||||
.tile > .icon, .tile > .tile-port { align-self: flex-start; }
|
||||
.tile:hover { border-color: var(--primary); }
|
||||
.tile[data-ev] { cursor: pointer; }
|
||||
.tile .icon { width: 44px; height: 44px; flex: none; display: grid; place-items: center; border: 1px solid var(--border); color: var(--primary); margin-bottom: .9rem; transition: border-color .15s ease; }
|
||||
.tile .icon svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.tile:hover .icon { border-color: var(--primary); }
|
||||
.tile-eyebrow { font-family: var(--font-mono); font-size: .7rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted-foreground); margin-bottom: .25rem; }
|
||||
.tile-title { font-family: var(--font-display); font-size: 1.25rem; font-weight: 700; line-height: 1.2; letter-spacing: -.01em; color: var(--foreground); margin: .25rem 0 .5rem; transition: color .15s ease; }
|
||||
.tile-title a { color: inherit; text-decoration: none; }
|
||||
.tile:hover .tile-title { color: var(--primary); }
|
||||
.tile-desc { font-size: .95rem; color: var(--muted-foreground); line-height: 1.55; margin: 0 0 1rem; }
|
||||
.tile-port { font-family: var(--font-mono); font-size: .65rem; color: var(--muted-foreground); opacity: .7; display: inline-block; }
|
||||
.stripe { position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: var(--border); }
|
||||
.tile .health { margin-top: auto; }
|
||||
.facts { list-style: none; padding: 0; display: grid; gap: 7px; color: var(--secondary-foreground); }
|
||||
.facts li { line-height: 1.5; }
|
||||
.tile .codes { margin: .25rem 0 .5rem; }
|
||||
.tile dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 12px; margin: 0 0 1rem; font-size: .9rem; color: var(--secondary-foreground); }
|
||||
.tile dt { font-family: var(--font-mono); font-size: .65rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted-foreground); padding-top: 4px; }
|
||||
.tile dd { margin: 0; min-width: 0; }
|
||||
|
||||
.cite { font-family: var(--f-mono); font-size: 11px; white-space: nowrap; text-decoration: none; color: var(--link); border: 1px solid var(--rule-soft); background: var(--surface); padding: 0 5px; margin-left: 2px; display: inline-block; line-height: 1.6; }
|
||||
.cite { font-family: var(--font-mono); font-size: 11px; white-space: nowrap; text-decoration: none; color: var(--primary); border: 1px solid var(--border); background: var(--card); padding: 1px 5px; margin-left: 2px; line-height: 1.9; }
|
||||
.cite::after { content: " \2197"; font-size: 10px; }
|
||||
.cite:hover { border-color: var(--link); }
|
||||
.cite.book { color: var(--fg-2); }
|
||||
.cite:hover { border-color: var(--primary); color: var(--primary); }
|
||||
.cite.book { color: var(--secondary-foreground); }
|
||||
.cite.book::after { content: ""; }
|
||||
|
||||
.codes { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.code { font-family: var(--f-mono); font-size: 12px; padding: 1px 6px; border: 1px solid var(--rule); background: var(--surface); }
|
||||
.code.del { text-decoration: line-through; color: var(--muted); border-style: dashed; }
|
||||
.code.lnk { border-color: var(--link); color: var(--link); }
|
||||
.code { font-family: var(--font-mono); font-size: 12px; padding: 1px 6px; border: 1px solid var(--border); background: var(--card); }
|
||||
.code.del { text-decoration: line-through; color: var(--muted-foreground); border-style: dashed; }
|
||||
.code.lnk { border-color: var(--primary); color: var(--primary); }
|
||||
|
||||
.hdr-grid { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 16px; margin: 10px 0 4px; align-items: baseline; }
|
||||
.hdr-grid { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 16px; margin: 0 0 4px; align-items: baseline; }
|
||||
.hdr-grid > .eyebrow { padding-top: 3px; }
|
||||
|
||||
.panel { border: 1px solid var(--rule); border-left-width: 4px; background: var(--surface); padding: 12px 16px; }
|
||||
.panel.warn { border-left-color: var(--warn); }
|
||||
.panel.info { border-left-color: var(--link); }
|
||||
.panel.soon { border-left-color: var(--muted); border-style: dashed; border-left-style: solid; }
|
||||
/* panel: the health-box shape at section scale (left edge carries the state) */
|
||||
.panel { border: 1px solid var(--border); border-left-width: 4px; background: var(--card); padding: 12px 16px; }
|
||||
.panel.warn { border-left-color: var(--chart-5); }
|
||||
.panel.info { border-left-color: var(--primary); }
|
||||
.panel.soon { border-left-color: var(--muted-foreground); border-style: dashed; border-left-style: solid; }
|
||||
.panel h3 { margin-top: 0 !important; }
|
||||
.gaps { margin: 6px 0 0; padding-left: 18px; display: grid; gap: 5px; font-size: 14px; }
|
||||
.gaps { margin: 6px 0 0; padding-left: 18px; display: grid; gap: 5px; font-size: 15px; }
|
||||
|
||||
.pill { font-family: var(--f-mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; padding: 1px 6px; border: 1px solid var(--rule); color: var(--fg-2); white-space: nowrap; display: inline-block; }
|
||||
.pill.prelim { border-color: var(--warn); color: var(--warn); background: var(--warn-bg); }
|
||||
.pill.cms { border-color: var(--link); color: var(--link); background: var(--info-bg); }
|
||||
.pill.prop { border-color: var(--s-prop); border-style: dashed; color: var(--fg); background: var(--prop-bg); }
|
||||
.pill.final { border-color: var(--s-final); color: var(--fg); }
|
||||
.pill.model { border-style: dotted; color: var(--muted); }
|
||||
.pill.soon { border-style: dashed; color: var(--muted); }
|
||||
/* pills: inject.css badges/labels (mono, secondary ground, hairline); the state is in the border */
|
||||
.pill { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; padding: 1px 6px; border: 1px solid var(--border); background: var(--secondary); color: var(--secondary-foreground); white-space: nowrap; display: inline-block; }
|
||||
.pill.prelim { border-color: var(--chart-5); background: color-mix(in srgb, var(--chart-5) 12%, var(--card)); color: var(--foreground); }
|
||||
.pill.cms { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 8%, var(--card)); color: var(--foreground); }
|
||||
.pill.prop { border-color: var(--s-prop); border-style: dashed; background: color-mix(in srgb, var(--s-prop) 10%, var(--card)); color: var(--foreground); }
|
||||
.pill.final { border-color: var(--s-final); background: var(--card); color: var(--foreground); }
|
||||
.pill.model { border-style: dotted; color: var(--muted-foreground); background: var(--card); }
|
||||
.pill.soon { border-style: dashed; color: var(--muted-foreground); background: var(--card); }
|
||||
|
||||
/* tables */
|
||||
.tbl-wrap { overflow-x: auto; border: 1px solid var(--rule); background: var(--surface); }
|
||||
table.tbl { border-collapse: collapse; width: 100%; font-size: 14px; }
|
||||
.tbl th { font-family: var(--f-mono); font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); font-weight: 500; text-align: left; padding: 8px 9px 6px; border-bottom: 2px solid var(--ink-rule); background: var(--surface); white-space: nowrap; vertical-align: bottom; }
|
||||
.tbl td { padding: 6px 9px; border-bottom: 1px solid var(--rule-soft); vertical-align: top; }
|
||||
.tbl tr:last-child td { border-bottom: 0; }
|
||||
/* ── tables: inject.css (navy mono header, hairline grid, zebra rows) ── */
|
||||
.tbl-wrap { overflow-x: auto; background: var(--card); }
|
||||
table.tbl { border-collapse: collapse; width: 100%; font-size: 13px; }
|
||||
.tbl th { background: var(--primary); color: var(--primary-foreground); font-family: var(--font-mono); font-size: 10px; font-weight: 500; letter-spacing: .1em; text-transform: uppercase; text-align: left; padding: 10px 12px; border: 1px solid var(--sidebar-accent); white-space: nowrap; vertical-align: bottom; }
|
||||
.tbl td { background: var(--card); color: var(--foreground); padding: 8px 12px; border: 1px solid var(--border); vertical-align: top; }
|
||||
.tbl tbody tr:nth-child(even) td { background: var(--secondary); }
|
||||
.tbl td.r, .tbl th.r { text-align: right; }
|
||||
.tbl tbody tr[data-ev] { cursor: pointer; }
|
||||
.tbl tbody tr[data-ev]:hover td { background: var(--hover); }
|
||||
.tbl tr.sel td { background: var(--info-bg) !important; }
|
||||
.tbl tr.sel td:first-child { box-shadow: inset 3px 0 0 var(--link); }
|
||||
.tbl .grp-row td { background: var(--surface-2); font-family: var(--f-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-2); padding-top: 7px; }
|
||||
.tbl td.pcol, .tbl th.pcol { background: var(--prop-bg); }
|
||||
.tbl tbody tr[data-ev]:hover td { background: color-mix(in srgb, var(--primary) 8%, var(--background)); }
|
||||
.tbl tr.sel td { background: color-mix(in srgb, var(--primary) 13%, var(--card)) !important; }
|
||||
.tbl tr.sel td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
|
||||
.tbl .grp-row td { background: var(--muted); font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--secondary-foreground); padding-top: 9px; border-top: 2px solid var(--foreground); }
|
||||
.tbl td.pcol { border-left: 2px dashed var(--s-prop); font-style: italic; background-image: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--s-prop) 16%, transparent) 0 1px, transparent 1px 6px); }
|
||||
.tbl th.pcol { border-left: 2px dashed var(--s-prop); }
|
||||
.tbl td.pcol { border-left: 2px dashed var(--s-prop); font-style: italic; }
|
||||
.tbl td.na { color: var(--muted); }
|
||||
.tbl td.na { color: var(--muted-foreground); }
|
||||
.chg { position: relative; }
|
||||
.chg::before { content: ""; position: absolute; left: 3px; top: 50%; width: 5px; height: 5px; margin-top: -3px; background: var(--s-prop); border-radius: 50%; }
|
||||
.tbl .stick { position: sticky; left: 0; z-index: 1; box-shadow: 1px 0 0 var(--border); }
|
||||
|
||||
/* stance */
|
||||
.st { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 20px; font-family: var(--f-mono); font-size: 11px; font-weight: 600; color: var(--chip-ink); }
|
||||
/* stance: teal support, amber modify, red oppose, grey unclear (chart-4, chart-5, destructive, muted) */
|
||||
.st { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 20px; font-family: var(--font-mono); font-size: 11px; font-weight: 600; color: var(--primary-foreground); }
|
||||
.st.support { background: var(--st-support); }
|
||||
.st.modify { background: var(--st-modify); }
|
||||
.st.modify { background: var(--st-modify); color: var(--on-amber); }
|
||||
.st.oppose { background: var(--st-oppose); }
|
||||
.st.unclear { background: var(--st-unclear); }
|
||||
.st.none { background: transparent; color: var(--rule); font-weight: 400; }
|
||||
.legend { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: 13px; color: var(--fg-2); }
|
||||
.st.none { background: transparent; color: var(--border); font-weight: 400; }
|
||||
.legend { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: 13px; color: var(--secondary-foreground); }
|
||||
.legend span { display: inline-flex; gap: 6px; align-items: center; }
|
||||
.sw { width: 12px; height: 12px; display: inline-block; }
|
||||
|
||||
.bar { display: flex; height: 22px; gap: 2px; min-width: 0; }
|
||||
.bar > div { display: flex; align-items: center; justify-content: center; font-family: var(--f-mono); font-size: 11px; color: var(--chip-ink); min-width: 18px; }
|
||||
.bar > div { display: flex; align-items: center; justify-content: center; font-family: var(--font-mono); font-size: 11px; color: var(--primary-foreground); min-width: 18px; }
|
||||
.bar > .st-modify { color: var(--on-amber); }
|
||||
|
||||
.two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px; align-items: start; }
|
||||
.two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.25rem; align-items: start; }
|
||||
.col-h { display: flex; gap: 8px; align-items: center; margin: 0 0 8px; flex-wrap: wrap; }
|
||||
.col-h h3 { margin: 0 !important; }
|
||||
.col-h h2, .col-h h3 { margin: 0 !important; }
|
||||
|
||||
.filters { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0 0 14px; }
|
||||
.fbtn { font-family: var(--f-mono); font-size: 11.5px; letter-spacing: .04em; padding: 4px 9px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg-2); cursor: pointer; border-radius: 0; }
|
||||
.fbtn:hover { border-color: var(--fg); }
|
||||
.fbtn[aria-pressed="true"] { background: var(--primary); color: var(--surface); border-color: var(--primary); }
|
||||
.fbtn[aria-pressed="true"] .muted { color: inherit; opacity: .75; }
|
||||
.tbl .stick { position: sticky; left: 0; z-index: 1; background: var(--surface); box-shadow: 1px 0 0 var(--rule-soft); }
|
||||
.tbl tr.sel td.stick { background: var(--info-bg); }
|
||||
.cms-list { display: grid; gap: 0; border: 1px solid var(--rule); background: var(--surface); }
|
||||
.cms-list > div { padding: 9px 12px; border-bottom: 1px solid var(--rule-soft); cursor: pointer; display: grid; gap: 4px; }
|
||||
/* buttons: inject.css (body face, secondary ground, primary on hover and when pressed) */
|
||||
.filters { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0 0 1rem; }
|
||||
.fbtn, .drawer .close { font-family: var(--font-body); font-size: 14px; font-weight: 500; line-height: 1.4; padding: 3px 10px; border: 1px solid var(--border); background: var(--secondary); color: var(--foreground); border-radius: var(--radius); cursor: pointer; transition: all .15s ease; }
|
||||
.fbtn:hover, .fbtn[aria-pressed="true"], .drawer .close:hover { background: var(--primary); color: var(--primary-foreground); border-color: var(--primary); }
|
||||
.fbtn:active { transform: translate(1px, 1px); }
|
||||
.fbtn:hover .muted, .fbtn[aria-pressed="true"] .muted { color: inherit; opacity: .75; }
|
||||
button.dis { font-family: var(--font-body); font-size: 14px; padding: 3px 10px; border: 1px dashed var(--border); background: var(--muted); color: var(--muted-foreground); border-radius: var(--radius); cursor: not-allowed; }
|
||||
|
||||
.cms-list { display: grid; gap: 0; border: 1px solid var(--border); background: var(--card); }
|
||||
.cms-list > div { padding: 10px 12px; border-bottom: 1px solid var(--border); cursor: pointer; display: grid; gap: 4px; }
|
||||
.cms-list > div:last-child { border-bottom: 0; }
|
||||
.cms-list > div:hover { background: var(--hover); }
|
||||
.cms-list > div.sel { background: var(--info-bg); box-shadow: inset 3px 0 0 var(--link); }
|
||||
.cms-list q { font-size: 13.5px; quotes: "\201C" "\201D"; }
|
||||
.charts { display: grid; gap: 16px; margin-top: 18px; }
|
||||
.cms-list > div:hover { background: var(--secondary); }
|
||||
.cms-list > div.sel { background: color-mix(in srgb, var(--primary) 13%, var(--card)); box-shadow: inset 3px 0 0 var(--primary); }
|
||||
.cms-list q { font-size: 14px; quotes: "\201C" "\201D"; }
|
||||
.charts { display: grid; gap: 1.25rem; margin-top: 1.25rem; }
|
||||
.charts .chart-box svg { max-width: 760px; }
|
||||
select.fsel { font-family: var(--f-mono); font-size: 12px; padding: 4px 6px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg); border-radius: 0; }
|
||||
|
||||
/* timeline */
|
||||
.tl-year { display: grid; grid-template-columns: 86px minmax(0, 1fr); gap: 12px; border-top: 1px solid var(--rule); padding: 12px 0 6px; }
|
||||
.tl-year > h4 { font-family: var(--f-display); font-size: 1.3rem; margin: 0; line-height: 1.1; }
|
||||
.tl-year > h4 small { display: block; font-family: var(--f-mono); font-size: 10.5px; color: var(--muted); font-weight: 400; letter-spacing: .06em; margin-top: 4px; }
|
||||
.tl-year { display: grid; grid-template-columns: 86px minmax(0, 1fr); gap: 12px; border-top: 1px solid var(--border); padding: 12px 0 6px; }
|
||||
.tl-year > h4 { font-size: 1.3rem; margin: 0; line-height: 1.1; }
|
||||
.tl-year > h4 small { display: block; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted-foreground); font-weight: 400; letter-spacing: .06em; margin-top: 4px; }
|
||||
.tl-items { display: grid; gap: 8px; min-width: 0; }
|
||||
.tl-item { border: 1px solid var(--rule-soft); background: var(--surface); padding: 8px 12px; cursor: pointer; display: grid; gap: 4px; min-width: 0; }
|
||||
.tl-item:hover { border-color: var(--rule); }
|
||||
.tl-item.sel { border-color: var(--link); box-shadow: inset 3px 0 0 var(--link); }
|
||||
.tl-item { border: 1px solid var(--border); background: var(--card); padding: 9px 13px; cursor: pointer; display: grid; gap: 4px; min-width: 0; transition: border-color .15s ease; }
|
||||
.tl-item:hover { border-color: var(--primary); }
|
||||
.tl-item.sel { border-color: var(--primary); box-shadow: inset 3px 0 0 var(--primary); }
|
||||
.tl-item .top { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
|
||||
.tl-item .ttl { font-weight: 600; }
|
||||
.tl-item .sum { font-size: 13.5px; color: var(--fg-2); }
|
||||
.type { font-family: var(--f-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; padding: 1px 5px; border: 1px solid var(--rule); color: var(--fg-2); white-space: nowrap; }
|
||||
.type.lineage { border-color: var(--rule); }
|
||||
.tl-item .sum { font-size: 14px; color: var(--secondary-foreground); }
|
||||
.type { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; padding: 1px 5px; border: 1px solid var(--border); color: var(--secondary-foreground); background: var(--secondary); white-space: nowrap; }
|
||||
.type.origin { border-color: var(--s-alt); }
|
||||
.type.valuation { border-color: var(--s-final); }
|
||||
.type.cms { border-color: var(--link); background: var(--info-bg); }
|
||||
.type.link { border-color: var(--ok); }
|
||||
.type.cms { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 8%, var(--card)); }
|
||||
.type.link { border-color: var(--chart-4); }
|
||||
|
||||
/* story strip */
|
||||
.steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0; border: 1px solid var(--rule); background: var(--surface); }
|
||||
.steps li { padding: 12px 14px; border-right: 1px solid var(--rule-soft); min-width: 0; display: grid; gap: 6px; align-content: start; cursor: pointer; }
|
||||
.steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0; border: 1px solid var(--border); border-top: 3px solid var(--foreground); background: var(--card); }
|
||||
.steps li { padding: 12px 14px; border-right: 1px solid var(--border); min-width: 0; display: grid; gap: 6px; align-content: start; cursor: pointer; }
|
||||
.steps li:last-child { border-right: 0; }
|
||||
.steps li:hover { background: var(--hover); }
|
||||
.steps .n { font-family: var(--f-mono); font-size: 11px; color: var(--muted); letter-spacing: .08em; }
|
||||
.steps .h { font-family: var(--f-display); font-weight: 700; font-size: 1.02rem; line-height: 1.25; }
|
||||
.steps q { font-size: 13.5px; color: var(--fg-2); quotes: "\201C" "\201D"; }
|
||||
.steps li:hover { background: var(--secondary); }
|
||||
.steps li.sel { box-shadow: inset 0 -3px 0 var(--primary); }
|
||||
.steps .n { font-family: var(--font-mono); font-size: .7rem; text-transform: uppercase; color: var(--muted-foreground); letter-spacing: .12em; }
|
||||
.steps .h { font-family: var(--font-display); font-weight: 700; font-size: 1.02rem; line-height: 1.25; }
|
||||
.steps q { font-size: 14px; color: var(--secondary-foreground); quotes: "\201C" "\201D"; }
|
||||
|
||||
.chart { width: 100%; height: auto; display: block; }
|
||||
.chart text { fill: var(--fg-2); font-family: var(--f-mono); font-size: 11px; }
|
||||
.chart .ax { stroke: var(--rule); stroke-width: 1; }
|
||||
.chart .grid { stroke: var(--rule-soft); stroke-width: 1; }
|
||||
.chart-box { border: 1px solid var(--rule); background: var(--surface); padding: 12px 12px 6px; min-width: 0; }
|
||||
.tip { position: fixed; pointer-events: none; z-index: 30; background: var(--fg); color: var(--bg); font-family: var(--f-mono); font-size: 11.5px; padding: 5px 8px; max-width: 260px; }
|
||||
.chart text { fill: var(--muted-foreground); font-family: var(--font-mono); font-size: 11px; }
|
||||
.chart .ax { stroke: var(--muted-foreground); stroke-width: 1; }
|
||||
.chart .grid { stroke: var(--border); stroke-width: 1; }
|
||||
.chart-box { border: 1px solid var(--border); background: var(--card); padding: 1rem 1rem .5rem; min-width: 0; }
|
||||
.tip { position: fixed; pointer-events: none; z-index: 30; background: var(--primary); color: var(--primary-foreground); font-family: var(--font-mono); font-size: 11.5px; padding: 5px 8px; max-width: 260px; }
|
||||
|
||||
/* evidence drawer */
|
||||
.drawer { position: sticky; top: 0; max-height: 100vh; overflow: auto; padding: 22px 0 28px 20px; border-left: 1px solid var(--rule-soft); min-width: 0; }
|
||||
.drawer { position: sticky; top: 0; max-height: 100vh; overflow: auto; padding: 1rem 0 2rem 1.5rem; border-left: 1px solid var(--border); min-width: 0; }
|
||||
.drawer .d-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
|
||||
.drawer .close { display: none; font-family: var(--f-mono); font-size: 12px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg); padding: 3px 8px; cursor: pointer; }
|
||||
.drawer h3 { font-family: var(--f-display); font-size: 1.12rem; margin: 6px 0 8px; line-height: 1.25; }
|
||||
.ev-src { border-top: 1px solid var(--rule); padding-top: 10px; margin-top: 12px; display: grid; gap: 6px; }
|
||||
.ev-src .src-t { font-size: 13px; color: var(--fg-2); }
|
||||
.ev-src blockquote { margin: 0; padding: 8px 10px; background: var(--surface); border-left: 3px solid var(--rule); font-size: 14px; line-height: 1.5; }
|
||||
.drawer .close { display: none; }
|
||||
.drawer h3 { font-size: 1.25rem; margin: 6px 0 10px; line-height: 1.25; }
|
||||
.ev-src { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 12px; display: grid; gap: 6px; }
|
||||
.ev-src .src-t { font-size: 13px; color: var(--muted-foreground); }
|
||||
.ev-src blockquote { margin: 0; padding: 8px 12px; background: var(--card); border-left: 3px solid var(--primary); font-size: 14px; line-height: 1.55; }
|
||||
.ev-src blockquote.cmt { border-left-color: var(--s-prop); }
|
||||
.ev-dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 3px 10px; font-size: 13.5px; margin: 0; }
|
||||
.ev-dl dt { font-family: var(--f-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding-top: 3px; }
|
||||
.ev-dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 3px 10px; font-size: 14px; margin: 0; }
|
||||
.ev-dl dt { font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted-foreground); padding-top: 3px; }
|
||||
.ev-dl dd { margin: 0; min-width: 0; }
|
||||
|
||||
.sketch { border: 1px dashed var(--rule); padding: 14px; background: var(--surface); position: relative; }
|
||||
.sketch::before { content: "Sketch with real rows"; position: absolute; top: -9px; left: 10px; background: var(--bg); padding: 0 6px; font-family: var(--f-mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
|
||||
.ph { border: 1px dashed var(--rule); color: var(--muted); font-style: italic; padding: 6px 8px; font-size: 13.5px; }
|
||||
button.dis { font-family: var(--f-mono); font-size: 11px; padding: 3px 8px; border: 1px solid var(--rule); background: var(--surface-2); color: var(--muted); cursor: not-allowed; }
|
||||
.sketch { border: 1px dashed var(--border); padding: 1.25rem 1rem 1rem; background: var(--card); position: relative; }
|
||||
.sketch::before { content: "Sketch with real rows"; position: absolute; top: -9px; left: 10px; background: var(--background); padding: 0 6px; font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted-foreground); }
|
||||
.ph { border: 1px dashed var(--border); color: var(--muted-foreground); font-style: italic; padding: 6px 8px; font-size: 14px; }
|
||||
|
||||
/* footer: dashboard.css */
|
||||
.ftr { margin-top: 3rem; padding-top: 1rem; border-top: 3px solid var(--foreground); color: var(--muted-foreground); font-family: var(--font-mono); font-size: .65rem; letter-spacing: .08em; }
|
||||
.ftr .site { color: var(--chart-5); }
|
||||
.ftr a { color: inherit; }
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.shell { grid-template-columns: 214px minmax(0, 1fr); }
|
||||
.drawer { position: fixed; left: 0; right: 0; bottom: 0; top: auto; max-height: 52vh; z-index: 20; background: var(--bg); border-left: 0; border-top: 3px solid var(--ink-rule); padding: 14px max(16px, env(safe-area-inset-right, 0px)) calc(16px + env(safe-area-inset-bottom, 0px)) max(16px, env(safe-area-inset-left, 0px)); box-shadow: 0 -8px 24px rgba(0,0,0,.18); }
|
||||
.drawer { position: fixed; left: 0; right: 0; bottom: 0; top: auto; max-height: 52vh; z-index: 20; background: var(--background); border-left: 0; border-top: 3px solid var(--foreground); padding: 14px max(1.5rem, env(safe-area-inset-right, 0px)) calc(16px + env(safe-area-inset-bottom, 0px)) max(1.5rem, env(safe-area-inset-left, 0px)); box-shadow: 0 -8px 24px color-mix(in srgb, var(--foreground) 18%, transparent); }
|
||||
.drawer:not(.open) { display: none; }
|
||||
.drawer .close { display: inline-block; }
|
||||
body.drawer-open .main { padding-bottom: 54vh; }
|
||||
body.drawer-open .page { padding-bottom: 54vh; }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.page { padding-top: 2rem; }
|
||||
.shell { grid-template-columns: minmax(0, 1fr); }
|
||||
.rail { position: static; max-height: none; border-right: 0; border-bottom: 1px solid var(--rule-soft); padding: 14px 0; }
|
||||
.main { padding: 18px 0 64px; }
|
||||
.rail { position: static; max-height: none; border-right: 0; border-bottom: 1px solid var(--border); padding: 0 0 1rem; }
|
||||
.main { padding: 1.25rem 0 3rem; }
|
||||
.two { grid-template-columns: minmax(0, 1fr); }
|
||||
.tabs { flex-wrap: nowrap; min-width: max-content; }
|
||||
.nav-note { display: none; }
|
||||
.nav-theme span { display: none; }
|
||||
.tl-year { grid-template-columns: minmax(0, 1fr); gap: 6px; }
|
||||
.steps { grid-template-columns: minmax(0, 1fr); }
|
||||
.steps li { border-right: 0; border-bottom: 1px solid var(--rule-soft); }
|
||||
.steps li { border-right: 0; border-bottom: 1px solid var(--border); }
|
||||
.hdr-grid { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
|
||||
.hdr-grid > .eyebrow { padding-top: 8px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
|
||||
@media (max-width: 480px) {
|
||||
.nav-in { padding-inline: 1rem; }
|
||||
.nav-explore { display: none; } /* the logo already links to the Gitea home */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Glyphs: Tabler Icons outline (MIT), the set the dashboard sprite uses; stroke and size come from the
|
||||
.tile .icon and .nav-item rules. #fw-logo is the fhirworx logo (assets/icons/logo.svg), inlined by build.py. -->
|
||||
<svg class="sprite" aria-hidden="true" focusable="false">
|
||||
<!--__FW_LOGO__-->
|
||||
<symbol id="i-home" viewBox="0 0 24 24"><path d="M5 12l-2 0l9 -9l9 9l-2 0" /><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" /><path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" /></symbol>
|
||||
<symbol id="i-stethoscope" viewBox="0 0 24 24"><path d="M6 4h-1a2 2 0 0 0 -2 2v3.5a5.5 5.5 0 0 0 11 0v-3.5a2 2 0 0 0 -2 -2h-1" /><path d="M8 15a6 6 0 1 0 12 0v-3" /><path d="M11 3v2" /><path d="M6 3v2" /><path d="M18 10a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /></symbol>
|
||||
<symbol id="i-versions" viewBox="0 0 24 24"><path d="M10 7a2 2 0 0 1 2 -2h6a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-6a2 2 0 0 1 -2 -2l0 -10" /><path d="M7 7l0 10" /><path d="M4 8l0 8" /></symbol>
|
||||
<symbol id="i-timeline" viewBox="0 0 24 24"><path d="M4 16l6 -7l5 5l5 -6" /><path d="M14 14a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M9 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M3 16a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M19 8a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></symbol>
|
||||
<symbol id="i-scale" viewBox="0 0 24 24"><path d="M7 20l10 0" /><path d="M6 6l6 -1l6 1" /><path d="M12 3l0 17" /><path d="M9 12l-3 -6l-3 6a3 3 0 0 0 6 0" /><path d="M21 12l-3 -6l-3 6a3 3 0 0 0 6 0" /></symbol>
|
||||
<symbol id="i-chart-dots" viewBox="0 0 24 24"><path d="M3 3v18h18" /><path d="M7 9a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M17 7a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M12 15a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M10.16 10.62l2.34 2.88" /><path d="M15.088 13.328l2.837 -4.586" /></symbol>
|
||||
<symbol id="i-coin" viewBox="0 0 24 24"><path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M14.8 9a2 2 0 0 0 -1.8 -1h-2a2 2 0 1 0 0 4h2a2 2 0 1 1 0 4h-2a2 2 0 0 1 -1.8 -1" /><path d="M12 7v10" /></symbol>
|
||||
<symbol id="i-messages" viewBox="0 0 24 24"><path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" /><path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" /></symbol>
|
||||
<symbol id="i-books" viewBox="0 0 24 24"><path d="M5 5a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1l0 -14" /><path d="M9 5a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1l0 -14" /><path d="M5 8h4" /><path d="M9 16h4" /><path d="M13.803 4.56l2.184 -.53c.562 -.135 1.133 .19 1.282 .732l3.695 13.418a1.02 1.02 0 0 1 -.634 1.219l-.133 .041l-2.184 .53c-.562 .135 -1.133 -.19 -1.282 -.732l-3.695 -13.418a1.02 1.02 0 0 1 .634 -1.219l.133 -.041" /><path d="M14 9l4 -1" /><path d="M16 16l3.923 -.98" /></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008" /></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><path d="M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" /></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="nav"><div class="nav-in">
|
||||
<span class="brand-group"><a class="brand" href="https://git./*__DOMAIN__*//">fhirworx</a><a class="app" href="/" aria-current="page">lex</a></span>
|
||||
<a class="nav-item" href="https://git./*__DOMAIN__*//explore/repos">Explore</a>
|
||||
<span class="brand-group"><a class="brand" href="https://git./*__DOMAIN__*//"><svg role="img" aria-label="fhirworx logo"><use href="#fw-logo"/></svg>fhirworx</a><a class="app" href="/" aria-current="page">lex</a></span>
|
||||
<a class="nav-item nav-explore" href="https://git./*__DOMAIN__*//explore/repos">Explore</a>
|
||||
<span class="nav-item on">Dossier</span>
|
||||
<span class="nav-sp"></span>
|
||||
<span class="nav-item nav-note">Policy evidence · CMS payment and quality</span>
|
||||
<button class="nav-item nav-theme" type="button" id="theme-btn" aria-pressed="false" title="Switch to the fhirworx dark palette"><svg aria-hidden="true"><use href="#i-moon"/></svg><span>Dark</span></button>
|
||||
</div></header>
|
||||
<div class="banner" role="note"><div class="banner-in">
|
||||
<p class="banner-what"><strong class="lex-word">lex</strong> — Latin for <em>law</em>. A legal-epidemiology workbench for CMS payment and quality rules: for each code family it traces what the rules said, how they changed, who argued for and against each change, and what happened after — to inform predictions, policy work and comment letters.</p>
|
||||
<p class="banner-status"><span class="status-pill">Preview</span><span>Static snapshot of live data, built /*__BUILT__*/. Not the finished app.</span><span class="muted">Every value below comes from a stored row; each carries its source.</span></p>
|
||||
</div></div>
|
||||
|
||||
<div class="page">
|
||||
<div class="masthead">
|
||||
<div class="rule-line"></div>
|
||||
<div class="masthead-row">
|
||||
<div class="masthead-brand"><svg class="masthead-logo" role="img" aria-label="fhirworx logo"><use href="#fw-logo"/></svg><h1>lex</h1></div>
|
||||
<span class="masthead-eyebrow">lex./*__DOMAIN__*/</span>
|
||||
</div>
|
||||
<div class="masthead-body">
|
||||
<p class="tagline">Latin for <em>law</em>. A legal-epidemiology workbench for CMS payment and quality rules: for each code family it traces what the rules said, how they changed, who argued for and against each change, and what happened after — to inform predictions, policy work and comment letters.</p>
|
||||
<div class="health health-warn" role="note"><span class="health-k">Preview</span><span class="health-v">Static snapshot of live data, built /*__BUILT__*/. Not the finished app.</span><span class="status-note">Every value below comes from a stored row; each carries its source.</span></div>
|
||||
</div>
|
||||
<div class="rule-line-thin"></div>
|
||||
</div>
|
||||
|
||||
<div class="shell">
|
||||
<aside class="rail" aria-label="The book">
|
||||
@@ -327,7 +403,7 @@ button.dis { font-family: var(--f-mono); font-size: 11px; padding: 3px 8px; bord
|
||||
<input id="q" type="search" placeholder="Family, code or name" autocomplete="off">
|
||||
<details id="toc-d" open>
|
||||
<summary><h2>The book</h2></summary>
|
||||
<div class="eyebrow" style="margin-top:4px">Reviewed</div>
|
||||
<div class="eyebrow">Reviewed</div>
|
||||
<ul class="toc" id="toc-rev"></ul>
|
||||
<div class="eyebrow" style="margin-top:12px">Planned · primary and home-based care</div>
|
||||
<ul class="toc" id="toc-plan"></ul>
|
||||
@@ -336,7 +412,7 @@ button.dis { font-family: var(--f-mono); font-size: 11px; padding: 3px 8px; bord
|
||||
</aside>
|
||||
|
||||
<main class="main" id="main">
|
||||
<div class="mast" id="mast"></div>
|
||||
<div id="mast"></div>
|
||||
<div class="tabs-wrap"><nav class="tabs" id="tabs" aria-label="Views"></nav></div>
|
||||
<section class="view" data-view="contents" hidden></section>
|
||||
<section class="view" data-view="dossier" hidden></section>
|
||||
@@ -354,6 +430,9 @@ button.dis { font-family: var(--f-mono); font-size: 11px; padding: 3px 8px; bord
|
||||
|
||||
<aside class="drawer" id="drawer" aria-label="Evidence" aria-live="polite"></aside>
|
||||
</div>
|
||||
|
||||
<footer class="ftr"><span class="site">lex./*__DOMAIN__*/</span> — legal-epidemiology workbench — snapshot /*__BUILT__*/ — part of <a href="https://dashboard./*__DOMAIN__*//">fhirworx</a></footer>
|
||||
</div>
|
||||
<div class="tip" id="tip" hidden></div>
|
||||
|
||||
<script id="data" type="application/json">/*__DATA__*/{}</script>
|
||||
@@ -370,6 +449,9 @@ const int = (v) => (v == null ? "—" : Math.round(v).toLocaleString("en-US"));
|
||||
const uniq = (a) => Array.from(new Set(a));
|
||||
const M = D.meta;
|
||||
const BASE = M.base, PRED = M.pred, LINKED = M.linked;
|
||||
const HOST = "lex./*__DOMAIN__*/";
|
||||
const icon = (id) => `<span class="icon" aria-hidden="true"><svg><use href="#${id}"/></svg></span>`;
|
||||
const health = (k, v, state) => `<span class="health health-${state}"><span class="health-k">${esc(k)}</span><span class="health-v">${esc(v)}</span></span>`;
|
||||
|
||||
function compact(codes) {
|
||||
const cs = uniq(codes).sort();
|
||||
@@ -544,15 +626,14 @@ function renderTabs() {
|
||||
}
|
||||
function renderMast(view) {
|
||||
if (view === "contents") {
|
||||
$("#mast").innerHTML = `<div class="rule"></div><div class="mast-row"><span class="eyebrow">The book · CMS payment and quality regulation</span></div>
|
||||
<h1>Contents</h1><div class="rule-thin"></div>`;
|
||||
$("#mast").innerHTML = `<div class="section-header"><span class="section-eyebrow">The book · CMS payment and quality regulation</span>
|
||||
<h2>Contents</h2></div>`;
|
||||
return;
|
||||
}
|
||||
$("#mast").innerHTML = `<div class="rule"></div>
|
||||
<div class="mast-row"><span class="eyebrow">${esc(M.family)} · code family dossier · phase 1</span><span class="tag">Reviewed dossier</span></div>
|
||||
<h1>${esc(M.name)}</h1>
|
||||
<div class="small muted">Home or residence E/M visits ${compact(BASE)} · domiciliary codes deleted for CY2023 · linked add-on G0318</div>
|
||||
<div class="rule-thin"></div>`;
|
||||
$("#mast").innerHTML = `<div class="section-header">
|
||||
<div class="section-row"><span class="section-eyebrow">${esc(M.family)} · code family dossier · phase 1</span><span class="badge">Reviewed dossier</span></div>
|
||||
<h2>${esc(M.name)}</h2>
|
||||
<p class="section-sub">Home or residence E/M visits ${compact(BASE)} · domiciliary codes deleted for CY2023 · linked add-on G0318</p></div>`;
|
||||
}
|
||||
|
||||
/* ── #contents ─────────────────────────────────────────────────── */
|
||||
@@ -573,22 +654,35 @@ function renderContents() {
|
||||
const lr = latestRule;
|
||||
const sec = ["Timeline", "Valuation CY2008, CY2023", `Payment ${M.rvu_years[0]}–${M.rvu_years[1]}`, "Politics CY2023 docket", "Sources"];
|
||||
const homeId = ev({ kind: "family", title: M.name, fields: [["Key", esc(M.family)], ["Tier", "Reviewed"], ["Codes", esc(compact(BASE))], ["Deleted CY2023", esc(compact(PRED))]], extra: dm.map((d) => `<div><b>${esc(d[0])}</b>: ${esc(d[1])} ${citeA(d[2])}</div>`) });
|
||||
// one tile per family, as the dashboard's service tiles: icon plate, eyebrow, title, description, health, address, stripe
|
||||
let h = `<p class="lede">One dossier per code family. Reviewed dossiers are hand-curated and pass the review queue; everything else is machine-built and labeled so.</p>
|
||||
<div class="tbl-wrap"><table class="tbl"><thead><tr><th style="min-width:210px">Family</th><th>Tier</th><th>Phase</th><th>Value domains</th><th>Coverage</th><th>Last rule activity</th><th>Open comment period</th></tr></thead><tbody>
|
||||
<tr ${evAttr(homeId)}><td><a href="#dossier"><b>${esc(M.name)}</b></a><div class="small muted mono">${esc(compact(BASE))}</div></td>
|
||||
<td><span class="pill final">Reviewed</span></td><td class="num">1</td>
|
||||
<td><div class="codes">${dm.map((d) => `<span class="pill" title="${esc(d[1])}">${esc(d[0])}</span>`).join("")}</div></td>
|
||||
<td class="small" style="min-width:150px">${sec.map((x) => `<div>${esc(x)}</div>`).join("")}<div class="muted">gaps stated on the dossier</div></td>
|
||||
<td class="small">${esc(lr.label)}<br>${lr.fr ? `<a class="cite" href="${esc(lr.fr.html)}" target="_blank" rel="noopener">${esc(lr.cite)}</a>` : ""} <span class="muted">${esc(lr.date)}</span></td>
|
||||
<td class="small muted">Tracker comes in S6</td></tr>
|
||||
<tr class="grp-row"><td colspan="7">Planned · reviewed tier · primary care and home-based care</td></tr>`;
|
||||
<div class="sec-head"><span class="section-eyebrow">Reviewed tier · hand-curated</span><h3>Built</h3></div>
|
||||
<div class="tiles"><div class="tile" ${evAttr(homeId)}><span class="badge">Reviewed</span>${icon("i-home")}
|
||||
<div class="tile-eyebrow">${esc(M.family)} · phase 1</div>
|
||||
<div class="tile-title"><a href="#dossier">${esc(M.name)}</a></div>
|
||||
<div class="codes">${BASE.map((c) => `<span class="code">${c}</span>`).join("")}</div>
|
||||
<dl>
|
||||
<dt>Value domains</dt><dd><div class="codes">${dm.map((d) => `<span class="pill" title="${esc(d[1])}">${esc(d[0])}</span>`).join("")}</div></dd>
|
||||
<dt>Coverage</dt><dd>${sec.map(esc).join(" · ")}<div class="muted">gaps stated on the dossier</div></dd>
|
||||
<dt>Last rule</dt><dd>${esc(lr.label)} ${lr.fr ? `<a class="cite" href="${esc(lr.fr.html)}" target="_blank" rel="noopener">${esc(lr.cite)}</a>` : ""} <span class="muted">${esc(lr.date)}</span></dd>
|
||||
<dt>Comment period</dt><dd class="muted">Tracker comes in S6</dd>
|
||||
</dl>
|
||||
${health("status", "built · reviewed", "ok")}
|
||||
<span class="tile-port">${esc(HOST)}/#dossier</span><div class="stripe"></div></div></div>
|
||||
<div class="sec-head"><span class="section-eyebrow">Reviewed tier · primary care and home-based care</span><h3>Planned</h3></div>
|
||||
<div class="tiles">`;
|
||||
PLANNED.forEach(([n, c]) => {
|
||||
h += `<tr><td>${esc(n)}<div class="small muted mono">${esc(c || "codes set at curation")}</div></td><td><span class="pill soon">Planned</span></td><td class="num">1</td><td colspan="4" class="muted small">Not built. Membership and value domains are set at curation.</td></tr>`;
|
||||
h += `<div class="tile">${icon("i-stethoscope")}<div class="tile-eyebrow">Planned · phase 1</div><div class="tile-title">${esc(n)}</div>
|
||||
<p class="tile-desc"><span class="mono small">${esc(c || "codes set at curation")}</span><br>Not built. Membership and value domains are set at curation.</p>
|
||||
${health("status", "planned", "unknown")}<div class="stripe"></div></div>`;
|
||||
});
|
||||
h += `<tr class="grp-row"><td colspan="7">Machine-built tier</td></tr>
|
||||
<tr><td colspan="7" class="small">Everything else: ${M.derived_families.toLocaleString("en-US")} derived code families, ${M.derived_single.toLocaleString("en-US")} of them single codes that fold into their CPT heading. Built in nightly batches and labeled “machine-built, not reviewed”, with gaps stated.</td></tr>
|
||||
</tbody></table></div>
|
||||
<div class="filters" style="margin-top:14px"><button class="dis" disabled>Propose a new family</button><span class="small muted">Goes to curation. Comes with S2.</span></div>`;
|
||||
h += `</div>
|
||||
<div class="sec-head"><span class="section-eyebrow">Everything else</span><h3>Machine-built tier</h3></div>
|
||||
<div class="tiles"><div class="tile">${icon("i-versions")}<div class="tile-eyebrow">Machine-built · not reviewed</div>
|
||||
<div class="tile-title">${M.derived_families.toLocaleString("en-US")} derived code families</div>
|
||||
<p class="tile-desc">${M.derived_single.toLocaleString("en-US")} of them single codes that fold into their CPT heading. Built in nightly batches and labeled “machine-built, not reviewed”, with gaps stated.</p>
|
||||
${health("status", "nightly batches", "unknown")}<div class="stripe"></div></div></div>
|
||||
<div class="filters" style="margin-top:1.25rem"><button class="dis" disabled>Propose a new family</button><span class="small muted">Goes to curation. Comes with S2.</span></div>`;
|
||||
$('[data-view="contents"]').innerHTML = h;
|
||||
}
|
||||
|
||||
@@ -626,35 +720,37 @@ function renderDossier() {
|
||||
const nRules = ruleSources.length;
|
||||
const cfrG = uniq(D.guidance.filter((g) => g.kind === "cfr" && !g.unresolved).map((g) => g.locator));
|
||||
const iomG = uniq(D.guidance.filter((g) => g.kind === "iom").map((g) => g.locator));
|
||||
const cyRounds = uniq(D.rounds.map((r) => r.cy)).sort((a, b) => a - b).map((y) => "CY" + y).join(" · ");
|
||||
// [anchor, title, glyph, facts, [health label, value, state]]
|
||||
const tiles = [
|
||||
["timeline", "Timeline", [
|
||||
["timeline", "Timeline", "i-timeline", [
|
||||
factLi(`${tlCount} dated entries from lineage, origin, valuation rounds, CMS's comment summaries and links, CY${earliestCY}–CY${latestRule.cy}.`),
|
||||
factLi(`Latest: ${esc(latestRule.label)}, B1 and B2 modifiers finalized.`, [b1.cite]),
|
||||
]],
|
||||
["timeline", "Origin", [
|
||||
], ["entries", `${tlCount} · CY${earliestCY}–CY${latestRule.cy}`, "ok"]],
|
||||
["timeline", "Origin", "i-scale", [
|
||||
factLi(`CPT Editorial Panel, February 2021 meeting: domiciliary codes ${esc(compact(predFr.filter((c) => c !== "99343")))} and home code 99343 deleted, effective 2023.`, [C1672]),
|
||||
factLi(`Statute cited: ${statutes.map(esc).join("; ")}.`, statCite),
|
||||
factLi(`Regulation cited: ${regs.length} CFR sections (${regs.map(esc).join(", ")}).`),
|
||||
]],
|
||||
["valuation", "Valuation", [
|
||||
], ["cited", `${statutes.length} statutes · ${regs.length} CFR sections`, "ok"]],
|
||||
["valuation", "Valuation", "i-chart-dots", [
|
||||
factLi(`CY2023: the RUC recommended the survey 25th percentile for ${pctCount(r23f, "25th")} codes and the median for ${esc(r23f.filter((r) => r.pct === "median").map((r) => r.code).join(", "))}.`, [C1676]),
|
||||
factLi(`CMS proposed the RUC values for ${r23p.length} of 8 codes and finalized them as proposed: final equals the RUC recommendation for ${acceptN(r23f)} of ${r23f.length}.`, [C878, C1685]),
|
||||
factLi(`CY2008: CMS proposed the RUC values (“Agree”), then refined all ${r08f.length} in the final rule; final equals the RUC recommendation for ${acceptN(r08f)} of ${r08f.length}.`, [C08p, C2242]),
|
||||
]],
|
||||
["payment", "Payment", [
|
||||
], ["rounds", cyRounds, "ok"]],
|
||||
["payment", "Payment", "i-coin", [
|
||||
factLi(`99350 national non-facility payment: ${money(p15 && p15.pay_nf)} (CY2015 final) → ${money(p27 && p27.pay_nf)} (CY2027 final); CY2027 proposed ${money(p27p && p27p.pay_nf)}.`, []).replace("</li>", [p15, p27, p27p].filter(Boolean).map((r) => ` <a class="cite" href="${esc(r.url)}" target="_blank" rel="noopener" title="${esc(r.label)}">${esc(r.cite)}</a>`).join("") + "</li>"),
|
||||
factLi(`Conversion factor CY2027 final $${cf27.cf.toFixed(4)}.`).replace("</li>", ` <a class="cite" href="https://www.federalregister.gov/citation/${cf27.cite.replace(/ /g, "-")}" target="_blank" rel="noopener">${esc(cf27.cite)}</a></li>`),
|
||||
factLi(`Final RVU files ${M.rvu_years[0]}–${M.rvu_years[1]}; proposed RVUs for the CY2026 and CY2027 proposed rules.`),
|
||||
]],
|
||||
["politics", "Politics", [
|
||||
], ["RVU files", `${M.rvu_years[0]}–${M.rvu_years[1]}`, "ok"]],
|
||||
["politics", "Politics", "i-messages", [
|
||||
factLi(`${D.commenters.length} commenters identified on the CY2023 docket ${linkA("https://www.regulations.gov/docket/" + M.docket, M.docket)}; ${withPos} took a position on at least one event <span class="pill prelim">preliminary</span>.`),
|
||||
factLi(`CMS's summary: “${esc(firstSentence(D.paras["PETM225D:1679"] || ""))}”`, [C1679]),
|
||||
factLi(`${D.summaries.length} CMS Comment/Response pairs about the family, CY${Math.min(...D.summaries.map((s) => s.cy))}–CY${Math.max(...D.summaries.map((s) => s.cy))}; ${cms23.length} in the CY2023 final rule.`),
|
||||
]],
|
||||
["sources", "Guidance and sources", [
|
||||
], ["stances", "preliminary", "warn"]],
|
||||
["sources", "Guidance and sources", "i-books", [
|
||||
factLi(`CFR pointers: ${cfrG.length} locators, e.g. ${esc(cfrG.slice(0, 2).join(", "))}. Manual pointers: ${esc(iomG.join(", "))}.`),
|
||||
factLi(`${nRules} Federal Register rules with paragraph anchors, ${D.commenters.length} public comments, ${Object.values(D.sources).filter((s) => s.type === "source" && /Medicare Physician/.test(s.title)).length} utilization files.`),
|
||||
]],
|
||||
], ["on file", `${nRules} rules · ${D.commenters.length} comments`, "ok"]],
|
||||
];
|
||||
const gaps = [
|
||||
"Federal Register PFS rules before CY2002 are not captured yet.",
|
||||
@@ -667,7 +763,8 @@ function renderDossier() {
|
||||
`${D.review.length} extracted rows are held for review (see Review).`,
|
||||
];
|
||||
$('[data-view="dossier"]').innerHTML = hdr + `
|
||||
<div class="sec"><h3>Sections</h3><div class="tiles">${tiles.map(([href, t, facts]) => `<div class="tile"><div class="eyebrow">Section</div><div class="t-title"><a href="#${href}">${t}</a></div><ul class="facts">${facts.join("")}</ul></div>`).join("")}</div></div>
|
||||
<div class="sec-head"><span class="section-eyebrow">Dossier</span><h3>Sections</h3></div>
|
||||
<div class="tiles">${tiles.map(([href, t, glyph, facts, [k, v, st]]) => `<div class="tile">${st === "warn" ? '<span class="badge new">Preliminary</span>' : ""}${icon(glyph)}<div class="tile-eyebrow">Section</div><div class="tile-title"><a href="#${href}">${t}</a></div><ul class="facts tile-desc">${facts.join("")}</ul>${health(k, v, st)}<span class="tile-port">${esc(HOST)}/#${href}</span><div class="stripe"></div></div>`).join("")}</div>
|
||||
<div class="sec panel warn"><h3>Gaps</h3><ul class="gaps">${gaps.map((g) => `<li>${esc(g)}</li>`).join("")}</ul></div>
|
||||
<div class="filters" style="margin-top:14px"><button class="dis" disabled>Flag this</button><span class="small muted">Sends a row to the review queue. Comes with S2.</span></div>`;
|
||||
}
|
||||
@@ -799,7 +896,7 @@ function dotPlot() {
|
||||
for (let v = 0; v <= 4; v++) s += `<line class="grid" x1="${X(v)}" x2="${X(v)}" y1="${top - 8}" y2="${H - 26}"/><text x="${X(v)}" y="${H - 10}" text-anchor="middle">${v.toFixed(1)}</text>`;
|
||||
s += `<text x="${left}" y="${H - 10}" text-anchor="end" dx="-8">work RVU</text>`;
|
||||
// legend
|
||||
s += `<g transform="translate(${left},14)"><circle cx="6" cy="0" r="4.5" fill="var(--surface)" stroke="var(--s-ruc)" stroke-width="2"/><text x="16" y="4">CY2008 RUC recommendation</text>
|
||||
s += `<g transform="translate(${left},14)"><circle cx="6" cy="0" r="4.5" fill="var(--card)" stroke="var(--s-ruc)" stroke-width="2"/><text x="16" y="4">CY2008 RUC recommendation</text>
|
||||
<rect x="200" y="-5" width="10" height="10" fill="var(--s-prop)"/><text x="216" y="4">CY2008 final</text>
|
||||
<circle cx="316" cy="0" r="5" fill="var(--s-final)"/><text x="326" y="4">CY2023 final (= RUC recommendation)</text></g>`;
|
||||
codes.forEach((c, i) => {
|
||||
@@ -811,10 +908,10 @@ function dotPlot() {
|
||||
const b = D.rounds.find((r) => r.code === c && r.cy === 2023 && r.kind === "final");
|
||||
if (a && a.ruc != null && a.fin != null) {
|
||||
s += `<line x1="${X(a.ruc)}" x2="${X(a.fin)}" y1="${y}" y2="${y}" stroke="var(--s-prop)" stroke-width="2"/>`;
|
||||
s += `<circle cx="${X(a.ruc)}" cy="${y}" r="4.5" fill="var(--surface)" stroke="var(--s-ruc)" stroke-width="2" data-tip="${c} · CY2008 RUC recommendation ${n2(a.ruc)}"/>`;
|
||||
s += `<rect x="${X(a.fin) - 5}" y="${y - 5}" width="10" height="10" fill="var(--s-prop)" stroke="var(--surface)" stroke-width="1.5" data-tip="${c} · CY2008 final ${n2(a.fin)} (refined)"/>`;
|
||||
s += `<circle cx="${X(a.ruc)}" cy="${y}" r="4.5" fill="var(--card)" stroke="var(--s-ruc)" stroke-width="2" data-tip="${c} · CY2008 RUC recommendation ${n2(a.ruc)}"/>`;
|
||||
s += `<rect x="${X(a.fin) - 5}" y="${y - 5}" width="10" height="10" fill="var(--s-prop)" stroke="var(--card)" stroke-width="1.5" data-tip="${c} · CY2008 final ${n2(a.fin)} (refined)"/>`;
|
||||
}
|
||||
if (b && b.fin != null) s += `<circle cx="${X(b.fin)}" cy="${y}" r="5.5" fill="var(--s-final)" stroke="var(--surface)" stroke-width="2" data-tip="${c} · CY2023 final ${n2(b.fin)} · RUC ${n2(b.ruc)} (${b.pct} percentile)"/>`;
|
||||
if (b && b.fin != null) s += `<circle cx="${X(b.fin)}" cy="${y}" r="5.5" fill="var(--s-final)" stroke="var(--card)" stroke-width="2" data-tip="${c} · CY2023 final ${n2(b.fin)} · RUC ${n2(b.ruc)} (${b.pct} percentile)"/>`;
|
||||
});
|
||||
return s + `</svg>`;
|
||||
}
|
||||
@@ -896,10 +993,10 @@ function payChart(code) {
|
||||
let path = "", prev = null;
|
||||
fin.forEach((p) => { path += (prev != null && p.cy === prev + 1 ? "L" : "M") + X(p.cy).toFixed(1) + " " + Y(p.pay_nf).toFixed(1); prev = p.cy; });
|
||||
s += `<path d="${path}" fill="none" stroke="var(--s-final)" stroke-width="2"/>`;
|
||||
fin.forEach((p) => { s += `<circle cx="${X(p.cy)}" cy="${Y(p.pay_nf)}" r="4" fill="var(--s-final)" stroke="var(--surface)" stroke-width="2" data-tip="${code} · CY${p.cy} final · ${money(p.pay_nf)} · CF $${p.cf.toFixed(4)}"/>`; });
|
||||
fin.forEach((p) => { s += `<circle cx="${X(p.cy)}" cy="${Y(p.pay_nf)}" r="4" fill="var(--s-final)" stroke="var(--card)" stroke-width="2" data-tip="${code} · CY${p.cy} final · ${money(p.pay_nf)} · CF $${p.cf.toFixed(4)}"/>`; });
|
||||
pro.forEach((p) => { s += `<circle cx="${X(p.cy)}" cy="${Y(p.pay_nf)}" r="6.5" fill="none" stroke="var(--s-prop)" stroke-width="2" data-tip="${code} · CY${p.cy} proposed · ${money(p.pay_nf)} · CF $${p.cf.toFixed(4)}"/>`; });
|
||||
const last = fin[fin.length - 1];
|
||||
if (last) s += `<text x="${X(last.cy) + 10}" y="${Y(last.pay_nf) + 4}" style="fill:var(--fg)">${money(last.pay_nf)}</text>`;
|
||||
if (last) s += `<text x="${X(last.cy) + 10}" y="${Y(last.pay_nf) + 4}" style="fill:var(--foreground)">${money(last.pay_nf)}</text>`;
|
||||
return s + `</svg><div class="legend"><span><span class="sw" style="background:var(--s-final)"></span>Final (Addendum B × CF)</span><span><span class="sw" style="border:2px solid var(--s-prop);border-radius:50%"></span>Proposed (proposed-rule Addendum B × proposed CF)</span></div>`;
|
||||
}
|
||||
function volChart(code) {
|
||||
@@ -1014,7 +1111,7 @@ function renderPolitics() {
|
||||
|
||||
// stakeholder matrix: organization type × specialty, events across
|
||||
const types = uniq(D.commenters.map((c) => c.type)).sort((a, b) => D.commenters.filter((c) => c.type === b).length - D.commenters.filter((c) => c.type === a).length);
|
||||
let mx = `<div class="tbl-wrap"><table class="tbl"><thead><tr><th>Commenter</th><th>Name as posted</th><th>Specialty</th>${evs.map((k) => `<th title="${esc(EVENT_LABEL[evSlug(k)])}">${esc(evSlug(k) === "G0318" ? "G0318" : EVENT_LABEL[evSlug(k)].split(" ")[0])}</th>`).join("")}<th>Comment</th></tr></thead><tbody>`;
|
||||
let mx = `<div class="tbl-wrap"><table class="tbl"><thead><tr><th style="min-width:190px">Commenter</th><th>Name as posted</th><th>Specialty</th>${evs.map((k) => `<th title="${esc(EVENT_LABEL[evSlug(k)])}">${esc(evSlug(k) === "G0318" ? "G0318" : EVENT_LABEL[evSlug(k)].split(" ")[0])}</th>`).join("")}<th>Comment</th></tr></thead><tbody>`;
|
||||
types.forEach((t) => {
|
||||
const cs = D.commenters.filter((c) => c.type === t).sort((a, b) => (a.specialty || "~").localeCompare(b.specialty || "~") || (a.org || a.reg_org).localeCompare(b.org || b.reg_org));
|
||||
mx += `<tr class="grp-row"><td colspan="${evs.length + 4}">${esc(TYPE_LABEL[t] || t)} · ${cs.length}</td></tr>`;
|
||||
@@ -1117,7 +1214,7 @@ function renderEvaluate() {
|
||||
if ((d.y - yrs[0]) % 2 === 1 || d.y === yrs[yrs.length - 1]) s += `<text x="${X(d.y) + bw / 2}" y="${H - 8}" text-anchor="middle">${d.y}</text>`;
|
||||
});
|
||||
const xe = X(2023);
|
||||
s += `<line x1="${xe}" x2="${xe}" y1="${T - 4}" y2="${Y(0)}" stroke="var(--fg)" stroke-width="1.5" stroke-dasharray="4 3"/><text x="${xe - 6}" y="${T + 8}" text-anchor="end" style="fill:var(--fg)">exposure: CY2023</text>`;
|
||||
s += `<line x1="${xe}" x2="${xe}" y1="${T - 4}" y2="${Y(0)}" stroke="var(--foreground)" stroke-width="1.5" stroke-dasharray="4 3"/><text x="${xe - 6}" y="${T + 8}" text-anchor="end" style="fill:var(--foreground)">exposure: CY2023</text>`;
|
||||
s += `<line class="ax" x1="${L}" x2="${W - R}" y1="${Y(0)}" y2="${Y(0)}"/></svg>`;
|
||||
const exps = [
|
||||
["CY2023 · domiciliary codes deleted and merged into the home codes", C1672],
|
||||
@@ -1192,6 +1289,23 @@ function route() {
|
||||
if (on && on.scrollIntoView && h) on.scrollIntoView({ block: "nearest", inline: "nearest" });
|
||||
}
|
||||
|
||||
/* ── theme: light by default; the fhirworx dark palette on request ── */
|
||||
const themeBtn = $("#theme-btn");
|
||||
function syncTheme() {
|
||||
const dark = document.documentElement.getAttribute("data-theme") === "dark";
|
||||
themeBtn.setAttribute("aria-pressed", String(dark));
|
||||
themeBtn.title = dark ? "Switch to the fhirworx light palette" : "Switch to the fhirworx dark palette";
|
||||
themeBtn.querySelector("use").setAttribute("href", dark ? "#i-sun" : "#i-moon");
|
||||
themeBtn.querySelector("span").textContent = dark ? "Light" : "Dark";
|
||||
}
|
||||
themeBtn.addEventListener("click", () => {
|
||||
const dark = document.documentElement.getAttribute("data-theme") !== "dark";
|
||||
if (dark) document.documentElement.setAttribute("data-theme", "dark"); else document.documentElement.removeAttribute("data-theme");
|
||||
try { localStorage.setItem("lex-theme", dark ? "dark" : "light"); } catch (e) {}
|
||||
syncTheme();
|
||||
});
|
||||
syncTheme();
|
||||
|
||||
buildTimeline();
|
||||
renderRail();
|
||||
renderTabs();
|
||||
|
||||
Reference in New Issue
Block a user