/* ═══════════════════════════════════════════════════════════════════════════
   shell.css — desktop surface chrome (docs/plans/desktop/app-shell.md)

   Everything that persists across navigations: the gradient rail, the aurora,
   the brand squircle, the dock + menu shelf, the hover label pill, the side
   panel slot, the loading screen, the content slot, the narrow-window re-lay
   and print. Consumes the tokens desktop.css defines on :root (--shell-*,
   --panel-*, --rt-*, --step-*, --font-*, --shadow-*); defines none of its own
   except the loading screen's --pl-* / --loading-* (self-contained block).

   Mechanics borrowed from the data room shell (dataroom/index.php), reshaped
   to the desktop- prefix and the mobile app's DOM contract. Driven by
   desktop/assets/js/shell.js (RT.shell.*), never by inline styles.

   Stacking: canvas + aurora 1 · brand + dock 2 · content 3 · (dock 4 under
   900px) · panel 5 · label pill 6 · loading screen 9999.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── The push ────────────────────────────────────────────────────────────── */
/* Opening the side panel slides the rail (parked --panel-reveal off the left
   edge) to x 0 and moves every mover right by the same distance. The flag
   SNAPS on <html> (never tweened there, see desktop.css); each mover
   transitions its own transform on --panel-ease-close, and the open pair
   under html.panel-open. html clips x so a pushed mover never grows a
   horizontal scrollbar. */

html {
    overflow-x: clip;
}

html.panel-open {
    --panel-push: var(--panel-reveal);
}

/* ─── Gradient rail — the 360px parked canvas ─────────────────────────────── */
/* Same palette as the mobile shell's canvas; Gradient.js reads the
   --gradient-color-* custom properties. A fixed 360px × 3000px strip,
   vertically centred (the same centring as the dock, so the edge never
   scales or drifts relative to the menu) and parked --panel-reveal (120px)
   off the left edge: at rest the visible rail is the old 240px silhouette,
   and the panel push reveals the rest as a translation, never a scale.
   Viewports taller than the curve see its straight vertical extensions.

   The clip-path is the rail's STATIC shape and the no-GL fallback, authored
   for the 360px canvas (every x sits 120px right of the 240px-era path).
   With WebGL up, assets/js/rail-gradient.js reads it, draws the same edge in
   the shader and turns the clip off: one source of truth for the shape. */

#gradient-canvas {
    --gradient-color-1: #2c75de;
    --gradient-color-2: #eb5d8f;
    --gradient-color-3: #69e2e1;
    --gradient-color-4: #153869;

    position: fixed;
    top: 50%;
    left: 0;
    width: 360px;
    height: 3000px;
    z-index: 1;
    pointer-events: none;
    transform: translate(calc(var(--panel-push) - var(--panel-reveal)), -50%);
    transition: transform var(--panel-ease-close);
    clip-path: path('M -100 0 L 337 0 L 337 850 C 337 910, 331 921, 309.6 964.7 C 154.4 1282, 337.2 1538.1, 270.5 2035.3 C 264.1 2083, 257 2120, 257 2160 L 257 3000 L -100 3000 Z');

    /* Static fallback: a canvas is transparent until WebGL paints, so this
       shows whenever the animation is not running (init failure, no WebGL,
       the first frames before boot). Top layer: a captured frame of the real
       gradient at desktop/assets/images/gradient-fallback.jpg; a missing
       file just leaves the frozen-mesh layers below. Keeps the white menu
       legible if the rail ever dies. */
    background:
        url('../images/gradient-fallback.jpg') center / cover no-repeat,
        radial-gradient(120% 45% at 30% 32%, #2c75de 0%, transparent 70%),
        radial-gradient(110% 40% at 15% 50%, #eb5d8f 0%, transparent 72%),
        radial-gradient(120% 45% at 35% 68%, #69e2e1 0%, transparent 70%),
        #153869;
}

html.panel-open #gradient-canvas {
    transition: transform var(--panel-ease-open);
}

/* ─── Aurora — soft brand wash behind/beyond the rail ─────────────────────── */
/* Blurred radial blobs + drift, rotated into a full-height left band that
   fades out rightward, extending the rail's colour past the canvas edge.
   Rides right with the rail while the panel is open. */

.desktop-aurora {
    position: fixed;
    top: 0;
    left: 80px;
    bottom: 0;
    width: 32vw;
    z-index: 1;
    pointer-events: none;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to right, #000 35%, transparent);
    mask-image: linear-gradient(to right, #000 35%, transparent);
    transform: translateX(var(--panel-push));
    transition: transform var(--panel-ease-close);
}

html.panel-open .desktop-aurora {
    transition: transform var(--panel-ease-open);
}

.desktop-aurora::before,
.desktop-aurora::after {
    content: "";
    position: absolute;
    inset: -20% -40%;
    filter: blur(46px);
    border-radius: 50%;
}

/* Blob colours: --aurora-1..5 are written live by shell.js's aurora sampler
   (probed from the canvas at each blob's height: x190 y.18 / x190 y.82 /
   x160 y.50 / x175 y.35 / x175 y.65 in canvas px). Without JS/GL the
   --rt-* fallbacks keep the static brand palette. */
.desktop-aurora::before {
    background:
        radial-gradient(55% 42% at 22% 18%, color-mix(in srgb, var(--aurora-1, var(--rt-purple)) 52%, transparent), transparent 70%),
        radial-gradient(60% 45% at 28% 82%, color-mix(in srgb, var(--aurora-2, var(--rt-blue)) 46%, transparent), transparent 72%),
        radial-gradient(50% 35% at 8% 50%, color-mix(in srgb, var(--aurora-3, var(--rt-pink)) 55%, transparent), transparent 70%);
    animation: desktop-aurora-drift 26s ease-in-out infinite alternate;
}

.desktop-aurora::after {
    background:
        radial-gradient(42% 30% at 15% 35%, color-mix(in srgb, var(--aurora-4, var(--rt-teal)) 30%, transparent), transparent 70%),
        radial-gradient(40% 28% at 12% 65%, color-mix(in srgb, var(--aurora-5, var(--rt-amber)) 20%, transparent), transparent 70%);
    animation: desktop-aurora-drift 32s ease-in-out infinite alternate-reverse;
}

@keyframes desktop-aurora-drift {
    from { transform: translate3d(1%, -3%, 0) scale(1); }
    to   { transform: translate3d(-4%, 3%, 0) scale(1.07); }
}

@media (prefers-reduced-motion: reduce) {
    .desktop-aurora::before,
    .desktop-aurora::after { animation: none; }
}

/* ─── Brand squircle — top of the rail ────────────────────────────────────── */
/* A [data-route=Home] link; the label pill reads its data-title like an icon. */

.desktop-brand {
    position: fixed;
    top: 2rem;
    left: 24px;
    z-index: 2;
    display: block;
    line-height: 0;
}

.desktop-brand img {
    height: 76px;
    border-radius: 18px;
    box-shadow: var(--shadow-sm);
}

/* ─── Dock — the fixed, vertically centred stack ──────────────────────────── */
/* The wrapper that used to be the shelf itself: stacks islands wearing one
   glass recipe (.desktop-menu). Today it holds #main-menu alone; its height
   is what shell.js's fit() measures against the brand and the window bottom. */

.desktop-dock {
    position: fixed;
    top: 50%;
    left: 24px;
    transform: translateY(-50%);
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: .6rem;
    align-items: stretch;
}

/* ─── Menu shelf — frosted glass ──────────────────────────────────────────── */
/* The shelf carries the frosted treatment; items are transparent boxes on it. */

.desktop-menu {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    padding: .85rem .6rem;
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.35);
    border-radius: 20px;
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: var(--shadow-md);
}

.desktop-menu-item {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 48px;
    height: 48px;
    border-radius: 13px;
    color: rgba(255, 255, 255, 0.9);
    font-size: 1.2rem;
    text-decoration: none;
    transition: background 150ms ease, color 150ms ease;
}

.desktop-menu-item:hover {
    background: rgba(255, 255, 255, 0.22);
    color: #fff;
    text-decoration: none;
}

.desktop-menu-item:focus-visible {
    outline: 2px solid rgba(255, 255, 255, 0.7);
    outline-offset: 2px;
}

.desktop-menu-item.active {
    background: rgba(255, 255, 255, 0.3);
    color: #fff;
}

/* Pre-territory (territory picker): the menu's destinations need a territory,
   so the rail keeps the shell silhouette with the menu dimmed and inert. */
.desktop-menu.locked .desktop-menu-item {
    opacity: 0.35;
    pointer-events: none;
}

/* Shelf divider: a hairline between item groups (none rendered today) */
.desktop-menu-rule {
    display: block;
    flex: none;
    height: 1px;
    margin: .2rem .5rem;
    background: rgba(255, 255, 255, 0.35);
}

/* ─── Compaction — the shelf shrinks to dots on short windows ─────────────── */
/* shell.js fit() adds .is-compact when the full-height dock would collide
   with the brand squircle or the window bottom (re-run on resize), and marks
   the items that may shrink .is-dot. A dot item is a short row showing its
   .mi-dot; when .active it grows back to the full 48px row and shows its own
   icon. .is-measuring is on while fit() measures the shelf at full height:
   no transitions, so the measurement is the resting size and the decision
   never plays as an animation. The icon is any direct child that is not the
   dot (main-menu.php renders a bare <i>; a .mi-icon wrapper works too). */

.desktop-menu-item .mi-dot {
    display: none;
}

.desktop-menu.is-measuring,
.desktop-menu.is-measuring * {
    transition: none !important;
}

.desktop-menu.is-compact {
    gap: .15rem;
}

.desktop-menu.is-compact .desktop-menu-item.is-dot {
    height: 16px;
    border-radius: 8px;
    font-size: .5rem;
    color: rgba(255, 255, 255, 0.7);
    transition: height 180ms ease, font-size 180ms ease, background 150ms ease, color 150ms ease;
}

.desktop-menu.is-compact .desktop-menu-item.is-dot:hover {
    color: #fff;
    background: rgba(255, 255, 255, 0.18);
}

.desktop-menu.is-compact .desktop-menu-item.is-dot:not(.active) > :not(.mi-dot) {
    display: none;
}

.desktop-menu.is-compact .desktop-menu-item.is-dot:not(.active) .mi-dot {
    display: inline-block;
}

.desktop-menu.is-compact .desktop-menu-item.is-dot.active {
    height: 48px;
    border-radius: 13px;
    font-size: 1.2rem;
    color: #fff;
}

/* ─── Menu label — one glass pill, moved to the hovered or focused icon ────── */
/* A fixed SIBLING of the dock, placed from the icon's screen rect (--x = the
   shelf's right edge, --y = the icon's centre, written by shell.js): an
   element with backdrop-filter is the backdrop root for its descendants, so
   a pill inside the shelf could only blur the shelf's own contents and
   rendered flat against the canvas edge (Joel 2026-09-02). Decorative: the
   icons keep their aria-labels. z 6 so it labels over the open panel (5). */

.desktop-menu-label {
    position: fixed;
    z-index: 6;
    left: calc(var(--x, 0px) + .55rem);
    top: var(--y, 0px);
    transform: translate(-6px, -50%);
    white-space: nowrap;
    pointer-events: none;
    padding: .85em 2.6em;
    border-radius: 13px;
    background: rgba(0, 0, 0, 0.28);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: 0 8px 32px rgba(21, 56, 105, 0.14);
    color: #fff;
    font-family: var(--font-head);
    font-size: var(--step--1);
    line-height: 1;
    letter-spacing: .08em;
    text-transform: uppercase;
    opacity: 0;
    visibility: hidden;
    transition: opacity 150ms ease, transform 150ms ease, visibility 0s linear 150ms;
}

.desktop-menu-label.is-on {
    opacity: 1;
    visibility: visible;
    transform: translate(0, -50%);
    transition-delay: 0s;
}

/* ─── Side panel slot ─────────────────────────────────────────────────────── */
/* The data room's Hoku panel geometry, empty inside: full height, fixed
   width, frosted, sliding in from the left edge. Closed, it is parked fully
   off-screen whatever the gap (--x = the shelf's right edge if shell.js
   writes it, 88px otherwise). The open move is long and eases hard into
   rest; close is quicker. visibility flips after the close slide so the
   panel is never focusable off-screen. What goes inside is a later plan. */

.desktop-panel {
    position: fixed;
    container-type: inline-size;   /* content inside may size itself from the panel's width (cqi), not the viewport */
    top: 1rem;
    bottom: 1rem;
    left: 0;
    width: clamp(420px, 35vw, 600px);
    z-index: 5;
    display: flex;
    flex-direction: column;
    background: rgba(255, 255, 255, 0.58);
    outline: 2px solid rgba(255, 255, 255, 0.6);
    -webkit-backdrop-filter: blur(24px) saturate(1.4);
    backdrop-filter: blur(24px) saturate(1.4);
    box-shadow: var(--shadow-lg);
    border-radius: 0 2rem 2rem 0;
    transform: translateX(calc(-100% - var(--x, 88px) - 2rem));
    visibility: hidden;
    transition: transform var(--panel-slide-close), visibility 0s linear 600ms;
}

.desktop-panel.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--panel-slide-open), visibility 0s;
}

.desktop-panel-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: 1.4rem;
}

/* Close: a dark circle overlapping the panel's top-right corner, a child of
   the panel so it can sit outside the box. */
.desktop-panel-close {
    position: absolute;
    top: -14px;
    right: -14px;
    z-index: 1;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    background: var(--rt-night);
    color: #fff;
    border: 2px solid rgba(255, 255, 255, 0.75);
    box-shadow: 0 6px 20px rgba(21, 56, 105, 0.28);
    cursor: pointer;
    transition: background 150ms ease, transform 150ms ease;
}

.desktop-panel-close:hover {
    background: var(--rt-ink);
    transform: scale(1.06);
}

.desktop-panel-close:active {
    transform: scale(.96);
}

/* ─── Content slot — the page, right of the rail ──────────────────────────── */
/* #content contributes no box of its own so a full-bleed page's root is a
   direct child of body for sizing. .desktop-main is the mobile shell's
   .page.current: margin-left is THE gutter, z 3 sits it over the aurora's
   fade. body.full-bleed releases the gutter and padding so the page owns the
   viewport (the picker's globe, a map page) and offsets its own stage by
   --shell-gutter. */

#content {
    display: contents;
}

.desktop-main {
    margin-left: var(--shell-gutter);
    min-height: 100vh;
    padding: 2.5rem 3rem;
    position: relative;
    z-index: 3;
}

body.full-bleed .desktop-main {
    margin: 0;
    padding: 0;
    position: static;
    z-index: auto;
}

/* ─── Loading screen — v1's orbital spinner ───────────────────────────────── */
/* Copied from the data room shell (which copied assets/css/style.css), the
   way the rest of this shell was copied: duplicated, never shared. Colours
   are v1's literal brand / success / primary values. Timing is v1's: 2.5s
   delay on the way IN (a fast navigation never shows it), none on the way
   OUT. Rendered WITHOUT .inactive in layout.php so a full load paints it
   first; shell.js adds .inactive once the page's init() has settled and
   .small for later navigations. */

:root {
    --pl-color1: #eb5d8f;
    --pl-color2: #5eca44;
    --pl-color3: #2c75de;
    --pl-size: 10.5em;
    --loading-overlay-bg: rgba(0, 0, 10, 0.65);
    --loading-card-bg: rgb(0, 0, 30);
    --loading-text-color: #fff;
    --loading-outline: rgba(0, 0, 0, 0.2);
    --loading-shadow-1: 0 50px 100px -20px rgba(129, 129, 240, 0.5);
    --loading-shadow-2: 0 30px 60px -30px rgba(255, 255, 255, 0.6);
}

/* font-size pinned (Joel 2026-09-02): the spinner is 10.5em of what
   #loading-screen inherits, and the app's body is .8rem where this shell's
   is --step-0, so the dots came out a third larger. .8rem locks it to the
   app's 134px whatever the desktop type scale does. */
#loading-screen {
    display: flex;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    font-size: .8rem;
    background-color: var(--loading-overlay-bg);
    transition: all 600ms cubic-bezier(.77, 0, 0, 1);
    transition-delay: 250ms;
    z-index: 9999;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}

#loading-screen.light {
    --loading-overlay-bg: rgba(255, 255, 255, 0.55);
    --loading-card-bg: rgba(255, 255, 255, 0.65);
    --loading-text-color: lightgrey;
    color: lightgrey !important;
}

#loading-screen.inactive {
    transition-delay: 0ms;
    transform: scale(.8);
    opacity: 0;
    pointer-events: none;
    border-radius: 3rem;
}

#loading-screen.now {
    transition-delay: 0ms !important;
}

#loading-screen #loading-bg {
    position: fixed;
    top: 52%;
    left: 50%;
    width: 12rem;
    height: 14rem;
    transform: translate(-50%, -50%);
    background-color: var(--loading-card-bg);
    border-radius: 3rem;
    z-index: -1;
    display: flex;
    align-items: end;
    justify-content: center;
    outline: 8px solid var(--loading-outline);
    box-shadow: var(--loading-shadow-1), var(--loading-shadow-2);
}

#loading-screen.small {
    color: lightgrey;
}

#loading-screen.small #loading-bg {
    width: 5rem;
    height: 5rem;
    top: 50%;
    border-radius: 1rem;
    outline: 4px solid var(--loading-outline);
}

#loading-screen.small #loading-bg #loading-text {
    display: none;
}

#loading-screen #loading-text {
    font-size: .75rem;
    text-transform: uppercase;
    margin-bottom: 1.5rem;
    color: var(--loading-text-color);
    font-family: var(--font-head);
}

.pl {
    --dur: 2s;
    animation-name: spin;
    animation-timing-function: cubic-bezier(.65, 0, .35, 1);
    margin: auto;
    position: relative;
    width: var(--pl-size);
    height: var(--pl-size);
    font-size: calc(var(--pl-size) / 10.5);
    color: inherit;
}

.pl,
.pl__dot-layer {
    animation-duration: var(--dur);
    animation-iteration-count: infinite;
}

.pl__dot {
    top: calc(50% - .875em);
    left: calc(50% - .875em);
    width: 1.75em;
    height: 1.75em;
}

.pl__dot,
.pl__dot-layer {
    position: absolute;
}

.pl__dot-layer {
    animation-name: scale-down-1;
    animation-timing-function: cubic-bezier(.85, 0, .15, 1);
    background-color: var(--pl-color1);
    border-radius: 50%;
    mix-blend-mode: var(--pl-blend, screen);
    width: 100%;
    height: 100%;
    transform-origin: 50% 100%;
}

.pl__dot-layer:nth-child(2) {
    animation-name: scale-down-2;
    background-color: var(--pl-color2);
    transform: translate(0, 20%) scale(.85);
}

.pl__dot-layer:nth-child(3) {
    animation-name: scale-down-3;
    background-color: var(--pl-color3);
    transform: translate(0, 40%) scale(.7);
}

.pl__dot:nth-child(even) {
    top: calc(50% - 1.125em);
    left: calc(50% - 1.125em);
    width: 2.25em;
    height: 2.25em;
}

.pl__dot:nth-child(even) .pl__dot-layer {
    animation-name: scale-up-1;
    transform: translate(0, 0) scale(.33);
}

.pl__dot:nth-child(even) .pl__dot-layer:nth-child(2) {
    animation-name: scale-up-2;
    transform: translate(0, 15%) scale(.3);
}

.pl__dot:nth-child(even) .pl__dot-layer:nth-child(3) {
    animation-name: scale-up-3;
    transform: translate(0, 30%) scale(.27);
}

.pl__dot:nth-child(1) { transform: rotate(0deg) translate(0, -4em); }
.pl__dot:nth-child(2) { transform: rotate(-60deg) translate(0, -4em); }
.pl__dot:nth-child(2) .pl__dot-layer { animation-delay: calc(var(--dur) * .02); }
.pl__dot:nth-child(3) { transform: rotate(-120deg) translate(0, -4em); }
.pl__dot:nth-child(3) .pl__dot-layer { animation-delay: calc(var(--dur) * .04); }
.pl__dot:nth-child(4) { transform: rotate(-180deg) translate(0, -4em); }
.pl__dot:nth-child(4) .pl__dot-layer { animation-delay: calc(var(--dur) * .06); }
.pl__dot:nth-child(5) { transform: rotate(-240deg) translate(0, -4em); }
.pl__dot:nth-child(5) .pl__dot-layer { animation-delay: calc(var(--dur) * .08); }
.pl__dot:nth-child(6) { transform: rotate(-300deg) translate(0, -4em); }
.pl__dot:nth-child(6) .pl__dot-layer { animation-delay: calc(var(--dur) * .1); }

/* Modifiers: an inline medium spinner, and a single-colour one that takes
   the text colour (or --pl-mono). */
.pl--md {
    --pl-size: 2.5rem;
}

.pl--mono {
    --pl-color1: var(--pl-mono, currentColor);
    --pl-color2: var(--pl-mono, currentColor);
    --pl-color3: var(--pl-mono, currentColor);
}

@keyframes scale-down-1 {
    from, 90%, to { transform: translate(0, 0) scale(1); }
    30% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(0, -45%) scale(.57); }
    40% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(10%, -45%) scale(.57); }
    50% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(-10%, -45%) scale(.57); }
    60% { animation-timing-function: cubic-bezier(.85, 0, .15, 1); transform: translate(0, -45%) scale(.57); }
}

@keyframes scale-down-2 {
    from, 90%, to { transform: translate(0, 20%) scale(.85); }
    30%, 60% { transform: translate(0, -45%) scale(.57); }
}

@keyframes scale-down-3 {
    from, 90%, to { transform: translate(0, 40%) scale(.7); }
    30% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(0, -45%) scale(.57); }
    40% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(-10%, -45%) scale(.57); }
    50% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(10%, -45%) scale(.57); }
    60% { animation-timing-function: cubic-bezier(.85, 0, .15, 1); transform: translate(0, -45%) scale(.57); }
}

@keyframes scale-up-1 {
    from, 90%, to { transform: translate(0, 0) scale(.33); }
    30% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(0, 0) scale(1); }
    40% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(10%, 0) scale(1); }
    50% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(-10%, 0) scale(1); }
    60% { animation-timing-function: cubic-bezier(.85, 0, .15, 1); transform: translate(0, 0) scale(1); }
}

@keyframes scale-up-2 {
    from, 90%, to { transform: translate(0, 15%) scale(.3); }
    30%, 60% { transform: translate(0, 0) scale(1); }
}

@keyframes scale-up-3 {
    from, 90%, to { transform: translate(0, 30%) scale(.27); }
    30% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(0, 0) scale(1); }
    40% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(-10%, 0) scale(1); }
    50% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: translate(10%, 0) scale(1); }
    60% { animation-timing-function: cubic-bezier(.85, 0, .15, 1); transform: translate(0, 0) scale(1); }
}

@keyframes spin {
    from { animation-timing-function: cubic-bezier(.45, 0, .55, 1); transform: rotate(120deg); }
    33.33% { animation-timing-function: cubic-bezier(.65, 0, .35, 1); transform: rotate(130deg); }
    66.67% { animation-timing-function: cubic-bezier(.45, 0, .55, 1); transform: rotate(255deg); }
    to { transform: rotate(240deg); }
}

/* ─── Narrow windows (≤ 900px) — the same shell, re-laid ──────────────────── */
/* Narrow desktop windows and small tablets ONLY (iPad Safari sends a Mac UA
   by default); phones never reach this surface (Surface::resolve). The rail,
   the aurora and the hover labels go: shell.js skips the gradient boot and
   the sampler under RT.shell.narrow. The dock becomes a bar across the top
   (brand squircle left, shelf right-aligned) in night glass: the white-on-
   rail glass would be white icons on a white page once the rail is gone.
   --shell-top is the bar's height; pages pad their top with it, so no page
   carries the number. The push is off (nothing to reveal); the panel is a
   full-width sheet from the right. No keyboard logic, no item trimming. */

@media (max-width: 900px) {
    html {
        --shell-top: calc(44px + 1.2rem);
    }

    html.panel-open {
        --panel-push: 0px;
    }

    .desktop-aurora,
    #gradient-canvas,
    .desktop-menu-label {
        display: none;
    }

    .desktop-brand {
        top: .6rem;
        left: .75rem;
    }

    .desktop-brand img {
        height: 44px;
        border-radius: 12px;
        box-shadow: 0 4px 14px rgba(21, 56, 105, 0.22);
    }

    .desktop-dock {
        top: .6rem;
        left: auto;
        right: .75rem;
        transform: none;
        z-index: 4;   /* above the page (3), under the panel (5) */
        flex-direction: row-reverse;
        align-items: center;
        gap: .4rem;
        max-width: calc(100% - 44px - 2rem);
    }

    .desktop-menu {
        flex-direction: row;
        gap: .15rem;
        padding: .25rem;
        border-radius: 16px;
        background: rgba(22, 24, 29, 0.55);
        border-color: rgba(255, 255, 255, 0.22);
        -webkit-backdrop-filter: blur(12px);
        backdrop-filter: blur(12px);
    }

    .desktop-menu-item {
        width: 40px;
        height: 40px;
        border-radius: 12px;
    }

    .desktop-menu-rule {
        width: 1px;
        height: auto;
        align-self: stretch;
        margin: .35rem .15rem;
    }

    /* Compaction is a tall-shelf concern; a row never compacts */
    .desktop-menu.is-compact .desktop-menu-item.is-dot {
        height: 40px;
        border-radius: 12px;
        font-size: 1.2rem;
    }

    .desktop-menu.is-compact .desktop-menu-item.is-dot:not(.active) > :not(.mi-dot) {
        display: revert;
    }

    .desktop-menu.is-compact .desktop-menu-item.is-dot:not(.active) .mi-dot {
        display: none;
    }

    .desktop-panel {
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        width: auto;
        border-radius: 0;
        outline: 0;
        box-shadow: none;
        background: rgba(255, 255, 255, 0.62);
        -webkit-backdrop-filter: blur(7px) saturate(1.4);
        backdrop-filter: blur(7px) saturate(1.4);
        transform: translateX(100%);
        transition: transform var(--panel-slide-close), visibility 0s linear 600ms;
    }

    .desktop-panel.is-open {
        transform: none;
        transition: transform var(--panel-slide-open), visibility 0s;
    }

    .desktop-panel-close {
        top: .75rem;
        right: .75rem;
    }

    .desktop-main {
        margin-left: 0;
        padding-top: calc(var(--shell-top) + 1rem);
    }
}

/* ─── Print — chrome hidden, the page alone ───────────────────────────────── */

@media print {
    .desktop-aurora,
    #gradient-canvas,
    .desktop-brand,
    .desktop-dock,
    .desktop-menu-label,
    .desktop-panel,
    #loading-screen {
        display: none;
    }

    .desktop-main {
        margin: 0;
    }
}
