/* ================================================================
   t stop radio

   The site is black, small-set and mostly empty, and the thing that
   carries it is a one-bit field: a morphing form dithered to pure
   black and white, running behind everything, all the time.

   Two rules hold the whole design together.

   Text never sits on the field. Content lives on hard black plates cut
   out of it, so nothing needs a scrim, a blur or a contrast model to
   stay readable. That is also the composition: the plates are the
   holes, the field is what shows through the gaps between them.

   And nothing is set large. The scale contrast comes from the form,
   not from the type. Every piece of copy on the site is between 10px
   and 52px, lowercase, and there are no slogans anywhere.
   ================================================================ */

:root {
  --bg: #000000;
  --fg: #f0eee9;
  --muted: #9d9992;
  --faint: #6b6862;
  --line: rgba(255, 255, 255, 0.14);
  --hair: rgba(255, 255, 255, 0.07);
  --accent: #ff2d17;

  /* The only colour in the mark, and the only place it appears. It is
     inside the roundel's SVG and nowhere else: as soon as gold reaches
     buttons and bands the page starts advertising at you. */
  --gold: #f4b223;

  --sans: 'Bricolage Grotesque', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono: 'DM Mono', ui-monospace, Menlo, monospace;

  --head: 54px;
  --bar: 64px;
  --pad: 26px;
  --ease: cubic-bezier(.4, 0, .2, 1);
  --slow: cubic-bezier(.32, .06, .16, 1);
}

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

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }
::selection { background: var(--fg); color: #000; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Data, not voice. Times, counts, states, labels. */
.mono {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ---------------- the field ----------------
   `.backdrop`, not `.field`: the form rows are `.field` and this rule
   is `position: fixed; inset: 0`, so sharing the name collapsed every
   input on the site into a stack of full-screen boxes.

   Drawn in app.js at roughly a fifth of the screen's resolution and
   thresholded through an ordered dither, so every pixel is either on
   or off. Nothing here smooths it: the upscale is nearest-neighbour
   and the pixels are meant to be the size of a fingernail.

   It sits under the page rather than behind the text, which is the
   whole reason it can be this bright. */
.backdrop {
  position: fixed; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  opacity: .62;
  transition: opacity 1.6s var(--slow);
}
body.playing .backdrop { opacity: 1; }

/* Everything else rides above it. */
header.site, .bar, .listen, .page, .vibe { position: relative; z-index: 1; }

/* ---------------- header ---------------- */
header.site {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  height: var(--head);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--pad);
  background: #000;
  border-bottom: 1px solid var(--line);
}
.mark {
  display: flex; align-items: center; gap: 9px;
  font-size: 14px; font-weight: 600; letter-spacing: -0.02em;
  text-decoration: none;
}

/* The roundel. A T drawn as a transit line, three terminus stops
   joined into the letter. Carried as a background rather than inline
   SVG so it is defined once instead of once per page, and because it
   is decorative: the wordmark beside it already names the station.

   Both users share this one rule. `.roundel` is a real element and
   sizes itself where it is used; `.mark::before` is the header's. */
.mark::before, .roundel {
  content: '';
  flex: none; aspect-ratio: 1; width: 19px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='%23f4b223'/%3E%3Cg stroke='%230a0a0a' stroke-width='11' stroke-linecap='round' fill='none'%3E%3Cpath d='M27 33H73'/%3E%3Cpath d='M50 33V72'/%3E%3C/g%3E%3Cg fill='%23f4b223'%3E%3Ccircle cx='27' cy='33' r='3.3'/%3E%3Ccircle cx='73' cy='33' r='3.3'/%3E%3Ccircle cx='50' cy='72' r='3.3'/%3E%3C/g%3E%3C/svg%3E")
    center / contain no-repeat;
}
.mark .bullet {
  flex: none; margin-left: 2px;
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(255, 255, 255, .18);
  transition: background .3s, box-shadow .3s;
}
.mark .bullet.on { background: var(--accent); box-shadow: 0 0 8px rgba(255, 45, 23, .9); }

nav.site { display: flex; gap: 20px; }
nav.site a {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--faint); text-decoration: none;
  transition: color .2s;
}
nav.site a:hover, nav.site a[aria-current] { color: var(--fg); }

/* ================================================================
   Listen

   Two plates over the field. Idle they take most of the screen and
   the field is a band between them. Press play and they pull apart:
   the stage drops to a small plate in the bottom corner, the schedule
   narrows to a column at the edge, and the form takes the room.

   Each edge is transitioned separately rather than as one `inset`
   shorthand, which is what lets the moves run in sequence instead of
   all at once.
   ================================================================ */
/* The padding is the point. Absolute children resolve against the
   padding box, so this is the margin of bare field around both plates,
   and the only reason the field is ever visible on this page. */
.listen {
  position: fixed; inset: var(--head) 0 var(--bar) 0;
  padding: 18px;
  display: grid; grid-template-columns: 1fr;
}

@media (min-width: 861px) {
  .stage, .sched-pane { position: absolute; }

  /* Idle: a wide plate and a narrow one, with a slot of field running
     between them. Playing: the stage drops to a small plate in the
     bottom corner and the schedule pulls to the edge, so most of the
     screen is form. */
  .stage {
    top: 0; left: 0; right: 52%; bottom: 0;
    transition:
      top 1.4s var(--slow) .1s,
      right 1.4s var(--slow) .1s;
  }
  .sched-pane {
    top: 0; right: 0; bottom: 0; left: 62%;
    transition: left 1.4s var(--slow) .25s;
  }

  body.playing .stage {
    top: 56%; right: 68%;
    transition:
      top 1.5s var(--slow) .35s,
      right 1.5s var(--slow) .35s;
  }
  body.playing .sched-pane {
    left: 81%;
    transition: left 1.5s var(--slow) .1s;
  }
}

.stage {
  background: #000;
  display: flex; flex-direction: column; justify-content: center;
  align-items: flex-start;
  padding: 40px 34px;
  min-width: 0; overflow: hidden;
  border: 1px solid var(--line);
}
.stage-inner { width: 100%; min-width: 0; }

.bill { display: flex; align-items: center; gap: 14px; }
.bill-text { min-width: 0; }
.bill .roundel { width: 26px; opacity: .9; }

/* The name of whatever is on. The largest thing on the site, and it is
   52px at the very most. */
.stage .artist {
  font-size: clamp(27px, 3.4vw, 50px);
  font-weight: 800; line-height: 1;
  letter-spacing: -0.045em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The track title. It changes every few minutes and shouldn't pull the
   eye each time, so it is set as data rather than as a headline. */
.stage .song {
  margin-top: 14px;
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .04em; color: var(--muted);
  min-height: 1.5em;              /* holds its line so nothing jumps */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: opacity .34s ease;
}
.stage .song.fading { opacity: 0; }
/* Not a track title, a state. */
.stage .song.live { color: var(--accent); text-transform: uppercase; letter-spacing: .14em; }
/* Retiring a title that is about to stop being true. Slower than the
   swap, so it reads as settling rather than as something vanishing. */
.stage .song.ending { opacity: 0; transition: opacity 2.4s ease; }

/* The state line. A pip, a word and a clock, on one rule, no box
   around it. It used to be an amber sign, which was the single most
   branded object on the page. */
.readout {
  margin-top: 26px; padding-top: 14px;
  border-top: 1px solid var(--hair);
  display: flex; align-items: center; gap: 11px;
  width: 100%;
}
.pip {
  flex: none; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent); animation: pulse 2.4s ease-in-out infinite;
  box-shadow: 0 0 8px rgba(255, 45, 23, .9);
}
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .12 } }
.readout .range {
  margin-left: auto;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: .1em; color: var(--faint);
  font-variant-numeric: tabular-nums; user-select: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.cue { margin-top: 26px; }
.cue .ring {
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--line); background: none; color: var(--fg);
  display: grid; place-items: center; font-size: 11px;
  cursor: pointer;
  transition: background-color .2s, color .2s, border-color .2s, opacity .2s;
}
.cue .ring:hover:not(:disabled) { background: var(--fg); color: #000; border-color: var(--fg); }
.cue .ring:disabled { opacity: .22; cursor: not-allowed; }

/* ---------------- schedule ----------------
   A line with twenty four stops on it. Most hours carry no name, and
   those rows are deliberately left blank rather than filled with the
   station's own: a column of the same words twenty four times reads as
   placeholder data. Blank, it reads as a diagram, and an hour that does
   carry a name is the only thing on the line. */
.sched-pane {
  background: #000;
  display: flex; flex-direction: column; min-height: 0;
  border: 1px solid var(--line);
}
.sched-top {
  flex: none;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; padding: 18px var(--pad) 12px;
}
#clock { font-variant-numeric: tabular-nums; }
.sched-scroll { overflow-y: auto; padding-bottom: 20px; scrollbar-width: none; }
.sched-scroll::-webkit-scrollbar { display: none; }

.slot {
  position: relative;
  display: grid; grid-template-columns: 14px 74px 1fr;
  gap: 12px; align-items: center;
  padding: 9px var(--pad);
}
.slot .t {
  font-family: var(--mono); font-size: 10px; color: var(--faint);
  font-variant-numeric: tabular-nums; letter-spacing: .04em;
}
.slot .w {
  font-size: 13px; font-weight: 600; letter-spacing: -0.015em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.slot.now .t, .slot.now .w { color: var(--fg); }

/* ::before is the stop and takes the row's first column; ::after is the
   rail and is out of flow so it doesn't claim a column of its own. The
   rail is painted second and would cover the stop, hence the z-index. */
.slot::before {
  content: '';
  grid-column: 1; justify-self: center;
  position: relative; z-index: 1;
  width: 5px; height: 5px; border-radius: 50%;
  background: #000; border: 1px solid rgba(255, 255, 255, .3);
  transition: background-color .3s, border-color .3s, box-shadow .3s;
}
.slot::after {
  content: '';
  position: absolute; z-index: 0;
  top: 0; bottom: 0; width: 1px;
  left: calc(var(--pad) + 7px); margin-left: -0.5px;
  background: var(--hair);
}
/* Terminus at each end: the rail starts and stops at a stop rather than
   running off into the padding. */
.slot:first-child::after { top: 50%; }
.slot:last-child::after { bottom: 50%; }

.slot.booked::before { background: var(--fg); border-color: var(--fg); }
.slot.now::before {
  background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 10px rgba(255, 45, 23, .8);
}

/* ---------------- player bar ---------------- */
.bar {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 50;
  height: var(--bar);
  display: flex; align-items: center; gap: 14px;
  padding: 0 var(--pad);
  background: #000;
  border-top: 1px solid var(--line);
}
.play {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  border: 1px solid var(--line); background: none; color: var(--fg);
  display: grid; place-items: center; font-size: 9px; line-height: 1;
  cursor: pointer;
  transition: background-color .2s, color .2s, border-color .2s, opacity .2s;
}
.play:hover:not(:disabled) { background: var(--fg); color: #000; border-color: var(--fg); }
.play:disabled { opacity: .22; cursor: not-allowed; }
.bar .meta { min-width: 0; }
.bar .meta .v {
  font-size: 13px; font-weight: 600; letter-spacing: -0.015em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bar .right {
  margin-left: auto; font-family: var(--mono); font-size: 9.5px;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--faint);
  /* `pre`, not `nowrap`: the runs are separated by a gap of spaces and
     nowrap still collapses them into one. */
  white-space: pre;
}

/* ================================================================
   The gate

   Shown once a session. No copy on it beyond the station's name and
   the word on the button: the field is doing the talking, and a door
   that explains itself isn't one.

   The reason it exists is the audio. A click on it is a user gesture,
   which is what lets the stream start with sound the instant you are
   through, instead of landing on a silent page with a button on it.
   The cross in the corner is the way past without sound, because a
   shared link to a page shouldn't start playing music at somebody.
   ================================================================ */
.gate {
  position: fixed; inset: 0; z-index: 200;
  background: #000;
  display: grid; place-items: center;
  overflow: hidden;
  opacity: 1; transition: opacity .6s var(--slow);
}
.gate.out { opacity: 0; pointer-events: none; }
html.gated, body.gated { overflow: hidden; }

.gate canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  image-rendering: pixelated; image-rendering: crisp-edges;
}
.gate-in {
  position: relative; z-index: 1;
  display: grid; justify-items: center; gap: 26px;
  animation: gateIn 1.1s var(--slow) both;
}
/* A hole punched in the field, the same pure black as every plate on
   the site. Nothing translucent: half-transparent over a one-bit
   pattern just makes mud. */
.gate-go {
  width: 108px; height: 108px; border-radius: 50%;
  border: 1px solid var(--fg); background: #000;
  color: var(--fg); cursor: pointer;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: .22em; text-transform: uppercase;
  transition: background-color .25s, color .25s;
}
.gate-go:hover { background: var(--fg); color: #000; }
.gate-name {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: .3em; color: var(--fg);
  background: #000; padding: 7px 12px;
}
/* The way past without sound. A cross, no sentence. */
.gate-skip {
  position: absolute; top: 18px; right: 20px; z-index: 2;
  width: 34px; height: 34px;
  background: none; border: none; cursor: pointer;
  color: rgba(255, 255, 255, .45); font-size: 17px; line-height: 1;
  transition: color .2s;
}
.gate-skip:hover { color: var(--fg); }
@keyframes gateIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ---------------- inner pages ---------------- */
.page { padding: calc(var(--head) + 92px) 0 calc(var(--bar) + 100px); }
.wrap {
  max-width: 620px; margin: 0 auto; padding: 34px var(--pad) 40px;
  background: #000; border: 1px solid var(--line);
}
@media (min-width: 720px) { .wrap { padding: 44px 46px 48px; } }

h1 {
  font-size: clamp(28px, 4.2vw, 44px);
  font-weight: 800; line-height: .98; letter-spacing: -0.05em;
  margin-top: 12px;
}
.page p { color: var(--muted); font-size: 15px; }
.page p + p { margin-top: 14px; }
.page .intro { color: var(--fg); margin-top: 22px; max-width: 34ch; }
.page a[href^="mailto"] {
  font-family: var(--mono); font-size: 14px; color: var(--fg);
  text-decoration: none; border-bottom: 1px solid var(--line); padding-bottom: 3px;
}
.page a[href^="mailto"]:hover { border-bottom-color: var(--fg); }

/* ---------------- form ----------------
   Four fields. It used to branch into three broadcast routes with
   their own follow-up questions, which was a form about logistics. The
   station decides what airs now, so all this has to do is let someone
   hand over their music. */
form { display: grid; gap: 22px; margin-top: 38px; }
.field { display: grid; gap: 8px; }
label {
  font-family: var(--mono); font-size: 9.5px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--faint);
}
label .opt { text-transform: none; letter-spacing: .04em; }
input[type=text], input[type=email], textarea {
  font: inherit; font-size: 15px; padding: 0 0 10px;
  border: none; border-bottom: 1px solid var(--line); border-radius: 0;
  background: none; color: var(--fg); width: 100%;
  transition: border-color .2s;
}
input::placeholder, textarea::placeholder { color: rgba(255, 255, 255, .2); }
input:focus, textarea:focus { outline: none; border-bottom-color: var(--fg); }
textarea { min-height: 30px; resize: vertical; line-height: 1.6; }
.check {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: 12.5px; color: var(--faint); line-height: 1.5;
}
.check input { margin-top: 2px; flex: none; width: 13px; height: 13px; accent-color: var(--fg); }
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 15px 34px; border-radius: 0;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: .24em; text-transform: uppercase;
  border: 1px solid var(--fg); background: none; color: var(--fg);
  text-decoration: none; cursor: pointer;
  transition: background-color .2s, color .2s;
}
.btn:hover { background: var(--fg); color: #000; }

/* ================================================================
   About

   The page is one sentence, set once at reading size, and then the
   same sentence again as raw material: a wall of it at 9px, with the
   field painted over the top in multiply. Where the field is white the
   words survive; where it is black they are gone. So the sentence is
   legible only in the shape the form happens to be making, and the
   shape is moving.

   Nothing else is on the page except an address.
   ================================================================ */
.vibe { position: relative; }

.mosaic {
  position: relative;
  height: 100vh; min-height: 520px;
  overflow: hidden;
  padding: calc(var(--head) + 4px) 0 0;
  background: #000;
  user-select: none;
}
.mosaic-text {
  font-family: var(--mono); font-size: 9px; line-height: 1.75;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--fg);
  word-break: break-all;
  padding: 0 10px;
}
/* Multiply against a one-bit field: white leaves the text alone, black
   erases it. The canvas covers the block completely, so this is the
   only thing deciding what can be read. */
.mosaic canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  mix-blend-mode: multiply;
  image-rendering: pixelated; image-rendering: crisp-edges;
  pointer-events: none;
}

/* The sentence once, plainly, on a plate over the wall. */
.claim {
  position: absolute; z-index: 2;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  background: #000; border: 1px solid var(--line);
  padding: 30px 34px;
  font-size: clamp(19px, 2.6vw, 30px); font-weight: 800;
  letter-spacing: -0.045em; line-height: 1.08;
  white-space: nowrap;
}

.vibe-foot {
  max-width: 620px; margin: 0 auto;
  padding: 70px var(--pad) calc(var(--bar) + 80px);
  position: relative; z-index: 2; background: #000;
}
.vibe-foot p { color: var(--muted); font-size: 15px; }
.vibe-foot p + p { margin-top: 14px; }
.vibe-foot a[href^="mailto"] {
  font-family: var(--mono); font-size: 15px; color: var(--fg);
  text-decoration: none; border-bottom: 1px solid var(--line); padding-bottom: 3px;
}
.vibe-foot a[href^="mailto"]:hover { border-bottom-color: var(--fg); }
.vibe-foot .fine {
  margin-top: 46px; padding-top: 18px; border-top: 1px solid var(--hair);
  font-family: var(--mono); font-size: 9.5px;
  letter-spacing: .13em; text-transform: uppercase; color: var(--faint);
}

/* ---------------- responsive ---------------- */
@media (max-width: 860px) {
  :root { --pad: 18px; }
  .listen {
    /* Relative, not static: static would drop the z-index above and let
       the field, which is positioned, paint over the content. */
    position: relative; inset: auto;
    display: block;
    padding-top: var(--head); padding-bottom: var(--bar);
  }
  .stage { border-right: none; border-bottom: 1px solid var(--line); padding: 54px var(--pad); }
  .sched-pane { border-left: none; }
  .sched-scroll { overflow: visible; }
  .backdrop { opacity: .34; }
  body.playing .backdrop { opacity: .8; }
  .claim { white-space: normal; text-align: center; max-width: 86vw; }
}

@media (max-width: 560px) {
  .slot { grid-template-columns: 14px 66px 1fr; gap: 10px; }
  nav.site { gap: 14px; }
  .wrap { padding: 30px 20px 34px; }
}

@media (prefers-reduced-motion: reduce) {
  .pip { animation: none; }
  .gate-in { animation: none; }
  .stage, .sched-pane, .backdrop { transition-duration: .01ms; }
}
