@import url('https://fonts.googleapis.com/css2?family=Black+Ops+One&family=Share+Tech+Mono&display=swap');

/*
  Ghost Station.

  Measured off the cover art rather than guessed: median brightness 18/255,
  median saturation 0.42 and warm, and strongly-red pixels make up 0.06% of
  the image — one signal, a long way down the tunnel.

  So: a warm near-black ground, bone type, and red reserved for exactly one
  element. No chassis, no bezels, no rivets. There is no equipment in the
  photograph and there should be none here.
*/

:root {
  /* Warm near-black — brown-tinted, never neutral grey and never red */
  --ground:   #0a0806;
  --ground2:  #141010;

  /* Bone. The only thing most text is allowed to be. */
  --bone:     #cfc4b6;
  --bone-dim: #8c8276;
  --bone-faint:#5a5248;

  /* The signal. Used for the trace and for the transport while it is live,
     and nowhere else. */
  --signal:   #c8321e;
  --signal-hi:#e8553a;
}

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

html, body {
  width: 100%; height: 100%;
  background: var(--ground);
  color: var(--bone);
  font-family: 'Share Tech Mono', 'Courier New', monospace;
  overflow: hidden;
  user-select: none;
}

#drag-handle {
  position: fixed; top: 0; left: 0; right: 0;
  height: 34px; z-index: 2000;
  -webkit-app-region: drag;
}

#stage {
  position: relative;
  width: 100%; height: 100%;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: clamp(28px, 5vmin, 64px) clamp(26px, 5vw, 80px) clamp(22px, 4vmin, 48px);
  gap: clamp(16px, 3vmin, 40px);
}

/* The tunnel. A vignette with its weight low and its centre high, so the
   room falls away behind the trace rather than framing it. */
#depth {
  position: absolute; inset: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 85% 65% at 50% 42%,
      rgba(32, 25, 20, 0.85) 0%,
      rgba(16, 12, 10, 0.55) 45%,
      rgba(0, 0, 0, 0.85) 100%),
    linear-gradient(to bottom, #0d0a08 0%, #0a0806 55%, #060404 100%);
  z-index: 0;
}

/* Only the elements that actually flow in the grid. The old blanket
   `#stage > *:not(#depth)` outranked #status on specificity and forced it
   back into position:relative, which made it a grid item and auto-placed it
   into a row at the bottom instead of centring over the frame. */
#head, #foot, #screen { position: relative; z-index: 1; }
#screen { min-height: 0; min-width: 0; }

/* ── Head ── */
#title {
  font-family: 'Black Ops One', serif;
  font-size: clamp(22px, 4.4vmin, 56px);
  letter-spacing: 0.18em;
  /* Darker than bone — stencilled onto a wall a long time ago, not printed. */
  color: #7d7266;
  text-shadow: 0 0 30px rgba(0,0,0,0.95), 0 1px 0 rgba(0,0,0,0.9);
  line-height: 1.1;

  /* Roughened by masking the type with fractal noise, so the letterforms are
     eaten into rather than outlined. The mask is mostly opaque with scattered
     holes; feComponentTransfer pushes the turbulence to high contrast so the
     erosion reads as flaked paint instead of a soft fade. */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='120'%3E%3Cfilter id='r'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='5' seed='17'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1 1 1 1 1 0 1 1 1 0 1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23r)'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='120'%3E%3Cfilter id='r'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='5' seed='17'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1 1 1 1 1 0 1 1 1 0 1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23r)'/%3E%3C/svg%3E");
  -webkit-mask-size: 260px 120px;
          mask-size: 260px 120px;
}

#sub {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: clamp(6px, 1.2vmin, 14px);
  font-size: clamp(9px, 1.35vmin, 16px);
  letter-spacing: 0.34em;
  color: var(--bone-faint);
  text-transform: lowercase;
}

/* The wordmark, lifted from the cover's own lettering rather than set in a
   substitute face. Dim enough to sit under the title. */
/* The lockup, taken from machineoil.com: the wordmark followed by three red
   slashes at roughly twice its size, tracked tight so they nest. The red is
   the site's rgb(151,17,17) — hsl(0,80%,33%), which is the same family as the
   trace, so it does not introduce a second red. */
#sub-lockup {
  /* One knob. The slashes are sized from the wordmark rather than set
     independently, so the lockup keeps its proportion at every viewport.
     1.55 of the wordmark's height lands the glyphs slightly above it —
     Impact's slash draws at roughly 0.75 of its font size. */
  --mark-h: clamp(13px, 2.3vmin, 29px);
  display: flex;
  align-items: center;
  gap: 0.37em;
}

#sub-slash {
  font-family: Impact, 'Arial Narrow', 'Share Tech Mono', sans-serif;
  font-size: calc(var(--mark-h) * 1.55);
  line-height: 1;
  letter-spacing: -0.14em;
  color: rgb(151, 17, 17);
  opacity: 0.62;
  transform: translateY(-0.03em);
}

#sub-logo {
  height: var(--mark-h);
  width: auto;
  display: block;
  opacity: 0.38;
  -webkit-user-drag: none;
}

#session-val {
  letter-spacing: 0.2em;
  text-transform: none;
  color: var(--bone-dim);
}

/* ── The signal ── */
canvas#waveform {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  min-height: 0;
  /* The static field and the trace are drawn into the same canvas, so one
     fade brings both up together — a tube warming rather than a picture
     being switched on. */
  opacity: 0;
  transition: opacity 2.6s cubic-bezier(0.35, 0, 0.55, 1);

  /* A tube, not a rectangle. Kept restrained — a real CRT corner is a gentle
     radius, not a lozenge, and overdoing it reads as a retro filter. */
  border-radius: 2.2% / 3.2%;
  overflow: hidden;
}

canvas#waveform.live { opacity: 1; }

canvas#spectrum { display: none; }

/* ── Foot ── */
#foot {
  display: flex;
  align-items: center;
  gap: clamp(18px, 3.5vw, 54px);
  font-size: clamp(10px, 1.5vmin, 17px);
}

#run-btn, #reset-btn {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: 'Share Tech Mono', monospace;
  font-size: inherit;
  letter-spacing: 0.30em;
  color: var(--bone-faint);
  transition: color 0.4s ease, text-shadow 0.4s ease;
}

#run-btn:hover, #reset-btn:hover:not(.inactive) { color: var(--bone); }

/* The one red thing besides the trace, and only while it is transmitting. */
#run-btn.running {
  color: var(--signal-hi);
  text-shadow: 0 0 14px rgba(200, 50, 30, 0.55);
}

#reset-btn.inactive { opacity: 0.3; cursor: default; pointer-events: none; }

#elapsed-val {
  margin-left: auto;
  letter-spacing: 0.22em;
  color: var(--bone-dim);
  font-variant-numeric: tabular-nums;
}

/* ── Gain ──
   A line. There is no knob in this world. */
#gain {
  display: flex;
  align-items: center;
  gap: 10px;
  opacity: 0.45;
  transition: opacity 0.35s;
}
#gain:hover { opacity: 1; }

#gain-label {
  font-size: 0.78em;
  letter-spacing: 0.3em;
  color: var(--bone-faint);
}

#gain-track {
  position: relative;
  touch-action: none;        /* or dragging it scrolls the page */
  width: clamp(54px, 7vw, 96px);
  height: 1px;
  background: rgba(140, 130, 118, 0.3);
  cursor: ew-resize;
}

/* Taller hit area than the hairline it draws */
/* A hairline is impossible to hit with a thumb; the target is 36px tall. */
#gain-track::before {
  content: '';
  position: absolute; left: 0; right: 0; top: -18px; bottom: -18px;
}

#gain-fill {
  height: 100%;
  background: var(--bone-dim);
  pointer-events: none;
}

/* ── Transmitting ──
   Between the press and the first sound, rendered as text on a failing CRT:
   phosphor bloom, red/cyan fringing from a misconverged tube, irregular
   flicker that varies in brightness rather than switching on and off, and
   occasional horizontal tearing. Sits BELOW the scan-line overlay so the
   lines cut through it rather than lying on top. */
#status {
  position: absolute; inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 38;
}

#status[hidden] { display: none; }

#status span {
  position: relative;
  font-family: 'Share Tech Mono', monospace;
  font-size: clamp(12px, 2.0vmin, 24px);
  letter-spacing: 0.62em;
  text-indent: 0.62em;
  color: #e8d9c4;
  /* Bloom: a tube this old does not render an edge */
  text-shadow:
    0 0 4px  rgba(232, 217, 196, 0.8),
    0 0 14px rgba(220, 120, 80, 0.45),
    0 0 34px rgba(200, 50, 30, 0.30);
  animation: crtFlicker 3.1s steps(1) infinite, crtTear 7.3s steps(1) infinite;
}

/* Misconvergence — the guns do not quite land together */
#status span::before,
#status span::after {
  content: attr(data-text);
  position: absolute;
  left: 0; top: 0;
  width: 100%;
  letter-spacing: inherit;
  text-indent: inherit;
  pointer-events: none;
}

#status span::before {
  color: rgba(255, 60, 40, 0.55);
  transform: translateX(-1.2px);
  text-shadow: none;
  mix-blend-mode: screen;
}

#status span::after {
  color: rgba(70, 190, 220, 0.38);
  transform: translateX(1.4px);
  text-shadow: none;
  mix-blend-mode: screen;
}

/* Brightness wander, not an on/off blink — a weak signal being held */
@keyframes crtFlicker {
  0%   { opacity: 0.00; }
  4%   { opacity: 0.85; }
  7%   { opacity: 0.35; }
  9%   { opacity: 1.00; }
  14%  { opacity: 0.55; }
  17%  { opacity: 0.90; }
  22%  { opacity: 0.10; }
  26%  { opacity: 0.70; }
  31%  { opacity: 0.25; }
  36%  { opacity: 0.95; }
  44%  { opacity: 0.45; }
  52%  { opacity: 0.00; }
  63%  { opacity: 0.80; }
  68%  { opacity: 0.30; }
  73%  { opacity: 0.88; }
  80%  { opacity: 0.18; }
  88%  { opacity: 0.62; }
  94%  { opacity: 0.08; }
  100% { opacity: 0.00; }
}

/* Occasional horizontal tear, held one frame and gone */
@keyframes crtTear {
  0%, 100% { transform: translateX(0) scaleY(1); }
  23%  { transform: translateX(0) scaleY(1); }
  24%  { transform: translateX(-7px) scaleY(1.06); }
  25%  { transform: translateX(4px)  scaleY(0.96); }
  26%  { transform: translateX(0) scaleY(1); }
  71%  { transform: translateX(0) scaleY(1); }
  72%  { transform: translateX(5px) scaleY(1.04); }
  73%  { transform: translateX(0) scaleY(1); }
}

/* The end of a tape, and the hunt for the next. Same failing-tube treatment
   as the ident — it is the same receiver saying something. */
#ended {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
  z-index: 39;
}
#ended[hidden] { display: none; }

#ended span {
  position: relative;
  font-family: 'Share Tech Mono', monospace;
  font-size: clamp(11px, 1.9vmin, 23px);
  letter-spacing: 0.58em;
  text-indent: 0.58em;
  color: #e8d9c4;
  text-shadow:
    0 0 4px  rgba(232, 217, 196, 0.8),
    0 0 14px rgba(220, 120, 80, 0.45),
    0 0 34px rgba(200, 50, 30, 0.30);
  animation: crtFlicker 3.1s steps(1) infinite, crtTear 7.3s steps(1) infinite;
}

#ended span::before, #ended span::after {
  content: attr(data-text);
  position: absolute; left: 0; top: 0; width: 100%;
  letter-spacing: inherit; text-indent: inherit;
  pointer-events: none;
}
#ended span::before {
  color: rgba(255, 60, 40, 0.55);
  transform: translateX(-1.2px);
  text-shadow: none; mix-blend-mode: screen;
}
#ended span::after {
  color: rgba(70, 190, 220, 0.38);
  transform: translateX(1.4px);
  text-shadow: none; mix-blend-mode: screen;
}

/* ── No signal ── */
#no-tracks {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 10px;
  background: rgba(10, 8, 6, 0.92);
  z-index: 50;
}

#no-tracks p {
  color: var(--bone-faint);
  font-size: clamp(10px, 1.5vmin, 18px);
  letter-spacing: 0.26em;
  text-transform: uppercase;
  text-align: center;
  line-height: 2.2;
}

#no-tracks p span {
  display: block;
  color: var(--bone-dim);
  font-size: 0.75em;
  margin-top: 8px;
}

/* ── Surface ──
   Grain and scan lines over everything, as before. Kept subtle: the cover is
   a photograph, not a monitor, and the signal is the only live thing. */
#stage::after {
  content: '';
  position: absolute; inset: 0;
  z-index: 40;
  pointer-events: none;
  /* Light lines. Dark ones are invisible against this ground — they only
     ever worked because the old chassis was bright underneath them. */
  background: repeating-linear-gradient(
    0deg, transparent, transparent 2px,
    rgba(206, 190, 170, 0.030) 2px, rgba(206, 190, 170, 0.030) 3px);
}

#stage::before {
  content: '';
  position: absolute; inset: 0;
  z-index: 41;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0.22;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' seed='8' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  background-size: 300px 300px;
  animation: grainDrift 14s steps(1) infinite;
}

@keyframes grainDrift {
  0%   { background-position:   0px   0px; }
  20%  { background-position: -40px  30px; }
  40%  { background-position:  35px -25px; }
  60%  { background-position: -25px -35px; }
  80%  { background-position:  30px  20px; }
  100% { background-position:   0px   0px; }
}

.roll {
  position: absolute; left: 0; right: 0;
  height: 16%;
  z-index: 42;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(210, 190, 170, 0) 0%,
    rgba(210, 190, 170, 0.016) 50%,
    rgba(210, 190, 170, 0) 100%);
  animation: rollDown 23s linear infinite;
}

@keyframes rollDown {
  0%   { top: -16%; }
  100% { top: 100%; }
}

/* ── Boot ── */
#boot-overlay {
  position: fixed; inset: 0; z-index: 3000;
  pointer-events: none;
  background: #000;
  animation: bootFlicker 1.8s steps(1) forwards;
}

@keyframes bootFlicker {
  0%   { opacity: 1; }
  12%  { opacity: 0; }
  16%  { opacity: 1; }
  19%  { opacity: 0; }
  46%  { opacity: 0; }
  48%  { opacity: 0.6; }
  50%  { opacity: 0; }
  100% { opacity: 0; }
}

/* ── Update notice ──
   Said quietly, once, a while into listening. Dim, monospaced, the same
   receiver talking; it surfaces over seconds and is never a dialog. */
#update-note {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(64px, 12vmin, 120px);
  margin: 0 auto;
  width: max-content;
  max-width: 90%;
  padding: 0.6em 1em;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: center;
  font-family: 'Share Tech Mono', monospace;
  color: var(--bone-dim);
  opacity: 0;
  transition: opacity 4s ease;
  z-index: 40;
}
#update-note[hidden] { display: none; }
#update-note.shown { opacity: 0.8; }
#update-note span {
  display: block;
  font-size: clamp(9px, 1.5vmin, 15px);
  letter-spacing: 0.4em;
  text-indent: 0.4em;
}
#update-note small {
  display: block;
  margin-top: 0.5em;
  font-size: clamp(8px, 1.2vmin, 12px);
  letter-spacing: 0.35em;
  text-indent: 0.35em;
  color: rgb(151, 17, 17);
}
#update-note:hover, #update-note:focus-visible { opacity: 1; outline: none; }

/* ── Web and phones ──
   From the beta review: on a phone the visible height is less than the window,
   so the page must fit the DYNAMIC viewport, the canvas must be allowed to
   shrink, and the transport must stay on screen. Scoped to html.web so the
   desktop window, with its drag handle and its own padding, is unchanged. */
html.web, html.web body { height: 100%; }
html.web body {
  height: 100vh;
  height: 100svh;
  height: 100dvh;
  display: flex;               /* with or without a banner above the stage */
  flex-direction: column;
}
html.web #drag-handle { display: none; }
html.web #stage {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
  min-width: 0;
  grid-template-rows: auto minmax(0, 1fr) auto;
  padding-bottom: max(clamp(22px, 4vmin, 48px), env(safe-area-inset-bottom));
}
html.web canvas#waveform { min-width: 0; }
html.web #foot { min-width: 0; gap: clamp(8px, 2vw, 24px); }
html.web #run-btn, html.web #reset-btn { min-height: 44px; touch-action: manipulation; }

@media (max-width: 600px) {
  html.web #stage {
    /* The top inset is 0 in a browser tab and the status bar / Dynamic Island in
       an installed app, so the title is never under them. */
    padding: max(16px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right))
      max(12px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left));
    gap: 12px;
  }
  html.web #title { font-size: clamp(19px, 5.5vw, 28px); letter-spacing: .14em; }
  html.web #foot {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0 16px;
    font-size: 13px;
  }
  html.web #run-btn, html.web #reset-btn { width: 100%; text-align: left; padding: 8px 0; }
  html.web #run-btn { color: var(--bone-dim); }
  html.web #run-btn.running { color: var(--signal-hi); }
  html.web #elapsed-val { margin-left: 0; min-height: 36px; display: flex; align-items: center; }
  html.web #gain { justify-content: flex-end; min-height: 36px; }
  html.web #gain-track { width: clamp(54px, 18vw, 96px); }
}
@media (max-height: 450px) and (min-width: 601px) {
  html.web #stage { padding: max(8px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left)); gap: 8px; }
  html.web #title { font-size: 20px; }
  html.web #sub { margin-top: 4px; }
  html.web #foot { font-size: 12px; }
}
