/* ============================================================================
   Portail AVE — feuille de style PARTAGÉE (v2 — chantier design 2026-08)
   ============================================================================

   Deux pages la chargent : « Mes outils » (index.html) et la console
   d'attribution (console.html). Les CONTRATS de la v1 sont conservés à
   l'identique — mêmes noms de variables (:root), mêmes classes de socle
   (header, .marque, .moi, .lien, .message, .erreur, footer) : la console
   continue de fonctionner sans modification.

   Ce qui change : la matière. Fond cosmique en couches (nébuleuses, deux
   champs d'étoiles, éclats), planètes à quatre couches de lumière + grain,
   soleil AVE au monogramme, panneaux vitrés sous la scène. Le niveau visé est
   celui des maquettes du 02/08/2026 — SANS quitter les règles du Hub :

   1. AUCUNE dépendance externe. Les polices Orbitron/Montserrat sont
      AUTO-HÉBERGÉES (fichiers .woff2 servis par le Hub lui-même, licence OFL
      jointe au chantier). La CSP impose `font-src 'self'` : un data-URI de
      police serait BLOQUÉ — les fichiers sont donc indispensables, et la pile
      de repli (`Segoe UI`) garde la page lisible tant que `portail_web.py`
      ne sert pas les .woff2 (correctif proposé dans le plan du chantier).
   2. Les textures (bruit, éclats d'étoiles) sont des SVG en data-URI :
      autorisés par `img-src 'self' data:`, zéro requête.
   3. AUCUNE 3D — leçon de la v1 conservée : les rotations ne commutent pas,
      la perspective vient de la FORME des orbites (ellipses aplaties) et
      `offset-rotate: 0deg` garde les étiquettes droites.

   Servie par la route générique de `api/portail_web.py`, avec `no-store`.
   ============================================================================ */

/* ── Polices auto-hébergées ──────────────────────────────────────────────────
   `font-display: swap` : le texte s'affiche immédiatement en repli système,
   puis bascule — jamais de page muette le temps d'un téléchargement. Les
   graisses non déclarées ici ne doivent pas être employées : le navigateur
   les synthétiserait (faux-gras), ce qui se voit. */
@font-face{font-family:"Orbitron";font-style:normal;font-weight:500;
  font-display:swap;src:url("orbitron-500.woff2") format("woff2")}
@font-face{font-family:"Orbitron";font-style:normal;font-weight:600;
  font-display:swap;src:url("orbitron-600.woff2") format("woff2")}
@font-face{font-family:"Orbitron";font-style:normal;font-weight:700;
  font-display:swap;src:url("orbitron-700.woff2") format("woff2")}
@font-face{font-family:"Orbitron";font-style:normal;font-weight:800;
  font-display:swap;src:url("orbitron-800.woff2") format("woff2")}
@font-face{font-family:"Montserrat";font-style:normal;font-weight:400;
  font-display:swap;src:url("montserrat-400.woff2") format("woff2")}
@font-face{font-family:"Montserrat";font-style:normal;font-weight:500;
  font-display:swap;src:url("montserrat-500.woff2") format("woff2")}
@font-face{font-family:"Montserrat";font-style:normal;font-weight:600;
  font-display:swap;src:url("montserrat-600.woff2") format("woff2")}
@font-face{font-family:"Montserrat";font-style:normal;font-weight:700;
  font-display:swap;src:url("montserrat-700.woff2") format("woff2")}

/* ── Jetons ──────────────────────────────────────────────────────────────────
   Les variables v1 gardent leur NOM et leur SENS (la console les consomme).
   Les ajouts v2 sont suffixés ou nouveaux — jamais de redéfinition d'usage. */
:root{
  --nuit:#0a0f2c; --nuit-clair:#131a3d; --accent:#6c63ff; --primaire:#3f51b5;
  --texte:#e8eaf6; --doux:#a8adc9; --bordure:rgba(108,99,255,.22);
  --titre:"Orbitron","Segoe UI",system-ui,sans-serif;
  --corps:"Montserrat","Segoe UI",system-ui,sans-serif;
  /* v2 */
  --abysse:#060a1f;                 /* le fond derrière le fond */
  --accent-2:#3e5cf5;               /* borne froide du dégradé signature */
  --faible:#7b83a8;
  --carte:rgba(16,22,52,.62);       /* panneaux vitrés */
  --degrade:linear-gradient(90deg,var(--accent-2),#7a5cff);
}

/* Bruit de surface des planètes : feTurbulence, dalle 120px, alpha cuit dans
   le SVG (≈.16). En data-URI — `img-src data:` l'autorise, zéro requête. */
:root{
  --grain:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.16'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
  /* Variante appuyée du bruit, pour la SURFACE des planètes : à 86 px de
     diamètre, l'alpha .16 fondu en `overlay` disparaissait — constaté sur la
     capture 01 du chantier, sphères « plastique lisse ». */
  --grain-fort:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.34'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='90' height='90' filter='url(%23n)'/%3E%3C/svg%3E");
  /* Éclat d'étoile à 4 branches, pour les 3-4 astres brillants du fond. */
  --eclat:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 0 L13.4 10.6 L24 12 L13.4 13.4 L12 24 L10.6 13.4 L0 12 L10.6 10.6 Z' fill='%23eaecf6'/%3E%3C/svg%3E");
  /* Monogramme AVE (le chevron du « A ») — même tracé et même dégradé que le
     thème Keycloak et le « Powered by ᐱVE » des briques : UNE seule marque. */
  --chevron:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 42'%3E%3ClinearGradient id='a' x1='0' y1='1' x2='1' y2='0'%3E%3Cstop offset='0' stop-color='%233E5CF5'/%3E%3Cstop offset='1' stop-color='%237A5CFF'/%3E%3C/linearGradient%3E%3Cpath d='M0,42 L22,4 C26,-3 34,-3 38,4 L60,42 L49,42 L30,13 L11,42 Z' fill='url(%23a)'/%3E%3C/svg%3E");
  /* Variante nuit du chevron, posée SUR le cœur lumineux du soleil. */
  --chevron-nuit:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 42'%3E%3Cpath d='M0,42 L22,4 C26,-3 34,-3 38,4 L60,42 L49,42 L30,13 L11,42 Z' fill='%230a0f2c'/%3E%3C/svg%3E");
}

*{box-sizing:border-box}
html,body{margin:0;min-height:100%}

/* ── Le fond : l'espace, en couches ─────────────────────────────────────────
   Trois plans fixes, du plus lointain au plus proche :
     1. `body`         — abysse + nébuleuses (violet 245°, indigo 228°, une
                          pointe pourpre 288° pour la profondeur) + vignette
     2. `body::before` — champ d'étoiles fin (2 dalles, 2 densités)
     3. `body::after`  — les astres BRILLANTS, rares, avec éclat à 4 branches
   Deux champs d'étoiles se lisent comme une profondeur ; un seul, comme une
   texture. Les couches sont `position:fixed` : l'espace ne défile pas avec la
   page — c'est le contenu qui flotte devant lui. */
body{
  background:var(--abysse);
  background-image:
    radial-gradient(58rem 40rem at 18% -12%,rgba(108,99,255,.24),transparent 62%),
    radial-gradient(50rem 36rem at 92% 112%,rgba(62,92,245,.2),transparent 62%),
    radial-gradient(34rem 26rem at 78% 12%,rgba(150,84,255,.12),transparent 60%),
    radial-gradient(40rem 30rem at 6% 88%,rgba(90,84,255,.12),transparent 60%),
    radial-gradient(120rem 80rem at 50% 50%,transparent 55%,rgba(2,4,14,.55) 100%);
  background-attachment:fixed;
  color:var(--texte);font-family:var(--corps);
  display:flex;flex-direction:column;
  position:relative;
}
body::before,body::after{
  content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
  background-repeat:repeat;
}
body::before{
  background-image:
    radial-gradient(1px 1px at 12% 18%,rgba(232,234,246,.85),transparent),
    radial-gradient(1px 1px at 68% 8%,rgba(232,234,246,.6),transparent),
    radial-gradient(1.5px 1.5px at 84% 62%,rgba(232,234,246,.8),transparent),
    radial-gradient(1px 1px at 26% 78%,rgba(232,234,246,.5),transparent),
    radial-gradient(1px 1px at 47% 42%,rgba(232,234,246,.45),transparent),
    radial-gradient(1.5px 1.5px at 92% 30%,rgba(200,204,255,.65),transparent),
    radial-gradient(1px 1px at 7% 55%,rgba(232,234,246,.55),transparent),
    radial-gradient(1px 1px at 58% 88%,rgba(232,234,246,.4),transparent);
  background-size:340px 340px;
  animation:scintille 11s ease-in-out infinite;
}
body::after{
  background-image:
    var(--eclat),var(--eclat),var(--eclat),
    radial-gradient(2px 2px at 34% 24%,rgba(122,92,255,.55),transparent),
    radial-gradient(2.5px 2.5px at 76% 84%,rgba(62,92,245,.5),transparent),
    radial-gradient(2px 2px at 8% 66%,rgba(122,92,255,.4),transparent);
  background-position:13% 22%,71% 64%,88% 12%,0 0,0 0,0 0;
  background-size:14px 14px,10px 10px,18px 18px,640px 640px,640px 640px,640px 640px;
  background-repeat:no-repeat,no-repeat,no-repeat,repeat,repeat,repeat;
  opacity:.8;
  animation:scintille 17s ease-in-out infinite reverse;
}
@keyframes scintille{0%,100%{opacity:.65}50%{opacity:1}}

/* Le contenu passe DEVANT les couches fixes. */
header,main,footer{position:relative;z-index:1}

/* ── En-tête ────────────────────────────────────────────────────────────── */
header{
  display:flex;align-items:center;justify-content:space-between;gap:1rem;
  padding:1.1rem 2rem;
  border-bottom:1px solid rgba(108,99,255,.16);
  background:linear-gradient(180deg,rgba(6,10,31,.55),rgba(6,10,31,.15));
  backdrop-filter:blur(8px);
}
.marque{font-family:var(--titre);font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;font-size:1.02rem;
  text-shadow:0 0 24px rgba(108,99,255,.45);
  display:flex;align-items:center;gap:.55em}
/* ── Le logotype : le chevron EST le A ──────────────────────────────────────
   Règle de marque (rappel d'Alex, 02/08/2026) : dans « ᐱVE », le chevron
   REMPLACE la lettre A — il ne se pose jamais à côté du mot. La lettre reste
   dans le DOM (lecteurs d'écran et copier-coller lisent bien « AVE ») : elle
   est masquée par `text-indent` et le chevron la recouvre en fond. */
.A{display:inline-block;width:1.04em;height:.74em;flex:none;
  background:var(--chevron) center/contain no-repeat;
  color:transparent;overflow:hidden;text-indent:200%;white-space:nowrap;
  vertical-align:baseline}
/* Dans la marque, l'espacement du chevron suit l'interlettrage du titre. */
.marque .A{margin-right:.14em}
/* L'accent ne colore que la DEVISE (dernier span) — jamais le logotype.
   `span:last-child` et non `span` : la console (« AVE · <span>Console
   d'attribution</span> ») garde ainsi son accent sans modification. */
.marque span:last-child{color:var(--accent)}
.moi{display:flex;align-items:center;gap:.9rem;font-size:.9rem;color:var(--doux)}
/* `display:flex` ci-dessus l'emporte sur l'attribut `hidden` du HTML :
   sans cette règle, le bouton « Se déconnecter » restait visible AVANT
   toute connexion. Constaté le 29/07/2026 sur une copie d'écran. */
.moi[hidden]{display:none}
/* Le courriel devient une pastille d'identité : lisible, délimitée, et le
   texte long est tronqué proprement au lieu de pousser les boutons. */
#courriel{
  max-width:26ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  padding:.38rem .8rem;border:1px solid var(--bordure);border-radius:999px;
  background:rgba(16,22,52,.5);font-size:.82rem;
}
/* `.lien` et non `button.lien` : le lien vers la console est un `<a>`.
   Un sélecteur restreint à `button` l'aurait laissé sans style. */
.lien{background:none;border:1px solid var(--bordure);color:var(--doux);
  border-radius:9px;padding:.42rem .85rem;cursor:pointer;font:inherit;
  font-size:.84rem;text-decoration:none;display:inline-block;
  transition:border-color .2s ease,color .2s ease,box-shadow .2s ease}
.lien:hover{border-color:var(--accent);color:var(--texte);
  box-shadow:0 0 0 1px rgba(108,99,255,.25),0 6px 18px rgba(108,99,255,.18)}
/* `display:inline-block` ci-dessus l'emporterait sur l'attribut `hidden` :
   même piège que `.moi[hidden]`, déjà tombé une fois. */
.lien[hidden]{display:none}

main{flex:1;display:flex;flex-direction:column;align-items:center;
  justify-content:flex-start;padding:1.6rem 1rem 2.4rem;gap:.4rem}
/* Titre et sous-titre passent DEVANT les satellites (z 2/4) : une planète de
   l'arc arrière peut croiser la ligne du sous-titre (vu sur la capture 10) —
   c'est alors le TEXTE qui doit gagner, pas l'astre. Idem pour les panneaux. */
h1{font-family:var(--titre);font-weight:700;letter-spacing:.05em;
  font-size:clamp(1.35rem,1.1rem + 1vw,1.8rem);margin:.4rem 0 .2rem;
  text-shadow:0 0 34px rgba(108,99,255,.5);
  position:relative;z-index:5}
.sous{color:var(--doux);font-size:.92rem;margin:0;text-align:center;
  position:relative;z-index:5}

/* ══════════════════════════════════════════════════════════════════════════
   LA SCÈNE — l'écosystème au centre, les cinq briques en orbite
   ══════════════════════════════════════════════════════════════════════════

   ⚠️ AUCUNE 3D. LA PREMIÈRE VERSION EN UTILISAIT, ET C'ÉTAIT L'ERREUR.
   `rotateX(64°) · rotate(θ) · rotateX(-64°)` n'est pas l'identité : les
   rotations ne commutent pas, les planètes s'écrasaient en ellipses et les
   étiquettes penchaient. La perspective vient de la FORME des orbites
   (ellipses aplaties, dessinées en 2D) ; les satellites suivent leur tracé
   avec `offset-path`, et `offset-rotate:0deg` garantit qu'ils ne tournent
   JAMAIS sur eux-mêmes. Le texte reste lisible en permanence. */
.scene{
  position:relative;width:100%;
  /* Un peu plus haute qu'avant : la projection isométrique 2:1 a besoin de
     hauteur — c'est le RAYON du système qui s'adapte à la place disponible,
     jamais le ratio des ellipses (voir tracer() dans rendu.js). */
  height:clamp(400px,58vh,640px);
  margin:.4rem auto 0;
}

/* Poussière locale de la scène : un voile de grain très léger qui « épaissit »
   l'espace autour des orbites sans toucher aux couches fixes du fond. */
.scene::before{
  content:"";position:absolute;inset:-4%;pointer-events:none;
  background:var(--grain);opacity:.22;mix-blend-mode:screen;
}

/* ── Les orbites ─────────────────────────────────────────────────────────
   Des ellipses aplaties : c'est ce qui donne la perspective, sans 3D. Sans
   elles le mouvement paraît erratique — l'œil a besoin de voir le chemin. */
.orbite{
  position:absolute;top:50%;left:50%;border-radius:50%;
  /* L'arc AVANT (bas) est plus lumineux que l'arc arrière : c'est le même
     indice de profondeur que le passage des planètes devant/derrière le
     soleil — le plan isométrique se lit au premier coup d'œil, et les
     planètes se lisent POSÉES sur leur trajectoire. */
  border:1px solid rgba(108,99,255,.13);
  border-bottom-color:rgba(140,130,255,.34);
  border-left-color:rgba(120,110,255,.22);
  border-right-color:rgba(120,110,255,.22);
  box-shadow:
    0 0 26px rgba(108,99,255,.07) inset,
    0 1px 0 rgba(160,150,255,.10) inset;
  transform:translate(-50%,-50%);pointer-events:none;
}

/* ── Les satellites ──────────────────────────────────────────────────────
   `offset-path` porte la trajectoire (posée par `rendu.js` depuis la taille
   réelle de la scène). `offset-rotate:0deg` est la ligne qui garantit que la
   tuile ne tourne jamais. */
.satellite{
  position:absolute;top:0;left:0;
  /* ⚠️ TAILLE NULLE — C'EST LA LIGNE QUI POSE LES PLANÈTES SUR LEUR ORBITE.
     `offset-anchor` vaut `auto`, donc le CENTRE de la boîte du satellite est
     posé sur le tracé. Sans `width/height:0`, cette boîte est celle de la
     tuile ENTIÈRE (planète + étiquette) : le point qui suivait l'ellipse
     était le milieu de l'ensemble, pas la planète.
     Mesuré sur le banc le 02/08/2026, à 1553×580 — les anneaux étaient
     pourtant parfaitement centrés (776,5 / 290) et le point exactement sur
     l'ellipse (556,5 = 776,5 − 220) :
         boîte 134×140 → planète décalée de (−67, −98) px
         boîte 152×134 → (−84, −105)
         boîte 180×152 → (−79, −92)
     L'écart vaut la MOITIÉ de la boîte : il change donc avec la largeur de
     l'étiquette, d'une brique à l'autre. C'est ce qui se lisait « les
     planètes ne suivent pas leur axe » plutôt que « tout est décalé ».
     Le satellite est désormais un POINT ; c'est la tuile qui se place par
     rapport à lui (voir `.tuile`). */
  width:0;height:0;
  offset-rotate:0deg;
  /* Deux animations sur la MÊME horloge (durée + retard identiques) :
     `orbite` déplace le satellite le long du tracé, `profondeur` le fait
     passer DEVANT le soleil (z-index 4) sur l'arc avant et DERRIÈRE
     (z-index 2, soleil à 3) sur l'arc arrière. Le tracé posé par rendu.js
     part du point GAUCHE et parcourt d'abord l'arc BAS (sweep-flag 0) :
     0 → 50 % est donc l'avant, 50 → 100 % l'arrière — et par symétrie de
     l'ellipse, les points latéraux tombent exactement à 0 % et 50 % : la
     bascule de plan se fait là où aucun chevauchement n'est possible. */
  animation:
    orbite var(--duree) linear infinite,
    profondeur var(--duree) linear infinite;
  animation-delay:var(--depart,0s);
  z-index:2;
  will-change:offset-distance;
}
@keyframes orbite{from{offset-distance:0%}to{offset-distance:100%}}
@keyframes profondeur{
  0%,49.99%{z-index:4}   /* arc avant — devant le soleil  */
  50%,100%{z-index:2}    /* arc arrière — derrière        */
}

/* La distance se lit aussi dans la TAILLE : +13 % au plus près (25 % du
   tracé, bas de l'ellipse), −15 % au plus loin (75 %, haut). La propriété
   `scale` est animée seule pour composer avec le translate(-50%,-50%) de la
   tuile ; navigateur trop ancien → pas d'échelle, système intact. */
.satellite .tuile{
  animation:echelle var(--duree) linear infinite;
  animation-delay:var(--depart,0s);
}
@keyframes echelle{
  0%,50%,100%{scale:1}
  25%{scale:1.13}
  75%{scale:.85}
}

/* Une cible qui bouge est une cible qu'on rate : l'orbite s'arrête dès que le
   pointeur ou le clavier atteint CE satellite — pas la scène entière. Les
   TROIS animations partagent l'horloge, les trois se suspendent ensemble. */
.satellite:hover,.satellite:focus-within,
.satellite:hover .tuile,.satellite:focus-within .tuile{
  animation-play-state:paused}

/* ── Les planètes ────────────────────────────────────────────────────────
   Six couches font la matière (contre quatre en v1) :
     1. grain            — bruit feTurbulence, fondu `overlay`
     2. bandes           — balayage oblique très doux (gazeuse, pas rayée)
     3. corps            — dégradé radial décentré vers la source de lumière
     4. ombre propre     — radial sombre au sud-est
     5. terminateur+rim  — ombres internes + liseré lumineux (box-shadow)
     6. éclat spéculaire — ::after, petit point haut-gauche
   La teinte reste PAR BRIQUE (`--teinte`, degrés HSL) : famille violet →
   indigo → bleu de la charte, jamais de couleur hors charte par accident. */
.planete{
  position:relative;width:var(--taille);height:var(--taille);border-radius:50%;
  /* `flex:none` : sans lui, le conteneur en colonne écrase la sphère en
     ellipse dès que l'étiquette est plus large. */
  flex:none;
  background:
    var(--grain-fort),
    linear-gradient(115deg,
      transparent 20%,
      hsl(var(--teinte) 85% 82% / .17) 33%,
      transparent 45%,
      hsl(var(--teinte) 85% 80% / .12) 57%,
      transparent 70%),
    radial-gradient(circle at 31% 26%,
      hsl(var(--teinte) 92% 82% / .96) 0%,
      hsl(var(--teinte) 82% 58% / .94) 17%,
      hsl(var(--teinte) 72% 38% / .96) 45%,
      hsl(var(--teinte) 68% 15%) 78%),
    radial-gradient(circle at 74% 80%,
      hsl(var(--teinte) 64% 30% / .6),transparent 58%);
  background-blend-mode:overlay,screen,normal,normal;
  box-shadow:
    inset -12px -14px 30px rgba(4,7,24,.9),      /* terminateur           */
    inset 4px 6px 14px hsl(var(--teinte) 92% 74% / .4),  /* lumière du jour */
    inset 0 0 0.5px 1px hsl(var(--teinte) 90% 78% / .18),/* liseré de bord  */
    0 0 26px hsl(var(--teinte) 88% 58% / .42),   /* atmosphère proche     */
    0 0 70px hsl(var(--teinte) 88% 48% / .22),   /* halo diffus           */
    0 18px 34px rgba(3,5,18,.5);                 /* la nuit sous l'astre  */
}
.planete::after{
  content:"";position:absolute;inset:0;border-radius:50%;
  background:radial-gradient(circle at 29% 22%,
    rgba(255,255,255,.95) 0%,rgba(255,255,255,.30) 6%,transparent 19%);
  pointer-events:none;
}

/* L'anneau (ERP — la brique « à venir » se distingue d'un coup d'œil).
   Deux moitiés pour que l'anneau passe DERRIÈRE le pôle nord et DEVANT le
   pôle sud, comme un vrai : le même trait, coupé à mi-hauteur par clip-path.
   L'élément est un <i> posé par `rendu.js` — un pseudo-élément ne suffisait
   pas, ::after porte déjà l'éclat spéculaire. */
.anneau,.anneau::before{
  position:absolute;left:50%;top:50%;
  width:170%;height:56%;
  transform:translate(-50%,-50%) rotate(-16deg);
  border-radius:50%;
  border:2px solid hsl(var(--teinte) 80% 72% / .34);
  border-top-color:hsl(var(--teinte) 85% 80% / .5);
  box-shadow:0 0 12px hsl(var(--teinte) 85% 65% / .25);
  pointer-events:none;
}
.anneau{clip-path:polygon(-10% 50%,110% 50%,110% 120%,-10% 120%)} /* avant */
.anneau::before{
  content:"";left:0;top:0;width:100%;height:100%;
  transform:none;
  clip-path:polygon(-10% -20%,110% -20%,110% 50%,-10% 50%);        /* arrière */
  opacity:.45;z-index:-1;
}

/* ── La tuile : planète + étiquette, toujours droite ─────────────────────── */
.tuile{
  display:flex;flex-direction:column;align-items:center;gap:.6rem;
  text-decoration:none;color:var(--texte);
  --taille:86px;
  /* ⚠️ Le satellite mesure 0×0 (c'est ce qui pose la planète sur l'orbite) :
     sans largeur explicite, la tuile hériterait d'un bloc conteneur de
     largeur NULLE, `translate(-50%)` vaudrait `translate(0)` et le centrage
     horizontal disparaîtrait — constaté immédiatement après la correction,
     `matrix(1,0,0,1,0,-42)`. `max-content` lui rend la largeur qu'elle avait
     quand elle dimensionnait le satellite : le rendu ne change pas. */
  width:max-content;
  /* La tuile se suspend au point porté par le satellite : centrée
     horizontalement, et décalée vers le haut de la MOITIÉ DE LA PLANÈTE —
     pas de la moitié de la tuile. La planète étant le premier enfant et
     mesurant `--taille`, son centre tombe alors exactement sur le point,
     et l'étiquette pend dessous sans tirer l'astre hors de sa trajectoire.
     C'était `translate(-50%,-50%)` : l'étiquette comptait pour moitié dans
     le centrage, et plus elle était large, plus la planète s'écartait. */
  /* ⚠️ `translate:` ET NON `transform:translate(...)`. L'ordre des propriétés
     individuelles est translate → rotate → scale → transform : écrit en
     `transform`, ce décalage serait appliqué À L'INTÉRIEUR du `scale` de
     l'animation `echelle`, donc MULTIPLIÉ par 1,13 au plus près et par 0,85
     au plus loin. La planète quittait alors son orbite d'autant plus qu'elle
     était grosse — mesuré : résidu d'ellipse 0,939 à 0,996 selon la phase,
     pour 1,000 attendu. En `translate:`, le décalage est appliqué AVANT le
     `scale`, qui pivote sur le centre de la planète : l'astre reste sur son
     tracé à toutes les phases. */
  translate:-50% calc(var(--taille) / -2);
  /* Le point de pivot suit la même règle, sinon l'animation `echelle`
     (1,13 au plus près, 0,85 au plus loin) ferait respirer la planète
     AUTOUR DU MILIEU DE LA TUILE : elle quitterait son orbite à chaque
     tour, d'autant plus que l'étiquette est haute. */
  transform-origin:50% calc(var(--taille) / 2);
  transition:filter .22s ease;
}
.etiquette{
  display:flex;align-items:center;gap:.55rem;
  padding:.48rem .7rem .48rem .5rem;border-radius:13px;
  background:rgba(10,15,44,.82);border:1px solid var(--bordure);
  box-shadow:0 10px 26px rgba(3,5,18,.45),0 1px 0 rgba(160,150,255,.08) inset;
  backdrop-filter:blur(7px);white-space:nowrap;
  transition:border-color .22s ease,box-shadow .22s ease,transform .22s ease;
}
/* L'icône de la brique (kit _branding, data-URI) : le même glyphe que sur
   l'écran de la brique elle-même — la reconnaissance se fait AVANT la lecture. */
.etiquette .glyphe{
  width:26px;height:26px;border-radius:7px;flex:none;display:block;
  box-shadow:0 0 0 1px rgba(108,99,255,.28),0 4px 10px rgba(3,5,18,.5);
}
.etiquette .textes{display:flex;flex-direction:column;align-items:flex-start;gap:.14rem}
.tuile .nom{
  font-family:var(--titre);font-weight:600;font-size:.7rem;letter-spacing:.07em;
  text-transform:uppercase;line-height:1.25;
}
.tuile .niveau{
  display:flex;align-items:center;gap:.34rem;font-size:.63rem;
  color:var(--doux);letter-spacing:.02em;text-transform:none;
}
.pastille{
  width:6px;height:6px;border-radius:50%;background:var(--accent);
  box-shadow:0 0 7px var(--accent);flex:none;
}
/* La note reste dans le balisage pour les lecteurs d'écran ; l'étiquette
   n'a pas la place de l'afficher : elle passe en infobulle. */
.tuile .note{display:none}

a.tuile:hover,a.tuile:focus-visible{filter:brightness(1.16)}
a.tuile:hover .etiquette,a.tuile:focus-visible .etiquette{
  border-color:var(--accent);transform:translateY(-2px);
  box-shadow:0 0 0 1px var(--accent),0 14px 34px rgba(108,99,255,.35);
}
/* Le focus clavier DOIT rester visible : sur une page où les cibles bougent,
   c'est le seul repère de qui a le focus. */
.tuile:focus-visible{outline:2px solid var(--accent);outline-offset:6px;
  border-radius:14px}

/* Une brique refusée reste AFFICHÉE, éteinte : savoir ce à quoi on n'a pas
   accès fait partie de l'information. Sa lumière est coupée — « hors
   tension », pas « défaut d'affichage ». */
.tuile.fermee{cursor:not-allowed}
/* La capture 01 du chantier montrait une sphère NOIRE — un trou, pas une
   planète éteinte. On garde la matière (grain, terminateur, bandes) et on ne
   coupe QUE la lumière : désaturée aux trois quarts, assombrie d'un tiers,
   halo réduit à une trace froide. */
.tuile.fermee .planete{
  filter:saturate(.28) brightness(.66) contrast(.94);
  box-shadow:inset -12px -14px 30px rgba(4,7,24,.92),
    inset 4px 6px 14px rgba(160,165,205,.14),
    0 0 16px rgba(107,112,153,.25),0 18px 34px rgba(3,5,18,.5);
}
.tuile.fermee .anneau,.tuile.fermee .anneau::before{
  border-color:rgba(107,112,153,.3);box-shadow:none;
}
.tuile.fermee .etiquette{opacity:.55}
.tuile.fermee .glyphe{filter:saturate(.2) brightness(.7)}
.tuile.fermee .pastille{background:#6b7099;box-shadow:none}

/* ── Le soleil : l'ÉCOSYSTÈME, pas une brique ────────────────────────────
   Il ne gravite pas et ne s'ouvre pas : il est ce autour de quoi tout
   tourne. Quatre couches — halo lointain, couronne tournante, cœur
   incandescent, monogramme AVE posé sur la lumière. */
.soleil{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  z-index:3;display:flex;flex-direction:column;align-items:center;gap:.8rem;
  pointer-events:none;
}
.noyau{
  position:relative;width:clamp(128px,15vw,178px);aspect-ratio:1;
  border-radius:50%;
  background:
    radial-gradient(circle at 50% 44%,
      #ffffff 0%,
      hsl(248 100% 92%) 10%,
      hsl(248 96% 78%) 24%,
      hsl(248 90% 63%) 42%,
      hsl(244 78% 46%) 66%,
      hsl(240 72% 28%) 100%),
    conic-gradient(from 0deg,
      hsl(248 92% 68% / .5),hsl(232 86% 56% / .16),
      hsl(258 94% 72% / .5),hsl(240 86% 58% / .16),hsl(248 92% 68% / .5));
  box-shadow:
    0 0 46px hsl(248 92% 66% / .8),
    0 0 130px hsl(244 88% 58% / .5),
    0 0 260px hsl(240 80% 50% / .28);
  animation:plasma 22s linear infinite;
}
@keyframes plasma{to{filter:hue-rotate(14deg)}}
/* La couronne : un anneau de flammes froid qui tourne LENTEMENT. C'est le
   seul élément en rotation continue — et il est parfaitement circulaire,
   donc la rotation ne déforme rien (la leçon « pas de 3D » ne l'interdit
   pas : ici rien à contre-incliner, rien qui penche). */
.noyau::before{
  content:"";position:absolute;inset:-9%;border-radius:50%;
  background:conic-gradient(
    hsl(252 95% 74% / .0), hsl(252 95% 74% / .5), hsl(252 95% 74% / .0) 22%,
    hsl(236 90% 64% / .0) 40%, hsl(236 90% 64% / .45), hsl(236 90% 64% / .0) 62%,
    hsl(258 95% 76% / .0) 78%, hsl(258 95% 76% / .4), hsl(258 95% 76% / .0));
  filter:blur(7px);
  animation:couronne 38s linear infinite;
}
@keyframes couronne{to{transform:rotate(360deg)}}
.noyau::after{
  content:"";position:absolute;inset:-18px;border-radius:50%;
  border:1px solid hsl(248 90% 74% / .3);
  animation:respire 5.5s ease-in-out infinite;pointer-events:none;
}
@keyframes respire{
  0%,100%{transform:scale(1);opacity:.6}
  50%{transform:scale(1.06);opacity:.15}
}
/* Le nom vit SUR le cœur, pas sous lui : la capture 01 du chantier montrait
   le sous-titre traversé par l'étiquette d'un satellite — tout texte posé
   SOUS le soleil finit sur le chemin d'une orbite. Le cœur porte le
   LOGOTYPE complet « ᐱVE » (le chevron est le A — règle de marque), en
   version nuit : seule silhouette assez simple pour rester nette sur un
   fond incandescent. */
.noyau .titre-int{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  font-family:var(--titre);font-weight:800;
  font-size:clamp(1.35rem,2vw,1.9rem);
  letter-spacing:.09em;
  color:var(--nuit);white-space:nowrap;
  filter:drop-shadow(0 1px 0 rgba(255,255,255,.3));
}
.noyau .titre-int .A{
  background-image:var(--chevron-nuit);
  margin-right:.09em; /* suit l'interlettrage du logotype */
}

/* ══════════════════════════════════════════════════════════════════════════
   LES PANNEAUX — la richesse des maquettes, mais UNIQUEMENT avec les données
   que `/moi/outils` possède : briques + niveaux, nombre de projets, statut
   administrateur. Pas d'« activité récente » ni d'« actualités » inventées —
   un panneau sans source de données est un mensonge en attente.
   ══════════════════════════════════════════════════════════════════════════ */
.tableau{
  display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;
  width:100%;max-width:1160px;margin:1.1rem auto 0;padding:0 .4rem;
  position:relative;z-index:5;
}
.carte{
  position:relative;
  background:var(--carte);
  border:1px solid rgba(108,99,255,.18);
  border-radius:16px;padding:1.05rem 1.15rem 1.15rem;
  box-shadow:0 18px 44px rgba(3,5,18,.45),0 1px 0 rgba(160,150,255,.09) inset;
  backdrop-filter:blur(9px);
  min-width:0;
}
.carte h2{
  display:flex;align-items:center;gap:.55rem;
  font-family:var(--titre);font-weight:600;font-size:.74rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--texte);
  margin:0 0 .85rem;
}
.carte h2::before{
  content:"";width:17px;height:3px;border-radius:2px;flex:none;
  background:var(--degrade);box-shadow:0 0 10px rgba(108,99,255,.6);
}

/* « Accès rapides » : les briques OUVERTES, prêtes à cliquer. */
.puces{display:flex;flex-wrap:wrap;gap:.55rem}
.puce{
  display:flex;align-items:center;gap:.55rem;
  padding:.5rem .75rem .5rem .5rem;border-radius:12px;
  border:1px solid var(--bordure);background:rgba(10,15,44,.55);
  color:var(--texte);text-decoration:none;font-size:.8rem;
  transition:border-color .2s ease,box-shadow .2s ease,transform .2s ease;
}
.puce img{width:30px;height:30px;border-radius:8px;display:block;flex:none;
  box-shadow:0 0 0 1px rgba(108,99,255,.25)}
.puce .p-nom{font-weight:600;line-height:1.2}
.puce .p-niv{display:block;font-size:.66rem;color:var(--doux);font-weight:400}
.puce:hover,.puce:focus-visible{
  border-color:var(--accent);transform:translateY(-2px);
  box-shadow:0 0 0 1px var(--accent),0 12px 28px rgba(108,99,255,.3);
}
.puce:focus-visible{outline:none}

/* « Mes accès » : les deux chiffres qui résument la situation, puis le
   détail par brique — y compris ce qui est FERMÉ. */
.kpis{display:flex;gap:1.6rem;margin-bottom:.8rem}
.kpi .k-val{font-family:var(--titre);font-weight:700;font-size:1.5rem;
  background:linear-gradient(90deg,#cfd3ff,var(--accent));
  -webkit-background-clip:text;background-clip:text;color:transparent}
.kpi .k-lib{font-size:.68rem;color:var(--doux);letter-spacing:.04em;
  text-transform:uppercase}
.lignes{display:flex;flex-direction:column;gap:.42rem}
.ligne{display:flex;align-items:center;gap:.5rem;font-size:.8rem;min-width:0}
.ligne .l-pastille{width:7px;height:7px;border-radius:50%;flex:none;
  background:hsl(var(--teinte,245) 85% 66%);
  box-shadow:0 0 8px hsl(var(--teinte,245) 85% 66% / .8)}
.ligne .l-nom{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ligne .l-niv{margin-left:auto;flex:none;font-size:.66rem;font-weight:600;
  padding:.14rem .5rem;border-radius:999px}
.ligne.est-NONE .l-pastille{background:#4a5177;box-shadow:none}
.ligne.est-NONE .l-nom{color:var(--faible)}
.n-ADMIN{background:rgba(108,99,255,.22);color:#c9c5ff}
.n-WRITE{background:rgba(62,92,245,.2);color:#b9c6ff}
.n-READ{background:rgba(232,234,246,.09);color:var(--doux)}
.n-NONE{background:rgba(232,234,246,.05);color:#6b7099}

/* « Administration » (réservée à l'admin plateforme) / « Besoin d'un
   accès ? » (tous les autres) : la même case, deux réponses. */
.carte .texte{font-size:.8rem;color:var(--doux);line-height:1.5;margin:0 0 .85rem}
.bouton-principal{
  display:inline-flex;align-items:center;gap:.5rem;
  background:var(--degrade);color:#fff;text-decoration:none;
  border:0;border-radius:11px;padding:.6rem 1rem;cursor:pointer;
  font:600 .82rem var(--corps);letter-spacing:.01em;
  box-shadow:0 10px 26px rgba(108,99,255,.35);
  transition:box-shadow .2s ease,transform .12s ease;
}
.bouton-principal:hover,.bouton-principal:focus-visible{
  box-shadow:0 14px 32px rgba(108,99,255,.5);transform:translateY(-1px);
}

/* ── Replis ────────────────────────────────────────────────────────────── */

/* Mouvement réduit : le système RESTE, l'animation s'arrête. On ne démonte
   pas la composition — on retire seulement ce qui bouge.
   ⚠️ `animation:none` ne suffit PAS : sans animation, `offset-distance`
   retombe à 0 % et toutes les planètes s'empilaient au point GAUCHE de leur
   orbite (vu sur la capture 08 du chantier — défaut aussi présent en v1).
   `--pos` (posée par rendu.js) redistribue les positions, statiquement. */
@media (prefers-reduced-motion:reduce){
  .satellite,.satellite .tuile,.noyau,.noyau::before,.noyau::after,
  body::before,body::after{animation:none}
  /* Position ET plan statiques (l'animation `profondeur` étant coupée, le
     z-index doit venir d'une variable, sinon une planète de l'arc avant
     glisserait derrière le soleil). */
  .satellite{offset-distance:var(--pos,0%);z-index:var(--zplan,2)}
  .tuile,.puce,.etiquette,.bouton-principal,.lien{transition:none}
}

/* `offset-path` non supporté : les satellites s'empileraient tous au centre.
   On repasse en grille — la page reste utilisable, seule chose qui compte. */
@supports not (offset-path: path("M 0 0 L 1 1")){
  .scene{height:auto;display:flex;flex-wrap:wrap;gap:1.6rem;
    justify-content:center;padding:1.5rem 0}
  .scene::before{display:none}
  .orbite{display:none}
  /* `width/height:auto` : hors orbite, le satellite redevient une boîte du
     flux. Laissé à 0×0 (sa valeur de bureau, cf. `.satellite`), il serait un
     élément flex sans dimensions et la tuile déborderait par-dessus ses
     voisines. */
  .satellite{position:static;width:auto;height:auto;animation:none}
  .satellite .tuile{animation:none}
  .tuile{transform:none;translate:none;width:auto}
  .soleil{position:static;transform:none;width:100%;order:-1}
}

/* Sous 1100 px, trois panneaux ne tiennent plus de front. */
@media (max-width:1100px){
  .tableau{grid-template-columns:1fr 1fr}
}

/* Sous 820 px, une orbite ne tient pas : les satellites se chevauchent.
   Même information, disposition qui tient dans la main. Les planètes
   RESTENT — ce sont elles qui portent l'identité. */
@media (max-width:820px){
  .scene{height:auto;display:flex;flex-wrap:wrap;gap:1.5rem;
    justify-content:center;padding:1.4rem 0}
  .scene::before{display:none}
  .orbite{display:none}
  /* `offset-path:none` ne suffit PAS à lui seul : le tracé est posé en style
     INLINE par rendu.js, qui l'emporte sur cette règle. C'est `tracer()` qui
     efface l'inline sous 820 px — la règle reste ici comme filet pour un
     satellite qui n'aurait pas encore été tracé. */
  .satellite{position:static;width:auto;height:auto;animation:none;offset-path:none}
  .satellite .tuile{animation:none}
  .tuile{transform:none;translate:none;width:auto;--taille:70px}
  .soleil{position:static;transform:none;width:100%;order:-1;margin-bottom:.4rem}
  .noyau{width:116px}
  .tableau{grid-template-columns:1fr}
  /* L'en-tête replie ses éléments au lieu de déborder : courriel + deux
     boutons dépassaient 390 px et toute la page devenait défilable
     horizontalement (capture 04 du chantier). */
  header{padding:1rem 1.2rem;flex-wrap:wrap;row-gap:.55rem}
  .moi{margin-left:auto;flex-wrap:wrap;justify-content:flex-end;row-gap:.45rem}
  #courriel{max-width:16ch}
}
/* Sous 560 px, la devise n'a plus la place : la marque redevient « ᐱVE »
   seul. `span:last-child` et non `span` : le logotype (premier span) doit
   RESTER — c'est lui qui porte le chevron-A. */
@media (max-width:560px){
  .marque span:last-child{display:none}
}
.note{font-size:.8rem;color:var(--doux);line-height:1.35}

.message{max-width:34rem;text-align:center;color:var(--doux);line-height:1.5;
  margin:auto}
.erreur{border:1px solid rgba(220,38,38,.45);background:rgba(220,38,38,.12);
  color:#ffb4b4;padding:1rem 1.2rem;border-radius:12px;max-width:38rem;
  margin:auto}
footer{padding:1.1rem;text-align:center;color:var(--faible);font-size:.76rem;
  letter-spacing:.04em}

@media (prefers-reduced-motion:reduce){.tuile{transition:none}}
