/*
  HOMEPAGE — hero intro sequence + paper collage.

  SCROLL SEQUENCE OVERVIEW (see js/main.js for the actual math):
    1. Page loads; title fades on letter-by-letter.
    2. User scrolls through .hero-track (a tall scroll "runway").
       - Papers 1 & 2 (Art Direction, UI) rise from below with a parallax
         lag, then rotate into their resting angle.
       - At the same time, the title block slides up and STICKS near the
         top, behind the papers (z-index-wise).
       - Papers 3 & 4 (Motion, About) rise from below BEHIND papers 1 & 2,
         staggered slightly later.
    3. Scroll runway ends; everything is now at rest in its final layout
       and further scrolling has no effect (there's nothing below it —
       the homepage "loads all at once" per spec, this is just how it
       gets there).

  All four papers are clickable (see paper.js) and, once active/front,
  link to their project page via the anchor wrapping each paper.
*/

.hero-track {
  position: relative;
  height: 260vh; /* scroll "runway" for the intro sequence — see js/main.js SCROLL_RANGE */
}

.hero-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

/* ---------- full-bleed background paper ---------- */
/* Sits directly on top of the --color-bg-hero solid fill, behind every
   other hero element. Overfills the viewport by 12px on every side (so
   there's never a sliver of the solid color peeking past a texture edge
   at any viewport size) per spec.
   PLACEHOLDER ASSET: Sadie's real background-paper graphic hasn't synced
   to disk yet (checked repeatedly — see chat) — using an existing paper
   texture from the archived build as a stand-in. Swap the url() below
   for the real asset once it lands; nothing else needs to change. */

.hero-bg-paper {
  position: absolute;
  inset: -12px;
  z-index: var(--z-bg-paper);
  background-image: url('../assets/backgrounds/full_paper_clear.png'); /* TODO: swap for Sadie's asset */
  background-size: cover;
  background-position: center;
  opacity: 0.7;
  pointer-events: none;
}

/* ---------- giant background logo watermark ---------- */
/* Large-scale reuse of the nav's logo-placeholder shape (rounded box,
   "LO"/"GO" stacked) as a faint background mark — mostly cropped off the
   bottom-right edge by .hero-stage's own overflow:hidden. Sits behind the
   paper texture (--z-bg-logo-mark < --z-bg-paper) but in front of the
   solid --color-bg-hero fill, per spec. */

.hero-bg-logo {
  position: absolute;
  width: 48vw;
  aspect-ratio: 1;
  right: -26vw;
  bottom: -32vh;
  z-index: var(--z-bg-logo-mark);
  border: 2px solid var(--color-text-on-dark);
  border-radius: 8%;
  /* Part of the load sequence (js/main.js): fades in from 0 to its set
     20%, at the same time as the nav, rather than being visible instantly. */
  opacity: 0;
  transition: opacity 500ms ease;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.2em;
  padding: 0 4vw;
  pointer-events: none;
  font-family: var(--font-mono);
  text-transform: uppercase; /* all Syne Mono text is uppercase, per spec */
  font-size: 7vw;
  line-height: 1;
  color: var(--color-text-on-dark);
}

.hero-bg-logo.is-in {
  opacity: 0.2;
}

/* ---------- header role line ("I'm a... / [cycling role]") ---------- */
/* Used to be 4 decorative CSS shapes; now holds the text that used to
   live small in the nav next to the logo, sized up and split across this
   same space — "I'm a..." left-justified, the cycling role right-
   justified (see js/role-cycle.js for the letter-cycle animation).
   Fades + rises off-screen early in the scroll sequence, same as the
   original decorative graphic was meant to (--decor-fade/--decor-y set
   by js/main.js — scrubbed by scroll position, so scrolling back up
   brings it back exactly like everything else on this page). */

.hero-decor {
  position: absolute;
  /* top is set from JS (js/main.js positionHeroDecor) as the nav's own
     live bottom edge + a fixed gutter, so there's always real breathing
     room below the nav's bottom line no matter how nav height changes
     (padding/border tweaks there used to silently close this gap) — this
     is a fallback only, for the instant before that JS runs. */
  top: 140px;
  left: var(--grid-margin);
  right: var(--grid-margin);
  z-index: var(--z-bg-decoration);
  opacity: var(--decor-fade, 1);
  transform: translateY(var(--decor-y, 0px));
}

.hero-decor__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  color: var(--color-text-on-dark);
}

.hero-decor__im-a,
.hero-decor__role {
  white-space: nowrap;
}

.hero-decor__role {
  position: relative;
}

@media (max-width: 900px) {
  .hero-decor { display: none; }
}

/* ---------- title block ---------- */

.hero-title-wrap {
  position: absolute;
  left: 12px;
  bottom: 12px;
  right: var(--grid-margin);
  z-index: var(--z-title);
  /* Title now sits IN FRONT of the papers (see --z-title in tokens.css),
     and this box spans nearly the full stage width — without this it
     would swallow hover/clicks on any paper underneath it. */
  pointer-events: none;
  transform: translateY(var(--title-y, 0));
  transition: transform 80ms linear; /* smoothing only — the value itself is scroll-scrubbed */
}

.hero-title-wrap__eyebrow {
  display: flex;
  gap: 16px;
  align-items: center;
  margin-bottom: 8px;
  /* Part of the load sequence (js/main.js): starts invisible, fades to
     90% at the same t=0 as the nav, over the same ~500ms. */
  opacity: 0;
  transition: opacity 500ms ease;
}

.hero-title-wrap__eyebrow.is-in {
  opacity: 0.9;
}

/* Two segments — "SG PORTFOLIO SITE" then the date — each followed by
   its own divider line, per the mockup: a long line after the site
   label, then a short stub after the date. */
.hero-title-wrap__eyebrow-seg {
  display: flex;
  align-items: center;
  gap: 16px;
  white-space: nowrap;
}

.hero-title-wrap__eyebrow-seg::after {
  content: '';
  width: 160px;
  height: 1px;
  background: currentColor;
  opacity: 0.5;
}

.hero-title-wrap__eyebrow-seg--date::after {
  width: 40px;
}

/* BUG FIX: on a narrow phone, "SG PORTFOLIO SITE" + its 160px line +
   the date + ITS line add up to well past the available width — since
   flex items don't shrink below their own nowrap content by default,
   the row was silently overflowing past the viewport edge and getting
   clipped by html/body's overflow-x:clip (the date read as just "10"
   instead of the full "10.06.2026"). Shrinking both divider stubs down
   is enough for everything to fit on one line at this width. */
@media (max-width: 600px) {
  .hero-title-wrap__eyebrow-seg::after {
    width: 20px;
  }
}

.hero-title {
  color: var(--color-text-on-dark);
}

/* Letter-by-letter load-in, reusable anywhere (title lines, the "I'm a…"
   row) — spans are injected by main.js. Each user sets its own
   --letter-duration (title: slow + dramatic; "I'm a…": fast, matching
   the role-cycle text's own motion language) and toggles .is-in on
   itself once its letters should animate in. */
.letter-reveal .letter {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.4em);
  transition: opacity var(--letter-duration, 400ms) ease, transform var(--letter-duration, 400ms) ease;
}

.letter-reveal.is-in .letter {
  opacity: 1;
  transform: translateY(0);
}

.letter-reveal--fast {
  --letter-duration: 220ms;
}

/* ---------- cycling role text (js/role-cycle.js) ---------- */
/* Each word is a row of per-letter spans, clipped to one line-height so a
   letter can slide fully out of view instead of just fading in place.
   Outgoing letters slide up+out; incoming letters slide up+in from
   below — both staggered left to right (transition-delay set per-letter
   in JS), so the whole word reads as a quick left-to-right wipe rather
   than everything moving at once. */

.role-cycle__clip {
  display: inline-block;
  overflow: hidden;
  height: 1.3em;
  vertical-align: top;
}

.role-cycle__word {
  display: inline-block;
}

.role-cycle__letter {
  display: inline-block;
  opacity: 1;
  transform: translateY(0);
  transition: opacity 220ms ease, transform 220ms ease;
}

.role-cycle__letter.is-out {
  opacity: 0;
  transform: translateY(-110%);
}

.role-cycle__letter.is-in-start {
  opacity: 0;
  transform: translateY(110%);
}

/* ---------- papers stage ---------- */

.papers-stage {
  position: absolute;
  inset: 0;
  z-index: var(--z-paper-front);
}

/* Each paper's RESTING position/size/rotation is set here as a per-id
   rule — intentionally not inline styles, so the whole "floor plan" of
   the collage is readable and editable in one place.

   JS drives two SEPARATE, sequential sub-phases (not one blended value):
     --rise-y    1 -> 0  as the paper travels up from below the viewport
     --rotate-mix 0 -> 1 afterward, blending --rotate-start -> --rotate-rest
   i.e. the paper finishes rising before it starts rotating to rest,
   matching "come up, THEN rotate (and settle) into place" rather than
   both happening at once. See PHASE_AB/PHASE_CD sub-split in js/main.js. */

.paper--category,
.paper--about {
  width: clamp(220px, 22vw, 320px);
  aspect-ratio: 3 / 4;
  /* BUG FIX: 70vh of travel sounds like plenty, but combined with
     About's own position (top:52%, moved up from its original 60% in an
     earlier "peek-through" pass) and its own rotated bounding box, it
     left About's top edge ~12px INSIDE the viewport even at scroll
     position 0 — visible before any scrolling happened at all. 90vh
     gives enough clearance to actually fully clear the fold for every
     paper at this size/position, not just most of them. */
  transform:
    translate(-50%, -50%)
    translateY(calc(var(--rise-y, 0) * 90vh))
    rotate(calc(var(--rotate-start, 0deg) * (1 - var(--rotate-mix, 0)) + var(--rotate-rest, 0deg) * var(--rotate-mix, 0)));
}

/* The two big hero papers (Art Direction, UI) are kept toward the LEFT
   half of the stage and close together on purpose, so their combined
   footprint — huge once rotated 90° — doesn't span the full width and
   bury Motion/About entirely. Motion peeks out below-left, About peeks
   out clearly on the right, both still reachable to hover/click. */
/* BUG FIX: at left:24%, #paperArtDirection's rotated-90° footprint
   (width / 0.75, since rotation swaps its dimensions) clips past the
   LEFT edge of the viewport for almost the entire real-world desktop
   range — not just small screens. Its width is clamp(500px, 45vw, 760px)
   (see further down), which stays pinned at its 500px floor until the
   viewport is ~1111px wide, so a fixed 24% left was only ever safe above
   ~1389px wide. 38% keeps the whole rotated card on-screen all the way
   down to the 900px breakpoint below, where a separate, smaller clamp
   takes over. */
#paperArtDirection { left: 38%; top: calc(50% + 12px); z-index: var(--z-paper-front); }
#paperUI           { left: calc(70% - 24px); top: calc(56% + 12px); z-index: var(--z-paper-front); }
#paperMotion       { left: 18%; top: min(84%, calc(100% - clamp(220px, 22vw, 320px) * 2 / 3 - 48px)); --rotate-rest: -1deg; z-index: var(--z-paper-back); }
#paperAbout        { left: calc(86% - 24px); top: calc(42% - 36px); --rotate-rest: 3deg;  z-index: var(--z-paper-back); }

/* Motion's top: never lower than "bottom edge sits 48px above the stage
   bottom". Its height is width x 4/3 (3:4 aspect, width = the shared
   clamp(220px, 22vw, 320px) above), so its half-height is width x 2/3;
   the calc puts its bottom at 100% - 48px. min() keeps the original 84%
   whenever that's already higher. Update the clamp here if the shared
   width rule changes. */

/* BUG FIX: an ID selector (#paperMotion/#paperAbout above) always beats
   a two-class selector (.paper.is-active) on specificity alone, no
   matter the source order — so the generic "clicked paper comes to
   front" rule in components.css was silently losing to these papers'
   own resting z-index, and clicking one while it sat behind another
   paper never actually brought it forward. Matching ID+class selectors
   here win on specificity instead. */
#paperMotion.is-active,
#paperAbout.is-active {
  z-index: var(--z-paper-active);
}

#paperMotion       { --rotate-start: -18deg; }
#paperAbout        { --rotate-start: 22deg; }

.paper.is-active {
  transform:
    translate(-50%, -50%)
    translateY(calc(var(--rise-y, 0) * 90vh))
    rotate(0deg)
    scale(1.06);
}

/* ---------- the two "hero" papers (Art Direction, UI) — 4x size, 3-stage motion ---------- */
/* These two get their own transform entirely (js/main.js sets
   --paper-y-vh and --paper-rotate directly, already fully blended —
   see setUpRotateDrop) instead of the --rise-y/--rotate-mix system
   above: rise up PAST their resting spot, rotate a full 90°, THEN pull
   down into the actual resting position — three sequential beats, not
   the simpler two-stage rise-then-rotate the other two papers use. */

#paperArtDirection,
#paperUI {
  /* NOTE: a literal 4x of the original clamp(220px, 22vw, 320px) —
     880-1280px wide — completely whites out the viewport once rotated
     90°, since rotation swaps the card's on-screen footprint to its
     (taller) 4:3 landscape dimension (height = width / 0.75). Dialed
     back to ~2.3x so the rotated footprint still fits on screen; flagged
     to Sadie to confirm vs. bumping back up. */
  width: clamp(500px, 45vw, 760px);
  transform:
    translate(-50%, -50%)
    translateY(calc(var(--paper-y-vh, 100) * 1vh))
    rotate(var(--paper-rotate, 0deg));
}

#paperArtDirection.is-active,
#paperUI.is-active {
  transform:
    translate(-50%, -50%)
    translateY(calc(var(--paper-y-vh, 100) * 1vh))
    rotate(0deg)
    scale(1.06);
}

@media (max-width: 900px) {
  /* BUG FIX: these two papers end up rotated a full 90° at rest, which
     SWAPS their on-screen footprint — a box this wide (aspect 3:4) is
     actually (width / 0.75) tall, and once rotated that height becomes
     the visible on-screen WIDTH. The previous 85vw here only constrained
     the CSS width pre-rotation, so the rotated footprint was really
     ~113vw and overflowed the viewport edge to edge. Capping width at
     48vw (rotated footprint ≈ 64vw) keeps it comfortably inside the
     viewport instead — left:38% from the base rule above already gives
     enough horizontal margin at this size, no override needed here. */
  #paperArtDirection,
  #paperUI {
    width: clamp(200px, 48vw, 280px);
  }
}

/* UI is 1.2x smaller than Art Direction — same clamps as the shared
   #paperArtDirection/#paperUI width rules above (desktop + <=900px),
   divided by 1.2. Keep these in sync if those change. */
#paperUI { width: calc(clamp(500px, 45vw, 760px) / 1.2); }
@media (max-width: 900px) {
  #paperUI { width: calc(clamp(200px, 48vw, 280px) / 1.2); }
}

.paper__tape--tl { top: -9px; left: 24px; transform: rotate(-8deg); }
.paper__tape--tr { top: -9px; right: 24px; transform: rotate(6deg); }

/* ---------- real paper textures (assets/papers/ — per Sadie's reference wireframes) ---------- */
/* Each of the 4 papers gets its OWN distinct texture, per spec ("all
   should be different") — Art Direction: taped gray paper (the default
   below), UI: the hole-punched sheet, Motion: the small poloroid/brown
   piece, About: the blue taped paper (see the --about rule further down).
   paper_taped.png / paper_blue.png already have tape corners baked in,
   so the separate .paper__tape divs (plain CSS rectangles) would double
   up with the texture's own — hide them here instead of touching the
   shared HTML markup; the other two textures don't use tape at all. */

.paper--category .paper__inner {
  background-image: url('../assets/papers/paper_taped.png');
  background-size: cover;
  background-position: center;
}

#paperUI .paper__inner {
  background-image: url('../assets/papers/paper_hole_punched.png');
}

#paperMotion .paper__inner {
  background-image: url('../assets/papers/paper_poloroid.png');
}

.paper--category .paper__tape,
.paper--about .paper__tape {
  display: none;
}

/* ---------- paper contents: contact-sheet grid of photo-holder boxes ---------- */

.paper-grid {
  position: absolute;
  inset: 20px 20px 52px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 8px;
}

.paper-grid__box {
  /* Plain placeholder fill, not a texture — paper_poloroid.png is Motion's
     own OUTER paper now (see above), so reusing it here too would make
     Motion's frame and its inner boxes blend into one indistinct piece. */
  background: rgba(29, 29, 30, 0.08);
  border: 1px solid rgba(29, 29, 30, 0.15);
}

.paper-grid__box--wide {
  grid-column: 1 / -1;
}

/* ---------- about paper: bio card, not a photo grid ---------- */

/* About is JUST the blue-paper graphic (assets/papers/paper_blue.png, 496x357
   landscape, tape baked in) — shown whole, never cropped, with no
   backing behind it: no navy fill, no white .paper__inner base, no
   rectangular box-shadow (the PNG has transparent/torn edges, so a
   rectangle shadow or fill would show around it). Sized to fit the bio
   text rather than matching the other papers' 3:4 portrait size. These
   override the shared .paper--category/.paper--about width+aspect rule
   above by source order (same specificity, later wins). */
.paper--about {
  width: clamp(300px, 27vw, 380px);
  aspect-ratio: 496 / 357;
}

.paper--about .paper__inner {
  background: url('../assets/papers/paper_blue.png') center / 100% 100% no-repeat;
  /* extra right/top room: the graphic's right edge is a lighter strip
     with tape over the top-right corner — keep text on the blue. */
  padding: 26px 44px 24px 28px;
  color: var(--color-paper-white);
}

/* ID-level so it also beats components.css's hover/active/hover-front
   shadow rules (.paper:hover .paper__inner etc. out-specify a plain
   two-class selector) — no rectangular shadow in ANY state. */
#paperAbout .paper__inner { box-shadow: none; }

.paper-about__name {
  font-family: var(--font-mono);
  text-transform: uppercase; /* all Syne Mono text is uppercase, per spec */
  font-size: 14pt;
  margin-bottom: 12px;
}

.paper-about__list {
  list-style: none;
  font-family: var(--font-mono);
  text-transform: uppercase; /* all Syne Mono text is uppercase, per spec */
  font-size: 8pt;
  line-height: 1.8;
  opacity: 0.9;
}

.paper-about__link {
  position: absolute;
  right: 44px; /* right-justified, matching .paper__inner's right padding (clear of the graphic's light edge strip) */
  bottom: 26px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  text-transform: uppercase; /* all Syne Mono text is uppercase, per spec */
  font-size: 9pt;
  letter-spacing: 0.06em;
}

/* Hover: the arrow's shaft grows 6px — the link is right-anchored, so
   that growth pushes the text 6px LEFT on its own (no separate text
   translate needed) — and the text gains a hairline stroke so it reads
   bolder. The arrow is drawn in CSS (shaft + chevron) rather than the
   "→" glyph so its length can actually animate. */
.paper-about__link {
  -webkit-text-stroke: 0 currentColor;
  transition: -webkit-text-stroke-width var(--duration-base) var(--ease-smooth);
}

.paper-about__arrow {
  position: relative;
  display: inline-block;
  width: 14px;
  height: 1px;
  background: currentColor;
  transition: width var(--duration-base) var(--ease-smooth);
}

.paper-about__arrow::after {
  content: '';
  position: absolute;
  right: 0;
  top: 50%;
  width: 5px;
  height: 5px;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: translateY(-50%) rotate(45deg);
}

.paper-about__link:hover {
  -webkit-text-stroke-width: 0.5px;
}

.paper-about__link:hover .paper-about__arrow {
  width: 20px;
}

@media (max-width: 760px) {
  .paper--category, .paper--about {
    width: 68vw;
  }
  #paperArtDirection { left: 50%; top: 40%; }
  #paperUI           { left: 50%; top: 52%; }
  #paperMotion       { left: 50%; top: 64%; }
  #paperAbout        { left: 50%; top: 76%; }
}
