/* styles.css — SoundAnnoyer, Windows 98 dress.
   ---------------------------------------------------------------------------
   NOTE ON THE SITE STYLE GUIDE: the root CLAUDE.md sets a dark, minimal theme
   site-wide "unless a subpage's context.md specifies otherwise". The owner
   asked for a Windows 98 skin here specifically; context.md records that
   override. This file is the only place the old dark theme lived, and git
   history holds it if it is ever wanted back.

   Every class name, state hook and layout contract from the previous stylesheet
   is preserved verbatim, so js/app.js is untouched by this reskin. The state
   hooks the JS actually sets, and which therefore MUST keep working:

     body.running / body.fired / body.test-mode / body.modal-open
     .sound-btn.armed / .sound-btn.playing / .sound-btn.demo
     .interval-btn.selected
     .tab[aria-selected="true"]
     .toggle[aria-checked="true"]
     .offline-badge.is-offline
     .hidden

   The look is Windows 98 chrome, which is not decoration but a system: every
   surface is either RAISED (a thing you can press), SUNKEN (a thing that holds
   content, or is currently pressed), or FLAT. The bevels below are the genuine
   four-shadow recipes — a 1px outer and 1px inner edge on each axis — matching
   98.css, which in turn mirrors the Win32 DrawEdge() edges.

   Deliberate deviations from 1998, all in service of "still easily navigable":
     * Touch targets are 36-60px, not 21px. A real Win98 button is unusable on
       a phone. Chrome (title bar, close button) stays small; anything you tap
       during a prank does not.
     * Font sizes are 11-13px rather than a flat 11px.
     * Safe-area insets and svh units are kept — 1998 had no notch.
   --------------------------------------------------------------------------- */

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

:root {
  /* ── The Windows 98 system palette ──────────────────────────────────────
     These are the actual COLOR_* system colours, not an approximation. */
  --desktop:      #008080;   /* COLOR_BACKGROUND — the famous teal */
  --surface:      #c0c0c0;   /* COLOR_BTNFACE    — silver, the whole UI */
  --btn-face:     #dfdfdf;   /* inner light bevel */
  --btn-hilite:   #ffffff;   /* COLOR_3DHILIGHT */
  --btn-shadow:   #808080;   /* COLOR_BTNSHADOW */
  --frame:        #0a0a0a;   /* COLOR_3DDKSHADOW / window frame */

  --title-a1:     #000080;   /* COLOR_ACTIVECAPTION — navy */
  --title-a2:     #1084d0;   /* COLOR_GRADIENTACTIVECAPTION */

  --text:         #000000;
  --muted:        #404040;
  --text-dis:     #808080;   /* COLOR_GRAYTEXT — always with a white emboss */
  --sel-bg:       #000080;
  --danger:       #a80000;
  --warn:         #808000;

  /* ── The four bevels. Order matters: outer edge first, then inner. ────── */
  /* A thing you can press. */
  --raised: inset -1px -1px var(--frame),      inset  1px  1px var(--btn-hilite),
            inset -2px -2px var(--btn-shadow), inset  2px  2px var(--btn-face);
  /* The same thing, held down. Light and dark simply swap sides. */
  --sunken: inset -1px -1px var(--btn-hilite), inset  1px  1px var(--frame),
            inset -2px -2px var(--btn-face),   inset  2px  2px var(--btn-shadow);
  /* A window or panel — softer than a button: no black on the inner edge. */
  --window: inset -1px -1px var(--frame),      inset  1px  1px var(--btn-face),
            inset -2px -2px var(--btn-shadow), inset  2px  2px var(--btn-hilite);
  /* A field that holds content (text box, list, well). Deepest of the four. */
  --field:  inset -1px -1px var(--btn-hilite), inset  1px  1px var(--btn-shadow),
            inset -2px -2px var(--btn-face),   inset  2px  2px var(--frame);
  /* Status bar cell — a single hairline groove. */
  --status: inset -1px -1px var(--btn-face),   inset  1px  1px var(--btn-shadow);

  /* MS Sans Serif is long gone; Tahoma is its closest survivor and still ships
     on Windows. Everything after it is a graceful decline, ending at the
     platform sans (Roboto on Android). The bevels carry the era, not the
     glyphs — so no webfont, which would also have to be cached for offline. */
  --sans: Tahoma, "MS Sans Serif", "Microsoft Sans Serif", Geneva, Verdana, sans-serif;

  --page-pad-x: clamp(6px, 2vw, 16px);
}

html {
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: contain;
}

body {
  min-height: 100svh;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.35;
  color: var(--text);
  background: var(--desktop);          /* the teal desktop */
  padding: max(env(safe-area-inset-top, 0px), 0px)
           max(var(--page-pad-x), env(safe-area-inset-right, 0px))
           max(env(safe-area-max-inset-bottom, env(safe-area-inset-bottom, 0px)), 0px)
           max(var(--page-pad-x), env(safe-area-inset-left, 0px));
}

/* Kill the sticky :hover touch devices leave behind, and the blue tap flash. */
button {
  font-family: inherit;
  font-size: inherit;
  color: var(--text);
  background: none;
  border: 0;
  border-radius: 0;          /* 1998 had no rounded corners. None. Anywhere. */
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

/* ── The application window ──────────────────────────────────────────────── */

.app {
  width: 100%;
  max-width: 470px;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  box-shadow: var(--window);
  padding: 3px;
}

/* ── Title bar (the .header) ─────────────────────────────────────────────── */

.header {
  flex-shrink: 0;
  display: flex;
  flex-direction: row;         /* was a centred column; a title bar is a row */
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 3px 3px 4px 4px;
  background: linear-gradient(90deg, var(--title-a1), var(--title-a2));
}

.logo {
  font-size: 12px;
  font-weight: bold;
  color: #fff;
  letter-spacing: 0.2px;
  display: flex;
  align-items: center;
  gap: 5px;
  /* Name first, art after it. The sprites read better beside the close button than
     jammed against the window's left edge. */
  flex-direction: row;
}
.logo-name { order: 0; }
.logo .px-ghost   { order: 1; }
.logo .px-speaker { order: 2; }
.logo-name { color: #fff; }

/* Pixel art. image-rendering: pixelated keeps the 1px grid hard at any scale, which is
   the whole point: a smoothed pixel icon reads as a mistake, not a style. The SVGs are
   inline in the HTML rather than separate files so there is nothing extra to precache
   for offline and no second request on first paint. */
.px { display: block; image-rendering: pixelated; shape-rendering: crispEdges; }
.px-ghost   { height: 15px; width: auto; }
.px-speaker { height: 13px; width: auto; }
.logo .px   { flex: 0 0 auto; }

/* The ghost drifts, because a poltergeist should not sit still. */
.px-ghost { animation: ghost-drift 4.2s ease-in-out infinite; }
@keyframes ghost-drift {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-1.5px); }
}
@keyframes imp-wiggle {
  0%, 88%, 100% { transform: rotate(0deg); }
  92%           { transform: rotate(-14deg); }
  96%           { transform: rotate(12deg); }
}

/* Title-bar close button — deliberately small: it is chrome, not content, and
   a fat X invites a mis-tap that would end the prank. */
.btn-close {
  /* Win98's real title-bar button is 16x14, which is far too small to hit on a
     phone. The GLYPH stays small so the chrome still reads as 1998; the BUTTON is
     grown to 30x26 and given an invisible tap area beyond that (see ::after), so
     the target clears the ~44px guidance without looking like a modern app. */
  position: relative;
  width: 30px;
  height: 26px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: bold;
  line-height: 1;
  color: var(--text);
  background: var(--surface);
  box-shadow: var(--raised);
}
/* Invisible hit-slop: 8px in every direction, so the real target is ~46x42. */
.btn-close::after {
  content: "";
  position: absolute;
  inset: -8px;
}
.btn-close:active { box-shadow: var(--sunken); }
@media (hover: hover) and (pointer: fine) {
  .btn-close:hover { background: #cfcfcf; }
}

/* ── Tabs ────────────────────────────────────────────────────────────────── */

.tabs {
  flex-shrink: 0;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  padding: 5px 0 0 4px;
  margin-bottom: -2px;         /* the selected tab bleeds into the page below */
  position: relative;
  z-index: 2;
}

.tab {
  position: relative;
  /* Share the full width instead of hugging their labels: three tabs across the
     window read as a tab strip, and each becomes a much larger touch target. */
  flex: 1 1 0;
  min-width: 0;
  min-height: 34px;
  padding: 8px 10px;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Unselected tabs sit BEHIND the page: darker, lower, and greyed. Bold text alone
     was not enough of a signal — the two tabs read as equally active. */
  background: #adadad;
  color: #2b2b2b;
  margin-top: 4px;
  box-shadow: inset  1px  1px var(--btn-face),
              inset -1px  0   var(--frame),
              inset -2px  0   var(--btn-shadow);
}
/* Info carries a label now, so it takes a share of the strip like the others, just a
   smaller one. */
.tab-info { flex: 0 1 auto; padding-left: 12px; padding-right: 12px; font-size: 12px; }

.tab[aria-selected="true"] {
  /* The selected tab is the page: same silver, taller, bolder, and it physically
     erases the page's top border beneath it (::after) so the two become one surface.
     That seam-break is what Win98 uses to show selection, and it is unambiguous. */
  background: var(--surface);
  color: var(--text);
  font-weight: bold;
  margin-top: 0;
  padding: 10px 10px 14px;
  z-index: 3;
  box-shadow: inset  1px  1px var(--btn-hilite),
              inset  2px  0   var(--btn-face),
              inset -1px  0   var(--frame),
              inset -2px  0   var(--btn-shadow);
}
/* Paints over the panel's top bevel so the active tab merges into the page. */
.tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: -3px;
  height: 4px;
  background: var(--surface);
  z-index: 4;
}

/* ── Tab page ────────────────────────────────────────────────────────────── */

.panel {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 10px;
  background: var(--surface);
  box-shadow: var(--window);
  position: relative;
  z-index: 1;
}

.section { display: flex; flex-direction: column; gap: 7px; }

/* ── Info tab ────────────────────────────────────────────────────────────── */

.info-lead {
  padding: 10px;
  font-size: 13px;
  line-height: 1.5;
  background: var(--btn-hilite);
  box-shadow: var(--field);
}
.info-steps {
  margin: 0;
  padding: 10px 10px 10px 8px;
  list-style: none;
  counter-reset: step;
  background: var(--surface);
  box-shadow: var(--field);
}
.info-steps li {
  position: relative;
  margin-bottom: 9px;
  padding-left: 24px;
  font-size: 13px;
  line-height: 1.45;
  counter-increment: step;
}
.info-steps li:last-child { margin-bottom: 0; }
.info-steps li::before {
  content: counter(step) ".";
  position: absolute;
  left: 4px;
  font-weight: bold;
}

/* Aside under the steps: same sunken field as .info-lead, but quieter, so it reads as
   a footnote to the list rather than a fourth instruction. */
.info-note {
  padding: 9px 10px;
  font-size: 12px;
  line-height: 1.45;
  background: var(--btn-hilite);
  box-shadow: var(--field);
}

.section-label {
  font-size: 12px;
  font-weight: bold;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.section-hint { font-weight: normal; font-size: 11px; color: var(--muted); }

/* Right-hand cluster of the section label: the hint, then the select-all button. */
.label-right {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: normal;
}

/* Small enough to sit inside the label row without pushing it around, big enough to
   hit: the visible chip is 24px tall, with 7px of invisible slop on every side. */
.btn-tiny {
  position: relative;
  min-height: 24px;
  padding: 4px 9px;
  font-size: 11px;
  white-space: nowrap;
  background: var(--surface);
  box-shadow: var(--raised);
}
.btn-tiny::after { content: ""; position: absolute; inset: -7px; }
.btn-tiny:active { box-shadow: var(--sunken); }

/* ── Hero: a sunken status well, like a Win98 preview pane ───────────────── */

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 14px 10px;
  text-align: center;
  background: var(--btn-hilite);
  box-shadow: var(--field);
}
.hero-emoji {
  font-size: 42px;              /* still used when a firing sound flashes its emoji */
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 52px;
}
.px-hero { height: 50px; width: auto; }
.px-hero-speaker { height: 42px; }

/* Waves are off by default. The HERO speaker earns them only while sound is actually
   being made, which is the single clearest state signal anywhere in the UI. */
.wave { opacity: 0; }

/* Decorative speakers, in the title bar and beside the volume slider, are labels rather
   than status: they always show the full three bars. */
.logo .px-speaker .wave,
.px-vol .wave { opacity: 1; }

/* Three frames, cycling 1 -> 2 -> 3 bars and back. Every stop is a hard cut with no
   interpolated tween, so it reads as a sprite flipbook rather than a CSS fade, which is
   what a 1998 animation actually looked like. */
body.running .px-hero-speaker .wave-1 { opacity: 1; }
body.running .px-hero-speaker .wave-2 { animation: wave-2 750ms linear infinite; }
body.running .px-hero-speaker .wave-3 { animation: wave-3 750ms linear infinite; }
@keyframes wave-2 {
  0%,      33.32% { opacity: 0; }
  33.33%, 100%    { opacity: 1; }
}
@keyframes wave-3 {
  0%,      66.65% { opacity: 0; }
  66.66%, 100%    { opacity: 1; }
}

/* ── Relative audio ─────────────────────────────────────────────────────── */

.volume-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  background: var(--surface);
  box-shadow: var(--field);
}
.px-vol { height: 15px; width: auto; flex: 0 0 auto; }
.volume-label { font-size: 12px; flex: 0 0 auto; }
.volume-value { font-size: 11px; min-width: 34px; text-align: right; flex: 0 0 auto; }

.volume-slider {
  /* --vol (0-1) is written by applyVolume(); --fill turns it into the position of the
     thumb's CENTRE, not of the raw percentage. A range thumb travels from half its own
     width to (track - half), so a naive `left: 80%` fill overshoots the tab at high
     values and undershoots at low ones. 6.5px is half the 13px thumb below. */
  --vol: 0.8;
  --fill: calc(6.5px + (100% - 13px) * var(--vol));
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  /* A range input must own the gesture or the page scroller steals a horizontal drag. */
  touch-action: none;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}
.volume-slider::-webkit-slider-runnable-track {
  height: 8px;
  /* Navy is COLOR_HIGHLIGHT - the same blue Win98 fills a selection and a progress bar
     with, so a filled trackbar reads as native rather than as a modern accent colour.
     Hard stops, no blend: this is a 256-colour desktop. */
  background: linear-gradient(to right,
              var(--sel-bg) 0 var(--fill), var(--surface) var(--fill) 100%);
  box-shadow: var(--field);
}
.volume-slider::-moz-range-track {
  height: 8px;
  /* Navy is COLOR_HIGHLIGHT - the same blue Win98 fills a selection and a progress bar
     with, so a filled trackbar reads as native rather than as a modern accent colour.
     Hard stops, no blend: this is a 256-colour desktop. */
  background: linear-gradient(to right,
              var(--sel-bg) 0 var(--fill), var(--surface) var(--fill) 100%);
  box-shadow: var(--field);
}
/* The Win98 trackbar thumb: a tall raised tab, not a circle. */
.volume-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 13px;
  height: 22px;
  margin-top: -7px;
  background: var(--surface);
  box-shadow: var(--raised);
  border-radius: 0;
}
.volume-slider::-moz-range-thumb {
  width: 13px;
  height: 22px;
  border: 0;
  background: var(--surface);
  box-shadow: var(--raised);
  border-radius: 0;
}
.hero-status { font-size: 13px; font-weight: bold; }
.hero-sub    { font-size: 11px; color: var(--muted); min-height: 14px; }

body.running .hero-emoji  { animation: hero-pulse 1.8s ease-in-out infinite; }
body.running .hero-status { color: var(--sel-bg); }
body.running .hero-sub    { color: var(--text); }
body.fired   .hero-emoji  { animation: hero-jolt 360ms ease; }
@keyframes hero-pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.09)} }
@keyframes hero-jolt  { 0%{transform:scale(1)} 35%{transform:scale(1.28) rotate(-7deg)}
                        60%{transform:scale(.96) rotate(4deg)} 100%{transform:scale(1)} }

/* ── The big button ──────────────────────────────────────────────────────── */

.launch-section { display: flex; flex-direction: column; gap: 8px; }

/* Stop and "Play a sound now" share a line. flex-basis 0 splits it evenly, and
   align-items:stretch (the default) makes the shorter button match the taller one. */
.launch-row { display: flex; gap: 8px; }

.btn-launch {
  flex: 1 1 0;
  min-width: 0;          /* opt out of the min-content floor, or the labels skew the split */
  order: 2;              /* visual order is skip, then stop — DOM order stays primary-first */
  min-height: 54px;
  padding: 12px;
  font-size: 16px;
  font-weight: bold;
  letter-spacing: 0.4px;
  background: var(--surface);
  box-shadow: var(--raised);
}
.btn-launch:active:not(:disabled) { box-shadow: var(--sunken); }
.btn-launch:disabled {
  /* The authentic Win98 disabled label: grey text embossed with a white
     shadow one pixel down-right. Nothing else in the era looks like it. */
  color: var(--text-dis);
  text-shadow: 1px 1px 0 var(--btn-hilite);
  cursor: default;
}
/* Stop, while running: a red button with white bold text. The bevel is rebuilt in
   reds rather than reusing --raised, because the silver highlight/shadow pair reads as
   dirt over a red face. Same four-shadow recipe, same light-from-top-left logic. */
body.running .btn-launch {
  background: var(--danger);
  color: #ffffff;
  text-shadow: none;
  box-shadow: inset -1px -1px var(--frame),   inset  1px  1px #ff9a9a,
              inset -2px -2px #4d0000,        inset  2px  2px #d81414;
}
body.running .btn-launch:active:not(:disabled) {
  box-shadow: inset -1px -1px #ff9a9a,        inset  1px  1px var(--frame),
              inset -2px -2px #d81414,        inset  2px  2px #4d0000;
}

.btn-skip {
  flex: 1 1 0;
  min-width: 0;
  order: 1;
  min-height: 42px;
  /* Horizontal padding must MATCH .btn-launch. Chrome adds a button's padding on top of
     a flex-basis:0 box, so a 12px/9px mismatch splits the row 173/167 instead of 170/170. */
  padding: 9px 12px;
  font-size: 13px;
  font-weight: bold;
  background: var(--surface);
  box-shadow: var(--raised);
}
.btn-skip:active { box-shadow: var(--sunken); }

/* ── Sound + interval grids, each in a sunken well ───────────────────────── */

.sounds-section { gap: 7px; }

.sound-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 5px;
  padding: 6px;
  background: var(--surface);
  box-shadow: var(--field);
}

.sound-btn {
  min-height: 62px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 7px 4px;
  background: var(--surface);
  box-shadow: var(--raised);
}
.sound-btn .s-emoji { font-size: 20px; line-height: 1; }
.sound-btn .s-label { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.sound-btn .s-name  { font-size: 11px; text-align: center; }

/* Armed = a latched toolbar button: held down, wearing the 50% dither wash
   Win98 uses for a checked toolbar item. */
.sound-btn.armed {
  box-shadow: var(--sunken);
  background-color: #c0c0c0;
  background-image:
    linear-gradient(45deg, var(--btn-hilite) 25%, transparent 25%, transparent 75%, var(--btn-hilite) 75%),
    linear-gradient(45deg, var(--btn-hilite) 25%, transparent 25%, transparent 75%, var(--btn-hilite) 75%);
  background-size: 2px 2px;
  background-position: 0 0, 1px 1px;
}
.sound-btn.armed .s-emoji { transform: scale(1.06); }
.sound-btn.armed .s-name  { font-weight: bold; }
.sound-btn:active         { box-shadow: var(--sunken); }
.sound-btn.playing        { animation: sound-pop 420ms ease; }
@keyframes sound-pop { 0%{transform:scale(1)} 30%{transform:scale(1.1)} 100%{transform:scale(1)} }

.interval-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 5px;
  padding: 6px;
  background: var(--surface);
  box-shadow: var(--field);
}

.interval-btn {
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 6px 4px;
  font-size: 12px;
  background: var(--surface);
  box-shadow: var(--raised);
}
.interval-btn .i-tag {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--warn);
  font-weight: bold;
}
.interval-btn.selected {
  box-shadow: var(--sunken);
  font-weight: bold;
  background-color: #d6d6d6;
}
.interval-btn:active { box-shadow: var(--sunken); }

/* Test mode tints the sounds well, the way a Win98 app flags an active mode. */
body.test-mode #sound-hint { color: var(--warn); font-weight: bold; }
body.test-mode .sound-btn  { background-color: #d8d4bf; }

/* ── Toggles, rendered as Win98 checkboxes ───────────────────────────────────
   The markup is button > span.toggle-track > span.toggle-thumb plus a text
   block. There is no sliding switch in 1998 — the honest translation is a
   checkbox, so .toggle-track becomes the 13px well and .toggle-thumb the tick.
   State still comes from aria-checked, exactly as before. */

.toggle-grid { gap: 4px; }

.toggle {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  width: 100%;
  min-height: 46px;
  padding: 9px 8px;
  text-align: left;
}
.toggle:active { background: rgba(0, 0, 128, 0.07); }

.toggle-track {
  flex: 0 0 auto;
  position: relative;
  width: 13px;
  height: 13px;
  margin-top: 3px;
  background: var(--btn-hilite);
  box-shadow: var(--field);
}
.toggle-thumb { position: absolute; inset: 0; display: block; opacity: 0; }
/* The checkmark, drawn with two rotated rules — no image, no font dependency,
   nothing extra to cache for offline. */
.toggle-thumb::before,
.toggle-thumb::after { content: ""; position: absolute; background: var(--text); }
.toggle-thumb::before { width: 2px; height: 5px; left: 3px; top: 6px; transform: rotate(-45deg); }
.toggle-thumb::after  { width: 2px; height: 9px; left: 7px; top: 2px; transform: rotate(38deg); }

.toggle[aria-checked="true"] .toggle-thumb { opacity: 1; }
.toggle[aria-checked="true"] .toggle-track { background: var(--btn-hilite); }
/* Test mode is the one "careful, this is loud" switch — tint its well. */
.toggle-test[aria-checked="true"] .toggle-track { background: #ffffcc; }
.toggle-test[aria-checked="true"] .toggle-thumb::before,
.toggle-test[aria-checked="true"] .toggle-thumb::after { background: #806000; }

.toggle-text  { display: flex; flex-direction: column; gap: 1px; }
.toggle-title { font-size: 13px; }
.toggle-sub   { font-size: 11px; color: var(--muted); }
.toggle-grid .toggle-start { margin-bottom: 2px; }

/* ── Small buttons + settings ────────────────────────────────────────────── */

/* The app plate: the home-screen icon and name, in the same white field the info
   panels use. It shows what gets installed, right above the Install button. */
.app-plate {
  flex-direction: row;
  align-items: center;
  gap: 9px;
  padding: 7px 9px;
  background: var(--btn-hilite);
  box-shadow: var(--field);
}
.app-plate-icon { width: 40px; height: 40px; flex: 0 0 auto; }
.app-plate-name { font-size: 13px; font-weight: bold; }

.settings-actions { display: flex; flex-direction: row; flex-wrap: wrap; gap: 6px; }
.settings-actions .btn-small { flex: 1 1 118px; }

/* Explanatory line under a section label, quieter than the control it describes. */
.settings-note { margin: 0; font-size: 11px; line-height: 1.45; color: var(--muted); }

/* Destructive: red text on the ordinary raised face, so it still reads as a Win98
   button while the action itself is unmistakably the dangerous one. */
.btn-danger { color: var(--danger); }

.btn-small {
  min-height: 38px;
  padding: 8px 12px;
  font-size: 12px;
  background: var(--surface);
  box-shadow: var(--raised);
}
.btn-small:active:not(:disabled) { box-shadow: var(--sunken); }
.btn-small:disabled {
  color: var(--text-dis);
  text-shadow: 1px 1px 0 var(--btn-hilite);
  cursor: default;
}

/* Install advice. Win98's own "infotip" colours (COLOR_INFOBK #ffffe1 on COLOR_INFOTEXT
   black, 1px black frame) so the note reads as a system hint, not as page content. */
.install-banner,
.install-note {
  margin: 0;
  padding: 6px 8px;
  font-size: 12px;
  line-height: 1.35;
  color: #000;
  background: #ffffe1;
  border: 1px solid #000;
}
.install-banner[hidden],
.install-note[hidden] { display: none; }
.install-banner { font-weight: bold; margin-bottom: 6px; }

/* ── Status bar ──────────────────────────────────────────────────────────── */

.settings-meta {
  flex-direction: row;
  align-items: stretch;
  gap: 2px;
  margin-top: auto;
  padding-top: 6px;
}
.version,
.offline-badge {
  display: flex;
  align-items: center;
  min-height: 22px;
  padding: 3px 7px;
  font-size: 11px;
  color: var(--muted);
  background: var(--surface);
  box-shadow: var(--status);
}
.version { flex: 1 1 auto; }
.offline-badge { flex: 0 0 auto; }
.offline-badge.is-offline { color: var(--danger); font-weight: bold; }

/* ── Dialogs ─────────────────────────────────────────────────────────────── */

.modal-layer {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  justify-content: center;
  /* Load-bearing: without an overflow here a dialog taller than the screen
     loses its buttons off both edges with no way to reach them. */
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-top: max(18px, env(safe-area-inset-top, 0px));
  padding-bottom: max(18px, env(safe-area-inset-bottom, 0px));
  padding-left: max(10px, env(safe-area-inset-left, 0px));
  padding-right: max(10px, env(safe-area-inset-right, 0px));
}
.modal-backdrop {
  position: fixed;             /* fixed, not absolute — see above */
  inset: 0;
  background: rgba(0, 0, 64, 0.45);
  cursor: default;
}
.modal-backdrop:active { background: rgba(0, 0, 64, 0.5); }

.custom-modal,
.help-modal {
  position: relative;
  margin: auto;
  width: 100%;
  max-width: 380px;
  display: flex;
  flex-direction: column;
  padding: 3px;
  background: var(--surface);
  /* Dialogs float, so give them the hard 1998 drop shadow. */
  box-shadow: var(--window), 4px 4px 0 rgba(0, 0, 0, 0.35);
}

/* The kicker text is already the dialog's name, so it becomes the title bar. */
.custom-modal-kicker,
.help-modal-kicker {
  margin-bottom: 7px;
  padding: 3px 3px 4px 5px;
  font-size: 12px;
  font-weight: bold;
  color: #fff;
  background: linear-gradient(90deg, var(--title-a1), var(--title-a2));
}

.custom-modal h2,
.help-modal h2 { margin: 0 8px 5px; font-size: 13px; font-weight: bold; }

.custom-modal-copy,
.help-summary { margin: 0 8px 9px; font-size: 12px; }

.help-details   { margin: 0 8px 8px; }
.help-details p { margin-bottom: 7px; font-size: 12px; }

.help-details-btn,
.help-close-btn,
.custom-modal-btn {
  min-height: 38px;
  padding: 8px 14px;
  font-size: 12px;
  background: var(--surface);
  box-shadow: var(--raised);
}
.help-details-btn { margin: 0 8px 8px; }
.help-close-btn   { margin: 0 8px 8px; align-self: flex-end; min-width: 94px; }
.help-details-btn:active,
.help-close-btn:active,
.custom-modal-btn:active { box-shadow: var(--sunken); }

.install-steps { margin: 0 8px 9px; padding-left: 4px; list-style: none; counter-reset: step; }
.install-steps li {
  position: relative;
  margin-bottom: 6px;
  padding-left: 20px;
  font-size: 12px;
  counter-increment: step;
}
.install-steps li::before { content: counter(step) "."; position: absolute; left: 0; font-weight: bold; }

/* ── Custom-interval dialog ──────────────────────────────────────────────── */

.custom-modal-form  { display: flex; flex-direction: column; gap: 8px; padding: 0 8px 8px; }
.custom-modal-label { font-size: 12px; }

.custom-input-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  background: var(--btn-hilite);
  box-shadow: var(--field);
}
.custom-display { justify-content: space-between; }
.custom-input   { font-family: var(--sans); font-size: 20px; font-weight: bold; }

/* The unit control renders BOTH units with the active one latched, so it reads as a
   switch. As a single "MIN" label it looked like a static unit and users concluded
   seconds were unsupported — they were always supported. */
.custom-unit-btn {
  display: flex;
  gap: 2px;
  min-height: 34px;
  padding: 3px;
  font-size: 11px;
  background: var(--surface);
  box-shadow: var(--field);
}
.unit-opt {
  display: flex;
  align-items: center;
  padding: 4px 9px;
  font-weight: bold;
  color: var(--text-dis);
  text-shadow: 1px 1px 0 var(--btn-hilite);
}
.unit-opt.on {
  color: var(--text);
  text-shadow: none;
  background: var(--surface);
  box-shadow: var(--raised);
}

.custom-keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.custom-keypad-btn {
  min-height: 48px;
  font-size: 16px;
  background: var(--surface);
  box-shadow: var(--raised);
}
.custom-keypad-btn:active { box-shadow: var(--sunken); }
.custom-keypad-btn-muted  { font-size: 11px; }

.custom-modal-error {
  min-height: 15px;
  font-size: 11px;
  font-weight: bold;
  color: var(--danger);
}
.custom-modal-actions { display: flex; justify-content: flex-end; gap: 6px; }
.custom-modal-btn     { min-width: 86px; }
.btn-cancel, .btn-save { background: var(--surface); }

/* ── Utility ─────────────────────────────────────────────────────────────── */

.hidden { display: none !important; }
body.modal-open { overflow: hidden; }
.tagline { font-size: 11px; color: var(--muted); }

/* Focus: Win98's 1px dotted rectangle, not a glowing ring. */
button:focus-visible { outline: 1px dotted var(--text); outline-offset: -4px; }

/* The dialog containers carry tabindex="-1" and are focused programmatically when
   they open, which makes Chrome paint its default blue focus ring around the whole
   window. Suppress it on the container only — every control inside keeps its own
   focus indicator, so nothing is lost for keyboard users. */
.custom-modal:focus,
.help-modal:focus,
.custom-modal:focus-visible,
.help-modal:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  /* The blanket rule above collapses the wave cycle to a single 0.001ms pass, and with
     no fill-mode the bars snap back to their base opacity: 0. That silently removed the
     running-state signal for anyone with Reduce Motion on. Show all three statically
     instead, so the meaning survives without any movement. */
  body.running .px-hero-speaker .wave { opacity: 1 !important; }
}
