/* ============================================================
   animations.css — reveal classes (toggled by GSAP / observers)
   ============================================================ */

/* Generic reveal containers — keep elements hidden until JS animates them.
   If JS is disabled or fails, the .no-js fallback ensures content is visible. */
.no-js [data-reveal],
.no-js [data-split],
.no-js [data-stagger] > * { opacity: 1 !important; transform: none !important; }

[data-reveal] { opacity: 0; transform: translateY(40px); will-change: transform, opacity; }
[data-reveal="fade"] { transform: none; }
[data-reveal="rise"] { transform: translateY(60px); }
[data-reveal="slide-left"] { transform: translateX(-60px); }
[data-reveal="slide-right"] { transform: translateX(60px); }
[data-reveal="scale"] { transform: scale(0.96); }
[data-reveal="blur"] { filter: blur(20px); opacity: 0; transform: translateY(20px); }

[data-stagger] > * { opacity: 0; transform: translateY(24px); }

/* Char/word split lines — each .split-line clips its own inner during the
   reveal animation; once the animation completes, JS sets overflow:visible
   so italic descenders ("g", "y", "p", "j", swash-ess) render fully. The
   wrapper [data-split] is intentionally NOT clipped — clipping at the wrapper
   would chop the descender off the very last line. */
[data-split] {
    overflow: visible;
    padding-bottom: 0.08em; /* descender breathing room at the very last line */
}
[data-split] .split-line {
    display: block;
    overflow: hidden;
    line-height: inherit;
}
[data-split] .split-line__inner {
    display: block;
    will-change: transform;
}

.char-line { display: block; overflow: hidden; line-height: 0.95; }
.char-line span { display: inline-block; }

/* Hover line for links inside content */
.hover-line {
    position: relative;
    color: inherit;
}
.hover-line::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -2px;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.5s var(--ease-out);
}
.hover-line:hover::after {
    transform: scaleX(1);
    transform-origin: left;
}

/* Marquee speed override for reduced motion */
@media (prefers-reduced-motion: reduce) {
    .marquee__track { animation-duration: 90s; }
}

/* Pillar art — animated orbits */
.orbit {
    width: 80%;
    max-width: 320px;
    aspect-ratio: 1;
    position: relative;
}
.orbit__ring {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 1px solid var(--line);
}
.orbit__ring--2 { inset: 12%; border-style: dashed; }
.orbit__ring--3 { inset: 24%; }
.orbit__node {
    position: absolute;
    top: 50%; left: 50%;
    width: 10px; height: 10px;
    background: var(--lime);
    border-radius: 50%;
    box-shadow: 0 0 18px var(--lime);
    transform-origin: center;
}
.orbit__core {
    position: absolute;
    inset: 38%;
    border-radius: 50%;
    background: radial-gradient(circle, var(--bone), var(--mute) 80%);
    box-shadow: 0 0 30px rgba(239,233,220,0.2);
}
.orbit--spin .orbit__ring { animation: orbitSpin 30s linear infinite; }
.orbit--spin .orbit__ring--2 { animation-duration: 22s; animation-direction: reverse; }
.orbit--spin .orbit__ring--3 { animation-duration: 14s; }

@keyframes orbitSpin {
    to { transform: rotate(360deg); }
}

/* Pillar wave SVG style */
.pillar__art svg {
    color: var(--lime);
}

/* Generic appear keyframes used as CSS-only fallback when JS hasn't reached */
@keyframes fadeUp {
    from { opacity: 0; transform: translateY(28px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Make sure body and main don't ghost on first paint while waiting for GSAP */
body.is-loading { opacity: 0; }
body.is-loaded  { opacity: 1; transition: opacity 0.7s ease; }
