/* ═══════════════════════════════════════════════════════════
   GLASS BLOCK WALL — home page
   Warm copper + steel blue, black mortar, cast-glass tiles
   Loaded AFTER style.css: overrides the old pink hero/nav.
═══════════════════════════════════════════════════════════ */

:root {
  /* ── mortar & ground ── */
  --ink:        #08080a;
  --ink-2:      #101013;
  --mortar:     10px;   /* gap between glass blocks */
  --cols:       3;
  --rows:       4;

  /* ── warm copper ── */
  --copper:     #c9703f;
  --amber:      #e6a763;
  --rust:       #8e3d22;
  --cream:      #f3e4d1;
  --clay:       #b2582f;

  /* ── steel blue ── */
  --steel:      #6f9dc4;
  --sky:        #a9cae2;
  --pale:       #d8e8f2;
  --slate:      #2c4257;

  --glass-edge: rgba(255, 244, 232, 0.42);
  --glass-dark: rgba(0, 0, 0, 0.62);

  --font-display: 'Instrument Serif', Georgia, serif;
  --font-mono:    'JetBrains Mono', ui-monospace, monospace;
}

/* ── kill the old chrome ─────────────────────────────────── */
.navbar,
#particleCanvas,
.brain-viz,
.hero-actions,
.scroll-indicator { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ink);
  color: var(--cream);
  font-family: 'Inter', system-ui, sans-serif;
}

body.wall-locked { overflow: hidden; height: 100%; }

/* ═══ THE WALL ══════════════════════════════════════════════ */
.wall {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  grid-template-rows: repeat(var(--rows), 1fr);
  gap: var(--mortar);
  padding: var(--mortar);
  background: var(--ink);
  /* faint uneven grout so the black isn't flat */
  background-image:
    radial-gradient(120% 90% at 20% 10%, rgba(201,112,63,.07), transparent 60%),
    radial-gradient(110% 80% at 85% 90%, rgba(111,157,196,.06), transparent 60%);
}

/* ═══ ONE GLASS BLOCK ═══════════════════════════════════════ */
.tile {
  position: relative;
  overflow: hidden;
  border-radius: 4px;
  background: var(--ink-2);
  cursor: default;
  isolation: isolate;
  transform: translateZ(0);
  transform-style: preserve-3d;

  /* ── permanent relief: the block sits ON the wall, not in it ──
     A cast shadow that spills across the mortar onto its neighbours is what
     reads as thickness; the bevel alone only ever looked like a drawn edge. */
  box-shadow:
    0 22px 44px -22px rgba(0, 0, 0, .92),
    0 6px 14px -8px rgba(0, 0, 0, .68);
  transition:
    transform .55s cubic-bezier(.2, .8, .3, 1),
    box-shadow .45s ease;

  /* staggered pour-in */
  opacity: 0;
  animation: tile-in .9s cubic-bezier(.16, .84, .34, 1) forwards;
  animation-delay: var(--d, 0s);
}

@keyframes tile-in {
  from { opacity: 0; transform: translate3d(0, 26px, 0) scale(.93); }
  to   { opacity: 1; transform: translate3d(0, 0, 0)   scale(1); }
}

/* `animation-fill-mode: forwards` holds the final keyframe forever, and an
   animation beats an inline style in the cascade — so while `tile-in` is still
   attached, JS cannot set `transform` for the tilt. It also leaves every tile
   carrying `filter: blur(0)`, which is not `none`: it forces a filtered render
   surface around the whole subtree, permanently.

   wall.js therefore adds `.is-poured` once the pour finishes, which detaches
   the animation and hands both properties back. */
.tile.is-poured {
  animation: none;
  opacity: 1;
  filter: none;
}

/* ── tilt: follows the pointer, applied by wall.js ── */
.tile.is-tilting {
  z-index: 6;
  transition: transform .1s linear, box-shadow .45s ease;
  box-shadow:
    0 44px 80px -28px rgba(0, 0, 0, .95),
    0 12px 26px -10px rgba(0, 0, 0, .72);
  will-change: transform;
}

.tile[data-target] { cursor: pointer; }

/* ── the stuff you see THROUGH the glass ── */
.tile__media {
  position: absolute;
  inset: -14%;
  z-index: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* procedural copper/steel fallback when no photo is dropped in */
  background-image: var(--img, var(--fallback));
  /* PERF: the feDisplacementMap glass warp used to live here. A url() filter
     on an HTML element drops it off the compositor fast path, so every frame
     of the parallax re-ran turbulence + displacement on a full-bleed layer.
     The ridges, bevel and tint carry the glass read on their own. */
  transform: translate3d(0, 0, 0) scale(1.06);
}

/* photo layer sits above the fallback so onerror can drop it cleanly */
.tile__photo {
  position: absolute;
  z-index: 1;
  /* explicit top/left rather than `inset` — with a width set, the right/bottom
     offsets were being dropped, which is where the edge gap came from */
  top: -16%;
  left: -16%;
  width: 132%;
  height: 132%;
  /* style.css sets a global `img { max-width: 100% }`, which was quietly
     clamping that 132% back to 100% — the photo stopped short of the tile's
     right edge and left a dark strip there. The old ripple filter smeared
     over the seam; without it the gap is plainly visible, so undo the clamp. */
  max-width: none;
  object-fit: cover;
  transform: translate3d(0, 0, 0) scale(1.06);
}

/* ── PERFORMANCE NOTE ─────────────────────────────────────
   There used to be a `drift` animation here: a slow infinite
   scale/translate on every filtered layer, so the wall was
   never fully still.

   It cost far more than it looked like it did. An SVG
   reference filter is rasterised once and then cached — but
   only while the filtered content holds still. Animating the
   element invalidates that cache every single frame, so the
   browser regenerated feTurbulence noise and re-ran
   feDisplacementMap on eleven large layers, sixty times a
   second, forever. That is the lag.

   The parallax survives because it is driven from JS as a
   direct transform on a promoted layer: the cached raster is
   moved by the compositor rather than rebuilt. It also only
   runs while the pointer is actually moving.

   `.tile--drift` is kept in the markup as a styling hook.
   Do not reattach an animation to it.
═══════════════════════════════════════════════════════════ */

/* Only one layer per tile ever needs filtering. The fallback
   gradient sits entirely behind the photo, so while a photo is
   loaded it is invisible — and an invisible layer was still
   paying full price for the glass filter. If the photo 404s,
   `onerror` removes the <img>, :has() stops matching, and the
   gradient comes back on its own. */
.tile:has(.tile__photo) .tile__media { display: none; }

/* ── the glass body: ridges, tint, thickness ── */
.tile__glass {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /* vertical ridges of a cast block + a warm/cool tint split */
  background:
    linear-gradient(115deg,
      rgba(233, 167, 99, .16) 0%,
      rgba(255, 255, 255, .05) 32%,
      rgba(111, 157, 196, .11) 68%,
      rgba(44, 66, 87, .12) 100%),
    repeating-linear-gradient(96deg,
      rgba(255, 255, 255, .055) 0px,
      rgba(255, 255, 255, .055) 1px,
      rgba(0, 0, 0, .05) 2px,
      rgba(0, 0, 0, .05) 9px);
  /* PERF: was `mix-blend-mode: screen`, which forces the tile's whole subtree
     through a non-isolated blend pass on every paint. Plain alpha at a lower
     opacity lands within a hair of the same tint. */
  opacity: .5;
  transition: opacity .55s ease;
}

/* ── bevel + specular: this is what makes it read as THICK ── */
.tile__bevel {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  border-radius: 4px;
  /* PERF: a 1.2px backdrop-filter here cost a full backdrop readback per tile,
     eleven times over, for a blur nobody can see. The bevel is pure box-shadow
     now, and the stack is trimmed from nine layers to four. */
  box-shadow:
    inset  0  1px 0   rgba(255, 246, 236, .40),
    inset  0 -1px 0   rgba(0, 0, 0, .70),
    inset  0  8px 14px -12px rgba(255, 244, 232, .50),
    inset  0 0 34px rgba(0, 0, 0, .22);
}

/* the diagonal shine that slides on hover */
.tile__bevel::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(118deg,
    transparent 22%,
    rgba(255, 248, 238, .13) 42%,
    rgba(255, 248, 238, .26) 50%,
    rgba(255, 248, 238, .10) 58%,
    transparent 76%);
  transform: translate3d(-38%, 0, 0);
  opacity: .55;
  transition: transform 1.1s cubic-bezier(.2,.8,.3,1), opacity .5s ease;
}

/* ═══ HOVER: the glass clears ═══════════════════════════════
   The CV tile navigates via data-href rather than data-target,
   so it used to get none of this — it looked inert while being
   the one tile that leaves the page. Both are covered now.
═══════════════════════════════════════════════════════════ */
@media (hover: hover) {
  .tile[data-target]:hover .tile__media,
  .tile[data-target]:hover .tile__photo,
  .tile[data-href]:hover   .tile__media,
  .tile[data-href]:hover   .tile__photo { filter: none; }

  .tile[data-target]:hover .tile__glass,
  .tile[data-href]:hover   .tile__glass { opacity: .34; }

  .tile[data-target]:hover .tile__bevel::after,
  .tile[data-href]:hover   .tile__bevel::after {
    transform: translate3d(38%, 0, 0);
    opacity: .9;
  }

  .tile[data-target]:hover .tile__label,
  .tile[data-href]:hover   .tile__label { color: var(--cream); letter-spacing: .26em; }

  .tile[data-target]:hover .tile__label::after,
  .tile[data-href]:hover   .tile__label::after { width: 100%; }

  .tile[data-target]:hover::after,
  .tile[data-href]:hover::after { border-color: rgba(233, 167, 99, .6); }
}

.tile[data-target]:focus-visible,
.tile[data-href]:focus-visible { outline: 2px solid var(--amber); outline-offset: -6px; }
.tile[data-target]:focus-visible .tile__media,
.tile[data-target]:focus-visible .tile__photo,
.tile[data-href]:focus-visible   .tile__media,
.tile[data-href]:focus-visible   .tile__photo { filter: none; }

/* ═══ ETCHED LABELS ═════════════════════════════════════════ */
/* ═══ SECTION LABELS ════════════════════════════════════════
   Dead centre and large, because these labels are the only
   thing telling you which blocks are clickable. Tucked small
   in a bottom corner they read as a caption on a photo, so a
   visitor tries the decorative tiles and gets nothing.
═══════════════════════════════════════════════════════════ */
.tile__label {
  position: absolute;
  z-index: 4;
  left: 50%;
  top: 50%;
  /* translate keeps it centred while letter-spacing grows on hover */
  transform: translate(-50%, -50%);
  max-width: 88%;
  text-align: center;
  font-family: var(--font-mono);
  font-size: clamp(.9rem, 1.55vw, 1.5rem);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(248, 238, 226, .95);
  text-shadow: 0 2px 4px rgba(0,0,0,.95), 0 0 30px rgba(0,0,0,.85);
  transition: color .4s ease, letter-spacing .5s cubic-bezier(.2,.8,.3,1);
  pointer-events: none;
}

.tile__label::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: -.7em;
  height: 1px;
  width: 34px;
  background: linear-gradient(90deg, transparent, var(--amber) 30%, var(--steel) 70%, transparent);
  transition: width .6s cubic-bezier(.2,.8,.3,1);
}

/* A quiet frame on the tiles that actually go somewhere. It is deliberately
   faint — the label carries the message; this only confirms it. */
.tile[data-target]::after,
.tile[data-href]::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  border: 1px solid rgba(233, 167, 99, .26);
  border-radius: 4px;
  pointer-events: none;
  transition: border-color .4s ease;
}

.tile__num {
  position: absolute;
  z-index: 4;
  right: clamp(.9rem, 1.8vw, 1.8rem);
  top: clamp(.8rem, 1.6vw, 1.5rem);
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .18em;
  color: rgba(243, 228, 209, .28);
  pointer-events: none;
}

/* ═══ TILE 1 — THE TITLE ════════════════════════════════════
   Double-wide: it eats the slot the first photo block used to
   occupy, so the name + role line have room to be read.
═══════════════════════════════════════════════════════════ */
.tile--title {
  grid-column: span 2;
  cursor: default;
}

/* Fallback tone, used when assets/tiles/01.jpg is missing. */
.tile--title .tile__media {
  background-image:
    radial-gradient(90% 120% at 18% 8%,  rgba(233,167,99,.34), transparent 62%),
    radial-gradient(80% 110% at 92% 96%, rgba(111,157,196,.30), transparent 60%),
    linear-gradient(160deg, #16110e 0%, #0b0d11 55%, #120e0c 100%);
  filter: none;
}

/* 01.jpg is now the title tile's background. Skip the glass warp here — this
   block carries the name, so the photo stays readable rather than molten. */
.tile.tile--title .tile__photo {
  filter: none !important;   /* beats the nth-child + no-svg-filter fallbacks */
  opacity: 1;
}

/* No overall veil any more — the photo is uncovered across the whole tile. Only
   the lower third is darkened, which is where the type actually sits, and it
   reads as part of the image rather than as a panel laid over it. */
.tile--title .tile__title::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to top,
    rgba(8, 8, 10, .86) 0%,
    rgba(8, 8, 10, .55) 22%,
    rgba(8, 8, 10, .16) 44%,
    transparent 62%);
  pointer-events: none;
}

/* Bottom-anchored and centred, so the photo owns the whole tile and the type
   sits in the one place it can be read without covering the image. */
.tile__title {
  position: absolute;
  z-index: 4;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  text-align: center;
  padding: clamp(1.4rem, 3vw, 3rem);
  pointer-events: none;
}

.tile__title h1 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2.6rem, 6.4vw, 6.4rem);
  line-height: .92;
  letter-spacing: -.015em;
  color: var(--cream);
  text-shadow: 0 2px 30px rgba(0,0,0,.85);
}

.tile__title h1 em {
  display: block;
  font-style: italic;
  background: linear-gradient(94deg, var(--amber), var(--copper) 45%, var(--steel));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.tile__title p {
  margin: clamp(.7rem, 1.4vw, 1.2rem) auto 0;
  max-width: 52ch;
  font-family: var(--font-mono);
  font-size: clamp(.72rem, 1.05vw, 1rem);
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(230, 240, 247, .82);
  text-shadow: 0 1px 12px rgba(0,0,0,.9);
}

.tile__rule {
  width: 74px;
  height: 1px;
  margin: clamp(.9rem, 1.8vw, 1.5rem) auto 0;
  /* symmetrical now that it sits under centred type */
  background: linear-gradient(90deg, transparent, var(--amber) 50%, transparent);
}

/* ═══ LE TITRE DOIT TENIR DANS SA TUILE ═════════════════════
   Les tailles ci-dessus sont calees sur la LARGEUR du viewport
   (vw). Sur un ecran large et peu haut — 1440x900, 1366x768 —
   deux lignes a 6.4vw font plus haut que la tuile, et comme le
   bloc est ancre en bas (justify-content:flex-end) le debordement
   part vers le HAUT, ou .tile{overflow:hidden} coupe le prenom.

   On recale donc la taille sur la hauteur de la TUILE (cqh) en
   gardant le plafond en vw pour la largeur. `min()` avec une unite
   de conteneur est ignore par les navigateurs qui ne la supportent
   pas : les declarations vw ci-dessus restent alors en place.
══════════════════════════════════════════════════════════════ */
.tile--title { container-type: size; }

.tile__title    { padding: clamp(.8rem, 5.5cqh, 3rem) clamp(1rem, 2.4vw, 3rem); }
.tile__title h1 { font-size: clamp(1.7rem, min(6.4vw, 30cqh), 6.4rem); }
.tile__rule     { margin-top: clamp(.5rem, 4cqh, 1.5rem); }
.tile__title p  { margin-top: clamp(.45rem, 3.4cqh, 1.2rem); }

/* ═══ LANGUAGE + SOCIAL, floated on the mortar ══════════════ */
.wall-utils {
  position: fixed;
  z-index: 6;
  right: calc(var(--mortar) + 14px);
  bottom: calc(var(--mortar) + 12px);
  display: flex;
  align-items: center;
  gap: 1.1rem;
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .2em;
  text-transform: uppercase;
}

.wall-utils a,
.wall-utils button {
  background: none;
  border: 0;
  padding: 0;
  color: rgba(243, 228, 209, .55);
  font: inherit;
  cursor: pointer;
  text-decoration: none;
  transition: color .3s ease;
}

.wall-utils a:hover,
.wall-utils button:hover { color: var(--amber); }

/* EN / FR switch — both options always visible, the active one lit. */
.wall-utils .lang-switch {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .38rem .7rem;
  border: 1px solid rgba(243, 228, 209, .22);
  border-radius: 99px;
}
.wall-utils .lang-switch:hover { border-color: var(--amber); }
.lang-switch .lang-opt { color: rgba(243, 228, 209, .4); transition: color .25s ease; }
.lang-switch .lang-opt:hover { color: rgba(243, 228, 209, .85); }
.lang-switch .lang-opt.is-on { color: var(--amber); }
.lang-switch .lang-sep { color: rgba(243, 228, 209, .25); }

/* ═══ EXPANDER — tile grows into the section ════════════════ */
/* The expander is ALWAYS viewport-sized and starts scaled down onto the tile
   it was opened from, then transitions to identity. Animating top/left/width/
   height instead — which is what this did originally — forces a full layout
   and repaint on every frame of the growth, with the whole wall still on
   screen behind it. Transform is composited: same motion, no layout. */
.expander {
  position: fixed;
  inset: 0;
  z-index: 20;
  overflow: hidden;
  border-radius: 4px;
  background: var(--ink);
  box-shadow: 0 40px 120px rgba(0,0,0,.9);
  transform-origin: 0 0;
  transition:
    transform .72s cubic-bezier(.66, 0, .21, 1),
    border-radius .72s ease;
  pointer-events: none;
  opacity: 0;
  will-change: transform;
}

.expander.is-active { pointer-events: auto; opacity: 1; }
.expander.is-full   { border-radius: 0; transform: none; }

.expander__wash {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(80% 60% at 12% 0%,  rgba(233,167,99,.14), transparent 62%),
    radial-gradient(70% 60% at 92% 100%, rgba(111,157,196,.13), transparent 60%),
    linear-gradient(170deg, #0c0b0d, #08080a 60%);
}

.expander__scroll {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow-y: auto;
  overflow-x: hidden;
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity .5s ease .34s, transform .6s cubic-bezier(.2,.8,.3,1) .34s;
  -webkit-overflow-scrolling: touch;
}

.expander.is-full .expander__scroll { opacity: 1; transform: none; }

.expander__close {
  position: fixed;
  z-index: 22;
  top: 24px;
  right: 26px;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(243, 228, 209, .2);
  border-radius: 50%;
  background: rgba(8, 8, 10, .65);
  backdrop-filter: blur(10px);
  color: var(--cream);
  font-size: 1rem;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity .4s ease .4s, background .3s ease, transform .3s ease;
}

.expander.is-full ~ .expander__close,
.expander__close.is-shown { opacity: 1; pointer-events: auto; }
.expander__close:hover { background: rgba(201, 112, 63, .3); transform: rotate(90deg); }

/* panels live inside the expander, one visible at a time */
.panel { display: none; }
.panel.is-open { display: block; }

/* ═══ RESPONSIVE ════════════════════════════════════════════ */
/* Wide landscape screens: 4 across, 3 down keeps the blocks
   closer to square. Flip --cols/--rows here or at :root. */
@media (min-aspect-ratio: 5/4) and (min-width: 900px) {
  .wall { --cols: 4; --rows: 3; }
}

@media (max-width: 720px) {
  .wall { --cols: 2; --rows: 6; --mortar: 7px; }
  /* Narrow tiles: keep the label big enough to be an obvious target, but pull
     the tracking in so "COMPÉTENCES" and "TÉLÉCHARGER CV" don't wrap badly. */
  .tile__label { font-size: .82rem; letter-spacing: .1em; max-width: 92%; }
}

@media (max-width: 420px) {
  .wall { --cols: 2; --rows: 6; }
  /* full-width row on phones — the copy still fits, so keep it */
  .tile__title       { padding: 1rem .9rem 1.1rem; }
  .tile__title h1    { font-size: clamp(1.9rem, 11vw, 2.9rem); font-size: clamp(1.4rem, min(11vw, 30cqh), 2.9rem); }
  .tile__title p     { font-size: .62rem; line-height: 1.55; letter-spacing: .1em; }
  .tile__rule        { width: 54px; margin-top: .7rem; }
}

/* ═══ SECOND FILTER so no two blocks warp alike ════════════ */
.tile:nth-child(even) .tile__media,

/* The Safari feDisplacementMap fallback lived here. With the SVG filter gone
   there is nothing to fall back from — and it was pinning a permanent 2.5px
   blur onto all eleven photo layers, which is the same cost by another name. */

/* ═══ RETONE THE SECTIONS ═══════════════════════════════════
   style.css is driven by these vars — repointing them swaps
   the whole inner UI from pink neon to copper/steel at once.
═══════════════════════════════════════════════════════════ */
:root {
  --bg:  #08080a;
  --bg2: #0c0b0d;
  --bg3: #121014;

  --surface:       rgba(201, 112, 63, .045);
  --surface2:      rgba(255, 255, 255, .028);
  --border:        rgba(201, 112, 63, .15);
  --border-bright: rgba(233, 167, 99, .42);

  --rose:      #c9703f;
  --pink:      #b2582f;
  --hot:       #e6a763;
  --magenta:   #6f9dc4;
  --blush:     #a9cae2;
  --coral:     #d98d52;
  --deep-rose: #5b2a16;

  --grad-main: linear-gradient(128deg, var(--amber), var(--copper) 46%, var(--steel));
  --grad-glow: linear-gradient(128deg, #e6a763, #c9703f, #6f9dc4);
  --grad-soft: linear-gradient(128deg, rgba(233,167,99,.14), rgba(111,157,196,.10));

  --text:       #f3e4d1;
  --text-muted: #b7a692;
  --text-dim:   #6f6153;

  --glow-sm: 0 0 20px rgba(201, 112, 63, .22);
  --glow-md: 0 0 40px rgba(201, 112, 63, .30);
  --glow-lg: 0 0 80px rgba(111, 157, 196, .18);
}

/* leftover hard-coded pink glows in style.css */
.skill-category:hover { box-shadow: 0 12px 40px rgba(201, 112, 63, .16) !important; }
.project-card:hover   { box-shadow: 0 20px 60px rgba(0,0,0,.55), 0 0 40px rgba(233, 167, 99, .14) !important; }
.tag-purple { background: rgba(201, 112, 63, .13); color: var(--copper); }
.tag-blue   { background: rgba(111, 157, 196, .13); color: var(--steel); }
.tag-green  { background: rgba(169, 202, 226, .12); color: var(--sky); }
.tag-orange { background: rgba(233, 167, 99, .13); color: var(--amber); }

/* section headings pick up the display serif */
.expander__scroll .section-header h2,
.expander__scroll .about-text h2 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -.01em;
}

.expander__scroll .section-tag {
  font-family: var(--font-mono);
  letter-spacing: .22em;
}

.expander__scroll .section { padding: clamp(4rem, 9vh, 7rem) 0; }

/* ═══ WORKFLOW — the pipeline, step by step ═════════════════ */
.pipeline {
  list-style: none;
  margin: 3rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
  gap: 1px;                       /* the mortar again, at panel scale */
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}

.pipeline-step {
  position: relative;
  padding: 1.5rem 1.4rem 1.6rem;
  background: var(--bg2);
  transition: background .35s ease;
}

.pipeline-step:hover { background: var(--surface); }

.pipeline-num {
  display: block;
  margin-bottom: .7rem;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .22em;
  color: var(--amber);
}

.pipeline-step h4 {
  margin: 0 0 .5rem;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.15rem;
  letter-spacing: -.01em;
  color: var(--text);
}

.pipeline-step p {
  margin: 0;
  font-size: .84rem;
  line-height: 1.65;
  color: var(--text-muted);
}

/* ═══ MOTION SAFETY ═════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .tile { animation: none; opacity: 1; transition: none; }
  .tile__media, .tile__photo { animation: none !important; transition: none; }
  .expander, .expander__scroll { transition-duration: .01ms; }
  /* The relief stays — it is depth, not motion. Only the tilt is dropped, and
     wall.js already skips wiring it. */
}

/* ═══ WORKFLOW — LA SORTIE DE LA CHAINE ══════════════════════
   Le panneau Workflow ne montrait qu'un pipeline decrit en mots.
   Ce bloc montre les plans reellement rendus par la chaine, plus
   une legende telle qu'elle sort du validateur.

   Les vignettes defilent horizontalement : une publication est une
   bande de plans, et les empiler verticalement casserait cette
   lecture. Les images sont en 540x960 (le rendu reel fait 1080x1920)
   et en `loading=lazy` — le panneau est ferme au chargement de la
   page, donc rien n'est telecharge tant qu'on ne l'ouvre pas.
═══════════════════════════════════════════════════════════════ */
.wf-output {
  margin: 2.6rem 0 3.2rem;
  padding-top: 2.2rem;
  border-top: 1px solid var(--border, rgba(201,112,63,.15));
}

.wf-output__head h3 {
  font-family: var(--font-display, 'Instrument Serif', Georgia, serif);
  font-weight: 400;
  font-size: clamp(1.35rem, 2.6vw, 1.9rem);
  letter-spacing: -.01em;
  color: var(--cream, #f3e4d1);
}

.wf-output__head p {
  max-width: 62ch;
  margin: .5rem 0 1.6rem;
  font-size: .94rem;
  color: var(--muted, rgba(226,224,224,.55));
}

.wf-slides {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 150px;
  gap: 1rem;
  overflow-x: auto;
  padding-bottom: .9rem;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
  scrollbar-color: var(--copper, #c9703f) transparent;
  /* deborde volontairement les gouttieres du conteneur : la bande doit
     se lire comme un film, pas comme une carte de plus */
  overscroll-behavior-x: contain;
}

.wf-slide { margin: 0; scroll-snap-align: start; }

.wf-slide img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 9 / 16;
  border-radius: 9px;
  border: 1px solid rgba(255, 255, 255, .07);
  background: #0b0d0c;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .5);
  transition: transform .3s cubic-bezier(.22,.61,.36,1), border-color .3s;
}

.wf-slide:hover img {
  transform: translateY(-4px);
  border-color: var(--border-bright, rgba(233,167,99,.42));
}

.wf-slide figcaption {
  margin-top: .55rem;
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted, rgba(226,224,224,.5));
}

.wf-caption {
  margin-top: 1.7rem;
  padding: 1.15rem 1.35rem 1.3rem;
  border: 1px solid var(--border, rgba(201,112,63,.15));
  border-radius: 12px;
  background: var(--surface, rgba(255,255,255,.025));
}

.wf-caption__label {
  display: block;
  margin-bottom: .5rem;
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: .58rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--copper, #c9703f);
}

.wf-caption p {
  margin: 0 0 .7rem;
  font-size: .95rem;
  color: var(--text, rgba(240,236,232,.9));
}

.wf-caption__note {
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: .68rem;
  line-height: 1.75;
  color: var(--dim, rgba(226,224,224,.34));
}

.wf-more {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  margin-top: 1.5rem;
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--amber, #e9a763);
  border-bottom: 1px solid transparent;
  padding-bottom: .25rem;
  transition: border-color .25s, gap .25s;
}

.wf-more:hover { border-color: var(--amber, #e9a763); gap: .95rem; }

@media (max-width: 720px) {
  .wf-slides { grid-auto-columns: 124px; }
}
