/* ============================================================
   Project page — vertical scroll of full-screen media,
   with the name / controls / nav fixed on top.
   ============================================================ */

:root {
  --bg: #000000;
  --fg: #ffffff;
  /* mix-blend-difference chrome source (bar, info text) */
  --ink: #ffffff;
  --font-sans: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-serif: "Instrument Serif", Georgia, serif;
  --fs-body: clamp(15px, 1.9vw, 18px);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  cursor: none !important;  /* always the custom + — never pointer/text/others */
}

/* Custom cursor: a "+" that inverts against the page, rotating into an "×"
   over anything clickable. */
.cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 18px;
  height: 18px;
  pointer-events: none;
  z-index: 9999;
  mix-blend-mode: difference;
}
.cursor__inner {
  position: absolute;
  inset: 0;
  transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.cursor__inner::before,
.cursor__inner::after {
  content: "";
  position: absolute;
  background: #fff;
}
.cursor__inner::before { left: 50%; top: 0; width: 1.5px; height: 100%; transform: translateX(-50%); }
.cursor__inner::after  { top: 50%; left: 0; height: 1.5px; width: 100%; transform: translateY(-50%); }
.cursor.is-x .cursor__inner { transform: rotate(45deg); }

html {
  scroll-behavior: smooth;   /* free scroll, no snapping */
  background: var(--bg);      /* theme colour on the root → no black overscroll band */
  overflow-x: clip;           /* never a horizontal scroll — the page moves vertically only */
}

html,
body {
  min-height: 100%;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  letter-spacing: -0.015em;
  font-weight: 400;
  overflow-x: clip;   /* clip horizontally without making the body a scroll container
                         (so the media fills under the status bar / toolbar, no black band) */
  -webkit-font-smoothing: antialiased;
}

/* ---------- Scrolling media ---------- */
.media {
  position: relative;
  z-index: 0;
}

.media__item {
  min-height: 100vh;          /* grows taller for full-width vertical images */
  min-height: 100lvh;         /* largest viewport: fills past the browser chrome on every
                                 device (Android/iOS in-app browsers where 100vh ≠ screen) */
  display: flex;
  align-items: center;
  justify-content: center;
  /* Overlap the next section by 1px so fractional 100vh rounding can't leave a
     hairline black seam between full-bleed media (the next video covers it). */
  margin-bottom: -1px;
}

.media__el {
  display: block;
}

/* Wrapper that holds the project's single shared <video> in each loop copy. It's
   transparent to layout, so the video lays out as a direct child of the section. */
.media__vslot {
  display: contents;
}

/* Landscape → fill the whole screen (scaled up, cropped if needed) so
   horizontal images are cohesive: all the same full-screen size. */
.media__el.is-landscape {
  width: 100vw;
  height: 100vh;
  height: 100lvh;             /* fill the full screen on every device (no black bands) */
  object-fit: cover;
}

/* Portrait → full width, natural height: the whole vertical image, uncropped. */
.media__el.is-portrait {
  width: 100vw;
  height: auto;
}

/* `align`: which edge of a cover crop is kept whole (the other edge is trimmed). */
.media__el.is-top    { object-position: top; }
.media__el.is-bottom { object-position: bottom; }

/* Two images side by side in one section (e.g. Luigi's 07 + 08). Each scales to
   fit within half the width and the screen height; gap matches the info column. */
/* A full-screen black section (same dimensions as a horizontal photo) holding
   the two verticals at their original proportions — not squeezed — centred with
   black padding around them. */
/* Pairs and the booklet are content-height "bands" (not full 100vh sections), so the
   black above/below is always the SAME fixed padding — consistent across the pairs,
   the PDF and Luigi's, regardless of whether the content is landscape or portrait. */
.media__pair {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(4px, 0.6vw, 9px);
  width: 100vw;
  padding-block: var(--band-pad, clamp(20px, 6vh, 80px));
  box-sizing: border-box;
}
/* Each image is at most 80vh tall, and its width is the 1/N share left after an
   equal gap between every image AND at the two screen edges — so all the gaps
   (between, left, right) match the vertical gap above/below. Uncropped, unstretched. */
.media__pair-img {
  display: block;
  width: auto;
  height: auto;
  max-height: 80vh;
  max-width: calc((100vw - (var(--n, 2) + 1) * clamp(4px, 0.6vw, 9px)) / var(--n, 2));
  object-fit: contain;
}

/* Pairs & booklet hug their content (no forced 100vh) with a small gap to neighbours. */
.media__item--pair {
  min-height: 0;
  margin-block: clamp(4px, 0.6vw, 9px);
}

/* `band` media (e.g. Dissolving Boundaries' videos): a content-height band with
   black padding all around it — uncropped/contained, like the booklet and pairs. */
.media__band {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100vw;
  padding-block: clamp(48px, 14vh, 160px);   /* generous black band above/below */
  box-sizing: border-box;
}
.media__band .media__el,
.media__band .media__el.is-landscape,
.media__band .media__el.is-portrait {
  width: auto;
  height: auto;
  max-width: 100vw;
  max-height: 80vh;
  object-fit: contain;
}

/* Booklet: ONE in-place frame. Every page is stacked here but only `.is-on` shows;
   the cursor's horizontal position (or the arrows) flips between them (see project.js). */
.media__booklet {
  position: relative;
  width: 100vw;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(48px, 14vh, 160px) clamp(16px, 4vw, 64px);   /* generous black band above/below */
  box-sizing: border-box;
}
.media__page {
  display: none;
  width: auto;
  height: auto;
  max-width: calc(100vw - clamp(56px, 12vw, 150px));   /* leave room for the side arrows */
  max-height: 80vh;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}
.media__page.is-on { display: block; }

/* The pages wrapper hugs the visible page, so the arrows anchor to its edges. */
.media__pages,
.info__pages {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
}
/* Page-turn arrows: the SAME thin arrow glyph as the mobile "↓" cue, just pointing
   left / right, tucked against the file's edges (also the way to flip on touch). */
.media__nav,
.info__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  padding: 0;
  border: 0;
  background: none;
  color: #fff;
  mix-blend-mode: difference;   /* dark on the white page, exactly like the ↓ */
  font-size: 1.2em;
  line-height: 1;
  cursor: pointer;
}
.media__nav--prev, .info__nav--prev { right: 100%; margin-right: clamp(5px, 0.8vw, 12px); }
.media__nav--next, .info__nav--next { left: 100%; margin-left: clamp(5px, 0.8vw, 12px); }

/* Before orientation is known, just keep it within the screen. */
.media__el:not(.is-landscape):not(.is-portrait):not(.media__pair-img) {
  max-width: 100vw;
  max-height: 100vh;
}

/* `full` media: real height, full width, no crop — the section hugs it so items
   stack flush with no black bands. Overrides the landscape cover crop above. */
.media__item--full {
  min-height: 0;
}
.media__item--full .media__el,
.media__item--full .media__el.is-landscape,
.media__item--full .media__el.is-portrait {
  width: 100vw;
  height: auto;
  max-width: 100vw;
  max-height: none;
  object-fit: contain;
}

/* "sound on" prompt — a white rectangle glued to the cursor: its top-left sits on
   the cross's centre and it grows out toward the bottom-right while hovering the
   main video. Click (on the video) toggles the audio. Visual only (pointer-events
   none) so it never steals the click. */
.sound-pill {
  position: fixed;
  left: 0;
  top: 0;                       /* JS eases its transform toward the cursor (a soft chase) */
  z-index: 4;
  opacity: 0;
  background: #fff;
  color: #000;
  mix-blend-mode: difference;   /* inverts against the video, like the cursor */
  font: inherit;
  font-size: var(--fs-body);    /* same size as the old "Welcome to my portfolio" pill */
  letter-spacing: -0.015em;
  padding: 0.3em 0.55em;
  white-space: nowrap;
  pointer-events: none;
  will-change: transform;
  transition: opacity 0.14s ease;
}
body.sound-hover .sound-pill { opacity: 1; }

/* ---------- Intro de-blur (continues the blurred image from the index) ---------- */
.intro {
  position: fixed;
  /* Match the index's .bg box (oversized) so background-size:cover scales the SAME
     image identically — no zoom/jump of the blurred blob at the page seam. */
  inset: -15vmax;
  z-index: 5;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  filter: blur(80px) saturate(1.15);    /* matches the index cover blur for a seamless handoff */
  transform: scale(1.1);
  opacity: 1;
  pointer-events: none;
  /* de-blur first, then fade out to reveal the real (clear) media beneath — quicker */
  transition: filter 0.5s ease, transform 0.5s ease, opacity 0.35s ease 0.3s;
}

.intro.is-clear {
  filter: blur(0) saturate(1);
  transform: scale(1);
  opacity: 0;
}

/* ---------- Leaving back to the index ---------- */
body.is-leaving .media {
  opacity: 0;
  transition: opacity 0.5s ease;
}

/* Keep the logo + nav visible so they slide into place on the next page;
   only the centre controls fade out. */
body.is-leaving .controls {
  opacity: 0;
  transition: opacity 0.5s ease;
}

/* ---------- Fixed bar (name · controls · nav) ---------- */
.bar {
  position: fixed;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: clamp(20px, 2.2vw, 36px);
  font-size: var(--fs-body);
  mix-blend-mode: difference;     /* invert against whatever scrolls behind */
  pointer-events: none;
}

.logo,
.nav,
.controls {
  pointer-events: auto;
}

.logo,
.nav__link,
.ctrl {
  color: var(--ink);   /* inside the mix-blend-difference bar */
  text-decoration: none;
  font-family: inherit;
  font-size: inherit;
  font-weight: 400;
}

.logo {
  letter-spacing: -0.015em;
}

.nav {
  display: flex;
  gap: clamp(20px, 3vw, 48px);
}

/* Centered controls — absolutely centered regardless of side widths */
.controls {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: clamp(22px, 2.6vw, 48px);
}

.ctrl {
  background: none;
  border: none;
}

/* Title window — the project name slides up to replace the previous one
   as you scroll from one project into the next. */
.title-window {
  position: relative;
  display: inline-block;
  overflow: hidden;
  height: 1.35em;
  line-height: 1.35em;
  vertical-align: bottom;
}
.title__cur {
  display: inline-block;
  white-space: nowrap;
}
.title__clone {
  position: absolute;
  left: 0;
  top: 0;
  display: inline-block;
  white-space: nowrap;
}

/* ---------- Info overlay ---------- */
.info {
  /* The Info panel is a light page (white bg, dark text) even though the project
     scroll behind it stays dark. Scoping the theme vars here flips every var-based
     colour inside the panel; the mix-blend-difference text (#fff) then reads black
     on the white panel and still inverts over photos on mobile. */
  --bg: #ffffff;
  --fg: #000000;
  --ink: #ffffff;
  position: fixed;
  inset: 0;
  z-index: 6;
  background: var(--bg);
  overflow: hidden;            /* the layout stays fixed; only thumbs scroll */
  opacity: 0;
  visibility: hidden;
  /* closing: stay opaque while the text slides, then fade out */
  transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.25s, visibility 0s linear 0.65s;
}

.info.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1);     /* opening: fade in immediately */
}

/* Two zones, like the reference: text block on the left (sitting on the
   nav-bar centre line, flowing down) and the images stacked on the right,
   scrolling the full screen height. */
.info__row {
  display: grid;
  grid-template-columns:
    auto                 /* close */
    minmax(0, 1.35fr)    /* text block — more room to write */
    minmax(0, 0.8fr)     /* images */
    auto;                /* next */
  grid-template-rows: calc(50vh - 0.6em) calc(50vh + 0.6em);
  height: 100vh;
  align-items: start;
  column-gap: clamp(24px, 2.4vw, 56px);
  padding-inline: clamp(20px, 2.2vw, 36px);
  font-size: var(--fs-body);
  line-height: 1.3;
  position: relative;
  z-index: 1;            /* above the layout lines */
}

/* Close (left) and Next (right) sit on the centre line */
.info__close { grid-column: 1; grid-row: 2; }
.info__next  { grid-column: 4; grid-row: 2; }

/* Text block — title line · description · credits · website.
   Spans the full screen height and scrolls up across the whole screen (like the
   About bio); the first line still starts on the centre line via padding-top. */
.info__text {
  grid-column: 2;
  grid-row: 1 / 3;
  align-self: stretch;
  min-height: 0;                    /* stay 100vh and scroll inside (don't grow + clip) */
  display: flex;
  flex-direction: column;
  max-width: 60ch;
  color: var(--fg);                 /* dark text on the white panel (scoped light) */
  padding-top: calc(50vh - 0.6em);  /* first line lines up with the bar title */
  padding-bottom: 15vh;             /* blank black space below the text */
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;            /* Firefox */
  -ms-overflow-style: none;         /* IE/Edge */
}
.info__text::-webkit-scrollbar { width: 0; }   /* WebKit */

.info__head {
  margin-bottom: 1.6em;
  letter-spacing: -0.015em;
}

.info__desc {
  margin: 0;
  max-width: none;
}

.info__credits {
  margin-top: 1.6em;
}
.info__credits p { margin: 0; }
.info__credits p + p { margin-top: 1em; }

.info__website {
  align-self: flex-start;
  margin-top: 1.6em;
}
.info__text a {
  color: var(--fg);   /* dark links on the white panel */
  text-decoration: underline;
  text-underline-offset: 3px;
}

.info__close,
.info__next {
  background: none;
  border: none;
  color: var(--fg);
  font: inherit;
  text-decoration: none;     /* Next/Close not underlined */
  white-space: nowrap;
}

/* --- Open transition --- close/text/next slide from the bar (handled by JS
   FLIP); the image column slides in from the right. --- */
.info__row > * {
  transition: transform 0.55s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.45s ease;
}
.info:not(.is-open) .info__row > * { opacity: 0; }
.info:not(.is-open) .info__thumbs { transform: translateX(-6vw); }
.info.is-open .info__thumbs { transform: translateX(0); }

/* Image column — right side, scrolls through the whole screen */
.info__thumbs {
  grid-column: 3;
  grid-row: 1 / 3;                  /* full viewport height */
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: clamp(4px, 0.6vw, 9px);      /* smaller space between the images */
  padding-top: calc(50vh - 0.6em);  /* first image lines up with the text */
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;            /* Firefox: hide scrollbar */
}

.info__thumbs::-webkit-scrollbar {
  width: 0;                         /* WebKit: hide scrollbar */
}

.info__thumb {
  display: block;
  width: 100%;                      /* fill the column — one wide image */
  aspect-ratio: var(--screen-ratio, 1.6);   /* landscape: same crop as full screen */
  object-fit: cover;
}

/* Portrait: full column width (same weight as the landscape ones),
   natural height so the whole image is visible. */
.info__thumb.is-portrait {
  aspect-ratio: auto;
  height: auto;
}

/* `full`/`band` media keeps its natural aspect in the Info column too — never
   cover-cropped to the screen ratio (e.g. a wide process strip stays whole). */
.info__thumb--full {
  aspect-ratio: auto;
  height: auto;
  object-fit: contain;
}

/* Two thumbnails side by side (same gap as the column). */
.info__pair {
  display: flex;
  gap: clamp(4px, 0.6vw, 9px);
  width: 100%;
}
.info__pair-img {
  flex: 1 1 0;
  min-width: 0;
  height: auto;
  align-self: stretch;              /* both fill the row's height... */
  object-fit: cover;                /* ...by cropping, never stretching (photos keep their proportions) */
  display: block;
}

/* A written section (Challenge / Problem / Solution / Outcome …) sitting between
   the images in the column. */
.info__story {
  color: var(--fg);
  padding-block: clamp(10px, 2vh, 22px);
  line-height: 1.3;
}
.info__story-h {
  font-size: 1em;          /* same font/size as the rest of the text */
  font-weight: 400;
  letter-spacing: -0.015em;
  margin-bottom: 0.45em;
  opacity: 0.5;            /* a quiet label above the text */
}
.info__story-b { margin: 0; }

/* The written sections live in the image column on desktop; the mobile copy in the
   text flow is hidden here and only revealed on small screens (below). */
.info__stories-m { display: none; }

/* "Scroll for more" cue — mobile-only (shown via the media query below). */
.info__more { display: none; }

/* Booklet inside the info column: one in-place page, the cursor's horizontal
   position flips between them (same as the full-screen one). */
.info__booklet {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.info__page {
  display: none;
  width: auto;
  height: auto;
  max-width: calc(100% - clamp(48px, 18vw, 90px));   /* leave room for the side arrows */
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}
.info__page.is-on { display: block; }

/* Stack into a single scrolling column on small screens */
@media (max-width: 900px) {
  .info {
    overflow-y: auto;             /* whole panel scrolls on mobile */
  }
  .info__row {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    height: auto;
    row-gap: clamp(16px, 4vw, 28px);
    padding: clamp(72px, 16vh, 140px) clamp(20px, 6vw, 36px) 10vh;
  }
  .info__row > * { grid-row: auto; grid-column: 1; }
  .info__text {
    padding-top: 0;
    padding-bottom: 0;
    overflow-y: visible;
    max-width: none;
  }
  .info__thumbs {
    grid-row: auto;
    grid-column: 1;
    padding-top: 0;
    width: min(420px, 80vw);
  }
}

/* ============================================================
   Mobile — logo centred on the left, controls centered, nav hidden
   (the nav lives only on the homepage on mobile)
   ============================================================ */
@media (max-width: 760px) {
  .nav { display: none; }

  /* A project scrolls exclusively in the vertical direction — block sideways panning
     so wide media / diagonal swipes can never scroll the page horizontally (pinch-zoom
     still allowed; the booklet flips by tapping its left/right half, not by swiping).
     `clip` (NOT hidden): hidden would make the root a scroll container and iOS then keeps
     the media out of the status-bar / toolbar safe areas — the black bands. clip fills. */
  html, body { touch-action: pan-y pinch-zoom; overflow-x: clip; }

  /* Drop the bar's translateY(-50%) so the fixed logo anchors to the viewport (the
     transformed bar would otherwise trap it mid-screen). The controls stay centred
     (they're absolute inside the still-fixed bar at top:50%). */
  .bar { transform: none; }

  /* GiuliaRinaldo moves up to the top-left corner (the home link), independent of
     the centred bar so the project name can take its old centre-line spot. */
  .logo {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + clamp(20px, 4vh, 34px));
    left: clamp(20px, 2.2vw, 36px);
    transform: none;
  }

  /* The project name sits on the left (where GiuliaRinaldo used to be) and "Info"
     on the right — both on the centre line. Tap either to open the info. */
  #next { display: none; }
  #info { display: inline-block; order: 1; }   /* re-shown, pushed to the right edge */
  .ctrl--title { order: 0; }
  .controls {
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    justify-content: space-between;
    padding-inline: clamp(20px, 2.2vw, 36px);
    gap: 0;
    text-align: left;
    pointer-events: none;   /* the empty middle lets touch-scrolls reach the media */
    transition: opacity 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  .ctrl--title,
  #info { pointer-events: auto; }   /* …only the name + Info stay tappable */
  body.info-open .controls {
    opacity: 0;
    pointer-events: none;
  }

  /* Media is responsive: full screen-width, natural height, stacked flush with
     no gaps (no full-screen letterboxing). Pairs keep their own full-height crop
     plus the before/after gap. */
  .media__item { min-height: 0; }
  /* Black breathing room above a flagged first image (Dissolving), matching the black
     band at the end of the project. */
  .media__item--padtop { padding-top: clamp(48px, 14vh, 160px); }
  .media__el:not(.media__pair-img) {
    width: 100vw;
    height: auto;
    max-width: 100vw;
    max-height: none;
    object-fit: contain;
  }

  /* Side-by-side pair: responsive (each at its natural height), with a gap
     between the two and above/below the whole section. */
  .media__pair {
    width: 100vw;
    height: auto;
    gap: clamp(6px, 2.4vw, 14px);
    align-items: flex-start;
  }
  .media__pair-img {
    flex: 1 1 0;
    min-width: 0;
    width: auto;
    height: auto;
    object-fit: contain;
  }
  .media__item--pair { margin-block: clamp(12px, 4vw, 24px); }

  /* Booklet: one full-width page at a time, swipe through them. */
  .media__booklet { height: auto; padding: clamp(12px, 3vw, 28px); }
  .media__page { max-width: 100%; max-height: 80vh; }

  /* Pairs cap to their 1/N share on mobile too. */
  .media__pair-img { max-width: calc((100vw - (var(--n, 2) - 1) * clamp(6px, 2.4vw, 14px)) / var(--n, 2)); }

  /* ---- Info as a bottom sheet over the still-visible full-screen media ---- */
  /* z-index:auto (no isolation) so the text's mix-blend-mode: difference inverts
     against the actual photo behind it, not just the dark scrim. The bar would
     then paint over the sheet, so it's hidden while the info is open. */
  .info {
    background: transparent;
    overflow: visible;
    z-index: auto;
  }
  body.info-open .bar {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  /* The row carries position:relative + z-index:1, which traps the text in its
     own stacking context — so difference could only see inside the row. Release
     it so the text blends all the way down to the scrim and the photo. */
  .info__row { z-index: auto; }
  .info__thumbs,
  .info__close,
  .info__next { display: none; }

  /* Very dark blur scrim behind the text: opaque at the bottom, fading up. */
  .info::before {
    content: "";
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: 82vh;
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    -webkit-mask-image: linear-gradient(to top, #000 50%, transparent);
            mask-image: linear-gradient(to top, #000 50%, transparent);
    /* Mostly dark (plain white text, readable) with only a gentle fade near the
       top — so the difference-blended text inverts against the photo just a
       little at the top, easing to plain white lower down (a top→bottom ramp). */
    background: linear-gradient(to top, rgba(0, 0, 0, 0.86) 0%, rgba(0, 0, 0, 0.66) 50%, rgba(0, 0, 0, 0.4) 100%);
    pointer-events: none;
  }
  .info__text {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    max-width: none;
    max-height: 74vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* Hug the bottom (over the darkest part of the scrim) and keep a big empty
       area above the text — tapping anywhere up there closes the info. */
    padding: clamp(28px, 6vh, 48px) clamp(20px, 6vw, 28px) clamp(26px, 7vh, 44px);
    color: #fff;
    mix-blend-mode: difference;   /* text in inverted colours over the dark scrim */
  }
  .info__text a { color: #fff; }   /* match the white blended text over the photo */
  .info__head { margin-bottom: 0.8em; }

  /* Tighter, consistent gap before the written sections and the link(s) — the same
     1em rhythm as the lines within the credits block (no oversized jump). */
  .info__stories-m,
  .info__website { margin-top: 1em; }

  /* When the description is taller than the sheet, a quiet bobbing "↓" hints there's
     more to read; it fades out once you've scrolled to the bottom (toggled in JS). */
  .info__more {
    display: block;
    position: fixed;
    left: 50%;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
    z-index: 3;
    color: #fff;
    mix-blend-mode: difference;
    font-size: 1.2em;
    line-height: 1;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
    animation: info-more-bob 1.5s ease-in-out infinite;
  }
  .info.has-more .info__more { opacity: 0.8; }
  @keyframes info-more-bob {
    0%, 100% { transform: translate(-50%, 0); }
    50%      { transform: translate(-50%, 5px); }
  }

  /* Show the written sections in the text sheet (the image column is hidden).
     They sit inside the mix-blend-difference text, so use the ink colour. */
  .info__stories-m { display: block; margin-top: 1.4em; }
  .info__stories-m .info__story { padding-block: 0.6em 0; color: var(--ink); }
}

::selection { background: var(--fg); color: var(--bg); }
::-moz-selection { background: var(--fg); color: var(--bg); }

/* Info text: selecting gives a white highlight with black text. The difference
   blend is switched off while selecting (JS adds .selecting) so it isn't
   inverted away. */
.info__text.selecting { mix-blend-mode: normal; color: var(--fg); }   /* dark text on the white panel while selecting */
.info__text ::selection,
.info__text::selection { background: #fff; color: #000; }
.info__text ::-moz-selection,
.info__text::-moz-selection { background: #fff; color: #000; }

@media (max-width: 760px) {
  :root { --fs-body: clamp(17px, 4.8vw, 20px); }
}

/* Mobile: the logo stays pinned to the left (base .bar space-between + hidden nav),
   matching every other page; the project title (controls) is absolutely positioned at
   the right edge. */

/* "Ask GiuLLM" trigger on the project page. Desktop: the cursor-trailing pill (.gm-fab),
   shown only while the Info panel is open. Mobile: the static button inside the panel. */
/* The static "Ask GiuLLM" button never shows inside the Info panel: desktop uses the
   cursor-trailing pill, mobile uses the persistent bottom-right bubble. */
.info .gm-ask { display: none; }
@media (hover: hover) {
  body:not(.info-open) .gm-fab { opacity: 0 !important; } /* pill only appears with the Info panel */
}

/* ---- Mobile project switcher: the full project list (like the index), slid in from
   the right when you tap the project name. Picking one opens that project. ---- */
.proj-switch {
  position: fixed;
  inset: 0;
  z-index: 8;
  /* Dark, blurred scrim concentrated on the right (under the list) and fading to clear
     on the left — like the Info panel's bottom-anchored scrim. The mask fades the blur
     out too, so the left side of the screen stays mostly untouched. */
  background: linear-gradient(to left, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.5) 45%, rgba(0, 0, 0, 0) 82%);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  -webkit-mask-image: linear-gradient(to left, #000 42%, transparent 88%);
          mask-image: linear-gradient(to left, #000 42%, transparent 88%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.4s ease, visibility 0s linear 0.4s;
}
.proj-switch.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.4s ease;
}
.proj-switch__list {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(80vw, 360px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-end;
  gap: clamp(30px, 8vw, 52px);   /* identical to the index project list's row-gap */
  padding: 14vh clamp(20px, 6vw, 36px);
  text-align: right;
  transform: translateX(40px);
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  overflow-y: auto;
  scrollbar-width: none;
}
.proj-switch__list::-webkit-scrollbar { display: none; }
.proj-switch.is-open .proj-switch__list { transform: translateX(0); }
/* Identical type to the index project list: one size, no hierarchy, year/title/cat
   each on their own line. */
.proj-switch__item {
  display: flex;
  flex-direction: column;
  color: #fff;
  text-decoration: none;
  line-height: 1.15;
  font-size: var(--fs-body);
  font-weight: 400;
}
.proj-switch__year,
.proj-switch__title,
.proj-switch__cat { display: block; }
.proj-switch__item.is-current { opacity: 0.4; }   /* the project you're already on */
