/* orbitron-regular - latin */
@font-face {
    font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
    font-family: 'Orbitron';
    font-style: normal;
    font-weight: 400;
    src: url('/static/fonts/orbitron-v34-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
  }
  
  /* orbitron-700 - latin */
  @font-face {
    font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
    font-family: 'Orbitron';
    font-style: normal;
    font-weight: 700;
    src: url('/static/fonts/orbitron-v34-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
  }
  
  /* orbitron-900 - latin */
  @font-face {
    font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
    font-family: 'Orbitron';
    font-style: normal;
    font-weight: 900;
    src: url('/static/fonts/orbitron-v34-latin-900.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
  }

  /* share-tech-mono-regular - latin */
@font-face {
    font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
    font-family: 'Share Tech Mono';
    font-style: normal;
    font-weight: 400;
    src: url('/static/fonts/share-tech-mono-v15-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
  }

  /* ---------------------------------------------------------------------------
     Per-theme faces. Themes repoint --b-font-display / --b-font-mono at these so
     each desktop has its OWN typeface, not just its own colours. Each src is a
     local woff2 that may not be on disk yet — the font-family stacks in the theme
     blocks below always name a real system fallback first (Comic Sans MS, Segoe
     UI, Tahoma), so a missing file degrades to a sensible face rather than
     Times New Roman. Drop the woff2 into /static/fonts/ to get the real thing.
     --------------------------------------------------------------------------- */

  /* Fredoka — the Anime desktop's rounded, chunky candy face. */
  @font-face {
    font-display: swap;
    font-family: 'Fredoka';
    font-style: normal;
    font-weight: 400;
    src: url('/static/fonts/fredoka-400.woff2') format('woff2');
  }
  @font-face {
    font-display: swap;
    font-family: 'Fredoka';
    font-style: normal;
    font-weight: 600;
    src: url('/static/fonts/fredoka-600.woff2') format('woff2');
  }
  @font-face {
    font-display: swap;
    font-family: 'Fredoka';
    font-style: normal;
    font-weight: 700;
    src: url('/static/fonts/fredoka-700.woff2') format('woff2');
  }

  /* Quicksand — the Aero desktop's geometric-rounded face, the signature look of
     Frutiger-Aero / glossy-2008 UI. Falls back to real Segoe UI on Windows. */
  @font-face {
    font-display: swap;
    font-family: 'Quicksand';
    font-style: normal;
    font-weight: 400;
    src: url('/static/fonts/quicksand-v37-latin-regular.woff2') format('woff2');
  }
  @font-face {
    font-display: swap;
    font-family: 'Quicksand';
    font-style: normal;
    font-weight: 600;
    src: url('/static/fonts/quicksand-v37-latin-600.woff2') format('woff2');
  }
  @font-face {
    font-display: swap;
    font-family: 'Quicksand';
    font-style: normal;
    font-weight: 700;
    src: url('/static/fonts/quicksand-v37-latin-700.woff2') format('woff2');
  }

  /* Chakra Petch — the Royale family's squarish techy-retro face: a 2000s
     set-top / Media Center HUD look that reads on both the Energy-Blue and the
     haunted noir build. Falls back to Tahoma. */
  @font-face {
    font-display: swap;
    font-family: 'Chakra Petch';
    font-style: normal;
    font-weight: 400;
    src: url('/static/fonts/chakra-petch-v13-latin-regular.woff2') format('woff2');
  }
  @font-face {
    font-display: swap;
    font-family: 'Chakra Petch';
    font-style: normal;
    font-weight: 600;
    src: url('/static/fonts/chakra-petch-v13-latin-600.woff2') format('woff2');
  }
  @font-face {
    font-display: swap;
    font-family: 'Chakra Petch';
    font-style: normal;
    font-weight: 700;
    src: url('/static/fonts/chakra-petch-v13-latin-700.woff2') format('woff2');
  }

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* ---------------------------------------------------------------------------
   Theme tokens. The default is the neon-vaporwave palette, defined once here
   and consumed everywhere below. Accents are raw RGB channel triplets so any
   alpha variant is a one-liner: `rgb(var(--accent-1) / 0.3)`. A theme is a
   `body[data-theme="..."]` block that overrides these tokens (plus a handful
   of targeted rules) — see the Themes section at the bottom of this file. The
   shell sets body[data-theme] from the wallpaper category (app.js).
   --------------------------------------------------------------------------- */
:root {
    /* Accent channel triplets */
    --accent-1: 0 255 255;        /* neon cyan — the workhorse */
    --accent-2: 255 0 128;        /* neon magenta */
    --accent-3: 255 107 157;      /* soft pink (clock, h3 headings) */
    --accent-max: 179 136 255;    /* violet orb — maximize/fullscreen controls */
    --accent-close: 255 45 126;   /* red-pink orb — close control */
    --b-ok: 0 255 0;              /* status-light / boot-OK green */
    --b-violet: 120 40 120;       /* taskbar/titlebar gradient mid stop */
    --b-violet-focus: 150 50 160; /* focused titlebar gradient mid stop */

    /* Whole colours */
    --b-text: #e0e0e0;
    /* Dimmed body text (hints, snippets, empty states). MUST be defined per
       theme — consumers used to fall back to a light grey that vanished on the
       light themes (Anime/Aero). Pierces shadow DOM, so it also fixes the
       Documents / System Monitor hints. */
    --b-text-dim: rgba(224, 224, 224, 0.68);
    --b-surface: rgba(10, 8, 24, 0.45);         /* window glass */
    --b-surface-taskbar: rgba(10, 8, 24, 0.55); /* taskbar glass (a bit denser) */
    --b-glow: rgb(var(--accent-1) / 0.5);       /* the standard cyan halo */

    /* Content panels inside shadow-DOM apps (System Monitor tabs/cards etc.).
       Custom properties pierce shadow roots, so light themes can flip these
       without global CSS ever reaching inside the apps. */
    --b-panel: rgba(0, 0, 0, 0.5);
    --b-panel-strong: rgba(0, 0, 0, 0.85);
    --b-panel-soft: rgba(0, 0, 0, 0.3);

    /* Chrome effects */
    --b-blur: 4px; /* backdrop blur on taskbar + windows; themes may zero it */

    /* Fonts */
    --b-font-display: 'Orbitron', sans-serif;
    --b-font-mono: 'Share Tech Mono', monospace;

    /* Corner-radius scale. The chrome sets its own window radii per theme, but
       panels/cards (System Monitor, radio) read these so a theme can be sharp
       (Void), bubbly (Anime/Aero) or middling (default) from one pair of knobs.
       --b-radius = small controls/cards; --b-radius-lg = big panels. */
    --b-radius: 8px;
    --b-radius-lg: 14px;

    /* Window-frame corner radius. Separate from --b-radius so a theme can pair a
       sharp window with round cards (or vice versa) — Anime is a blocky sticker
       window around bubbly cards; Void goes fully square. The base .winbox /
       .wb-header / .app-window rules consume this, so setting it per theme is all
       a theme needs to change how square its windows are. */
    --b-window-radius: 8px;
}

body {
    font-family: var(--b-font-mono);
    color: rgb(var(--accent-1));
    overflow: hidden;
    height: 100vh;
    background: #000;
    /* Ease theme swaps in with the 2s wallpaper crossfade. */
    transition: background-color 2s, color 2s;
}

.desktop {
    position: relative;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
}

.desktop-background-video {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: -2;
}

/* Transitioning layers for the server-coordinated mood wallpaper. Each mood
   change appends a new layer (an <img> or looping <video>) on top and animates
   it in while the old one animates out, so wallpapers drift rather than cut. The
   exact effect (fade, zoom, blur, slide, wipe) is picked at random per change in
   crossfadeTo() — hence the transition covers transform/filter/clip-path too,
   not just opacity. Sits at the same depth as the static background. */
.wallpaper-layer {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: -2;
    opacity: 1;
    transition: opacity 2s ease, transform 2s ease,
                filter 2s ease, clip-path 2s ease;
    pointer-events: none;
    will-change: opacity, transform, filter, clip-path;
}

.desktop-background-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.3);
    pointer-events: none;
    z-index: -1;
}

/* Taskbar */
.taskbar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 50px;
    /* Neon vaporwave glass, matching the window titlebars: faint CRT scanlines
       layered over a cyan→violet→magenta sweep, on a dark violet tint so the
       neon reads against the wallpaper. */
    background:
        repeating-linear-gradient(0deg,
            rgba(0, 0, 0, 0) 0px, rgba(0, 0, 0, 0) 2px,
            rgba(0, 0, 0, 0.16) 2px, rgba(0, 0, 0, 0.16) 3px),
        linear-gradient(180deg,
            rgb(var(--accent-1) / 0.12) 0%,
            rgb(var(--b-violet) / 0.16) 55%,
            rgb(var(--accent-2) / 0.12) 100%),
        var(--b-surface-taskbar);
    backdrop-filter: blur(var(--b-blur)) saturate(150%);
    -webkit-backdrop-filter: blur(var(--b-blur)) saturate(150%);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 5px;
    z-index: 1000;
    /* Glowing cyan top edge (mirrors the focused titlebar's cyan hairline), plus
       the float shadow above so it sits over the wallpaper. */
    border-top: 1px solid rgb(var(--accent-1) / 0.45);
    box-shadow:
        0 -8px 22px rgba(0, 0, 0, 0.25),
        inset 0 1px 0 rgb(var(--accent-1) / 0.18);
    overflow: visible; /* lets dock icons zoom above the taskbar */
    /* Theme swaps ease in alongside the 2s wallpaper crossfade; opacity eases
       the Launchpad-open fade-out. */
    transition: background-color 2s, border-color 2s, color 2s, box-shadow 2s, opacity 0.3s ease;
}

.taskbar-left {
    display: flex;
    align-items: center;
}

.start-button {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 16px;
    /* A raised, glossy fill (top highlight + drop shadow) so it reads clearly as
       a BUTTON, not a search field or status chip. */
    background: linear-gradient(180deg, rgb(var(--accent-1) / 0.22), rgb(var(--accent-2) / 0.18));
    border: 1px solid rgb(var(--accent-1) / 0.7);
    border-radius: var(--b-radius, 8px); /* follow the theme — sharp on Void, bubbly on Aero */
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.18), 0 2px 8px rgba(0, 0, 0, 0.35);
    cursor: pointer;
    transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
    font-family: var(--b-font-display);
    font-weight: bold;
}

.start-button:hover {
    background: linear-gradient(180deg, rgb(var(--accent-1) / 0.34), rgb(var(--accent-2) / 0.28));
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.25), 0 0 16px var(--b-glow), 0 2px 8px rgba(0, 0, 0, 0.4);
    transform: translateY(-2px);
}
.start-button:active {
    transform: translateY(0);
    box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
}

/* ═══ Themed command-prompt ">" glyph ═══════════════════════════════════════
   The prompt in the taskbar pill + launchpad omnibox (and the radio's idle
   Babelcom launcher) is a MASK, so its SHAPE follows the theme (the
   --b-prompt-glyph token below) while its FILL follows --accent-1. Each theme
   gets a structurally different mark — a neon double-chevron, a stark Void
   caret, a chunky Anime play-triangle, a glossy Aero chevron, an inked Royale
   Noir » and a bold bright-Royale ». */
:root                          { --b-prompt-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 6l6 6-6 6'/%3E%3Cpath d='M12 6l6 6-6 6'/%3E%3C/svg%3E"); }
body[data-theme="void"]        { --b-prompt-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='square'%3E%3Cpath d='M8 4l8 8-8 8'/%3E%3C/svg%3E"); }
body[data-theme="anime"]       { --b-prompt-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000' stroke-width='3' stroke-linejoin='round'%3E%3Cpath d='M8 5.5l10 6.5-10 6.5z'/%3E%3C/svg%3E"); }
body[data-theme="aero"]        { --b-prompt-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 5l7 7-7 7'/%3E%3C/svg%3E"); }
body[data-theme="royale-noir"] { --b-prompt-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'%3E%3Cpath d='M4 5l2-2 9 9-9 9-2-2 7-7z'/%3E%3Cpath d='M11 5l2-2 9 9-9 9-2-2 7-7z'/%3E%3C/svg%3E"); }
body[data-theme="royale"]      { --b-prompt-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 6l5 6-5 6'/%3E%3Cpath d='M13 6l5 6-5 6'/%3E%3C/svg%3E"); }

.start-icon {
    display: inline-block;
    width: 17px;
    height: 17px;
    background-color: rgb(var(--accent-1));
    -webkit-mask: var(--b-prompt-glyph) center / contain no-repeat;
            mask: var(--b-prompt-glyph) center / contain no-repeat;
}

.start-text {
    font-size: 0.9em;
    letter-spacing: 1px;
    color: rgb(var(--accent-2));
}

/* "press Space" hint living in the search pill — pulses to draw the eye until
   the user has opened the palette once (then body.palette-seen hides it). */
.start-key {
    font-family: var(--b-font-mono);
    font-size: 0.68em;
    line-height: 1;
    color: rgb(var(--accent-1));
    background: rgb(var(--accent-1) / 0.12);
    border: 1px solid rgb(var(--accent-1) / 0.5);
    border-radius: 4px;
    padding: 3px 6px;
    letter-spacing: 0.5px;
    /* A finite burst, NOT infinite: an always-running animation recomposites the
       whole window every frame in Firefox/WebRender (see the status-dot note), and
       this is a passively-watched piece. ~9 pulses grabs a new arrival's eye, then
       it rests static; opening the palette once hides it for good anyway. */
    animation: startKeyPulse 1.8s ease-in-out 9;
}
@keyframes startKeyPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgb(var(--accent-1) / 0);      background: rgb(var(--accent-1) / 0.12); }
    50%      { box-shadow: 0 0 12px 1px rgb(var(--accent-1) / 0.7); background: rgb(var(--accent-1) / 0.28); }
}
/* Learned it — retire the nudge. */
body.palette-seen .start-key { display: none; }

.taskbar-center {
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: flex-end;
    /* Bottom-anchor the dock inside the taskbar so any grow goes upward only;
       otherwise the taskbar's align-items: center pushes the bottom half of
       magnified tiles below the screen edge. */
    align-self: flex-end;
    height: 100%;
    min-width: 0;
}

.running-apps {
    display: flex;
    gap: 10px;
    align-items: flex-end;
    overflow: visible;
    min-width: 0;
}

/* macOS-style dock. Tiles are taller than the 50px taskbar so the icon
   protrudes above it; the running/focused dot stays inside the taskbar at
   the bottom of the tile. */
.taskbar-app {
    position: relative;
    width: 52px;
    height: 50px;
    flex-shrink: 0; /* JS sets width/height live; don't let flex squish them */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    padding-top: 2px;
    cursor: pointer;
    /* The focus glow on background still smooths; width/height are JS-driven
       and any easing would lag behind the cursor. */
    transition: background 0.2s ease;
    border-radius: 12px;
}

.taskbar-app img {
    width: 40px;
    height: 40px;
    /* contain, not the default fill — a non-square icon must letterbox, never
       stretch to the 40×40 box. */
    object-fit: contain;
    display: block;
    filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.55));
    pointer-events: none;
    transition: opacity 0.2s ease;
}

/* Divider between the launcher apps (left) and transient open windows (right),
   like the apps|files split on a real macOS dock. Only present when a transient
   window is open. Bottom-anchored to sit within the taskbar with the tiles. */
.dock-separator {
    flex-shrink: 0;
    align-self: center;
    width: 1px;
    height: 30px;
    margin: 0 8px;
    background: rgb(var(--accent-1) / 0.35);
    box-shadow: 0 0 6px rgb(var(--accent-1) / 0.25);
}

/* Apps that aren't open are dimmed — still clickable to launch. */
.taskbar-app:not(.running) img {
    opacity: 0.7;
}

/* Running-state indicator dot, inside the taskbar at the bottom of the tile. */
.taskbar-app::after {
    content: '';
    position: absolute;
    bottom: 2px;
    left: 50%;
    transform: translateX(-50%);
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: transparent;
    pointer-events: none;
    transition: background 0.2s ease, box-shadow 0.2s ease, width 0.2s ease, height 0.2s ease;
}

.taskbar-app.running::after {
    background: rgb(var(--accent-1));
    box-shadow: 0 0 8px rgb(var(--accent-1));
}

/* Focused (frontmost) app gets a soft glow tile + brighter dot. */
.taskbar-app.focused {
    background: radial-gradient(ellipse at 50% 70%,
        rgb(var(--accent-1) / 0.66),
        rgb(var(--accent-1) / 0) 70%);
}

.taskbar-app.focused::after {
    width: 6px;
    height: 6px;
    background: rgb(var(--accent-1));
    box-shadow: 0 0 10px rgb(var(--accent-1)), 0 0 22px rgb(var(--accent-1) / 0.7);
}

/* Dock magnifier is driven by JS (setupDockMagnifier in app.js) — it sets
   each tile and icon's width/height inline based on cursor distance using a
   cosine falloff. Sizing (not transform) so the flex row re-flows and
   neighbours physically push aside. */

.taskbar-right {
    display: flex;
    align-items: center;
    gap: 20px;
}

.system-tray {
    display: flex;
    align-items: center;
    gap: 15px;
    padding-left: 5px;
}

.taskbar-metric {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--b-font-display);
    color: rgb(var(--accent-1));
    text-shadow: 0 0 8px rgb(var(--accent-1) / 0.4);
    transition: opacity 0.1s ease;
    min-width: 64px;
    justify-content: flex-end;
}

.taskbar-metric.fading {
    opacity: 0;
}

.taskbar-metric-icon {
    display: inline-flex;
    align-items: center;
    font-size: 16px;
    line-height: 1;
}

.taskbar-metric-icon img {
    width: 16px;
    height: 16px;
    vertical-align: middle;
}

.taskbar-metric-value {
    font-size: 0.95em;
}

.clock {
    font-family: var(--b-font-display);
    font-size: 1.2em;
    color: rgb(var(--accent-3));
    text-shadow: 0 0 8px rgb(var(--accent-3) / 0.5);
}

.taskbar-mute {
    cursor: pointer;
    font-size: 1.1em;
    line-height: 1;
    user-select: none;
    transition: transform 0.2s ease, opacity 0.2s ease;
}

.taskbar-mute:hover {
    transform: scale(1.15);
}

.taskbar-mute.muted {
    opacity: 1;
    filter: grayscale(0.5);
}

.taskbar-fullscreen {
    display: flex;
    align-items: center;
    cursor: pointer;
    user-select: none;
    transition: transform 0.2s ease;
}

/* Source icon is a pale cyan that vanishes on the translucent taskbar.
   `brightness(0) invert(1)` forces it to pure white, then the drop-shadow
   gives it the cyan halo so it reads as part of the existing tray. Sized
   to nearly fill the 50px taskbar height. */
.taskbar-fullscreen img {
    width: 40px;
    height: 40px;
    display: block;
    filter: brightness(0) invert(1) drop-shadow(0 0 10px rgb(var(--accent-1) / 0.85));
}

.taskbar-fullscreen:hover {
    transform: scale(1.10);
}

.taskbar-fullscreen.active img {
    filter: brightness(0) invert(1) drop-shadow(0 0 12px rgb(var(--accent-3) / 0.9));
}

.status-indicator {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 1em;
    font-family: var(--b-font-display);
}

.status-dot {
    width: 8px;
    height: 8px;
    background: rgb(var(--b-ok));
    border-radius: 50%;
    /* NO infinite animation here. In Firefox/WebRender any continuously-running
       CSS animation forces a full-window recomposite every frame — even this
       8px dot pegged the GPU process at ~50%. A static glow gives the same
       "online/alive" feel for free (painted once, never recomposited). */
    box-shadow: 0 0 6px rgb(var(--b-ok) / 0.9);
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* Start Menu */
.start-menu {
    position: fixed;
    bottom: 50px;
    left: 5px;
    width: 300px;
    background: rgba(0, 0, 0, 0.95);
    backdrop-filter: blur(15px);
    border: 2px solid rgb(var(--accent-1));
    border-radius: 15px;
    box-shadow: 0 0 30px rgb(var(--accent-1) / 0.4);
    z-index: 999;
    transform: translateY(100%);
    opacity: 0;
    /* Open/close keeps its snappy 0.3s slide; the palette itself eases over
       2s so theme swaps ride the wallpaper crossfade. */
    transition: transform 0.3s ease, opacity 0.3s ease,
                background-color 2s, border-color 2s, color 2s, box-shadow 2s;
    display: none;
}

.start-menu.active {
    transform: translateY(0);
    opacity: 1;
    display: block;
}

.start-menu-header {
    padding: 20px;
    border-bottom: 1px solid rgb(var(--accent-1) / 0.3);
}

.start-menu-header h3 {
    font-family: var(--b-font-display);
    color: rgb(var(--accent-1));
    text-shadow: 0 0 10px var(--b-glow);
}

.start-menu-apps {
    padding: 20px;
}

.app-item {
    display: flex;
    align-items: center;
    gap: 15px;
    padding: 12px;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.3s ease;
    margin-bottom: 8px;
}

.app-item:hover {
    background: rgb(var(--accent-1) / 0.1);
    border: 1px solid rgb(var(--accent-1) / 0.3);
    transform: translateX(5px);
}

.app-icon {
    font-size: 1.5em; /* emoji fallback (e.g. Restart 🔄) */
}

img.app-icon {
    width: 28px;
    height: 28px;
    object-fit: contain;
    display: block;
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
}

.app-name {
    font-family: var(--b-font-display);
    font-size: 1em;
    color: var(--b-text);
}

.start-menu-divider {
    height: 1px;
    background: rgb(var(--accent-1) / 0.3);
    margin: 8px 4px;
}

/* Desktop Icons */
.desktop-icons {
    position: absolute;
    top: 20px;
    left: 20px;
    display: flex;
    flex-direction: column;
    gap: 20px;
    z-index: 10;
}

.desktop-icon {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 15px;
    border-radius: 10px;
    cursor: pointer;
    transition: all 0.3s ease;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid transparent;
    backdrop-filter: blur(5px);
}

.desktop-icon:hover {
    background: rgb(var(--accent-1) / 0.1);
    border-color: rgb(var(--accent-1));
    box-shadow: 0 0 20px rgb(var(--accent-1) / 0.3);
    transform: scale(1.05);
}

.icon {
    font-size: 2.5em;
    text-shadow: 0 0 10px var(--b-glow);
}

img.icon {
    width: 44px;
    height: 44px;
    object-fit: contain;
    display: block;
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.6));
}

.icon-label {
    font-family: var(--b-font-display);
    font-size: 1em;
    text-align: center;
    color: var(--b-text);
    text-shadow: 0 0 5px rgba(224, 224, 224, 0.3);
    max-width: 80px;
    word-wrap: break-word;
}

/* Window Container */
#windowContainer {
    position: relative;
    width: 100%;
    height: calc(100vh - 50px);
    z-index: 100;
    pointer-events: none;
}

/* WinBox Customization — Aero glass on the window chrome. The blur is only
   really visible through the titlebar strip + rounded corners (app bodies are
   mostly opaque). Note: a backdrop-filter is recomputed over the window's whole
   footprint every frame while the backdrop animates (the visualizer wallpaper),
   regardless of opaque content hiding it — so windows that gain nothing visible
   from it should opt out (see the Writer exception below). */
.winbox {
    /* Neon vaporwave glass: a dark violet tint (so the neon pops) over a blur of
       whatever's behind. (No !important needed — WinBox injects its own style at
       head.firstChild, so these same-specificity rules win on cascade order.) */
    background: var(--b-surface);
    backdrop-filter: blur(var(--b-blur)) saturate(150%);
    -webkit-backdrop-filter: blur(var(--b-blur)) saturate(150%);
    /* Inactive (background) windows recede: a dim cyan hairline and a flat drop
       shadow with no neon bloom. The focused window lights up via .winbox.focus. */
    border: 1px solid rgb(var(--accent-1) / 0.22);
    border-radius: var(--b-window-radius, 8px);
    box-shadow:
        0 12px 30px rgba(0, 0, 0, 0.55),
        0 0 0 1px rgb(var(--accent-2) / 0.10),
        inset 0 0 18px rgb(var(--accent-1) / 0.05);
    pointer-events: auto;
    animation: winboxOpen 0.52s cubic-bezier(0.22, 1, 0.36, 1);
    /* Over-exaggerated tiling: the position/size transitions carry a big spring
       overshoot so keyboard snap/maximise/restore feel bouncy. WinBox adds
       body.wb-lock (transition:none) during drag/resize, so live dragging stays
       instant — only PROGRAMMATIC move()/resize() (Ctrl+Alt tiling) and the
       transform (show-desktop) spring. box-shadow/border stay snappy for focus;
       glass colour eases over 2s to ride theme swaps. */
    transition: left 0.46s cubic-bezier(0.34, 1.56, 0.64, 1),
                top 0.46s cubic-bezier(0.34, 1.56, 0.64, 1),
                width 0.46s cubic-bezier(0.34, 1.56, 0.64, 1),
                height 0.46s cubic-bezier(0.34, 1.56, 0.64, 1),
                transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1),
                box-shadow 0.2s ease, border-color 0.2s ease, opacity 0.32s ease,
                background-color 2s, color 2s;
}

/* The frontmost (focused) window is the hero: a bright cyan rim, a cyan+magenta
   bloom, and a hard 2px RGB-split fringe (cyan left / magenta right) for that
   chromatic-aberration look. WinBox toggles the `focus` class as focus moves, so
   this is pure CSS; specificity (0,2,0) beats the base .winbox. */
.winbox.focus {
    border-color: rgb(var(--accent-1) / 0.60);
    box-shadow:
        0 16px 44px rgba(0, 0, 0, 0.60),
        0 0 22px rgb(var(--accent-1) / 0.45),
        0 0 46px rgb(var(--accent-2) / 0.28),
        2px 0 0 rgb(var(--accent-2) / 0.35),
        -2px 0 0 rgb(var(--accent-1) / 0.35),
        inset 0 0 22px rgb(var(--accent-1) / 0.08);
}

/* The Writer is fully opaque, so the blur is invisible behind it — pure cost,
   no benefit. Opt it out (border + box-shadow still define its edge). The
   Workstation is the same babel-writer chrome on the work_* stream, so it
   opts out too. */
.winbox.winbox-app-writer,
.winbox.winbox-app-workstation {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Windows burst in — drop from above, scale up past 1, settle. Over-exaggerated
   on purpose (WinBox positions via left/top, so transform is free here). */
@keyframes winboxOpen {
    0%   { opacity: 0; transform: translateY(-64px) scale(0.6); }
    55%  { opacity: 1; transform: translateY(10px) scale(1.06); }
    75%  { transform: translateY(-3px) scale(0.99); }
    100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* Show-desktop (H): windows scale down and drop away, leaving only the wallpaper
   and taskbar; toggle again to spring them back. The .winbox transition above
   carries the bounce. */
body.show-desktop .winbox {
    opacity: 0;
    transform: scale(0.55) translateY(60px);
    pointer-events: none;
}

/* Vista Aero titlebar: a uniform white-frosted glass with just a subtle
   brightening at the top edge and a faint cool tint at the base — Vista's
   titlebar is nearly flat, distinguished from the body only by a thin
   hairline separator. */
.wb-header {
    /* Two stacked background layers: faint CRT scanlines on top of a neon
       cyan→violet→magenta glass sweep. Muted by default (inactive window);
       .winbox.focus .wb-header lights it up. */
    background:
        repeating-linear-gradient(0deg,
            rgba(0, 0, 0, 0) 0px, rgba(0, 0, 0, 0) 2px,
            rgba(0, 0, 0, 0.16) 2px, rgba(0, 0, 0, 0.16) 3px),
        linear-gradient(180deg,
            rgb(var(--accent-1) / 0.10) 0%,
            rgb(var(--b-violet) / 0.10) 55%,
            rgb(var(--accent-2) / 0.08) 100%);
    /* Match the .winbox border-radius on the top corners so the header's
       rectangular bg doesn't stick out past the rounded frame. */
    border-radius: var(--b-window-radius, 8px) var(--b-window-radius, 8px) 0 0;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
    border-bottom: 1px solid rgb(var(--accent-1) / 0.18);
    font-family: var(--b-font-display);
    font-weight: bold;
    /* 0.2s keeps focus/blur snappy (the header backgrounds are gradients, which
       snap rather than tween anyway); color eases with theme swaps. */
    transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease,
                color 2s;
}

/* Focused window's titlebar: a vivid neon sweep, an inner cyan glow, and a
   bright cyan hairline along the bottom edge as a "this window is live" marker. */
.winbox.focus .wb-header {
    background:
        repeating-linear-gradient(0deg,
            rgba(0, 0, 0, 0) 0px, rgba(0, 0, 0, 0) 2px,
            rgba(0, 0, 0, 0.22) 2px, rgba(0, 0, 0, 0.22) 3px),
        linear-gradient(180deg,
            rgb(var(--accent-1) / 0.22) 0%,
            rgb(var(--b-violet-focus) / 0.20) 55%,
            rgb(var(--accent-2) / 0.18) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.30),
        inset 0 0 18px rgb(var(--accent-1) / 0.12);
    border-bottom-color: rgb(var(--accent-1) / 0.60);
}

.wb-body {
    background: transparent;
    color: var(--b-text);
    font-family: var(--b-font-mono);
}
.wb-title {
    font-size: 1em;
    font-family: var(--b-font-display);
    font-weight: 700;
    letter-spacing: 0.06em;
    /* Dimmed neon for inactive windows; the focused title ignites (below). */
    color: rgba(170, 220, 235, 0.5);
    text-shadow: 0 0 4px rgb(var(--accent-1) / 0.25);
    transition: color 0.2s ease, text-shadow 0.2s ease;
}

/* Focused title: a near-white core with a layered cyan glow and a magenta outer
   bloom — neon sign, not Aero glass. */
.winbox.focus .wb-title {
    /* Crisp white core with the glow pushed OUTWARD — a tight, high-opacity inner
       halo (e.g. 0 0 4px) just smears the letter edges and reads as blur, not
       neon. Larger radii at lower opacity bloom around the sharp glyph instead. */
    color: #ffffff;
    text-shadow:
        0 0 7px rgb(var(--accent-1) / 0.55),
        0 0 15px rgb(var(--accent-1) / 0.35),
        0 0 26px rgb(var(--accent-2) / 0.22);
}

/* Dim the titlebar icon on inactive windows to match the dimmed title text
   (~0.5 alpha), and bring it to full on the focused window. */
.wb-icon {
    opacity: 0.5;
    transition: opacity 0.2s ease;
}
.winbox.focus .wb-icon {
    opacity: 1;
}

/* Window controls — neon "traffic light" orbs. WinBox renders min/max/full/close
   as <span>s with a centered white SVG glyph; we wrap each glyph in a glowing
   circle. `.wb-control > span` (0,1,1) outranks WinBox's `.wb-control *` (0,1,0),
   so the 30px default sizing is overridden without !important. */
.wb-control > span {
    width: 18px;
    height: 18px;
    /* Vertically centre the 18px orb in the 35px header. */
    margin: 8px 4px 0;
    border-radius: 50%;
    border: 1px solid currentColor;
    background-color: rgba(255, 255, 255, 0.04);
    background-position: center;   /* override WinBox's per-glyph vertical nudge */
    background-size: 10px auto;
    vertical-align: top;
    opacity: 0.9;
    transition: background-color 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease, transform 0.12s ease;
}

/* Per-button neon colour (currentColor drives the border; the hover glow matches). */
.wb-control > span.wb-min  { color: rgb(var(--accent-1) / 0.65); }
.wb-control > span.wb-max,
.wb-control > span.wb-full { color: rgb(var(--accent-max) / 0.7); }
.wb-control > span.wb-close { color: rgb(var(--accent-close) / 0.75); }

.wb-control > span:hover { opacity: 1; transform: translateY(-1px); }
.wb-control > span.wb-min:hover {
    background-color: rgb(var(--accent-1) / 0.18);
    box-shadow: 0 0 10px rgb(var(--accent-1) / 0.85);
}
.wb-control > span.wb-max:hover,
.wb-control > span.wb-full:hover {
    background-color: rgb(var(--accent-max) / 0.20);
    box-shadow: 0 0 10px rgb(var(--accent-max) / 0.85);
}
.wb-control > span.wb-close:hover {
    background-color: rgb(var(--accent-close) / 0.22);
    box-shadow: 0 0 12px rgb(var(--accent-close) / 0.95);
}

/* Inactive windows: dim the orbs so the focused window's controls read brighter
   — but not while hovered, so the hover glow still fires on a background window. */
.winbox:not(.focus) .wb-control > span:not(:hover) { opacity: 0.4; }

/* Neon scrollbars on light-DOM window bodies. (Shadow-DOM apps and the wiki
   iframe keep their own — global CSS can't reach inside them.) */
.wb-body::-webkit-scrollbar,
.app-window::-webkit-scrollbar { width: 10px; height: 10px; }
.wb-body::-webkit-scrollbar-track,
.app-window::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.30); }
.wb-body::-webkit-scrollbar-thumb,
.app-window::-webkit-scrollbar-thumb {
    background: linear-gradient(180deg, rgb(var(--accent-1)), rgb(var(--accent-2)));
    border-radius: 6px;
    border: 2px solid rgba(0, 0, 0, 0.30);
    box-shadow: 0 0 6px var(--b-glow);
}
.wb-body::-webkit-scrollbar-thumb:hover,
.app-window::-webkit-scrollbar-thumb:hover {
    box-shadow: 0 0 10px rgb(var(--accent-1) / 0.9);
}
.wb-body, .app-window {
    scrollbar-width: thin;
    scrollbar-color: rgb(var(--accent-1)) rgba(0, 0, 0, 0.30);
}



/* App-specific styles */
.app-window {
    height: 100%;
    overflow-y: auto;
    /* Only the bottom corners round — the top meets the flat underside of
       the titlebar, so any radius there would create a visible mismatch. */
    border-radius: 0 0 var(--b-window-radius, 8px) var(--b-window-radius, 8px);
}

.app-window h1 {
    font-family: var(--b-font-display);
    color: rgb(var(--accent-1));
    border-bottom: 3px solid rgb(var(--accent-2));
    padding-bottom: 15px;
    text-shadow: 0 0 10px var(--b-glow);
    font-size: 2.2em;
    letter-spacing: 2px;
    margin-bottom: 30px;
}

.app-window h2 {
    font-family: var(--b-font-display);
    color: rgb(var(--accent-2));
    margin-top: 30px;
    text-shadow: 0 0 8px rgb(var(--accent-2) / 0.5);
    font-size: 1.8em;
}

.app-window h3 {
    font-family: var(--b-font-display);
    color: rgb(var(--accent-3));
    text-shadow: 0 0 6px rgb(var(--accent-3) / 0.5);
    font-size: 36px;
}

.app-window p {
    margin-bottom: 20px;
    color: var(--b-text);
    text-shadow: 0 0 5px rgba(224, 224, 224, 0.3);
    line-height: 1.6;
}

.app-window a {
    color: rgb(var(--accent-1));
    text-decoration: none;
    text-shadow: 0 0 5px rgb(var(--accent-1) / 0.3);
    transition: all 0.3s ease;
}

.app-window a:hover {
    color: rgb(var(--accent-2));
    text-shadow: 0 0 10px rgb(var(--accent-2) / 0.8);
    transform: translateY(-2px);
}

/* Radio Styles */
.radio-container {
    padding: 20px;
    height: 100%;
    display: flex;
    flex-direction: column;
    background: rgba(0, 0, 0, 0.8);
    border-radius: 8px;
}



.player-description {
    text-align: center;
    margin-bottom: 20px;
}

.player-description p {
    color: var(--b-text);
    font-size: 0.9em;
    margin: 0;
}

.radio-iframe-container {
    /* flex: 1;
    display: flex; */
    align-items: stretch;
    justify-content: center;
    margin: 20px 0;
    background: rgba(0, 0, 0, 0.5);
    border-radius: 8px;
    /* border: 1px solid rgb(var(--accent-1) / 0.3); */
    overflow: hidden;
}

.radio-iframe {
    width: 100% !important;
    height: 100% !important;
    max-height: 250px !important;
    border-radius: 8px !important;
    background: #000 !important;
    border: none !important;
    display: block !important;
    margin: 0 !important;
    padding: 0 !important;
    line-height: 0 !important;
}

/* Override any global iframe styles for radio */
.winbox .radio-iframe-container .radio-iframe {
    width: 100% !important;
    height: 100% !important;
    max-height: 250px !important;
    border-radius: 8px !important;
    background: #000 !important;
    border: none !important;
    position: relative !important;
    display: block !important;
    margin: 0 !important;
    padding: 0 !important;
    line-height: 0 !important;
}

/* Override WinBox's .winbox iframe rule specifically for radio */
.winbox iframe.radio-iframe {
    position: relative !important;
    display: block !important;
    margin: 0 !important;
    padding: 0 !important;
    line-height: 0 !important;
}

.player-info {
    text-align: center;
    margin-top: 15px;
}

.player-info p {
    color: #888;
    font-size: 0.8em;
    font-style: italic;
    margin: 0;
}

/* Responsive Design */
@media (max-width: 768px) {
    .desktop-icons {
        top: 10px;
        left: 10px;
        gap: 15px;
    }
    
    .desktop-icon {
        padding: 10px;
    }
    
    .icon {
        font-size: 2em;
    }
    
    .icon-label {
        font-size: 0.7em;
        max-width: 60px;
    }
    
    .start-menu {
        width: 250px;
        left: 10px;
    }
    
    .taskbar {
        padding: 0 10px;
    }
    .status-indicator {
        display: none;
    }

}

/* Translucent chrome so the wallpaper / visualizer shows through. Applied at
   all times. These come after the base rules, so they win by source order. */
.desktop-background-overlay {
    background: rgba(0, 0, 0, 0.05);
}


.start-menu {
    background: rgba(0, 0, 0, 0.65);
}

.desktop-icon {
    background: rgba(0, 0, 0, 0.12);
}

/* .taskbar-app {
    background: rgb(var(--accent-1) / 0.06);
} */

/* "Click to enable audio" prompt (autoplay blocked) */
#audio-enable-prompt {
    position: fixed;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9000;
    padding: 12px 24px;
    background: rgba(0, 0, 0, 0.9);
    border: 2px solid rgb(var(--accent-1));
    border-radius: 10px;
    color: rgb(var(--accent-1));
    font-family: var(--b-font-display);
    font-size: 0.95em;
    letter-spacing: 1px;
    text-shadow: 0 0 8px rgb(var(--accent-1) / 0.6);
    pointer-events: none; /* clicks pass through; the document listener still fires */
    animation: audioPromptPulse 1.5s ease-in-out infinite;
}

@keyframes audioPromptPulse {
    0%, 100% { box-shadow: 0 0 18px rgb(var(--accent-1) / 0.4); }
    50%      { box-shadow: 0 0 32px rgb(var(--accent-1) / 0.85); }
}

/* First-load setup picker */
.setup-picker {
    position: fixed;
    inset: 0;
    z-index: 99999;
    background: rgba(0, 0, 0, 0.92);
    backdrop-filter: blur(8px);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Scroll rather than clip if the questions outgrow a short viewport. */
    overflow-y: auto;
    padding: 24px 0;
    font-family: var(--b-font-mono);
    color: rgb(var(--accent-1));
    transition: opacity 0.5s ease;
}

.setup-picker.setup-done {
    opacity: 0;
    pointer-events: none;
}

.setup-content {
    text-align: center;
    width: 90%;
    max-width: 480px;
}

.setup-logo {
    font-family: var(--b-font-display);
    font-size: 2.4em;
    font-weight: 900;
    letter-spacing: 4px;
    text-shadow: 0 0 20px rgb(var(--accent-1) / 0.7);
}

.setup-sub {
    color: rgb(var(--accent-2));
    letter-spacing: 2px;
    margin-bottom: 30px;
    font-size: 0.9em;
}

.setup-question { margin-bottom: 22px; }

.setup-label {
    font-family: var(--b-font-display);
    color: rgb(var(--accent-3));
    text-align: left;
    margin-bottom: 10px;
    letter-spacing: 1px;
    font-size: 1.2em;
}

.setup-options { display: flex; gap: 16px; border-radius: 12px; transition: box-shadow 0.2s ease; }
/* The keyboard-selected question (↑/↓) glows so you can see where you are. */
.setup-options.kb-active { box-shadow: 0 0 0 1px rgb(var(--accent-1) / 0.4), 0 0 18px rgb(var(--accent-1) / 0.18); }

.setup-card {
    flex: 1;
    background: rgba(0, 0, 0, 0.5);
    border: 2px solid rgb(var(--accent-1) / 0.3);
    border-radius: 12px;
    padding: 18px;
    color: var(--b-text);
    cursor: pointer;
    font-family: var(--b-font-display);
    transition: all 0.2s ease;
    /* Spring in, staggered by --i (set in showSetupPicker). */
    animation: setupCardIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
    animation-delay: calc(var(--i, 0) * 55ms);
}
@keyframes setupCardIn {
    0%   { opacity: 0; transform: translateY(26px) scale(0.84); }
    60%  { opacity: 1; transform: translateY(-4px) scale(1.03); }
    100% { opacity: 1; transform: none; }
}

.setup-card:hover {
    border-color: rgb(var(--accent-1));
    background: rgb(var(--accent-1) / 0.08);
}

.setup-card.selected {
    border-color: rgb(var(--accent-1));
    background: rgb(var(--accent-1) / 0.15);
    box-shadow: 0 0 20px rgb(var(--accent-1) / 0.4);
    color: #fff;
}

.setup-card-preview {
    height: 90px;
    border-radius: 8px;
    overflow: hidden;
    margin-bottom: 10px;
    background: linear-gradient(135deg, #001a1a, #0a0020);
    display: flex;
    align-items: center;
    justify-content: center;
}

.setup-card-preview.emoji { font-size: 2.4em; }

.setup-card-preview img,
.setup-card-preview canvas {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Square icon previews (radio stations): fit to the box height and keep their
   aspect ratio instead of cropping to the rectangle's width. */
.setup-card-preview.icon img {
    width: auto;
    object-fit: contain;
}

.setup-card-name { font-family: var(--b-font-display); }

.setup-enter {
    margin-top: 14px;
    padding: 14px 40px;
    background: linear-gradient(45deg, rgb(var(--accent-1) / 0.15), rgb(var(--accent-2) / 0.15));
    border: 2px solid rgb(var(--accent-1));
    color: rgb(var(--accent-1));
    border-radius: 10px;
    font-family: var(--b-font-display);
    font-weight: bold;
    font-size: 1.1em;
    letter-spacing: 2px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.setup-enter:hover {
    background: linear-gradient(45deg, rgb(var(--accent-1) / 0.3), rgb(var(--accent-2) / 0.3));
    box-shadow: 0 0 20px var(--b-glow);
    transform: translateY(-2px);
}

.setup-hint {
    margin-top: 16px;
    font-size: 0.78em;
    line-height: 1.5;
    color: rgb(var(--accent-1) / 0.5);
    letter-spacing: 0.5px;
    max-width: 380px;
    margin-left: auto;
    margin-right: auto;
}

/* Boot screen */
.boot-screen {
    position: fixed;
    inset: 0;
    z-index: 100000;
    background: #000;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--b-font-mono);
    color: rgb(var(--accent-1));
    transition: opacity 0.6s ease;
    overflow: hidden;
}

.boot-screen.boot-done {
    opacity: 0;
    pointer-events: none;
}

.boot-content {
    width: 90%;
    max-width: 600px;
    padding: 20px;
    position: relative;
    z-index: 1;
}

.boot-logo {
    font-family: var(--b-font-display);
    font-size: 3em;
    font-weight: 900;
    color: rgb(var(--accent-1));
    text-shadow: 0 0 20px rgb(var(--accent-1) / 0.7);
    letter-spacing: 4px;
    margin-bottom: 6px;
}

.boot-sub {
    color: rgb(var(--accent-2));
    text-shadow: 0 0 10px rgb(var(--accent-2) / 0.5);
    letter-spacing: 2px;
    margin-bottom: 24px;
    font-size: 0.9em;
}

.boot-log {
    min-height: 200px;
    font-size: 0.95em;
    line-height: 1.7;
}

.boot-line {
    opacity: 0;
    animation: bootLineIn 0.2s ease forwards;
    white-space: pre;
}

.boot-line .ok {
    color: rgb(var(--b-ok));
    text-shadow: 0 0 6px rgb(var(--b-ok) / 0.5);
}

@keyframes bootLineIn {
    from { opacity: 0; transform: translateX(-6px); }
    to { opacity: 1; transform: none; }
}

.boot-progress {
    margin-top: 24px;
    height: 6px;
    background: rgb(var(--accent-1) / 0.12);
    border: 1px solid rgb(var(--accent-1) / 0.4);
    border-radius: 3px;
    overflow: hidden;
}

.boot-progress-fill {
    height: 100%;
    width: 0;
    background: linear-gradient(90deg, rgb(var(--accent-1)), rgb(var(--accent-2)));
    box-shadow: 0 0 12px rgb(var(--accent-1) / 0.6);
    animation: bootProgress 3s ease-in-out forwards;
}

@keyframes bootProgress {
    from { width: 0; }
    to { width: 100%; }
}

.boot-hint {
    margin-top: 18px;
    font-size: 0.8em;
    color: rgb(var(--accent-1) / 0.4);
    text-align: center;
    letter-spacing: 1px;
}

/* CRT scanlines over the whole boot screen */
.boot-screen::after {
    content: '';
    position: absolute;
    inset: 0;
    background: repeating-linear-gradient(
        to bottom,
        rgb(var(--accent-1) / 0.04) 0px,
        rgb(var(--accent-1) / 0.04) 1px,
        transparent 1px,
        transparent 3px
    );
    pointer-events: none;
    z-index: 2;
}

/* ---------------------------------------------------------------------------
   Global tooltip — opt in by putting data-tooltip="..." on any element. Shows a
   themed pill above the element on hover. Preferred over the native title=
   attribute (which is slow to appear and can't be styled). An empty/absent
   value renders nothing, so toggling the attribute off hides the tooltip.
   Uses ::before so it never collides with elements that already use ::after
   (e.g. the dock tiles' running-indicator dot).
   --------------------------------------------------------------------------- */
[data-tooltip] {
    position: relative;
}

[data-tooltip]:not([data-tooltip=""])::before {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    background: rgba(0, 0, 0, 0.92);
    color: rgb(var(--accent-1));
    border: 1px solid rgb(var(--accent-1));
    border-radius: 6px;
    padding: 4px 8px;
    font-family: var(--b-font-display);
    font-size: 11px;
    line-height: 1;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease, transform 0.15s ease;
    box-shadow: 0 0 12px rgb(var(--accent-1) / 0.4);
    z-index: 10000;
}

[data-tooltip]:not([data-tooltip=""]):hover::before {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* ── Mobile mode ──────────────────────────────────────────────────────────
   Babelcom is a desktop art piece; on a phone we make it *kind of* usable by
   pinning every window full-screen and stripping the taskbar to just the
   centered Babelcom button — you navigate with the omnibox. This is the visual
   half — openApp() also opens apps maximised (see app.js). The breakpoint
   matches `isMobile` (innerWidth <= 768) there. */
@media (max-width: 768px) {
    /* Taskbar: strip to just the Babelcom button, centered. The dock and system
       tray don't fit a phone — navigation is the omnibox (tap Babelcom → search
       apps / documents / features). */
    .taskbar-center,
    .taskbar-right { display: none; }
    .taskbar-left {
        flex: 1;
        justify-content: center;
    }
    /* No spacebar on a phone; the button is tapped, so drop the "space" nudge. */
    .start-key { display: none; }

    /* "Maximised at all times": force every window to fill the screen above the
       50px taskbar. CSS !important overrides WinBox's inline left/top/width/
       height, so this holds even if a window's internal state isn't maximised
       (e.g. after a double-tap), and it tracks rotation/resize for free. */
    .winbox {
        left: 0 !important;
        right: 0 !important;
        top: 0 !important;
        bottom: 50px !important;
        width: auto !important;
        height: auto !important;
        border-radius: 0 !important;
    }
    /* No moving, resizing, maximise-toggle or fullscreen on mobile. */
    .winbox .wb-body ~ div { display: none !important; } /* resize handles */
    .wb-max,
    .wb-full { display: none !important; }
    .wb-drag { cursor: default !important; }
}
/* ═══════════════════════════════════════════════════════════════════════════
   Themes. Each theme is a body[data-theme="..."] token override plus a few
   targeted rules. The shell derives the theme from the wallpaper's top-level
   category (THEME_FOR_CATEGORY in app.js); the console can override it via
   window.babel.theme(...). No attribute = the default neon above.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Void ── worn with the Empty (looping-video) moods. Subtractive: the neon
   drains to dim grey, every glow goes out, the chrome recedes toward black and
   thins so the desktop politely ceases to exist. Also a perf win: backdrop
   blur is off while a video wallpaper is animating behind everything. */
body[data-theme="void"] {
    --accent-1: 158 163 168;
    --accent-2: 110 114 120;
    --accent-3: 138 140 146;
    --accent-max: 146 148 156;
    --accent-close: 158 148 152;
    --b-ok: 148 152 156;
    --b-violet: 28 28 32;
    --b-violet-focus: 40 40 46;
    --b-text: #b6b9bd;
    --b-text-dim: rgba(182, 185, 189, 0.72);
    --b-surface: rgba(4, 4, 6, 0.32);
    --b-surface-taskbar: rgba(4, 4, 6, 0.40);
    --b-glow: transparent;
    --b-blur: 0px;
    /* Sharp, austere: negative space has no rounded corners. Windows go fully
       square — the biggest single tell that this isn't the default OS. */
    --b-radius: 2px;
    --b-radius-lg: 3px;
    --b-window-radius: 0px;
    /* One recessive monospace for everything — the display face stops shouting
       in Orbitron; the desktop reads like a dead terminal. */
    --b-font-display: 'Share Tech Mono', monospace;
}

/* Kill the backdrop passes outright — even blur(0) saturate() forces the
   compositor to re-filter the video wallpaper under every surface. */
body[data-theme="void"] .taskbar,
body[data-theme="void"] .winbox,
body[data-theme="void"] .start-menu,
body[data-theme="void"] .desktop-icon {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Shadows keep only their plain black depth — no neon bloom, no RGB fringe. */
body[data-theme="void"] .taskbar {
    border-top-color: rgba(255, 255, 255, 0.10);
    box-shadow: 0 -8px 22px rgba(0, 0, 0, 0.25);
}
body[data-theme="void"] .winbox {
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
}
body[data-theme="void"] .winbox.focus {
    border-color: rgba(220, 224, 228, 0.30);
    box-shadow: 0 16px 44px rgba(0, 0, 0, 0.60);
}
body[data-theme="void"] .winbox.focus .wb-header {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
body[data-theme="void"] .start-menu {
    box-shadow: 0 0 30px rgba(0, 0, 0, 0.50);
}
body[data-theme="void"] .winbox.focus .wb-title {
    color: #cfd2d6;
}
body[data-theme="void"] .wb-title {
    text-shadow: none;
}
body[data-theme="void"] .status-dot,
body[data-theme="void"] .taskbar-app.running::after,
body[data-theme="void"] .taskbar-app.focused::after {
    box-shadow: none;
}
body[data-theme="void"] .taskbar-fullscreen img {
    filter: brightness(0) invert(0.75);
}

/* No text glow anywhere the chrome speaks. */
body[data-theme="void"] .clock,
body[data-theme="void"] .taskbar-metric,
body[data-theme="void"] .icon,
body[data-theme="void"] .icon-label,
body[data-theme="void"] .start-menu-header h3,
body[data-theme="void"] .app-window h1,
body[data-theme="void"] .app-window h2,
body[data-theme="void"] .app-window h3,
body[data-theme="void"] .app-window p,
body[data-theme="void"] [data-tooltip]:not([data-tooltip=""])::before {
    text-shadow: none;
}
body[data-theme="void"] [data-tooltip]:not([data-tooltip=""])::before {
    box-shadow: none;
}

/* Window controls: no orbs. Just the bare white WinBox glyphs, dimmed, with no
   circle, border, fill or glow — the least chrome that still works. They come
   up to full opacity on hover so the buttons still read as interactive. */
body[data-theme="void"] .wb-control > span {
    border: none;
    background-color: transparent;
    border-radius: 0;
    opacity: 0.45;
    background-size: 12px auto;
}
body[data-theme="void"] .wb-control > span:hover {
    background-color: transparent;
    box-shadow: none;
    opacity: 0.95;
    transform: none;
}

/* ── Anime ── worn with the Anime moods. Cheerful candy-shell desktop: solid
   opaque pastel-pink surfaces, thick crisp borders, hard offset shadows like
   a sticker sheet, zero glow, zero scanlines, zero blur. Late-90s LiveJournal
   energy. Text flips dark — every hardcoded-dark chrome surface flips light
   with it below. */
body[data-theme="anime"] {
    --accent-1: 219 39 119;      /* hot bubblegum pink — the workhorse */
    --accent-2: 147 51 234;      /* bubblegum purple */
    --accent-3: 190 24 93;       /* deep raspberry (clock, h3) */
    --accent-max: 168 85 247;    /* candy violet orb */
    --accent-close: 244 63 94;   /* strawberry orb */
    --b-ok: 22 163 74;           /* leaf green status light */
    --b-violet: 252 231 243;     /* gradient stops go pastel */
    --b-violet-focus: 251 207 232;
    --b-text: #3b2b45;           /* dark plum on light surfaces */
    --b-text-dim: rgba(59, 43, 69, 0.66);
    --b-surface: #fff0f7;        /* OPAQUE lavender-blush glass — no glass */
    --b-surface-taskbar: #ffe4f1;
    --b-glow: transparent;       /* flat: every var glow goes out */
    --b-blur: 0px;
    --b-panel: #ffffff;
    --b-panel-strong: #ffffff;
    --b-panel-soft: rgba(255, 255, 255, 0.65);
    /* Bubbly candy cards, but a blocky near-square sticker WINDOW — the contrast
       (sharp sticker frame, round jelly cards inside) is the joke. */
    --b-radius: 12px;
    --b-radius-lg: 18px;
    --b-window-radius: 2px;
    /* Rounded kawaii type. Fredoka if the woff2 is on disk; Comic Sans MS is a
       genuinely on-brand fallback that ships on most Win/Mac machines. */
    --b-font-display: 'Fredoka', 'Comic Sans MS', 'Baloo 2', system-ui, cursive;
    --b-font-mono: 'Fredoka', 'Comic Sans MS', 'Baloo 2', system-ui, cursive;
}

/* Opaque surfaces make the backdrop passes pure cost — kill them. */
body[data-theme="anime"] .taskbar,
body[data-theme="anime"] .winbox,
body[data-theme="anime"] .start-menu,
body[data-theme="anime"] .desktop-icon,
body[data-theme="anime"] .setup-picker {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Flat opaque taskbar with a thick candy top edge. No scanlines, no sweep. */
body[data-theme="anime"] .taskbar {
    background: var(--b-surface-taskbar);
    border-top: 2px solid rgb(var(--accent-2));
    box-shadow: 0 -4px 12px rgba(59, 43, 69, 0.18);
}

/* Blocky windows: near-square corners, thick borders, hard offset shadow. */
body[data-theme="anime"] .winbox {
    background: var(--b-surface);
    border: 2px solid rgb(var(--accent-2) / 0.75);
    box-shadow: 5px 5px 0 rgba(59, 43, 69, 0.20);
}
body[data-theme="anime"] .winbox.focus {
    border-color: rgb(var(--accent-1));
    box-shadow: 7px 7px 0 rgba(219, 39, 119, 0.30);
}
body[data-theme="anime"] .wb-header {
    background: #fce7f3; /* flat pastel — no scanlines, no gradient */
    border-bottom: 2px solid rgb(var(--accent-2) / 0.55);
    box-shadow: none;
}
body[data-theme="anime"] .winbox.focus .wb-header {
    background: #fbcfe8;
    border-bottom-color: rgb(var(--accent-1));
    box-shadow: none;
}

/* Titles go dark on the light titlebar; no neon halo. */
body[data-theme="anime"] .wb-title {
    color: rgba(59, 43, 69, 0.55);
    text-shadow: none;
}
body[data-theme="anime"] .winbox.focus .wb-title {
    color: #9d174d;
    text-shadow: none;
}

/* Window controls: the anime desktop's loudest tell. NOT the neon orbs — big
   chunky sticker buttons that nearly fill the 35px header. Solid candy fill in
   each button's own colour (WinBox's glyphs are white SVGs, invisible on a
   pastel header), a crisp dark keyline like a die-cut sticker, square with only
   a hair of rounding. The white glyph is scaled up to match the bigger tile. */
body[data-theme="anime"] .wb-control > span.wb-min  { color: rgb(var(--accent-1)); }
body[data-theme="anime"] .wb-control > span.wb-max,
body[data-theme="anime"] .wb-control > span.wb-full { color: rgb(var(--accent-max)); }
body[data-theme="anime"] .wb-control > span.wb-close { color: rgb(var(--accent-close)); }
body[data-theme="anime"] .wb-control > span {
    /* Fill the header: 27px tile in a 35px bar (box-sizing: border-box, so the
       2px keyline is inside the 27px). Vertically centred by the 4px top margin. */
    width: 27px;
    height: 27px;
    margin: 4px 3px 0;
    background-color: currentColor;
    border: 2px solid rgba(59, 43, 69, 0.6); /* die-cut sticker keyline */
    border-radius: 5px; /* blocky, not orb */
    background-size: 14px auto; /* scale the white glyph up to the bigger tile */
    opacity: 1; /* candy stickers don't dim — they're always fully saturated */
}
/* Inactive windows keep full-strength stickers too (override the global 0.4
   dim), so the chunky controls read as a deliberate style, not a bug. */
body[data-theme="anime"] .winbox:not(.focus) .wb-control > span:not(:hover) {
    opacity: 0.85;
}
body[data-theme="anime"] .wb-control > span.wb-min:hover,
body[data-theme="anime"] .wb-control > span.wb-max:hover,
body[data-theme="anime"] .wb-control > span.wb-full:hover,
body[data-theme="anime"] .wb-control > span.wb-close:hover {
    background-color: currentColor;
    box-shadow: 2px 2px 0 rgba(59, 43, 69, 0.30);
}

/* Chrome that is hardcoded dark elsewhere flips light. */
body[data-theme="anime"] .start-menu {
    background: #fff5fa;
    border: 2px solid rgb(var(--accent-2));
    border-radius: 6px;
    box-shadow: 6px 6px 0 rgba(59, 43, 69, 0.18);
}
body[data-theme="anime"] .desktop-icon {
    background: rgba(255, 255, 255, 0.62);
    border-radius: 4px;
}
body[data-theme="anime"] .desktop-icon:hover {
    background: #ffffff;
    box-shadow: 4px 4px 0 rgba(219, 39, 119, 0.25);
}
body[data-theme="anime"] .setup-picker { background: rgba(255, 240, 247, 0.97); }
body[data-theme="anime"] .setup-card { background: #ffffff; }
body[data-theme="anime"] .setup-card.selected {
    color: var(--b-text);
    box-shadow: 4px 4px 0 rgba(219, 39, 119, 0.25);
}
body[data-theme="anime"] .setup-card-preview { background: linear-gradient(135deg, #ffe4f1, #ede9fe); }
body[data-theme="anime"] .radio-container { background: #fff0f7; }
body[data-theme="anime"] [data-tooltip]:not([data-tooltip=""])::before {
    background: #ffffff;
    color: var(--b-text);
    border-color: rgb(var(--accent-1));
    border-radius: 3px;
    box-shadow: 3px 3px 0 rgba(59, 43, 69, 0.15);
}

/* Fullscreen tray icon: forced-white is invisible on pastel — go dark plum. */
body[data-theme="anime"] .taskbar-fullscreen img {
    filter: brightness(0) invert(0.28);
}
body[data-theme="anime"] .taskbar-fullscreen.active img {
    filter: brightness(0) invert(0.28) drop-shadow(0 0 6px rgb(var(--accent-1) / 0.9));
}

/* Flat means flat: every neon text/box glow goes out. */
body[data-theme="anime"] .clock,
body[data-theme="anime"] .taskbar-metric,
body[data-theme="anime"] .icon,
body[data-theme="anime"] .icon-label,
body[data-theme="anime"] .start-menu-header h3,
body[data-theme="anime"] .setup-logo,
body[data-theme="anime"] .setup-sub,
body[data-theme="anime"] .app-window h1,
body[data-theme="anime"] .app-window h2,
body[data-theme="anime"] .app-window h3,
body[data-theme="anime"] .app-window p,
body[data-theme="anime"] .app-window a {
    text-shadow: none;
}
body[data-theme="anime"] .status-dot,
body[data-theme="anime"] .taskbar-app.running::after,
body[data-theme="anime"] .taskbar-app.focused::after {
    box-shadow: none;
}

/* Candy scrollbars: solid pink thumb, milk track, no glow. */
body[data-theme="anime"] .wb-body::-webkit-scrollbar-track,
body[data-theme="anime"] .app-window::-webkit-scrollbar-track {
    background: rgb(var(--accent-1) / 0.10);
}
body[data-theme="anime"] .wb-body::-webkit-scrollbar-thumb,
body[data-theme="anime"] .app-window::-webkit-scrollbar-thumb {
    background: rgb(var(--accent-1));
    border: 2px solid #fff0f7;
    border-radius: 3px;
    box-shadow: none;
}
body[data-theme="anime"] .wb-body::-webkit-scrollbar-thumb:hover,
body[data-theme="anime"] .app-window::-webkit-scrollbar-thumb:hover {
    background: rgb(var(--accent-2));
    box-shadow: none;
}
body[data-theme="anime"] .wb-body,
body[data-theme="anime"] .app-window {
    scrollbar-color: rgb(var(--accent-1)) rgb(var(--accent-1) / 0.10);
}

/* ── Aero ── worn with the Aero (Frutiger/Vista) moods. The flagship: cool
   frosted white-blue glass, heavy backdrop blur, glossy top-half-lighter
   highlights on every chrome strip, soft white bloom instead of neon. */
body[data-theme="aero"] {
    --accent-1: 30 115 190;      /* Vista blue — readable on frosted glass */
    --accent-2: 34 139 74;       /* Frutiger leaf green */
    --accent-3: 12 90 150;       /* deep azure (clock, h3) */
    --accent-max: 70 130 200;    /* glass-blue orb */
    --accent-close: 205 60 70;   /* Vista close red */
    --b-ok: 60 170 80;
    --b-violet: 200 230 250;     /* gradient stops go pale sky */
    --b-violet-focus: 170 215 245;
    --b-text: #10314a;           /* deep slate blue on frosted glass */
    --b-text-dim: rgba(16, 49, 74, 0.70);
    --b-surface: rgba(236, 245, 252, 0.58);         /* frosted window glass */
    --b-surface-taskbar: rgba(228, 240, 250, 0.62);
    --b-glow: rgba(255, 255, 255, 0.85);            /* soft white bloom */
    --b-blur: 16px;                                 /* LEAN IN */
    --b-panel: rgba(255, 255, 255, 0.55);
    --b-panel-strong: rgba(252, 254, 255, 0.82);
    --b-panel-soft: rgba(255, 255, 255, 0.40);
    /* Glossy Vista glass is soft and round — windows included. */
    --b-radius: 12px;
    --b-radius-lg: 20px;
    --b-window-radius: 10px;
    /* Frutiger-Aero typography: geometric-rounded Quicksand, falling back to real
       Segoe UI on Windows. No Orbitron — that alone stops it reading as the same
       sci-fi OS. */
    --b-font-display: 'Quicksand', 'Segoe UI', system-ui, -apple-system, sans-serif;
    --b-font-mono: 'Quicksand', 'Segoe UI', system-ui, -apple-system, sans-serif;
}

/* Taskbar: glass with the classic gloss break — lighter above the midline,
   deeper aqua below — over the frosted tint. White hairline top edge. */
body[data-theme="aero"] .taskbar {
    background:
        linear-gradient(180deg,
            rgba(255, 255, 255, 0.72) 0%,
            rgba(255, 255, 255, 0.30) 47%,
            rgba(160, 205, 240, 0.14) 53%,
            rgba(140, 195, 235, 0.30) 100%),
        var(--b-surface-taskbar);
    border-top: 1px solid rgba(255, 255, 255, 0.85);
    box-shadow:
        0 -8px 22px rgba(90, 140, 190, 0.28),
        inset 0 1px 0 rgba(255, 255, 255, 0.95);
}

/* Windows: double-walled glass edge (white rim + inner hairline), rounder
   corners, soft cool shadow. Focus blooms white-blue, no RGB fringe. */
body[data-theme="aero"] .winbox {
    border: 1px solid rgba(255, 255, 255, 0.70);
    box-shadow:
        0 14px 40px rgba(30, 70, 110, 0.35),
        inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}
body[data-theme="aero"] .winbox.focus {
    border-color: rgba(255, 255, 255, 0.95);
    box-shadow:
        0 18px 50px rgba(30, 70, 110, 0.42),
        0 0 26px rgba(150, 205, 245, 0.60),
        inset 0 0 0 1px rgba(255, 255, 255, 0.55);
}

/* Titlebars: no scanlines — a glossy shine that brightens on focus. */
body[data-theme="aero"] .wb-header {
    background: linear-gradient(180deg,
        rgba(255, 255, 255, 0.80) 0%,
        rgba(255, 255, 255, 0.38) 47%,
        rgba(190, 222, 248, 0.18) 53%,
        rgba(170, 212, 244, 0.34) 100%);
    border-bottom: 1px solid rgba(255, 255, 255, 0.75);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95);
}
body[data-theme="aero"] .winbox.focus .wb-header {
    background: linear-gradient(180deg,
        rgba(255, 255, 255, 0.92) 0%,
        rgba(240, 250, 255, 0.55) 47%,
        rgba(170, 215, 248, 0.30) 53%,
        rgba(150, 205, 245, 0.48) 100%);
    border-bottom-color: rgba(255, 255, 255, 0.95);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 1),
        inset 0 0 18px rgba(255, 255, 255, 0.35);
}

/* Vista title text: dark ink with the trademark white bloom behind it. */
body[data-theme="aero"] .wb-title {
    color: rgba(16, 49, 74, 0.55);
    text-shadow: 0 0 6px rgba(255, 255, 255, 0.8);
}
body[data-theme="aero"] .winbox.focus .wb-title {
    color: #0b2b45;
    text-shadow:
        0 0 6px rgba(255, 255, 255, 1),
        0 0 14px rgba(255, 255, 255, 0.9);
}

/* Control orbs: bigger, glossy round glass buttons — plump Vista candy. A solid
   colour tint (background-image is taken by the glyph, so the gloss lives in the
   inset highlights) with a bright top edge and a soft blue drop. */
body[data-theme="aero"] .wb-control > span {
    width: 22px;
    height: 22px;
    margin: 6px 4px 0;
    background-size: 11px auto;
    border-color: rgba(255, 255, 255, 0.85);
    box-shadow:
        inset 0 2px 2px rgba(255, 255, 255, 0.85),
        inset 0 -2px 3px rgba(30, 70, 110, 0.25),
        0 1px 3px rgba(30, 70, 110, 0.35);
}
body[data-theme="aero"] .wb-control > span.wb-min  { background-color: rgb(var(--accent-1) / 0.80); }
body[data-theme="aero"] .wb-control > span.wb-max,
body[data-theme="aero"] .wb-control > span.wb-full { background-color: rgb(var(--accent-max) / 0.80); }
body[data-theme="aero"] .wb-control > span.wb-close { background-color: rgb(var(--accent-close) / 0.90); }
body[data-theme="aero"] .wb-control > span.wb-min:hover {
    background-color: rgb(var(--accent-1));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 0 10px rgba(255, 255, 255, 0.9);
}
body[data-theme="aero"] .wb-control > span.wb-max:hover,
body[data-theme="aero"] .wb-control > span.wb-full:hover {
    background-color: rgb(var(--accent-max));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 0 10px rgba(255, 255, 255, 0.9);
}
body[data-theme="aero"] .wb-control > span.wb-close:hover {
    background-color: rgb(var(--accent-close));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 0 12px rgba(255, 255, 255, 0.95);
}

/* Dark-by-default chrome flips to frosted light glass. */
body[data-theme="aero"] .start-menu {
    background: linear-gradient(180deg,
            rgba(255, 255, 255, 0.55) 0%,
            rgba(255, 255, 255, 0.10) 30%,
            rgba(200, 230, 250, 0.15) 100%),
        rgba(238, 246, 253, 0.82);
    border: 1px solid rgba(255, 255, 255, 0.85);
    box-shadow: 0 0 30px rgba(90, 140, 190, 0.40), inset 0 1px 0 rgba(255, 255, 255, 0.95);
}
body[data-theme="aero"] .desktop-icon {
    background: rgba(255, 255, 255, 0.30);
}
body[data-theme="aero"] .desktop-icon:hover {
    background: rgba(255, 255, 255, 0.50);
    box-shadow: 0 0 20px rgba(255, 255, 255, 0.75);
}
body[data-theme="aero"] .setup-picker { background: rgba(228, 241, 250, 0.95); }
body[data-theme="aero"] .setup-card { background: rgba(255, 255, 255, 0.72); }
body[data-theme="aero"] .setup-card.selected {
    color: var(--b-text);
    box-shadow: 0 0 20px rgba(255, 255, 255, 0.9);
}
body[data-theme="aero"] .setup-card-preview { background: linear-gradient(135deg, #dceefb, #eaf7ef); }
body[data-theme="aero"] .radio-container { background: rgba(240, 248, 255, 0.78); }
body[data-theme="aero"] [data-tooltip]:not([data-tooltip=""])::before {
    background: rgba(250, 253, 255, 0.96);
    color: #0b2b45;
    border-color: rgba(130, 190, 240, 0.95);
    box-shadow: 0 4px 14px rgba(80, 130, 180, 0.35);
}

/* Fullscreen tray icon: dark ink with a white halo, like the title text. */
body[data-theme="aero"] .taskbar-fullscreen img {
    filter: brightness(0) invert(0.25) drop-shadow(0 0 6px rgba(255, 255, 255, 0.9));
}
body[data-theme="aero"] .taskbar-fullscreen.active img {
    filter: brightness(0) invert(0.25) drop-shadow(0 0 8px rgb(var(--accent-2) / 0.9));
}

/* Neon halos become subtle white lifts (or nothing) on the light glass. */
body[data-theme="aero"] .clock,
body[data-theme="aero"] .taskbar-metric {
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.75);
}
body[data-theme="aero"] .icon-label,
body[data-theme="aero"] .setup-logo,
body[data-theme="aero"] .setup-sub,
body[data-theme="aero"] .app-window h2,
body[data-theme="aero"] .app-window h3,
body[data-theme="aero"] .app-window p {
    text-shadow: none;
}
body[data-theme="aero"] .app-window h1 {
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
}

/* Glossy glass scrollbars. */
body[data-theme="aero"] .wb-body::-webkit-scrollbar-track,
body[data-theme="aero"] .app-window::-webkit-scrollbar-track {
    background: rgba(255, 255, 255, 0.35);
}
body[data-theme="aero"] .wb-body::-webkit-scrollbar-thumb,
body[data-theme="aero"] .app-window::-webkit-scrollbar-thumb {
    background: linear-gradient(180deg, #d9ecfa 0%, #a9cfee 48%, #86b9e4 52%, #9cc6ea 100%);
    border: 2px solid rgba(255, 255, 255, 0.75);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95);
}
body[data-theme="aero"] .wb-body::-webkit-scrollbar-thumb:hover,
body[data-theme="aero"] .app-window::-webkit-scrollbar-thumb:hover {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0 0 8px rgba(255, 255, 255, 0.9);
}
body[data-theme="aero"] .wb-body,
body[data-theme="aero"] .app-window {
    scrollbar-color: #86b9e4 rgba(255, 255, 255, 0.35);
}

/* ── Royale family ── the shared gloss machinery. `royale` is the XP Media
   Center Energy-Blue skin (console-only: babel.theme('royale')); `royale-noir`
   is the leaked black-purple build that shipped haunted (worn with Spook
   moods). Both are fully opaque, blur-free, and share these gloss rules; each
   variant just supplies the --ryl-* gradient/edge tokens below. */
body[data-theme^="royale"] {
    --b-blur: 0px;
    /* XP plastic: medium, even rounding — chunky rounded windows included. */
    --b-radius: 8px;
    --b-radius-lg: 14px;
    --b-text-dim: rgba(210, 203, 226, 0.66);
    --b-window-radius: 10px;
    /* Techy 2000s Media-Center HUD type — squarish Chakra Petch, falling back to
       XP's Tahoma / system sans. Not Orbitron. */
    --b-font-display: 'Chakra Petch', 'Tahoma', 'Segoe UI', system-ui, sans-serif;
    --b-font-mono: 'Chakra Petch', 'Tahoma', 'Segoe UI', system-ui, sans-serif;
}
body[data-theme^="royale"] .taskbar,
body[data-theme^="royale"] .winbox,
body[data-theme^="royale"] .start-menu,
body[data-theme^="royale"] .desktop-icon,
body[data-theme^="royale"] .setup-picker {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Chunky glossy taskbar: highlight cap, hard midline break, deep base. */
body[data-theme^="royale"] .taskbar {
    background: var(--ryl-bar);
    border-top: 1px solid rgb(var(--ryl-edge) / 0.9);
    box-shadow:
        0 -8px 22px rgba(0, 0, 0, 0.35),
        inset 0 1px 0 rgba(255, 255, 255, var(--ryl-shine));
}

/* Titlebars: same gloss, brighter when focused. No scanlines. */
body[data-theme^="royale"] .wb-header {
    background: var(--ryl-hdr);
    border-bottom: 1px solid rgb(var(--ryl-edge) / 0.45);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, var(--ryl-shine));
}
body[data-theme^="royale"] .winbox.focus .wb-header {
    background: var(--ryl-hdr-focus);
    border-bottom-color: rgb(var(--ryl-edge) / 0.85);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, calc(var(--ryl-shine) + 0.15)),
        inset 0 0 16px rgb(var(--ryl-edge) / 0.15);
}

/* Chunky rounded, fully opaque windows; the bloom is plastic, not neon. */
body[data-theme^="royale"] .winbox {
    border: 1px solid rgb(var(--ryl-edge) / 0.55);
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
}
body[data-theme^="royale"] .winbox.focus {
    border-color: rgb(var(--ryl-edge) / 0.95);
    box-shadow:
        0 16px 44px rgba(0, 0, 0, 0.60),
        0 0 20px rgb(var(--ryl-edge) / 0.30);
}

/* XP-style embossed titles: solid ink with a 1px dark drop, no neon bloom. */
body[data-theme^="royale"] .wb-title {
    color: rgb(var(--ryl-title) / 0.60);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.55);
}
body[data-theme^="royale"] .winbox.focus .wb-title {
    color: rgb(var(--ryl-title));
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.65);
}

/* Start menu: opaque plastic panel in the variant's key. */
body[data-theme^="royale"] .start-menu {
    background: var(--ryl-menu);
    border: 2px solid rgb(var(--ryl-edge) / 0.8);
    box-shadow: 0 0 30px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, var(--ryl-shine));
}

/* Window controls: XP title buttons — rounded-rectangle plastic, grouped tight
   at the right, glossed with the variant's shine. Not orbs. The close button is
   wider and wears the close accent, the way XP separated it out. */
body[data-theme^="royale"] .wb-control > span {
    width: 26px;
    height: 20px;
    margin: 7px 2px 0;
    border-radius: 4px;
    border: 1px solid rgb(var(--ryl-edge) / 0.6);
    background-color: rgb(var(--ryl-edge) / 0.22);
    background-size: 12px auto;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, var(--ryl-shine));
    opacity: 1;
}
body[data-theme^="royale"] .wb-control > span.wb-close {
    width: 32px;
    background-color: rgb(var(--accent-close) / 0.85);
    border-color: rgb(var(--accent-close));
}
body[data-theme^="royale"] .wb-control > span:hover {
    background-color: rgb(var(--ryl-edge) / 0.45);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, calc(var(--ryl-shine) + 0.2));
    transform: none;
}
body[data-theme^="royale"] .wb-control > span.wb-close:hover {
    background-color: rgb(var(--accent-close));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
/* Inactive royale windows keep readable buttons (override the global 0.4 dim). */
body[data-theme^="royale"] .winbox:not(.focus) .wb-control > span:not(:hover) {
    opacity: 0.7;
}

/* Gloss, not neon: kill the CRT text halos; the clock gets an XP emboss. */
body[data-theme^="royale"] .icon,
body[data-theme^="royale"] .icon-label,
body[data-theme^="royale"] .start-menu-header h3,
body[data-theme^="royale"] .app-window h1,
body[data-theme^="royale"] .app-window h2,
body[data-theme^="royale"] .app-window h3,
body[data-theme^="royale"] .app-window p {
    text-shadow: none;
}
body[data-theme^="royale"] .clock,
body[data-theme^="royale"] .taskbar-metric {
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.55);
}

/* ── Royale ── Energy Blue: saturated glossy blue plastic, opaque everywhere. */
body[data-theme="royale"] {
    --accent-1: 120 200 245;     /* energy-blue highlight — the workhorse */
    --accent-2: 60 140 225;      /* mid royal blue */
    --accent-3: 170 220 250;     /* pale ice (clock on the blue bar) */
    --accent-max: 130 180 235;
    --accent-close: 230 90 80;   /* XP close red */
    --b-ok: 130 220 100;         /* XP green orb */
    --b-violet: 30 80 160;
    --b-violet-focus: 45 105 195;
    --b-text: #e8f3fc;
    --b-surface: #0e2a52;        /* OPAQUE deep energy blue */
    --b-surface-taskbar: #0b2344;
    --b-glow: rgb(120 200 245 / 0.40);
    /* Gloss machinery */
    --ryl-edge: 120 190 240;
    --ryl-shine: 0.50;
    --ryl-title: 255 255 255;
    --ryl-bar: linear-gradient(180deg, #3f8fd4 0%, #2668b8 47%, #17509e 53%, #123f84 100%);
    --ryl-hdr: linear-gradient(180deg, #5a94cf 0%, #3a6cb0 47%, #2a5694 53%, #234a80 100%);
    --ryl-hdr-focus: linear-gradient(180deg, #6cc0f0 0%, #2f7ad0 47%, #1b5cb4 53%, #164a94 100%);
    --ryl-menu: #123a6e;
}

/* ── Royale Noir ── the leaked build. Same plastic, wrong colours: near-black
   purple, dim violet accents, the shine barely there. Glossy but haunted. */
body[data-theme="royale-noir"] {
    --accent-1: 176 148 220;     /* dim violet — the workhorse */
    --accent-2: 130 95 180;
    --accent-3: 150 122 190;     /* dusk lavender clock */
    --accent-max: 140 110 190;
    --accent-close: 175 75 115;
    --b-ok: 152 128 200;         /* even the OK light reads violet here */
    --b-violet: 30 22 48;
    --b-violet-focus: 44 32 68;
    --b-text: #cfc6dd;
    --b-surface: #14101e;        /* OPAQUE near-black purple */
    --b-surface-taskbar: #0f0b18;
    --b-glow: rgb(150 100 220 / 0.22);
    /* Gloss machinery — the same shine, turned down to a smear. */
    --ryl-edge: 130 100 180;
    --ryl-shine: 0.16;
    --ryl-title: 210 196 232;
    --ryl-bar: linear-gradient(180deg, #3a2f52 0%, #241c3a 47%, #170f2a 53%, #100a1e 100%);
    --ryl-hdr: linear-gradient(180deg, #322947 0%, #221a35 47%, #171028 53%, #120c20 100%);
    --ryl-hdr-focus: linear-gradient(180deg, #4a3b68 0%, #2f2348 47%, #201536 53%, #180f2a 100%);
    --ryl-menu: #171126;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Launchpad — the search-first overlay that replaced the Start menu. A themed
   scrim (NO backdrop-filter — the wallpaper/visualiser animates behind it, and
   a live blur would recomposite every frame), a big themed search box, a row of
   app icons that rise/stagger in from the dock, a document results list, and a
   footer of system actions. All colours/fonts/radii come from the theme tokens,
   so it re-skins across every theme for free; only the scrim tint is set per
   theme below (dark themes get a dark wash, the light themes a light one).
   ═══════════════════════════════════════════════════════════════════════════ */

/* The Babelcom "start" pill is now a search affordance. */
.start-button svg.start-icon { width: 16px; height: 16px; color: rgb(var(--accent-1)); }

/* While the Launchpad is open, the desktop chrome (windows, taskbar, desktop
   icons) fades fully OUT — only the wallpaper shows behind the overlay, so it
   reads as a clean Launchpad takeover rather than a dimmed desktop peeking
   through. The wallpaper layers are separate fixed elements (z-index -2) and
   stay. Transitions live on the base rules so it eases both ways. */
/* WinBox mounts each window onto document.body (not #windowContainer), so the
   windows are hidden by targeting .winbox directly. */
#windowContainer, .desktop-icons { transition: opacity 0.3s ease; }
body.launchpad-open #windowContainer,
body.launchpad-open .desktop-icons,
body.launchpad-open .winbox,
body.launchpad-open .taskbar {
    opacity: 0;
    pointer-events: none;
}
/* Clippy (clippyjs default z-index 1000) sits below the z9500 overlay — lift him
   and his balloon above it while the Launchpad is open so he's visible reacting
   to your search rather than buried behind the scrim. */
body.launchpad-open .clippy,
body.launchpad-open .clippy-balloon {
    z-index: 9600 !important;
}

.launchpad {
    position: fixed;
    inset: 0;
    z-index: 9500;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px 20px;
    overflow-y: auto;
    /* Tint, not blackout — the desktop chrome is hidden, so this only needs to
       hold text contrast over the wallpaper while letting it read through. */
    background: rgba(6, 4, 16, 0.50);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}
.launchpad.open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity 0.28s ease;
}
/* Closing: held visible so the children can bounce OUT (keyframes below) while
   the scrim fades; JS strips .closing once the exit finishes. pointer-events off
   so the returning desktop takes clicks immediately. */
.launchpad.closing {
    opacity: 0;
    visibility: visible;
    pointer-events: none;
    transition: opacity 0.34s ease;
}

/* ── Over-exaggerated launchpad entrances ──────────────────────────────────
   Now that windows wobble, the omnibox has to match: every piece OVERSHOOTS its
   target and settles (a real "boing" a single eased transition can't do), the
   same renounce-structural-integrity energy as the wobbly windows. Applied as
   animations on the .open state; the base rules keep a plain transition so the
   close is a snappy collapse. ─────────────────────────────────────────────── */
@keyframes lpSearchIn {
    0%   { opacity: 0; transform: translateY(-64px) scale(0.72) rotate(-1.5deg); }
    55%  { opacity: 1; transform: translateY(12px)  scale(1.06) rotate(1deg); }
    76%  {             transform: translateY(-5px)  scale(0.985); }
    100% { opacity: 1; transform: translateY(0)     scale(1) rotate(0); }
}
@keyframes lpHintsIn {
    0%   { opacity: 0; transform: translateY(-20px) scale(0.8); }
    60%  { opacity: 1; transform: translateY(6px)   scale(1.05); }
    100% { opacity: 1; transform: translateY(0)     scale(1); }
}
@keyframes lpAppIn {
    0%   { opacity: 0; transform: translateY(84px)  scale(0.4)  rotate(7deg); }
    58%  { opacity: 1; transform: translateY(-16px) scale(1.14) rotate(-4deg); }
    78%  {             transform: translateY(5px)   scale(0.96) rotate(1.5deg); }
    100% { opacity: 1; transform: translateY(0)     scale(1)    rotate(0); }
}
@keyframes lpActionsIn {
    0%   { opacity: 0; transform: translateY(52px) scale(0.7); }
    56%  { opacity: 1; transform: translateY(-9px) scale(1.08); }
    78%  {             transform: translateY(3px)  scale(0.98); }
    100% { opacity: 1; transform: translateY(0)    scale(1); }
}

/* ── Bouncy EXITS ──────────────────────────────────────────────────────────
   A wind-up then a launch — the anticipation timing function (negative control
   point) makes each piece recoil the opposite way before flinging off, the
   mirror of the springy overshoot on the way in. So closing has as much
   character as opening. Applied on .closing; JS holds it long enough to run. ── */
@keyframes lpSearchOut  { from { opacity: 1; transform: translateY(0) scale(1) rotate(0); }  to { opacity: 0; transform: translateY(-58px) scale(0.66) rotate(-3deg); } }
@keyframes lpHintsOut   { from { opacity: 1; transform: translateY(0) scale(1); }            to { opacity: 0; transform: translateY(-22px) scale(0.78); } }
@keyframes lpAppOut     { from { opacity: 1; transform: translateY(0) scale(1) rotate(0); }  to { opacity: 0; transform: translateY(78px) scale(0.4) rotate(8deg); } }
@keyframes lpActionsOut { from { opacity: 1; transform: translateY(0) scale(1); }            to { opacity: 0; transform: translateY(46px) scale(0.66); } }

.launchpad.closing .lp-searchwrap { animation: lpSearchOut  0.42s cubic-bezier(0.5, -0.45, 0.75, 0.35) both; }
.launchpad.closing .lp-hints      { animation: lpHintsOut   0.36s cubic-bezier(0.5, -0.45, 0.75, 0.35) both; }
.launchpad.closing .lp-app        { animation: lpAppOut     0.42s cubic-bezier(0.5, -0.45, 0.75, 0.35) both; }
.launchpad.closing .lp-actions    { opacity: 1; } /* keep the strip up; its chips fly out themselves */
.launchpad.closing .lp-action     { animation: lpActionsOut 0.40s cubic-bezier(0.5, -0.45, 0.75, 0.35) both; }

.launchpad-inner {
    width: 100%;
    max-width: 760px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 26px;
}

/* Search box — themed like the app inputs, but the hero of the overlay. */
.lp-searchwrap {
    position: relative;
    width: 100%;
    max-width: 620px;
    /* Drops in from above as the app row rises to meet it. */
    opacity: 0;
    transform: translateY(-26px) scale(0.9);
    transition: opacity 0.3s ease, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.launchpad.open .lp-searchwrap {
    opacity: 1; transform: none;
    animation: lpSearchIn 0.66s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.lp-search-ico {
    position: absolute;
    left: 18px;
    top: 50%;
    transform: translateY(-50%);
    width: 22px;
    height: 22px;
    background-color: rgb(var(--accent-1));
    -webkit-mask: var(--b-prompt-glyph) center / contain no-repeat;
            mask: var(--b-prompt-glyph) center / contain no-repeat;
    pointer-events: none;
}
.lp-search {
    width: 100%;
    box-sizing: border-box;
    padding: 18px 20px 18px 52px;
    background: var(--b-surface);
    border: 1px solid rgb(var(--accent-1) / 0.5);
    border-radius: var(--b-radius, 8px);
    color: var(--b-text);
    font-family: var(--b-font-display);
    font-size: 1.4em;
    letter-spacing: 0.02em;
    box-shadow: 0 10px 44px rgba(0, 0, 0, 0.42);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.lp-search::placeholder { color: rgb(var(--accent-1) / 0.4); }
.lp-search:focus {
    outline: none;
    border-color: rgb(var(--accent-1));
    box-shadow: 0 10px 44px rgba(0, 0, 0, 0.42), 0 0 26px rgb(var(--accent-1) / 0.35);
}

/* App row — icons rise + scale in, staggered by --i (set inline per tile). */
.lp-apps {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px 4px;
    width: 100%;
}
.lp-app {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 9px;
    width: 108px;
    padding: 14px 6px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--b-radius-lg, 14px);
    color: var(--b-text);
    cursor: pointer;
    opacity: 0;
    transform: translateY(38px) scale(0.76);
    transition: opacity 0.34s ease, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
                background 0.15s ease, border-color 0.15s ease;
}
/* Stagger only on the way IN — on close every tile leaves at once (a snappy
   collapse), rather than dribbling out tile-by-tile. */
.launchpad.open .lp-app {
    opacity: 1;
    transform: none;
    animation: lpAppIn 0.68s cubic-bezier(0.34, 1.56, 0.64, 1) both;
    animation-delay: calc(var(--i, 0) * 44ms);
}
.lp-app:hover { background: rgb(var(--accent-1) / 0.10); border-color: rgb(var(--accent-1) / 0.4); }
.lp-app.dim { opacity: 0.28; }
.lp-app-icon img {
    width: 60px;
    height: 60px;
    object-fit: contain;
    display: block;
    filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.5));
}
.lp-app-name { font-family: var(--b-font-display); font-size: 0.92em; text-align: center; }

/* Document results (federated search). */
.lp-results {
    width: 100%;
    max-width: 620px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 42vh;
    overflow-y: auto;
}
.lp-results-head {
    font-family: var(--b-font-display);
    font-size: 0.72em;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: rgb(var(--accent-3));
    margin: 2px 2px 4px;
}
.lp-doc {
    display: flex;
    align-items: center;
    gap: 12px;
    text-align: left;
    width: 100%;
    padding: 10px 14px;
    background: var(--b-surface);
    border: 1px solid rgb(var(--accent-1) / 0.18);
    border-radius: var(--b-radius, 8px);
    color: var(--b-text);
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.lp-doc:hover { background: rgb(var(--accent-1) / 0.12); border-color: rgb(var(--accent-1) / 0.6); }
.lp-doc-icon { color: rgb(var(--accent-1)); flex-shrink: 0; display: flex; align-self: center; }
.lp-doc-ico { width: 26px; height: 26px; display: block; }
.lp-doc-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.lp-doc-title { color: rgb(var(--accent-1)); font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-doc-snippet { color: var(--b-text-dim); font-size: 0.82em; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
/* Type badge on the right — the federated-search benefit: which folder each hit
   is from (the Documents browser shows type as the folder, not per row). */
.lp-doc-type {
    flex-shrink: 0;
    align-self: center;
    font-size: 0.66em;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: rgb(var(--accent-3));
    border: 1px solid rgb(var(--accent-3) / 0.4);
    border-radius: 4px;
    padding: 3px 7px;
    white-space: nowrap;
}
/* Search-term highlight — same treatment as the Documents browser's mark.hl. */
.lp-results mark.hl { background: rgb(var(--accent-2) / 0.32); color: inherit; border-radius: 2px; padding: 0 1px; }
.lp-empty { color: var(--b-text-dim, rgba(224, 224, 224, 0.55)); font-style: italic; text-align: center; padding: 22px; }

/* System actions footer. */
/* The features row — a single horizontally-scrolling strip so MANY features can
   be surfaced for discovery without wrapping into a wall. Chips spring in
   staggered (via --i); the container just fades and holds the scroll. */
.lp-actions {
    display: flex;
    justify-content: flex-start;
    flex-wrap: nowrap;
    gap: 12px;
    width: 100%;
    max-width: 620px;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 10px 2px 8px; /* top room for the chips' upward overshoot */
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
    /* Fade both ends — signals "there's more, scroll me". */
    -webkit-mask: linear-gradient(90deg, transparent 0, #000 26px, #000 calc(100% - 26px), transparent 100%);
            mask: linear-gradient(90deg, transparent 0, #000 26px, #000 calc(100% - 26px), transparent 100%);
    opacity: 0;
    transition: opacity 0.3s ease;
}
.launchpad.open .lp-actions { opacity: 1; transition-delay: 0.12s; }
.lp-action {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 16px;
    border-radius: var(--b-radius, 8px);
    background: rgb(var(--accent-1) / 0.08);
    border: 1px solid rgb(var(--accent-1) / 0.35);
    color: rgb(var(--accent-1));
    font-family: var(--b-font-display);
    font-size: 0.9em;
    cursor: pointer;
    flex-shrink: 0;      /* don't squash chips in the scroller */
    white-space: nowrap;
    opacity: 0;
    transform: translateY(30px) scale(0.7);
    transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease,
                opacity 0.3s ease, transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.launchpad.open .lp-action {
    opacity: 1; transform: none;
    animation: lpActionsIn 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
    animation-delay: calc(var(--i, 0) * 40ms + 0.14s);
}
.lp-action:hover, .lp-action.match {
    background: rgb(var(--accent-1) / 0.2);
    border-color: rgb(var(--accent-1));
    box-shadow: 0 0 16px rgb(var(--accent-1) / 0.4);
}
/* Muted radio → the Mute chip wears the close accent, like the radio's own. */
.lp-action.muted {
    background: rgb(var(--accent-close) / 0.16);
    border-color: rgb(var(--accent-close));
    color: rgb(var(--accent-close));
}
.lp-action-ico { width: 16px; height: 16px; }

/* Per-theme scrim tint. Dark themes wash dark; the light themes (Anime, Aero)
   wash light so their dark text/tiles keep contrast. */
body[data-theme="void"] .launchpad { background: rgba(0, 0, 0, 0.60); }
body[data-theme="anime"] .launchpad { background: rgba(255, 240, 247, 0.68); }
body[data-theme="aero"] .launchpad { background: rgba(228, 242, 251, 0.60); }
body[data-theme="royale"] .launchpad { background: rgba(8, 22, 46, 0.58); }
body[data-theme="royale-noir"] .launchpad { background: rgba(12, 8, 22, 0.58); }

/* Keyboard-selected item (↑/↓). Stronger than :hover so the cursor is obvious.
   Applies to the highlighted app tile, document row, or action chip. */
.lp-app.active {
    background: rgb(var(--accent-1) / 0.16);
    border-color: rgb(var(--accent-1));
    box-shadow: 0 0 22px rgb(var(--accent-1) / 0.4);
}
.lp-doc.active {
    background: rgb(var(--accent-1) / 0.16);
    border-color: rgb(var(--accent-1));
    box-shadow: 0 0 16px rgb(var(--accent-1) / 0.32);
}
.lp-action.active {
    background: rgb(var(--accent-1) / 0.22);
    border-color: rgb(var(--accent-1));
    box-shadow: 0 0 16px rgb(var(--accent-1) / 0.4);
}

/* Launchpad browse ↔ search toggle. The .lp-* containers set display:flex,
   which would override the [hidden] attribute, so force the hide. */
.lp-apps[hidden], .lp-hints[hidden], .lp-actions[hidden], .lp-results[hidden] { display: none !important; }

/* Browse-only "try typing …" row — subtle, so it teaches without cluttering. */
.lp-hints {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: -12px; /* tuck up toward the search box it hints at */
    opacity: 0;
    transform: translateY(-18px) scale(0.85);
    transition: opacity 0.3s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.launchpad.open .lp-hints {
    opacity: 1; transform: none;
    animation: lpHintsIn 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) both;
    animation-delay: 0.06s;
}
.lp-hints-lead { color: var(--b-text-dim); font-size: 0.78em; letter-spacing: 0.5px; margin-right: 2px; }
.lp-hint {
    font-family: var(--b-font-mono);
    font-size: 0.8em;
    color: rgb(var(--accent-1) / 0.75);
    background: transparent;
    border: 1px solid rgb(var(--accent-1) / 0.28);
    border-radius: 999px;
    padding: 4px 12px;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.lp-hint::before { content: '“'; opacity: 0.6; }
.lp-hint::after { content: '”'; opacity: 0.6; }
.lp-hint:hover { color: rgb(var(--accent-1)); border-color: rgb(var(--accent-1) / 0.7); background: rgb(var(--accent-1) / 0.1); }

/* Unified search result rows (app | feature | document) — one ranked list with
   a single highlight, so it's obvious what each hit is and that Enter runs it. */
.lp-result {
    display: flex;
    align-items: center;
    gap: 12px;
    text-align: left;
    width: 100%;
    padding: 10px 14px;
    margin-bottom: 6px;
    background: var(--b-surface);
    border: 1px solid rgb(var(--accent-1) / 0.18);
    border-radius: var(--b-radius, 8px);
    color: var(--b-text);
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.lp-result:hover { background: rgb(var(--accent-1) / 0.12); border-color: rgb(var(--accent-1) / 0.6); }
.lp-result.active { background: rgb(var(--accent-1) / 0.16); border-color: rgb(var(--accent-1)); box-shadow: 0 0 16px rgb(var(--accent-1) / 0.32); }
.lp-result-icon { flex-shrink: 0; display: flex; align-items: center; justify-content: center; width: 30px; color: rgb(var(--accent-1)); }
.lp-result-icon img { width: 28px; height: 28px; object-fit: contain; display: block; }
.lp-result-svg { width: 24px; height: 24px; display: block; }
.lp-result-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.lp-result-title { color: rgb(var(--accent-1)); font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-result-sub { color: var(--b-text-dim); font-size: 0.82em; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.lp-result-kind {
    flex-shrink: 0;
    align-self: center;
    font-size: 0.64em;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: rgb(var(--accent-3));
    border: 1px solid rgb(var(--accent-3) / 0.4);
    border-radius: 4px;
    padding: 3px 7px;
    white-space: nowrap;
}
.lp-results mark.hl { background: rgb(var(--accent-2) / 0.32); color: inherit; border-radius: 2px; padding: 0 1px; }

/* ═══ Shortcuts cheat sheet (?) — a small themed modal over the desktop. ═══ */
.shortcuts {
    position: fixed;
    inset: 0;
    z-index: 9700;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(6, 4, 16, 0.6);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.22s ease, visibility 0s linear 0.22s;
}
.shortcuts.open { opacity: 1; visibility: visible; transition: opacity 0.22s ease; }
.shortcuts-panel {
    width: 100%;
    max-width: 440px;
    background: var(--b-surface);
    border: 1px solid rgb(var(--accent-1) / 0.5);
    border-radius: var(--b-radius-lg, 14px);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
    padding: 24px 26px;
    max-height: 86vh;
    overflow-y: auto;
    transform: translateY(18px) scale(0.92);
    transition: transform 0.44s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.shortcuts.open .shortcuts-panel { transform: none; }
.shortcuts-title { font-family: var(--b-font-display); font-size: 1.25em; color: rgb(var(--accent-1)); letter-spacing: 1px; margin-bottom: 18px; }
.shortcuts-section { margin-bottom: 18px; }
.shortcuts-section:last-of-type { margin-bottom: 0; }
.shortcuts-section-title {
    font-family: var(--b-font-display);
    font-size: 0.7em;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: rgb(var(--accent-3));
    margin-bottom: 9px;
    padding-bottom: 5px;
    border-bottom: 1px solid rgb(var(--accent-1) / 0.14);
}
.shortcuts-row { display: flex; align-items: center; gap: 14px; margin-bottom: 8px; }
.shortcuts-row:last-child { margin-bottom: 0; }
.shortcuts-keys { display: flex; align-items: center; gap: 6px; flex-shrink: 0; min-width: 132px; }
.shortcuts-or { color: var(--b-text-dim); font-size: 0.75em; }
.shortcuts kbd {
    font-family: var(--b-font-mono);
    font-size: 0.82em;
    line-height: 1;
    background: rgb(var(--accent-1) / 0.12);
    border: 1px solid rgb(var(--accent-1) / 0.5);
    border-radius: 5px;
    padding: 4px 8px;
    color: rgb(var(--accent-1));
    min-width: 12px;
    text-align: center;
}
.shortcuts-label { color: var(--b-text); font-size: 0.95em; }
.shortcuts-foot { margin-top: 18px; font-size: 0.76em; line-height: 1.5; color: var(--b-text-dim); }

/* Mobile expectations banner — top, tap or auto-fade to dismiss (app.js drives
   the show/hide; it only appears on a phone-width viewport). */
.mobile-notice {
    position: fixed;
    top: 12px;
    left: 12px;
    right: 12px;
    z-index: 9800;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 12px 16px;
    background: rgba(6, 4, 16, 0.92);
    border: 1px solid rgb(var(--accent-1) / 0.6);
    border-radius: var(--b-radius, 8px);
    color: rgb(var(--accent-1));
    font-family: var(--b-font-display);
    font-size: 0.9em;
    line-height: 1.4;
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
    opacity: 0;
    transform: translateY(-14px);
    transition: opacity 0.4s ease, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* The base rule above sets display:flex, which OUTRANKS the `hidden` attribute's
   UA display:none — so without this the notice stays laid out (opacity:0 but
   still hit-testable) as an invisible bar pinned across the top of the screen,
   swallowing clicks and showing a text I-beam. Honour `hidden` so it's truly
   gone until showMobileNotice() flips it. */
.mobile-notice[hidden] { display: none; }
.mobile-notice.show { opacity: 1; transform: none; }
