Files
remote-control-support-webapp/public/styles.css
T
rmancinasandClaude Opus 5 cfda6b19b7 feat(install): one-command device registration for macOS, Windows and Linux
Registering a machine meant reading a multi-step page, installing Node, and
running three commands in the right order. Now it is one line per platform.

The hub serves scripts/install.sh and scripts/install.ps1 with its own address
and the enrolment token substituted in, so the published command carries
everything and there is nothing to fill in:

  curl -fsSL https://support.freakma.com/install.sh?token=TOKEN | sh
  irm https://support.freakma.com/install.ps1?token=TOKEN | iex

That output is piped straight into a shell, so the token — the only untrusted
value in either file — is refused unless it matches the base64url shape that
randomToken produces.

Each script checks for a usable runtime and stops with instructions rather than
guessing, warns when nothing is serving RFB on the loopback, installs per-user
with no root or administrator, and registers a login-scoped service: launchd on
macOS, a lingering systemd user service on Linux, a logon task on Windows. The
Windows script uses Node 22 when it is present and falls back to the bundled
executable otherwise, which is what lets one command cover both Windows 11 and
Server 2008 R2.

It registers a logon task rather than a service on purpose: services run in
session 0 and cannot draw on the interactive desktop, so the "ask first" consent
prompt would never appear.

Also adds /docs — a per-OS setup guide with service management and a
troubleshooting table — and reworks the enrolment page into OS tabs that open on
whichever platform the reader is sitting at.

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

1057 lines
28 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; }
/* Links that have to read as controls: downloads, and anything that navigates
rather than acts. Same surface as a button, minus the states a link cannot
have. Keep the modifiers in sync with the button rules above. */
a.btn {
display: inline-flex;
align-items: center;
text-decoration: none;
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);
white-space: nowrap;
transition: border-color var(--t-fast), background var(--t-fast),
color var(--t-fast), box-shadow var(--t-fast);
}
a.btn:hover { background: var(--surface-3); border-color: var(--line-hover); }
a.btn.small { padding: 5px 11px; font-size: 12.5px; border-radius: 6px; }
a.btn.ghost { background: transparent; border-color: transparent; color: var(--text-dim); }
a.btn.ghost:hover { background: var(--surface-2); color: var(--text); }
a.btn.primary {
background: var(--mint);
border-color: var(--mint);
color: var(--mint-ink);
font-weight: 650;
}
a.btn.primary:hover { background: var(--mint-bright); border-color: var(--mint-bright); }
/* ------------------------------------------------------------------ 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;
}
}