@font-face {
    font-family: "BigBlueTerm";
    /* woff2 first (990 KB vs 2225 KB - same 10,810 glyphs, verified lossless);
       the ttf stays as the fallback for anything that cannot take woff2. The
       browser downloads only the first format it supports, so nobody pays for
       both. font-display: swap means text is readable before either lands. */
    src: url("/static/fonts/BigBlueTerm437NerdFontMono-Regular.woff2") format("woff2"),
         url("/static/fonts/BigBlueTerm437NerdFontMono-Regular.ttf") format("truetype");
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

:root {
    /* THE NECRONOMICON'S PALETTE, 2026-08-29.
       michaelslop.org wore the classic Win98 teal-and-silver while the rest of
       the mesh -- the book, the panel, the terminal, the wallpaper, the art --
       moved to the book's greens. One identity everywhere was the ask, so the
       public face speaks it too.
       These are panefx flames.rs's own values (C_WARM/C_HOT/C_COOL/C_DIM), the
       same four the book documents, so nothing here is a fresh invention that
       can drift from the rest. */
    --void:        #05070a;
    --crypt:       #0a0e14;
    --crypt-hi:    #111823;
    --stone:       #1a2430;
    --stone-hi:    #26333f;
    --bone:        #d8d4c4;
    --bone-dim:    #8b8778;
    --ash:         #5c6470;
    --spectral:    #62e670;
    --spectral-hi: #c8ffd0;
    --spectral-lo: #288c36;
    --corpse:      #4fb8d6;
    --gold:        #d4a843;
    --viscera:     #b8453a;

    /* The old names, REMAPPED rather than deleted: fifteen templates reference
       --btn-face and --navy, and renaming them all to chase a palette is how
       one page ends up unstyled with nobody noticing. */
    --desktop: var(--void);
    --btn-face: var(--crypt-hi);
    --navy: var(--stone);
    --fg: var(--bone);
    --muted: var(--bone-dim);
    --accent: var(--spectral);
    --pixel: "BigBlueTerm", "Pixelated MS Sans Serif", "Lucida Console", "Cascadia Mono", Consolas, monospace;
    /* the terminal mono carries more weight than the thin sans ever did, so
       it is the UI font too - everything on the site speaks it now */
    --ui: "BigBlueTerm", "Pixelated MS Sans Serif", "Lucida Console", "Cascadia Mono", Consolas, monospace;
    --taskbar-h: 36px;
}

* { box-sizing: border-box; }

/* The pixel font is wide and the content is full of URLs, paths and
   `%TEMP%`-style tokens. Without this they push straight through the window
   chrome instead of wrapping inside it. */
body { overflow-wrap: break-word; }

html, body { height: 100%; }

body {
    margin: 0;
    background: var(--desktop);
    background-image:
        radial-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px),
        radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
    background-position: 0 0, 8px 8px;
    background-size: 16px 16px;
    color: var(--fg);
    font-family: var(--ui);
    font-size: 16px;
    line-height: 1.5;
    -webkit-font-smoothing: none;
}

a { color: var(--fg); text-decoration: underline; }
a:hover { color: var(--navy); }

.desktop {
    min-height: 100vh;
    padding: 1.5rem 1rem calc(1.5rem + var(--taskbar-h));
    display: flex;
    justify-content: center;
    align-items: flex-start;
}

.site-window { width: 100%; max-width: 1000px; margin: 0 auto; }

/* ---- fonts ----
   98.css puts Pixelated MS Sans Serif at its native 11px on .window and all
   form chrome; override its font on everything so the whole site is the
   terminal mono. Sizes stay as 98.css set them - only the face changes. */
body, button, input, select, textarea, label, legend,
.window, .title-bar-text, .status-bar-field {
    font-family: var(--ui);
}
.window-body { font-size: 16px; }
.window-body table { font-size: 14px; }

/* Flex items default to min-width:auto, which lets a long title shove the
   window controls off the right edge instead of ellipsising. */
.title-bar-text { min-width: 0; flex: 1 1 auto; text-align: left; }
.title-bar-text a { color: var(--spectral-hi); text-decoration: none; }
.title-bar-text a:hover { text-decoration: underline; }

.window-body { padding: 1rem; }
.window-body > :first-child { margin-top: 0; }
.window-body > :last-child { margin-bottom: 0; }

/* ---- status bar ---- */
.status-bar { margin-top: 0; }
.status-bar .status-bar-field { padding: 3px 8px; }

/* ---- content layout ---- */
.content { padding: 1.25rem; }
.content.wide { max-width: none; }

h1 { font-size: 32px; margin: 0.25rem 0 0.5rem; }
h2 { font-size: 24px; }
h3 { font-size: 20px; }
h1, h2, h3, h4 { font-weight: normal; letter-spacing: 0.5px; }

.subtitle { color: var(--muted); margin-top: -0.25rem; }
.meta { color: var(--muted); font-size: 14px; }
.empty { color: var(--muted); font-style: italic; }
.hidden { display: none !important; }

.section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin: 1.5rem 0 0.75rem;
}
.section-head h2 { margin: 0; }
.section-head a { font-size: 14px; }

/* ---- cards as windows ---- */
.card { margin-top: 1rem; }
.card .title-bar-text a { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card .window-body { padding: 0.75rem 1rem; }
.card .window-body p { margin: 0.35rem 0 0; }
.card .window-body .meta { margin-bottom: 0.25rem; }

.grid {
    display: grid;
    /* min() so the track can drop below 340px on a narrow phone rather than
       forcing the whole grid wider than the window. */
    grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
    gap: 1rem;
}
.grid .card { margin-top: 0; min-width: 0; }
.grid .card .window-body { padding: 0.75rem 1rem; }

/* ---- prose ---- */
.post-header { margin-bottom: 1rem; }
.post-header h1 { margin: 0.25rem 0 0.5rem; }
.backlink { font-size: 14px; }

.prose p { margin: 0.75rem 0; }
.prose ul, .prose ol { margin: 0.75rem 0; padding-left: 2rem; }
.prose code {
    font-family: var(--pixel);
    background: var(--bone);
    border: 1px solid var(--bone-dim);
    padding: 0 0.3em;
    font-size: 0.9em;
    overflow-wrap: anywhere;
}
.prose pre {
    background: var(--bone);
    border: 2px solid var(--bone-dim);
    border-top-color: var(--spectral-hi);
    border-left-color: var(--spectral-hi);
    box-shadow: inset 1px 1px 0 var(--crypt);
    padding: 1rem;
    overflow-x: auto;
    font-family: var(--pixel);
    font-size: 15px;
}
.prose pre code { background: none; border: none; padding: 0; font-size: inherit; }
.prose h1, .prose h2, .prose h3 { margin-top: 1.25em; }
.prose blockquote {
    margin: 1rem 0;
    padding: 0.5rem 1rem;
    border-left: 3px solid var(--navy);
    color: var(--muted);
}
/* 98.css sets `white-space: nowrap` on every table, so a prose table sizes to
   its longest cell and overflows the window. Let cells wrap, and use `anywhere`
   so an unbroken URL doesn't set a min-content width of its own. */
.prose table { border-collapse: collapse; width: 100%; margin: 0.75rem 0; white-space: normal; }
.prose th, .prose td { border: 1px solid var(--bone-dim); padding: 0.35rem 0.7rem; text-align: left; overflow-wrap: anywhere; }
.prose th { background: var(--bone); }
.prose hr { border: none; border-top: 1px solid var(--bone-dim); }

/* Wrapping handles almost everything; this is the last resort for a table that
   still can't fit (many columns on a phone) — scroll it instead of bleeding
   out of the window. Applied by app.js. */
.table-wrap { overflow-x: auto; margin: 0.75rem 0; }
.table-wrap table { margin: 0; }
.prose a { color: var(--navy); }

.copy-btn {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    min-width: 0;
    min-height: 0;
    padding: 0.15rem 0.6rem;
    font-size: 14px;
}
.copy-btn:hover { background: var(--spectral-hi); }

.tags { display: flex; gap: 0.75rem; margin-top: 0.75rem; }
.tags a { font-size: 14px; }

/* ---- auth area ---- */
/* Present for screen readers and search engines, invisible on screen. Used
   where the 98 chrome already shows the text (a window title bar is not a
   heading, so the page still needs a real h1). */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.error-body { display: flex; gap: 0.9rem; align-items: flex-start; margin-bottom: 1rem; }
.error-body p { margin: 0 0 0.35rem; }
.error-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.error-actions a { text-decoration: none; }

.connect-result {
    margin: 0 0 0.75rem;
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--bone-dim);
    border-top-color: var(--spectral-hi); border-left-color: var(--spectral-hi);
    font-weight: bold;
}
.connect-result.ok  { background: #dff0d8; }
.connect-result.bad { background: #ffd6d6; }

.auth-area { display: flex; align-items: center; gap: 1rem; margin: 1rem 0; }

/* ---- streaming ---- */
/* The shared 1000px site window left the canvas ~612px wide for a 1920px
   stream. Drawing is the point of this page, so it gets a wider shell. */
.streaming-page .site-window { max-width: 1720px; }

.live-section {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 1rem;
    align-items: start;
}

/* "Bigger canvas": drop the chat column so the player takes the full width.
   Strokes are stored normalised (0-1) and .embed-wrap keeps 16/9, so the
   canvas stays aligned with the stream at any size. */
.live-section.wide-canvas { grid-template-columns: minmax(0, 1fr); }
.live-section.wide-canvas .live-chat { display: none; }

.live-player .window-body { padding: 0.75rem; }

.embed-wrap {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #000;
    border: 2px solid var(--bone-dim);
    border-top-color: var(--spectral-hi);
    border-left-color: var(--spectral-hi);
    box-shadow: inset 1px 1px 0 var(--crypt);
    overflow: hidden;
}

.live-player iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Height of Twitch's control bar. The input surface stops this far above the
   bottom so play/pause, volume and fullscreen stay reachable while drawing. */
.embed-wrap { --player-controls-h: 44px; }

/* The canvas only ever renders - it never takes input, so it can safely cover
   the whole frame including the control bar. */
.embed-wrap canvas#draw-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    touch-action: none;
}

/* Input layer. Separate from the canvas so the two can have different
   geometry: full-frame rendering, but input that stops above the controls. */
#draw-surface {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: var(--player-controls-h);
    pointer-events: none;
    touch-action: none;
    cursor: crosshair;
    z-index: 4;
}
#draw-surface.active { pointer-events: auto; }

.draw-status {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    padding: 0.2rem 0.7rem;
    font-size: 14px;
    background: var(--btn-face);
    border: 1px solid var(--bone-dim);
    border-top-color: var(--spectral-hi);
    border-left-color: var(--spectral-hi);
    color: var(--fg);
    z-index: 5;
    pointer-events: none;
}
.draw-status.on {
    background: var(--navy);
    color: var(--spectral-hi);
    border: 1px solid var(--bone-dim);
    border-bottom-color: var(--spectral-hi);
    border-right-color: var(--spectral-hi);
}

.draw-toolbar { display: flex; align-items: center; gap: 1rem; margin-top: 0.75rem; flex-wrap: wrap; }
.draw-toolbar.hidden { display: none; }
.draw-toolbar label { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 15px; }
.draw-toolbar input[type="color"] { width: 44px; height: 26px; padding: 1px; border: 2px solid var(--bone-dim); border-top-color: var(--spectral-hi); border-left-color: var(--spectral-hi); background: var(--btn-face); cursor: pointer; }
.draw-toolbar input[type="range"] { width: 120px; accent-color: var(--navy); }

.draw-history { display: inline-flex; gap: 0.35rem; }
/* Zoom is viewer-local, so it reads as a view control rather than a drawing
   one. The level is fixed-width and tabular so stepping 100 -> 150 -> 225%
   does not shuffle the buttons around under the cursor. */
.draw-zoom { display: inline-flex; align-items: center; gap: 0.35rem; }
.draw-zoom-level {
    min-width: 3.5em;
    text-align: center;
    font-size: 14px;
    font-variant-numeric: tabular-nums;
    color: var(--fg);
}

/* Viewer TTS composer. Sits under the draw toolbar and is hidden unless
   /api/auth/me reports can_tts, so it never appears for a signed-out viewer or
   while viewer TTS is switched off. */
/* Viewer TTS blender. Deliberately quieter than the composer above it: this
   is a toy, the composer is the thing people came for. Collapsed by default
   and it wraps to one control per line on a phone. */
.gate-note { margin: 0.5rem 0; font-style: italic; }
.gate-note.hidden { display: none; }
.blend-panel { margin-top: 0.6rem; border: 1px solid var(--bone-dim); padding: 0.4rem 0.6rem 0.6rem; }
.blend-panel.hidden { display: none; }
.blend-panel > summary { cursor: pointer; font-size: 15px; font-weight: bold; }
.blend-help { margin: 0.4rem 0; }
.blend-slots { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.blend-slots label { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 15px; }
.blend-slots select { min-width: 8.5rem; }
.blend-actions { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.5rem; flex-wrap: wrap; }
#blend-status { min-height: 1.2em; }
@media (max-width: 640px) {
    .blend-slots { flex-direction: column; align-items: stretch; }
    .blend-slots label { justify-content: space-between; }
}

.tts-panel { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.6rem; flex-wrap: wrap; }
.tts-panel.hidden { display: none; }
.tts-panel label { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 15px; }
#tts-text { flex: 1 1 260px; min-width: 0; font: inherit; font-size: 15px; padding: 0.25rem 0.4rem; }
.tts-tags { display: flex; flex-wrap: wrap; gap: 0.3rem; flex: 1 0 100%; }
.tts-tag { font-size: 12px; padding: 0.1rem 0.45rem; min-height: 0; }

.streamer-controls { margin-top: 1.25rem; }
.streamer-controls .window-body button { margin-right: 0.5rem; }
.streamer-controls .window-body .meta { margin-left: 0.5rem; }
.ctrl-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.6rem; }
.ctrl-row:last-child { margin-bottom: 0; }
.ctrl-mute { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 15px; }
.ctrl-mute input { width: 150px; }
/* Mods-only shortcut to /hub. Sits in the streamer-controls panel, which is
   already hidden unless the signed-in user can control. */
.ctrl-link {
    font-size: 15px;
    font-weight: bold;
    color: var(--navy);
    text-decoration: none;
    padding: 0.15rem 0.4rem;
    border: 1px solid transparent;
}
.ctrl-link:hover, .ctrl-link:focus { text-decoration: underline; border-color: var(--navy); }

.health { cursor: pointer; }
.health.bad { color: #a80000; font-weight: bold; }

.live-chat .window-body { padding: 0; }
.live-chat { min-height: 420px; }
.live-chat iframe {
    display: block;
    width: 100%;
    height: 420px;
    border: 0;
    background: var(--crypt);
}

.live-fallback { display: inline-block; margin-top: 0.75rem; font-size: 14px; }

@media (max-width: 900px) {
    .live-section { grid-template-columns: 1fr; }
    /* Viewport-relative so the chat doesn't eat a fixed 480px on short screens. */
    .live-chat { min-height: 0; }
    .live-chat iframe { height: clamp(320px, 55vh, 620px); }
}

/* ================== Win98 desktop shell ================== */

.title-bar { align-items: center; }
.title-bar-icon { margin: 0 4px 0 2px; flex: 0 0 auto; }

/* pixel art must scale blocky, never smoothed */
.title-bar-icon, .desktop-icon img, .start-menu img, .start-button img, .task-button img {
    image-rendering: pixelated;
}

/* window sits above the desktop icons */
.site-window { position: relative; z-index: 2; }

/* ---- desktop icons ---- */
.desktop-icons {
    position: fixed;
    top: 16px;
    left: 10px;
    display: flex;
    flex-direction: column;
    gap: 20px;
    /* Above .skull-hero (z 1), not tied with it. They used to share z-index 1
       and rely on DOM order alone, so any squeeze that moved the fixed skull
       under the icon strip let the ASCII art paint through the labels. */
    z-index: 2;
}
.desktop-icon {
    width: 78px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    text-decoration: none;
}
.desktop-icon span {
    font-size: 11px;
    color: var(--spectral-hi);
    text-align: center;
    text-shadow: 1px 1px 0 #000;
    padding: 1px 3px;
}
.desktop-icon:hover span, .desktop-icon:focus-visible span {
    background: var(--navy);
    outline: 1px dotted var(--gold);
}
/* desktop icons that are <button>s (the cassette) need the 98.css button
   chrome stripped, same as the recycle bin */
button.desktop-icon {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    cursor: pointer;
    min-width: 0;
    font-family: var(--ui);
}

/* Press feedback. This lived ONLY inside the phone media query, so a desktop
   click had no acknowledgement at all until the next page painted - on a slow
   route that reads as a missed click. :active rather than :hover because
   :hover sticks after a tap on touch (same reasoning as .start-menu below). */
.desktop-icon:active,
.recycle-bin:active {
    background: rgba(255, 255, 255, 0.25);
}

/* The bin asks the server who you are before it can know where to go, so it
   has a real waiting state. Dim + wait cursor only; no layout change, because
   the icon sits in a fixed corner and must not shift under the cursor. */
.recycle-bin.busy {
    opacity: 0.55;
    cursor: wait;
}

/* ---- recycle bin (mod entrance to /hub) ----
   Mirrors .desktop-icon so it reads as part of the desktop, but pinned to the
   opposite corner. It is a <button>, not an <a>, because where it goes depends
   on who is signed in - there is no single href that is correct. */
.recycle-bin {
    position: fixed;
    top: 16px;
    right: 10px;
    width: 78px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    z-index: 1;
    /* strip the 98.css button chrome - this is an icon, not a control */
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    cursor: pointer;
}
.recycle-bin img { image-rendering: pixelated; }
.recycle-bin span {
    font-size: 11px;
    color: var(--spectral-hi);
    text-align: center;
    text-shadow: 1px 1px 0 #000;
    padding: 1px 3px;
}
.recycle-bin:hover span, .recycle-bin:focus-visible span {
    background: var(--navy);
    outline: 1px dotted var(--gold);
}
.recycle-bin:active { background: none; box-shadow: none; }
/* Single click selects (this state), double click opens - the icon needs to
   look like it registered the first click, or the second one feels like a
   guess. Blue wash on the glyph is what 9x did to a selected icon. */
.recycle-bin.selected span {
    background: var(--navy);
    color: var(--spectral-hi);
    outline: 1px dotted var(--gold);
}
.recycle-bin.selected img {
    filter: sepia(1) saturate(3) hue-rotate(190deg) brightness(0.75);
}
/* A double click on a desktop icon should never leave the label highlighted
   as if it were text. */
.recycle-bin, .recycle-bin span { user-select: none; }

.bin-modal {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.25);
}
.bin-dialog { width: min(340px, 90vw); }
.bin-dialog .window-body { font-size: 14px; }
.bin-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1rem;
}
.bin-actions button { min-width: 75px; }

/* ---- taskbar ---- */
.taskbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    height: var(--taskbar-h);
    background: var(--btn-face);
    border-top: 1px solid var(--bone);
    box-shadow: 0 -1px 0 var(--spectral-hi);
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 3px 4px;
    z-index: 100;
    font-family: var(--ui);
    font-size: 11px;
}
.start-button {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-weight: bold;
    min-width: 0;
    height: 28px;
    padding: 0 8px;
}
.start-button.active {
    box-shadow: inset -1px -1px var(--spectral-hi), inset 1px 1px var(--crypt), inset -2px -2px var(--bone), inset 2px 2px grey;
}
.taskbar-divider {
    align-self: stretch;
    margin: 2px 2px;
    border-left: 1px solid var(--bone-dim);
    border-right: 1px solid var(--spectral-hi);
}
.task-button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 28px;
    padding: 0 8px;
    min-width: 0;
    max-width: 240px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
/* pressed task button: sunken border + the classic white dither */
.task-button.pressed {
    box-shadow: inset -1px -1px var(--spectral-hi), inset 1px 1px var(--crypt), inset -2px -2px var(--bone), inset 2px 2px grey;
    background-image: repeating-conic-gradient(var(--spectral-hi) 0% 25%, var(--btn-face) 0% 50%);
    background-size: 4px 4px;
    background-position: 1px 1px;
    font-weight: bold;
}
.taskbar-clock {
    margin-left: auto;
    height: 28px;
    display: inline-flex;
    align-items: center;
    padding: 0 10px;
    box-shadow: inset -1px -1px var(--spectral-hi), inset 1px 1px grey;
}

/* ---- start menu ---- */
.start-menu {
    position: fixed;
    left: 2px;
    bottom: calc(var(--taskbar-h) + 2px);
    z-index: 101;
    display: flex;
    width: 220px;
    background: var(--btn-face);
    box-shadow: inset -1px -1px var(--crypt), inset 1px 1px var(--bone), inset -2px -2px grey, inset 2px 2px var(--spectral-hi);
    padding: 3px;
    font-family: var(--ui);
}
.start-banner {
    width: 26px;
    background: linear-gradient(to top, var(--navy), #1084d0);
    color: var(--spectral-hi);
    font-weight: bold;
    font-size: 13px;
    letter-spacing: 1px;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    display: flex;
    align-items: center;
    padding: 8px 0;
}
.start-banner b { color: var(--spectral); }
.start-menu ul { list-style: none; margin: 0; padding: 0; flex: 1; }
.start-menu a {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    font-size: 12px;
    text-decoration: none;
    color: var(--fg);
}
.start-menu a:hover, .start-menu a:focus { background: var(--navy); color: var(--spectral-hi); outline: none; }

/* ---- window states (drag / maximize handled in app.js) ---- */
@media (min-width: 900px) {
    .site-window .title-bar { touch-action: none; }
}
.site-window.maximized {
    position: fixed;
    inset: 0 0 var(--taskbar-h) 0;
    max-width: none;
    margin: 0;
    transform: none !important;
    display: flex;
    flex-direction: column;
    z-index: 50;
}
.site-window.maximized .window-body { flex: 1 1 auto; overflow: auto; min-height: 0; }

/* ---- skeleton walker ---- */
#skeleton-walker {
    position: fixed;
    bottom: var(--taskbar-h);
    left: 0;
    width: 32px;
    height: 32px;
    background: var(--walk-sprite) 0 0 / 64px 32px no-repeat;
    image-rendering: pixelated;
    z-index: 99;
    pointer-events: none;
    display: none;
}
#skeleton-walker.on { display: block; }
#skeleton-walker.step { background-position: -32px 0; }

/* ---- boot screen ---- */
#boot-screen {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: #000;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: opacity 0.4s;
}
#boot-screen.fade { opacity: 0; pointer-events: none; }
#boot-rain { position: absolute; inset: 0; width: 100%; height: 100%; }
.boot-inner { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 20px; }
.boot-inner img { image-rendering: pixelated; animation: boot-bob 0.6s steps(1) infinite; }
@keyframes boot-bob { 50% { transform: translateY(-5px); } }
.boot-title { color: var(--spectral-hi); font-family: var(--ui); font-size: 28px; letter-spacing: 1px; }
.boot-title b { color: var(--spectral); }
.boot-bar {
    width: 220px;
    height: 16px;
    border: 1px solid var(--bone-dim);
    padding: 2px;
    overflow: hidden;
}
.boot-bar-blocks {
    height: 100%;
    width: 40px;
    background: repeating-linear-gradient(to right, #1084d0 0 6px, var(--navy) 6px 8px);
    animation: boot-scan 1.1s linear infinite;
}
@keyframes boot-scan { from { transform: translateX(-40px); } to { transform: translateX(220px); } }

/* ---- home hero: no window on the home page, just the desktop with the
   skull spinning ghostty-style in the middle. The <pre> is filled by
   initSkullSpin() with ASCII resampled live from the mascot sprite. ---- */

/* THE BASE RULE. Restored 2026-09-01 -- it was deleted by 6253148 ("the ether
   hero, the tome as favicon") while swapping #skull-spin for #ether-hero, and
   nothing replaced it.

   That single deletion caused every centering symptom at once: without
   display:flex the column stopped centring, so the hand went left; without
   text-align:center the words under it went left; and #ether-hero's negative
   margin below -- written for justify-content:flex-end -- started pulling the
   image up over the text instead.

   The three media queries at ~758, ~1076 and ~1141 all set justify-content on
   .skull-hero. For four days they were NO-OPS, because the element they
   modify was no longer a flex container.

   A media query that overrides a rule which does not exist fails silently.
   Nothing errors; the page simply renders wrong at every width. */
.skull-hero {
    position: fixed;
    inset: 0 0 var(--taskbar-h) 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    text-align: center;
    user-select: none;
    pointer-events: none;
}

.skull-hero #ether-hero {
    /* MEGA. The source is 256x256 and this blows it up to ~58vmin, so
       image-rendering:pixelated is load-bearing -- without it the browser
       smooths a pixel drawing into mush.

       The negative bottom margin pulls the wrist through the taskbar. The
       drawing carries empty pixels below the hand, and without this it floats
       a finger's width above the ground, which is the one thing the flex-end
       change above exists to prevent. */
    width: min(620px, 58vmin);
    height: auto;
    image-rendering: pixelated;
    margin: 0 0 calc(var(--taskbar-h) * -1 - 6px);
    /* Lifted off the flames rather than sitting flat on them. */
    filter: drop-shadow(0 0 26px rgba(98, 230, 112, .30));
}
/* Short viewports (landscape phones, squat windows): centered, the hero is
   taller than the screen and squats mid-page over whatever is open. Dock it
   to the top instead - the window may cover it, which is fine; it comes
   back when the window closes. */
@media (max-height: 560px) {
    .skull-hero { justify-content: flex-start; padding-top: 10px; }
}
/* THE NECRONOMICON'S COVER, on the public site.
   The book's title card is a centred stack over the flames: the mark, a
   wide-tracked title, a spaced sub in the accent. These are its own values --
   .22em tracking on the title, .4em on the sub, the spectral glow behind
   both -- so the site and the book read as one thing rather than two that
   happen to share a palette. */
.skull-wordmark {
    /* Pulled DOWN into the seam between the ether and the hand.
       Measured from the source rather than guessed: the 128x128 original
       carries green from y=10 to y=70 and grey from y=75 down, so the gap
       sits at ~56% of the art's height. This margin lands the text in it. */
    position: relative; z-index: 1; margin: 0 0 -1.2rem;
    color: var(--bone); font-family: var(--ui);
    font-size: clamp(1.5rem, 5vw, 2.6rem); font-weight: normal;
    letter-spacing: .22em; text-transform: uppercase;
    text-shadow: 0 0 30px rgba(98, 230, 112, .35);
}
.skull-wordmark b { color: var(--spectral); }
.skull-tagline {
    margin: .9rem 0 0; color: var(--spectral); font-size: .72rem;
    letter-spacing: .4em; text-transform: uppercase;
}

/* ---- window open / minimize / restore ---- */
@media (prefers-reduced-motion: no-preference) {
    .site-window { animation: win-open 0.14s steps(3); }
}
@keyframes win-open { from { transform: scale(0.94); } }
@keyframes win-minimize { to { transform: translate(-25vw, 55vh) scale(0.08); opacity: 0; } }
@keyframes win-restore { from { transform: translate(-25vw, 55vh) scale(0.08); opacity: 0; } }
.site-window.minimizing { animation: win-minimize 0.2s steps(5) forwards; }
.site-window.restoring { animation: win-restore 0.2s steps(5); }
.site-window.settled { animation: none; }

/* ---- desktop drag-select marquee ---- */
.marquee {
    position: fixed;
    z-index: 5;
    border: 1px dotted var(--spectral-hi);
    background: rgba(0, 0, 128, 0.18);
    pointer-events: none;
}
.desktop-icon.selected span, .recycle-bin.selected span { background: var(--navy); color: var(--spectral-hi); }

/* ---- desktop fluff: context menu, properties, shut down ---- */
.ctx-menu {
    position: fixed;
    z-index: 150;
    min-width: 165px;
    padding: 2px;
    background: var(--btn-face);
    box-shadow: inset -1px -1px var(--crypt), inset 1px 1px var(--bone), inset -2px -2px grey, inset 2px 2px var(--spectral-hi);
}
/* flatten 98.css's raised-button chrome into menu rows */
.ctx-menu button {
    display: block;
    width: 100%;
    min-width: 0;
    min-height: 0;
    padding: 5px 18px;
    text-align: left;
    background: none;
    border: none;
    box-shadow: none;
    font-family: var(--ui);
    font-size: 12px;
}
.ctx-menu button:hover, .ctx-menu button:focus { background: var(--navy); color: var(--spectral-hi); outline: none; }
.ctx-menu hr { margin: 3px 2px; border: none; border-top: 1px solid grey; border-bottom: 1px solid var(--spectral-hi); }

.start-sep { margin: 3px 4px; border-top: 1px solid grey; border-bottom: 1px solid var(--spectral-hi); }

.icons-shake .desktop-icon img { animation: icon-shake 0.4s; }
@keyframes icon-shake {
    25% { transform: translateX(-2px) rotate(-4deg); }
    75% { transform: translateX(2px) rotate(4deg); }
}

.props-head { display: flex; align-items: center; gap: 12px; margin-bottom: 0.75rem; }
.props-head img { image-rendering: pixelated; }
.props-tbl { margin: 0; overflow: hidden; }
.props-tbl dt { float: left; clear: left; width: 105px; color: var(--muted); }
.props-tbl dd { margin: 0 0 4px 113px; }

#shutdown-screen {
    position: fixed;
    inset: 0;
    z-index: 400;
    background: #000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    text-align: center;
    cursor: pointer;
}
#shutdown-screen p { margin: 0; color: #fba21b; font-family: var(--pixel); font-size: clamp(20px, 4vw, 38px); }
#shutdown-screen .shutdown-hint { font-size: 14px; color: #7a5410; }

/* ---- slop.fun game slots ---- */
.game-slot {
    min-height: 160px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: var(--bone-dim);
    background: var(--spectral-hi);
    /* the 98.css sunken-well look */
    box-shadow: inset -1px -1px var(--spectral-hi), inset 1px 1px grey, inset -2px -2px var(--bone), inset 2px 2px var(--crypt);
}
.game-slot .q { font-family: var(--pixel); font-size: 40px; }
.game-embed { position: relative; aspect-ratio: 4 / 3; }
.game-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* ---- photo album ----
   The filter output is 640px wide at most, so thumbnails and the enlarged
   view both upscale it; pixelated keeps the mush honest instead of blurry. */
.art-photo {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    /* hide until app.js swaps in the filtered version - nobody gets to see
       the clean original, that would break kayfabe */
    visibility: hidden;
}
.art-photo.developed { visibility: visible; }
.art-photo img { display: block; width: 100%; height: auto; image-rendering: pixelated; }
.art-dialog { width: min(700px, 94vw); }
#art-full { display: block; width: 100%; height: auto; image-rendering: pixelated; }

/* ---- (f)art ----
   Drawings, shown as-is. Same frame as the album, but visible immediately:
   there is no filter pass to wait on. */
.fart-piece {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
}
.fart-piece img { display: block; width: 100%; height: auto; image-rendering: pixelated; }
#fart-full { display: block; width: 100%; height: auto; image-rendering: pixelated; }

/* ---- KEIFERG33N: tray transport + media window ----
   The transport cluster docks left of the clock like a 2002 systray player.
   Green LED text on the active button is the only new color note - the
   cassette's green earns exactly one echo in the chrome. */
.tray-player { display: inline-flex; align-items: center; gap: 2px; margin-left: auto; }
.tray-player:not(.hidden) + .taskbar-clock { margin-left: 8px; }
.tray-player button {
    min-width: 30px;
    height: 26px;
    padding: 0 4px;
    font-family: var(--ui);
    font-size: 10px;
    line-height: 1;
}
.tray-player button.lit { color: #007830; text-shadow: 0 0 2px #7dffa8; }
.tray-player #keefer-vol { width: 72px; margin: 0 2px 0 6px; }
/* The tape plays as the wallpaper: video pinned behind all desktop
   furniture, then the teal + dot pattern laid back over it at ~55% so the
   desktop still reads as the desktop with the mix glowing through. Both
   layers ignore the pointer so wallpaper right-click/marquee still work. */
#keefer-bg {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
    pointer-events: none;
}
#keefer-tint {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-color: var(--desktop);
    background-image:
        radial-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px),
        radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
    background-position: 0 0, 8px 8px;
    background-size: 16px 16px;
    opacity: 0.55;
}
/* KG card: the audio-without-video placeholder, shown while the tape rolls
   with the video parked (hidden tab, locked phone). Black wallpaper with the
   monogram up-and-right of center, clear of the skull that owns the middle. */
#keefer-kg {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background: #000;
}
#keefer-kg span {
    position: absolute;
    left: 66%;
    top: 34%;
    transform: translate(-50%, -50%);
    font-family: var(--pixel);
    font-size: clamp(90px, 24vw, 260px);
    color: #00cc55;
    text-shadow: 0 0 18px rgba(0, 204, 85, 0.45), 3px 3px 0 #003814;
}
@media (max-width: 640px) {
    /* keep the taskbar breathable on phones: transport shrinks to the core,
       and phones have hardware volume anyway */
    .tray-player [data-k="rew"], .tray-player [data-k="ff"], .tray-player #keefer-vol { display: none; }
}

/* ---- live badge ----
   Fed by /api/live (OBS stream status). Empty until the first fetch answers,
   so pages never flash the wrong state. Red blinks like a camcorder REC
   light; grey idles with a slow breathe. */
.live-badge { display: inline-flex; align-items: center; gap: 5px; margin-left: 9px; vertical-align: baseline; }
.live-badge .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.live-badge.is-live { color: #e00000; }
.live-badge.is-off { color: var(--bone-dim); }
.skull-tagline .live-badge.is-off { color: #a8bcbc; }
/* on the navy title-bar gradient the darker shades disappear */
.title-bar-text .live-badge.is-live { color: #ff4438; }
.title-bar-text .live-badge.is-off { color: var(--bone); }
@media (prefers-reduced-motion: no-preference) {
    .live-badge.is-live .dot { animation: live-blink 1s steps(1) infinite; }
    .live-badge.is-off .dot { animation: off-breathe 3.5s ease-in-out infinite; }
}
@keyframes live-blink { 50% { opacity: 0.12; } }
@keyframes off-breathe { 50% { opacity: 0.3; } }

/* Mobile-only LIVE banner on the streaming page. Twitch's embedded player is
   unreliable on phone browsers and can show the channel-offline card while
   the stream is up; /api/live (OBS-side truth) drives this instead. Hidden on
   desktop at any state - the player works there. */
.live-note {
    display: none;
    background: #e00000;
    color: var(--spectral-hi);
    font-weight: bold;
    text-align: center;
    text-decoration: none;
    padding: 6px 8px;
    margin-bottom: 6px;
    border: 2px solid;
    border-color: #ff6a5e #7a0000 #7a0000 #ff6a5e;
}

/* ---- matrix easter egg (Konami code) ---- */
#matrix-egg { position: fixed; inset: 0; z-index: 300; background: #000; }
#matrix-egg canvas { position: absolute; inset: 0; width: 100%; height: 100%; }

@media (prefers-reduced-motion: reduce) {
    #skeleton-walker, #boot-screen, #matrix-egg { display: none !important; }
}

@media (max-width: 899px) {
    /* The icons used to be hidden entirely here, which meant a phone visitor
       saw no desktop apps at all - only the Start menu, if they thought to
       look. They can't stay fixed to the left because .site-window fills the
       width below this breakpoint, so instead they sit above the window.
       WRAPPED, not a horizontal scroller: a sideways strip hid half the nav
       off-screen with no affordance that anything was there. Every
       destination is now visible without scrolling. */
    /* order:-1 pins the icon strip to the TOP of the desktop, and
       justify-content must go back to flex-start with it: the base rule's
       `center` means "centre horizontally" in the desktop row layout, but the
       moment this switches to a column it centres VERTICALLY and parked the
       nav ~280px down, in the middle of the viewport. */
    .desktop {
        flex-direction: column;
        align-items: stretch;
        justify-content: flex-start;
    }
    .desktop-icons {
        order: -1;
        /* relative, not static: static would discard the z-index and let the
           fixed skull-hero (z 1) paint over the icon strip */
        position: relative;
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: center;
        align-items: flex-start;
        gap: 4px;
        overflow: visible;
        padding: 2px 2px 10px;
        margin-bottom: 2px;
        top: auto;
        left: auto;
    }
    /* 44px is the documented minimum touch target on both iOS and Android;
       these were 62x~40 with a 28px icon, small enough to mis-tap. */
    .desktop-icon, .recycle-bin {
        width: 72px;
        min-height: 44px;
        flex: 0 0 auto;
        padding: 4px 2px;
        border-radius: 3px;
    }
    .desktop-icon:active, .recycle-bin:active { background: rgba(255, 255, 255, 0.25); }
    .desktop-icon img, .recycle-bin img { width: 32px; height: 32px; }
    .desktop-icon span, .recycle-bin span { font-size: 11px; }
    /* The icon strip owns the top of the page here, and the hero is
       position:fixed so it does not know the strip exists. Push the skull's
       fixed box down past the strip rather than just padding it, so the ASCII
       art can never paint through the icon labels however the strip wraps.
       --icon-strip-h is measured from the real strip in app.js; the fallback
       covers the no-JS case and the first frame. */
    .skull-hero {
        justify-content: flex-start;
        padding-top: 12px;
        top: var(--icon-strip-h, 104px);
    }
    /* Only shown when JS marks the page live - tells a phone visitor the
       truth when Twitch's mobile iframe wrongly claims the channel is off. */
    .live-note:not(.hidden) { display: block; }
    /* Static on a phone so it rides along in that strip. On desktop it stays
       position:fixed in the top-right corner. Mods use phones too, and having
       no way to reach /hub from one was a real gap. */
    .recycle-bin {
        position: static;
        top: auto;
        right: auto;
    }
    .task-button { max-width: 45vw; }

    /* --- phone chrome -----------------------------------------------------
       The taskbar is fixed to the bottom, which on a phone with a home bar
       means the OS gesture area sits ON TOP of the Start button. Grow the bar
       by the inset and pad the content above it, so Start is always reachable
       and the 98 look is preserved (the extra height reads as the bar being
       flush with the bottom of the screen, which is what Win98 did anyway). */
    .taskbar {
        height: calc(var(--taskbar-h) + env(safe-area-inset-bottom));
        padding-bottom: calc(3px + env(safe-area-inset-bottom));
        align-items: flex-start;
    }
    .desktop {
        padding-bottom: calc(1.5rem + var(--taskbar-h) + env(safe-area-inset-bottom));
    }
    /* Every bar control to a real touch target: 44px is the documented
       minimum on iOS and Android. 28px was a miss-tap. The bar itself grows
       to fit, so nothing overflows and the content padding follows the var. */
    :root { --taskbar-h: 52px; }
    .start-button, .task-button { height: 44px; padding: 0 12px; font-size: 13px; }
    .taskbar-clock { align-self: center; }

    /* The Start menu opened as a 220px panel in the bottom-left corner - the
       hardest place to reach one-handed, with 26px rows. On a phone it
       becomes a full-width sheet above the taskbar with 48px rows. */
    .start-menu {
        width: auto;
        left: 4px;
        right: 4px;
        bottom: calc(var(--taskbar-h) + env(safe-area-inset-bottom) + 4px);
        max-height: 70vh;
    }
    .start-menu ul { overflow-y: auto; -webkit-overflow-scrolling: touch; }
    .start-menu a {
        min-height: 48px;
        padding: 10px 14px;
        font-size: 15px;
        gap: 12px;
    }
    .start-menu a img { width: 28px; height: 28px; }
    /* :hover sticks after a tap on touch devices - the previously tapped row
       stays highlighted as if focused. Use :active for press feedback. */
    .start-menu a:hover { background: transparent; color: var(--fg); }
    .start-menu a:active, .start-menu a:focus-visible { background: var(--navy); color: var(--spectral-hi); }
}

@media (max-width: 640px) {
    .desktop { padding: 0.5rem 0.5rem calc(0.5rem + var(--taskbar-h)); }
    .skull-hero { padding-top: 84px; }
    .window-body { padding: 0.75rem; }
    .content { padding: 0.75rem; }
    .status-bar { flex-direction: column; }

    .live-player .window-body { padding: 0.4rem; }
    .live-chat iframe { height: clamp(280px, 50vh, 520px); }

    /* The toolbar is a flex row on desktop; on a phone it has to wrap into
       rows that stay tappable instead of overflowing the window. */
    .draw-toolbar { gap: 0.5rem 0.75rem; }
    .draw-toolbar label { font-size: 14px; }
    .draw-toolbar input[type="range"] { width: 100px; }
    .draw-toolbar button,
    .draw-history button { min-height: 34px; padding: 0 0.9rem; }
    #draw-mode { flex: 1 0 100%; min-height: 40px; }

    /* Phone: the text box and Speak share a row, tags wrap under them. */
    #tts-text { flex: 1 1 100%; min-height: 34px; }
    .tts-panel button { min-height: 34px; }
    .tts-tag { min-height: 28px; }

    .draw-status { font-size: 12px; padding: 0.15rem 0.5rem; }
    .ctrl-mute input { width: 100%; }
    .ctrl-mute { flex: 1 0 100%; }
}


/* ── the ASCII flame backdrop ─────────────────────────────────────────────
   Fixed behind everything, like the book's cover. pointer-events:none so it
   cannot eat a click meant for an icon, and aria-hidden in the markup so a
   screen reader is not read a wall of punctuation.

   The four bands are panefx flames.rs's own C_DIM/C_COOL/C_WARM/C_HOT --
   spaced widely rather than evenly because the cool band covers ~70% of the
   screen, and a linear ramp puts most of the display in one tone and loses
   the tongues. */
#fire {
    position: fixed; inset: 0; margin: 0; z-index: 0;
    /* TRANSPARENT, explicitly. <pre> inherits the Win98 sheet's white field
       background, and at opacity .5 over the void that painted the ENTIRE
       viewport mid-grey -- which read as "the palette did not apply" when the
       body was in fact already #05070a. Measured with the browser: the fire
       element itself was rgb(255,255,255). */
    background: transparent;
    font-family: var(--ui); font-size: 12px; line-height: 1;
    white-space: pre; overflow: hidden; pointer-events: none;
    opacity: .5; letter-spacing: .5px;
}
/* The book's own four, read out of necronomicon.html. NOT four greens: the
   cool bands are a teal and a purple, which is what gives the fire its depth
   and what every all-green attempt kept missing. */
#fire .f0 { color: #4a3568 }  /* C_DIM  -- purple */
#fire .f1 { color: #3d8fa8 }  /* C_COOL -- teal   */
#fire .f2 { color: #62e670 }  /* C_WARM */
#fire .f3 { color: #c8ffd0 }  /* C_HOT  */

/* Reduced motion turns it off entirely rather than slowing it: a backdrop
   that still moves is still motion, and this is decoration nobody needs. */
@media (prefers-reduced-motion: reduce) { #fire { display: none } }

/* ============================================================
   98.css, RECOLOURED.

   static/98.css is vendored upstream (jdan/98.css v0.1.21) and paints real
   Windows 98: silver #c0c0c0 windows, white #fff fields, #dfdfdf bevels,
   navy title bars. It loads BEFORE this file and nothing here repainted it
   -- only layout was overridden -- so every window, button, input, table and
   scrollbar on the public site still rendered in Win98 greys while the
   wallpaper behind them was the necronomicon.

   Fixed HERE rather than by editing 98.css, because that file is vendored:
   the next upstream bump would silently revert hand-edits and nobody would
   know until the site looked wrong again. An override layer survives it.

   Values are the book's own, from necronomicon.html:
     --void #05070a  --crypt #0a0e14  --crypt-hi #111823
     --stone #1a2430 --stone-hi #26333f --ash #5c6470
     --bone-dim #8b8778 --bone #d8d4c4 --spectral #62e670
   ============================================================ */

/* Windows: the crypt, with stone bevels instead of silver/white ones. The
   box-shadow IS the bevel in 98.css, so it has to be restated in full --
   a background alone leaves a bright silver rim around a dark window. */
.window, .window[role="tabpanel"] {
    background: var(--crypt-hi);
    box-shadow: inset -1px -1px var(--void), inset 1px 1px var(--stone-hi),
                inset -2px -2px var(--stone), inset 2px 2px var(--stone-hi);
    color: var(--bone);
}
.window-body { color: var(--bone); }

/* Title bars: the book's stone gradient, not navy. */
.title-bar {
    background: linear-gradient(90deg, var(--stone), var(--stone-hi));
}
.title-bar.inactive {
    background: linear-gradient(90deg, var(--crypt), var(--stone));
}
.title-bar-text { color: var(--bone); }

/* Buttons: raised crypt. Same four-inset bevel 98.css uses, restated so the
   highlight is stone rather than white. */
button, input[type="reset"], input[type="submit"] {
    background: var(--crypt-hi);
    color: var(--bone);
    box-shadow: inset -1px -1px var(--void), inset 1px 1px var(--stone-hi),
                inset -2px -2px var(--stone), inset 2px 2px var(--stone-hi);
    text-shadow: none;
}
button:not(:disabled):active,
input[type="reset"]:not(:disabled):active,
input[type="submit"]:not(:disabled):active {
    box-shadow: inset -1px -1px var(--stone-hi), inset 1px 1px var(--void),
                inset -2px -2px var(--stone), inset 2px 2px var(--stone);
    text-shadow: none;
}
button:disabled, :disabled + label, input[readonly] { color: var(--ash); }
button:disabled, :disabled + label { text-shadow: none; }

/* Fields: sunken crypt with a bone caret, never a white box. A white input
   on this ground is the single brightest thing on the page. */
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="search"], input[type="tel"],
input[type="url"], select, textarea {
    background-color: var(--crypt);
    color: var(--bone);
    box-shadow: inset -1px -1px var(--stone-hi), inset 1px 1px var(--void),
                inset -2px -2px var(--stone), inset 2px 2px var(--void);
}
select:focus { background-color: var(--stone); color: var(--spectral-hi); }
select:focus option { background-color: var(--crypt); color: var(--bone); }
input[type="checkbox"] + label::before { background: var(--crypt); }

/* Tables and sunken panels. */
.sunken-panel, table { background-color: var(--crypt); }
table > thead > tr > * {
    background: var(--crypt-hi);
    color: var(--bone);
    box-shadow: inset -1px -1px var(--void), inset 1px 1px var(--stone-hi),
                inset -2px -2px var(--stone), inset 2px 2px var(--stone-hi);
}
table > tbody > tr.highlighted { background-color: var(--stone); color: var(--spectral-hi); }
.status-bar-field {
    color: var(--bone);
    box-shadow: inset -1px -1px var(--stone-hi), inset 1px 1px var(--void);
}
.field-border, .field-border-disabled, .status-field-border { background: var(--crypt); }
legend { background: var(--crypt-hi); color: var(--bone); }

/* Code blocks and the tree view: 98.css gives both a white ground. */
pre, ul.tree-view {
    background: var(--crypt);
    color: var(--bone);
    box-shadow: inset -1px -1px var(--stone-hi), inset 1px 1px var(--void),
                inset -2px -2px var(--stone), inset 2px 2px var(--void);
}
ul.tree-view a { color: var(--bone); }
ul.tree-view a:focus { background-color: var(--stone); color: var(--spectral-hi); }

/* Tabs. */
menu[role="tablist"] > li {
    box-shadow: inset -1px 0 var(--void), inset 1px 1px var(--stone-hi),
                inset -2px 0 var(--stone), inset 2px 2px var(--stone-hi);
}
menu[role="tablist"] > li[aria-selected="true"] { background-color: var(--crypt-hi); }
menu[role="tablist"] > li > a { color: var(--bone); }

/* Links: 98.css sets #00f, which is unreadable on the crypt. */
a { color: var(--spectral); }
a:focus { outline: 1px dotted var(--spectral); }

/* Scrollbars: the one piece of chrome that is pure Win98 grey and appears on
   every page that scrolls, which is all of them. */
::-webkit-scrollbar-corner { background: var(--crypt); }
::-webkit-scrollbar-track { background: var(--crypt); background-image: none; }
::-webkit-scrollbar-thumb {
    background-color: var(--crypt-hi);
    box-shadow: inset -1px -1px var(--void), inset 1px 1px var(--stone-hi),
                inset -2px -2px var(--stone), inset 2px 2px var(--stone-hi);
}
::-webkit-scrollbar-button:vertical:start,
::-webkit-scrollbar-button:vertical:end,
::-webkit-scrollbar-button:horizontal:start,
::-webkit-scrollbar-button:horizontal:end {
    background-color: var(--crypt-hi);
    background-image: none;
}
/* Firefox has no ::-webkit- pseudo-elements; this is the whole API it gives. */
* { scrollbar-color: var(--stone-hi) var(--crypt); }
