/* ============================================================
   QreaLAB V01 — world.css
   ------------------------------------------------------------
   LE MONDE : viewport, caméra, couches de parallaxe, sol, fibres,
   vignette.

   ── Direction V02 ──────────────────────────────────────────
   Une composition spatiale ABSTRAITE, pas un lieu réaliste.
   Vue frontale, profondeur suggérée (lumière, ombre, parallaxe
   discrète). Le vide est un matériau visuel : une grande partie
   de la scène reste presque noire.

   ── Rappel du modèle ───────────────────────────────────────
   Aucune 3D CSS. Le relief vient de la variable `--d` portée par
   chaque objet et de la géométrie aplatie du sol (ellipses, voir
   js/world/floor.js). Texte net, empilement prévisible, survol
   fiable.
   ============================================================ */

/* ══════════════════════════════════════════════════════════
   1. VIEWPORT — la fenêtre par laquelle on regarde
   ══════════════════════════════════════════════════════════ */

.viewport {
    position: fixed;
    inset: 0;
    overflow: hidden;
    /* Le noir de la périphérie. Comme la caméra est contrainte à
       l'intérieur du monde, cette couleur n'apparaît jamais : elle
       n'est là qu'en filet de sécurité. */
    background: var(--bg-void);
    /* Indispensable : sans cela le navigateur intercepte les gestes
       tactiles pour faire défiler la page ou zoomer. */
    touch-action: none;
    cursor: grab;
    /* Empêche la sélection de texte pendant un glissement. */
    user-select: none;
    -webkit-user-select: none;
    /* Le monde n'existe pas avant d'y être entré. */
    visibility: hidden;
}

.viewport.is-live {
    visibility: visible;
}

.viewport.is-grabbing {
    cursor: grabbing;
}

/* Le curseur redevient explicite sur ce qui est ouvrable. */
.viewport [data-interactive] {
    cursor: pointer;
}

/* ══════════════════════════════════════════════════════════
   2. LE MONDE — le conteneur transformé par la caméra
   ══════════════════════════════════════════════════════════ */

.world {
    position: absolute;
    top: 0;
    left: 0;
    /* La caméra écrit ici, et nulle part ailleurs. */
    transform-origin: 0 0;
    /* Une seule couche composite pour toute la scène. */
    will-change: transform;
    /* Puissance du monde : 0 au départ, 1 quand tout est stabilisé.
       Alimente l'intensité des lumières. Écrite par scene.js. */
    --world-power: 0;
}

.world__layer {
    position: absolute;
    inset: 0;
    transform-origin: 0 0;
    /* Une couche ne doit JAMAIS capter un clic : seuls les objets
       qu'elle contient sont des cibles. Sans cela, la couche de
       premier plan (parallaxe, z-index supérieur à celui des objets)
       formerait un panneau transparent plein cadre qui intercepterait
       tous les clics destinés aux modules, au Q et à Q-ARCHIVE —
       alors que le drag continuerait de fonctionner par bubbling. */
    pointer-events: none;
}

/* Les objets redeviennent des cibles, quelle que soit leur couche. */
.world__layer .obj {
    pointer-events: auto;
}

/* Les couches de parallaxe reçoivent une translation correctrice
   écrite par scene.js. Les couches non parallaxées n'en ont pas. */
.world__layer--back,
.world__layer--floor,
.world__layer--front {
    will-change: transform;
}

.world__layer--back { z-index: 0; }
.world__layer--floor { z-index: var(--z-floor); }
.world__layer--cables { z-index: var(--z-cables); }
.world__layer--objects { z-index: var(--z-objects); }
.world__layer--front { z-index: 6; }

/* ══════════════════════════════════════════════════════════
   3. MASSES DU FOND
   ──────────────────────────────────────────────────────────
   Pas des volumes : des présences. Gradients très faibles, formes
   géométriques à peine détachées du noir. Elles ne décrivent rien :
   elles suggèrent que l'installation continue au-delà du visible.
   ══════════════════════════════════════════════════════════ */

.massing {
    position: absolute;
    pointer-events: none;
}

/* Une nappe de graphite bleu, sans bord net. */
.massing--far-a {
    left: 220px;
    top: 340px;
    width: 1080px;
    height: 620px;
    border-radius: 50%;
    background: radial-gradient(50% 50% at 50% 50%,
        rgba(27, 35, 48, 0.5) 0%,
        rgba(20, 26, 36, 0.22) 52%,
        rgba(0, 0, 0, 0) 100%);
}

/* Un très grand anneau, presque entièrement dissous dans le noir. */
.massing--far-b {
    left: 1220px;
    top: 240px;
    width: 1380px;
    height: 1380px;
    border-radius: 50%;
    border: 1px solid rgba(127, 149, 173, 0.05);
    background: radial-gradient(50% 50% at 50% 50%,
        rgba(0, 0, 0, 0) 58%,
        rgba(27, 35, 48, 0.1) 66%,
        rgba(0, 0, 0, 0) 74%);
}

/* Une seconde nappe, plus petite et plus lointaine encore. */
.massing--far-c {
    left: 2450px;
    top: 480px;
    width: 880px;
    height: 520px;
    border-radius: 50%;
    background: radial-gradient(50% 50% at 50% 50%,
        rgba(36, 46, 61, 0.34) 0%,
        rgba(20, 26, 36, 0.14) 55%,
        rgba(0, 0, 0, 0) 100%);
    opacity: 0.8;
}

/* ══════════════════════════════════════════════════════════
   4. SOL — anneaux concentriques, plateforme, rien de plus
   ──────────────────────────────────────────────────────────
   Le sol n'est pas une surface visible : c'est une géométrie
   d'accueil. Des anneaux aplaties autour du Q, sa plateforme,
   et la lumière qu'il y dépose. Tout le reste disparaît.
   ══════════════════════════════════════════════════════════ */

.floor {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* Le sol est un graphique : il ne doit jamais capter un clic. */
    pointer-events: none;
}

/* Lignes de fuite : quasi invisibles, elles orientent l'œil vers
   le Q sans décrire un sol. */
.floor__radial {
    stroke: rgba(127, 149, 173, 0.04);
    stroke-width: 1;
}

.floor__radial--major {
    stroke: rgba(127, 149, 173, 0.07);
}

/* Anneaux : le seul élément qui décrit vraiment l'assise. */
.floor__ring {
    fill: none;
    stroke: rgba(140, 165, 190, 0.055);
    stroke-width: 1.5;
}

.floor__ring--core {
    stroke: rgba(95, 232, 255, 0.12);
    stroke-width: 2;
}

.floor__arc {
    fill: none;
    stroke: rgba(140, 165, 190, 0.09);
    stroke-width: 2;
    stroke-linecap: round;
}

.floor__tick {
    stroke: rgba(140, 165, 190, 0.12);
    stroke-width: 2;
    stroke-linecap: round;
}

/* ── Plateforme du Q ────────────────────────────────────── */

.floor__platform-edge {
    fill: #0e141d;
    stroke: rgba(0, 0, 0, 0.6);
    stroke-width: 1;
}

.floor__platform-top {
    stroke: rgba(185, 201, 217, 0.06);
    stroke-width: 1.5;
}

/* Le liseré : la seule ligne franchement éclairée de l'assise.
   Son intensité suit la puissance du monde. */
.floor__platform-rim {
    fill: none;
    stroke: rgba(95, 232, 255, calc(0.2 + var(--world-power, 0) * 0.26));
    stroke-width: 2;
}

.floor__anchor {
    fill: #1c2531;
    stroke: rgba(185, 201, 217, 0.07);
    stroke-width: 1;
}

/* ══════════════════════════════════════════════════════════
   5. CONNEXIONS — des fibres, pas des tuyaux
   ──────────────────────────────────────────────────────────
   Fines, lumineuses quand le courant passe, dissoutes quand le
   lien est perdu. Quatre états, tous lisibles sans cadenas :
     dead        fibre présente, interrompue, aucune couleur
     powered     alimentée, pas encore identifiée : lueur faible
     active      identifiée : flux visible
     stabilized  menée à son terme : dense et calme
     lost        part du Q puis s'efface dans l'obscurité
   ══════════════════════════════════════════════════════════ */

.cables {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

.cable__sheath,
.cable__core,
.cable__flow {
    fill: none;
    stroke-linecap: round;
}

/* La gaine : une trace matérielle fine, toujours présente. C'est
   elle qui rend l'abandon intact : le chemin existe, même éteint. */
.cable__sheath {
    stroke: #131a24;
    stroke-width: 4;
}

/* Le conducteur porte la lumière. */
.cable__core {
    stroke: var(--signal-dead);
    stroke-width: 1.5;
    transition: stroke var(--t-slow) var(--ease-out);
}

/* Le flux : tirets qui remontent la fibre. */
.cable__flow {
    stroke: var(--signal-hot);
    stroke-width: 1.5;
    opacity: 0;
    filter: drop-shadow(0 0 4px rgba(46, 230, 255, 0.5));
    transition: opacity var(--t-slow) var(--ease-out);
}

/* ── Variantes de matière ───────────────────────────────── */

/* Fibre : le défaut de l'installation. Fine et nette. */
.cable--fiber .cable__sheath { stroke-width: 3; }
.cable--fiber .cable__core { stroke-width: 1.2; }
.cable--fiber .cable__flow { stroke-width: 1.2; }

/* Conduit : un peu plus marqué, sans jamais devenir tuyauterie. */
.cable--conduit .cable__sheath { stroke-width: 6; }
.cable--conduit .cable__core { stroke-width: 2; }

/* Bande : large et plate, presque un rail lumineux. */
.cable--band .cable__sheath { stroke-width: 10; stroke-linecap: butt; }
.cable--band .cable__core { stroke-width: 3.5; stroke-linecap: butt; }
.cable--band .cable__flow { stroke-width: 3.5; stroke-linecap: butt; }

/* Structure : un lien abstrait en pointillés longs. */
.cable--struct .cable__sheath { stroke-width: 3; }
.cable--struct .cable__core { stroke-width: 1.2; stroke-dasharray: 18 14; }
.cable--struct .cable__flow { display: none; }

/* ── États ──────────────────────────────────────────────── */

.cable[data-state="dead"] .cable__core {
    stroke: var(--signal-dead);
}

.cable[data-state="powered"] .cable__core {
    stroke: var(--signal-dim);
}

.cable[data-state="powered"] .cable__flow {
    opacity: 0.28;
    animation: cable-flow var(--flow-duration, 6s) linear infinite;
}

.cable[data-state="active"] .cable__core {
    stroke: var(--signal);
    filter: drop-shadow(0 0 5px rgba(88, 210, 238, 0.3));
}

.cable[data-state="active"] .cable__flow {
    opacity: 0.8;
    animation: cable-flow var(--flow-duration, 6s) linear infinite;
}

/* Stabilisé : le flux nerveux s'arrête. La connexion est simplement
   là — c'est plus fort qu'une animation supplémentaire. */
.cable[data-state="stabilized"] .cable__core {
    stroke: var(--q-cyan);
    stroke-width: 2;
    filter: drop-shadow(0 0 8px rgba(95, 232, 255, 0.35));
}

.cable--fiber[data-state="stabilized"] .cable__core { stroke-width: 1.6; }
.cable--band[data-state="stabilized"] .cable__core { stroke-width: 4; }

.cable[data-state="stabilized"] .cable__flow {
    opacity: 0.18;
    animation: cable-flow calc(var(--flow-duration, 6s) * 2.4) linear infinite;
}

/* ── Connexion principale (module-01) ─────────────────────
   Le premier module accessible doit montrer clairement que le Q
   l'alimente : gaine plus marquée, conducteur cyan plus lumineux,
   flux plus dense. Les autres connexions gardent leur discrétion. */

.cable[data-cable="module-01"][data-state="powered"] .cable__sheath {
    stroke-width: 5;
    stroke: #1a2430;
}

.cable[data-cable="module-01"][data-state="powered"] .cable__core {
    stroke: var(--q-cyan);
    stroke-width: 2.2;
    filter: drop-shadow(0 0 6px rgba(95, 232, 255, 0.5));
}

.cable[data-cable="module-01"][data-state="powered"] .cable__flow {
    stroke-width: 1.8;
    opacity: 0.7;
    animation: cable-flow var(--flow-duration, 6s) linear infinite;
}

.cable[data-cable="module-01"][data-state="active"] .cable__core {
    stroke: var(--signal);
    stroke-width: 2.2;
    filter: drop-shadow(0 0 6px rgba(88, 210, 238, 0.4));
}

.cable[data-cable="module-01"][data-state="stabilized"] .cable__core {
    stroke-width: 2.4;
}

/* Perdu dans l'obscurité. La dissolution est GÉOMÉTRIQUE (segments
   décroissants calculés par le JS) et non un masque en dégradé :
   un masque devrait connaître la direction du lien pour s'effacer
   du bon côté, et se tromperait donc sur la moitié des branches. */
.cable[data-state="lost"] .cable__sheath {
    opacity: 0.4;
}

.cable[data-state="lost"] .cable__core {
    opacity: 0.5;
    stroke: var(--signal-dead);
}

.cable[data-state="lost"] .cable__flow {
    display: none;
}

@keyframes cable-flow {
    from { stroke-dashoffset: 0; }
    to { stroke-dashoffset: calc(var(--flow-length, 100) * -1px); }
}

/* Propagation de l'alimentation : jouée une fois quand la
   progression change l'état du monde. Le déblocage a donc une
   conséquence visible, pas seulement une variable. */
.cable.is-surging .cable__core {
    animation: cable-surge 1.5s var(--ease-out);
}

@keyframes cable-surge {
    0% { stroke: var(--signal-dead); filter: none; }
    25% { stroke: #eaf8ff; filter: drop-shadow(0 0 16px rgba(95, 232, 255, 0.8)); }
    100% { stroke: var(--signal); filter: drop-shadow(0 0 5px rgba(88, 210, 238, 0.3)); }
}

/* ══════════════════════════════════════════════════════════
   6. AMBIANCE (canvas)
   ══════════════════════════════════════════════════════════ */

.ambient {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: var(--z-ambient);
    pointer-events: none;
}

/* ══════════════════════════════════════════════════════════
   7. VIGNETTE
   ══════════════════════════════════════════════════════════
   Ce qui garantit que la périphérie reste très sombre et qu'on ne
   perçoit jamais les limites de l'espace. Posée sur le VIEWPORT,
   donc elle ne bouge pas avec la caméra : où qu'on regarde, les
   bords s'éteignent.
   ══════════════════════════════════════════════════════════ */

.vignette {
    position: absolute;
    inset: 0;
    z-index: var(--z-vignette);
    pointer-events: none;
    background:
        /* assombrissement radial général */
        radial-gradient(120% 100% at 50% 46%,
            rgba(0, 0, 0, 0) 0%,
            rgba(0, 0, 0, 0.14) 44%,
            rgba(0, 0, 0, 0.56) 74%,
            rgba(0, 0, 0, 0.9) 100%),
        /* extinction franche du bas : le premier plan sort du cadre */
        linear-gradient(to top,
            rgba(1, 2, 5, 0.72) 0%,
            rgba(1, 2, 5, 0) 26%),
        /* extinction du haut : le fond se perd */
        linear-gradient(to bottom,
            rgba(1, 2, 5, 0.64) 0%,
            rgba(1, 2, 5, 0) 30%);
}

/* ══════════════════════════════════════════════════════════
   8. INDICE DE NAVIGATION
   ══════════════════════════════════════════════════════════
   Affiché brièvement à la première entrée, puis effacé. Le monde
   ne garde pas d'aide permanente à l'écran.
   ══════════════════════════════════════════════════════════ */

.drag-hint {
    position: absolute;
    left: 50%;
    bottom: calc(var(--space-2xl) + var(--safe-bottom));
    z-index: var(--z-hud);
    transform: translateX(-50%);
    padding: var(--space-xs) var(--space-md);
    color: var(--text-muted);
    background: rgba(3, 5, 9, 0.6);
    border: 1px solid var(--line-subtle);
    border-radius: var(--radius-full);
    backdrop-filter: blur(8px);
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--t-slow) var(--ease-out);
}

.drag-hint.is-visible {
    opacity: 1;
}

@media (max-width: 767px) {
    .drag-hint {
        bottom: calc(var(--hud-height) + var(--safe-bottom) + var(--space-md));
        font-size: var(--fs-2xs);
    }
}
