@font-face{font-family:Druk;src:url('fonts/DrukHeavy.woff2') format('woff2');font-display:swap;font-weight:400 900}
@font-face{font-family:Archivo;src:url('fonts/Archivo-ExtraBold.woff2') format('woff2');font-display:swap;font-weight:800}
:root{--ink:#10100e;--bone:#f4ecdc;--blue:#175dde;--deep:#052b72;--lime:#c7ff32;--coral:#ff6d58;--line:2px solid var(--ink);--fish-accent:var(--lime);--hero-ui-opacity:0;
  /* LA regle de detachement pour tout texte pose sur la scene: une ombre courte et franche qui
     decolle la lettre du pixel voisin + un halo large qui assombrit la zone sans la masquer.
     Toute exception documentee doit garder ces deux couches (le mobile de .intro-line en a une
     variante en px fixes, meme idee). */
  --scene-text-shadow:0 2px 0 rgba(3,27,68,.9),0 0 .45em rgba(3,27,68,.55)}
*{box-sizing:border-box}
html{background:var(--deep);scroll-behavior:smooth;scrollbar-color:var(--lime) var(--deep)}
html.is-guided-dive{scroll-behavior:auto}
body{margin:0;min-height:100%;overflow-x:hidden;background:var(--deep);color:var(--ink);font-family:Archivo,sans-serif}
button,a{font:inherit}
.skip-link{position:fixed;z-index:100;top:8px;left:8px;translate:0 -150%;padding:10px 14px;background:var(--bone);border:var(--line);color:var(--ink);font-weight:900}
.skip-link:focus{translate:0}
.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
.fish-world{position:relative;min-height:2040vh;background:var(--deep)}
.scroll-depth{height:2040vh;pointer-events:none}
.masthead{position:fixed;z-index:10;inset:0 0 auto;display:grid;grid-template-columns:1fr auto;align-items:start;padding:clamp(18px,3vw,38px);pointer-events:none;transition:transform .65s cubic-bezier(.32,.72,0,1),opacity .65s cubic-bezier(.32,.72,0,1)}
.masthead a,.masthead button{pointer-events:auto}
.wordmark{display:block;width:max-content;text-decoration:none}
.wordmark img{display:block;width:auto;height:clamp(38px,3.4vw,52px)}
.tools{justify-self:end;display:flex;gap:8px;position:relative}
/* Tant que le loader couvre la page, les outils sont invisibles ET injoignables (visibility
   coupe aussi le focus clavier); le wordmark, lui, reste visible pendant le chargement. */
.masthead .tools{opacity:0;visibility:hidden;transition:opacity .5s cubic-bezier(.32,.72,0,1) .1s,visibility .5s .1s}
html.is-loaded .masthead .tools{opacity:1;visibility:visible}
/* Petite invitation qui pointe vers #sound-toggle sans le doubler: cliquer dessus active le
   vrai bouton (source de vérité unique). Papier découpé: pastille cernée, ombre dure décalée,
   même vocabulaire que .tools button, pas une bannière. */
.sound-nudge{position:absolute;top:calc(100% + 10px);right:0;display:flex;align-items:center;
  gap:6px;padding:7px 14px;border:var(--line);border-radius:999px;background:var(--lime);
  box-shadow:3px 3px 0 var(--ink);font-weight:900;font-size:13px;white-space:nowrap;cursor:pointer;
  z-index:11;animation:sound-nudge-in .5s cubic-bezier(.32,.72,0,1) both,
  sound-nudge-bob 2.3s ease-in-out .6s infinite}
.sound-nudge::before{content:'';position:absolute;top:-7px;right:22px;width:12px;height:12px;
  background:var(--lime);border-left:var(--line);border-top:var(--line);transform:rotate(45deg)}
.sound-nudge[hidden]{display:none}
@keyframes sound-nudge-in{from{opacity:0;transform:translateY(-6px) scale(.92)}to{opacity:1;transform:translateY(0) scale(1)}}
@keyframes sound-nudge-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@media(prefers-reduced-motion:reduce){.sound-nudge{animation:sound-nudge-in .01ms both}}
.tools button{min-width:48px;height:44px;padding:0 12px;border:var(--line);border-radius:999px;background:var(--bone);box-shadow:3px 3px 0 var(--ink);font-weight:950;cursor:pointer}
.tools button[aria-pressed=true]{background:var(--lime)}
.tools .sound-label{margin-left:6px}
.tools .fish-search-toggle{display:grid;place-items:center;width:48px;padding:0;transition:background .14s,transform .14s}
.fish-search-toggle:hover,.fish-search-toggle:focus-visible,.fish-search-toggle[aria-expanded=true]{background:var(--lime);transform:translateY(-2px)}
.fish-search-toggle svg,.fish-search-field svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:2.35;stroke-linecap:round}
.tank,#webgl{position:fixed;inset:0}
.tank{z-index:1;overflow:hidden}
#webgl canvas{display:block;width:100%;height:100%;touch-action:pan-y}
.hero-veil{position:absolute;z-index:2;inset:-2%;background:linear-gradient(180deg,rgba(22,96,185,.08),rgba(4,52,116,.32));pointer-events:none;transform:scale(1.035);transform-origin:50% 38%}
.hero-veil::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(4,48,112,.02) 0%,rgba(5,43,114,.2) 72%,rgba(3,35,89,.48) 100%);mix-blend-mode:multiply}
.hero-story{position:absolute;z-index:4;inset:0;min-height:100dvh;color:var(--bone);pointer-events:none;overflow:hidden}
.hero-title{position:absolute;z-index:3;inset:0;display:grid;place-content:center;justify-items:center;padding:8vh clamp(18px,4vw,64px) 12vh;text-align:center;transform-origin:50% 46%}
.hero-kicker{display:flex;align-items:center;gap:14px;margin:0 0 clamp(22px,3vh,38px);font-size:clamp(10px,.76vw,13px);font-weight:900;letter-spacing:.22em;text-transform:uppercase}
.hero-kicker span:first-child{display:grid;place-items:center;width:34px;height:34px;border:2px solid currentColor;border-radius:50%;letter-spacing:0}
.hero-title h1{display:grid;gap:.035em;margin:0;color:var(--bone);font:900 clamp(86px,15.5vw,260px)/.68 Druk,sans-serif;letter-spacing:.055em;text-transform:uppercase;text-wrap:balance;text-shadow:0 .018em 0 rgba(3,27,68,.24)}
.hero-title h1 span:last-child{color:var(--lime)}
.hero-scroll-cue{position:absolute;bottom:max(29px,env(safe-area-inset-bottom));left:50%;translate:-50% 0;display:flex;align-items:center;gap:12px;margin:0;font-size:10px;font-weight:900;letter-spacing:.18em;text-transform:uppercase;white-space:nowrap}
.hero-scroll-cue span:first-child{display:grid;place-items:center;width:28px;height:28px;border:2px solid currentColor;border-radius:50%;font-size:15px;letter-spacing:0}
.hero-intro{position:absolute;z-index:5;top:50%;left:clamp(24px,9vw,150px);width:min(790px,72vw);opacity:0;transform:translate3d(0,8vh,0)}
.hero-chapter{margin:0 0 22px;color:var(--lime);font-size:11px;font-weight:900;letter-spacing:.2em;text-transform:uppercase}
.hero-intro-copy{margin:0;font-size:clamp(25px,3.55vw,58px);font-weight:850;line-height:1.02;letter-spacing:-.025em;text-wrap:balance;text-shadow:0 2px 0 rgba(3,27,68,.2)}
.biome-story{position:absolute;z-index:5;isolation:isolate;left:clamp(24px,8vw,132px);bottom:clamp(92px,13vh,150px);width:min(760px,70vw);color:var(--bone);opacity:0;pointer-events:none;transform:translate3d(0,34px,0);will-change:opacity,transform}
/* Le voile est elargi vers le haut et les cotes: un poisson qui traverse la zone du titre a
   900-1300 m ne doit plus passer sous un texte perche sur du vide. */
.biome-story::before{content:"";position:absolute;z-index:-1;inset:-58% -54vw -26vh -22vw;background:linear-gradient(to top,rgba(3,27,68,.9) 0,rgba(3,27,68,.86) 26%,rgba(3,27,68,.66) 52%,rgba(3,27,68,.3) 76%,transparent 96%);opacity:0;transform:scale(.96);transform-origin:26% 60%;transition:opacity .42s cubic-bezier(.32,.72,0,1),transform .55s cubic-bezier(.32,.72,0,1);pointer-events:none}
.biome-story.is-story-visible::before{opacity:1;transform:scale(1)}
.biome-kicker{display:flex;align-items:center;gap:12px;margin:0 0 15px;color:var(--lime);font-size:11px;font-weight:950;letter-spacing:.18em;text-transform:uppercase;text-shadow:var(--scene-text-shadow)}
/* Le tiret lime porte l'accent sur les biomes ou le texte passe en bone (ci-dessous): l'accent
   reste present sans que le mot entier se perde dans l'eau verte. */
.biome-kicker::before{content:'';flex:none;width:26px;height:3px;background:var(--lime);box-shadow:0 1px 0 rgba(3,27,68,.9)}
/* Eaux claires et vertes (estran, kelp): le lime sur lime se dissolvait. Le texte passe bone,
   l'accent lime reste au tiret. Pilote par dataset.biome, pose par updateBiomeUI. */
.biome-story[data-biome=intertidal] .biome-kicker,.biome-story[data-biome=rocky-kelp] .biome-kicker{color:var(--bone)}
.biome-story h2{margin:0;font:900 clamp(64px,9vw,150px)/.76 Druk,sans-serif;letter-spacing:.055em;text-transform:uppercase;text-wrap:balance;text-shadow:0 .02em 0 rgba(3,27,68,.5),0 0 .5em rgba(3,27,68,.55)}
.biome-subtitle{max-width:50ch;margin:22px 0 0;color:var(--bone);font-size:clamp(17px,1.7vw,26px);font-weight:800;line-height:1.15;letter-spacing:-.015em;text-wrap:balance;text-shadow:var(--scene-text-shadow)}
.hero-wave{position:absolute;z-index:4;left:-10vw;top:0;width:120vw;height:46vh;transform:translate3d(0,115vh,0) rotate(-1.2deg);transform-origin:50% 50%;will-change:transform}
.hero-wave svg{display:block;width:100%;height:100%;overflow:visible}
.hero-wave-shadow{fill:none;stroke:rgba(5,43,114,.82);stroke-width:36;stroke-linecap:round;stroke-linejoin:round}
.hero-wave-foam-back{fill:none;stroke:rgba(199,255,50,.82);stroke-width:23;stroke-linecap:round;stroke-linejoin:round}
.hero-wave-foam-front{fill:none;stroke:var(--bone);stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3 9}
.hero-bubbles{position:absolute;z-index:6;inset:0;overflow:hidden;pointer-events:none}
.hero-bubble{position:absolute;left:var(--bubble-x);bottom:-12vh;width:var(--bubble-size);height:var(--bubble-size);border:clamp(2px,.22vw,4px) solid rgba(244,236,220,.82);border-radius:50%;box-shadow:inset .12em .1em 0 rgba(199,255,50,.38);opacity:0}
.waterline{position:absolute;z-index:3;inset:17% 0 auto;height:3px;background:rgba(244,236,220,.7);box-shadow:0 8px 0 rgba(199,255,50,.14),0 18px 35px rgba(244,236,220,.16);pointer-events:none;transition:opacity .55s cubic-bezier(.32,.72,0,1)}
.waterline i{position:absolute;left:8vw;right:8vw;top:-6px;height:11px;border-block:2px solid rgba(244,236,220,.38);transform:skewX(-18deg)}
.is-aquarium-active .waterline,.is-diving .waterline{opacity:0}
.hint{position:absolute;z-index:5;left:50%;bottom:92px;translate:-50% 12px;width:min(88vw,680px);margin:0;color:var(--bone);font-size:12px;font-weight:850;letter-spacing:.055em;text-align:center;text-shadow:0 1px 0 rgba(0,0,0,.3);opacity:0;pointer-events:none;transition:opacity .7s cubic-bezier(.32,.72,0,1),translate .7s cubic-bezier(.32,.72,0,1)}
.is-aquarium-active.has-organisms .hint{opacity:1;translate:-50% 0}
.is-diving .hint{opacity:0}
.dive-guide{position:absolute;z-index:9;left:50%;bottom:max(22px,env(safe-area-inset-bottom));display:grid;grid-template-columns:54px minmax(0,1fr);grid-template-rows:auto 4px;align-items:center;gap:0 12px;width:min(330px,calc(100vw - 32px));padding:8px 13px 9px 8px;border:var(--line);border-radius:18px;background:rgba(244,236,220,.96);box-shadow:5px 5px 0 var(--ink);opacity:0;pointer-events:none;transform:translate3d(-50%,20px,0) scale(.96);transition:opacity .2s,transform .28s cubic-bezier(.32,.72,0,1)}
.dive-guide.is-visible{opacity:1;transform:translate3d(-50%,0,0) scale(1)}
.dive-guide-thumb{grid-row:1/3;display:grid;place-items:center;width:54px;height:54px;border:var(--line);border-radius:12px;background:var(--blue);overflow:hidden}
.dive-guide-thumb img{width:92%;height:92%;object-fit:contain;filter:drop-shadow(4px 5px 0 rgba(5,43,114,.26))}
.dive-guide-copy{display:flex;align-items:baseline;gap:7px;min-width:0;color:var(--ink)}
.dive-guide-copy small{flex:none;font-size:9px;font-weight:900;letter-spacing:.1em;text-transform:uppercase}.dive-guide-copy strong{overflow:hidden;font-size:15px;text-overflow:ellipsis;white-space:nowrap}
.dive-guide-track{align-self:end;display:block;height:4px;margin-top:7px;border-radius:99px;background:rgba(16,16,14,.16);overflow:hidden}.dive-guide-track i{display:block;width:100%;height:100%;background:var(--fish-accent,var(--lime));transform:scaleX(0);transform-origin:left center;will-change:transform}
.dive-guide-bubbles{position:absolute;inset:auto 12px calc(100% + 4px) auto;width:40px;height:42px}.dive-guide-bubbles i{position:absolute;bottom:0;border:2px solid var(--bone);border-radius:50%;opacity:0}.dive-guide.is-visible .dive-guide-bubbles i{animation:dive-bubble 1.1s ease-out infinite}.dive-guide-bubbles i:nth-child(1){left:2px;width:9px;height:9px}.dive-guide-bubbles i:nth-child(2){left:15px;width:6px;height:6px;animation-delay:.22s!important}.dive-guide-bubbles i:nth-child(3){left:28px;width:12px;height:12px;animation-delay:.46s!important}
.fish-arrival{position:absolute;z-index:8;left:0;top:0;display:grid;place-items:center;width:92px;height:92px;color:var(--bone);opacity:0;pointer-events:none;transform:translate3d(calc(var(--arrival-x,50vw) - 46px),calc(var(--arrival-y,50vh) - 46px),0) scale(.72);transition:opacity .14s,transform .34s cubic-bezier(.2,.9,.25,1.2)}
.fish-arrival.is-visible{opacity:1;transform:translate3d(calc(var(--arrival-x,50vw) - 46px),calc(var(--arrival-y,50vh) - 46px),0) scale(1)}
.fish-arrival>i{position:absolute;inset:0;border:3px dashed var(--lime);border-radius:50%;box-shadow:0 0 0 7px rgba(5,43,114,.3)}
.fish-arrival>span{position:absolute;top:calc(100% + 7px);left:50%;translate:-50% 0;min-width:max-content;padding:6px 9px;border:2px solid currentColor;border-radius:999px;background:rgba(5,43,114,.86);font-size:9px;font-weight:950;letter-spacing:.1em;text-transform:uppercase}
.depth-meter{position:absolute;z-index:7;right:clamp(16px,3vw,42px);top:50%;translate:12px -50%;display:grid;justify-items:center;gap:10px;color:var(--bone);pointer-events:none;opacity:0;transition:opacity .7s cubic-bezier(.32,.72,0,1),translate .7s cubic-bezier(.32,.72,0,1)}
/* Le compteur n'apparait qu'avec la plongee reelle (.is-diving, premier metre > 8 dans
   updateDepthUI): pendant le titre et tout le manifeste il n'a que "0 M" a dire, donc il ne
   s'affiche pas. L'ecran de texte reste vierge. */
.is-diving .depth-meter{opacity:1;translate:0 -50%}
.depth-meter output{min-width:62px;padding:6px 8px;border:2px solid currentColor;border-radius:999px;background:rgba(4,33,88,.68);font:900 14px/1 Archivo,sans-serif;letter-spacing:.08em;text-align:center;box-shadow:3px 3px 0 rgba(16,16,14,.6)}
.depth-track{position:relative;display:block;width:10px;height:min(42vh,350px);border:1.5px solid rgba(244,236,220,.75);border-radius:99px;background:rgba(5,29,66,.55)}
/* La pastille glisse SUR la barre: un marqueur ponctuel positionne par le haut (top, plus
   height), jamais un remplissage qui la couvrirait et cacherait les segments de biome en
   dessous. Le but est justement de voir tous les chapitres, traverses ou non, d'un coup d'oeil.
   Draggable au pointeur (pointer-events rehabilites, l'aside entier est en none). */
/* L'aside entier est en pointer-events:none; les interactions ne se rehabiltent QUE quand le
   compteur est visible (.is-diving): invisible pendant le manifeste, les segments ne doivent
   capter aucun tap. */
.depth-track>i{position:absolute;left:50%;top:0;width:14px;height:14px;translate:-50% -50%;border-radius:50%;background:var(--lime);box-shadow:0 0 0 2px var(--deep),0 0 12px rgba(199,255,50,.65);z-index:2;transition:top .3s cubic-bezier(.32,.72,0,1);pointer-events:none;cursor:grab;touch-action:none}
.depth-track>i.is-dragging{cursor:grabbing;transition:none}
/* Six boutons empiles sur toute la hauteur, un par biome: la barre sert de sommaire ET de
   navigation (clic = plongee guidee vers le chapitre). Le fond encre du conteneur plus le gap
   de 1px dessinent les separateurs. Chaque segment garde sa teinte d'eau SATUREE en permanence
   (BIOME_TRACK_COLORS); fini le grayscale qui les rendait invisibles sur eau sombre. */
.depth-track b{position:absolute;inset:0;display:flex;flex-direction:column;gap:1px;border-radius:inherit;overflow:hidden;background:var(--ink);pointer-events:none}
.is-diving .depth-track>i,.is-diving .depth-track b{pointer-events:auto}
.depth-segment{flex-grow:1;flex-basis:0;min-height:24px;position:relative;padding:0;border:0;cursor:pointer;appearance:none}
/* Le biome courant est cerne de lime, pas desature/reatture par filtre: un changement d'etat
   instantane, sans couche superposee. inset parce que le conteneur arrondi clippe. */
.depth-segment.is-current{box-shadow:inset 0 0 0 2px var(--lime)}
.depth-segment:hover,.depth-segment:focus-visible{filter:brightness(1.25) saturate(1.15)}
.depth-segment:focus-visible{outline:2px solid #fff;outline-offset:-2px}
/* Reperes de profondeur le long du track, petits et discrets: 0 en haut, 700 et 1350 M vers le
   bas (la graduation est logarithmique, comme la course de la pastille). Decoratifs. */
.depth-tick{position:absolute;right:calc(100% + 7px);translate:0 -50%;font-size:9px;font-weight:800;letter-spacing:.05em;color:rgba(244,236,220,.62);white-space:nowrap;pointer-events:none}
/* Nom du biome au survol/focus d'un segment: une pastille papier a gauche du track, calee sur
   le milieu du segment (--tip-y ecrit par showDepthTip, evenementiel, jamais par frame). */
.depth-tip{position:absolute;right:calc(100% + 14px);top:var(--tip-y,50%);translate:0 -50%;padding:6px 10px;border:var(--line);border-radius:999px;background:var(--bone);box-shadow:3px 3px 0 var(--ink);color:var(--ink);font-size:10px;font-weight:950;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .16s cubic-bezier(.32,.72,0,1)}
.depth-tip.is-visible{opacity:1}
.loader{position:fixed;z-index:20;inset:0;display:grid;place-content:center;background:var(--bone);transition:opacity .5s,visibility .5s}
.loader.is-done{opacity:0;visibility:hidden}
/* La sortie: le contenu monte de 12px pendant le fondu, "remontee vers la surface". */
.loader-stage{display:grid;justify-items:center;gap:13px;transition:transform .5s cubic-bezier(.32,.72,0,1)}
.loader.is-done .loader-stage{transform:translateY(-12px)}
.loader-fish{display:block;animation:loader-swim .8s steps(4,end) infinite}
.loader-fish svg{display:block;width:clamp(96px,14vw,120px);height:auto}
.loader-fish-main{fill:var(--lime);stroke:var(--ink);stroke-width:3.5;stroke-linejoin:round}
.loader-fish-tail path{fill:var(--bone);stroke:var(--ink);stroke-width:3.5;stroke-linejoin:round}
.loader-fish-fin{fill:var(--blue);stroke:var(--ink);stroke-width:3;stroke-linejoin:round}
.loader-fish-gill{fill:none;stroke:var(--ink);stroke-width:3;stroke-linecap:round}
.loader-fish-eye{fill:#fff;stroke:var(--ink);stroke-width:2.6}
.loader-fish-pupil{fill:var(--ink)}
/* La queue remue autour de son attache, en steps comme le ballotement du corps: papier, pas
   motion capture. transform-origin en unites de viewBox (transform-box par defaut). */
.loader-fish-tail{transform-origin:86px 40px;animation:loader-tail .44s steps(2,jump-none) infinite}
.loader strong{font-size:13px;letter-spacing:.16em;text-transform:uppercase}
.loader-track{display:block;width:min(240px,60vw);height:8px;border:var(--line);background:#fff}
.loader-track i{display:block;width:0;height:100%;background:var(--lime);transition:width .15s}
/* Quatre repliques dans une meme cellule: la rotation est un cycle d'opacites, jamais un
   changement de contenu qui referait mesurer la boite. */
.loader-quips{display:grid;width:min(280px,72vw);height:18px;margin-top:1px}
.loader-quip{grid-area:1/1;text-align:center;font:italic 600 12px Georgia,serif;color:#5a564c;opacity:0;animation:loader-quip 4.8s linear infinite}
.loader-quip:nth-child(2){animation-delay:1.2s}
.loader-quip:nth-child(3){animation-delay:2.4s}
.loader-quip:nth-child(4){animation-delay:3.6s}
@keyframes loader-quip{0%{opacity:0;transform:translateY(3px)}4%{opacity:1;transform:none}21%{opacity:1;transform:none}25%,100%{opacity:0;transform:translateY(-3px)}}
.fish-search{width:min(900px,calc(100vw - 30px));max-height:min(820px,calc(100dvh - 30px));padding:0;border:var(--line);border-radius:26px;background:var(--bone);box-shadow:10px 10px 0 var(--ink);opacity:0;overflow:hidden;transform:translateY(18px) scale(.98);transition:opacity .18s,transform .28s cubic-bezier(.32,.72,0,1)}
.fish-search.is-visible{opacity:1;transform:none}
.fish-search::backdrop{background:rgba(4,24,62,.78);backdrop-filter:blur(4px)}
.fish-search-shell{display:grid;grid-template-rows:auto auto auto minmax(0,1fr);max-height:calc(100dvh - 34px);padding:clamp(22px,4vw,42px)}
.fish-search-header{display:flex;align-items:start;justify-content:space-between;gap:22px}.fish-search-header p{margin:0 0 7px;color:var(--blue);font-size:10px;font-weight:950;letter-spacing:.16em}.fish-search-header h2{margin:0;font:900 clamp(44px,7vw,82px)/.82 Druk,sans-serif;letter-spacing:.045em;text-transform:uppercase}
.fish-search-close{flex:none;width:46px;height:46px;padding:0;border:var(--line);border-radius:50%;background:var(--lime);box-shadow:3px 3px 0 var(--ink);font-size:27px;font-weight:900;cursor:pointer}
.fish-search-field{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:12px;margin-top:30px;padding:0 17px;border:var(--line);border-radius:999px;background:#fff;box-shadow:4px 4px 0 var(--ink)}
.fish-search-field:focus-within{background:#fbffe8;box-shadow:4px 4px 0 var(--lime)}.fish-search-field input{width:100%;height:54px;padding:0;border:0;outline:0;background:transparent;color:var(--ink);font-size:16px;font-weight:800}.fish-search-field input::placeholder{color:#77736b}
.fish-search-meta{display:flex;justify-content:space-between;margin:24px 2px 11px;color:var(--blue);font-size:10px;font-weight:950;letter-spacing:.14em;text-transform:uppercase}.fish-search-count{color:var(--ink)}
.fish-search-results{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:2px 7px 10px 2px;overflow:auto;overscroll-behavior:contain}
.fish-search-result{display:grid;grid-template-columns:110px minmax(0,1fr) auto;align-items:center;gap:14px;min-height:96px;padding:7px 13px 7px 7px;border:var(--line);border-radius:17px;background:#fff;text-align:left;cursor:pointer;transition:background .14s,transform .14s,box-shadow .14s}
.fish-search-result:hover,.fish-search-result:focus-visible,.fish-search-result[aria-current=true]{background:var(--result-accent,var(--lime));box-shadow:4px 4px 0 var(--ink);transform:translateY(-2px)}
.fish-search-result img{width:110px;height:80px;padding:5px;border-radius:11px;background:var(--blue);object-fit:contain;filter:drop-shadow(4px 5px 0 rgba(5,43,114,.24))}.fish-search-result-copy{min-width:0}.fish-search-result strong{display:block;overflow:hidden;font-size:16px;text-overflow:ellipsis;white-space:nowrap}.fish-search-result em{display:block;overflow:hidden;margin-top:4px;color:#555149;font-family:Georgia,serif;font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.fish-search-result b{padding:5px 7px;border:2px solid currentColor;border-radius:999px;font-size:9px;letter-spacing:.08em;white-space:nowrap}
.fish-search-empty{margin:30px 0 16px;padding:30px;border:2px dashed var(--ink);border-radius:18px;text-align:center;font-size:15px;font-weight:850}
.fish-dialog{width:min(1400px,calc(100vw - 28px));height:min(720px,calc(100dvh - 28px));max-height:calc(100dvh - 28px);padding:0;border:var(--line);border-radius:28px;background:var(--bone);box-shadow:10px 10px 0 var(--ink);overflow:hidden}
.fish-dialog::backdrop{background:rgba(8,18,28,.72);backdrop-filter:blur(5px)}
.dialog-close{position:absolute;z-index:5;top:14px;right:14px;width:46px;height:46px;border:var(--line);border-radius:50%;background:var(--lime);font-size:28px;font-weight:900;cursor:pointer}
.fish-sheet{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);height:100%;overflow:hidden}
.photo-card{min-height:0;height:100%;overflow:hidden;padding:0;border:0;border-right:var(--line);background:var(--blue);perspective:1200px;cursor:pointer}
.photo-card-inner{position:relative;display:block;width:100%;height:100%;min-height:0;transform-style:preserve-3d;transition:transform .65s cubic-bezier(.2,.8,.2,1)}
.photo-card.is-flipped .photo-card-inner{transform:rotateY(180deg)}
.photo-front,.photo-back{position:absolute;inset:0;display:grid;grid-template:1fr/1fr;place-items:stretch;padding:clamp(16px,3vh,44px) 22px;backface-visibility:hidden}
.photo-front img{width:100%;height:100%;min-width:0;min-height:0;object-fit:contain;object-position:center;filter:drop-shadow(15px 20px 0 rgba(10,14,16,.28))}
.photo-back{transform:rotateY(180deg);padding:0;background:#062036}
.photo-back img{width:100%;height:100%;min-width:0;min-height:0;object-fit:contain}
.photo-card em{position:absolute;left:24px;bottom:22px;z-index:2;padding:8px 12px;border:var(--line);background:var(--lime);color:var(--ink);font-style:normal;font-size:11px;font-weight:950;letter-spacing:.06em;text-transform:uppercase}
/* Une image generee ne peut pas s'annoncer comme une photographie: la fiche sert a reconnaitre
   l'animal, donc elle dit d'ou vient ce qu'elle montre. */
.photo-credit{position:absolute;right:14px;bottom:14px;left:14px;max-width:100%;padding:6px 9px;border-radius:9px;background:rgba(8,18,28,.62);color:#f3eee3;font-size:9px;font-weight:700;letter-spacing:.02em;line-height:1.3;text-align:right;text-wrap:balance}
.photo-credit:empty{display:none}
.photo-back em{bottom:56px}
.photo-card.no-photo{cursor:default}
.photo-card.no-photo .photo-front em{background:var(--bone);color:var(--ink);opacity:.72}
.prototype-note{opacity:.62;font-style:italic}
.sheet-copy{display:block;height:100%;padding:clamp(20px,3.4vw,52px);overflow:hidden}
.sheet-inner{zoom:var(--sheet-fit,1);overflow-wrap:anywhere;hyphens:auto}
.specimen{margin:0 0 8px;color:var(--blue);font-size:11px;font-weight:900;letter-spacing:.12em;text-transform:uppercase}
.sheet-copy h2{margin:0;font:900 clamp(44px,6.2vw,104px)/.78 Druk,sans-serif;letter-spacing:.045em;text-transform:uppercase}
.tagline{max-width:35ch;margin:12px 0 22px;font-size:clamp(16px,1.6vw,22px);font-weight:850;line-height:1.2}
.facts{display:grid;grid-template-columns:1fr 1fr;gap:18px 26px}
.facts h3{margin:0 0 8px;font-size:11px;font-weight:950;letter-spacing:.14em;text-transform:uppercase}
.facts p,.facts ul{margin:0;padding:0;font-size:14px;font-weight:650;line-height:1.45}
.facts ul{list-style:none}.facts li::before{content:'↳ ';color:var(--blue);font-weight:950}
.facts .avoid{margin-top:10px}.facts .avoid::before{content:none}
.sheet-copy blockquote{margin:20px 0;padding:14px 0;border-block:var(--line);font:900 clamp(15px,1.35vw,19px)/1.25 Archivo,sans-serif}
.recipe-link{display:flex;justify-content:space-between;align-items:center;min-height:50px;padding:11px 18px;border:var(--line);border-radius:999px;background:var(--lime);box-shadow:4px 4px 0 var(--ink);color:var(--ink);font-weight:950;text-decoration:none}
/* Une espece protegee garde sa fiche entiere, mais ne peut jamais proposer de la cuisiner. */
.protected{margin:12px 0 18px;padding:12px 16px;border:var(--line);border-radius:18px;background:var(--coral);box-shadow:4px 4px 0 var(--ink);color:var(--ink)}
.protected b{display:block;font-size:12px;font-weight:950;letter-spacing:.08em;text-transform:uppercase}
.protected span{display:block;margin-top:6px;font-size:15px;font-weight:650;line-height:1.35}
.protected[data-notice="toxic"]{background:var(--lime)}
.recipe-link b{font-size:26px}.recipe-link:hover{background:var(--coral)}
button:focus-visible,a:focus-visible{outline:3px solid #fff;outline-offset:3px}
@keyframes loader-swim{0%{translate:-16px 0;rotate:-5deg}50%{translate:16px -5px;rotate:3deg}100%{translate:-16px 0;rotate:-5deg}}
@keyframes loader-tail{0%{transform:rotate(-8deg)}100%{transform:rotate(8deg)}}
@keyframes dive-bubble{0%{opacity:0;transform:translate3d(0,6px,0) scale(.7)}28%{opacity:.9}100%{opacity:0;transform:translate3d(4px,-36px,0) scale(1.1)}}
/* Le layout mobile vaut aussi pour un telephone tourne en paysage: assez large pour sortir
   du seuil 760px, trop court pour le layout desktop (fiche zoomee a .5). Le pointeur coarse
   exclut les fenetres desktop simplement basses. Meme condition cote JS: isMobileLayout(). */
@media (max-width:760px),(pointer:coarse) and (max-height:520px){
  /* Le flou des pleins ecrans se paie a chaque frame de la fiche sur mobile; le voile rgba,
     seul, y est visuellement identique. */
  .fish-search::backdrop,.fish-dialog::backdrop{backdrop-filter:none}
  .fish-world,.scroll-depth{min-height:1800vh;height:1800vh}
  .masthead{grid-template-columns:1fr auto;padding:18px}.wordmark img{height:34px}.tools{grid-column:2}.sound-label{display:none}
  .hero-title{padding-inline:16px}.hero-title h1{font-size:clamp(68px,22.5vw,118px);line-height:.72;letter-spacing:.045em}.hero-kicker{margin-bottom:26px;letter-spacing:.16em}.hero-kicker span:first-child{width:30px;height:30px}
  .hero-intro{left:20px;right:20px;top:48%;width:auto}.hero-intro-copy{font-size:clamp(25px,8vw,39px);line-height:1.04}.hero-chapter{margin-bottom:16px}
  /* Variante mobile en px fixes de --scene-text-shadow, meme architecture a deux couches: une
     ombre courte et franche pour le detachement, la large garde le halo. */
  .intro-line{text-shadow:0 2px 6px rgba(3,20,42,.7),0 6px 26px rgba(3,20,42,.5)}
  .biome-story{left:20px;right:58px;bottom:104px;width:auto}.biome-story::before{inset:-56% -52vw -28vh -26vw;background:linear-gradient(to top,rgba(3,27,68,.92) 0,rgba(3,27,68,.88) 28%,rgba(3,27,68,.68) 54%,rgba(3,27,68,.3) 78%,transparent 96%)}.biome-story h2{font-size:clamp(54px,17vw,86px);line-height:.79}.biome-subtitle{margin-top:16px;font-size:16px}.biome-kicker{font-size:9px}
  .biome-story[data-biome=coralligenous] h2{font-size:clamp(43px,13.5vw,68px);line-height:.82;letter-spacing:.04em}
  .hero-wave{left:-22vw;width:144vw;height:40vh}.hero-scroll-cue{bottom:max(24px,env(safe-area-inset-bottom));font-size:9px}
  .hint{bottom:74px;font-size:11px}.dive-guide{bottom:max(16px,env(safe-area-inset-bottom))}
  /* Track au bord droit, pouce: cibles de >= 24px par segment (min-height sur .depth-segment),
     le nom du biome remonte AU-DESSUS de la pastille de profondeur plutot qu'a gauche du track
     ou il deborderait du petit ecran. Les reperes en metres sautent, le track seul suffit. */
  .depth-meter{right:12px;top:48%}.depth-track{height:34vh}.depth-meter output{min-width:52px;font-size:11px}
  .depth-tick{display:none}
  .depth-tip{right:0;top:auto;bottom:calc(100% + 2px);translate:0 0}
  .fish-search{width:calc(100vw - 16px);max-height:calc(100dvh - 16px);border-radius:22px}.fish-search-shell{max-height:calc(100dvh - 20px);padding:22px 16px 16px}.fish-search-header h2{font-size:48px}.fish-search-field{margin-top:22px}.fish-search-results{grid-template-columns:1fr}.fish-search-result{grid-template-columns:94px minmax(0,1fr) auto}.fish-search-result img{width:94px;height:72px}
  .fish-sheet{grid-template-columns:1fr}.photo-card,.photo-card-inner{min-height:42dvh}.photo-card{border-right:0;border-bottom:var(--line)}
  .sheet-copy{height:auto;padding:20px 18px 24px;overflow:visible}.facts{grid-template-columns:1fr}.sheet-copy h2{font-size:clamp(38px,11vw,60px)}
  /* Sur mobile on accepte le defilement vertical, mais jamais un poisson coupe: la carte garde
     son format et l'illustration entiere, et c'est la feuille qui defile. */
  .fish-dialog{height:auto;max-height:calc(100dvh - 16px)}
  /* height:auto ne deborde jamais de soi-meme: sans cette borne, c'est le dialog (overflow:hidden)
     qui clippait la feuille et le bouton recette restait hors ecran, inatteignable. */
  .fish-sheet{grid-template-rows:auto auto;height:auto;max-height:inherit;overflow:auto;-webkit-overflow-scrolling:touch}
  .photo-card,.photo-card-inner{height:auto;min-height:0;aspect-ratio:4/3}
  .sheet-inner{zoom:1}
  /* Le lien recette reste accessible sans avoir a defiler jusqu'au bas de la fiche. Le voile
     evite qu'il flotte sur du texte au milieu du defilement. */
  .recipe-link{position:sticky;z-index:2;bottom:calc(6px + env(safe-area-inset-bottom));margin-top:14px}
  .sheet-copy{padding-bottom:10px}
  .sheet-inner::after{content:'';display:block;height:6px}
}
/* Telephone en paysage (court, tactile, plus large que haut — y compris les 667x375 et 740x360
   qui restent sous 760px de large): la pile verticale du bloc mobile imposerait un ecran et demi
   d'illustration avant le premier mot. On restaure les deux colonnes du desktop, et c'est la
   colonne texte qui defile, avec le meme lien recette sticky que le mobile portrait.
   Place APRES le bloc mobile: a specificite egale, l'ordre des sources doit le faire gagner. */
@media (pointer:coarse) and (max-height:520px) and (orientation:landscape){
  .fish-dialog{height:min(720px,calc(100dvh - 16px))}
  .fish-sheet{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);grid-template-rows:none;height:100%;overflow:hidden}
  .photo-card,.photo-card-inner{aspect-ratio:auto;height:100%;min-height:0}
  .photo-card{border-bottom:0;border-right:var(--line)}
  .sheet-copy{height:100%;padding:18px 20px;overflow:auto;-webkit-overflow-scrolling:touch}
}
@media(prefers-reduced-motion:reduce){
  *{scroll-behavior:auto!important}.loader-fish,.loader-fish-tail,.dive-guide-bubbles i{animation:none!important}.loader.is-done .loader-stage{transform:none}.loader-quip{animation:none}.loader-quip:first-child{opacity:1;transform:none}.photo-card-inner{transition:none}.fish-search,.fish-search-result,.dive-guide,.fish-arrival{transition-duration:.01ms!important}.fish-search-result:hover,.fish-search-result:focus-visible{transform:none}.hero-wave,.hero-bubbles,.waterline{display:none}.hero-title,.hero-intro,.hero-veil,.biome-story{transform:none!important}.hint{display:none}
}

/* ---------- INTRO cinetique ---------- */
/* L'ancien hero reste dans le DOM pour les lecteurs d'ecran (le h1 porte aria-labelledby) mais
   ne se voit plus: c'est la decoupe qui raconte l'arrivee. */
.hero-story,.hero-veil{opacity:0!important;pointer-events:none}

.intro{position:absolute;z-index:6;inset:0;pointer-events:none}
.intro-clip{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
/* Assombrie tant qu'elle sert de remplissage: le mot doit rester une masse franchement plus
   sombre que le rideau, quelle que soit la frame qui passe derriere. L'etalonnage
   (brightness .66, saturate 1.25, contrast 1.06, puis mediums pousses vers le cyan-bleu
   #1a6fd6 pour que les frames de kelp ne lisent plus boue orangee sur le rideau cyan) est
   desormais CUIT dans loop-intro.mp4 (ffmpeg lutrgb/eq/colorbalance, la chaine du filtre CSS
   en sRGB + colorbalance sur les mediums) plutot qu'en filter CSS — le filtre forçait un
   repaint complet de la video a chaque frame. loop.mp4 d'origine et loop-graded.mp4 (grade
   sans poussee cyan) conserves; A/B captures dans /tmp/fish-qa/grade-intro-ab. */
.intro-curtain{position:absolute;inset:0}
/* Lisiere d'ecume qui coiffe le rideau pendant l'arrivee (le rideau monte depuis le bas de
   l'ecran, translate par updateIntro): au repos elle est au-dessus du cadre, donc invisible,
   et sous reduced-motion le rideau ne bouge jamais. */
.intro-curtain::before{content:'';position:absolute;left:-2%;right:-2%;top:-17px;height:17px;pointer-events:none;background:radial-gradient(circle at 12px 15px,var(--bone) 10px,transparent 11px) 0 0/30px 17px repeat-x}
.intro-curtain svg{display:block;width:100%;height:100%}
#intro-hole text,#intro-edge text{font-family:Druk,sans-serif;font-weight:900;text-transform:uppercase}
/* Pose sur le <g>, vector-effect ne descend pas jusqu'aux <text>: l'epaisseur serait alors lue en
   unites de viewBox, donc dix fois trop grosse, et le contour remplirait les lettres. */
#intro-edge text{vector-effect:non-scaling-stroke}
.intro-line{position:absolute;left:50%;top:50%;translate:-50% -50%;margin:0;width:min(940px,84vw);
  text-align:center;font:900 clamp(28px,4.6vw,64px)/1.02 Druk,sans-serif;text-transform:uppercase;
  color:var(--bone);text-shadow:var(--scene-text-shadow)}
.intro-line b{display:inline-block;font-weight:inherit}
.intro.is-done{display:none}
@media (prefers-reduced-motion:reduce){.intro-line b{transform:none!important}}

/* ---------- MANIFESTE ---------- */
/* Ecran plein cadre entre le titre et le premier biome. Opacite et transform sont ecrits a
   chaque frame par updateManifesto() depuis le scroll: jamais une transition ni une @keyframes
   sur ce panneau ou sur une phrase, pour qu'il arrive/se tienne/reparte au scroll et non sur une
   horloge. */
/* Aucun fond: le texte se lit sur le decor de l'estran, deja rendu derriere. Un aplat, meme
   discret, coupe le lecteur du monde dans lequel il vient d'entrer, et c'est precisement ce
   qui a ete refuse deux fois. La lisibilite se joue sur le texte lui-meme PLUS un voile local
   (ci-dessous) qui assombrit doucement la zone du texte sans devenir un panneau. */
.manifesto{position:absolute;z-index:9;inset:0;display:grid;place-items:center;
  padding:9vh clamp(20px,6vw,64px);overflow:hidden;
  opacity:0;pointer-events:none}
/* Le voile: un gradient radial centre sur le bloc de texte qui s'eteint a ~72 % du rayon —
   l'eau reste visible tout autour, seule la zone de lecture s'assombrit. L'opacite est ecrite
   par updateManifesto (meme garde dirty-check que le panneau), donc il suit panelOpacity a la
   frame pres, sans transition ni @keyframes. PAS de mix-blend-mode: .manifesto est un contexte
   d'empilement (z-index), le multiply n'y voyait que du transparent et ne faisait rien; un
   simple voile rgba donne exactement l'assombrissement local voulu. L'ellipse est volontairement
   plus grande que le texte: ce sont les stops (72 %) qui bornent l'assombrissement. */
.manifesto-veil{position:absolute;z-index:0;left:50%;top:50%;translate:-50% -50%;
  width:min(1360px,108vw);height:min(112vh,980px);pointer-events:none;opacity:0;
  background:radial-gradient(50% 50% at 50% 50%,rgba(3,20,42,.38) 0,rgba(3,20,42,.3) 48%,transparent 72%);
  will-change:opacity}
.manifesto-copy{position:relative;z-index:1;display:grid;gap:clamp(20px,3.6vh,36px);
  max-width:min(760px,80vw);color:var(--bone)}
/* La regle partagee --scene-text-shadow (ombre courte + halo large) remplace l'empile de trois
   ombres historique: meme lisibilite sur ecume claire comme sur roche, une seule recette pour
   toute la page. */
.manifesto-copy p{margin:0;font-size:clamp(17px,2.05vw,25px);line-height:1.5;font-weight:650;
  text-wrap:balance;text-shadow:var(--scene-text-shadow)}
.manifesto-copy p:first-child{font-size:clamp(19px,2.5vw,29px);font-weight:800}
/* inline-block, pas inline: meme raison que .intro-line b, un transform par phrase y est fiable
   partout. Une seule phrase par span, jamais un span par mot: la lecture ne doit pas etre
   perturbee par un decoupage trop fin, et douze calques de composition sur un mot a la fois ont
   deja fait perdre un mot par intermittence dans l'intro (voir bible, pieges moteur). */
.manifesto-line{display:inline-block}

/* ---------- OUTRO ---------- */
.outro{position:relative;z-index:8;padding:clamp(48px,8vh,96px) 0 clamp(40px,7vh,72px);overflow:hidden;
  background:radial-gradient(120% 90% at 50% 0,#0a2b4d 0,var(--deep) 62%)}
/* L'eau continue sous la table: les bakes du rideau d'intro (caustiques + grain, deja servis,
   aucun nouvel asset) poses en couches quasi immobiles sur le degrade navy. Overlay a 10 % et
   multiply a 6 %, c'est une presence, pas une texture. Transform uniquement pour la derive. */
.outro-water,.outro-grain{position:absolute;z-index:0;inset:-4%;pointer-events:none}
.outro-water{background-image:image-set(url('fish-world/intro-v1/caustics-390.webp') 1x,url('fish-world/intro-v1/caustics-1512.webp') 2x);
  background-size:cover;mix-blend-mode:overlay;opacity:.1;animation:outro-drift 36s ease-in-out infinite alternate;will-change:transform}
.outro-grain{background-image:image-set(url('fish-world/intro-v1/grain-390.webp') 1x,url('fish-world/intro-v1/grain-1512.webp') 2x);
  background-size:cover;mix-blend-mode:multiply;opacity:.06}
@keyframes outro-drift{from{transform:translate3d(-1.4%,0,0) scale(1.04)}to{transform:translate3d(1.4%,-1.8%,0) scale(1.09)}}
@media (prefers-reduced-motion:reduce){.outro-water,.outro-grain{animation:none}}
.outro-wrap,.outro-rail-wrap{position:relative;z-index:1}
.outro-wrap{max-width:1400px;margin:0 auto;padding:0 clamp(20px,4vw,40px)}
.outro-head{display:flex;align-items:center;justify-content:space-between;gap:18px;margin:0 0 18px}
.outro-kicker{display:flex;gap:14px;align-items:center;margin:0;color:var(--lime);
  font-size:11px;font-weight:950;letter-spacing:.16em;text-transform:uppercase}
.outro-kicker i{width:34px;height:2px;background:var(--lime)}
/* Position dans le rail, "1 / 12": petit, discret, mis a jour au scroll du rail (throttle rAF
   cote JS). */
.outro-count{flex:none;padding:5px 11px;border:2px solid currentColor;border-radius:999px;color:var(--bone);
  font-size:11px;font-weight:900;letter-spacing:.1em}
.outro-title{margin:0;font:900 clamp(46px,7.4vw,116px)/.82 Druk,sans-serif;letter-spacing:.04em;
  text-transform:uppercase;color:var(--bone)}
.outro-title em{font-style:normal;color:var(--lime)}
.outro-lead{max-width:52ch;margin:22px 0 0;font-size:clamp(16px,1.3vw,19px);font-weight:650;
  line-height:1.35;color:#cfe0f2}
/* Le fondu du bord droit annonce qu'il reste des cartes a voir, desktop comme mobile. */
.outro-rail{display:flex;gap:22px;margin:44px 0 0;padding:6px clamp(20px,4vw,40px) 26px;
  overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;
  -webkit-mask-image:linear-gradient(to right,#000 84%,transparent 98%);
  mask-image:linear-gradient(to right,#000 84%,transparent 98%)}
.outro-rail::-webkit-scrollbar{display:none}
/* Fleches papier, meme vocabulaire que .tools button (cerne encre, fond bone, ombre dure,
   lift au survol). Centrees sur le rail, masquees en debut/fin de course par le JS et sur
   mobile ou le swipe nu suffit. */
.outro-nav{position:absolute;z-index:3;top:50%;translate:0 -50%;display:grid;place-items:center;
  width:52px;height:52px;padding:0;border:var(--line);border-radius:50%;background:var(--bone);
  box-shadow:4px 4px 0 var(--ink);color:var(--ink);font-size:23px;font-weight:950;line-height:1;
  cursor:pointer;transition:translate .14s cubic-bezier(.32,.72,0,1),background .14s}
.outro-nav:hover,.outro-nav:focus-visible{background:var(--lime);translate:0 calc(-50% - 3px)}
.outro-nav[hidden]{display:none}
.outro-nav.prev{left:10px}.outro-nav.next{right:10px}
@media (max-width:760px),(pointer:coarse) and (max-height:520px){.outro-nav{display:none}}
.dish{display:flex;flex-direction:column;flex:0 0 320px;height:420px;scroll-snap-align:center;
  border:var(--line);border-radius:22px;background:var(--bone);box-shadow:9px 9px 0 var(--ink);
  overflow:hidden;text-decoration:none;
  /* Portees par les flots. Le decalage est en fraction de periode: toutes les cartes partagent la
     meme houle et y entrent a des moments differents, sinon on voit une file d'animations
     independantes au lieu d'une masse d'eau qui passe. */
  /* PAS d'animation tant que l'entree n'est pas finie. Une animation EN PAUSE continue d'imposer
     la valeur de sa frame courante, et `houle` ecrit transform: elle ecrasait donc le translateY
     de l'entree, qui ne pouvait plus bouger d'un pixel. C'est ce qui faisait arriver l'outro sans
     aucune animation alors que les classes basculaient correctement. Elle demarre a
     `is-entered`, ce qui la garde aussi hors des vingt ecrans de plongee. */
  animation:none}
@keyframes houle{0%,100%{transform:translateY(0) rotate(0)}28%{transform:translateY(-9px) rotate(-.5deg)}62%{transform:translateY(5px) rotate(.42deg)}}
/* On ne lit pas une carte qui bouge. */
.outro-rail:hover .dish,.outro-rail:focus-within .dish{animation-play-state:paused!important}
@media (prefers-reduced-motion:reduce){.dish{animation:none}}
/* Les photos rejoignent la langue illustree: duotone chaud discret (sepia + voile multiply dans
   la figure) et un cadre encre interieur qui reprend le liseré des stickers. Le filtre est
   statique, applique une fois au paint de la carte, jamais anime. */
.dish-photo{position:relative;display:block;overflow:hidden}
.dish-photo::after{content:'';position:absolute;inset:0;background:#c98036;mix-blend-mode:multiply;opacity:.15;pointer-events:none}
.dish-photo::before{content:'';position:absolute;z-index:1;inset:9px;border:2px solid var(--ink);pointer-events:none}
.dish img{display:block;width:100%;height:216px;object-fit:cover;border-bottom:var(--line);
  filter:sepia(.28) saturate(1.05) contrast(1.02)}
.dish .dish-meta{display:flex;flex-direction:column;flex:1;padding:14px 16px 16px}
.dish .dish-depth{align-self:flex-start;margin:0 0 10px;padding:4px 9px;border:2px solid var(--ink);
  border-radius:999px;background:var(--lime);color:var(--ink);font-size:10px;font-weight:950;letter-spacing:.08em}
.dish strong{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:3;
  color:var(--ink);font-size:16px;font-weight:900;line-height:1.18}
/* L'espece suit le titre de 12px au lieu d'etre clouee en bas: fini le puits vide entre les
   deux pour les titres courts; l'air restant tombe sous l'espece, en pied de carte. */
.dish span{display:block;margin:12px 0 0;color:#5a564c;font-family:Georgia,serif;
  font-size:12px;font-style:italic}
.outro-foot{display:flex;flex-wrap:wrap;gap:22px;align-items:center;justify-content:space-between;margin-top:14px}
.outro-cta{display:inline-flex;gap:18px;align-items:center;padding:17px 26px;border:var(--line);
  border-radius:999px;background:var(--lime);box-shadow:6px 6px 0 var(--ink);color:var(--ink);
  font-size:clamp(17px,1.4vw,20px);font-weight:950;text-decoration:none}
.outro-note{color:#8fb0cf;font-size:13px;font-weight:750}
@media (max-width:640px){.dish{flex:0 0 268px;height:392px}.dish img{height:184px}}
/* Telephone en paysage: 420px (ou 392) de carte dans ~390px de viewport deborde sous le
   masthead; format reduit pour une carte entierement visible. */
@media (pointer:coarse) and (max-height:520px){.dish{flex:0 0 232px;height:340px}.dish img{height:150px}}

/* Grain de papier sur le rideau. Multiply a faible opacite: on ne change pas les valeurs, on pose
   de la matiere. Le mot reste exactement aussi lisible qu'avant. */
.intro-grain{mix-blend-mode:multiply;opacity:.18}
/* Caustiques. Deux nappes qui derivent a des vitesses et des echelles differentes: c'est le
   decalage entre les deux qui donne le scintillement, pas le mouvement de chacune. Depuis le
   rebake de 2026-08-09 elles couvrent TOUT le rideau, en s'estompant vers le bas (la lumiere
   vient de la surface): voir le commentaire dans fish-world.html et bake.mjs. */
.intro-caustics{mix-blend-mode:screen;opacity:.6}
/* will-change promeut la nappe en couche a part: sans lui le navigateur a encore le droit de
   redessiner le resultat filtre sous un ancetre anime, et le bruit reste recalcule. */
.intro-caustic-a{animation:caustic-a 19s ease-in-out infinite alternate;will-change:transform}
.intro-caustic-b{animation:caustic-b 27s ease-in-out infinite alternate;opacity:.7;will-change:transform}
@keyframes caustic-a{
  from{transform:translate(-9px,0) scale(1)}
  to{transform:translate(12px,-6px) scale(1.08)}
}
@keyframes caustic-b{
  from{transform:translate(9px,-3px) scale(1.12)}
  to{transform:translate(-12px,3px) scale(1.01)}
}
/* Ondulation secondaire, beaucoup plus lente: le meme bake, retourne horizontalement et plus
   grand, en overlay tres faible. Elle desynchronise les nappes screen sans ajouter d'asset. */
.intro-swell{mix-blend-mode:overlay;opacity:.3;transform-box:view-box;transform-origin:50% 50%;animation:caustic-swell 43s ease-in-out infinite alternate}
@keyframes caustic-swell{
  from{transform:translate(6px,2px) scale(-1.18,1.18)}
  to{transform:translate(-7px,-3px) scale(-1.26,1.26)}
}
@media (prefers-reduced-motion:reduce){.intro-caustic-a,.intro-caustic-b,.intro-swell{animation:none}}

/* ---------- Entree de l'outro ----------
   Contrat de mouvement. Declencheur: l'outro entre dans le cadre, apres que le dernier biome se
   soit desagrege. Role: continuite (on remonte du noir vers la table) puis emphase sur le CTA.
   Etat initial: tout est en bas et transparent. Etat final: en place, opaque. 620 ms, easing
   sortie douce, decalage de 70 ms par element. Repli reduced-motion: opacite seule, zero
   deplacement. Uniquement transform et opacity, donc rien ne relayoute. Le mouvement est
   reversible par construction puisqu'il est pilote par la visibilite, pas par une horloge. */
/* Une ANIMATION et pas une transition. Une transition a besoin d'un etat de depart deja rendu
   pour interpoler; l'outro n'a jamais ete peinte avant d'entrer dans le cadre, donc le navigateur
   passait directement a l'etat final. Mesure: rien ne bougeait pendant 700 ms, puis tout
   apparaissait d'un coup, sans une seule valeur intermediaire. Une animation part au moment ou
   elle est posee, que l'element ait ete rendu avant ou non. */
@keyframes outro-monte{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}
@keyframes outro-carte{from{opacity:0;transform:translateY(46px) scale(.965)}to{opacity:1;transform:none}}
.outro-kicker,.outro-title,.outro-lead,.outro-count,.outro-foot>*{opacity:0}
.outro.is-visible .outro-kicker,.outro.is-visible .outro-title,
.outro.is-visible .outro-lead,.outro.is-visible .outro-count,.outro.is-visible .outro-foot>*{
  animation:outro-monte .62s cubic-bezier(.22,.75,.2,1) both}
.outro.is-visible .outro-kicker{animation-delay:.02s}
.outro.is-visible .outro-count{animation-delay:.06s}
.outro.is-visible .outro-title{animation-delay:.09s}
.outro.is-visible .outro-lead{animation-delay:.16s}
.outro.is-visible .outro-foot>*{animation-delay:.42s}
/* Les cartes montent en cascade, comme portees par la remontee. Le decalage est indexe sur la
   position dans le rail: c'est une vague qui traverse, pas douze entrees independantes. */
.dish{opacity:0}
.outro.is-visible .dish{animation:outro-carte .62s cubic-bezier(.22,.75,.2,1) both;
  animation-delay:calc(.2s + var(--i) * .07s)}
/* La houle ne reprend qu'une fois l'entree finie, sinon les deux transformations se marchent
   dessus et la carte tremble en arrivant. */
@media (prefers-reduced-motion:reduce){
  .outro-kicker,.outro-title,.outro-lead,.outro-count,.outro-foot>*,.dish{animation-duration:.01ms!important}
}

/* La houle passe APRES les regles d'entree: meme specificite, donc c'est l'ordre source qui
   tranche, et la derniere ecrite gagne. Placee avant, elle ne reprenait jamais la main une fois
   la cascade finie. L'opacite est reaffirmee ici: en remplacant l'animation d'entree, on perd son
   fill-mode, et la carte retombait a l'opacite de base, c'est-a-dire zero. */
.outro.is-entered .dish{opacity:1;animation:houle 7.2s ease-in-out infinite;animation-delay:calc(var(--i) * -.9s)}
.outro-rail:hover .dish,.outro-rail:focus-within .dish{animation-play-state:paused}
