/* ============================================================
   HS Warp — reusable "stargate" matrix-transporter load effect.

   Any element with [data-warp="<direction>"] gets a matrix-egg cloud
   that flies in from that direction, holds over the element, then
   dissolves away as the element materializes via a clip-path wipe
   (opposite direction of entry — if the cloud came from the top, the
   element reveals feet→head; left, then right-to-left; etc.).

   Triggers:
     - Inside [data-warp-section]: all [data-warp] children fire when
       the section enters the viewport (one IntersectionObserver per
       section), each at its own [data-warp-delay="ms"] offset.
     - Standalone [data-warp] (no section ancestor): fires when the
       element itself enters the viewport.

   8 directions: top, bottom, left, right, top-left, top-right,
   bottom-left, bottom-right.

   Timing (shared with the footer griffin storm so the site's motion
   language is consistent):
     0–1.5s    descent  (canvas translates in from offscreen)
     1.5–1.75s hold     (cloud anchored, matrix keeps cycling)
     1.75–1.95s dissolve + reveal (canvas clips away, content wipes in)
     ~1.95s   JS adds .hs-warp-done → clip cleared, canvas hidden
   ============================================================ */

.hs-warp {
    position: relative;
    isolation: isolate;
    overflow: visible;
}

/* Inner wrapper that holds user content. Gets the directional clip-path
   reveal. Lives as a sibling of the storm canvas so the canvas itself
   isn't clipped during descent. JS auto-wraps the user's children into
   this wrapper at init time. */
.hs-warp-content {
    position: relative;
    display: block;
    width: 100%;
}

/* Hidden starting state per direction — clip-path cuts the content
   away from the OPPOSITE side of where the cloud enters. (Cloud from
   top → top of content clipped → reveal goes feet-to-head.) Side
   insets at -50% leave room for any drop-shadow/box-shadow to render
   beyond the content's box without getting squared off. */
.hs-warp:not(.hs-warp-done)[data-warp="top"]          > .hs-warp-content { -webkit-clip-path: inset(100% -50% -50% -50%); clip-path: inset(100% -50% -50% -50%); }
.hs-warp:not(.hs-warp-done)[data-warp="bottom"]       > .hs-warp-content { -webkit-clip-path: inset(-50% -50% 100% -50%); clip-path: inset(-50% -50% 100% -50%); }
.hs-warp:not(.hs-warp-done)[data-warp="left"]         > .hs-warp-content { -webkit-clip-path: inset(-50% -50% -50% 100%); clip-path: inset(-50% -50% -50% 100%); }
.hs-warp:not(.hs-warp-done)[data-warp="right"]        > .hs-warp-content { -webkit-clip-path: inset(-50% 100% -50% -50%); clip-path: inset(-50% 100% -50% -50%); }
.hs-warp:not(.hs-warp-done)[data-warp="top-left"]     > .hs-warp-content { -webkit-clip-path: inset(100% -50% -50% 100%); clip-path: inset(100% -50% -50% 100%); }
.hs-warp:not(.hs-warp-done)[data-warp="top-right"]    > .hs-warp-content { -webkit-clip-path: inset(100% 100% -50% -50%); clip-path: inset(100% 100% -50% -50%); }
.hs-warp:not(.hs-warp-done)[data-warp="bottom-left"]  > .hs-warp-content { -webkit-clip-path: inset(-50% -50% 100% 100%); clip-path: inset(-50% -50% 100% 100%); }
.hs-warp:not(.hs-warp-done)[data-warp="bottom-right"] > .hs-warp-content { -webkit-clip-path: inset(-50% 100% 100% -50%); clip-path: inset(-50% 100% 100% -50%); }

/* ── Storm canvas ───────────────────────────────────────────────── */
/* Sibling of .hs-warp-content, absolute-positioned at 150% of the
   element's box (centered) so the egg-shaped mask generously covers
   the content. Egg shape is a radial-gradient mask (solid center,
   soft fall-off at edges). JS drives the matrix rain inside. */
.hs-warp > .hs-warp-storm {
    position: absolute;
    left: -25%;
    top: -25%;
    width: 150%;
    height: 150%;
    opacity: 0;
    z-index: 2;
    pointer-events: none;
    -webkit-mask-image: radial-gradient(ellipse 40% 40% at 50% 50%, #000 0%, #000 62%, transparent 100%);
            mask-image: radial-gradient(ellipse 40% 40% at 50% 50%, #000 0%, #000 62%, transparent 100%);
    transition:
        transform 1.5s cubic-bezier(.22, .55, .3, 1),
        opacity .25s ease;
}

/* Initial offscreen offsets per direction — large enough to guarantee
   the cloud is past the viewport edge at every common screen size. */
.hs-warp[data-warp="top"]          > .hs-warp-storm { transform: translate(0, -420%); }
.hs-warp[data-warp="bottom"]       > .hs-warp-storm { transform: translate(0, 420%); }
.hs-warp[data-warp="left"]         > .hs-warp-storm { transform: translate(-420%, 0); }
.hs-warp[data-warp="right"]        > .hs-warp-storm { transform: translate(420%, 0); }
.hs-warp[data-warp="top-left"]     > .hs-warp-storm { transform: translate(-420%, -420%); }
.hs-warp[data-warp="top-right"]    > .hs-warp-storm { transform: translate(420%, -420%); }
.hs-warp[data-warp="bottom-left"]  > .hs-warp-storm { transform: translate(-420%, 420%); }
.hs-warp[data-warp="bottom-right"] > .hs-warp-storm { transform: translate(420%, 420%); }

/* When JS adds .hs-warp-running, canvas settles in via the transition
   above (transform → 0, opacity → 1). The content rise + canvas
   dissolve animations kick in at the 1.75s delay. */
.hs-warp.hs-warp-running > .hs-warp-storm {
    opacity: 1;
    transform: translate(0, 0);
}

/* Reveal animation — wipes content in from the direction opposite
   the entry, 200ms, slight overshoot easing. */
.hs-warp.hs-warp-running[data-warp="top"]          > .hs-warp-content { animation: hs-warp-rise-top          .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="bottom"]       > .hs-warp-content { animation: hs-warp-rise-bottom       .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="left"]         > .hs-warp-content { animation: hs-warp-rise-left         .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="right"]        > .hs-warp-content { animation: hs-warp-rise-right        .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="top-left"]     > .hs-warp-content { animation: hs-warp-rise-top-left     .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="top-right"]    > .hs-warp-content { animation: hs-warp-rise-top-right    .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="bottom-left"]  > .hs-warp-content { animation: hs-warp-rise-bottom-left  .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="bottom-right"] > .hs-warp-content { animation: hs-warp-rise-bottom-right .2s cubic-bezier(.3, .8, .25, 1.1) 1.75s forwards; }

/* Dissolve animation — canvas clips away in the SAME direction as
   entry (cloud drains back toward where it came from), 200ms. */
.hs-warp.hs-warp-running[data-warp="top"]          > .hs-warp-storm { animation: hs-warp-dissolve-top          .2s ease 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="bottom"]       > .hs-warp-storm { animation: hs-warp-dissolve-bottom       .2s ease 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="left"]         > .hs-warp-storm { animation: hs-warp-dissolve-left         .2s ease 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="right"]        > .hs-warp-storm { animation: hs-warp-dissolve-right        .2s ease 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="top-left"]     > .hs-warp-storm { animation: hs-warp-dissolve-top-left     .2s ease 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="top-right"]    > .hs-warp-storm { animation: hs-warp-dissolve-top-right    .2s ease 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="bottom-left"]  > .hs-warp-storm { animation: hs-warp-dissolve-bottom-left  .2s ease 1.75s forwards; }
.hs-warp.hs-warp-running[data-warp="bottom-right"] > .hs-warp-storm { animation: hs-warp-dissolve-bottom-right .2s ease 1.75s forwards; }

/* Terminal state — JS adds .hs-warp-done at ~1.95s after running.
   Clip-path cleared so drop-shadow etc. render normally. Canvas
   hidden entirely (no more paint work, no stacking cost). */
.hs-warp.hs-warp-done > .hs-warp-content {
    -webkit-clip-path: none !important;
            clip-path: none !important;
    animation: none !important;
}
.hs-warp.hs-warp-done > .hs-warp-storm {
    display: none !important;
    animation: none !important;
}

/* ── Rise keyframes (element clip-path wipe in) ─────────────────── */
@keyframes hs-warp-rise-top          { from { -webkit-clip-path: inset(100% -50% -50% -50%); clip-path: inset(100% -50% -50% -50%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }
@keyframes hs-warp-rise-bottom       { from { -webkit-clip-path: inset(-50% -50% 100% -50%); clip-path: inset(-50% -50% 100% -50%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }
@keyframes hs-warp-rise-left         { from { -webkit-clip-path: inset(-50% -50% -50% 100%); clip-path: inset(-50% -50% -50% 100%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }
@keyframes hs-warp-rise-right        { from { -webkit-clip-path: inset(-50% 100% -50% -50%); clip-path: inset(-50% 100% -50% -50%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }
@keyframes hs-warp-rise-top-left     { from { -webkit-clip-path: inset(100% -50% -50% 100%); clip-path: inset(100% -50% -50% 100%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }
@keyframes hs-warp-rise-top-right    { from { -webkit-clip-path: inset(100% 100% -50% -50%); clip-path: inset(100% 100% -50% -50%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }
@keyframes hs-warp-rise-bottom-left  { from { -webkit-clip-path: inset(-50% -50% 100% 100%); clip-path: inset(-50% -50% 100% 100%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }
@keyframes hs-warp-rise-bottom-right { from { -webkit-clip-path: inset(-50% 100% 100% -50%); clip-path: inset(-50% 100% 100% -50%); } to { -webkit-clip-path: inset(-50% -50% -50% -50%); clip-path: inset(-50% -50% -50% -50%); } }

/* ── Dissolve keyframes (canvas clipped away) ───────────────────── */
@keyframes hs-warp-dissolve-top          { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(100% 0 0 0); clip-path: inset(100% 0 0 0); } }
@keyframes hs-warp-dissolve-bottom       { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(0 0 100% 0); clip-path: inset(0 0 100% 0); } }
@keyframes hs-warp-dissolve-left         { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(0 0 0 100%); clip-path: inset(0 0 0 100%); } }
@keyframes hs-warp-dissolve-right        { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(0 100% 0 0); clip-path: inset(0 100% 0 0); } }
@keyframes hs-warp-dissolve-top-left     { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(100% 0 0 100%); clip-path: inset(100% 0 0 100%); } }
@keyframes hs-warp-dissolve-top-right    { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(100% 100% 0 0); clip-path: inset(100% 100% 0 0); } }
@keyframes hs-warp-dissolve-bottom-left  { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(0 0 100% 100%); clip-path: inset(0 0 100% 100%); } }
@keyframes hs-warp-dissolve-bottom-right { from { -webkit-clip-path: inset(0); clip-path: inset(0); } to { -webkit-clip-path: inset(0 100% 100% 0); clip-path: inset(0 100% 100% 0); } }

/* ── Reduced motion ─────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .hs-warp:not(.hs-warp-done) > .hs-warp-content {
        -webkit-clip-path: none !important;
                clip-path: none !important;
    }
    .hs-warp > .hs-warp-storm { display: none !important; }
}
