/* Self-hosted — see public/fonts/. font-display: optional means visitors on a
   slow connection just keep the fallback monospace; the swap never happens
   late enough to cause a layout shift. */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: optional;
  src: url('/fonts/ibm-plex-mono-400.woff2') format('woff2');
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: optional;
  src: url('/fonts/ibm-plex-mono-600.woff2') format('woff2');
}

/* Decorative wordmark face for the artist's name only. OFL-licensed, see
   flor-des-ruina-main/. font-display: swap (not optional, unlike the body
   font above) — this is a small, isolated piece of text, so a brief flash
   of the fallback before swapping in is harmless, whereas optional's
   "never swap after first paint" rule would mean this font almost never
   actually shows up: the header paints before a cold font fetch can
   realistically finish. */
@font-face {
  font-family: 'Flor De Ruina Fractura';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/flor-de-ruina-fractura.woff2') format('woff2');
}

/* The body face for everything else on the site. Same font-display: swap
   reasoning as above, just at body-text scale instead of one wordmark. */
@font-face {
  font-family: 'Flor De Ruina Flor';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/flor-de-ruina-flor.woff2') format('woff2');
}

:root {
  --bg: #050505;
  --fg: #ededed;
  --dim: #8a8a8a;
  --accent: #ff1f3d;
  --line: #1c1c1c;

  --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  --row: 1.5rem; /* vertical grid unit — chrome dimensions snap to multiples of this */
  --logo-size: 2.4rem;
  --header-h: calc(var(--logo-size) + 0.7rem); /* a few px of clearance above/below the logo, not row-grid-derived */

  /* Shared motion vocabulary — one easing curve, one "fast" duration, reused
     everywhere instead of each transition inventing its own numbers. */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --motion-fast: 0.18s;
}

* { box-sizing: border-box; }

html {
  /* fluid root size — everything sized in rem/ch (header height, chrome
     padding, the detail caption position) scales proportionally with
     viewport width instead of being pinned to one fixed pixel grid. */
  font-size: clamp(16px, 0.9vw + 0.9rem, 20px);
}

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font-family: 'Flor De Ruina Flor', var(--font-mono);
  line-height: var(--row); /* the custom cursor's grid cell height matches this */
  overflow: hidden; /* the viewport, not the page, owns scrolling */
}

/* ---------- email gate ---------- */

.email-gate {
  position: fixed;
  inset: 0;
  z-index: 300; /* above everything, including the detail overlay (200) */
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}

.email-gate-form {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  width: min(420px, 88vw);
  padding: 2rem;
  text-align: center;
}

.email-gate-logo {
  font-family: 'Flor De Ruina Fractura', var(--font-mono);
  font-size: 2.4rem;
  color: var(--fg);
  line-height: 1;
  transform: translateY(0.14em); /* see .logo — same font, same ink-vs-line-box offset */
}

.email-gate-tagline {
  margin: 0;
  color: var(--dim);
  font-size: 0.85rem;
  letter-spacing: 0.02em;
}

.email-gate-row {
  display: flex;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
  transition: border-color var(--motion-fast) ease;
}

.email-gate-row:focus-within { border-color: var(--accent); }

.email-gate-input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  padding: 0.7rem 1ch;
  color: var(--fg);
  font-family: inherit;
  font-size: 0.85rem;
}

.email-gate-input::placeholder { color: var(--dim); }

.email-gate-submit {
  background: var(--accent);
  border: none;
  padding: 0.7rem 2ch;
  color: #050505;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: opacity var(--motion-fast) ease;
}

.email-gate-submit:hover { opacity: 0.85; }
.email-gate-submit:disabled { opacity: 0.5; cursor: default; }

.email-gate-error {
  margin: 0;
  min-height: 1em;
  color: var(--accent);
  font-size: 0.75rem;
}

.email-gate-error:empty { display: none; }

.email-gate-note {
  margin: 0;
  color: var(--dim);
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  opacity: 0.8;
}

/* ---------- header ---------- */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--header-h);
  display: flex;
  align-items: center;
  gap: 2.5ch;
  padding: 0 2ch;
  background: rgba(5, 5, 5, 0.85);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  z-index: 50;
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: 0.7ch;
  color: var(--fg);
  font-family: 'Flor De Ruina Fractura', var(--font-mono);
  font-size: var(--logo-size);
  line-height: 1;
  white-space: nowrap;
  /* Fractura's actual ink (its decorative texture/flourishes) overshoots
     the font's nominal ascent — measured by scanning rendered pixels, the
     glyphs sit flush against the top of their CSS line-box with all the
     slack below, so flexbox's align-items: center (which only knows about
     the line-box, not the ink) looks centered on paper but reads as
     bottom-heavy/top-clipped on screen. Nudged down by eye to balance the
     real ink instead. */
  transform: translateY(0.14em);
}

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

.tabs {
  display: flex;
  gap: 0.5ch;
  margin-left: 1ch;
  /* flex items default to a min-width equal to their content, which blocks
     shrinking below that — min-width: 0 lets this row actually shrink
     smaller than "every tab's natural width" so overflow-x can kick in
     (scrollable tabs) instead of the row just pushing the rest of the
     header off the edge of the screen. */
  min-width: 0;
  flex: 1 1 auto;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.tabs::-webkit-scrollbar { display: none; }

.tab {
  background: transparent;
  border: 1px solid transparent;
  color: var(--dim);
  font-family: inherit;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  padding: 0.3rem 1.5ch;
  border-radius: 2px;
  cursor: none; /* custom cursor (see js/cursor.js) replaces the native pointer everywhere */
  flex-shrink: 0; /* never squish a tab's label — the row scrolls instead */
  white-space: nowrap;
}

.tab:hover { color: var(--fg); font-weight: 600; }

.tab[aria-selected="true"] {
  color: var(--fg);
  border-color: var(--line);
  background: #0e0e0e;
}

.count {
  margin-left: auto;
  color: var(--dim);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.count::before { content: '[ '; }
.count::after { content: ' ]'; }

.search-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--dim);
  cursor: pointer;
  transition: color var(--motion-fast) ease, border-color var(--motion-fast) ease;
  flex-shrink: 0;
}

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

/* ---------- filter bar ---------- */

.filter-bar {
  position: fixed;
  top: var(--header-h);
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 1ch;
  padding: 0.5rem 2ch;
  background: rgba(5, 5, 5, 0.92);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
  z-index: 40;
  /* slide in from just above — translate is the same distance as its own height
     (~38px) so it appears to emerge from under the header. */
  transform: translateY(-100%);
  opacity: 0;
  pointer-events: none;
  transition: transform var(--motion-fast) var(--ease), opacity var(--motion-fast) ease;
}

.filter-bar.is-open {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
  transition: transform var(--motion-fast) var(--ease), opacity var(--motion-fast) ease;
}

.filter-input {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--fg);
  font-family: inherit;
  font-size: 0.82rem;
  letter-spacing: 0.01em;
  outline: none;
  /* browsers add their own cancel button for type=search; suppress it */
  -webkit-appearance: none;
  appearance: none;
}

.filter-input::placeholder { color: var(--dim); }
.filter-input::-webkit-search-cancel-button { display: none; }

.filter-count {
  color: var(--dim);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex-shrink: 0;
}

.filter-clear {
  background: transparent;
  border: none;
  color: var(--dim);
  font-size: 0.8rem;
  cursor: pointer;
  padding: 4px;
  line-height: 1;
  transition: color var(--motion-fast) ease;
  flex-shrink: 0;
}

.filter-clear:hover { color: var(--fg); }

.cursor-settings { position: relative; margin-left: 1.5ch; }

.cursor-settings-trigger {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--dim);
  /* native pointer, not the decorative grid cursor: the grid-snap effect
     can visually offset the ring by up to half a character cell from the
     real pointer position, which made this small button hard to hit with
     no native-cursor feedback at all. */
  cursor: pointer;
  transition: color var(--motion-fast) ease, border-color var(--motion-fast) ease;
}

/* The grid-snapped cursor isn't a precise pointer, so the actual hit/hover
   zone is grown well past the visible 28px button via this pseudo-element
   rather than by enlarging the button itself — clicks/hovers anywhere on it
   count as the button's, since it's generated content of the button, not a
   separate element. */
.cursor-settings-trigger::before {
  content: '';
  position: absolute;
  inset: -12px;
  border-radius: 10px;
  transition: background-color var(--motion-fast) ease;
}

.cursor-settings-trigger:hover::before,
.cursor-settings-trigger[aria-expanded="true"]::before {
  background-color: rgba(255, 255, 255, 0.07);
}

.cursor-settings-trigger:hover,
.cursor-settings-trigger[aria-expanded="true"] {
  color: var(--fg);
  border-color: var(--accent);
}

/* Apple-style popover: anchored to the trigger, not part of the header's
   normal flow, so showing/hiding it never reflows the rest of the chrome. */
.cursor-panel {
  cursor: default; /* native pointer throughout this small, precision-critical popover */
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: 220px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  background: rgba(14, 14, 14, 0.92);
  backdrop-filter: blur(20px);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px) scale(0.97);
  transform-origin: top right;
  transition: opacity var(--motion-fast) var(--ease), transform var(--motion-fast) var(--ease), visibility 0s var(--motion-fast);
  z-index: 80;
}

.cursor-panel.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
  transition: opacity var(--motion-fast) var(--ease), transform var(--motion-fast) var(--ease);
}

/* Each row is a <label> (not a div) wrapping its input, so the click/hover
   zone is the whole row, not just the tiny native swatch/thumb — clicking
   anywhere on a label activates its associated control (opens the color
   picker, focuses the slider). The negative margin + matching padding grows
   the hoverable box without moving the row's content or widening the panel:
   the box expands outward by 6px on every side, then padding pulls the
   content back in by the same amount. */
.cursor-row {
  display: flex;
  align-items: center;
  gap: 0.8ch;
  color: var(--dim);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  padding: 6px;
  margin: -6px;
  border-radius: 8px;
  cursor: pointer; /* native pointer — see .cursor-panel */
  transition: background-color var(--motion-fast) ease;
}

.cursor-row:hover {
  background-color: rgba(255, 255, 255, 0.05);
}

.cursor-row-label {
  width: 4.2ch;
  flex-shrink: 0;
  text-transform: uppercase;
}

.cursor-row input[type="range"] {
  flex: 1;
  accent-color: var(--accent);
  cursor: pointer;
}

.cursor-row input[type="color"] {
  width: 24px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
}

.cursor-control-value {
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  min-width: 3ch;
  text-align: right;
  flex-shrink: 0;
}

.cursor-reset {
  margin: 2px -6px -6px;
  padding: 10px 6px 12px;
  border: none;
  border-top: 1px solid var(--line);
  border-radius: 0 0 8px 8px;
  background: transparent;
  color: var(--dim);
  font-family: inherit;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  text-align: center;
  cursor: pointer; /* native pointer — see .cursor-panel */
  transition: background-color var(--motion-fast) ease, color var(--motion-fast) ease;
}

.cursor-reset:hover,
.cursor-reset:focus-visible {
  color: var(--accent);
  background-color: rgba(255, 255, 255, 0.05);
  outline: none;
}

/* Header on narrow/portrait phones: the logo at full size plus every tab
   plus the work count plus search plus cursor settings simply doesn't fit
   in ~375-430px. Shrinking the logo and dropping the cursor-settings button
   (its custom-cursor feature already self-disables on coarse pointers in
   js/cursor.js — the button was dead weight on touch, not just decorative)
   buys back most of the room; the tabs row above is scrollable regardless,
   as a fallback for whatever's still tight. */
@media (max-width: 700px) {
  :root { --logo-size: 1.05rem; }

  .site-header { gap: 1ch; padding: 0 1.2ch; }

  .logo { gap: 0.4ch; }

  .tabs { margin-left: 0.5ch; gap: 0.3ch; }

  .tab { padding: 0.3rem 1ch; font-size: 0.74rem; }

  .cursor-settings { display: none; }

  .count { display: none; }
}

/* ---------- virtualized grid ---------- */

.viewport {
  position: fixed;
  top: var(--header-h); /* JS bumps this to header + filter-bar when search is open */
  right: 0;
  bottom: 0;
  left: 0;
  overflow-y: auto;
  overflow-x: hidden;
  contain: layout paint; /* isolate layout/paint cost to the grid only */
  overscroll-behavior-y: contain;
  padding: 0 28px; /* more frame around the work — gallery margin, not a dense product grid */
  transition: top var(--motion-fast) var(--ease);
}

/* Fully removed from layout (not just hidden) while the Shows tab is active,
   so its width collapses to 0 and grid.relayout() — which already no-ops on
   zero width — never does real layout work on an invisible grid. */
.viewport.is-hidden { display: none; }

.canvas {
  position: relative; /* rows are absolutely positioned within this; height is set by JS to match scroll extent */
  width: 100%;
}

/* Sibling of .canvas, not a child — .canvas has height:0 until the first
   layout pass runs, so an absolutely-positioned child of it would have
   nothing to center against. Anchored to .viewport (which always has real
   height) instead. Only ever relevant on first load — collection switches
   after that use the crossfade in VirtualGrid.setItems instead. */
.loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6ch;
  color: var(--dim);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  pointer-events: none;
  transition: opacity var(--motion-fast) ease;
}

.loading::after {
  content: '';
  width: 1ch;
  height: 1em;
  background: var(--dim);
  animation: cursor-blink 1.1s step-end infinite;
}

.loading.is-hidden { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .loading::after { animation: none; }
}

.row {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  /* positioned via translateY(top) only — compositor-only move, never triggers layout */
}

.cell {
  position: absolute;
  top: 0;
  left: 0;
  /* width/height set inline per-item from the justified-layout pass; never
     recalculated except on remount. --tx is the only layout position — the
     artwork itself never moves, scales, or reflows for any interaction.
     Mouse hover has no DOM/CSS representation at all — that's the canvas
     cursor overlay's job (js/cursor.js), which knows nothing about grid
     items. This outline is keyboard-focus-only. */
  transform: translateX(var(--tx, 0px));
  outline: 1px solid transparent;
  transition: outline-color 130ms ease;
  overflow: hidden; /* no scale effect anymore, so nothing ever needs to escape the box */
  border-radius: 3px;
  will-change: transform;
}

.cell:focus-visible {
  outline-color: var(--accent);
}

.cell .thumb,
.cell .preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  border-radius: 3px;
  background-color: var(--cell-bg, #111); /* dominant-color placeholder, set inline before decode */
  opacity: 0;
  transition: opacity 0.25s ease;
}

.cell .thumb.is-loaded { opacity: 1; }

/* preview (hover-res) is fetched on hover-intent (grid.js), never preloaded
   up front — but once it's loaded, it stays shown permanently rather than
   reverting when the pointer moves away. The bytes are already spent at
   that point, so there's no DOM/hover-state needed to gate visibility —
   just "has this finished loading," which sidesteps needing any per-cell
   "is this currently hovered" class at all. */
.cell .preview { pointer-events: none; }
.cell .preview.is-loaded { opacity: 1; }

.cell .cell-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  border-radius: 3px;
  background-color: var(--cell-bg, #111);
  pointer-events: none;
}

/* ---------- shows / press CV ---------- */

.shows-view {
  position: fixed;
  top: var(--header-h);
  right: 0;
  bottom: 0;
  left: 0;
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--bg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--motion-fast) ease, visibility 0s var(--motion-fast);
}

.shows-view.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--motion-fast) ease;
}

.shows-content {
  max-width: 620px;
  margin: 0 auto;
  padding: 3rem 28px 5rem;
}

.shows-subtitle {
  color: var(--dim);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  margin: 0 0 2.5rem;
}

.shows-section-title {
  color: var(--fg);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 3rem 0 1rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--line);
}

.shows-section-title:first-of-type { margin-top: 0; }

.shows-year {
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  margin: 1.4rem 0 0.4rem;
}

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

.shows-list li {
  display: flex;
  gap: 0.8ch;
  color: var(--dim);
  font-size: 0.82rem;
  line-height: 1.55;
  padding: 0.2rem 0;
}

.shows-list li::before {
  content: '—';
  flex-shrink: 0;
  color: var(--line);
}

/* ---------- detail overlay ---------- */

.detail {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: center;
  background: rgba(2, 2, 2, 0.94);
  z-index: 200;
  overflow: hidden; /* zoomed image stays within the screen */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* visibility flips to hidden only once the fade-out finishes, so closing
     never cuts opacity short; opening flips it immediately. */
  transition: opacity var(--motion-fast) ease, visibility 0s var(--motion-fast);
  --detail-meta-width: 340px;
}

.detail.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--motion-fast) ease;
}

.detail-frame {
  flex: 1 1 auto;
  min-width: 0;
  max-width: calc(100% - var(--detail-meta-width));
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 24px;
  box-sizing: border-box;
  transform: scale(0.98);
  transition: transform var(--motion-fast) var(--ease);
}

.detail.is-open .detail-frame { transform: scale(1); }

.detail-img {
  max-width: 100%;
  max-height: 90vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 2px;
  background-color: var(--cell-bg, #111);
  transform-origin: center center;
  /* No CSS transition on transform — zoom is driven frame-by-frame by wheel
     events and pinch, where a lag between input and output reads as jank. */
  will-change: transform;
}

.detail-close, .detail-nav {
  position: absolute;
  background: transparent;
  border: none;
  color: var(--fg);
  font-family: inherit;
  font-size: 28px;
  cursor: none; /* custom cursor (see js/cursor.js) replaces the native pointer everywhere */
  padding: 12px 16px;
  opacity: 0.7;
  z-index: 15; /* stay above the zoomed image and the meta sidebar */
}

.detail-close:hover, .detail-nav:hover { opacity: 1; color: var(--accent); }

.detail-close { top: 10px; right: 14px; }
.detail-nav.prev { left: 6px; top: 50%; transform: translateY(-50%); }
.detail-nav.next { right: calc(var(--detail-meta-width) + 6px); top: 50%; transform: translateY(-50%); }

.detail-meta {
  position: relative;
  flex: 0 0 var(--detail-meta-width);
  width: var(--detail-meta-width);
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 1.1rem;
  text-align: left;
  padding: 76px 32px 28px;
  border-left: 1px solid var(--line);
  background: rgba(8, 8, 8, 0.5);
  overflow-y: auto;
  z-index: 5;
  font-weight: 600; /* bold — matches the loaded IBM Plex Mono 600 weight (700 isn't self-hosted, so it'd fall back to faux-bold) */
}

.detail-meta p { margin: 0; }

.detail-title {
  color: var(--fg);
  font-size: 1.05rem;
  line-height: 1.4;
  letter-spacing: 0.01em;
}

.detail-meta-list {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.detail-meta-list li {
  position: relative;
  padding-left: 1.1em;
  color: var(--fg);
  font-size: 0.82rem;
  line-height: 1.55;
  letter-spacing: 0.01em;
}

.detail-meta-list li::before {
  content: '–';
  position: absolute;
  left: 0;
  color: var(--accent);
}

.detail-meta-label {
  display: block;
  color: var(--dim);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 0.2rem;
}

.detail-position {
  color: var(--dim);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

/* Below ~700px there's no room for a fixed sidebar — stack the caption
   under the image instead, same as the rest of the site's desktop-only
   interaction model (see roadmap 3.4). */
@media (max-width: 700px) {
  .detail {
    flex-direction: column;
    --detail-meta-width: 0px;
  }

  .detail-frame {
    max-width: 100%;
    height: auto;
    flex: 1 1 auto;
    padding: 16px 16px 0;
  }

  .detail-img { max-height: 60vh; }

  .detail-nav.next { right: 6px; }

  .detail-meta {
    width: 100%;
    flex: 0 0 auto;
    max-height: 38vh;
    border-left: none;
    border-top: 1px solid var(--line);
    padding: 18px 20px;
  }
}

/* ---------- detail related-works rail (3.8) ---------- */

.detail-related {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  width: 100%;
}

.detail-related::-webkit-scrollbar { display: none; }

.detail-related-btn {
  flex-shrink: 0;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 2px;
  background: transparent;
  cursor: none;
  opacity: 0.5;
  transition: opacity var(--motion-fast) ease, border-color var(--motion-fast) ease;
  overflow: hidden;
}

.detail-related-btn:hover,
.detail-related-btn:focus-visible {
  opacity: 1;
  border-color: var(--accent);
  outline: none;
}

.detail-related-btn.is-current {
  opacity: 1;
  border-color: var(--accent);
}

.detail-related-btn img {
  display: block;
  width: auto;
  height: 44px;
  object-fit: cover;
  background-color: var(--line);
}
