/* andrewpaley.com - a blue CRT monitor with a bad connection.
   The lyric is drawn on canvas so it can be torn and split per frame; the
   links are real anchors sitting on the tube, and the glass layer lies over
   everything so scanlines fall across the lot. */

:root {
  --void:     #05080c;   /* the tube with the gun off */
  --phosphor: #9ad4ff;   /* the core of the stroke */
  --halo:     #2b8fd6;   /* the bloom around it */

  /* 1970s, in the sense of a Kodak box rather than a brand guideline. Left
     warm on purpose: they are the only warm thing on a cold screen. */
  --rust:   #d9482f;
  --gold:   #e8a33d;
  --palm:   #7ba05b;
  --teal:   #4fa89b;
  --collab: #e0b563;   /* the warm hue that marks a collaboration */

  /* Cover art, cooled to the tube's temperature. Named because both the
     hover and the scrolled-into-view rules clear back from exactly this. */
  --sleeve-tint: grayscale(0.4) contrast(1.06) brightness(0.8) sepia(0.3) hue-rotate(168deg);
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  background: #000;
  color: var(--phosphor);
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  -webkit-font-smoothing: antialiased;
}

body.is-home { overflow: hidden; }

.tube {
  position: relative;
  min-height: 100dvh;
  background: var(--void);
}

/* The lyric page is a single screen and never scrolls. */
body.is-home .tube {
  position: fixed;
  inset: 0;
  overflow: hidden;
}

#screen {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* --- the glass ---------------------------------------------------------- */
/* Scanlines, the fine vertical grille of a shadow mask, and the vignette a
   curved tube throws into its own corners. Nothing here takes a click. */

.glass {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background:
    repeating-linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0)    0px,
      rgba(0, 0, 0, 0)    1.6px,
      rgba(0, 0, 0, 0.32) 2.3px,
      rgba(0, 0, 0, 0.32) 3px
    ),
    repeating-linear-gradient(
      to right,
      rgba(255, 255, 255, 0.025) 0px,
      rgba(255, 255, 255, 0.025) 1px,
      rgba(0, 0, 0, 0.05)        2px,
      rgba(0, 0, 0, 0.05)        3px
    );
}

/* The corners of a real tube fall off hard. */
.glass::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    120% 100% at 50% 50%,
    rgba(0, 0, 0, 0) 42%,
    rgba(0, 0, 0, 0.42) 78%,
    rgba(0, 0, 0, 0.82) 100%
  );
}

/* --- links -------------------------------------------------------------- */

.links {
  position: fixed;
  right: clamp(1.1rem, 3.4vw, 3rem);
  bottom: clamp(1.1rem, 3.4vh, 3rem);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.7rem, 1.6vh, 1.2rem);
}

.links a {
  display: block;
  line-height: 0;
  border-radius: 4px;
  opacity: 0.9;
  transition: opacity 200ms ease, transform 200ms ease;
}

.links a:hover,
.links a:focus-visible { opacity: 1; transform: translateY(-2px); }

.links a:focus-visible {
  outline: 2px solid var(--phosphor);
  outline-offset: 5px;
}

.mark {
  width: clamp(28px, 3.4vw, 40px);
  height: auto;
  overflow: visible;
  /* Everything on this screen is made of light, so the marks glow too. */
  filter: drop-shadow(0 0 6px rgba(90, 175, 255, 0.45));
}

.mark .ink {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mark .stripe {
  fill: none;
  stroke-width: 2.4;
  stroke-linecap: round;
}

.mark-tv   .ink { stroke: var(--rust); }
.mark-tv   .fill { fill: #ffe6c2; }

.mark-cam  .ink { stroke: var(--gold); }
.mark-cam  .fill { fill: #ffe6c2; }
.mark-cam  .s1 { stroke: var(--rust); }
.mark-cam  .s2 { stroke: var(--gold); }
.mark-cam  .s3 { stroke: var(--palm); }

.mark-wave .ink { stroke: var(--palm); }

.mark-bird .ink  { stroke: var(--teal); }
.mark-bird .wire { fill: none; stroke: var(--teal); stroke-width: 2.4; stroke-linecap: round; }
.mark-bird .fill { fill: #ffe6c2; }

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

@media (prefers-reduced-motion: reduce) {
  .links a { transition: none; }
}

/* --- content on the tube ------------------------------------------------ */
/* Everything below is ordinary DOM rather than canvas: it has to be readable
   by a screen reader and by a crawler, and it has to be linkable. The glass
   lies over it all, so it still reads as part of the display. */

.readout {
  position: relative;
  z-index: 1;
  padding: clamp(1.5rem, 5vw, 4rem);
  padding-top: clamp(4.5rem, 11vh, 7rem);
  padding-bottom: clamp(7rem, 16vh, 11rem);
  max-width: 78rem;
  font-size: clamp(0.78rem, 1.05vw, 0.92rem);
  line-height: 1.65;
}

body.is-home .readout {
  /* The lyric owns the middle of the screen; the bulletin sits above it. */
  position: fixed;
  inset: 0;
  padding-bottom: 0;
  pointer-events: none;
}

body.is-home .readout > * { pointer-events: auto; }

.page-title {
  margin: 0 0 2.5rem;
  font-size: clamp(1.5rem, 3.4vw, 2.4rem);
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--phosphor);
  text-shadow: 0 0 14px rgba(90, 175, 255, 0.45);
}

.block { margin-bottom: clamp(2.5rem, 6vh, 4.5rem); }

.block-title {
  margin: 0 0 1.1rem;
  font-size: inherit;
  font-weight: 400;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--halo);
  border-bottom: 1px solid rgba(90, 175, 255, 0.18);
  padding-bottom: 0.5rem;
}

.block-title .count {
  float: right;
  letter-spacing: 0.12em;
  opacity: 0.6;
}

.empty {
  margin: 0;
  opacity: 0.7;
}

/* --- grids -------------------------------------------------------------- */

.grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(0.9rem, 2vw, 1.6rem);
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}

.card {
  border: 1px solid rgba(90, 175, 255, 0.16);
  padding: clamp(0.85rem, 1.6vw, 1.25rem);
  background: rgba(154, 212, 255, 0.02);
}

.card-title {
  margin: 0 0 0.4rem;
  color: var(--phosphor);
  letter-spacing: 0.06em;
  text-shadow: 0 0 10px rgba(90, 175, 255, 0.3);
}

.card-meta {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  font-size: 0.9em;
  opacity: 0.72;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.card-note {
  margin: 0.7rem 0 0;
  opacity: 0.68;
  /* 15% down on both counts from what the readout sets, so the credits sit
     under the title rather than competing with it. 1.65 x 0.85 = 1.40. */
  font-size: 0.85em;
  line-height: 1.4;
}

.card-links {
  margin: 0.9rem 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.85rem;
}

/* --- link glyphs -------------------------------------------------------- */

.glyph-link {
  display: inline-flex;
  line-height: 0;
  border-bottom: 0 !important;
  padding: 0 !important;
  opacity: 0.62;
  transition: opacity 180ms ease, transform 180ms ease;
}

.glyph-link:hover,
.glyph-link:focus-visible {
  opacity: 1;
  transform: translateY(-1px);
}

.glyph-link:focus-visible {
  outline: 1px solid var(--phosphor);
  outline-offset: 3px;
}

.glyph {
  width: 1.45rem;
  height: 1.45rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  overflow: visible;
}

.glyph .solid {
  fill: currentColor;
  stroke: none;
}

.card.is-collab .glyph-link { color: var(--collab); }

@media (prefers-reduced-motion: reduce) {
  .glyph-link { transition: none; }
}

.card-links a,
.archive a {
  color: var(--phosphor);
  text-decoration: none;
  border-bottom: 1px solid rgba(90, 175, 255, 0.35);
  padding-bottom: 1px;
}

.card-links a:hover,
.archive a:hover { border-bottom-color: var(--phosphor); }

/* --- the archive -------------------------------------------------------- */

.archive {
  list-style: none;
  margin: 0;
  padding: 0;
}

.archive li {
  display: grid;
  grid-template-columns: 9rem 1fr;
  gap: 0.3rem 1.2rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid rgba(90, 175, 255, 0.08);
}

.archive time {
  opacity: 0.6;
  letter-spacing: 0.06em;
}

.archive .a-venue { color: var(--phosphor); }

.archive .a-place,
.archive .a-bands {
  grid-column: 2;
  opacity: 0.6;
}

@media (max-width: 640px) {
  .archive li { grid-template-columns: 1fr; }
  .archive .a-place,
  .archive .a-bands { grid-column: 1; }
}

/* --- the bulletin ------------------------------------------------------- */
/* The newest dispatch, top left, sized so it never crowds the lyric. */

.bulletin {
  position: absolute;
  top: clamp(1.2rem, 4vh, 2.6rem);
  left: clamp(1.2rem, 8.5vw, 6rem);
  max-width: min(30rem, 62vw);
}

.bulletin-label {
  margin: 0 0 0.35rem;
  font-size: inherit;
  font-weight: 400;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--halo);
}

.bulletin-title {
  margin: 0;
  color: var(--phosphor);
  letter-spacing: 0.04em;
  text-shadow: 0 0 10px rgba(90, 175, 255, 0.3);
}

.bulletin-date {
  margin: 0.25rem 0 0;
  opacity: 0.6;
  letter-spacing: 0.08em;
}

.bulletin-body {
  margin-top: 0.6rem;
  opacity: 0.72;
  max-height: 8.5em;
  overflow: hidden;
  /* The post trails off rather than being cut on a hard line. */
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
}

.bulletin-body p { margin: 0 0 0.6rem; }
.bulletin-body a { color: var(--phosphor); }
/* Post images would fight the lyric for the screen. The CMS writes an inline
   display:block onto them, so this has to outrank it. */
.bulletin-body img,
.bulletin-body iframe { display: none !important; }

/* --- the strip --------------------------------------------------------- */
/* Fixed along the top: the name on the left, sections on the right. It sits
   under the glass with everything else, so the scanlines fall across it. */

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: clamp(0.7rem, 1.8vh, 1.1rem) clamp(1.1rem, 3.4vw, 3rem);
  background: linear-gradient(to bottom, var(--void) 55%, transparent);
  font-size: clamp(0.72rem, 1vw, 0.85rem);
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

.topbar::after {
  content: "";
  position: absolute;
  left: clamp(1.1rem, 3.4vw, 3rem);
  right: clamp(1.1rem, 3.4vw, 3rem);
  bottom: 0;
  border-bottom: 1px solid rgba(90, 175, 255, 0.14);
}

.wordmark {
  color: var(--phosphor);
  text-decoration: none;
  letter-spacing: 0.3em;
  opacity: 0.9;
  text-shadow: 0 0 12px rgba(90, 175, 255, 0.4);
  white-space: nowrap;
}

.wordmark:hover { opacity: 1; }

.sitenav {
  display: flex;
  gap: clamp(0.9rem, 2vw, 1.6rem);
}

.sitenav a {
  color: var(--phosphor);
  text-decoration: none;
  opacity: 0.6;
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: opacity 200ms ease, border-color 200ms ease;
  text-shadow: 0 0 10px rgba(90, 175, 255, 0.35);
}

.sitenav a:hover,
.sitenav a:focus-visible,
.sitenav a[aria-current="page"] {
  opacity: 1;
  border-bottom-color: rgba(154, 212, 255, 0.6);
}

/* On the lyric page the strip steps back so the verse still owns the screen. */
body.is-home .topbar { background: none; }
body.is-home .topbar::after { border-bottom-color: rgba(90, 175, 255, 0.07); }
body.is-home .wordmark { opacity: 0.62; }
body.is-home .sitenav a { opacity: 0.45; }
body.is-home .wordmark:hover,
body.is-home .sitenav a:hover,
body.is-home .sitenav a:focus-visible { opacity: 1; }

@media (max-width: 560px) {
  .bulletin { max-width: 78vw; }
  .bulletin-body { max-height: 5.5em; }
}

/* On the scrolling pages the corner nav floats over the content, so give the
   foot of the viewport a scrim to lift it off whatever is passing underneath. */
body:not(.is-home) .tube::after {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: clamp(5rem, 13vh, 8.5rem);
  background: linear-gradient(to top, var(--void) 26%, transparent);
  pointer-events: none;
  z-index: 1;
}

/* --- news --------------------------------------------------------------- */

.posts {
  display: grid;
  gap: clamp(2rem, 5vh, 3.5rem);
  max-width: 44rem;
}

.post {
  padding-bottom: clamp(1.5rem, 4vh, 2.5rem);
  border-bottom: 1px solid rgba(90, 175, 255, 0.1);
}

.post:last-child { border-bottom: 0; }

.post-title {
  margin: 0 0 0.3rem;
  font-size: 1.15em;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--phosphor);
  text-shadow: 0 0 12px rgba(90, 175, 255, 0.35);
}

.post-date {
  margin: 0 0 1rem;
  opacity: 0.55;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 0.88em;
}

.post-body { opacity: 0.82; }
.post-body p { margin: 0 0 0.9rem; }
.post-body a { color: var(--phosphor); }

.post-body img {
  max-width: 100%;
  /* A full-bleed photo per post would bury the writing. */
  max-height: 24rem;
  width: auto;
  height: auto;
  margin: 0.4rem 0 1rem;
  /* Photographs on a phosphor screen: cooled down to belong to the tube. */
  filter: grayscale(0.35) contrast(1.05) brightness(0.82) sepia(0.25) hue-rotate(165deg);
}

.post-body iframe {
  max-width: 100%;
  aspect-ratio: 16 / 9;
  height: auto;
  border: 1px solid rgba(90, 175, 255, 0.18);
}

/* --- cover art ---------------------------------------------------------- */

.grid-records { grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr)); }

.card-cover {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  margin-bottom: 0.9rem;
  border: 1px solid rgba(90, 175, 255, 0.14);
  /* Sleeves cooled to the temperature of the tube, so they sit on the screen
     rather than on top of it. They come back to full colour when the tube
     catches them: on hover at a desk, on scrolling into view on a phone. */
  filter: var(--sleeve-tint);
  transition: filter 150ms ease-out;
}

/* Desktop: the pointer brings it up. Quick rather than a dissolve, so it
   reads as a sleeve coming into focus instead of a slow fade. */
@media (hover: hover) and (pointer: fine) {
  .card:hover .card-cover { filter: none; }
}

/* Keyboard users get it too, on any device. */
.card:focus-within .card-cover {
  filter: none;
}

/* Phones: scrolling the sleeve fully into frame brings it up instead. The
   class comes from sleeves.js, which only arms itself where there is no
   hover. It stays on, so a sleeve clears once and then holds. */
.card-cover.is-locked {
  filter: none;
}

.card-status {
  opacity: 0.75;
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.04em;
}

@media (prefers-reduced-motion: reduce) {
  /* Sleeves still clear, they just arrive rather than ease in. */
  .card-cover { transition: none; }
  .card:hover .card-cover,
  .card:focus-within .card-cover,
  .card-cover.is-locked { filter: none; }
}

/* --- collaborations ----------------------------------------------------- */
/* Folded into the same run of years as the solo records and told apart by the
   colour of the panel alone: a warm card in a cold room. Titles stay the same
   phosphor as every other release, so the eye sorts by box, not by headline. */

.card { position: relative; }

.card.is-collab {
  border-color: rgba(224, 181, 99, 0.25);
  background: rgba(224, 181, 99, 0.055);
}

.card-with {
  margin: 0.45rem 0 0;
  color: var(--collab);
  letter-spacing: 0.06em;
  opacity: 0.9;
}

/* A stand-in sleeve for the releases whose artwork we don't hold. */
.card-plate {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.35rem;
  aspect-ratio: 1;
  margin-bottom: 0.9rem;
  padding: 1.1rem;
  border: 1px solid rgba(224, 181, 99, 0.22);
  background:
    repeating-linear-gradient(
      -45deg,
      rgba(224, 181, 99, 0.05) 0 6px,
      transparent 6px 12px
    ),
    rgba(224, 181, 99, 0.03);
}

.plate-with {
  font-size: 0.8em;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  opacity: 0.55;
}

.plate-who {
  font-size: 1.35em;
  line-height: 1.25;
  color: var(--collab);
  text-shadow: 0 0 12px rgba(224, 181, 99, 0.3);
}

.card-from {
  margin: 0.45rem 0 0;
  opacity: 0.7;
  letter-spacing: 0.04em;
}

.card-from cite {
  font-style: normal;
  color: var(--collab);
}

/* --- news, page to page ------------------------------------------------- */

.lede {
  margin: -1.5rem 0 2rem;
  opacity: 0.7;
  max-width: 44rem;
}

.lede a,
.more a,
.post-title a,
.archive a {
  color: var(--phosphor);
  text-decoration: none;
  border-bottom: 1px solid rgba(90, 175, 255, 0.3);
}

.lede a:hover,
.more a:hover,
.post-title a:hover,
.archive a:hover { border-bottom-color: var(--phosphor); }

.post-title a { border-bottom-color: transparent; }
.post-title a:hover { border-bottom-color: rgba(90, 175, 255, 0.45); }

.more {
  margin: 2.5rem 0 0;
  letter-spacing: 0.1em;
}

/* A single dispatch gets room to breathe and a real heading. */
.post-single {
  max-width: 44rem;
  border-bottom: 0;
}

.post-single .post-title {
  font-size: clamp(1.25rem, 2.6vw, 1.9rem);
  line-height: 1.25;
  margin-bottom: 0.5rem;
}

/* --- mailing list ------------------------------------------------------ */

/* Opposite corner from the social marks, so the bottom of the tube reads as
   balanced. Set as a prompt rather than a form: a caret, a field with no box
   around it, and a rule underneath that lights up on focus. */
.subscribe {
  position: fixed;
  left: clamp(1.1rem, 3.4vw, 3rem);
  bottom: clamp(1.1rem, 3.4vh, 3rem);
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 0.5ch;
  font-size: clamp(0.72rem, 1.5vh, 0.88rem);
  letter-spacing: 0.06em;
}

.subscribe .prompt {
  color: var(--halo);
  opacity: 0.85;
}

.subscribe input[type="email"] {
  width: clamp(9ch, 22vw, 18ch);
  padding: 0.15em 0 0.25em;
  border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--halo) 55%, transparent);
  background: none;
  color: var(--phosphor);
  font: inherit;
  letter-spacing: inherit;
  transition: border-color 200ms ease;
}

.subscribe input[type="email"]::placeholder {
  color: var(--halo);
  opacity: 0.6;
}

.subscribe input[type="email"]:focus {
  outline: none;
  border-bottom-color: var(--phosphor);
}

.subscribe button {
  padding: 0.15em 0.6em 0.25em;
  white-space: nowrap;
  border: 1px solid color-mix(in srgb, var(--halo) 55%, transparent);
  border-radius: 3px;
  background: none;
  color: var(--phosphor);
  font: inherit;
  letter-spacing: inherit;
  cursor: pointer;
  opacity: 0.9;
  transition: opacity 200ms ease, border-color 200ms ease;
}

.subscribe button:hover,
.subscribe button:focus-visible { opacity: 1; border-color: var(--phosphor); }

/* The honeypot. Off-screen rather than display:none, which some bots skip. */
.subscribe .pot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* The reply, which replaces the row once the address is away. */
.subscribe .note {
  margin: 0;
  color: var(--halo);
}
.subscribe .note.is-bad { color: #e0b563; }

.subscribe.is-busy input,
.subscribe.is-busy button { opacity: 0.45; pointer-events: none; }

.subscribe.is-done .prompt,
.subscribe.is-done input,
.subscribe.is-done button { display: none; }

@media (max-width: 40rem) {
  /* Narrow enough that the two corners would collide, so the prompt takes the
     full width along the bottom and the marks lift clear above it. */
  .subscribe {
    right: clamp(1.1rem, 3.4vw, 3rem);
    bottom: clamp(0.7rem, 2vh, 1.2rem);
  }
  .subscribe input[type="email"] { width: 100%; }

  .links { bottom: calc(clamp(0.7rem, 2vh, 1.2rem) + 2.8rem); }
}
