/* Web fonts (VT323, DotGothic16) are linked from index.html */

/* ── RESET */
*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #000;
}

html {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}
input, textarea, [contenteditable] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

:root { --u: calc(100vw / 1440); }
@media (orientation: portrait) {
  :root { --u: calc(100vh / 844); --u: calc(100dvh / 844); }
}

/* ── CONTROLS — the size of each part */
:root {
  /* ── LANDSCAPE (wide screens) */
  --filter-h:    0.8;    /* "filter: all" box, top left */
  --guestbook-h: 0.8;    /* guest book form, bottom centre */
  --entries-h:   0.8;    /* guest book cards, right edge */
  --counter-h:   1;    /* visitor counter, bottom */
  --loader-h:    0.7;    /* ENTER screen: loading bar + ENTER gif */

  /* ── PORTRAIT (tall screens) */
  --filter-v:    1;
  --guestbook-v: 1;
  --entries-v:   1;
  --counter-v:   1;
  --loader-v:    0.8;

  /* ── SMALLEST SIZE (landscape) — guest book form, cards and counter.
     --u shrinks with the screen width, which makes text unreadable on
     laptops. These parts never get smaller than this many screen px per
     design px (1.75px ≈ how they look on a 2560 px wide screen)… */
  --ui-min:      1.55px;
  /* …except on short screens (landscape phones): the floor shrinks below
     this screen height (px) so the parts still fit. */
  --ui-min-h:    400;
}

/* Wiring — no need to touch. Each part gets its own unit: the site unit
   (--u, THE ONE RULE above) × that part's control. Guest book and counter
   use --u-ui = --u with the floor above (landscape only). */
:root {
  --u-ui: max(var(--u), min(var(--ui-min), 100vh / var(--ui-min-h)));
  --u-ui: max(var(--u), min(var(--ui-min), 100dvh / var(--ui-min-h)));
  --u-filter:    calc(var(--u) * var(--filter-h));
  --u-guestbook: calc(var(--u-ui) * var(--guestbook-h));
  --u-entries:   calc(var(--u-ui) * var(--entries-h));
  --u-counter:   calc(var(--u-ui) * var(--counter-h));
  --u-loader:    calc(var(--u) * var(--loader-h));
}
@media (orientation: portrait) {
  :root {
    --u-ui: var(--u);
    --u-filter:    calc(var(--u) * var(--filter-v));
    --u-guestbook: calc(var(--u) * var(--guestbook-v));
    --u-entries:   calc(var(--u) * var(--entries-v));
    --u-counter:   calc(var(--u) * var(--counter-v));
    --u-loader:    calc(var(--u) * var(--loader-v));
  }
}

/* ── WORLD (rotated on portrait) */
.world {
  position: fixed;
  inset: 0;
  z-index: 0;
}

/* Portrait: rotate the background art to fill the screen.
   Buttons live outside .world and stay upright. */
@media (orientation: portrait) {
  .world {
    inset: auto;
    width: 100vh;
    width: 100dvh;
    height: 100vw;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(90deg);
  }
}

/* ── BACKGROUND LOOP */
.bg {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}
.bg video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ── PARALLAX LAYERS */
.layer {
  position: fixed;
  inset: 0;
  z-index: 5;
  overflow: hidden;
  pointer-events: none;
  will-change: transform;
}

.layer img {
  position: absolute;
  left: -5%;
  top: 50%;
  width: 110%;
  height: auto;
  transform: translateY(-50%);
}

/* ── BUTTONS: drawn by play.js (WebGL); sizes in play.js WIDE / TALL. Its
   own rules are at the end of this file ("THE BUTTONS").
   The one hidden player per button video (play-buttons.js). Kept on screen
   (tiny, near-invisible) so phones don't pause them as "not visible". */
#bv-players { position: fixed; left: 0; top: 0; width: 2px; height: 2px; overflow: hidden; opacity: 0.01; pointer-events: none; z-index: 0; }
#bv-players video { display: block; width: 2px; height: 2px; }

video::-webkit-media-controls { display: none !important; }
video::-webkit-media-controls-panel { display: none !important; }
video::-webkit-media-controls-start-playback-button { display: none !important; -webkit-appearance: none; }
video::-webkit-media-controls-overlay-play-button { display: none !important; }

/* ── LOCK OVERLAY */
.lock-overlay {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s;
}

.lock-overlay.active {
  opacity: 1;
  pointer-events: auto;
}

.lock-overlay video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (orientation: portrait) {
  .lock-overlay video {
    position: absolute;
    width: 100vh;
    width: 100dvh;
    height: 100vw;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(90deg);
  }
}

/* Buttons hidden during locked video playback */
/* ── Project feeds (project-feed.js) */
.feed {
  position: fixed;
  inset: 0;
  z-index: 45;
  opacity: 0;
  transform: scale(0.12);
  transition: opacity var(--feed-open-ms, 320ms) ease-out, transform var(--feed-open-ms, 320ms) cubic-bezier(.2, .8, .2, 1);
  cursor: grab;
  touch-action: pinch-zoom;
}
.feed.is-open { opacity: 1; transform: none; }
/* The page behind a feed, darkened (project-feed.js, FEED.dim). */
.feed-dim {
  position: fixed;
  inset: 0;
  z-index: 44;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--feed-open-ms, 320ms) ease-out;
}
.feed-dim.is-open { opacity: var(--feed-dim, 0.85); }
/* While a project is open, nothing behind it takes a tap: not the guest
   book, the filter or the buttons. The dim layer catches any tap that lands
   while the project pops in or out. */
body.feed-shown .feed-dim { pointer-events: auto; }
body.feed-shown #filter,
body.feed-shown #form-container,
body.feed-shown #entries-container,
body.feed-shown #visitor-counter-wrap,
body.feed-shown .pie-menu {
  pointer-events: none !important;
}
body.feed-shown #form-container *,
body.feed-shown #entries-container *,
body.feed-shown #filter * { pointer-events: none !important; }
/* Full focus on the project: the guestbook stops scrolling behind it. */
body.feed-shown #entries-scroll { animation-play-state: paused !important; }
.feed-track {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
/* Each tile is placed along the strip by project-feed.js (transform). */
.feed-tile {
  position: absolute;
  left: 0;
  top: 0;
  height: calc(var(--feed-size) * 100vh);
  aspect-ratio: var(--feed-aspect);
  will-change: transform;
}
.feed.is-vertical .feed-tile { height: auto; width: calc(var(--feed-size) * 100vw); }
.feed-tile video,
.feed-tile img { display: block; width: 100%; height: 100%; object-fit: cover; pointer-events: none; user-select: none; }
/* A project's text card (project-feed.js: title / text per project). Same
   height as the videos on wide screens, same width on tall ones. */
.feed-tile.feed-info {
  aspect-ratio: auto;
  width: min(30em, 60vw);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.8em;
  padding: 0 1.5em;
  box-sizing: border-box;
  color: #fff;
  font-family: var(--ab-font, 'VT323', monospace);
  font-size: 18px;
  line-height: 1.35;
  text-align: left;
}
.feed.is-vertical .feed-tile.feed-info { height: auto; width: calc(var(--feed-size) * 100vw); padding: 1.5em 0; font-size: 16px; }
/* Loading squares in a tile that isn't showing yet (project-feed.js WAITING
   sets every number). */
.feed-wait {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  gap: var(--wait-gap, 5px);
  pointer-events: none;
}
.feed-wait i {
  display: block;
  width: var(--wait-size, 5px);
  height: var(--wait-size, 5px);
  background: #fff;
}
/* A square with its own design (WAITING.files): the file, filling it. */
.feed-wait i.has-file { background: none; }
.feed-wait i.has-file img,
.feed-wait i.has-file video { display: block; width: 100%; height: 100%; object-fit: contain; }
.feed-wait i.is-sharp img,
.feed-wait i.is-sharp video { image-rendering: pixelated; }
.feed-info h2 { margin: 0; font: inherit; font-size: 1.4em; }
.feed-info p { margin: 0; }

/* A sound video's volume bar (project-feed.js SOUND): under the video on wide
   screens, on its left on tall ones. It travels with its video. */
.feed-vol {
  position: absolute;
  top: 100%;
  left: 50%;
  width: calc(var(--vol-len, 0.5) * 100%);
  height: 22px;                       /* the line is thin; this is the place to touch */
  margin-top: calc(var(--vol-gap, 14px) - 11px + 1px);
  transform: translateX(-50%);
  cursor: pointer;
  touch-action: none;
  outline: none;
}
.feed-vol-track { position: absolute; left: 0; right: 0; top: 50%; height: 1px; background: rgba(255, 255, 255, 0.3); }
.feed-vol-fill  { position: absolute; left: 0; top: 0; bottom: 0; width: calc(var(--vol, 0.8) * 100%); background: #fff; }
.feed-vol-knob  {
  position: absolute; top: 50%; left: calc(var(--vol, 0.8) * 100%);
  width: 7px; height: 7px; background: #fff;
  transform: translate(-50%, -50%);
}
.feed-vol:focus-visible .feed-vol-knob { outline: 1px solid #fff; outline-offset: 2px; }
.feed.is-vertical .feed-vol {
  top: 50%;
  left: auto;
  right: 100%;
  width: 22px;
  height: calc(var(--vol-len, 0.5) * 100%);
  margin: 0 calc(var(--vol-gap, 14px) - 11px + 1px) 0 0;
  transform: translateY(-50%);
}
.feed.is-vertical .feed-vol-track { left: 50%; right: auto; top: 0; bottom: 0; width: 1px; height: auto; }
.feed.is-vertical .feed-vol-fill  { top: auto; bottom: 0; left: 0; right: 0; width: auto; height: calc(var(--vol, 0.8) * 100%); }
.feed.is-vertical .feed-vol-knob  { top: auto; left: 50%; bottom: calc(var(--vol, 0.8) * 100%); transform: translate(-50%, 50%); }

/* The loading bar above the videos (project-feed.js LOADING): fills as they
   download, then fades away. */
.feed-load {
  position: absolute;
  left: 50%;
  top: calc(50vh - var(--feed-size) * 50vh - var(--load-gap, 18px) - 3px);
  width: calc(var(--load-w, 0.3) * 100vw);
  height: 3px;
  transform: translateX(-50%);
  background: rgba(255, 255, 255, 0.2);
  pointer-events: none;
  transition: opacity 0.6s ease-out;
}
.feed.is-vertical .feed-load { top: var(--load-gap, 18px); }
.feed-load-fill {
  position: absolute; inset: 0;
  background: #fff;
  transform: scaleX(var(--load, 0));
  transform-origin: left;
  transition: transform 0.2s linear;
}
.feed-load.is-done { opacity: 0; }
.looker { position: relative; }
.looker > .look-stack { position: absolute; inset: 0; pointer-events: none; }
.look-frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; visibility: hidden; }
.look-frame.is-on { visibility: inherit; }
/* An image already in the element (e.g. an about-page layer's own picture)
   only holds the size; the stack draws over it. */
.looker > img:not(.look-frame) { visibility: hidden; }
.ab-el-img .look-frame { height: 100%; }

.ab-swarm { position: fixed; inset: 0; z-index: 70; overflow: hidden; cursor: pointer; touch-action: pinch-zoom; /* a finger drag wakes them (about-swarm.js), not a scroll */ }
/* Every face is the design exactly as drawn; they differ only in behaviour. */
.ab-swarm-face { position: absolute; }
/* The clicked face, staying put on top of the swarm (about-swarm.js). */
.ab-swarm-hero { position: absolute; z-index: 3; }
/* Blinking (about-swarm.js): the shut / half-open eyes over the eye frames. */
.ab-swarm-blink { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: 1; pointer-events: none; }
/* Preview aid only (markStill in about-swarm.js / the ?tune SWARM tab):
   the still faces shown inverted, so they're easy to spot while designing. */
.ab-swarm.is-marking .ab-swarm-face[data-type="still"] { filter: invert(1); }
/* A face that starts the swarm looks clickable. */
.ab-el[data-look-swarm] { cursor: pointer; }

.ab-soul { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.ab-soul.is-behind { z-index: -1; }
/* Preview aid only (placeholder in about-soul.js / the ?tune SOUL tab): the
   face's own images stand in for the spirit, inverted to tell them apart. */
.ab-soul.is-placeholder { filter: invert(1); }
.ab-soul-fly { position: fixed; z-index: 75; pointer-events: none; }
.ab-soul-video { z-index: 80; }

/* ── FILTER (filter.js) — opposite the guest book form: top centre on wide
   screens, on the right on tall ones. Sizes in design px. */
:root {
  --fi-top:  100;       /* wide: distance from the top of the screen (inside the UI frame, above the buttons) */
  --fi-edge: 65;        /* tall: distance from the right edge (clear of the guest book cards) */

  /* Scale of the squares, lines and arrow (not the text, not the placement).
     The whole filter, placement included, also scales with --filter-h /
     --filter-v in CONTROLS at the top of this file. */
  --fi-scale-h: 0.2;    /* wide screens */
  --fi-scale-v: 0.2;    /* tall screens */

  /* ── TREE (filter.js STYLE 'tree'). × --fi-scale; filter.js rounds every
     one to whole screen pixels, so the gap inside a picked square is even. */
  --ft-main:      40;   /* the square you open it with */
  --ft-child:     40;   /* the three category squares */
  --ft-outline:   3;    /* their outline */
  --ft-inset:     8;    /* gap between the outline and the fill of a picked one */
  --ft-line:      3;    /* the lines out to them */
  --ft-spread:    90;   /* centre to centre, between the three */
  --ft-drop:      70;   /* from the square to the three */
  --ft-main-inset: 8;   /* gap between the main square's outline and its fill, while open */
  --ft-arrow-size: 40;  /* the arrow pointing at the square (a pixel-font character: filter.js ARROW_*) */
  --ft-arrow-gap: 14;   /* between the arrow and the square */
  --ft-arrow-jump: 12;  /* how far it hops, now and then (how often: filter.js JUMP_*) */
  /* The text ("filters", then the picked name): NOT × --fi-scale, so it stays readable. */
  --ft-text:      12;
  --ft-text-gap:  4;   /* between the text and the square */
  /* The three squares wiggle a little while it's open (their lines follow). */
  --ft-wiggle-x:     3; /* how far, sideways (× --fi-scale, like the squares) */
  --ft-wiggle-y:     3; /* how far, up and down */
  --ft-wiggle-speed: 1; /* 0 = still */
  --ft-blur:     0.4px; /* soften the whole filter (squares, lines, arrow, text); 0 = sharp */

  /* ── ART (filter.js STYLE 'art'): icons in a row. */
  --fi-size: 40;        /* icon height */
  --fi-gap:  10;        /* space between the icons */

  --fi-scale: var(--fi-scale-h); /* live value — don't edit */
}
@media (orientation: portrait) {
  :root { --fi-scale: var(--fi-scale-v); }
}


#filter {
  position: fixed;
  filter: blur(var(--ft-blur, 0px));
  z-index: 7; /* over the carousel (2) and HUD (3–6), under the lock overlay (10) */
}

/* ── Tree: filter.js sets every size and position in px. */
.ft-probe { /* filter.js reads the design units off this */
  position: fixed; left: 0; top: var(--gb-top);
  width: calc(1000 * var(--u-filter) * var(--fi-scale));
  height: calc(1000 * var(--u-filter));
  visibility: hidden; pointer-events: none;
}
.ft-svg { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
.ft-arrow {
  position: absolute;
  display: flex; align-items: center; justify-content: center;
  font-family: 'fi-font', 'VT323', monospace;
  line-height: 1;
  color: #fff;
  pointer-events: none;
}
#filter.is-open .ft-arrow { visibility: hidden; }
/* The hop: out, back, a smaller one, back (length: filter.js JUMP_MS). */
.ft-arrow.is-hopping { animation: ft-hop 520ms ease-in-out 1; }
@keyframes ft-hop {
  0%, 100% { transform: translate(0, 0); }
  25%      { transform: translate(var(--ft-jx), var(--ft-jy)); }
  50%      { transform: translate(0, 0); }
  68%      { transform: translate(calc(var(--ft-jx) * 0.4), calc(var(--ft-jy) * 0.4)); }
  84%      { transform: translate(0, 0); }
}
.ft-line {
  stroke: #fff;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;               /* pathLength 1: fully hidden */
  transition: stroke-dashoffset 120ms linear; /* duration: filter.js LINE_MS / OUT_MS */
}
#filter.is-open .ft-line { stroke-dashoffset: 0; }

.ft-main, .ft-pick {
  position: absolute;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
/* Bigger than they look, for fingers. */
.ft-main::before, .ft-pick::before { content: ''; position: absolute; inset: -8px; }
/* The main square: an invisible tap area over its drawing (.ft-mainsq):
   an outline, filled (inset) while open. */
.ft-main { background: transparent; border: none; }
.ft-mainsq .ft-dot { visibility: hidden; }
#filter.is-open .ft-mainsq .ft-dot { visibility: visible; }
/* The three: invisible tap areas over their drawing in the svg (.ft-kid). */
.ft-pick { background: transparent; border: none; visibility: hidden; }
#filter.is-open .ft-pick { visibility: visible; }
.ft-pick:disabled { cursor: default; }
.ft-ring, .ft-dot { fill: #fff; }
.ft-kid { visibility: hidden; }
.ft-dot { visibility: hidden; }
.ft-kid.is-on .ft-dot { visibility: inherit; }
/* They blink in as the lines reach them; closing, they're gone at once. */
#filter.is-open .ft-kid {
  visibility: visible;
  animation: ft-in var(--ft-blink-ms, 150ms) steps(1, end) var(--ft-line-ms, 120ms) both; /* filter.js BLINK_MS / LINE_MS */
}
@keyframes ft-in {
  0%   { opacity: 0; }
  25%  { opacity: 1; }
  50%  { opacity: 0.4; }
  75%, 100% { opacity: 1; }
}

.ft-text {
  position: absolute;
  font-family: 'fi-font', 'VT323', monospace;
  line-height: 1;
  color: #fff;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
}

/* The decipher, as on the about page: a scrambling glyph cut out of a block
   in the text colour, until the letter resolves. */
.fi-ch { position: relative; }
.fi-ch.is-ciphered { color: transparent; }
.fi-ch.is-ciphered::before {
  content: '';
  position: absolute;
  inset: 0;
  background: #fff;
}
.fi-ch.is-ciphered::after {
  content: attr(data-g);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'fi-glyph', 'DotGothic16', monospace;
  font-size: 0.5em;
  line-height: 1;
  color: #000;
  writing-mode: horizontal-tb;
}

/* ── Art: icons in a row (a column on tall screens). */
#filter.fi-style-art {
  top: calc(var(--fi-top) * var(--u-filter));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: calc(var(--fi-gap) * var(--u-filter));
}
@media (orientation: portrait) {
  #filter.fi-style-art {
    top: var(--gb-top); /* level with the guest book tab on the left */
    left: auto;
    right: calc(var(--fi-edge) * var(--u-filter));
    transform: translateY(-50%);
    flex-direction: column;
  }
}
.filter-icon {
  display: block;
  position: relative;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  line-height: 0;
}
.filter-icon img { display: none; height: calc(var(--fi-size) * var(--fi-scale) * var(--u-filter)); width: auto; }
/* One picture at a time: filter.js sets show-idle / show-active / show-tap
   (the tap GIF plays once, then it settles on idle or active). */
.filter-icon.show-idle .fi-idle,
.filter-icon.show-active .fi-active,
.filter-icon.show-tap .fi-tap { display: block; }
.filter-icon:disabled { cursor: default; }
/* Until an icon's art is uploaded: its name as text. */
.filter-icon .fi-label { display: none; }
.filter-icon.no-art img { display: none; }
.filter-icon.no-art .fi-label {
  display: block;
  padding: 0.3em 0.6em;
  border: 1px solid #fff;
  background: #000;
  color: #fff;
  font-family: 'VT323', monospace;
  font-size: calc(12 * var(--fi-scale) * var(--u-filter));
  line-height: 1;
  letter-spacing: 0.1em;
}
.filter-icon.no-art.is-on .fi-label { background: #fff; color: #000; }

/* ── LOADING GATE */
.enter-overlay { position: fixed; inset: 0; z-index: 20; background: #000; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: opacity 0.5s; }
.enter-overlay.dismissed { opacity: 0; pointer-events: none; }
.enter-overlay:not(.ready) { cursor: default; }
:root { --gate-btn: calc(120 * var(--u-loader)); --gate-bar: calc(165 * var(--u-loader)); }
@media (orientation: portrait) { :root { --gate-btn: calc(117 * var(--u-loader)); --gate-bar: calc(195 * var(--u-loader)); } }
.enter-overlay .enter-btn { max-width: var(--gate-btn); max-height: var(--gate-btn); display: none; cursor: pointer; }
.enter-overlay.ready .enter-btn { display: block; }
.enter-overlay.ready .loader { display: none; }
.loader { display: flex; flex-direction: column; align-items: center; gap: calc(10 * var(--u-loader)); width: var(--gate-bar); }
.loader-bar { width: 100%; height: calc(8 * var(--u-loader)); border: 1px solid #fff; background: #000; overflow: hidden; }
.loader-fill { height: 100%; width: 0%; background: #fff; transition: width 0.2s ease-out; }
.loader-bar + .loader-sub { margin-top: calc(-6 * var(--u-loader)); } /* just under the bar */
/* The thin bar (loading-gate.js): one file at a time. While it loads, a short
   segment steps back and forth; when it's ready the bar fills for a moment. */
.loader-sub { position: relative; width: 100%; height: max(1px, calc(2 * var(--u-loader))); background: rgba(255, 255, 255, 0.18); overflow: hidden; }
.loader-sub-fill { position: absolute; top: 0; bottom: 0; left: 0; width: 25%; background: #fff; }
.loader-sub.is-fail .loader-sub-fill { background: #ff5a5a; }
.loader-pct { font-family: monospace; font-size: calc(11 * var(--u-loader)); letter-spacing: 0.2em; color: #fff; }
.loader { position: relative; }
.loader-log {
  position: absolute;
  top: calc(100% + 18 * var(--u-loader));
  left: 0;
  width: max-content;
  max-width: calc(50vw + var(--gate-bar) / 2 - 16px);
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: monospace;
  font-size: calc(9 * var(--u-loader));
  line-height: 1.5;
  letter-spacing: 0.05em;
  color: #fff;
  text-align: left;
  white-space: pre;       /* keep every space: the status field is fixed-width */
}
.loader-log.is-waiting { opacity: 0.45; }
.loader-log.is-fail { color: #ff5a5a; }

/* ── PIE MENU */
.pie-menu {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 50;
}

.pie-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}

.pie-center {
  position: absolute;
  overflow: hidden;
  pointer-events: none;
  transform: scale(0);
  transform-origin: center;
}

.pie-center img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.pie-item {
  position: absolute;
  overflow: hidden;
  cursor: pointer;
  pointer-events: auto;
  transform: scale(0);
  transform-origin: center;
}

.pie-item img,
.pie-item video,
.pie-item canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The items' one shared player (pie-menu.js draws it into each item's canvas).
   Kept on screen, tiny and near-invisible, so phones don't pause it. */
.pie-video { position: fixed; left: 0; top: 0; width: 2px; height: 2px; opacity: 0.01; pointer-events: none; }
.pie-item img { filter: grayscale(1); } /* the old GIF placeholders only; videos show as they are */

.pie-item-label {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  text-align: center;
  color: #fff;
  font-family: 'VT323', monospace;
  font-size: calc(8 * var(--u)); /* pie-menu.js PIE_LABEL_SIZE overrides, in the same units */
  letter-spacing: 0.2em;
  text-transform: uppercase;
  z-index: 2;
  mix-blend-mode: difference;
  pointer-events: none;
}

.pie-toast {
  position: fixed;
  background: #000;
  color: #fff;
  border: 1px solid #fff;
  padding: calc(5 * var(--u)) calc(10 * var(--u));
  font-family: 'VT323', monospace;
  font-size: calc(16 * var(--u));
  letter-spacing: 0.1em;
  white-space: nowrap;
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
  z-index: 60;
}

.pie-toast.visible { opacity: 1; }

/* ── GUESTBOOK — FORM */
/* ── The little character */
:root {
  --gb-gap: 10;         /* wide: design px between the box and the visitor counter */
  --gb-jump: 1.2em;     /* wide: how high the box jumps (em = the form's size) */
  --gb-top: 50%;        /* tall: where the tab sits, from the top */
  --gb-hop: 3em;        /* tall: how far the tab hops out */
  --gb-hop-time: 1.3s;  /* both: how long one jump / hop lasts */
}

/* ── Wide screens: face box at the bottom centre */
#form-container {
  position: fixed;
  bottom: calc((12 + 13.5) * var(--u-counter) + 2px + var(--gb-gap) * var(--u-ui));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 3;
  font-size: calc(9 * var(--u-guestbook));
}
#form-container #form-inner { display: none; }
#form-container.is-revealed #form-inner { display: grid; animation: gb-open 0.18s ease-out; }
@media (orientation: landscape) {
  #form-container.is-revealed #form-inner { position: relative; background: #000; }
  #form-container.is-revealed #form-tab {
    position: absolute;
    bottom: calc(100% - 1px);
    left: 50%;
    z-index: -1;
    pointer-events: none;
    transform: translate(-50%, 110%);
    /* Invisible while hiding: the form fades in when it opens, and a tab
       parked behind it would show through for those few frames. */
    visibility: hidden;
  }
  #form-container.is-revealed.gb-peek #form-tab,
  #form-container.is-revealed.gb-duck #form-tab { visibility: visible; }
  #form-container.is-revealed.gb-peek #form-tab { transform: translate(-50%, 0); animation: gb-peek-up 0.3s ease-out; }
  #form-container.is-revealed.gb-duck #form-tab { animation: gb-peek-down 0.2s ease-in; }
}
@keyframes gb-peek-up {
  0%   { transform: translate(-50%, 110%); }
  70%  { transform: translate(-50%, -12%); }
  100% { transform: translate(-50%, 0); }
}
@keyframes gb-peek-down {
  from { transform: translate(-50%, 0); }
  to   { transform: translate(-50%, 110%); }
}
@media (orientation: landscape) {
  /* The bubble then sits above the character, not the form. */
  #form-container.is-revealed.gb-peek #gb-bubble { bottom: calc(100% + 2.4em + 0.667em + 2px); }
}
/* Opening: a quick fade up. Size stays put, so the pointer that opened it
   is already inside the form and hover doesn't flicker. */
@keyframes gb-open { from { opacity: 0; transform: translateY(0.4em); } }

#form-tab {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  border: 1px solid #fff;
  padding: 0.25em 0.6em;
  cursor: pointer;
  font-family: 'VT323', monospace;
  font-size: 1.591em;
  line-height: 1;
  color: #fff;
  letter-spacing: 0.1em;
  white-space: pre;
  user-select: none;
}

/* The jump: up fast, a small bounce on landing, a beat, back down. */
@keyframes gb-jump {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  16%      { transform: translateX(-50%) translateY(calc(var(--gb-jump) * -1)); }
  30%      { transform: translateX(-50%) translateY(0); }
  40%      { transform: translateX(-50%) translateY(calc(var(--gb-jump) * -0.35)); }
  50%      { transform: translateX(-50%) translateY(0); }
}
#form-container.gb-hop { animation: gb-jump var(--gb-hop-time) ease-out; }

/* ── Tall screens: tab on the left edge, form slides out ── */
@media (orientation: portrait) {
  #form-container {
    font-size: calc(8.8 * var(--u-guestbook));
    bottom: auto;
    top: var(--gb-top);
    left: -17.5em;          /* = #form-inner's width: only the tab shows */
    transform: translateY(-50%);
    flex-direction: row;
    align-items: stretch;
    transition: left 0.3s ease-out;
  }
  #form-container.is-revealed { left: 0; }
  #form-container #form-inner { display: grid; pointer-events: none; }
  #form-container.is-revealed #form-inner { pointer-events: auto; animation: none; }
  #form-container.is-revealed #form-tab { display: flex; }
  #form-tab {
    width: 2.286em;
    padding: 0;
    border-left: none;
    writing-mode: vertical-rl;
    line-height: normal;
  }
  #form-container.gb-hop { animation-name: gb-hop; }
}
/* The hop: out fast, a little bounce, a beat, then back in. */
@keyframes gb-hop {
  0%, 100% { transform: translateY(-50%) translateX(0); }
  18%      { transform: translateY(-50%) translateX(calc(var(--gb-hop) * 1.1)); }
  28%      { transform: translateY(-50%) translateX(calc(var(--gb-hop) * 0.8)); }
  36%      { transform: translateY(-50%) translateX(var(--gb-hop)); }
  72%      { transform: translateY(-50%) translateX(var(--gb-hop)); }
}
@media (prefers-reduced-motion: reduce) {
  #form-container.gb-hop { animation: none; }
  #form-container.is-revealed #form-inner { animation: none; }
}

#form-inner { box-sizing: border-box; border: 1px solid #fff; padding: 0.75em; display: grid; grid-template-columns: 1fr auto; gap: 0.375em; width: 17.5em; overflow: hidden; background: #000; }
#form-inner input, #form-inner button { min-width: 0; }
/* After sending, the fields are hidden but keep their space; the face and the
   thanks message sit on top of them (outside the grid, so the size never changes). */
#form-inner { position: relative; }
#form-inner.is-sent { background: #000; } /* phones: solid behind the thanks, like on desktop */
#title { grid-column: 1/-1; font-family: 'VT323', monospace; font-size: 1em; letter-spacing: 0.15em; text-align: center; pointer-events: none; color: #fff; }
#char-counter { grid-column: 1/-1; font-family: 'VT323', monospace; font-size: 0.8125em; text-align: right; background: #000; color: #fff; opacity: 0.5; padding: 0.154em 0.308em; }
#char-counter.warn { opacity: 1; color: #c00; }
#form-container input[type="text"] { border: 1px solid #000; padding: 0.5em 0.75em; font-family: 'VT323', monospace; font-size: 1em; outline: none; background: #fff; }
#form-container button { border: 1px solid #000; padding: 0.5em 1em; font-family: 'VT323', monospace; font-size: 1em; background: #000; color: #fff; cursor: pointer; transition: background 0.15s, color 0.15s; }
#form-container button:hover { background: #fff; color: #000; }
#form-container #kaomoji-btn { padding: 0.533em 0.667em; font-size: 0.9375em; white-space: nowrap; }
#kaomoji-btn, #enter-btn { border: 1px solid #fff; }
#kaomoji-picker { display: none; position: absolute; bottom: calc(100% + 0.5em); left: 50%; transform: translateX(-50%); background: #000; border: 1px solid #444; padding: 0.5em; grid-template-columns: repeat(4, 1fr); gap: 0.25em; z-index: 4; width: max-content; }
#kaomoji-picker.open { display: grid; }
#form-container .kao { font-size: 0.875em; padding: 0.286em 0.429em; color: #fff; cursor: pointer; white-space: nowrap; background: transparent; border: 1px solid transparent; font-family: 'VT323', monospace; }
.kao:hover { border-color: #444; }
#submission-kao { display: none; position: absolute; left: 0; right: 0; top: calc(50% + 0.6em); transform: translateY(-50%); text-align: center; font-family: 'VT323', monospace; font-size: 1.375em; color: #fff; padding: 0.182em 0; white-space: nowrap; }
#done-message { display: none; position: absolute; left: 0; right: 0; top: calc(50% + 0.6em); transform: translateY(-50%); text-align: center; font-family: 'VT323', monospace; font-size: 1em; color: #fff; opacity: 0.5; padding: 0.25em 0; }
@keyframes gb-annoyed {
  0%, 100% { translate: 0 0; }
  20%      { translate: -0.35em 0; }
  40%      { translate: 0.35em 0; }
  60%      { translate: -0.2em 0; }
  80%      { translate: 0.12em 0; }
}
#form-container.gb-annoyed { animation: gb-annoyed 0.32s steps(2); }
#form-container input[type="text"].gb-missing { border-color: #c00; box-shadow: 0 0 0 1px #c00; }
@media (prefers-reduced-motion: reduce) { #form-container.gb-annoyed { animation: none; } }
/* Tall screens: the picker opens along the slide-out, three across. */
@media (orientation: portrait) {
  #kaomoji-picker { left: 0; transform: none; width: 17.5em; grid-template-columns: repeat(3, 1fr); padding: 0.682em; gap: 0.341em; }
  #form-container .kao { font-size: 0.75em; padding: 0.25em 0.333em; }
}

/* "psst!" — a speech bubble pointing at the face. Wide: above the box.
   Tall: just right of the tab. It rides along with the jump / hop. */
#gb-bubble { position: absolute; bottom: calc(100% + 0.667em); left: 50%; transform: translateX(-50%) scale(0.85); transform-origin: bottom center; background: #000; border: 1px solid #fff; color: #fff; font-family: 'VT323', monospace; font-size: 0.9375em; letter-spacing: 0.05em; padding: 0.4em 0.667em; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease; z-index: 5; }
#gb-bubble.show { opacity: 1; transform: translateX(-50%) scale(1); }
#gb-bubble::before, #gb-bubble::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 0.4em solid transparent; border-top-color: #fff; }
#gb-bubble::after { top: calc(100% - 1px); border-width: 0.333em; border-top-color: #000; }
@media (orientation: portrait) {
  #gb-bubble { bottom: auto; left: calc(100% + 0.667em); top: 50%; transform: translateY(-50%) scale(0.85); transform-origin: left center; }
  #gb-bubble.show { transform: translateY(-50%) scale(1); }
  #gb-bubble::before, #gb-bubble::after { top: 50%; left: auto; right: 100%; transform: translateY(-50%); border-top-color: transparent; border-right-color: #fff; }
  #gb-bubble::after { top: 50%; right: calc(100% - 1px); border-right-color: #000; }
}
#form-container.is-revealed #gb-bubble { opacity: 0; }
#form-container.is-revealed.gb-peek #gb-bubble.show { opacity: 1; }

/* ── GUESTBOOK — ENTRIES */
:root {
  --entries-peek: 0.3;  /* wide: how much of a card shows while hidden (0.5 = half, 0 = none) */
  --card-w: calc(165 * var(--u-entries));
  --card-off: calc(var(--card-w) * (1 - var(--entries-peek)));
  --scroll-gap: calc(12 * var(--u-entries));
  --entry-font: calc(10.5 * var(--u-entries));
  --entry-pad: calc(6 * var(--u-entries)) calc(9 * var(--u-entries));
  --meta-name-font: calc(9.75 * var(--u-entries));
  --meta-date-font: calc(8.25 * var(--u-entries));
}
@media (orientation: portrait) {
  :root {
    --card-w: calc(170 * var(--u-entries));
    --card-off: calc(145 * var(--u-entries));
    --scroll-gap: calc(5 * var(--u-entries));
    --entry-font: calc(10 * var(--u-entries));
    --entry-pad: calc(5 * var(--u-entries)) calc(7 * var(--u-entries));
    --meta-name-font: calc(10 * var(--u-entries));
    --meta-date-font: calc(8 * var(--u-entries));
  }
}

#entries-container {
  position: fixed;
  right: calc(-1 * var(--card-off));
  top: 0;
  bottom: 0;
  width: var(--card-w);
  overflow: hidden;
  pointer-events: none;
  z-index: 8; /* over the filter (7), under the lock overlay (10) */
  background: #000; /* the gaps between the entries too: nothing shows through */
  transition: right 0.3s ease-out;
}
#entries-container.is-hovered { right: 0; }

@keyframes entries-scroll {
  from { transform: translateY(0); }
  to   { transform: translateY(calc(-1 * var(--scroll-dist))); }
}

#entries-scroll {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--scroll-gap);
  will-change: transform;
  /* The strip itself catches the mouse (its container doesn't): the gaps
     between the entries count as hovering too — no hiding and showing as the
     cursor crosses one. */
  pointer-events: auto;
}

.entry {
  width: var(--card-w);
  flex-shrink: 0;
  padding: var(--entry-pad);
  background: #000;
  color: #fff;
  border: 1px solid #444;
  font-family: 'VT323', monospace;
  font-size: var(--entry-font);
  line-height: 1.0;
  text-align: left;
  pointer-events: auto;
  transition: border-color 0.25s, background 0.6s ease, color 0.6s ease;
}
.entry.new { background: #fff; color: #000; border-color: #fff; }
.entry.new .meta-name { color: #000; }
.entry.new .meta-date { color: #555; }
#entries-container.is-hovered .entry { border-color: #888; }
.entry .message { word-break: break-word; }
.entry .kao-nw { white-space: nowrap; } /* a kaomoji moves to the next line whole (guestbook.js) */
.meta-name { font-size: var(--meta-name-font); font-weight: bold; color: #fff; margin-top: calc(6 * var(--u-entries)); font-family: 'VT323', monospace; }
.meta-date { font-size: var(--meta-date-font); color: #777; margin-top: calc(2 * var(--u-entries)); font-family: 'VT323', monospace; }

/* ── VISITOR COUNTER */
#visitor-counter-wrap {
  position: fixed;
  left: 50%;
  bottom: calc(12 * var(--u-counter));
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  justify-content: center;
}
#y2k-counter { display: inline-flex; gap: 1px; }
#y2k-counter span {
  display: inline-block;
  width: calc(10.5 * var(--u-counter));
  height: calc(13.5 * var(--u-counter));
  line-height: calc(13.5 * var(--u-counter));
  text-align: center;
  background: #000;
  color: #fff;
  font-family: 'VT323', monospace;
  font-size: calc(9 * var(--u-counter));
  border: 1px solid #444;
}

@media (orientation: portrait) {
  #y2k-counter span {
    width: calc(10 * var(--u-counter));
    height: calc(13 * var(--u-counter));
    line-height: calc(13 * var(--u-counter));
    font-size: calc(9 * var(--u-counter));
  }
}

/* ── ABOUT PAGE */
:root {
  --stage-ease: cubic-bezier(.3, .7, .2, 1); /* the about text's entrance */

  --about-bg:   #c40000; /* the about page red: background, wipe AND cipher glyph colour */
  /* Film grain over the about background (about-bg.js, made in code). */
  --grain-amount: 0.02;  /* strength: 0 = none, 0.06 = subtle film, 0.15 = heavy */
  --grain-size:   1.3;   /* grain size in the screen's own pixels (1 = finest): finer on sharp phone screens */
  --grain-fps:    24;    /* how often the grain changes per second (film = 24) */
  --px-cell:    1px;    /* pixel size of the wipe, in design px (× --u, about-pixels.js) */
  --px-dur:     1s;    /* wipe duration, each way (before hitches) */
  --px-jitter:  0.05;    /* random per-pixel noise on the front — 0 = none */
  --px-dither:  .2;   /* ordered (Bayer) dither band on the front — 0 = none, higher = wider band */
  --px-veins:   4;     /* physarum scouts per click — more = more tendrils */
  --px-lag:     0;    /* 0 = smooth; higher = more slow zones + clock hitches */
}

#about-pixels {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 90; /* above the whole stage and #about, below the ?tune panel (100) */
  pointer-events: none;
  display: none;
}
#about-pixels.is-active { display: block; }

body.about-open #filter,
body.about-open #form-container,
body.about-open #entries-container,
body.about-open #visitor-counter-wrap {
  pointer-events: none;
}
body.about-shown .world,
body.about-shown #filter,
body.about-shown #form-container,
body.about-shown #entries-container,
body.about-shown #visitor-counter-wrap {
  visibility: hidden;
}

/* ── The page itself */
/* The about background: red + film grain (about-bg.js), right behind #about. */
#about-bg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 19;
  pointer-events: none;
  background: var(--about-bg);
  visibility: hidden;
}
body.about-shown #about-bg { visibility: visible; }

#about {
  position: fixed;
  inset: 0;
  z-index: 20;
  background: transparent; /* the grain background (#about-bg) shows through */
  color: #fff;
  font-family: 'VT323', monospace;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* No transition of its own: it only ever switches while the pixel wipe
     covers the screen, so it snaps. */
  pointer-events: none;
  visibility: hidden;
}

body.about-shown #about {
  pointer-events: auto;
  visibility: visible;
  /* A finger drag belongs to the page (the face follows it, pulling on its
     rope), not to the browser's own pan / bounce. Pinch-zoom still works.
     The board is scaled to fit, so there's nothing to scroll anyway. */
  touch-action: pinch-zoom;
}

/* ── TYPOGRAPHY TOKENS */
#about {
  --ab-weight:       400;

  /* Terminal effect. about-text.js reads these too, so one slider moves both. */
  --ab-char-w:       1ch;     /* fixed character cell — set to `auto` for a
                                 proportional, non-grid look */
  --ab-cursor-color: #000000;
  --ab-cipher-color: var(--about-bg); /* glyph — cut out of the box in the page colour */
  --ab-cipher-box:   #000000;         /* the inverted block behind each glyph */
  --ab-cipher-scale:     0.5; /* glyph size × text size (0.5 ≈ one cell wide) */
  --ab-cipher-box-scale: 1;   /* box height × text size */
  --ab-cipher-box-shift: 0;   /* nudge the box up (−) / down (+), × text size */

  --ab-cipher-size:  calc(var(--ab-cipher-scale) * 1em);
  --ab-cipher-box-h: calc(var(--ab-cipher-box-scale) * 1em);
  --ab-cipher-box-y: calc(var(--ab-cipher-box-shift) * 1em);
  --ab-cursor-blink: 0.5s;
  --ab-type-ms:      18;      /* ms per character (unitless — JS reads it) */
  --ab-line-pause:   260;     /* ms between lines */
  --ab-cipher-r:     110;     /* cursor influence radius, in board px (scales with the page) */
  --ab-cipher-hz:    26;      /* glyph reshuffles per second */
  --ab-cipher-on:    1;       /* 0 = no cipher near the cursor, anywhere */
  --ab-loader-color: #000000; /* the loading bar top-left */
  --ab-blob-wobble:  0.5;     /* how much the cipher blob's edge breathes; 0 = still */
  --ab-blob-trail:   0.5;     /* how far it lags, stretches and tails behind; 0 = glued */
}

/* ── TERMINAL TEXT */
.ab-w { white-space: nowrap; }

.ab-char {
  display: inline-block;
  /* inline-size, not width: in vertical text the cell's run is its height. */
  inline-size: calc(var(--ab-char-w) + var(--ab-track, 0em));
  letter-spacing: 0;
  text-align: center;
}
.ab-char.is-wide { inline-size: calc(var(--ab-char-w) * 2 + var(--ab-track, 0em)); }

.ab-el-text.is-vertical {
  writing-mode: vertical-rl;
  text-orientation: upright;
}
.ab-el-text.is-vertical .ab-char,
.ab-el-text.is-vertical .ab-char.is-wide {
  inline-size: calc(1em + var(--ab-track, 0em));
}

/* Not yet typed: holds its space so nothing reflows as the line fills in. */
.ab-char.is-pending { visibility: hidden; }

/* The write head — a character-sized red block that blinks as text appears. */
.ab-char.is-head {
  visibility: visible;
  color: transparent;
  background: var(--ab-cursor-color);
  animation: ab-blink var(--ab-cursor-blink) steps(1) infinite;
}

@keyframes ab-blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0.15; }
}

.ab-char { position: relative; }
.ab-char.is-ciphered { color: transparent; }
.ab-char.is-ciphered::before {
  content: '';
  position: absolute;
  /* Logical sides, so the box also sits right in vertical text. */
  inset-inline: 0;
  inset-block-start: calc(50% - var(--ab-cipher-box-h) / 2 + var(--ab-cipher-box-y));
  block-size: var(--ab-cipher-box-h);
  background: var(--ab-cipher-box);
  pointer-events: none;
}
.ab-char.is-ciphered::after {
  content: attr(data-g);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ab-glyph-font, 'DotGothic16', monospace);
  font-size: var(--ab-cipher-size);
  line-height: 1;
  color: var(--ab-cipher-color);
  pointer-events: none;
}

/* Trailing caret parked after the last line once everything has typed. */
.ab-caret {
  display: inline-block;
  width: var(--ab-char-w);
  height: 1em;
  vertical-align: text-bottom;
  background: var(--ab-cursor-color);
  animation: ab-blink var(--ab-cursor-blink) steps(1) infinite;
}

/* No focus box on a tap (about.js only focuses it for keyboard users). */
#about-close:focus:not(:focus-visible) { outline: none; }
#about-close {
  position: fixed;
  top: clamp(16px, 3vh, 32px);
  right: clamp(16px, 3vw, 40px);
  z-index: 2;
  background: none;
  border: none;
  color: #000; /* always black, at full strength (no dimming, no hover change) */
  font-family: 'VT323', monospace;
  font-size: clamp(18px, 2.4vw, 26px);
  cursor: pointer;
}

#about-loader {
  /* Fallback spot, only until about-scene.js puts it on the board. */
  position: fixed;
  top: clamp(16px, 3vh, 32px);
  left: clamp(16px, 3vw, 40px);
  display: flex;
  align-items: center;
  color: var(--ab-loader-color, #000);
  font-size: 8px; /* = thickness (the layer's size) */
  pointer-events: none;
}
/* On the board it's a layer: position/size from the scene (inline styles). */
#about-loader.ab-el {
  position: absolute;
  top: auto;
  left: auto;
  z-index: 60;
}
.ab-ldr-bar {
  flex: 1;
  min-width: 0;
  height: 1em;
  box-sizing: border-box;
  border: max(1px, 0.12em) solid currentColor;
  padding: max(1px, 0.14em);
  overflow: hidden;
}
.ab-ldr-fill { height: 100%; width: 0; background: currentColor; transition: width 0.15s linear; }
/* 100%: hold a beat, then blink away (ends hidden). Leaving the page
   (.is-gone) hides it at once. The editor keeps it visible to place it. */
#about-loader.is-done { animation: ab-ldr-blink 0.9s steps(1) 0.35s forwards; }
#about-loader.is-gone { visibility: hidden; }
@keyframes ab-ldr-blink {
  0%   { opacity: 1; }
  15%  { opacity: 0; }
  30%  { opacity: 1; }
  45%  { opacity: 0; }
  60%  { opacity: 1; }
  75%, 100% { opacity: 0; visibility: hidden; }
}
#about.ab-editing #about-loader { animation: none; opacity: 1; visibility: visible; pointer-events: auto; cursor: move; }
/* Once about-scene.js has put it on the board, position and size come from
   the scene data (inline styles), in board units like every other element. */
#about-close.ab-el {
  position: absolute;
  top: auto;
  right: auto;
  z-index: 60; /* above every item.z in use, below the ?tune guides overlay's labels */
  padding: 0;
  text-align: left;
  white-space: nowrap;
  font-family: var(--ab-font, 'VT323', monospace); /* follow the master font */
}

/* ── Frame layers (about-scene.js / about-text.js) */
.ab-el-frame { box-sizing: border-box; }
.ab-el-frame .ab-fc { position: absolute; line-height: 1; white-space: nowrap; }
.ab-el-frame .ab-fc-tl { left: 0;  top: 0; }
.ab-el-frame .ab-fc-tr { right: 0; top: 0; }
.ab-el-frame .ab-fc-bl { left: 0;  bottom: 0; }
.ab-el-frame .ab-fc-br { right: 0; bottom: 0; }

/* ── Staggered entrance */
/* Delay is only applied in the shown state, so closing collapses uniformly
   instead of unwinding line by line in reverse. */
.about-line {
  opacity: 0;
  transition: opacity 0.55s ease;
  transition-delay: 0s;
}

.ab-el.about-line.is-in {
  opacity: 1;
  transform: none;
}

.ab-el[data-hover] { cursor: default; }
.ab-el[data-hover="shake"].is-hovering  { animation: ab-shake 0.35s steps(2) infinite; }
.ab-el[data-hover="glitch"].is-hovering { animation: ab-glitch 0.5s steps(1) infinite; }
.ab-el[data-hover="grow"]   { transition: scale 0.18s steps(3); }
.ab-el[data-hover="grow"].is-hovering   { scale: 1.08; }
.ab-el[data-hover="blink"].is-hovering  { animation: ab-blink-el 0.3s steps(1) infinite; }
.ab-el[data-hover="invert"].is-hovering {
  background: var(--ab-cipher-box, #000);
  color: var(--about-bg, #c40000) !important;
}
@keyframes ab-shake {
  0%   { translate: 2px 0; }
  25%  { translate: -2px 1px; }
  50%  { translate: 1px -2px; }
  75%  { translate: -1px 2px; }
  100% { translate: 0 0; }
}
/* Glitch hover: small jumps and flickers (was ±6–8px and down to 20% — toned down). */
@keyframes ab-glitch {
  0%   { translate: 0 0;      opacity: 1; }
  20%  { translate: -3px 0;   opacity: 0.75; }
  40%  { translate: 2px 0.5px; opacity: 1; }
  60%  { translate: 0 -1px;   opacity: 0.5; }
  80%  { translate: 4px 0;    opacity: 1; }
}
@keyframes ab-blink-el {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ab-el.is-hovering { animation: none !important; }
  .about-line { transition-duration: 0.01s; }
  .ab-char.is-head, .ab-caret { animation: none; }
}

/* ── ABOUT — ARTBOARD */
.ab-stage {
  position: relative;
  width: 100%;
}

.ab-board {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: top left;
}

.ab-el {
  position: absolute;
  /* --ab-font is the master font from the ?tune editor (about-fonts.js);
     a locked element sets its own font-family inline instead. */
  font-family: var(--ab-font, 'VT323', monospace);
  font-weight: var(--ab-weight, 400);
}

.ab-el-text {
  /* Honour Enter presses from the editor's text box on plain (non-terminal)
     text. Terminal text gets real <br>s from about-text.js instead. */
  white-space: pre-line;
  /* Not selectable: selecting drew highlight boxes over the page. */
}

.ab-el-img img {
  display: block;
  width: 100%;
  height: auto;
}
/* Hover media over an image layer (about-scene.js): the video, then the
   hover image on top — both fill the layer, both hidden until hover. */
.ab-el-img .ab-hv,
.ab-el-img img.ab-hg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
}
.ab-el-img .ab-hv { z-index: 1; }
.ab-el-img img.ab-hg { z-index: 2; }
.ab-el-img.is-hovering .ab-hv,
.ab-el-img.is-hovering img.ab-hg { opacity: 1; }

/* ══ THE BUTTONS (play.js, play-buttons.js) — one WebGL canvas (#stage) with
   the frames over it (#frames) and the dial (#dial). After everything above,
   so these win over the older button rules. */
body { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; overscroll-behavior: none; }
/* The canvas takes every touch itself: no page scroll, no pinch, no double-tap
   zoom. Over the background (.world, 0), under the frames, the dial and the rest. */
#stage { display: block; position: fixed; inset: 0; width: 100%; height: 100%; touch-action: none; z-index: 1; }
/* The dither on the sides (edge-dissolve.js): the buttons and their frames
   dissolve toward the edges. */
#stage, #frames {
  mask-image: var(--edge-dissolve-mask); mask-size: 100% 100%; mask-repeat: no-repeat; mask-position: center;
  -webkit-mask-image: var(--edge-dissolve-mask); -webkit-mask-size: 100% 100%; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center;
}
#stage.is-over { cursor: pointer; }
#stage.is-locked { cursor: not-allowed; }
#label { position: fixed; left: 0; right: 0; bottom: 6%; text-align: center; color: #fff; font: 14px monospace; letter-spacing: 0.1em; opacity: 0; transition: opacity 0.3s; pointer-events: none; z-index: 3; }
#label.on { opacity: 1; transition: none; }
/* The dial: a small window over the buttons (play.js sizes and places it).
   "screen": it only adds light to what's behind it, like a glowing layer in
   front of the device — never darkens it. */
#dial { position: fixed; left: 0; top: 0; pointer-events: none; z-index: 3; mix-blend-mode: screen; }
#fps { position: fixed; left: 8px; top: 8px; color: #0f0; font: 11px monospace; pointer-events: none; z-index: 30; }

/* ── The frames (frame.js draws them, play.js places each over its button).
   Sizes come from play.js: --btn-size, --btn-deco (px). */
#frames { position: fixed; inset: 0; pointer-events: none; z-index: 2; overflow: hidden; display: block; padding: 0; will-change: auto; }
#frames .btn-wrap { position: absolute; left: 0; top: 0; width: var(--btn-size); height: var(--btn-size); transform-origin: 50% 50%; }
/* Until play.js takes them over (it waits for three.js to download), the
   stand-ins sit stacked in the top-left corner: unseen. They keep their size,
   so frame.js can still measure them. */
#frames:not(.is-live) .btn-wrap { visibility: hidden; }
#frames .btn { position: absolute; inset: 0; }
#frames .btn-frame {
  position: absolute; width: var(--btn-deco); height: var(--btn-deco);
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  overflow: visible; pointer-events: none;
}
#frames .bf-swell { transform-box: view-box; transform-origin: 50% 50%; transition: transform 0.15s ease-out; }
#frames .btn-wrap.is-hover .bf-swell { transform: scale(1.02); }
/* Safari's engine: drawn 3× bigger with a CSS blur and shrunk back (frame.js). */
html.is-webkit #frames .btn-frame.bf-ss {
  width: calc(var(--btn-deco) * 3); height: calc(var(--btn-deco) * 3);
  transform: translate(-50%, -50%) scale(0.333333);
  filter: blur(var(--bf-blur-px, 0px));
  /* Its own layer: else Safari redraws every blurred frame every frame
     (7 fps on a MacBook; 60 with this — looks the same). */
  will-change: transform;
}
/* The about page covers everything; behind a project the buttons fade (play.js). */
body.about-shown #stage, body.about-shown #frames, body.about-shown #dial { visibility: hidden; }

/* ══ THE WINDOW (window.js — sizes in its control panel, design px × --u) */
#win-controls {
  /* window.js moves it onto the UI art's corner line (WIN.wide / WIN.tall) */
  position: fixed;
  left: 0;
  top: 0;
  display: flex;
  align-items: flex-end;
  gap: calc(var(--win-gap) * var(--win-px, 0.375px)); /* window.js: sizes in the UI art's pixels */
  z-index: 7; /* under the guestbook entries (8: always on top), over the filter (7, comes later in the page) */
}
/* No UI art found: the screen's top-right corner. */
body.win-corner #win-controls { left: auto; right: calc(12 * var(--u) + env(safe-area-inset-right, 0px)); top: calc(12 * var(--u) + env(safe-area-inset-top, 0px)); }
.win-anchor { position: absolute; left: -5%; top: 50%; width: 110%; transform: translateY(-50%); pointer-events: none; visibility: hidden; }
.win-anchor i { position: absolute; width: 0; height: 0; }
.win-btn {
  position: relative;
  width: calc(var(--win-size) * var(--win-px, 0.375px));
  height: calc(var(--win-size) * var(--win-px, 0.375px));
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  color: #fff;
  cursor: pointer;
  display: block;
}
/* A bigger invisible tap area round each one (phones): taller, and across
   only to the middle of the gap, so two never overlap (the outer ones reach out further). */
.win-btn::after { content: ''; position: absolute; top: -12px; bottom: -12px; left: calc(var(--win-gap) * var(--win-px, 0.375px) / -2); right: calc(var(--win-gap) * var(--win-px, 0.375px) / -2); }
.win-btn:first-child::after { left: -12px; }
.win-btn:last-child::after { right: -12px; }
.win-btn svg, .win-btn img { width: 100%; height: 100%; display: block; overflow: visible; }
.win-btn svg { fill: none; stroke: currentColor; } /* line thickness: window.js WIN.line, in art pixels */
#win-version {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 7; /* under the guestbook entries, like the buttons */
  color: #fff;
  font-family: 'VT323', monospace;
  font-size: calc(var(--win-vsize) * var(--win-px, 0.375px));
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}
body.win-corner #win-version { display: none; }
.win-btn.is-off { opacity: var(--win-greyed); cursor: default; pointer-events: none; }
.win-btn:focus:not(:focus-visible) { outline: none; }
/* Covered by the about page or a project: the buttons go with the rest. */
body.about-open #win-controls,
body.about-shown #win-controls,
body.feed-shown #win-controls,
body.about-open #win-version,
body.about-shown #win-version,
body.feed-shown #win-version { visibility: hidden; pointer-events: none; }

/* Closed / minimized: only the desktop shows; everything else is hidden and
   paused (play.js, frame.js, main.js check body.win-closed). */
body.win-closed > *:not(#desktop) { visibility: hidden !important; pointer-events: none !important; }
body.win-closed #entries-scroll { animation-play-state: paused !important; }

/* ── The desktop: Windows 98, in black (window.js DESK; sizes in screen px).
   The 98 bevels: a light edge top-left and a dark one bottom-right (raised),
   or the other way round (pressed / sunk in). */
:root {
  --w98-face: #1c1c1c;     /* the grey of 98, here near-black */
  --w98-hi: #5a5a5a;       /* outer light edge */
  --w98-hi2: #333;         /* inner light edge */
  --w98-lo: #000;          /* outer dark edge */
  --w98-lo2: #0a0a0a;      /* inner dark edge */
  --w98-text: #fff;
  --w98-select: #000080;   /* 98's selection blue */
  --w98-raised: inset -1px -1px var(--w98-lo), inset 1px 1px var(--w98-hi), inset -2px -2px var(--w98-lo2), inset 2px 2px var(--w98-hi2);
  --w98-pressed: inset -1px -1px var(--w98-hi), inset 1px 1px var(--w98-lo), inset -2px -2px var(--w98-hi2), inset 2px 2px var(--w98-lo2);
  --w98-sunk: inset -1px -1px var(--w98-hi), inset 1px 1px var(--w98-lo2);
}
#desktop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #000 center / cover no-repeat;
  font-family: 'MS Sans Serif', 'Microsoft Sans Serif', Tahoma, Arial, sans-serif;
  -webkit-user-select: none;
  user-select: none;
}
body.win-closed #desktop { display: block; }
/* 98's scrollbars: raised thumb and arrow buttons on a checkered track
   (98: grey + white squares; here --w98-face + --w98-hi). Firefox can only
   colour its own scrollbar, so it gets the colours. (scrollbar-color would
   switch the 98 drawing off in Chrome, hence the @supports.) */
@supports not selector(::-webkit-scrollbar) {
  #desktop { scrollbar-color: var(--w98-hi) var(--w98-face); }
}
#desktop ::-webkit-scrollbar { width: 16px; height: 16px; }
#desktop ::-webkit-scrollbar-track {
  background: repeating-conic-gradient(var(--w98-face) 0 25%, var(--w98-hi) 0 50%) 0 0 / 2px 2px;
}
#desktop ::-webkit-scrollbar-thumb { background: var(--w98-face); box-shadow: var(--w98-raised); }
#desktop ::-webkit-scrollbar-corner { background: var(--w98-face); }
#desktop ::-webkit-scrollbar-button:single-button {
  display: block;
  width: 16px;
  height: 16px;
  background: var(--w98-face) center no-repeat;
  box-shadow: var(--w98-raised);
}
#desktop ::-webkit-scrollbar-button:vertical:start:increment, #desktop ::-webkit-scrollbar-button:vertical:end:decrement,
#desktop ::-webkit-scrollbar-button:horizontal:start:increment, #desktop ::-webkit-scrollbar-button:horizontal:end:decrement { display: none; } /* one arrow at each end, as in 98 */
#desktop ::-webkit-scrollbar-button:single-button:active { box-shadow: inset 0 0 0 1px var(--w98-hi2); background-position: calc(50% + 1px) calc(50% + 1px); } /* 98: flat, the arrow moves a pixel */
#desktop ::-webkit-scrollbar-button:single-button:vertical:decrement { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='4' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M3 0h1v1h1v1h1v1h1v1H0V3h1V2h1V1h1z'/%3E%3C/svg%3E"); }
#desktop ::-webkit-scrollbar-button:single-button:vertical:increment { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='4' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M0 0h7v1H6v1H5v1H4v1H3V3H2V2H1V1H0z'/%3E%3C/svg%3E"); }
#desktop ::-webkit-scrollbar-button:single-button:horizontal:decrement { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='7' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M3 0h1v7H3V6H2V5H1V4H0V3h1V2h1V1h1z'/%3E%3C/svg%3E"); }
#desktop ::-webkit-scrollbar-button:single-button:horizontal:increment { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='7' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M0 0h1v1h1v1h1v1h1v1H3v1H2v1H1v1H0z'/%3E%3C/svg%3E"); }
/* ── 98 scrollbars drawn by the site (window.js tbWindow.scrollbars, SCROLL): the same in every
   browser and phone, and in a broken window's bits. The box's own browser scrollbars are hidden. */
.w98-scroller { scrollbar-width: none; }
#desktop .w98-scroller::-webkit-scrollbar { display: none; }
.w98-sb { position: absolute; display: none; z-index: 1; touch-action: none; }
.w98-sb.is-on { display: flex; }
.w98-sb-v { flex-direction: column; }
#desktop .w98-sb-btn { flex: none; width: 16px; height: 16px; padding: 0; cursor: default; background: var(--w98-face) center no-repeat; box-shadow: var(--w98-raised); }
#desktop .w98-sb-btn:active { box-shadow: inset 0 0 0 1px var(--w98-hi2); background-position: calc(50% + 1px) calc(50% + 1px); } /* 98: flat, the arrow moves a pixel */
.w98-sb-track { flex: 1; position: relative; min-width: 0; min-height: 0; background: repeating-conic-gradient(var(--w98-face) 0 25%, var(--w98-hi) 0 50%) 0 0 / 2px 2px; }
.w98-sb-thumb { position: absolute; background: var(--w98-face); box-shadow: var(--w98-raised); }
.w98-sb-v .w98-sb-thumb { left: 0; right: 0; }
.w98-sb-h .w98-sb-thumb { top: 0; bottom: 0; }
.w98-sb-corner { position: absolute; display: none; z-index: 1; width: 16px; height: 16px; background: var(--w98-face); }
#desktop .w98-sb-up { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='4' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M3 0h1v1h1v1h1v1h1v1H0V3h1V2h1V1h1z'/%3E%3C/svg%3E"); }
#desktop .w98-sb-down { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='4' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M0 0h7v1H6v1H5v1H4v1H3V3H2V2H1V1H0z'/%3E%3C/svg%3E"); }
#desktop .w98-sb-left { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='7' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M3 0h1v7H3V6H2V5H1V4H0V3h1V2h1V1h1z'/%3E%3C/svg%3E"); }
#desktop .w98-sb-right { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='7' shape-rendering='crispEdges'%3E%3Cpath fill='%23fff' d='M0 0h1v1h1v1h1v1h1v1H3v1H2v1H1v1H0z'/%3E%3C/svg%3E"); }
#desktop button { margin: 0; border: 0; background: none; color: var(--w98-text); font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
#desktop button:focus:not(:focus-visible) { outline: none; }
#desktop .desk-icon { /* #desktop: beats "#desktop button { font: inherit }" for the size */
  position: absolute;
  left: calc(var(--desk-left) + env(safe-area-inset-left, 0px));
  top: calc(var(--desk-top) + env(safe-area-inset-top, 0px));
  width: calc(var(--desk-icon) * 2.3);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 2px;
  font-size: var(--desk-label);
  line-height: 1.2;
  text-align: center;
  touch-action: none; /* a finger drags it (window.js) instead of scrolling */
}
.desk-icon img { width: var(--desk-icon); height: var(--desk-icon); object-fit: contain; display: block; pointer-events: none; -webkit-user-drag: none; }
.desk-icon span { padding: 0 2px; text-shadow: 1px 1px 0 #000; }
/* Selected (98): the name on selection blue, a dotted line round it. */
#desktop .desk-icon:focus span, .desk-icon:active span { background: var(--w98-select); text-shadow: none; outline: 1px dotted var(--w98-text); }

/* The outline flying to / from the taskbar (window.js FLY). */
.win-fly {
  display: none;
  position: fixed;
  box-sizing: border-box;
  border: var(--fly-line) solid var(--fly-color);
  pointer-events: none;
  z-index: 3;
}

/* ── The taskbar. --tb: 1 = 98's own 28 px. */
#taskbar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(28px * var(--tb));
  box-sizing: border-box;
  padding: calc(4px * var(--tb)) calc(2px * var(--tb)) calc(2px * var(--tb));
  padding-bottom: calc(2px * var(--tb) + env(safe-area-inset-bottom, 0px));
  display: flex;
  align-items: stretch;
  gap: calc(4px * var(--tb));
  font-size: calc(11px * var(--tb));
  background: var(--w98-face);
  box-shadow: inset 0 1px var(--w98-hi2), inset 0 2px var(--w98-hi);
  color: var(--w98-text);
  z-index: 2;
}
#taskbar button {
  display: flex;
  align-items: center;
  gap: calc(4px * var(--tb));
  padding: 0 calc(5px * var(--tb));
  background: var(--w98-face);
  box-shadow: var(--w98-raised);
  white-space: nowrap;
}
#taskbar button:active { box-shadow: var(--w98-pressed); }
#taskbar button img { width: calc(16px * var(--tb)); height: calc(16px * var(--tb)); object-fit: contain; display: block; flex: none; }
#taskbar .tb-start { font-weight: bold; padding-right: calc(6px * var(--tb)) !important; }
.tb-tasks { flex: 1; min-width: 0; display: flex; gap: calc(3px * var(--tb)); border-left: 1px solid var(--w98-lo); box-shadow: inset 1px 0 var(--w98-hi); padding-left: calc(5px * var(--tb)); }
.tb-task {
  display: none !important;
  width: calc(160px * var(--tb));
  min-width: 0;
  text-align: left;
}
body.win-min .tb-task { display: flex !important; }
.tb-task span { overflow: hidden; text-overflow: ellipsis; }
.tb-tray {
  display: flex;
  align-items: center;
  padding: 0 calc(10px * var(--tb));
  box-shadow: var(--w98-sunk);
  white-space: nowrap;
}

/* ── Program windows (window.js tbWindow.make; the programs: programs.js):
   Windows 98 windows on the desktop, in the same black. The title bar keeps
   98's blue. Each program's own look is in its own .css (loaded with it). */
:root {
  --w98-title: #000080;    /* title bar, left */
  --w98-title2: #1084d0;   /* title bar, right */
  --w98-well: #000;        /* the sunk-in areas: files, preview, fields */
  --w98-dim: #9a9a9a;      /* the labels in the info pane */
  --w98-link: #6fb6ff;
}
.w98-window {
  position: absolute;
  z-index: 1;              /* over the desktop icons, under the taskbar */
  display: none;
  flex-direction: column;
  gap: 2px;
  box-sizing: border-box;
  padding: 3px;
  background: var(--w98-face);
  color: var(--w98-text);
  box-shadow: var(--w98-raised);
  font-size: 11px;
}
.w98-window.is-open { display: flex; }
.w98-title {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 18px;
  flex: none;
  padding: 0 2px 0 3px;
  background: linear-gradient(90deg, var(--w98-title), var(--w98-title2));
  font-weight: bold;
  touch-action: none;      /* a finger drags the window (window.js) */
}
.w98-title.is-dither { background-repeat: repeat-y; background-position: 0 0; image-rendering: pixelated; } /* the fade dithered (window.js TITLE): each dot a crisp pixel */
.w98-title img { width: 16px; height: 16px; image-rendering: pixelated; }
.w98-title span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#desktop .w98-x { width: 16px; height: 14px; display: grid; place-items: center; background: var(--w98-face); box-shadow: var(--w98-raised); }
#desktop .w98-x:active { box-shadow: var(--w98-pressed); }
.w98-x svg { width: 8px; height: 7px; fill: currentColor; shape-rendering: crispEdges; }
.w98-x[data-act="max"] svg { width: 9px; height: 9px; }
.w98-window:not(.is-max) .w98-x[data-act="max"] svg + svg, .w98-window.is-max .w98-x[data-act="max"] svg:first-child { display: none; } /* maximize ↔ restore */
.w98-title .w98-x[data-act="close"] { margin-left: 2px; } /* 98: _ □ together, × a step apart */
.w98-window.is-max { box-shadow: none; }
.w98-well { background: var(--w98-well); box-shadow: var(--w98-pressed); }
.w98-field { display: flex; align-items: center; gap: 4px; padding: 2px 4px; background: var(--w98-well); box-shadow: var(--w98-sunk); min-width: 0; overflow: hidden; white-space: nowrap; }
.w98-btn { display: inline-block; padding: 4px 12px; background: var(--w98-face); box-shadow: var(--w98-raised); color: var(--w98-text); text-decoration: none; }
.w98-btn:active { box-shadow: var(--w98-pressed); }

/* The trash bin (programs.js tbWindow.bin): lit up as a drop target while something is carried over it (98). */
#desktop .desk-icon.is-drop span { background: var(--w98-select); text-shadow: none; outline: 1px dotted var(--w98-text); }
#desktop .desk-icon.is-drop img { filter: brightness(0.6) sepia(1) hue-rotate(190deg) saturate(4); }
/* Its window (trash.js). */
.trash-bar, .trash-status { display: flex; flex: none; gap: 2px; padding: 2px; }
.trash-status { padding: 0; }
.trash-status .w98-field { flex: 1; }
#desktop .trash-empty:disabled { color: var(--w98-dim); cursor: default; }
.trash-list { flex: 1; min-height: 0; overflow: auto; padding: 4px; }
.trash-item { display: flex; align-items: center; gap: 6px; padding: 2px; }
.trash-item img { width: 24px; height: 24px; object-fit: contain; image-rendering: pixelated; }
.trash-none { margin: 4px; color: var(--w98-dim); }

/* ── Menus (desk-menus.js): right clicks and the Start menu, 98's. */
#desktop { -webkit-touch-callout: none; } /* (phones: a held finger is our right click, not the browser's own menu) */
.w98-menu { position: absolute; z-index: 5; display: flex; flex-direction: column; min-width: 140px; padding: 2px; background: var(--w98-face); box-shadow: var(--w98-raised); color: var(--w98-text); font-size: 11px; }
#desktop .w98-menu-item { display: flex; align-items: center; height: 19px; padding: 0 4px 0 0; text-align: left; white-space: nowrap; cursor: default; }
.w98-menu-item .ic { width: 22px; flex: none; display: flex; justify-content: center; }
.w98-menu-item .ic img { width: 16px; height: 16px; image-rendering: pixelated; }
.w98-menu-item .lb { flex: 1; padding-right: 18px; }
.w98-menu-item .key { padding-right: 6px; }
.w98-menu-item .arr { width: 8px; flex: none; }
.w98-menu-item.has-sub .arr { width: 0; height: 0; border: 4px solid transparent; border-left-color: currentColor; border-right-width: 0; margin-left: 4px; } /* 98's little black triangle */
.w98-menu-item.is-bold .lb { font-weight: bold; }
#desktop .w98-menu-item.is-hot { background: var(--w98-select); }
#desktop .w98-menu-item.is-off { color: var(--w98-dim); text-shadow: 1px 1px var(--w98-hi2); } /* 98's greyed-out (not available) */
#desktop .w98-menu-item.is-off.is-hot { text-shadow: none; }
.w98-menu-sep { height: 2px; margin: 3px 1px; box-shadow: inset 0 1px var(--w98-lo), inset 0 -1px var(--w98-hi); }
/* The Start menu: the banner up the left side, bigger rows. */
.w98-menu.w98-start { flex-direction: row; padding: 3px; }
.w98-start-banner { width: 21px; flex: none; display: flex; align-items: flex-end; background: linear-gradient(0deg, var(--w98-title), var(--w98-title2)); }
.w98-start-banner span { writing-mode: vertical-rl; transform: rotate(180deg); padding: 6px 0 4px; font-size: 15px; font-weight: bold; color: #fff; white-space: nowrap; }
.w98-start-items { display: flex; flex-direction: column; min-width: 160px; }
#desktop .w98-start .w98-menu-item { height: 32px; font-size: 12px; }
.w98-start .w98-menu-item .ic { width: 38px; }
.w98-start .w98-menu-item .ic img { width: 24px; height: 24px; }
#taskbar .tb-start.is-on { box-shadow: var(--w98-pressed); }

/* ── The pop-ups (popups.js): 98 message boxes. */
.pop-body { display: flex; align-items: flex-start; gap: 14px; padding: 12px 12px 6px; }
.pop-body img { width: 32px; height: 32px; flex: none; image-rendering: pixelated; }
.pop-body p { margin: 8px 0 0; line-height: 1.4; }
.pop-buttons { display: flex; justify-content: center; gap: 6px; padding: 6px 0 8px; }
#desktop .pop-buttons .w98-btn { min-width: 75px; padding: 4px 10px; }

/* The windows' layer: one above another by the order they were last in front (window.js). */
.desk-windows { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; } /* (a window dragged out is cut off at the screen's edge) */
.desk-windows > * { pointer-events: auto; }
/* A program loading (programs.js): 98's pointer with the hourglass. */
#desktop.is-loading, #desktop.is-loading * { cursor: progress !important; }

/* Its taskbar button (pressed while the window is up). */
#taskbar .tb-app { width: calc(160px * var(--tb)); min-width: 0; text-align: left; }
#taskbar .tb-app.is-on { box-shadow: var(--w98-pressed); background: #2a2a2a; font-weight: bold; }
.tb-app span { overflow: hidden; text-overflow: ellipsis; }

/* Phones and small windows: the whole screen above the taskbar. */
@media (max-width: 640px), (max-height: 480px) {
  .w98-window:not(.is-dialog) { left: 0 !important; top: 0 !important; width: auto !important; height: auto !important; right: 0; bottom: calc(28px * var(--tb) + env(safe-area-inset-bottom, 0px)); padding-top: calc(3px + env(safe-area-inset-top, 0px)); }
}

/* ── The desktop's PIN (window.js PIN): a 98 "Log On" box on a black screen before the desktop shows. */
.pin-gate { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 16px; background: #000; font-family: 'MS Sans Serif', 'Microsoft Sans Serif', Tahoma, Arial, sans-serif; font-size: 11px; color: var(--w98-text); }
.pin-box { width: min(380px, 100%); padding: 3px; background: var(--w98-face); box-shadow: var(--w98-raised); }
.pin-title { height: 18px; padding: 0 4px; display: flex; align-items: center; font-weight: bold; color: #fff; background: linear-gradient(90deg, var(--w98-title), var(--w98-title2)); }
.pin-body { display: flex; gap: 14px; padding: 14px 12px 6px; }
.pin-key { width: 32px; height: 32px; flex: none; shape-rendering: crispEdges; }
.pin-right { flex: 1; min-width: 0; }
.pin-text { margin: 2px 0 12px; line-height: 1.4; }
.pin-row { display: flex; align-items: center; gap: 8px; }
.pin-input { flex: 1; min-width: 0; padding: 3px 5px; border: 0; outline: 0; background: var(--w98-well); color: var(--w98-text); box-shadow: var(--w98-sunk); font: inherit; font-size: 14px; letter-spacing: 0.25em; }
.pin-wrong { margin: 8px 0 0; color: #ff7a7a; }
.pin-wrong[hidden] { display: none; }
.pin-buttons { display: flex; justify-content: flex-end; padding: 6px 10px 8px; }
.pin-ok { min-width: 75px; padding: 4px 12px; border: 0; background: var(--w98-face); box-shadow: var(--w98-raised); color: var(--w98-text); font: inherit; cursor: pointer; }
.pin-ok:active { box-shadow: var(--w98-pressed); }
.pin-ok:focus-visible { outline: 1px dotted var(--w98-text); outline-offset: -5px; }
.pin-box.is-shake { animation: pin-shake 0.32s steps(6); }
@keyframes pin-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-8px); } 50% { transform: translateX(7px); } 75% { transform: translateX(-4px); } }
