/* ════════════════════════════════════════════════════════════════════════
   card-motion.css — cards arrive, and answer the pointer.
   service.tnaado.ca · no dependencies · tokens only
   ------------------------------------------------------------------------

   WHY THIS EXISTS

   Ethan: "i want aniamtions on the cards".

   The Tier 1 doors already move — every plate is flown in by the storm. The
   FLAT pages are where cards just sit: /pricing's build tiers, the sell pages'
   tier rails, the reference tables. Those are the ones this file is for.

   ------------------------------------------------------------------------
   THE FINISHED STATE IS THE AUTHORED DEFAULT

   Nothing here hides a card in CSS. A card is fully visible, fully readable and
   fully indexable with no JavaScript at all; js/card-motion.js adds
   `is-cm-pre` only after it has proved it runs, and removes it on intersection.
   That ordering is the whole safety property: a blocked, failed or slow script
   leaves a complete page rather than an empty one. The rebuild has already
   shipped the other failure mode once — a `<details>` panel whose prices were
   invisible until JS ran — and it is not worth repeating for decoration.

   ------------------------------------------------------------------------
   TWO MOTIONS, AND WHY EACH IS THE CHEAP KIND

   1. ARRIVAL. Opacity and a 14px translateY, staggered along the row. Both are
      compositor-only properties, so a row of four cards costs no layout and no
      paint — which matters on /pricing, a page that already carries a fixed
      photographic backdrop and a WebGL storm on its close.

   2. THE POINTER ANSWER. The red rule at the head of a card grows from 0 to
      full width, and the card lifts 2px. `scaleX` on a rule rather than a width
      transition, again to stay off the layout path.

   The stagger is capped at six steps. Past that the last card in a long row
   waits nearly a second after the first, which reads as a page still loading
   rather than a page arriving.

   ------------------------------------------------------------------------
   REDUCED MOTION IS HONOURED BY NOT STARTING

   js/card-motion.js checks the query and simply never adds `is-cm-pre`, so
   there is no transition to interrupt and no state to unwind. The hover lift is
   dropped here too, and only the colour change survives — a person who asked
   for less motion still needs to know what is interactive.
   ════════════════════════════════════════════════════════════════════════ */

/* ── ARRIVAL ───────────────────────────────────────────────────────────── */
/* ONE `transition` DECLARATION OWNS ALL FOUR PROPERTIES, and it has to.
   MEASURED DEFECT, 2026-08-27: the hover rule below set the `transition`
   SHORTHAND on the same element at the same (0,1,0) specificity, and a shorthand
   REPLACES rather than merges -- so later source order dropped opacity and
   transform entirely. Computed transition-property on a .tier under
   `pointer: fine` read `translate, border-color`. The arrival therefore did not
   animate at all on desktop: card-motion.js hid the cards, then they snapped in
   over one frame, which is strictly worse than never running. Sampling opacity
   per frame: desktop `1,1,1,1,1,1` against touch `0, .057, .128, .197, …`.
   The stagger below was dead for the same reason.
   Listing every animated property here, and again on the hover rule, means
   whichever declaration wins the tie still carries all four. */
[data-cm] > * {
  transition:
    opacity .62s cubic-bezier(.22, 1, .36, 1),
    transform .62s cubic-bezier(.22, 1, .36, 1),
    translate .28s cubic-bezier(.22, 1, .36, 1),
    border-color .28s ease;
}

/* The only rule that hides anything, and it is applied by script. */
[data-cm].is-cm-pre > * {
  opacity: 0;
  transform: translateY(14px);
}

[data-cm].is-cm-in > * {
  opacity: 1;
  transform: none;
}

/* THE STAGGER. Set as a custom property by the script so the delay survives
   any number of children without a rule per index. Capped at 6 in the JS. */
[data-cm].is-cm-in > * { transition-delay: calc(var(--cm-i, 0) * 70ms); }

/* ── THE POINTER ANSWER ────────────────────────────────────────────────── */
/* Scoped to a fine pointer. On touch there is no hover, and a :hover rule that
   sticks after a tap is the classic mobile artefact. */
/* THE LIFT USES `translate`, NOT `transform`, AND THAT IS THE POINT.
   Measured: as `transform: translateY(-2px)` the lift never applied. A card is a
   direct child of the [data-cm] row, so the arrival rule
   `[data-cm].is-cm-in > * { transform: none }` targets the same element and the
   same property at the same specificity -- (0,2,0) each, one attribute plus one
   class against one attribute plus one pseudo-class -- and the two fought.
   `translate` is an independent transform property: it composes with whatever
   `transform` holds instead of replacing it, so the arrival and the hover can
   both own the card without a specificity tie to lose. */
@media (pointer: fine) {
  /* Same four properties as the arrival rule above -- see the note there. A
     shorthand listing only the hover pair silently disabled the arrival. */
  [data-cm-lift] {
    transition:
      opacity .62s cubic-bezier(.22, 1, .36, 1),
      transform .62s cubic-bezier(.22, 1, .36, 1),
      translate .28s cubic-bezier(.22, 1, .36, 1),
      border-color .28s ease;
  }
  [data-cm-lift]:hover,
  [data-cm-lift]:focus-within {
    translate: 0 -2px;
  }
}

/* THE RULE THAT GROWS. Authored at full width so the card is correct with no
   JS and with reduced motion; only the hover state scales it from nothing. */
[data-cm-rule] {
  position: relative;
}
[data-cm-rule]::before {
  content: '';
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 2px;
  background: var(--red, #c8102e);
  transform-origin: 0 50%;
}
@media (pointer: fine) {
  [data-cm-rule]::before {
    transform: scaleX(0);
    transition: transform .34s cubic-bezier(.22, 1, .36, 1);
  }
  [data-cm-rule]:hover::before,
  [data-cm-rule]:focus-within::before {
    transform: scaleX(1);
  }
}

/* ── REDUCED MOTION ───────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  [data-cm] > *,
  [data-cm].is-cm-pre > *,
  [data-cm].is-cm-in > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  [data-cm-lift]:hover,
  [data-cm-lift]:focus-within { translate: none; }
  /* The rule stays PAINTED rather than animating, so the card still reads as
     finished and the hover still says "this is interactive" via colour alone. */
  [data-cm-rule]::before { transform: none; transition: none; }
}
