/* ==========================================================================
   Netpack "Ribbon & Decode" motion system
   Pairs with netpack-motion.js. No dependencies, ~2 KB gzipped.
   Elements are only ever hidden when html.np-motion is set (by the inline
   head snippet) AND the visitor has not asked for reduced motion.
   ========================================================================== */

:root {
    --np-magenta: #e91270;   /* logo ribbon, outer */
    --np-violet:  #8b0fa3;   /* logo ribbon, middle */
    --np-blue:    #4a6bb5;   /* site primary */
    --np-cyan:    #21dede;   /* section titles */
    --np-ease:    cubic-bezier(.16, 1, .3, 1);
}

/* ---------- 1. Hero: starfield, aurora, warp bloom ---------- */
.np-stars {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* two huge soft brand-colour glows drifting behind the stars (transform only) */
.np-aurora {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
}
.np-aurora::before,
.np-aurora::after {
    content: '';
    position: absolute;
    width: 75vmax;
    height: 75vmax;
    border-radius: 50%;
    will-change: transform;
}
.np-aurora::before {
    left: -30vmax;
    top: -38vmax;
    background: radial-gradient(circle, rgba(233, 18, 112, .30), rgba(139, 15, 163, .12) 38%, transparent 64%);
    animation: np-drift-a 24s ease-in-out infinite alternate;
}
.np-aurora::after {
    right: -30vmax;
    bottom: -42vmax;
    background: radial-gradient(circle, rgba(74, 107, 181, .45), rgba(33, 222, 222, .10) 40%, transparent 65%);
    animation: np-drift-b 30s ease-in-out infinite alternate;
}
@keyframes np-drift-a { to { transform: translate(22vmax, 14vmax) scale(1.15); } }
@keyframes np-drift-b { to { transform: translate(-18vmax, -12vmax) scale(1.2); } }

/* flash of light as the warp drops out */
.np-bloom {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse at 50% 50%, rgba(255, 255, 255, .55), rgba(233, 18, 112, .18) 30%, transparent 62%);
    opacity: 0;
    animation: np-bloom 1.6s ease-out .95s forwards;
}
@keyframes np-bloom {
    15% { opacity: 1; transform: scale(.9); }
    to  { opacity: 0; transform: scale(1.5); }
}

/* Hero copy "focuses" out of the warp with an RGB split. Opacity never hits 0,
   so the headline still counts as the first paint (LCP is not delayed). */
@media (prefers-reduced-motion: no-preference) {
    .np-intro .np-arrive {
        animation: np-arrive 1.5s var(--np-ease) both;
        animation-delay: calc(var(--np-d, 0) * 120ms + 250ms);
    }
}
@keyframes np-arrive {
    from {
        transform: scale(.92);
        filter: blur(10px);
        opacity: .35;
        letter-spacing: .3em;
        text-shadow: -14px 0 rgba(233, 18, 112, .9), 14px 0 rgba(33, 222, 222, .9);
    }
    60% { text-shadow: -2px 0 rgba(233, 18, 112, .6), 2px 0 rgba(33, 222, 222, .6); }
}

/* ---------- 2. Decode ---------- */
.np-glyph {
    color: var(--np-magenta);
    text-shadow: -1px 0 rgba(33, 222, 222, .8), 1px 0 rgba(233, 18, 112, .8);
}

/* ---------- 3. Ribbon ---------- */
.np-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 2;            /* above section backgrounds (sections sit at z-index 1) */
    overflow: hidden;
    pointer-events: none;
}
.np-ribbon {
    position: absolute;
    top: 0;
    left: 0;
    overflow: visible;
}
.np-ribbon path {
    fill: none;
    stroke: url(#np-ribbon-grad);
    stroke-linecap: round;
    stroke-linejoin: round;
}
.np-ribbon .np-ribbon-line   { stroke-width: 2.5; }
.np-ribbon .np-ribbon-glow   { stroke-width: 16; opacity: .13; }
.np-ribbon .np-ribbon-strand { stroke-width: 1.1; opacity: .75; }
.np-ribbon .np-ribbon-hot    { stroke: #fff; stroke-width: 3; opacity: .9; transition: opacity .6s; }
.np-written .np-ribbon-hot    { opacity: 0; }   /* the tip hands over to the logo */

/* the comet head and the packets riding the ribbon (transform only) */
.np-ribbon-head,
.np-packet {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 0;
    border-radius: 50%;
    pointer-events: none;
    will-change: transform;
    opacity: 0;
    transition: opacity .35s;
}
.np-ribbon-head {
    width: 56px;
    height: 56px;
    margin: -28px 0 0 -28px;
    background: radial-gradient(circle, #fff 0 7%, rgba(255, 95, 162, .7) 18%, rgba(139, 15, 163, .22) 42%, transparent 70%);
}
.np-packet {
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    background: radial-gradient(circle, #fff 0 16%, rgba(33, 222, 222, .75) 34%, transparent 70%);
}
.np-ribbon-head.np-on,
.np-packet.np-on { opacity: 1; }

/* the section underline the ribbon passes through lights up */
.np-anchor {
    transition: width .7s var(--np-ease), border-color .5s, box-shadow .7s;
}
.np-anchor.np-lit {
    width: 120px !important;
    border-bottom-color: transparent !important;
    border-image: linear-gradient(90deg, var(--np-magenta), var(--np-violet), var(--np-blue)) 1;
    background: linear-gradient(90deg, var(--np-magenta), var(--np-violet), var(--np-blue)) !important;
    box-shadow: 0 14px 24px -14px rgba(233, 18, 112, .9);
}

/* ...and its title takes the hit: a short RGB-split glitch */
.np-hit { animation: np-hit .9s var(--np-ease); }
@keyframes np-hit {
    0%   { transform: skewX(-14deg) scale(1.08); text-shadow: -8px 0 var(--np-magenta), 8px 0 var(--np-cyan); }
    12%  { transform: skewX(8deg) translateX(-4px); text-shadow: 6px 0 var(--np-magenta), -6px 0 var(--np-cyan); }
    24%  { transform: skewX(-3deg) translateX(2px); text-shadow: -3px 0 var(--np-magenta), 3px 0 var(--np-cyan); }
    40%  { transform: none; text-shadow: 0 0 22px rgba(233, 18, 112, .8); }
    100% { text-shadow: 0 0 0 rgba(233, 18, 112, 0); }
}

/* spark bursts */
.np-burst {
    position: absolute;
    width: 0;
    height: 0;
    z-index: 2;
    pointer-events: none;
}
.np-burst i {
    position: absolute;
    left: -2.5px;
    top: -2.5px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--c);
    box-shadow: 0 0 8px var(--c);
    animation: np-spark .8s cubic-bezier(.12, .8, .3, 1) forwards;
}
@keyframes np-spark {
    to { transform: translate(var(--dx), var(--dy)) scale(.15); opacity: 0; }
}

/* lightning arcs from the tip to the elements it passes... */
.np-ribbon .np-arc path {
    stroke-dasharray: 1 1;
    stroke-dashoffset: 1;
    animation: np-arc .55s ease-out forwards;
}
.np-ribbon .np-arc path:first-child { stroke: rgba(233, 18, 112, .5); stroke-width: 7; }
.np-ribbon .np-arc path:last-child  { stroke: #fff; stroke-width: 1.6; }
@keyframes np-arc {
    30% { stroke-dashoffset: 0; opacity: 1; }
    45% { stroke-dashoffset: 0; opacity: .25; }
    60% { stroke-dashoffset: 0; opacity: 1; }
    to  { stroke-dashoffset: 0; opacity: 0; }
}
/* ...which light up, glowing on the side facing the ribbon (no 0%/100%: the element's
   own shadow is the start and the end) */
.np-surge { animation: np-surge 1.1s var(--np-sd, 0s) var(--np-ease); }
@keyframes np-surge {
    22% {
        box-shadow: 0 0 0 2px rgba(255, 95, 162, .85), var(--np-sx, 0) 0 38px 0 rgba(233, 18, 112, .55),
                    0 0 60px 6px rgba(139, 15, 163, .25);
    }
}
.np-card.np-surge::after { animation: np-surge-ring 1.2s var(--np-sd, 0s) linear; }
@keyframes np-surge-ring {
    15%, 70% { opacity: 1; }
    to { opacity: 0; --np-a: 360deg; }
}

/* key words in the copy (.np-key): a highlighter stroke sweeps under them as the ribbon
   passes, with a short RGB flicker; scrolling back above them wipes it off again */
.np-motion .np-key {
    border-radius: 3px;
    background: linear-gradient(90deg, rgba(233, 18, 112, .85), rgba(139, 15, 163, .8) 55%, rgba(33, 222, 222, .7))
                no-repeat 0 100% / 0% 48%;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    transition: background-size .8s var(--np-ease), color .5s, text-shadow .5s;
}
.np-motion .np-key.np-key-on {
    background-size: 100% 48%;
    color: #fff;
    text-shadow: 0 0 16px rgba(233, 18, 112, .55);
}
.np-key-hit { animation: np-key-hit .7s var(--np-ease); }
@keyframes np-key-hit {
    0%  { text-shadow: -5px 0 var(--np-magenta), 5px 0 var(--np-cyan), 0 0 20px #fff; }
    20% { text-shadow: 4px 0 var(--np-magenta), -4px 0 var(--np-cyan); }
    40% { text-shadow: -2px 0 var(--np-magenta), 2px 0 var(--np-cyan); }
}

/* the nav mirrors the ribbon: a progress line with a comet, and the current section lit */
.np-motion #menu::after,
.np-motion #menu::before {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    pointer-events: none;
}
.np-motion #menu::after {
    width: 100%;
    height: 2px;
    transform-origin: 0 50%;
    transform: scaleX(var(--np-p, 0));
    background: linear-gradient(90deg, var(--np-magenta), var(--np-violet) 40%, var(--np-blue) 75%, var(--np-cyan));
}
.np-motion #menu::before {
    width: 26px;
    height: 6px;
    margin: 0 0 -2px -24px;
    border-radius: 50%;
    background: radial-gradient(closest-side, #fff, rgba(255, 95, 162, .6) 50%, transparent);
    transform: translateX(calc(var(--np-p, 0) * 100vw));
    opacity: min(1, calc(var(--np-p, 0) * 40));
}
#menu a.np-nav-on {
    color: #fff !important;
    text-shadow: 0 0 14px rgba(233, 18, 112, .9), -1px 0 rgba(33, 222, 222, .6), 1px 0 rgba(233, 18, 112, .6);
    transition: text-shadow .4s;
}

/* finale: the ribbon writes the Netpack "N" into an empty box marked data-ribbon-finale.
   Leave ~80px clear below it: the ribbon swings in underneath before rising into the N. */
.np-signature {
    width: 170px;
    height: 135px;
    margin: 40px auto 80px;
}
.np-ribbon .np-finale,
.np-ribbon .np-finale-glow {
    stroke: url(#np-logo-grad);
    stroke-dasharray: var(--np-f, 0 99999);   /* invisible until JS has measured it */
    stroke-dashoffset: var(--np-f, 0);
}
.np-ribbon .np-finale      { stroke-width: 10; }
.np-ribbon .np-finale-glow { stroke-width: 34; opacity: .16; }
.np-written .np-finale,
.np-written .np-finale-glow {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 1.6s cubic-bezier(.65, 0, .35, 1);   /* only animate drawing in */
}
.np-written .np-finale-glow { animation: np-breathe 3.2s 1.6s ease-in-out infinite; }
@keyframes np-breathe { 50% { opacity: .4; } }

/* ---------- 4. Reveal (one observer, staggered per group) ---------- */
@media (prefers-reduced-motion: no-preference) {
    .np-motion .np-reveal {
        transition: opacity .8s var(--np-ease), transform .8s var(--np-ease);
        transition-delay: calc(var(--np-i, 0) * 80ms);
    }
    .np-motion .np-reveal:not(.np-in) {
        opacity: 0;
        transform: translateY(32px) scale(.97);
    }
    /* carousels clone and slide their items: never hide those */
    .np-motion .slick-slide .np-reveal { opacity: 1; transform: none; }
}

/* ---------- 5. Cards: tilt, cursor spotlight, ribbon border ---------- */
@property --np-a {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0deg;
}
.np-card { position: relative; isolation: isolate; }
.np-motion .np-card:not(.np-reveal),
.np-motion .np-card.np-done {
    transition: transform .35s var(--np-ease), box-shadow .35s;
    transition-delay: 0s;
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .np-card:not(.np-reveal):hover,
    .np-card.np-done:hover {
        transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(-8px);
        box-shadow: 0 34px 60px -28px rgba(233, 18, 112, .5);
    }
}
.np-card::before,
.np-card::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    opacity: 0;
    transition: opacity .35s;
}
.np-card::before {
    background: radial-gradient(380px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, .13), rgba(233, 18, 112, .07) 40%, transparent 65%);
}
.np-card::after {
    padding: 2px;
    background: conic-gradient(from var(--np-a), transparent 0 55%, var(--np-magenta), var(--np-violet), var(--np-blue), var(--np-cyan), transparent 95%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
}
.np-card:hover::before,
.np-card:hover::after,
.np-card:focus-within::after { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
    .np-card:hover::after,
    .np-card:focus-within::after { animation: np-spin 2.2s linear infinite; }
}
@keyframes np-spin { to { --np-a: 360deg; } }

/* ---------- reduced motion: keep the look, drop the movement ---------- */
@media (prefers-reduced-motion: reduce) {
    .np-aurora::before, .np-aurora::after { animation: none; }
    .np-ribbon-head, .np-packet { display: none; }
    .np-written .np-finale, .np-written .np-finale-glow { transition: none; }
    .np-anchor { transition: none; }
    .np-motion #menu::before { display: none; }
}
