feat(web): SPA skeleton embedded in the binary (#60)
ci / web (push) Successful in 33s
ci / rust (push) Successful in 57s
e2e / e2e (push) Successful in 1m7s

This commit was merged in pull request #60.
This commit is contained in:
2026-08-22 21:07:40 +01:00
parent 94c334ffe9
commit 514628f089
22 changed files with 2277 additions and 2 deletions
+488
View File
@@ -0,0 +1,488 @@
/* arr — master-control board.
Tokens mirror .impeccable/design.json; components consume tokens, never
literals. Committed dark: a control room is lit by its lamps. */
@font-face {
font-family: "Rajdhani";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/fonts/rajdhani-600-latin.woff2") format("woff2");
}
@font-face {
font-family: "Rajdhani";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/fonts/rajdhani-700-latin.woff2") format("woff2");
}
:root {
/* ground and panels */
--ground: oklch(0.14 0.01 250);
--panel: oklch(0.18 0.012 250);
--panel-raised: oklch(0.21 0.014 250);
--line: oklch(0.3 0.015 250);
--line-strong: oklch(0.42 0.02 250);
--highlight: oklch(1 0 0 / 4%);
/* ink */
--ink: oklch(0.93 0.008 250);
--ink-muted: oklch(0.72 0.015 250);
--ink-faint: oklch(0.58 0.015 250);
/* signal lamps */
--signal-ok: oklch(0.8 0.17 150);
--signal-warn: oklch(0.82 0.14 80);
--signal-fault: oklch(0.68 0.21 25);
--signal-idle: oklch(0.45 0.02 250);
/* the one interactive hue */
--accent: oklch(0.8 0.12 220);
/* type */
--font-label: system-ui, "Segoe UI", sans-serif;
--font-readout:
ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", Menlo, Consolas, monospace;
--font-display: "Rajdhani", "Avenir Next Condensed", "Arial Narrow", sans-serif;
/* rhythm */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--radius: 4px;
--ease-out: cubic-bezier(0.25, 1, 0.5, 1);
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
}
body {
min-height: 100dvh;
display: flex;
flex-direction: column;
background: var(--ground);
color: var(--ink);
font-family: var(--font-label);
font-size: 1rem;
line-height: 1.5;
}
.readout {
font-family: var(--font-readout);
font-size: 0.8125rem;
}
.dim {
color: var(--ink-muted);
}
/* ---- rails ---------------------------------------------------------- */
.rail {
display: flex;
align-items: center;
gap: var(--space-6);
padding: var(--space-3) var(--space-6);
border-bottom: 1px solid var(--line);
background: var(--panel);
box-shadow: inset 0 1px var(--highlight);
}
.rail-foot {
margin-top: auto;
border-bottom: 0;
border-top: 1px solid var(--line);
justify-content: space-between;
font-size: 0.75rem;
}
.rail-id {
display: flex;
align-items: center;
gap: var(--space-3);
}
.wordmark {
margin: 0;
font-family: var(--font-display);
font-size: 1.625rem;
font-weight: 700;
letter-spacing: 0.1em;
line-height: 1;
}
.rail-verdict {
margin: 0;
flex: 1;
color: var(--ink-muted);
}
.rail-verdict[data-tone="ok"] {
color: var(--signal-ok);
}
.rail-verdict[data-tone="warn"] {
color: var(--signal-warn);
}
.rail-verdict[data-tone="fault"] {
color: var(--signal-fault);
}
.rail-meta {
display: flex;
align-items: center;
gap: var(--space-4);
}
/* ---- lamps ---------------------------------------------------------- */
.lamp {
--lamp: var(--signal-idle);
width: 0.625rem;
height: 0.625rem;
flex: none;
border-radius: 50%;
background: var(--lamp);
/* the panel's own device: a lit indicator, not a depth shadow */
box-shadow:
0 0 6px var(--lamp),
0 0 14px oklch(from var(--lamp) l c h / 35%);
transition: background 300ms var(--ease-out);
}
.lamp-big {
width: 0.875rem;
height: 0.875rem;
}
.lamp[data-state="ok"] {
--lamp: var(--signal-ok);
}
.lamp[data-state="unconfigured"] {
--lamp: var(--signal-warn);
}
.lamp[data-state="degraded"] {
--lamp: var(--signal-warn);
}
.lamp[data-state="unreachable"] {
--lamp: var(--signal-fault);
}
.lamp[data-state="probing"] {
--lamp: var(--signal-idle);
animation: lamp-probe 1.2s var(--ease-out) infinite alternate;
}
/* dead air: dark, no glow, no motion */
.lamp[data-state="off"] {
--lamp: oklch(0.3 0.012 250);
box-shadow: none;
}
@keyframes lamp-probe {
from {
opacity: 0.45;
}
to {
opacity: 1;
}
}
/* power-up: lamps flicker on left to right, once, on first verdict */
@keyframes lamp-strike {
0% {
opacity: 0.2;
}
40% {
opacity: 1;
}
55% {
opacity: 0.35;
}
100% {
opacity: 1;
}
}
.board.powered .lamp {
animation: lamp-strike 420ms var(--ease-out) both;
animation-delay: calc(var(--strike, 0) * 120ms);
}
.board.powered .lamp[data-state="off"] {
animation: none;
}
@media (prefers-reduced-motion: reduce) {
.lamp[data-state="probing"],
.board.powered .lamp {
animation: none;
}
}
/* ---- board and chain ------------------------------------------------- */
.board {
width: 100%;
max-width: 68rem;
margin: 0 auto;
padding: var(--space-12) var(--space-6);
flex: 1;
display: flex;
align-items: center;
}
.chain {
width: 100%;
}
.chain {
display: grid;
grid-template-columns: 1fr 2.5rem 1.2fr 2.5rem 1fr;
grid-template-areas:
"tmdb t1 master out sink"
"prow t2 master out sink";
align-items: center;
row-gap: var(--space-6);
}
.area-tmdb {
grid-area: tmdb;
}
.area-prowlarr {
grid-area: prow;
}
.area-t1 {
grid-area: t1;
}
.area-t2 {
grid-area: t2;
}
.area-master {
grid-area: master;
}
.area-out {
grid-area: out;
}
.area-transmission {
grid-area: sink;
}
/* ---- modules --------------------------------------------------------- */
.module {
background: var(--panel);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: inset 0 1px var(--highlight);
padding: var(--space-4) var(--space-4) var(--space-3);
}
.module-master {
background: var(--panel-raised);
border-color: var(--line-strong);
padding: var(--space-6) var(--space-4) var(--space-4);
}
.module-head {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-1) var(--space-2);
}
.module-name {
margin: 0;
font-family: var(--font-display);
font-size: 1.0625rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
line-height: 1;
}
.module-master .module-name {
font-size: 1.3125rem;
}
.module-status {
margin-left: auto;
padding-left: var(--space-3);
color: var(--ink-muted);
}
.module-status[data-tone="ok"] {
color: var(--signal-ok);
}
.module-status[data-tone="warn"] {
color: var(--signal-warn);
}
.module-status[data-tone="fault"] {
color: var(--signal-fault);
}
.module-role {
margin: var(--space-2) 0 0;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--ink-muted);
}
.module-detail {
margin: var(--space-1) 0 0;
font-size: 0.75rem;
color: var(--ink-muted);
min-height: 1.2em;
}
/* ---- bus traces ------------------------------------------------------ */
.bus {
display: grid;
align-content: center;
justify-items: center;
}
.bus-trace {
--trace: var(--line-strong);
width: 100%;
height: 0.5rem;
display: block;
transition: filter 300ms var(--ease-out);
}
.bus-trace line {
stroke: var(--trace);
stroke-width: 1;
}
.bus-arrow {
fill: var(--trace);
}
.bus-trace[data-tone="ok"] {
--trace: var(--signal-ok);
}
.bus-trace[data-tone="warn"] {
--trace: var(--signal-warn);
}
.bus-trace[data-tone="fault"] {
--trace: var(--signal-fault);
}
/* ---- controls -------------------------------------------------------- */
.control {
min-height: 2.75rem;
padding: 0 var(--space-4);
font-family: var(--font-readout);
font-size: 0.8125rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--accent);
background: var(--panel-raised);
border: 1px solid var(--line-strong);
border-radius: var(--radius);
box-shadow: inset 0 1px var(--highlight);
cursor: pointer;
transition:
border-color 150ms var(--ease-out),
color 150ms var(--ease-out);
}
.control:hover {
border-color: var(--accent);
color: oklch(from var(--accent) calc(l + 0.08) c h);
}
.control:active {
box-shadow: inset 0 1px oklch(0 0 0 / 30%);
}
.control:disabled {
color: var(--ink-faint);
border-color: var(--line);
cursor: default;
}
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* ---- small screens --------------------------------------------------- */
@media (max-width: 46rem) {
.rail {
flex-wrap: wrap;
gap: var(--space-3) var(--space-4);
}
.rail-verdict {
flex-basis: 100%;
order: 3;
}
.board {
padding: var(--space-6) var(--space-4);
}
.chain {
grid-template-columns: 1fr;
grid-template-areas:
"tmdb"
"prow"
"t1"
"master"
"out"
"sink";
row-gap: var(--space-4);
}
/* one junctioned trace on the stacked chain; the second is redundant */
.area-t2 {
display: none;
}
.bus {
height: 2.25rem;
justify-content: center;
}
.bus svg {
transform: rotate(90deg);
transform-origin: center;
width: 2.25rem;
height: 0.5rem;
}
}