The board's card width changed at every group boundary because `columns:` was being tuned per group under the 2026-08-18 "columns = member count" rule. That rule is retired: it sets `lg:grid-cols-N` for one group, so it fixes that group's CARD WIDTH, not its density. Measured on the live board, Notes rendered a single 1464px card, News and Media 728px, Eval & Retrieval 286px, everything else 360px. All 20 groups are now `columns: 4` and every card renders at 360. `.service-name` reserved a 78px status gutter with `padding-right` and relied on `overflow: hidden` to hold it, but overflow clips at the PADDING box — the gutter was spill room the title printed straight through. Six cards on the AI tab rendered their name underneath their own status pill, measured by testing the title text node's box against the status cluster's. The intended ellipsis never fired either: it is painted by whichever block's own line overflows, and that is the anonymous box around the bare title text node, which does not carry `overflow`. The gutter holds by wrapping now, and the description opts back out of it with a negative margin since the pill only ever covers the first line. Scriberr's `homepage.group=AI Systems` named a group absent from `layout:`, so it had no `tab:` and rendered on all four tabs — the same defect as UltraSeedbox in 2026-08-18, arriving this time from a container label. Relabelled to `AI - Audio Tools`, where the other ASR seats already live. The `homepage.group=AI Systems` sample in the repo-root CLAUDE.md was the source of the name and now carries the constraint, plus the fact that a labelled container is discovered from any of the five configured engines and must not also be listed in services.yaml. Also: descriptions clamped to three lines so a five-line outlier stops dragging its row 50px taller than its neighbours; icon ramp overridden off slate-400 -> slate-700, which was sinking the bottom half of every glyph into the card fill; bookmark groups and Jellyfin's trailing stream rows brought into the card and eyebrow vocabulary; group gap 10px -> 22px now that width no longer separates them. The icon override sits on `html[class]`, not `:root`: Homepage sets the same variables on `.theme-slate`, which is on the <html> element, and a class beats `:root` on the same element. Verified with Playwright against the live board — per-group card width, card height spread, and a geometric title-vs-status collision check, before and after. Before/after captures: http://10.100.10.50:8090/b/homepage-relayout/
698 lines
25 KiB
Plaintext
698 lines
25 KiB
Plaintext
/* Homepage — Australis.
|
|
*
|
|
* SOURCE OF TRUTH: this file. Do not hand-edit `conf/custom.css`; it is
|
|
* generated. Run `theme/build.py` after changing anything here, then
|
|
* `scripts/deploy-stack.sh esh-docker-vm homepage --conf`.
|
|
*
|
|
* WHY A BUILD STEP: 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/*.woff2` would 404 — the faces have to arrive inside the
|
|
* stylesheet as data: URIs. The build inlines them.
|
|
*
|
|
* TOKENS ARE CANONICAL, NOT DERIVED. Every hex below is copied verbatim from
|
|
* `~/.claude/skills/australis-design/colors_and_type.css`. The predecessor
|
|
* theme built a parallel OKLCH palette "derived from the Australis
|
|
* philosophy" rather than using the system's own values, which is how a
|
|
* design system quietly forks. `build.py` re-checks these against the skill
|
|
* file at build time when it is present and warns on drift.
|
|
*
|
|
* DIRECTION: instrument panel. This is engineering chrome, not a page with
|
|
* wallpaper. Canon: "the screen is the polar sky — empty, with light coming
|
|
* through it." Three moves carry it:
|
|
*
|
|
* 1. Group headings become the Australis eyebrow (mono / uppercase / 11px /
|
|
* 0.08em, cyan) with a hairline running to the right edge. Canon names
|
|
* the eyebrow a system signature and says to use it often; here it turns
|
|
* the groups into register bands instead of headings floating over a
|
|
* grid.
|
|
* 2. Status stops shouting. The filled emerald chips read louder than the
|
|
* service names they annotate; they become a semantic dot plus a mono
|
|
* micro-label at tertiary contrast.
|
|
* 3. Cards are bordered and opaque, sized to their own content. Canon puts
|
|
* border above shadow for grouping in chrome, and equal-height rows turn
|
|
* short cards into hollow boxes.
|
|
*
|
|
* WHAT WAS REMOVED, AND WHY IT IS NOT A REGRESSION: the previous theme
|
|
* carried a generated full-bleed aurora image behind the whole dashboard,
|
|
* added on the operator explicitly asking for an Arbo-generated background.
|
|
* Canon forbids exactly that ("solid fills only on chrome — no full-bleed
|
|
* photography, no decorative gradients", and the aurora motif "never as a
|
|
* background fill behind text"). The operator called the result ugly on
|
|
* 2026-08-19 and asked for a canonical-colors rebuild, so the image is gone.
|
|
* The aurora survives where canon actually sanctions it: a single 1px accent
|
|
* edge under the tab bar. If a background is ever wanted again, that is the
|
|
* conversation to reopen — not a quiet re-add.
|
|
*
|
|
* TYPE: Space Grotesk (display) / Inter (UI, body) / JetBrains Mono (all
|
|
* metadata) — canonical per the system README. Vendored as latin-subset
|
|
* VARIABLE woff2, one file per family covering the whole weight axis: 102 KB
|
|
* for three families against 56 KB for the three static Supreme cuts the
|
|
* predecessor shipped, and no Google Fonts request at page load.
|
|
*/
|
|
|
|
/* @@FONTS@@ */
|
|
|
|
:root {
|
|
/* Ice */
|
|
--aus-black: #222531;
|
|
--aus-white: #a9bcc3;
|
|
--aus-bright-white: #cce7ec;
|
|
/* Sea */
|
|
--aus-bright-black: #373b46;
|
|
--aus-dark-30: #414751;
|
|
--aus-dark-40: #565f69;
|
|
--aus-dark-50: #6e7882;
|
|
--aus-dark-60: #86929d;
|
|
--aus-bright-70: #9daeb6;
|
|
--aus-bright-80: #b3cbcf;
|
|
/* Aurora */
|
|
--aus-blue: #6388d8;
|
|
--aus-bright-blue: #a4c4ff;
|
|
--aus-cyan: #00b1a8;
|
|
--aus-bright-cyan: #42dcd1;
|
|
--aus-green: #16b866;
|
|
--aus-bright-green: #51e08a;
|
|
/* Dawn */
|
|
--aus-red: #ff491a;
|
|
--aus-yellow: #e1c631;
|
|
--aus-magenta: #9d78ff;
|
|
|
|
--bg-0: var(--aus-black);
|
|
--bg-1: var(--aus-bright-black);
|
|
--bg-2: var(--aus-dark-30);
|
|
--border-subtle: var(--aus-dark-30);
|
|
--border-default: var(--aus-dark-40);
|
|
|
|
--fg-0: var(--aus-bright-white);
|
|
--fg-1: var(--aus-white);
|
|
--fg-2: var(--aus-bright-70);
|
|
--fg-3: var(--aus-dark-60);
|
|
--fg-muted: var(--aus-dark-50);
|
|
|
|
--font-display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
|
|
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
|
|
|
|
--tracking-caps: 0.08em;
|
|
--radius-sm: 4px;
|
|
--radius-md: 6px;
|
|
--radius-lg: 10px;
|
|
--shadow-1: 0 1px 2px rgba(10, 12, 18, 0.4);
|
|
--dur-fast: 120ms;
|
|
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
|
|
/* Width the status cluster is allowed to occupy in a card's top-right.
|
|
Load-bearing in two places that must agree: the right inset reserved on
|
|
the title line, and the negative margin the description uses to opt back
|
|
out of it. Sized for the widest real cluster — a latency badge plus a
|
|
status word ("21 ms ● RUNNING") — plus the 14px card inset. */
|
|
--status-gutter: 96px;
|
|
|
|
}
|
|
|
|
/* ---- Icon colour ---------------------------------------------------------
|
|
Homepage draws every mdi and simple-icons glyph as a MASK over a gradient
|
|
fill, so this ramp IS the icon colour — `color:` does nothing to them, and
|
|
a filter only muddies them. Stock is slate-400 -> slate-700, and that dark
|
|
stop sinks the bottom half of every glyph into the card fill: at a glance
|
|
the rail reads as a column of grey smudges rather than as icons. Ice ->
|
|
aurora blue keeps both ends clear of --bg-1 and ties the rail to the tab
|
|
accent. Space-separated channels, not hex — Homepage consumes them as
|
|
`rgb(var(--color-logo-start))`.
|
|
|
|
⚠ NOT `:root`. Homepage sets these same two variables on `.theme-slate`,
|
|
and that class sits on the <html> element — the very element `:root`
|
|
matches. Same element, so the cascade decides, and `.theme-slate` (0,1,0)
|
|
outranks `:root` (0,0,1): a `:root` override here is simply ignored, which
|
|
is how the first attempt at this silently did nothing. `html[class]` is
|
|
(0,1,1) and wins on the same element without hard-coding which `theme-*`
|
|
class is active, so it survives a change to `color:` in settings.yaml.
|
|
Raising specificity is not enough on its own either — a custom property is
|
|
resolved from the NEAREST ancestor that sets it, so the override has to
|
|
land on <html> too, not on <body>. */
|
|
html[class] {
|
|
--color-logo-start: 179 203 207; /* #b3cbcf aus-bright-80 */
|
|
--color-logo-stop: 99 136 216; /* #6388d8 aus-blue */
|
|
}
|
|
|
|
/* ---- Canvas --------------------------------------------------------------
|
|
Solid fill. Canon forbids full-bleed imagery and decorative gradient fills
|
|
behind text. The single sanctioned aurora moment is the hairline under the
|
|
header chrome, further down. */
|
|
html,
|
|
body,
|
|
#page_wrapper,
|
|
#background,
|
|
body > div {
|
|
background-color: var(--bg-0) !important;
|
|
background-image: none !important;
|
|
}
|
|
|
|
/* `#background` above is load-bearing, not defensive tidiness. Homepage
|
|
server-renders the wallpaper as an INLINE style on `<div id="background">`,
|
|
and Next.js caches that rendered page — so removing the `background:` block
|
|
from settings.yaml is not enough on its own. A restart does not clear it;
|
|
only a full recreate does, and recreating this container has its own cost
|
|
(the tab bar and i18n go missing for up to an hour and then heal on their
|
|
own). Enforcing it in the stylesheet is deterministic, immediate, and also
|
|
means the canvas stays correct if the setting is ever re-added by accident.
|
|
The deliberate absence of the config block is documented in settings.yaml. */
|
|
|
|
body {
|
|
font-family: var(--font-sans) !important;
|
|
color: var(--fg-1);
|
|
font-size: 15px;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
/* ---- Group heading — the Australis eyebrow --------------------------------
|
|
Canon calls mono/uppercase/11px/0.08em the system's signature and says to
|
|
use it often. It replaces the oversized sans heading, and a hairline runs
|
|
from the label to the right edge so the groups read as register bands
|
|
rather than as titles floating over a grid. */
|
|
/* BOOKMARK GROUPS GET THE SAME TREATMENT. A bookmark group is a different
|
|
component with a parallel class family (`.bookmark-group`,
|
|
`h2.bookmark-group-name`, `.bookmark-group-icon`) and none of the selectors
|
|
below matched it, so UltraSeedbox rendered with Homepage's stock 20px sans
|
|
heading and a full-colour icon while every service group above it wore the
|
|
mono eyebrow. One un-themed heading in a column of themed ones is more
|
|
conspicuous than the whole page being un-themed. */
|
|
|
|
/* 22px, not 10px. Now that every group is the same four-across grid (see the
|
|
columns note in settings.yaml), the only thing separating one band from the
|
|
next is this gap — with card width no longer changing at the boundary, a
|
|
10px gap let two groups read as one long run of cards. */
|
|
.services-group > button.group,
|
|
.bookmark-group > button.group {
|
|
gap: 0;
|
|
margin-bottom: 4px;
|
|
margin-top: 22px;
|
|
}
|
|
|
|
/* …except the first group on a tab, which should sit up against the tab bar
|
|
rather than floating 22px below it. */
|
|
.services-group:first-of-type > button.group {
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.service-group-icon,
|
|
.bookmark-group-icon {
|
|
display: none !important;
|
|
}
|
|
|
|
h2.service-group-name,
|
|
h2.bookmark-group-name {
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 11px !important;
|
|
font-weight: 500 !important;
|
|
line-height: 1 !important;
|
|
text-transform: uppercase;
|
|
letter-spacing: var(--tracking-caps);
|
|
color: var(--aus-cyan) !important;
|
|
white-space: nowrap;
|
|
display: flex;
|
|
}
|
|
|
|
.services-group > button.group::after,
|
|
.bookmark-group > button.group::after {
|
|
content: "";
|
|
flex: 1;
|
|
height: 1px;
|
|
margin-left: 12px;
|
|
background: var(--border-subtle);
|
|
}
|
|
|
|
/* The chevron sits after the rule, muted. */
|
|
.services-group > button.group svg,
|
|
.bookmark-group > button.group svg {
|
|
color: var(--fg-muted);
|
|
margin-left: 8px;
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
/* ---- Service cards -------------------------------------------------------
|
|
Border > shadow for grouping in chrome (canon). Opaque fill, 1px border,
|
|
one soft shadow. Height is content-driven — equal-height stretching turns
|
|
short cards into hollow boxes. */
|
|
ul.services-list {
|
|
align-items: start !important;
|
|
row-gap: 8px;
|
|
}
|
|
|
|
li.service > .service-card {
|
|
height: auto !important;
|
|
background: var(--bg-1) !important;
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-lg) !important;
|
|
box-shadow: var(--shadow-1) !important;
|
|
margin-bottom: 0 !important;
|
|
transition: background-color var(--dur-fast) var(--ease-out),
|
|
border-color var(--dur-fast) var(--ease-out);
|
|
}
|
|
|
|
li.service > .service-card:hover {
|
|
background: var(--bg-2) !important;
|
|
border-color: var(--border-default);
|
|
}
|
|
|
|
/* Service name — display face, tight. Description — body face, secondary.
|
|
*
|
|
* ⚠ `.service-name` IS NOT THE TITLE. Homepage puts the title in as a bare
|
|
* text node and then nests `<p class="service-description">` as a sibling
|
|
* inside the SAME div, so every rule here lands on a box containing both. The
|
|
* title has no element of its own and cannot be selected — which is why it
|
|
* cannot be given its own truncation, and why the description needs an
|
|
* explicit escape hatch from anything the title needs. */
|
|
.service-name {
|
|
font-family: var(--font-display) !important;
|
|
font-size: 14px !important;
|
|
font-weight: 500 !important;
|
|
/* Pinned so the status cluster can be centred on this exact line rather
|
|
than floating against the card's top edge. Changing one without the
|
|
other breaks the alignment. */
|
|
line-height: 17px !important;
|
|
letter-spacing: -0.015em;
|
|
color: var(--fg-0) !important;
|
|
|
|
/* THE GUTTER MUST BE HELD BY WRAPPING, NOT BY CLIPPING.
|
|
This rule used to read `padding-right: 78px; overflow: hidden;
|
|
text-overflow: ellipsis; white-space: nowrap` and it did not work: CSS
|
|
clips overflow at the PADDING box, not the content box, so the reserved
|
|
78px was spill room the title printed straight through. Every long name on
|
|
the AI tab rendered underneath its own status pill — "char-rp (MeroMero-v2
|
|
NVFP4, multimodal)" collided with HEALTHY, "char-rp-reasoning (Heretic2
|
|
NVFP4+MTP)" with EXITED. The ellipsis never appeared either: it is painted
|
|
by the block whose own line overflows, and that block here is the
|
|
anonymous box around the text node, which does not carry the `overflow`.
|
|
Wrapping has neither problem — line breaking respects the content box, so
|
|
the gutter genuinely holds. Long names take two lines instead of one; on
|
|
the AI tab nearly every name in a group is long, so those rows stay flush
|
|
with each other. */
|
|
padding-right: var(--status-gutter) !important;
|
|
white-space: normal;
|
|
overflow: visible;
|
|
}
|
|
|
|
p.service-description {
|
|
font-family: var(--font-sans) !important;
|
|
font-size: 12.5px !important;
|
|
font-weight: 400 !important;
|
|
line-height: 17px !important;
|
|
color: var(--fg-3) !important;
|
|
margin-top: 3px;
|
|
white-space: normal;
|
|
|
|
/* Opt back out of the title's gutter. The status cluster is absolutely
|
|
positioned over the FIRST line only (top: 13px, height: 17px, so it
|
|
occupies y 13-30); the description never starts above y 33. Nothing can
|
|
collide, so it takes the full card width back and the gutter costs no
|
|
description space at all. */
|
|
margin-right: calc(8px - var(--status-gutter));
|
|
|
|
/* Floor of two lines, ceiling of three.
|
|
The floor keeps the common one-line/two-line mix bottom-aligned, which is
|
|
most of what made rows look ragged. The ceiling is the other half: a
|
|
handful of four- and five-line descriptions (The Booth, Waterland Studio,
|
|
the vLLM seats) were dragging their entire row 30-50px taller than every
|
|
card beside them, and at four cards across that gap is the most visible
|
|
defect on the page. Three lines truncates almost nothing in practice — it
|
|
bites only the few entries whose description had already outgrown a
|
|
dashboard card and belongs in the service's README instead.
|
|
Not equal-heights: that inflates short cards into hollow boxes to match a
|
|
widget card twice their height, which is worse than a ragged edge. */
|
|
min-height: 34px;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 3;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.service-icon {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
/* ---- Status — dots, not chips --------------------------------------------
|
|
The filled emerald pills read louder than the service names they annotate.
|
|
Replaced with a semantic dot plus a mono micro-label at tertiary contrast:
|
|
present when you look for it, silent when you are not. */
|
|
/* The status cluster is CENTRED ON THE SERVICE NAME'S LINE, not parked in the
|
|
card's top-right corner. Homepage positions it `absolute top-0 right-0`,
|
|
which floats it ~7px above the title's optical centre and reads as a
|
|
misalignment on every card. The offsets below reconstruct the title line's
|
|
geometry: .service-name sits 5px below the card's top edge and carries 8px
|
|
of its own top padding, so its 17px line box starts at 13px and centres at
|
|
21.5px. Giving the cluster that top and that height, and centring inside it,
|
|
puts the pill and the name on one optical line.
|
|
These three numbers move together — if the name's font-size or line-height
|
|
changes, recompute all of them. */
|
|
.service-tags {
|
|
top: 13px !important;
|
|
height: 17px;
|
|
align-items: center !important;
|
|
gap: 12px !important;
|
|
margin-top: 0 !important;
|
|
/* 14px right inset, against the 8px of text padding on the left — the
|
|
optical match, since the pill has no glyph hard against its edge. */
|
|
margin-right: 14px !important;
|
|
}
|
|
|
|
/* Every child of the cluster shares one baseline: status pills, latency
|
|
badges and container-stat buttons alike. */
|
|
.service-tag {
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
height: 17px;
|
|
line-height: 17px !important;
|
|
}
|
|
|
|
.service-tag,
|
|
.docker-status,
|
|
.site-monitor-status,
|
|
.service-tag > div:not(.sr-only) {
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
padding: 0 !important;
|
|
border-radius: 0 !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.docker-status > div,
|
|
.service-tag .text-\[8px\] {
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 9px !important;
|
|
font-weight: 500 !important;
|
|
line-height: 1 !important;
|
|
letter-spacing: var(--tracking-caps);
|
|
color: var(--fg-3) !important;
|
|
}
|
|
|
|
/* Flex-centred rather than nudged with vertical-align: the dot is an optical
|
|
round on a cap-height uppercase run, so it needs true centring, not a magic
|
|
pixel offset that only looks right at one font size. */
|
|
.docker-status > div::before {
|
|
content: "\25CF"; /* ● */
|
|
display: inline-block;
|
|
margin-right: 6px;
|
|
font-size: 7px;
|
|
line-height: 1;
|
|
color: var(--aus-dark-50);
|
|
}
|
|
|
|
.docker-status-healthy > div::before,
|
|
.docker-status-running > div::before {
|
|
color: var(--aus-green);
|
|
}
|
|
|
|
.docker-status-unhealthy > div::before,
|
|
.docker-status-exited > div::before,
|
|
.docker-status-dead > div::before {
|
|
color: var(--aus-red);
|
|
}
|
|
|
|
.docker-status-starting > div::before,
|
|
.docker-status-paused > div::before {
|
|
color: var(--aus-yellow);
|
|
}
|
|
|
|
/* Ping / latency — mono metadata, tabular, no chip. */
|
|
.site-monitor-status > div {
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 9px !important;
|
|
font-weight: 400 !important;
|
|
letter-spacing: 0.04em;
|
|
text-transform: lowercase !important;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--fg-muted) !important;
|
|
}
|
|
|
|
/* ---- Widget stat strip ---------------------------------------------------
|
|
Was nested boxes-in-a-box. Now a footer strip: hairline above, cells
|
|
divided by hairlines, value in the display face, label as a mono eyebrow.
|
|
The card stays one object instead of three. */
|
|
.service-container {
|
|
margin-top: 8px;
|
|
padding-top: 2px;
|
|
border-top: 1px solid var(--border-subtle);
|
|
gap: 0 !important;
|
|
}
|
|
|
|
/* Padding trimmed from `8px 4px 6px` on 2026-08-24. Every stat strip adds its
|
|
full height to its card and to nothing else in the row, so at four cards
|
|
across one AdGuard card opens a void beside three ordinary ones. The strip
|
|
cannot be made free, but it can be made small. */
|
|
.service-container > div {
|
|
background: transparent !important;
|
|
border-radius: 0 !important;
|
|
margin: 0 !important;
|
|
padding: 6px 4px 5px !important;
|
|
border-left: 1px solid var(--border-subtle);
|
|
}
|
|
|
|
.service-container > div:first-child {
|
|
border-left: 0;
|
|
}
|
|
|
|
/* value — nowrap because a long one (AdGuard's "32.767 ms") otherwise wraps
|
|
mid-cell and drags the whole card taller than its row-mates.
|
|
15px, down from 17px: at four cards across, AdGuard's four-cell strip has
|
|
~72px per cell and "130,253" no longer fits at 17px. */
|
|
.service-container > div > div:not(.uppercase) {
|
|
font-family: var(--font-display) !important;
|
|
font-size: 15px !important;
|
|
font-weight: 600 !important;
|
|
letter-spacing: -0.015em;
|
|
line-height: 1.2 !important;
|
|
white-space: nowrap;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--fg-0) !important;
|
|
}
|
|
|
|
.service-container > div {
|
|
min-width: 0; /* let a long value shrink the cell instead of overflowing */
|
|
}
|
|
|
|
/* label */
|
|
.service-container > div > div.uppercase {
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 9px !important;
|
|
font-weight: 400 !important;
|
|
letter-spacing: var(--tracking-caps);
|
|
color: var(--fg-3) !important;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* Trailing widget rows — Jellyfin's "No Active Streams" strip and its empty
|
|
second row, which Homepage emits AFTER .service-container as its own block
|
|
of Tailwind-tinted pills. Untouched they broke out of the card's language:
|
|
two light grey bars with their own radius hanging under an otherwise
|
|
hairline-ruled footer. Same footer vocabulary, so the card stays one object.
|
|
Not hidden — "no active streams" is the answer to the question the card is
|
|
there to answer. */
|
|
li.service .service-container ~ div > div {
|
|
background: transparent !important;
|
|
border-radius: 0 !important;
|
|
border-top: 1px solid var(--border-subtle);
|
|
height: auto !important;
|
|
min-height: 16px;
|
|
margin-top: 0 !important;
|
|
}
|
|
|
|
li.service .service-container ~ div span {
|
|
position: static !important;
|
|
display: block;
|
|
padding: 3px 4px 2px !important;
|
|
margin: 0 !important;
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 9px !important;
|
|
letter-spacing: var(--tracking-caps);
|
|
text-transform: uppercase;
|
|
color: var(--fg-3) !important;
|
|
}
|
|
|
|
/* ---- Bookmarks -----------------------------------------------------------
|
|
Same card language as a service: opaque fill, hairline, one soft shadow. The
|
|
name takes the display face and the description drops to mono metadata at
|
|
tertiary contrast, so a bookmark row reads as a quieter sibling of a service
|
|
card rather than as a different widget that wandered in. */
|
|
li.bookmark > a {
|
|
background: var(--bg-1) !important;
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-lg) !important;
|
|
box-shadow: var(--shadow-1) !important;
|
|
margin-bottom: 8px !important;
|
|
overflow: hidden;
|
|
transition: background-color var(--dur-fast) var(--ease-out),
|
|
border-color var(--dur-fast) var(--ease-out);
|
|
}
|
|
|
|
li.bookmark > a:hover {
|
|
background: var(--bg-2) !important;
|
|
border-color: var(--border-default);
|
|
}
|
|
|
|
.bookmark-icon {
|
|
background: transparent !important;
|
|
border-radius: 0 !important;
|
|
border-right: 1px solid var(--border-subtle);
|
|
}
|
|
|
|
.bookmark-name {
|
|
font-family: var(--font-display) !important;
|
|
font-size: 13px !important;
|
|
font-weight: 500 !important;
|
|
color: var(--fg-0) !important;
|
|
}
|
|
|
|
.bookmark-description {
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 10px !important;
|
|
letter-spacing: 0.02em;
|
|
color: var(--fg-3) !important;
|
|
}
|
|
|
|
/* ---- Tab bar -------------------------------------------------------------
|
|
The filled pill is replaced by a 2px top edge in aurora blue — canon's one
|
|
sanctioned accent-border pattern (and explicitly never a left border). */
|
|
ul.sm\:flex {
|
|
position: relative;
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
border-radius: 0 !important;
|
|
gap: 0;
|
|
overflow: visible;
|
|
}
|
|
|
|
/* The one moment of colour on the page. Canon sanctions the aurora glow —
|
|
bright-blue → bright-cyan → green — as an accent EDGE, never as a fill
|
|
behind text. A 1px rule under the tab bar is that edge, and it is the only
|
|
place the full aurora appears. */
|
|
ul.sm\:flex::after {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
height: 1px;
|
|
background: linear-gradient(
|
|
90deg,
|
|
var(--aus-blue) 0%,
|
|
var(--aus-cyan) 42%,
|
|
var(--aus-green) 68%,
|
|
var(--border-subtle) 100%
|
|
);
|
|
opacity: 0.9;
|
|
}
|
|
|
|
ul.sm\:flex > li {
|
|
height: 40px !important;
|
|
border-radius: 0 !important;
|
|
background: transparent !important;
|
|
overflow: visible;
|
|
}
|
|
|
|
ul.sm\:flex > li > button {
|
|
margin: 0 !important;
|
|
height: 100%;
|
|
border-radius: 0 !important;
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
box-shadow: none !important;
|
|
border-top: 2px solid transparent;
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 11px !important;
|
|
font-weight: 500 !important;
|
|
text-transform: uppercase;
|
|
letter-spacing: var(--tracking-caps);
|
|
color: var(--fg-3) !important;
|
|
transition: color var(--dur-fast) var(--ease-out);
|
|
}
|
|
|
|
ul.sm\:flex > li > button:hover {
|
|
color: var(--fg-1) !important;
|
|
background: transparent !important;
|
|
}
|
|
|
|
ul.sm\:flex > li > button.dark\:bg-white\/10 {
|
|
border-top-color: var(--aus-blue) !important;
|
|
color: var(--fg-0) !important;
|
|
background: linear-gradient(
|
|
180deg,
|
|
rgba(99, 136, 216, 0.12) 0%,
|
|
rgba(99, 136, 216, 0) 72%
|
|
) !important;
|
|
}
|
|
|
|
/* ---- Header chrome: resource widget + search -----------------------------
|
|
Same card treatment as everything else, so the header belongs to the page
|
|
instead of hovering above it. */
|
|
.information-widget-resource,
|
|
div.flex.flex-col.justify-center.mt-2 {
|
|
background: var(--bg-1) !important;
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-lg) !important;
|
|
box-shadow: var(--shadow-1) !important;
|
|
}
|
|
|
|
.information-widget-resource {
|
|
border: 0 !important;
|
|
border-radius: 0 !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
/* The header strip that holds the resource widgets. */
|
|
div.flex.flex-row.self-center.flex-wrap.justify-between {
|
|
background: var(--bg-1) !important;
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-1);
|
|
}
|
|
|
|
.information-widget-resource .text-xs,
|
|
.information-widget-resource span {
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 11px !important;
|
|
letter-spacing: 0.02em;
|
|
color: var(--fg-2) !important;
|
|
}
|
|
|
|
.resource-usage,
|
|
[class*="resource-usage"] {
|
|
background: var(--aus-black) !important;
|
|
height: 3px !important;
|
|
border-radius: 2px !important;
|
|
}
|
|
|
|
.resource-usage > div,
|
|
[class*="resource-usage"] > div {
|
|
background: var(--aus-cyan) !important;
|
|
border-radius: 2px !important;
|
|
}
|
|
|
|
/* Search input */
|
|
input[type="text"],
|
|
input[type="search"] {
|
|
font-family: var(--font-sans) !important;
|
|
font-size: 14px !important;
|
|
color: var(--fg-1) !important;
|
|
}
|
|
|
|
input::placeholder {
|
|
color: var(--fg-muted) !important;
|
|
font-family: var(--font-mono) !important;
|
|
font-size: 12px !important;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
/* ---- Focus — the aurora glow, canon's signature interaction motif -------- */
|
|
a:focus-visible,
|
|
button:focus-visible,
|
|
input:focus-visible {
|
|
outline: none !important;
|
|
box-shadow: 0 0 0 3px rgba(99, 136, 216, 0.35) !important;
|
|
border-radius: var(--radius-sm);
|
|
}
|