/* ---------- Projects page ---------- */
/* Page-specific styling only. Shares ../style.css for design tokens,
   reset, menu, footer, .contact-heading, and the base (non-stacked)
   .category/.grid/.tile rules this page falls back to under reduced
   motion — those stay in the shared stylesheet since they're also
   home's fallback. */

/* body's sitewide padding-bottom (clearance for the fixed footer) — and,
   confirmed by isolated testing, ANY trailing margin near the end of
   the stack too — feeds into the constraint position:sticky uses to
   cap how far an element can be offset. With either in place, the last
   stacked card can never fully reach its declared offset, permanently
   overlapping the card above it. Zeroed out here; the footer clearance
   is restored below as real HEIGHT on .stack-spacer instead, which
   (unlike padding or margin) doesn't feed into that constraint. */
body {
  padding-bottom: 0;
}

.page-intro {
  padding: var(--space-4) var(--container-pad) var(--space-4);
}

.category-intro {
  max-width: 60ch;
  font-size: clamp(1rem, 2vw, 1.25rem);
  font-weight: 500;
  color: var(--c-gray-100);
  margin-bottom: var(--space-3);
}

/* bigger images than the homepage teaser, full-bleed edge to edge with
   just a small gap between rows (columns stay flush). Breaks out of
   .category's left/right padding via negative margins so only the
   heading/intro keep an inset. Applies regardless of motion preference
   or viewport width. */
.grid {
  grid-template-columns: repeat(2, 1fr);
  column-gap: 0;
  row-gap: var(--space-1);
  margin: 0 calc(-1 * var(--container-pad));
  width: calc(100% + var(--container-pad) * 2);
}

/* restores the footer clearance body's own padding-bottom used to
   provide, as real height instead — see the note on body above */
.stack-spacer {
  height: var(--footer-h);
}

/* ---------- Category stack (scroll-pinned cards) ---------- */
/* Same scroll-pinned peek-card mechanic as the homepage's version (in
   the shared stylesheet), on both mobile and desktop. Unlike home, each
   category here holds big 4:5 tiles that don't fit the fixed peek-card
   height, so .grid is a scrollable window (overflow-y: auto) onto its
   own content instead of being squashed to fit — main.js's wheel
   handler lets wheel input scroll this inner window natively until it
   hits top/bottom, then hands off to the page-level stack scroll (see
   the comment on onWheel in main.js). Mobile only shows 2 tiles (see
   the max-width:700px block below), which fit their row exactly with
   no overflow, so this is a no-op there.

   The four .category sections are wrapped in .stack-container (see
   index.html), which must stay padding- and margin-free, plus a
   trailing .stack-spacer inside it (see above) — without a real
   element providing the old footer clearance, the constraint above
   bites regardless of which element carries it, in or out of this
   media query. */

@media (prefers-reduced-motion: no-preference) {
  :root {
    /* 74vh/6vh: the leftover (100 - height - step*3 = 8vh) is what
       --stack-inset below splits in half, so trimming height a bit off
       the earlier 80vh gives each pinned card a bit more top clearance
       when stuck (was ~9px at a 900px-tall viewport, now ~36px) and
       makes it engage sooner while scrolling, since a bigger inset is a
       closer/easier threshold to reach. Structural cap (74 + 6*3 = 92vh)
       still comfortably under the 100vh ceiling — this is a hard
       ceiling, not just a safety margin: for the *last* stacked card,
       inset + step*3 + height can never exceed 100vh no matter how much
       trailing space (.stack-spacer) follows it, since at the true end
       of the page the containing block's remaining space below the
       viewport is pinned at exactly (100vh - height) regardless of the
       page's total length (confirmed by testing — adding far more
       trailing space had zero effect on a version that violated this). */
    --stack-height: 74vh;
    --stack-step: 6vh;
    /* --stack-inset itself is declared once, in the shared style.css — its
       calc() re-resolves against whatever --stack-height/--stack-step are
       cascaded here, so it doesn't need to be repeated per override. */
  }

  .stack-container {
    padding: 0;
    margin: 0;
  }

  .category {
    height: var(--stack-height);
    padding: 0 var(--container-pad) 3vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--c-black);
    /* promotes the whole pinned card to its own stable compositor layer —
       without this, the two grid columns could repaint independently for
       a frame during the fast programmatic scroll that drives category
       transitions, visible as a brief seam down the column boundary right
       as the next category slides into place. */
    will-change: transform;
  }

  /* .category-heading's stacked styling is unscoped in the shared
     style.css now (only depends on --stack-step, which is scoped here) —
     no need to repeat it. */

  /* no room in the pinned card budget (sized for heading + grid only,
     matching home) — shows normally in the plain reduced-motion layout */
  .category-intro {
    display: none;
  }

  /* explicit row height instead of aspect-ratio-on-the-tile: inside a
     flex-constrained, overflow:auto grid, "auto" rows get compressed to
     fit the container's fixed height rather than sized to content,
     which made every tile overflow and overlap the row below it.
     62.5vw = (100vw / 2 columns) * 5/4 keeps each row matching the 4:5
     ratio at this column width regardless of the container's own
     height, so the grid can genuinely grow taller than its window. */
  .grid {
    flex: 1 1 auto;
    min-height: 0;
    grid-auto-rows: 62.5vw;
    overflow-y: auto;
  }

  .tile {
    aspect-ratio: auto;
    height: 100%;
  }

  #photography {
    position: sticky;
    top: var(--stack-inset);
    scroll-margin-top: var(--stack-inset);
    z-index: var(--z-stack-base);
  }

  #architectural-design {
    position: sticky;
    top: calc(var(--stack-inset) + var(--stack-step));
    scroll-margin-top: calc(var(--stack-inset) + var(--stack-step));
    z-index: calc(var(--z-stack-base) + 1);
  }

  #creative-sessions {
    position: sticky;
    top: calc(var(--stack-inset) + var(--stack-step) * 2);
    scroll-margin-top: calc(var(--stack-inset) + var(--stack-step) * 2);
    z-index: calc(var(--z-stack-base) + 2);
  }

  #travel-hotels {
    position: sticky;
    top: calc(var(--stack-inset) + var(--stack-step) * 3);
    scroll-margin-top: calc(var(--stack-inset) + var(--stack-step) * 3);
    z-index: calc(var(--z-stack-base) + 3);
  }
}

@media (max-width: 700px) {
  /* drops back to home's smaller stack budget on mobile — the desktop
     80vh/6vh card is sized for a bigger 2x3 grid, which doesn't fit a
     phone screen at any reasonable tile size, so mobile shows 2 photos
     per card (2x1) instead of 6 */
  :root {
    --stack-height: 60vh;
    --stack-step: 12vh;
  }

  .grid {
    grid-template-rows: 1fr;
    overflow-y: visible;
  }

  .tile:nth-child(n+3) {
    display: none;
  }
}

/* .tile's height:100% below depends on .category/.grid having a definite
   height, which only exists under the flex/sticky layout further up this
   file — that's gated to no-preference, so this needs to be too, or a
   reduced-motion mobile visitor gets 0-height (invisible) tiles: .tile's
   only child, .tile-media, is position:absolute and contributes no height
   of its own for the percentage to resolve against. */
@media (max-width: 700px) and (prefers-reduced-motion: no-preference) {
  /* mobile's single row of 2 tiles fits its budget exactly with no
     overflow — fill the row instead of the desktop 4:5 aspect-ratio,
     which would make tiles taller than the available space */
  .tile {
    aspect-ratio: auto;
    height: 100%;
  }
}
