/* ==========================================================================
   03 - BUILDS
   Every project's artwork is drawn live on a canvas, not screenshotted.
   ========================================================================== */

.builds .build + .build,
.builds .build + .build-pair,
.builds .build-pair + .build { margin-top: clamp(var(--sp-9), 7vw, var(--sp-12)); }

.build {
  display: grid;
  gap: clamp(var(--sp-5), 3.5vw, var(--sp-8));
  align-items: center;
}

@media (min-width: 900px) {
  .build--feature,
  .build--wide { grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr); }

  .build--feature .build__body,
  .build--wide   .build__body { order: -1; }

  .build--flip .build__body { order: 1; }
}

/* Artwork frame ----------------------------------------------------------- */

.build__art {
  position: relative;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-sunk);
  box-shadow: var(--shadow-1);
  transition: border-color var(--dur-3) var(--ease-out),
              box-shadow var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-out);
}

.build__art--tall { aspect-ratio: 4 / 3; }

.build__art canvas { width: 100%; height: 100%; display: block; }

/* Real screenshots. Both are wider than 16:10 and already sit on a
   near-black background, so letterboxing with `contain` is invisible and
   beats cropping the composition. */
.build__art--shot {
  aspect-ratio: 16 / 9;
  background: #0b0b0c;
}

.build__art--shot img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Scanlines would moiré against a screenshot's own pixel grid. */
.build__art--shot .build__scan { display: none; }

.build__art--schematic {
  background: #fff;
}

.build__art--gif {
  background: #0b0b0c;
}

.build__art--gif img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
}

/* --------------------------------------------------------------------------
   Sprite-sheet playback.

   The source was a 5.4 MB screen-recorded GIF. Sampling 36 consecutive
   frames onto one strip gets the same motion for 350 KB, and lets the
   frames be letterboxed to the card's ratio up front.

   Positioning maths: with background-size N*100%, a background-position-x
   of p% resolves to an offset of p/100 x W x (N-1). Stepping 0% to 100%
   with steps(N-1) therefore lands on exact frame boundaries (k x W). Using
   steps(N) would land on fractional offsets and tear every frame.
   -------------------------------------------------------------------------- */

.sprite {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  background-position: 0% 0;
  image-rendering: pixelated;
}

.sprite--gameboy {
  background-image: url('../img/gameboy-sprite.jpg');
  background-size: 3600% 100%;                 /* 36 frames */
  animation: sprite-36 1.75s steps(35) infinite;
}

@keyframes sprite-36 {
  from { background-position-x: 0%; }
  to   { background-position-x: 100%; }
}

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

/* While scrubbing, the animation has to be removed outright rather than
   paused: a running animation outranks inline styles in the cascade, so
   pausing it would leave the scrub position ignored. */
.sprite--gameboy.is-scrubbing { animation: none; cursor: ew-resize; }

.build__hint {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 2;
  padding: 5px 10px;
  border-radius: 99px;
  border: 1px solid color-mix(in srgb, var(--copper) 30%, transparent);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(4px);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--copper);
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}

.build__art:hover .build__hint,
.build__hint.is-on { opacity: 1; transform: none; }

/* Touch targets. The audit found the inline project links at 24px tall,
   well under the 44px minimum. Padding only where the pointer is coarse, so
   the desktop layout keeps its tight rhythm. */
@media (pointer: coarse) {
  .build__links .link,
  .builds__more .link,
  .foot .link { padding-block: 10px; }

  .build__links { gap: var(--sp-3) var(--sp-5); }
}

/* Corner ticks + vignette + scanlines: an instrument capture, not a screenshot. */
.build__scan {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    repeating-linear-gradient(180deg,
      transparent 0 2px,
      color-mix(in srgb, var(--bg) 22%, transparent) 2px 3px),
    radial-gradient(120% 100% at 50% 50%,
      transparent 45%,
      color-mix(in srgb, var(--bg) 55%, transparent) 100%);
}

.build__art::after {
  content: '';
  position: absolute;
  inset: 12px;
  pointer-events: none;
  border: 1px solid color-mix(in srgb, var(--copper) 16%, transparent);
  border-radius: var(--radius-sm);
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-out), inset var(--dur-3) var(--ease-out);
}

.build:hover .build__art {
  border-color: color-mix(in srgb, var(--copper) 40%, transparent);
  box-shadow: var(--shadow-2);
}

.build:hover .build__art::after { opacity: 1; inset: 9px; }

/* Body -------------------------------------------------------------------- */

.build__body { display: grid; gap: var(--sp-4); align-content: start; min-width: 0; }

.build__award {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  align-self: start;
  margin: 0;
  padding: 0.4rem 0.85rem 0.4rem 0.6rem;
  border: 1px solid color-mix(in srgb, var(--copper) 34%, transparent);
  border-radius: 99px;
  background: var(--copper-wash);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--copper);
}

.build__medal {
  display: grid;
  place-items: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--copper);
  color: #0a0b0e;
  font-size: 10px;
  line-height: 1;
}

.build__title {
  margin: 0;
  color: var(--ink);
  transition: color var(--dur-3) var(--ease-out);
}

.build:hover .build__title { color: var(--copper-hi); }

.build__sub { margin: 0; color: var(--ink-3); }

.build__body .prose { margin: 0; }

/* Pair -------------------------------------------------------------------- */

.build-pair { display: grid; gap: clamp(var(--sp-8), 5vw, var(--sp-9)); }

@media (min-width: 820px) {
  .build-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}

.build--tall { grid-template-columns: minmax(0, 1fr); align-items: start; gap: var(--sp-5); }

.build__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  margin: var(--sp-1) 0 0;
}

.builds__more {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  margin-top: clamp(var(--sp-8), 5vw, var(--sp-9));
}
