@font-face {
  font-family: "PP Mori";
  src:
    url("./fonts/PPMori-Thin.woff2") format("woff2"),
    url("./fonts/PPMori-Thin.woff") format("woff");
  font-weight: 100;
  font-style: normal;
}

@font-face {
  font-family: "PP Mori";
  src:
    url("./fonts/PPMori-Thin.woff2") format("woff2"),
    url("./fonts/PPMori-Thin.woff") format("woff");
  font-weight: 200;
  font-style: normal;
}

@font-face {
  font-family: "PP Mori";
  src:
    url("./fonts/PPMori-Regular.woff2") format("woff2"),
    url("./fonts/PPMori-Regular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: "PP Mori";
  src:
    url("./fonts/PPMori-Bold.woff2") format("woff2"),
    url("./fonts/PPMori-Bold.woff") format("woff");
  font-weight: 600;
  font-style: normal;
}

@font-face {
  font-family: "PP Mori";
  src:
    url("./fonts/PPMori-Bold.woff2") format("woff2"),
    url("./fonts/PPMori-Bold.woff") format("woff");
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: "PP Mori";
  src:
    url("./fonts/PPMori-Bold.woff2") format("woff2"),
    url("./fonts/PPMori-Bold.woff") format("woff");
  font-weight: 900;
  font-style: normal;
}

@font-face {
  font-family: "PP Mori";
  src:
    url("./fonts/PPMori-Italic.woff2") format("woff2"),
    url("./fonts/PPMori-Italic.woff") format("woff");
  font-weight: 400;
  font-style: italic;
}

:root {
  --bg: #ffffff;
  --text: #111111;
  --muted: #111111;
  --text-on-video: #ffffff;
  --line-on-video: rgba(255, 255, 255, 0.45);
  --line: #e7e7e7;
  --line-strong: #999999;
  --body-size: 12px;
  --menu-size: 14px;
  --title-size: 24px;
  --section-title-size: 14px;
  --footer-size: 11px;
  --location-size: 10px;
  --kicker-size: 10px;
  --body-weight: 400;
  --kicker-weight: 400;
  --section-title-weight: 400;
  --hero-title-weight: 400;
  --work-title-weight: 400;
  --footer-weight: 400;
  --font-sans: "PP Mori", Arial, Helvetica, sans-serif;
  --hover-fade-duration: 360ms;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--body-size);
  transition: color var(--hover-fade-duration) ease, background-color var(--hover-fade-duration) ease;
}

body.video-active {
  color: var(--text-on-video);
}



body.project-inline-open {
  overflow: hidden;
}

/* Same treatment as body.project-inline-open: the overlay below is fixed +
   scrolls its own content internally, so the real page underneath must not
   scroll at all — that's what let the (now invisible-but-still-in-flow)
   homepage content extend the document past where the overlay's own
   backdrop covers, revealing a sliver of it past that point. */
body.about-open {
  overflow: hidden;
}

.project-preview {
  position: fixed;
  inset: 0;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--hover-fade-duration) ease;
  /* Solid dark backdrop so nothing white can bleed through between layers. */
  background: #000;
}

.project-preview__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.42);
  opacity: 0;
  transition: opacity var(--hover-fade-duration) ease;
}

.project-preview__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.42);
  opacity: 0;
  transition: opacity var(--hover-fade-duration) ease;
}

.project-preview__video.is-active,
.project-preview__image.is-active {
  opacity: 1;
}

.project-preview.is-visible {
  opacity: 1;
}





.about-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  opacity: 0;
  pointer-events: none;
  transition: opacity 260ms ease;
}

.about-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.about-overlay__backdrop {
  position: absolute;
  inset: 0;
  background: #ffffff;
}

.about-overlay__panel {
  position: absolute;
  inset: 0;
  color: var(--text);
}

.about-overlay__close {
  position: absolute;
  top: 16px;
  right: 10px;
  z-index: 2;
  width: 28px;
  height: 28px;
  border: 0;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  padding: 0;
}

.about-overlay__close::before,
.about-overlay__close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 18px;
  height: 1px;
  background: currentColor;
  transform-origin: center;
}

.about-overlay__close::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.about-overlay__close::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}

.about-overlay__inner {
  position: relative;
  width: min(740px, calc(100% - 180px));
  max-height: 100vh;
  margin: 0 auto;
  overflow-y: auto;
  padding: 88px 0 110px;
  display: grid;
  gap: 26px;
}

.about-overlay__section .hero__text {
  max-width: none;
}

/* Hidden on mobile — the photo is desktop-only, see the min-width:641px
   override below, where there's room in the left margin for it. */
.about-overlay__photo {
  display: none;
}

/* Desktop already shows the name via .desktop-chrome__name, fixed
   independently and unaffected by the overlay's own scroll — this
   duplicate only exists for the mobile treatment below. */
.about-overlay__name {
  display: none;
}

.filmography-group {
  margin-top: 14px;
}

.filmography-group:first-of-type {
  margin-top: 8px;
}

@media (min-width: 641px) {
  .filmography-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 32px;
  }

  .filmography-group,
  .filmography-group:first-of-type {
    margin-top: 8px;
  }
}

.filmography-group__label {
  margin: 0 0 6px;
  color: var(--muted);
  font-size: calc(var(--body-size) * 0.92);
  letter-spacing: 0.02em;
}

.filmography-legend {
  margin: 14px 0 0;
  color: var(--muted);
  font-size: calc(var(--body-size) * 0.8);
}

/* Two copies of the same legend: on mobile (single stacked column) it
   belongs right after Réalisations, the only group it applies to; on
   desktop (two columns side by side) that spot would land it mid-row
   next to Montages, so it moves back below both columns instead. */
.filmography-legend--inline {
  display: block;
}

.filmography-legend--block {
  display: none;
}

@media (min-width: 641px) {
  .filmography-legend--inline {
    display: none;
  }

  .filmography-legend--block {
    display: block;
  }
}

.filmography-group--ads {
  margin-top: 26px;
}

.filmography-group--ads .title-list,
.about-overlay__section--collaborators .title-list {
  text-align: center;
}

.about-overlay__bottom {
  margin-top: 6px;
  padding-top: 8px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.about-overlay__page-label {
  margin: 0;
  color: var(--text);
  font-size: var(--kicker-size);
  font-weight: var(--kicker-weight);
  line-height: 1;
}

.about-overlay__icons {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}





























.site-frame {
  position: relative;
  max-width: 880px;
  margin: 0 auto;
  min-height: 100vh;
  padding: 22px 72px 96px;
}

.desktop-chrome {
  position: fixed;
  top: 16px;
  left: 0;
  right: 0;
  max-width: none;
  margin: 0;
  padding: 0 10px;
  z-index: 35;
  display: block;
  min-height: 32px;
  pointer-events: none;
}

.desktop-chrome__name,
.desktop-chrome__role {
  margin: 0;
  font-size: var(--kicker-size);
  font-weight: var(--kicker-weight);
  letter-spacing: 0;
  line-height: 1.1;
  color: var(--text);
  transition: color var(--hover-fade-duration) ease;
}

.desktop-chrome__name {
  position: absolute;
  top: 0;
  left: 10px;
  pointer-events: auto;
  border: 0;
  padding: 0;
  background: transparent;
  font-family: var(--font-sans);
  cursor: pointer;
}

.desktop-chrome__location {
  margin: 0;
  font-size: var(--location-size);
  font-weight: var(--kicker-weight);
  letter-spacing: 0;
  color: var(--text);
  transition: color var(--hover-fade-duration) ease;
}

.desktop-chrome__about {
  position: absolute;
  top: 0;
  right: 10px;
  pointer-events: auto;
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--kicker-size);
  font-weight: var(--kicker-weight);
  line-height: 1.1;
}

.desktop-chrome__meta {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
  display: block;
}

.desktop-chrome__role {
  position: relative;
  display: block;
  /* A taller line-height (rather than a top offset on .role-word) gives
     accents on capitals (RÉALISATEUR) headroom via the browser's own
     half-leading, split evenly above and below the glyph — height just
     matches so the box doesn't clip that leading. Since the flip below
     is driven by translateY(±115%) of the word's own (now taller) line
     box, the motion stays symmetric instead of the top-offset approach,
     which pushed the resting word down without moving the clipping
     window to match, throwing off the animation. */
  height: 1.5em;
  overflow: hidden;
  line-height: 1.5;
}

.desktop-chrome__role .role-word {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  transition: transform 240ms ease, opacity 240ms ease;
}

.desktop-chrome__role .role-word--out {
  transform: translateY(0);
  opacity: 1;
}

.desktop-chrome__role .role-word--in {
  transform: translateY(-115%);
  opacity: 0;
}

.desktop-chrome__role.is-rolling .role-word--out {
  transform: translateY(115%);
  opacity: 0;
}

.desktop-chrome__role.is-rolling .role-word--in {
  transform: translateY(0);
  opacity: 1;
}

.desktop-page-label {
  position: fixed;
  left: 10px;
  bottom: 18px;
  z-index: 41;
  margin: 0;
  /* See .desktop-chrome__role above — same accent-clipping fix. */
  height: 1.5em;
  line-height: 1.5;
  overflow: hidden;
  font-size: var(--kicker-size);
  font-weight: var(--kicker-weight);
  color: var(--text);
  transition: color var(--hover-fade-duration) ease;
}

.desktop-page-label .label-word {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  transition: transform 240ms ease, opacity 240ms ease;
}

.desktop-page-label .label-word--out {
  transform: translateY(0);
  opacity: 1;
}

.desktop-page-label .label-word--in {
  transform: translateY(-115%);
  opacity: 0;
}

.desktop-page-label.is-rolling .label-word--out {
  transform: translateY(115%);
  opacity: 0;
}

.desktop-page-label.is-rolling .label-word--in {
  transform: translateY(0);
  opacity: 1;
}

.mobile-name,
.mobile-about-toggle {
  display: none;
}

.mobile-page-label {
  display: none;
  margin: 0;
  color: var(--text);
  font-size: var(--kicker-size);
  font-weight: var(--kicker-weight);
  line-height: 1;
}

.mobile-topbar {
  display: none;
}

.mobile-about-toggle {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--kicker-size);
  font-weight: var(--kicker-weight);
  line-height: 1;
}

.eyebrow {
  margin: 0;
  color: var(--muted);
  font-size: var(--section-title-size);
  font-weight: var(--section-title-weight);
  transition: color var(--hover-fade-duration) ease;
}

.nameplate,
.hero h2,
.section-heading h3,
.project-card h4 {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  letter-spacing: 0;
}

.frame-name {
  position: fixed;
  top: 0;
  bottom: 0;
  z-index: 5;
  color: var(--text);
  font-size: clamp(42px, 8vw, 72px);
  font-weight: 100;
  line-height: 1;
  text-transform: uppercase;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  padding: 0 2px;
  transition: color var(--hover-fade-duration) ease;
  display: none;
}

.frame-name--left {
  left: 0;
  transform: rotate(180deg);
}

.frame-name--right {
  right: 0;
  transform: rotate(180deg);
}

.frame-name span {
  display: block;
  transform: rotate(90deg);
  transform-origin: center;
}

.footer-bar a,
.footer-bar a:visited,
.footer-bar a {
  color: var(--text);
  text-decoration: none;
  transition: color var(--hover-fade-duration) ease;
}

.content-stack {
  display: grid;
  gap: 36px;
  padding-top: 68px;
}

.hero,
.section {
  scroll-margin-top: 32px;
}

.page-intro {
  display: none;
}

.hero__panel {
  display: grid;
  gap: 10px;
}

.hero__kicker {
  margin: 0;
  color: var(--muted);
  font-size: var(--section-title-size);
  font-weight: var(--section-title-weight);
  transition: color var(--hover-fade-duration) ease;
}

.hero h2 {
  max-width: 14em;
  font-size: var(--title-size);
  font-weight: var(--hero-title-weight);
  line-height: 1.08;
}

.intro,
.hero__text,
.project-card p,
.list-row,
.footer-bar {
  color: var(--muted);
  font-size: calc(var(--body-size) * 0.92);
  font-weight: var(--body-weight);
  line-height: 1.55;
  transition: color var(--hover-fade-duration) ease, border-color var(--hover-fade-duration) ease;
}

.hero__text,
.project-card p {
  margin: 0;
  max-width: 36rem;
  white-space: pre-line;
}

.section-heading {
  display: grid;
  gap: 4px;
  margin-bottom: 10px;
}

/* display:contents removes the button from the layout box entirely on
   desktop, so .eyebrow lays out exactly as if the button weren't there —
   the collapse affordance only exists on mobile (see media query below). */
.section-heading__toggle {
  all: unset;
  display: contents;
}

.section-heading__chevron {
  display: none;
}

.title-list {
  display: block;
  max-width: 40rem;
  color: var(--text);
  line-height: 1.35;
}

.title-list__item {
  display: inline-block;
  padding: 0;
  color: var(--text);
  font-size: calc(var(--body-size) * 0.92);
  font-weight: var(--work-title-weight);
  line-height: inherit;
  cursor: default;
  text-decoration: none;
  transition: color var(--hover-fade-duration) ease;
  white-space: nowrap;
}

button.title-list__item {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  margin: 0;
  padding: 0;
  background: transparent;
  font-family: var(--font-sans);
  text-align: inherit;
}

.title-list__thumb {
  display: none;
}

.title-list[data-project-overlay="true"] .title-list__item {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.14em;
  text-decoration-color: currentColor;
}

.title-list[data-project-overlay="true"] .title-list__item,
.title-list__item--link,
.project-inline__external,
.desktop-chrome__name,
.desktop-chrome__about,
.mobile-about-toggle {
  cursor: pointer;
}

.title-list__item--link:link,
.title-list__item--link:visited,
.title-list__item--link:hover,
.title-list__item--link:focus,
.title-list__item--link:active {
  color: var(--text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.14em;
  text-decoration-color: currentColor;
}

.title-list__item--stacked {
  display: block;
  white-space: pre-line;
}

/* "🏆 2025 — Rouge forêt — ..." split into marker/year/text columns: the
   trophy sits in its own column to the left (outside the date+text block,
   in line with the section headings' own left edge, which stay plain text
   since they don't match the year pattern), years all line up in the next
   column regardless of whether a given entry has a trophy, and wrapped
   lines hang-indent under the text column instead of under the year. */
.title-list__item--aligned {
  display: grid;
  grid-template-columns: 1.5em 3.2em 1fr;
  column-gap: 0.3em;
  /* Without this, stretch (the grid default) sizes each cell to the full,
     possibly-multi-line row height when the text column wraps — the marker
     and year cells then aren't positioned consistently, which is what was
     throwing off centering the trophy against the year. Anchoring all
     three columns to the row's start keeps them level with each other and
     with the first line of wrapped text. */
  align-items: start;
}

/* Edit mode (editor.js) flattens marker/year/text into a single synthetic
   .title-list__label so the whole row can be one contenteditable region —
   without an explicit column span that label auto-places into just the
   first (1.5em) grid track, wrapping every couple characters. */
.title-list__item--aligned .title-list__label {
  grid-column: 1 / -1;
}

.title-list__marker {
  text-align: center;
}

/* Emoji glyphs (🏆) render taller than surrounding text at the same
   font-size, with their own font's internal centering — vertical-align
   tuning alone kept landing off in one context or the other. Instead,
   center the glyph within its own explicit line-height box (matching the
   text's line-height), which lines its middle up with the middle of the
   adjacent year regardless of context (grid cell vs. inline text). */
.title-list__marker,
.project-inline__awards-emoji,
.title-list__suffix-emoji {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.35em;
  font-size: 0.75em;
  vertical-align: middle;
}

.title-list__suffix-emoji {
  margin-left: 0.3em;
}

.title-list__marker {
  margin-top: 0.15em;
}

.title-list__year {
  font-variant-numeric: tabular-nums;
}

.title-list__break {
  display: block;
  height: 0.95em;
}

.title-list__separator {
  color: var(--muted);
  font-size: calc(var(--body-size) * 0.92);
  font-weight: var(--work-title-weight);
  transition: color var(--hover-fade-duration) ease;
}

.project-mention {
  cursor: default;
  text-decoration: none;
}

.bio-mention {
  cursor: default;
}

/* Fullscreen sheet on every breakpoint: covers the page (and the background
   video), and both states share the same geometry so open/close is a pure
   fade. */
.project-inline {
  position: fixed;
  inset: 0;
  z-index: 90;
  padding: 56px 16px 40px;
  background: #ffffff;
  overflow-y: auto;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 260ms ease,
    visibility 0s linear 260ms;
}

.project-inline.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition:
    opacity 260ms ease,
    visibility 0s linear 0s;
}

.project-inline__close {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  width: 22px;
  height: 22px;
  border: 0;
  padding: 0;
  margin: 0;
  background: transparent;
  color: var(--text);
  cursor: pointer;
}

/* Mobile default: overlaid on the player (there's no room beside it on a
   narrow screen), centered vertically, flush to its left/right edges. */
.project-inline__nav {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 10px;
  pointer-events: none;
  transition: opacity 200ms ease;
}

/* Mobile only: the arrows overlay the video here (no room beside it, see
   above), so fade them out during playback instead of leaving them sitting
   on top of the picture the whole time — desktop's arrows live outside the
   player already and don't need this. */
@media (max-width: 640px) {
  .project-inline__nav.is-playing {
    opacity: 0;
    pointer-events: none;
  }

  .project-inline__nav.is-playing .project-inline__nav-link {
    pointer-events: none;
  }
}

.project-inline__nav-link {
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 50%;
  padding: 0;
  margin: 0;
  background: rgba(255, 255, 255, 0.85);
  color: #111111;
  cursor: pointer;
  transition: background 160ms ease;
}

.project-inline__nav-link:hover {
  background: #ffffff;
}

.project-inline__nav-link:disabled {
  visibility: hidden;
}

.project-inline__nav-icon {
  width: 18px;
  height: 18px;
}

/* Desktop: enough side margin exists around the player to move the arrows
   fully outside it instead of overlapping the picture — extend the nav's
   box past the player's own edges (its wrapper doesn't clip, unlike the
   player itself) and drop the video-scrim circle since these now sit over
   the plain page background, not the image. */
@media (min-width: 641px) {
  .project-inline__nav {
    /* .project-inline's own side padding at this breakpoint is 32px (see
       below) — offsetting by exactly that much keeps the arrows just
       outside the player while still landing a few px inside the sheet's
       edge at the narrowest widths this breakpoint covers (~641px), where
       there's no extra margin beyond that padding to spare. */
    left: -28px;
    right: -28px;
    padding: 0;
  }

  .project-inline__nav-link {
    width: 24px;
    height: 24px;
    background: transparent;
    color: var(--text);
  }

  .project-inline__nav-link:hover {
    background: transparent;
    opacity: 0.6;
  }

  .project-inline__nav-icon {
    width: 18px;
    height: 18px;
  }
}

.project-inline__close::before,
.project-inline__close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 1px;
  background: currentColor;
  transform-origin: center;
}

.project-inline__close::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.project-inline__close::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}

.project-inline__inner {
  display: grid;
  gap: 14px;
}

/* Holds the arrows outside .project-inline__player (which clips its own
   contents to crop the video/embed) so they can sit beside it instead of
   overlapping the picture — see .project-inline__nav below. */
.project-inline__player-wrap {
  position: relative;
}

.project-inline__player {
  position: relative;
  width: 100%;
  background: #000;
  overflow: hidden;
}

.project-inline__player:fullscreen {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
}

.project-inline__player:fullscreen .project-inline__video,
.project-inline__player:fullscreen .project-inline__embed {
  width: 100%;
  height: 100%;
  max-height: none;
  aspect-ratio: auto;
  object-fit: contain;
}

.project-inline__player:-webkit-full-screen {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
}

.project-inline__player:-webkit-full-screen .project-inline__video,
.project-inline__player:-webkit-full-screen .project-inline__embed {
  width: 100%;
  height: 100%;
  max-height: none;
  aspect-ratio: auto;
  object-fit: contain;
}

/* CSS fallback when no fullscreen API exists (iPhone + iframe player). */
.project-inline__player.is-fake-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: #000;
}

.project-inline__player.is-fake-fullscreen .project-inline__video,
.project-inline__player.is-fake-fullscreen .project-inline__embed {
  width: 100%;
  height: 100%;
  max-height: none;
  aspect-ratio: auto;
  object-fit: contain;
}

.project-inline__title {
  margin: 0;
  padding-right: 34px;
  font-size: var(--title-size);
  font-weight: var(--hero-title-weight);
}

.project-inline__video {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: none;
  object-fit: contain;
  background: #000;
}

.project-inline__embed {
  display: none;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  background: #000;
}

.project-inline__controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  grid-template-columns: auto 1fr auto auto auto;
  align-items: center;
  gap: 14px;
  padding: 10px 10px 8px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.72));
}

.project-inline__controls.is-hidden {
  display: none;
}

.project-inline__control {
  border: 0;
  padding: 0;
  margin: 0;
  background: transparent;
  color: #ffffff;
  font-family: var(--font-sans);
  font-size: calc(var(--body-size) * 0.82);
  font-weight: var(--body-weight);
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
}

.project-inline__control--icon {
  width: 18px;
  min-width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.project-inline__icon {
  width: 16px;
  height: 16px;
  display: block;
}

.project-inline__control--icon .project-inline__icon--volume-off {
  display: none;
}

.project-inline__control--icon.is-muted .project-inline__icon--volume-on {
  display: none;
}

.project-inline__control--icon.is-muted .project-inline__icon--volume-off {
  display: block;
}

.project-inline__progress {
  position: relative;
  display: flex;
  align-items: center;
  height: 16px;
  width: 100%;
  cursor: pointer;
  touch-action: none;
}

.project-inline__progress-track {
  position: relative;
  height: 2px;
  width: 100%;
  background: rgba(255, 255, 255, 0.35);
  overflow: hidden;
}

.project-inline__progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: #ffffff;
}

.project-inline__volume {
  -webkit-appearance: none;
  appearance: none;
  width: 60px;
  height: 16px;
  background: transparent;
  cursor: pointer;
  touch-action: none;
}

.project-inline__volume::-webkit-slider-runnable-track {
  height: 2px;
  background: rgba(255, 255, 255, 0.35);
  border-radius: 1px;
}

.project-inline__volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #ffffff;
  cursor: pointer;
  margin-top: -4px;
}

.project-inline__volume::-moz-range-track {
  height: 2px;
  background: rgba(255, 255, 255, 0.35);
  border-radius: 1px;
}

.project-inline__volume::-moz-range-thumb {
  width: 10px;
  height: 10px;
  border: 0;
  border-radius: 50%;
  background: #ffffff;
  cursor: pointer;
}

.project-inline__external {
  display: none;
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

.project-inline__description {
  margin: 0;
  font-size: calc(var(--body-size) * 0.92);
  font-weight: var(--body-weight);
  line-height: 1.55;
  white-space: pre-line;
}

.project-inline__credits {
  margin: 0 auto;
  max-width: 760px;
  width: 100%;
  font-size: calc(var(--body-size) * 0.92);
  font-weight: var(--body-weight);
  line-height: 1.55;
  text-align: center;
  white-space: normal;
}

/* Reminder of the project's own win(s), reusing the exact lines from the
   About page's Prix & distinctions list — only rendered when the project
   has a project.awards value (real wins only, not bare festival selections
   or nominations). */
.project-inline__awards {
  margin: 0 auto;
  max-width: 760px;
  width: 100%;
  font-size: calc(var(--body-size) * 0.92);
  font-weight: var(--body-weight);
  line-height: 1.55;
  text-align: center;
}

.project-inline__awards.is-empty {
  display: none;
}

.project-inline__awards-line {
  margin: 0;
}

.project-inline__awards-line + .project-inline__awards-line {
  margin-top: 0.3em;
}

@media (min-width: 641px) {
  .project-inline__credits,
  .project-inline__awards {
    font-size: calc(var(--body-size) * 0.8);
  }
}

@media (max-width: 640px) {
  .project-inline__credits,
  .project-inline__awards {
    font-size: calc(var(--body-size) * 0.8);
  }
}

.project-inline,
.project-inline__title,
.project-inline__description,
.project-inline__awards,
.project-inline__credits,
.project-inline__external,
.project-inline__close {
  color: var(--text);
}

.footer-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-width: 880px;
  width: calc(100% - 20px);
  z-index: 40;
  display: flex;
  justify-content: center;
  gap: 0;
  align-items: center;
  margin: 0 auto;
  padding: 0 0 12px;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  font-size: var(--footer-size);
  font-weight: var(--footer-weight);
  overflow: visible;
  isolation: isolate;
}

body.about-open .desktop-chrome,
body.about-open .footer-bar {
  z-index: 85;
}

/* Same z-index as .footer-bar above would fall back to DOM order —
   the footer (later in the markup) would win that tiebreak and cover
   this label, same bug as project pages (see project-inline-open
   below). One level higher keeps it on top instead. */
body.about-open .desktop-page-label {
  z-index: 86;
}

/* .project-inline sits above the footer's default z-index
   (40), fully covering it — same reasoning as the About page above. */
body.project-inline-open .footer-bar {
  z-index: 95;
}

body.about-open .mobile-name,
body.about-open .mobile-about-toggle {
  z-index: 85;
}

body.about-open .desktop-chrome__about,
body.about-open .desktop-chrome__meta {
  visibility: hidden;
  pointer-events: none;
}

body.about-open .content-stack,
body.project-inline-open .content-stack {
  visibility: hidden;
  pointer-events: none;
}

body.about-open .desktop-page-label,
body.about-open .footer-bar,
body.about-open .back-to-top {
  visibility: hidden;
  pointer-events: none;
}

body.about-open .mobile-about-toggle {
  visibility: hidden;
  pointer-events: none;
}

@media (min-width: 641px) {
  .content-stack {
    min-height: calc(100vh - 136px);
    display: flex;
    flex-direction: column;
    justify-content: center;
    position: relative;
  }

  /* .about-overlay__inner scrolling itself put its scrollbar right against
     the text column's edge (it's a centered, fixed-width box). Move the
     scroll to .about-overlay__panel instead — full viewport width, so the
     scrollbar lands at the true right edge of the screen — and let inner
     grow to its natural content height instead of capping/scrolling itself.
     The close button is absolutely positioned within panel, so without
     position:fixed here it would now scroll away with the content. */
  .about-overlay__panel {
    overflow-y: auto;
  }

  .about-overlay__inner {
    max-height: none;
    overflow-y: visible;
  }

  .about-overlay__close {
    position: fixed;
  }

  #realis,
  #montage {
    width: 100%;
  }

  #realis .section-heading,
  #montage .section-heading {
    justify-items: center;
    text-align: center;
  }

  #realis .title-list,
  #montage .title-list {
    margin-left: auto;
    margin-right: auto;
    text-align: center;
  }

  .project-inline {
    /* Extra top padding vs. the mobile default (56px) clears the persistent
       .desktop-chrome header, which ends around 48px on this breakpoint. */
    padding: 72px 32px 48px;
  }

  .project-inline .project-inline__inner {
    max-width: 860px;
    margin: 0 auto;
  }

  .project-inline .project-inline__video,
  .project-inline .project-inline__embed {
    max-height: min(68vh, 720px);
  }

  .project-inline .project-inline__close {
    top: 18px;
    right: 18px;
    color: #111111 !important;
  }

  .project-inline .project-inline__close::before,
  .project-inline .project-inline__close::after {
    background: #111111;
  }

  body.about-open .desktop-chrome {
    position: fixed;
    top: 16px;
  }

  body.video-active .project-inline,
  body.video-active .project-inline__title,
  body.video-active .project-inline__description,
  body.video-active .project-inline__credits,
  body.video-active .project-inline__external,
  body.video-active .project-inline__close {
    color: var(--text);
  }

  body.project-inline-open #realis,
  body.project-inline-open #montage {
    pointer-events: none;
  }

  body.about-open .desktop-page-label {
    visibility: visible;
    pointer-events: auto;
    position: fixed;
    left: 10px;
    bottom: 18px;
  }

  .about-overlay__page-label {
    display: none;
  }

  .about-overlay__bottom {
    display: none;
  }

  body.about-open .footer-bar,
  body.project-inline-open .footer-bar {
    visibility: visible;
    pointer-events: auto;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: none;
    margin: 0;
    /* Match the idle/home state's own padding (0 0 12px) exactly — this
       used to be 10px 0, which shifted the icon row a couple pixels from
       where it sits on the home page, visible as a jump on close. */
    padding: 0 0 12px;
    background: var(--bg);
    color: var(--text);
  }

  /* Project pages keep their own background video playing (and with it,
     .video-active) behind the sheet, whose same-specificity color rule
     (body.video-active .footer-bar) would otherwise win by coming later
     in the stylesheet and paint the icons white-on-white against this
     bar's opaque backing. */
  body.about-open.video-active .footer-bar,
  body.about-open.video-active .footer-bar a,
  body.about-open.video-active .footer-bar a:visited,
  body.project-inline-open.video-active .footer-bar,
  body.project-inline-open.video-active .footer-bar a,
  body.project-inline-open.video-active .footer-bar a:visited {
    color: var(--text);
  }
}

/* Charles's About-page photo, in the left margin — its own breakpoint
   (matches .about-overlay__inner's own min-width:641px switch to desktop
   behavior) since it's positioned relative to that column's own centering
   formula. Sized fluidly off the same formula the whole way down: the
   margin naturally stabilizes around ~58px wide once .about-overlay__inner's
   own width formula (min(740px, 100vw-180px)) starts shrinking below its
   740px cap (~920px viewport and under), so the photo keeps shrinking in
   step with the content column instead of disappearing at some threshold. */
@media (min-width: 641px) {
  /* Lives outside .about-overlay__inner entirely, so the content column
     keeps its original width instead of sharing it with a grid column.
     Fixed (not part of the scrolling content) so it holds its place the way
     the CHARLES GRENIER wordmark and the close button do. Horizontally
     centered at the midpoint of the left margin (half of
     .about-overlay__inner's own centering formula, since the margin runs
     from the viewport edge to the content column's left edge). Top/bottom
     pinned to the same 88px/110px .about-overlay__inner uses for its own
     padding, so the photo's edges line up with where the text column
     starts and ends — that implicitly sets the box's height, independent
     of its width. Like a Figma "Fill" image box: object-fit:cover recrops
     the photo to whatever the box's current width/height ratio is, so
     narrowing the window only changes the crop, not the photo's height —
     unlike an aspect-ratio box, which shrinks both dimensions together. */
  .about-overlay__photo {
    display: block;
    position: fixed;
    top: 88px;
    /* <img> is a replaced element: with width definite and height left to
       "auto", the browser derives height from the intrinsic image ratio
       no matter what top/bottom say — bottom alone never actually
       constrained it. Setting height explicitly is what makes it real. */
    height: calc(100vh - 198px);
    left: calc((50vw - min(370px, calc(50vw - 90px))) / 2);
    transform: translateX(-50%);
    /* A flat min(460px, …) cap meant the photo held steady at 460px while
       the margin around it kept shrinking as the viewport narrowed — the
       margin only became constant once the viewport dropped low enough to
       hit the other branch. Dropping the cap (just a generous ceiling for
       absurdly wide screens) keeps the same 40px margin on both sides of
       the photo at every width instead of margin-then-photo taking turns
       absorbing the shrink. */
    width: min(560px, calc(50vw - min(370px, calc(50vw - 90px)) - 80px));
    object-fit: cover;
  }
}

.footer-bar::before {
  content: none;
}

.footer-bar > * {
  position: relative;
  z-index: 1;
}

.footer-bar__icons {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  width: auto;
  max-width: none;
  gap: 12px;
}

.footer-icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: var(--text);
  text-decoration: none;
}

.footer-icon {
  width: 20px;
  height: 20px;
}

.footer-icon--image {
  width: 22px;
  height: 22px;
  object-fit: contain;
}

body.video-active .footer-icon--image {
  filter: brightness(0) invert(1);
}

body.about-open .footer-icon--image,
body.project-inline-open .footer-icon--image {
  filter: none;
}

.back-to-top {
  position: fixed;
  top: 56px;
  right: 14px;
  z-index: 61;
  width: 22px;
  height: 22px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--kicker-size);
  font-weight: var(--kicker-weight);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: color var(--hover-fade-duration) ease, opacity 220ms ease;
}

.back-to-top.is-visible {
  opacity: 1;
  pointer-events: auto;
}

body.video-active .frame-name,
body.video-active .eyebrow,
body.video-active .hero__kicker,
body.video-active .hero h2,
body.video-active .hero__text,
body.video-active .title-list__item,
body.video-active .title-list__separator,
body.video-active .project-mention,
body.video-active .desktop-chrome__name,
body.video-active .desktop-chrome__about,
body.video-active .desktop-chrome__role,
body.video-active .desktop-chrome__location,
body.video-active .desktop-page-label,
body.video-active .mobile-name,
body.video-active .mobile-about-toggle,
body.video-active .mobile-page-label,
body.video-active .footer-bar,
body.video-active .footer-bar a,
body.video-active .footer-bar a:visited,
body.video-active .admin-entry,
body.video-active .back-to-top,
body.video-active .project-inline__close {
  color: var(--text-on-video);
}

body.project-inline-open .desktop-page-label {
  /* The label's own z-index (41) sits BELOW the fullscreen sheet (90), so it
     was being fully covered/hidden by the opaque white sheet regardless of
     its text color — the color fix alone was treating the wrong symptom.
     Raise it above the sheet, same treatment as .desktop-chrome below.
     Also above .footer-bar's own z-index (95) in this same state — equal
     z-index falls back to DOM order, and the footer (later in the markup)
     was winning that tiebreak and covering the label. */
  z-index: 96;
  /* Once visible again, keep it in the normal (dark) text color instead of
     inheriting "video-active"'s white (meant for text over the background
     video, not over this opaque sheet) — and skip the transition: hovering
     a title (which sets video-active, turning the label white while still
     on the home page) almost always precedes the click that opens it, so
     without this the label would fade from white over ~360ms every time. */
  color: var(--text);
  transition: none;
}

/* Keep the name + role/location header visible above the fullscreen project
   sheet (z-index 90), same treatment already used for the About overlay. */
body.project-inline-open .desktop-chrome {
  z-index: 95;
}

body.project-inline-open .desktop-chrome__name,
body.project-inline-open .desktop-chrome__role,
body.project-inline-open .desktop-chrome__location {
  /* Same reasoning as the page label above: snap dark instantly instead of
     fading in over ~360ms from whatever color a pre-click hover left behind. */
  color: var(--text);
  transition: none;
}

/* The About button has no reason to float above the fullscreen sheet, and
   its raised z-index (inherited from .desktop-chrome above) would otherwise
   intercept clicks meant for the sheet's own close button underneath it. */
body.project-inline-open .desktop-chrome__about {
  visibility: hidden;
  pointer-events: none;
}

body.about-open .desktop-chrome__name,
body.about-open .desktop-page-label,
body.about-open .mobile-name,
body.about-open .mobile-about-toggle,
body.about-open .mobile-page-label {
  color: var(--text);
}

.section--mobile-only {
  display: none;
}

.mobile-header-video {
  display: none;
}

body.video-active .footer-bar {
  box-shadow: none;
}

body.video-active .footer-bar::before {
  content: none;
}

@media (max-width: 640px) {
  .site-frame {
    min-height: 100dvh;
    padding: 10px;
    display: flex;
    flex-direction: column;
  }

  /* .content-stack's base rule (shared with desktop) is display:grid, which
     doesn't grow to fill .site-frame's leftover space — switching it to a
     flex column here lets .mobile-header-video's own flex:1 (below) actually
     take effect, so the photo section can grow to fill the real remaining
     gap between the piles and the fixed footer instead of just hugging its
     own content size. */
  .content-stack {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
  }

  .desktop-chrome {
    display: none !important;
  }

  .desktop-page-label {
    display: none;
  }

  .mobile-name {
    display: block;
    position: static;
    top: auto;
    left: auto;
    z-index: auto;
    margin: 0;
    color: var(--text);
    font-size: var(--kicker-size);
    font-weight: var(--kicker-weight);
    line-height: 1;
  }

  .mobile-about-toggle {
    display: block;
    position: static;
    top: auto;
    right: auto;
    z-index: auto;
    margin: 0;
    cursor: pointer;
  }

  .mobile-topbar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin: 0 0 6px;
  }

  /* The real "CHARLES GRENIER" (.mobile-name) lives outside .about-overlay
     in the DOM — position:fixed on .about-overlay (always on, even closed)
     makes it a stacking context, so anything outside it (however high its
     own z-index) is compared against that context as a single unit, not
     against individual elements inside. A fixed banner living outside
     could therefore never reliably out-stack the overlay's own scrolled
     content. Simplest fix: hide the outside copy while About is open and
     use .about-overlay__name below instead — a duplicate that lives
     inside the overlay's own stacking context, alongside the close
     button, both sitting on .about-overlay__panel::before's full-width
     backing bar (same principle as the bottom footer bar). */
  body.about-open .mobile-name {
    visibility: hidden;
  }

  .about-overlay__name {
    display: block;
    position: fixed;
    top: 10px;
    left: 10px;
    z-index: 2;
    margin: 0;
    color: var(--text);
    font-size: var(--kicker-size);
    font-weight: var(--kicker-weight);
    line-height: 1;
  }

  /* Full-width backing for .about-overlay__name and .about-overlay__close
     (same principle as the bottom footer bar) — lives in the same
     stacking context as both (a child of .about-overlay__panel), so it
     reliably sits below them and above the overlay's own scrolled
     content regardless of what's outside the overlay entirely. */
  .about-overlay__panel::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 50px;
    background: var(--bg);
    z-index: 1;
  }

  .mobile-topbar__identity {
    display: grid;
    gap: 5px;
  }

  .mobile-meta {
    margin: 0;
    display: flex;
    gap: 8px;
    color: var(--text);
    font-size: var(--location-size);
    font-weight: var(--kicker-weight);
    line-height: 1;
    text-transform: uppercase;
  }

  /* Tap "RÉALISATEUR"/"MONTEUR" on the pile heading itself: it rolls to
     "RÉALISATIONS"/"MONTAGES" (the section's real title) as the pile opens. */
  .section-heading__toggle .eyebrow {
    position: relative;
    display: inline-block;
    height: 1.2em;
    overflow: hidden;
    line-height: 1.2;
  }

  .section-heading__toggle .eyebrow .role-word {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    transition: transform 400ms ease;
    white-space: nowrap;
  }

  .section-heading__toggle .eyebrow .role-word--out {
    transform: translateY(0);
  }

  .section-heading__toggle .eyebrow .role-word--in {
    transform: translateY(-100%);
  }

  .section-heading__toggle .eyebrow.is-rolling .role-word--out {
    transform: translateY(100%);
  }

  .section-heading__toggle .eyebrow.is-rolling .role-word--in {
    transform: translateY(0);
  }

  .title-list[data-project-overlay="true"] .title-list__item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    min-height: 44px;
    padding: 0 0 26px;
    white-space: normal;
    text-align: left;
    text-decoration: none;
  }

  .title-list[data-project-overlay="true"] .title-list__separator {
    display: none;
  }

  .title-list__thumb {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    background: #f2f2f2;
  }

  .section--mobile-only {
    display: none;
  }

  .page-intro {
    display: none;
  }

  .back-to-top {
    top: 36px;
    right: 10px;
  }







  .frame-name {
    display: none;
  }

  .frame-name--left {
    left: 0;
  }

  .frame-name--right {
    right: 0;
  }

  .footer-bar {
    position: fixed;
    width: 100%;
    left: 0;
    right: 0;
    bottom: 0;
    max-width: none;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    padding: 10px 0;
    margin-top: auto;
    background: var(--bg);
    /* iOS Safari can visibly lag/detach position:fixed elements from the
       viewport during momentum scrolling on long pages — promoting this to
       its own GPU compositor layer (rather than repainting it with the
       page's main-thread scroll) is the standard fix. translate3d is the
       more reliably-honored form of this hint on iOS specifically (over
       translateZ alone); will-change reinforces the same intent for
       browsers that read it. */
    transform: translate3d(0, 0, 0);
    -webkit-transform: translate3d(0, 0, 0);
    will-change: transform;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
  }

  .mobile-page-label {
    display: none;
  }

  .footer-bar__icons {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    width: 100%;
    gap: 24px;
  }

  .footer-icon-link {
    width: 30px;
    height: 30px;
  }

  .footer-icon {
    width: 24px;
    height: 24px;
  }

  .footer-icon--image {
    width: 26px;
    height: 26px;
  }

  .footer-bar__contact {
    width: 100%;
    justify-content: space-between;
  }

  .title-list__item {
    cursor: pointer;
  }

  .about-overlay__inner {
    width: calc(100% - 20px);
    max-height: 100vh;
    padding: 54px 0 84px;
    gap: 18px;
  }

  .about-overlay__close {
    top: 8px;
    right: 10px;
  }

  .about-overlay__bottom {
    margin-top: 2px;
    padding-top: 4px;
    justify-content: center;
  }

  /* Pin to the viewport, not the (potentially long) About content flow —
     mirrors the fixed .footer-bar treatment used on the main site. */
  body.about-open .about-overlay__bottom {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 85;
    margin: 0;
    padding: 10px 0;
    background: var(--bg);
  }

  .about-overlay__page-label {
    display: none;
  }

  .about-overlay__icons {
    width: 100%;
    justify-content: center;
    gap: 10px;
  }

  .project-inline__controls {
    grid-template-columns: auto auto 1fr auto;
    grid-template-areas:
      "play progress progress progress"
      "mute volume . fullscreen";
    gap: 10px 12px;
    padding: 10px 8px 8px;
  }

  .project-inline__control[data-player-action="play"] {
    grid-area: play;
  }

  .project-inline__progress {
    grid-area: progress;
  }

  .project-inline__control[data-player-action="mute"] {
    grid-area: mute;
    justify-self: start;
  }

  .project-inline__volume {
    grid-area: volume;
  }

  .project-inline__control[data-player-action="fullscreen"] {
    grid-area: fullscreen;
    justify-self: end;
  }

  /* Réalisations / Montages: collapsed by default, tap the heading to open. */
  .section-heading__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    cursor: pointer;
  }

  .section-heading__chevron {
    display: block;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    color: var(--text);
    transition: transform 220ms ease;
  }

  .section--collapsible.is-expanded .section-heading__chevron {
    transform: rotate(180deg);
  }

  /* Pull "MONTEUR" up closer to "RÉALISATEUR" — the grid's uniform 36px
     row gap (.content-stack) is otherwise the same above the hero too. */
  #montage {
    margin-top: -26px;
  }

  /* No transition on max-height: animating 0 -> 6000px (a stand-in for
     "tall enough") made the real, much-shorter content finish clipping in
     a small fraction of that duration, so the pile visually snapped open
     while the item stagger below was still mid-cascade — two clocks
     fighting each other read as janky. Snapping this instantly and
     leaving all the perceived motion to the item stagger reads as one
     clean reveal instead. */
  .section--collapsible .title-list {
    max-height: 0;
    overflow: hidden;
  }

  .section--collapsible.is-expanded .title-list {
    max-height: none;
  }

  /* Cards cascade in one after another instead of all popping at once —
     each item's own --i (set at render time) staggers its delay. */
  .section--collapsible .title-list__item {
    opacity: 0;
    transform: translateY(14px);
    transition:
      opacity 420ms cubic-bezier(0.16, 1, 0.3, 1),
      transform 420ms cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: calc(var(--i, 0) * 45ms);
  }

  .section--collapsible.is-expanded .title-list__item {
    opacity: 1;
    transform: translateY(0);
  }

  /* Closing: everything resets together, no staggered fade-out — the
     container's own max-height collapse already clips it from view. */
  .section--collapsible:not(.is-expanded) .title-list__item {
    transition-delay: 0ms;
  }

  /* A portrait photo of Charles, purely decorative, below the two piles —
     replaced the looping Vimeo reel that used to sit here. Fills whatever
     vertical room is left between the piles and the fixed footer (flex:1
     inside .site-frame's column) so the whole photo fits on load without
     scrolling, instead of a fixed size driven by the viewport's width. */
  .mobile-header-video {
    display: flex;
    align-items: center;
    justify-content: center;
    /* basis:auto (not the bare "flex:1" shorthand's 0%) — the mobile-only
       awards/studies/collaborators sections further down this same flex
       column have real content and push total height past one viewport, so
       a 0% basis would let this shrink toward nothing to make room; auto
       keeps the photo's own natural (image-driven) size as its floor while
       still growing to fill leftover space when there's less content above. */
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    overflow: hidden;
    margin: 20px 0 0;
    /* .footer-bar is fixed (floats on top, doesn't consume flex-column
       space), so this container's box actually extends behind it — without
       excluding that height, align-items:center below centers the photo
       within space that's partly invisible, skewing it upward. Matches
       .footer-bar's measured height (~50px: icon row + bottom padding). */
    padding-bottom: 40px;
  }

  .mobile-header-video__image {
    width: auto;
    height: auto;
    max-width: 55%;
    max-height: 100%;
    aspect-ratio: 4 / 5;
    object-fit: cover;
  }
}

/* Keep ABOUT page colors fixed (do not follow dynamic video-active color swap). */
body.about-open .about-overlay,
body.about-open .about-overlay *,
body.about-open .desktop-chrome__name,
body.about-open .desktop-page-label,
body.about-open .mobile-name,
body.about-open .mobile-about-toggle,
body.about-open .mobile-page-label,
body.about-open .footer-bar,
body.about-open .footer-bar a,
body.about-open .footer-bar a:visited,
body.about-open .admin-entry,
body.about-open .back-to-top {
  color: var(--text);
}

/* --- Éditeur inline (mode édition) --------------------------------------- */

body.is-edit-mode .editor-editable {
  /* Certains champs (ex: .desktop-chrome__location) héritent
     pointer-events: none d'un parent décoratif en mode normal — on force
     l'interactivité ici, seulement pendant l'édition. */
  pointer-events: auto !important;
  outline: 1px dashed var(--line-strong);
  outline-offset: 3px;
  cursor: text;
  border-radius: 2px;
}

body.is-edit-mode .about-overlay:not(.is-open) .editor-editable {
  /* The rule above re-enables pointer-events for every editable field, but
     the About overlay keeps its own (still-closed) copy of these fields in
     the DOM, fixed and covering the full viewport — without this, that
     invisible copy intercepts clicks meant for whatever sits underneath it
     (e.g. the directing project list, which happens to overlap the same
     screen area). */
  pointer-events: none !important;
}

body.is-edit-mode .editor-editable:hover {
  outline-color: var(--text);
}

body.is-edit-mode .editor-editable:focus {
  outline-style: solid;
  outline-color: var(--text);
}

body.is-edit-mode .editor-editable--icon {
  cursor: pointer;
  outline-offset: 4px;
}

/* Pendant l'édition des crédits (texte brut "Rôle: Noms"), les retours à la
   ligne doivent s'afficher — le rendu normal (HTML enrichi) les collapse. */
.project-inline__credits.is-editing-raw {
  white-space: pre-wrap;
}

.title-list__item--hidden {
  opacity: 0.35;
}

.editor-list-item {
  position: relative;
}

.editor-item-controls {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 6px;
  vertical-align: middle;
}

.editor-item-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: var(--bg);
  color: var(--text);
  font-size: 11px;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
}

/* Boutons icône seule (✎ ✕ ⠿ ⤢) dans les contrôles d'item de liste : carrés
   et compacts. Les boutons avec libellé texte (ex: "✎ Crédits" dans la
   fiche) gardent la largeur automatique de la règle de base ci-dessus. */
.editor-item-controls .editor-item-btn {
  width: 20px;
  height: 20px;
  padding: 0;
}

.editor-item-btn--drag {
  cursor: grab;
}

.editor-add-btn {
  display: inline-block;
  margin: 8px 0;
  padding: 4px 10px;
  border: 1px dashed var(--line-strong);
  border-radius: 3px;
  background: transparent;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 11px;
  cursor: pointer;
}

.editor-fiche-toolbar {
  display: none;
  gap: 8px;
  margin-top: 8px;
}

.editor-save-btn {
  position: fixed;
  bottom: 50px;
  right: 14px;
  z-index: 62;
  padding: 6px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--footer-size);
  cursor: pointer;
}

.editor-save-btn.is-dirty {
  background: var(--text);
  color: var(--bg);
}

.editor-popover-host {
  position: static;
}

.editor-popover {
  position: fixed;
  z-index: 95;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 300px;
  max-width: calc(100vw - 16px);
  padding: 12px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
  font-family: var(--font-sans);
  font-size: 12px;
}

.editor-popover__field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.editor-popover__field--checkbox {
  flex-direction: row;
  align-items: center;
}

.editor-popover__field input,
.editor-popover__field textarea {
  font-family: var(--font-sans);
  font-size: 12px;
  padding: 5px 7px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--bg);
  color: var(--text);
}

.editor-popover__status {
  margin: 0;
  font-size: 11px;
  color: var(--muted);
}

.editor-popover__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.editor-popover__actions button {
  padding: 5px 12px;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 11px;
  cursor: pointer;
}

.editor-popover__submit {
  background: var(--text);
  color: var(--bg);
}

@media (max-width: 640px) {
  /* Cibles tactiles trop petites (20px) sur téléphone — agrandies pour un
     doigt plutôt qu'un curseur de souris. */
  .editor-item-controls .editor-item-btn {
    width: 32px;
    height: 32px;
    font-size: 14px;
  }

  .editor-fiche-toolbar .editor-item-btn {
    padding: 8px 12px;
    font-size: 13px;
  }

  .editor-item-controls {
    gap: 8px;
  }

  .editor-popover {
    width: min(320px, calc(100vw - 32px));
  }

  .editor-popover__field input,
  .editor-popover__field textarea {
    font-size: 16px; /* évite le zoom automatique de Safari iOS au focus */
  }
}
