style(homepage): tone the stat values down, run the aurora through the page
Two operator corrections in one pass. Stat values overshot: the previous commit took them from font-thin 13px to bold 22px in heading white, which went from whisper to shout. A stat only has to out-rank its own label, not the service name above it — now --text-md at medium weight in cyan, which clears the label but sits below the card title where it belongs. Colour lift, staying inside the system rather than around it: Skyfall names Aurora (blue, cyan, green) the PRIMARY families, 'used generously, in that order', while Dawn (amber, red, violet) is semantic-only. So group markers now cycle blue -> cyan -> green down the page — icons at full strength, names at 0.72 — service icons take a single cool wash, header resource icons go cyan, and latency tags move to the info family so 'how fast' stops looking like 'is it alive'. No Dawn colour is used decoratively anywhere. Also fixes selectors that never bound: Homepage emits docker-status-<state>, not status-<state>, so the green pills up to now were stock colouring rather than this file. Both forms are matched and the trap is commented. README records the iteration loop that would have caught the overshoot: CSS is served per-request, so it needs a reload, not a recreate and not the layout warm-up — and candidate CSS can be injected into the live page for a seconds-long feedback loop instead of a 10-minute one.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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-<state>`, NOT
|
||||
`status-<state>`. 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,
|
||||
|
||||
@@ -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-<state>`, NOT
|
||||
`status-<state>`. 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,
|
||||
|
||||
Reference in New Issue
Block a user