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
|
## 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
|
## Data lakehouse
|
||||||
|
|
||||||
@@ -491,7 +491,7 @@ stack/
|
|||||||
│ └── zot/
|
│ └── zot/
|
||||||
├── infra/ Service configs and Dockerfiles
|
├── infra/ Service configs and Dockerfiles
|
||||||
│ ├── images/ All Dockerfiles (api, notebooks, zotero, docs, mc)
|
│ ├── 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
|
│ ├── grafana/ Dashboards and datasource provisioning
|
||||||
│ ├── prometheus/ Metrics collection
|
│ ├── prometheus/ Metrics collection
|
||||||
│ ├── loki/ Log aggregation
|
│ ├── loki/ Log aggregation
|
||||||
@@ -499,7 +499,7 @@ stack/
|
|||||||
├── assets/ Branding, styles, generated artifacts
|
├── assets/ Branding, styles, generated artifacts
|
||||||
│ ├── css/ Per-service CSS (dashboard, gitea, marimo, woodpecker)
|
│ ├── css/ Per-service CSS (dashboard, gitea, marimo, woodpecker)
|
||||||
│ ├── icons/ Favicons, logos, coverage badge
|
│ ├── icons/ Favicons, logos, coverage badge
|
||||||
│ └── nature.py Nature-conformant chart palette
|
│ └── fhirworx.py fhirworx chart palette and Altair theme
|
||||||
├── cloud/ Cloud provider deployment guides
|
├── cloud/ Cloud provider deployment guides
|
||||||
│ ├── self-hosted/ Docker Compose (default)
|
│ ├── self-hosted/ Docker Compose (default)
|
||||||
│ ├── aws/ S3, RDS, Glue, ECS
|
│ ├── aws/ S3, RDS, Glue, ECS
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ ROOT_KEY=$KEY uv run python -m api.auth derive $COMMIT_SHA --redact
|
|||||||
|
|
||||||
## Design system
|
## 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
|
## Data lakehouse
|
||||||
|
|
||||||
@@ -472,7 +472,7 @@ stack/
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
├── infra/ Service configs and Dockerfiles
|
├── infra/ Service configs and Dockerfiles
|
||||||
│ ├── images/ All Dockerfiles (api, notebooks, zotero, docs, mc)
|
│ ├── 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
|
│ ├── grafana/ Dashboards and datasource provisioning
|
||||||
│ ├── prometheus/ Metrics collection
|
│ ├── prometheus/ Metrics collection
|
||||||
│ ├── loki/ Log aggregation
|
│ ├── loki/ Log aggregation
|
||||||
@@ -480,7 +480,7 @@ stack/
|
|||||||
├── assets/ Branding, styles, generated artifacts
|
├── assets/ Branding, styles, generated artifacts
|
||||||
│ ├── css/ Per-service CSS (dashboard, gitea, marimo, woodpecker)
|
│ ├── css/ Per-service CSS (dashboard, gitea, marimo, woodpecker)
|
||||||
│ ├── icons/ Favicons, logos, coverage badge
|
│ ├── icons/ Favicons, logos, coverage badge
|
||||||
│ └── nature.py Nature-conformant chart palette
|
│ └── fhirworx.py fhirworx chart palette and Altair theme
|
||||||
├── cloud/ Cloud provider deployment guides
|
├── cloud/ Cloud provider deployment guides
|
||||||
│ ├── self-hosted/ Docker Compose (default)
|
│ ├── self-hosted/ Docker Compose (default)
|
||||||
│ ├── aws/ S3, RDS, Glue, ECS
|
│ ├── 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 {
|
:root {
|
||||||
--ds-navy: #0a1628;
|
--background: #F7F5F0;
|
||||||
--ds-navy-light: #162040;
|
--foreground: #1A1A18;
|
||||||
--ds-navy-mid: #1e2d50;
|
--card: #FAFAF7;
|
||||||
--ds-slate: #2a3f5f;
|
--card-foreground: #1A1A18;
|
||||||
--ds-steel: #7a90b0;
|
--primary: #1C2B3A;
|
||||||
--ds-white: #f0f0f0;
|
--primary-foreground: #F7F5F0;
|
||||||
--ds-blue: #4488dd;
|
--secondary: #EDEBE6;
|
||||||
--ds-blue-light: #5588dd;
|
--muted-foreground: #6B6B68;
|
||||||
--ds-blue-bright: #88aaee;
|
--border: #D4D0C8;
|
||||||
--ds-red: #ee5577;
|
--sidebar: #1C2B3A;
|
||||||
--ds-red-light: #dd4466;
|
--sidebar-foreground: #B8C5D0;
|
||||||
--ds-green: #44aa44;
|
--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 {
|
body {
|
||||||
font-family: 'IBM Plex Mono', 'Courier New', monospace;
|
font-family: var(--font-body);
|
||||||
background: var(--ds-navy);
|
font-size: 16px;
|
||||||
|
line-height: 1.65;
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
color: var(--ds-white);
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1400px;
|
max-width: 1180px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 3rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
/* ── Masthead ──────────────────────────────────────────────── */
|
||||||
text-align: center;
|
|
||||||
padding: 40px 20px;
|
.masthead {
|
||||||
border-bottom: 4px solid var(--ds-red);
|
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 {
|
h1 {
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
font-family: var(--font-display);
|
||||||
font-weight: 600;
|
font-weight: 800;
|
||||||
font-size: 2rem;
|
font-size: clamp(2.5rem, 5vw, 3.5rem);
|
||||||
color: var(--ds-white);
|
line-height: 1.1;
|
||||||
letter-spacing: 6px;
|
letter-spacing: -0.02em;
|
||||||
text-shadow: 3px 3px 0 var(--ds-navy-mid);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.masthead-eyebrow {
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
font-family: var(--font-mono);
|
||||||
color: var(--ds-blue-bright);
|
font-size: 0.7rem;
|
||||||
font-size: 0.55rem;
|
text-transform: uppercase;
|
||||||
margin-top: 15px;
|
letter-spacing: 0.12em;
|
||||||
letter-spacing: 2px;
|
color: var(--muted-foreground);
|
||||||
|
align-self: flex-end;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.score-bar {
|
.tagline {
|
||||||
display: flex;
|
font-family: var(--font-body);
|
||||||
justify-content: center;
|
font-size: 1.125rem;
|
||||||
gap: 40px;
|
color: var(--muted-foreground);
|
||||||
margin-top: 20px;
|
max-width: 65ch;
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
line-height: 1.65;
|
||||||
font-size: 0.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.score-item {
|
/* ── Services section ─────────────────────────────────────── */
|
||||||
color: var(--ds-white);
|
|
||||||
background: var(--ds-blue);
|
.section-header {
|
||||||
padding: 6px 14px;
|
margin-bottom: 1.5rem;
|
||||||
border: 2px solid var(--ds-blue-light);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 20px;
|
gap: 1.25rem;
|
||||||
padding: 20px 0;
|
padding: 0;
|
||||||
margin-top: 20px;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile {
|
.tile {
|
||||||
background: var(--ds-navy-light);
|
background: var(--card);
|
||||||
border: 3px solid var(--ds-slate);
|
border: 1px solid var(--border);
|
||||||
padding: 25px;
|
border-top: 3px solid var(--foreground);
|
||||||
|
padding: 1.5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.2s ease;
|
transition: border-color 0.15s ease, background 0.15s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile:hover {
|
.tile:hover {
|
||||||
border-color: var(--ds-blue);
|
border-color: var(--primary);
|
||||||
background: var(--ds-navy-mid);
|
background: var(--card);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All tile variants use the same base style */
|
.tile:hover .tile-title {
|
||||||
.tile .icon {
|
color: var(--primary);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Status dot */
|
||||||
.status {
|
.status {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px;
|
top: 1rem;
|
||||||
right: 15px;
|
right: 1rem;
|
||||||
width: 10px;
|
width: 8px;
|
||||||
height: 10px;
|
height: 8px;
|
||||||
background: var(--ds-green);
|
background: var(--chart-4);
|
||||||
border-radius: 50%;
|
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 {
|
.stripe {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 3px;
|
height: 2px;
|
||||||
background: var(--ds-red);
|
background: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
/* ── Badge labels ─────────────────────────────────────────── */
|
||||||
text-align: center;
|
|
||||||
padding: 40px;
|
|
||||||
color: var(--ds-slate);
|
|
||||||
font-size: 0.7rem;
|
|
||||||
border-top: 3px solid var(--ds-slate);
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer span {
|
|
||||||
color: var(--ds-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Badges */
|
|
||||||
.badge {
|
.badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
left: 20px;
|
left: 1.25rem;
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
font-family: var(--font-mono);
|
||||||
color: var(--ds-white);
|
color: var(--primary-foreground);
|
||||||
font-size: 0.35rem;
|
font-size: 0.6rem;
|
||||||
padding: 4px 8px;
|
padding: 3px 7px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge.new { background: var(--ds-red); }
|
.badge.new { background: var(--chart-5); }
|
||||||
.badge.gpu { background: var(--ds-blue); }
|
.badge.gpu { background: var(--primary); }
|
||||||
.badge.data { background: var(--ds-blue); }
|
.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 */
|
/* Theme metadata */
|
||||||
gitea-theme-meta-info {
|
gitea-theme-meta-info {
|
||||||
--theme-display-name: "Loch";
|
--theme-display-name: "Fhirworx";
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--is-dark-theme: true;
|
--is-dark-theme: false;
|
||||||
|
|
||||||
/* Loch Color Palette */
|
/* HTI-5 Color Palette */
|
||||||
--ds-navy: #0a1628;
|
--background: #F7F5F0;
|
||||||
--ds-navy-light: #162040;
|
--foreground: #1A1A18;
|
||||||
--ds-navy-mid: #1e2d50;
|
--card: #FAFAF7;
|
||||||
--ds-slate: #2a3f5f;
|
--primary: #1C2B3A;
|
||||||
--ds-steel: #7a90b0;
|
--primary-foreground: #F7F5F0;
|
||||||
--ds-white: #f0f0f0;
|
--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;
|
--font-display: "Playfair Display", Georgia, serif;
|
||||||
--ds-blue-light: #5588dd;
|
--font-body: "Source Serif 4", Georgia, serif;
|
||||||
--ds-blue-bright: #88aaee;
|
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||||
|
|
||||||
--ds-red: #ee5577;
|
/* Gitea color mappings — derived from hti5 tokens */
|
||||||
--ds-red-light: #dd4466;
|
--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;
|
--color-secondary: var(--muted-foreground);
|
||||||
--ds-green-light: #66cc66;
|
--color-secondary-light: #9B9B98;
|
||||||
--ds-yellow: #ddaa33;
|
--color-secondary-dark: var(--foreground);
|
||||||
|
|
||||||
/* 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);
|
|
||||||
|
|
||||||
/* Background colors */
|
/* Background colors */
|
||||||
--color-body: var(--ds-navy);
|
--color-body: var(--background);
|
||||||
--color-box-body: var(--ds-navy);
|
--color-box-body: var(--card);
|
||||||
--color-box-header: var(--ds-navy-light);
|
--color-box-header: var(--secondary);
|
||||||
|
|
||||||
/* Text colors */
|
/* Text colors */
|
||||||
--color-text: var(--ds-white);
|
--color-text: var(--foreground);
|
||||||
--color-text-light: var(--ds-steel);
|
--color-text-light: var(--muted-foreground);
|
||||||
--color-text-dark: var(--ds-white);
|
--color-text-dark: var(--foreground);
|
||||||
|
|
||||||
/* Input colors */
|
/* Input colors */
|
||||||
--color-input-text: var(--ds-white);
|
--color-input-text: var(--foreground);
|
||||||
--color-input-background: var(--ds-navy-mid);
|
--color-input-background: var(--card);
|
||||||
--color-input-border: var(--ds-slate);
|
--color-input-border: var(--border);
|
||||||
|
|
||||||
/* Status colors */
|
/* Status colors */
|
||||||
--color-success: var(--ds-green);
|
--color-success: var(--chart-4);
|
||||||
--color-success-text: var(--ds-white);
|
--color-success-text: #fff;
|
||||||
--color-warning: var(--ds-yellow);
|
--color-warning: var(--chart-5);
|
||||||
--color-warning-text: var(--ds-navy);
|
--color-warning-text: #fff;
|
||||||
--color-error: var(--ds-red);
|
--color-error: var(--destructive);
|
||||||
--color-error-text: var(--ds-white);
|
--color-error-text: #fff;
|
||||||
--color-info: var(--ds-blue-light);
|
--color-info: var(--primary);
|
||||||
--color-info-text: var(--ds-white);
|
--color-info-text: var(--primary-foreground);
|
||||||
|
|
||||||
/* Border */
|
/* Border */
|
||||||
--color-border: var(--ds-slate);
|
--color-border: var(--border);
|
||||||
|
|
||||||
/* Link colors */
|
/* Link colors */
|
||||||
--color-link: var(--ds-blue-bright);
|
--color-link: var(--primary);
|
||||||
--color-link-hover: var(--ds-white);
|
--color-link-hover: var(--foreground);
|
||||||
|
|
||||||
/* Code */
|
/* Code */
|
||||||
--color-code-bg: var(--ds-navy-light);
|
--color-code-bg: var(--secondary);
|
||||||
--color-code-border: var(--ds-slate);
|
--color-code-border: var(--border);
|
||||||
|
|
||||||
/* Shadows */
|
/* Shadows */
|
||||||
--color-shadow: rgba(0, 0, 0, 0.4);
|
--color-shadow: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global styles */
|
/* Global styles */
|
||||||
body {
|
body {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
background: var(--ds-navy) !important;
|
background: var(--background) !important;
|
||||||
|
color: var(--foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Headings */
|
/* Headings */
|
||||||
h1, h2, h3, h4, h5, h6,
|
h1, h2, h3, h4, h5, h6,
|
||||||
.header-wrapper .header {
|
.header-wrapper .header {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-display) !important;
|
||||||
font-weight: 600 !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; }
|
h1 { font-size: 22px !important; color: var(--foreground) !important; }
|
||||||
h2 { font-size: 14px !important; color: var(--ds-blue-bright) !important; }
|
h2 { font-size: 17px !important; color: var(--primary) !important; }
|
||||||
h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
h3 { font-size: 14px !important; color: var(--foreground) !important; }
|
||||||
|
|
||||||
/* Navbar */
|
/* Navbar */
|
||||||
.ui.secondary.menu.navbar,
|
.ui.secondary.menu.navbar,
|
||||||
.navbar {
|
.navbar {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--sidebar) !important;
|
||||||
border-bottom: 3px solid var(--ds-slate) !important;
|
border-bottom: 3px solid var(--foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .item {
|
.navbar .item {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--sidebar-foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .item:hover {
|
.navbar .item:hover {
|
||||||
background: var(--ds-navy-mid) !important;
|
background: var(--sidebar-accent) !important;
|
||||||
color: var(--ds-blue-bright) !important;
|
color: var(--sidebar-accent-foreground, #EEE9E0) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .item.active {
|
.navbar .item.active {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--sidebar-accent) !important;
|
||||||
color: var(--ds-white) !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
.ui.button,
|
.ui.button,
|
||||||
.button {
|
.button {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
transition: all 0.15s ease !important;
|
transition: all 0.15s ease !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.primary.button,
|
.ui.primary.button,
|
||||||
.ui.blue.button {
|
.ui.blue.button {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--primary) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--primary-foreground) !important;
|
||||||
border: 2px solid var(--ds-blue-light) !important;
|
border: 1px solid var(--primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.primary.button:hover,
|
.ui.primary.button:hover,
|
||||||
.ui.blue.button:hover {
|
.ui.blue.button:hover {
|
||||||
background: var(--ds-blue-light) !important;
|
background: var(--sidebar-accent) !important;
|
||||||
color: var(--ds-white) !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.green.button {
|
.ui.green.button {
|
||||||
background: var(--ds-green) !important;
|
background: var(--chart-4) !important;
|
||||||
color: var(--ds-white) !important;
|
color: #fff !important;
|
||||||
border: 2px solid var(--ds-green-light) !important;
|
border: 1px solid var(--chart-4) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.green.button:hover {
|
.ui.green.button:hover {
|
||||||
background: var(--ds-green-light) !important;
|
opacity: 0.85;
|
||||||
color: var(--ds-navy) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.red.button {
|
.ui.red.button {
|
||||||
background: var(--ds-red) !important;
|
background: var(--destructive) !important;
|
||||||
color: var(--ds-white) !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Segments and boxes */
|
/* Segments and boxes */
|
||||||
@@ -164,43 +165,43 @@ h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
|||||||
.ui.segments,
|
.ui.segments,
|
||||||
.repository,
|
.repository,
|
||||||
.box {
|
.box {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.attached.header,
|
.ui.attached.header,
|
||||||
.box-header {
|
.box-header {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--secondary) !important;
|
||||||
border-bottom: 2px solid var(--ds-slate) !important;
|
border-bottom: 1px solid var(--border) !important;
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-display) !important;
|
||||||
font-size: 11px !important;
|
font-size: 13px !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tables */
|
/* Tables */
|
||||||
.ui.table {
|
.ui.table {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.table th {
|
.ui.table th {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--primary) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--primary-foreground) !important;
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-mono) !important;
|
||||||
font-size: 10px !important;
|
font-size: 10px !important;
|
||||||
text-transform: uppercase !important;
|
text-transform: uppercase !important;
|
||||||
border: 1px solid var(--ds-blue-light) !important;
|
border: 1px solid var(--sidebar-accent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.table td {
|
.ui.table td {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
border: 1px solid var(--ds-navy-light) !important;
|
border: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.table tr:hover td {
|
.ui.table tr:hover td {
|
||||||
background: rgba(51, 102, 204, 0.2) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Forms */
|
/* Forms */
|
||||||
@@ -208,38 +209,70 @@ h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
|||||||
.ui.form textarea,
|
.ui.form textarea,
|
||||||
.ui.form select,
|
.ui.form select,
|
||||||
.ui.input input {
|
.ui.input input {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
background: var(--ds-navy-mid) !important;
|
background: var(--card) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.form input:focus,
|
.ui.form input:focus,
|
||||||
.ui.form textarea:focus,
|
.ui.form textarea:focus,
|
||||||
.ui.input input:focus {
|
.ui.input input:focus {
|
||||||
border-color: var(--ds-blue-light) !important;
|
border-color: var(--primary) !important;
|
||||||
background: var(--ds-navy-mid) !important;
|
background: var(--card) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Labels */
|
/* Labels — mono for metadata labels (issue numbers, ref badges) */
|
||||||
.ui.label {
|
.ui.label {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-mono) !important;
|
||||||
border-radius: 4px !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 */
|
/* Dropdowns */
|
||||||
.ui.dropdown .menu {
|
.ui.dropdown .menu {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.dropdown .menu .item {
|
.ui.dropdown .menu .item {
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.dropdown .menu .item:hover {
|
.ui.dropdown .menu .item:hover {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Code and diffs */
|
/* Code and diffs */
|
||||||
@@ -247,101 +280,101 @@ h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
|||||||
.file-view,
|
.file-view,
|
||||||
.CodeMirror,
|
.CodeMirror,
|
||||||
.highlight {
|
.highlight {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-mono) !important;
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-file-box .diff-file-header {
|
.diff-file-box .diff-file-header {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Commit graph */
|
/* Commit graph */
|
||||||
.repository .commit-list .commit {
|
.repository .commit-list .commit {
|
||||||
border-bottom: 1px solid var(--ds-navy-light) !important;
|
border-bottom: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Issues and PRs */
|
/* Issues and PRs */
|
||||||
.issue-list .item,
|
.issue-list .item,
|
||||||
.pull-list .item {
|
.pull-list .item {
|
||||||
border-bottom: 1px solid var(--ds-navy-light) !important;
|
border-bottom: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
.footer {
|
.footer {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--secondary) !important;
|
||||||
border-top: 3px solid var(--ds-slate) !important;
|
border-top: 3px solid var(--foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cards */
|
/* Cards */
|
||||||
.ui.card,
|
.ui.card,
|
||||||
.ui.cards .card {
|
.ui.cards .card {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Messages */
|
/* Messages */
|
||||||
.ui.message {
|
.ui.message {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.positive.message,
|
.ui.positive.message,
|
||||||
.ui.success.message {
|
.ui.success.message {
|
||||||
background: rgba(68, 170, 68, 0.2) !important;
|
background: color-mix(in srgb, var(--chart-4) 12%, var(--background)) !important;
|
||||||
border: 2px solid var(--ds-green) !important;
|
border: 1px solid var(--chart-4) !important;
|
||||||
color: var(--ds-green-light) !important;
|
color: var(--chart-4) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.negative.message,
|
.ui.negative.message,
|
||||||
.ui.error.message {
|
.ui.error.message {
|
||||||
background: rgba(204, 34, 68, 0.2) !important;
|
background: color-mix(in srgb, var(--destructive) 10%, var(--background)) !important;
|
||||||
border: 2px solid var(--ds-red) !important;
|
border: 1px solid var(--destructive) !important;
|
||||||
color: var(--ds-red-light) !important;
|
color: var(--destructive) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.warning.message {
|
.ui.warning.message {
|
||||||
background: rgba(221, 170, 51, 0.2) !important;
|
background: color-mix(in srgb, var(--chart-5) 10%, var(--background)) !important;
|
||||||
border: 2px solid var(--ds-yellow) !important;
|
border: 1px solid var(--chart-5) !important;
|
||||||
color: var(--ds-yellow) !important;
|
color: var(--chart-5) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.info.message {
|
.ui.info.message {
|
||||||
background: rgba(85, 136, 221, 0.2) !important;
|
background: color-mix(in srgb, var(--primary) 10%, var(--background)) !important;
|
||||||
border: 2px solid var(--ds-blue-light) !important;
|
border: 1px solid var(--primary) !important;
|
||||||
color: var(--ds-blue-bright) !important;
|
color: var(--primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbars */
|
/* Scrollbars */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 12px;
|
width: 10px;
|
||||||
height: 12px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: var(--ds-navy-light);
|
background: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: var(--ds-slate);
|
background: var(--border);
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--ds-steel);
|
background: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Selection */
|
/* Selection */
|
||||||
::selection {
|
::selection {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--primary) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--primary-foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Links */
|
/* Links */
|
||||||
a {
|
a {
|
||||||
color: var(--ds-blue-bright) !important;
|
color: var(--primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
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 {
|
:root {
|
||||||
/* Loch Color Palette */
|
/* HTI-5 Color Palette */
|
||||||
--ds-navy: #0a1628;
|
--background: #F7F5F0;
|
||||||
--ds-navy-light: #162040;
|
--foreground: #1A1A18;
|
||||||
--ds-navy-mid: #1e2d50;
|
--card: #FAFAF7;
|
||||||
--ds-slate: #2a3f5f;
|
--primary: #1C2B3A;
|
||||||
--ds-steel: #7a90b0;
|
--primary-foreground: #F7F5F0;
|
||||||
--ds-white: #f0f0f0;
|
--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;
|
--font-display: "Playfair Display", Georgia, serif;
|
||||||
--ds-blue-light: #5588dd;
|
--font-body: "Source Serif 4", Georgia, serif;
|
||||||
--ds-blue-bright: #88aaee;
|
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||||
|
|
||||||
--ds-red: #ee5577;
|
/* Woodpecker color overrides — mapped from hti5 tokens */
|
||||||
--ds-red-light: #dd4466;
|
--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;
|
--color-wp-success-100: #56B397;
|
||||||
--ds-green-light: #66cc66;
|
--color-wp-success-200: #2E8B6E;
|
||||||
--ds-yellow: #ddaa33;
|
|
||||||
|
|
||||||
/* Woodpecker color overrides */
|
--color-wp-error-100: #D9534F;
|
||||||
--color-wp-primary-100: var(--ds-blue-bright);
|
--color-wp-error-200: #C0392B;
|
||||||
--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-success-100: var(--ds-green-light);
|
--color-wp-warning-100: #DDA060;
|
||||||
--color-wp-success-200: var(--ds-green);
|
--color-wp-warning-200: #C8702A;
|
||||||
|
|
||||||
--color-wp-error-100: var(--ds-red-light);
|
--color-wp-gray-100: #1A1A18;
|
||||||
--color-wp-error-200: var(--ds-red);
|
--color-wp-gray-200: #6B6B68;
|
||||||
|
--color-wp-gray-300: #D4D0C8;
|
||||||
--color-wp-warning-100: var(--ds-yellow);
|
--color-wp-gray-400: #EDEBE6;
|
||||||
--color-wp-warning-200: #bb8822;
|
--color-wp-gray-500: #F7F5F0;
|
||||||
|
--color-wp-gray-600: #FAFAF7;
|
||||||
--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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global styles */
|
/* Global styles */
|
||||||
body,
|
body,
|
||||||
html {
|
html {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
background: var(--ds-navy) !important;
|
background: var(--background) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Headings */
|
/* Headings */
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-display) !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
letter-spacing: 0.5px !important;
|
line-height: 1.3 !important;
|
||||||
line-height: 1.8 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 { font-size: 18px !important; color: var(--ds-white) !important; }
|
h1 { font-size: 22px !important; color: var(--foreground) !important; }
|
||||||
h2 { font-size: 14px !important; color: var(--ds-blue-bright) !important; }
|
h2 { font-size: 17px !important; color: var(--primary) !important; }
|
||||||
h3 { font-size: 12px !important; color: var(--ds-white) !important; }
|
h3 { font-size: 14px !important; color: var(--foreground) !important; }
|
||||||
|
|
||||||
/* Header/Navbar */
|
/* Header/Navbar */
|
||||||
header,
|
header,
|
||||||
@@ -72,16 +73,16 @@ nav,
|
|||||||
.navbar,
|
.navbar,
|
||||||
[class*="header"],
|
[class*="header"],
|
||||||
[class*="Header"] {
|
[class*="Header"] {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--sidebar) !important;
|
||||||
border-bottom: 3px solid var(--ds-slate) !important;
|
border-bottom: 3px solid var(--foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
aside,
|
aside,
|
||||||
[class*="sidebar"],
|
[class*="sidebar"],
|
||||||
[class*="Sidebar"] {
|
[class*="Sidebar"] {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--sidebar) !important;
|
||||||
border-right: 3px solid var(--ds-slate) !important;
|
border-right: 1px solid var(--sidebar-border, var(--sidebar-accent)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Main content area */
|
/* Main content area */
|
||||||
@@ -98,20 +99,20 @@ main,
|
|||||||
[class*="Panel"],
|
[class*="Panel"],
|
||||||
[class*="box"],
|
[class*="box"],
|
||||||
[class*="Box"] {
|
[class*="Box"] {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons — serif body for button labels */
|
||||||
button,
|
button,
|
||||||
[class*="btn"],
|
[class*="btn"],
|
||||||
[class*="Btn"],
|
[class*="Btn"],
|
||||||
[class*="button"],
|
[class*="button"],
|
||||||
[class*="Button"] {
|
[class*="Button"] {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
transition: all 0.15s ease !important;
|
transition: all 0.15s ease !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,16 +120,16 @@ button,
|
|||||||
[class*="primary"],
|
[class*="primary"],
|
||||||
[class*="Primary"],
|
[class*="Primary"],
|
||||||
button[type="submit"] {
|
button[type="submit"] {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--primary) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--primary-foreground) !important;
|
||||||
border: 2px solid var(--ds-blue-light) !important;
|
border: 1px solid var(--primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="primary"]:hover,
|
[class*="primary"]:hover,
|
||||||
[class*="Primary"]:hover,
|
[class*="Primary"]:hover,
|
||||||
button[type="submit"]:hover {
|
button[type="submit"]:hover {
|
||||||
background: var(--ds-blue-light) !important;
|
background: var(--sidebar-accent) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--sidebar-accent-foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Success/green buttons */
|
/* Success/green buttons */
|
||||||
@@ -136,15 +137,14 @@ button[type="submit"]:hover {
|
|||||||
[class*="Success"],
|
[class*="Success"],
|
||||||
[class*="green"],
|
[class*="green"],
|
||||||
[class*="Green"] {
|
[class*="Green"] {
|
||||||
background: var(--ds-green) !important;
|
background: var(--chart-4) !important;
|
||||||
color: var(--ds-white) !important;
|
color: #fff !important;
|
||||||
border: 2px solid var(--ds-green-light) !important;
|
border: 1px solid var(--chart-4) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="success"]:hover,
|
[class*="success"]:hover,
|
||||||
[class*="Success"]:hover {
|
[class*="Success"]:hover {
|
||||||
background: var(--ds-green-light) !important;
|
opacity: 0.85 !important;
|
||||||
color: var(--ds-navy) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Danger/red buttons */
|
/* Danger/red buttons */
|
||||||
@@ -154,60 +154,82 @@ button[type="submit"]:hover {
|
|||||||
[class*="Error"],
|
[class*="Error"],
|
||||||
[class*="red"],
|
[class*="red"],
|
||||||
[class*="Red"] {
|
[class*="Red"] {
|
||||||
background: var(--ds-red) !important;
|
background: var(--destructive) !important;
|
||||||
color: var(--ds-white) !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Forms */
|
/* Forms */
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
background: var(--ds-navy-mid) !important;
|
background: var(--card) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
padding: 8px 12px !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,
|
input:focus,
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
select:focus {
|
select:focus {
|
||||||
border-color: var(--ds-blue-light) !important;
|
border-color: var(--primary) !important;
|
||||||
outline: none !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 */
|
/* Tables */
|
||||||
table {
|
table {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-collapse: collapse !important;
|
border-collapse: collapse !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--primary) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--primary-foreground) !important;
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-mono) !important;
|
||||||
font-size: 10px !important;
|
font-size: 10px !important;
|
||||||
text-transform: uppercase !important;
|
text-transform: uppercase !important;
|
||||||
padding: 10px 12px !important;
|
padding: 10px 12px !important;
|
||||||
border: 1px solid var(--ds-blue-light) !important;
|
border: 1px solid var(--sidebar-accent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
background: var(--ds-navy) !important;
|
background: var(--card) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
padding: 8px 12px !important;
|
padding: 8px 12px !important;
|
||||||
border: 1px solid var(--ds-navy-light) !important;
|
border: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:nth-child(even) td {
|
tr:nth-child(even) td {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:hover td {
|
tr:hover td {
|
||||||
background: rgba(51, 102, 204, 0.2) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pipeline/build status colors */
|
/* Pipeline/build status colors */
|
||||||
@@ -215,14 +237,14 @@ tr:hover td {
|
|||||||
[class*="Running"],
|
[class*="Running"],
|
||||||
[class*="pending"],
|
[class*="pending"],
|
||||||
[class*="Pending"] {
|
[class*="Pending"] {
|
||||||
color: var(--ds-yellow) !important;
|
color: var(--chart-5) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="success"],
|
[class*="success"],
|
||||||
[class*="Success"],
|
[class*="Success"],
|
||||||
[class*="passed"],
|
[class*="passed"],
|
||||||
[class*="Passed"] {
|
[class*="Passed"] {
|
||||||
color: var(--ds-green-light) !important;
|
color: var(--chart-4) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="failure"],
|
[class*="failure"],
|
||||||
@@ -231,7 +253,7 @@ tr:hover td {
|
|||||||
[class*="Failed"],
|
[class*="Failed"],
|
||||||
[class*="error"],
|
[class*="error"],
|
||||||
[class*="Error"] {
|
[class*="Error"] {
|
||||||
color: var(--ds-red) !important;
|
color: var(--destructive) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Build logs */
|
/* Build logs */
|
||||||
@@ -241,29 +263,29 @@ code,
|
|||||||
[class*="Log"],
|
[class*="Log"],
|
||||||
[class*="console"],
|
[class*="console"],
|
||||||
[class*="Console"] {
|
[class*="Console"] {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-mono) !important;
|
||||||
background: var(--ds-navy) !important;
|
background: var(--secondary) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Inline code */
|
/* Inline code */
|
||||||
code {
|
code {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--secondary) !important;
|
||||||
color: var(--ds-blue-bright) !important;
|
color: var(--primary) !important;
|
||||||
padding: 2px 6px !important;
|
padding: 2px 6px !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Links */
|
/* Links */
|
||||||
a {
|
a {
|
||||||
color: var(--ds-blue-bright) !important;
|
color: var(--primary) !important;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--ds-white) !important;
|
color: var(--foreground) !important;
|
||||||
text-decoration: underline !important;
|
text-decoration: underline !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,16 +296,16 @@ a:hover {
|
|||||||
[class*="Menu"],
|
[class*="Menu"],
|
||||||
[class*="popover"],
|
[class*="popover"],
|
||||||
[class*="Popover"] {
|
[class*="Popover"] {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="dropdown"] li:hover,
|
[class*="dropdown"] li:hover,
|
||||||
[class*="menu"] li:hover,
|
[class*="menu"] li:hover,
|
||||||
[role="menuitem"]:hover,
|
[role="menuitem"]:hover,
|
||||||
[role="option"]:hover {
|
[role="option"]:hover {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Badges and tags */
|
/* Badges and tags */
|
||||||
@@ -293,62 +315,63 @@ a:hover {
|
|||||||
[class*="Tag"],
|
[class*="Tag"],
|
||||||
[class*="label"],
|
[class*="label"],
|
||||||
[class*="Label"] {
|
[class*="Label"] {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-mono) !important;
|
||||||
font-size: 11px !important;
|
font-size: 11px !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tooltips */
|
/* Tooltips */
|
||||||
[role="tooltip"],
|
[role="tooltip"],
|
||||||
[class*="tooltip"],
|
[class*="tooltip"],
|
||||||
[class*="Tooltip"] {
|
[class*="Tooltip"] {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
font-family: var(--font-body) !important;
|
||||||
background: var(--ds-navy-light) !important;
|
font-size: 13px !important;
|
||||||
color: var(--ds-white) !important;
|
background: var(--card) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
color: var(--foreground) !important;
|
||||||
border-radius: 4px !important;
|
border: 1px solid var(--border) !important;
|
||||||
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Progress bars */
|
/* Progress bars */
|
||||||
progress,
|
progress,
|
||||||
[class*="progress"],
|
[class*="progress"],
|
||||||
[class*="Progress"] {
|
[class*="Progress"] {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--secondary) !important;
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress::-webkit-progress-bar {
|
progress::-webkit-progress-bar {
|
||||||
background: var(--ds-navy-light) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress::-webkit-progress-value {
|
progress::-webkit-progress-value {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbars */
|
/* Scrollbars */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 12px;
|
width: 10px;
|
||||||
height: 12px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: var(--ds-navy-light);
|
background: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: var(--ds-slate);
|
background: var(--border);
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--ds-steel);
|
background: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Selection */
|
/* Selection */
|
||||||
::selection {
|
::selection {
|
||||||
background: var(--ds-blue) !important;
|
background: var(--primary) !important;
|
||||||
color: var(--ds-white) !important;
|
color: var(--primary-foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Icons - ensure visibility */
|
/* Icons - ensure visibility */
|
||||||
@@ -361,8 +384,8 @@ svg,
|
|||||||
/* Avatar images */
|
/* Avatar images */
|
||||||
img[class*="avatar"],
|
img[class*="avatar"],
|
||||||
[class*="avatar"] img {
|
[class*="avatar"] img {
|
||||||
border: 2px solid var(--ds-slate) !important;
|
border: 1px solid var(--border) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: var(--radius, 0.2rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Repo/pipeline list items */
|
/* Repo/pipeline list items */
|
||||||
@@ -370,10 +393,10 @@ img[class*="avatar"],
|
|||||||
[class*="Item"],
|
[class*="Item"],
|
||||||
[class*="list-item"],
|
[class*="list-item"],
|
||||||
[class*="ListItem"] {
|
[class*="ListItem"] {
|
||||||
border-bottom: 1px solid var(--ds-navy-light) !important;
|
border-bottom: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="item"]:hover,
|
[class*="item"]:hover,
|
||||||
[class*="Item"]:hover {
|
[class*="Item"]:hover {
|
||||||
background: rgba(51, 102, 204, 0.1) !important;
|
background: var(--secondary) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,32 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>ACO Pipeline DAG</title>
|
<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/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/dagre@0.8/dist/dagre.min.js"></script>
|
||||||
<script src="https://unpkg.com/cytoscape-dagre@2/cytoscape-dagre.js"></script>
|
<script src="https://unpkg.com/cytoscape-dagre@2/cytoscape-dagre.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { 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; }
|
#cy { width: 100vw; height: 100vh; }
|
||||||
#controls {
|
#controls {
|
||||||
position: fixed; top: 12px; left: 12px; z-index: 10;
|
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;
|
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 {
|
#search {
|
||||||
width: 100%; padding: 6px 8px; border-radius: 4px;
|
width: 100%; padding: 6px 8px; border-radius: 0;
|
||||||
border: 1px solid #444; background: #0f3460; color: #e0e0e0;
|
border: 1px solid #253748; background: #253748; color: #EEE9E0;
|
||||||
margin-bottom: 8px; font-size: 13px;
|
margin-bottom: 8px; font-size: 13px; font-family: "JetBrains Mono", monospace;
|
||||||
}
|
}
|
||||||
#legend { display: flex; flex-wrap: wrap; gap: 4px; }
|
#legend { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||||
.legend-item {
|
.legend-item {
|
||||||
@@ -33,16 +42,16 @@
|
|||||||
display: inline-block; flex-shrink: 0;
|
display: inline-block; flex-shrink: 0;
|
||||||
}
|
}
|
||||||
#tooltip {
|
#tooltip {
|
||||||
position: fixed; display: none; background: #16213e;
|
position: fixed; display: none; background: var(--panel);
|
||||||
border: 1px solid #444; border-radius: 6px; padding: 10px;
|
border: 1px solid var(--border); border-radius: 0; padding: 10px;
|
||||||
max-width: 340px; font-size: 12px; z-index: 20;
|
max-width: 340px; font-size: 12px; z-index: 20;
|
||||||
pointer-events: none; line-height: 1.4;
|
pointer-events: none; line-height: 1.4;
|
||||||
}
|
}
|
||||||
#tooltip .tt-name { font-weight: bold; color: #fff; margin-bottom: 4px; }
|
#tooltip .tt-name { font-weight: bold; color: var(--text); margin-bottom: 4px; font-family: "Playfair Display", Georgia, serif; }
|
||||||
#tooltip .tt-desc { color: #bbb; }
|
#tooltip .tt-desc { color: var(--muted); }
|
||||||
#stats {
|
#stats {
|
||||||
position: fixed; bottom: 12px; left: 12px; z-index: 10;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -5911,12 +5920,13 @@
|
|||||||
"background-color": "transparent",
|
"background-color": "transparent",
|
||||||
"background-opacity": 0.05,
|
"background-opacity": 0.05,
|
||||||
"border-width": 1,
|
"border-width": 1,
|
||||||
"border-color": "#444",
|
"border-color": "#D4D0C8",
|
||||||
"label": "data(label)",
|
"label": "data(label)",
|
||||||
"text-valign": "top",
|
"text-valign": "top",
|
||||||
"text-halign": "center",
|
"text-halign": "center",
|
||||||
"font-size": "14px",
|
"font-size": "13px",
|
||||||
"color": "#666",
|
"font-family": "Playfair Display, Georgia, serif",
|
||||||
|
"color": "#6B6B68",
|
||||||
"padding": "16px",
|
"padding": "16px",
|
||||||
"shape": "round-rectangle",
|
"shape": "round-rectangle",
|
||||||
}
|
}
|
||||||
@@ -5948,15 +5958,16 @@
|
|||||||
{
|
{
|
||||||
selector: "node.external",
|
selector: "node.external",
|
||||||
style: {
|
style: {
|
||||||
"background-color": "#2a2a2a",
|
"background-color": "#EDEBE6",
|
||||||
"border-width": 1,
|
"border-width": 1,
|
||||||
"border-style": "dashed",
|
"border-style": "dashed",
|
||||||
"border-color": "#666",
|
"border-color": "#D4D0C8",
|
||||||
"label": "data(label)",
|
"label": "data(label)",
|
||||||
"text-valign": "center",
|
"text-valign": "center",
|
||||||
"text-halign": "center",
|
"text-halign": "center",
|
||||||
"font-size": "8px",
|
"font-size": "8px",
|
||||||
"color": "#888",
|
"font-family": "JetBrains Mono, Fira Code, monospace",
|
||||||
|
"color": "#6B6B68",
|
||||||
"width": "label",
|
"width": "label",
|
||||||
"height": 24,
|
"height": 24,
|
||||||
"padding": "6px",
|
"padding": "6px",
|
||||||
@@ -5967,8 +5978,8 @@
|
|||||||
selector: "edge",
|
selector: "edge",
|
||||||
style: {
|
style: {
|
||||||
"width": 1.5,
|
"width": 1.5,
|
||||||
"line-color": "#444",
|
"line-color": "#D4D0C8",
|
||||||
"target-arrow-color": "#444",
|
"target-arrow-color": "#D4D0C8",
|
||||||
"target-arrow-shape": "triangle",
|
"target-arrow-shape": "triangle",
|
||||||
"curve-style": "bezier",
|
"curve-style": "bezier",
|
||||||
"arrow-scale": 0.8,
|
"arrow-scale": 0.8,
|
||||||
@@ -5977,16 +5988,16 @@
|
|||||||
{
|
{
|
||||||
selector: ".highlighted",
|
selector: ".highlighted",
|
||||||
style: {
|
style: {
|
||||||
"border-color": "#fff",
|
"border-color": "#1C2B3A",
|
||||||
"border-width": 3,
|
"border-width": 3,
|
||||||
"color": "#fff",
|
"color": "#1C2B3A",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: "edge.highlighted",
|
selector: "edge.highlighted",
|
||||||
style: {
|
style: {
|
||||||
"line-color": "#e0e0e0",
|
"line-color": "#1C2B3A",
|
||||||
"target-arrow-color": "#e0e0e0",
|
"target-arrow-color": "#1C2B3A",
|
||||||
"width": 2.5,
|
"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>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>PFS Equation Explorer</title>
|
<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">
|
<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>
|
<script src="https://unpkg.com/katex@0.16/dist/katex.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg: #1a1a2e; --panel: #16213e; --panel-hi: #0f3460;
|
/* HTI-5 palette */
|
||||||
--border: #333; --text: #e0e0e0; --muted: #888; --dim: #666;
|
--bg: #F7F5F0; --panel: #FAFAF7; --panel-hi: #EDEBE6;
|
||||||
--c-mul: #e15759; --c-dot: #59a14f; --c-add: #4e79a7;
|
--border: #D4D0C8; --text: #1A1A18; --muted: #6B6B68; --dim: #9B9B98;
|
||||||
--c-vec: #edc948; --c-scalar: #b07aa1; --c-ref: #76b7b2;
|
/* Node-kind colours — Tableau-style but on light bg */
|
||||||
--c-fn: #f28e2b; --c-cond: #ff9da7;
|
--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; }
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
body {
|
body {
|
||||||
background: var(--bg); color: var(--text);
|
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;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +31,8 @@
|
|||||||
display: flex; align-items: baseline; gap: 16px;
|
display: flex; align-items: baseline; gap: 16px;
|
||||||
}
|
}
|
||||||
header h1 {
|
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;
|
letter-spacing: -0.3px;
|
||||||
}
|
}
|
||||||
header .subtitle {
|
header .subtitle {
|
||||||
@@ -46,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
.tab:hover { color: var(--text); background: var(--panel); }
|
.tab:hover { color: var(--text); background: var(--panel); }
|
||||||
.tab.active {
|
.tab.active {
|
||||||
color: #fff; background: var(--panel);
|
color: var(--text); background: var(--panel);
|
||||||
border-color: var(--border);
|
border-color: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +120,7 @@
|
|||||||
color: var(--dim); font-size: 13px; padding: 40px 0; text-align: center;
|
color: var(--dim); font-size: 13px; padding: 40px 0; text-align: center;
|
||||||
}
|
}
|
||||||
.detail h3 {
|
.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;
|
font-weight: 600; letter-spacing: -0.2px;
|
||||||
}
|
}
|
||||||
.detail-latex {
|
.detail-latex {
|
||||||
@@ -122,7 +128,7 @@
|
|||||||
margin-bottom: 14px; text-align: center; overflow-x: auto;
|
margin-bottom: 14px; text-align: center; overflow-x: auto;
|
||||||
}
|
}
|
||||||
.detail-doc {
|
.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 { font-size: 12px; color: var(--muted); }
|
||||||
.detail-meta div { margin-bottom: 5px; }
|
.detail-meta div { margin-bottom: 5px; }
|
||||||
@@ -148,7 +154,7 @@
|
|||||||
border-radius: 10px; padding: 20px; margin-top: 20px;
|
border-radius: 10px; padding: 20px; margin-top: 20px;
|
||||||
}
|
}
|
||||||
.evaluator h3 {
|
.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 {
|
.eval-grid {
|
||||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||||
@@ -169,7 +175,7 @@
|
|||||||
outline: none; border-color: var(--c-add);
|
outline: none; border-color: var(--c-add);
|
||||||
}
|
}
|
||||||
.eval-result {
|
.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;
|
display: flex; align-items: baseline; gap: 8px;
|
||||||
}
|
}
|
||||||
.eval-result small {
|
.eval-result small {
|
||||||
|
|||||||
@@ -165,13 +165,13 @@ services:
|
|||||||
- GITEA__server__ROOT_URL=https://git.${DOMAIN:-fhirworx.io}/
|
- GITEA__server__ROOT_URL=https://git.${DOMAIN:-fhirworx.io}/
|
||||||
- GITEA__server__SSH_DOMAIN=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__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__THEMES=gitea-auto,gitea-light,gitea-dark,fhirworx
|
||||||
- GITEA__ui__DEFAULT_THEME=loch
|
- GITEA__ui__DEFAULT_THEME=fhirworx
|
||||||
volumes:
|
volumes:
|
||||||
- gitea_data:/var/lib/gitea
|
- gitea_data:/var/lib/gitea
|
||||||
- gitea_config:/etc/gitea
|
- gitea_config:/etc/gitea
|
||||||
- ./infra/gitea/custom:/var/lib/gitea/custom
|
- ./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:
|
ports:
|
||||||
- "2222:2222"
|
- "2222:2222"
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
@@ -459,7 +459,7 @@ services:
|
|||||||
- ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
- ./infra/nginx/index.html:/usr/share/nginx/html/index.html: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/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
|
- ./assets/icons/fav32.png:/usr/share/nginx/html/fav32.png:ro
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- 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 {
|
:root {
|
||||||
--ifm-color-primary: #4488dd;
|
--ifm-color-primary: #4488dd;
|
||||||
|
|||||||
@@ -1,59 +1,105 @@
|
|||||||
{{template "base/head" .}}
|
{{template "base/head" .}}
|
||||||
<style>
|
<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 {
|
.homelab-dashboard {
|
||||||
max-width: 1400px;
|
max-width: 1180px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 3rem 1.5rem;
|
||||||
|
font-family: "Source Serif 4", Georgia, serif;
|
||||||
}
|
}
|
||||||
.homelab-dashboard .hdr {
|
|
||||||
text-align: center;
|
/* Rule lines */
|
||||||
padding: 40px 20px;
|
.homelab-dashboard .rule-line {
|
||||||
border-bottom: 4px solid var(--color-red);
|
border: none;
|
||||||
|
border-top: 3px solid var(--color-text);
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.homelab-dashboard .hdr h1 {
|
.homelab-dashboard .rule-line-thin {
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
border: none;
|
||||||
font-size: 2rem !important;
|
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;
|
color: var(--color-text) !important;
|
||||||
letter-spacing: 6px !important;
|
letter-spacing: -0.02em !important;
|
||||||
text-shadow: 3px 3px 0 var(--color-body) !important;
|
line-height: 1.1 !important;
|
||||||
line-height: 1.4 !important;
|
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
.homelab-dashboard .subtitle {
|
.homelab-dashboard .masthead-eyebrow {
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||||
color: var(--color-primary);
|
font-size: 0.7rem;
|
||||||
font-size: 0.55rem;
|
text-transform: uppercase;
|
||||||
margin-top: 15px;
|
letter-spacing: 0.12em;
|
||||||
letter-spacing: 2px;
|
color: var(--color-text-light);
|
||||||
|
align-self: flex-end;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
.homelab-dashboard .score-bar {
|
.homelab-dashboard .tagline {
|
||||||
display: flex;
|
font-family: "Source Serif 4", Georgia, serif;
|
||||||
justify-content: center;
|
font-size: 1.125rem;
|
||||||
gap: 40px;
|
color: var(--color-text-light);
|
||||||
margin-top: 20px;
|
max-width: 65ch;
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
line-height: 1.65;
|
||||||
font-size: 0.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
.homelab-dashboard .score-item {
|
|
||||||
color: var(--color-text);
|
/* Section header */
|
||||||
background: var(--color-primary);
|
.homelab-dashboard .section-header {
|
||||||
padding: 6px 14px;
|
margin-bottom: 1.5rem;
|
||||||
border: 2px solid var(--color-primary-light-2);
|
|
||||||
}
|
}
|
||||||
|
.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 {
|
.homelab-dashboard .grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 20px;
|
gap: 1.25rem;
|
||||||
padding: 20px 0;
|
margin-bottom: 3rem;
|
||||||
margin-top: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tiles */
|
||||||
.homelab-dashboard .tile {
|
.homelab-dashboard .tile {
|
||||||
background: var(--color-box-body);
|
background: var(--color-box-body);
|
||||||
border: 3px solid var(--color-secondary-dark-2) !important;
|
border: 1px solid var(--color-border) !important;
|
||||||
padding: 25px;
|
border-top: 3px solid var(--color-text) !important;
|
||||||
|
padding: 1.5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.2s ease;
|
transition: border-color 0.15s ease, background 0.15s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -64,97 +110,143 @@
|
|||||||
background: var(--color-body);
|
background: var(--color-body);
|
||||||
}
|
}
|
||||||
.homelab-dashboard .tile .icon {
|
.homelab-dashboard .tile .icon {
|
||||||
font-size: 2.5rem;
|
font-size: 1.5rem;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 0.75rem;
|
||||||
display: block;
|
display: block;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.homelab-dashboard .tile.storage .icon,
|
.homelab-dashboard .tile.storage .icon,
|
||||||
.homelab-dashboard .tile.database .icon { color: var(--color-red); }
|
.homelab-dashboard .tile.database .icon { color: var(--color-warning); }
|
||||||
.homelab-dashboard .tile.git .icon { color: var(--color-primary-light-2); }
|
.homelab-dashboard .tile.git .icon { color: var(--color-primary); }
|
||||||
.homelab-dashboard .tile.ci .icon { color: var(--color-primary-light-1); }
|
.homelab-dashboard .tile.ci .icon { color: var(--color-primary); }
|
||||||
.homelab-dashboard .tile.notebook .icon,
|
.homelab-dashboard .tile.notebook .icon,
|
||||||
.homelab-dashboard .tile.research .icon { color: var(--color-text); }
|
.homelab-dashboard .tile.research .icon { color: var(--color-text); }
|
||||||
.homelab-dashboard .tile.catalog .icon { color: var(--color-primary-light-2); }
|
.homelab-dashboard .tile.catalog .icon { color: var(--color-primary); }
|
||||||
.homelab-dashboard .tile.query .icon { color: var(--color-primary-light-1); }
|
.homelab-dashboard .tile.query .icon { color: var(--color-primary); }
|
||||||
.homelab-dashboard .tile.observability .icon { color: var(--color-red); }
|
.homelab-dashboard .tile.observability .icon { color: var(--color-warning); }
|
||||||
.homelab-dashboard .tile.metrics .icon { color: var(--color-primary-light-2); }
|
.homelab-dashboard .tile.metrics .icon { color: var(--color-primary); }
|
||||||
.homelab-dashboard .tile.logs .icon { color: var(--color-primary-light-1); }
|
.homelab-dashboard .tile.logs .icon { color: var(--color-primary); }
|
||||||
.homelab-dashboard .tile.proxy .icon { color: var(--color-text); }
|
.homelab-dashboard .tile.proxy .icon { color: var(--color-text); }
|
||||||
.homelab-dashboard .tile-title {
|
|
||||||
font-family: 'IBM Plex Mono', monospace !important;
|
/* Tile eyebrow — mono metadata label */
|
||||||
font-size: 0.7rem !important;
|
.homelab-dashboard .tile-eyebrow {
|
||||||
color: var(--color-text) !important;
|
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||||
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 {
|
|
||||||
font-size: 0.7rem;
|
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;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Status dot */
|
||||||
.homelab-dashboard .status-dot {
|
.homelab-dashboard .status-dot {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px;
|
top: 1rem;
|
||||||
right: 15px;
|
right: 1rem;
|
||||||
width: 10px;
|
width: 8px;
|
||||||
height: 10px;
|
height: 8px;
|
||||||
background: var(--color-green);
|
background: var(--color-success);
|
||||||
border-radius: 50%;
|
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 {
|
.homelab-dashboard .stripe {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 3px;
|
height: 2px;
|
||||||
background: var(--color-red);
|
background: var(--color-border);
|
||||||
}
|
}
|
||||||
.homelab-dashboard .hdr-badge {
|
|
||||||
position: absolute;
|
/* Footer */
|
||||||
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); }
|
|
||||||
.homelab-dashboard .ftr {
|
.homelab-dashboard .ftr {
|
||||||
text-align: center;
|
padding-top: 1rem;
|
||||||
padding: 40px;
|
border-top: 3px solid var(--color-text);
|
||||||
color: var(--color-text-light-2);
|
color: var(--color-text-light);
|
||||||
font-size: 0.7rem;
|
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||||
border-top: 3px solid var(--color-secondary-dark-2);
|
font-size: 0.65rem;
|
||||||
margin-top: 20px;
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
.homelab-dashboard .ftr span { color: var(--color-red); }
|
.homelab-dashboard .ftr span { color: var(--color-warning); }
|
||||||
</style>
|
</style>
|
||||||
<div class="homelab-dashboard">
|
<div class="homelab-dashboard">
|
||||||
<div class="hdr">
|
<div class="masthead">
|
||||||
<h1>HOMELAB</h1>
|
<div class="rule-line"></div>
|
||||||
<p class="subtitle">COMMAND CENTER</p>
|
<div class="masthead-row">
|
||||||
<div class="score-bar">
|
<h1>Homelab</h1>
|
||||||
<span class="score-item">SERVICES: 15</span>
|
<span class="masthead-eyebrow">git.fhirworx.io</span>
|
||||||
<span class="score-item">STATUS: ONLINE</span>
|
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
<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="status-dot"></span>
|
||||||
<span class="icon">🔗</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>
|
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -163,7 +255,8 @@
|
|||||||
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="icon">💾</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>
|
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -172,8 +265,9 @@
|
|||||||
<a data-subdomain="git" class="tile git">
|
<a data-subdomain="git" class="tile git">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="icon">🐙</span>
|
<span class="icon">🐙</span>
|
||||||
<h2 class="tile-title">GITEA</h2>
|
<div class="tile-eyebrow">Version Control</div>
|
||||||
<p class="tile-desc">Self-hosted Git service with packages & CI</p>
|
<div class="tile-title">Gitea</div>
|
||||||
|
<p class="tile-desc">Self-hosted Git service with packages and CI</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -181,68 +275,75 @@
|
|||||||
<a data-subdomain="ci" target="_blank" class="tile ci">
|
<a data-subdomain="ci" target="_blank" class="tile ci">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="icon">🔨</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>
|
<p class="tile-desc">Simple yet powerful CI/CD engine</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="notebooks" target="_blank" class="tile notebook">
|
<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="status-dot"></span>
|
||||||
<span class="icon">📑</span>
|
<span class="icon">📑</span>
|
||||||
<h2 class="tile-title">NOTEBOOKS</h2>
|
<div class="tile-eyebrow">Analysis</div>
|
||||||
<p class="tile-desc">Marimo reactive notebooks with CUDA</p>
|
<div class="tile-title">Notebooks</div>
|
||||||
|
<p class="tile-desc">Marimo reactive notebooks with CUDA acceleration</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="zotero" target="_blank" class="tile research">
|
<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="status-dot"></span>
|
||||||
<span class="icon">📚</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>
|
<p class="tile-desc">Research reference manager in the cloud</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="docs" target="_blank" class="tile research">
|
<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="status-dot"></span>
|
||||||
<span class="icon">📖</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>
|
<p class="tile-desc">API reference and CMS bibliography browser</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="nessie" data-path="/api/v2/config" target="_blank" class="tile catalog">
|
<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="status-dot"></span>
|
||||||
<span class="icon">🌳</span>
|
<span class="icon">🌳</span>
|
||||||
<h2 class="tile-title">NESSIE</h2>
|
<div class="tile-eyebrow">Catalog</div>
|
||||||
<p class="tile-desc">Git-like versioned data catalog for Iceberg</p>
|
<div class="tile-title">Nessie</div>
|
||||||
|
<p class="tile-desc">Git-like versioned data catalog for Apache Iceberg</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="trino" target="_blank" class="tile query">
|
<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="status-dot"></span>
|
||||||
<span class="icon">🔍</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>
|
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
<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="status-dot"></span>
|
||||||
<span class="icon">⭐</span>
|
<span class="icon">⭐</span>
|
||||||
<h2 class="tile-title">POLARIS</h2>
|
<div class="tile-eyebrow">Catalog</div>
|
||||||
<p class="tile-desc">Apache Iceberg catalog with governance & Unity-style access</p>
|
<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>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -250,8 +351,9 @@
|
|||||||
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="icon">📊</span>
|
<span class="icon">📊</span>
|
||||||
<h2 class="tile-title">GRAFANA</h2>
|
<div class="tile-eyebrow">Observability</div>
|
||||||
<p class="tile-desc">Observability dashboards for logs and traces</p>
|
<div class="tile-title">Grafana</div>
|
||||||
|
<p class="tile-desc">Observability dashboards for metrics, logs, and traces</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -259,7 +361,8 @@
|
|||||||
<a data-subdomain="jaeger" target="_blank" class="tile metrics">
|
<a data-subdomain="jaeger" target="_blank" class="tile metrics">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="icon">👁</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>
|
<p class="tile-desc">Distributed tracing for microservices</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -268,7 +371,8 @@
|
|||||||
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="icon">📈</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>
|
<p class="tile-desc">Metrics collection and alerting</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -277,7 +381,8 @@
|
|||||||
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="icon">📋</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>
|
<p class="tile-desc">Log aggregation system by Grafana</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -285,14 +390,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ftr">
|
<div class="ftr">
|
||||||
MADE WITH <span>♥</span> FOR THE HOMELAB
|
<span>fhirworx.io</span> — self-hosted data lakehouse — © 2025
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(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 parts = location.hostname.split('.');
|
||||||
var base = parts.length > 2 ? parts.slice(1).join('.') : location.hostname;
|
var base = parts.length > 2 ? parts.slice(1).join('.') : location.hostname;
|
||||||
document.querySelectorAll('.homelab-dashboard [data-subdomain]').forEach(function(el) {
|
document.querySelectorAll('.homelab-dashboard [data-subdomain]').forEach(function(el) {
|
||||||
|
|||||||
@@ -3,27 +3,40 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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="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">
|
<link rel="stylesheet" href="/dashboard.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
|
||||||
<h1>FHIRWORX</h1>
|
<div class="masthead">
|
||||||
<p class="subtitle">COMMAND CENTER</p>
|
<div class="rule-line"></div>
|
||||||
<div class="score-bar">
|
<div class="masthead-row">
|
||||||
<span class="score-item">SERVICES: 15</span>
|
<h1>Fhirworx</h1>
|
||||||
<span class="score-item">STATUS: ONLINE</span>
|
<span class="masthead-eyebrow">dashboard.fhirworx.io</span>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">🔗</span>
|
<span class="icon">🔗</span>
|
||||||
<h2 class="tile-title">TRAEFIK</h2>
|
<div class="tile-eyebrow">Networking</div>
|
||||||
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
<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>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -31,7 +44,8 @@
|
|||||||
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">💾</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>
|
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -40,8 +54,9 @@
|
|||||||
<a data-subdomain="git" target="_blank" class="tile git">
|
<a data-subdomain="git" target="_blank" class="tile git">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">🐙</span>
|
<span class="icon">🐙</span>
|
||||||
<h2 class="tile-title">GITEA</h2>
|
<div class="tile-eyebrow">Version Control</div>
|
||||||
<p class="tile-desc">Self-hosted Git service with packages & CI</p>
|
<div class="tile-title">Gitea</div>
|
||||||
|
<p class="tile-desc">Self-hosted Git service with packages and CI</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -49,7 +64,8 @@
|
|||||||
<a data-subdomain="ci" target="_blank" class="tile ci">
|
<a data-subdomain="ci" target="_blank" class="tile ci">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">🔨</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>
|
<p class="tile-desc">Simple yet powerful CI/CD engine</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -59,8 +75,9 @@
|
|||||||
<span class="badge gpu">GPU</span>
|
<span class="badge gpu">GPU</span>
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">📑</span>
|
<span class="icon">📑</span>
|
||||||
<h2 class="tile-title">NOTEBOOKS</h2>
|
<div class="tile-eyebrow">Analysis</div>
|
||||||
<p class="tile-desc">Marimo reactive notebooks with CUDA</p>
|
<div class="tile-title">Notebooks</div>
|
||||||
|
<p class="tile-desc">Marimo reactive notebooks with CUDA acceleration</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -69,7 +86,8 @@
|
|||||||
<span class="badge gpu">GPU</span>
|
<span class="badge gpu">GPU</span>
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">📚</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>
|
<p class="tile-desc">Research reference manager in the cloud</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -78,27 +96,30 @@
|
|||||||
<a data-subdomain="docs" target="_blank" class="tile research">
|
<a data-subdomain="docs" target="_blank" class="tile research">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">📖</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>
|
<p class="tile-desc">API reference and CMS bibliography browser</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="nessie" data-path="/api/v2/config" target="_blank" class="tile catalog">
|
<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="status"></span>
|
||||||
<span class="icon">🌳</span>
|
<span class="icon">🌳</span>
|
||||||
<h2 class="tile-title">NESSIE</h2>
|
<div class="tile-eyebrow">Catalog</div>
|
||||||
<p class="tile-desc">Git-like versioned data catalog for Iceberg</p>
|
<div class="tile-title">Nessie</div>
|
||||||
|
<p class="tile-desc">Git-like versioned data catalog for Apache Iceberg</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a data-subdomain="trino" target="_blank" class="tile query">
|
<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="status"></span>
|
||||||
<span class="icon">🔍</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>
|
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -107,8 +128,9 @@
|
|||||||
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">⭐</span>
|
<span class="icon">⭐</span>
|
||||||
<h2 class="tile-title">POLARIS</h2>
|
<div class="tile-eyebrow">Catalog</div>
|
||||||
<p class="tile-desc">Apache Iceberg catalog with governance & Unity-style access</p>
|
<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>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -116,8 +138,9 @@
|
|||||||
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">📊</span>
|
<span class="icon">📊</span>
|
||||||
<h2 class="tile-title">GRAFANA</h2>
|
<div class="tile-eyebrow">Observability</div>
|
||||||
<p class="tile-desc">Observability dashboards for logs and traces</p>
|
<div class="tile-title">Grafana</div>
|
||||||
|
<p class="tile-desc">Observability dashboards for metrics, logs, and traces</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
</a>
|
</a>
|
||||||
@@ -125,7 +148,8 @@
|
|||||||
<a data-subdomain="jaeger" target="_blank" class="tile metrics">
|
<a data-subdomain="jaeger" target="_blank" class="tile metrics">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">👁</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>
|
<p class="tile-desc">Distributed tracing for microservices</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -134,7 +158,8 @@
|
|||||||
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">📈</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>
|
<p class="tile-desc">Metrics collection and alerting</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -143,7 +168,8 @@
|
|||||||
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">📋</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>
|
<p class="tile-desc">Log aggregation system by Grafana</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -152,7 +178,8 @@
|
|||||||
<a data-subdomain="api" data-path="/health" target="_blank" class="tile proxy">
|
<a data-subdomain="api" data-path="/health" target="_blank" class="tile proxy">
|
||||||
<span class="status"></span>
|
<span class="status"></span>
|
||||||
<span class="icon">🔌</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>
|
<p class="tile-desc">FastAPI platform backend</p>
|
||||||
<span class="tile-port"></span>
|
<span class="tile-port"></span>
|
||||||
<div class="stripe"></div>
|
<div class="stripe"></div>
|
||||||
@@ -160,12 +187,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
MADE WITH <span>♥</span> FOR FHIRWORX
|
<span>fhirworx.io</span> — self-hosted data lakehouse — © 2025
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// All tiles use https://{subdomain}.fhirworx.io
|
|
||||||
(function() {
|
(function() {
|
||||||
var base = location.hostname.replace(/^dashboard\./, '');
|
var base = location.hostname.replace(/^dashboard\./, '');
|
||||||
document.querySelectorAll('[data-subdomain]').forEach(function(el) {
|
document.querySelectorAll('[data-subdomain]').forEach(function(el) {
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ http:
|
|||||||
referrerPolicy: "strict-origin-when-cross-origin"
|
referrerPolicy: "strict-origin-when-cross-origin"
|
||||||
customResponseHeaders:
|
customResponseHeaders:
|
||||||
X-Robots-Tag: "noindex, nofollow"
|
X-Robots-Tag: "noindex, nofollow"
|
||||||
inject-loch:
|
inject-fhirworx:
|
||||||
plugin:
|
plugin:
|
||||||
rewrite-body:
|
rewrite-body:
|
||||||
lastModified: true
|
lastModified: true
|
||||||
rewrites:
|
rewrites:
|
||||||
- regex: "</head>"
|
- 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:
|
routers:
|
||||||
# auth.DOMAIN — oauth2-proxy public endpoints (login, callback, sign_out)
|
# auth.DOMAIN — oauth2-proxy public endpoints (login, callback, sign_out)
|
||||||
auth-host:
|
auth-host:
|
||||||
@@ -71,7 +71,7 @@ http:
|
|||||||
entryPoints:
|
entryPoints:
|
||||||
- web
|
- web
|
||||||
{{- $mwList := list -}}
|
{{- $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 "" -}}
|
{{- $extra := get $svc "mw" | default "" -}}
|
||||||
{{- if ne $extra "" }}{{ range splitList "," $extra }}{{ $mwList = append $mwList (trim .) }}{{ end }}{{ end -}}
|
{{- if ne $extra "" }}{{ range splitList "," $extra }}{{ $mwList = append $mwList (trim .) }}{{ end }}{{ end -}}
|
||||||
{{- if $mwList }}
|
{{- if $mwList }}
|
||||||
@@ -100,7 +100,7 @@ http:
|
|||||||
entryPoints:
|
entryPoints:
|
||||||
- web
|
- web
|
||||||
{{- $mwList := list -}}
|
{{- $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 "" -}}
|
{{- $extra := get $svc "mw" | default "" -}}
|
||||||
{{- if ne $extra "" }}{{ range splitList "," $extra }}{{ $mwList = append $mwList (trim .) }}{{ end }}{{ end -}}
|
{{- if ne $extra "" }}{{ range splitList "," $extra }}{{ $mwList = append $mwList (trim .) }}{{ end }}{{ end -}}
|
||||||
{{- if $mwList }}
|
{{- if $mwList }}
|
||||||
@@ -128,7 +128,7 @@ http:
|
|||||||
entryPoints:
|
entryPoints:
|
||||||
- web
|
- web
|
||||||
middlewares:
|
middlewares:
|
||||||
- inject-loch
|
- inject-fhirworx
|
||||||
- gitea-sso
|
- gitea-sso
|
||||||
- infra-headers
|
- infra-headers
|
||||||
traefik-dashboard-tls:
|
traefik-dashboard-tls:
|
||||||
@@ -138,7 +138,7 @@ http:
|
|||||||
- websecure
|
- websecure
|
||||||
tls: {}
|
tls: {}
|
||||||
middlewares:
|
middlewares:
|
||||||
- inject-loch
|
- inject-fhirworx
|
||||||
- gitea-sso
|
- gitea-sso
|
||||||
- infra-headers
|
- infra-headers
|
||||||
services:
|
services:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ def _():
|
|||||||
connect.theme()
|
connect.theme()
|
||||||
_s3 = connect.obstore() # noqa: F841 — marimo auto-discovers in Files
|
_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()
|
con = connect.duckdb()
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ _assets_dir = str(_Path(__file__).resolve().parents[2] / "assets")
|
|||||||
if _assets_dir not in _sys.path:
|
if _assets_dir not in _sys.path:
|
||||||
_sys.path.insert(0, _assets_dir)
|
_sys.path.insert(0, _assets_dir)
|
||||||
|
|
||||||
from nature import EXTERNAL_COLOR as _EXTERNAL_COLOR # noqa: E402, I001
|
from fhirworx import EXTERNAL_COLOR as _EXTERNAL_COLOR # noqa: E402, I001
|
||||||
from nature import SCHEMA_COLORS as _SCHEMA_COLORS # noqa: E402, F401
|
from fhirworx import SCHEMA_COLORS as _SCHEMA_COLORS # noqa: E402, F401
|
||||||
from nature import schema_color as _color # noqa: E402
|
from fhirworx import schema_color as _color # noqa: E402
|
||||||
|
|
||||||
from aco.pipe.base import Pipeline # noqa: E402
|
from aco.pipe.base import Pipeline # noqa: E402
|
||||||
|
|
||||||
|
|||||||
@@ -152,12 +152,12 @@ def obstore(*, bucket: str = "lakehouse") -> Any:
|
|||||||
|
|
||||||
|
|
||||||
def theme() -> None:
|
def theme() -> None:
|
||||||
"""Activate the Nature/loch Altair chart theme."""
|
"""Activate the fhirworx Altair chart theme."""
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
assets_dir = str(ROOT / "assets")
|
assets_dir = str(ROOT / "assets")
|
||||||
if assets_dir not in sys.path:
|
if assets_dir not in sys.path:
|
||||||
sys.path.insert(0, assets_dir)
|
sys.path.insert(0, assets_dir)
|
||||||
from nature import altair_theme
|
from fhirworx import altair_theme
|
||||||
|
|
||||||
altair_theme()
|
altair_theme()
|
||||||
|
|||||||
@@ -188,4 +188,4 @@ class TestTheme:
|
|||||||
connect.theme()
|
connect.theme()
|
||||||
import altair as alt
|
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):
|
def test_home_page_js_exists(self):
|
||||||
assert (INFRA_MARIMO / "home-page-patched.js").is_file()
|
assert (INFRA_MARIMO / "home-page-patched.js").is_file()
|
||||||
|
|
||||||
def test_no_loch_css_duplicate(self):
|
def test_no_fhirworx_css_duplicate(self):
|
||||||
"""loch.css should NOT be in infra/marimo/ — served from assets/css/."""
|
"""fhirworx.css should NOT be in infra/marimo/ — served from assets/css/."""
|
||||||
assert not (INFRA_MARIMO / "loch.css").exists()
|
assert not (INFRA_MARIMO / "fhirworx.css").exists()
|
||||||
|
|
||||||
|
|
||||||
class TestNoStaleReferences:
|
class TestNoStaleReferences:
|
||||||
|
|||||||
Reference in New Issue
Block a user