@import url("assets/css/fonts.css");

/* ------------------------------------------------------------------ tokens */

:root {
  /* Dark is locked, not a preference. This is a video synth portfolio: the
     page is the unlit room the work is shown in. Colours are painted
     explicitly so nothing borrows the host theme. */
  --bg:          #07080a;
  --bg-raised:   #0e1013;
  --bg-sunken:   #030405;
  --line:        #1d2126;
  --line-bright: #2c3238;

  --ink:      #e8ece9;
  --ink-mid:  #a3aca6;
  --ink-dim:  #6d7671;

  /* One accent for the whole site. Every other colour on screen comes out of
     the artwork itself. */
  --accent:      #7de0a6;
  --accent-dim:  #3d7a5a;
  --warn:        #ffb35c;

  --display: "Archivo", ui-sans-serif, system-ui, -apple-system, "Helvetica Neue", sans-serif;
  --mono: "Fira Code", ui-monospace, SFMono-Regular, Menlo, "Courier New", monospace;

  --shell: 1360px;
  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  --rule: 1px solid var(--line);

  /* Radius is zero everywhere. Sharp corners match the pixelate stages the
     work is built on. */
  --radius: 0;
}

/* ------------------------------------------------------------------- reset */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--display);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.6;
  font-weight: 500;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, canvas, iframe, video { display: block; max-width: 100%; }
img { height: auto; }
iframe { border: 0; }

h1, h2, h3 { font-weight: 700; line-height: 1.04; letter-spacing: -0.025em; margin: 0; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }

a { color: var(--ink); text-decoration-color: var(--accent-dim); text-underline-offset: 0.25em; }
a:hover { text-decoration-color: var(--accent); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  border-radius: var(--radius);
}

/* --------------------------------------------------------------- utilities */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.mono {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--ink-dim);
}

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

/* Grain sits on a fixed layer so it never repaints while the page scrolls. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background-image: url("sources/static-tv-1.png");
  background-size: 420px;
  opacity: 0.035;
  mix-blend-mode: screen;
}

/* -------------------------------------------------------------------- nav */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: var(--rule);
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: clamp(0.5rem, 2vw, 1.75rem);
  height: 68px;
}

.nav__name {
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin-right: auto;
  text-decoration: none;
  white-space: nowrap;
}

.nav__links { display: flex; align-items: center; gap: clamp(0.5rem, 2vw, 1.5rem); }

.nav a[data-nav] {
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--ink-mid);
  text-decoration: none;
  white-space: nowrap;
  padding-block: 0.35rem;
  border-bottom: 1px solid transparent;
}
.nav a[data-nav]:hover { color: var(--ink); border-bottom-color: var(--line-bright); }
.nav a[data-nav][aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); }

/* ------------------------------------------------------------------ frames */

/* Every live canvas is an iframe: each sketch patches hydra's output
   prototype and grabs canvas[0] by tag name, so two in one document fight. */
.frame {
  position: relative;
  background: var(--bg-sunken);
  border: var(--rule);
  overflow: hidden;
}
.frame > iframe { width: 100%; height: 100%; position: absolute; inset: 0; }

.frame--wide { aspect-ratio: 16 / 9; }

/* The retune roll. Vertical hold slipping, paired with the tear the player
   runs inside hydra itself. */
.frame[data-tearing] { animation: vhold 420ms steps(9, end); }

@keyframes vhold {
  0%   { transform: translateY(0); filter: none; }
  35%  { transform: translateY(-1.5%); filter: contrast(1.35) saturate(0.6); }
  60%  { transform: translateY(2.5%); filter: contrast(1.6) saturate(0.3) brightness(1.2); }
  85%  { transform: translateY(-0.8%); filter: contrast(1.2); }
  100% { transform: translateY(0); filter: none; }
}

/* ----------------------------------------------------------------- section */

.section { padding-block: clamp(4rem, 9vw, 8rem); }
.section--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }

.section__head { max-width: 46ch; margin-bottom: clamp(2rem, 4vw, 3.25rem); }

/* Wider measure where the head carries instructions rather than a tagline. */
.section__head--wide { max-width: 76ch; }

.section__head h2 {
  font-size: clamp(1.75rem, 1.2rem + 2.4vw, 3rem);
  margin-bottom: 0.75rem;
}

.section__head p { color: var(--ink-mid); }

.eyebrow {
  font-family: var(--mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--accent);
  display: block;
  margin-bottom: 1rem;
}

/* -------------------------------------------------------------------- hero */

.hero {
  position: relative;
  min-height: min(92dvh, 860px);
  display: grid;
  isolation: isolate;
}

.hero__media { position: absolute; inset: 0; z-index: 0; border: 0; }

/* The hero is the one place the work plays second to something else, so it is
   dimmed to let the type sit on it. Everywhere else it runs at full strength. */
.hero__media iframe {
  width: 100%;
  height: 100%;
  filter: brightness(0.52) saturate(0.72) contrast(1.05);
}

/* Scrim keeps the type legible over whatever the sketch is doing. */
.hero__scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(to top, var(--bg) 4%, color-mix(in srgb, var(--bg) 82%, transparent) 34%, color-mix(in srgb, var(--bg) 30%, transparent) 66%, transparent),
    linear-gradient(to right, color-mix(in srgb, var(--bg) 88%, transparent), color-mix(in srgb, var(--bg) 35%, transparent) 46%, transparent 72%),
    linear-gradient(to bottom, color-mix(in srgb, var(--bg) 70%, transparent), transparent 22%);
}

/* Anchored bottom-left rather than centred. */
.hero__body {
  position: relative;
  z-index: 2;
  align-self: end;
  padding-block: clamp(2.5rem, 6vw, 5rem);
  max-width: 48ch;
}

.hero h1 {
  font-size: clamp(2.5rem, 1.4rem + 5.2vw, 5.25rem);
  margin-bottom: 0.85rem;
}

.hero__lede {
  color: var(--ink-mid);
  font-size: clamp(1rem, 0.95rem + 0.35vw, 1.1875rem);
  max-width: 34ch;
}

.hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.75rem; }

/* ----------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.15rem;
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid var(--line-bright);
  background: color-mix(in srgb, var(--bg-raised) 80%, transparent);
  color: var(--ink);
  transition: transform 120ms, border-color 160ms, background-color 160ms, color 160ms;
}
.btn:hover { border-color: var(--accent-dim); background: var(--bg-raised); }
.btn:active { transform: translateY(1px); }

/* Solid accent on near-black clears AA comfortably. */
.btn--primary { background: var(--accent); border-color: var(--accent); color: #04150c; font-weight: 700; }
.btn--primary:hover { background: #93e9b6; border-color: #93e9b6; color: #04150c; }

.btn[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }

/* ------------------------------------------------------------ player + dial */

/* Player and list side by side so choosing a piece never scrolls the piece
   out of view. Stacks under 900px. */
.stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(17rem, 23vw, 22rem);
  height: min(74dvh, 780px);
  border-block: var(--rule);
}

.stage__player { position: relative; min-width: 0; background: var(--bg-sunken); }
.stage__player iframe { position: absolute; inset: 0; width: 100%; height: 100%; }

/* Fallback fullscreen for browsers without Element.requestFullscreen, which
   on iPhone is all of them. */
.stage__player.is-overlaid {
  position: fixed;
  inset: 0;
  z-index: 80;
  aspect-ratio: auto;
  border: 0;
}

html.has-overlay, html.has-overlay body { overflow: hidden; }

/* Native fullscreen paints its own backdrop; the overlay needs one. */
.stage__player.is-overlaid { background: var(--bg); }

.fs-exit {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 2;
  display: none;
}

.stage__player.is-overlaid .fs-exit,
.stage__player:fullscreen .fs-exit { display: inline-flex; }

/* Vertical, scrolling in its own box. */
.dial {
  position: relative;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-left: var(--rule);
  scrollbar-width: thin;
  scrollbar-color: var(--line-bright) transparent;
}
.dial::-webkit-scrollbar { width: 4px; }
.dial::-webkit-scrollbar-thumb { background: var(--line-bright); }
.dial:focus-visible { outline-offset: -2px; }

.station {
  display: grid;
  grid-template-columns: 5.25rem minmax(0, 1fr);
  gap: 0.75rem;
  width: 100%;
  text-align: left;
  padding: 0.7rem 0.85rem;
  border-bottom: var(--rule);
  color: var(--ink-mid);
  transition: background-color 160ms, color 160ms;
}
.station:hover { background: var(--bg-raised); color: var(--ink); }

.station[aria-current="true"] {
  background: var(--bg-raised);
  color: var(--ink);
  box-shadow: inset 3px 0 0 var(--accent);
}

.station__thumb {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--bg-sunken);
}

.station__meta { display: grid; gap: 0.15rem; min-width: 0; }
.station__no { font-family: var(--mono); font-size: 0.625rem; color: var(--accent); letter-spacing: 0.1em; }
.station__title { font-weight: 700; font-size: 0.875rem; color: var(--ink); letter-spacing: -0.01em; }

.station__blurb {
  font-size: 0.75rem;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.station .badge { justify-self: start; margin-top: 0.2rem; }

.player__bar {
  display: flex;
  align-items: center;
  gap: 0.5rem 0.9rem;
  flex-wrap: wrap;
  padding-block: 0.9rem;
  border-bottom: var(--rule);
}

.player__now { margin-right: auto; min-width: 0; }
.player__title { font-size: 1.0625rem; font-weight: 700; letter-spacing: -0.01em; }
.player__blurb { color: var(--ink-mid); font-size: 0.875rem; }

.player__controls { display: flex; gap: 0.4rem; flex-wrap: wrap; }

.badge {
  font-family: var(--mono);
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding: 0.2rem 0.45rem;
  border: 1px solid var(--line-bright);
  color: var(--ink-dim);
}
.badge--warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }

@media (max-width: 900px) {
  /* Dissolve the stage grid so the player, the controls and the list become
     siblings of one column and can be reordered. Controls move directly under
     the player: reaching them below the list meant scrolling the piece away. */
  [data-gallery] { display: flex; flex-direction: column; }
  .stage { display: contents; }

  .stage__player {
    order: 1;
    aspect-ratio: 4 / 3;
    border-block: var(--rule);
  }

  [data-gallery] > .shell { order: 2; }

  .dial {
    order: 3;
    border-left: 0;
    border-top: var(--rule);
    max-height: 46dvh;
  }

  /* The dial supplies the rule under the controls. */
  .player__bar { border-bottom: 0; }
}

/* ------------------------------------------------------------------- lists */

/* Archive rows. One hairline between rows, never top and bottom on each. */
.rows { border-top: var(--rule); }

.row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.5rem 1.5rem;
  align-items: baseline;
  padding-block: 1.05rem;
  border-bottom: var(--rule);
}

.row__name { font-weight: 700; letter-spacing: -0.01em; text-decoration: none; }
.row__name:hover { color: var(--accent); }
.row__note { grid-column: 1 / -1; color: var(--ink-dim); font-size: 0.875rem; margin-top: -0.35rem; }
.row__actions { display: flex; gap: 0.4rem; }

@media (max-width: 560px) {
  .row { grid-template-columns: 1fr; }
  .row__actions { grid-column: 1; }
}

/* ------------------------------------------------------------------- about */

.split {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.split__prose { display: grid; gap: 1.15rem; max-width: 58ch; color: var(--ink-mid); }
.split__prose strong { color: var(--ink); font-weight: 700; }

.split__figure figcaption { margin-top: 0.75rem; }

/* About stacks: a readable prose column, then the photograph full width. The
   subject sits low in the frame, so the crop is weighted downward. */
/* Centred column rather than full bleed: at this shell width an unconstrained
   line would run to about 150 characters, roughly double a readable measure. */
.about__intro {
  max-width: 64ch;
  margin-inline: auto;
}

.about__prose {
  display: grid;
  gap: 1.15rem;
  color: var(--ink-mid);
}
.about__prose strong { color: var(--ink); font-weight: 700; }

.about__figure { margin: clamp(2.25rem, 4vw, 3.5rem) 0 0; }

.about__figure img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center 70%;
  border: var(--rule);
}

@media (max-width: 640px) {
  /* Give the wall more presence on a phone than a letterbox strip would. */
  .about__figure img { aspect-ratio: 4 / 3; }
}

@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------------ footer */

.footer {
  border-top: var(--rule);
  padding-block: clamp(2.5rem, 5vw, 4rem);
}

.footer__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 2rem;
}

.footer h2 { font-size: 1.5rem; margin-bottom: 0.75rem; }

.linklist { display: grid; gap: 0.55rem; }
.linklist a { font-family: var(--mono); font-size: 0.8125rem; color: var(--ink-mid); text-decoration: none; }
.linklist a:hover { color: var(--accent); }

.colophon { margin-top: 2.5rem; color: var(--ink-dim); font-size: 0.75rem; font-family: var(--mono); }

/* Explanatory copy under the dial. Long enough that mono would be a wall. */
.note {
  color: var(--ink-mid);
  font-size: 0.875rem;
  line-height: 1.6;
  max-width: 64ch;
}

/* ------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .frame[data-tearing] { animation: none; }
}
