@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');

/* ── Variables ────────────────────────────────────────────────── */
:root {
  /* Terminal palette — amber CRT (WYSE-style) */
  --crt-bg:      #000000;
  --crt-fg:      #ffb000;
  --crt-dim:     #7a4a00;
  --crt-hi:      #ffd060;
  --crt-pale:    #ffd9a3;

  /* Section palette */
  --bg:          #090807;
  --bg-card:     #0e0d0c;
  --fg:          #e2ddd7;
  --fg-dim:      #555;
  --fg-muted:    #2a2a2a;
  --hot-pink:    #ff0055;
  --lime:        #c8ff00;
  --cyan:        #00eeff;
  --scan-green:  #ffa000;

  --frame-line:  #1e1c1a;
  --frame-corner: rgba(255,176,0,0.85);

  --mono:   'VT323', 'Courier New', monospace;
  --body:   'VT323', 'Courier New', monospace;

  --nav-h:     58px;
  --ticker-h:  28px;
  --pad:       clamp(1.5rem, 4vw, 4rem);
}

/* ── Reset ────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* scrollbar-gutter reserves the scrollbar's space up front, always — the
   justified painting gallery (painting.js) measures its container's
   clientWidth to lay out rows, and without this that measurement would be
   taken before the page's own content makes it tall enough to trigger a
   real scrollbar, then be too wide by a scrollbar's width once it appears. */
html { scroll-behavior: smooth; scrollbar-gutter: stable; }
body { overflow-x: hidden; }
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }

/* ══════════════════════════════════════════════════════════════
   TERMINAL PAGE  (body.terminal)
══════════════════════════════════════════════════════════════ */

body.terminal {
  background: var(--crt-bg);
  color: var(--crt-fg);
  font-family: var(--mono);
  font-size: clamp(15px, 1.6vw, 18px);
  line-height: 1.35;
  height: 100vh;
  overflow: hidden;
}

/* CRT scanlines */
body.terminal::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9000;
  background: repeating-linear-gradient(
    transparent 0px, transparent 2px,
    rgba(255,176,0,0.03) 2px, rgba(255,176,0,0.03) 4px
  );
  animation: crt-flicker 8s step-start infinite;
}

@keyframes crt-flicker {
  0%, 95%, 100% { opacity: 1; }
  96%           { opacity: 0.92; }
  97%           { opacity: 1; }
  98%           { opacity: 0.96; }
}

/* CRT vignette */
body.terminal::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 8999;
  background: radial-gradient(ellipse at center, transparent 60%, rgba(0,0,0,0.55) 100%);
}

.terminal-screen {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: clamp(1rem, 3vw, 2rem) clamp(1.5rem, 5vw, 5rem);
  max-width: 900px;
  box-sizing: border-box;
}

/* Boot log — sizes to content, scrolls if it grows beyond cap. min-height
   matches max-height so this reserves the same space whether it's full
   (first-visit boot) or holds just one line (return-visit echo) — that's
   what keeps AUGUST QUINN landing at the same spot in both cases, purely
   via CSS, with no per-state JS. */
#output {
  flex-shrink: 0;
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 37vh;
  max-height: 37vh;
  scrollbar-width: none;
  font-family: var(--mono);
}
#output::-webkit-scrollbar { display: none; }

/* Menu and prompt — pinned below the log, never pushed offscreen */
.terminal-menu { flex-shrink: 0; font-family: var(--mono); }
#prompt        { flex-shrink: 0; margin: 0; font-family: var(--mono); }

.terminal-line {
  display: block;
  color: var(--crt-fg);
  text-shadow: 0 0 6px rgba(255,176,0,0.5);
  white-space: pre;
}

.terminal-line.dim   { color: var(--crt-dim); text-shadow: none; }
.terminal-line.hi    { color: var(--crt-hi); text-shadow: 0 0 10px rgba(255,208,96,0.7); }
.terminal-line.blank { height: 1.35em; }

/* ── Glitch / signal-dropout animations ───────────────────────── */

/* For the terminal title — soft amber glow flicker */
@keyframes glitch-crt {
  0%, 86%, 100% { text-shadow: 0 0 24px rgba(255,208,96,0.6); transform: none; opacity: 1; }
  87%  { text-shadow: -3px 0 rgba(255,160,0,0.9), 3px 0 rgba(255,208,96,0.5); transform: translateX(2px); opacity: 0.85; }
  88%  { text-shadow: 0 0 24px rgba(255,208,96,0.6); transform: none; opacity: 1; }
  89%  { text-shadow: 2px 0 rgba(255,160,0,0.7), -2px 0 rgba(255,208,96,0.9); transform: translateX(-1px); opacity: 0.9; }
  90%  { text-shadow: 0 0 24px rgba(255,208,96,0.6); transform: none; opacity: 1; }
  91%  { text-shadow: -1px 0 rgba(255,160,0,0.5), 1px 0 rgba(255,208,96,0.5); transform: translateX(1px); opacity: 0.95; }
  92%  { text-shadow: 0 0 24px rgba(255,208,96,0.6); transform: none; opacity: 1; }
}

/* For section titles — no base glow, just the split appears and vanishes */
@keyframes glitch-title {
  0%, 86%, 100% { text-shadow: none; transform: none; }
  87%  { text-shadow: -4px 0 rgba(255,0,85,0.65), 4px 0 rgba(0,238,255,0.65); transform: translateX(3px); }
  88%  { text-shadow: 3px 0 rgba(255,0,85,0.65), -3px 0 rgba(0,238,255,0.65); transform: translateX(-2px); }
  89%  { text-shadow: none; transform: none; }
  90%  { text-shadow: -2px 0 rgba(255,0,85,0.4), 2px 0 rgba(0,238,255,0.4); transform: translateX(2px); }
  91%  { text-shadow: none; transform: none; }
}

/* Big arrival title inside the terminal */
.terminal-title {
  display: block;
  font-family: var(--mono);
  font-size: clamp(4.5rem, 10vw, 8rem);
  line-height: 1;
  letter-spacing: 0.04em;
  color: var(--crt-hi);
  text-shadow: 0 0 24px rgba(255,208,96,0.6);
  margin: 0.15rem 0 1rem;
  animation: glitch-crt 7s step-end infinite;
}

.terminal-line.progress-bar { letter-spacing: 0.02em; }

/* Menu */
.terminal-menu {
  margin-top: 0.3em;
}

.terminal-menu-item {
  display: block;
  cursor: pointer;
  color: var(--crt-fg);
  text-shadow: 0 0 6px rgba(255,176,0,0.5);
  white-space: pre;
  transition: background 0.05s, color 0.05s;
  padding: 0.05em 0;
  user-select: none;
}

.terminal-menu-item:hover,
.terminal-menu-item:focus {
  background: var(--crt-fg);
  color: var(--crt-bg);
  text-shadow: none;
  outline: none;
}

.terminal-separator {
  color: var(--crt-dim);
  text-shadow: none;
  white-space: pre;
  display: block;
  width: 100%;
  overflow: hidden;
}

/* Blinking block cursor */
.blink-cursor {
  display: inline-block;
  width: 0.55em;
  height: 1em;
  background: var(--crt-fg);
  vertical-align: text-bottom;
  animation: cursor-blink 0.85s step-start infinite;
  box-shadow: 0 0 8px rgba(255,176,0,0.7);
}

@keyframes cursor-blink {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0; }
}

/* ── Terminal art (dithered CRT image reveal) ─────────────────── */
.terminal-art {
  opacity: 0;
  pointer-events: none;
  transition: opacity 1.2s ease;
  z-index: 9500;
}

.terminal-art.visible { opacity: 1; }

.terminal-art canvas {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

/* Wide viewports: a fixed side panel filling the empty space right of the
   900px terminal column. top:48% is a single static value approximating
   the AUGUST QUINN title band once the boot log has hit its 37vh cap (see
   #output) — not dynamically measured, so it renders identically and
   immediately regardless of which path (full boot or instant return) led
   here, with no JS and no post-load movement. */
@media (min-width: 1201px) {
  .terminal-art {
    position: fixed;
    top: 48%;
    left: 960px;
    width: min(380px, calc(100vw - 1000px));
    transform: translateY(-50%);
  }
}

/* Narrow viewports: no room beside the terminal column, so stack the art
   below it in normal flow instead of hiding it. The terminal page is
   normally a fixed, non-scrolling 100vh screen (body.terminal has
   overflow:hidden) — that has to give here so the stacked image is actually
   reachable by scrolling. .terminal-screen deliberately has no min-height
   here (unlike body) — it must hug its actual content so the art sits right
   after the menu instead of always being pushed a full screen down. */
@media (max-width: 1200px) {
  body.terminal {
    height: auto;
    min-height: 100vh;
    overflow-y: auto;
    overflow-x: hidden;
  }
  .terminal-screen {
    height: auto;
  }
  .terminal-art {
    position: static;
    width: min(360px, 82vw);
    margin: 2.5rem auto 3.5rem;
  }
}

/* Skip hint */
.terminal-skip {
  position: fixed;
  bottom: 1.5rem;
  right: 2rem;
  font-size: 0.65em;
  color: var(--crt-dim);
  letter-spacing: 0.12em;
  opacity: 0.6;
  cursor: pointer;
  transition: opacity 0.2s;
}
.terminal-skip:hover { opacity: 1; color: var(--crt-fg); }

/* ══════════════════════════════════════════════════════════════
   SECTION PAGES  (body.section)
══════════════════════════════════════════════════════════════ */

body.section {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--body);
  line-height: 1.5;
}

/* Subtle scanlines on section pages too */
body.section::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9998;
  background: repeating-linear-gradient(
    transparent 0px, transparent 3px,
    rgba(0,0,0,0.04) 3px, rgba(0,0,0,0.04) 4px
  );
}

/* ── Top nav ──────────────────────────────────────────────────── */
/* Scrolls with the page like everything else — not pinned to the
   viewport top. */
.section-nav {
  height: var(--nav-h);
  background: rgba(8,8,8,0.97);
  border-bottom: 1px solid var(--frame-line);
  display: flex;
  align-items: center;
  font-family: var(--mono);
  font-size: 0.95rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0;
  gap: 0;
  overflow: hidden;
}

.section-nav .nav-home {
  color: var(--scan-green);
  padding: 0 1.4rem;
  height: 100%;
  display: flex;
  align-items: center;
  border-right: 1px solid var(--frame-line);
  flex-shrink: 0;
  transition: background 0.15s;
}

.section-nav .nav-home:hover { background: rgba(255,160,0,0.07); }

.section-nav .nav-links {
  display: flex;
  align-items: center;
  height: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

.section-nav .nav-links::-webkit-scrollbar { display: none; }

.section-nav .nav-links a {
  height: 100%;
  display: flex;
  align-items: center;
  padding: 0 1.1rem;
  color: var(--fg-dim);
  border-right: 1px solid var(--frame-line);
  transition: color 0.15s, background 0.15s;
  white-space: nowrap;
}

.section-nav .nav-links a:hover { color: var(--crt-fg); background: rgba(255,176,0,0.05); }

.section-nav .nav-links a.active {
  color: var(--crt-fg);
  background: rgba(255,176,0,0.1);
  border-bottom: 2px solid var(--crt-fg);
}

/* ── Page layout ──────────────────────────────────────────────── */
.page-content {
  padding-top: clamp(1.5rem, 3vw, 2.5rem);
  padding-left: clamp(2rem, 6vw, 7rem);
  padding-right: clamp(2rem, 6vw, 7rem);
  padding-bottom: 3rem;
  min-height: 100vh;
}

/* ── Section title ────────────────────────────────────────────── */
.chrome-title {
  font-family: var(--mono);
  color: var(--crt-hi);
  text-shadow: 0 0 24px rgba(255,208,96,0.6);
  text-transform: uppercase;
  line-height: 1;
  animation: glitch-crt 6s step-end infinite;
  animation-delay: -1.8s;
}

/* ── Page wordmark (arrival title) ───────────────────────────── */
.page-wordmark-wrap {
  margin-bottom: clamp(3rem, 6vw, 6rem);
  padding-bottom: 2rem;
  border-bottom: 1px solid var(--frame-line);
}

.page-wordmark {
  font-family: var(--mono);
  font-size: clamp(4rem, 10vw, 10rem);
  color: var(--fg);
  letter-spacing: -0.03em;
  line-height: 0.95;
  text-transform: uppercase;
  animation: glitch-title 8s step-end infinite;
  animation-delay: -3.2s;
}

.page-wordmark-sub {
  font-family: var(--mono);
  font-size: clamp(0.65rem, 1.2vw, 0.8rem);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--scan-green);
  margin-top: 1rem;
}

/* ── File frame (card with bracket corners) ───────────────────── */
.file-frame {
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--frame-line);
  overflow: visible;
}

.file-frame::before {
  content: '';
  position: absolute;
  inset: -3px;
  pointer-events: none;
  z-index: 2;
  transition: inset 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  background:
    linear-gradient(var(--frame-corner), var(--frame-corner)) 0    0    / 2px 22px no-repeat,
    linear-gradient(var(--frame-corner), var(--frame-corner)) 0    0    / 22px 2px no-repeat,
    linear-gradient(var(--frame-corner), var(--frame-corner)) 100% 0    / 2px 22px no-repeat,
    linear-gradient(var(--frame-corner), var(--frame-corner)) 100% 0    / 22px 2px no-repeat,
    linear-gradient(var(--frame-corner), var(--frame-corner)) 0    100% / 2px 22px no-repeat,
    linear-gradient(var(--frame-corner), var(--frame-corner)) 0    100% / 22px 2px no-repeat,
    linear-gradient(var(--frame-corner), var(--frame-corner)) 100% 100% / 2px 22px no-repeat,
    linear-gradient(var(--frame-corner), var(--frame-corner)) 100% 100% / 22px 2px no-repeat;
}

/* Focus-lock: corners spring outward like a camera viewfinder locking on,
   entirely independent of the (much subtler) image zoom below. Painting
   cards don't use .file-frame at all — see .painting-card below, which
   keeps only the image-zoom affordance with no frame/border. */
.file-frame:hover::before,
.file-frame:focus-within::before {
  inset: -9px;
}

.file-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.file-frame:hover img { transform: scale(1.015); }

.file-frame-placeholder {
  width: 100%;
  min-height: 240px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 0.4rem;
  background: #0b0b0b;
  font-family: var(--mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #202020;
}

/* ── File metadata ────────────────────────────────────────────── */
.file-meta {
  padding: 0.7rem 0.75rem 0.6rem;
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.06em;
  line-height: 1.65;
  color: var(--fg-dim);
  border-top: 1px solid var(--frame-line);
  background: var(--bg-card);
}

.file-meta .file-name {
  color: var(--scan-green);
  display: block;
  margin-bottom: 0.2rem;
  font-size: 0.52rem;
  letter-spacing: 0.1em;
}

.file-meta .file-title {
  color: var(--crt-hi);
  font-size: 0.7rem;
  display: block;
  margin-bottom: 0.12rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.file-meta .file-line { display: block; }

.file-meta .file-status {
  display: inline-block;
  margin-top: 0.4rem;
  padding: 0.1rem 0.45rem;
  border: 1px solid rgba(255,0,85,0.5);
  color: var(--hot-pink);
  font-size: 0.5rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* ── Section header ───────────────────────────────────────────── */
.section-header {
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--frame-line);
}

.section-eyebrow {
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--scan-green);
  margin-bottom: 0.75rem;
  min-height: 1em;
}

.section-eyebrow.typing::after {
  content: '';
  display: inline-block;
  width: 0.5em;
  height: 0.85em;
  margin-left: 0.15em;
  background: var(--scan-green);
  vertical-align: text-bottom;
  animation: cursor-blink 0.85s step-start infinite;
}

.section-title {
  font-size: clamp(1.8rem, 4vw, 3.25rem);
  letter-spacing: -0.01em;
  line-height: 1;
}

/* ── Section body (gated behind the command-echo typing) ─────────── */
.section-body {
  opacity: 0;
  transition: opacity 0.5s ease;
}
.section-body.ready { opacity: 1; }

/* ── Year index (DOS DIR-style listing) — shared by painting.js and
   video3d.js, anything organized reverse-chronologically by year ──── */
.year-nav {
  display: flex;
  flex-direction: column;
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
  font-family: var(--mono);
  font-size: 0.9rem;
  letter-spacing: 0.06em;
}

.year-row {
  display: block;
  width: fit-content;
  min-width: 16ch;
  cursor: pointer;
  color: var(--crt-fg);
  white-space: pre;
  padding: 0.15em 0.35em;
  user-select: none;
  transition: background 0.05s, color 0.05s;
}

/* Same reverse-video amber treatment as the homepage menu */
.year-row:hover,
.year-row:focus {
  background: var(--crt-fg);
  color: var(--crt-bg);
  outline: none;
}

/* align-self:start matters specifically for the masonry grid (painting.js
   sets an explicit grid-row-end there — without it, the divider gets
   clamped to a single 4px auto-row and its real content overlaps whatever
   comes after it). Harmless on a plain auto-sized grid (video3d.js). */
.year-divider {
  grid-column: 1 / -1;
  align-self: start;
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 1rem 0 1.5rem;
  padding-bottom: 0.4rem;
  font-family: var(--mono);
  font-size: 1.1rem;
  letter-spacing: 0.2em;
  color: var(--crt-fg);
  text-shadow: 0 0 6px rgba(255,176,0,0.5);
}
.year-divider::after {
  content: '';
  flex: 1;
  border-bottom: 1px solid var(--crt-dim);
}
.year-divider:first-child { margin-top: 0; }

/* ── Work grid (video/placeholder pages — fixed-height cards) ─────── */
.work-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.5rem;
}

/* ── Paintings grid: justified/row-based gallery (Flickr/Google Photos
   style), computed per year-section in painting.js. Each row's images are
   scaled to a shared height solved so the row's total width fills the
   container exactly — unlike a column-based masonry, there's no per-
   column leftover space, since nothing is ever assigned to a column in
   the first place. Card width and image height are set inline per-card
   by JS; everything here is just the row/card scaffolding, and the whole
   thing rebuilds on resize since row membership itself changes with
   container width. */
.painting-grid {
  display: flex;
  flex-direction: column;
}

.justified-row {
  display: flex;
  gap: 1.5rem;
  margin-bottom: 2.5rem;
}

.painting-card {
  display: block;
  flex: none;
  cursor: pointer;
  /* Lifts the artwork above body.section::after's fixed CRT scanline
     overlay (z-index:9998) — that texture is meant for the page chrome,
     not laid over the work itself. */
  position: relative;
  z-index: 9999;
}

/* No frame, no border, no background — full-bleed artwork, only a subtle
   zoom on hover to signal it's clickable */
.painting-card img {
  width: 100%;
  display: block;
  object-fit: cover;
  transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.painting-card:hover img { transform: scale(1.015); }

@media (max-width: 560px) { .justified-row { gap: 0.6rem; margin-bottom: 1.75rem; } }

/* One-line metadata, larger and more legible than the multi-line
   file-directory format used elsewhere on the site, and amber to match
   the rest of the page instead of the general site's off-white text.
   Tight against its own image above (proximity = "belongs to this piece")
   — the real separation before the next piece comes from the card's own
   margin-bottom above, not from spacing here. */
.painting-meta-line {
  padding: 0.35rem 0.1rem 0;
  font-family: var(--mono);
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  line-height: 1.4;
  /* Deliberately lighter/paler than the DIR listing (--crt-fg) and titles
     (--crt-hi) — its own visual tier rather than blending into the same
     amber as everything else. */
  color: var(--crt-pale);
}

.painting-meta-line .painting-status {
  color: var(--hot-pink);
  text-transform: uppercase;
}

/* Hover affordance — this card opens a larger view on click */
.painting-card:hover .painting-meta-line { color: var(--crt-hi); }

/* ── 3D video grid ─────────────────────────────────────────────────
   Plain auto-sized grid, not masonry — videos need a consistent width for
   their control bar, unlike the paintings' natural-aspect-ratio images. */
.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 2.5rem 1.5rem;
}

/* Groups videos under a named project within a year — distinct from (and
   secondary to) the year-divider above it. */
.project-heading {
  grid-column: 1 / -1;
  align-self: start;
  font-family: var(--mono);
  font-size: 1.05rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--crt-pale);
  margin: 0 0 1.25rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px dashed var(--crt-dim);
}

/* Film-credits list for a project, shown once near its heading. */
.video-credits {
  grid-column: 1 / -1;
  align-self: start;
  margin: 0.5rem 0 2.5rem;
  font-family: var(--mono);
}
.video-credits-list {
  list-style: none;
  max-width: 56rem;
  column-count: 2;
  column-gap: 2.5rem;
}
.video-credits-list li {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.32rem 0;
  border-bottom: 1px solid var(--frame-line);
  font-size: 0.8rem;
  break-inside: avoid;
}
@media (max-width: 560px) {
  .video-credits-list { column-count: 1; }
}
.video-credits-name { color: var(--crt-pale); }
.video-credits-list li:first-child .video-credits-name { color: var(--crt-hi); }
.video-credits-role { color: var(--crt-fg); text-align: right; }

/* A single video entry paired with a separate designed key-art image
   (e.g. RIP) — spans the full grid width so there's room for both side by
   side, rather than squeezing them into one auto-fill column. */
.video-feature {
  grid-column: 1 / -1;
  align-self: start;
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  align-items: flex-start;
}
.video-feature .video-card { flex: 2 1 480px; max-width: 640px; }
.video-poster-art {
  flex: 1 1 260px;
  max-width: 340px;
  display: block;
  border: 1px solid var(--frame-line);
  position: relative;
  z-index: 9999;
}

/* Lifts the video above body.section::after's fixed CRT scanline overlay
   (z-index:9998) — same reasoning as .painting-card. */
.video-card { display: block; position: relative; z-index: 9999; }

.video-frame {
  position: relative;
  background: #000;
  overflow: hidden;
  aspect-ratio: 16 / 9;
}

.video-frame video,
.video-frame iframe {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
  background: #000;
  border: 0;
}

/* Captured mid-clip frame (see capturePoster in video3d.js) shown until
   the viewer presses play — hides the black first-frame most of these
   clips otherwise open on. */
.video-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.video-poster.gone { opacity: 0; }

.video-poster-play {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.25);
  border: none;
  cursor: pointer;
  color: var(--crt-fg);
  font-size: 2.5rem;
  text-shadow: 0 0 16px rgba(255,176,0,0.7);
  transition: opacity 0.2s ease, background 0.15s ease;
}
.video-poster-play:hover { background: rgba(0,0,0,0.1); color: var(--crt-hi); }
.video-poster-play.gone { opacity: 0; pointer-events: none; }

/* Click-to-play facade for Livid-hosted videos (see buildLividCard in
   video3d.js) — no local frame to auto-capture a poster from, so this
   shows a title card instead until clicked, at which point it's replaced
   by the real embed iframe. */
.video-livid-facade {
  flex-direction: column;
  gap: 0.85rem;
  background: #000;
}
.video-livid-facade:hover { background: rgba(255,176,0,0.06); }
.video-livid-icon { font-size: 2.5rem; }
.video-livid-title {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-shadow: none;
  padding: 0 1rem;
  text-align: center;
}

/* Custom transport bar — replaces native <video controls> entirely (no
   generic browser chrome, no download/speed menu) */
.video-controls {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0.1rem 0;
  font-family: var(--mono);
}

.video-playpause {
  flex-shrink: 0;
  width: 1.8rem;
  height: 1.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--frame-line);
  color: var(--crt-fg);
  font-size: 0.7rem;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.video-playpause:hover { color: var(--crt-hi); border-color: var(--crt-fg); }

.video-scrubber {
  flex: 1;
  position: relative;
  height: 1.8rem;
  cursor: pointer;
  outline: none;
}
.video-scrubber::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  height: 3px;
  background: var(--frame-line);
}
.video-scrubber-fill {
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  height: 3px;
  width: 0%;
  background: var(--crt-fg);
  box-shadow: 0 0 6px rgba(255,176,0,0.6);
}
.video-scrubber:focus-visible::before { background: var(--crt-dim); }

.video-time {
  flex-shrink: 0;
  font-size: 0.75rem;
  color: var(--crt-fg);
  white-space: nowrap;
  min-width: 7ch;
  text-align: right;
}

.video-fullscreen {
  flex-shrink: 0;
  width: 1.8rem;
  height: 1.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--frame-line);
  color: var(--crt-fg);
  font-size: 0.75rem;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.video-fullscreen:hover { color: var(--crt-hi); border-color: var(--crt-fg); }

/* Fullscreened card: center the video, keep the controls usable and
   readable, drop the metadata line to keep the video the focus */
.video-card:fullscreen {
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: #000;
  padding: 0 0 1rem;
}
.video-card:fullscreen .video-frame {
  aspect-ratio: unset;
  flex: 1;
  min-height: 0;
}
.video-card:fullscreen .painting-meta-line { display: none; }

/* ── Installations (chronological log, not a grid) ─────────────────
   Each installation is one full-width log entry — a status/meta header,
   then a walkthrough sequence (establishing shot, detail pairs, crowd
   shot), frameless like the painting cards rather than bracketed. */
.install-grid { display: flex; flex-direction: column; }

.log-entry {
  padding: 2.75rem 0;
  border-bottom: 1px solid var(--frame-line);
}
.log-entry:first-child { padding-top: 0; }
.log-entry:last-child { border-bottom: none; }

.log-header { font-family: var(--mono); }

.log-status {
  display: inline-block;
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--crt-dim);
  border: 1px solid var(--crt-dim);
  padding: 0.15rem 0.5rem;
  margin-bottom: 0.65rem;
}

.log-title {
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  color: var(--crt-hi);
  text-shadow: 0 0 10px rgba(255,208,96,0.5);
  letter-spacing: 0.02em;
  line-height: 1.2;
  margin-bottom: 0.4rem;
}

.log-meta {
  font-size: 0.85rem;
  color: var(--crt-fg);
  line-height: 1.7;
  letter-spacing: 0.02em;
}
.log-meta .dim { color: var(--crt-pale); }

.log-media { display: flex; flex-direction: column; gap: 1.75rem; margin-top: 2rem; }

/* Establishing/crowd shots and the walkthrough video — full width,
   standalone, natural aspect ratio, no cropping. */
.log-media-full { position: relative; z-index: 9999; }
.log-media-full img { width: 100%; display: block; }

/* Detail-shot pairs — rows normalized to one shared target height via
   justified-rows.js, same technique as the Paintings page, so one
   very-high-resolution photo (RAINWORLD) never renders disproportionately
   larger than the one next to it. */
.log-detail-grid { display: flex; flex-direction: column; }
.log-detail-grid .justified-row:last-child { margin-bottom: 0; }

.install-media {
  display: block;
  flex: none;
  position: relative;
  z-index: 9999;
}
.install-media img { width: 100%; display: block; }

/* ── Upcoming installation teaser — visually distinct from logged
   entries: bordered callout, blinking pink status tag, sits above the log. */
.log-entry.scheduled {
  border: 1px solid var(--hot-pink);
  border-bottom: 1px solid var(--hot-pink);
  padding: 1.75rem;
  margin-bottom: 2.5rem;
  background: rgba(255,0,85,0.05);
}
.log-entry.scheduled .log-status {
  color: var(--hot-pink);
  border-color: var(--hot-pink);
  animation: cursor-blink 1.2s step-start infinite;
}

/* ── Press highlight — a credibility callout within an entry, not a
   separate press section. */
.log-press {
  margin-top: 2.25rem;
  padding: 1.5rem;
  border: 1px solid var(--frame-line);
  background: var(--bg-card);
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
  flex-wrap: wrap;
}
.log-press-quote {
  max-width: 340px;
  width: 100%;
  display: block;
  border: 1px solid var(--frame-line);
}
.log-press-cite {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--crt-pale);
  letter-spacing: 0.03em;
  padding-top: 0.4rem;
  flex: 1;
  min-width: 200px;
}
.log-press-cite a {
  color: var(--crt-hi);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.log-press-cite a:hover { color: var(--crt-fg); }

/* ── Painting lightbox ─────────────────────────────────────────── */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 10500;
  background: rgba(5,4,3,0.94);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1.5rem, 5vw, 4rem);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.lightbox.open { opacity: 1; pointer-events: auto; }

.lightbox-inner {
  position: relative;
  max-width: 100%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.lightbox-inner img {
  max-width: 100%;
  max-height: 78vh;
  width: auto;
  object-fit: contain;
  border: 1px solid var(--frame-line);
}

.lightbox-caption {
  font-family: var(--mono);
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  color: var(--fg);
  text-align: center;
}
.lightbox-caption .painting-status { color: var(--hot-pink); text-transform: uppercase; }

.lightbox-close {
  position: absolute;
  top: -2.4rem;
  right: 0;
  font-family: var(--mono);
  font-size: 0.9rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-dim);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.3rem 0.5rem;
}
.lightbox-close:hover { color: var(--crt-hi); }

/* ── Ticker ───────────────────────────────────────────────────── */
/* A true end-of-page footer, not pinned to the viewport — it's the last
   element in the body, so in normal flow it just sits at the bottom of
   the page once and never scrolls back over content above it (which is
   what happened when this was position:fixed at a lower z-index than the
   artwork cards above it). */
.ticker-bar {
  background: var(--bg);
  border-top: 1px solid var(--frame-line);
  height: var(--ticker-h);
  overflow: hidden;
  display: flex;
  align-items: center;
}

.ticker-label {
  font-family: var(--mono);
  font-size: 0.5rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--hot-pink);
  padding: 0 1rem;
  flex-shrink: 0;
  border-right: 1px solid var(--frame-line);
  height: 100%;
  display: flex;
  align-items: center;
}

.ticker-track {
  display: flex;
  white-space: nowrap;
  animation: ticker-scroll 35s linear infinite;
}

.ticker-text {
  font-family: var(--mono);
  font-size: 0.57rem;
  letter-spacing: 0.12em;
  color: var(--crt-fg);
  padding-right: 6rem;
  flex-shrink: 0;
}

.ticker-text .hi { color: var(--crt-hi); }

@keyframes ticker-scroll {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}

/* ── Glitch overlay ───────────────────────────────────────────── */
.glitch-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  pointer-events: none;
  background: #000;
  animation: page-fade 0.18s ease-in forwards;
}

@keyframes page-fade {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

/* ── Reveal ───────────────────────────────────────────────────── */
/* margin-top rather than transform: translateY() — an animated CSS
   transform on any ancestor of a <video> element (this reveal wraps video
   cards directly, and wraps the Maker Maxity log entry on the
   Installations page) can freeze that video's displayed frame mid-
   playback in Chromium's hardware video decode path while the audio (and
   the video's own clock) keeps advancing underneath. margin-top gives the
   same slide-up-while-fading effect without ever touching transform. */
.reveal {
  opacity: 0;
  margin-top: 18px;
  transition: opacity 0.6s ease, margin-top 0.6s ease;
}
.reveal.visible { opacity: 1; margin-top: 0; }

/* ── Bio ──────────────────────────────────────────────────────── */
.bio-grid {
  display: flex;
  gap: 4rem;
  align-items: flex-start;
}

.bio-profile-card { flex: none; }

/* Height is refined in JS (bio.js) to match .bio-text's rendered height —
   width is left to auto so the image scales at its natural aspect ratio
   instead of being cropped/stretched into a fixed box. The 420px default
   below matters even though JS overrides it almost immediately: without
   it, height:100% on the img resolves against an indeterminate parent
   height, which makes the browser fall back to the photo's natural pixel
   size (thousands of px) on first paint — that briefly squeezes .bio-text
   down to a sliver, which inflates its wrapped-text height, which JS then
   reads and feeds back into an even bigger image. A sane fixed default
   here breaks that first-paint circularity. Lifted above
   body.section::after's fixed CRT scanline overlay (z-index:9998), same
   reasoning as .painting-card/.video-card. */
.bio-profile-img {
  height: 420px;
  background: #0b0b0b;
  overflow: hidden;
  position: relative;
  z-index: 9999;
}

.bio-profile-img img { height: 100%; width: auto; display: block; }

.bio-text { flex: 1 1 auto; min-width: 0; }

.bio-text p {
  font-size: clamp(0.95rem, 1.4vw, 1.1rem);
  line-height: 1.8;
  color: var(--crt-pale);
  max-width: 62ch;
  margin-bottom: 1.25rem;
}

/* ── Press / CV ───────────────────────────────────────────────── */
.press-block { margin-bottom: 3rem; }

.press-block-title {
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--crt-fg);
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--frame-line);
  margin-bottom: 0.25rem;
}

.press-list { list-style: none; }

.press-list li {
  display: grid;
  grid-template-columns: 3.5rem 1fr auto;
  gap: 1.5rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--frame-line);
  align-items: baseline;
  transition: background 0.12s;
}

.press-list li:hover { background: rgba(255,255,255,0.02); }

.press-year { font-family: var(--mono); font-size: 0.62rem; color: var(--crt-pale); }
.press-title { font-size: 0.85rem; color: var(--crt-hi); }
.press-venue { font-size: 0.72rem; color: var(--crt-pale); text-align: right; }

.education-list li { grid-template-columns: 1fr auto; }

.press-note {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--crt-pale);
  line-height: 1.7;
  max-width: 60ch;
}

.press-downloads { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2.5rem; }

.dl-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 1.1rem;
  border: 1px solid rgba(255,0,85,0.4);
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--hot-pink);
  transition: background 0.15s;
  cursor: pointer;
}
.dl-btn:hover { background: rgba(255,0,85,0.08); }

/* ── Contact ──────────────────────────────────────────────────── */
.contact-label {
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 0.5rem;
}

.contact-email-link {
  font-family: var(--mono);
  font-size: clamp(1.5rem, 3.5vw, 3.5rem);
  display: block;
  margin-bottom: 3rem;
  color: var(--fg);
  transition: color 0.2s;
}
.contact-email-link:hover { color: var(--hot-pink); }

.contact-note {
  font-size: 0.82rem;
  color: var(--fg-dim);
  line-height: 1.7;
  max-width: 44ch;
  margin-bottom: 1rem;
}

/* ── Chat widget ──────────────────────────────────────────────── */
.chat-tab {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 2000;
  background: #000;
  border: 1px solid #ffb000;
  color: #ffb000;
  font-family: var(--mono);
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  padding: 0.38rem 0.75rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  text-shadow: 0 0 6px rgba(255,176,0,0.45);
  transition: background 0.15s;
}

.chat-tab:hover,
.chat-tab.active { background: rgba(255,176,0,0.08); }

.chat-cursor {
  display: inline-block;
  width: 0.5em;
  height: 0.85em;
  background: #ffb000;
  vertical-align: text-bottom;
  animation: cursor-blink 0.85s step-start infinite;
}

.chat-window {
  position: fixed;
  bottom: calc(1.5rem + 42px);
  right: 1.5rem;
  z-index: 1999;
  width: min(360px, calc(100vw - 3rem));
  height: 420px;
  background: #000;
  border: 1px solid #ffb000;
  font-family: var(--mono);
  font-size: 0.7rem;
  display: flex;
  flex-direction: column;
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px);
  transition: opacity 0.18s, transform 0.18s;
}

.chat-window.open {
  opacity: 1;
  pointer-events: all;
  transform: none;
}

.chat-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.45rem 0.75rem;
  border-bottom: 1px solid #7a4a00;
  color: #ffb000;
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  flex-shrink: 0;
  text-shadow: 0 0 6px rgba(255,176,0,0.4);
}

.chat-close {
  background: none;
  border: none;
  color: #ffb000;
  font-family: var(--mono);
  font-size: 0.58rem;
  cursor: pointer;
  padding: 0;
  letter-spacing: 0.1em;
  line-height: 1;
}
.chat-close:hover { color: #fff; }

.chat-output {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  scrollbar-width: thin;
  scrollbar-color: #7a4a00 #000;
}

.chat-line {
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  color: #7a4a00;
}

.chat-line.chat-sys {
  font-size: 0.58rem;
  letter-spacing: 0.06em;
  opacity: 0.7;
}

.chat-line.chat-user {
  color: #ffb000;
  text-shadow: 0 0 5px rgba(255,176,0,0.35);
}

.chat-line.chat-reply {
  color: #ffd060;
}

.chat-input-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-top: 1px solid #2e1a00;
  flex-shrink: 0;
}

.chat-prompt {
  color: #ffb000;
  flex-shrink: 0;
  text-shadow: 0 0 5px rgba(255,176,0,0.4);
}

.chat-input {
  flex: 1;
  background: none;
  border: none;
  color: #ffb000;
  font-family: var(--mono);
  font-size: 0.7rem;
  outline: none;
  caret-color: #ffb000;
  min-width: 0;
}

.chat-input::placeholder { color: #5c3a00; }
.chat-input:disabled { opacity: 0.45; }

/* ── Responsive ───────────────────────────────────────────────── */
@media (max-width: 768px) {
  .bio-grid { flex-direction: column; }
  .bio-profile-card { max-width: 100%; }
  .bio-profile-img { height: auto !important; width: 100%; }
  .bio-profile-img img { height: auto; width: 100%; }
  .press-list li { grid-template-columns: 3rem 1fr; }
  .press-venue { display: none; }
}
