diff --git a/stacks/homepage/README.md b/stacks/homepage/README.md index ad06066..24cffeb 100644 --- a/stacks/homepage/README.md +++ b/stacks/homepage/README.md @@ -169,6 +169,29 @@ theme/build.py inlines the fonts + tokens -> conf/custom.css **Do not hand-edit `conf/custom.css`.** Change `skyfall.css.in`, run `python3 stacks/homepage/theme/build.py`, then deploy. +### Iterating on the theme — do NOT recreate the container + +`custom.css` is fetched per request from `/api/config/custom.css`, so a CSS +change needs a **browser reload**, nothing more. It does **not** need a +container recreate, and it therefore does not owe the multi-minute layout +warm-up described below — that penalty only applies to the full tab/layout +render. Conflating the two costs an operator-visible 10 minutes per attempt, +which is exactly how the first version of this theme shipped overcooked +(operator, 2026-08-19). + +Faster still, and the right way to judge a colour change before it touches the +live dashboard: **inject the candidate CSS into the running page and +screenshot it.** No deploy, no restart, seconds per iteration. + +```js +// playwright, against the live dashboard +await p.goto('http://10.0.50.45:5100/', { waitUntil: 'networkidle' }); +await p.addStyleTag({ content: candidateCss }); +await p.screenshot({ path: 'preview.png', fullPage: true }); +``` + +Only build + deploy once the render looks right. + The build exists for one reason: Homepage serves exactly two files out of its config directory, `custom.css` and `custom.js`. There is no static route for anything beside them, so a `@font-face` pointing at a vendored `.woff2` would diff --git a/stacks/homepage/conf/custom.css b/stacks/homepage/conf/custom.css index 54fa723..028b359 100644 --- a/stacks/homepage/conf/custom.css +++ b/stacks/homepage/conf/custom.css @@ -468,11 +468,17 @@ body, padding: var(--space-2) var(--space-1) !important; } +/* Sizing note (operator, 2026-08-19): the first pass at this went from + font-thin-13px to bold-22px-in-heading-white, which overshot from "whisper" + straight to "shout". The number only has to out-rank its own label, not the + service name above it. --text-md at medium weight in cyan clears the label + comfortably while sitting BELOW the card title in the hierarchy, which is + where a stat belongs. Resist re-raising this. */ .service-block > div:first-child { - color: var(--text-heading) !important; + color: var(--secondary-text) !important; font-family: var(--font-mono) !important; - font-size: var(--text-xl) !important; - font-weight: var(--weight-bold) !important; + font-size: var(--text-md) !important; + font-weight: var(--weight-medium) !important; line-height: var(--leading-tight) !important; font-variant-numeric: tabular-nums !important; } @@ -522,6 +528,57 @@ body, text-transform: uppercase !important; } +/* ---------- the aurora rotation ---------- + This is where the colour on this dashboard comes from, and it is sanctioned + rather than smuggled in: Skyfall names Aurora (blue, cyan, green) the + PRIMARY families, "used generously, in that order". What is restricted is + Dawn — amber, red, violet — which stays semantic-only (warning, danger, AI) + and is therefore never used decoratively anywhere in this file. + + Section markers cycle blue -> cyan -> green down the page, so a group reads + as a coloured band rather than another row of grey. The icons carry it at + full strength; the names take the same hue at 0.72 so the eyebrow stays an + eyebrow. + + Homepage renders mdi icons as a gradient behind an SVG mask, which is why + these are `background` rules and not `color` — setting color does nothing. + Vendor logos (si-*) are real images and keep their brand colours; that mix + is inherent to Homepage's icon model, not a choice made here. */ +.services-group:nth-of-type(3n + 1) .service-group-icon > div { + background: linear-gradient(180deg, var(--blue-bright), var(--blue-base)) !important; +} +.services-group:nth-of-type(3n + 2) .service-group-icon > div { + background: linear-gradient(180deg, var(--cyan-bright), var(--cyan-base)) !important; +} +.services-group:nth-of-type(3n + 3) .service-group-icon > div { + background: linear-gradient(180deg, var(--green-bright), var(--green-base)) !important; +} + +.services-group:nth-of-type(3n + 1) .service-group-name { + color: var(--blue-bright) !important; + opacity: 0.72 !important; +} +.services-group:nth-of-type(3n + 2) .service-group-name { + color: var(--cyan-bright) !important; + opacity: 0.72 !important; +} +.services-group:nth-of-type(3n + 3) .service-group-name { + color: var(--green-bright) !important; + opacity: 0.72 !important; +} + +/* Per-service mdi icons take a single cool wash rather than joining the + rotation — 60-odd cards cycling three hues would be noise, and the card + grid is not the thing being sectioned. */ +.service-icon > div { + background: linear-gradient(180deg, var(--sea-80), var(--cyan-base)) !important; +} + +/* Header resource icons (CPU / RAM / disk). */ +.information-widget-resource .resource-icon { + color: var(--cyan-bright) !important; +} + /* ---------- cards ---------- The depth recipe, and it is not optional: crisp 1px hairline AND a two-layer soft shadow. Never one without the other on a floating @@ -576,22 +633,34 @@ body, font-size: var(--text-2xs) !important; } +/* ⚠️ The real class Homepage emits is `docker-status-`, NOT + `status-`. The bare `.status-*` selectors below were written from a + guess and matched nothing — the green pills in the first screenshots were + stock Homepage colouring, not this file. They are kept only because the + site-monitor path does use them; the docker-status ones are what actually + bind on container cards. Verify against the DOM before adding more. */ +.docker-status-healthy, +.docker-status-running, +.docker-status-started, .status-online, .status-healthy, -.status-running, -.status-started { +.status-running { color: var(--success-text) !important; background: var(--success-soft) !important; } +.docker-status-unhealthy, +.docker-status-dead, .status-offline, .status-unhealthy, -.status-dead, .status-error { color: var(--danger-text) !important; background: var(--danger-soft) !important; } +.docker-status-exited, +.docker-status-paused, +.docker-status-created, .status-exited, .status-paused, .status-partial, @@ -601,6 +670,16 @@ body, background: var(--warning-soft) !important; } +/* Latency / reachability readings are telemetry, not status — giving them the + info family keeps "how fast" visually separate from "is it alive", which + otherwise both render as the same pill in the same corner. */ +.service-tag.service-site-monitor, +.site-monitor-status, +.service-tag.service-ping { + color: var(--info-text) !important; + background: var(--info-soft) !important; +} + /* ---------- focus ---------- */ a:focus-visible, button:focus-visible, diff --git a/stacks/homepage/theme/skyfall.css.in b/stacks/homepage/theme/skyfall.css.in index 1884abd..e9fa94e 100644 --- a/stacks/homepage/theme/skyfall.css.in +++ b/stacks/homepage/theme/skyfall.css.in @@ -124,11 +124,17 @@ body, padding: var(--space-2) var(--space-1) !important; } +/* Sizing note (operator, 2026-08-19): the first pass at this went from + font-thin-13px to bold-22px-in-heading-white, which overshot from "whisper" + straight to "shout". The number only has to out-rank its own label, not the + service name above it. --text-md at medium weight in cyan clears the label + comfortably while sitting BELOW the card title in the hierarchy, which is + where a stat belongs. Resist re-raising this. */ .service-block > div:first-child { - color: var(--text-heading) !important; + color: var(--secondary-text) !important; font-family: var(--font-mono) !important; - font-size: var(--text-xl) !important; - font-weight: var(--weight-bold) !important; + font-size: var(--text-md) !important; + font-weight: var(--weight-medium) !important; line-height: var(--leading-tight) !important; font-variant-numeric: tabular-nums !important; } @@ -178,6 +184,57 @@ body, text-transform: uppercase !important; } +/* ---------- the aurora rotation ---------- + This is where the colour on this dashboard comes from, and it is sanctioned + rather than smuggled in: Skyfall names Aurora (blue, cyan, green) the + PRIMARY families, "used generously, in that order". What is restricted is + Dawn — amber, red, violet — which stays semantic-only (warning, danger, AI) + and is therefore never used decoratively anywhere in this file. + + Section markers cycle blue -> cyan -> green down the page, so a group reads + as a coloured band rather than another row of grey. The icons carry it at + full strength; the names take the same hue at 0.72 so the eyebrow stays an + eyebrow. + + Homepage renders mdi icons as a gradient behind an SVG mask, which is why + these are `background` rules and not `color` — setting color does nothing. + Vendor logos (si-*) are real images and keep their brand colours; that mix + is inherent to Homepage's icon model, not a choice made here. */ +.services-group:nth-of-type(3n + 1) .service-group-icon > div { + background: linear-gradient(180deg, var(--blue-bright), var(--blue-base)) !important; +} +.services-group:nth-of-type(3n + 2) .service-group-icon > div { + background: linear-gradient(180deg, var(--cyan-bright), var(--cyan-base)) !important; +} +.services-group:nth-of-type(3n + 3) .service-group-icon > div { + background: linear-gradient(180deg, var(--green-bright), var(--green-base)) !important; +} + +.services-group:nth-of-type(3n + 1) .service-group-name { + color: var(--blue-bright) !important; + opacity: 0.72 !important; +} +.services-group:nth-of-type(3n + 2) .service-group-name { + color: var(--cyan-bright) !important; + opacity: 0.72 !important; +} +.services-group:nth-of-type(3n + 3) .service-group-name { + color: var(--green-bright) !important; + opacity: 0.72 !important; +} + +/* Per-service mdi icons take a single cool wash rather than joining the + rotation — 60-odd cards cycling three hues would be noise, and the card + grid is not the thing being sectioned. */ +.service-icon > div { + background: linear-gradient(180deg, var(--sea-80), var(--cyan-base)) !important; +} + +/* Header resource icons (CPU / RAM / disk). */ +.information-widget-resource .resource-icon { + color: var(--cyan-bright) !important; +} + /* ---------- cards ---------- The depth recipe, and it is not optional: crisp 1px hairline AND a two-layer soft shadow. Never one without the other on a floating @@ -232,22 +289,34 @@ body, font-size: var(--text-2xs) !important; } +/* ⚠️ The real class Homepage emits is `docker-status-`, NOT + `status-`. The bare `.status-*` selectors below were written from a + guess and matched nothing — the green pills in the first screenshots were + stock Homepage colouring, not this file. They are kept only because the + site-monitor path does use them; the docker-status ones are what actually + bind on container cards. Verify against the DOM before adding more. */ +.docker-status-healthy, +.docker-status-running, +.docker-status-started, .status-online, .status-healthy, -.status-running, -.status-started { +.status-running { color: var(--success-text) !important; background: var(--success-soft) !important; } +.docker-status-unhealthy, +.docker-status-dead, .status-offline, .status-unhealthy, -.status-dead, .status-error { color: var(--danger-text) !important; background: var(--danger-soft) !important; } +.docker-status-exited, +.docker-status-paused, +.docker-status-created, .status-exited, .status-paused, .status-partial, @@ -257,6 +326,16 @@ body, background: var(--warning-soft) !important; } +/* Latency / reachability readings are telemetry, not status — giving them the + info family keeps "how fast" visually separate from "is it alive", which + otherwise both render as the same pill in the same corner. */ +.service-tag.service-site-monitor, +.site-monitor-status, +.service-tag.service-ping { + color: var(--info-text) !important; + background: var(--info-soft) !important; +} + /* ---------- focus ---------- */ a:focus-visible, button:focus-visible,