Files
remote-control-support-webapp/public/styles.css
T
rmancinasandClaude Opus 5 8af9d39ffd
Build and Deploy Remote Control Support / Test (push) Successful in 18s
Build and Deploy Remote Control Support / Build Image (push) Failing after 20s
Build and Deploy Remote Control Support / Deploy to Portainer (push) Skipped
feat(ui): warm-graphite design system and particle background
Reworks the console's look away from generic dark-mode defaults: the
neutral ramp carries a faint violet cast, and the signal palette now
distinguishes connection topology by colour — mint for live agents,
iris for direct connections, copper for consent-required machines.

Type contrast comes from pairing the platform mono against the platform
sans rather than a webfont, since the box this runs on has no internet.

particles.js draws a proximity-linked node field: full strength on the
login and public pages, frozen static in the console, and entirely
absent from the viewer, where every frame belongs to the remote desktop.
It caps DPR at 2, scales node count to viewport area, throttles to 30fps,
cancels rAF when the tab is hidden, and honours prefers-reduced-motion.

app.js and viewer.js are unchanged; the particle layer mounts itself and
tracks the console via MutationObserver on #app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:40:16 -07:00

1027 lines
27 KiB
CSS

/* ============================================================================
Remote Support — design system
Dark-first ops console. Warm-graphite neutrals, mint signal, copper accent.
Typography pairs the platform mono (structure, labels, identifiers) with the
platform sans (prose, controls) — no webfonts, this box may have no internet.
========================================================================= */
:root {
/* --- neutrals: warm graphite, faint violet cast (not the usual blue-gray) */
--bg: #0b0a0d;
--bg-deep: #070609;
--surface: #131218;
--surface-2: #1a1922;
--surface-3: #211f2b;
--well: #0e0d12;
--line: #232230;
--line-strong: #333042;
--line-hover: #474357;
--text: #eceaf2;
--text-dim: #a6a0b5; /* 7.4:1 on --surface */
--text-faint: #857f96; /* 4.9:1 on --surface */
/* --- signal palette */
--mint: #3ddc97; /* brand + primary action + "live" */
--mint-bright: #5ff0b1;
--mint-deep: #1c6f52;
--mint-glow: rgba(61, 220, 151, .14);
--mint-ink: #04140c;
--copper: #e9a05c; /* warnings, consent, secondary highlight */
--copper-deep: #59401f;
--iris: #9aa2ff; /* informational: direct connections, links */
--iris-deep: #2f3164;
--danger: #f4626f;
--danger-text: #ff9aa2;
--danger-deep: #4d2229;
/* --- shape + motion */
--r-sm: 7px;
--r-md: 10px;
--r-lg: 14px;
--r-xl: 18px;
--ease-out-quart: cubic-bezier(.25, 1, .5, 1);
--ease-out-expo: cubic-bezier(.16, 1, .3, 1);
--t-fast: 130ms var(--ease-out-quart);
--t-mid: 260ms var(--ease-out-quart);
--shadow-lift: 0 1px 2px rgba(0,0,0,.4), 0 12px 28px -14px rgba(0,0,0,.8);
--shadow-pop: 0 24px 70px -20px rgba(0,0,0,.85), 0 2px 6px rgba(0,0,0,.5);
--sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
--mono: ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Mono",
"Segoe UI Mono", Menlo, Consolas, "Liberation Mono", monospace;
}
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text);
font: 15px/1.55 var(--sans);
letter-spacing: .001em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Ambient page texture: a faint engineering grid plus one cool bloom.
Fixed, pointer-transparent, and never applied to the live session view. */
body:not(.viewer-body)::before {
content: "";
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
background-image:
linear-gradient(to right, rgba(255,255,255,.020) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255,255,255,.020) 1px, transparent 1px);
background-size: 68px 68px;
-webkit-mask-image: radial-gradient(120% 80% at 50% -10%, #000 15%, transparent 70%);
mask-image: radial-gradient(120% 80% at 50% -10%, #000 15%, transparent 70%);
}
body:not(.viewer-body)::after {
content: "";
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
background:
radial-gradient(70% 55% at 12% -8%, rgba(61,220,151,.09), transparent 62%),
radial-gradient(60% 50% at 92% 4%, rgba(154,162,255,.06), transparent 60%),
radial-gradient(90% 60% at 50% 108%, rgba(233,160,92,.045), transparent 65%);
}
/* Everything real sits above the ambient layers. #modal-root and #toast are
deliberately left out: their children are fixed with their own z-index and
must stack against the root, not inside a container's stacking context. */
#login, #app, .center-shell { position: relative; z-index: 1; }
a {
color: var(--mint);
text-decoration-color: rgba(61,220,151,.35);
text-underline-offset: 3px;
transition: color var(--t-fast);
}
a:hover { color: var(--mint-bright); }
h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.012em; }
h1 { font-size: 18px; }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
p { margin: 0 0 12px; }
::selection { background: rgba(61,220,151,.28); color: #fff; }
/* --------------------------------------------------------------- utilities */
.hidden { display: none !important; }
[hidden] { display: none !important; }
.mono { font-family: var(--mono); font-size: 12px; letter-spacing: 0; }
.dim { color: var(--text-dim); }
.faint { color: var(--text-faint); font-size: 13px; }
.row { display: flex; align-items: center; gap: 10px; }
.wrap { flex-wrap: wrap; }
.spacer { flex: 1 1 auto; }
/* Small structural label used across panels, cards, tables and forms. */
.eyebrow {
font-family: var(--mono);
font-size: 10.5px;
font-weight: 600;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--text-faint);
}
/* ----------------------------------------------------------- focus states */
:where(button, [href], input, select, textarea, [tabindex]):focus-visible {
outline: 2px solid var(--mint);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* --------------------------------------------------------------- controls */
button {
font: 500 13.5px/1.2 var(--sans);
letter-spacing: .005em;
padding: 8px 14px;
border-radius: var(--r-sm);
border: 1px solid var(--line-strong);
background: var(--surface-2);
color: var(--text);
cursor: pointer;
white-space: nowrap;
transition: border-color var(--t-fast), background var(--t-fast),
color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}
button:hover:not(:disabled) { background: var(--surface-3); border-color: var(--line-hover); }
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { opacity: .4; cursor: not-allowed; }
button.primary {
background: var(--mint);
border-color: var(--mint);
color: var(--mint-ink);
font-weight: 650;
}
button.primary:hover:not(:disabled) {
background: var(--mint-bright);
border-color: var(--mint-bright);
box-shadow: 0 6px 20px -10px rgba(61,220,151,.9);
}
button.danger {
color: var(--danger-text);
border-color: var(--danger-deep);
background: rgba(244,98,111,.06);
}
button.danger:hover:not(:disabled) {
background: rgba(244,98,111,.14);
border-color: var(--danger);
color: #ffd0d4;
}
button.ghost {
background: transparent;
border-color: transparent;
color: var(--text-dim);
}
button.ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--text); }
button.ghost.danger { background: transparent; border-color: transparent; }
button.ghost.danger:hover:not(:disabled) { background: rgba(244,98,111,.12); }
button.small { padding: 5px 11px; font-size: 12.5px; border-radius: 6px; }
/* ------------------------------------------------------------------ forms */
input, select, textarea {
font: 14px/1.4 var(--sans);
width: 100%;
padding: 9px 12px;
border-radius: var(--r-sm);
border: 1px solid var(--line);
background: var(--well);
color: var(--text);
transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
/* --text-faint keeps placeholders at ~5:1 on the input well, not decorative mush */
input::placeholder, textarea::placeholder { color: var(--text-faint); opacity: 1; }
input:hover:not(:focus), select:hover:not(:focus) { border-color: var(--line-strong); }
input:focus, select:focus, textarea:focus {
outline: none;
border-color: var(--mint-deep);
background: #0b0a0f;
box-shadow: 0 0 0 3px var(--mint-glow);
}
select {
appearance: none;
padding-right: 34px;
background-image: linear-gradient(45deg, transparent 50%, var(--text-faint) 50%),
linear-gradient(135deg, var(--text-faint) 50%, transparent 50%);
background-position: calc(100% - 17px) calc(50% + 1px), calc(100% - 12px) calc(50% + 1px);
background-size: 5px 5px, 5px 5px;
background-repeat: no-repeat;
}
label {
display: block;
margin-bottom: 6px;
font-family: var(--mono);
font-size: 10.5px;
font-weight: 600;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--text-dim);
}
.field { margin-bottom: 15px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* Checkbox rows are sentences, not labels — opt them back out of the mono cap style. */
label.check {
display: flex;
align-items: flex-start;
gap: 9px;
margin-bottom: 0;
font: 13.5px/1.45 var(--sans);
letter-spacing: normal;
text-transform: none;
color: var(--text);
cursor: pointer;
}
label.check input {
width: auto;
flex: none;
margin-top: 1px;
accent-color: var(--mint);
padding: 0;
}
/* ------------------------------------------------------------ brand mark */
.brand { display: flex; align-items: center; gap: 11px; }
.brand-mark {
position: relative;
width: 32px;
height: 32px;
flex: none;
display: grid;
place-items: center;
border-radius: 9px;
background:
linear-gradient(150deg, rgba(61,220,151,.22), rgba(154,162,255,.14) 55%, transparent),
var(--surface-2);
border: 1px solid var(--line-strong);
box-shadow: inset 0 1px 0 rgba(255,255,255,.06);
font: 700 12px/1 var(--mono);
letter-spacing: .04em;
color: var(--mint);
}
/* a single lit node on the mark — the product in one glyph */
.brand-mark::after {
content: "";
position: absolute;
top: -2px;
right: -2px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--mint);
box-shadow: 0 0 0 2px var(--bg), 0 0 10px rgba(61,220,151,.8);
}
.brand h1 { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.brand .brand-sub,
.card-header .brand-sub {
display: block;
margin-top: 1px;
font-family: var(--mono);
font-size: 9.5px;
font-weight: 600;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--text-faint);
}
/* --------------------------------------------------------- particle field */
.bg-field {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
opacity: 1;
transition: opacity 600ms var(--ease-out-quart);
}
.bg-field canvas { display: block; width: 100%; height: 100%; }
/* Inside the console it is a whisper, not a feature. */
body[data-screen="console"] .bg-field { opacity: .38; }
/* ------------------------------------------------------------------ login */
.login-shell {
min-height: 100vh;
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(340px, .95fr);
align-items: center;
gap: clamp(32px, 7vw, 90px);
max-width: 1080px;
margin: 0 auto;
padding: 40px 28px;
}
.login-aside { max-width: 460px; }
.login-aside .brand { margin-bottom: 34px; }
.login-headline {
font-size: clamp(28px, 4.2vw, 40px);
line-height: 1.08;
font-weight: 600;
letter-spacing: -.03em;
margin: 0 0 16px;
text-wrap: balance;
}
.login-headline em {
font-style: normal;
color: var(--mint);
}
.login-lede {
color: var(--text-dim);
font-size: 15px;
line-height: 1.6;
max-width: 42ch;
margin-bottom: 30px;
}
.login-facts {
list-style: none;
margin: 0;
padding: 0;
border-top: 1px solid var(--line);
}
.login-facts li {
display: flex;
align-items: baseline;
gap: 12px;
padding: 11px 2px;
border-bottom: 1px solid var(--line);
font-family: var(--mono);
font-size: 11px;
letter-spacing: .11em;
text-transform: uppercase;
color: var(--text-dim);
}
.login-facts li::before {
content: "";
width: 5px;
height: 5px;
flex: none;
border-radius: 1px;
background: var(--mint);
transform: translateY(-1px);
box-shadow: 0 0 8px rgba(61,220,151,.6);
}
.login-facts li:nth-child(2)::before { background: var(--copper); box-shadow: 0 0 8px rgba(233,160,92,.55); }
.login-facts li:nth-child(3)::before { background: var(--iris); box-shadow: 0 0 8px rgba(154,162,255,.55); }
.login-card {
width: 100%;
background: linear-gradient(180deg, rgba(255,255,255,.028), transparent 42%), var(--surface);
border: 1px solid var(--line-strong);
border-radius: var(--r-xl);
padding: 30px 30px 32px;
box-shadow: var(--shadow-pop);
animation: card-rise 520ms var(--ease-out-expo) both;
}
/* the compact brand only appears once the aside is dropped on narrow screens */
.card-brand { display: none; margin-bottom: 26px; }
.login-card .form-title {
font-size: 17px;
font-weight: 600;
letter-spacing: -.015em;
margin-bottom: 4px;
}
.login-card .form-note { margin: 0 0 22px; }
.login-card button[type="submit"] { width: 100%; padding: 11px 14px; font-size: 14px; }
.login-foot {
margin: 20px 0 0;
padding-top: 16px;
border-top: 1px solid var(--line);
font-family: var(--mono);
font-size: 10.5px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--text-faint);
}
@keyframes card-rise {
from { opacity: 0; transform: translateY(14px) scale(.985); }
to { opacity: 1; transform: none; }
}
@media (max-width: 900px) {
.login-shell { grid-template-columns: minmax(0, 1fr); max-width: 420px; gap: 30px; }
.login-aside { display: none; }
.login-shell .login-card .card-brand { display: flex; }
}
/* ---------------------------------------------------------------- console */
header.topbar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
gap: 16px;
padding: 11px 22px;
background: rgba(11,10,13,.86);
-webkit-backdrop-filter: blur(14px) saturate(140%);
backdrop-filter: blur(14px) saturate(140%);
border-bottom: 1px solid var(--line);
}
/* hairline of signal along the very top of the console */
header.topbar::before {
content: "";
position: absolute;
inset: 0 0 auto;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(61,220,151,.55) 18%, rgba(154,162,255,.4) 55%, transparent 88%);
}
.topbar-divider {
width: 1px;
height: 22px;
background: var(--line-strong);
flex: none;
}
nav.tabs {
display: flex;
gap: 3px;
overflow-x: auto;
scrollbar-width: none;
}
nav.tabs::-webkit-scrollbar { display: none; }
nav.tabs button {
background: none;
border: 1px solid transparent;
border-radius: 6px;
padding: 7px 12px;
color: var(--text-dim);
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
letter-spacing: .12em;
text-transform: uppercase;
}
nav.tabs button:hover:not(:disabled) { background: var(--surface-2); color: var(--text); }
nav.tabs button.active {
color: var(--mint);
background: rgba(61,220,151,.10);
border-color: rgba(61,220,151,.24);
}
#whoami {
font-family: var(--mono);
font-size: 11px;
letter-spacing: .06em;
color: var(--text-dim);
}
main {
padding: 26px 24px 72px;
max-width: 1240px;
margin: 0 auto;
}
.panel-head {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--line);
}
.panel-head h2 {
display: flex;
align-items: center;
gap: 9px;
font-family: var(--mono);
font-size: 12px;
font-weight: 700;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--text);
}
.panel-head h2::before {
content: "";
width: 3px;
height: 13px;
border-radius: 2px;
background: var(--mint);
box-shadow: 0 0 10px rgba(61,220,151,.5);
}
#machines-count {
font-family: var(--mono);
font-size: 10.5px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--text-faint);
padding: 3px 8px;
border: 1px solid var(--line);
border-radius: 20px;
}
/* app.js blanks this when there are no machines — don't leave an empty pill */
#machines-count:empty { display: none; }
/* ------------------------------------------------------------------ cards */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(296px, 1fr));
gap: 14px;
}
.card {
position: relative;
overflow: hidden;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 15px 16px;
display: flex;
flex-direction: column;
gap: 9px;
transition: border-color var(--t-fast), background var(--t-fast), transform var(--t-fast);
}
/* status rail: neutral by default, lit when the machine reports online */
.card::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background: var(--line-strong);
transition: background var(--t-fast);
}
.card:has(.dot.online)::before { background: linear-gradient(180deg, var(--mint), rgba(61,220,151,.15)); }
.card:has(.dot.direct)::before { background: linear-gradient(180deg, var(--iris), rgba(154,162,255,.15)); }
.card:hover { border-color: var(--line-strong); background: var(--surface-2); }
.card-title { display: flex; align-items: center; gap: 9px; }
.card-title h3 {
font-family: var(--mono);
font-size: 13.5px;
font-weight: 600;
letter-spacing: -.005em;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: auto -16px -15px;
padding: 12px 16px;
border-top: 1px solid var(--line);
background: rgba(0,0,0,.16);
}
/* ------------------------------------------------------------------- dots */
.dot {
width: 8px;
height: 8px;
flex: none;
border-radius: 50%;
background: var(--text-faint);
}
.dot.online {
background: var(--mint);
box-shadow: 0 0 0 3px var(--mint-glow);
animation: pulse 2.6s var(--ease-out-quart) infinite;
}
.dot.offline { background: #4b4757; box-shadow: none; animation: none; }
.dot.direct { background: var(--iris); box-shadow: 0 0 0 3px rgba(154,162,255,.13); animation: none; }
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 3px var(--mint-glow); }
50% { box-shadow: 0 0 0 5px rgba(61,220,151,.05); }
}
/* ------------------------------------------------------------------- tags */
.tag {
display: inline-flex;
align-items: center;
font-family: var(--mono);
font-size: 10px;
font-weight: 600;
letter-spacing: .1em;
text-transform: uppercase;
padding: 3px 8px;
border-radius: 20px;
border: 1px solid var(--line-strong);
background: rgba(255,255,255,.02);
color: var(--text-dim);
white-space: nowrap;
}
.tag.role { border-color: rgba(61,220,151,.32); color: var(--mint); background: rgba(61,220,151,.08); }
.tag.warn { border-color: rgba(233,160,92,.34); color: var(--copper); background: rgba(233,160,92,.08); }
/* ----------------------------------------------------------------- tables */
.table-wrap {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: var(--surface);
}
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th {
text-align: left;
padding: 10px 12px;
font-family: var(--mono);
font-size: 10.5px;
font-weight: 700;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--text-dim);
background: rgba(255,255,255,.022);
border-bottom: 1px solid var(--line);
white-space: nowrap;
}
td {
padding: 10px 12px;
border-bottom: 1px solid var(--line);
vertical-align: middle;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background var(--t-fast); }
tbody tr:hover { background: rgba(255,255,255,.028); }
td .faint, th .faint { font-size: 12.5px; }
/* ------------------------------------------------------------------ modal */
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 50;
display: grid;
place-items: center;
padding: 20px;
background: rgba(5,4,7,.74);
-webkit-backdrop-filter: blur(3px);
backdrop-filter: blur(3px);
animation: fade-in 180ms var(--ease-out-quart) both;
}
.modal {
width: 100%;
max-width: 470px;
max-height: 88vh;
overflow-y: auto;
background: linear-gradient(180deg, rgba(255,255,255,.03), transparent 30%), var(--surface);
border: 1px solid var(--line-strong);
border-radius: var(--r-lg);
padding: 22px 24px 24px;
box-shadow: var(--shadow-pop);
animation: card-rise 300ms var(--ease-out-expo) both;
}
.modal-head {
display: flex;
align-items: center;
gap: 10px;
margin: -22px -24px 20px;
padding: 16px 18px 15px 24px;
border-bottom: 1px solid var(--line);
}
.modal-head h2 {
font-size: 15px;
font-weight: 600;
letter-spacing: -.01em;
}
.modal-head button { padding: 3px 9px; font-size: 13px; line-height: 1; }
.modal-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
margin: 22px -24px -24px;
padding: 16px 24px;
border-top: 1px solid var(--line);
background: rgba(0,0,0,.18);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
/* ---------------------------------------------------------------- notices */
.notice {
padding: 11px 14px;
border-radius: var(--r-sm);
border: 1px solid var(--line-strong);
border-left-width: 2px;
background: var(--well);
font-size: 13.5px;
line-height: 1.45;
margin-bottom: 15px;
}
.notice.error {
border-color: rgba(244,98,111,.34);
border-left-color: var(--danger);
color: var(--danger-text);
background: rgba(244,98,111,.08);
}
.notice.ok {
border-color: rgba(61,220,151,.3);
border-left-color: var(--mint);
color: var(--mint);
background: rgba(61,220,151,.07);
}
.empty {
padding: 52px 24px;
text-align: center;
font-size: 13.5px;
color: var(--text-faint);
border: 1px dashed var(--line-strong);
border-radius: var(--r-md);
background:
repeating-linear-gradient(-45deg, rgba(255,255,255,.012) 0 8px, transparent 8px 16px);
}
.copybox { display: flex; gap: 8px; align-items: stretch; }
.copybox input {
font-family: var(--mono);
font-size: 12px;
letter-spacing: 0;
}
.copybox button { flex: none; }
#toast {
position: fixed;
left: 50%;
bottom: 26px;
z-index: 100;
transform: translateX(-50%);
padding: 11px 18px;
font-size: 13.5px;
background: var(--surface-2);
border: 1px solid var(--line-strong);
border-left: 2px solid var(--mint);
border-radius: var(--r-sm);
box-shadow: var(--shadow-pop);
animation: toast-in 300ms var(--ease-out-expo) both;
}
@keyframes toast-in {
from { opacity: 0; transform: translate(-50%, 12px); }
to { opacity: 1; transform: translate(-50%, 0); }
}
/* ----------------------------------------------------- public page shells */
.center-shell {
min-height: 100vh;
display: grid;
place-items: center;
padding: 40px 22px;
}
.center-card {
width: 100%;
max-width: 560px;
background: linear-gradient(180deg, rgba(255,255,255,.028), transparent 34%), var(--surface);
border: 1px solid var(--line-strong);
border-radius: var(--r-xl);
box-shadow: var(--shadow-pop);
animation: card-rise 520ms var(--ease-out-expo) both;
overflow: hidden;
}
.card-header {
display: flex;
align-items: center;
gap: 11px;
padding: 18px 26px;
border-bottom: 1px solid var(--line);
background: rgba(0,0,0,.2);
}
.card-body { padding: 26px; }
.card-body > h1 {
font-size: 22px;
letter-spacing: -.022em;
margin-bottom: 8px;
}
.card-kicker {
display: block;
margin-bottom: 10px;
font-family: var(--mono);
font-size: 10.5px;
font-weight: 600;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--mint);
}
pre.code {
background: var(--bg-deep);
border: 1px solid var(--line);
border-left: 2px solid rgba(61,220,151,.45);
border-radius: var(--r-sm);
padding: 13px 15px;
margin: 10px 0 0;
overflow-x: auto;
font-family: var(--mono);
font-size: 12.5px;
line-height: 1.65;
color: #cbe7da;
}
ol.steps {
list-style: none;
counter-reset: step;
margin: 22px 0 0;
padding: 0;
}
ol.steps li {
counter-increment: step;
position: relative;
padding: 0 0 20px 40px;
font-size: 14px;
line-height: 1.55;
}
ol.steps li::before {
content: counter(step, decimal-leading-zero);
position: absolute;
left: 0;
top: 0;
width: 26px;
height: 26px;
display: grid;
place-items: center;
border-radius: 7px;
border: 1px solid var(--line-strong);
background: var(--surface-2);
font: 700 10.5px/1 var(--mono);
letter-spacing: .04em;
color: var(--mint);
}
/* connector line between steps — the "sequence" reads at a glance */
ol.steps li::after {
content: "";
position: absolute;
left: 13px;
top: 30px;
bottom: 8px;
width: 1px;
background: linear-gradient(180deg, var(--line-strong), transparent);
}
ol.steps li:last-child { padding-bottom: 0; }
ol.steps li:last-child::after { display: none; }
ol.steps li strong { font-weight: 600; }
/* ----------------------------------------------------------------- viewer */
body.viewer-body {
overflow: hidden;
height: 100vh;
display: flex;
flex-direction: column;
background: #05070a;
}
.viewer-bar {
flex: none;
display: flex;
align-items: center;
gap: 9px;
height: 38px;
padding: 0 10px 0 12px;
background: linear-gradient(180deg, #17161d, #121118);
border-bottom: 1px solid #262433;
}
.viewer-bar #client-name {
font-family: var(--mono);
font-size: 12.5px;
font-weight: 600;
letter-spacing: -.005em;
max-width: 34vw;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.viewer-bar #conn-status {
font-family: var(--mono);
font-size: 10.5px;
letter-spacing: .1em;
text-transform: uppercase;
}
.viewer-bar .tag { padding: 2px 7px; font-size: 9.5px; }
.viewer-bar button {
padding: 4px 10px;
font-size: 12px;
border-radius: 6px;
background: rgba(255,255,255,.045);
border-color: rgba(255,255,255,.10);
}
.viewer-bar button:hover:not(:disabled) { background: rgba(255,255,255,.10); border-color: rgba(255,255,255,.2); }
.viewer-bar button.danger {
background: rgba(244,98,111,.10);
border-color: rgba(244,98,111,.28);
color: var(--danger-text);
}
.viewer-bar button.danger:hover:not(:disabled) { background: rgba(244,98,111,.2); border-color: var(--danger); }
.viewer-sep { width: 1px; height: 18px; background: rgba(255,255,255,.10); flex: none; }
#screen {
flex: 1 1 auto;
min-height: 0;
background: #05070a;
overflow: hidden;
}
.viewer-status {
position: absolute;
inset: 0;
display: grid;
place-items: center;
pointer-events: none;
background: radial-gradient(60% 60% at 50% 45%, rgba(11,10,13,.72), rgba(5,7,10,.94));
}
.viewer-status .box {
pointer-events: auto;
min-width: 300px;
max-width: 430px;
padding: 26px 30px 24px;
text-align: center;
background: var(--surface);
border: 1px solid var(--line-strong);
border-radius: var(--r-lg);
box-shadow: var(--shadow-pop);
}
.viewer-status .box h2 {
font-family: var(--mono);
font-size: 12px;
font-weight: 700;
letter-spacing: .15em;
text-transform: uppercase;
color: var(--text);
margin-bottom: 10px;
}
.viewer-status .box p { font-size: 13.5px; line-height: 1.5; }
/* indeterminate progress hairline under the status title */
.viewer-status .box::before {
content: "";
display: block;
width: 46px;
height: 2px;
margin: 0 auto 16px;
border-radius: 2px;
background: linear-gradient(90deg, transparent, var(--mint), transparent);
animation: scan 1.8s var(--ease-out-quart) infinite;
}
@keyframes scan {
0%, 100% { opacity: .25; transform: scaleX(.5); }
50% { opacity: 1; transform: scaleX(1); }
}
/* ------------------------------------------------------------- responsive */
@media (max-width: 820px) {
header.topbar { flex-wrap: wrap; gap: 10px 12px; padding: 10px 16px; }
header.topbar .topbar-divider { display: none; }
nav.tabs { order: 3; width: 100%; }
main { padding: 20px 16px 60px; }
.panel-head { flex-wrap: wrap; }
.field-row { grid-template-columns: 1fr; }
.card-body, .card-header { padding-left: 20px; padding-right: 20px; }
}
/* --------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .001ms !important;
animation-iteration-count: 1 !important;
transition-duration: .001ms !important;
scroll-behavior: auto !important;
}
}