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.
358 lines
14 KiB
Plaintext
358 lines
14 KiB
Plaintext
/* Homepage — Australis Skyfall theme.
|
|
*
|
|
* SOURCE OF TRUTH: this file plus `theme/colors.css`, `theme/layout.css` and
|
|
* `theme/typography.css`, which are vendored VERBATIM from the Australis
|
|
* Skyfall handoff bundle. Do not hand-edit `conf/custom.css` — it is
|
|
* generated. Run `theme/build.py` after changing anything here.
|
|
*
|
|
* WHY A BUILD STEP AT ALL: Homepage serves exactly two files out of its
|
|
* config directory, `/api/config/custom.css` and `/api/config/custom.js`.
|
|
* There is no static route for anything else, so a `@font-face` pointing at
|
|
* `theme/fonts/Supreme-400.woff2` would 404 — the face has to arrive inside
|
|
* the stylesheet as a data: URI. The build inlines it.
|
|
*
|
|
* WHICH FACES, AND WHY NOT ALL THREE: Skyfall specifies Bespoke Sans
|
|
* (display), Supreme (body/UI) and Victor Mono (code/data). Only Supreme is
|
|
* embedded. A link dashboard has no display type — nothing here is a headline
|
|
* or a hero number — and Victor Mono ships as 2.4 MB TTF statics per cut,
|
|
* which is 30x this whole stylesheet for the handful of latency figures on
|
|
* the cards. Both are named in the stacks below so they resolve if they ever
|
|
* get vendored.
|
|
*
|
|
* DEVIATIONS FROM THE SYSTEM, STATED PLAINLY:
|
|
*
|
|
* 1. Skyfall says "no photography, no textures" for backgrounds, and permits
|
|
* exactly one decorative motif: a subtle aurora gradient, on hero and
|
|
* empty-state areas ONLY, "never behind body text blocks". This dashboard
|
|
* IS a body-text block, and it carries a generated aurora image behind it.
|
|
* That is a real departure, taken on the operator's explicit instruction
|
|
* to generate a background with Arbo. It is mitigated, not waved away:
|
|
* the image is abstract with no subject (Arbo's `t2i-ui-background`
|
|
* workflow), strictly cool-temperature per the imagery rule, and pinned
|
|
* to low opacity so card contrast is never the thing that suffers.
|
|
* If it still reads as busy, the honest fix is to drop the image and let
|
|
* `--surface-sunken` be the canvas, which is what the system actually asks
|
|
* for.
|
|
*
|
|
* 2. Homepage renders service icons as full-colour vendor logos. Skyfall
|
|
* wants one cool, technical icon family. Not fixable from CSS without
|
|
* desaturating every logo into illegibility, so the icons stay as they
|
|
* are.
|
|
*
|
|
* SPECIFICITY: Homepage is Tailwind-generated, so utilities win on equal
|
|
* specificity. Every `!important` here is load-bearing; removing one silently
|
|
* reverts that rule to the stock theme.
|
|
*/
|
|
|
|
/* @@FONTS@@ */
|
|
|
|
/* ============================================================
|
|
Skyfall tokens — vendored verbatim from the handoff bundle.
|
|
============================================================ */
|
|
|
|
/* @@TOKENS@@ */
|
|
|
|
/* ============================================================
|
|
Homepage bindings — the only part of this file that is ours.
|
|
Everything above is Skyfall; everything below maps Skyfall's
|
|
semantic layer onto Homepage's DOM. Never hardcode a hex here:
|
|
the system's rule is that UI consumes --surface-*/--text-*/
|
|
--border-*/--accent*/status tokens, not raw values.
|
|
============================================================ */
|
|
|
|
body,
|
|
#page_wrapper,
|
|
#page_container {
|
|
background-color: var(--surface-sunken) !important;
|
|
color: var(--text-body) !important;
|
|
font-family: var(--font-body) !important;
|
|
font-size: var(--text-base) !important;
|
|
line-height: var(--leading-normal) !important;
|
|
}
|
|
|
|
::selection {
|
|
background: var(--selection-bg);
|
|
color: var(--selection-fg);
|
|
}
|
|
|
|
/* ---------- header chrome ----------
|
|
Compact chrome per the negative-space rule: --pad-chrome, not --pad-content. */
|
|
#information-widgets .widget-container,
|
|
#information-widgets .information-widget-search {
|
|
background: var(--surface-card) !important;
|
|
border: 1px solid var(--border-subtle) !important;
|
|
border-radius: var(--radius-lg) !important;
|
|
box-shadow: var(--shadow-sm) !important;
|
|
padding: var(--pad-chrome) !important;
|
|
}
|
|
|
|
#information-widgets .information-widget-search input {
|
|
background: transparent !important;
|
|
color: var(--text-body) !important;
|
|
font-family: var(--font-body) !important;
|
|
}
|
|
|
|
#information-widgets .information-widget-search input::placeholder {
|
|
color: var(--text-faint) !important;
|
|
}
|
|
|
|
/* Telemetry is data — the system says data is always mono. */
|
|
.information-widget-resource .resource-value,
|
|
.service-block,
|
|
.service-stats {
|
|
font-family: var(--font-mono) !important;
|
|
font-variant-numeric: tabular-nums !important;
|
|
}
|
|
|
|
/* ---------- widget stat wells (Plex, Jellyfin, Paperless, Uptime Kuma) ----------
|
|
Stock Homepage builds each stat as:
|
|
<div class="font-thin text-sm">353</div> <- the VALUE
|
|
<div class="font-bold text-xs uppercase">Movies</div> <- the LABEL
|
|
which is exactly backwards. The number you actually came to read is
|
|
weight 100 at 13px; the label shouting beside it is weight 700. Skyfall's
|
|
rule is that hierarchy comes emphatically from weight AND size, and that
|
|
numbers are the data — so this inverts the pair: the value goes big, bold
|
|
and bright, the label drops to a quiet eyebrow.
|
|
|
|
The well itself is --surface-input, one step DOWN from the card it sits on,
|
|
so stats read as inset data rather than as another floating surface. Depth
|
|
here is a recess, so it takes the hairline without the shadow. */
|
|
.service-block {
|
|
background: var(--surface-input) !important;
|
|
border: 1px solid var(--border-subtle) !important;
|
|
border-radius: var(--radius-sm) !important;
|
|
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(--secondary-text) !important;
|
|
font-family: var(--font-mono) !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;
|
|
}
|
|
|
|
.service-block > div:last-child {
|
|
color: var(--text-faint) !important;
|
|
font-family: var(--font-body) !important;
|
|
font-size: var(--text-2xs) !important;
|
|
font-weight: var(--weight-medium) !important;
|
|
letter-spacing: var(--tracking-caps) !important;
|
|
line-height: var(--leading-snug) !important;
|
|
}
|
|
|
|
/* ---------- tabs ----------
|
|
The active rail is one of only two sanctioned accent expressions:
|
|
a 2px accent bar plus an --accent-soft fill. No glow — glows are
|
|
reserved for hero moments, and a tab is not one. */
|
|
#myTab li button {
|
|
color: var(--text-muted) !important;
|
|
font-family: var(--font-body) !important;
|
|
font-weight: var(--weight-medium) !important;
|
|
border-radius: var(--radius-md) !important;
|
|
transition: color var(--duration-fast) var(--ease-out),
|
|
background var(--duration-fast) var(--ease-out) !important;
|
|
}
|
|
|
|
#myTab li button:hover {
|
|
color: var(--text-body) !important;
|
|
background: var(--accent-soft) !important;
|
|
}
|
|
|
|
#myTab li button[aria-selected="true"] {
|
|
background: var(--accent-soft) !important;
|
|
color: var(--text-heading) !important;
|
|
box-shadow: var(--rail-active) !important;
|
|
}
|
|
|
|
/* ---------- group headers ----------
|
|
ALL-CAPS is permitted only for tiny eyebrow labels — this is that case. */
|
|
.service-group-name,
|
|
.bookmark-group-name {
|
|
color: var(--text-faint) !important;
|
|
font-family: var(--font-body) !important;
|
|
font-size: var(--text-xs) !important;
|
|
font-weight: var(--weight-semibold) !important;
|
|
letter-spacing: var(--tracking-caps) !important;
|
|
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
|
|
surface — a card with only a border reads flat, one with only a
|
|
shadow reads smudged. */
|
|
.service-card,
|
|
.bookmark-list li > a {
|
|
background: var(--surface-card) !important;
|
|
border: 1px solid var(--border-subtle) !important;
|
|
border-radius: var(--radius-lg) !important;
|
|
box-shadow: var(--shadow-sm) !important;
|
|
transition: background var(--duration-fast) var(--ease-out),
|
|
border-color var(--duration-fast) var(--ease-out),
|
|
box-shadow var(--duration-base) var(--ease-out) !important;
|
|
}
|
|
|
|
/* Hover: surfaces lighten one step, interactive cards lift sm -> md. */
|
|
.service-card:hover,
|
|
.bookmark-list li > a:hover {
|
|
background: var(--surface-raised) !important;
|
|
border-color: var(--border-default) !important;
|
|
box-shadow: var(--shadow-md) !important;
|
|
}
|
|
|
|
.service-name,
|
|
.bookmark-name {
|
|
color: var(--text-heading) !important;
|
|
font-weight: var(--weight-semibold) !important;
|
|
letter-spacing: var(--tracking-normal) !important;
|
|
}
|
|
|
|
.service-description,
|
|
.bookmark-description {
|
|
color: var(--text-muted) !important;
|
|
font-size: var(--text-sm) !important;
|
|
line-height: var(--leading-snug) !important;
|
|
}
|
|
|
|
/* ---------- status ----------
|
|
Semantic colour is the whole point: the system communicates status with
|
|
colour and icons, never emoji, and accents are semantic-ONLY. Stock
|
|
Homepage paints every pill the same slate, which throws away the signal. */
|
|
/* .service-block is deliberately NOT in this list — the stat wells get their
|
|
own treatment above, and folding them in here is what made every number
|
|
render at --text-2xs. */
|
|
.service-tag,
|
|
.service-stats {
|
|
background: var(--surface-raised) !important;
|
|
border: 1px solid var(--border-subtle) !important;
|
|
border-radius: var(--radius-md) !important;
|
|
color: var(--text-muted) !important;
|
|
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 {
|
|
color: var(--success-text) !important;
|
|
background: var(--success-soft) !important;
|
|
}
|
|
|
|
.docker-status-unhealthy,
|
|
.docker-status-dead,
|
|
.status-offline,
|
|
.status-unhealthy,
|
|
.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,
|
|
.status-unknown,
|
|
.status-not-found {
|
|
color: var(--warning-text) !important;
|
|
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,
|
|
input:focus-visible {
|
|
outline: none !important;
|
|
box-shadow: var(--focus-ring) !important;
|
|
border-radius: var(--radius-sm) !important;
|
|
}
|
|
|
|
/* ---------- motion ----------
|
|
"Respect reduced-motion" is in the system's motion rule, not an extra. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
transition-duration: 0.01ms !important;
|
|
animation-duration: 0.01ms !important;
|
|
}
|
|
}
|