*,
*::before,
*::after {
    box-sizing: border-box;
}

html,
body {
    height: 100%;
}

body {
    margin: 0;
    background: var(--surface-desktop);
    color: var(--ink-primary);
    font-family: var(--face-label);
    font-size: var(--size-body);
    line-height: var(--leading-body);
    overflow: hidden;
    -webkit-font-smoothing: antialiased;
}

:focus-visible {
    outline: var(--hairline-width) solid var(--border-focus);
    outline-offset: 1px;
}

::selection {
    background: var(--color-phosphor-dim);
    color: var(--color-void);
}

.starfield-surface {
    position: fixed;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    z-index: var(--layer-starfield);
}

/* Sitting the readout inside the console layer at z-index zero rather than back with the starfield, because the layer paints a grid and a vignette over everything behind it and a line of nine pixel text underneath both was legible only where the sky happened to be dark */
.range-drift {
    position: absolute;
    left: 0;
    bottom: 96px;
    z-index: 0;
    white-space: nowrap;
    font-family: var(--face-numeral);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
    color: var(--ink-label);
    pointer-events: none;
    will-change: transform;
    /* Deriving the traverse from the sweep token rather than declaring a fifty-ninth one, so a station retuned to a faster scope sweep in Ship 1 drags the desktop readout along with it at the same ratio */
    animation: range-traverse calc(var(--dur-sweep) * 8) linear infinite;
}

@keyframes range-traverse {
    from {
        transform: translate3d(-30vw, 0, 0);
    }

    to {
        transform: translate3d(120vw, 0, 0);
    }
}

.station-rail {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    height: var(--rail-height);
    display: flex;
    align-items: stretch;
    background: var(--surface-chrome);
    border-bottom: var(--hairline-width) solid var(--border-hairline);
    z-index: var(--layer-rail);
    /* Letting the five fields scroll sideways instead of wrapping, because the rail is a fixed 28px and a wrapped second row would push the console layer down by exactly the height Ship 1's panel mode is going to reclaim anyway */
    overflow-x: auto;
    scrollbar-width: none;
}

.station-rail::-webkit-scrollbar {
    display: none;
}

.rail-field {
    display: flex;
    align-items: center;
    gap: var(--pad-base);
    flex: 0 0 auto;
    padding: 0 var(--pad-wide);
    border-right: var(--hairline-width) solid var(--border-hairline);
}

.rail-field-action {
    background: none;
    border-top: 0;
    border-right: var(--hairline-width) solid var(--border-hairline);
    border-bottom: 0;
    border-left: 0;
    color: inherit;
    font: inherit;
    cursor: pointer;
    transition: background-color var(--dur-chrome) var(--ease-chrome);
}

.rail-field-action:hover {
    background: var(--surface-console);
}

.rail-label {
    font-size: var(--size-micro);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    color: var(--ink-label);
}

.rail-value {
    font-family: var(--face-numeral);
    font-size: var(--size-rail);
    letter-spacing: var(--tracking-numeral);
    font-variant-numeric: tabular-nums;
    color: var(--ink-primary);
}

/* Recolouring the light time only while something is unresolved, since the field is a plain fact about the solar system when the queue is empty and a live countdown the rest of the time, and the operator should be able to tell which at a glance */
.rail-value[data-counting="true"] {
    color: var(--state-caution);
}

.launcher-gutter {
    position: fixed;
    top: var(--rail-height);
    bottom: 0;
    left: 0;
    width: var(--gutter-width);
    display: flex;
    flex-direction: column;
    gap: var(--gap-row);
    padding: var(--pad-wide) var(--pad-base);
    z-index: var(--layer-console-base);
}

.launcher {
    display: flex;
    align-items: center;
    gap: var(--pad-base);
    padding: var(--pad-tight) var(--pad-base);
    background: none;
    border: var(--hairline-width) solid transparent;
    border-radius: var(--radius-chrome);
    color: var(--ink-inert);
    font-family: var(--face-numeral);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
    text-align: left;
    cursor: pointer;
    transition: color var(--dur-chrome) var(--ease-chrome), border-color var(--dur-chrome) var(--ease-chrome);
}

.launcher:hover {
    color: var(--ink-primary);
}

.launcher[data-console-state="normal"] {
    color: var(--ink-primary);
    border-color: var(--border-hairline);
}

.launcher[data-console-state="min"] {
    color: var(--ink-label);
    border-color: var(--border-hairline);
}

.launcher-glyph {
    font-size: var(--size-body);
}

.console-layer {
    position: fixed;
    top: var(--rail-height);
    right: 0;
    bottom: 0;
    left: var(--gutter-width);
    z-index: var(--layer-console-base);
    /* Painting the survey grid from repeating gradients instead of a tiled image, because the repository ships no bitmaps at all and a gradient reflows to any viewport without a second asset at twice the density */
    background-image:
        repeating-linear-gradient(to right, var(--trace-grid) 0 var(--hairline-width), transparent var(--hairline-width) calc(var(--grid-pitch) * 4)),
        repeating-linear-gradient(to bottom, var(--trace-grid) 0 var(--hairline-width), transparent var(--hairline-width) calc(var(--grid-pitch) * 4));
}

/* Splitting the mesh into a major rule every four cells and a dimmed minor rule between them, since a single grid at one weight reads as wallpaper while a surveyed chart reads as an instrument, and the second tier costs one pseudo-element rather than a fifty-ninth token */
.console-layer::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0.3;
    background-image:
        repeating-linear-gradient(to right, var(--trace-grid) 0 var(--hairline-width), transparent var(--hairline-width) var(--grid-pitch)),
        repeating-linear-gradient(to bottom, var(--trace-grid) 0 var(--hairline-width), transparent var(--hairline-width) var(--grid-pitch));
}

.console-layer::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0.62;
    background: radial-gradient(ellipse 78% 68% at 46% 40%, transparent 0%, var(--surface-desktop) 100%);
}

.console {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    min-width: var(--console-min-width);
    min-height: var(--console-min-height);
    background: var(--surface-console);
    border: var(--hairline-width) solid var(--border-hairline);
    border-radius: var(--radius-chrome);
    overflow: hidden;
}

.console[data-focused="true"] {
    border-color: var(--color-phosphor-dim);
}

.console[data-console-state="min"] {
    display: none;
}

.console[data-dragging="true"] {
    border-color: var(--border-focus);
}

.console[data-dragging="true"] .console-body {
    opacity: 0.4;
}

.console-titlebar {
    display: flex;
    align-items: center;
    gap: var(--pad-base);
    height: var(--titlebar-height);
    flex: 0 0 auto;
    padding: 0 var(--pad-tight) 0 var(--pad-base);
    background: var(--surface-chrome);
    border-bottom: var(--hairline-width) solid var(--border-hairline);
    /* Killing the browser's own touch gestures on the header, since without this a finger drag scrolls the page underneath and the console never moves at all on a phone */
    touch-action: none;
    user-select: none;
    /* Leaving the arrow in place across the whole header, because a real window manager never swaps the pointer for a hand on a title bar and the flicker between grab and pointer as the cursor crosses the close button reads as a fault rather than an affordance */
    cursor: default;
}

.console-glyph {
    font-family: var(--face-numeral);
    font-size: var(--size-body);
    color: var(--ink-label);
}

.console-title {
    flex: 1 1 auto;
    margin: 0;
    font-size: var(--size-label);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    line-height: var(--leading-tight);
    color: var(--ink-label);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.console[data-focused="true"] .console-title,
.console[data-focused="true"] .console-glyph {
    color: var(--ink-primary);
}

.console-actions {
    display: flex;
    align-items: center;
    gap: var(--pad-tight);
}

.chrome-action {
    width: 18px;
    height: 18px;
    display: grid;
    place-items: center;
    padding: 0;
    background: none;
    border: var(--hairline-width) solid transparent;
    border-radius: var(--radius-chrome);
    color: var(--ink-label);
    font-family: var(--face-numeral);
    font-size: var(--size-body);
    line-height: 1;
    cursor: default;
    transition: color var(--dur-chrome) var(--ease-chrome), border-color var(--dur-chrome) var(--ease-chrome);
}

.chrome-action:hover {
    color: var(--ink-primary);
    border-color: var(--border-hairline);
}

.chrome-action[data-chrome-action="close"]:hover {
    color: var(--state-fault);
}

.console-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    padding: var(--pad-wide);
    overflow: auto;
    transition: opacity var(--dur-chrome) var(--ease-chrome);
    /* Isolating each console body so a panel repainting its own telemetry cannot force the browser to re-lay-out the two consoles sitting behind it */
    contain: content;
}

/* Styling the scrollbar in both dialects rather than picking one, since scrollbar-color is the standard property Firefox honours while every Chromium and WebKit build still only understands the webkit pseudo-elements, and a default grey bar inside an amber console is the one piece of the browser the operator can see */
.console-body {
    scrollbar-width: thin;
    scrollbar-color: var(--color-phosphor-ghost) transparent;
}

.console-body::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

.console-body::-webkit-scrollbar-track {
    background: transparent;
}

.console-body::-webkit-scrollbar-thumb {
    background: var(--color-phosphor-ghost);
    border-radius: var(--radius-chrome);
}

.console-body::-webkit-scrollbar-thumb:hover {
    background: var(--color-phosphor-dim);
}

.console-body::-webkit-scrollbar-corner {
    background: transparent;
}

.panel-readout {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--pad-tight) var(--pad-wide);
    margin: 0;
}

.panel-label {
    font-size: var(--size-micro);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    color: var(--ink-label);
    align-self: center;
}

.panel-value {
    margin: 0;
    font-family: var(--face-numeral);
    font-size: var(--size-readout);
    letter-spacing: var(--tracking-numeral);
    font-variant-numeric: tabular-nums;
    color: var(--ink-primary);
    line-height: var(--leading-tight);
}

.panel-note {
    margin: var(--pad-wide) 0 0;
    font-size: var(--size-micro);
    letter-spacing: var(--tracking-label);
    color: var(--ink-inert);
}

.station-rail,
.launcher-gutter,
.console-layer {
    transition: opacity var(--dur-boot) var(--ease-chrome);
}

body[data-station="offline"] .station-rail,
body[data-station="offline"] .launcher-gutter,
body[data-station="offline"] .console-layer {
    opacity: 0;
}

.acquisition-gate,
.scripting-fault {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background: var(--surface-scrim);
    z-index: var(--layer-boot);
}

/* Laying an opaque pool under the panel and letting the scrim stay thin everywhere else, since a flat eighty percent veil made the boot screen readable by hiding the sky it was standing in front of */
.acquisition-gate {
    background:
        radial-gradient(ellipse 34% 40% at 50% 50%, var(--color-void) 0%, transparent 100%),
        var(--surface-scrim);
    transition: opacity var(--dur-boot) var(--ease-chrome), visibility var(--dur-boot) var(--ease-chrome);
}

.gate-cleared {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.gate-panel {
    display: grid;
    justify-items: center;
    gap: var(--pad-wide);
    padding: var(--pad-wide);
    text-align: center;
}

.gate-station,
.gate-status,
.gate-detail {
    margin: 0;
    font-family: var(--face-numeral);
    letter-spacing: var(--tracking-label);
}

.gate-station {
    font-size: var(--size-readout);
    color: var(--ink-primary);
}

.gate-status {
    font-size: var(--size-label);
    color: var(--state-caution);
}

.gate-detail {
    font-size: var(--size-micro);
    color: var(--ink-inert);
}

.gate-identity {
    display: grid;
    grid-template-columns: max-content max-content;
    gap: var(--pad-tight) var(--pad-wide);
    margin: 0;
    padding: var(--pad-base) var(--pad-wide);
    border-top: var(--hairline-width) solid var(--border-hairline);
    border-bottom: var(--hairline-width) solid var(--border-hairline);
    font-family: var(--face-numeral);
    font-size: var(--size-micro);
    letter-spacing: var(--tracking-label);
    text-align: left;
}

.gate-identity dt {
    color: var(--ink-inert);
}

.gate-identity dd {
    margin: 0;
    color: var(--ink-label);
}

.gate-acquire {
    margin-top: var(--pad-base);
    padding: var(--pad-base) var(--pad-wide);
    background: none;
    border: var(--hairline-width) solid var(--border-hairline);
    border-radius: var(--radius-chrome);
    color: var(--ink-primary);
    font-family: var(--face-numeral);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
    cursor: pointer;
    animation: gate-breathe calc(var(--dur-sweep) / 4) var(--ease-chrome) infinite alternate;
    transition: border-color var(--dur-chrome) var(--ease-chrome);
}

.gate-acquire:hover {
    border-color: var(--border-focus);
}

@keyframes gate-breathe {
    from {
        opacity: 0.55;
    }

    to {
        opacity: 1;
    }
}

/* Switching the repeating animations off by name rather than relying on the zero durations from tokens.css, since an animation of no length set to repeat forever still completes and restarts on every frame and would burn more battery than the motion it replaced */
@media (prefers-reduced-motion: reduce) {

    .range-drift,
    .gate-acquire {
        animation: none;
    }

    .range-drift {
        transform: translate3d(var(--pad-wide), 0, 0);
        will-change: auto;
    }

    .gate-acquire {
        opacity: 1;
    }
}

/* Wrapping the rail onto a second row below 720px rather than leaving six fields to scroll sideways behind a hidden scrollbar, because a readout the operator cannot find is worse than a rail that is two rows tall, and Ship 1's panel mode reclaims the height anyway */
@media (max-width: 720px) {
    :root {
        --rail-height: 56px;
        --gutter-width: 46px;
    }

    .station-rail {
        flex-wrap: wrap;
        align-content: flex-start;
        overflow-x: visible;
    }

    .rail-field {
        height: calc(var(--rail-height) / 2);
        padding: 0 var(--pad-base);
        gap: var(--pad-tight);
    }

    .launcher-label {
        display: none;
    }

    .launcher {
        justify-content: center;
        padding: var(--pad-base) 0;
    }

    .console-body {
        padding: var(--pad-base);
    }
}