/* ==========================================================================
   projects-portfolio — iteration block
   --------------------------------------------------------------------------
   Dark sticky-pinned section, two columns:
     left  → static intro (eyebrow, headline, body, sublabel, red pill CTA)
     right → vertical list of 71 industry terms that scroll past a fixed
             centre line. The term sitting at centre is "active" — Milano-
             Red leader rule + full white text; everything else dims back
             to ~22 % white.
   Single rAF scroll listener drives translateY on the list and toggles
   an .is-active class on the centred item.
   PLAYGROUND MODE: raw values throughout (no token snap).
   ========================================================================== */

.pp {
  /* Inverted from the previous dark scene — paper background, dark ink
     for the left intro. The right-column scrolling list is rendered in
     Milano Red instead of dim white, so the type itself carries the
     brand colour and the leader rule reads as an underline rather than
     a separate accent. */
  --pp-bg:        #FFFFFF;
  --pp-ink:       #141A26;
  --pp-dim:       rgba(20, 26, 38, 0.62);
  --pp-divider:   rgba(20, 26, 38, 0.10);
  --pp-accent:    #C42800;
  /* Item row bumped from 66 → 84 so the larger labels (clamp 36–58)
     have proper editorial breathing room. */
  --pp-item-h:    84px;
  --pp-leader-w:  88px;

  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  background: var(--pp-bg);
  color: var(--pp-ink);
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* Tall scroll track — height determines how long the list cycles. 400 vh
   gives ≈ 4.5 vh per item across the first 80 % of scroll, with the last
   20 % reserved for the final image fade-in. Roughly 33 % faster pace
   than the previous 600 vh. */
.pp__track {
  position: relative;
  height: 400vh;
}

.pp__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  width: 100%;
  overflow: hidden;
  background: var(--pp-bg);
}

/* Diagonal topo pattern — 45° hairlines every 16 px at low alpha,
   masked with a 225° gradient so the dense corner is TOP-RIGHT
   (sharing the visual weight with the right-hand scrolling list)
   and fades out toward the bottom-left intro column. Sits behind
   all the content (z-index: 0). */
.pp__topo {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.16;
  background-image: repeating-linear-gradient(
    45deg,
    rgba(20, 26, 38, 0.9) 0,
    rgba(20, 26, 38, 0.9) 1px,
    transparent 1px,
    transparent 16px
  );
  -webkit-mask-image: linear-gradient(225deg, #000 0%, transparent 65%);
  mask-image: linear-gradient(225deg, #000 0%, transparent 65%);
  z-index: 0;
}

.pp__inner {
  position: relative;
  width: 100%;
  height: 100%;
  max-width: var(--teako-page-max);
  margin: 0 auto;
  padding: 0 var(--teako-page-gutter);
  display: grid;
  /* Swiss proportions — left intro takes one third, divider, right list
     takes two thirds. */
  grid-template-columns: 1fr 1px 2fr;
  gap: 64px;
  align-items: center;
}

/* ---------- Left column ---------- */

.pp__left {
  display: flex;
  flex-direction: column;
  gap: 32px;
  align-self: center;
  max-width: 520px;
}

.pp__eyebrow {
  font-family: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(20, 26, 38, 0.62);
}

.pp__heading {
  margin: 0;
  font-family: 'Geologica', system-ui, sans-serif;
  font-weight: 300;
  /* Reduced from clamp(48, 5.6vw, 84) so the headline flows naturally to
     2 lines instead of stacking each word on its own. */
  font-size: clamp(36px, 3.6vw, 54px);
  line-height: 1.05;
  letter-spacing: -0.022em;
  color: var(--pp-ink);
}

.pp__body {
  margin: 0;
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  color: rgba(20, 26, 38, 0.68);
  max-width: 38ch;
}

.pp__sublabel {
  font-family: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(20, 26, 38, 0.5);
}

/* CTA — outline-only, no glow. The big red fill + box-shadow on the
   previous pass was pulling focus from the list, which is the real
   centre of attention. Now: transparent bg, hairline white border,
   Milano-Red arrow as the only accent. Restrained, magazine-CTA quiet. */
.pp__cta {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 14px 24px;
  /* D2: one rectangular button system — radius 0 everywhere. */
  border-radius: 0;
  background: transparent;
  /* Dark hairline outline on the paper background. */
  border: 1px solid rgba(20, 26, 38, 0.22);
  font-family: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--pp-ink);
  text-decoration: none;
  transition: background 220ms ease,
              border-color 220ms ease,
              transform 240ms cubic-bezier(0.33, 1, 0.68, 1);
}
/* D2: hover is a tone change, not a lift — every button on the site behaves
   the same way. */
.pp__cta:hover {
  background: rgba(20, 26, 38, 0.04);
  border-color: rgba(20, 26, 38, 0.45);
}
.pp__cta svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--pp-accent);
  transition: transform 240ms cubic-bezier(0.33, 1, 0.68, 1);
}
.pp__cta:hover svg {
  transform: translateX(3px);
}

/* ---------- Vertical divider (the grid column itself) ---------- */

.pp__divider {
  width: 1px;
  height: 100%;
  background: var(--pp-divider);
  justify-self: center;
}

/* ---------- Right column: scrolling list ---------- */

.pp__right {
  position: relative;
  height: 100%;
  overflow: hidden;
}

.pp__list {
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  /* Initially positioned so item 0 sits at vertical centre. JS adjusts
     translateY each frame; this top: 50% gives us the reference plane. */
  top: 50%;
  left: 0;
  right: 0;
  /* Offset by half an item height so item 0's text baseline sits centred,
     then the JS translateY shifts the whole list upward as scroll
     progresses. */
  transform: translateY(calc(-1 * var(--pp-item-h) / 2 + var(--pp-list-shift, 0px)));
  will-change: transform;
  /* Soft top + bottom mask on the list itself so items fade as they enter
     and leave — moved off .pp__right so the final-image overlay below
     can stay fully crisp. */
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 18%,
    #000 82%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 18%,
    #000 82%,
    transparent 100%
  );
}

/* Final image overlay — sits on top of the list inside .pp__right and
   fades in across the last 20 % of scroll so the section concludes on a
   single dramatic visual rather than the list reaching its tail. The
   JS-written --pp-final-op drives both opacity and a small 16-px rise. */
.pp__final-image {
  position: absolute;
  inset: 16px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  pointer-events: none;
  opacity: var(--pp-final-op, 0);
  transition: opacity 220ms ease;
}
/* The plate is an image OR a muted autoplay loop (2026-08-20, client polish
   2: the standalone lead-video banner above the register was removed and its
   clip moved down here). Both are framed identically — the video simply
   replaces the still, so the closing beat is unchanged apart from motion. */
.pp__final-image img,
.pp__final-image video {
  width: 100%;
  height: 100%;
  max-height: 78vh;
  object-fit: cover;
  display: block;
  /* Hard corners — keeps the section's visual register consistent with
     the carousel cards and the wider Swiss grid. */
  border-radius: 0;
  transform: translateY(calc((1 - var(--pp-final-op, 0)) * 16px));
  transition: transform 280ms ease;
}
@media (prefers-reduced-motion: reduce) {
  /* The loop is decorative; hold the poster frame instead. */
  .pp__final-image video { transform: none; transition: none; }
}
/* As the image takes over, dim the list a touch so the two reads layer
   cleanly rather than fighting. */
.pp__list {
  opacity: calc(1 - var(--pp-final-op, 0) * 0.65);
  transition: opacity 220ms ease;
}

/* Right-column list — Milano Red on the paper background. Non-active
   items sit at 28 % red so they recede; the centred row is full
   Milano Red + the leader rule fades in. */
.pp__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 28px;
  height: var(--pp-item-h);
  padding-left: calc(var(--pp-leader-w) + 24px);
  color: rgba(196, 40, 0, 0.28);
  transition: color 320ms ease;
}

.pp__num {
  position: absolute;
  left: 0;
  width: 44px;
  font-family: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: rgba(20, 26, 38, 0.32);
  transition: color 320ms ease;
}

.pp__label {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  /* Scaled up so the list is the dominant element on the page (left intro
     is now visibly secondary). */
  font-size: clamp(36px, 3.8vw, 58px);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.015em;
}

/* Active row — solid Milano Red, leader rule fades in. */
.pp__item.is-active {
  color: var(--pp-accent);
}
.pp__item.is-active .pp__num {
  color: rgba(20, 26, 38, 0.78);
}

/* Leader rule — short ink hairline between the number and the label.
   Width is intentionally hard-coded (NOT --pp-leader-w) because that
   variable controls the LABEL's padding-left and we want the line to
   read as a quiet connector dash, not an underline beneath the text. */
.pp__leader {
  position: absolute;
  left: 48px;
  top: 50%;
  width: 32px;
  height: 1px;
  background: var(--pp-ink);
  transform: translateY(-50%) scaleX(0);
  transform-origin: left center;
  opacity: 0;
  transition: transform 360ms cubic-bezier(0.33, 1, 0.68, 1),
              opacity 280ms ease;
}
.pp__item.is-active .pp__leader {
  transform: translateY(-50%) scaleX(1);
  opacity: 1;
}

/* ---------- Responsive collapse ---------- */

@media (max-width: 960px) {
  .pp__inner {
    grid-template-columns: 1fr;
    gap: 32px;
    padding: 0 24px;
    align-content: center;
  }
  .pp__divider { display: none; }
  .pp__left { max-width: none; }
  .pp__right { height: 60vh; }
  .pp__item { padding-left: 60px; }
  .pp__leader { left: 0; width: 36px; }
}

/* Phones (client, 29 Aug 2026): the pinned stage centred a column taller
   than the screen, so the heading sat above the clip ("headings cut off").
   Stack from the top instead, clear the fixed nav, tighten the intro, and
   let the scrolling list take whatever height remains. */
@media (max-width: 760px) {
  /* Phones (client, 10 Sep 2026, third pass — "the scroll is not showing up on
     mobile"): the intro column was pushing the list window below the bottom of
     the pinned 100vh stage, so the names were off screen. On phones: eyebrow,
     heading, paragraph, CTA, then the list takes the rest of the stage, then
     the ending plate. Rows 52px with readable ink; the active name keeps the accent. */
  /* align-items: stretch is the whole fix — the base rule centres items, which in a
     column flexbox shrinks .pp__right to its content width; its list is absolutely
     positioned and has no width, so the column collapsed to 0px and clipped every name. */
  .pp__inner { display: flex; flex-direction: column; align-items: stretch; align-content: start; justify-content: flex-start; gap: 14px; height: 100%; box-sizing: border-box; padding-top: 72px; padding-bottom: 16px; }
  .pp__left { gap: 10px; flex: none; }
  .pp__heading { font-size: 26px; }
  .pp__sublabel, .pp__divider { display: none; }
  .pp__body { font-size: 14px; line-height: 1.45; }   /* the paragraph stays (client, 11 Sep) */
  .pp__right { flex: 1 1 auto; min-height: 0; height: auto; width: 100%; align-self: stretch; }
  /* Phones (client, 11 Sep): the ending autoplay plate stays — it REPLACES the list
     over the last 20% of the track (the list fades out as the plate fades in) instead
     of ghosting over the names. Window edges soft so no half rows peek out. */
  /* The plate sits crisp with a little air above and below it (client, 11 Sep):
     no mask on the column any more — the soft list edges are painted by the two
     paper gradients below, which sit above the list and under the plate. */
  .pp__final-image { display: block; inset: 20px 0; }
  /* Sequenced, not crossfaded (eyes check, 11 Sep: names ghosted through the sky
     at the midpoint). The list is gone over the first 30% of the ending beat, the
     plate rises over 25%–70%, so they never both sit at half strength. */
  .pp__list { opacity: clamp(0, 1 - (var(--pp-final-op, 0) - 0.1) / 0.25, 1); }   /* last row holds red, gone by 35% */
  .pp__final-image { opacity: clamp(0, (var(--pp-final-op, 0) - 0.3) / 0.4, 1); }   /* plate rises 30–70%, no blank beat */
  /* The base rule masks the list ITSELF (percent of its ~3700px height), which faded
     the first and last dozen rows to nothing — the register's final entry was never
     shown. Off on phones; the window edges are the 52px paper gradients (one row). */
  .pp__list { -webkit-mask-image: none; mask-image: none; }
  .pp__right::before, .pp__right::after { content: ''; position: absolute; left: 0; right: 0; height: 52px; z-index: 1; pointer-events: none; }
  .pp__right::before { top: 0; background: linear-gradient(to bottom, var(--pp-bg), transparent); }
  .pp__right::after { bottom: 0; background: linear-gradient(to top, var(--pp-bg), transparent); }
  .pp { --pp-item-h: 52px; }
  .pp__item { color: rgba(20, 26, 38, 0.46); }
  .pp__item .pp__num { color: rgba(20, 26, 38, 0.32); }
  .pp__item.is-active { color: var(--pp-accent); }
  .pp__item.is-active .pp__num { color: rgba(20, 26, 38, 0.78); }
  .pp__label { font-size: 30px; }
}

