/* ==========================================================================
   Logic Loop — motion primitives
   Reveals are driven by IntersectionObserver adding .is-in (see script.js).
   Everything degrades to "visible, no motion" under prefers-reduced-motion.
   ========================================================================== */

/* Scroll reveal ----------------------------------------------------------- */

[data-reveal] {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
    transition:
        opacity var(--dur) var(--ease),
        transform var(--dur) var(--ease);
    transition-delay: var(--reveal-delay, 0ms);
    will-change: opacity, transform;
}

[data-reveal="fade"] {
    transform: none;
}

[data-reveal="left"] {
    transform: translate3d(-26px, 0, 0);
}

[data-reveal="scale"] {
    transform: scale(.965);
}

.is-in[data-reveal] {
    opacity: 1;
    transform: none;
}

/* Line-by-line masked reveal for display headings ------------------------- */

.lines {
    display: block;
}

.line {
    display: block;
    overflow: hidden;
}

.line > span {
    display: block;
    transform: translate3d(0, 105%, 0);
    transition: transform .95s var(--ease);
    transition-delay: var(--line-delay, 0ms);
    will-change: transform;
}

.is-in .line > span {
    transform: none;
}

/* Marquee ----------------------------------------------------------------- */

@keyframes ll-marquee {
    from {
        transform: translate3d(0, 0, 0);
    }

    to {
        transform: translate3d(-50%, 0, 0);
    }
}

/* Count-up / misc --------------------------------------------------------- */

@keyframes ll-fade-up {
    from {
        opacity: 0;
        transform: translate3d(0, 14px, 0);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes ll-pulse-ring {
    0% {
        transform: scale(.9);
        opacity: .55;
    }

    70% {
        transform: scale(1.6);
        opacity: 0;
    }

    100% {
        opacity: 0;
    }
}

@keyframes ll-drift {

    0%,
    100% {
        transform: translate3d(0, 0, 0);
    }

    50% {
        transform: translate3d(0, -14px, 0);
    }
}

@media (prefers-reduced-motion: reduce) {

    [data-reveal],
    .is-in[data-reveal] {
        opacity: 1 !important;
        transform: none !important;
    }

    .line > span {
        transform: none !important;
    }
}

/* Attention "boop" — used on the hero badge. Deliberately at rest for ~90% of
   the cycle: a continuous animation in a hero becomes wallpaper and is read as
   noise, whereas an occasional small movement keeps drawing the eye back. */
@keyframes ll-boop {
    0%, 86%, 100% {
        transform: translate3d(0, 0, 0) scale(1);
    }

    90% {
        transform: translate3d(0, -4px, 0) scale(1.035);
    }

    94% {
        transform: translate3d(0, 0, 0) scale(.995);
    }

    97% {
        transform: translate3d(0, -1px, 0) scale(1.008);
    }
}

/* Flow diagram — "The problem". A packet crosses the rail, then the line runs
   out and it fades before arriving anywhere. */
/* The snowball: leaves week one small and arrives at year one twice the size,
   picking up three full turns on the way. */
@keyframes ll-snowball {
    0% {
        left: 0;
        opacity: 0;
        transform: scale(.32) rotate(0deg);
    }

    7% {
        opacity: .95;
    }

    86% {
        opacity: .95;
    }

    100% {
        left: 100%;
        opacity: 0;
        transform: scale(2.1) rotate(1080deg);
    }
}
}


/* ── The worker, three stages of burnout ────────────────────────────────────
   Amplitudes are deliberately theatrical. The section previously read as too
   subtle, so rotations here are 20-45 degrees and nothing ever holds still.
   ------------------------------------------------------------------------- */

/* Stage 1 — a nervous double-time bounce. Hinged at the feet, so it is pure
   vertical squash: the head travels several px, the shoes travel nothing. */
@keyframes ll-f1-bounce {
    0%, 100% { transform: scaleY(1); }
    30%      { transform: scaleY(.955); }
    62%      { transform: scaleY(1.035); }
}

/* Head whipping between the machine and the page: 45 degrees of travel, with
   three hurried nods at the page in the middle of every cycle. */
@keyframes ll-f1-snap {
    0%   { transform: rotate(-20deg); }
    9%   { transform: rotate(-24deg); }
    22%  { transform: rotate(21deg); }
    36%  { transform: rotate(14deg); }
    50%  { transform: rotate(21deg); }
    64%  { transform: rotate(14deg); }
    78%  { transform: rotate(21deg); }
    90%  { transform: rotate(-16deg); }
    100% { transform: rotate(-20deg); }
}

/* The scribbling arm: lifted while he reads, then a hard plunge onto the page
   and five fast strokes. 48 degrees of total sweep. */
@keyframes ll-f1-scribble {
    0%   { transform: rotate(-26deg); }
    16%  { transform: rotate(-22deg); }
    26%  { transform: rotate(16deg); }
    38%  { transform: rotate(6deg); }
    50%  { transform: rotate(18deg); }
    62%  { transform: rotate(7deg); }
    74%  { transform: rotate(19deg); }
    86%  { transform: rotate(-14deg); }
    100% { transform: rotate(-26deg); }
}

@keyframes ll-f1-brace {
    0%, 100% { transform: rotate(0deg); }
    30%      { transform: rotate(-9deg); }
    70%      { transform: rotate(6deg); }
}

@keyframes ll-f1-board {
    0%, 100% { transform: rotate(-6deg) translate3d(0, 0, 0); }
    30%      { transform: rotate(9deg) translate3d(1.5px, -5px, 0); }
    70%      { transform: rotate(-11deg) translate3d(-1px, 4px, 0); }
}

/* Handwriting appearing and being wiped, so the page is never done. */
@keyframes ll-f1-scrawl {
    0%       { stroke-dashoffset: 14; }
    35%, 55% { stroke-dashoffset: 0; }
    90%, 100% { stroke-dashoffset: -14; }
}

/* A sheet that never lands. */
@keyframes ll-f1-paper {
    0%, 100% { transform: rotate(-10deg) translate3d(0, 0, 0); }
    35%      { transform: rotate(16deg) translate3d(3px, -7px, 0); }
    70%      { transform: rotate(-6deg) translate3d(-2px, 4px, 0); }
}

/* Stage 2 — tearing his hair out. Biggest amplitude in the set, and the only
   figure with jerky attack-then-settle timing. Frustration is not smooth. */
@keyframes ll-f2-rock {
    0%   { transform: rotate(-7deg) translate3d(0, 0, 0); }
    14%  { transform: rotate(6deg) translate3d(0, -4px, 0); }
    28%  { transform: rotate(1deg) translate3d(0, -1px, 0); }
    42%  { transform: rotate(8deg) translate3d(0, -3.5px, 0); }
    56%  { transform: rotate(-4deg) translate3d(0, 0, 0); }
    70%  { transform: rotate(-8deg) translate3d(0, -3px, 0); }
    86%  { transform: rotate(4deg) translate3d(0, -1px, 0); }
    100% { transform: rotate(-7deg) translate3d(0, 0, 0); }
}

@keyframes ll-f2-lean {
    0%, 100% { transform: rotate(-6deg); }
    45%      { transform: rotate(7deg); }
    72%      { transform: rotate(-2deg); }
}

/* A 26-degree "no-no-no" head shake at ~2.4Hz, riding on the slower lean. */
@keyframes ll-f2-shake {
    0%   { transform: rotate(-13deg); }
    6%   { transform: rotate(13deg); }
    32%  { transform: rotate(9deg); }
    38%  { transform: rotate(-14deg); }
    64%  { transform: rotate(-10deg); }
    70%  { transform: rotate(12deg); }
    96%  { transform: rotate(8deg); }
    100% { transform: rotate(-13deg); }
}

@keyframes ll-f2-tuft {
    0%, 100% { transform: rotate(-16deg) scaleY(1); }
    30%      { transform: rotate(18deg) scaleY(1.14); }
    62%      { transform: rotate(-9deg) scaleY(.94); }
}

/* Strands torn loose, flying off and resetting. */
@keyframes ll-f2-strand-a {
    0%, 8%   { opacity: 0; transform: translate3d(0, 0, 0) rotate(0deg); }
    18%      { opacity: 1; transform: translate3d(-4px, -5px, 0) rotate(-22deg); }
    58%      { opacity: .5; transform: translate3d(-16px, -15px, 0) rotate(-52deg); }
    80%, 100% { opacity: 0; transform: translate3d(-24px, -22px, 0) rotate(-74deg); }
}

@keyframes ll-f2-strand-b {
    0%, 12%  { opacity: 0; transform: translate3d(0, 0, 0) rotate(0deg); }
    22%      { opacity: 1; transform: translate3d(5px, -4px, 0) rotate(20deg); }
    62%      { opacity: .5; transform: translate3d(17px, -13px, 0) rotate(48deg); }
    84%, 100% { opacity: 0; transform: translate3d(25px, -20px, 0) rotate(70deg); }
}


/* Stage 3 — the tantrum. Hardest, fastest motion in the set. The arm cocks
   back, hangs at the top, then slams down on a steep ease-in and recoils off
   the boards. 0% and 100% match so the loop closes invisibly. */
@keyframes ll-f3-hammer {
    0%   { transform: rotate(-26deg); }
    26%  { transform: rotate(-32deg); animation-timing-function: cubic-bezier(.85, 0, .95, .5); }
    44%  { transform: rotate(0deg); }
    54%  { transform: rotate(-8deg); }    /* bounce off the floor */
    62%  { transform: rotate(-2deg); }
    100% { transform: rotate(-26deg); }
}

/* The heels do the same on a longer clock. Positive, because the legs point
   left from the hip, and shallower, because a 24-unit leg is a longer lever
   than an arm and buys the same travel from fewer degrees. */
@keyframes ll-f3-drum {
    0%   { transform: rotate(15deg); }
    26%  { transform: rotate(18deg); animation-timing-function: cubic-bezier(.88, 0, .95, .5); }
    44%  { transform: rotate(0deg); }
    54%  { transform: rotate(5deg); }
    62%  { transform: rotate(1deg); }
    100% { transform: rotate(15deg); }
}

/* He rocks back and forth on his seat, hardest right after the fists land. */
@keyframes ll-f3-rock {
    0%, 100% { transform: rotate(4deg); }
    24%      { transform: rotate(6deg); }
    44%      { transform: rotate(-5deg); }
    58%      { transform: rotate(-1deg); }
    76%      { transform: rotate(2.5deg); }
}

/* The head whips side to side at twice the arm tempo. */
@keyframes ll-f3-thrash {
    0%   { transform: rotate(-16deg); }
    50%  { transform: rotate(16deg); }
    100% { transform: rotate(-16deg); }
}

/* Splash ticks: absent until the frame of impact, then a hard flash outward
   that is gone before the limb lifts again. */
@keyframes ll-f3-hit {
    0%, 38%   { opacity: 0; transform: scale(.35); }
    44%       { opacity: .95; transform: scale(1); }
    58%       { opacity: .35; transform: scale(1.55); }
    66%, 100% { opacity: 0; transform: scale(1.9); }
}


/* The comet running the loop. One slow, unbroken revolution: the section is
   about a cycle that never stops, so this is the one animation on the page
   with no rest beat in it. */
@keyframes ll-loop-orbit {
    to { transform: rotate(360deg); }
}

/* Each stage's readout arriving in the middle of the ring. */
@keyframes ll-loop-in {
    from { opacity: 0; transform: translate3d(0, 8px, 0); }
    to   { opacity: 1; transform: none; }
}


/* ── What we build ──────────────────────────────────────────────────────── */

/* The dwell clock, drawn as the underline of the active row. */
@keyframes ll-w-fill {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

@keyframes ll-w-panel {
    from { opacity: 0; transform: translate3d(0, 10px, 0); }
    to   { opacity: 1; transform: none; }
}

/* 01 — the hour filling in, bar by bar, then clearing to run again. */
@keyframes ll-w-bar {
    0%        { transform: scaleY(0); }
    18%, 82%  { transform: scaleY(1); }
    100%      { transform: scaleY(0); }
}

@keyframes ll-w-ring {
    0%        { stroke-dashoffset: 213.6; }
    30%, 84%  { stroke-dashoffset: 47; }
    100%      { stroke-dashoffset: 213.6; }
}

@keyframes ll-w-blink {
    0%, 100% { opacity: 1; }
    50%      { opacity: .45; }
}

/* 02 — the trace draws toward the threshold; the marker lands before it. */
@keyframes ll-w-trace {
    0%        { stroke-dashoffset: 340; }
    55%, 88%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 340; }
}

@keyframes ll-w-flag {
    0%, 34%   { opacity: 0; transform: scale(.4); }
    44%       { opacity: 1; transform: scale(1.15); }
    52%, 88%  { opacity: 1; transform: scale(1); }
    100%      { opacity: 0; transform: scale(1); }
}

/* 03 — units crossing the scanner, and the flagged one traced back. */
@keyframes ll-w-belt {
    0%       { transform: translateX(-30px); opacity: 0; }
    8%       { opacity: 1; }
    88%      { opacity: 1; }
    100%     { transform: translateX(330px); opacity: 0; }
}

@keyframes ll-w-scan {
    0%, 100% { opacity: .18; }
    50%      { opacity: .75; }
}

@keyframes ll-w-back {
    0%, 10%   { opacity: 0; stroke-dashoffset: 190; }
    35%, 72%  { opacity: .9; stroke-dashoffset: 0; }
    88%, 100% { opacity: 0; stroke-dashoffset: 0; }
}

/* 04 — levels moving as the crate travels, and the counts agreeing. */
@keyframes ll-w-stock {
    0%, 100% { transform: scaleY(1); }
    45%      { transform: scaleY(.55); }
    70%      { transform: scaleY(1.12); }
}

@keyframes ll-w-move {
    0%       { transform: translate3d(30px, 132px, 0); opacity: 0; }
    12%      { opacity: 1; }
    50%      { transform: translate3d(120px, 64px, 0); }
    82%      { transform: translate3d(200px, 128px, 0); opacity: 1; }
    100%     { transform: translate3d(216px, 132px, 0); opacity: 0; }
}

@keyframes ll-w-tick {
    0%, 30%   { stroke-dashoffset: 30; }
    48%, 88%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 30; }
}

/* 05 — history draws, then the projection and its confidence open out. */
@keyframes ll-w-hist {
    0%        { stroke-dashoffset: 190; }
    40%, 86%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 190; }
}

@keyframes ll-w-fore {
    0%, 40%   { opacity: 0; }
    62%, 86%  { opacity: 1; }
    100%      { opacity: 0; }
}

@keyframes ll-w-band {
    0%, 42%   { opacity: 0; transform: scaleX(0); }
    68%, 86%  { opacity: .1; transform: scaleX(1); }
    100%      { opacity: 0; transform: scaleX(1); }
}

/* 06 — the links close, then data crosses them. */
@keyframes ll-w-link {
    0%        { stroke-dashoffset: 220; }
    35%, 88%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 220; }
}

@keyframes ll-w-pkt {
    0%       { offset-distance: 0%; opacity: 0; }
    10%      { opacity: 1; }
    85%      { opacity: 1; }
    100%     { offset-distance: 100%; opacity: 0; }
}


/* ── Capabilities ───────────────────────────────────────────── */

/* 01 — paper, spreadsheet and machine drifting together and giving way to
   the single panel that replaces them. */
@keyframes ll-c-gather {
    0%, 12%   { opacity: 1; transform: none; }
    52%       { opacity: .5; transform: translate3d(34px, 0, 0) scale(.86); }
    68%, 88%  { opacity: 0; transform: translate3d(62px, 0, 0) scale(.6); }
    100%      { opacity: 1; transform: none; }
}

@keyframes ll-c-trace {
    0%, 40%   { stroke-dashoffset: 70; }
    72%, 90%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 70; }
}

/* 02 — the operator and the two-step hand-off dropping out. */
@keyframes ll-c-leave {
    0%, 26%   { opacity: 1; }
    46%, 88%  { opacity: 0; }
    100%      { opacity: 1; }
}

@keyframes ll-c-close {
    0%, 30%   { stroke-dashoffset: 110; }
    52%, 90%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 110; }
}

@keyframes ll-c-flow {
    0%, 52%   { offset-distance: 0%; opacity: 0; }
    58%       { opacity: 1; }
    84%       { offset-distance: 100%; opacity: 1; }
    88%, 100% { offset-distance: 100%; opacity: 0; }
}

/* 03 — the hidden costs surfacing, then being engineered out. */
@keyframes ll-c-cost {
    0%       { transform: scaleY(0); }
    22%, 46% { transform: scaleY(1); }        /* surfaced */
    72%, 88% { transform: scaleY(.22); }      /* engineered out */
    100%     { transform: scaleY(0); }
}

/* The total drops as the bars come down. */
@keyframes ll-c-total {
    0%, 46%  { transform: translateY(0); }
    74%, 88% { transform: translateY(44px); }
    100%     { transform: translateY(0); }
}

/* 04 — the archive compressing as it starts driving something. */
@keyframes ll-c-stack {
    0%, 20%  { transform: scaleY(1); opacity: 1; }
    56%, 84% { transform: scaleY(.8); opacity: .5; }
    100%     { transform: scaleY(1); opacity: 1; }
}

@keyframes ll-c-beam {
    0%, 24%   { opacity: 0; stroke-dashoffset: 90; }
    52%, 84%  { opacity: 1; stroke-dashoffset: 0; }
    100%      { opacity: 0; stroke-dashoffset: 90; }
}

/* The forecast's targets, landing as their beams reach them. */
@keyframes ll-c-tip {
    0%, 46%   { opacity: 0; }
    58%, 84%  { opacity: 1; }
    100%      { opacity: 0; }
}


/* ── Industry 5.0 ───────────────────────────────────────────── */

/* 01 — the panels break their row and take up station around the operator.
   The figure itself never moves: that is the whole claim. */
@keyframes ll-i-surround {
    0%, 12%   { opacity: 1; transform: none; }
    30%       { opacity: .18; }
    50%, 84%  { opacity: 1; transform: translate3d(var(--tx, 0), var(--ty, 0), 0); }
    93%       { opacity: .18; }
    100%      { opacity: 1; transform: none; }
}

@keyframes ll-i-tie {
    0%, 44%   { stroke-dashoffset: 24; }
    66%, 86%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 24; }
}

/* 02 — the failed node and the straight run it took with it. */
@keyframes ll-i-drop {
    0%, 30%   { opacity: 1; }
    46%, 88%  { opacity: .16; }
    100%      { opacity: 1; }
}

@keyframes ll-i-x {
    0%, 32%   { opacity: 0; }
    48%, 82%  { opacity: 1; }
    92%, 100% { opacity: 0; }
}

@keyframes ll-i-reroute {
    0%, 36%   { stroke-dashoffset: 190; }
    58%, 88%  { stroke-dashoffset: 0; }
    100%      { stroke-dashoffset: 190; }
}

/* Two tokens, one per route: the straight one runs while the chain is whole,
   the bypass one takes over once it is not. */
@keyframes ll-i-tok-a {
    0%       { offset-distance: 0%; opacity: 1; }
    28%      { offset-distance: 62%; opacity: 1; }
    34%, 100% { offset-distance: 62%; opacity: 0; }
}

@keyframes ll-i-tok-b {
    0%, 56%   { offset-distance: 0%; opacity: 0; }
    62%       { opacity: 1; }
    88%       { offset-distance: 100%; opacity: 1; }
    92%, 100% { offset-distance: 100%; opacity: 0; }
}

/* 03 — the three meters retreating, with the measurement line following them
   down rather than a number being asserted over the top. */
@keyframes ll-i-meter {
    0%       { transform: scaleX(1); }
    30%      { transform: scaleX(1); }
    64%, 88% { transform: scaleX(.34); }
    100%     { transform: scaleX(1); }
}

@keyframes ll-i-gauge {
    0%, 26%  { transform: translateX(140px); }
    64%, 88% { transform: translateX(82px); }
    100%     { transform: translateX(140px); }
}

/* The WhatsApp tip, breathing just enough to catch the eye without nagging. */
@keyframes ll-wa-bob {
    0%, 100% { transform: translate3d(0, 0, 0); }
    50%      { transform: translate3d(0, -5px, 0); }
}
