* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--rand-font-body);
  color: var(--rand-text);
  overflow: hidden;
}
#desktop {
  position: fixed; inset: 0;
  background: var(--wall-bg);
}
/* Animated generative wallpaper (js/wallpaper.js). Sits BEHIND #icons,
   #taskbar and all windows, and never intercepts pointer events so the
   desktop stays fully clickable. The --wall-bg gradient shows through as a
   base wash on first paint and as a graceful fallback. */
#wallpaper {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  pointer-events: none;
}
#icons, #taskbar { z-index: 1; }

/* Shared particle / effects layer (js/particles.js). A single full-screen
   canvas that renders bursts, confetti, and the cursor trail ABOVE windows
   (z-index 101+) but BELOW the boot overlay (#boot is 9999). pointer-events:
   none is load-bearing — particles must never intercept clicks or drag. */
#fx-layer {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 9000;
  pointer-events: none;
}
#icons {
  position: absolute; top: 16px; left: 16px;
  display: flex; flex-direction: column; gap: 14px;
}
.app-icon {
  width: 84px; cursor: var(--cursor-pointer, pointer); text-align: center;
  color: #fff; font-size: 13px; user-select: none;
  text-shadow: 0 1px 4px rgba(0,0,0,0.6);
}
.app-icon .glyph { display: block; margin: 0 auto 4px; }
.app-icon .app-label { display: block; }
#taskbar {
  position: absolute; bottom: 0; left: 0; right: 0; height: 46px;
  display: flex; align-items: center; gap: 8px; padding: 0 10px;
  background: rgba(0,0,0,0.35); backdrop-filter: blur(8px);
}
#taskbar .spacer { flex: 1; }
.task-entry, .shuffle-btn {
  background: rgba(255,255,255,0.12); color: #fff; border: none;
  padding: 6px 12px; border-radius: 8px; cursor: var(--cursor-pointer, pointer); font-size: 13px;
}
.shuffle-btn {
  display: inline-flex; align-items: center; gap: 7px;
}
.shuffle-btn-icon { flex: 0 0 auto; }
.window {
  position: absolute; min-width: 240px;
  background: var(--rand-surface); color: var(--rand-text);
  border-radius: var(--rand-radius); box-shadow: var(--rand-shadow);
  font-family: var(--rand-font-body);
  display: flex; flex-direction: column; overflow: hidden;
}
.window-title {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; cursor: var(--cursor-pointer, move); user-select: none;
  background: var(--rand-accent); color: var(--rand-accent-text, #fff);
  font-family: var(--rand-font-head);
}
.window-title .title-text { flex: 1; font-size: 14px; }
.window-title-icon { flex: 0 0 auto; }
.window-close {
  border: none; background: rgba(0,0,0,0.25); color: var(--rand-accent-text, #fff);
  width: 22px; height: 22px; border-radius: 6px; cursor: var(--cursor-pointer, pointer);
}
/* Windows carry a fixed per-app size (js/windows.js); the body scrolls rather
   than clips when an app's content is taller than the box. min-height: 0 lets the
   flex child actually shrink so overflow:auto engages. Content is centered both
   ways so an app with a fixed intrinsic width sits balanced inside its (larger)
   window instead of hugging the top-left. */
.window-body {
  padding: 14px; overflow: auto; min-height: 0; flex: 1 1 auto;
  display: flex; flex-direction: column; align-items: center;
  /* `safe` centers content when it fits but falls back to top-alignment when an
     app is taller than the window, so the top is never clipped/unscrollable. */
  justify-content: safe center;
}

/* ════════════════════════════════════════════════════════════════════════
   Calculator app
   Skinned entirely by the window's scoped --rand-* custom properties.
   Each key carries data-key, mapped here to a named grid-area so the three
   layout variants (grid | stacked | compact) can rearrange the keypad
   without any JS changes.
   ═══════════════════════════════════════════════════════════════════════ */

.calc {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 268px;
  font-family: var(--rand-font-body);
}

/* ── Display ── */
.calc-display {
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  min-height: 58px;
  padding: 10px 14px;
  background: var(--rand-bg);
  color: var(--rand-text);
  font-family: var(--rand-font-head);
  font-size: 26px;
  line-height: 1.2;
  text-align: right;
  word-break: break-all;
  overflow: hidden;
  border-radius: calc(var(--rand-radius) * 0.55);
  box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.18);
}

/* ── Keys ── */
.calc-key {
  min-height: 52px;
  border: 1px solid transparent;
  border-radius: calc(var(--rand-radius) * 0.5);
  font-family: inherit;
  font-size: 17px;
  cursor: var(--cursor-pointer, pointer);
  color: var(--rand-text);
  background: var(--rand-surface);
  background: color-mix(in srgb, var(--rand-accent) 14%, var(--rand-surface));
  border-color: color-mix(in srgb, var(--rand-accent) 30%, transparent);
  transition: filter 120ms ease, transform 120ms ease;
}
.calc-key:hover {
  filter: brightness(1.08);
  transform: translateY(-1px);
}
.calc-key:active {
  filter: brightness(0.94);
  transform: translateY(0) scale(0.97);
}
.calc-key:focus-visible {
  outline: 2px solid var(--rand-accent-2);
  outline-offset: 2px;
}

/* Operators sit on the accent; --rand-accent-text is contrast-safe by
   construction (every palette's accentText clears WCAG AA on its accent). */
.calc-key-op {
  background: var(--rand-accent);
  color: var(--rand-accent-text);
  border-color: transparent;
  font-weight: 700;
}
.calc-key-eq {
  background: var(--rand-accent-2);
  color: var(--rand-accent-2-text);
  border-color: transparent;
  font-weight: 700;
}
.calc-key-clear {
  background: transparent;
  border: 1.5px solid var(--rand-accent);
  color: var(--rand-text);
  font-weight: 700;
}

/* ── Keypad: base = classic desk calculator (also the "grid" layout) ──
   Wide C across the top, operator column on the right, tall equals. */
.calc-pad {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(4, 1fr);
  grid-template-areas:
    "clr clr div mul"
    "k7  k8  k9  sub"
    "k4  k5  k6  add"
    "k1  k2  k3  eq"
    "k0  k0  dot eq";
}

/* data-key -> grid-area mapping (shared by all layouts) */
.calc-key[data-key="0"] { grid-area: k0; }
.calc-key[data-key="1"] { grid-area: k1; }
.calc-key[data-key="2"] { grid-area: k2; }
.calc-key[data-key="3"] { grid-area: k3; }
.calc-key[data-key="4"] { grid-area: k4; }
.calc-key[data-key="5"] { grid-area: k5; }
.calc-key[data-key="6"] { grid-area: k6; }
.calc-key[data-key="7"] { grid-area: k7; }
.calc-key[data-key="8"] { grid-area: k8; }
.calc-key[data-key="9"] { grid-area: k9; }
.calc-key[data-key="."] { grid-area: dot; }
.calc-key[data-key="+"] { grid-area: add; }
.calc-key[data-key="-"] { grid-area: sub; }
.calc-key[data-key="*"] { grid-area: mul; }
.calc-key[data-key="/"] { grid-area: div; }
.calc-key[data-key="="] { grid-area: eq; }
.calc-key[data-key="C"] { grid-area: clr; }

/* ── Layout: stacked — oversized keys, operator rail on the right,
   and a full-width equals bar anchoring the bottom. ── */
.window[data-layout="stacked"] .calc {
  width: 312px;
  gap: 12px;
}
.window[data-layout="stacked"] .calc-display {
  min-height: 72px;
  font-size: 32px;
}
.window[data-layout="stacked"] .calc-pad {
  gap: 10px;
  grid-template-areas:
    "k7  k8  k9  div"
    "k4  k5  k6  mul"
    "k1  k2  k3  sub"
    "clr k0  dot add"
    "eq  eq  eq  eq";
}
.window[data-layout="stacked"] .calc-key {
  min-height: 62px;
  font-size: 22px;
}
.window[data-layout="stacked"] .calc-key-eq {
  min-height: 54px;
  letter-spacing: 0.35em;
  text-indent: 0.35em; /* visually re-center the spaced glyph */
}

/* ── Layout: compact — dense 5-column slab, small quiet keys,
   tall + and = bracketing the lower-right corner. ── */
.window[data-layout="compact"] .calc {
  width: 252px;
  gap: 6px;
}
.window[data-layout="compact"] .calc-display {
  min-height: 40px;
  padding: 6px 10px;
  font-size: 19px;
}
.window[data-layout="compact"] .calc-pad {
  gap: 5px;
  grid-template-columns: repeat(5, 1fr);
  grid-template-areas:
    "k7 k8 k9  clr div"
    "k4 k5 k6  mul sub"
    "k1 k2 k3  add eq"
    "k0 k0 dot add eq";
}
.window[data-layout="compact"] .calc-key {
  min-height: 34px;
  font-size: 13.5px;
}

/* ════════════════════════════════════════════════════════════════════════
   Clock app
   Three faces, chosen by the window's data-layout (digital | analog |
   worded). JS builds the matching DOM; these rules skin each face with the
   window's scoped --rand-* custom properties so every clock wears its own
   randomly-rolled outfit.
   ═══════════════════════════════════════════════════════════════════════ */

.clock {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--rand-font-body);
  color: var(--rand-text);
}
.clock-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

/* ── Digital: glowing segment-style readout on an inset panel ── */
.clock-digital {
  padding: 20px 28px 16px;
  background: var(--rand-bg);
  border-radius: calc(var(--rand-radius) * 0.55);
  box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.25);
}
.clock-digital-time {
  display: flex;
  align-items: baseline;
  font-family: var(--rand-font-head);
  font-size: 46px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
  text-shadow: 0 0 18px color-mix(in srgb, var(--rand-accent) 45%, transparent);
}
.clock-digit { min-width: 2ch; text-align: center; }
.clock-digit-sec { color: var(--rand-accent); }
.clock-colon {
  color: var(--rand-accent);
  padding: 0 0.06em;
  transition: opacity 250ms ease;
}
.clock-colon.is-dim { opacity: 0.25; }
.clock-digital-meta {
  font-size: 12px;
  letter-spacing: 0.28em;
  text-indent: 0.28em; /* re-center the tracked-out caps */
  opacity: 0.65;
}

/* ── Analog: SVG dial — accent hands, ticked rim, cardinal numerals ── */
.clock-analog { padding: 4px; }
.clock-analog-svg {
  width: 228px;
  height: 228px;
  display: block;
}
.clock-dial {
  fill: var(--rand-bg);
  stroke: color-mix(in srgb, var(--rand-accent) 50%, transparent);
  stroke-width: 2.5;
}
.clock-tick {
  stroke: var(--rand-text);
  stroke-width: 1;
  opacity: 0.32;
}
.clock-tick-major {
  stroke: var(--rand-accent);
  stroke-width: 2.5;
  opacity: 0.9;
}
.clock-numeral {
  fill: var(--rand-text);
  font-family: var(--rand-font-head);
  font-size: 19px;
  opacity: 0.8;
}
.clock-hand { stroke-linecap: round; }
.clock-hand-hour { stroke: var(--rand-accent); stroke-width: 6.5; }
.clock-hand-min  { stroke: var(--rand-accent); stroke-width: 3.5; opacity: 0.85; }
.clock-hand-sec  { stroke: var(--rand-accent-2); stroke-width: 1.6; }
.clock-hub     { fill: var(--rand-accent); }
.clock-hub-pin { fill: var(--rand-accent-2); }

/* ── Worded: editorial sentence with the hour set in accent ── */
.clock-worded {
  align-items: flex-start;
  gap: 7px;
  min-width: 240px;
  max-width: 320px;
  padding: 12px 10px 12px 18px;
  border-left: 3px solid var(--rand-accent);
  text-align: left;
}
.clock-worded-pre {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.6;
}
.clock-worded-main {
  font-family: var(--rand-font-head);
  font-size: 36px;
  line-height: 1.12;
}
.clock-worded-hour { color: var(--rand-accent); }
.clock-worded-sub {
  font-size: 13px;
  opacity: 0.7;
}

/* ════════════════════════════════════════════════════════════════════════
   Calendar app
   Two faces, chosen by the window's data-layout (grid | list). JS builds
   the matching DOM (.cal-grid or .cal-list); these rules dress each one in
   the window's scoped --rand-* custom properties.
   ═══════════════════════════════════════════════════════════════════════ */

.cal {
  font-family: var(--rand-font-body);
  color: var(--rand-text);
}

/* ── Grid: classic month matrix — chunky day tiles on surface, today as a
   glowing accent tile. ── */
.cal-grid {
  width: 288px;
  padding: 2px;
}
.cal-grid-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0 4px;
  margin-bottom: 12px;
}
.cal-grid-month {
  font-family: var(--rand-font-head);
  font-size: 21px;
  line-height: 1;
}
.cal-grid-year {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--rand-accent);
  font-variant-numeric: tabular-nums;
}
.cal-dow-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin-bottom: 6px;
  padding-bottom: 6px;
  border-bottom: 1px solid color-mix(in srgb, var(--rand-text) 18%, transparent);
}
.cal-dow {
  text-align: center;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.55;
}
.cal-cells {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.cal-cell {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  background: var(--rand-surface);
  border-radius: calc(var(--rand-radius) * 0.35);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.cal-cell-blank { background: transparent; }
.cal-cell-weekend {
  color: color-mix(in srgb, var(--rand-text) 55%, var(--rand-accent-2));
}
.cal-cell-today {
  background: var(--rand-accent);
  color: var(--rand-accent-text);
  font-weight: 700;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--rand-accent) 50%, transparent);
}

/* ── List: agenda column — airy dashed-rule rows grouped by week, today as
   a solid accent pill with a TODAY tag. Scrolls; JS centers today. ── */
.cal-list {
  width: 246px;
  padding: 2px;
}
.cal-list-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 0 6px 9px;
  margin-bottom: 8px;
  border-bottom: 2.5px solid var(--rand-accent);
}
.cal-list-month {
  font-family: var(--rand-font-head);
  font-size: 23px;
  line-height: 1;
}
.cal-list-year {
  font-size: 12px;
  letter-spacing: 0.2em;
  opacity: 0.6;
  font-variant-numeric: tabular-nums;
}
.cal-list-days {
  display: flex;
  flex-direction: column;
  max-height: 268px;
  overflow-y: auto;
  padding-right: 5px;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--rand-accent) 65%, transparent) transparent;
}
.cal-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 5px 10px;
  border-radius: calc(var(--rand-radius) * 0.3);
  border-bottom: 1px dashed color-mix(in srgb, var(--rand-text) 22%, transparent);
}
.cal-row-dow {
  width: 34px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.6;
}
.cal-row-num {
  min-width: 2ch;
  text-align: right;
  font-family: var(--rand-font-head);
  font-size: 16px;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.cal-row-weekend { background: color-mix(in srgb, var(--rand-surface) 55%, transparent); }
.cal-row-week-end { margin-bottom: 8px; }
.cal-row-today {
  background: var(--rand-accent);
  color: var(--rand-accent-text);
  border-bottom-color: transparent;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--rand-accent) 45%, transparent);
}
.cal-row-today .cal-row-dow { opacity: 0.85; }
.cal-today-tag {
  margin-left: auto;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

/* ════════════════════════════════════════════════════════════════════════
   Randomizer app — RandOS's on-theme signature
   Three actions (Coin / Dice / 1–100) feed a shared result area that pops on
   every outcome. JS builds the matching DOM (.rng-cards or .rng-stack) per the
   window's data-layout (cards | stack); these rules dress each arrangement in
   the window's scoped --rand-* custom properties.
   ═══════════════════════════════════════════════════════════════════════ */

.rng {
  font-family: var(--rand-font-body);
  color: var(--rand-text);
}
.rng-cards,
.rng-stack {
  display: flex;
  flex-direction: column;
}

/* ── Result area: contextual tag + big headline outcome on an inset panel ── */
.rng-result {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 18px 22px 20px;
  background: var(--rand-bg);
  border-radius: calc(var(--rand-radius) * 0.5);
  box-shadow: inset 0 1px 8px rgba(0, 0, 0, 0.22);
  text-align: center;
}
.rng-result-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--rand-accent);
  opacity: 0.85;
}
.rng-result-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  transform-origin: center;
}
.rng-result-glyph {
  font-size: 56px;
  line-height: 1;
  filter: drop-shadow(0 3px 10px color-mix(in srgb, var(--rand-accent) 45%, transparent));
}
.rng-result-glyph.is-hidden { display: none; }
.rng-result-value {
  font-family: var(--rand-font-head);
  font-size: 52px;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 18px color-mix(in srgb, var(--rand-accent) 35%, transparent);
}

/* The satisfying reveal: a quick pop with a touch of spin, accent glow flash.
   JS re-adds .is-revealing after a forced reflow so it restarts every roll. */
.rng-result-display.is-revealing {
  animation: rngPop 460ms cubic-bezier(0.2, 1.5, 0.45, 1) both;
}
@keyframes rngPop {
  0%   { transform: scale(0.55) rotate(-12deg); opacity: 0; }
  55%  { transform: scale(1.12) rotate(4deg);   opacity: 1; }
  100% { transform: scale(1)    rotate(0deg);   opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .rng-result-display.is-revealing {
    animation: rngFade 200ms ease both;
  }
  @keyframes rngFade { from { opacity: 0; } to { opacity: 1; } }
}

/* ── Action buttons: accent fills with accent-text, shared by both layouts ── */
.rng-actions { display: flex; gap: 10px; }
.rng-action {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: var(--rand-accent);
  color: var(--rand-accent-text);
  border: none;
  border-radius: calc(var(--rand-radius) * 0.45);
  font-family: var(--rand-font-body);
  cursor: var(--cursor-pointer, pointer);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--rand-accent) 40%, transparent);
  transition: transform 120ms ease, box-shadow 120ms ease, filter 120ms ease;
}
.rng-action-glyph { font-size: 22px; line-height: 1; }
.rng-action-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.15;
}
.rng-action-label {
  font-family: var(--rand-font-head);
  font-size: 15px;
  font-weight: 700;
}
.rng-action-name {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.78;
}
.rng-action:hover {
  filter: brightness(1.06);
  transform: translateY(-2px);
  box-shadow: 0 5px 16px color-mix(in srgb, var(--rand-accent) 55%, transparent);
}
.rng-action:active { transform: translateY(0) scale(0.97); }
.rng-action:focus-visible {
  outline: 2px solid var(--rand-accent-2);
  outline-offset: 2px;
}

/* ── Cards layout: roomy panel, the three actions as a horizontal row ── */
.window[data-layout="cards"] .rng-cards {
  width: 320px;
  gap: 16px;
}
.window[data-layout="cards"] .rng-actions {
  flex-direction: row;
}
.window[data-layout="cards"] .rng-action {
  flex-direction: column;
  text-align: center;
  gap: 6px;
}
.window[data-layout="cards"] .rng-action-text { align-items: center; }
.window[data-layout="cards"] .rng-action-glyph { font-size: 26px; }

/* ── Stack layout: narrow column, actions stacked as full-width rows ── */
.window[data-layout="stack"] .rng-stack {
  width: 234px;
  gap: 12px;
}
.window[data-layout="stack"] .rng-actions {
  flex-direction: column;
}
.window[data-layout="stack"] .rng-result { padding: 14px 18px 16px; }
.window[data-layout="stack"] .rng-result-glyph { font-size: 46px; }
.window[data-layout="stack"] .rng-result-value { font-size: 42px; }
.window[data-layout="stack"] .rng-action { width: 100%; }

/* ════════════════════════════════════════════════════════════════════════
   Pixel-art icon system (js/pixel-icons.js)
   Crisp SVG-rect sprites that recolor with the inherited scoped --rand-*
   vars. Each icon keeps a recognizable silhouette but randomizes a few
   detail pixels per render, and a short CSS loop keeps it subtly alive.
   All motion is gated behind prefers-reduced-motion: reduce.
   ═══════════════════════════════════════════════════════════════════════ */

.pxi {
  display: inline-block;
  vertical-align: middle;
  overflow: visible;
  /* Keep edges razor-sharp even when scaled. */
  shape-rendering: crispEdges;
  image-rendering: pixelated;
}

/* The randomizer result area renders a large pixel icon; give it room and a
   soft accent drop-shadow to match the old glyph's presence. */
.rng-result-glyph .pxi {
  filter: drop-shadow(0 3px 10px color-mix(in srgb, var(--rand-accent) 45%, transparent));
}

/* ── Ambient wrapper motion ── */
.pxi-bob  { animation: pxiBob 2.6s ease-in-out infinite; }
.pxi-sway { animation: pxiSway 3.2s ease-in-out infinite; transform-origin: 50% 90%; }

@keyframes pxiBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-1.5px); }
}
@keyframes pxiSway {
  0%, 100% { transform: rotate(-3deg); }
  50%      { transform: rotate(3deg); }
}

/* ── Detail-pixel animations ── */
.pxi-blink    { animation: pxiBlink 1.4s steps(1) infinite; }
.pxi-blink2   { animation: pxiBlink 1.9s steps(1) infinite 0.4s; }
.pxi-twinkle  { animation: pxiTwinkle 1.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
.pxi-twinkle2 { animation: pxiTwinkle 2.4s ease-in-out infinite 0.6s; transform-box: fill-box; transform-origin: center; }
.pxi-shimmer  { animation: pxiShimmer 2.8s ease-in-out infinite; }

@keyframes pxiBlink {
  0%, 60%  { opacity: 1; }
  61%, 100% { opacity: 0.15; }
}
@keyframes pxiTwinkle {
  0%, 100% { opacity: 0.25; transform: scale(0.6); }
  50%      { opacity: 1;    transform: scale(1.15); }
}
@keyframes pxiShimmer {
  0%, 70%, 100% { opacity: 0; }
  82%           { opacity: 0.35; }
}

/* ── Clock hands ── */
.pxi-hand-hour,
.pxi-hand-min,
.pxi-hand-sec { transform-box: fill-box; transform-origin: 50% 100%; }
.pxi-clockhands .pxi-hand-hour { transform: rotate(var(--pxi-hour, 0deg)); }
.pxi-clockhands .pxi-hand-min  { transform: rotate(var(--pxi-min, 90deg)); }
.pxi-clockhands .pxi-hand-sec  { animation: pxiSpin 6s linear infinite; }
@keyframes pxiSpin { to { transform: rotate(360deg); } }

/* ── Shuffle arrows: a gentle swap nudge ── */
.pxi-shuffle { animation: pxiSwap 2.2s ease-in-out infinite; transform-box: view-box; transform-origin: center; }
@keyframes pxiSwap {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(0.6px); }
  75%      { transform: translateX(-0.6px); }
}

/* ── Coin: slow flip-bob ── */
.pxi-coin { animation: pxiCoin 3.4s ease-in-out infinite; transform-box: view-box; transform-origin: center; }
@keyframes pxiCoin {
  0%, 100% { transform: scaleX(1)    translateY(0); }
  45%      { transform: scaleX(0.82) translateY(-1px); }
  50%      { transform: scaleX(0.65) translateY(-1.5px); }
  55%      { transform: scaleX(0.82) translateY(-1px); }
}

/* ── Spinner: slow continuous rotation ── */
.pxi-spin { transform-origin: 50% 50%; animation: pxi-spin 6s linear infinite; }
@keyframes pxi-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .pxi-spin { animation: none; } }

/* Calm everything when the user prefers reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .pxi,
  .pxi * {
    animation: none !important;
  }
  /* Keep clock hands at their rolled position (no spin), still readable. */
  .pxi-clockhands .pxi-hand-sec { transform: rotate(180deg); }
}

/* ════════════════════════════════════════════════════════════════════════
   Motion pass — playful window + app liveliness (js/windows.js drives the
   .is-opening / .is-closing / .is-dragging classes). Everything here is
   purely cosmetic and is calmed to instant/static under reduced-motion.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Window open: ONE of three selectable materialize effects (js/windows.js
   picks 'confetti' | 'glitch' | 'origami', random by default or locked via
   window.setOpenEffect). Each adds .is-opening plus its variant class. All are
   transform/opacity/clip based (GPU-friendly) and neutralized under reduced
   motion by the block further down. ── */

/* Confetti option — a smooth, polished scale + fade in with a gentle overshoot
   (the particle confetti burst is fired alongside from JS). */
.window.is-opening.is-open-confetti {
  animation: winConfetti 360ms cubic-bezier(0.22, 1.28, 0.36, 1) both;
  transform-origin: 50% 35%;
  will-change: transform, opacity;
}
@keyframes winConfetti {
  0%   { transform: scale(0.86) translateY(10px); opacity: 0; }
  55%  { transform: scale(1.03) translateY(-3px); opacity: 1; }
  100% { transform: scale(1)    translateY(0);    opacity: 1; }
}

/* Origami option — a clean paper fold-in: the window unfolds down from its top
   edge in 3D, overshoots once, then settles flat. */
.window.is-opening.is-open-origami {
  animation: winOrigami 560ms cubic-bezier(0.22, 1.0, 0.32, 1) both;
  transform-origin: 50% 0%;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform, opacity;
}
@keyframes winOrigami {
  0%   { transform: perspective(1100px) rotateX(-92deg) scale(0.94); opacity: 0; }
  45%  { transform: perspective(1100px) rotateX(16deg)  scale(1.01); opacity: 1; }
  70%  { transform: perspective(1100px) rotateX(-7deg)  scale(1);    opacity: 1; }
  86%  { transform: perspective(1100px) rotateX(3deg)   scale(1);    opacity: 1; }
  100% { transform: perspective(1100px) rotateX(0deg)   scale(1);    opacity: 1; }
}

/* Glitch option — the window snaps in with a brief, seamless digital glitch:
   horizontal slice clipping + small position jitter that resolves to clean. */
.window.is-opening.is-open-glitch {
  animation: winGlitch 420ms steps(1, end) both;
  will-change: transform, clip-path, opacity;
}
@keyframes winGlitch {
  0%   { opacity: 0; transform: translateX(-7px); clip-path: inset(0 0 72% 0); }
  12%  { opacity: 1; transform: translateX(7px);  clip-path: inset(42% 0 18% 0); }
  24%  { transform: translateX(-5px); clip-path: inset(8% 0 64% 0); }
  36%  { transform: translateX(4px);  clip-path: inset(62% 0 6% 0); }
  48%  { transform: translateX(-3px); clip-path: inset(22% 0 44% 0); }
  60%  { transform: translateX(3px);  clip-path: inset(4% 0 30% 0); }
  72%  { transform: translateX(-2px); clip-path: inset(48% 0 12% 0); }
  84%  { transform: translateX(1px);  clip-path: inset(0 0 8% 0); }
  100% { opacity: 1; transform: translateX(0); clip-path: inset(0 0 0 0); }
}

/* ── Window close: scale down + fade out, then JS removes the element ── */
.window.is-closing {
  animation: winClose 200ms cubic-bezier(0.4, 0, 1, 0.7) both;
  transform-origin: 50% 40%;
  pointer-events: none;
}
@keyframes winClose {
  0%   { transform: scale(1)    translateY(0);  opacity: 1; }
  100% { transform: scale(0.82) translateY(6px); opacity: 0; }
}

/* ── Focus feedback: subtle shadow bump (default windows get a calm base) ── */
.window {
  transition: box-shadow 180ms ease;
}

/* ── Re-box (window.randomizeWindowBox / chaos teleport): glide the window to
   its new random position + size. Class is added for the duration of one
   transition by js/windows.js, then removed so dragging stays instant. ── */
.window.is-rebox {
  transition: left 360ms cubic-bezier(0.22, 1.0, 0.36, 1),
              top 360ms cubic-bezier(0.22, 1.0, 0.36, 1),
              width 360ms cubic-bezier(0.22, 1.0, 0.36, 1),
              height 360ms cubic-bezier(0.22, 1.0, 0.36, 1),
              box-shadow 180ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .window.is-rebox { transition: none !important; }
}

/* ── Dragging: a playful grabbed tilt + lift. Transform-based only, so the
   left/top drag math is unaffected. ── */
.window.is-dragging {
  transform: scale(1.025) rotate(-0.8deg);
  cursor: var(--cursor-grab, grabbing);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.4);
  transition: transform 120ms ease, box-shadow 120ms ease;
}
.window.is-dragging .window-title { cursor: var(--cursor-grab, grabbing); }

/* ── Desktop app icons: out-of-phase bob + a springy hover bounce ── */
.app-icon {
  transition: transform 180ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.app-icon .glyph {
  animation: iconBob 3.4s ease-in-out infinite;
}
.app-icon:nth-child(2) .glyph { animation-delay: -0.5s; animation-duration: 3.8s; }
.app-icon:nth-child(3) .glyph { animation-delay: -1.1s; animation-duration: 3.1s; }
.app-icon:nth-child(4) .glyph { animation-delay: -1.7s; animation-duration: 4.0s; }
.app-icon:nth-child(5) .glyph { animation-delay: -2.3s; animation-duration: 3.5s; }
@keyframes iconBob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-4px) rotate(-1.5deg); }
}
.app-icon:hover {
  transform: scale(1.14) translateY(-2px);
}
.app-icon:active {
  transform: scale(0.96);
}

/* ── Buttons: satisfying hover/active on calc keys (covered above), taskbar
   entries, shuffle, and rng actions get a unified springy press. ── */
.task-entry, .shuffle-btn {
  transition: transform 140ms cubic-bezier(0.34, 1.56, 0.64, 1),
              background 140ms ease;
}
.task-entry:hover, .shuffle-btn:hover {
  transform: translateY(-2px) scale(1.04);
  background: rgba(255, 255, 255, 0.22);
}
.task-entry:active, .shuffle-btn:active {
  transform: translateY(0) scale(0.95);
}
/* A subtle ambient breathe so the taskbar feels alive without distracting. */
.shuffle-btn .shuffle-btn-icon { animation: iconBob 4.2s ease-in-out infinite; }

.window-close {
  transition: transform 140ms cubic-bezier(0.34, 1.56, 0.64, 1),
              background 140ms ease;
}
.window-close:hover {
  transform: scale(1.18) rotate(90deg);
  background: rgba(0, 0, 0, 0.4);
}
.window-close:active { transform: scale(0.9) rotate(90deg); }

/* ── Reduced-motion: calm everything. Instant open/close (the JS reduced-
   motion branch already tears down without waiting on animationend), no bob,
   no tilt, no hover springs. ── */
@media (prefers-reduced-motion: reduce) {
  .window.is-opening,
  .window.is-closing {
    animation: none !important;
  }
  .window.is-dragging {
    transform: none !important;
    transition: none !important;
  }
  .app-icon,
  .app-icon .glyph,
  .shuffle-btn .shuffle-btn-icon {
    animation: none !important;
    transition: none !important;
  }
  .app-icon:hover,
  .app-icon:active,
  .task-entry:hover,
  .task-entry:active,
  .shuffle-btn:hover,
  .shuffle-btn:active,
  .window-close:hover,
  .window-close:active {
    transform: none !important;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   Boot animation — RandOS's signature jack-in-the-box (js/boot.js)
   The #boot overlay is the LAST element in <body> and carries .boot-cover so
   it paints over the whole screen on first paint (no desktop flash). boot.js
   then adds .boot-playing to run the sequence, or removes the overlay outright
   if this session already booted. The crank winds up (building tension), the
   lid flips, the jack — the RandOS wordmark on a coil — springs out, then the
   overlay fades to reveal the live desktop underneath.
   All motion is gated: under reduced-motion boot.js uses .boot-reduced (a calm
   static title) instead of .boot-playing.
   ═══════════════════════════════════════════════════════════════════════ */

#boot.boot-cover {
  position: fixed;
  inset: 0;
  z-index: 9999;            /* above windows (101+) and taskbar */
  display: flex;
  align-items: center;
  justify-content: center;
  /* Opaque from first paint — neutral dark wash matching the theme defaults. */
  background:
    radial-gradient(120% 120% at 50% 38%,
      color-mix(in srgb, var(--rand-accent) 22%, #14141f) 0%,
      #0e0e16 70%);
  /* Snappy reveal fade (boot.js toggles .boot-revealing). */
  opacity: 1;
  transition: opacity 360ms ease;
  will-change: opacity;
}
#boot.boot-revealing {
  opacity: 0;
  pointer-events: none;
}

.boot-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

.boot-jib { display: block; overflow: visible; }
/* Pixel display font (matches the pixel-art jack). Press Start 2P is chunky and
   wide, so sizes/letter-spacing are dialled down vs a normal face; monospace is
   the fallback if the font is blocked. */
.boot-wordmark {
  font-family: 'Press Start 2P', ui-monospace, 'Courier New', monospace;
  font-size: 24px;
  line-height: 1.3;
  letter-spacing: 0.02em;
  color: color-mix(in srgb, var(--rand-accent) 40%, #ffffff);
  text-shadow: 0 3px 0 color-mix(in srgb, var(--rand-accent) 55%, #14141f);
  opacity: 0;
}
.boot-tagline {
  font-family: 'Press Start 2P', ui-monospace, 'Courier New', monospace;
  font-size: 9px;
  line-height: 1.6;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--rand-accent) 60%, #ffffff);
  opacity: 0;
}

/* ── Pixel-art part styling (js/boot-jib.js renders rect-per-cell sprites) ──
   Crisp pixels; hues routed through the live theme so each boot is on-theme,
   while the face/cheeks/mouth keep classic clown colors. */
#boot-jib rect { shape-rendering: crispEdges; }

.j-out      { fill: #14141f; }                                              /* outline */
/* Jester hat */
.j-hat      { fill: var(--rand-accent); }
.j-hat-s    { fill: color-mix(in srgb, var(--rand-accent) 58%, #14141f); }   /* hat shade */
.j-band     { fill: color-mix(in srgb, var(--rand-accent) 42%, #14141f); }   /* hat band */
.j-pom      { fill: var(--rand-accent-2); }                                  /* pompom */
.j-pom-s    { fill: color-mix(in srgb, var(--rand-accent-2) 55%, #14141f); }
/* Face */
.j-face     { fill: #f7ead8; }
.j-cheek    { fill: #ff9bbf; }
.j-eye      { fill: var(--rand-accent-2); }                                  /* star (+) eyes */
.j-mouth    { fill: #b3243f; }
.j-teeth    { fill: #fff7ef; }
.j-collar   { fill: color-mix(in srgb, var(--rand-accent-2) 70%, #ffffff); }
.j-collar-s { fill: color-mix(in srgb, var(--rand-accent-2) 50%, #14141f); }
/* Coil */
.j-coil-w   { fill: #f3f4ff; }
.j-coil     { fill: var(--rand-accent-2); }
.j-coil-s   { fill: color-mix(in srgb, var(--rand-accent-2) 55%, #14141f); }
/* Box */
.j-box      { fill: var(--rand-accent); }
.j-box-s    { fill: color-mix(in srgb, var(--rand-accent) 62%, #14141f); }   /* box shade */
.j-box-l    { fill: color-mix(in srgb, var(--rand-accent) 55%, #ffffff); }   /* trim */
.j-star     { fill: var(--rand-accent-2); }
/* Lid */
.j-lid      { fill: var(--rand-accent); }
.j-lid-l    { fill: color-mix(in srgb, var(--rand-accent) 55%, #ffffff); }
/* Crank */
.j-metal    { fill: color-mix(in srgb, var(--rand-accent) 28%, #d8d8e4); }
.j-metal-d  { fill: #2a2a3c; }
.j-knob     { fill: var(--rand-accent-2); }

/* ── Playing state: orchestrated keyframe sequence ──
   Timeline (boot.js safety net is 6.4s; reveal waits for the swing to finish):
     0.00–2.55s  whole rig scales UP through the cranking (mounting suspense)
     0.00–2.30s  crank winds up (6 turns, accelerating = mounting tension)
     2.10–2.40s  anticipation: box shivers/recoils just before the burst
     2.40s       lid flips open
     2.55–3.85s  jack springs out on the coil (smooth damped spring) -> jibPop
     3.80–5.30s  jack sways side to side on the coil, then settles -> jibWobble
     3.20s+      wordmark + tagline fade up underneath
*/
/* Whole rig scales UP through the cranking for mounting suspense. Lives on the
   <svg> element so it COMPOSES with the box shiver / lid / pop transforms inside
   (those are on child groups in SVG coordinate space). Eases in so it creeps,
   then holds large into the pop. */
.boot-playing .boot-jib {
  transform-origin: 50% 92%;
  animation: jibStageGrow 2.55s ease-in-out both;
}
@keyframes jibStageGrow {
  0%   { transform: scale(0.96); }
  80%  { transform: scale(1.12); }   /* grows through the cranking (suspense) */
  100% { transform: scale(1.0); }    /* relaxes to natural scale AS it pops, so
                                        the SVG isn't re-rasterized scaled-up
                                        while the jack spring animates = smooth */
}
.boot-playing .jib-box {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: jibShiver 0.32s ease-in-out 2.12s both;
}
.boot-playing .jib-crank {
  transform-box: fill-box;
  transform-origin: 0% 50%;           /* pivot at the hub (left end of arm) */
  animation: jibWind 2.3s cubic-bezier(0.45, 0, 0.85, 0.35) both;
}
.boot-playing .jib-lid {
  transform-box: fill-box;
  transform-origin: 0% 100%;          /* hinge at back-left */
  animation: jibLid 0.55s cubic-bezier(0.3, 1.4, 0.5, 1) 2.40s both;
}
.boot-playing .jib-jack {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  /* Hidden down inside the box until the pop (the tall pixel jack tucks behind
     the box front; bottom-centre origin keeps it anchored at the opening). */
  transform: translateY(60px) scale(0.22);
  opacity: 0;
  /* Promote to its own compositor layer so the complex SVG group isn't repainted
     every frame as it springs (the main cause of the stutter). Linear base so the
     per-keyframe easing fully shapes the spring. */
  will-change: transform;
  animation: jibPop 1.3s linear 2.55s both;
}
.boot-playing .jib-head {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  /* Side-to-side swing on the coil right after the jack lands. boot.js waits for
     THIS animation to finish before revealing, so the full swing is always seen. */
  animation: jibWobble 1.5s ease-in-out 3.8s both;
}
.boot-playing .boot-wordmark {
  animation: jibTagline 0.6s ease 3.20s both;
}
.boot-playing .boot-tagline {
  animation: jibTagline 0.6s ease 3.45s both;
}

/* Crank wind-up: full rotations that speed up, conveying mounting tension. */
@keyframes jibWind {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(2160deg); }   /* 6 turns, eased to accelerate */
}

/* Anticipation: the box recoils/shivers a beat before the jack bursts out. */
@keyframes jibShiver {
  0%, 100% { transform: translateX(0) scale(1); }
  20% { transform: translateX(-2px) scale(1.01, 0.99); }
  45% { transform: translateX(3px)  scale(0.99, 1.02); }
  70% { transform: translateX(-2px) scale(1.01, 0.99); }
}

/* Lid flips open and back like a hinged trapdoor flung by the pop. */
@keyframes jibLid {
  0%   { transform: rotate(0deg); }
  60%  { transform: rotate(-122deg); }
  100% { transform: rotate(-106deg); }
}

/* The pop: a smooth, damped spring. The jack launches up out of the box,
   overshoots, then oscillates with ever-smaller bounces until it settles flat.
   Per-keyframe timing functions (ease-out on the up-swings, ease-in on the
   down-swings) make the motion read as a natural spring rather than a jerk. */
@keyframes jibPop {
  0%   { transform: translateY(60px) scale(0.22); opacity: 0; animation-timing-function: cubic-bezier(0.33, 0, 0.2, 1); }
  12%  { transform: translateY(60px) scale(0.22); opacity: 1; animation-timing-function: cubic-bezier(0.16, 0.65, 0.3, 1); }
  46%  { transform: translateY(-26px) scale(1.05); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1); }
  62%  { transform: translateY(8px)   scale(0.985); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1); }
  76%  { transform: translateY(-10px) scale(1.02); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1); }
  88%  { transform: translateY(3px)   scale(0.995); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1); }
  100% { transform: translateY(0)     scale(1); opacity: 1; }
}

/* A friendly post-landing side-to-side swing on the coil — a clear damped sway
   that rocks left/right a couple of times before settling upright. */
@keyframes jibWobble {
  0%   { transform: rotate(0deg); }
  15%  { transform: rotate(9deg); }
  38%  { transform: rotate(-7deg); }
  60%  { transform: rotate(5deg); }
  80%  { transform: rotate(-3deg); }
  100% { transform: rotate(0deg); }
}

@keyframes jibTagline {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 0.92; transform: translateY(0); }
}

/* ── Boot-local confetti (the in-overlay pop burst) ──
   boot.js injects a .boot-confetti layer of .boot-shard spans into .boot-stage
   at the pop moment. These render INSIDE #boot (above the boot scene), unlike
   the FX canvas which sits behind the overlay. Each shard's trajectory is set
   via inline CSS custom props (--dx/--dy/--rot/--dur/--delay/--hue). Skipped
   under reduced motion (boot.js never injects them then, and the keyframe is
   neutralized below as a belt-and-braces). */
.boot-confetti {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
}
.boot-shard {
  position: absolute;
  left: 0;
  top: 0;
  width: 9px;
  height: 13px;
  border-radius: 2px;
  background: hsl(var(--hue) 85% 62%);
  opacity: 0;
  transform: translate(-50%, -50%);
  animation: jibShard var(--dur, 1s) cubic-bezier(0.15, 0.7, 0.3, 1) var(--delay, 0s) forwards;
}
@keyframes jibShard {
  0%   { opacity: 1; transform: translate(-50%, -50%) rotate(0deg) scale(0.6); }
  15%  { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) rotate(var(--rot)) scale(1); }
}

/* ── Reduced motion: no winding, no springing. The jack character simply rests
   already-out of the box, the wordmark/tagline show, then boot.js fades the
   overlay. boot.js applies .boot-reduced instead of .boot-playing. ── */
.boot-reduced .jib-box   { transform: none; }
.boot-reduced .jib-crank { transform: none; }
.boot-reduced .jib-jack  { transform: none; opacity: 1; }
.boot-reduced .jib-lid {
  transform-box: fill-box;
  transform-origin: 0% 100%;
  transform: rotate(-106deg);
}
.boot-reduced .boot-wordmark { opacity: 0.92; }
.boot-reduced .boot-tagline  { opacity: 0.92; }

@media (prefers-reduced-motion: reduce) {
  /* Hard stop on any boot keyframes that might otherwise be applied. */
  .boot-playing .boot-jib,
  .boot-playing .jib-box,
  .boot-playing .jib-crank,
  .boot-playing .jib-lid,
  .boot-playing .jib-jack,
  .boot-playing .jib-head,
  .boot-playing .boot-wordmark,
  .boot-playing .boot-tagline,
  .boot-shard,
  #boot * {
    animation: none !important;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   Sound Board app
   ═══════════════════════════════════════════════════════════════════════ */

.soundboard { display: flex; flex-direction: column; height: 100%; gap: 10px; }
.sb-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sb-title { font-family: var(--rand-font-head); font-weight: 700; }
.sb-surprise { padding: 6px 12px; border: none; border-radius: var(--rand-radius, 8px);
  background: var(--rand-accent); color: var(--rand-accent-text, #fff); cursor: pointer; }
.sb-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; flex: 1;
  overflow: auto; }
.sb-pad { display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 2px; padding: 10px 12px; border: 2px solid var(--rand-accent-2, #28e0c8);
  border-radius: var(--rand-radius, 8px); background: var(--rand-surface);
  color: var(--rand-text); cursor: pointer; transition: transform .08s ease; }
.sb-pad-label { font-weight: 700; }
.sb-pad-cat { font-size: 11px; opacity: .65; }
.sb-pad.is-hit { animation: sb-hit .25s ease; }
@keyframes sb-hit { 0% { transform: scale(.92); background: var(--rand-accent); color: var(--rand-accent-text, #fff); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .sb-pad.is-hit { animation: none; } }

/* ── Chaos heads-up banner ──────────────────────────────────────────────────
   Shown ~3.5s before the periodic CHAOS EVENT rerolls everything, so the user
   is warned. Pinned near the top-center, on-theme via --rand-accent, and
   pointer-events:none so it NEVER blocks a click. z-index sits below the FX
   particle layer (9000) so the celebration renders over it, and below #boot
   (9999) so it never covers the boot overlay. No emoji. */
.chaos-headsup {
  position: fixed;
  top: 32px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 8500;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 14px 26px;
  border-radius: 14px;
  text-align: center;
  color: var(--rand-accent-text, #0d0d0d);
  background:
    linear-gradient(135deg,
      var(--rand-accent, #8b5cf6),
      color-mix(in srgb, var(--rand-accent, #8b5cf6) 55%, var(--rand-accent-2, #ec4899)));
  border: 2px solid color-mix(in srgb, var(--rand-accent, #8b5cf6) 60%, #fff);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.45),
              0 0 22px color-mix(in srgb, var(--rand-accent, #8b5cf6) 50%, transparent);
  animation: chaosHeadsupIn 0.28s ease-out both, chaosHeadsupPulse 1.1s ease-in-out 0.28s infinite;
}
.chaos-headsup-title {
  font-family: var(--rand-font-head, 'Arial Black', Impact, sans-serif);
  font-weight: 800;
  letter-spacing: 0.14em;
  font-size: 14px;
  text-transform: uppercase;
}
.chaos-headsup-count {
  font-family: var(--rand-font-body, system-ui, sans-serif);
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Per-second tick: a quick scale nudge each time the number changes. */
.chaos-headsup.is-tick {
  animation: chaosHeadsupIn 0.28s ease-out both,
             chaosHeadsupPulse 1.1s ease-in-out infinite,
             chaosHeadsupTick 0.34s ease-out;
}
@keyframes chaosHeadsupIn {
  from { opacity: 0; transform: translateX(-50%) translateY(-14px) scale(0.94); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}
@keyframes chaosHeadsupPulse {
  0%, 100% { box-shadow: 0 10px 34px rgba(0,0,0,0.45),
                         0 0 18px color-mix(in srgb, var(--rand-accent, #8b5cf6) 45%, transparent); }
  50%      { box-shadow: 0 10px 34px rgba(0,0,0,0.45),
                         0 0 34px color-mix(in srgb, var(--rand-accent, #8b5cf6) 75%, transparent); }
}
@keyframes chaosHeadsupTick {
  0%   { transform: translateX(-50%) scale(1); }
  40%  { transform: translateX(-50%) scale(1.07); }
  100% { transform: translateX(-50%) scale(1); }
}

/* Reduced motion: the banner STILL shows (it's a warning), but it holds steady
   — no entrance bounce, no pulsing glow, no per-tick scale. The chaos event
   itself still randomizes everything; only the banner's motion is calmed. */
.chaos-headsup.is-calm,
.chaos-headsup.is-calm.is-tick {
  animation: none;
}
@media (prefers-reduced-motion: reduce) {
  .chaos-headsup,
  .chaos-headsup.is-tick {
    animation: none !important;
  }
}

.sgbox { display: flex; flex-direction: column; align-items: center; justify-content: center;
  height: 100%; gap: 16px; }
.sgbox-stage { position: relative; width: 160px; height: 140px; border: none;
  background: transparent; cursor: pointer; }
.sgbox-body { position: absolute; left: 20px; bottom: 0; width: 120px; height: 80px;
  background: var(--rand-accent); border-radius: 6px; box-shadow: inset 0 -8px 0 rgba(0,0,0,.18); }
.sgbox-lid { position: absolute; left: 14px; bottom: 76px; width: 132px; height: 18px;
  background: var(--rand-accent-2, #28e0c8); border-radius: 5px; transform-origin: left bottom;
  transition: transform .35s cubic-bezier(.34,1.56,.64,1); }
.sgbox-slip { position: absolute; left: 40px; bottom: 70px; width: 80px; min-height: 50px;
  padding: 8px; background: var(--rand-surface); color: var(--rand-text);
  border: 2px solid var(--rand-accent-2, #28e0c8); border-radius: 4px; font-size: 13px;
  text-align: center; opacity: 0; transform: translateY(20px) scale(.6);
  transition: transform .35s cubic-bezier(.34,1.56,.64,1), opacity .25s ease; }
.sgbox.is-open .sgbox-lid { transform: rotate(-105deg); }
.sgbox.is-open .sgbox-slip { opacity: 1; transform: translateY(-30px) scale(1); z-index: 1; }
.sgbox-hint { font-size: 13px; opacity: .7; }
@media (prefers-reduced-motion: reduce) {
  .sgbox-lid, .sgbox-slip { transition: none; }
}

.quote-app { display: flex; flex-direction: column; height: 100%; gap: 10px; justify-content: center; }
.quote-mark { font-family: Georgia, serif; font-size: 48px; line-height: .6; color: var(--rand-accent); }
.quote-text { margin: 0; font-family: var(--rand-font-head); font-size: 18px; }
.quote-by { display: block; text-align: right; font-style: normal; opacity: .75; margin-top: 6px; }
.quote-kind { align-self: flex-start; font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  padding: 2px 8px; border-radius: 999px; background: var(--rand-accent-2, #28e0c8);
  color: var(--rand-accent-2-text, #000); }
.quote-again { align-self: flex-end; margin-top: 6px; padding: 6px 14px; border: none;
  border-radius: var(--rand-radius, 8px); background: var(--rand-accent);
  color: var(--rand-accent-text, #fff); cursor: pointer; }

.spinner-app { display: flex; flex-direction: column; align-items: center; height: 100%; gap: 10px; }
.spin-wheel-wrap { position: relative; width: 240px; height: 240px; }
.spin-canvas { display: block; border-radius: 50%; box-shadow: 0 4px 16px rgba(0,0,0,.3); }
.spin-pointer { position: absolute; top: -6px; left: 50%; transform: translateX(-50%);
  width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent;
  border-top: 18px solid var(--rand-text); z-index: 2; }
.spin-result { font-family: var(--rand-font-head); font-weight: 700; min-height: 22px; }
.spin-go { padding: 8px 24px; border: none; border-radius: var(--rand-radius, 8px);
  background: var(--rand-accent); color: var(--rand-accent-text, #fff); cursor: pointer; font-weight: 700; }
.spin-editor { width: 100%; resize: none; border: 2px solid var(--rand-accent-2, #28e0c8);
  border-radius: var(--rand-radius, 8px); background: var(--rand-surface); color: var(--rand-text);
  padding: 8px; font-family: monospace; }

.notepad { display: flex; flex-direction: column; height: 100%; gap: 8px; }
.np-bar { display: flex; justify-content: flex-end; }
.np-calm { padding: 4px 12px; border: 2px solid var(--rand-accent-2, #28e0c8);
  border-radius: var(--rand-radius, 8px); background: var(--rand-surface);
  color: var(--rand-text); cursor: pointer; font-size: 12px; }
.np-note { flex: 1; overflow: auto; padding: 10px; border-radius: var(--rand-radius, 8px);
  background: var(--rand-surface); color: var(--rand-text); line-height: 1.5;
  outline: none; border: 1px solid color-mix(in srgb, var(--rand-text) 15%, transparent); }
.np-wild { transition: all .2s ease; }

.paint-app { display: flex; flex-direction: column; height: 100%; gap: 8px; }
.paint-bar { display: flex; align-items: center; gap: 10px; }
.paint-swatch { width: 22px; height: 22px; border-radius: 4px; border: 2px solid var(--rand-text); }
.paint-info { font-size: 12px; opacity: .8; }
.paint-clear { margin-left: auto; padding: 4px 12px; border: none; border-radius: var(--rand-radius, 8px);
  background: var(--rand-accent); color: var(--rand-accent-text, #fff); cursor: pointer; }
.paint-canvas { flex: 1; align-self: center; background: var(--rand-surface);
  border-radius: var(--rand-radius, 8px); cursor: crosshair;
  image-rendering: pixelated; box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--rand-text) 15%, transparent); }

.qte-overlay { position: fixed; inset: 0; z-index: 9500; display: flex; align-items: center;
  justify-content: center; background: rgba(0,0,0,.45); animation: qte-in .2s ease; }
.qte-panel { width: min(440px, 90vw); background: var(--wall-bg, #1d1933); color: #fff;
  border: 3px solid var(--rand-accent, #ff4d9d); border-radius: 14px; padding: 18px;
  box-shadow: 0 16px 48px rgba(0,0,0,.5); text-align: center; }
.qte-title { font-family: var(--rand-font-head, system-ui); font-size: 20px; font-weight: 800; }
.qte-score { margin: 6px 0; font-size: 16px; opacity: .9; }
.qte-bar-wrap { height: 10px; background: rgba(255,255,255,.18); border-radius: 6px; overflow: hidden; }
.qte-bar { height: 100%; background: var(--rand-accent, #ff4d9d); transform-origin: left center; transform: scaleX(1); }
.qte-arena { position: relative; height: 220px; margin-top: 12px; }
.qte-dot { position: absolute; width: 40px; height: 40px; border-radius: 50%; border: none;
  background: var(--rand-accent-2, #28e0c8); cursor: pointer; }
.qte-mash { width: 140px; height: 140px; border-radius: 50%; border: none; margin: 30px auto 0;
  display: block; background: var(--rand-accent, #ff4d9d); color: #fff; font-size: 22px; font-weight: 900; cursor: pointer; }
.qte-arena { display: grid; grid-template-columns: repeat(3, 1fr); place-content: center; }
.qte-grid-dot { width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer; margin: 6px; }
.qte-grid-dot.is-green { background: #2ecc71; } .qte-grid-dot.is-red { background: #e74c3c; }
.qte-grid-dot.is-got { opacity: .35; } .qte-grid-dot.is-bad { outline: 3px solid #fff; }
.qte-panel.is-win { border-color: #2ecc71; } .qte-panel.is-lose { border-color: #e74c3c; }
@keyframes qte-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .qte-overlay { animation: none; } }

body.backrooms { --wall-bg: #b9a82a; }
body.backrooms #fx-layer { display: none; }
body.backrooms::before {
  content: ''; position: fixed; inset: 0; z-index: 1;
  background: #c2b02e url('../assets/backrooms.png') center / cover no-repeat;
  box-shadow: inset 0 0 160px rgba(40,34,0,.55);
  animation: backrooms-flicker 5s ease-in-out infinite;
  pointer-events: none;
}
body.backrooms .window { filter: sepia(.5) saturate(1.6) hue-rotate(-10deg); }
.backrooms-prompt { position: fixed; left: 50%; bottom: 40px; transform: translateX(-50%);
  z-index: 9600; color: #fff7c2; font-family: monospace; font-size: 24px; letter-spacing: .1em; opacity: .8;
  text-shadow: 0 0 8px rgba(0,0,0,.6); animation: backrooms-pulse 2s ease-in-out infinite; }
@keyframes backrooms-flicker {
  0%, 100% { filter: brightness(1); }
  48% { filter: brightness(1); }
  49% { filter: brightness(.8); }
  50% { filter: brightness(1.04); }
  51% { filter: brightness(.86); }
  52% { filter: brightness(1); }
  92% { filter: brightness(.92); }
  93% { filter: brightness(1); }
}
@keyframes backrooms-pulse { 0%,100% { opacity: .55; } 50% { opacity: .9; } }
body.backrooms.backrooms-calm::before { animation: none; }
@media (prefers-reduced-motion: reduce) {
  body.backrooms::before { animation: none; }
  .backrooms-prompt { animation: none; }
}
