/* ==========================================================================
   crt.css -- the Retrommodore 64 monitor.

   Everything in this file is costume: the beige plastic, the recessed tube,
   the phosphor bloom, the scanlines. None of it knows anything about
   RetroServe. Delete this file and the service still works, it just looks
   like 2026.
   ========================================================================== */

:root {
  /* The 16-color IBM/ANSI palette, which is what these screens actually had. */
  --ansi-black:      #16181d;
  --ansi-red:        #d24b4b;
  --ansi-green:      #57bd57;
  --ansi-yellow:     #cb9a33;
  --ansi-blue:       #5478dd;
  --ansi-magenta:    #c163c1;
  --ansi-cyan:       #46b3b3;
  --ansi-white:      #c3c8d1;

  --ansi-br-black:   #626871;
  --ansi-br-red:     #ff7373;
  --ansi-br-green:   #7dfb7d;
  --ansi-br-yellow:  #ffdf6b;
  --ansi-br-blue:    #8aabff;
  --ansi-br-magenta: #ff92ff;
  --ansi-br-cyan:    #7cfafa;
  --ansi-br-white:   #ffffff;

  --phosphor: #92eeee;
  --screen-bg: #060a0c;

  /* Retrommodore putty-beige, aged. */
  --case-lit:  #d3c9b2;
  --case-mid:  #bdb29a;
  --case-dark: #968c76;
  --case-edge: #7d7462;
  --case-deep: #4b4438;

  /* Column count and the font scale derived from it. Both are set from JS
     (RS.COLS in effects.js) so the text engine and the layout can never
     disagree about how wide the world is; these are just fallbacks. */
  --cols: 64;
  --char-mult: .02358;
  --logo-scale: .74;
  --lh: 1.3;

  /*
     How wide the tube is. Everything else scales off this.

     A 4:3 tube is height-bound: tube_h = 0.75 * tube_w, and the case padding
     adds another ~6%, so total height ≈ 0.81 * tube_w plus the fixed
     furniture (chin, stand, desk row, page padding ≈ 150px). Solving for the
     width that fits the window gives the 1.23 multiplier below. Get this
     wrong on the generous side and the monitor runs off the bottom of the
     screen, which is why it is derived rather than guessed.
  */
  --tube-w: max(320px, min(88vw, calc((100vh - 150px) * 1.23), 1200px));
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  min-height: 100%;
  background:
    radial-gradient(ellipse at 50% -10%, #262a33 0%, #0f1115 55%, #07080a 100%);
  color: var(--phosphor);
  font-family: ui-monospace, "SFMono-Regular", "Cascadia Mono", Menlo,
               Consolas, "DejaVu Sans Mono", "Courier New", monospace;
}

.desk {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 10px 8px 14px;
}

/* Modem and controls side by side, wrapping only when there's no room. */
.deskrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 14px;
  max-width: 96vw;
}

/* ==========================================================================
   The plastic
   ========================================================================== */

.monitor { display: flex; flex-direction: column; align-items: center; }

.case {
  /* No width and no percentage padding: the case shrink-wraps the tube, and
     every dimension is derived from --tube-w so nothing resolves against an
     auto-width ancestor. */
  padding: calc(var(--tube-w) * .028) calc(var(--tube-w) * .028) 0;
  border-radius: 18px 18px 10px 10px;
  background:
    linear-gradient(168deg,
      var(--case-lit) 0%,
      var(--case-mid) 42%,
      var(--case-dark) 88%,
      var(--case-deep) 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, .5) inset,      /* top highlight on the moulding */
    0 -22px 40px rgba(0, 0, 0, .22) inset,
    0 26px 50px rgba(0, 0, 0, .55),
    0 3px 10px rgba(0, 0, 0, .45);
  /* Injection-moulded plastic has a faint speckle to it. */
  background-image:
    linear-gradient(168deg,
      var(--case-lit) 0%,
      var(--case-mid) 42%,
      var(--case-dark) 88%,
      var(--case-deep) 100%),
    repeating-linear-gradient(90deg,
      rgba(255,255,255,.025) 0 1px, transparent 1px 3px);
}

/* The tube is sunk into the case, with a dark surround. */
.recess {
  padding: calc(var(--tube-w) * .016);
  border-radius: 22px;
  background: linear-gradient(160deg, #2a2823 0%, #1a1815 60%, #24221d 100%);
  box-shadow:
    0 3px 9px rgba(0, 0, 0, .75) inset,
    0 -1px 0 rgba(255, 255, 255, .16) inset,
    0 1px 0 rgba(255, 255, 255, .35);
}

/* --- Front panel ---------------------------------------------------------- */

.chin {
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
}

.badge { display: flex; align-items: center; gap: 8px; }

/* The rainbow, in-universe. */
.rainbow { display: flex; height: 15px; border-radius: 2px; overflow: hidden;
           box-shadow: 0 1px 1px rgba(0,0,0,.35); }
.rainbow i { width: 4px; }
.rainbow i:nth-child(1) { background: #d8392b; }
.rainbow i:nth-child(2) { background: #e8762a; }
.rainbow i:nth-child(3) { background: #e8c33a; }
.rainbow i:nth-child(4) { background: #4d9d4f; }
.rainbow i:nth-child(5) { background: #3d76b8; }

.badge-name {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .01em;
  color: #4a4437;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .45);
}
.badge-model {
  font-size: 12px;
  font-weight: 700;
  color: #5d5646;
  border: 1px solid rgba(0, 0, 0, .22);
  border-radius: 3px;
  padding: 1px 5px;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .4);
}

.panel { display: flex; align-items: center; gap: 12px; }

/* Brightness and contrast, purely ornamental. */
.knob {
  width: 15px; height: 15px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 28%, #766c58 0%, #4a4438 60%, #2e2a22 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,.35), 0 1px 3px rgba(0,0,0,.5) inset;
  position: relative;
}
.knob::after {           /* the pointer notch */
  content: '';
  position: absolute;
  left: 50%; top: 2px;
  width: 1px; height: 5px;
  background: #c9c0ad;
  transform: translateX(-50%);
}

.pwr {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: #ff4d4d;
  box-shadow: 0 0 8px rgba(255, 70, 70, .9), 0 0 2px rgba(0,0,0,.5) inset;
}

/* --- Stand ---------------------------------------------------------------- */

.neck {
  width: calc(var(--tube-w) * .26);
  height: 16px;
  background: linear-gradient(180deg, var(--case-dark), var(--case-deep));
  clip-path: polygon(18% 0, 82% 0, 100% 100%, 0 100%);
}
.foot {
  width: calc(var(--tube-w) * .38);
  height: 9px;
  border-radius: 0 0 10px 10px;
  background: linear-gradient(180deg, var(--case-mid), #3f392f);
  box-shadow: 0 12px 26px rgba(0, 0, 0, .55);
}

/* ==========================================================================
   The glass
   ========================================================================== */

.crt {
  position: relative;
  width: var(--tube-w);
  aspect-ratio: 4 / 3;             /* the whole point */
  display: grid;
  padding: 2.8% 3%;
  overflow: hidden;
  background:
    radial-gradient(ellipse 120% 120% at 50% 45%, #0a1014 0%, var(--screen-bg) 70%);
  /* Uneven radii bow the corners the way a real tube's faceplate does. */
  border-radius: 7% / 9%;
  box-shadow:
    0 0 0 2px #131110,
    0 6px 26px rgba(0, 0, 0, .9) inset;
}

.screen {
  position: relative;
  z-index: 1;
  width: calc(var(--cols) * 1ch);
  max-width: 100%;
  height: 100%;
  /* Grid items default to min-height:auto, which makes overflowing content
     stretch the tube instead of scrolling inside it. This is the fix. */
  min-height: 0;
  min-width: 0;
  margin: 0 auto;
  /* Sized off the tube so a full row of columns always fits inside it.
     --char-mult is derived from RS.COLS in effects.js; the value here is
     only the fallback for COLS = 64 if scripts haven't run yet. */
  font-size: clamp(7px, calc(var(--tube-w) * var(--char-mult)), 26px);
  line-height: var(--lh);
  /* The screen is a fixed grid and never scrolls. Anything that does not
     fit is a bug in the screen's layout, not something to scroll past --
     menu.js breaks long content into pages instead. */
  overflow: hidden;
  scrollbar-width: none;
  /* Crisp glyph, halo kept wide and faint so it never smears the letterform. */
  text-shadow: 0 0 7px rgba(150, 240, 240, .20);
}
.screen::-webkit-scrollbar { display: none; }

/* ==========================================================================
   Glass layers

   Ordering matters: scanlines and shadow mask sit closest to the text,
   then bloom, then the specular sheen that sells the curve, then flicker,
   then the vignette that darkens the bowed edges.
   ========================================================================== */

.fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  border-radius: inherit;
}

/* Horizontal scanlines. Masked to fade toward the rim, which is the cheapest
   honest cue that the surface is curving away from you. */
.fx-scanlines {
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0)   0px,
    rgba(0, 0, 0, 0)   1px,
    rgba(0, 0, 0, .20) 2px,
    rgba(0, 0, 0, .20) 3px
  );
  mix-blend-mode: multiply;
  opacity: .6;
  -webkit-mask-image: radial-gradient(ellipse 82% 82% at 50% 50%,
    #000 52%, rgba(0,0,0,.45) 86%, transparent 100%);
  mask-image: radial-gradient(ellipse 82% 82% at 50% 50%,
    #000 52%, rgba(0,0,0,.45) 86%, transparent 100%);
}

/* The RGB stripe of an aperture-grille tube. Very faint -- this one costs
   legibility fast if you turn it up. */
.fx-mask {
  background: repeating-linear-gradient(
    to right,
    rgba(255, 0, 0, .05) 0px,
    rgba(0, 255, 0, .05) 1px,
    rgba(0, 0, 255, .05) 2px,
    rgba(0, 0, 0, 0) 3px
  );
  mix-blend-mode: screen;
  opacity: .22;
}

/* Phosphor bloom -- the haze the tube throws into the glass itself. */
.fx-bloom {
  background: radial-gradient(ellipse at 50% 45%,
    rgba(120, 235, 235, .07) 0%,
    rgba(120, 235, 235, .022) 46%,
    transparent 74%);
  mix-blend-mode: screen;
}

/*
   The bulge.

   A genuine barrel warp needs an SVG displacement filter, which renders
   inconsistently across browsers and fails ugly. This does it with light
   instead: a broad sheen across the top of the dome, a tight hotspot where
   the room's light source would catch it, and a faint bounce off the bottom
   curve. Combined with the bowed corner radii, the masked scanlines and the
   vignette below, the surface reads as convex.
*/
.fx-bulge {
  background:
    radial-gradient(115% 68% at 50% -14%,
      rgba(255, 255, 255, .11) 0%,
      rgba(255, 255, 255, .045) 40%,
      transparent 66%),
    radial-gradient(30% 20% at 24% 13%,
      rgba(255, 255, 255, .085) 0%,
      transparent 72%),
    radial-gradient(120% 42% at 50% 118%,
      rgba(180, 230, 255, .05) 0%,
      transparent 62%);
  mix-blend-mode: screen;
}

/* Mains hum, rendered as a barely-there brightness wobble. */
.fx-flicker {
  background: rgba(150, 255, 255, .016);
  animation: flicker 6.5s infinite steps(1);
  mix-blend-mode: screen;
}
@keyframes flicker {
  0%, 96%, 100% { opacity: .30; }
  97%           { opacity: .70; }
  98%           { opacity: .14; }
  99%           { opacity: .50; }
}

/* Light falls off toward the rim, and pools in the corners. */
.fx-vignette {
  background:
    radial-gradient(ellipse 88% 88% at 50% 50%,
      transparent 46%,
      rgba(0, 0, 0, .30) 78%,
      rgba(0, 0, 0, .80) 100%),
    radial-gradient(circle at 0% 0%,   rgba(0,0,0,.55), transparent 26%),
    radial-gradient(circle at 100% 0%, rgba(0,0,0,.55), transparent 26%),
    radial-gradient(circle at 0% 100%, rgba(0,0,0,.55), transparent 26%),
    radial-gradient(circle at 100% 100%, rgba(0,0,0,.55), transparent 26%);
}

/* Degauss/power-on: a white band snaps open from a horizontal line. */
.fx-power {
  background: #dffcff;
  opacity: 0;
  z-index: 5;
}
.fx-power.firing { animation: powerOn .62s ease-out forwards; }
@keyframes powerOn {
  0%   { opacity: 1; transform: scaleY(.004); }
  35%  { opacity: 1; transform: scaleY(.012); }
  55%  { opacity: .9; transform: scaleY(1); }
  100% { opacity: 0; transform: scaleY(1); }
}

@media (prefers-reduced-motion: reduce) {
  .fx-flicker { animation: none; opacity: .28; }
  .fx-power.firing { animation: powerOn .2s linear forwards; }
}

/* ==========================================================================
   Boot overlay
   ========================================================================== */

.boot {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 9, 11, .94);
  border-radius: inherit;
  text-align: center;
  transition: opacity .35s ease;
}
.boot.hidden { opacity: 0; pointer-events: none; }

.boot-title {
  font-size: clamp(22px, calc(var(--tube-w) / 17), 50px);
  letter-spacing: .16em;
  color: var(--ansi-br-cyan);
  text-shadow: 0 0 14px rgba(124, 250, 250, .55);
}
.boot-sub {
  margin-top: 8px;
  font-size: clamp(9px, calc(var(--tube-w) / 62), 14px);
  letter-spacing: .3em;
  color: var(--ansi-magenta);
}
.boot-btn {
  margin-top: 28px;
  font: inherit;
  font-size: clamp(12px, calc(var(--tube-w) / 50), 18px);
  letter-spacing: .16em;
  color: #04160f;
  background: var(--ansi-br-green);
  border: 0;
  border-radius: 4px;
  padding: 12px 28px;
  cursor: pointer;
  box-shadow: 0 0 20px rgba(125, 251, 125, .4);
  transition: transform .1s, box-shadow .2s;
}
.boot-btn:hover  { box-shadow: 0 0 32px rgba(125, 251, 125, .75); }
.boot-btn:active { transform: translateY(1px); }
.boot-hint {
  margin-top: 20px;
  font-size: clamp(8px, calc(var(--tube-w) / 74), 12px);
  line-height: 1.9;
  color: var(--ansi-br-black);
}
.boot-key {
  border: 1px solid #4a5058;
  border-radius: 3px;
  padding: 1px 5px;
  color: #8b929b;
}

/* ==========================================================================
   The modem, and the controls that aren't part of the fiction
   ========================================================================== */

.modem {
  min-width: 210px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 14px;
  border-radius: 5px;
  background: linear-gradient(170deg, var(--case-mid) 0%, var(--case-dark) 70%, #4f4839 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, .4) inset,
    0 10px 20px rgba(0, 0, 0, .5);
}
.modem-name {
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: #4d4638;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .35);
}
.modem-name b { color: #2f2a21; }

.leds { display: flex; gap: 14px; }
.led-cell { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.led-cell em {
  font-style: normal;
  font-size: 7px;
  letter-spacing: .1em;
  color: #4d4638;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .3);
}

.led {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #55503f;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35) inset;
  transition: background .1s, box-shadow .1s;
}
.led.on-green { background: #6cff6c; box-shadow: 0 0 7px #4cff4c; }
.led.on-red   { background: #ff5f5f; box-shadow: 0 0 7px #ff3b3b; }
.led.on-amber { background: #ffc247; box-shadow: 0 0 7px #ff9d20; }

.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.ctl {
  font: inherit;
  font-size: 12px;
  letter-spacing: .06em;
  color: #9aa2ac;
  background: #1a1d22;
  border: 1px solid #2f343c;
  border-radius: 6px;
  padding: 7px 13px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.ctl:hover { color: #d5dbe3; border-color: #454c56; }
.ctl[aria-pressed="false"] { opacity: .45; }
.ctl-range { cursor: default; }
.ctl-range input { width: 96px; accent-color: var(--ansi-cyan); }
#speedVal { min-width: 66px; color: var(--ansi-cyan); }
