/* ============================================================
   GRAVITY — motion layer
   One physical rule: nothing decelerates into existence.
   Bodies arrive by accelerating, arrest, and settle.
   Bodies leave by accelerating away. Mass sets the timing.

   Calibrated at "Standard". Everything downstream is expressed
   in the six tokens below, so retuning the whole site is a
   matter of changing six numbers.
   ============================================================ */

:root {
  --g-dur: 520ms;      /* base fall time for a unit mass */
  --g-rise: 18px;      /* how far above its resting place a body starts */
  --g-over: 4px;       /* overshoot past rest before it settles back */
  --g-stagger: 42ms;   /* delay between neighbouring bodies */
  --g-squash: 0.988;   /* scale at the moment of release */
  --g-pull: 1;         /* multiplier on every pointer/well displacement */

  /* The two halves of a fall. Never one symmetrical ease. */
  --g-ease-infall: cubic-bezier(0.45, 0, 0.9, 0.35);
  --g-ease-arrest: cubic-bezier(0.22, 0.7, 0.28, 1);
  --g-ease-escape: cubic-bezier(0.5, 0, 1, 0.55);
}

/* ------------------------------------------------------------
   Infall — items arriving on a page.
   Pre-hidden only where scripting can bring them back.
   ------------------------------------------------------------ */

@media (scripting: enabled) {
  .g-body { opacity: 0; }
}

.g-body.g-in {
  animation: g-infall calc(var(--g-dur) * var(--g-m, 1)) both;
  animation-delay: var(--g-d, 0ms);
  will-change: transform, opacity;
}

@keyframes g-infall {
  0% {
    opacity: 0;
    transform: translate3d(0, calc(-1 * var(--g-rise) * var(--g-m, 1)), 0) scale(var(--g-squash));
    animation-timing-function: var(--g-ease-infall);
  }
  64% {
    opacity: 1;
    transform: translate3d(0, calc(var(--g-over) * var(--g-m, 1)), 0) scale(1);
    animation-timing-function: var(--g-ease-arrest);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

/* Once a body has landed it stays landed, including on scroll-back. */
.g-body.g-landed {
  opacity: 1;
  animation: none;
  will-change: auto;
}

/* how-it-works.html already reveals its journey steps with .in-view.
   Keep that contract, replace the fade-up with the fall curve. */
.journey-step { opacity: 0; transform: none; transition: none; }
.journey-step.in-view {
  animation: g-infall calc(var(--g-dur) * 1.1) both;
}

/* ------------------------------------------------------------
   The well — page to page.
   The link you click becomes the mass. The outgoing page falls
   into that point, the incoming page comes back out of it.
   Browsers without cross-document view transitions simply cut,
   exactly as they do today.
   ------------------------------------------------------------ */

@view-transition { navigation: auto; }

/* The header is not part of the page that falls; it persists. */
.site-header { view-transition-name: g-header; }

::view-transition-old(root) {
  animation: g-well-out calc(var(--g-dur) * 0.8) cubic-bezier(0.6, 0, 1, 1) both;
  transform-origin: var(--g-ox, 50%) var(--g-oy, 0%);
}

::view-transition-new(root) {
  animation: g-well-in calc(var(--g-dur) * 0.96) cubic-bezier(0.2, 0.8, 0.2, 1) both;
  transform-origin: var(--g-ox, 50%) var(--g-oy, 0%);
}

@keyframes g-well-out {
  to { transform: scale(0.72); opacity: 0; filter: blur(6px); }
}

@keyframes g-well-in {
  from { transform: scale(1.04); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* ------------------------------------------------------------
   Deflection — the pointer has mass too.
   Cards lean toward it on an inverse-square falloff, capped at
   6px, and the one underneath sinks into the sheet rather than
   lifting off it. A mass depresses a rubber sheet; it does not
   float above one.
   ------------------------------------------------------------ */

.g-sheet > .g-tile {
  transition:
    transform 420ms var(--g-ease-arrest),
    box-shadow 300ms ease,
    border-color 300ms ease,
    background 300ms ease;
}

/* Where the script drives the lean frame by frame, CSS must keep
   its hands off transform: two eases on one property fight, and
   the fight is what reads as a stutter. */
.g-sheet-live > .g-tile {
  transition:
    box-shadow 600ms ease,
    border-color 600ms ease,
    background 600ms ease;
}

.g-sheet > .g-tile.g-hot {
  box-shadow: inset 0 1px 3px rgba(23, 22, 15, 0.10);
}

/* Cards currently lift on hover. Invert them. */
.problem-card:hover {
  transform: scale(0.991);
  box-shadow: 3px 3px 0 rgba(20, 19, 13, 0.10);
}

/* ------------------------------------------------------------
   Escape velocity — removal.
   A ruled-out thing did not gently stop mattering. It was thrown
   out, and the survivors fall back down to close the gap.
   ------------------------------------------------------------ */

/* Outranks .g-body.g-in: a body can be told to leave while it is
   still arriving, and leaving always wins. */
.g-escaping,
.g-body.g-escaping {
  animation: g-escape calc(var(--g-dur) * 0.85) var(--g-ease-escape) both;
  pointer-events: none;
}

@keyframes g-escape {
  0%   { opacity: 1; transform: none; filter: blur(0); }
  35%  { opacity: 0.85; }
  100% {
    opacity: 0;
    transform: translate3d(calc(var(--g-rise) * 2.4), calc(var(--g-rise) * -1.1), 0)
               scale(calc(var(--g-squash) - 0.04));
    filter: blur(1.5px);
  }
}

.g-resettling,
.g-body.g-resettling {
  animation: g-resettle var(--g-dur) var(--g-ease-arrest) both;
}

@keyframes g-resettle {
  from { transform: translateY(calc(var(--g-gap, 46px) * -1)); }
  to   { transform: translateY(0); }
}

/* ------------------------------------------------------------
   Reduced motion — every specimen degrades to an instant cut.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .g-body,
  .g-body.g-in,
  .journey-step,
  .journey-step.in-view {
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
  }
  .g-sheet > .g-tile { transition: none !important; transform: none !important; }
  .g-escaping { animation-duration: 1ms !important; }
  .g-resettling { animation: none !important; }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
