/* ============================================================================
   Rayspring Ministry Digital — motion layer.

   Everything here is decoration. Every rule is written so that its absence
   changes nothing about whether the site works: no layout depends on these
   elements, none of them can take a click, and all of them switch off under
   `prefers-reduced-motion`.

   Separate from styles.css on purpose. It is loaded after the stylesheet and
   is the only file that can be deleted outright without breaking a page — if a
   future effect turns out to cost more than it gives, that is the file to
   remove.

   Colours come from the theme tokens in styles.css, so all seven themes get
   their own version of each effect without a single value repeated here.
   ========================================================================== */

/* ── Cursor glow ────────────────────────────────────────────────────────────
   A soft disc that trails the pointer. Created by motion.js and only on a
   device with a fine pointer, so it does not exist at all on a phone — the
   media query below is the second lock, not the first.
   ------------------------------------------------------------------------ */

.cursor-orb {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 180;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  border: 1px solid var(--accent-line);
  background: radial-gradient(circle,
    var(--accent-soft) 0%,
    var(--accent-soft) 45%,
    transparent 72%);
  transition: opacity 0.25s ease, width 0.2s ease, height 0.2s ease;
  will-change: transform;
}
.cursor-orb.is-visible { opacity: 0.75; }

/* Over something you can actually press, it swells — the only part of this
   that carries information rather than atmosphere. */
.cursor-orb.is-hovering {
  width: 54px;
  height: 54px;
  opacity: 0.45;
}

/* ── Hero fireflies ─────────────────────────────────────────────────────────
   A canvas of slow gold motes inside the hero. Dark themes only: on ivory a
   pale mote on a pale field is either invisible or dirt, and there is no
   opacity that is both.
   ------------------------------------------------------------------------ */

.hero-motes {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  display: none;
}
html[data-mode="dark"] .hero-motes { display: block; }

/* ── The night sky ──────────────────────────────────────────────────────────
   A full-viewport canvas of stars with a meteor across it every few seconds,
   for the cosmos theme and only for the cosmos theme. Fixed, not absolute:
   the sky must sit still while the page scrolls over it, exactly like the
   ambient wash it is drawn on top of. Scrolling a starfield turns depth into
   wallpaper.

   z-index -1 puts it above body::before's nebula and below every scrap of
   content, and pointer-events: none means it can never take a click.
   ------------------------------------------------------------------------ */

.night-sky {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  pointer-events: none;
  display: none;
  /* The theme can change under the reader's cursor, so the sky fades in
     rather than snapping on. */
  opacity: 0;
  transition: opacity 0.6s ease;
}
html[data-theme="cosmos"] .night-sky { display: block; opacity: 1; }

/* ── Off ────────────────────────────────────────────────────────────────────
   Three ways this all goes away, and they must all work independently.
   ------------------------------------------------------------------------ */

/* No fine pointer: there is no cursor to follow. */
@media (max-width: 920px), (pointer: coarse) {
  .cursor-orb { display: none; }
}

/*
   Reduced motion does not mean "no stars". It means nothing may move. The
   sky is still drawn — a night theme without a sky is just a dark grey page —
   but motion.js paints it once and never animates it, and no meteor is ever
   sent. A still starfield is a picture, and a picture is allowed.
*/
@media (prefers-reduced-motion: reduce) {
  .cursor-orb,
  .hero-motes { display: none; }
}

@media print {
  .cursor-orb,
  .hero-motes,
  .night-sky { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   The workflow, in motion — Ray's §15 and §35
   ═══════════════════════════════════════════════════════════════════════════

   Everything below is decoration. Delete this file and the hero still shows a
   complete, readable, translated six-stage diagram; what goes is the light
   travelling down the spine and the nodes lighting in turn.

   §15's list, honoured literally: light moves slowly, nodes light in sequence,
   sections lift a little on entry, nothing loops fast enough to pull the eye
   off a sentence. Durations sit in the 180–500ms band it asks for, except the
   spine sweep, which is deliberately slow — a 300ms sweep would read as a
   loading bar.

   The whole block is inside `prefers-reduced-motion: no-preference`, so the
   reduced-motion default is "no animation at all" rather than "animation we
   remembered to switch off". That is the safer way round: a rule added later
   without a matching override still does nothing for the people who asked for
   nothing. */

@media (prefers-reduced-motion: no-preference) {

  /* ── The travelling light on the connector ──────────────────────────────
     One 1px gradient sliding down each connector, staggered by index so the
     eye follows the sequence rather than seeing six things twitch at once.
     `will-change` is deliberately absent: six promoted layers on a phone costs
     more than the effect is worth. */
  .workflow__step:not(:last-child)::after {
    content: "";
    position: absolute;
    left: 1.125rem;
    top: 2.5rem;
    width: 1px;
    height: 1.1rem;
    background: linear-gradient(to bottom, transparent, var(--spring), transparent);
    opacity: 0;
    animation: flow-travel 5.4s ease-in-out infinite;
  }
  .workflow__step:nth-child(1)::after { animation-delay: 0s; }
  .workflow__step:nth-child(2)::after { animation-delay: 0.55s; }
  .workflow__step:nth-child(3)::after { animation-delay: 1.1s; }
  .workflow__step:nth-child(4)::after { animation-delay: 1.65s; }
  .workflow__step:nth-child(5)::after { animation-delay: 2.2s; }

  @keyframes flow-travel {
    0%, 8%    { opacity: 0; transform: translateY(0); }
    14%       { opacity: 1; }
    30%       { opacity: 0; transform: translateY(1.5rem); }
    100%      { opacity: 0; transform: translateY(1.5rem); }
  }

  /* The node picking the light up as it arrives. A border and shadow change,
     not a scale — a scaling dot in a list makes the rows below it jump. */
  .workflow__dot { animation: flow-node 5.4s ease-in-out infinite; }
  .workflow__step:nth-child(2) .workflow__dot { animation-delay: 0.55s; }
  .workflow__step:nth-child(3) .workflow__dot { animation-delay: 1.1s; }
  .workflow__step:nth-child(4) .workflow__dot { animation-delay: 1.65s; }
  .workflow__step:nth-child(5) .workflow__dot { animation-delay: 2.2s; }
  .workflow__step:nth-child(6) .workflow__dot { animation-delay: 2.75s; }

  @keyframes flow-node {
    0%, 10%  { box-shadow: 0 0 0 0 transparent; }
    16%      { box-shadow: 0 0 0 4px var(--spring-soft); }
    34%, 100%{ box-shadow: 0 0 0 0 transparent; }
  }

  /* ── The ripple wash behind the hero ────────────────────────────────────
     A very slow drift on the spring background. 34 seconds, so on any single
     screenful it reads as stillness rather than as movement. */
  .spring-bg::before {
    animation: spring-drift 34s ease-in-out infinite alternate;
  }
  @keyframes spring-drift {
    from { background-position: 0% 0%, 100% 0%, 50% 100%; }
    to   { background-position: 6% 4%, 94% 6%, 44% 96%; }
  }

  /* ── LanguageStream ─────────────────────────────────────────────────────
     The three chips breathing in turn. Opacity only. */
  .lang-chip { animation: lang-pulse 6.6s ease-in-out infinite; }
  .lang-chip:nth-child(2) { animation-delay: 0.7s; }
  .lang-chip:nth-child(3) { animation-delay: 1.4s; }
  @keyframes lang-pulse {
    0%, 70%, 100% { opacity: 0.82; }
    12%           { opacity: 1; }
  }

  /* ── RippleCTA ──────────────────────────────────────────────────────────
     One 480ms ripple on press, then gone. §15 bans anything that keeps
     flashing, so this fires on :active and has no infinite variant. */
  .ripple-cta::after {
    content: "";
    position: absolute;
    left: 50%; top: 50%;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--spring-glow);
    transform: translate(-50%, -50%) scale(0);
    opacity: 0;
    pointer-events: none;
  }
  .ripple-cta:active::after { animation: cta-ripple 480ms ease-out; }
  @keyframes cta-ripple {
    from { transform: translate(-50%, -50%) scale(0);  opacity: 0.55; }
    to   { transform: translate(-50%, -50%) scale(34); opacity: 0; }
  }

  /* The pillars lift 3px on hover — §15 asks for 2–4px, and only where a
     pointer exists. On a touch screen :hover sticks after a tap. */
  @media (hover: hover) and (pointer: fine) {
    .pillar { transition: transform 220ms ease, border-color 220ms ease; }
    .pillar:hover { transform: translateY(-3px); border-color: var(--spring-line); }
  }
}
