/* Animated explainers.
   ---------------------------------------------------------------------------
   Six mechanisms this product is built on, drawn rather than described. Each
   figure animates the thing it names: chunks actually deduplicate, changed
   blocks actually fly out alone, the WORM lock actually closes.

   The rule these follow: an animation has to TEACH, not decorate. If the loop
   could be replaced by a static image without losing meaning, it should be a
   static image. Every one below shows a before and an after that a sentence
   would need a paragraph to convey.

   Constraints kept from the rest of the site: no build step, no libraries, no
   webfonts. Pure CSS keyframes over inline SVG, themed through the same tokens
   as everything else, and frozen at a representative frame for anybody who has
   asked their system for less motion. */

.ex-grid {
  display: grid;
  gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  margin-top: 30px;
}

.ex {
  background: var(--surface);
  border-radius: var(--r);
  box-shadow: var(--ring), var(--lift-1);
  padding: 18px 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ex figure { margin: 0; }

.ex-stage {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  background: var(--surface-2);
  overflow: hidden;
}

.ex h3 {
  margin: 0;
  font-size: 16.5px;
  letter-spacing: -0.01em;
}

.ex p {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-2);
}

.ex .ex-meta {
  margin-top: auto;
  padding-top: 4px;
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Shared SVG vocabulary. Blocks are data, rails are movement, the ink colours
   come from the same pastel set the product uses so a screenshot and an
   illustration sit together without clashing. */
.ex-stage { --blk: var(--pastel-ink-blue); --blk-bg: var(--pastel-blue); }
.ex-stage .rail { stroke: var(--hairline); stroke-width: 1.5; fill: none; }
.ex-stage .lbl {
  font: 600 9px/1 ui-sans-serif, system-ui, sans-serif;
  fill: var(--muted);
  letter-spacing: .04em;
  text-transform: uppercase;
}
/* Sentence-length labels. The uppercase, letter-spaced .lbl is right for a
   two-word axis title and wrong for "chunk 5 failed its hash" — which
   overflowed its box, because uppercase plus tracking is roughly 35% wider
   than the same words set normally. */
.ex-stage .cap {
  font: 500 9.5px/1 ui-sans-serif, system-ui, sans-serif;
  fill: var(--ink-2);
  letter-spacing: 0;
}
.ex-stage .num {
  font: 700 13px/1 ui-sans-serif, system-ui, sans-serif;
  fill: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ex-stage .blk { fill: var(--pastel-blue); stroke: var(--pastel-ink-blue); stroke-width: 1.2; rx: 2; }
.ex-stage .blk.dup { fill: var(--pastel-lemon); stroke: var(--pastel-ink-lemon); }
.ex-stage .blk.chg { fill: var(--pastel-peach); stroke: var(--pastel-ink-peach); }
.ex-stage .blk.ok  { fill: var(--pastel-mint); stroke: var(--pastel-ink-mint); }
.ex-stage .blk.bad { fill: var(--pastel-rose); stroke: var(--pastel-ink-rose); }

/* Animations run always so the page needs no JS to work. The observer below
   only pauses them off-screen, which is a battery courtesy rather than a
   correctness requirement. */
.ex-stage [class*="anim-"] { animation-play-state: running; }
.ex-paused .ex-stage [class*="anim-"] { animation-play-state: paused; }

/* ── 1. deduplication ─────────────────────────────────────────────────────
   Four chunks arrive; two are already in the store. Those two do not travel —
   a reference line snaps to what is already there. That is the whole idea and
   it is very hard to say in a sentence. */
@keyframes ex-flow-store {
  0%, 6%    { transform: translateX(0);     opacity: 0; }
  14%       { transform: translateX(0);     opacity: 1; }
  62%       { transform: translateX(150px); opacity: 1; }
  74%, 100% { transform: translateX(150px); opacity: 0; }
}
@keyframes ex-flow-ref {
  0%, 6%   { transform: translateX(0);    opacity: 0; }
  14%      { transform: translateX(0);    opacity: 1; }
  44%      { transform: translateX(56px); opacity: 1; }
  54%      { transform: translateX(56px); opacity: 0; }
  100%     { transform: translateX(56px); opacity: 0; }
}
/* The reference lines are the POINT of this figure, so they are drawn for most
   of the loop rather than flashing. A viewer who glances at any moment should
   see two chunks referenced and two stored. */
@keyframes ex-ref-line {
  0%, 30%  { stroke-dashoffset: 90; }
  46%,100% { stroke-dashoffset: 0; }
}
.anim-store { animation: ex-flow-store 4.2s cubic-bezier(.5,0,.2,1) infinite; }
.anim-ref   { animation: ex-flow-ref   4.2s cubic-bezier(.5,0,.2,1) infinite; }
.anim-refline {
  stroke: var(--pastel-ink-lemon); stroke-width: 1.4; fill: none;
  stroke-dasharray: 90; stroke-linecap: round;
  animation: ex-ref-line 4.2s ease-in-out infinite;
}

/* ── 2. changed-block tracking ────────────────────────────────────────────
   A disk of 48 blocks. Three change. Only those three leave. The counter is
   the point: the read cost is the churn, not the disk. */
@keyframes ex-pulse-chg {
  0%, 22%  { fill: var(--pastel-blue);  stroke: var(--pastel-ink-blue); }
  30%, 60% { fill: var(--pastel-peach); stroke: var(--pastel-ink-peach); }
  75%,100% { fill: var(--pastel-peach); stroke: var(--pastel-ink-peach); }
}
@keyframes ex-eject {
  0%, 58%   { transform: translate(0,0); opacity: 1; }
  85%       { transform: translate(0,-42px); opacity: 1; }
  92%, 100% { transform: translate(0,-42px); opacity: 0; }
}
/* The counter never hides. It is the sentence this figure exists to say, and a
   figure that says it for only a third of its loop says nothing to somebody
   scrolling past. */
@keyframes ex-count {
  0%, 100% { opacity: 1; }
  50%      { opacity: .55; }
}
.anim-chg   { animation: ex-pulse-chg 5s ease-in-out infinite; }
.anim-eject { animation: ex-eject 5s cubic-bezier(.4,0,.2,1) infinite; }
.anim-count { animation: ex-count 5s ease-in-out infinite; }

/* ── 3. the chain, and the lock ───────────────────────────────────────────
   Data walks primary → copy → archive, and the padlock on the immutable link
   closes behind it. The shackle closing is the frame worth animating: WORM is
   a thing that happens at WRITE time, not a setting you tick afterwards. */
@keyframes ex-hop {
  0%       { offset-distance: 0%;   opacity: 0; }
  6%       { offset-distance: 0%;   opacity: 1; }
  92%      { offset-distance: 100%; opacity: 1; }
  100%     { offset-distance: 100%; opacity: 0; }
}
@keyframes ex-lock-shut {
  0%, 45%  { transform: translateY(-3px); }
  58%      { transform: translateY(0); }
  62%      { transform: translateY(-0.6px); }
  66%,100% { transform: translateY(0); }
}
@keyframes ex-lock-glow {
  0%, 55%  { opacity: 0; }
  62%      { opacity: .55; }
  100%     { opacity: 0; }
}
.anim-hop {
  offset-path: path("M 26 96 L 150 96 L 274 96");
  animation: ex-hop 4.6s cubic-bezier(.45,0,.25,1) infinite;
}
.anim-lock  { animation: ex-lock-shut 4.6s cubic-bezier(.3,1.4,.5,1) infinite; transform-origin: center; }
.anim-glow  { animation: ex-lock-glow 4.6s ease-out infinite; }

/* ── 4. discovery, then one click ─────────────────────────────────────────
   A scan line crosses a machine; what it finds appears as rows; one click
   turns the row green. The gap this closes is the whole reason discovery
   exists — a finding you cannot act on is trivia. */
@keyframes ex-scan {
  0%       { transform: translateY(0);    opacity: 0; }
  8%       { transform: translateY(0);    opacity: .9; }
  46%      { transform: translateY(74px); opacity: .9; }
  54%,100% { transform: translateY(74px); opacity: 0; }
}

@keyframes ex-turn-green {
  0%, 74%  { fill: var(--pastel-peach); stroke: var(--pastel-ink-peach); }
  82%,100% { fill: var(--pastel-mint);  stroke: var(--pastel-ink-mint); }
}
@keyframes ex-click {
  0%, 66%  { opacity: 0; transform: scale(.4); }
  72%      { opacity: 1; transform: scale(1.5); }
  80%,100% { opacity: 0; transform: scale(1.9); }
}
.anim-scan  { animation: ex-scan 5.4s cubic-bezier(.4,0,.3,1) infinite; }
.anim-f1    { animation: ex-fade-in 5.4s ease-out infinite; animation-delay: .25s; }
.anim-f2    { animation: ex-fade-in 5.4s ease-out infinite; animation-delay: .5s; }
.anim-f3    { animation: ex-fade-in 5.4s ease-out infinite; animation-delay: .75s; }
.anim-green { animation: ex-turn-green 5.4s ease-out infinite; }
.anim-click { animation: ex-click 5.4s ease-out infinite; transform-origin: center; transform-box: fill-box; }
/* Findings settle in early and then stay. The scan line is the motion; the
   list is the content, and content that blinks is content nobody reads. */
@keyframes ex-fade-in {
  0%       { opacity: 0; transform: translateX(-7px); }
  18%,100% { opacity: 1; transform: none; }
}

/* ── 5. anomaly detection ─────────────────────────────────────────────────
   A backup's shape over time: steady, steady, then a night where everything
   changed and nothing compressed. That is what encryption looks like from the
   repository's side, and it is visible before anybody notices in production. */
@keyframes ex-trace {
  0%       { stroke-dashoffset: 300; }
  70%,100% { stroke-dashoffset: 0; }
}
@keyframes ex-alarm {
  0%, 55%  { opacity: 1; transform: scale(1); }
  62%      { opacity: 1; transform: scale(1.35); }
  70%      { opacity: 1; transform: scale(1); }
  76%      { opacity: .45; transform: scale(1); }
  84%,100% { opacity: 1; transform: scale(1); }
}
.anim-trace {
  stroke-dasharray: 300;
  animation: ex-trace 5.2s cubic-bezier(.35,0,.3,1) infinite;
}
.anim-alarm { animation: ex-alarm 5.2s ease-out infinite; transform-origin: center; transform-box: fill-box; }

/* ── 6. verified restore ──────────────────────────────────────────────────
   Chunks are re-hashed on the way back and ticked one by one. The last one
   fails, and the restore stops rather than handing over a file that is nearly
   right. A tick sequence that always succeeds would be advertising; the
   failure is the feature. */
/* Staggered by DELAY, not by different keyframes: with an infinite animation
   the delay fixes each element's phase for every iteration, so the five chunks
   verify left to right on every loop instead of in an order that depends on
   when the page happened to load. */
@keyframes ex-verify-ok {
  0%       { opacity: 0; }
  6%,100%  { opacity: 1; }
}
@keyframes ex-verify-bad {
  0%       { opacity: 0; }
  6%,100%  { opacity: 1; }
}
@keyframes ex-refuse {
  0%      { opacity: 0; transform: translateY(4px); }
  8%,100% { opacity: 1; transform: none; }
}
.anim-v1 { animation: ex-verify-ok 5.6s ease-out infinite; animation-delay: .2s; }
.anim-v2 { animation: ex-verify-ok 5.6s ease-out infinite; animation-delay: .8s; }
.anim-v3 { animation: ex-verify-ok 5.6s ease-out infinite; animation-delay: 1.4s; }
.anim-v4 { animation: ex-verify-ok 5.6s ease-out infinite; animation-delay: 2.0s; }
.anim-vbad { animation: ex-verify-bad 5.6s ease-out infinite; animation-delay: 2.6s; }
.anim-refuse { animation: ex-refuse 5.6s ease-out infinite; animation-delay: 2.9s; }

/* Anybody who has asked for less motion gets the FINAL frame of each idea —
   the dedup reference drawn, the changed blocks marked, the lock shut — so the
   illustration still explains itself. Freezing on frame zero would leave six
   blank boxes and no explanation. */
@media (prefers-reduced-motion: reduce) {
  .ex-stage [class*="anim-"] {
    animation: none !important;
  }
  .anim-ref, .anim-scan, .anim-click, .anim-eject, .anim-store { opacity: 0; }
  .anim-refline { stroke-dashoffset: 0; opacity: 1; }
  .anim-chg { fill: var(--pastel-peach); stroke: var(--pastel-ink-peach); }
  .anim-hop { offset-distance: 100%; }
  .anim-trace { stroke-dashoffset: 0; }
  .anim-alarm, .anim-count, .anim-refuse { opacity: 1; transform: none; }
  .anim-f1, .anim-f2, .anim-f3 { opacity: 1; transform: none; }
  .anim-green { fill: var(--pastel-mint); stroke: var(--pastel-ink-mint); }
  .anim-v1, .anim-v2, .anim-v3, .anim-v4, .anim-vbad { opacity: 1; }
}

/* A four-card row on a three-column grid leaves one card stranded on its own
   line, which reads as "and one more thing we forgot". Two by two instead. */
@media (min-width: 941px) {
  .cards.pairs { grid-template-columns: repeat(2, 1fr); }
}
