/* ============================================================
   EFFECTS — starfield, aurora, glow, scroll reveal

   Everything here animates only `transform` and `opacity`, which
   the compositor handles without repainting, so the background
   stays smooth on an integrated GPU. All of it is switched off
   under `prefers-reduced-motion`.
   ============================================================ */

/* ------------------------------------------------------------
   STARFIELD
   Two fixed layers drifting at different speeds for depth. The
   stars are radial-gradients painted once into a composited
   layer, not thousands of DOM nodes.
   ------------------------------------------------------------ */

.sky {
    position: fixed;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
}

.sky__layer {
    position: absolute;
    /* Oversized so the drift never exposes an edge. */
    inset: -20%;
    background-repeat: repeat;
    will-change: transform;
}

.sky__layer--far {
    background-image:
        radial-gradient(1px 1px at 12% 18%, var(--star-dim) 50%, transparent 100%),
        radial-gradient(1px 1px at 47% 9%,  var(--star-dim) 50%, transparent 100%),
        radial-gradient(1px 1px at 78% 27%, var(--star-dim) 50%, transparent 100%),
        radial-gradient(1px 1px at 29% 51%, var(--star-dim) 50%, transparent 100%),
        radial-gradient(1px 1px at 66% 63%, var(--star-dim) 50%, transparent 100%),
        radial-gradient(1px 1px at 88% 81%, var(--star-dim) 50%, transparent 100%),
        radial-gradient(1px 1px at 7%  73%, var(--star-dim) 50%, transparent 100%),
        radial-gradient(1px 1px at 38% 88%, var(--star-dim) 50%, transparent 100%);
    background-size: 420px 420px;
    opacity: var(--sky-opacity-far);
    animation: sky-drift-far 240s linear infinite;
}

.sky__layer--near {
    background-image:
        radial-gradient(1.6px 1.6px at 22% 31%, var(--star-bright) 50%, transparent 100%),
        radial-gradient(2px 2px   at 61% 14%, var(--star-gold) 50%, transparent 100%),
        radial-gradient(1.4px 1.4px at 85% 44%, var(--star-bright) 50%, transparent 100%),
        radial-gradient(1.8px 1.8px at 15% 67%, var(--star-gold) 50%, transparent 100%),
        radial-gradient(1.4px 1.4px at 54% 84%, var(--star-bright) 50%, transparent 100%);
    background-size: 680px 680px;
    opacity: var(--sky-opacity-near);
    animation: sky-drift-near 160s linear infinite;
}

@keyframes sky-drift-far {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-420px, -420px, 0); }
}

@keyframes sky-drift-near {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(680px, -680px, 0); }
}

/* --- Aurora: slow colour bloom behind the content ----------- */

.sky__aurora {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(
            44% 32% at 18% 12%,
            color-mix(in srgb, var(--secondary) var(--aurora-strength), transparent),
            transparent 70%
        ),
        radial-gradient(
            40% 30% at 82% 26%,
            color-mix(in srgb, var(--accent) var(--aurora-strength), transparent),
            transparent 70%
        ),
        radial-gradient(
            48% 34% at 50% 92%,
            color-mix(in srgb, var(--aurora-warm) var(--aurora-strength), transparent),
            transparent 70%
        );
    animation: aurora-breathe 26s var(--ease-in-out) infinite;
    will-change: transform, opacity;
}

@keyframes aurora-breathe {
    0%, 100% { transform: scale(1)    translate3d(0, 0, 0); opacity: 0.85; }
    50%      { transform: scale(1.12) translate3d(0, -2%, 0); opacity: 1; }
}

/* --- Theme-specific intensity --------------------------------
   The dark theme is the mystical night sky. The light theme is
   quieter, but it used to be invisible: an indigo mote at 42%
   alpha behind a layer at 40% opacity resolved to 17%, which on
   a 1px dot over near-white measured 1.29 against the page.
   Technically present, and nobody ever saw it.
   -------------------------------------------------------------- */

/* Dark is the default, so it is declared on :root; light overrides it.
   `--aurora-warm` is the third bloom: violet by day, gold by night. Using
   gold in the light theme washed every page warm beige. */

:root,
:root[data-theme="dark"] {
    --star-dim: rgba(214, 198, 243, 0.75);
    --star-bright: rgba(255, 255, 255, 0.95);
    --star-gold: rgba(243, 227, 173, 0.95);
    --sky-opacity-far: 0.75;
    --sky-opacity-near: 0.9;
    --aurora-strength: 16%;
    --aurora-warm: var(--gold);
    --shooting-peak: 0.9;
}

:root[data-theme="light"] {
    --star-dim: rgba(90, 62, 120, 0.55);
    --star-bright: rgba(255, 248, 235, 0.95);
    --star-gold: rgba(240, 196, 120, 0.9);
    --sky-opacity-far: 0.85;
    --sky-opacity-near: 0.95;
    --aurora-strength: 26%;
    --aurora-warm: #e8a06a;
    /* Fainter by day: it crosses a pale sky, not a dark one. */
    --shooting-peak: 0.85;
}

/* ------------------------------------------------------------
   SHOOTING STAR
   One element, a long pause between passes so it reads as a
   lucky glimpse rather than decoration.
   ------------------------------------------------------------ */

.sky__shooting-star {
    position: absolute;
    top: 14%;
    left: -10%;
    width: 140px;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--star-gold), transparent);
    opacity: 0;
    animation: shooting-star 19s var(--ease-out) infinite;
}

/* The peak is a variable because the animation sets `opacity` on every
   keyframe: a plain rule on the element would simply be overridden. */
@keyframes shooting-star {
    0%, 86%  { opacity: 0; transform: translate3d(0, 0, 0) rotate(14deg); }
    88%      { opacity: var(--shooting-peak, 0.9); }
    97%      { opacity: 0; transform: translate3d(120vw, 42vh, 0) rotate(14deg); }
    100%     { opacity: 0; transform: translate3d(120vw, 42vh, 0) rotate(14deg); }
}

/* ------------------------------------------------------------
   SCROLL REVEAL
   Elements start lowered and fade up as they enter the viewport.
   `reveal.js` adds `is-visible`; without JavaScript the
   `no-js-safe` rule below keeps everything visible.
   ------------------------------------------------------------ */

[data-reveal] {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
    transition:
        opacity var(--duration-slow) var(--ease-out),
        transform var(--duration-slow) var(--ease-out);
    transition-delay: calc(var(--reveal-delay, 0) * 85ms);
}

[data-reveal].is-visible {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}

/* If scripting never runs, nothing may stay invisible. */
html:not(.js) [data-reveal] {
    opacity: 1;
    transform: none;
}

/* ------------------------------------------------------------
   GOLD ACCENTS
   ------------------------------------------------------------ */

/* A thin luminous rule, used under section headings. */
/* Fades out to the right so it reads as trim rather than a cut-off line.
   The fixed 110px bar looked like a stray mark under a left-aligned header. */
.gold-rule {
    width: min(220px, 40%);
    height: 1px;
    border: 0;
    margin: var(--space-3) 0 0;
    background: linear-gradient(
        90deg,
        color-mix(in srgb, var(--gold) 70%, transparent),
        transparent
    );
}

.section-header-center .gold-rule {
    margin-inline: auto;
}

@keyframes gild-sweep {
    0%, 100% { background-position: 150% 0; }
    50%      { background-position: -50% 0; }
}

/* Halo for the character portrait and other focal art. */
.moonlit {
    filter: drop-shadow(0 0 26px color-mix(in srgb, var(--gold) 34%, transparent));
}

/* ------------------------------------------------------------
   MOTION PREFERENCES
   Everything decorative stops; nothing disappears.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
    .sky__layer,
    .sky__aurora {
        animation: none;
    }

    .sky__shooting-star {
        display: none;
    }

    [data-reveal] {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* ------------------------------------------------------------
   SPARKLE SHAPE
   A four-pointed star used as a mask, so the sparkle takes its
   colour from `background-color` and follows the active theme
   instead of being baked into the SVG.
   ------------------------------------------------------------ */

:root {
    --sparkle-star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 0C12.6 7 17 11.4 24 12c-7 .6-11.4 5-12 12-.6-7-5-11.4-12-12 7-.6 11.4-5 12-12z' fill='%23000'/%3E%3C/svg%3E");
}

/* ------------------------------------------------------------
   A TRAIL OF STARS
   One fixed layer holding a fixed pool of sparks that
   `flourish.js` moves and relights as the cursor passes. The
   pool is why a fast cursor cannot flood the page with nodes.
   ------------------------------------------------------------ */

.trail {
    position: fixed;
    inset: 0;
    z-index: var(--z-nav);
    overflow: hidden;
    pointer-events: none;
}

.trail__star {
    position: absolute;
    width: var(--size, 10px);
    height: var(--size, 10px);
    /* Centred on the cursor rather than hanging below and right of it. */
    margin: calc(var(--size, 10px) / -2);
    background-color: var(--star-gold);
    mask: var(--sparkle-star) center / contain no-repeat;
    -webkit-mask: var(--sparkle-star) center / contain no-repeat;
    opacity: 0;
}

.trail__star.is-lit {
    animation: trail-spark 780ms var(--ease-out) forwards;
}

/* Each spark drifts a little way off the path and turns as it goes, so
   fourteen identical elements do not read as fourteen identical elements. */
@keyframes trail-spark {
    0% {
        opacity: 0;
        transform: translate3d(0, 0, 0) scale(0.3) rotate(0);
    }
    22% {
        opacity: 0.9;
        transform: translate3d(calc(var(--drift, 0px) * 0.3), -4px, 0)
                   scale(1) rotate(calc(var(--turn, 0deg) * 0.3));
    }
    100% {
        opacity: 0;
        transform: translate3d(var(--drift, 0px), 26px, 0)
                   scale(0.4) rotate(var(--turn, 0deg));
    }
}

/* ------------------------------------------------------------
   THE MOON IN THE BACKGROUND
   Not decoration with a made-up shape: the lit fraction is
   today's real illumination, computed in `horoscope/sky.py`
   from the synodic month, and the disc is mirrored while the
   moon wanes so the crescent faces the right way.
   ------------------------------------------------------------ */

.sky__moon {
    position: absolute;
    /* Debajo de la banda de cabeceras, y entera en pantalla.
       Dos intentos fallidos antes de este, los dos medidos:

       Arriba del todo, a 7vw del borde, chocaba con el retrato del signo,
       que ocupa de los 105 a los 201 px de esa misma franja. Dos circulos
       superpuestos se leen como un error.

       Sacandola fuera del borde dejaba de chocar, pero desaparecia: el
       limbo iluminado es el derecho, asi que lo que recortaba la pantalla
       era justo la parte brillante y solo quedaba el disco apagado.

       Asi que ni fuera ni arriba: mas abajo. Medido en la portada y en el
       horoscopo, por debajo de los 210 px no hay nada en esa franja salvo
       alguna chispa decorativa, que si se puede solapar. */
    top: clamp(13rem, 26vh, 20rem);
    right: clamp(1rem, 4vw, 3.5rem);
    width: clamp(70px, 9vw, 120px);
    aspect-ratio: 1;
    border-radius: 50%;
    /* The light grows from the right-hand limb, which is the side a waxing
       moon is lit from, and the terminator is feathered across eight per
       cent of the disc. A hard stop read as a two-tone pie chart.

       Lit and unlit are separate tokens, not one strength divided. Tied
       together, the day theme could not have a crescent you could see
       without also having a full disc you could see: the unlit side came
       out at five per cent alpha, the ring outlined it anyway, and the
       moon read as an empty circle drawn on the page. */
    background:
        /* El resplandor del limbo iluminado, por delante. Es lo que le da
           bulto: sin el, las dos mitades se leen como un disco partido en
           dos colores planos y no como una esfera iluminada de lado. */
        radial-gradient(
            70% 70% at calc(100% - var(--lit, 50%) / 2) 42%,
            var(--moon-sheen),
            transparent 72%
        ),
        linear-gradient(
            90deg,
            var(--moon-dark) 0 calc(100% - var(--lit, 50%) - 9%),
            var(--moon-lit) calc(100% - var(--lit, 50%) + 9%) 100%
        );
    /* The ring is the unlit limb catching earthshine: without it a new moon
       is nothing at all, and the disc blinks out of the sky once a month. */
    box-shadow:
        inset 0 0 0 1px color-mix(in srgb, var(--gold) var(--moon-ring), transparent),
        0 0 var(--moon-halo) color-mix(in srgb, var(--gold) 16%, transparent);
    animation: moon-breathe 14s var(--ease-in-out) infinite;
}

/* Waxing light grows on the right in the northern sky, waning on the left,
   and one gradient cannot do both. Mirroring the disc does. */
.sky__moon[data-phase^="waning"],
.sky__moon[data-phase="last_quarter"] {
    transform: scaleX(-1);
}

@keyframes moon-breathe {
    0%, 100% { opacity: 0.75; }
    50%      { opacity: 1; }
}

/* A moon on a dark sky is lit against nothing, so it can be soft. A moon
   on a pale sky has no such luck: at the same strength it disappears into
   the lavender and only its outline survives. By day it is painted the way
   a daytime moon really looks — a warm sliver against a cooler disc, both
   firmer than at night. */
:root,
:root[data-theme="dark"] {
    --moon-lit: color-mix(in srgb, var(--gold-bright) 60%, transparent);
    --moon-dark: color-mix(in srgb, var(--secondary) 12%, transparent);
    --moon-sheen: color-mix(in srgb, var(--gold-bright) 26%, transparent);
    --moon-ring: 22%;
    --moon-halo: 50px;
}

:root[data-theme="light"] {
    --moon-lit: color-mix(in srgb, var(--gold-bright) 92%, transparent);
    /* Lavanda, no indigo: el azul saturado cantaba sobre un cielo que ya
       es lila, y la sombra parecia pintura en vez de sombra. */
    --moon-dark: color-mix(in srgb, var(--yuna-indigo-700) 22%, transparent);
    --moon-sheen: color-mix(in srgb, var(--gold-bright) 45%, transparent);
    --moon-ring: 42%;
    --moon-halo: 48px;
}

@media (prefers-reduced-motion: reduce) {
    .sky__moon {
        animation: none;
    }
}

/* ------------------------------------------------------------
   LA REGLA DE ORO SE DIBUJA

   Debajo de cada titulo de seccion. Estaba ahi desde siempre,
   quieta; ahora se traza de izquierda a derecha cuando la
   seccion entra en pantalla, justo detras del titulo que acaba
   de aparecer. Cuesta una transicion y es lo que remata la
   entrada de cada bloque.
   ------------------------------------------------------------ */

[data-reveal] .gold-rule {
    /* `scaleX` sobre el origen izquierdo: anima en el compositor,
       mientras que animar `width` obliga a recalcular la linea. */
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--duration-slow) var(--ease-out);
    /* Despues del titulo, no a la vez. */
    transition-delay: calc(var(--reveal-delay, 0) * 85ms + 220ms);
}

[data-reveal].is-visible .gold-rule {
    transform: scaleX(1);
}

.section-header-center .gold-rule {
    transform-origin: center;
}

/* Sin scripts nunca llega `is-visible`, asi que la regla no puede
   depender de el para verse. */
html:not(.js) [data-reveal] .gold-rule {
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    [data-reveal] .gold-rule,
    [data-reveal].is-visible .gold-rule {
        transform: none;
        transition: none;
    }
}
