/* ---------------------------------------------------------------- fonts --- */

@font-face {
  font-family: 'PPMondwest';
  /* WOFF2 first: same outlines, roughly half the bytes. The OTF stays
     as the fallback for anything that cannot read it. */
  src: url('fonts/PPMondwest-Regular.woff2') format('woff2'),
       url('fonts/PPMondwest-Regular.otf') format('opentype');
  font-weight: 400;
  font-display: block;
}
@font-face {
  font-family: 'PPNeueBit';
  /* WOFF2 first: same outlines, roughly half the bytes. The OTF stays
     as the fallback for anything that cannot read it. */
  src: url('fonts/PPNeueBit-Bold.woff2') format('woff2'),
       url('fonts/PPNeueBit-Bold.otf') format('opentype');
  font-weight: 700;
  font-display: block;
}
@font-face {
  font-family: 'Micro5';
  /* WOFF2 first: same outlines, roughly half the bytes. The TTF stays
     as the fallback for anything that cannot read it. */
  src: url('fonts/Micro5-Regular.woff2') format('woff2'),
       url('fonts/Micro5-Regular.ttf') format('truetype');
  font-weight: 400;
  font-display: block;
}

:root {
  /* Set every frame from the visible Colectivo/Individuo ratio. The initial
     value is the ramp midpoint, so the first paint is never black. */
  --field-bg: #993171;
  --hud: rgba(255, 255, 255, 0.72);

  /* Written by main.js on every relayout. --sub is one subtile (a quarter of
     a tile edge) in CSS px: the module all the panel chrome is measured in.
     --field-w is the tile field's width, snapped to a whole number of tiles. */
  --sub: 4px;
  --field-w: 60vw;
  --panel-w: 40vw;
}

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

html, body {
  height: 100%;
  overflow: hidden;              /* the field and the panel scroll, never the page */
  background: var(--field-bg);
  color: #fff;
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* 100mm reference used to derive device px-per-mm. */
#mmprobe {
  position: absolute;
  top: -1000px;
  left: 0;
  width: 100mm;
  height: 0;
  visibility: hidden;
  pointer-events: none;
}

/* Background plate, full viewport, under both columns. Add gradients, noise,
   blend modes or animation here without touching the tile renderer. */
#backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: var(--field-bg);
  pointer-events: none;
}

/* ------------------------------------------------- left 3/5: tile field --- */

#field {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1;
  display: block;
  pointer-events: none;
}

#scroller {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--field-w);
  height: 100%;
  z-index: 2;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  outline: none;
  scrollbar-width: none;
}
#scroller::-webkit-scrollbar { width: 0; height: 0; }
#spacer { width: 1px; }


/* ------------------------------------------------------ right 2/5: text --- */

#panel {
  position: fixed;
  top: 0;
  left: var(--field-w);
  right: 0;
  bottom: 0;
  z-index: 2;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-width: none;
  padding: calc(var(--sub) * 6) calc(var(--sub) * 5) calc(var(--sub) * 20);
  /* The reference art is aliased pixel type; keep the edges hard. */
  -webkit-font-smoothing: none;
  text-rendering: geometricPrecision;
}
#panel::-webkit-scrollbar { width: 0; height: 0; }

/* --- manifiesto --- */

#manifiesto { margin-bottom: calc(var(--sub) * 22); }

#mf-title {
  font-family: 'PPMondwest', serif;
  font-weight: 400;
  font-size: calc(var(--panel-w) * 0.105);
  line-height: 0.92;
  letter-spacing: 0.01em;
  text-align: center;
  margin-bottom: calc(var(--sub) * 3);
}

#mf-subtitle {
  font-family: 'PPNeueBit', monospace;
  font-weight: 700;
  font-size: calc(var(--panel-w) * 0.061);
  line-height: 0.94;
  text-align: center;
  margin-bottom: calc(var(--sub) * 11);
}

#mf-body {
  font-family: 'PPNeueBit', monospace;
  font-weight: 700;
  /* The -2px keeps the reduction constant at every width. */
  font-size: calc(var(--panel-w) * 0.038 - 2px);
  line-height: 1.04;
}

/* --- explorer --- */

#explorer {
  border: var(--sub) solid #fff;
}

#legend {
  margin: calc(var(--sub) * 2);
  border: var(--sub) solid #fff;
  padding: calc(var(--sub) * 3);
}

#legend-title {
  font-family: 'PPMondwest', serif;
  font-size: calc(var(--panel-w) * 0.115);
  line-height: 0.9;
  margin-bottom: calc(var(--sub) * 3);
}

/* Micro5 is a pixel face with a small cap height inside its em; it needs to
   run large to read at all. Everything in the legend below the title uses it. */
#legend, #legend-hex, #meter, #legend-tallies {
  font-family: 'Micro5', monospace;
  font-size: calc(var(--panel-w) * 0.046);
  line-height: 1;
  letter-spacing: 0.06em;
}

/* Colectivo <-> Individuo meter: a solid white bar with the labels knocked
   out in the background colour, and a marker tracking the ratio inside the
   seeker. */
#meter {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #fff;
  color: var(--field-bg);
  padding: 0 calc(var(--sub) * 0.75);
  height: calc(var(--sub) * 6);
}

#meter-track {
  position: relative;
  height: calc(var(--sub) * 5);
}

#meter-mark {
  position: absolute;
  top: calc(var(--sub) * 0.5);
  /* --meter-t is 0 at Colectivo, 1 at Individuo. */
  left: calc(var(--meter-t, 0.5) * 100%);
  width: calc(var(--sub) * 2);
  height: calc(var(--sub) * 2);
  margin-left: calc(var(--sub) * -1);
  background: #fff;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  font-size: 0;
}

#legend-hex {
  margin-bottom: calc(var(--sub) * 5);
  text-transform: uppercase;
}

#legend-tallies {
  display: grid;
  grid-template-columns: auto 1fr auto 1fr;
  align-items: center;
  column-gap: calc(var(--sub) * 2);
  row-gap: calc(var(--sub) * 3.5);
  padding-left: calc(var(--sub) * 3);
}
#legend-tallies span[data-kind] { white-space: nowrap; }
#legend-tallies .glyph {
  width: 0.75em;
  height: 0.75em;
  display: block;
}

/* --- one explanation per tile kind --- */

#entries { padding: calc(var(--sub) * 3) calc(var(--sub) * 3) calc(var(--sub) * 5); }

.entry { margin-bottom: calc(var(--sub) * 11); }
.entry:last-child { margin-bottom: 0; }

.entry h2 {
  display: inline-block;
  font-family: 'PPMondwest', serif;
  font-weight: 400;
  font-size: calc(var(--panel-w) * 0.078);
  line-height: 1;
  /* Bound to its own body: the gap here must stay well under the 11 subtiles
     that separate one entry from the next. */
  margin-bottom: calc(var(--sub) * 3);
}

.entry p {
  font-family: 'PPNeueBit', monospace;
  font-weight: 700;
  font-size: calc(var(--panel-w) * 0.036);
  line-height: 1.12;
  text-align: justify;
}

/* ------------------------------------------------------------ dev chrome --- */

#hud {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 3;
  padding: 10px 12px;
  min-width: 260px;
  background: rgba(8, 10, 14, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  backdrop-filter: blur(8px);
  color: var(--hud);
  pointer-events: none;
  white-space: nowrap;
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
#hud[hidden] { display: none; }
#hud .row + .row { margin-top: 2px; }
#hud b { color: #fff; font-weight: 600; }
#hud .dim { opacity: 0.45; }
#hud .live { color: #6ee7a8; }
#hud .connecting { color: #f5d06f; }
#hud .offline { color: #f57b7b; }
#hud .swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  vertical-align: -1px;
}

#banner {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--field-w);
  height: 100%;
  z-index: 3;
  display: grid;
  place-items: center;
  pointer-events: none;
  opacity: 0.5;
}
#banner[hidden] { display: none; }

/* ------------------------------------------------------------ the drawer --- */
/* Phone only. A bottom sheet holding the explorer, opened by tapping a block
   of tiles. Hidden entirely on desktop, where the explorer lives in #panel. */

#drawer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  max-height: 74vh;
  background: var(--field-bg);
  border-top: var(--sub) solid #fff;
  transform: translateY(100%);
  transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
  will-change: transform;
}
#drawer[hidden] { display: none; }
#drawer.open { transform: translateY(0); }
#drawer.dragging { transition: none; }

/* The head is the drag handle: touch-action none so the browser does not
   claim the gesture for scrolling. */
#drawer-head {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(var(--sub) * 9);
  touch-action: none;
  cursor: grab;
}
#drawer-head:active { cursor: grabbing; }

#drawer-grip {
  width: calc(var(--sub) * 14);
  height: calc(var(--sub) * 1.5);
  background: #fff;
}

#drawer-close {
  position: absolute;
  top: 50%;
  right: calc(var(--sub) * 3);
  transform: translateY(-50%);
  width: calc(var(--sub) * 6);
  height: calc(var(--sub) * 6);
  padding: calc(var(--sub) * 1);
  background: none;
  border: none;
  color: #fff;
  cursor: pointer;
  touch-action: manipulation;
}
#drawer-close svg { width: 100%; height: 100%; display: block; }

#drawer-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  padding: 0 calc(var(--sub) * 3) calc(var(--sub) * 8);
  -webkit-font-smoothing: none;
}
#drawer-body::-webkit-scrollbar { width: 0; height: 0; }

/* ------------------------------------------------------------- phone --- */

@media (max-width: 820px) {
  /* One column: the manifesto scrolls above an endless field. */
  #panel { display: none; }
  #scroller { width: 100%; }
  #hud { display: none; }
  #crtfx { display: none; }   /* a development tool, like the HUD */

  /* The manifesto is moved into #scroller by main.js and scrolls with it. */
  #manifiesto {
    position: relative;
    z-index: 1;
    padding: calc(var(--sub) * 8) calc(var(--sub) * 4) calc(var(--sub) * 10);
    margin-bottom: 0;
  }

  #mf-title    { font-size: calc(var(--panel-w) * 0.125); }
  #mf-subtitle { font-size: calc(var(--panel-w) * 0.070); }
  #mf-body     { font-size: calc(var(--panel-w) * 0.046 - 2px); }

  #legend-title { font-size: calc(var(--panel-w) * 0.135); }
  #legend, #legend-hex, #meter, #legend-tallies {
    font-size: calc(var(--panel-w) * 0.055);
  }
  .entry h2 { font-size: calc(var(--panel-w) * 0.092); }
  .entry p  { font-size: calc(var(--panel-w) * 0.044); }

  /* In the drawer the explorer is already framed by the sheet's own edge. */
  #explorer { border: none; }
  #legend { margin: 0 0 calc(var(--sub) * 4); }

  /* One tally per line: a two-up row wraps at this width. */
  #legend-tallies {
    grid-template-columns: auto 1fr;
    padding-left: calc(var(--sub) * 2);
    row-gap: calc(var(--sub) * 3);
  }
}

/* ---------------------------------------------------------- the preamble --- */
/* Holding page on arrival: the Mechanical Turk over the ramp's red end, with
   the notice set in PPMondwest. Sits above everything and blocks interaction
   until it lifts; `display:none` afterwards so it can never intercept. */

#preamble {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  /* The page's own colour, which the server's corpus ratio picks. The art
     above it is ink over transparency, so this is what shows through. */
  background: var(--field-bg);
  padding: calc(var(--sub) * 4) calc(var(--sub) * 4) calc(var(--sub) * 3);
  -webkit-font-smoothing: none;
  opacity: 1;
  transition: opacity 600ms ease;
}
#preamble.leaving { opacity: 0; }
#preamble[hidden] { display: none; }

/* The animated backdrop. Absolute, so it sits under the flex children without
   taking part in the layout. Cover-cropped: these are atmosphere, not
   composition, and the solid red behind matches their field exactly. */
#preamble-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
#preamble-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  image-rendering: pixelated;
}

/* <picture> is the flex item; the <img> inside fits within it. */
#preamble-art {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  justify-content: center;
}
#preamble-art img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  /* 1-bit dithered art: never let the browser smooth it. */
  image-rendering: pixelated;
}

#preamble-text {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  margin-top: calc(var(--sub) * 3);
  max-width: 116ch;
  font-family: 'PPMondwest', serif;
  font-weight: 400;
  color: #fff;
  text-align: center;
  font-size: clamp(13px, 1.65vw, 42px);
  line-height: 1.16;
}

@media (max-width: 820px) {
  /* Keep the desktop composition — art filling the space above, notice on the
     baseline. Centring the pair instead leaves a dead band under the text. */
  #preamble { padding: calc(var(--sub) * 6) calc(var(--sub) * 4) calc(var(--sub) * 6); }
  #preamble-text { font-size: clamp(15px, 3.9vw, 30px); }
}

/* --------------------------------------------------------- presence panel --- */
/* Controls for the tile presence field. Press "p" to show or to hide them. */

#pattern,
#crtfx {
  position: fixed;
  right: 12px;
  top: 12px;
  z-index: 4;
  width: 268px;
  max-height: calc(100vh - 24px);
  overflow-y: auto;
  padding: 10px 12px 12px;
  background: rgba(8, 10, 14, 0.86);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 6px;
  backdrop-filter: blur(8px);
  color: var(--hud);
  font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
  scrollbar-width: none;
}
#pattern[hidden],
#crtfx[hidden] { display: none; }
#pattern::-webkit-scrollbar,
#crtfx::-webkit-scrollbar { width: 0; }

#pattern h3,
#crtfx h3 {
  margin-bottom: 8px;
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
#pattern .row,
#crtfx .row {
  display: grid;
  grid-template-columns: 84px 1fr 42px;
  align-items: center;
  gap: 6px;
  margin-bottom: 5px;
}
#pattern label,
#crtfx label { opacity: 0.75; }
#pattern .val,
#crtfx .val { color: #fff; text-align: right; font-variant-numeric: tabular-nums; }
#pattern input[type="range"],
#crtfx input[type="range"] { width: 100%; accent-color: #6ee7a8; }
#pattern select,
#pattern input[type="number"],
#crtfx select,
#crtfx input[type="number"] {
  grid-column: 2 / span 2;
  width: 100%;
  padding: 2px 4px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 3px;
  color: #fff;
  font: inherit;
}
#pattern input[type="checkbox"],
#crtfx input[type="checkbox"] { accent-color: #6ee7a8; justify-self: start; }

#pattern .ink,
#crtfx .ink { margin: 8px 0 6px; color: #fff; }
#pattern .actions,
#crtfx .actions { display: flex; gap: 6px; }
#pattern button,
#crtfx button {
  flex: 1;
  padding: 4px 6px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 3px;
  color: #fff;
  font: inherit;
  cursor: pointer;
}
#pattern button:hover,
#crtfx button:hover { background: rgba(255, 255, 255, 0.18); }
#pattern .hint,
#crtfx .hint { margin-top: 6px; opacity: 0.45; }

/* Two panels, so they cannot cover each other. The tube's names are longer
   than the presence field's, so its label column is wider. */
#crtfx { right: auto; left: 12px; }
#crtfx .row { grid-template-columns: 108px 1fr 42px; }

/* ------------------------------------------------------------- crt veil --- */
/* The light of the tube: everything that is not the curve. It sits above the
   whole page, the holding page included — that is the first thing anybody
   sees, so it is the first thing that has to be inside the tube. It never
   takes a pointer event, so nothing about the page's interaction changes when
   it is on, and elementFromPoint looks straight through it.

   Each layer is one gradient and one blend mode. Nothing here rasterizes the
   page, so nothing goes stale when it scrolls. */

#crt {
  position: fixed;
  inset: 0;
  z-index: 11;   /* above #preamble (10): the holding page is lit too */
  pointer-events: none;
  /* The pointer mask, in CSS px, and how far it is open. */
  --crt-x: 50vw;
  --crt-y: 50vh;
  --crt-r: 0px;
  --crt-mask: 0;
}
#crt[hidden] { display: none; }
#crt > div {
  position: absolute;
  inset: 0;
}

/* Scanlines. A tube's lines fall on the whole picture, so this multiplies the
   composited page — the tiles, the backdrop and the text panel together. */
.crt-lines {
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, var(--crt-scan)) 0,
    rgba(0, 0, 0, var(--crt-scan)) calc(var(--crt-scan-px) / 2),
    transparent calc(var(--crt-scan-px) / 2),
    transparent var(--crt-scan-px));
  mix-blend-mode: multiply;
}

/* Phosphor bloom, plus the disc the pointer opens. The pointer term is what
   makes the mask visible on a still page: the tube brightens where the cursor
   is and settles back when it leaves. */
.crt-glow {
  background:
    radial-gradient(circle var(--crt-r) at var(--crt-x) var(--crt-y),
      rgba(255, 255, 255, calc(var(--crt-mask) * 0.10)) 0%,
      transparent 100%),
    radial-gradient(ellipse at center,
      rgba(255, 255, 255, var(--crt-glow)) 0%,
      transparent 70%);
  mix-blend-mode: screen;
}

.crt-vignette {
  background: radial-gradient(ellipse at center,
    transparent 45%,
    rgba(0, 0, 0, var(--crt-vig)) 100%);
  mix-blend-mode: multiply;
}

/* Mains hum. The opacity is written per frame from a real clock, so it runs
   at the same speed on a 60 Hz and a 120 Hz display. */
.crt-flicker {
  background: #fff;
  opacity: var(--crt-flicker, 0);
  mix-blend-mode: screen;
}

/* Convergence error. A real tube misconverges at the edges, so the two washes
   are transparent in the middle and grow to the corners, pushed apart by
   `aberration`. It is an approximation: a per-pixel RGB shift of the whole
   page needs a filter on an ancestor, and that would make the ancestor the
   containing block for every position:fixed layer under it. */
.crt-chroma-r,
.crt-chroma-b {
  mix-blend-mode: screen;
  opacity: var(--crt-chroma, 0);
}
.crt-chroma-r {
  background: radial-gradient(ellipse at center, transparent 55%, rgba(255, 0, 0, 0.5) 100%);
  transform: translateX(calc(var(--crt-chroma-px) * -1));
}
.crt-chroma-b {
  background: radial-gradient(ellipse at center, transparent 55%, rgba(0, 128, 255, 0.5) 100%);
  transform: translateX(var(--crt-chroma-px));
}
