/* SPANIEL SYNDICATE — gallery (gallery.html) styles.
 *
 * Companion to style.css. Loaded only by gallery.html so the homepage
 * stays lean. Bauhaus discipline: flat dark base, sharp corners, no
 * gradients, restrained palette inherited from style.css's CSS
 * custom properties.
 *
 * Layout strategy: a fixed-row-height virtual scroller. The "stage"
 * element scrolls; an inner "spacer" sets the total scroll height
 * (rows × ROW_HEIGHT); the "viewport" div is positioned with
 * translate3d to the current row offset and re-rendered as the user
 * scrolls past row boundaries. js/visuals.js owns that math; CSS only
 * needs to nail the row geometry so the math has stable numbers to
 * work with.
 */

/* ── HERO ───────────────────────────────────────────────────────── */

.visuals-hero {
  padding: 40px 0 28px;
  border-bottom: 2px solid var(--soft-line);
  margin-bottom: 0;
}
.visuals-hero-eyebrow {
  font-family: "DM Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 18px;
}
.visuals-hero-title {
  font-family: "Bebas Neue", sans-serif;
  font-size: clamp(48px, 9vw, 96px);
  line-height: 0.92;
  letter-spacing: 0.02em;
  margin: 0 0 20px;
  color: var(--ink);
}
.visuals-hero-line-1 { display: block; }
.visuals-hero-line-2 {
  display: block;
  color: var(--accent);
}
.visuals-hero-sub {
  max-width: 720px;
  color: var(--ink-soft);
  font-size: 15px;
  line-height: 1.65;
  margin: 0 0 22px;
}
.visuals-hero-sub strong { color: var(--ink); }
.visuals-hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  font-family: "DM Mono", monospace;
  font-size: 12px;
}
.visuals-hero-link {
  color: var(--accent);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
}
.visuals-hero-link:hover {
  color: var(--accent-2);
  border-bottom-color: var(--accent-2);
  text-decoration: none;
}

/* ── TYPE SUMMARY ──────────────────────────────────────────────── */

.visuals-types {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 0.72fr);
  gap: 28px;
  padding: 22px 0;
  border-bottom: 2px solid var(--soft-line);
}
.visuals-types-copy h2 {
  margin: 0 0 8px;
  font-family: "Bebas Neue", sans-serif;
  font-size: 30px;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--ink);
}
.visuals-types-copy p {
  max-width: 760px;
  margin: 0;
  color: var(--ink-soft);
  font-size: 14px;
  line-height: 1.65;
}
.visuals-type-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  align-content: start;
}
.visuals-type-grid span {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  min-height: 30px;
  padding: 7px 9px;
  background: var(--panel);
  border: 1px solid var(--soft-line);
  color: var(--ink-soft);
  font-family: "DM Mono", monospace;
  font-size: 11px;
  text-transform: uppercase;
}
.visuals-type-grid strong {
  color: var(--accent);
  font-weight: 700;
}

/* ── CONTROLS ───────────────────────────────────────────────────── */

.visuals-controls {
  position: sticky;
  /* Sits just under the sticky nav. Nav height is ~52 px; round up so
   * the controls row never overlaps it during sub-pixel rounding. */
  top: 56px;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 24px;
  padding: 14px 0;
  margin-bottom: 18px;
  background: var(--bg);
  border-bottom: 2px solid var(--line);
}
.visuals-jump {
  display: flex;
  align-items: center;
  gap: 10px;
}
.visuals-jump-label {
  font-family: "DM Mono", monospace;
  font-size: 12px;
  color: var(--ink-soft);
  letter-spacing: 0.04em;
}
#visualsJumpInput {
  width: 130px;
  padding: 8px 10px;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--soft-line);
  font: 13px "DM Mono", monospace;
  appearance: none;
  -moz-appearance: textfield;
}
#visualsJumpInput::-webkit-outer-spin-button,
#visualsJumpInput::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
#visualsJumpInput:focus {
  outline: none;
  border-color: var(--accent);
}
.visuals-jump-btn {
  padding: 8px 14px;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--accent);
  font: 12px "DM Mono", monospace;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.visuals-jump-btn:hover {
  background: var(--accent);
  color: #0b0a08;
  border-color: var(--accent);
}
.visuals-meta {
  display: flex;
  align-items: center;
  gap: 18px;
  font-family: "DM Mono", monospace;
  font-size: 12px;
  color: var(--muted);
}
.visuals-meta-count strong { color: var(--ink); }
.visuals-meta-hint {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--muted);
}
.visuals-meta-hint kbd {
  display: inline-block;
  padding: 1px 5px;
  margin: 0 1px;
  background: var(--panel);
  border: 1px solid var(--soft-line);
  border-bottom-width: 2px;
  font: 11px "DM Mono", monospace;
  color: var(--ink-soft);
  line-height: 1.3;
}

/* ── VIRTUALIZED STAGE ──────────────────────────────────────────── */

/* The stage is the scroll container the user actually drags. We let
 * the document scroll instead of nesting a scrollable element — nested
 * scroll containers play badly with mobile rubber-banding and break
 * the browser's native scroll-restoration on back/forward. Instead,
 * the stage just establishes a stable layout box for the spacer + the
 * absolute-positioned viewport. js/visuals.js listens to window
 * scroll. */
.visuals-stage {
  position: relative;
  width: 100%;
  /* Outline only visible if user tabs in; clicks still focus silently
   * because tabindex="0" is set without explicit outline-style. */
}
.visuals-stage:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* Spacer sets the total scrollable height: rows × ROW_HEIGHT. Sized
 * at runtime by js/visuals.js once it knows the column count. */
.visuals-spacer {
  position: relative;
  width: 100%;
}

/* Viewport sits inside the spacer and is offset via inline transform
 * by js/visuals.js. CSS only nails the geometry; positioning is JS. */
.visuals-viewport {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  /*
   * Do not use `contain: paint` here. The virtualized rows are
   * absolutely positioned children while this viewport has no
   * intrinsic height; paint containment clips them to a zero-height
   * box and the live gallery appears blank.
   */
  contain: layout style;
}

/* The grid is set by JS via inline style (auto-fill, minmax based on
 * computed column count). Tiles themselves are styled here. */
.visuals-row {
  display: grid;
  /* grid-template-columns set inline by JS so column count stays
   * synchronized with the row-height math. */
  gap: 0;
  /* Removing default whitespace between inline-block-y children. */
  font-size: 0;
}

.visuals-tile {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  width: 100%;
  background: var(--panel-2);
  border: 1px solid rgba(255, 255, 255, 0.04);
  overflow: hidden;
  /* Pixel-perfect upscale of the 24×24 native art. The PNGs ship at
   * 384×384 but the tile sizes render them down or up — either way,
   * nearest-neighbor keeps the pixel grid sharp. */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.26);
  transition: border-color .12s ease, transform .12s ease, background .12s ease;
  /* Tap targets — the tile itself is the link, no inner click area. */
}
.visuals-tile:hover {
  border-color: var(--accent);
  background: var(--panel);
  text-decoration: none;
  /* Lift the tile half a pixel so the hover ring reads as a frame
   * rather than a thicker border. */
  transform: translateY(-1px);
  z-index: 2;
}
.visuals-tile:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
  z-index: 3;
}

.visuals-tile-art {
  display: block;
  width: calc(100% - 10px);
  height: calc(100% - 10px);
  margin: 5px;
  background-repeat: no-repeat;
  background-color: var(--panel);
  background-origin: content-box;
  background-clip: content-box;
  /*
   * The gallery is drawn from 24px native atlas shards rather than
   * 69,420 individual PNG requests. Scaling happens here, with
   * nearest-neighbor rendering preserving the original dog pixels.
   */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  border: 1px solid rgba(255, 255, 255, 0.06);
}

/* Label appears on hover only. Token id is decorative — the link
 * target tells the rest of the story. Kept small + monospace to match
 * the rest of the site's information density. */
.visuals-tile-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 3px 5px;
  font-family: "DM Mono", monospace;
  font-size: 10px;
  line-height: 1.2;
  letter-spacing: 0.04em;
  color: var(--ink);
  background: rgba(8, 7, 10, 0.78);
  opacity: 0;
  transition: opacity .12s ease;
  text-align: right;
  /* Reset because the parent .visuals-row sets font-size:0 to kill
   * inter-tile whitespace. */
}
.visuals-tile:hover .visuals-tile-label,
.visuals-tile:focus-visible .visuals-tile-label {
  opacity: 1;
}

/* Elaborate visuals get a quiet accent ring so they're visible in the
 * grid without screaming. Same five universal + three calendar-day
 * decorated visuals listed elsewhere on the site (#69, #214, #420,
 * #777, #1031, #1225, #1337, #8888). The on-chain rarity story is
 * already documented in /index.html#mythics — this is purely visual
 * scaffolding for the gallery view. */
.visuals-tile.is-decorated {
  border-color: rgba(245, 223, 136, 0.32);
}
.visuals-tile.is-decorated::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(245, 223, 136, 0.45);
  z-index: 1;
}
.visuals-tile.is-decorated:hover {
  border-color: var(--accent);
}

/* Skeleton state for unloaded atlas-backed tiles. */
.visuals-tile[data-state="pending"] {
  background: var(--panel-2);
}
.visuals-tile[data-state="pending"] .visuals-tile-art {
  visibility: hidden;
}

.visuals-noscript {
  padding: 32px 20px;
  margin: 24px 0;
  border: 1px dashed var(--soft-line);
  color: var(--ink-soft);
  font-size: 14px;
  line-height: 1.6;
  text-align: center;
}
.visuals-noscript code {
  font-size: 12px;
}

/* ── RESPONSIVE TUNING ──────────────────────────────────────────── */
/* The grid column count is computed in JS based on container width and
 * a minimum-tile-size constant. CSS here only adjusts ambient chrome
 * at narrow widths. */

@media (max-width: 880px) {
  .visuals-hero { padding: 28px 0 22px; }
  .visuals-types {
    grid-template-columns: 1fr;
    gap: 16px;
  }
  .visuals-controls {
    /* Nav loses height on smaller widths; sticky top follows. */
    top: 48px;
    padding: 10px 0;
  }
  .visuals-meta-hint {
    /* Keyboard hints aren't relevant on touch devices. The media query
     * isn't a perfect detector for touch, but the nav has already shed
     * its medium-density affordances by this width, so the savings on
     * a phone-or-narrow-window outweigh the rare desktop visitor with
     * an unusually narrow window. */
    display: none;
  }
}

@media (max-width: 640px) {
  .visuals-hero-title { line-height: 0.95; }
  .visuals-types { padding: 18px 0; }
  .visuals-types-copy h2 { font-size: 26px; }
  .visuals-type-grid { grid-template-columns: 1fr; }
  .visuals-controls {
    top: 44px;
    padding: 8px 0;
    gap: 8px 16px;
  }
  .visuals-jump-label { font-size: 11px; }
  #visualsJumpInput { width: 100px; font-size: 12px; padding: 7px 9px; }
  .visuals-jump-btn { padding: 7px 12px; font-size: 11px; }
  .visuals-meta { font-size: 11px; gap: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .visuals-tile,
  .visuals-tile-label {
    transition: none;
  }
  .visuals-tile:hover {
    transform: none;
  }
}
