/* ═════════════════════════════════════════════════════════════════════════════
   Firalis — treillis cristal (css/crystal.css), lot S-b de la refonte 0.9.0
   Champs générés par inc/crystal.php (fsa_crystal_field(), [fsa_crystal]),
   glyphes-habits (fsa_crystal_glyph()), substitut « sans photo » (inc/photos.php).
   Mis en file partout par inc/crystal.php ; aussi dans le canevas de l'éditeur.
   Couleurs : variables seulement (--fsa-lat-*, posées par ton ; défaut papier).
   Mouvement (§ 4) : les 8 calques de nœuds d'un palier glissent du bruit à leur
   place, les liaisons paraissent. Sans JS, en mouvement réduit, au doigt, jusqu'à
   760 px et dans l'éditeur : l'image finale.
   API et habits : docs/85-refonte-design/lots/s-b.md.
   ⚠️ Fichier du SOCLE (lot S-b) : gelé pour les agents de page.
   ═════════════════════════════════════════════════════════════════════════════ */

/* Valeurs initiales = image finale (aucune agitation, processus entièrement cristallisé).
   --fsa-lat-n n'est plus animée (passe G : héritée, elle faisait recalculer chaque cercle à
   chaque image) ; gardée pour les feuilles qui la liraient encore. */
@property --fsa-lat-n { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --fsa-lat-p { syntax: '<number>'; inherits: true; initial-value: 1; }

/* ── 1. Couleurs du treillis par ton de fond ─────────────────────────────────
   Défaut (papier) sur :root ; une bande peut poser les siennes (les variables
   s'héritent) ou recevoir une classe .fsa-lat-tone-* ; le champ peut porter
   la sienne (attribut `tone`). --fsa-lat-bg = fond d'un substitut de photo. */
:root,
.fsa-lat-tone-paper {
	--fsa-lat-bg: var(--fsa-paper);
	--fsa-lat-nn: var(--fsa-noise);
	--fsa-lat-no: var(--fsa-brand);
	--fsa-lat-nf: var(--fsa-brand);
	--fsa-lat-nb: var(--fsa-brand-light);
	--fsa-lat-bond: var(--fsa-brand);
	--fsa-lat-cb: var(--fsa-brand-light);
	--fsa-lat-trace: var(--fsa-brand-ink);
	--fsa-lat-gnb: color-mix(in srgb, var(--fsa-brand) 55%, var(--fsa-brand-light));
	--fsa-lat-gcb: color-mix(in srgb, var(--fsa-brand) 55%, var(--fsa-brand-light));
}
.fsa-lat-tone-tint {
	--fsa-lat-bg: var(--fsa-tint);
	--fsa-lat-nn: color-mix(in srgb, var(--fsa-noise) 80%, var(--fsa-brand-ink));
	--fsa-lat-no: var(--fsa-brand);
	--fsa-lat-nf: var(--fsa-brand);
	--fsa-lat-nb: color-mix(in srgb, var(--fsa-brand) 30%, var(--fsa-brand-light));
	--fsa-lat-bond: var(--fsa-brand);
	--fsa-lat-cb: color-mix(in srgb, var(--fsa-brand) 30%, var(--fsa-brand-light));
	--fsa-lat-trace: var(--fsa-brand-ink);
	--fsa-lat-gnb: color-mix(in srgb, var(--fsa-brand) 55%, var(--fsa-brand-light));
	--fsa-lat-gcb: color-mix(in srgb, var(--fsa-brand) 55%, var(--fsa-brand-light));
}
:where(.fsa-on-blue),
.fsa-lat-tone-blue {
	--fsa-lat-bg: var(--fsa-brand);
	--fsa-lat-nn: var(--fsa-paper);
	--fsa-lat-no: var(--fsa-paper);
	--fsa-lat-nf: var(--fsa-paper);
	--fsa-lat-nb: color-mix(in srgb, var(--fsa-brand-light) 60%, var(--fsa-paper));
	--fsa-lat-bond: var(--fsa-paper);
	--fsa-lat-cb: color-mix(in srgb, var(--fsa-brand-light) 60%, var(--fsa-paper));
	--fsa-lat-trace: var(--fsa-paper);
	--fsa-lat-gnb: initial;
	--fsa-lat-gcb: initial;
}
:where(.fsa-on-dark),
.fsa-lat-tone-navy {
	--fsa-lat-bg: var(--fsa-navy);
	--fsa-lat-nn: var(--fsa-on-dark-muted);
	--fsa-lat-no: var(--fsa-brand-light);
	--fsa-lat-nf: var(--fsa-paper);
	--fsa-lat-nb: var(--fsa-brand);
	--fsa-lat-bond: var(--fsa-brand-light);
	--fsa-lat-cb: var(--fsa-brand);
	--fsa-lat-trace: var(--fsa-paper);
	--fsa-lat-gnb: initial;
	--fsa-lat-gcb: initial;
}

/* ── 2. Le champ : fond absolu de son conteneur (contrat S-a ↔ S-b § 2) ──────
   Le conteneur devient le repère et isole l'empilement (spécificité nulle :
   toute règle du conteneur l'emporte) ; le champ passe sous le texte. */
:where(:has(> .fsa-field)) { position: relative; isolation: isolate; }
.fsa-field {
	position: absolute;
	inset: 0;
	z-index: -1;
	overflow: hidden;
	pointer-events: none;
	container: fsa-field / size;
}
.fsa-lat { display: block; width: 100%; height: 100%; overflow: hidden; }

/* Agitation du PROCESSUS collant (seuils --t, § 4) : vecteurs pseudo-aléatoires par
   cycles premiers (nth-child, aucune donnée par nœud), amplitude par classe d'ordre
   (--a) et par champ (--fsa-lat-amp = pas × agitation, posé sur le <svg>). Les autres
   champs bougent par calques (§ 4) : ces règles ne visent plus leurs cercles. */
.fsa-lat--process .lat-n circle { --x5: 0; --x7: 0; --x11: 0; --y3: 0; --y13: 0; --y17: 0; --x: calc(var(--x5) + var(--x7) + var(--x11)); --y: calc(var(--y3) + var(--y13) + var(--y17)); }
.fsa-lat--process .lat-n circle:nth-child(5n+1) { --x5: .5; } .fsa-lat--process .lat-n circle:nth-child(5n+2) { --x5: -.8; } .fsa-lat--process .lat-n circle:nth-child(5n+3) { --x5: .15; } .fsa-lat--process .lat-n circle:nth-child(5n+4) { --x5: -.35; } .fsa-lat--process .lat-n circle:nth-child(5n+5) { --x5: .7; }
.fsa-lat--process .lat-n circle:nth-child(7n+1) { --x7: -.45; } .fsa-lat--process .lat-n circle:nth-child(7n+2) { --x7: .3; } .fsa-lat--process .lat-n circle:nth-child(7n+3) { --x7: .75; } .fsa-lat--process .lat-n circle:nth-child(7n+4) { --x7: -.7; } .fsa-lat--process .lat-n circle:nth-child(7n+5) { --x7: .1; } .fsa-lat--process .lat-n circle:nth-child(7n+6) { --x7: -.25; } .fsa-lat--process .lat-n circle:nth-child(7n+7) { --x7: .55; }
.fsa-lat--process .lat-n circle:nth-child(11n+1) { --x11: .2; } .fsa-lat--process .lat-n circle:nth-child(11n+2) { --x11: -.4; } .fsa-lat--process .lat-n circle:nth-child(11n+3) { --x11: .35; } .fsa-lat--process .lat-n circle:nth-child(11n+4) { --x11: -.1; } .fsa-lat--process .lat-n circle:nth-child(11n+5) { --x11: .45; } .fsa-lat--process .lat-n circle:nth-child(11n+6) { --x11: -.3; }
.fsa-lat--process .lat-n circle:nth-child(11n+7) { --x11: .05; } .fsa-lat--process .lat-n circle:nth-child(11n+8) { --x11: .4; } .fsa-lat--process .lat-n circle:nth-child(11n+9) { --x11: -.5; } .fsa-lat--process .lat-n circle:nth-child(11n+10) { --x11: .25; } .fsa-lat--process .lat-n circle:nth-child(11n+11) { --x11: -.15; }
.fsa-lat--process .lat-n circle:nth-child(3n+1) { --y3: .4; } .fsa-lat--process .lat-n circle:nth-child(3n+2) { --y3: -.6; } .fsa-lat--process .lat-n circle:nth-child(3n+3) { --y3: .2; }
.fsa-lat--process .lat-n circle:nth-child(13n+1) { --y13: -.5; } .fsa-lat--process .lat-n circle:nth-child(13n+2) { --y13: .3; } .fsa-lat--process .lat-n circle:nth-child(13n+3) { --y13: .6; } .fsa-lat--process .lat-n circle:nth-child(13n+4) { --y13: -.2; } .fsa-lat--process .lat-n circle:nth-child(13n+5) { --y13: .1; } .fsa-lat--process .lat-n circle:nth-child(13n+6) { --y13: -.65; } .fsa-lat--process .lat-n circle:nth-child(13n+7) { --y13: .45; }
.fsa-lat--process .lat-n circle:nth-child(13n+8) { --y13: -.35; } .fsa-lat--process .lat-n circle:nth-child(13n+9) { --y13: .25; } .fsa-lat--process .lat-n circle:nth-child(13n+10) { --y13: -.05; } .fsa-lat--process .lat-n circle:nth-child(13n+11) { --y13: .5; } .fsa-lat--process .lat-n circle:nth-child(13n+12) { --y13: -.4; } .fsa-lat--process .lat-n circle:nth-child(13n+13) { --y13: .15; }
.fsa-lat--process .lat-n circle:nth-child(17n+1) { --y17: .3; } .fsa-lat--process .lat-n circle:nth-child(17n+2) { --y17: -.25; } .fsa-lat--process .lat-n circle:nth-child(17n+3) { --y17: .45; } .fsa-lat--process .lat-n circle:nth-child(17n+4) { --y17: -.5; } .fsa-lat--process .lat-n circle:nth-child(17n+5) { --y17: .1; } .fsa-lat--process .lat-n circle:nth-child(17n+6) { --y17: .35; }
.fsa-lat--process .lat-n circle:nth-child(17n+7) { --y17: -.15; } .fsa-lat--process .lat-n circle:nth-child(17n+8) { --y17: .55; } .fsa-lat--process .lat-n circle:nth-child(17n+9) { --y17: -.4; } .fsa-lat--process .lat-n circle:nth-child(17n+10) { --y17: .05; } .fsa-lat--process .lat-n circle:nth-child(17n+11) { --y17: -.3; } .fsa-lat--process .lat-n circle:nth-child(17n+12) { --y17: .2; }
.fsa-lat--process .lat-n circle:nth-child(17n+13) { --y17: -.55; } .fsa-lat--process .lat-n circle:nth-child(17n+14) { --y17: .4; } .fsa-lat--process .lat-n circle:nth-child(17n+15) { --y17: -.1; } .fsa-lat--process .lat-n circle:nth-child(17n+16) { --y17: .25; } .fsa-lat--process .lat-n circle:nth-child(17n+17) { --y17: -.35; }

/* Nœuds : bruit (nn), ordre (no), cristal plein (nf), cristal clair (nb). */
.fsa-lat .nn { --a: 1.3; fill: var(--fsa-lat-nn); }
.fsa-lat .no { --a: .8; fill: var(--fsa-lat-no); }
.fsa-lat .nf { --a: .55; fill: var(--fsa-lat-nf); }
.fsa-lat .nb { --a: .55; fill: var(--fsa-lat-nb); }
.fsa-lat .o1 { opacity: .2; } .fsa-lat .o2 { opacity: .32; } .fsa-lat .o3 { opacity: .48; } .fsa-lat .o4 { opacity: .66; } .fsa-lat .o5 { opacity: .86; }
.fsa-lat .t0 { --t: .06; } .fsa-lat .t1 { --t: .19; } .fsa-lat .t2 { --t: .31; } .fsa-lat .t3 { --t: .44; } .fsa-lat .t4 { --t: .56; } .fsa-lat .t5 { --t: .69; } .fsa-lat .t6 { --t: .81; } .fsa-lat .t7 { --t: .94; }

/* Liaisons : plans réticulaires (p1–p3), cristal (cf, cb) ; 1 px à toute échelle. */
.fsa-lat .lat-b path { fill: none; stroke: var(--fsa-lat-bond); stroke-width: 1; vector-effect: non-scaling-stroke; }
.fsa-lat .lat-b .p1 { opacity: .13; } .fsa-lat .lat-b .p2 { opacity: .22; } .fsa-lat .lat-b .p3 { opacity: .34; }
.fsa-lat .lat-b .cf { opacity: .62; } .fsa-lat .lat-b .cb { stroke: var(--fsa-lat-cb); opacity: .8; }
/* Bloc d'aplat (ouverture Catalogue, bloc en débord) : les liaisons du cristal à pleine valeur
   (passe G, audit design P2-4 : sans elles, plaque, hexagone et colonnes se lisaient comme un
   même nuage de points). Le groupe porte stroke-opacity .3 (repli sans feuille). */
.fsa-lat--block .lat-b :is(.cf, .cb) { stroke-opacity: 1; }
/* Chemin de traçabilité (habit « trace », Quality). */
.fsa-lat .lat-t .tr { fill: none; stroke: var(--fsa-lat-trace); stroke-width: 2; vector-effect: non-scaling-stroke; }
.fsa-lat .lat-t .trn { fill: var(--fsa-lat-trace); stroke: none; }

/* ── 3. Lisibilité du texte posé dessus ───────────────────────────────────────
   Par construction, le côté du texte est du bruit (petits nœuds pâles, aucune
   liaison). Quand le cadre devient étroit (téléphone, colonne), le recadrage
   « slice » amène la structure sous le texte : un voile la ramène à 22 % sur la
   moitié haute (le texte), pleine en bas. */
@container fsa-field (max-aspect-ratio: 13 / 10) {
	.fsa-lat--under { -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / .22) 0, rgb(0 0 0 / .22) 50%, #000 88%); mask-image: linear-gradient(to bottom, rgb(0 0 0 / .22) 0, rgb(0 0 0 / .22) 50%, #000 88%); }
	/* Moment et panneau posés sous un texte, en cadre étroit (passe G, audit a11y P2-4 : un gros
	   nœud passait sous un lien bleu clair du panneau navy de Biobank) : voilés aux deux tiers. */
	.fsa-lat--moment,
	.fsa-lat--panel { -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / .35) 0, rgb(0 0 0 / .35) 60%, #000 92%); mask-image: linear-gradient(to bottom, rgb(0 0 0 / .35) 0, rgb(0 0 0 / .35) 60%, #000 92%); }
}
/* Hero éditorial (« fantôme ») : tiers droit, le bord gauche s'efface. */
.fsa-lat--ghost { -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 55%); mask-image: linear-gradient(90deg, transparent 0, #000 55%); }
.fsa-field--ghost { opacity: .7; }

/* ── 4. Mouvement : « le bruit se résout en structure » ───────────────────────
   Au chargement (heros) : porte « fsa-lat-js » posée en tête de page par
   inc/crystal.php. Au défilement : js/crystal.js arme (is-armed) les champs
   encore sous l'écran, puis les résout (is-resolving) quand ils entrent — seul
   le champ visible s'anime, une fois.
   Passe G (13/11, audit a11y P1-1) : chaque palier de nœuds est rangé en 8
   calques <g class="ld dK"> (inc/crystal.php) ; on anime le `translate` de ces
   calques (vecteur --dx/--dy × --a du palier × --fsa-lat-amp) et l'opacité des
   liaisons — une poignée d'éléments par champ, au lieu d'une variable héritée
   qui faisait recalculer le style de chaque cercle à chaque image (accueil au
   bureau : 2,5 s → 0,25 s de recalcul au chargement). Les voisins tombent dans
   des calques différents : le bruit se lit toujours comme un désordre. */
.fsa-lat .d0 { --dx: .9; --dy: -.4; }
.fsa-lat .d1 { --dx: -.7; --dy: .8; }
.fsa-lat .d2 { --dx: .3; --dy: 1.1; }
.fsa-lat .d3 { --dx: -1.2; --dy: -.2; }
.fsa-lat .d4 { --dx: 1.1; --dy: .6; }
.fsa-lat .d5 { --dx: -.4; --dy: -1; }
.fsa-lat .d6 { --dx: .6; --dy: -1.2; }
.fsa-lat .d7 { --dx: -.9; --dy: .35; }
@keyframes fsa-lat-resolve {
	from { translate: calc(var(--dx, 0) * var(--a, 1) * var(--fsa-lat-amp, 0) * 1px) calc(var(--dy, 0) * var(--a, 1) * var(--fsa-lat-amp, 0) * 1px); }
	to { translate: 0 0; }
}
@keyframes fsa-lat-bonds { from { opacity: 0; } to { opacity: 1; } }
.fsa-lat.is-armed .ld { translate: calc(var(--dx, 0) * var(--a, 1) * var(--fsa-lat-amp, 0) * 1px) calc(var(--dy, 0) * var(--a, 1) * var(--fsa-lat-amp, 0) * 1px); }
.fsa-lat.is-armed :is(.lat-b, .lat-t) { opacity: 0; }
.fsa-lat-js .fsa-lat--load .ld { animation: fsa-lat-resolve 2.8s cubic-bezier(.16, 1, .3, 1) .15s both; }
.fsa-lat-js .fsa-lat--load :is(.lat-b, .lat-t) { animation: fsa-lat-bonds 2.4s cubic-bezier(.16, 1, .3, 1) .4s both; }
.fsa-lat.is-resolving .ld { animation: fsa-lat-resolve 2.2s cubic-bezier(.16, 1, .3, 1) both; }
.fsa-lat.is-resolving :is(.lat-b, .lat-t) { animation: fsa-lat-bonds 2s cubic-bezier(.16, 1, .3, 1) .2s both; }

/* Processus collant : chaque nœud porte un seuil (--t) ; le panneau pose
   --fsa-p (0 → 1, étape active : js/site.js, lot S-a) et cristallise jusqu'à ce
   seuil. Sans valeur posée : 1, entièrement cristallisé. */
.fsa-lat--process { --fsa-lat-p: var(--fsa-p, 1); }
.fsa-lat--process .lat-n circle { --f: clamp(0, (var(--t, 0) - var(--fsa-lat-p)) * 5, 1); transform: translate(calc(var(--x) * var(--a, 1) * var(--fsa-lat-amp, 0) * var(--f) * 1px), calc(var(--y) * var(--a, 1) * var(--fsa-lat-amp, 0) * var(--f) * 1px)); fill-opacity: calc(1 - var(--f) * .6); }
.fsa-lat--process .lat-b { opacity: 1; }
.fsa-lat--process .lat-b path { opacity: .3; }
.fsa-lat--process .lat-b .s0 { opacity: calc(clamp(0, (var(--fsa-lat-p) - .2) * 8, 1) * .32); }
.fsa-lat--process .lat-b .s1 { opacity: calc(clamp(0, (var(--fsa-lat-p) - .45) * 8, 1) * .32); }
.fsa-lat--process .lat-b .s2 { opacity: calc(clamp(0, (var(--fsa-lat-p) - .7) * 8, 1) * .32); }
.fsa-lat--process .lat-b .s3 { opacity: calc(clamp(0, (var(--fsa-lat-p) - .95) * 12, 1) * .32); }
.fsa-lat--process .lat-b .cf { opacity: calc(clamp(0, (var(--fsa-lat-p) - .95) * 12, 1) * .7); }

/* ── 5. Glyphes-habits (méga-menu, portes, rôles, sites) ─────────────────────
   Passe G : sur un fond clair, la part « claire » de l'habit (nœuds nb, liaisons cb) prend
   --fsa-lat-gnb / -gcb, un bleu moyen posé par le ton (papier, teinte) — en bleu clair pur, à
   44 px, elle se lisait pâle (« link », « sheet ») ; sur bleu et navy : la valeur du ton. */
.fsa-glyph { display: block; flex: none; width: 44px; height: 44px; overflow: visible; }

/* ── 6. Substitut « sans photo » : l'habit de la page au rapport de l'emplacement
   (spécificité nulle : le cadrage de la page — place, rapport — l'emporte). */
:where(.fsa-photo--habit) { position: relative; overflow: hidden; aspect-ratio: var(--fsa-habit-ratio, 4 / 3); background: var(--fsa-lat-bg); }
:where(.fsa-photo--habit) > .fsa-lat { position: absolute; inset: 0; }
/* Posé sur un fond du MÊME ton (panneau navy, hero bleu) : plus de rectangle de points à bords
   francs — le substitut se fond dans son fond (passe G, audit design P2-3). */
:is(.fsa-on-dark, .fsa-t-navy, .fsa-hero-inner--navy, .fsa-sec--navy, .fsa-panel) .fsa-photo--habit.fsa-lat-tone-navy,
:is(.fsa-on-blue, .fsa-t-blue, .fsa-cta) .fsa-photo--habit.fsa-lat-tone-blue { background: transparent; }
:is(.fsa-on-dark, .fsa-t-navy, .fsa-hero-inner--navy, .fsa-sec--navy, .fsa-panel) .fsa-photo--habit.fsa-lat-tone-navy > .fsa-lat,
:is(.fsa-on-blue, .fsa-t-blue, .fsa-cta) .fsa-photo--habit.fsa-lat-tone-blue > .fsa-lat {
	-webkit-mask-image: radial-gradient(75% 70% at 60% 50%, #000 55%, transparent);
	mask-image: radial-gradient(75% 70% at 60% 50%, #000 55%, transparent);
}

/* ── 7. Mouvement réduit, au doigt, téléphone : image finale, aucune agitation ──
   Passe G (13/11, audit a11y P1-1, arbitrage 8) : au doigt et jusqu'à 760 px, la
   résolution saturait le fil principal (2,2 s de recalcul au défilement à 390 px,
   CPU × 4) ; l'image finale y est montrée d'emblée, comme en mouvement réduit
   (inc/crystal.php ne pose pas la porte, js/crystal.js n'arme rien). */
@media (prefers-reduced-motion: reduce), (pointer: coarse), (max-width: 760px) {
	.fsa-lat .ld,
	.fsa-lat :is(.lat-b, .lat-t) { animation: none !important; }
	.fsa-lat.is-armed .ld { translate: none !important; }
	.fsa-lat.is-armed :is(.lat-b, .lat-t) { opacity: 1 !important; }
}
@media (prefers-reduced-motion: reduce) {
	.fsa-lat--process { --fsa-lat-p: 1 !important; }
}
