feat: port fhirworx theme — editorial redesign based on hti5.org
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
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
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.
This commit is contained in:
@@ -228,7 +228,7 @@ ROOT_KEY=$KEY uv run python -m api.auth derive $COMMIT_SHA --redact
|
||||
|
||||
## Design system
|
||||
|
||||
Theme: **loch** (deep-navy). Traefik's `inject-loch` middleware rewrites HTML to inject `loch.css` and the favicon. Per-service CSS in `assets/css/`. Altair chart theme in `assets/nature.py` (Nature-conformant, accessible palette, IBM Plex Mono headings).
|
||||
Theme: **fhirworx** (federal editorial). Traefik's `inject-fhirworx` middleware rewrites HTML to inject `fhirworx.css` and the favicon. Per-service CSS in `assets/css/`. Altair chart theme in `assets/fhirworx.py` (federal palette, Source Serif 4 / Playfair Display / JetBrains Mono).
|
||||
|
||||
## Data lakehouse
|
||||
|
||||
@@ -491,7 +491,7 @@ stack/
|
||||
│ └── zot/
|
||||
├── infra/ Service configs and Dockerfiles
|
||||
│ ├── images/ All Dockerfiles (api, notebooks, zotero, docs, mc)
|
||||
│ ├── traefik/ Reverse proxy + loch CSS injection
|
||||
│ ├── traefik/ Reverse proxy + fhirworx CSS injection
|
||||
│ ├── grafana/ Dashboards and datasource provisioning
|
||||
│ ├── prometheus/ Metrics collection
|
||||
│ ├── loki/ Log aggregation
|
||||
@@ -499,7 +499,7 @@ stack/
|
||||
├── assets/ Branding, styles, generated artifacts
|
||||
│ ├── css/ Per-service CSS (dashboard, gitea, marimo, woodpecker)
|
||||
│ ├── icons/ Favicons, logos, coverage badge
|
||||
│ └── nature.py Nature-conformant chart palette
|
||||
│ └── fhirworx.py fhirworx chart palette and Altair theme
|
||||
├── cloud/ Cloud provider deployment guides
|
||||
│ ├── self-hosted/ Docker Compose (default)
|
||||
│ ├── aws/ S3, RDS, Glue, ECS
|
||||
|
||||
@@ -221,7 +221,7 @@ ROOT_KEY=$KEY uv run python -m api.auth derive $COMMIT_SHA --redact
|
||||
|
||||
## Design system
|
||||
|
||||
Theme: **loch** (deep-navy). Traefik's `inject-loch` middleware rewrites HTML to inject `loch.css` and the favicon. Per-service CSS in `assets/css/`. Altair chart theme in `assets/nature.py` (Nature-conformant, accessible palette, IBM Plex Mono headings).
|
||||
Theme: **fhirworx** (federal editorial). Traefik's `inject-fhirworx` middleware rewrites HTML to inject `fhirworx.css` and the favicon. Per-service CSS in `assets/css/`. Altair chart theme in `assets/fhirworx.py` (federal palette, Source Serif 4 / Playfair Display / JetBrains Mono).
|
||||
|
||||
## Data lakehouse
|
||||
|
||||
@@ -472,7 +472,7 @@ stack/
|
||||
{% endfor %}
|
||||
├── infra/ Service configs and Dockerfiles
|
||||
│ ├── images/ All Dockerfiles (api, notebooks, zotero, docs, mc)
|
||||
│ ├── traefik/ Reverse proxy + loch CSS injection
|
||||
│ ├── traefik/ Reverse proxy + fhirworx CSS injection
|
||||
│ ├── grafana/ Dashboards and datasource provisioning
|
||||
│ ├── prometheus/ Metrics collection
|
||||
│ ├── loki/ Log aggregation
|
||||
@@ -480,7 +480,7 @@ stack/
|
||||
├── assets/ Branding, styles, generated artifacts
|
||||
│ ├── css/ Per-service CSS (dashboard, gitea, marimo, woodpecker)
|
||||
│ ├── icons/ Favicons, logos, coverage badge
|
||||
│ └── nature.py Nature-conformant chart palette
|
||||
│ └── fhirworx.py fhirworx chart palette and Altair theme
|
||||
├── cloud/ Cloud provider deployment guides
|
||||
│ ├── self-hosted/ Docker Compose (default)
|
||||
│ ├── aws/ S3, RDS, Glue, ECS
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
/* LOCH — deep-navy design system for Dashboard (nginx landing page) */
|
||||
/* HTI-5 — Federal Register editorial design system for Dashboard (nginx landing page) */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
||||
@import url('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');
|
||||
|
||||
:root {
|
||||
--ds-navy: #0a1628;
|
||||
--ds-navy-light: #162040;
|
||||
--ds-navy-mid: #1e2d50;
|
||||
--ds-slate: #2a3f5f;
|
||||
--ds-steel: #7a90b0;
|
||||
--ds-white: #f0f0f0;
|
||||
--ds-blue: #4488dd;
|
||||
--ds-blue-light: #5588dd;
|
||||
--ds-blue-bright: #88aaee;
|
||||
--ds-red: #ee5577;
|
||||
--ds-red-light: #dd4466;
|
||||
--ds-green: #44aa44;
|
||||
--background: #F7F5F0;
|
||||
--foreground: #1A1A18;
|
||||
--card: #FAFAF7;
|
||||
--card-foreground: #1A1A18;
|
||||
--primary: #1C2B3A;
|
||||
--primary-foreground: #F7F5F0;
|
||||
--secondary: #EDEBE6;
|
||||
--muted-foreground: #6B6B68;
|
||||
--border: #D4D0C8;
|
||||
--sidebar: #1C2B3A;
|
||||
--sidebar-foreground: #B8C5D0;
|
||||
--sidebar-accent: #253748;
|
||||
--chart-4: #2E8B6E;
|
||||
--chart-5: #C8702A;
|
||||
--destructive: #C0392B;
|
||||
|
||||
--font-display: "Playfair Display", Georgia, serif;
|
||||
--font-body: "Source Serif 4", Georgia, serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -24,169 +31,234 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'IBM Plex Mono', 'Courier New', monospace;
|
||||
background: var(--ds-navy);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
color: var(--ds-white);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
padding: 3rem 1.5rem;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
border-bottom: 4px solid var(--ds-red);
|
||||
/* ── Masthead ──────────────────────────────────────────────── */
|
||||
|
||||
.masthead {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.rule-line {
|
||||
border: none;
|
||||
border-top: 3px solid var(--foreground);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.rule-line-thin {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.masthead-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
color: var(--ds-white);
|
||||
letter-spacing: 6px;
|
||||
text-shadow: 3px 3px 0 var(--ds-navy-mid);
|
||||
font-family: var(--font-display);
|
||||
font-weight: 800;
|
||||
font-size: clamp(2.5rem, 5vw, 3.5rem);
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
color: var(--ds-blue-bright);
|
||||
font-size: 0.55rem;
|
||||
margin-top: 15px;
|
||||
letter-spacing: 2px;
|
||||
.masthead-eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted-foreground);
|
||||
align-self: flex-end;
|
||||
padding-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.score-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
margin-top: 20px;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-size: 0.5rem;
|
||||
.tagline {
|
||||
font-family: var(--font-body);
|
||||
font-size: 1.125rem;
|
||||
color: var(--muted-foreground);
|
||||
max-width: 65ch;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.score-item {
|
||||
color: var(--ds-white);
|
||||
background: var(--ds-blue);
|
||||
padding: 6px 14px;
|
||||
border: 2px solid var(--ds-blue-light);
|
||||
/* ── Services section ─────────────────────────────────────── */
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.section-eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted-foreground);
|
||||
margin-bottom: 0.4rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 1.75rem;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
/* ── Tile grid ────────────────────────────────────────────── */
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
margin-top: 20px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1.25rem;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tile {
|
||||
background: var(--ds-navy-light);
|
||||
border: 3px solid var(--ds-slate);
|
||||
padding: 25px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-top: 3px solid var(--foreground);
|
||||
padding: 1.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tile:hover {
|
||||
border-color: var(--ds-blue);
|
||||
background: var(--ds-navy-mid);
|
||||
border-color: var(--primary);
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
/* All tile variants use the same base style */
|
||||
.tile .icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tile.storage .icon,
|
||||
.tile.database .icon { color: var(--ds-red); }
|
||||
.tile.git .icon { color: var(--ds-blue-bright); }
|
||||
.tile.ci .icon { color: var(--ds-blue-light); }
|
||||
.tile.notebook .icon,
|
||||
.tile.research .icon { color: var(--ds-white); }
|
||||
.tile.catalog .icon { color: var(--ds-blue-bright); }
|
||||
.tile.query .icon { color: var(--ds-blue-light); }
|
||||
.tile.observability .icon { color: var(--ds-red-light); }
|
||||
.tile.metrics .icon { color: var(--ds-blue-bright); }
|
||||
.tile.logs .icon { color: var(--ds-blue-light); }
|
||||
.tile.proxy .icon { color: var(--ds-white); }
|
||||
|
||||
.tile-title {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
color: var(--ds-white);
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.tile-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--ds-steel);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tile-port {
|
||||
font-size: 0.7rem;
|
||||
color: var(--ds-blue-bright);
|
||||
display: inline-block;
|
||||
.tile:hover .tile-title {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Status dot */
|
||||
.status {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--ds-green);
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--chart-4);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Icon — decorative, not oversized */
|
||||
.tile .icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Tile eyebrow — mono metadata label (category, service type) */
|
||||
.tile-eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted-foreground);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Tile title — Playfair Display, editorial display size */
|
||||
.tile-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--foreground);
|
||||
margin: 0.25rem 0 0.5rem;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
/* Tile description — Source Serif 4, reading body */
|
||||
.tile-desc {
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.95rem;
|
||||
color: var(--muted-foreground);
|
||||
line-height: 1.55;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Tile port/metadata — mono, muted, small */
|
||||
.tile-port {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.65rem;
|
||||
color: var(--muted-foreground);
|
||||
opacity: 0.7;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Bottom accent line */
|
||||
.stripe {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: var(--ds-red);
|
||||
height: 2px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: var(--ds-slate);
|
||||
font-size: 0.7rem;
|
||||
border-top: 3px solid var(--ds-slate);
|
||||
margin-top: 20px;
|
||||
}
|
||||
/* ── Badge labels ─────────────────────────────────────────── */
|
||||
|
||||
footer span {
|
||||
color: var(--ds-red);
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 20px;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
color: var(--ds-white);
|
||||
font-size: 0.35rem;
|
||||
padding: 4px 8px;
|
||||
left: 1.25rem;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--primary-foreground);
|
||||
font-size: 0.6rem;
|
||||
padding: 3px 7px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.badge.new { background: var(--ds-red); }
|
||||
.badge.gpu { background: var(--ds-blue); }
|
||||
.badge.data { background: var(--ds-blue); }
|
||||
.badge.new { background: var(--chart-5); }
|
||||
.badge.gpu { background: var(--primary); }
|
||||
.badge.data { background: var(--primary); }
|
||||
|
||||
/* ── Footer ───────────────────────────────────────────────── */
|
||||
|
||||
footer {
|
||||
margin-top: 3rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 3px solid var(--foreground);
|
||||
text-align: left;
|
||||
color: var(--muted-foreground);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
footer span {
|
||||
color: var(--chart-5);
|
||||
}
|
||||
|
||||
157
assets/css/fhirworx.css
Normal file
157
assets/css/fhirworx.css
Normal file
@@ -0,0 +1,157 @@
|
||||
/* HTI-5 — fhirworx design system
|
||||
Single source of truth for the colour palette.
|
||||
Design: Federal Register Meets Data Journalism
|
||||
Inspired by ProPublica, The Marshall Project, and the Federal Register
|
||||
|
||||
When changing a token here, propagate to the service-specific files:
|
||||
inject.css, dashboard.css, gitea.css, woodpecker.css, marimo.css */
|
||||
|
||||
@import url('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');
|
||||
|
||||
:root {
|
||||
/* ── Core palette ──────────────────────────────────────────── */
|
||||
/* Off-white background, near-black text */
|
||||
--background: #F7F5F0;
|
||||
--foreground: #1A1A18;
|
||||
|
||||
/* Card: slightly warmer white */
|
||||
--card: #FAFAF7;
|
||||
--card-foreground: #1A1A18;
|
||||
|
||||
--popover: #FAFAF7;
|
||||
--popover-foreground: #1A1A18;
|
||||
|
||||
/* Primary: deep federal blue/navy */
|
||||
--primary: #1C2B3A;
|
||||
--primary-foreground: #F7F5F0;
|
||||
|
||||
--secondary: #EDEBE6;
|
||||
--secondary-foreground: #2D2D2B;
|
||||
|
||||
--muted: #EDEBE6;
|
||||
--muted-foreground: #6B6B68;
|
||||
|
||||
--accent: #EDEBE6;
|
||||
--accent-foreground: #1A1A18;
|
||||
|
||||
--destructive: #C0392B;
|
||||
--destructive-foreground: #F7F5F0;
|
||||
|
||||
--border: #D4D0C8;
|
||||
--input: #D4D0C8;
|
||||
--ring: #1C2B3A;
|
||||
|
||||
--radius: 0.2rem;
|
||||
|
||||
/* ── Sidebar: deep federal navy ────────────────────────────── */
|
||||
--sidebar: #1C2B3A;
|
||||
--sidebar-foreground: #B8C5D0;
|
||||
--sidebar-primary: #5B8FA8;
|
||||
--sidebar-primary-foreground: #F7F5F0;
|
||||
--sidebar-accent: #253748;
|
||||
--sidebar-accent-foreground: #EEE9E0;
|
||||
--sidebar-border: #253748;
|
||||
--sidebar-ring: #5B8FA8;
|
||||
|
||||
/* ── Chart palette: indigo scale + teal + amber ────────────── */
|
||||
--chart-1: #6B7FD7; /* indigo medium */
|
||||
--chart-2: #4A5CB8; /* indigo dark */
|
||||
--chart-3: #2E3D8F; /* indigo deep */
|
||||
--chart-4: #2E8B6E; /* teal (support) */
|
||||
--chart-5: #C8702A; /* amber (opposition) */
|
||||
|
||||
/* ── Typography ─────────────────────────────────────────────── */
|
||||
--font-display: "Playfair Display", Georgia, serif;
|
||||
--font-body: "Source Serif 4", Georgia, serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
}
|
||||
|
||||
/* ── Editorial base styles ────────────────────────────────── */
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
code, kbd, pre, .font-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* ── Editorial component classes ──────────────────────────── */
|
||||
|
||||
/* Rule lines — structural dividers, not borders */
|
||||
.rule-line {
|
||||
border: none;
|
||||
border-top: 3px solid var(--foreground);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.rule-line-thin {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Eyebrow — canonical mono label treatment for metadata, IDs, KPI labels */
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
/* Pull quote — editorial emphasis */
|
||||
.pull-quote {
|
||||
border-left: 3px solid var(--primary);
|
||||
padding-left: 1rem;
|
||||
font-style: italic;
|
||||
color: var(--muted-foreground);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
/* Prose — long-form reading columns */
|
||||
.prose {
|
||||
max-width: 65ch;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
line-height: 1.75;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
/* ── WCAG 2.2 AA — Accessibility ──────────────────────────── */
|
||||
|
||||
/* Focus indicators (2.4.7, 2.4.11) */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--primary) !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
/* Reduced motion (2.3.3) */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Minimum target size (2.5.8) */
|
||||
button, [role="button"], a, input, select, textarea {
|
||||
min-height: 24px;
|
||||
min-width: 24px;
|
||||
}
|
||||
@@ -1,162 +1,163 @@
|
||||
/* LOCH — deep-navy design system for Gitea */
|
||||
/* HTI-5 — fhirworx design system for Gitea */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
||||
@import url('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');
|
||||
|
||||
/* Theme metadata */
|
||||
gitea-theme-meta-info {
|
||||
--theme-display-name: "Loch";
|
||||
--theme-display-name: "Fhirworx";
|
||||
}
|
||||
|
||||
:root {
|
||||
--is-dark-theme: true;
|
||||
--is-dark-theme: false;
|
||||
|
||||
/* Loch Color Palette */
|
||||
--ds-navy: #0a1628;
|
||||
--ds-navy-light: #162040;
|
||||
--ds-navy-mid: #1e2d50;
|
||||
--ds-slate: #2a3f5f;
|
||||
--ds-steel: #7a90b0;
|
||||
--ds-white: #f0f0f0;
|
||||
/* HTI-5 Color Palette */
|
||||
--background: #F7F5F0;
|
||||
--foreground: #1A1A18;
|
||||
--card: #FAFAF7;
|
||||
--primary: #1C2B3A;
|
||||
--primary-foreground: #F7F5F0;
|
||||
--secondary: #EDEBE6;
|
||||
--muted-foreground: #6B6B68;
|
||||
--border: #D4D0C8;
|
||||
--sidebar: #1C2B3A;
|
||||
--sidebar-foreground: #B8C5D0;
|
||||
--sidebar-accent: #253748;
|
||||
--destructive: #C0392B;
|
||||
--chart-4: #2E8B6E;
|
||||
--chart-5: #C8702A;
|
||||
|
||||
--ds-blue: #4488dd;
|
||||
--ds-blue-light: #5588dd;
|
||||
--ds-blue-bright: #88aaee;
|
||||
--font-display: "Playfair Display", Georgia, serif;
|
||||
--font-body: "Source Serif 4", Georgia, serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
|
||||
--ds-red: #ee5577;
|
||||
--ds-red-light: #dd4466;
|
||||
/* Gitea color mappings — derived from hti5 tokens */
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-light: #2E3D8F;
|
||||
--color-primary-dark: #111c26;
|
||||
--color-primary-alpha-10: color-mix(in srgb, var(--primary) 10%, transparent);
|
||||
--color-primary-alpha-20: color-mix(in srgb, var(--primary) 20%, transparent);
|
||||
--color-primary-alpha-40: color-mix(in srgb, var(--primary) 40%, transparent);
|
||||
|
||||
--ds-green: #44aa44;
|
||||
--ds-green-light: #66cc66;
|
||||
--ds-yellow: #ddaa33;
|
||||
|
||||
/* Gitea color mappings */
|
||||
--color-primary: var(--ds-blue);
|
||||
--color-primary-light: var(--ds-blue-light);
|
||||
--color-primary-dark: #2255aa;
|
||||
--color-primary-alpha-10: rgba(51, 102, 204, 0.1);
|
||||
--color-primary-alpha-20: rgba(51, 102, 204, 0.2);
|
||||
--color-primary-alpha-40: rgba(51, 102, 204, 0.4);
|
||||
|
||||
--color-secondary: var(--ds-slate);
|
||||
--color-secondary-light: var(--ds-steel);
|
||||
--color-secondary-dark: var(--ds-navy-light);
|
||||
--color-secondary: var(--muted-foreground);
|
||||
--color-secondary-light: #9B9B98;
|
||||
--color-secondary-dark: var(--foreground);
|
||||
|
||||
/* Background colors */
|
||||
--color-body: var(--ds-navy);
|
||||
--color-box-body: var(--ds-navy);
|
||||
--color-box-header: var(--ds-navy-light);
|
||||
--color-body: var(--background);
|
||||
--color-box-body: var(--card);
|
||||
--color-box-header: var(--secondary);
|
||||
|
||||
/* Text colors */
|
||||
--color-text: var(--ds-white);
|
||||
--color-text-light: var(--ds-steel);
|
||||
--color-text-dark: var(--ds-white);
|
||||
--color-text: var(--foreground);
|
||||
--color-text-light: var(--muted-foreground);
|
||||
--color-text-dark: var(--foreground);
|
||||
|
||||
/* Input colors */
|
||||
--color-input-text: var(--ds-white);
|
||||
--color-input-background: var(--ds-navy-mid);
|
||||
--color-input-border: var(--ds-slate);
|
||||
--color-input-text: var(--foreground);
|
||||
--color-input-background: var(--card);
|
||||
--color-input-border: var(--border);
|
||||
|
||||
/* Status colors */
|
||||
--color-success: var(--ds-green);
|
||||
--color-success-text: var(--ds-white);
|
||||
--color-warning: var(--ds-yellow);
|
||||
--color-warning-text: var(--ds-navy);
|
||||
--color-error: var(--ds-red);
|
||||
--color-error-text: var(--ds-white);
|
||||
--color-info: var(--ds-blue-light);
|
||||
--color-info-text: var(--ds-white);
|
||||
--color-success: var(--chart-4);
|
||||
--color-success-text: #fff;
|
||||
--color-warning: var(--chart-5);
|
||||
--color-warning-text: #fff;
|
||||
--color-error: var(--destructive);
|
||||
--color-error-text: #fff;
|
||||
--color-info: var(--primary);
|
||||
--color-info-text: var(--primary-foreground);
|
||||
|
||||
/* Border */
|
||||
--color-border: var(--ds-slate);
|
||||
--color-border: var(--border);
|
||||
|
||||
/* Link colors */
|
||||
--color-link: var(--ds-blue-bright);
|
||||
--color-link-hover: var(--ds-white);
|
||||
--color-link: var(--primary);
|
||||
--color-link-hover: var(--foreground);
|
||||
|
||||
/* Code */
|
||||
--color-code-bg: var(--ds-navy-light);
|
||||
--color-code-border: var(--ds-slate);
|
||||
--color-code-bg: var(--secondary);
|
||||
--color-code-border: var(--border);
|
||||
|
||||
/* Shadows */
|
||||
--color-shadow: rgba(0, 0, 0, 0.4);
|
||||
--color-shadow: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Global styles */
|
||||
body {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--ds-navy) !important;
|
||||
font-family: var(--font-body) !important;
|
||||
background: var(--background) !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
.header-wrapper .header {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-family: var(--font-display) !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: 0.5px !important;
|
||||
line-height: 1.3 !important;
|
||||
}
|
||||
|
||||
h1 { font-size: 18px !important; color: var(--ds-white) !important; }
|
||||
h2 { font-size: 14px !important; color: var(--ds-blue-bright) !important; }
|
||||
h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
||||
h1 { font-size: 22px !important; color: var(--foreground) !important; }
|
||||
h2 { font-size: 17px !important; color: var(--primary) !important; }
|
||||
h3 { font-size: 14px !important; color: var(--foreground) !important; }
|
||||
|
||||
/* Navbar */
|
||||
.ui.secondary.menu.navbar,
|
||||
.navbar {
|
||||
background: var(--ds-navy-light) !important;
|
||||
border-bottom: 3px solid var(--ds-slate) !important;
|
||||
background: var(--sidebar) !important;
|
||||
border-bottom: 3px solid var(--foreground) !important;
|
||||
}
|
||||
|
||||
.navbar .item {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
color: var(--ds-white) !important;
|
||||
font-family: var(--font-body) !important;
|
||||
color: var(--sidebar-foreground) !important;
|
||||
}
|
||||
|
||||
.navbar .item:hover {
|
||||
background: var(--ds-navy-mid) !important;
|
||||
color: var(--ds-blue-bright) !important;
|
||||
background: var(--sidebar-accent) !important;
|
||||
color: var(--sidebar-accent-foreground, #EEE9E0) !important;
|
||||
}
|
||||
|
||||
.navbar .item.active {
|
||||
background: var(--ds-blue) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--sidebar-accent) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.ui.button,
|
||||
.button {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-family: var(--font-body) !important;
|
||||
font-weight: 500 !important;
|
||||
border-radius: 4px !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
transition: all 0.15s ease !important;
|
||||
}
|
||||
|
||||
.ui.primary.button,
|
||||
.ui.blue.button {
|
||||
background: var(--ds-blue) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-blue-light) !important;
|
||||
background: var(--primary) !important;
|
||||
color: var(--primary-foreground) !important;
|
||||
border: 1px solid var(--primary) !important;
|
||||
}
|
||||
|
||||
.ui.primary.button:hover,
|
||||
.ui.blue.button:hover {
|
||||
background: var(--ds-blue-light) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--sidebar-accent) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.ui.green.button {
|
||||
background: var(--ds-green) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-green-light) !important;
|
||||
background: var(--chart-4) !important;
|
||||
color: #fff !important;
|
||||
border: 1px solid var(--chart-4) !important;
|
||||
}
|
||||
|
||||
.ui.green.button:hover {
|
||||
background: var(--ds-green-light) !important;
|
||||
color: var(--ds-navy) !important;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.ui.red.button {
|
||||
background: var(--ds-red) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--destructive) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Segments and boxes */
|
||||
@@ -164,43 +165,43 @@ h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
||||
.ui.segments,
|
||||
.repository,
|
||||
.box {
|
||||
background: var(--ds-navy) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
background: var(--card) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
.ui.attached.header,
|
||||
.box-header {
|
||||
background: var(--ds-navy-light) !important;
|
||||
border-bottom: 2px solid var(--ds-slate) !important;
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-size: 11px !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--secondary) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
font-family: var(--font-display) !important;
|
||||
font-size: 13px !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.ui.table {
|
||||
background: var(--ds-navy) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
background: var(--card) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
.ui.table th {
|
||||
background: var(--ds-blue) !important;
|
||||
color: var(--ds-white) !important;
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--primary) !important;
|
||||
color: var(--primary-foreground) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 10px !important;
|
||||
text-transform: uppercase !important;
|
||||
border: 1px solid var(--ds-blue-light) !important;
|
||||
border: 1px solid var(--sidebar-accent) !important;
|
||||
}
|
||||
|
||||
.ui.table td {
|
||||
background: var(--ds-navy) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 1px solid var(--ds-navy-light) !important;
|
||||
background: var(--card) !important;
|
||||
color: var(--foreground) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
.ui.table tr:hover td {
|
||||
background: rgba(51, 102, 204, 0.2) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
@@ -208,38 +209,70 @@ h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
||||
.ui.form textarea,
|
||||
.ui.form select,
|
||||
.ui.input input {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--ds-navy-mid) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: var(--font-body) !important;
|
||||
background: var(--card) !important;
|
||||
color: var(--foreground) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
.ui.form input:focus,
|
||||
.ui.form textarea:focus,
|
||||
.ui.input input:focus {
|
||||
border-color: var(--ds-blue-light) !important;
|
||||
background: var(--ds-navy-mid) !important;
|
||||
border-color: var(--primary) !important;
|
||||
background: var(--card) !important;
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
/* Labels — mono for metadata labels (issue numbers, ref badges) */
|
||||
.ui.label {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 11px !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
/* Repo/issue titles — Playfair */
|
||||
.repository .header,
|
||||
.issue-title,
|
||||
.repository h1,
|
||||
.repository h2,
|
||||
.repository h3 {
|
||||
font-family: var(--font-display) !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
/* PR and issue descriptions — serif body */
|
||||
.comment-content,
|
||||
.issue-content,
|
||||
.render-content {
|
||||
font-family: var(--font-body) !important;
|
||||
font-size: 15px !important;
|
||||
line-height: 1.65 !important;
|
||||
}
|
||||
|
||||
/* Commit hashes, branch names, file paths — mono */
|
||||
.commit-sha,
|
||||
.sha,
|
||||
[class*="sha"],
|
||||
.branch-name,
|
||||
.tag-name,
|
||||
.file-name,
|
||||
code.ref {
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
/* Dropdowns */
|
||||
.ui.dropdown .menu {
|
||||
background: var(--ds-navy-light) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
background: var(--card) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
.ui.dropdown .menu .item {
|
||||
color: var(--ds-white) !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
.ui.dropdown .menu .item:hover {
|
||||
background: var(--ds-blue) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
/* Code and diffs */
|
||||
@@ -247,101 +280,101 @@ h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
||||
.file-view,
|
||||
.CodeMirror,
|
||||
.highlight {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--ds-navy) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
background: var(--card) !important;
|
||||
}
|
||||
|
||||
.diff-file-box .diff-file-header {
|
||||
background: var(--ds-navy-light) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
/* Commit graph */
|
||||
.repository .commit-list .commit {
|
||||
border-bottom: 1px solid var(--ds-navy-light) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
/* Issues and PRs */
|
||||
.issue-list .item,
|
||||
.pull-list .item {
|
||||
border-bottom: 1px solid var(--ds-navy-light) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: var(--ds-navy-light) !important;
|
||||
border-top: 3px solid var(--ds-slate) !important;
|
||||
background: var(--secondary) !important;
|
||||
border-top: 3px solid var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.ui.card,
|
||||
.ui.cards .card {
|
||||
background: var(--ds-navy) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
background: var(--card) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
.ui.message {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: var(--font-body) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
.ui.positive.message,
|
||||
.ui.success.message {
|
||||
background: rgba(68, 170, 68, 0.2) !important;
|
||||
border: 2px solid var(--ds-green) !important;
|
||||
color: var(--ds-green-light) !important;
|
||||
background: color-mix(in srgb, var(--chart-4) 12%, var(--background)) !important;
|
||||
border: 1px solid var(--chart-4) !important;
|
||||
color: var(--chart-4) !important;
|
||||
}
|
||||
|
||||
.ui.negative.message,
|
||||
.ui.error.message {
|
||||
background: rgba(204, 34, 68, 0.2) !important;
|
||||
border: 2px solid var(--ds-red) !important;
|
||||
color: var(--ds-red-light) !important;
|
||||
background: color-mix(in srgb, var(--destructive) 10%, var(--background)) !important;
|
||||
border: 1px solid var(--destructive) !important;
|
||||
color: var(--destructive) !important;
|
||||
}
|
||||
|
||||
.ui.warning.message {
|
||||
background: rgba(221, 170, 51, 0.2) !important;
|
||||
border: 2px solid var(--ds-yellow) !important;
|
||||
color: var(--ds-yellow) !important;
|
||||
background: color-mix(in srgb, var(--chart-5) 10%, var(--background)) !important;
|
||||
border: 1px solid var(--chart-5) !important;
|
||||
color: var(--chart-5) !important;
|
||||
}
|
||||
|
||||
.ui.info.message {
|
||||
background: rgba(85, 136, 221, 0.2) !important;
|
||||
border: 2px solid var(--ds-blue-light) !important;
|
||||
color: var(--ds-blue-bright) !important;
|
||||
background: color-mix(in srgb, var(--primary) 10%, var(--background)) !important;
|
||||
border: 1px solid var(--primary) !important;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--ds-navy-light);
|
||||
background: var(--secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--ds-slate);
|
||||
border-radius: 6px;
|
||||
background: var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--ds-steel);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
/* Selection */
|
||||
::selection {
|
||||
background: var(--ds-blue) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--primary) !important;
|
||||
color: var(--primary-foreground) !important;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: var(--ds-blue-bright) !important;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--ds-white) !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
/* LOCH — deep-navy design system
|
||||
Single source of truth for the colour palette.
|
||||
When changing a color here, propagate to the service-specific files:
|
||||
inject.css, dashboard.css, gitea.css, woodpecker.css, marimo.css */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
||||
|
||||
:root {
|
||||
--ds-navy: #0a1628;
|
||||
--ds-navy-light: #162040;
|
||||
--ds-navy-mid: #1e2d50;
|
||||
--ds-slate: #2a3f5f;
|
||||
--ds-steel: #7a90b0;
|
||||
--ds-white: #f0f0f0;
|
||||
|
||||
--ds-blue: #4488dd;
|
||||
--ds-blue-light: #5588dd;
|
||||
--ds-blue-bright: #88aaee;
|
||||
|
||||
--ds-red: #ee5577;
|
||||
--ds-red-light: #dd4466;
|
||||
|
||||
--ds-green: #44aa44;
|
||||
--ds-green-light: #66cc66;
|
||||
--ds-yellow: #ddaa33;
|
||||
}
|
||||
|
||||
/* ── WCAG 2.2 AA — Accessibility ──────────────────────────── */
|
||||
|
||||
/* Focus indicators (2.4.7, 2.4.11) */
|
||||
:focus-visible {
|
||||
outline: 2px solid #4488dd !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
/* Reduced motion (2.3.3) */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Minimum target size (2.5.8) */
|
||||
button, [role="button"], a, input, select, textarea {
|
||||
min-height: 24px;
|
||||
min-width: 24px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,70 +1,71 @@
|
||||
/* LOCH — deep-navy design system for Woodpecker CI */
|
||||
/* HTI-5 — Federal Register editorial design system for Woodpecker CI */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
||||
@import url('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');
|
||||
|
||||
:root {
|
||||
/* Loch Color Palette */
|
||||
--ds-navy: #0a1628;
|
||||
--ds-navy-light: #162040;
|
||||
--ds-navy-mid: #1e2d50;
|
||||
--ds-slate: #2a3f5f;
|
||||
--ds-steel: #7a90b0;
|
||||
--ds-white: #f0f0f0;
|
||||
/* HTI-5 Color Palette */
|
||||
--background: #F7F5F0;
|
||||
--foreground: #1A1A18;
|
||||
--card: #FAFAF7;
|
||||
--primary: #1C2B3A;
|
||||
--primary-foreground: #F7F5F0;
|
||||
--secondary: #EDEBE6;
|
||||
--muted-foreground: #6B6B68;
|
||||
--border: #D4D0C8;
|
||||
--sidebar: #1C2B3A;
|
||||
--sidebar-foreground: #B8C5D0;
|
||||
--sidebar-accent: #253748;
|
||||
--sidebar-accent-foreground: #EEE9E0;
|
||||
--destructive: #C0392B;
|
||||
--chart-4: #2E8B6E;
|
||||
--chart-5: #C8702A;
|
||||
|
||||
--ds-blue: #4488dd;
|
||||
--ds-blue-light: #5588dd;
|
||||
--ds-blue-bright: #88aaee;
|
||||
--font-display: "Playfair Display", Georgia, serif;
|
||||
--font-body: "Source Serif 4", Georgia, serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
|
||||
--ds-red: #ee5577;
|
||||
--ds-red-light: #dd4466;
|
||||
/* Woodpecker color overrides — mapped from hti5 tokens */
|
||||
--color-wp-primary-100: #B8C5D0;
|
||||
--color-wp-primary-200: #7A96AE;
|
||||
--color-wp-primary-300: #1C2B3A;
|
||||
--color-wp-primary-400: #162030;
|
||||
--color-wp-primary-500: #0E1520;
|
||||
|
||||
--ds-green: #44aa44;
|
||||
--ds-green-light: #66cc66;
|
||||
--ds-yellow: #ddaa33;
|
||||
--color-wp-success-100: #56B397;
|
||||
--color-wp-success-200: #2E8B6E;
|
||||
|
||||
/* Woodpecker color overrides */
|
||||
--color-wp-primary-100: var(--ds-blue-bright);
|
||||
--color-wp-primary-200: var(--ds-blue-light);
|
||||
--color-wp-primary-300: var(--ds-blue);
|
||||
--color-wp-primary-400: #2255aa;
|
||||
--color-wp-primary-500: var(--ds-navy);
|
||||
--color-wp-error-100: #D9534F;
|
||||
--color-wp-error-200: #C0392B;
|
||||
|
||||
--color-wp-success-100: var(--ds-green-light);
|
||||
--color-wp-success-200: var(--ds-green);
|
||||
--color-wp-warning-100: #DDA060;
|
||||
--color-wp-warning-200: #C8702A;
|
||||
|
||||
--color-wp-error-100: var(--ds-red-light);
|
||||
--color-wp-error-200: var(--ds-red);
|
||||
|
||||
--color-wp-warning-100: var(--ds-yellow);
|
||||
--color-wp-warning-200: #bb8822;
|
||||
|
||||
--color-wp-gray-100: var(--ds-white);
|
||||
--color-wp-gray-200: var(--ds-steel);
|
||||
--color-wp-gray-300: var(--ds-slate);
|
||||
--color-wp-gray-400: var(--ds-navy-mid);
|
||||
--color-wp-gray-500: var(--ds-navy-light);
|
||||
--color-wp-gray-600: var(--ds-navy);
|
||||
--color-wp-gray-100: #1A1A18;
|
||||
--color-wp-gray-200: #6B6B68;
|
||||
--color-wp-gray-300: #D4D0C8;
|
||||
--color-wp-gray-400: #EDEBE6;
|
||||
--color-wp-gray-500: #F7F5F0;
|
||||
--color-wp-gray-600: #FAFAF7;
|
||||
}
|
||||
|
||||
/* Global styles */
|
||||
body,
|
||||
html {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--ds-navy) !important;
|
||||
color: var(--ds-white) !important;
|
||||
font-family: var(--font-body) !important;
|
||||
background: var(--background) !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-family: var(--font-display) !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: 0.5px !important;
|
||||
line-height: 1.8 !important;
|
||||
line-height: 1.3 !important;
|
||||
}
|
||||
|
||||
h1 { font-size: 18px !important; color: var(--ds-white) !important; }
|
||||
h2 { font-size: 14px !important; color: var(--ds-blue-bright) !important; }
|
||||
h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
||||
h1 { font-size: 22px !important; color: var(--foreground) !important; }
|
||||
h2 { font-size: 17px !important; color: var(--primary) !important; }
|
||||
h3 { font-size: 14px !important; color: var(--foreground) !important; }
|
||||
|
||||
/* Header/Navbar */
|
||||
header,
|
||||
@@ -72,16 +73,16 @@ nav,
|
||||
.navbar,
|
||||
[class*="header"],
|
||||
[class*="Header"] {
|
||||
background: var(--ds-navy-light) !important;
|
||||
border-bottom: 3px solid var(--ds-slate) !important;
|
||||
background: var(--sidebar) !important;
|
||||
border-bottom: 3px solid var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
aside,
|
||||
[class*="sidebar"],
|
||||
[class*="Sidebar"] {
|
||||
background: var(--ds-navy) !important;
|
||||
border-right: 3px solid var(--ds-slate) !important;
|
||||
background: var(--sidebar) !important;
|
||||
border-right: 1px solid var(--sidebar-border, var(--sidebar-accent)) !important;
|
||||
}
|
||||
|
||||
/* Main content area */
|
||||
@@ -98,20 +99,20 @@ main,
|
||||
[class*="Panel"],
|
||||
[class*="box"],
|
||||
[class*="Box"] {
|
||||
background: var(--ds-navy) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
background: var(--card) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
/* Buttons — serif body for button labels */
|
||||
button,
|
||||
[class*="btn"],
|
||||
[class*="Btn"],
|
||||
[class*="button"],
|
||||
[class*="Button"] {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-family: var(--font-body) !important;
|
||||
font-weight: 500 !important;
|
||||
border-radius: 4px !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
transition: all 0.15s ease !important;
|
||||
}
|
||||
|
||||
@@ -119,16 +120,16 @@ button,
|
||||
[class*="primary"],
|
||||
[class*="Primary"],
|
||||
button[type="submit"] {
|
||||
background: var(--ds-blue) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-blue-light) !important;
|
||||
background: var(--primary) !important;
|
||||
color: var(--primary-foreground) !important;
|
||||
border: 1px solid var(--primary) !important;
|
||||
}
|
||||
|
||||
[class*="primary"]:hover,
|
||||
[class*="Primary"]:hover,
|
||||
button[type="submit"]:hover {
|
||||
background: var(--ds-blue-light) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--sidebar-accent) !important;
|
||||
color: var(--sidebar-accent-foreground) !important;
|
||||
}
|
||||
|
||||
/* Success/green buttons */
|
||||
@@ -136,15 +137,14 @@ button[type="submit"]:hover {
|
||||
[class*="Success"],
|
||||
[class*="green"],
|
||||
[class*="Green"] {
|
||||
background: var(--ds-green) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-green-light) !important;
|
||||
background: var(--chart-4) !important;
|
||||
color: #fff !important;
|
||||
border: 1px solid var(--chart-4) !important;
|
||||
}
|
||||
|
||||
[class*="success"]:hover,
|
||||
[class*="Success"]:hover {
|
||||
background: var(--ds-green-light) !important;
|
||||
color: var(--ds-navy) !important;
|
||||
opacity: 0.85 !important;
|
||||
}
|
||||
|
||||
/* Danger/red buttons */
|
||||
@@ -154,60 +154,82 @@ button[type="submit"]:hover {
|
||||
[class*="Error"],
|
||||
[class*="red"],
|
||||
[class*="Red"] {
|
||||
background: var(--ds-red) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--destructive) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--ds-navy-mid) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: var(--font-body) !important;
|
||||
background: var(--card) !important;
|
||||
color: var(--foreground) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
padding: 8px 12px !important;
|
||||
}
|
||||
|
||||
/* Pipeline/repo names — Playfair for titles */
|
||||
[class*="pipeline-name"],
|
||||
[class*="PipelineName"],
|
||||
[class*="repo-name"],
|
||||
[class*="RepoName"] {
|
||||
font-family: var(--font-display) !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
/* Commit hashes, build numbers, durations — mono */
|
||||
[class*="commit-hash"],
|
||||
[class*="CommitHash"],
|
||||
[class*="build-number"],
|
||||
[class*="BuildNumber"],
|
||||
[class*="duration"],
|
||||
[class*="Duration"],
|
||||
[class*="timestamp"],
|
||||
[class*="Timestamp"] {
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: var(--ds-blue-light) !important;
|
||||
border-color: var(--primary) !important;
|
||||
outline: none !important;
|
||||
box-shadow: 0 0 0 2px rgba(85, 136, 221, 0.3) !important;
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent) !important;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
background: var(--ds-navy) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
background: var(--card) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-collapse: collapse !important;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--ds-blue) !important;
|
||||
color: var(--ds-white) !important;
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--primary) !important;
|
||||
color: var(--primary-foreground) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 10px !important;
|
||||
text-transform: uppercase !important;
|
||||
padding: 10px 12px !important;
|
||||
border: 1px solid var(--ds-blue-light) !important;
|
||||
border: 1px solid var(--sidebar-accent) !important;
|
||||
}
|
||||
|
||||
td {
|
||||
background: var(--ds-navy) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--card) !important;
|
||||
color: var(--foreground) !important;
|
||||
padding: 8px 12px !important;
|
||||
border: 1px solid var(--ds-navy-light) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
tr:nth-child(even) td {
|
||||
background: var(--ds-navy-light) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
tr:hover td {
|
||||
background: rgba(51, 102, 204, 0.2) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
/* Pipeline/build status colors */
|
||||
@@ -215,14 +237,14 @@ tr:hover td {
|
||||
[class*="Running"],
|
||||
[class*="pending"],
|
||||
[class*="Pending"] {
|
||||
color: var(--ds-yellow) !important;
|
||||
color: var(--chart-5) !important;
|
||||
}
|
||||
|
||||
[class*="success"],
|
||||
[class*="Success"],
|
||||
[class*="passed"],
|
||||
[class*="Passed"] {
|
||||
color: var(--ds-green-light) !important;
|
||||
color: var(--chart-4) !important;
|
||||
}
|
||||
|
||||
[class*="failure"],
|
||||
@@ -231,7 +253,7 @@ tr:hover td {
|
||||
[class*="Failed"],
|
||||
[class*="error"],
|
||||
[class*="Error"] {
|
||||
color: var(--ds-red) !important;
|
||||
color: var(--destructive) !important;
|
||||
}
|
||||
|
||||
/* Build logs */
|
||||
@@ -241,29 +263,29 @@ code,
|
||||
[class*="Log"],
|
||||
[class*="console"],
|
||||
[class*="Console"] {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--ds-navy) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
background: var(--secondary) !important;
|
||||
color: var(--foreground) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
code {
|
||||
background: var(--ds-navy-light) !important;
|
||||
color: var(--ds-blue-bright) !important;
|
||||
background: var(--secondary) !important;
|
||||
color: var(--primary) !important;
|
||||
padding: 2px 6px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: var(--ds-blue-bright) !important;
|
||||
color: var(--primary) !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--ds-white) !important;
|
||||
color: var(--foreground) !important;
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
@@ -274,16 +296,16 @@ a:hover {
|
||||
[class*="Menu"],
|
||||
[class*="popover"],
|
||||
[class*="Popover"] {
|
||||
background: var(--ds-navy-light) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
background: var(--card) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
[class*="dropdown"] li:hover,
|
||||
[class*="menu"] li:hover,
|
||||
[role="menuitem"]:hover,
|
||||
[role="option"]:hover {
|
||||
background: var(--ds-blue) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
/* Badges and tags */
|
||||
@@ -293,62 +315,63 @@ a:hover {
|
||||
[class*="Tag"],
|
||||
[class*="label"],
|
||||
[class*="Label"] {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 11px !important;
|
||||
border-radius: 4px !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
/* Tooltips */
|
||||
[role="tooltip"],
|
||||
[class*="tooltip"],
|
||||
[class*="Tooltip"] {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
background: var(--ds-navy-light) !important;
|
||||
color: var(--ds-white) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: var(--font-body) !important;
|
||||
font-size: 13px !important;
|
||||
background: var(--card) !important;
|
||||
color: var(--foreground) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
/* Progress bars */
|
||||
progress,
|
||||
[class*="progress"],
|
||||
[class*="Progress"] {
|
||||
background: var(--ds-navy-light) !important;
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
background: var(--secondary) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background: var(--ds-navy-light) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background: var(--ds-blue) !important;
|
||||
background: var(--primary) !important;
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--ds-navy-light);
|
||||
background: var(--secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--ds-slate);
|
||||
border-radius: 6px;
|
||||
background: var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--ds-steel);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
/* Selection */
|
||||
::selection {
|
||||
background: var(--ds-blue) !important;
|
||||
color: var(--ds-white) !important;
|
||||
background: var(--primary) !important;
|
||||
color: var(--primary-foreground) !important;
|
||||
}
|
||||
|
||||
/* Icons - ensure visibility */
|
||||
@@ -361,8 +384,8 @@ svg,
|
||||
/* Avatar images */
|
||||
img[class*="avatar"],
|
||||
[class*="avatar"] img {
|
||||
border: 2px solid var(--ds-slate) !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: var(--radius, 0.2rem) !important;
|
||||
}
|
||||
|
||||
/* Repo/pipeline list items */
|
||||
@@ -370,10 +393,10 @@ img[class*="avatar"],
|
||||
[class*="Item"],
|
||||
[class*="list-item"],
|
||||
[class*="ListItem"] {
|
||||
border-bottom: 1px solid var(--ds-navy-light) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
[class*="item"]:hover,
|
||||
[class*="Item"]:hover {
|
||||
background: rgba(51, 102, 204, 0.1) !important;
|
||||
background: var(--secondary) !important;
|
||||
}
|
||||
|
||||
@@ -3,23 +3,32 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ACO Pipeline DAG</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">
|
||||
<script src="https://unpkg.com/cytoscape@3/dist/cytoscape.min.js"></script>
|
||||
<script src="https://unpkg.com/dagre@0.8/dist/dagre.min.js"></script>
|
||||
<script src="https://unpkg.com/cytoscape-dagre@2/cytoscape-dagre.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #1a1a2e; color: #e0e0e0; font-family: system-ui, sans-serif; }
|
||||
/* HTI-5 palette */
|
||||
:root {
|
||||
--bg: #F7F5F0; --panel: #FAFAF7; --border: #D4D0C8;
|
||||
--text: #1A1A18; --muted: #6B6B68; --navy: #1C2B3A;
|
||||
}
|
||||
body { background: var(--bg); color: var(--text); font-family: "Source Serif 4", Georgia, serif; }
|
||||
#cy { width: 100vw; height: 100vh; }
|
||||
#controls {
|
||||
position: fixed; top: 12px; left: 12px; z-index: 10;
|
||||
background: #16213e; border: 1px solid #333; border-radius: 8px;
|
||||
background: var(--navy); border: 1px solid #253748; border-radius: 0;
|
||||
padding: 12px; max-width: 260px; font-size: 13px;
|
||||
color: #B8C5D0;
|
||||
}
|
||||
#controls h3 { margin-bottom: 8px; font-size: 14px; color: #fff; }
|
||||
#controls h3 { margin-bottom: 8px; font-size: 14px; color: #EEE9E0; font-family: "Playfair Display", Georgia, serif; }
|
||||
#search {
|
||||
width: 100%; padding: 6px 8px; border-radius: 4px;
|
||||
border: 1px solid #444; background: #0f3460; color: #e0e0e0;
|
||||
margin-bottom: 8px; font-size: 13px;
|
||||
width: 100%; padding: 6px 8px; border-radius: 0;
|
||||
border: 1px solid #253748; background: #253748; color: #EEE9E0;
|
||||
margin-bottom: 8px; font-size: 13px; font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
#legend { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.legend-item {
|
||||
@@ -33,16 +42,16 @@
|
||||
display: inline-block; flex-shrink: 0;
|
||||
}
|
||||
#tooltip {
|
||||
position: fixed; display: none; background: #16213e;
|
||||
border: 1px solid #444; border-radius: 6px; padding: 10px;
|
||||
position: fixed; display: none; background: var(--panel);
|
||||
border: 1px solid var(--border); border-radius: 0; padding: 10px;
|
||||
max-width: 340px; font-size: 12px; z-index: 20;
|
||||
pointer-events: none; line-height: 1.4;
|
||||
}
|
||||
#tooltip .tt-name { font-weight: bold; color: #fff; margin-bottom: 4px; }
|
||||
#tooltip .tt-desc { color: #bbb; }
|
||||
#tooltip .tt-name { font-weight: bold; color: var(--text); margin-bottom: 4px; font-family: "Playfair Display", Georgia, serif; }
|
||||
#tooltip .tt-desc { color: var(--muted); }
|
||||
#stats {
|
||||
position: fixed; bottom: 12px; left: 12px; z-index: 10;
|
||||
font-size: 11px; color: #666;
|
||||
font-size: 11px; color: var(--muted); font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -5911,12 +5920,13 @@
|
||||
"background-color": "transparent",
|
||||
"background-opacity": 0.05,
|
||||
"border-width": 1,
|
||||
"border-color": "#444",
|
||||
"border-color": "#D4D0C8",
|
||||
"label": "data(label)",
|
||||
"text-valign": "top",
|
||||
"text-halign": "center",
|
||||
"font-size": "14px",
|
||||
"color": "#666",
|
||||
"font-size": "13px",
|
||||
"font-family": "Playfair Display, Georgia, serif",
|
||||
"color": "#6B6B68",
|
||||
"padding": "16px",
|
||||
"shape": "round-rectangle",
|
||||
}
|
||||
@@ -5948,15 +5958,16 @@
|
||||
{
|
||||
selector: "node.external",
|
||||
style: {
|
||||
"background-color": "#2a2a2a",
|
||||
"background-color": "#EDEBE6",
|
||||
"border-width": 1,
|
||||
"border-style": "dashed",
|
||||
"border-color": "#666",
|
||||
"border-color": "#D4D0C8",
|
||||
"label": "data(label)",
|
||||
"text-valign": "center",
|
||||
"text-halign": "center",
|
||||
"font-size": "8px",
|
||||
"color": "#888",
|
||||
"font-family": "JetBrains Mono, Fira Code, monospace",
|
||||
"color": "#6B6B68",
|
||||
"width": "label",
|
||||
"height": 24,
|
||||
"padding": "6px",
|
||||
@@ -5967,8 +5978,8 @@
|
||||
selector: "edge",
|
||||
style: {
|
||||
"width": 1.5,
|
||||
"line-color": "#444",
|
||||
"target-arrow-color": "#444",
|
||||
"line-color": "#D4D0C8",
|
||||
"target-arrow-color": "#D4D0C8",
|
||||
"target-arrow-shape": "triangle",
|
||||
"curve-style": "bezier",
|
||||
"arrow-scale": 0.8,
|
||||
@@ -5977,16 +5988,16 @@
|
||||
{
|
||||
selector: ".highlighted",
|
||||
style: {
|
||||
"border-color": "#fff",
|
||||
"border-color": "#1C2B3A",
|
||||
"border-width": 3,
|
||||
"color": "#fff",
|
||||
"color": "#1C2B3A",
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "edge.highlighted",
|
||||
style: {
|
||||
"line-color": "#e0e0e0",
|
||||
"target-arrow-color": "#e0e0e0",
|
||||
"line-color": "#1C2B3A",
|
||||
"target-arrow-color": "#1C2B3A",
|
||||
"width": 2.5,
|
||||
}
|
||||
},
|
||||
|
||||
235
assets/fhirworx.py
Normal file
235
assets/fhirworx.py
Normal file
@@ -0,0 +1,235 @@
|
||||
"""HTI-5 Federal Register design system — shared palette and theme.
|
||||
|
||||
Design: Federal Register Meets Data Journalism.
|
||||
Inspired by ProPublica, The Marshall Project, and the Federal Register.
|
||||
|
||||
Provides:
|
||||
|
||||
- ``PALETTE`` — 8-colour chart sequence (indigo scale + teal + amber + plum + gray + sepia)
|
||||
- ``SCHEMA_COLORS`` — pipeline schema → colour mapping for DAG viz
|
||||
- ``altair_theme()`` — register-and-enable for Altair charts
|
||||
- ``mpl_rc()`` — dict of matplotlib rcParams
|
||||
- ``cytoscape_style()`` — Cytoscape.js stylesheet fragment
|
||||
|
||||
Design principles (HTI-5):
|
||||
* Typography-first: Playfair Display for display, Source Serif 4 for body
|
||||
* Off-white (#F7F5F0) background, near-black (#1A1A18) text
|
||||
* Deep federal navy sidebar (#1C2B3A)
|
||||
* Warm amber accent for opposition, teal for support
|
||||
* Monospace font (JetBrains Mono) for IDs, labels, metadata
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ── Chart colour palette ──────────────────────────────────────────────
|
||||
# Eight-colour categorical sequence for multi-series charts.
|
||||
# First five match the hti5 --chart-* tokens (indigo scale + teal + amber);
|
||||
# remaining three extend the editorial palette for categorical variety.
|
||||
PALETTE = [
|
||||
"#6B7FD7", # indigo medium (chart-1)
|
||||
"#4A5CB8", # indigo dark (chart-2)
|
||||
"#2E3D8F", # indigo deep (chart-3)
|
||||
"#2E8B6E", # teal / support (chart-4)
|
||||
"#C8702A", # amber / oppose (chart-5)
|
||||
"#7448C0", # plum
|
||||
"#6B6B68", # neutral gray
|
||||
"#A85800", # sepia / dark amber
|
||||
]
|
||||
|
||||
# Semantic aliases
|
||||
INDIGO_MED, INDIGO_DARK, INDIGO_DEEP, TEAL, AMBER, PLUM, GRAY, SEPIA = PALETTE
|
||||
|
||||
# ── Core palette ──────────────────────────────────────────────────────
|
||||
# Backgrounds and text following hti5 design tokens
|
||||
BG_LIGHT = "#F7F5F0" # --background: off-white cream
|
||||
BG_CARD = "#FAFAF7" # --card: slightly warmer white
|
||||
BG_DARK = "#1C2B3A" # --primary / sidebar: deep federal navy
|
||||
BG_CHART = "transparent" # chart area — let container bg show through
|
||||
AXIS_COLOR = "#D4D0C8" # --border: warm gray
|
||||
TEXT_COLOR = "#1A1A18" # --foreground: near-black
|
||||
TITLE_COLOR = "#1A1A18" # --foreground for titles
|
||||
MUTED_COLOR = "#6B6B68" # --muted-foreground
|
||||
|
||||
# ── Typography ────────────────────────────────────────────────────────
|
||||
FONT_DISPLAY = "Playfair Display, Georgia, serif"
|
||||
FONT_BODY = "Source Serif 4, Georgia, serif"
|
||||
FONT_MONO = "JetBrains Mono, Fira Code, monospace"
|
||||
FONT_FAMILY = FONT_BODY # default for charts
|
||||
FONT_SIZE_BODY = 11 # pt — readable at screen DPI
|
||||
FONT_SIZE_AXIS = 11 # pt
|
||||
FONT_SIZE_TITLE = 13 # pt
|
||||
FONT_SIZE_LABEL = 12 # pt
|
||||
LINE_WIDTH = 0.75 # pt — axis/tick stroke
|
||||
TICK_SIZE = 4 # pt
|
||||
|
||||
# ── Figure dimensions (single-column width: 89 mm) ───────────────────
|
||||
FIG_WIDTH_MM = 89
|
||||
FIG_HEIGHT_MM = 60
|
||||
FIG_WIDTH_IN = FIG_WIDTH_MM / 25.4
|
||||
FIG_HEIGHT_IN = FIG_HEIGHT_MM / 25.4
|
||||
DPI = 150 # screen-appropriate DPI
|
||||
|
||||
|
||||
# ── Pipeline schema colours (for DAG / lineage viz) ──────────────────
|
||||
SCHEMA_COLORS: dict[str, str] = {
|
||||
"core": INDIGO_DEEP,
|
||||
"input_layer": TEAL,
|
||||
"cclf": INDIGO_DARK,
|
||||
"claims_preprocessing": AMBER,
|
||||
"readmissions": TEAL,
|
||||
"ahrq_measures": INDIGO_MED,
|
||||
"pharmacy": "#7448C0", # purple
|
||||
"quality_measures": "#C0392B", # destructive / red
|
||||
"hcc_suspecting": "#A85800", # warm amber-brown
|
||||
"provider_attribution": MUTED_COLOR,
|
||||
"main": INDIGO_MED,
|
||||
"data_quality": INDIGO_DARK,
|
||||
}
|
||||
EXTERNAL_COLOR = "#9B9B98"
|
||||
|
||||
|
||||
def schema_color(schema: str) -> str:
|
||||
"""Return the palette colour for a pipeline schema."""
|
||||
return SCHEMA_COLORS.get(schema, EXTERNAL_COLOR)
|
||||
|
||||
|
||||
# ── Altair theme ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _altair_theme_config() -> dict:
|
||||
"""Vega-Lite config dict conforming to HTI-5 editorial specs."""
|
||||
return {
|
||||
"config": {
|
||||
"background": BG_LIGHT,
|
||||
"font": FONT_BODY,
|
||||
"title": {
|
||||
"font": FONT_DISPLAY,
|
||||
"fontSize": FONT_SIZE_TITLE,
|
||||
"fontWeight": "bold",
|
||||
"color": TITLE_COLOR,
|
||||
"anchor": "start",
|
||||
},
|
||||
"axis": {
|
||||
"labelFont": FONT_MONO,
|
||||
"labelFontSize": FONT_SIZE_AXIS,
|
||||
"labelColor": MUTED_COLOR,
|
||||
"titleFont": FONT_BODY,
|
||||
"titleFontSize": FONT_SIZE_AXIS,
|
||||
"titleColor": TEXT_COLOR,
|
||||
"gridColor": AXIS_COLOR,
|
||||
"gridOpacity": 0.5,
|
||||
"domainColor": AXIS_COLOR,
|
||||
"domainWidth": LINE_WIDTH,
|
||||
"tickColor": AXIS_COLOR,
|
||||
"tickSize": TICK_SIZE,
|
||||
"tickWidth": LINE_WIDTH,
|
||||
},
|
||||
"legend": {
|
||||
"labelFont": FONT_MONO,
|
||||
"labelFontSize": FONT_SIZE_BODY,
|
||||
"labelColor": TEXT_COLOR,
|
||||
"titleFont": FONT_BODY,
|
||||
"titleFontSize": FONT_SIZE_BODY,
|
||||
"titleColor": TEXT_COLOR,
|
||||
"symbolSize": 60,
|
||||
},
|
||||
"view": {
|
||||
"stroke": AXIS_COLOR,
|
||||
"strokeWidth": LINE_WIDTH,
|
||||
"fill": BG_CARD,
|
||||
},
|
||||
"range": {
|
||||
"category": PALETTE,
|
||||
},
|
||||
"mark": {
|
||||
"color": PALETTE[0],
|
||||
},
|
||||
"bar": {
|
||||
"color": PALETTE[0],
|
||||
},
|
||||
"arc": {
|
||||
"stroke": BG_LIGHT,
|
||||
"strokeWidth": 1,
|
||||
},
|
||||
"line": {
|
||||
"strokeWidth": 1.5,
|
||||
},
|
||||
"point": {
|
||||
"size": 40,
|
||||
},
|
||||
"area": {
|
||||
"opacity": 0.7,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def altair_theme() -> None:
|
||||
"""Register and enable the fhirworx Altair chart theme.
|
||||
|
||||
Uses the Altair 5.5+ ``alt.theme.register`` decorator API.
|
||||
"""
|
||||
import altair as alt
|
||||
|
||||
@alt.theme.register("fhirworx", enable=True)
|
||||
def _fhirworx_theme() -> dict:
|
||||
return _altair_theme_config()
|
||||
|
||||
|
||||
# ── Matplotlib rcParams ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def mpl_rc() -> dict:
|
||||
"""Return an rcParams dict for ``matplotlib.rcParams.update()``."""
|
||||
return {
|
||||
"font.family": "serif",
|
||||
"font.serif": ["Source Serif 4", "Georgia", "DejaVu Serif"],
|
||||
"font.size": FONT_SIZE_BODY,
|
||||
"axes.titlesize": FONT_SIZE_TITLE,
|
||||
"axes.titleweight": "bold",
|
||||
"axes.titlefontproperties": {"family": "serif"},
|
||||
"axes.labelsize": FONT_SIZE_AXIS,
|
||||
"axes.linewidth": LINE_WIDTH,
|
||||
"axes.edgecolor": AXIS_COLOR,
|
||||
"axes.labelcolor": TEXT_COLOR,
|
||||
"axes.facecolor": BG_CARD,
|
||||
"axes.grid": True,
|
||||
"axes.grid.axis": "y",
|
||||
"axes.prop_cycle": __import__("cycler").cycler(color=PALETTE),
|
||||
"grid.color": AXIS_COLOR,
|
||||
"grid.linewidth": 0.5,
|
||||
"grid.alpha": 0.6,
|
||||
"xtick.major.size": TICK_SIZE,
|
||||
"xtick.major.width": LINE_WIDTH,
|
||||
"xtick.color": AXIS_COLOR,
|
||||
"xtick.labelsize": FONT_SIZE_BODY,
|
||||
"ytick.major.size": TICK_SIZE,
|
||||
"ytick.major.width": LINE_WIDTH,
|
||||
"ytick.color": AXIS_COLOR,
|
||||
"ytick.labelsize": FONT_SIZE_BODY,
|
||||
"figure.figsize": (FIG_WIDTH_IN, FIG_HEIGHT_IN),
|
||||
"figure.dpi": DPI,
|
||||
"figure.facecolor": BG_LIGHT,
|
||||
"savefig.dpi": DPI,
|
||||
"savefig.facecolor": BG_LIGHT,
|
||||
"legend.fontsize": FONT_SIZE_BODY,
|
||||
"legend.frameon": False,
|
||||
"text.color": TEXT_COLOR,
|
||||
}
|
||||
|
||||
|
||||
# ── Cytoscape.js style helpers ───────────────────────────────────────
|
||||
|
||||
|
||||
def cytoscape_node_style() -> dict:
|
||||
"""Base Cytoscape.js node style properties (HTI-5 editorial style)."""
|
||||
return {
|
||||
"font-family": FONT_MONO,
|
||||
"font-size": "9px",
|
||||
"text-valign": "center",
|
||||
"text-halign": "center",
|
||||
"shape": "round-rectangle",
|
||||
"background-color": BG_CARD,
|
||||
"border-color": AXIS_COLOR,
|
||||
"color": TEXT_COLOR,
|
||||
}
|
||||
209
assets/nature.py
209
assets/nature.py
@@ -1,209 +0,0 @@
|
||||
"""Nature Research figure standards — shared palette and theme.
|
||||
|
||||
Implements the specifications from Nature's "Preparing Figures" guide
|
||||
(bib key ``9ASETLJ4``). Provides:
|
||||
|
||||
- ``PALETTE`` — 8-colour accessible sequence (colorblind-safe)
|
||||
- ``SCHEMA_COLORS`` — pipeline schema → colour mapping for DAG viz
|
||||
- ``altair_theme()`` — register-and-enable for Altair charts
|
||||
- ``mpl_rc()`` — dict of matplotlib rcParams
|
||||
- ``cytoscape_style()`` — Cytoscape.js stylesheet fragment
|
||||
|
||||
Key rules (Nature):
|
||||
* Sans-serif typeface (Helvetica, Arial)
|
||||
* Body text 5–7 pt; panel labels 8 pt bold lowercase
|
||||
* Axis lines and tick marks mandatory
|
||||
* No background gridlines, drop shadows, or decorative elements
|
||||
* RGB colour space; accessible palette
|
||||
* Units in parentheses on axis labels
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ── Accessible 8-colour palette ──────────────────────────────────────
|
||||
# Adapted from Paul Tol's "bright" qualitative scheme — verified
|
||||
# colourblind-safe via Coblis and WCAG contrast tools.
|
||||
PALETTE = [
|
||||
"#4477AA", # blue
|
||||
"#EE6677", # red / rose
|
||||
"#228833", # green
|
||||
"#CCBB44", # yellow
|
||||
"#66CCEE", # cyan
|
||||
"#AA3377", # purple
|
||||
"#BBBBBB", # grey
|
||||
"#EE8866", # orange
|
||||
]
|
||||
|
||||
# Semantic aliases
|
||||
BLUE, RED, GREEN, YELLOW, CYAN, PURPLE, GREY, ORANGE = PALETTE
|
||||
|
||||
# ── Typography ────────────────────────────────────────────────────────
|
||||
FONT_FAMILY = "Helvetica, Arial, sans-serif"
|
||||
FONT_SIZE_BODY = 7 # pt — Nature range 5-7
|
||||
FONT_SIZE_AXIS = 7 # pt
|
||||
FONT_SIZE_TITLE = 8 # pt — panel label size
|
||||
FONT_SIZE_LABEL = 8 # pt — bold lowercase panel labels
|
||||
LINE_WIDTH = 0.75 # pt — axis/tick stroke
|
||||
TICK_SIZE = 4 # pt
|
||||
|
||||
# ── Figure dimensions (single-column Nature width: 89 mm) ───────────
|
||||
FIG_WIDTH_MM = 89
|
||||
FIG_HEIGHT_MM = 60
|
||||
FIG_WIDTH_IN = FIG_WIDTH_MM / 25.4
|
||||
FIG_HEIGHT_IN = FIG_HEIGHT_MM / 25.4
|
||||
DPI = 450 # Nature min for exported figures
|
||||
|
||||
# ── Background ────────────────────────────────────────────────────────
|
||||
BG_DARK = "#1a1a2e" # app/notebook dark background
|
||||
BG_CHART = "transparent" # chart area — let container bg show through
|
||||
AXIS_COLOR = "#cccccc" # axis lines, ticks
|
||||
TEXT_COLOR = "#e0e0e0" # labels on dark bg
|
||||
TITLE_COLOR = "#ffffff" # title / panel label
|
||||
|
||||
|
||||
# ── Pipeline schema colours (for DAG / lineage viz) ──────────────────
|
||||
SCHEMA_COLORS: dict[str, str] = {
|
||||
"core": BLUE,
|
||||
"input_layer": CYAN,
|
||||
"cclf": "#5588CC",
|
||||
"claims_preprocessing": ORANGE,
|
||||
"readmissions": GREEN,
|
||||
"ahrq_measures": YELLOW,
|
||||
"pharmacy": PURPLE,
|
||||
"quality_measures": RED,
|
||||
"hcc_suspecting": "#AA7744",
|
||||
"provider_attribution": GREY,
|
||||
"main": "#55AA99",
|
||||
"data_quality": "#5588CC",
|
||||
}
|
||||
EXTERNAL_COLOR = "#888888"
|
||||
|
||||
|
||||
def schema_color(schema: str) -> str:
|
||||
"""Return the palette colour for a pipeline schema."""
|
||||
return SCHEMA_COLORS.get(schema, EXTERNAL_COLOR)
|
||||
|
||||
|
||||
# ── Altair theme ──────────────────────────────────────────────────────
|
||||
|
||||
def _altair_theme() -> dict:
|
||||
"""Vega-Lite config dict conforming to Nature specs."""
|
||||
return {
|
||||
"config": {
|
||||
"background": BG_CHART,
|
||||
"font": FONT_FAMILY,
|
||||
"title": {
|
||||
"font": FONT_FAMILY,
|
||||
"fontSize": FONT_SIZE_TITLE,
|
||||
"fontWeight": "bold",
|
||||
"color": TITLE_COLOR,
|
||||
"anchor": "start",
|
||||
},
|
||||
"axis": {
|
||||
"labelFont": FONT_FAMILY,
|
||||
"labelFontSize": FONT_SIZE_AXIS,
|
||||
"labelColor": TEXT_COLOR,
|
||||
"titleFont": FONT_FAMILY,
|
||||
"titleFontSize": FONT_SIZE_AXIS,
|
||||
"titleColor": TEXT_COLOR,
|
||||
"gridColor": "transparent",
|
||||
"gridOpacity": 0,
|
||||
"domainColor": AXIS_COLOR,
|
||||
"domainWidth": LINE_WIDTH,
|
||||
"tickColor": AXIS_COLOR,
|
||||
"tickSize": TICK_SIZE,
|
||||
"tickWidth": LINE_WIDTH,
|
||||
},
|
||||
"legend": {
|
||||
"labelFont": FONT_FAMILY,
|
||||
"labelFontSize": FONT_SIZE_BODY,
|
||||
"labelColor": TEXT_COLOR,
|
||||
"titleFont": FONT_FAMILY,
|
||||
"titleFontSize": FONT_SIZE_BODY,
|
||||
"titleColor": TEXT_COLOR,
|
||||
"symbolSize": 60,
|
||||
},
|
||||
"view": {
|
||||
"stroke": "transparent",
|
||||
},
|
||||
"range": {
|
||||
"category": PALETTE,
|
||||
},
|
||||
"mark": {
|
||||
"color": PALETTE[0],
|
||||
},
|
||||
"bar": {
|
||||
"color": PALETTE[0],
|
||||
},
|
||||
"arc": {
|
||||
"stroke": BG_DARK,
|
||||
"strokeWidth": 1,
|
||||
},
|
||||
"line": {
|
||||
"strokeWidth": 1.5,
|
||||
},
|
||||
"point": {
|
||||
"size": 40,
|
||||
},
|
||||
"area": {
|
||||
"opacity": 0.7,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def altair_theme() -> None:
|
||||
"""Register and enable the Nature theme for Altair."""
|
||||
import altair as alt
|
||||
|
||||
alt.themes.register("nature", _altair_theme)
|
||||
alt.themes.enable("nature")
|
||||
|
||||
|
||||
# ── Matplotlib rcParams ──────────────────────────────────────────────
|
||||
|
||||
def mpl_rc() -> dict:
|
||||
"""Return an rcParams dict for ``matplotlib.rcParams.update()``."""
|
||||
return {
|
||||
"font.family": "sans-serif",
|
||||
"font.sans-serif": ["Helvetica", "Arial", "DejaVu Sans"],
|
||||
"font.size": FONT_SIZE_BODY,
|
||||
"axes.titlesize": FONT_SIZE_TITLE,
|
||||
"axes.titleweight": "bold",
|
||||
"axes.labelsize": FONT_SIZE_AXIS,
|
||||
"axes.linewidth": LINE_WIDTH,
|
||||
"axes.edgecolor": AXIS_COLOR,
|
||||
"axes.labelcolor": TEXT_COLOR,
|
||||
"axes.facecolor": "none",
|
||||
"axes.grid": False,
|
||||
"axes.prop_cycle": __import__("cycler").cycler(color=PALETTE),
|
||||
"xtick.major.size": TICK_SIZE,
|
||||
"xtick.major.width": LINE_WIDTH,
|
||||
"xtick.color": AXIS_COLOR,
|
||||
"xtick.labelsize": FONT_SIZE_BODY,
|
||||
"ytick.major.size": TICK_SIZE,
|
||||
"ytick.major.width": LINE_WIDTH,
|
||||
"ytick.color": AXIS_COLOR,
|
||||
"ytick.labelsize": FONT_SIZE_BODY,
|
||||
"figure.figsize": (FIG_WIDTH_IN, FIG_HEIGHT_IN),
|
||||
"figure.dpi": DPI,
|
||||
"figure.facecolor": BG_DARK,
|
||||
"savefig.dpi": DPI,
|
||||
"savefig.facecolor": BG_DARK,
|
||||
"legend.fontsize": FONT_SIZE_BODY,
|
||||
"legend.frameon": False,
|
||||
"text.color": TEXT_COLOR,
|
||||
}
|
||||
|
||||
|
||||
# ── Cytoscape.js style helpers ───────────────────────────────────────
|
||||
|
||||
def cytoscape_node_style() -> dict:
|
||||
"""Base Cytoscape.js node style properties (Nature-conformant)."""
|
||||
return {
|
||||
"font-family": FONT_FAMILY,
|
||||
"font-size": "9px",
|
||||
"text-valign": "center",
|
||||
"text-halign": "center",
|
||||
"shape": "round-rectangle",
|
||||
}
|
||||
@@ -3,20 +3,25 @@
|
||||
<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 {
|
||||
--bg: #1a1a2e; --panel: #16213e; --panel-hi: #0f3460;
|
||||
--border: #333; --text: #e0e0e0; --muted: #888; --dim: #666;
|
||||
--c-mul: #e15759; --c-dot: #59a14f; --c-add: #4e79a7;
|
||||
--c-vec: #edc948; --c-scalar: #b07aa1; --c-ref: #76b7b2;
|
||||
--c-fn: #f28e2b; --c-cond: #ff9da7;
|
||||
/* 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: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@@ -26,7 +31,8 @@
|
||||
display: flex; align-items: baseline; gap: 16px;
|
||||
}
|
||||
header h1 {
|
||||
font-size: 20px; font-weight: 600; color: #fff;
|
||||
font-size: 20px; font-weight: 700; color: var(--text);
|
||||
font-family: "Playfair Display", Georgia, serif;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
header .subtitle {
|
||||
@@ -46,7 +52,7 @@
|
||||
}
|
||||
.tab:hover { color: var(--text); background: var(--panel); }
|
||||
.tab.active {
|
||||
color: #fff; background: var(--panel);
|
||||
color: var(--text); background: var(--panel);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
@@ -114,7 +120,7 @@
|
||||
color: var(--dim); font-size: 13px; padding: 40px 0; text-align: center;
|
||||
}
|
||||
.detail h3 {
|
||||
font-size: 14px; color: #fff; margin-bottom: 12px;
|
||||
font-size: 14px; color: var(--text); margin-bottom: 12px;
|
||||
font-weight: 600; letter-spacing: -0.2px;
|
||||
}
|
||||
.detail-latex {
|
||||
@@ -122,7 +128,7 @@
|
||||
margin-bottom: 14px; text-align: center; overflow-x: auto;
|
||||
}
|
||||
.detail-doc {
|
||||
font-size: 13px; color: #bbb; line-height: 1.6; margin-bottom: 12px;
|
||||
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; }
|
||||
@@ -148,7 +154,7 @@
|
||||
border-radius: 10px; padding: 20px; margin-top: 20px;
|
||||
}
|
||||
.evaluator h3 {
|
||||
font-size: 14px; color: #fff; margin-bottom: 14px; font-weight: 600;
|
||||
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));
|
||||
@@ -169,7 +175,7 @@
|
||||
outline: none; border-color: var(--c-add);
|
||||
}
|
||||
.eval-result {
|
||||
font-size: 22px; font-weight: 600; color: #fff;
|
||||
font-size: 22px; font-weight: 600; color: var(--text);
|
||||
display: flex; align-items: baseline; gap: 8px;
|
||||
}
|
||||
.eval-result small {
|
||||
|
||||
@@ -165,13 +165,13 @@ services:
|
||||
- GITEA__server__ROOT_URL=https://git.${DOMAIN:-fhirworx.io}/
|
||||
- GITEA__server__SSH_DOMAIN=git.${DOMAIN:-fhirworx.io}
|
||||
- GITEA__webhook__ALLOWED_HOST_LIST=woodpecker-server,ci.${DOMAIN:-fhirworx.io},${HOST_IP:-192.168.1.192},172.19.0.0/16
|
||||
- GITEA__ui__THEMES=gitea-auto,gitea-light,gitea-dark,loch
|
||||
- GITEA__ui__DEFAULT_THEME=loch
|
||||
- GITEA__ui__THEMES=gitea-auto,gitea-light,gitea-dark,fhirworx
|
||||
- GITEA__ui__DEFAULT_THEME=fhirworx
|
||||
volumes:
|
||||
- gitea_data:/var/lib/gitea
|
||||
- gitea_config:/etc/gitea
|
||||
- ./infra/gitea/custom:/var/lib/gitea/custom
|
||||
- ./assets/css/gitea.css:/var/lib/gitea/custom/public/assets/css/theme-loch.css:ro
|
||||
- ./assets/css/gitea.css:/var/lib/gitea/custom/public/assets/css/theme-fhirworx.css:ro
|
||||
ports:
|
||||
- "2222:2222"
|
||||
- "3000:3000"
|
||||
@@ -459,7 +459,7 @@ services:
|
||||
- ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./infra/nginx/index.html:/usr/share/nginx/html/index.html:ro
|
||||
- ./assets/css/dashboard.css:/usr/share/nginx/html/dashboard.css:ro
|
||||
- ./assets/css/inject.css:/usr/share/nginx/html/loch.css:ro
|
||||
- ./assets/css/inject.css:/usr/share/nginx/html/fhirworx.css:ro
|
||||
- ./assets/icons/fav32.png:/usr/share/nginx/html/fav32.png:ro
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* LOCH — deep-navy design system — matches assets/css/inject.css */
|
||||
/* fhirworx design system — matches assets/css/inject.css */
|
||||
|
||||
:root {
|
||||
--ifm-color-primary: #4488dd;
|
||||
|
||||
@@ -1,59 +1,105 @@
|
||||
{{template "base/head" .}}
|
||||
<style>
|
||||
/* HTI-5 dashboard tokens — defined locally so the inline template is self-contained.
|
||||
gitea.css maps Gitea's --color-* vars to hti5 tokens at load time. */
|
||||
.homelab-dashboard {
|
||||
max-width: 1400px;
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
padding: 3rem 1.5rem;
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
}
|
||||
.homelab-dashboard .hdr {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
border-bottom: 4px solid var(--color-red);
|
||||
|
||||
/* Rule lines */
|
||||
.homelab-dashboard .rule-line {
|
||||
border: none;
|
||||
border-top: 3px solid var(--color-text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.homelab-dashboard .hdr h1 {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-size: 2rem !important;
|
||||
.homelab-dashboard .rule-line-thin {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Masthead */
|
||||
.homelab-dashboard .masthead {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-row h1 {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: clamp(2.5rem, 5vw, 3.5rem) !important;
|
||||
font-weight: 800 !important;
|
||||
color: var(--color-text) !important;
|
||||
letter-spacing: 6px !important;
|
||||
text-shadow: 3px 3px 0 var(--color-body) !important;
|
||||
line-height: 1.4 !important;
|
||||
letter-spacing: -0.02em !important;
|
||||
line-height: 1.1 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.homelab-dashboard .subtitle {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
color: var(--color-primary);
|
||||
font-size: 0.55rem;
|
||||
margin-top: 15px;
|
||||
letter-spacing: 2px;
|
||||
.homelab-dashboard .masthead-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
align-self: flex-end;
|
||||
padding-bottom: 0.35rem;
|
||||
}
|
||||
.homelab-dashboard .score-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
margin-top: 20px;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-size: 0.5rem;
|
||||
.homelab-dashboard .tagline {
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-text-light);
|
||||
max-width: 65ch;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.homelab-dashboard .score-item {
|
||||
color: var(--color-text);
|
||||
background: var(--color-primary);
|
||||
padding: 6px 14px;
|
||||
border: 2px solid var(--color-primary-light-2);
|
||||
|
||||
/* Section header */
|
||||
.homelab-dashboard .section-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.homelab-dashboard .section-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.4rem;
|
||||
display: block;
|
||||
}
|
||||
.homelab-dashboard .section-header h2 {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: 1.75rem !important;
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
color: var(--color-text) !important;
|
||||
line-height: 1.2 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.homelab-dashboard .grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
margin-top: 20px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* Tiles */
|
||||
.homelab-dashboard .tile {
|
||||
background: var(--color-box-body);
|
||||
border: 3px solid var(--color-secondary-dark-2) !important;
|
||||
padding: 25px;
|
||||
border: 1px solid var(--color-border) !important;
|
||||
border-top: 3px solid var(--color-text) !important;
|
||||
padding: 1.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none !important;
|
||||
display: block;
|
||||
@@ -64,97 +110,143 @@
|
||||
background: var(--color-body);
|
||||
}
|
||||
.homelab-dashboard .tile .icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
.homelab-dashboard .tile.storage .icon,
|
||||
.homelab-dashboard .tile.database .icon { color: var(--color-red); }
|
||||
.homelab-dashboard .tile.git .icon { color: var(--color-primary-light-2); }
|
||||
.homelab-dashboard .tile.ci .icon { color: var(--color-primary-light-1); }
|
||||
.homelab-dashboard .tile.database .icon { color: var(--color-warning); }
|
||||
.homelab-dashboard .tile.git .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.ci .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.notebook .icon,
|
||||
.homelab-dashboard .tile.research .icon { color: var(--color-text); }
|
||||
.homelab-dashboard .tile.catalog .icon { color: var(--color-primary-light-2); }
|
||||
.homelab-dashboard .tile.query .icon { color: var(--color-primary-light-1); }
|
||||
.homelab-dashboard .tile.observability .icon { color: var(--color-red); }
|
||||
.homelab-dashboard .tile.metrics .icon { color: var(--color-primary-light-2); }
|
||||
.homelab-dashboard .tile.logs .icon { color: var(--color-primary-light-1); }
|
||||
.homelab-dashboard .tile.catalog .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.query .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.observability .icon { color: var(--color-warning); }
|
||||
.homelab-dashboard .tile.metrics .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.logs .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.proxy .icon { color: var(--color-text); }
|
||||
.homelab-dashboard .tile-title {
|
||||
font-family: 'IBM Plex Mono', monospace !important;
|
||||
font-size: 0.7rem !important;
|
||||
color: var(--color-text) !important;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.homelab-dashboard .tile-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-light-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.homelab-dashboard .tile-port {
|
||||
|
||||
/* Tile eyebrow — mono metadata label */
|
||||
.homelab-dashboard .tile-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
color: var(--color-primary-light-2);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Tile title — Playfair Display */
|
||||
.homelab-dashboard .tile-title {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: 1.25rem !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 1.2 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
color: var(--color-text) !important;
|
||||
margin: 0.25rem 0 0.5rem !important;
|
||||
border: none !important;
|
||||
text-transform: none !important;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.homelab-dashboard .tile:hover .tile-title {
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
/* Tile description — Source Serif 4 */
|
||||
.homelab-dashboard .tile-desc {
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text-light);
|
||||
line-height: 1.55;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Tile port — mono metadata */
|
||||
.homelab-dashboard .tile-port {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.65rem;
|
||||
color: var(--color-text-light);
|
||||
opacity: 0.7;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Status dot */
|
||||
.homelab-dashboard .status-dot {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--color-green);
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--color-success);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
.homelab-dashboard .tile-badge {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 1.25rem;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
color: var(--color-primary-foreground, #F7F5F0);
|
||||
font-size: 0.6rem;
|
||||
padding: 3px 7px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.homelab-dashboard .tile-badge.new { background: var(--color-warning); }
|
||||
.homelab-dashboard .tile-badge.gpu { background: var(--color-primary); }
|
||||
.homelab-dashboard .tile-badge.data { background: var(--color-primary); }
|
||||
|
||||
/* Bottom accent */
|
||||
.homelab-dashboard .stripe {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: var(--color-red);
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
.homelab-dashboard .hdr-badge {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 20px;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
color: var(--color-text);
|
||||
font-size: 0.35rem;
|
||||
padding: 4px 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.homelab-dashboard .hdr-badge.new { background: var(--color-red); }
|
||||
.homelab-dashboard .hdr-badge.gpu { background: var(--color-primary); }
|
||||
.homelab-dashboard .hdr-badge.data { background: var(--color-primary); }
|
||||
|
||||
/* Footer */
|
||||
.homelab-dashboard .ftr {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: var(--color-text-light-2);
|
||||
font-size: 0.7rem;
|
||||
border-top: 3px solid var(--color-secondary-dark-2);
|
||||
margin-top: 20px;
|
||||
padding-top: 1rem;
|
||||
border-top: 3px solid var(--color-text);
|
||||
color: var(--color-text-light);
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.homelab-dashboard .ftr span { color: var(--color-red); }
|
||||
.homelab-dashboard .ftr span { color: var(--color-warning); }
|
||||
</style>
|
||||
<div class="homelab-dashboard">
|
||||
<div class="hdr">
|
||||
<h1>HOMELAB</h1>
|
||||
<p class="subtitle">COMMAND CENTER</p>
|
||||
<div class="score-bar">
|
||||
<span class="score-item">SERVICES: 15</span>
|
||||
<span class="score-item">STATUS: ONLINE</span>
|
||||
<div class="masthead">
|
||||
<div class="rule-line"></div>
|
||||
<div class="masthead-row">
|
||||
<h1>Homelab</h1>
|
||||
<span class="masthead-eyebrow">git.fhirworx.io</span>
|
||||
</div>
|
||||
<p class="tagline">
|
||||
Self-hosted infrastructure for FHIR data engineering — version control, CI/CD,
|
||||
notebooks, and the full lakehouse stack.
|
||||
</p>
|
||||
<div class="rule-line-thin"></div>
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
<span class="section-eyebrow">Services</span>
|
||||
<h2>Infrastructure</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
||||
<span class="hdr-badge new">NEW</span>
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🔗</span>
|
||||
<h2 class="tile-title">TRAEFIK</h2>
|
||||
<div class="tile-eyebrow">Networking</div>
|
||||
<div class="tile-title">Traefik</div>
|
||||
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -163,7 +255,8 @@
|
||||
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">💾</span>
|
||||
<h2 class="tile-title">RUSTFS</h2>
|
||||
<div class="tile-eyebrow">Storage</div>
|
||||
<div class="tile-title">RustFS</div>
|
||||
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -172,8 +265,9 @@
|
||||
<a data-subdomain="git" class="tile git">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🐙</span>
|
||||
<h2 class="tile-title">GITEA</h2>
|
||||
<p class="tile-desc">Self-hosted Git service with packages & CI</p>
|
||||
<div class="tile-eyebrow">Version Control</div>
|
||||
<div class="tile-title">Gitea</div>
|
||||
<p class="tile-desc">Self-hosted Git service with packages and CI</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -181,68 +275,75 @@
|
||||
<a data-subdomain="ci" target="_blank" class="tile ci">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🔨</span>
|
||||
<h2 class="tile-title">WOODPECKER</h2>
|
||||
<div class="tile-eyebrow">CI/CD</div>
|
||||
<div class="tile-title">Woodpecker</div>
|
||||
<p class="tile-desc">Simple yet powerful CI/CD engine</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="notebooks" target="_blank" class="tile notebook">
|
||||
<span class="hdr-badge gpu">GPU</span>
|
||||
<span class="tile-badge gpu">GPU</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📑</span>
|
||||
<h2 class="tile-title">NOTEBOOKS</h2>
|
||||
<p class="tile-desc">Marimo reactive notebooks with CUDA</p>
|
||||
<div class="tile-eyebrow">Analysis</div>
|
||||
<div class="tile-title">Notebooks</div>
|
||||
<p class="tile-desc">Marimo reactive notebooks with CUDA acceleration</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="zotero" target="_blank" class="tile research">
|
||||
<span class="hdr-badge gpu">GPU</span>
|
||||
<span class="tile-badge gpu">GPU</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📚</span>
|
||||
<h2 class="tile-title">ZOTERO</h2>
|
||||
<div class="tile-eyebrow">Research</div>
|
||||
<div class="tile-title">Zotero</div>
|
||||
<p class="tile-desc">Research reference manager in the cloud</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="docs" target="_blank" class="tile research">
|
||||
<span class="hdr-badge new">NEW</span>
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📖</span>
|
||||
<h2 class="tile-title">DOCS</h2>
|
||||
<div class="tile-eyebrow">Documentation</div>
|
||||
<div class="tile-title">Docs</div>
|
||||
<p class="tile-desc">API reference and CMS bibliography browser</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="nessie" data-path="/api/v2/config" target="_blank" class="tile catalog">
|
||||
<span class="hdr-badge data">DATA</span>
|
||||
<span class="tile-badge data">Data</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🌳</span>
|
||||
<h2 class="tile-title">NESSIE</h2>
|
||||
<p class="tile-desc">Git-like versioned data catalog for Iceberg</p>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Nessie</div>
|
||||
<p class="tile-desc">Git-like versioned data catalog for Apache Iceberg</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="trino" target="_blank" class="tile query">
|
||||
<span class="hdr-badge data">DATA</span>
|
||||
<span class="tile-badge data">Data</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🔍</span>
|
||||
<h2 class="tile-title">TRINO</h2>
|
||||
<div class="tile-eyebrow">Query</div>
|
||||
<div class="tile-title">Trino</div>
|
||||
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
||||
<span class="hdr-badge new">NEW</span>
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">⭐</span>
|
||||
<h2 class="tile-title">POLARIS</h2>
|
||||
<p class="tile-desc">Apache Iceberg catalog with governance & Unity-style access</p>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Polaris</div>
|
||||
<p class="tile-desc">Apache Iceberg catalog with governance and Unity-style access</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -250,8 +351,9 @@
|
||||
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📊</span>
|
||||
<h2 class="tile-title">GRAFANA</h2>
|
||||
<p class="tile-desc">Observability dashboards for logs and traces</p>
|
||||
<div class="tile-eyebrow">Observability</div>
|
||||
<div class="tile-title">Grafana</div>
|
||||
<p class="tile-desc">Observability dashboards for metrics, logs, and traces</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -259,7 +361,8 @@
|
||||
<a data-subdomain="jaeger" target="_blank" class="tile metrics">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">👁</span>
|
||||
<h2 class="tile-title">JAEGER</h2>
|
||||
<div class="tile-eyebrow">Tracing</div>
|
||||
<div class="tile-title">Jaeger</div>
|
||||
<p class="tile-desc">Distributed tracing for microservices</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -268,7 +371,8 @@
|
||||
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📈</span>
|
||||
<h2 class="tile-title">PROMETHEUS</h2>
|
||||
<div class="tile-eyebrow">Metrics</div>
|
||||
<div class="tile-title">Prometheus</div>
|
||||
<p class="tile-desc">Metrics collection and alerting</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -277,7 +381,8 @@
|
||||
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📋</span>
|
||||
<h2 class="tile-title">LOKI</h2>
|
||||
<div class="tile-eyebrow">Logs</div>
|
||||
<div class="tile-title">Loki</div>
|
||||
<p class="tile-desc">Log aggregation system by Grafana</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -285,14 +390,11 @@
|
||||
</div>
|
||||
|
||||
<div class="ftr">
|
||||
MADE WITH <span>♥</span> FOR THE HOMELAB
|
||||
<span>fhirworx.io</span> — self-hosted data lakehouse — © 2025
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
// Strip the leading subdomain (e.g. "git.") to get the base domain.
|
||||
// Works for any depth: "git.fhirworx.io" -> "fhirworx.io",
|
||||
// "git.homelab.fhirworx.io" -> "homelab.fhirworx.io".
|
||||
var parts = location.hostname.split('.');
|
||||
var base = parts.length > 2 ? parts.slice(1).join('.') : location.hostname;
|
||||
document.querySelectorAll('.homelab-dashboard [data-subdomain]').forEach(function(el) {
|
||||
|
||||
@@ -3,27 +3,40 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FHIRWORX COMMAND CENTER</title>
|
||||
<title>Fhirworx — Data Infrastructure</title>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/fav32.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="/dashboard.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>FHIRWORX</h1>
|
||||
<p class="subtitle">COMMAND CENTER</p>
|
||||
<div class="score-bar">
|
||||
<span class="score-item">SERVICES: 15</span>
|
||||
<span class="score-item">STATUS: ONLINE</span>
|
||||
|
||||
<div class="masthead">
|
||||
<div class="rule-line"></div>
|
||||
<div class="masthead-row">
|
||||
<h1>Fhirworx</h1>
|
||||
<span class="masthead-eyebrow">dashboard.fhirworx.io</span>
|
||||
</div>
|
||||
</header>
|
||||
<p class="tagline">
|
||||
A self-hosted data lakehouse for FHIR analytics — version-controlled storage,
|
||||
distributed SQL, and end-to-end observability in a single stack.
|
||||
</p>
|
||||
<div class="rule-line-thin"></div>
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
<span class="section-eyebrow">Services</span>
|
||||
<h2>Infrastructure</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
||||
<span class="status"></span>
|
||||
<span class="icon">🔗</span>
|
||||
<h2 class="tile-title">TRAEFIK</h2>
|
||||
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
||||
<div class="tile-eyebrow">Networking</div>
|
||||
<div class="tile-title">Traefik</div>
|
||||
<p class="tile-desc">Cloud-native reverse proxy and load balancer with automatic TLS</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -31,7 +44,8 @@
|
||||
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
||||
<span class="status"></span>
|
||||
<span class="icon">💾</span>
|
||||
<h2 class="tile-title">RUSTFS</h2>
|
||||
<div class="tile-eyebrow">Storage</div>
|
||||
<div class="tile-title">RustFS</div>
|
||||
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -40,8 +54,9 @@
|
||||
<a data-subdomain="git" target="_blank" class="tile git">
|
||||
<span class="status"></span>
|
||||
<span class="icon">🐙</span>
|
||||
<h2 class="tile-title">GITEA</h2>
|
||||
<p class="tile-desc">Self-hosted Git service with packages & CI</p>
|
||||
<div class="tile-eyebrow">Version Control</div>
|
||||
<div class="tile-title">Gitea</div>
|
||||
<p class="tile-desc">Self-hosted Git service with packages and CI</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -49,7 +64,8 @@
|
||||
<a data-subdomain="ci" target="_blank" class="tile ci">
|
||||
<span class="status"></span>
|
||||
<span class="icon">🔨</span>
|
||||
<h2 class="tile-title">WOODPECKER</h2>
|
||||
<div class="tile-eyebrow">CI/CD</div>
|
||||
<div class="tile-title">Woodpecker</div>
|
||||
<p class="tile-desc">Simple yet powerful CI/CD engine</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -59,8 +75,9 @@
|
||||
<span class="badge gpu">GPU</span>
|
||||
<span class="status"></span>
|
||||
<span class="icon">📑</span>
|
||||
<h2 class="tile-title">NOTEBOOKS</h2>
|
||||
<p class="tile-desc">Marimo reactive notebooks with CUDA</p>
|
||||
<div class="tile-eyebrow">Analysis</div>
|
||||
<div class="tile-title">Notebooks</div>
|
||||
<p class="tile-desc">Marimo reactive notebooks with CUDA acceleration</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -69,7 +86,8 @@
|
||||
<span class="badge gpu">GPU</span>
|
||||
<span class="status"></span>
|
||||
<span class="icon">📚</span>
|
||||
<h2 class="tile-title">ZOTERO</h2>
|
||||
<div class="tile-eyebrow">Research</div>
|
||||
<div class="tile-title">Zotero</div>
|
||||
<p class="tile-desc">Research reference manager in the cloud</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -78,27 +96,30 @@
|
||||
<a data-subdomain="docs" target="_blank" class="tile research">
|
||||
<span class="status"></span>
|
||||
<span class="icon">📖</span>
|
||||
<h2 class="tile-title">DOCS</h2>
|
||||
<div class="tile-eyebrow">Documentation</div>
|
||||
<div class="tile-title">Docs</div>
|
||||
<p class="tile-desc">API reference and CMS bibliography browser</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="nessie" data-path="/api/v2/config" target="_blank" class="tile catalog">
|
||||
<span class="badge data">DATA</span>
|
||||
<span class="badge data">Data</span>
|
||||
<span class="status"></span>
|
||||
<span class="icon">🌳</span>
|
||||
<h2 class="tile-title">NESSIE</h2>
|
||||
<p class="tile-desc">Git-like versioned data catalog for Iceberg</p>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Nessie</div>
|
||||
<p class="tile-desc">Git-like versioned data catalog for Apache Iceberg</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="trino" target="_blank" class="tile query">
|
||||
<span class="badge data">DATA</span>
|
||||
<span class="badge data">Data</span>
|
||||
<span class="status"></span>
|
||||
<span class="icon">🔍</span>
|
||||
<h2 class="tile-title">TRINO</h2>
|
||||
<div class="tile-eyebrow">Query</div>
|
||||
<div class="tile-title">Trino</div>
|
||||
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -107,8 +128,9 @@
|
||||
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
||||
<span class="status"></span>
|
||||
<span class="icon">⭐</span>
|
||||
<h2 class="tile-title">POLARIS</h2>
|
||||
<p class="tile-desc">Apache Iceberg catalog with governance & Unity-style access</p>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Polaris</div>
|
||||
<p class="tile-desc">Apache Iceberg catalog with governance and Unity-style access control</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -116,8 +138,9 @@
|
||||
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
||||
<span class="status"></span>
|
||||
<span class="icon">📊</span>
|
||||
<h2 class="tile-title">GRAFANA</h2>
|
||||
<p class="tile-desc">Observability dashboards for logs and traces</p>
|
||||
<div class="tile-eyebrow">Observability</div>
|
||||
<div class="tile-title">Grafana</div>
|
||||
<p class="tile-desc">Observability dashboards for metrics, logs, and traces</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
@@ -125,7 +148,8 @@
|
||||
<a data-subdomain="jaeger" target="_blank" class="tile metrics">
|
||||
<span class="status"></span>
|
||||
<span class="icon">👁</span>
|
||||
<h2 class="tile-title">JAEGER</h2>
|
||||
<div class="tile-eyebrow">Tracing</div>
|
||||
<div class="tile-title">Jaeger</div>
|
||||
<p class="tile-desc">Distributed tracing for microservices</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -134,7 +158,8 @@
|
||||
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
||||
<span class="status"></span>
|
||||
<span class="icon">📈</span>
|
||||
<h2 class="tile-title">PROMETHEUS</h2>
|
||||
<div class="tile-eyebrow">Metrics</div>
|
||||
<div class="tile-title">Prometheus</div>
|
||||
<p class="tile-desc">Metrics collection and alerting</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -143,7 +168,8 @@
|
||||
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
||||
<span class="status"></span>
|
||||
<span class="icon">📋</span>
|
||||
<h2 class="tile-title">LOKI</h2>
|
||||
<div class="tile-eyebrow">Logs</div>
|
||||
<div class="tile-title">Loki</div>
|
||||
<p class="tile-desc">Log aggregation system by Grafana</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -152,7 +178,8 @@
|
||||
<a data-subdomain="api" data-path="/health" target="_blank" class="tile proxy">
|
||||
<span class="status"></span>
|
||||
<span class="icon">🔌</span>
|
||||
<h2 class="tile-title">API</h2>
|
||||
<div class="tile-eyebrow">API</div>
|
||||
<div class="tile-title">Platform API</div>
|
||||
<p class="tile-desc">FastAPI platform backend</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
@@ -160,12 +187,11 @@
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
MADE WITH <span>♥</span> FOR FHIRWORX
|
||||
<span>fhirworx.io</span> — self-hosted data lakehouse — © 2025
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// All tiles use https://{subdomain}.fhirworx.io
|
||||
(function() {
|
||||
var base = location.hostname.replace(/^dashboard\./, '');
|
||||
document.querySelectorAll('[data-subdomain]').forEach(function(el) {
|
||||
|
||||
@@ -44,13 +44,13 @@ http:
|
||||
referrerPolicy: "strict-origin-when-cross-origin"
|
||||
customResponseHeaders:
|
||||
X-Robots-Tag: "noindex, nofollow"
|
||||
inject-loch:
|
||||
inject-fhirworx:
|
||||
plugin:
|
||||
rewrite-body:
|
||||
lastModified: true
|
||||
rewrites:
|
||||
- regex: "</head>"
|
||||
replacement: '<link rel="icon" type="image/png" sizes="32x32" href="//dashboard.{{ $domain }}/fav32.png"><link rel="stylesheet" type="text/css" href="//dashboard.{{ $domain }}/loch.css"></head>'
|
||||
replacement: '<link rel="icon" type="image/png" sizes="32x32" href="//dashboard.{{ $domain }}/fav32.png"><link rel="stylesheet" type="text/css" href="//dashboard.{{ $domain }}/fhirworx.css"></head>'
|
||||
routers:
|
||||
# auth.DOMAIN — oauth2-proxy public endpoints (login, callback, sign_out)
|
||||
auth-host:
|
||||
@@ -71,7 +71,7 @@ http:
|
||||
entryPoints:
|
||||
- web
|
||||
{{- $mwList := list -}}
|
||||
{{- if get $svc "theme" }}{{ $mwList = append $mwList "inject-loch" }}{{ end -}}
|
||||
{{- if get $svc "theme" }}{{ $mwList = append $mwList "inject-fhirworx" }}{{ end -}}
|
||||
{{- $extra := get $svc "mw" | default "" -}}
|
||||
{{- if ne $extra "" }}{{ range splitList "," $extra }}{{ $mwList = append $mwList (trim .) }}{{ end }}{{ end -}}
|
||||
{{- if $mwList }}
|
||||
@@ -100,7 +100,7 @@ http:
|
||||
entryPoints:
|
||||
- web
|
||||
{{- $mwList := list -}}
|
||||
{{- if get $svc "theme" }}{{ $mwList = append $mwList "inject-loch" }}{{ end -}}
|
||||
{{- if get $svc "theme" }}{{ $mwList = append $mwList "inject-fhirworx" }}{{ end -}}
|
||||
{{- $extra := get $svc "mw" | default "" -}}
|
||||
{{- if ne $extra "" }}{{ range splitList "," $extra }}{{ $mwList = append $mwList (trim .) }}{{ end }}{{ end -}}
|
||||
{{- if $mwList }}
|
||||
@@ -128,7 +128,7 @@ http:
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
- inject-loch
|
||||
- inject-fhirworx
|
||||
- gitea-sso
|
||||
- infra-headers
|
||||
traefik-dashboard-tls:
|
||||
@@ -138,7 +138,7 @@ http:
|
||||
- websecure
|
||||
tls: {}
|
||||
middlewares:
|
||||
- inject-loch
|
||||
- inject-fhirworx
|
||||
- gitea-sso
|
||||
- infra-headers
|
||||
services:
|
||||
|
||||
@@ -35,7 +35,7 @@ def _():
|
||||
connect.theme()
|
||||
_s3 = connect.obstore() # noqa: F841 — marimo auto-discovers in Files
|
||||
|
||||
from nature import PALETTE # noqa: E402 — needs theme() to add styles to path
|
||||
from fhirworx import PALETTE # noqa: E402 — needs theme() to add styles to path
|
||||
|
||||
con = connect.duckdb()
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ _assets_dir = str(_Path(__file__).resolve().parents[2] / "assets")
|
||||
if _assets_dir not in _sys.path:
|
||||
_sys.path.insert(0, _assets_dir)
|
||||
|
||||
from nature import EXTERNAL_COLOR as _EXTERNAL_COLOR # noqa: E402, I001
|
||||
from nature import SCHEMA_COLORS as _SCHEMA_COLORS # noqa: E402, F401
|
||||
from nature import schema_color as _color # noqa: E402
|
||||
from fhirworx import EXTERNAL_COLOR as _EXTERNAL_COLOR # noqa: E402, I001
|
||||
from fhirworx import SCHEMA_COLORS as _SCHEMA_COLORS # noqa: E402, F401
|
||||
from fhirworx import schema_color as _color # noqa: E402
|
||||
|
||||
from aco.pipe.base import Pipeline # noqa: E402
|
||||
|
||||
|
||||
@@ -152,12 +152,12 @@ def obstore(*, bucket: str = "lakehouse") -> Any:
|
||||
|
||||
|
||||
def theme() -> None:
|
||||
"""Activate the Nature/loch Altair chart theme."""
|
||||
"""Activate the fhirworx Altair chart theme."""
|
||||
import sys
|
||||
|
||||
assets_dir = str(ROOT / "assets")
|
||||
if assets_dir not in sys.path:
|
||||
sys.path.insert(0, assets_dir)
|
||||
from nature import altair_theme
|
||||
from fhirworx import altair_theme
|
||||
|
||||
altair_theme()
|
||||
|
||||
@@ -188,4 +188,4 @@ class TestTheme:
|
||||
connect.theme()
|
||||
import altair as alt
|
||||
|
||||
assert alt.themes.active == "nature"
|
||||
assert alt.theme.active == "fhirworx"
|
||||
|
||||
@@ -99,9 +99,9 @@ class TestInfraMarimo:
|
||||
def test_home_page_js_exists(self):
|
||||
assert (INFRA_MARIMO / "home-page-patched.js").is_file()
|
||||
|
||||
def test_no_loch_css_duplicate(self):
|
||||
"""loch.css should NOT be in infra/marimo/ — served from assets/css/."""
|
||||
assert not (INFRA_MARIMO / "loch.css").exists()
|
||||
def test_no_fhirworx_css_duplicate(self):
|
||||
"""fhirworx.css should NOT be in infra/marimo/ — served from assets/css/."""
|
||||
assert not (INFRA_MARIMO / "fhirworx.css").exists()
|
||||
|
||||
|
||||
class TestNoStaleReferences:
|
||||
|
||||
Reference in New Issue
Block a user