:root {
    --void: #04060d;
    --deep: #070b16;
    --surface: #0a0f1e;
    --raised: #0e1428;
    --line: #161d33;
    --line-bright: #202a47;

    --klein: #002fa7;
    --sapphire: #0f52ba;
    --royal: #2b5bd7;
    --azure: #6c9bf0;
    --ice: #a8c4f5;

    --text: #e7ecf8;
    --text-dim: #98a3be;
    --text-faint: #6e7b99;

    --ok: #3ecf8e;
    --alert: #e4606d;

    --sans: ui-sans-serif, system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

    --radius: 8px;
    --sidebar: 244px;

    /* a handful of stars, deliberately sparse - slightly larger than a single
       hairline pixel so they actually read as stars rather than noise */
    --starfield:
        radial-gradient(1.6px 1.6px at 16% 30%, rgba(255, 255, 255, 0.62), transparent 60%),
        radial-gradient(1.4px 1.4px at 61% 66%, rgba(255, 255, 255, 0.44), transparent 60%),
        radial-gradient(1.4px 1.4px at 83% 32%, rgba(255, 255, 255, 0.32), transparent 60%),
        radial-gradient(1.2px 1.2px at 37% 78%, rgba(255, 255, 255, 0.26), transparent 60%),
        radial-gradient(1.1px 1.1px at 8% 82%, rgba(255, 255, 255, 0.2), transparent 60%);
}

* {
    box-sizing: border-box;
}

/* ---------- ambient starfield ----------
 *
 * A reusable decorative backdrop for the few places a starfield is a whole
 * scene rather than a small accent (the sign-in screen, the Orbit panel) -
 * `--starfield` itself stays a plain, unanimated background layer everywhere
 * else (the active nav pill, primary buttons) since those are small
 * interactive controls, not backdrops. Very slow drift plus a handful of
 * independently-timed twinkle points keep it feeling alive without being
 * distracting; `prefers-reduced-motion` (below) turns every animation here
 * off along with everything else in the app. */

.starfield {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    background-image: var(--starfield);
    background-repeat: no-repeat;
    animation: starfield-drift 220s ease-in-out infinite alternate;
}

.starfield .twinkle {
    position: absolute;
    width: 2px;
    height: 2px;
    border-radius: 50%;
    background: #fff;
    opacity: 0;
    animation: star-twinkle 6.5s ease-in-out infinite;
}

@keyframes starfield-drift {
    from { background-position: 0% 0%; }
    to { background-position: 1.6% 2.2%; }
}

@keyframes star-twinkle {
    0%, 100% { opacity: 0; }
    50% { opacity: 0.8; }
}

html,
body {
    height: 100%;
}

body {
    display: grid;
    grid-template-columns: var(--sidebar) 1fr;
    margin: 0;
    overflow: hidden;
    background: var(--void);
    color: var(--text);
    font-family: var(--sans);
    font-size: 14px;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3 {
    margin: 0;
    font-weight: 600;
    letter-spacing: -0.01em;
}

.eyebrow {
    margin: 0;
    color: var(--text-faint);
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.16em;
}

.muted {
    color: var(--text-dim);
}

/* ---------- sidebar ---------- */

.sidebar {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding: 1.25rem 0 0.75rem;
    border-right: 1px solid var(--line);
    background: linear-gradient(180deg, var(--deep), var(--void));
}

/* dims the page behind the sidebar while it's open as an overlay on phone
   widths (see the responsive section below); does nothing at any other
   viewport size, where the sidebar is never an overlay */
.sidebar-backdrop {
    display: none;
}

.brand {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0 1.15rem 1.25rem;
}

.brand-mark {
    flex: none;
    color: var(--azure);
}

.brand-text strong {
    display: block;
    font-size: 0.95rem;
    letter-spacing: 0.02em;
}

.brand-text span {
    display: block;
    color: var(--text-faint);
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.16em;
}

.nav-section {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0 1.15rem 0.5rem;
}

.nav-section::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--line-bright), transparent);
}

.nav-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0;
    padding: 0 0.7rem;
    list-style: none;
}

.nav-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.45rem 0.55rem;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-dim);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}

.nav-item svg,
.nav-item .dot {
    flex: none;
    color: var(--text-faint);
    transition: color 120ms ease;
}

.nav-item span {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.nav-item:hover {
    background: rgba(43, 91, 215, 0.1);
    color: var(--text);
}

.nav-item:hover svg {
    color: var(--azure);
}

.nav-item.active {
    color: #fff;
    background:
        var(--starfield),
        linear-gradient(90deg, rgba(0, 47, 167, 0.65), rgba(15, 82, 186, 0.16));
}

.nav-item.active svg,
.nav-item.active .dot {
    color: var(--ice);
}

.nav-item.active::before {
    content: "";
    position: absolute;
    left: -0.7rem;
    top: 50%;
    width: 2px;
    height: 18px;
    transform: translateY(-50%);
    border-radius: 0 2px 2px 0;
    background: linear-gradient(180deg, var(--azure), var(--sapphire));
}

.nav-item .dot {
    width: 6px;
    height: 6px;
    margin: 0 5px;
    border-radius: 50%;
    background: currentColor;
}

.nav-item .dot.on {
    color: var(--ok);
}

.nav-item .dot.error {
    color: var(--alert);
}

/* asteroid belt dividing the Core from its satellites */
.belt {
    display: block;
    width: 100%;
    height: 24px;
    margin: 0.8rem 0 0.7rem;
    color: var(--azure);
    opacity: 0.7;
}

.belt-rocks {
    animation: drift 90s ease-in-out infinite alternate;
}

@keyframes drift {
    from { transform: translateX(0); }
    to { transform: translateX(-42px); }
}

/* a few of the rocks catch the light now and then - only a handful (not the
   whole belt), each on its own delay so they never sparkle in unison */
.belt-twinkle {
    animation: belt-twinkle 6s ease-in-out infinite;
}

@keyframes belt-twinkle {
    0%, 100% { opacity: 0.35; }
    50% { opacity: 1; }
}

.satellite-nav {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding-bottom: 0.5rem;
    scrollbar-width: thin;
    scrollbar-color: var(--line-bright) transparent;
}

.satellite-nav .empty {
    padding: 0.15rem 1.25rem;
    color: var(--text-faint);
    font-size: 0.78rem;
}

.sidebar-foot {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin-top: 0.5rem;
    padding: 0.7rem 1.15rem 0;
    border-top: 1px solid var(--line);
    color: var(--text-faint);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
}

.pulse {
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--text-faint);
}

.pulse.live {
    background: var(--ok);
    box-shadow: 0 0 0 3px rgba(62, 207, 142, 0.12);
}

.pulse.lost {
    background: var(--alert);
    box-shadow: 0 0 0 3px rgba(228, 96, 109, 0.12);
}

/* ---------- main column ---------- */

.main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.75rem;
    border-bottom: 1px solid var(--line);
    background: var(--deep);
}

.topbar h1 {
    font-size: 1.15rem;
}

.topbar-title {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.topbar-right {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.icon-button {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.35rem;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--text-dim);
    cursor: pointer;
    transition: color 120ms ease, background 120ms ease;
}

.icon-button:hover {
    background: var(--raised);
    color: var(--azure);
}

/* an author rule with the same specificity as the [hidden] attribute's own
   UA default otherwise wins by source order and would keep showing the
   button - this affected the existing config button too, just less visibly
   with only one icon-button in the topbar */
.icon-button[hidden] {
    display: none;
}

.icon-button:disabled {
    opacity: 0.45;
    cursor: default;
    background: none;
    color: var(--text-dim);
}

.icon-button-alert:hover {
    background: rgba(228, 96, 109, 0.12);
    color: var(--alert);
}

/* the sidebar collapse/expand toggle - a plain icon-button that only exists
   on genuinely phone-sized viewports (see the responsive section below); on
   desktop and tablet widths the sidebar is always visible so there is
   nothing for it to do */
.sidebar-toggle {
    display: none;
}

/* ---------- modal ---------- */

.modal-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgba(4, 6, 13, 0.72);
    backdrop-filter: blur(2px);
    z-index: 20;
}

.modal-overlay[hidden] {
    display: none;
}

.modal {
    width: 100%;
    max-width: 420px;
    max-height: 85vh;
    overflow-y: auto;
    padding: 1.25rem 1.35rem;
    background: var(--surface);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}

.modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.9rem;
}

.modal-head h2 {
    font-size: 1.02rem;
}

.modal-close {
    padding: 0.15rem 0.5rem;
    border: 0;
    background: none;
    color: var(--text-faint);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}

.modal-close:hover {
    color: var(--text);
}

.modal .banner {
    margin: 0 0 0.9rem;
    padding: 0.6rem 0.85rem;
    border: 1px solid var(--line);
    border-radius: 6px;
}

#updated {
    color: var(--text-faint);
    font-family: var(--mono);
    font-size: 0.74rem;
}

.view {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 1.5rem 1.75rem 3rem;
}

.view[hidden] {
    display: none;
}

.view > * {
    max-width: 1400px;
    animation: rise 180ms ease-out both;
}

@keyframes rise {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
}

.stack {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
}

.split > .stack {
    height: 100%;
}

.split > .stack > :last-child {
    flex: 1;
}

.split {
    display: grid;
    grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
    gap: 1.1rem;
}

.page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 1.15rem;
}

.page-head h2 {
    font-size: 1.05rem;
}

.page-head p {
    flex-basis: 100%;
    margin: 0;
    color: var(--text-dim);
    font-size: 0.85rem;
}

/* ---------- banner ---------- */

.banner[hidden] {
    display: none;
}

.banner {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0;
    padding: 0.6rem 1.75rem;
    border-bottom: 1px solid var(--line);
    font-size: 0.85rem;
}

.banner::before {
    content: "";
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

.banner.error {
    background: rgba(228, 96, 109, 0.08);
    color: var(--alert);
}

.banner.notice {
    background: rgba(108, 155, 240, 0.08);
    color: var(--azure);
}

/* ---------- panels, tiles, facts ---------- */

.panel {
    padding: 1.05rem 1.15rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
}

.panel > .eyebrow {
    margin-bottom: 0.9rem;
}

.tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
    gap: 1px;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--line);
}

.tile {
    padding: 0.85rem 1.1rem;
    background: var(--surface);
}

.tile dt {
    color: var(--text-faint);
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.14em;
}

.tile dd {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin: 0.3rem 0 0;
    font-family: var(--mono);
    font-size: 1.3rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
}

.tile dd small {
    color: var(--text-faint);
    font-size: 0.72rem;
}

.state-dot {
    align-self: center;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--text-faint);
}

.state-dot.on {
    background: var(--ok);
}

.state-dot.off {
    background: var(--alert);
}

.facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0.85rem 1.25rem;
    margin: 0;
}

.facts div {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.facts dt {
    color: var(--text-faint);
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.facts dd {
    margin: 0;
    font-family: var(--mono);
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}

/* ---------- orbital map ---------- */

/* the panel Orbit lives in gets its own small starfield behind it (see
   "ambient starfield" above) - relative positioning plus lifting the panel's
   real content onto its own stacking layer keeps that backdrop from ever
   painting over the rings, bodies or legend */
.orbit-panel {
    position: relative;
    overflow: hidden;
}

.orbit-panel > .eyebrow,
.orbit-panel > .orbit,
.orbit-panel > .orbit-legend {
    position: relative;
    z-index: 1;
}

.orbit {
    display: block;
    width: 100%;
    max-width: 300px;
    margin: 0.5rem auto 0;
}

.orbit-ring {
    fill: none;
    stroke: #27355c;
}

/* the inner (native) and outer (link) rings spin at different speeds and in
   opposite directions, the way two orbits at different radii actually would
   - a small, physically-motivated touch that reads as "alive" rather than a
   single stiff carousel */
.orbit-spin {
    transform-origin: 110px 110px;
}

.orbit-spin-native {
    animation: spin 220s linear infinite;
}

.orbit-spin-link {
    animation: spin-reverse 340s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

@keyframes spin-reverse {
    to { transform: rotate(-360deg); }
}

/* a slow, faint breathing glow on the core - restrained enough to read as
   ambient rather than a flashing light */
.orbit-nucleus {
    animation: nucleus-glow 7s ease-in-out infinite;
}

@keyframes nucleus-glow {
    0%, 100% { opacity: 0.14; }
    50% { opacity: 0.26; }
}

.orbit-body {
    fill: var(--azure);
}

.orbit-body.link {
    fill: var(--text-dim);
}

.orbit-body.off {
    fill: none;
    stroke: var(--text-faint);
    stroke-width: 1.2;
}

.orbit-body.status-error {
    fill: var(--alert);
    stroke: none;
    animation: orbit-pulse 1.6s ease-in-out infinite;
}

@keyframes orbit-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}

.orbit-body {
    cursor: default;
}

.orbit-body:focus-visible {
    outline: 2px solid var(--ice);
    outline-offset: 2px;
}

.orbit-legend {
    display: flex;
    justify-content: center;
    gap: 1.1rem;
    margin-top: 0.9rem;
    color: var(--text-faint);
    font-size: 0.72rem;
}

.orbit-legend span {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.orbit-legend i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--azure);
}

.orbit-legend i.link {
    background: var(--text-faint);
}

/* ---------- satellite groups & cards ---------- */

.satellite-group + .satellite-group {
    margin-top: 1.75rem;
}

.satellite-group-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin-bottom: 0.85rem;
}

.cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
    gap: 1.1rem;
}

.card {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding: 1.05rem 1.15rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    transition: border-color 140ms ease;
}

.card:hover {
    border-color: var(--line-bright);
}

.card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.card-head h3 {
    font-size: 0.98rem;
}

/* the satellite's own icon (or the generic fallback) - a small fixed square so
   cards, the topbar and the sidebar nav can all show it at a consistent size
   without it ever dominating the layout */
.satellite-icon {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 6px;
    background: var(--raised);
    border: 1px solid var(--line);
    color: var(--azure);
}

.satellite-icon img,
.satellite-icon svg {
    width: 14px;
    height: 14px;
}

.satellite-icon.small {
    width: 18px;
    height: 18px;
    border-radius: 5px;
}

.satellite-icon.small img,
.satellite-icon.small svg {
    width: 11px;
    height: 11px;
}

/* reload + enable/disable (and the config gear, when present) - always
   right-aligned, and never the reason the head has to wrap onto a second
   line before the name and badges do */
.card-actions {
    display: flex;
    flex: none;
    align-items: center;
    gap: 0.15rem;
    margin-left: auto;
}

.card-meta {
    margin: -0.55rem 0 0;
    color: var(--text-faint);
    font-family: var(--mono);
    font-size: 0.75rem;
}

.card .description {
    margin: 0;
    color: var(--text-dim);
    font-size: 0.85rem;
}

.card-link {
    align-self: flex-start;
    margin-top: auto;
    padding: 0;
    border: 0;
    background: none;
    color: var(--azure);
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
}

.card-link:hover {
    color: var(--ice);
}

/* ---------- chips & badges ---------- */

.chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}

.chip {
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--line-bright);
    border-radius: 4px;
    background: var(--raised);
    color: var(--text-dim);
    font-family: var(--mono);
    font-size: 0.75rem;
}

button.chip {
    cursor: pointer;
    transition: border-color 120ms ease, color 120ms ease;
}

button.chip:hover {
    border-color: var(--sapphire);
    color: var(--ice);
}

.badge {
    padding: 0.1rem 0.5rem;
    border-radius: 4px;
    font-size: 0.66rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.badge-up {
    background: rgba(62, 207, 142, 0.12);
    color: var(--ok);
}

.badge-down {
    background: rgba(228, 96, 109, 0.12);
    color: var(--alert);
}

.badge-unknown {
    background: rgba(110, 123, 153, 0.14);
    color: var(--text-dim);
}

/* a satellite that's enabled but has no running background service right now
   (or whose service is only stopped because satellite services are globally
   off) - distinct from "disabled" (turned off on purpose) and "error" */
.badge-stopped {
    background: rgba(110, 123, 153, 0.14);
    color: var(--text-dim);
}

.badge-error {
    background: rgba(228, 96, 109, 0.22);
    color: var(--alert);
}

.badge-type {
    border: 1px solid var(--line-bright);
    color: var(--text-faint);
}

/* ---------- buttons ---------- */

.button {
    padding: 0.4rem 0.85rem;
    border: 1px solid var(--line-bright);
    border-radius: 6px;
    background: var(--raised);
    color: var(--text);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}

.button:hover {
    border-color: var(--sapphire);
}

.button-primary {
    border-color: var(--sapphire);
    background:
        var(--starfield),
        linear-gradient(180deg, var(--sapphire), var(--klein));
    color: #fff;
}

.button-primary:hover {
    border-color: var(--royal);
}

.button-stop {
    border-color: rgba(228, 96, 109, 0.45);
    background: transparent;
    color: var(--alert);
}

.button-stop:hover {
    border-color: var(--alert);
    background: rgba(228, 96, 109, 0.1);
}

.button-stop.confirm {
    border-color: var(--alert);
    background: var(--alert);
    color: #17070a;
    font-weight: 600;
}

/* generic confirm state for any other button that asks for a second click
   (Restart StarCore, in particular) - the same idea as .button-stop.confirm,
   just not in alert red since restarting isn't as destructive as stopping */
.button.confirm {
    border-color: var(--sapphire);
    background: var(--sapphire);
    color: #fff;
    font-weight: 600;
}

.button:disabled {
    opacity: 0.55;
    cursor: default;
}

/* ---------- console ---------- */

.console-form {
    display: flex;
    gap: 0.5rem;
    margin: 0.9rem 0;
}

.console-field {
    position: relative;
    flex: 1;
}

.console-field::before {
    content: "\203a";
    position: absolute;
    left: 0.7rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--azure);
    font-family: var(--mono);
}

.console-form input {
    width: 100%;
    padding: 0.5rem 0.75rem 0.5rem 1.6rem;
    border: 1px solid var(--line-bright);
    border-radius: 6px;
    background: var(--raised);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.85rem;
}

.console-form input:focus {
    outline: none;
    border-color: var(--sapphire);
}

.console-form input::placeholder {
    color: var(--text-faint);
}

/* ---------- configuration ---------- */

/* ---------- authentication ---------- */

.auth-overlay {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.25rem;
    background:
        radial-gradient(circle at 50% 20%, rgba(15, 82, 186, 0.18), transparent 60%),
        var(--deep);
}

/* the animated .starfield element (see "ambient starfield" above) replaces
   the plain --starfield layer that used to sit in the background above */

/* [hidden] and a same-specificity "display: flex" rule tie on specificity, so
   the later one in source order wins - without this override the overlay
   would stay visible (and still intercept clicks) even after JS sets
   .hidden = true. Same bug class already fixed once for .icon-button. */
.auth-overlay[hidden] {
    display: none;
}

.auth-card {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    width: 100%;
    max-width: 340px;
    padding: 1.75rem 1.6rem;
    border: 1px solid var(--line-bright);
    border-radius: var(--radius);
    background: var(--surface);
}

.auth-card h2 {
    margin: 0;
    font-size: 1.1rem;
}

.auth-card .muted {
    margin: -0.5rem 0 0;
    font-size: 0.82rem;
}

.auth-card .button {
    margin-top: 0.2rem;
}

.field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 0.82rem;
    color: var(--text-dim);
}

.field input {
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--line-bright);
    border-radius: 6px;
    background: var(--raised);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.85rem;
}

.field input:focus {
    outline: none;
    border-color: var(--sapphire);
}

.config-field {
    margin-bottom: 0.9rem;
}

.config-field label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: 0.82rem;
    color: var(--text-dim);
}

.config-field input {
    width: 100%;
    max-width: 320px;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--line-bright);
    border-radius: 6px;
    background: var(--raised);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.85rem;
}

.config-field input:focus {
    outline: none;
    border-color: var(--sapphire);
}

.config-field .hint {
    margin: 0.35rem 0 0;
    color: var(--text-faint);
    font-size: 0.78rem;
}

.config-checkbox {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    color: var(--text-dim);
    cursor: pointer;
}

/* a "path"-typed setting's input plus its "Browse…" button, side by side */
.config-field-row {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

.config-field-row input {
    flex: 1;
    max-width: none;
}

.config-field-row .button {
    flex: none;
    white-space: nowrap;
}

/* ---------- directory picker ----------
 *
 * A generic filesystem directory picker, opened from any "path"-typed
 * configuration field. It delegates entirely to the FileExplorer satellite's
 * own /entries route (docs/satellites.md) - nothing here knows about Photo,
 * Music, or any other satellite's settings. */

#directory-picker-overlay .modal {
    max-width: 560px;
}

.dir-picker-breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.2rem;
    margin-bottom: 0.75rem;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--raised);
    font-family: var(--mono);
    font-size: 0.82rem;
    overflow-x: auto;
}

.dir-picker-crumb {
    padding: 0.1rem 0.3rem;
    border: 0;
    border-radius: 4px;
    background: none;
    color: var(--azure);
    font: inherit;
    cursor: pointer;
    white-space: nowrap;
}

.dir-picker-crumb:hover {
    background: rgba(43, 91, 215, 0.12);
    color: var(--ice);
}

.dir-picker-crumb-sep {
    color: var(--text-faint);
}

.dir-picker-list {
    max-height: 40vh;
    overflow-y: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin-bottom: 0.9rem;
}

.dir-picker-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 0;
    border-bottom: 1px solid var(--line);
    background: none;
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.dir-picker-row:last-child {
    border-bottom: 0;
}

.dir-picker-row:hover {
    background: var(--raised);
}

.dir-picker-empty {
    margin: 0;
    padding: 0.75rem;
    color: var(--text-faint);
    font-size: 0.85rem;
}

.dir-picker-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.dir-picker-current {
    flex: 1;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-faint);
    font-family: var(--mono);
    font-size: 0.78rem;
}

.console-output {
    min-height: 300px;
    max-height: 52vh;
    overflow-y: auto;
    margin: 0;
    padding: 1rem 1.15rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text-dim);
    font-family: var(--mono);
    font-size: 0.82rem;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-word;
    scrollbar-width: thin;
    scrollbar-color: var(--line-bright) transparent;
}

.console-entry {
    padding-bottom: 0.75rem;
}

.console-echo {
    color: var(--ice);
}

.console-echo::before {
    content: "\203a  ";
    color: var(--azure);
}

.console-error {
    color: var(--alert);
}

.console-idle {
    color: var(--text-faint);
}

/* ---------- scrollbars ---------- */

::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: 8px;
    background: var(--line-bright);
    background-clip: content-box;
}

/* ---------- responsive ---------- */

@media (max-width: 1040px) {
    body {
        grid-template-columns: 64px 1fr;
    }

    .brand {
        justify-content: center;
        padding: 0 0 1.25rem;
    }

    .brand-text,
    .nav-section,
    .nav-item span,
    .satellite-nav .empty,
    .sidebar-foot span {
        display: none;
    }

    .nav-item {
        justify-content: center;
        padding: 0.5rem;
    }

    .sidebar-foot {
        justify-content: center;
        padding-inline: 0;
    }

    .topbar,
    .banner,
    .view {
        padding-inline: 1.25rem;
    }
}

@media (max-width: 820px) {
    .split {
        grid-template-columns: minmax(0, 1fr);
    }

    /* the topbar's left side can now carry a title plus up to three icon
       buttons (satellite reload/enable-disable/configuration) - past a point
       that no longer fits next to the state badge and Stop button on one
       line, so the right side drops to its own row instead of being pushed
       off screen */
    .topbar {
        flex-wrap: wrap;
        row-gap: 0.6rem;
    }

    /* the state badge, timestamp and now two lifecycle buttons (Restart,
       Stop) no longer fit on one line even once they've dropped to their own
       row below the title - wrap those too rather than letting them push the
       page wider than the viewport */
    .topbar-right {
        flex-wrap: wrap;
        row-gap: 0.5rem;
    }
}

@media (max-width: 480px) {
    /* the exact capture time isn't worth the width it costs once the two
       lifecycle buttons are also fighting for room on a phone-sized topbar */
    #updated {
        display: none;
    }

    #restart-button,
    #stop-button {
        padding: 0.35rem 0.6rem;
        font-size: 0.78rem;
    }
}

/* ---------- phone-width sidebar overlay ----------
 *
 * Below this the icon-only rail from the >=1040px rules above is no longer
 * usable - there isn't enough width to spare a permanent column for it at
 * all. The sidebar instead becomes a full, labelled off-canvas panel that
 * slides in over the page (never squeezing it) and is opened/closed with the
 * new sidebar-toggle button; app.js toggles `sidebar-open` on <body>. This
 * block is deliberately last in source order so its overrides win the
 * cascade over the wider breakpoints above wherever both apply at once. */
@media (max-width: 600px) {
    body {
        grid-template-columns: 1fr;
    }

    .sidebar {
        position: fixed;
        inset: 0 auto 0 0;
        z-index: 40;
        width: min(82vw, 300px);
        transform: translateX(-100%);
        box-shadow: 12px 0 32px rgba(4, 6, 13, 0.55);
        transition: transform 200ms ease;
    }

    body.sidebar-open .sidebar {
        transform: translateX(0);
    }

    .sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 39;
        background: rgba(4, 6, 13, 0.6);
        opacity: 0;
        pointer-events: none;
        transition: opacity 200ms ease;
    }

    body.sidebar-open .sidebar-backdrop {
        opacity: 1;
        pointer-events: auto;
    }

    /* the sidebar is a full overlay again here, not the >=1040px icon rail -
       restore the labelled layout explicitly rather than relying on
       `revert`, which would fall back to each element's plain UA default
       instead of the specific value it actually needs */
    .brand {
        justify-content: flex-start;
        padding: 0 1.15rem 1.25rem;
    }

    .brand-text {
        display: block;
    }

    .nav-section {
        display: flex;
    }

    .nav-item span {
        display: inline;
    }

    .satellite-nav .empty {
        display: block;
    }

    /* .satellite-nav .empty's two classes outrank the [hidden] attribute's
       own UA specificity, so without this override the empty-state message
       would stay visible even while JS has it hidden - the same
       specificity tie already fixed for .icon-button and .auth-overlay */
    .satellite-nav .empty[hidden] {
        display: none;
    }

    .sidebar-foot {
        justify-content: flex-start;
        padding-inline: 1.15rem;
    }

    .sidebar-foot span {
        display: inline;
    }

    .nav-item {
        justify-content: flex-start;
        padding: 0.45rem 0.55rem;
    }

    .sidebar-toggle {
        display: flex;
        position: relative;
        z-index: 46;
        padding: 0.45rem;
    }

    body.sidebar-open .sidebar-toggle .icon-menu,
    .sidebar-toggle .icon-close {
        display: none;
    }

    body.sidebar-open .sidebar-toggle .icon-close {
        display: block;
    }

    .topbar,
    .banner,
    .view {
        padding-inline: 1rem;
    }

    .view {
        padding-block: 1.1rem 2.5rem;
    }

    .cards {
        grid-template-columns: minmax(0, 1fr);
    }

    .tiles {
        grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    }

    .facts {
        grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation: none !important;
        transition: none !important;
    }
}
