feat(homepage): add Arbo 'Raven' theme to custom.css; correct the tab-bar note

Ports Arbo's design tokens (irv-ml1:8201) into conf/custom.css — flat raven
ink #021425, card surface #112333 on #1B2E3D borders, Manrope, and mint
#2FFC89 reserved for signal so a green pill means the service is actually
serving. Values read off Arbo's running :root custom properties rather than
sampled from a screenshot. CSS rather than settings.yaml because Homepage's
color: setting only accepts built-in Tailwind ramps.

NOT YET DEPLOYED — live still runs the stock theme pending an A/B decision.
Prototype is at 10.0.50.45:5199; shots in ~/booth-data/homepage-cleanup/.
Promoting it also means dropping the background: block from settings.yaml.

Also corrects the previous commit's tab-bar claim. It is not a fixed few
minutes of warm-up: a fresh container was still tab-less at 4m30s twice, and
recovered on its own about an hour later. Cause remains unpinned; the README
now records the measured timing and the four ruled-out causes.
This commit is contained in:
vh
2026-08-18 22:38:21 -07:00
parent 42c594c29f
commit c3de7dbd58
2 changed files with 224 additions and 22 deletions
+53 -20
View File
@@ -84,21 +84,39 @@ Three fixes, all in this stack's config except where noted:
`settings.yaml`. Check with `GET /api/services`, which prints live per-group `settings.yaml`. Check with `GET /api/services`, which prints live per-group
counts. counts.
## RESOLVED — the tab bar takes several minutes to appear after a recreate ## The tab bar goes missing after a recreate, then comes back on its own
**Status: closed, and the answer is "wait".** After the 2026-08-18 recreate the **Status: self-healing, cause not pinned. Do not chase it.** After a recreate
client render came up with no tab bar, no wallpaper and no i18n (the search box the client render comes up with no tab bar, no wallpaper and no i18n (the
read the raw key `search.search`); groups fell back to side-by-side columns. search box shows the raw key `search.search`), and groups fall back to
It restored itself with no further intervention. **A freshly recreated homepage side-by-side columns. It restores itself with no intervention.
container needs a few minutes before the client render is whole** — far longer
than the healthcheck takes to report `healthy`, which is the trap: `docker ps`
says the service is up while the page is still visibly wrong.
**So: after any `docker compose up -d --force-recreate` here, do not judge the **Timing, measured rather than assumed:** five minutes is NOT enough — a fresh
dashboard for at least ~5 minutes, and do not start changing config to chase container was still tab-less at 4m30s, twice. It was observed healthy again
it.** Everything below is the evidence trail from doing exactly that, kept after a gap of roughly an hour. So the window is somewhere between those, and
because it rules out four plausible causes and will save the next session the `docker ps` reporting `healthy` says nothing about it: the container is
same hour. serving, the page is just wrong.
**Practical rule: recreate, then walk away and re-check later.** Do not start
editing config to fix it — that is how an hour got spent ruling out four
causes that were never the cause:
- **Not the config.** Restoring `settings.yaml` *and* `services.yaml` to their
committed versions reproduces it, as does the pre-adoption backup config in
`/opt/docker-bu/conf/homepage/`.
- **Not the v2.0.0 release.** A throwaway container on `v1.13.2` (the last v1)
shows identical symptoms, and the image never changed anyway — working and
broken containers both report `v2.0.0` / rev `17456f2`.
- **Not `PUID`/`PGID`**, and not Docker discovery — tested with both, and with
the socket unmounted entirely.
- **Not a server-side failure.** The server-rendered HTML still contains the
tab markup, the background URL and `useEqualHeights` throughout, and
`GET /api/validate` returns `[]`. Whatever is lost, is lost client-side, and
with no page error, no failed chunk and no non-200.
If someone does want to pin it: the shape (recovers on a long timescale, no
client exception) points at a cache or revalidation window rather than a
config fault. Start there, not in this directory.
**What it is not** — both obvious suspects were tested and cleared: **What it is not** — both obvious suspects were tested and cleared:
@@ -119,17 +137,32 @@ widget 403s. `GET /api/validate` returns `[]`. A fresh container never
renders tabs here regardless of image version, config version, `PUID`/`PGID`, renders tabs here regardless of image version, config version, `PUID`/`PGID`,
or whether Docker discovery is mounted at all. or whether Docker discovery is mounted at all.
**What it actually was: warm-up time.** Every throwaway container in the list Every throwaway container in that list was judged within ~30s of starting, so
above was judged within ~30 seconds of starting, which is why they all looked they were all inside the same window — and that consistency read as a
broken — they were all in the same warm-up window, and that consistency read as reproduction when it was really the same measurement mistake five times over.
a reproduction when it was really the same mistake five times. The live This sits alongside the existing warning that `docker ps` health and a correct
container recovered on its own once left alone. The lesson is a measurement render are different questions.
discipline, not a config one, and it sits alongside the existing warning that
`docker ps` health and a correct render are different questions.
Before/after evidence: `~/booth-data/homepage-cleanup/` on nh3-dev → Before/after evidence: `~/booth-data/homepage-cleanup/` on nh3-dev →
`http://10.100.10.50:8090/b/homepage-cleanup/` (24h TTL). `http://10.100.10.50:8090/b/homepage-cleanup/` (24h TTL).
## Theme — the Arbo "Raven" palette (`conf/custom.css`)
`conf/custom.css` carries a port of Arbo's design tokens (irv-ml1:8201): flat
raven ink `#021425`, card surface `#112333` on `#1B2E3D` borders, Manrope, and
mint `#2FFC89` reserved for signal — a status pill is green only when the
service is actually serving. Values were read off Arbo's running `:root`
custom properties, not sampled from a screenshot, so drift checks are a diff.
It lives in CSS rather than `settings.yaml` because Homepage's `color:` setting
only accepts built-in Tailwind ramps and has no separate surface/border/accent
knobs. `color: slate` stays as the base ramp. The `!important` flags are
load-bearing against Tailwind utility specificity.
Pairs with removing the `background:` block from `settings.yaml` — Arbo has no
background image, and the wallpaper fought the card surfaces, which sit only a
little lighter than the page.
## Open question ## Open question
`ESH-FileBot` (`10.0.50.70`) is still described as "role TBC" — it responds to `ESH-FileBot` (`10.0.50.70`) is still described as "role TBC" — it responds to
+171 -2
View File
@@ -1,2 +1,171 @@
/* Custom styling for homepage. Empty placeholder — keep file present so /* Homepage — Corviduo "Raven" theme, ported from Arbo (irv-ml1:8201).
* homepage doesn't 404 on the asset. */ *
* PROVENANCE: every value below is lifted verbatim from Arbo's own :root
* custom properties, read off the running app rather than eyeballed from a
* screenshot. Keep the --raven-* / --eye-* / --surface* names identical to
* Arbo's so a future drift check is a diff, not an interpretation.
*
* WHY CSS AND NOT settings.yaml: Homepage's `color:` setting only accepts
* built-in Tailwind ramps (slate, zinc, ...). None of them is the raven mint,
* and there is no setting for surface/border/accent separately. `color: slate`
* stays as the base ramp because it is the closest neutral; everything that
* makes this look like Arbo is here.
*
* SPECIFICITY: Homepage is Tailwind-generated, so utility classes win on
* equal specificity. `!important` below is deliberate and load-bearing —
* removing it silently reverts individual rules to the stock theme.
*/
@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap");
:root {
/* --- Arbo brand ramp (verbatim) --- */
--raven-ink: #021425;
--raven-black: #0f2131;
--raven-eye: #2ffc89;
--raven-bone: #e5f4f4;
--black-700: #1b2e3d;
--black-600: #283b4a;
--slate-400: #6a7f88;
--slate-300: #8499a0;
--eye-500: #2ffc89;
--eye-400: #77ffa3;
--eye-600: #00cf67;
--info-500: #00a5fb;
--success-500: #30ab60;
--warning-500: #ecb230;
--error-500: #f5686c;
/* --- semantic surfaces (Arbo's own mapping) --- */
--bg: var(--raven-ink);
--bg-sunken: #010e1b;
--surface: #112333;
--surface-2: #1b2e3d;
--surface-3: #283b4a;
--fg: #dfeeee;
--fg-muted: var(--slate-300);
--fg-subtle: var(--slate-400);
--border: #1b2e3d;
--border-strong: #283b4a;
--accent: var(--eye-500);
}
/* ---------- page ---------- */
/* Flat ink, no photo. Arbo has no background image and the wallpaper was
* fighting the card surfaces for contrast — the cards are only ~0x10 lighter
* than the page, which reads as depth on flat ink and as mud over a photo. */
body,
#page_wrapper,
#page_container {
background: var(--bg) !important;
color: var(--fg) !important;
font-family: Manrope, ui-sans-serif, system-ui, -apple-system, "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif !important;
}
/* ---------- header widgets ---------- */
#information-widgets .widget-container,
#information-widgets .information-widget-search {
background: var(--surface) !important;
border: 1px solid var(--border) !important;
border-radius: 0.5rem !important;
}
#information-widgets .information-widget-search input {
background: transparent !important;
color: var(--fg) !important;
}
/* ---------- tabs ---------- */
/* Arbo marks the active nav item with a mint rule and a raised surface. */
#myTab li button,
#services + div li button {
color: var(--fg-muted) !important;
border-radius: 0.5rem !important;
}
#myTab li button[aria-selected="true"],
#myTab li button.active {
background: var(--surface-2) !important;
color: var(--fg) !important;
box-shadow: inset 0 -2px 0 0 var(--accent) !important;
}
/* ---------- group headers ---------- */
.service-group-name,
.bookmark-group-name {
color: var(--fg-subtle) !important;
font-weight: 600 !important;
letter-spacing: 0.04em !important;
text-transform: uppercase !important;
font-size: 0.78rem !important;
}
/* ---------- service cards ---------- */
.service-card,
.bookmark li > a,
.bookmark-list li > a {
background: var(--surface) !important;
border: 1px solid var(--border) !important;
border-radius: 0.5rem !important;
transition: background 120ms ease, border-color 120ms ease !important;
}
.service-card:hover,
.bookmark li > a:hover,
.bookmark-list li > a:hover {
background: var(--surface-2) !important;
border-color: var(--border-strong) !important;
}
.service-name,
.bookmark-name {
color: var(--fg) !important;
font-weight: 600 !important;
}
.service-description,
.bookmark-description {
color: var(--fg-muted) !important;
}
/* ---------- status pills + widget stat blocks ---------- */
/* Stock Homepage renders every pill in the same slate; Arbo reserves colour
* for signal. Green only means "this is actually serving". */
.service-tag,
.service-block,
.service-stats {
background: var(--surface-2) !important;
border: 1px solid var(--border) !important;
color: var(--fg-muted) !important;
border-radius: 0.375rem !important;
}
.status-online,
.status-healthy,
.status-running {
color: var(--eye-500) !important;
}
.status-offline,
.status-unhealthy,
.status-error {
color: var(--error-500) !important;
}
.status-unknown,
.status-exited,
.status-partial {
color: var(--warning-500) !important;
}
/* ---------- focus ---------- */
a:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 2px solid var(--accent) !important;
outline-offset: 2px !important;
}