/* Les cinq univers /portfolio/[slug]/ — registre éditorial. Bucket ③, identité d'île.
 *
 * Le papier déchiré, le coup de pinceau et les rotations de clichés restent au HUB : ils
 * n'apparaissent pas ici. Les valeurs de mouvement sont celles du tronc, relevées dans
 * css/style.css de JMB et recopiées — jamais réinventées.
 *
 * Chargée APRÈS css/style.css : elle surcharge sans `!important`.
 */

/* ── Ouverture : tranche · texte · photo ──────────────────────────────────────── */
.pu-ouverture {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
  max-width: calc(var(--max-w) + var(--gutter) * 2);
  margin: 0 auto;
  padding-inline: var(--gutter);
  padding-block: clamp(2rem, 6vw, 4.5rem) clamp(3rem, 8vw, 6rem);
}

/* La tranche est une TOUCHE, pas un mot à lire : elle doit pouvoir passer inaperçue sans que la
   page perde son sens. D'où `aria-hidden` côté HTML et l'opacité basse ici. */
.pu-tranche {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-family: var(--font-title);
  font-weight: 300;
  font-size: clamp(3rem, 7vw, 5.5rem);
  line-height: 1;
  color: var(--color-text);
  opacity: 0.14;
  user-select: none;
}

/* Le fil est le RETOUR au hub : il doit se lire, pas se deviner. En minuscules serrées il pesait
   le même poids visuel que le surtitre juste en dessous, et l'œil ne savait plus lequel était la
   navigation — c'est la forme du patron v4 qui tranche, capitales espacées. */
.pu-fil {
  font-size: 0.86rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted-text);
  margin: 0 0 clamp(1.7rem, 3.5vw, 2.4rem);
}
.pu-fil a { color: inherit; text-decoration: none; border-bottom: 1px solid rgba(28, 25, 21, 0.2); }
.pu-fil a:hover { color: var(--color-text); }
.pu-num { color: var(--tape, #9E4B38); font-variant-numeric: tabular-nums; }

/* Colonne, et chaque segment sur sa propre ligne : la coupe du titre ne dépend plus de la largeur
   disponible, elle est décidée par `seo.pages.*.h1.words`. */
.pu-h1 {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin: 0 0 clamp(1.9rem, 3.5vw, 2.5rem);
  font-size: clamp(min(2.2rem, 9.3867vw), 5vw, 3.6rem);
  line-height: 1.05;
}

/* ── Sous-titre de zone : DERNIER segment du <h1>, donc sous le titre sans rien réordonner ──────
   L'ordre du DOM est l'ordre de lecture (contrat SEO v1.7, « H1 en plusieurs lignes ») : aucun
   `order` ici. Une seule zone (« jamais deux zones ») ; le rayonnement va dans l'intro et le schema.
   Le `margin-top` laisse passer la descendante du « g » de « grossesse » : sans lui, les deux
   lignes se touchent. Encre : --color-muted-text sur --color-bg = 5,65:1 (contrast-core).
   Mise en page (#57, demandée par Luca) : la zone est un mot court ; posée au ras de la marge, elle
   flottait seule à gauche. Son texte s'aligne sur le retrait de la ligne en italique (2,5rem,
   `.italic-mask .word-inner` dans style.css) et un filet fin relie ce retrait à la marge :
   filet (1,6rem) + gouttière (0,9rem) = 2,5rem. Le filet est un pseudo-élément sans texte — il
   n'entre ni dans le heading lu ni dans l'audit. */
.pu-h1 .h1-location {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  color: var(--color-muted-text);
  margin: 0.95rem 0 0;
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 500;
  font-variant: small-caps;
  text-transform: uppercase;
  letter-spacing: 0.26em;
  line-height: 1.4;
}
.pu-h1 .h1-location::before {
  content: '';
  flex: 0 0 1.6rem;
  height: 1px;
  background: currentColor;
  opacity: 0.45;
}
.pu-h1 .word-mask { display: block; overflow: visible; }
.pu-h1 .word-inner { font-family: var(--font-title); font-weight: 400; }
.pu-h1 .italic-mask .word-inner { font-style: italic; color: var(--color-accent); }

.pu-para { margin: 0 0 1rem; line-height: 1.75; color: var(--color-muted-text); max-width: 46ch; }
.pu-para + .pu-para { margin-inline-start: clamp(0rem, 4vw, 3rem); }

/* Hauteur BORNÉE, jamais d'aspect-ratio : c'est un aspect-ratio imposé qui produisait le cadre
   trop haut, corrigé en v3 de la maquette.
   Aucun `view-transition-name` ici : la plongée hub → univers a été REFUSÉE à l'œil (13/09/26 —
   déplacement plutôt que plongée, flashs, absente de Firefox). */
.pu-cover { position: relative; margin: 0; overflow: hidden; }
.pu-cover img {
  width: 100%;
  height: clamp(360px, 54vh, 560px);
  object-fit: cover;
}

/* ── Le rideau de la photo d'ouverture ───────────────────────────────────────────
   La photo entrait d'un coup pendant que le texte s'animait : l'écart se voyait sur les trois
   moteurs. Elle se découvre donc de bas en haut sous un aplat couleur de PAGE — le geste de
   `jmpRevelePhoto` à l'accueil, même courbe, même durée.
   Le rideau est un pseudo-élément PAR-DESSUS l'image, jamais l'image elle-même : c'est la
   candidate LCP, que le contrat SEO interdit d'animer. Peinte dès la première image sous le
   rideau, elle ne recule pas le LCP d'une milliseconde.
   Il REPOSE replié et l'animation part de l'état déplié (`backwards`) : si elle ne joue pas
   — réglage d'OS, moteur qui l'ignore — il n'existe aucun état où la photo reste couverte.
   `transform` seul : composé par le GPU, aucune repeinture de la photo pendant le geste. */
.pu-cover::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--color-bg);
  transform: scaleY(0);
  transform-origin: top;
  pointer-events: none;
}
@keyframes pu-rideau { from { transform: scaleY(1); } to { transform: scaleY(0); } }
html.pu-anime .pu-cover::after { animation: pu-rideau 1.15s cubic-bezier(0.22, 1, 0.36, 1) 0.1s backwards; }

/* ── Entrée des textes — armée AVANT le premier paint ─────────────────────────────
   ⚠️ DÉFAUT PAYÉ, ne pas revenir en arrière. Ces animations étaient gouvernées par une classe
   `est-entree` posée depuis `js/portfolio-univers.js`, chargé en `defer` : le navigateur peignait
   donc AU MOINS une image où le texte était à son état naturel — visible — avant que la classe
   n'arrive et que `both` ne le renvoie à `from { opacity: 0 }` pour le refaire entrer. Observé à
   l'œil : Chrome une fois sur deux, Safari saccadé au même endroit, Firefox jamais (il pose le
   style avant de peindre plus souvent). La maquette v4 ne l'a jamais montré parce que son script
   est inline en fin de body, donc exécuté avant le premier paint — c'est CE détail qu'il fallait
   porter, pas la règle CSS.
   `pu-anime` est posée par un script inline SYNCHRONE dans le <head> de chaque page : il n'existe
   aucune image peinte sans elle. Et l'animation part directement de la classe, sans attendre un
   second script — si `portfolio-univers.js` ne se chargeait pas, le texte entrerait quand même.
   Sans JavaScript du tout : pas de classe, donc aucun état caché. Le texte est simplement là. */
@keyframes sp-enter-down { from { opacity: 0; transform: translateY(1.6rem); } to { opacity: 1; transform: translateY(0); } }
@keyframes sp-enter-left { from { opacity: 0; transform: translateX(-2rem); } to { opacity: 1; transform: translateX(0); } }

html.pu-anime .pu-fil,
html.pu-anime .pu-h1 { animation: sp-enter-down 0.7s cubic-bezier(0.16, 1, 0.3, 1) backwards; }
html.pu-anime .pu-para { animation: sp-enter-left 0.7s cubic-bezier(0.16, 1, 0.3, 1) backwards; }

/* Les délais du tronc (0,42 / 0,54 / 0,68 / 0,82 s) avancés de 0,30 s : ils attendaient la fin
   du fondu entre pages, retiré le 13/09/26. Gardés tels quels, la page arrivait vide un tiers de
   seconde. L'écart entre eux, lui, est celui du tronc. */
html.pu-anime .pu-fil  { animation-delay: 0.12s; }
html.pu-anime .pu-h1   { animation-delay: 0.24s; }
html.pu-anime .pu-para { animation-delay: 0.38s; }
html.pu-anime .pu-para + .pu-para { animation-delay: 0.52s; }

/* ── La planche ───────────────────────────────────────────────────────────────────
   La bande utile vaut --max-w PLEIN, gouttières EN PLUS. Enfermer la grille dans un
   max-width: 1200px coûte 43 px par colonne et produit des cadres étroits — c'est le défaut
   corrigé en v3 de la maquette. */
.pu-planche {
  max-width: calc(var(--max-w) + var(--gutter) * 2);
  margin: 0 auto;
  padding-inline: var(--gutter);
  padding-bottom: clamp(3rem, 8vw, 6rem);
}
.pu-grille { display: grid; grid-template-columns: repeat(3, 1fr); gap: calc(var(--gutter) * 0.4); }

.pu-cliche { overflow: hidden; aspect-ratio: 2/3; background: var(--color-surface); }
/* Vague éditoriale : colonnes latérales en haut, colonne centrale en bas. */
.pu-cliche:nth-child(3n-2), .pu-cliche:nth-child(3n) { transform: translateY(-1.2rem); }
.pu-cliche:nth-child(3n-1) { transform: translateY(1.2rem); }

/* UN SEUL GESTE : la cascade en fondu (#178 D3, Luca 12/10). La découpe verticale (`clip-path`)
   jouait PAR-DESSUS la cascade — « double mouvement, comme buggé » ; elle est retirée partout.
   L'état d'entrée vit sur l'ENFANT, le cadre `.pu-cliche` reste l'observé (règle de l'île, 812a718). */
.pu-cliche-inner {
  height: 100%;
  opacity: 0;
  transition: opacity 0.65s ease;
  /* Propriété LONGUE déclarée APRÈS le raccourci : `transition` remet le délai à 0s, donc
     l'ordre des deux déclarations est porteur. */
  transition-delay: var(--reveal-delay, 0ms);
}
.pu-cliche.est-visible .pu-cliche-inner { opacity: 1; }
/* Photo non peinte au plafond ou en erreur (#178 D2, O5 amendé) : le groupe est POSÉ, état final sans transition.
   Même poids que la règle d'au-dessus, posée APRÈS : c'est la position qui tranche. */
.pu-cliche.est-pose .pu-cliche-inner { opacity: 1; transition: none; }

.pu-cliche img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; }
.pu-cliche:hover img { transform: scale(1.04); }

.pu-appel { text-align: center; margin: clamp(3rem, 7vw, 5rem) 0 0; }

/* ── La galerie complète ──────────────────────────────────────────────────────── */
.pu-complete {
  max-width: calc(var(--max-w) + var(--gutter) * 2);
  margin: 0 auto;
  padding-inline: var(--gutter);
  padding-bottom: clamp(3rem, 8vw, 5rem);
}
.pu-filet { display: flex; align-items: center; gap: 1rem; margin: 0 0 1.6rem; }
.pu-filet::before, .pu-filet::after { content: ''; flex: 1; }
/* SYMÉTRIQUE, et un cheveu de chaque côté. Le filet naissait à 2 px à gauche contre 1 px à
   droite : l'asymétrie se voyait et lisait comme un défaut, pas comme un parti. */
.pu-filet::before,
.pu-filet::after { border-top: 1px solid var(--color-accent); }
.pu-filet span { font-size: 0.68rem; letter-spacing: 0.26em; color: var(--color-accent); }

.pu-bande { display: flex; gap: 1rem; overflow-x: auto; scrollbar-width: none; }
.pu-bande::-webkit-scrollbar { display: none; }
/* L'effet de vague du tronc : la vignette MONTE, en plus du zoom de son image. */
.pu-vignette {
  flex: 0 0 clamp(190px, 22vw, 270px);
  aspect-ratio: 3/4;
  overflow: hidden;
  transition: transform 0.4s ease;
}
.pu-vignette img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; }
.pu-vignette:hover { transform: translateY(-6px); }
.pu-vignette:hover img { transform: scale(1.04); }
/* Le filet reprend l'idiome de l'île (`.nav-link::after`, `.btn-filet-filet::after`) : un
   pseudo-élément peint par `scaleX`, jamais une largeur qui grandit — une largeur animée relance
   un reflow à chaque frame. Ici le repos est le filet ENTIER et le survol le rétracte : le pied
   d'une page se lit comme une fin de ligne, pas comme un bouton à découvrir. */
.pu-parcourir {
  margin: 1.6rem 0 0;
  font-family: var(--font-title);
  font-weight: 300;
  font-size: 1.15rem;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.18em;
}
.pu-parcourir a { position: relative; color: var(--color-text); text-decoration: none; }
.pu-parcourir a::after {
  content: '';
  position: absolute;
  left: 0;
  /* Le tracking pose une espace APRÈS la dernière lettre : sans cette compensation, le filet
     dépasse du mot de 0.18em à droite et le centrage optique se voit décalé. */
  right: 0.18em;
  bottom: -6px;
  height: 1px;
  background: var(--color-accent);
  transform: scaleX(1);
  transform-origin: right;
  transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
.pu-parcourir a:hover::after,
.pu-parcourir a:focus-visible::after { transform: scaleX(0.35); }

/* ── Le cartouche : le passage d'un univers à l'autre ────────────────────────────
   Il remplace la section « autres catégories » refusée par la spec : le retour vit sur la ligne
   qui existait déjà, et la page ne gagne pas un défilement de plus. */
.pu-cartouche {
  max-width: calc(var(--max-w) + var(--gutter) * 2);
  margin: 0 auto;
  padding-inline: var(--gutter);
  padding-block: 0 clamp(3rem, 8vw, 5rem);
}

/* Desktop : le filet de fin de page se partage en cinq colonnes, une par univers. Le courant
   est tracé à l'encre ; au survol, le filet d'un autre se trace de gauche à droite. */
.pu-univers-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0 clamp(0.8rem, 2vw, 1.6rem);
}
.pu-univers { position: relative; display: grid; gap: 0.55rem; padding-top: 1.5rem; color: var(--color-muted-text); text-decoration: none; }
.pu-univers::before,
.pu-univers::after { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; }
.pu-univers::before { background: rgba(28, 25, 21, 0.13); }
.pu-univers::after { background: var(--color-accent); transform: scaleX(0); transform-origin: left; transition: transform 0.8s cubic-bezier(0.22, 1, 0.36, 1); }
.pu-univers-num { font-size: 0.66rem; letter-spacing: 0.22em; color: var(--color-muted); font-variant-numeric: tabular-nums; transition: color 0.5s ease; }
.pu-univers-nom { font-family: var(--font-title); font-weight: 300; font-size: clamp(1.2rem, 1.9vw, 1.75rem); line-height: 1.1; transition: color 0.5s ease, transform 0.7s cubic-bezier(0.22, 1, 0.36, 1); }
a.pu-univers:hover::after,
a.pu-univers:focus-visible::after { transform: scaleX(1); }
a.pu-univers:hover .pu-univers-nom,
a.pu-univers:focus-visible .pu-univers-nom { color: var(--color-text); transform: translateX(0.3rem); }
a.pu-univers:hover .pu-univers-num,
a.pu-univers:focus-visible .pu-univers-num { color: var(--color-accent); }
.pu-univers.est-courant { color: var(--color-accent); }
.pu-univers.est-courant::after { height: 2px; top: -0.5px; background: currentColor; transform: scaleX(1); }
.pu-univers.est-courant .pu-univers-num { color: currentColor; }
.pu-univers.est-courant .pu-univers-nom { font-style: italic; }

/* Les voisins n'existent qu'au mobile. */
.pu-voisins { display: none; }

/* ── Ruptures — celles du tronc ───────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .pu-grille { grid-template-columns: repeat(2, 1fr); }
  .pu-cliche:nth-child(3n-2), .pu-cliche:nth-child(3n), .pu-cliche:nth-child(3n-1) { transform: none; }
  .pu-cliche:nth-child(odd) { transform: translateY(-1rem); }
  .pu-cliche:nth-child(even) { transform: translateY(1rem); }
  .pu-ouverture { grid-template-columns: auto minmax(0, 1fr); }
  .pu-cover { grid-column: 1 / -1; }
}

@media (max-width: 768px) {
  .pu-grille { gap: calc(var(--gutter) * 0.3); }
}

@media (max-width: 620px) {
  .pu-tranche { display: none; }
  /* Mobile : les deux univers voisins en grand, les cinq en petites capitales dessous — serré en
     hauteur, la page ne doit pas s'allonger. */
  .pu-cartouche { border-top: 1px solid rgba(28, 25, 21, 0.13); padding-block: 1.6rem 2.6rem; }
  .pu-voisins { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
  .pu-voisin { display: grid; gap: 0.35rem; color: var(--color-text); text-decoration: none; }
  .pu-voisin-suiv { justify-items: end; text-align: right; }
  .pu-voisin small { display: flex; align-items: center; gap: 0.6rem; font-size: 0.62rem; letter-spacing: 0.24em; text-transform: uppercase; color: var(--color-muted); }
  .pu-voisin-suiv small { flex-direction: row-reverse; }
  .pu-voisin-trait { width: 1.6rem; height: 1px; background: currentColor; }
  .pu-voisin strong { font-family: var(--font-title); font-weight: 400; font-size: 1.6rem; line-height: 1; }
  .pu-voisin:active strong { color: var(--color-accent); }
  .pu-univers-liste { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.4rem 0.7rem; margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px solid rgba(28, 25, 21, 0.13); }
  .pu-univers { display: block; padding: 0.2rem 0; }
  .pu-univers::before, .pu-univers::after, .pu-univers-num { display: none; }
  .pu-univers-nom { font-family: var(--font-body); font-weight: 400; font-size: 0.6rem; letter-spacing: 0.14em; text-transform: uppercase; }
  .pu-univers.est-courant .pu-univers-nom { font-style: normal; }
  .pu-ouverture { grid-template-columns: minmax(0, 1fr); }
  .pu-para + .pu-para { margin-inline-start: 0; }
}

/* ── Doctrine tactile : le mouvement ne dépend JAMAIS du survol ──────────────────
   Au tap, Safari iOS active un `:hover` collant → zoom et vague rejoués, repaint et jank.
   Le socle est DÉSARMÉ dès que l'aperçu impose un pointeur : c'est alors l'appareil VISÉ qui
   décide, jamais le pointeur de l'hôte. `:where()` pour une spécificité nulle sur le garde.
   L'attribut `data-jmb-preview-pointer` porte `jmb` par héritage de fork : c'est un contrat
   technique éditeur↔page, pas une identité de marque. Le renommer casse l'aperçu. */
@media (hover: none) {
  :where(html:not([data-jmb-preview-pointer])) .pu-cliche:hover img,
  :where(html:not([data-jmb-preview-pointer])) .pu-vignette:hover,
  :where(html:not([data-jmb-preview-pointer])) .pu-vignette:hover img,
  :where(html:not([data-jmb-preview-pointer])) a.pu-univers:hover .pu-univers-nom { transform: none; }
}
html[data-jmb-preview-pointer="coarse"] .pu-cliche:hover img,
html[data-jmb-preview-pointer="coarse"] .pu-vignette:hover,
html[data-jmb-preview-pointer="coarse"] .pu-vignette:hover img,
html[data-jmb-preview-pointer="coarse"] a.pu-univers:hover .pu-univers-nom { transform: none; }

/* ── Sans JavaScript : la planche reste ENTIÈRE ────────────────────────────────────
   `.pu-cliche-inner` naît à `opacity: 0` et c'est `js/portfolio-univers.js` qui la révèle.
   Sans lui, la planche est purement et simplement INVISIBLE — pas « immobile », absente.
   `scripting: none` est la réponse déclarative ; le `<noscript>` de chaque page couvre les
   moteurs qui ne l'implémentent pas encore. Deux mécanismes, parce qu'un seul laisserait un
   trou dont la conséquence est une page vide. */
@media (scripting: none) {
  .pu-cliche-inner { opacity: 1; transition: none; }
}

/* ── Réglage OS : les mouvements se coupent, la page reste entière ─────────────────
   ⚠️ Le sélecteur porte `.pu-cliche` EN PLUS de l'enfant, et ce n'est pas cosmétique :
   `.pu-cliche.est-visible .pu-cliche-inner` pèse (0,3,0), et une media-query n'ajoute AUCUNE
   spécificité. Écrites sur `.pu-cliche-inner` seul (0,1,0), ces déclarations étaient INERTES —
   servies, jamais appliquées (mesuré sur la page déployée, du temps de la découpe retirée en #178). */
@media (prefers-reduced-motion: reduce) {
  /* Même poids que les règles d'entrée ci-dessus (0,2,1) : c'est la POSITION, plus bas dans la
     feuille, qui tranche. Un sélecteur plus court serait servi et jamais appliqué. */
  html.pu-anime .pu-fil,
  html.pu-anime .pu-h1,
  html.pu-anime .pu-para { animation: none; opacity: 1; }
  /* `*` ne vise pas les pseudo-éléments : le réglage global de style.css laisserait jouer le rideau. */
  html.pu-anime .pu-cover::after { animation: none; }
  .pu-cliche .pu-cliche-inner,
  .pu-cliche.est-visible .pu-cliche-inner { opacity: 1; transition: none; }
  .pu-cliche img, .pu-vignette, .pu-vignette img { transition: none; }
  .pu-univers::after, .pu-univers-num, .pu-univers-nom { transition: none; }
  .pu-cliche:hover img, .pu-vignette:hover, .pu-vignette:hover img { transform: none; }
}
