:root {
  --nc-black: #0D0D0D;
  --nc-surface: #1A1A1A;
  --nc-surface-soft: rgba(255, 255, 255, 0.035);
  --nc-white: #FFFFFF;
  --nc-white-72: rgba(255, 255, 255, 0.72);
  --nc-white-56: rgba(255, 255, 255, 0.56);
  --nc-white-38: rgba(255, 255, 255, 0.38);
  --nc-border: rgba(255, 255, 255, 0.10);
  --nc-mint: #A8DADC;
  --nc-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --frame-x: clamp(24px, 5.8vw, 84px);
  --content-max: 1200px;
  --header-h: 104px;
  --footer-h: 56px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--nc-black); scroll-behavior: smooth; }
body {
  min-width: 320px;
  font-family: "Poppins", sans-serif;
  color: var(--nc-white);
  background: var(--nc-black);
  overflow-x: hidden;
}
a { color: inherit; text-decoration: none; }

.page {
  min-height: 100vh;
  background:
    radial-gradient(circle at 18% 8%, rgba(168, 218, 220, 0.075), transparent 28%),
    var(--nc-black);
}

/* PORTFOLIO VIEWPORT MODE */

.portfolio-page,
.portfolio-body {
  height: 100%;
  overflow: hidden;
}

.portfolio-viewport {
  height: 100svh;
  min-height: 100svh;
  overflow: hidden;
  display: grid;
  grid-template-rows: var(--header-h) minmax(0, 1fr) var(--footer-h);
}

.portfolio-main {
  min-height: 0;
  overflow: hidden;
}

/* HEADER */

.header {
  padding: 24px var(--frame-x) 0;
  position: relative;
  z-index: 20;
}

.portfolio-header {
  height: var(--header-h);
}

.header-inner {
  max-width: var(--content-max);
  min-height: 64px;
  margin: 0 auto;
  padding-bottom: 24px;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 32px;
  border-bottom: 1px solid var(--nc-border);
}

.logo {
  display: inline-flex;
  align-items: center;
  color: var(--nc-mint);
  font-size: 24px;
  font-weight: 500;
  line-height: 1;
}

.logo-mark {
  display: block;
  height: 34px;
  width: auto;
}

.quick-links { display: flex; flex-direction: column; gap: 6px; }

.quick-title {
  color: var(--nc-white);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
}

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  justify-content: flex-end;
  color: rgb(153, 153, 153);
  font-size: 14px;
  line-height: 18px;
}

.nav a { transition: color 240ms var(--nc-ease-out), opacity 240ms var(--nc-ease-out); }
.nav:hover a { opacity: 0.45; }
.nav a:hover { color: var(--nc-mint); opacity: 1; }

/* PORTFOLIO INDEX - NO NATIVE SCROLL */

.portfolio-stage {
  height: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 var(--frame-x);
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(430px, 1.08fr);
  gap: clamp(40px, 6vw, 92px);
  align-items: center;
  overflow: hidden;
}

.portfolio-copy {
  align-self: center;
  min-width: 0;
}

.eyebrow {
  margin-bottom: 18px;
  color: var(--nc-mint);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.portfolio-copy h1 {
  max-width: 690px;
  font-size: clamp(36px, 4.5vw, 64px);
  font-weight: 500;
  letter-spacing: clamp(-2.4px, -0.2vw, -1px);
  line-height: 1.05;
}

.hero-copy {
  max-width: 610px;
  margin-top: 24px;
  color: var(--nc-white-56);
  font-size: clamp(16px, 1.12vw, 19px);
  line-height: 1.56;
}

.portfolio-counter {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 34px;
  color: var(--nc-white);
  font-size: 14px;
  font-weight: 600;
}

.counter-line {
  width: 56px;
  height: 1px;
  background: var(--nc-white-38);
}

.project-controls {
  margin-top: 22px;
  display: grid;
  gap: 8px;
}

.project-dot {
  width: fit-content;
  max-width: 100%;
  border: 0;
  background: transparent;
  color: var(--nc-white-38);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: color 220ms var(--nc-ease-out), transform 220ms var(--nc-ease-out);
}

.project-dot span {
  display: inline-block;
  min-width: 28px;
  color: var(--nc-mint);
  opacity: 0.7;
}

.project-dot:hover,
.project-dot.is-active {
  color: var(--nc-white);
  transform: translateX(4px);
}

.scroll-hint {
  margin-top: 18px;
  color: var(--nc-white-38);
  font-size: 13px;
}

.portfolio-spiral {
  position: relative;
  height: 100%;
  perspective: 1400px;
  transform-style: preserve-3d;
  min-width: 0;
}

.project-slide {
  position: absolute;
  width: min(438px, 100%);
  right: clamp(0px, 2vw, 24px);
  top: 50%;
  transform:
    translateY(calc(-50% + (var(--offset) * 82px)))
    translateX(calc(var(--offset) * -54px))
    translateZ(calc(var(--abs) * -112px))
    rotateZ(calc(var(--offset) * -6deg))
    rotateY(calc(var(--offset) * 8deg))
    scale(calc(1 - (var(--abs) * 0.075)));
  opacity: calc(1 - (var(--abs) * 0.20));
  filter: blur(calc(var(--abs) * 0.8px));
  z-index: calc(60 - (var(--abs) * 7));
  cursor: pointer;
  outline: none;
  transition:
    transform 520ms var(--nc-ease-out),
    opacity 520ms var(--nc-ease-out),
    filter 520ms var(--nc-ease-out);
}

.project-slide.is-hovered {
  z-index: 100;
  opacity: 1;
  filter: none;
}

.project-slide:focus-visible .project-card {
  border-color: var(--nc-mint);
}

.project-card {
  min-height: min(572px, calc(100svh - var(--header-h) - var(--footer-h) - 44px));
  display: flex;
  flex-direction: column;
  border: 1px solid var(--nc-border);
  border-radius: 28px;
  overflow: hidden;
  background: var(--nc-surface);
  box-shadow: 0 30px 120px rgba(0, 0, 0, 0.28);
  transition:
    transform 280ms var(--nc-ease-out),
    border-color 280ms var(--nc-ease-out),
    box-shadow 280ms var(--nc-ease-out);
}

.project-slide.is-hovered .project-card {
  transform: translateY(-8px) scale(1.012);
  border-color: rgba(168, 218, 220, 0.48);
  box-shadow: 0 40px 140px rgba(168, 218, 220, 0.08);
}

.project-cover {
  min-height: 250px;
  background:
    var(--cover-image) center / cover no-repeat,
    linear-gradient(135deg, rgba(168, 218, 220, 0.18), rgba(255,255,255,0.03));
  border-bottom: 1px solid var(--nc-border);
  position: relative;
}

.cover-fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 32px;
  color: rgba(255, 255, 255, 0.18);
  font-size: clamp(30px, 4vw, 56px);
  font-weight: 600;
  letter-spacing: -0.06em;
  text-align: center;
}

.project-card-content {
  flex: 1;
  padding: 18px;
  display: flex;
  flex-direction: column;
}

.project-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  color: var(--nc-white-56);
  font-size: 10px;
  line-height: 1.2;
}

.project-type {
  color: var(--nc-mint);
  background: transparent;
  border: 1px solid var(--nc-mint);
  padding: 4px 8px;
  border-radius: 999px;
  font-weight: 600;
}

.project-nda-badge {
  color: #FFC1CC;
  background: transparent;
  border: 1px solid #FFC1CC;
  padding: 4px 8px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 10px;
}

.project-card h2 {
  margin-bottom: 8px;
  color: var(--nc-white);
  font-size: clamp(16px, 1.4vw, 20px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.2;
}

.project-card p {
  color: var(--nc-white-56);
  font-size: 12px;
  line-height: 1.5;
}

.tag-row {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.project-tag {
  padding: 4px 9px;
  border: 1px solid #FFC1CC;
  border-radius: 999px;
  color: #FFC1CC;
  font-size: 10px;
}

.project-credit {
  flex: 1;
  min-height: 0;
  margin-top: 6px;
  padding: 14px 0;
  border-top: 1px solid var(--nc-border);
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--nc-white-56);
  font-size: 14px;
  font-weight: 400;
}

.project-credit-icon-badge {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
}

.project-credit-icon {
  width: 16px;
  height: 16px;
  color: var(--nc-white);
}

.project-link-divider {
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid var(--nc-border);
}

.project-link-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 14px;
}

.project-link {
  color: var(--nc-mint);
  font-size: 12px;
  font-weight: 600;
}

/* CASE STUDY TEMPLATE */

.case-main {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: clamp(16px, 1.8vw, 24px) var(--frame-x) clamp(88px, 10vw, 140px);
}

.case-hero {
  /* Two fixed groups - .case-hero-top (tags/title/summary) and
     .case-hero-bottom (the "View online" link + image/video) - each with
     their own internal 24px gap, separated from each other by a fixed
     64px. On mobile/tablet this is just plain block flow with that
     rhythm; on desktop (see the min-width: 981px block below) the whole
     two-group stack is vertically centered inside a box that's exactly
     one viewport tall, so leftover space splits evenly above and below
     instead of stretching any of the fixed 24px/64px gaps. */
  display: flex;
  flex-direction: column;
  gap: 64px;
  padding: clamp(16px, 1.8vw, 24px) 0 clamp(24px, 3vw, 40px);
}

.case-hero-top,
.case-hero-bottom {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

@media (min-width: 981px) {
  .case-main {
    /* .case-hero takes over vertical spacing entirely at this width (see
       below) - after several rounds of guessing fixed px/vw/svh values
       for every individual gap and still overflowing on some real window
       shape, it now claims the exact remaining viewport height itself,
       so there's nothing left for .case-main to contribute above it. */
    padding-top: 0;
  }

  .case-hero {
    /* Exactly fills the rest of the first screen (100svh minus the real
       fixed-header height, exposed as --site-nav-height by
       compensateLayout() in site-chrome.js) - not a height guess.
       justify-content: center keeps the two fixed-gap groups centered as
       one block, so any extra room becomes equal padding above and below
       instead of inflating the 24px/64px rhythm. */
    height: calc(100svh - var(--site-nav-height, 88px));
    padding: 24px 0;
    justify-content: center;
  }
}

.case-kicker {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.case-type,
.case-tag {
  padding: 8px 12px;
  border-radius: 999px;
  font-size: 13px;
  line-height: 1;
}

/* Matched to the equivalent pills on the "My Work" card this case
   study is opened from (portfolio/js/render-portfolio.js's
   .project-type/.project-tag) - this page kept the old solid-fill
   mint treatment and a plain neutral tag border from before those
   cards were redesigned to mint/pink outlines, so the same project's
   info visually changed color between the card and its own case study. */
.case-type {
  background: transparent;
  color: var(--nc-mint);
  border: 1px solid var(--nc-mint);
  font-weight: 600;
}

.case-tag {
  border: 1px solid #FFC1CC;
  color: #FFC1CC;
}

.case-hero h1 {
  /* Widened from 940px - at common desktop widths this was wrapping to 3
     lines; the extra width brings most headlines down to 2, a real chunk
     of the height cut needed to fit the whole hero in one viewport. */
  max-width: 1120px;
  /* Trimmed from clamp(38px, 5vw, 72px) - one of a few cuts (alongside
     the removed dead top padding, the wider summary, and a shorter hero
     image below) aimed at fitting tags+title+summary+CTA+image in one
     viewport on a typical laptop screen. */
  font-size: clamp(34px, 4.4vw, 62px);
  font-weight: 500;
  letter-spacing: clamp(-3px, -0.24vw, -1.2px);
  line-height: 1.04;
}

.case-summary {
  /* Widened from 720px so longer descriptions wrap to fewer lines -
     shorter in height without shortening the actual text. Vertical
     spacing above/below is no longer a margin here - .case-hero (a flex
     column with justify-content: space-between) distributes it
     automatically along with every other gap in the hero. */
  max-width: 960px;
  color: var(--nc-white-56);
  font-size: clamp(17px, 1.28vw, 21px);
  line-height: 1.58;
}

/* Optional "View online" CTA (live_url in Supabase) - only renders when
   set, sits between the summary and the hero image/video. Same aesthetic
   as the "Learning Lab ↗︎" link in Home's About paragraph - plain mint
   text, no pill/background - not another loud button competing with the
   media right below it. Spacing above/below comes from .case-hero's
   flex distribution, same as every other child. */
.case-hero-cta a {
  display: inline-flex;
  align-items: center;
  color: var(--nc-mint);
  font-size: 16px;
  font-weight: 600;
}

.case-hero-image {
  /* Standard fixed size (960 wide x 3:1 ratio = 320 tall), the same on
     every project and every screen - it doesn't flex or shrink to make
     the hero fit a viewport; .case-hero centering the two fixed-gap
     groups (see above) handles fitting instead. flex-shrink still
     applies as a last resort on a genuinely too-short viewport (shrinks
     proportionally, aspect-ratio holds), but that's the edge case, not
     the normal path. Export image/video content at 3:1 (e.g. 1500x500)
     to fill this edge to edge with no bars. */
  width: min(960px, 100%);
  aspect-ratio: 3 / 1;
  flex-shrink: 1;
  min-height: 0;
  border-radius: 28px;
  overflow: hidden;
  border: 1px solid var(--nc-border);
  /* Plain black, not the mint-tinted gradient this used to have - if a
     video/image is ever a hair off 3:1 (rounding, or something not
     re-exported yet), whatever sliver shows should disappear into the
     page's own background instead of standing out as a visibly different
     tinted edge. */
  background: var(--hero-image, none) center / cover no-repeat, var(--nc-black);
}

/* object-fit: cover, not contain - now that the box has a real fixed
   ratio (matching what content should be exported at), cover fills it
   edge to edge with no gap even if an asset is a hair off from 3:1,
   instead of contain showing a thin gap on any mismatch. */
.case-hero-image video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.case-section {
  padding: clamp(48px, 6.5vw, 84px) 0;
  border-bottom: 1px solid var(--nc-border);
}

.case-section:last-child { border-bottom: none; }

.section-grid {
  display: grid;
  grid-template-columns: minmax(190px, 0.26fr) minmax(0, 0.74fr);
  gap: clamp(28px, 4.6vw, 64px);
}

.section-label {
  color: var(--nc-mint);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding-top: 0.42em;
}

.case-section h2 {
  max-width: 760px;
  font-size: clamp(28px, 3.1vw, 46px);
  font-weight: 500;
  letter-spacing: -0.04em;
  line-height: 1.09;
}

.case-text {
  margin-top: 18px;
  max-width: 620px;
  color: var(--nc-white-56);
  font-size: clamp(16px, 1.08vw, 18.5px);
  line-height: 1.68;
}

/* Pull-quote: optional per-section, editable in Supabase (context_quote/
   role_quote/outro_quote) - breaks up an otherwise single-paragraph
   section instead of leaving it as one unbroken block of body text. */
.case-quote {
  margin: 32px 0 0;
  padding-left: 24px;
  max-width: 560px;
  border-left: 2px solid var(--nc-mint);
  color: var(--nc-white);
  font-size: clamp(22px, 2.2vw, 30px);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.32;
}

.snapshot-grid,
.challenge-grid,
.impact-grid,
.decisions-grid {
  display: grid;
  gap: 14px;
}

.snapshot-grid {
  grid-template-columns: repeat(3, 1fr);
  margin-top: 0;
}

.challenge-grid,
.impact-grid,
.decisions-grid {
  grid-template-columns: repeat(2, 1fr);
  margin-top: 28px;
}

.info-card {
  padding: 22px;
  border: 1px solid var(--nc-border);
  border-radius: 20px;
  background: var(--nc-surface-soft);
}

.info-card h3 {
  margin-bottom: 8px;
  color: var(--nc-white);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* Optional per-impact-card headline figure (impact_*_stat in Supabase) -
   only set when the body copy already names a real, quantifiable result
   (e.g. "+10% cross-selling"), so most cards render exactly as before.
   Selector needs .info-card qualifying it (not just .info-card-stat
   alone): this is a <p>, so the plain class alone ties in specificity
   with ".info-card p" below and loses on source order, leaving the
   stat in the same muted gray as regular body text instead of mint. */
.info-card .info-card-stat {
  margin-bottom: 6px;
  color: var(--nc-mint);
  font-size: clamp(22px, 2vw, 28px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.info-card p,
.info-card li {
  color: var(--nc-white-56);
  font-size: 14.5px;
  line-height: 1.62;
}

.info-card ul { padding-left: 18px; }

/* Strategic Approach bullets rendered as completed checkboxes in brand
   mint, instead of plain disc markers - reads as "done", matching the
   list's own content (steps already taken). Scoped to this specific list
   (not .info-card ul generally) so Experience System's system_bullets
   keep their plain bullet look. */
/* h2 -> .info-card is a direct sibling only in this section (every other
   .case-section h2 is followed by .case-text or a grid, both of which
   already carry their own margin-top) - scoped this way instead of on
   .info-card generally so it doesn't affect Snapshot/Challenge/etc. */
.case-section h2 + .info-card {
  margin-top: 28px;
}

.approach-checklist {
  padding-left: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.approach-checklist li {
  position: relative;
  padding-left: 28px;
}

/* Outline checkbox, not filled - a mint stroke with a mint check inside,
   rather than a solid mint square with a black check cut into it. */
.approach-checklist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 3px;
  width: 16px;
  height: 16px;
  border-radius: 4px;
  border: 1.5px solid var(--nc-mint);
  background: transparent;
}

.approach-checklist li::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 6px;
  width: 5px;
  height: 9px;
  border: solid var(--nc-mint);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* Optional bullet list under a section's lead paragraph (system_bullets in
   Supabase) - reuses the .info-card look, just needs breathing room above
   since it follows a .case-text paragraph instead of sitting right after h2
   like "Strategic Approach" does. */
.case-bullets { margin-top: 24px; }

/* Real gap between bullet items, not just each <li>'s own line-height -
   .info-card ul had no spacing of its own between consecutive items.
   Matches the same 10px gap already used for Strategic Approach's
   checklist, so both bullet lists read consistently. */
.case-bullets ul {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.case-image-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 18px;
  margin-top: 32px;
}

/* Fixed aspect-ratio, not a min-height against a fluid width - the two
   columns' own width already drifted from ~0.96:1 to ~1.09:1 across
   ordinary desktop widths with the old min-height:320px, so no single
   exported square video/image could ever fill it exactly at every
   screen size (the same lesson as .case-hero-image's own fix). 1:1 is a
   clean, deliberate standard: export square content (e.g. 1000x1000) to
   fill this edge to edge with no crop or gap, on any screen size. */
.case-image {
  aspect-ratio: 1 / 1;
  border: 1px solid var(--nc-border);
  border-radius: 22px;
  overflow: hidden;
  background:
    var(--image) center / cover no-repeat,
    linear-gradient(135deg, rgba(168, 218, 220, 0.16), rgba(255,255,255,0.03));
}

.case-image video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Optional per-section media slot (currently only Strategic Challenge)
   for a supporting image, diagram or video - editable in Supabase like
   everything else here. Renders nothing until challenge_media_path is
   set, same "leave it blank" pattern as role_title. */
.case-media {
  margin-top: 32px;
  min-height: 320px;
  border: 1px solid var(--nc-border);
  border-radius: 22px;
  overflow: hidden;
  background:
    var(--image) center / cover no-repeat,
    linear-gradient(135deg, rgba(168, 218, 220, 0.16), rgba(255,255,255,0.03));
}

.case-media video {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 320px;
  object-fit: cover;
}

.case-cta {
  padding-top: 54px;
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

.button,
.button-secondary {
  min-height: 46px;
  padding: 0 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
}

.button { background: var(--nc-mint); color: var(--nc-black); }
.button-secondary { border: 1px solid var(--nc-border); color: var(--nc-white); }

.nda-form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 24px;
}

.nda-form input[type="password"] {
  min-height: 46px;
  padding: 0 18px;
  border: 1px solid var(--nc-border);
  border-radius: 999px;
  background: var(--nc-surface);
  color: var(--nc-white);
  font-family: inherit;
  font-size: 14px;
  min-width: 220px;
}

.nda-form input[type="password"]:focus {
  outline: none;
  border-color: var(--nc-mint);
}

.nda-form .button {
  border: none;
  cursor: pointer;
}

.nda-error {
  margin-top: 12px;
  color: #FFC1CC;
  font-size: 14px;
}

/* Matches Home's "Download Resume" button (css/styles.css .about-button)
   - white outline, transparent fill, bold - instead of a filled pill, so
   Contact me reads as the secondary action next to "Back to My Work". */
.button-contact {
  background: transparent;
  border: 2px solid var(--nc-white);
  color: var(--nc-white);
  font-weight: 700;
  transition:
    color 240ms var(--nc-ease-out),
    border-color 240ms var(--nc-ease-out),
    transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.button-contact:hover {
  color: var(--nc-mint);
  border-color: var(--nc-mint);
  transform: scale(1.04);
}

/* FOOTER */

.footer {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 40px var(--frame-x);
  border-top: 1px solid var(--nc-border);
  color: var(--nc-white-56);
  font-size: 14px;
  display: flex;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.portfolio-footer {
  height: var(--footer-h);
  padding-top: 0;
  padding-bottom: 0;
  align-items: center;
  border-top: 1px solid var(--nc-border);
}

/* RESPONSIVE */

@media (max-width: 980px) {
  .portfolio-page,
  .portfolio-body {
    height: auto;
    overflow: auto;
  }

  .portfolio-viewport {
    min-height: 100svh;
    height: auto;
    overflow: visible;
    display: block;
  }

  .portfolio-main {
    overflow: visible;
  }

  .portfolio-stage {
    height: auto;
    grid-template-columns: 1fr;
    padding-top: 88px;
    padding-bottom: 72px;
    overflow: visible;
  }

  .portfolio-spiral {
    display: grid;
    gap: 22px;
    perspective: none;
    height: auto;
  }

  .project-slide {
    position: relative;
    width: 100%;
    right: auto;
    top: auto;
    transform: none !important;
    opacity: 1 !important;
    filter: none !important;
  }

  .project-card { min-height: auto; }
  .project-cover { min-height: 280px; }

  .portfolio-footer {
    height: auto;
    padding: 40px var(--frame-x);
  }

  .section-grid {
    grid-template-columns: 1fr;
  }

  .snapshot-grid,
  .challenge-grid,
  .impact-grid,
  .decisions-grid,
  .case-image-grid {
    grid-template-columns: 1fr;
  }

  /* No mobile-specific override anymore - the base rule's aspect-ratio
     keeps the same 2.8:1 shape at every width on purpose, so mobile
     doesn't show the empty space around a mismatched fixed height. */
}

@media (max-width: 520px) {
  .header-inner { gap: 18px; }
  .quick-links { max-width: 260px; }
  .nav { font-size: 12px; gap: 4px 8px; }

  .portfolio-stage,
  .case-main {
    padding-left: 20px;
    padding-right: 20px;
  }

  .portfolio-copy h1 {
    font-size: clamp(36px, 11vw, 52px);
  }

  .project-card-content { padding: 22px; }

  .case-hero h1 {
    font-size: clamp(34px, 10vw, 48px);
  }

  .case-section h2 {
    font-size: clamp(25px, 7.5vw, 36px);
  }

  .case-summary,
  .case-text {
    font-size: 16px;
  }
}


/* V5 PORTFOLIO REFINEMENTS */

.active-project-label {
  margin-top: 22px;
  width: fit-content;
  max-width: 100%;
  display: inline-flex;
  align-items: baseline;
  gap: 14px;
  color: var(--nc-white);
  font-size: clamp(18px, 1.45vw, 24px);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.2;
  white-space: nowrap;
}

.active-project-label span {
  color: var(--nc-mint);
  font-weight: 600;
  min-width: 32px;
}

.scroll-hint {
  display: block;
  max-width: 440px;
  margin-top: 18px;
  color: var(--nc-white-38);
  font-size: 13px;
  line-height: 1.45;
}

.portfolio-footer {
  width: 100%;
  height: var(--footer-h);
  padding: 0 var(--frame-x);
  border-top: 1px solid var(--nc-border);
  display: flex;
  align-items: center;
}

.portfolio-footer .footer-inner {
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  color: var(--nc-white-56);
  font-size: 14px;
}

.portfolio-footer a {
  color: var(--nc-mint);
  transition: opacity 220ms var(--nc-ease-out), transform 220ms var(--nc-ease-out);
}

.portfolio-footer a:hover {
  opacity: 0.82;
  transform: translateY(-1px);
}

/* Hide old project controls if a cached HTML still contains them */
.project-controls,
.project-dot {
  display: none !important;
}

@media (max-width: 980px) {
  .portfolio-footer {
    height: auto;
    padding: 40px var(--frame-x);
  }

  .portfolio-footer .footer-inner {
    flex-wrap: wrap;
  }
}

@media (max-width: 520px) {
  .active-project-label {
    white-space: normal;
    font-size: 18px;
  }
}


/* V6 PROJECT INDICATOR REFINEMENT */

.portfolio-counter,
.counter-line {
  display: none !important;
}

.active-project-label {
  margin-top: 34px;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: var(--nc-white);
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  white-space: nowrap;
}

.active-project-label span {
  color: var(--nc-mint);
  font-size: 28px;
  font-weight: 700;
  min-width: auto;
}

@media (max-width: 520px) {
  .active-project-label,
  .active-project-label span {
    font-size: 22px;
  }
}


/* V7 PROJECT INDICATOR + INTERACTION CUE */

.active-project-label {
  margin-top: 26px;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--nc-white-56);
}

.active-project-label span {
  font-size: 14px;
  font-weight: 600;
  color: var(--nc-mint);
  min-width: 24px;
}

.scroll-hint-left {
  display: none;
}

.interaction-cue {
  position: absolute;
  left: clamp(0px, 1vw, 18px);
  bottom: clamp(36px, 7vh, 70px);
  z-index: 120;
  display: flex;
  align-items: center;
  gap: 14px;
  color: var(--nc-white-38);
  font-size: 12px;
  line-height: 1.25;
  pointer-events: none;
  user-select: none;
}

.gesture-icon {
  position: relative;
  width: 54px;
  height: 74px;
  display: grid;
  place-items: center;
  color: var(--nc-mint);
}

.gesture-hand {
  position: relative;
  z-index: 2;
  display: inline-block;
  font-size: 34px;
  line-height: 1;
  transform: rotate(-18deg);
  animation: gestureSwipe 1800ms var(--nc-ease-out) infinite;
  transform-origin: center bottom;
}

.gesture-arrow {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  color: var(--nc-white-56);
  font-size: 24px;
  font-weight: 500;
  line-height: 1;
}

.arrow-up {
  top: 0;
  animation: arrowUpPulse 1800ms var(--nc-ease-out) infinite;
}

.arrow-down {
  bottom: 0;
  animation: arrowDownPulse 1800ms var(--nc-ease-out) infinite;
}

@keyframes gestureSwipe {
  0%, 100% {
    transform: translateY(9px) rotate(-18deg);
    opacity: 0.66;
  }

  45% {
    transform: translateY(-9px) rotate(-18deg);
    opacity: 1;
  }
}

@keyframes arrowUpPulse {
  0%, 100% {
    transform: translate(-50%, 5px);
    opacity: 0.34;
  }

  45% {
    transform: translate(-50%, -2px);
    opacity: 0.88;
  }
}

@keyframes arrowDownPulse {
  0%, 100% {
    transform: translate(-50%, -5px);
    opacity: 0.34;
  }

  45% {
    transform: translate(-50%, 2px);
    opacity: 0.88;
  }
}

@media (max-width: 980px) {
  .scroll-hint-left {
    display: block;
  }

  .interaction-cue {
    display: none;
  }
}

@media (max-width: 520px) {
  .active-project-label,
  .active-project-label span {
    font-size: 13px;
  }
}


/* V8 REMOVE PORTFOLIO FOOTER */

.portfolio-viewport {
  grid-template-rows: var(--header-h) minmax(0, 1fr);
}

.portfolio-footer {
  display: none !important;
}

.portfolio-stage {
  padding-bottom: clamp(18px, 3vh, 32px);
}


/* V9 CLEAR SCROLL CUE */

.interaction-cue {
  position: absolute;
  left: auto;
  right: clamp(0px, 1.2vw, 18px);
  bottom: clamp(22px, 4.5vh, 48px);
  z-index: 140;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  border: 1px solid rgba(168, 218, 220, 0.28);
  border-radius: 999px;
  background: rgba(13, 13, 13, 0.72);
  backdrop-filter: blur(12px);
  color: var(--nc-white-56);
  pointer-events: none;
  user-select: none;
}

.scroll-motion-icon {
  position: relative;
  width: 24px;
  height: 42px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
}

.motion-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--nc-mint);
  animation: scrollDotMove 1600ms var(--nc-ease-out) infinite;
}

.motion-arrow {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  color: var(--nc-mint);
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
}

.motion-arrow-up {
  top: 0;
  animation: arrowFadeUp 1600ms var(--nc-ease-out) infinite;
}

.motion-arrow-down {
  bottom: 0;
  animation: arrowFadeDown 1600ms var(--nc-ease-out) infinite;
}

.interaction-copy {
  display: flex;
  flex-direction: column;
  gap: 1px;
  white-space: nowrap;
}

.interaction-copy strong {
  color: var(--nc-white);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.15;
}

.interaction-copy span {
  color: var(--nc-white-38);
  font-size: 11px;
  line-height: 1.15;
}

@keyframes scrollDotMove {
  0%, 100% {
    transform: translateY(-8px);
    opacity: 0.55;
  }

  50% {
    transform: translateY(8px);
    opacity: 1;
  }
}

@keyframes arrowFadeUp {
  0%, 100% {
    opacity: 0.35;
    transform: translate(-50%, 2px);
  }

  50% {
    opacity: 0.95;
    transform: translate(-50%, -2px);
  }
}

@keyframes arrowFadeDown {
  0%, 100% {
    opacity: 0.35;
    transform: translate(-50%, -2px);
  }

  50% {
    opacity: 0.95;
    transform: translate(-50%, 2px);
  }
}

@media (max-width: 980px) {
  .interaction-cue {
    display: none;
  }
}


/* V10 FILTERS + ONBOARDING MOTION */

.portfolio-filters {
  margin-top: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  max-width: 520px;
}

.filter-pill {
  min-height: 28px;
  padding: 0 11px;
  border: 1px solid var(--nc-border);
  border-radius: 999px;
  background: transparent;
  color: var(--nc-white-38);
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  transition:
    color 220ms var(--nc-ease-out),
    border-color 220ms var(--nc-ease-out),
    background 220ms var(--nc-ease-out),
    transform 220ms var(--nc-ease-out);
}

.filter-pill:hover,
.filter-pill.is-active {
  color: var(--nc-black);
  border-color: var(--nc-mint);
  background: var(--nc-mint);
  transform: translateY(-1px);
}

.is-teaching-scroll .interaction-cue {
  animation: cueTeachPulse 1900ms var(--nc-ease-out) 1;
}

.is-teaching-scroll .project-slide.is-active {
  animation: activeCardTeach 1900ms var(--nc-ease-out) 1;
}

.is-teaching-scroll .project-slide.is-active + .project-slide {
  animation: nextCardTeach 1900ms var(--nc-ease-out) 1;
}

@keyframes cueTeachPulse {
  0%, 100% {
    transform: translateY(0) scale(1);
    border-color: rgba(168, 218, 220, 0.28);
  }

  35% {
    transform: translateY(-8px) scale(1.04);
    border-color: rgba(168, 218, 220, 0.78);
  }

  70% {
    transform: translateY(5px) scale(1.02);
    border-color: rgba(168, 218, 220, 0.48);
  }
}

@keyframes activeCardTeach {
  0%, 100% {
    transform:
      translateY(calc(-50% + (var(--offset) * 82px)))
      translateX(calc(var(--offset) * -54px))
      translateZ(calc(var(--abs) * -112px))
      rotateZ(calc(var(--offset) * -6deg))
      rotateY(calc(var(--offset) * 8deg))
      scale(calc(1 - (var(--abs) * 0.075)));
  }

  42% {
    transform:
      translateY(calc(-50% - 18px + (var(--offset) * 82px)))
      translateX(calc(-12px + (var(--offset) * -54px)))
      translateZ(calc(var(--abs) * -112px))
      rotateZ(calc(-2deg + (var(--offset) * -6deg)))
      rotateY(calc(var(--offset) * 8deg))
      scale(calc(1.015 - (var(--abs) * 0.075)));
  }
}

@keyframes nextCardTeach {
  0%, 100% {
    opacity: calc(1 - (var(--abs) * 0.20));
  }

  42% {
    opacity: 0.92;
  }
}

@media (max-width: 980px) {
  .portfolio-filters {
    margin-top: 24px;
  }
}


/* V11 MINIMAL RIGHT-EDGE SCROLL INDICATOR */

.interaction-cue {
  position: absolute;
  right: clamp(-2px, 0.6vw, 10px);
  top: 50%;
  bottom: auto;
  transform: translateY(-50%);
  z-index: 140;
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  backdrop-filter: none;
  color: var(--nc-white-56);
  pointer-events: none;
  user-select: none;
}

.minimal-scroll-indicator {
  width: 18px;
  height: 112px;
  display: grid;
  grid-template-rows: auto 1fr auto 1fr auto;
  justify-items: center;
  align-items: center;
  color: var(--nc-mint);
}

.indicator-arrow {
  color: var(--nc-mint);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  opacity: 0.84;
}

.indicator-line {
  width: 1px;
  height: 100%;
  min-height: 22px;
  background: rgba(168, 218, 220, 0.36);
}

.indicator-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--nc-mint);
  box-shadow: 0 0 18px rgba(168, 218, 220, 0.32);
  animation: minimalDotTravel 1700ms var(--nc-ease-out) infinite;
}

.interaction-copy {
  display: flex;
  flex-direction: column;
  gap: 3px;
  white-space: nowrap;
  opacity: 0.78;
}

.interaction-copy strong {
  color: var(--nc-white);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.15;
}

.interaction-copy span {
  color: var(--nc-white-38);
  font-size: 10.5px;
  line-height: 1.15;
}

.is-teaching-scroll .interaction-cue {
  animation: minimalCueIntro 1900ms var(--nc-ease-out) 1;
}

@keyframes minimalDotTravel {
  0%, 100% {
    transform: translateY(-13px);
    opacity: 0.48;
  }

  50% {
    transform: translateY(13px);
    opacity: 1;
  }
}

@keyframes minimalCueIntro {
  0% {
    opacity: 0;
    transform: translateY(-50%) translateX(16px);
  }

  28% {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
  }

  54% {
    transform: translateY(calc(-50% - 7px)) translateX(0);
  }

  78% {
    transform: translateY(calc(-50% + 5px)) translateX(0);
  }

  100% {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
  }
}

/* Disable previous cue internals if cached */
.scroll-motion-icon,
.gesture-icon,
.motion-arrow,
.motion-dot,
.gesture-hand,
.gesture-arrow {
  display: none !important;
}

@media (max-width: 980px) {
  .interaction-cue {
    display: none;
  }
}


/* V12 RIGHT-SIDE FILTERS + NON-OVERLAY SCROLL CUE */

.portfolio-stage {
  grid-template-columns: minmax(0, 0.86fr) minmax(520px, 1.14fr);
  gap: clamp(48px, 7vw, 112px);
}

.portfolio-right {
  position: relative;
  height: 100%;
  min-width: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: stretch;
}

.portfolio-right .portfolio-filters {
  position: relative;
  z-index: 160;
  align-self: start;
  justify-self: end;
  margin: 0 0 14px 0;
  padding-right: clamp(78px, 8vw, 142px);
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  max-width: min(560px, 100%);
}

.portfolio-right .filter-pill {
  min-height: 28px;
  padding: 0 11px;
  border: 1px solid var(--nc-border);
  border-radius: 999px;
  background: rgba(13, 13, 13, 0.34);
  color: var(--nc-white-38);
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  backdrop-filter: blur(8px);
  transition:
    color 220ms var(--nc-ease-out),
    border-color 220ms var(--nc-ease-out),
    background 220ms var(--nc-ease-out),
    transform 220ms var(--nc-ease-out);
}

.portfolio-right .filter-pill:hover,
.portfolio-right .filter-pill.is-active {
  color: var(--nc-black);
  border-color: var(--nc-mint);
  background: var(--nc-mint);
  transform: translateY(-1px);
}

.portfolio-spiral {
  position: relative;
  width: 100%;
  height: 100%;
  padding-right: clamp(96px, 9vw, 150px);
  overflow: visible;
}

.project-slide {
  right: clamp(96px, 9vw, 150px);
}

.interaction-cue {
  right: 0;
  top: 52%;
  transform: translateY(-50%);
  z-index: 130;
  gap: 12px;
  max-width: clamp(92px, 9vw, 132px);
}

.interaction-copy {
  white-space: normal;
  width: clamp(78px, 8vw, 112px);
  opacity: 0.72;
}

.interaction-copy strong {
  display: block;
  font-size: 10.5px;
  line-height: 1.18;
}

.interaction-copy span {
  display: block;
  margin-top: 3px;
  font-size: 10px;
  line-height: 1.18;
}

.minimal-scroll-indicator {
  height: 104px;
}

.is-teaching-scroll .interaction-cue {
  animation: minimalCueIntroRightSpace 1900ms var(--nc-ease-out) 1;
}

@keyframes minimalCueIntroRightSpace {
  0% {
    opacity: 0;
    transform: translateY(-50%) translateX(10px);
  }

  28% {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
  }

  54% {
    transform: translateY(calc(-50% - 6px)) translateX(0);
  }

  78% {
    transform: translateY(calc(-50% + 4px)) translateX(0);
  }

  100% {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
  }
}

@media (max-width: 1180px) {
  .portfolio-stage {
    grid-template-columns: minmax(0, 0.82fr) minmax(470px, 1.18fr);
    gap: clamp(36px, 5vw, 72px);
  }

  .portfolio-spiral {
    padding-right: 112px;
  }

  .project-slide {
    right: 112px;
  }

  .portfolio-right .portfolio-filters {
    padding-right: 112px;
  }
}

@media (max-width: 980px) {
  .portfolio-stage {
    grid-template-columns: 1fr;
  }

  .portfolio-right {
    height: auto;
    display: block;
  }

  .portfolio-right .portfolio-filters {
    justify-content: flex-start;
    padding-right: 0;
    margin: 24px 0 22px;
  }

  .portfolio-spiral {
    padding-right: 0;
    overflow: visible;
  }

  .project-slide {
    right: auto;
  }

  .interaction-cue {
    display: none;
  }
}


/* V13 LAYOUT REBALANCE — MORE AIR, LESS CENTER CROWDING */

/* Give the portfolio a wider stage so content breathes like the home */
:root {
  --portfolio-max: 1480px;
}

/* Header keeps the same line logic but can use the wider portfolio frame */
.portfolio-page .header-inner,
.portfolio-body .header-inner {
  max-width: var(--portfolio-max);
}

/* Main stage: wider, less centered cluster, more intentional left/right spacing */
.portfolio-stage {
  max-width: var(--portfolio-max);
  grid-template-columns: minmax(300px, 0.72fr) minmax(680px, 1.28fr);
  gap: clamp(72px, 8vw, 150px);
  padding-left: clamp(52px, 7vw, 112px);
  padding-right: clamp(52px, 7vw, 112px);
}

/* Left copy: reduce visual weight and keep it from becoming a second hero */
.portfolio-copy {
  max-width: 440px;
  transform: translateY(10px);
}

.portfolio-copy h1 {
  max-width: 420px;
  font-size: clamp(36px, 3.9vw, 58px);
  font-weight: 500;
  letter-spacing: clamp(-2.2px, -0.18vw, -0.8px);
  line-height: 1.05;
}

.hero-copy {
  max-width: 390px;
  margin-top: 24px;
  font-size: clamp(15px, 1vw, 18px);
  line-height: 1.58;
}

.active-project-label {
  margin-top: 28px;
}

/* Right area: filters sit above cards, aligned with stack, not floating in the center */
.portfolio-right {
  align-self: stretch;
  width: 100%;
  max-width: 830px;
  justify-self: end;
}

.portfolio-right .portfolio-filters {
  justify-self: start;
  padding-right: 0;
  margin: 0 0 18px clamp(10px, 2vw, 32px);
  max-width: 650px;
}

/* Cards: slightly smaller and moved right, cue uses the reserved outside lane */
.portfolio-spiral {
  padding-right: 118px;
  min-height: 0;
}

.project-slide {
  width: min(390px, 100%);
  right: 118px;
}

.project-card {
  min-height: min(522px, calc(100svh - var(--header-h) - 78px));
  border-radius: 24px;
}

.project-cover {
  min-height: 220px;
}

.project-card-content {
  padding: 22px;
}

.project-card h2 {
  font-size: clamp(27px, 2.4vw, 36px);
}

.project-card p {
  font-size: 14px;
  line-height: 1.5;
}

.project-meta {
  /* margin-bottom removed: it predates .project-card-top's grid layout
     (image beside title), where .project-meta is a grid item bottom-
     aligned against the cover image - the margin sat inside that
     alignment box, so the visible text stopped 16px short of actually
     reaching the image's bottom edge, leaving a gap under a short
     (one-line) title even though align-items:end was working correctly. */
  font-size: 11px;
}

.project-tag {
  padding: 4px 9px;
  font-size: 10px;
}

.project-link-row {
  padding-top: 18px;
}

/* Right-side scroll cue: outside card zone, more discrete */
.interaction-cue {
  right: 0;
  top: 54%;
  gap: 10px;
  max-width: 104px;
  opacity: 0.78;
}

.interaction-copy {
  width: 76px;
}

.interaction-copy strong {
  font-size: 10px;
}

.interaction-copy span {
  font-size: 9.6px;
}

.minimal-scroll-indicator {
  height: 94px;
}

.indicator-arrow {
  font-size: 12px;
}

/* Keep background cards visible but less heavy */
.project-slide:not(.is-hovered):not(.is-active) {
  opacity: calc(0.88 - (var(--abs) * 0.18));
}

/* Large desktop refinement */
@media (min-width: 1500px) {
  .portfolio-stage {
    padding-left: clamp(72px, 8vw, 130px);
    padding-right: clamp(72px, 8vw, 130px);
  }

  .portfolio-right {
    max-width: 900px;
  }

  .project-slide {
    width: 410px;
    right: 128px;
  }

  .portfolio-spiral {
    padding-right: 128px;
  }
}

/* Medium desktop: prevent crowding before tablet breakpoint */
@media (max-width: 1280px) {
  .portfolio-stage {
    grid-template-columns: minmax(260px, 0.68fr) minmax(600px, 1.32fr);
    gap: clamp(48px, 5vw, 84px);
    padding-left: clamp(36px, 5vw, 72px);
    padding-right: clamp(36px, 5vw, 72px);
  }

  .portfolio-copy h1 {
    font-size: clamp(34px, 3.7vw, 52px);
  }

  .hero-copy {
    max-width: 360px;
  }

  .project-slide {
    width: 372px;
    right: 104px;
  }

  .portfolio-spiral {
    padding-right: 104px;
  }

  .interaction-cue {
    right: 0;
  }
}

/* Override previous 1180 breakpoint from v12 if present */
@media (max-width: 1180px) {
  .portfolio-stage {
    grid-template-columns: minmax(240px, 0.68fr) minmax(560px, 1.32fr);
    gap: 44px;
  }

  .portfolio-right .portfolio-filters {
    padding-right: 0;
    margin-left: 18px;
  }

  .portfolio-spiral {
    padding-right: 96px;
  }

  .project-slide {
    right: 96px;
    width: 350px;
  }

  .project-card {
    min-height: min(500px, calc(100svh - var(--header-h) - 78px));
  }

  .project-cover {
    min-height: 205px;
  }
}

/* Mobile/tablet keeps the normal stacked behavior */
@media (max-width: 980px) {
  .portfolio-stage {
    max-width: var(--content-max);
    grid-template-columns: 1fr;
    padding-left: 20px;
    padding-right: 20px;
    gap: 28px;
  }

  .portfolio-copy {
    max-width: none;
    transform: none;
  }

  .portfolio-copy h1,
  .hero-copy {
    max-width: 100%;
  }

  .portfolio-right {
    max-width: none;
    justify-self: stretch;
  }

  .portfolio-right .portfolio-filters {
    margin: 24px 0 22px;
  }

  .portfolio-spiral {
    padding-right: 0;
  }

  .project-slide {
    width: 100%;
    right: auto;
  }

  .project-card {
    min-height: auto;
  }

  .project-cover {
    min-height: 280px;
  }
}


/* V14 DESKTOP ALIGNMENT FIXES */

/* 1. Header returns to the home proportions */
.portfolio-page .header-inner,
.portfolio-body .header-inner,
.header-inner {
  max-width: var(--content-max);
}

/* Keep the portfolio stage spacious without expanding the header */
.portfolio-stage {
  max-width: 1440px;
  grid-template-columns: minmax(320px, 0.78fr) minmax(620px, 1.22fr);
  gap: clamp(76px, 8vw, 136px);
  padding-left: clamp(64px, 7vw, 104px);
  padding-right: clamp(64px, 7vw, 104px);
}

/* Left block keeps enough presence but stops pushing the whole layout into the center */
.portfolio-copy {
  max-width: 470px;
}

.portfolio-copy h1 {
  max-width: 450px;
  font-size: clamp(38px, 4vw, 60px);
  line-height: 1.04;
}

.hero-copy {
  max-width: 420px;
}

/* 3. Filters centered with the card stack, not thrown to the side */
.portfolio-right {
  max-width: 760px;
  justify-self: end;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

.portfolio-right .portfolio-filters {
  width: min(390px, 100%);
  justify-self: start;
  margin: 0 0 14px 0;
  padding-right: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 2 + 4. Card area reserves clean right whitespace for scroll animation.
   The stack is clipped vertically so ghost cards don't continue below the active card. */
.portfolio-spiral {
  width: 100%;
  height: min(560px, calc(100svh - var(--header-h) - 86px));
  align-self: center;
  padding-right: 116px;
  overflow: hidden;
  border-radius: 34px;
}

.project-slide {
  width: min(390px, 100%);
  right: 116px;
}

/* Cards are sized to fit the clipped stage */
.project-card {
  min-height: min(510px, calc(100svh - var(--header-h) - 122px));
  border-radius: 24px;
}

.project-cover {
  min-height: 210px;
}

.project-card-content {
  padding: 22px;
}

.project-card h2 {
  font-size: clamp(28px, 2.45vw, 38px);
}

.project-card p {
  font-size: 14px;
  line-height: 1.5;
}

/* The scroll indicator lives in the right whitespace, never on top of the card */
.interaction-cue {
  right: 6px;
  top: 50%;
  max-width: 96px;
  gap: 9px;
}

.interaction-copy {
  width: 72px;
}

/* Avoid old wider-header rule from v13 taking over */
@media (min-width: 1500px) {
  .portfolio-page .header-inner,
  .portfolio-body .header-inner,
  .header-inner {
    max-width: var(--content-max);
  }

  .portfolio-stage {
    max-width: 1460px;
    padding-left: clamp(72px, 7vw, 116px);
    padding-right: clamp(72px, 7vw, 116px);
  }

  .portfolio-right {
    max-width: 780px;
  }

  .portfolio-spiral {
    padding-right: 122px;
  }

  .project-slide {
    width: 400px;
    right: 122px;
  }

  .portfolio-right .portfolio-filters {
    width: 400px;
  }
}

@media (max-width: 1280px) {
  .portfolio-stage {
    grid-template-columns: minmax(285px, 0.76fr) minmax(560px, 1.24fr);
    gap: clamp(48px, 5vw, 86px);
    padding-left: clamp(40px, 5vw, 72px);
    padding-right: clamp(40px, 5vw, 72px);
  }

  .portfolio-right {
    max-width: 690px;
  }

  .portfolio-spiral {
    padding-right: 104px;
  }

  .project-slide {
    width: 360px;
    right: 104px;
  }

  .portfolio-right .portfolio-filters {
    width: 360px;
  }

  .interaction-cue {
    right: 0;
  }
}

@media (max-width: 1180px) {
  .portfolio-stage {
    grid-template-columns: minmax(260px, 0.74fr) minmax(520px, 1.26fr);
    gap: 42px;
  }

  .portfolio-right {
    max-width: 630px;
  }

  .portfolio-spiral {
    padding-right: 92px;
  }

  .project-slide {
    width: 340px;
    right: 92px;
  }

  .portfolio-right .portfolio-filters {
    width: 340px;
    margin-left: 0;
    padding-right: 0;
  }

  .project-card {
    min-height: min(492px, calc(100svh - var(--header-h) - 122px));
  }

  .project-cover {
    min-height: 200px;
  }
}

@media (max-width: 980px) {
  .portfolio-stage {
    max-width: var(--content-max);
    grid-template-columns: 1fr;
    padding-left: 20px;
    padding-right: 20px;
    gap: 28px;
  }

  .portfolio-right {
    max-width: none;
    justify-self: stretch;
    display: block;
  }

  .portfolio-right .portfolio-filters {
    width: 100%;
    justify-content: flex-start;
    margin: 24px 0 22px;
  }

  .portfolio-spiral {
    height: auto;
    padding-right: 0;
    overflow: visible;
    border-radius: 0;
  }

  .project-slide {
    width: 100%;
    right: auto;
  }

  .project-card {
    min-height: auto;
  }

  .project-cover {
    min-height: 280px;
  }
}


/* V15 FILTER ALIGNMENT — CENTERED AND CLOSER TO THE CARD STACK */

/* Make the right area behave like a card column + right cue lane */
.portfolio-right {
  max-width: 760px;
  justify-self: end;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-content: center;
}

/* Filters now share the same width and horizontal position as the active card */
.portfolio-right .portfolio-filters {
  width: min(390px, 100%);
  justify-self: start;
  margin: 0 0 -10px 0;
  padding-right: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  transform: translateY(34px);
  z-index: 180;
}

/* Pull the card stack slightly upward as a unified group, so filters feel attached */
.portfolio-spiral {
  align-self: center;
  transform: translateY(16px);
}

/* Ensure the active card remains aligned with the filter row */
.project-slide {
  right: 116px;
}

/* The cue remains in the right whitespace, not part of the filter/card alignment */
.interaction-cue {
  right: 4px;
  top: 54%;
}

/* Large desktop */
@media (min-width: 1500px) {
  .portfolio-right .portfolio-filters {
    width: 400px;
    transform: translateY(34px);
  }

  .project-slide {
    right: 122px;
  }
}

/* Medium desktop */
@media (max-width: 1280px) {
  .portfolio-right .portfolio-filters {
    width: 360px;
    transform: translateY(30px);
  }

  .portfolio-spiral {
    transform: translateY(14px);
  }

  .project-slide {
    right: 104px;
  }
}

@media (max-width: 1180px) {
  .portfolio-right .portfolio-filters {
    width: 340px;
    margin-left: 0;
    transform: translateY(28px);
  }

  .portfolio-spiral {
    transform: translateY(12px);
  }

  .project-slide {
    right: 92px;
  }
}

/* Mobile/tablet: filters become normal content again */
@media (max-width: 980px) {
  .portfolio-right .portfolio-filters {
    width: 100%;
    justify-content: flex-start;
    margin: 24px 0 22px;
    transform: none;
  }

  .portfolio-spiral {
    transform: none;
  }
}


/* V16 STABLE STACK SCROLL BEHAVIOR */

/* Keep the stack container visually stable; movement should only happen inside project-slide transforms. */
.portfolio-spiral {
  transform: none !important;
  will-change: auto;
}

/* Make card transitions feel controlled, not like continuous falling. */
.project-slide {
  transition:
    transform 480ms var(--nc-ease-out),
    opacity 480ms var(--nc-ease-out),
    filter 480ms var(--nc-ease-out) !important;
}

/* Slightly reduce vertical offset per step so cards do not appear to jump or skip. */
.project-slide {
  transform:
    translateY(calc(-50% + (var(--offset) * 66px)))
    translateX(calc(var(--offset) * -46px))
    translateZ(calc(var(--abs) * -105px))
    rotateZ(calc(var(--offset) * -5deg))
    rotateY(calc(var(--offset) * 7deg))
    scale(calc(1 - (var(--abs) * 0.07))) !important;
}

@media (max-width: 980px) {
  .project-slide {
    transform: none !important;
  }
}


/* V17 SHARED HEADER BEHAVIOR — MATCH HOME EXPERIENCE */

/* Header uses the same fixed, translucent behavior across portfolio + case pages. */
.header {
  position: fixed !important;
  top: 0;
  left: 0;
  z-index: 500;
  width: 100%;
  padding: 24px var(--frame-x) 0;
  background: rgba(13, 13, 13, 0.88);
  backdrop-filter: blur(18px);
  transition:
    transform 420ms var(--nc-ease-out),
    opacity 420ms var(--nc-ease-out),
    background 300ms var(--nc-ease-out);
}

.header.is-hidden {
  transform: translate3d(0, -120%, 0);
  opacity: 0;
  pointer-events: none;
}

.header.is-visible {
  transform: translate3d(0, 0, 0);
  opacity: 1;
  pointer-events: auto;
}

.header-inner {
  max-width: var(--content-max) !important;
  min-height: 64px;
  margin: 0 auto;
  padding-bottom: 24px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 32px;
  border-bottom: 1px solid var(--nc-border);
}

.portfolio-header {
  height: auto;
}

/* Because header is fixed, portfolio main owns the offset. */
.portfolio-viewport {
  grid-template-rows: minmax(0, 1fr);
}

.portfolio-main {
  padding-top: var(--header-h);
}

/* .case-main's own padding-top override removed from here - this whole
   block predates the shared site-chrome.js header (it uses the old
   static --header-h: 104px fallback, not the real measured
   --site-nav-height), and this rule specifically was re-adding the exact
   double-padding dead zone the authoritative .case-main rule near the
   top of this file was rewritten to remove - it just came later in the
   file, so it silently won. */

/* Nav hover interaction aligned with the home behavior. */
.nav {
  align-items: flex-start;
}

.nav a,
.nav-link {
  position: relative;
  display: inline-block;
  height: 18px;
  overflow: visible;
  white-space: nowrap;
  color: rgb(153, 153, 153);
  line-height: 18px;
  transition: opacity 260ms var(--nc-ease-out);
}

.nav:hover a,
.nav:hover .nav-link {
  opacity: 0.42;
}

.nav:hover a:hover,
.nav:hover .nav-link:hover {
  opacity: 1;
}

.nav-link-inner {
  position: relative;
  display: inline-block;
  height: 18px;
  white-space: nowrap;
  overflow: hidden;
  padding-right: 3px;
}

.nav-line {
  display: flex;
  width: max-content;
  height: 18px;
  line-height: 18px;
  white-space: nowrap;
}

.nav-line-original {
  position: relative;
}

.nav-line-clone {
  position: absolute;
  left: 0;
  top: 18px;
}

.nav-char {
  display: inline-block;
  line-height: 18px;
  color: rgb(153, 153, 153);
  transform: translateY(0);
  transition:
    transform 420ms var(--nc-ease-out),
    color 260ms var(--nc-ease-out);
  transition-delay: calc(var(--char-index) * 14ms);
}

.nav-line-clone .nav-char {
  color: var(--nc-mint);
}

.nav a:hover .nav-line-original .nav-char,
.nav a:hover .nav-line-clone .nav-char,
.nav-link:hover .nav-line-original .nav-char,
.nav-link:hover .nav-line-clone .nav-char {
  transform: translateY(-18px);
}

.logo {
  transition:
    color 240ms var(--nc-ease-out),
    transform 240ms var(--nc-ease-out);
}

.logo:hover {
  color: var(--nc-mint);
  transform: translateY(-1px);
}

/* Project pages use native scroll: JS not needed, but the fixed header should not cover content. */
body:not(.portfolio-body) .page {
  padding-top: 0;
}

@media (max-width: 980px) {
  .portfolio-main {
    padding-top: 0;
  }

  .portfolio-viewport {
    display: block;
  }

  .header {
    position: relative !important;
    transform: none !important;
    opacity: 1 !important;
    pointer-events: auto !important;
  }

  /* .case-main's padding-top override removed from here too - same dead
     legacy rule as the one removed near .portfolio-main above, just
     scoped to this breakpoint. */
}


/* V18 FILTERS RIGHT ALIGNMENT */

/*
  Align filters with the right edge of the active card.
  The right margin matches the reserved scroll-cue lane / card offset.
*/

.portfolio-right .portfolio-filters {
  width: auto !important;
  max-width: calc(100% - 116px);
  justify-self: end !important;
  justify-content: flex-end !important;
  margin: 0 116px 14px 0 !important;
  padding-right: 0 !important;
  transform: translateY(28px);
  z-index: 180;
}

/* Keep the scroll cue in the right whitespace */
.interaction-cue {
  right: 4px;
}

/* Large desktop: match the card offset from the active stack */
@media (min-width: 1500px) {
  .portfolio-right .portfolio-filters {
    max-width: calc(100% - 122px);
    margin-right: 122px !important;
    transform: translateY(28px);
  }
}

/* Medium desktop */
@media (max-width: 1280px) {
  .portfolio-right .portfolio-filters {
    max-width: calc(100% - 104px);
    margin-right: 104px !important;
    transform: translateY(26px);
  }
}

@media (max-width: 1180px) {
  .portfolio-right .portfolio-filters {
    max-width: calc(100% - 92px);
    margin-right: 92px !important;
    margin-left: 0 !important;
    transform: translateY(24px);
  }
}

/* Mobile/tablet: return to normal left-aligned filter flow */
@media (max-width: 980px) {
  .portfolio-right .portfolio-filters {
    width: 100% !important;
    max-width: 100%;
    justify-self: stretch !important;
    justify-content: flex-start !important;
    margin: 24px 0 22px !important;
    transform: none;
  }
}


/* V19 EMBED MODE — FOR HOME FEATURED WORK */

.portfolio-embed-page,
.portfolio-embed-body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: transparent;
}

.portfolio-embed-viewport {
  height: 100svh;
  min-height: 100svh;
  display: block;
  overflow: hidden;
  background: transparent;
}

.portfolio-embed-main {
  height: 100%;
  padding-top: 0 !important;
  overflow: hidden;
}

.portfolio-embed-stage {
  height: 100%;
  max-width: 1440px;
  margin: 0 auto;
}

/* In iframe mode there is no portfolio header, so the card can use more vertical space. */
.portfolio-embed-page .project-card {
  min-height: min(510px, calc(100svh - 92px));
}

.portfolio-embed-page .portfolio-spiral {
  height: min(560px, calc(100svh - 70px));
}

.portfolio-embed-page .portfolio-copy h1 {
  font-size: clamp(34px, 3.9vw, 58px);
}

.portfolio-embed-page .hero-copy {
  font-size: clamp(15px, 1vw, 18px);
}

/* Hide any header inherited from previous versions if cache mixes files. */
.portfolio-embed-page .header {
  display: none !important;
}

/* Home page iframe support. Copy these classes into the Home repo CSS. */
.featured-work-embed-section {
  width: 100%;
  padding: clamp(80px, 10vw, 140px) 0;
  background: #0D0D0D;
}

.featured-work-embed-frame {
  width: 100%;
  height: min(820px, 100svh);
  min-height: 680px;
  border: 0;
  display: block;
  background: transparent;
}

@media (max-width: 980px) {
  .portfolio-embed-page,
  .portfolio-embed-body {
    height: auto;
    overflow: auto;
  }

  .portfolio-embed-viewport {
    height: auto;
    min-height: 100svh;
    overflow: visible;
  }

  .portfolio-embed-main {
    height: auto;
    overflow: visible;
  }

  .portfolio-embed-stage {
    height: auto;
    padding-top: 40px;
    padding-bottom: 70px;
  }

  .featured-work-embed-frame {
    height: 1200px;
    min-height: 900px;
  }
}


/* FINAL NAV FOOTER EMBED CONSOLIDATION */

.header-inner {
  max-width: var(--content-max) !important;
}

.quick-links {
  align-items: flex-start;
  text-align: left;
}

.nav {
  gap: 4px 12px;
}

/* Shared footer structure */
.footer {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 40px var(--frame-x);
  border-top: 1px solid var(--nc-border);
  color: var(--nc-white-56, rgba(255, 255, 255, 0.56));
  font-size: 14px;
}

.site-footer-inner {
  display: flex;
  flex-direction: column;
  gap: 18px;
  align-items: stretch;
}

.footer-links,
.footer-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 24px;
}

.footer-links a,
.footer-meta a {
  color: var(--nc-mint);
  text-decoration: none;
  transition: opacity 220ms var(--nc-ease-out);
}

.footer-links a:hover,
.footer-meta a:hover {
  opacity: 0.82;
}

.footer-meta {
  color: var(--nc-white-56, rgba(255, 255, 255, 0.56));
}

/* Embed mode for Home Featured Work */
.portfolio-embed-page,
.portfolio-embed-body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: transparent;
}

.portfolio-embed-viewport {
  height: 100svh;
  min-height: 100svh;
  display: block;
  overflow: hidden;
  background: transparent;
}

.portfolio-embed-main {
  height: 100%;
  padding-top: 0 !important;
  overflow: hidden;
}

.portfolio-embed-stage {
  height: 100%;
  max-width: 1440px;
  margin: 0 auto;
}

.portfolio-embed-page .header {
  display: none !important;
}

.portfolio-embed-page .project-card {
  min-height: min(510px, calc(100svh - 92px));
}

.portfolio-embed-page .portfolio-spiral {
  height: min(560px, calc(100svh - 70px));
}

@media (max-width: 980px) {
  .portfolio-embed-page,
  .portfolio-embed-body {
    height: auto;
    overflow: auto;
  }

  .portfolio-embed-viewport {
    height: auto;
    min-height: 100svh;
    overflow: visible;
  }

  .portfolio-embed-main {
    height: auto;
    overflow: visible;
  }

  .portfolio-embed-stage {
    height: auto;
    padding-top: 40px;
    padding-bottom: 70px;
  }
}

@media (max-width: 520px) {
  .footer-links,
  .footer-meta {
    flex-direction: column;
    align-items: flex-start;
  }
}


/* V21 final navigation + footer updates */
.logo { color: var(--nc-mint); font-weight: 600; letter-spacing: -0.04em; }
.nav { align-items: center; }
.nav-separator { color: rgba(255,255,255,.35); font-size: 14px; line-height: 18px; margin: 0 -2px; }
.site-footer-inner { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.footer-links { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px; }
.footer-links .nav-link { height: 18px; color: rgb(153,153,153); text-decoration: none; }
.footer-back { color: var(--nc-mint); text-decoration: none; white-space: nowrap; transition: opacity 220ms var(--nc-ease-out); }
.footer-back:hover { opacity: .82; }
@media (max-width: 620px) {
  .site-footer-inner { flex-direction: column; align-items: flex-start; }
  .footer-links { flex-direction: column; align-items: flex-start; gap: 10px; }
}


/* ============================================================
   V22 — SIMPLIFIED SCROLL STACK
   Replaces the 3D perspective carousel (rotateY/translateZ,
   driven by --offset/--abs) with a flat, scroll-driven card
   stack. Cards sit in normal flow inside a real scrollable
   container (.portfolio-stack-scroller); JS drives
   --slide-y / --slide-scale as the user scrolls, pinning and
   stacking each card in turn — closer to a physical deck of
   cards than a carousel. This is the authoritative section for
   .portfolio-spiral / .project-slide from here on; it overrides
   every earlier version above, including the !important
   transform/transition rules from V16.
   ============================================================ */

.portfolio-spiral {
  position: relative;
  overflow: hidden;
  perspective: none;
  transform: none !important;
}

.portfolio-stack-scroller {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.portfolio-stack-scroller::-webkit-scrollbar {
  display: none;
}

.portfolio-stack-inner {
  position: relative;
  padding: 6px 0;
}

.scroll-stack-end {
  width: 100%;
  height: 1px;
}

.project-slide {
  position: relative;
  top: auto;
  right: auto;
  width: min(390px, 100%);
  margin: 0 auto 72px;
  opacity: 1;
  filter: none;
  cursor: pointer;
  outline: none;
  transform-origin: top center;
  backface-visibility: hidden;
  will-change: transform;
  transform: translate3d(0, var(--slide-y, 0px), 0) scale(var(--slide-scale, 1)) !important;
  transition: none !important;
}

.project-slide:last-child {
  margin-bottom: 6px;
}

.project-card {
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.38);
  transition:
    transform 280ms var(--nc-ease-out),
    border-color 280ms var(--nc-ease-out),
    box-shadow 280ms var(--nc-ease-out);
}

/* Pure CSS hover — deliberately not JS-driven, so hovering a
   card never triggers a scroll-transform recalculation.
   :hover is additionally gated on .portfolio-spiral:not(.is-scrolling):
   the coverflow rotates cards' screen position under a stationary mouse
   cursor as you scroll, so without this guard, whichever card happens to
   pass under the cursor mid-scroll would suddenly pick up this lift +
   its 280ms transition on top of its own scroll-driven 3D transform -
   a little unrequested "jump" completely decoupled from the actual
   scroll input. render-portfolio.js toggles .is-scrolling on
   .portfolio-spiral for ~120ms after the last scroll event. */
.portfolio-spiral:not(.is-scrolling) .project-slide:hover .project-card,
.project-slide:focus-visible .project-card {
  transform: translateY(-6px);
  border-color: rgba(168, 218, 220, 0.48);
  box-shadow: 0 32px 90px rgba(168, 218, 220, 0.14);
}

.is-teaching-scroll .project-slide.is-active,
.is-teaching-scroll .project-slide.is-active + .project-slide {
  animation: none !important;
}

@media (min-width: 1500px) {
  .project-slide { width: 400px; }
}

@media (max-width: 1280px) {
  .project-slide { width: 360px; }
}

@media (max-width: 1180px) {
  .project-slide { width: 340px; }
}

@media (max-width: 980px) {
  .portfolio-spiral {
    overflow: visible;
  }

  .portfolio-stack-scroller {
    position: static;
    overflow: visible;
  }

  .portfolio-stack-inner {
    display: grid;
    gap: 22px;
    padding: 0;
  }

  .scroll-stack-end {
    display: none;
  }

  .project-slide {
    width: 100%;
    margin: 0;
    transform: none !important;
  }

  .project-slide:hover .project-card,
  .project-slide:focus-visible .project-card {
    transform: none;
  }
}


/* ============================================================
   V23 — FULL-WIDTH STACK, COPY PANEL REMOVED
   Drops the left text column (title/description/active label
   now live only as an sr-only <h1> for accessibility). The
   stack takes the full width of the stage. Cards move from a
   tall vertical layout (cover image on top) to a short
   horizontal bar: title on the left, a small rounded preview
   box on the right — a plain card stack, not a hero layout.
   ============================================================ */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.portfolio-stage {
  display: block;
  width: 100%;
  max-width: var(--content-max);
  height: 100%;
  margin: 0 auto;
  padding: 0 var(--frame-x);
}

.portfolio-right {
  position: relative;
  width: 100%;
  max-width: none;
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-content: center;
  justify-self: stretch;
}

.portfolio-right .portfolio-filters {
  width: auto;
  max-width: none;
  justify-self: start;
  justify-content: flex-start;
  margin: 0 0 16px 0;
  padding-right: 0;
  transform: none;
}

.portfolio-spiral {
  width: 100%;
  padding-right: 0;
}

.project-slide {
  width: 100%;
}

.project-card {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: clamp(20px, 3vw, 40px);
  height: auto;
  min-height: auto;
  padding: clamp(24px, 3vw, 40px);
}

/* Higher-specificity embed override from V19 forced a tall card
   (min(510px, ...)); this scoped rule matches it so the shorter
   horizontal card wins in the iframe context too. */
.portfolio-embed-page .project-card {
  min-height: auto;
}

.project-card-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.project-card-body .project-type {
  align-self: flex-start;
}

.project-card h2 {
  margin-bottom: 0;
  font-size: clamp(22px, 3vw, 42px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.1;
}

.project-cover {
  flex: 0 0 auto;
  width: clamp(110px, 20vw, 220px);
  aspect-ratio: 1 / 1;
  min-height: 0;
  border: 1px solid var(--nc-border);
  border-radius: 20px;
}

.cover-fallback {
  padding: 10px;
  font-size: clamp(12px, 1.6vw, 20px);
}

.interaction-cue {
  position: absolute;
  top: 16px;
  right: 16px;
  bottom: auto;
  left: auto;
  z-index: 20;
  transform: none;
  gap: 10px;
  max-width: none;
}

@media (max-width: 1180px) {
  .project-slide { width: 100%; }
}

@media (max-width: 980px) {
  .portfolio-stage {
    padding-left: 20px;
    padding-right: 20px;
  }

  .portfolio-right .portfolio-filters {
    margin: 0 0 20px;
  }

  .interaction-cue {
    display: flex;
    position: static;
    margin: 0 0 16px;
  }

  .project-card {
    padding: 20px;
    gap: 16px;
  }

  .project-cover {
    width: clamp(84px, 26vw, 130px);
  }

  .project-card h2 {
    font-size: clamp(20px, 6vw, 28px);
  }
}

@media (max-width: 520px) {
  .project-card {
    padding: 16px;
    gap: 12px;
  }

  .project-cover {
    width: 76px;
  }
}


/* ============================================================
   V24 — CONNECT TO REAL PAGE SCROLL
   The stack no longer lives in its own locked, self-scrolling
   box. The page (or, when embedded in the home iframe, the
   parent page) scrolls normally, and the stack pins/stacks each
   card as that real scroll passes it — same JS transform engine
   as before (translate3d + scale via --slide-y/--slide-scale),
   just re-pointed at window.scrollY (or the parent's scrollY
   when embedded) instead of an internal scrollTop. This section
   overrides the V22 container-locking rules.
   ============================================================ */

.portfolio-page,
.portfolio-body {
  height: auto;
  min-height: 100%;
  overflow: visible;
}

.portfolio-viewport {
  height: auto;
  min-height: 100svh;
  overflow: visible;
  display: block;
}

.portfolio-main {
  min-height: 0;
  overflow: visible;
}

.portfolio-stage {
  height: auto;
}

.portfolio-right {
  height: auto;
  display: block;
}

.portfolio-spiral {
  height: auto;
  overflow: visible;
  padding-right: 0;
}

.portfolio-stack-scroller {
  position: static;
  overflow: visible;
}

.portfolio-stack-inner {
  padding: 24px 0;
}

.project-slide {
  margin: 0 auto clamp(140px, 20vh, 260px);
}

.project-slide:last-child {
  margin-bottom: 24px;
}

.interaction-cue {
  position: absolute;
  top: 0;
  right: 16px;
}

/* Embed (home iframe) mode: same unlock, plus it drops its own
   fixed height entirely — the iframe element itself is resized
   to match this content from the parent page. */
.portfolio-embed-page,
.portfolio-embed-body {
  height: auto;
  overflow: visible;
}

.portfolio-embed-viewport {
  height: auto;
  min-height: 0;
  overflow: visible;
}

.portfolio-embed-main {
  height: auto;
  overflow: visible;
}

.portfolio-embed-stage {
  height: auto;
}

.portfolio-embed-page .portfolio-spiral {
  height: auto;
}

.portfolio-embed-page .project-card {
  min-height: auto;
}

@media (max-width: 980px) {
  .portfolio-spiral {
    padding-right: 0;
  }

  .project-slide {
    margin: 0 0 22px;
  }

  .interaction-cue {
    position: static;
  }
}

/* Fallback: if the embed can't reach window.frameElement for some
   reason, JS adds this class and reverts to a self-contained
   scrollbox (the pre-V24 behavior) instead of a broken page. */
.portfolio-spiral.is-container-scroll {
  position: relative;
  height: min(560px, calc(100svh - 70px));
  overflow: hidden;
  padding-right: 96px;
}

.portfolio-spiral.is-container-scroll .portfolio-stack-scroller {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

.portfolio-spiral.is-container-scroll .project-slide {
  margin: 0 auto 72px;
}

/* ============================================================
   V25 — CONNECTED 3D COVERFLOW + FULL CARD CONTENT
   Cards fan out in a shallow 3D coverflow (rotateY/translateZ)
   driven by real page scroll, modeled on the "My Work" section
   of narroyac.framer.website: one active card faces forward at
   full scale, neighbours rotate and recede to each side. A tall
   wrapper (.portfolio-spiral, height set by JS) gives the page
   scroll distance to pass through every card; the fixed-height
   stage (.portfolio-stack-scroller) pins at a constant on-screen
   position for that whole range — JS sets its own `top` rather
   than relying on native `sticky` (sticky can't track a parent
   page's scroll from inside an iframe, which the home embed
   needs). Cards carry full content again: cover image, type +
   role, title, short description, tags and an open-case link —
   this is the authoritative section for .portfolio-spiral /
   .portfolio-stack-scroller / .project-slide / .project-card
   from here on, overriding every earlier version above.
   ============================================================ */

.portfolio-spiral {
  position: relative;
  overflow: visible;
  overflow-anchor: none;
  padding-right: 0;
}

.portfolio-stack-scroller {
  /* Tried switching this to position:sticky (native pin, no per-frame JS
     write) since Home doesn't use the iframe embed the JS simulation was
     originally built for - reverted. Confirmed live: sticky never
     engages here at all (the stage just scrolls linearly with the page)
     because <body> carries its own overflow (needed for the mobile drawer's
     scroll lock - see lockBodyScroll() in js/site-chrome.js), and a body
     with an explicit overflow value breaks position:sticky's containing-
     block resolution for descendants, a known browser quirk. Back to the
     JS-computed top below. */
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: clamp(540px, 74vh, 740px);
  overflow: visible;
  perspective: 1600px;
}

.portfolio-stack-inner {
  position: relative;
  width: 100%;
  height: 100%;
  padding: 0;
  transform-style: preserve-3d;
}

.project-slide {
  position: absolute;
  top: 50%;
  left: 50%;
  right: auto;
  width: min(300px, 88vw);
  margin: 0;
  opacity: var(--card-opacity, 1) !important;
  transform-origin: center center;
  transform:
    translate(-50%, -50%)
    translateX(var(--card-x, 0px))
    translateY(var(--card-y, 0px))
    rotateY(var(--card-rotate-y, 0deg))
    translateZ(var(--card-z, 0px))
    rotateZ(var(--card-rotate-z, 0deg))
    scale(var(--card-scale, 1)) !important;
  transition: none !important;
  will-change: transform, opacity;
  backface-visibility: hidden;
}

.project-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 0;
  height: auto;
  min-height: 0;
  padding: 0;
  /* The base rule up top sets overflow: hidden (never reset by any later
     block). With height: auto, actual real-world content (webfont
     metrics, real cover images vs. the placeholder gradient) can end up
     a hair taller than the box computes on a given render, and
     overflow: hidden silently clips it - exactly the "cut off at the
     bottom" report from an earlier round. .project-cover is now a
     self-contained square box with its own overflow: hidden (see below),
     not reliant on this card-level rule for its own clipping, so this is
     safe to leave open. */
  overflow: visible;
}

.project-card-top {
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr: without it this column's minimum
     width defaults to its content's min-content (the role/type text),
     which at narrow mobile card widths was wide enough to push the
     whole grid - including the fixed 88px cover image - past the
     card's own right edge. Same overflow pattern already fixed once
     for .portfolio-stack-inner. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 12px;
  margin-bottom: 12px;
}

.project-cover {
  flex: 0 0 auto;
  width: 88px;
  aspect-ratio: 1 / 1;
  min-height: 0;
  border: none;
  border-radius: 16px;
  overflow: hidden;
}

.portfolio-stack-inner .cover-fallback {
  padding: 6px;
  font-size: 9px;
  letter-spacing: -0.02em;
}

.project-card-content {
  padding: 18px;
}

/* Higher specificity than the bare ".project-card h2" above is required
   here: now that this carousel is embedded directly on Home, its card
   titles sit inside Home's own ".section" wrapper, and Home's own
   ".section h2" rule (clamp(42px, 6vw, 88px), meant for big page-section
   headings) ties in specificity with ".project-card h2" - and since
   Home's stylesheet loads after this one, that tie went to Home's giant
   heading size instead of this card's own small title.
   This used to be scoped to "@media (min-width: 981px)" only, on the
   assumption that a separate, already-small mobile-only rule handled
   anything narrower - but that mobile rule (.project-card h2 at
   max-width:980px, further up this file) has the SAME (lower)
   specificity as .section h2, so it lost the exact same tie-break at
   any width below 981px, so at anything under 981px (including real
   phone widths) it fell back to .section h2's giant clamp(42-88px) -
   which was the actual cause of the title reading "gigante" on mobile,
   even larger than desktop. Unscoped from the media query entirely so
   this rule always wins the tie regardless of width. Font-size is a
   flat 24px at every width by explicit request (first set narrower to
   match the small cover image, then raised back up once - 24px - on
   both mobile and desktop, so there's no longer a separate mobile
   override for it further down this file). */
.project-slide .project-card h2 {
  /* No margin-bottom here: h2 is now the last item stacked inside
     .project-meta (see project-card-top below), not a standalone
     element followed directly by the description paragraph - that
     spacing job moved to .project-card-top's own margin-bottom. */
  margin-bottom: 0;
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.2;
}

.project-role {
  color: var(--nc-white-56);
  font-size: 12px;
}

.interaction-cue {
  position: absolute;
  top: 16px;
  right: 16px;
  bottom: auto;
  left: auto;
  z-index: 20;
  transform: none;
  gap: 10px;
  max-width: none;
}

/* Progress indicator ("01 / 05") for the coverflow. .portfolio-stack-scroller
   (the direct parent) is position:absolute at this width, so this pins to
   its top-right corner and stays put as the pinned stage scrolls the cards
   through it - it isn't meant to track any one card. */
.portfolio-progress {
  position: absolute;
  top: 0;
  right: 16px;
  z-index: 20;
  display: flex;
  align-items: baseline;
  gap: 4px;
  color: var(--nc-white-56);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}

.portfolio-progress #active-index {
  color: var(--nc-white);
}

.portfolio-progress-sep {
  color: var(--nc-white-38);
}

@media (max-width: 980px) {
  .portfolio-spiral {
    height: auto !important;
    padding-right: 0;
  }

  .portfolio-stack-scroller {
    position: static;
    height: auto;
    top: auto !important;
    perspective: none;
  }

  .portfolio-stack-inner {
    display: grid;
    /* No grid-template-columns anywhere for this rule (in any of its
       occurrences in this file) meant the single implicit column sized
       itself to its content's preferred width (min-content) instead of
       stretching to fill .portfolio-spiral - since .project-card's text
       content is comfortably wider than the mobile viewport, the column
       (and every .project-slide inside it, each width:100% of THIS)
       grew past the container, flush against the right edge of the
       screen. minmax(0, 1fr) forces the column to exactly the
       container's width regardless of content. */
    grid-template-columns: minmax(0, 1fr);
    gap: 22px;
    height: auto;
  }

  .project-slide {
    position: static;
    width: 100%;
    margin: 0;
    transform: none !important;
    opacity: 1 !important;
  }

  .project-card {
    flex-direction: column;
  }

  /* This used to be a full-width top banner (24px rounded top corners,
     flat bottom, meant to flow flush into the content below) - a
     leftover from before the grid redesign put the cover back to a
     small square next to the title. Now it's the same small square as
     desktop, so it needs all 4 corners rounded, and 24px reads as much
     too heavy on an 88px thumbnail. */
  .project-cover {
    border-radius: 4px;
  }

  .interaction-cue {
    display: flex;
    position: static;
    margin: 0 0 16px;
  }

  /* Mobile falls back to a plain static list (no pin/scroll-hijack, see
     .portfolio-stack-scroller above) - there's no single "active" card
     to count against, so the indicator doesn't apply here. */
  .portfolio-progress {
    display: none;
  }
}
