/* ==================================================================
   Base — the chassis, the sky, and the shared chrome type treatment.
   ================================================================== */

* { box-sizing: border-box; }

html { height: 100%; }

body {
    margin: 0;
    min-height: 100%;
    /* Three offset nebulae over the chassis tone, same construction as
       the visualiser's body. They are what stops a near-black page from
       reading as flat grey once a monitor is not in a dark room. */
    background:
        radial-gradient(ellipse 92% 72% at 50% 36%, rgba(28,42,70,.40), transparent 62%),
        radial-gradient(ellipse 72% 56% at 84% 94%, rgba(17,29,52,.32), transparent 70%),
        radial-gradient(ellipse 62% 52% at 10% 6%,  rgba(23,34,60,.26), transparent 68%),
        var(--cs-chassis);
    background-attachment: fixed;
    color: var(--cs-text);
    font-family: var(--cs-font-ui);
    /* Cards are dragged, not selected. Anything genuinely readable opts
       back in below rather than every drag smearing a text selection
       across the board. */
    user-select: none;
}

/* The star field renders here, beneath the whole interface.
   width/height are explicit and not left to `inset: 0`: a <canvas> is a
   replaced element, so an over-constrained box falls back to its
   intrinsic size (300x150, or whatever the bitmap was last set to)
   rather than stretching — and the script sizes the bitmap from the
   layout box, which makes that a feedback loop that never converges. */
#sky {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    display: block;
    pointer-events: none;
}

/* A vignette seats the field in the chassis so stars do not run off a
   hard edge at the window bounds. */
#sky-vignette {
    position: fixed;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: radial-gradient(ellipse 78% 66% at 50% 46%,
                transparent 42%, rgba(5,6,7,.55) 100%);
}

#app {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--cs-4);
    padding: var(--cs-6) var(--cs-4) var(--cs-12);
    min-height: 100vh;
}

/* ---------------- chrome type ----------------
   The word plates in the brand kit are bitmaps of specific words
   ("ANALYSIS", "APPEARANCE"), so headings that this app needs and the
   kit does not ship are set in live type with the same metal clipped
   into the glyphs. Same ramp, same seating shadow. */
.cs-chrome-type {
    background: var(--cs-chrome);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* A drop-shadow on the element would fall behind the transparent
       fill; the shadow has to be part of the filter chain instead. */
    filter: drop-shadow(0 1px 0 rgba(0,0,0,.85))
            drop-shadow(0 0 14px rgba(221,246,255,.14));
    font-weight: 800;
    letter-spacing: .14em;
    text-transform: uppercase;
}

/* The way back to the studio. Every page under this folder is reached
   from freebounce.studio, so every page carries one — which is why it
   lives here rather than with the board it used to sit above. */
.cs-site-link {
    color: var(--cs-text-muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .12em;
    text-decoration: none;
    text-transform: uppercase;
    transition: color var(--cs-fast), text-shadow var(--cs-fast);
}
.cs-site-link:hover,
.cs-site-link:focus-visible {
    color: var(--cs-cosmic);
    outline: none;
    text-shadow: 0 0 12px rgba(142,220,255,.38);
}

.cs-label {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--cs-text-secondary);
    line-height: 1.2;
}

.cs-value {
    font-family: var(--cs-font-mono);
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--cs-text);
}

.cs-divider {
    height: 1px;
    background: var(--cs-chrome-shadow);
    flex: none;
}

/* Scrollbars in the same metal as everything else. */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--cs-dark-chrome) transparent;
}
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: var(--cs-dark-chrome);
    border-radius: 4px;
}
*::-webkit-scrollbar-thumb:hover { background: var(--cs-titanium); }

/* Only ever seen when the modules did not run. The delay is what keeps
   it from flashing during a normal start-up. */
#boot-warning {
    max-width: 620px;
    padding: var(--cs-3) var(--cs-4);
    border-radius: var(--cs-radius-md);
    background: rgba(226,125,134,.07);
    border: 1px solid rgba(226,125,134,.42);
    color: var(--cs-text-secondary);
    font-size: 12px;
    line-height: 1.6;
    text-align: center;
    opacity: 0;
    animation: boot-warn 240ms 500ms ease-out forwards;
    user-select: text;
}
#boot-warning strong { display: block; color: #f4c3c7; margin-bottom: 2px; }
#boot-warning code {
    font-family: var(--cs-font-mono);
    font-size: 11px;
    padding: 1px 5px;
    border-radius: var(--cs-radius-xs);
    background: rgba(0,0,0,.5);
    color: var(--cs-lunar);
}
@keyframes boot-warn { to { opacity: 1; } }

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* No blanket motion kill here any more.

   It used to read  * { transition: none; animation: none }  under
   prefers-reduced-motion, which is a bigger hammer than it looks: it
   reached the arrival fade as well, so a page crossed into would snap
   its content on instead of bringing it up, and the crossing lost the
   one gesture that makes it read as one movement rather than two
   pages swapping. The site now animates the same way for everyone. */
