/* =========================================================================
   THE ARRIVAL — one way that everything on this site opens.

   Taufik, 23 August 2026: "currently its sudden pop-up, no, make it have
   animation of the card spin and fly into the middle, then the card stop, and
   then background blur. [example, card on left side, make it fly to the
   middle, then only the background blur] make it smooth continuous. not
   sudden. take the time. make the viewer feel the anticipation, the wait.
   this apply as well to main pages cards / box / pop up. make it all standard
   same style of transition"

   ── WHAT WAS WRONG ──

   There were four ways to open a box on this site and no two agreed. The card
   popup faded its scrim and sprang its panel over 240ms. The knowledge-graph
   stage did nearly the same over 260ms. The milestone box had no transition at
   all — one frame it was not there, the next it was. The film panel threw
   itself at the screen in 620ms. And the card that had been pressed turned
   over WHERE IT SAT, 900ms of it, while the panel opened somewhere else at the
   same time. Two things moved at once, in different places, and neither
   explained the other. That is what "sudden" means here: not that it was fast,
   but that nothing carried the eye from the thing pressed to the thing opened.

   ── THE ORDER, AND WHY IT IS THIS ORDER ──

   1. THE DRAW      the card sinks and leans away from where it is going.
                    An eighth of a second of going nowhere. It is the whole
                    reason the flight reads as intended rather than as a jump.
   2. THE FLIGHT    it lifts, turns a full circle, and travels to the middle,
                    growing as it comes. Its back — the NEUON logo — passes the
                    eye halfway round.
   3. THE STOP      it lands and NOTHING happens. A fifth of a second of stillness.
                    This beat is the request. Blur arriving with the card would
                    put us back where we started.
   4. THE VEIL      only now does the page behind go soft.
   5. THE SETTLE    the panel takes the card's place as the card dissolves.

   About a second and a half from press to panel. That is deliberate and it is
   the instruction: the wait IS the effect.

   ── THE BACK IS A REAL BACK FACE HERE ──

   public/assets/js/flip.js could not do that. It turned the card in place, and
   a card in place is inside `.acard { overflow: hidden }` — a box that clips
   its content computes `transform-style: flat`, so a child at rotateY(180deg)
   is a 2-D mirror rather than the other side of anything, and
   `backface-visibility` never comes into play. It had to fake the back with a
   counter-mirrored overlay.

   The thing that flies is not the card. It is a copy of the card on a layer of
   OUR making, above everything, and we choose its overflow. So it gets two
   honest faces and the browser decides which one you are looking at.
   ========================================================================= */

:root {
  /* Every box on the site reads these. Change a number here and the front
     page, the eight chapters, the timeline and the film panel all change
     together — which is the point of there being one file. */
  --arr-fly: 780ms;      /* the draw and the flight, together */
  /* THE STILLNESS AFTER IT LANDS. It was 200ms, and with the old curves the
     card arrived at a dead stop and then sat there — 330ms of nothing moving
     between the landing and the panel, which reads as a hang rather than as a
     beat. The flight now GLIDES to rest, so the pause is the tail of the
     movement instead of a gap after it, and it needs less of it. */
  --arr-hold: 150ms;     /* it has landed, and nothing moves */
  --arr-veil: 380ms;     /* the page behind going soft */
  --arr-lead: 90ms;      /* how far into the veil the panel starts */
  --arr-settle: 300ms;   /* the panel taking the card's place */
  --arr-out: 260ms;      /* leaving, which is not a ceremony */
  --arr-ease: cubic-bezier(0.16, 0.84, 0.28, 1);
}

/* ── THE LAYER THE CARD FLIES ON ──────────────────────────────────────────
   Above every overlay on the site — the milestone box is at 90, the graph
   stage at 180, the card popup at 200, the film panel at 250. The thing in
   flight is in front of all of them by definition: it is what is being
   carried TO one of them. */
.arrive {
  position: fixed; inset: 0; z-index: 900;
  pointer-events: none;          /* it is a picture of a card, not a card */
  perspective: 1500px;
}

/* ── AND THE SHADOW IS ON THE FACES, NOT ON THIS ──
   It was `filter: drop-shadow(...)` here, which is the nicer way to shadow a
   thing that turns. It cost the whole effect and said nothing: a `filter`
   makes an element a GROUPING element, and a grouping element's
   `transform-style` computes to `flat` no matter what is written. The two
   faces stopped being two sides of a card and became two flat pictures, so at
   190 degrees the reader saw the FRONT of the card written backwards instead
   of the NEUON back.

   It is the same trap as `.acard { overflow: hidden }` in flip.js, wearing a
   different property — and it looked correct in the computed styles both
   times. A `box-shadow` on each face is painted per face and does not group
   anything. */
.arrive__card {
  position: absolute;
  transform-style: preserve-3d;
  will-change: transform;
}

.arrive__face {
  position: absolute; inset: 0;
  border-radius: inherit;
  overflow: hidden;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  box-shadow: 0 26px 55px rgba(0, 0, 0, 0.5);
}
/* the copy of the card fills the face exactly, whatever it was before */
.arrive__face--front > * {
  width: 100%; height: 100%;
  margin: 0;
  transform: none;
}

/* ── THE BACK OF A NEUON CARD ──
   Only which way it faces, and how the logo sits in it. THE PAINT IS NOT HERE
   and must not come back: it is written once in shine.css, shared with the
   card that turns in place and with the foil pack, so that a reader who
   presses one card and then another is looking at the same object both times.
   This file is read AFTER shine.css, so a `background` here would silently
   win and there would be two backs again. */
.arrive__face--back {
  transform: rotateY(180deg);
  display: grid; place-items: center;
}

/* ── THE VEIL ─────────────────────────────────────────────────────────────
   Every scrim on the site is now this one element, and it starts CLEAR. The
   four components used to each paint their own, at full strength, from the
   first frame — which is exactly the thing being fixed: the blur cannot be
   there before the card has landed, because arriving is what the blur is a
   reaction to.

   ── AND THE BLUR RADIUS IS FIXED, NOT ANIMATED ──

   It was written as `blur(0px)` transitioning to `blur(14px)`, which is the
   obvious way and the expensive one: every frame of that transition is a
   fresh full-screen gaussian blur at a radius the browser has never computed
   before, so nothing can be reused and the work lands in the same 400ms as
   the panel fading in. On a page carrying a hundred-odd cards it was a
   visible stutter.

   The radius is constant now and only `opacity` moves. An element at opacity
   0 is not painted at all, so there is no blur until there needs to be one;
   from then on it is the SAME blur every frame, which is cheap, and the
   transition cross-fades the sharp page into the blurred one. It looks the
   same and it costs a fraction. */
.veil {
  position: absolute; inset: 0;
  opacity: 0;
  background: color-mix(in srgb, var(--black) 58%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  transition: opacity var(--arr-veil) ease;
}
.is-veiled > .veil,
.is-veiled .veil { opacity: 1; }
/* Every scrim on this site closes what is over it, so the veil is pressable
   and says so — rather than five components each remembering to say it. */
.veil { cursor: pointer; pointer-events: auto; }

/* …except the pack's, which closes nothing. A stray press there would throw
   away the card somebody had just torn open, and pack.js already carries a
   note about how easily that happened when clicking away dropped the pick.
   "I know this one, pull another" is the only way back out of it. */
.veil--still { cursor: default; pointer-events: none; }

/* ── THE PACK'S VEIL IS A DIFFERENT ANIMAL ────────────────────────────────
   Every other scrim on this site covers the viewport, so a dark ground and a
   hard edge are invisible: there is no edge to see. This one covers a BAND in
   the middle of a white section — the pack stage — and the first version of
   it read as somebody having dropped a grey rectangle onto the page. Two
   things fix that and both are necessary:

     light      the section is white. A 58%-black scrim on white is a grey
                box; a white one is the ring going soft, which is the effect.
     no edges   a radial mask fades the scrim out on every side, so it has no
                boundary anywhere for the eye to catch on.

   It spills 7rem above and below and only 2rem to the sides. The obvious
   -50vw for a full-width wash is a horizontal scrollbar waiting to happen on
   some page or other, and the stage is nearly the width of the screen
   already. */
.pack__card .veil {
  inset: -7rem -2rem;
  background: color-mix(in srgb, var(--white) 62%, transparent);
  -webkit-mask-image: radial-gradient(112% 104% at 50% 46%,
    #000 46%, rgba(0, 0, 0, 0.55) 74%, transparent 100%);
  mask-image: radial-gradient(112% 104% at 50% 46%,
    #000 46%, rgba(0, 0, 0, 0.55) 74%, transparent 100%);
}
/* ── AND THE WAY BACK HAS TO BE ABOVE THE VEIL ──
   Taufik, 23 August 2026, of an opened pack: "the button missing".

   It was not missing. It was there, laid out, at full opacity — and painted
   UNDER a white wash and a fourteen-pixel blur, which is a thorough way to
   hide something. `.pack__again` is `position: static`, and static in-flow
   content paints in an earlier step than every positioned sibling; the veil
   is positioned and covers the whole area. Before this file existed there was
   no full-size positioned sibling for it to lose to.

   `position: relative` is the whole fix: it puts the button in the same paint
   step as the veil, and it comes after it in the markup, so it wins.

   It still fades in with the card rather than ahead of it — a way out that
   appears before the thing it is a way out of is a way out of nothing. */
.pack__card .pack__again {
  position: relative;
  z-index: 1;
  opacity: 0;
  transition: opacity var(--arr-settle) ease;
}
.pack__card.is-here .pack__again { opacity: 1; }

/* darker behind the graph stage, which is a working surface rather than a
   page you are reading over */
.gstage .veil { background: color-mix(in srgb, var(--black) 76%, transparent); }
.reelopen .veil { background: color-mix(in srgb, var(--black) 64%, transparent); }

/* ── THE PANEL ────────────────────────────────────────────────────────────
   It does not spring, it does not throw itself, it does not bounce. It takes
   the place of a card that has just stopped moving, so the only thing left to
   do is be there. A twentieth of a scale and a fade, over a third of a second.

   This is the rule that replaces `panel-in` in shine.css, `card-throw` and
   `reel-open` in site.css, and the four separate `transform` pairs the
   components each carried. */
[data-arrive-panel] {
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  opacity: 0;
  transform: scale(0.965) rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
  transition: opacity var(--arr-settle) ease,
              transform var(--arr-settle) var(--arr-ease);
}
.is-here [data-arrive-panel] {
  opacity: 1;
  transform: scale(1) rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
}

/* leaving: the panel goes first, then the blur lifts, then the card flies
   home. The reverse of arriving, at rather more than twice the speed —
   anticipation is for going in. */
.is-going [data-arrive-panel] {
  opacity: 0;
  transform: scale(0.975) rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
  transition-duration: 180ms;
}

/* ── AND THEN IT IS A CARD, AND BEHAVES LIKE ONE ──────────────────────────
   Taufik, 23 August 2026: "all these popup, make it same as the card that
   can be tilt and have the shining effect and shining effect mouse over".

   The panel a card flies into was, once it landed, a flat white rectangle —
   the one object on the site that had stopped being an object. Now it leans
   towards the pointer, catches the sweep, and takes the gloss under the
   cursor, exactly as the card that opened it does.

   `--tilt-x` and `--tilt-y` above are why the arrival's scale and the lean
   are ONE transform with one source of truth: tilt.js writes two numbers and
   never touches `style.transform`, so the two cannot fight over who wrote
   last. Same arrangement as `.acard` in shine.css — see the note at the top
   of public/assets/js/tilt.js.

   The perspective goes on the PARENT. On the panel itself a rotation is an
   orthographic squash, which reads as the panel being scaled oddly rather
   than tipped. 1100px because these are far bigger than a card: the 600px a
   rail card uses would make a 30rem panel look like it was on a turntable. */
.pop, .mdet, .pack__card { perspective: 1100px; }

/* WHILE THE POINTER IS ON IT there is no transition on the transform, or the
   panel lags behind the cursor by the length of the ease. Letting go DOES
   ease — that one is a release rather than a follow. */
[data-arrive-panel].is-tilting {
  transition: box-shadow 0.5s linear;
}

/* ── THE GLOSS ──
   A soft light where the pointer is, the way a foil card catches a lamp.
   `soft-light` rather than `screen`: on a white panel screen washes the words
   out, and the point is the surface catching light, not the panel going
   brighter.

   On the panels that also carry `.shine` this is the SAME pseudo-element the
   sweep uses — deliberately, and for the reason shine.css gives about cards:
   two lights on one object at once reads as a glitch. arrive.css is read
   after shine.css, so this takes it over for as long as a hand is on it. */
[data-arrive-panel].is-tilting::after {
  content: '';
  position: absolute; inset: 0; z-index: 4;
  pointer-events: none; border-radius: inherit;
  background-image:
    radial-gradient(circle at var(--gloss-x, 50%) var(--gloss-y, 50%),
      rgba(255, 255, 255, 0.42) 0%,
      rgba(255, 255, 255, 0.12) 28%,
      transparent 62%);
  background-size: 100% 100%;
  background-position: 0 0;
  animation: none;
  opacity: 1;
  mix-blend-mode: soft-light;
}
/* and the shadow moves out under it, which is what sells the lean */
.pop__panel.is-tilting, .mdet__box.is-tilting {
  box-shadow: 0 46px 96px -28px rgba(0, 0, 0, 0.62);
}
.is-going > .veil, .is-going .veil { transition-duration: var(--arr-out); }

/* ── THE PAGE HOLDS ITS BREATH WHILE SOMETHING IS ARRIVING ────────────────
   Taufik: "the movement not smooth ... got stuck/delay".

   The front page carries a hundred and twenty-eight cards and every one of
   them runs `shine-sweep` — a moving gradient, forever, whether or not
   anybody is looking at it. Add the headline sweeps, the pack foils, the
   card holo and the reel's forty-eight-second marquee, and there are well
   over a hundred CSS animations painting at once. They were all still going
   during the flight, competing for the same frames as the one movement the
   reader is actually watching, and then carrying on UNDER the veil — where a
   `backdrop-filter` re-reads them every frame for nothing at all.

   None of it is missed. It goes quiet the moment a card leaves its place and
   comes back when the box is closed. `animation-play-state` rather than
   `animation: none`, so every sweep resumes where it was rather than jumping
   back to the start of its cycle as the panel closes. */
.is-arriving .shine::after,
.is-arriving .tshine::after,
.is-arriving .reel__track,
.is-arriving .ppack__shine,
.is-arriving .ppack__glow::after,
.is-arriving .pcardx__holo,
.is-arriving .pop__view,
.is-arriving .pop__view::after {
  animation-play-state: paused;
}
/* …except the panel that has just arrived. It is the one thing on the screen
   the reader IS looking at, and the rule above would otherwise hold its sweep
   still for as long as the box was open. */
.is-arriving [data-arrive-panel].shine::after { animation-play-state: running; }

/* ── MEASURING ────────────────────────────────────────────────────────────
   The card flies to where the panel WILL be, which means the panel has to be
   laid out before the flight starts. It is shown for no frames at all: the
   attribute comes off and this class goes on in the same block of script, and
   the browser paints neither. Every transition inside is killed for that
   instant so nothing starts running while it is being read. */
.is-measuring { opacity: 0 !important; pointer-events: none !important; }
.is-measuring *, .is-measuring *::before, .is-measuring *::after {
  transition: none !important; animation: none !important;
}

/* ── ASKED FOR LESS MOVEMENT ──────────────────────────────────────────────
   Somebody who has told their computer they do not want things flying across
   it has told us too. No flight, no turn, no drift — the panel is simply
   there, and the blur with it. arrive.js does not even build the card. */
@media (prefers-reduced-motion: reduce) {
  .arrive { display: none; }
  .veil, [data-arrive-panel] { transition: none; }
  [data-arrive-panel] { transform: none; }
}
