/* ===========================================================
   Dr. Fixit — The Perfect Challenge
   MOTION LAYER — loaded after drfixit.css, so it wins ties.

   One rule about this file: it only ever moves things that are
   already there. No colour, no type, no spacing, no layout is
   decided here — drfixit.css owns all of that and this sheet
   never re-states it.

   Three things it does own:

     1. the timing vocabulary (section 0), so a fade on the
        badge and a fade on the leaderboard are the same fade;
     2. a reveal system (section 1) driven by one attribute,
        data-df-reveal, so no page has to write its own;
     3. the reward moments — a correct answer, a bonus, a
        badge — which are the only places the motion is
        allowed to be noticed.

   Everything only animates transform and opacity, so the
   compositor carries it and a low-end handset does not.

   ARMING. The reveal system hides its targets before it shows
   them, which would strand the page if the script never ran.
   So the hidden state is scoped to .df-motion, a class the
   inline snippet in each <head> adds only when JS is alive and
   the device has not asked for reduced motion. No class, no
   hiding: the page renders exactly as it did before this file
   existed. That is also why the reduced-motion block at the
   bottom is a safety net rather than the mechanism.
   =========================================================== */

/* ===========================================================
   0. TIMING — the whole vocabulary, in one place
   =========================================================== */
:root{
  /* Durations. The brief asked for 200–500ms and meant it: the
     quiz is timed, and motion the participant has to wait out
     is motion that costs them points. */
  --df-t-xs:120ms;
  --df-t-sm:180ms;
  --df-t:260ms;
  --df-t-lg:420ms;
  --df-t-xl:620ms;          /* reveals only — never blocks an action */

  /* Curves. Arrivals decelerate hard; rewards overshoot once. */
  --df-ease:cubic-bezier(.22,.61,.36,1);
  --df-ease-out:cubic-bezier(.16,1,.3,1);
  --df-ease-back:cubic-bezier(.34,1.4,.64,1);

  --df-step:55ms;           /* one place in a stagger */
  --df-lift:-3px;           /* how far anything rises on hover */
}

/* ===========================================================
   1. THE REVEAL SYSTEM
   -----------------------------------------------------------
   Mark anything with data-df-reveal and motion.js adds .is-in
   when it first reaches the viewport. A parent carrying
   data-df-stagger numbers its children into --df-i, so a list
   arrives as a list rather than as one block.

   Five directions, one timing. Use "up" (the default) unless
   the element genuinely comes from somewhere.
   =========================================================== */
.df-motion [data-df-reveal]{ opacity:0; }
.df-motion [data-df-reveal].is-in{
  animation:df-rise var(--df-t-xl) var(--df-ease-out) both;
  animation-delay:calc(var(--df-i, 0) * var(--df-step));
}
.df-motion [data-df-reveal="fade"].is-in { animation-name:df-fade; }
.df-motion [data-df-reveal="scale"].is-in{ animation-name:df-scale-in; }
.df-motion [data-df-reveal="left"].is-in { animation-name:df-slide-l; }
.df-motion [data-df-reveal="right"].is-in{ animation-name:df-slide-r; }

/* The five arrivals. Every one lands on transform:none, never
   translate(0) — a resting transform would make the element a
   containing block and break the player's fixed full-screen. */
@keyframes df-rise    { from{ opacity:0; transform:translate3d(0,16px,0); }      to{ opacity:1; transform:none; } }
@keyframes df-fade    { from{ opacity:0; }                                       to{ opacity:1; } }
@keyframes df-scale-in{ from{ opacity:0; transform:scale(.94); }                 to{ opacity:1; transform:none; } }
@keyframes df-slide-l { from{ opacity:0; transform:translate3d(-22px,0,0); }     to{ opacity:1; transform:none; } }
@keyframes df-slide-r { from{ opacity:0; transform:translate3d(22px,0,0); }      to{ opacity:1; transform:none; } }

/* Sticky things are the exception to "everything rises".
   position:sticky resolves against the nearest ancestor with a
   transform, so a rise — its own, or one it is sitting inside —
   changes what it sticks to for the length of the animation.
   The handset dock at the bottom of verify, result and badge is
   exactly that, so those fade instead. The :has() rule covers
   the ancestor case and is simply dropped where :has() is not
   understood, which costs nothing. */
.df-motion [data-df-reveal].df-dock--sticky.is-in,
.df-motion [data-df-reveal]:has(.df-dock--sticky).is-in{ animation-name:df-fade; }

/* A one-shot arrival for content the script builds after load —
   quiz options, mainly. motion.js adds it and takes it off again. */
.df-motion .df-enter{
  animation:df-rise var(--df-t-lg) var(--df-ease-out) both;
  animation-delay:calc(var(--df-i, 0) * var(--df-step));
}

/* ===========================================================
   2. PAGE TRANSITION
   -----------------------------------------------------------
   Opacity only, and deliberately so. A transform here would
   make <main> the containing block for position:fixed, and the
   film's full-screen stand-in is fixed inside it.
   =========================================================== */
.df-motion .df-main{ animation:df-fade var(--df-t) var(--df-ease) both; }

/* ===========================================================
   3. BUTTONS AND CONTROLS
   -----------------------------------------------------------
   One grammar everywhere: hover lifts, press sinks, and the
   press is faster than the lift so it feels mechanical.
   Re-stated after drfixit.css because :hover and :active have
   equal specificity and the later sheet has to win.
   =========================================================== */
.btn{ transition:transform var(--df-t-xs) var(--df-ease),
                 box-shadow var(--df-t-xs) var(--df-ease),
                 filter     var(--df-t-xs) var(--df-ease),
                 opacity    var(--df-t-sm) var(--df-ease),
                 background-color var(--df-t-sm) var(--df-ease),
                 border-color     var(--df-t-sm) var(--df-ease); }

.btn-df:hover:not(:disabled):not(.disabled){
  transform:translateY(var(--df-lift));
  box-shadow:0 8px 0 #A87400, 0 20px 30px -14px rgba(255,222,0,.6);
}
.btn-df:active:not(:disabled):not(.disabled){
  transform:translateY(4px); box-shadow:0 1px 0 #A87400;
  transition-duration:var(--df-t-xs);
}
.btn-df-ghost:hover:not(:disabled):not(.disabled){
  transform:translateY(var(--df-lift)); box-shadow:var(--df-shadow-2);
}
.btn-df-ghost:active:not(:disabled):not(.disabled){ transform:translateY(0) scale(.98); }

/* Disabled is a state, not an event: it must read as clearly off
   without the button flinching when it gets there. */
.btn:disabled,.btn.disabled{ transform:none !important; box-shadow:none; }

/* The header controls and the identity chip share the grammar. */
.df-back:hover,.df-login:hover,.df-fs:hover{ transform:translateY(-2px); }
.df-id{ transition:background var(--df-t-sm), border-color var(--df-t-sm), transform var(--df-t-xs); }
.df-id:active{ transform:scale(.97); }
.df-tab{ transition:background var(--df-t-sm) var(--df-ease), color var(--df-t-sm) var(--df-ease), transform var(--df-t-xs); }
.df-tab:active{ transform:scale(.96); }
.df-chip{ transition:background var(--df-t-sm), border-color var(--df-t-sm), transform var(--df-t-sm); }

/* ---- attention, used sparingly -----------------------------
   Two beats, then it stops for good. motion.js hands this out
   once per session per key, so a participant who walks back to
   a page they have already seen is not nudged a second time. */
.df-motion .df-attn{ animation:df-attn 1.5s var(--df-ease) 2; }
@keyframes df-attn{
  0%,100%{ transform:none;             box-shadow:0 5px 0 #A87400, 0 12px 22px -10px rgba(255,222,0,.55); }
  40%    { transform:translateY(-4px); box-shadow:0 9px 0 #A87400, 0 22px 34px -12px rgba(255,222,0,.75); }
}

/* ---- loading ----------------------------------------------
   busy() in drfixit.js already swaps the label and disables the
   control; this only adds the turning ring, so the button never
   goes silent while html2canvas paints a 1080x1920 sheet. */
.btn.is-busy{ position:relative; padding-left:2.9rem; }
.btn.is-busy::before{
  content:""; position:absolute; left:1.35rem; top:50%; width:16px; height:16px; margin-top:-8px;
  border:2px solid currentColor; border-right-color:transparent; border-radius:50%;
  opacity:.75; animation:df-spin .7s linear infinite;
}
@keyframes df-spin{ to{ transform:rotate(360deg); } }

/* A skeleton, for any panel that ends up waiting on the API. */
.df-skel{
  border-radius:10px; background:var(--df-surface-2); position:relative; overflow:hidden; color:transparent;
}
.df-skel::after{
  content:""; position:absolute; inset:0; transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,var(--df-surface-3),transparent);
  animation:df-sweep 1.4s ease-in-out infinite;
}
@keyframes df-sweep{ to{ transform:translateX(100%); } }

/* ---- confirmation -----------------------------------------
   The full stop on a finished action: one pop, then nothing. */
.df-motion .df-pop{ animation:df-pop var(--df-t-lg) var(--df-ease-back) both; }
@keyframes df-pop{ 0%{ opacity:0; transform:scale(.8); } 60%{ opacity:1; } 100%{ opacity:1; transform:none; } }

/* ===========================================================
   4. HERO — the landing page
   -----------------------------------------------------------
   The rain is already the campaign's ambient motion. All this
   adds is one very slow pass of light across it, on the same
   diagonal as the rain, so the sky reads as weather moving
   rather than a loop. Fourteen seconds, low opacity, one
   composited layer.
   =========================================================== */
.df-shimmer{
  position:absolute; inset:-30% -60%; z-index:0; pointer-events:none;
  background:radial-gradient(38% 46% at 50% 50%, rgba(255,255,255,.5), transparent 70%);
  animation:df-drift 14s ease-in-out infinite;
}
@keyframes df-drift{
  0%,100%{ transform:translate3d(-16%,-8%,0); }
  50%    { transform:translate3d(16%,8%,0); }
}

/* The mark in the welcome card breathes, very slightly, so the
   card is not a flat sticker on a moving field. The float is on
   the image rather than on the roundel, because the roundel sits
   inside a reveal target and one element cannot run two. */
.df-motion .df-welcome-badge img{ animation:df-float 7s ease-in-out infinite; }
@keyframes df-float{ 0%,100%{ transform:translate3d(0,0,0); } 50%{ transform:translate3d(0,-7px,0); } }

/* ===========================================================
   5. ROLE CHOICE — dealer or contractor
   -----------------------------------------------------------
   These two cards are links: the click navigates. So the
   confirmation cannot be a sequence that has to finish, it has
   to be instant. .is-picking goes on at pointerdown and the
   page leaves underneath it.
   =========================================================== */
.df-role{ transition:border-color var(--df-t-sm), transform var(--df-t-xs), box-shadow var(--df-t-sm); }
.df-role:hover{ transform:translateY(var(--df-lift)); }
.df-role::before{ transition:transform var(--df-t) var(--df-ease-out); }
.df-role-ic{ transition:transform var(--df-t) var(--df-ease-back), border-color var(--df-t-sm); }
.df-role:hover .df-role-ic{ transform:scale(1.07) rotate(-3deg); }
.df-role-chev{ transition:transform var(--df-t) var(--df-ease-out), color var(--df-t-sm); }
.df-role:hover .df-role-chev{ transform:translateX(5px); color:var(--df-ink-accent); }

.df-role.is-picking{
  transform:scale(.985);
  border-color:var(--df-yellow);
  box-shadow:0 0 0 3px rgba(255,222,0,.28), var(--df-shadow-2);
}
.df-role.is-picking::before{ transform:scaleY(1); }
/* The tick. Drawn in CSS so no page has to carry markup for it. */
.df-role.is-picking .df-role-chev{ color:transparent; }
.df-role.is-picking .df-role-chev::after{
  content:""; position:absolute; top:50%; right:1.1rem; width:22px; height:22px; margin-top:-11px;
  border-radius:50%; background:var(--df-yellow);
  animation:df-pop 260ms var(--df-ease-back) both;
}

/* ===========================================================
   6. LANGUAGE TILES — the one real selection in the journey
   =========================================================== */
.df-lang{ transition:background var(--df-t-sm), border-color var(--df-t-sm), transform var(--df-t-xs), box-shadow var(--df-t-sm); }
.df-lang:hover{ transform:translateY(-2px); box-shadow:var(--df-shadow-1); }
.df-lang:active{ transform:scale(.97); }
.df-motion .df-lang.active{ animation:df-pick 320ms var(--df-ease-back); }
.df-motion .df-lang.active .df-lang-tick{ animation:df-pop 300ms var(--df-ease-back) both; }
@keyframes df-pick{ 0%{ transform:scale(1); } 45%{ transform:scale(1.035); } 100%{ transform:scale(1); } }

/* ===========================================================
   7. THE FILM
   -----------------------------------------------------------
   Three moments: the stage arriving, the sixty seconds passing,
   and the small full stop when it ends.
   =========================================================== */
.df-motion .df-player.is-playing .df-player-stage{ animation:df-fade var(--df-t-lg) var(--df-ease) both; }
.df-motion .df-player.is-playing .df-player-bar{ animation:df-rise var(--df-t-lg) var(--df-ease-out) both; }
.df-play{ transition:transform var(--df-t-sm) var(--df-ease-back), box-shadow var(--df-t-sm); }
.df-play:active{ transform:scale(.94); }
/* The play button asks once, before anything has happened. */
.df-motion .df-player:not(.is-playing):not(.is-done) .df-play{ animation:df-ping-ring 2.6s ease-out infinite; }
@keyframes df-ping-ring{
  0%  { box-shadow:0 0 0 10px rgba(255,222,0,.18), 0 12px 30px -8px rgba(0,0,0,.8); }
  70% { box-shadow:0 0 0 26px rgba(255,222,0,0),   0 12px 30px -8px rgba(0,0,0,.8); }
  100%{ box-shadow:0 0 0 10px rgba(255,222,0,.18), 0 12px 30px -8px rgba(0,0,0,.8); }
}

/* Progress, while it runs. The head of the bar is the indicator —
   it says "still going" without a second element on screen. */
.df-player.is-playing .df-track{ overflow:visible; }
.df-player.is-playing .df-track i{ position:relative; box-shadow:0 0 12px -2px rgba(255,222,0,.75); }
.df-motion .df-player.is-playing .df-track i::after{
  content:""; position:absolute; right:0; top:50%; width:9px; height:9px; margin-top:-4.5px;
  border-radius:50%; background:var(--df-yellow); transform:translateX(50%);
  animation:df-ping 1.8s ease-out infinite;
}
@keyframes df-ping{
  0%  { box-shadow:0 0 0 0 rgba(255,222,0,.55); }
  70% { box-shadow:0 0 0 9px rgba(255,222,0,0); }
  100%{ box-shadow:0 0 0 0 rgba(255,222,0,0); }
}

/* The end of the film. One ring off the panel edge, once. */
.df-motion .df-player.is-done::after{
  content:""; position:absolute; inset:0; z-index:5; pointer-events:none; border-radius:inherit;
  border:2px solid var(--df-yellow); animation:df-seal 900ms var(--df-ease-out) both;
}
@keyframes df-seal{ 0%{ opacity:0; transform:scale(1.03); } 35%{ opacity:.95; } 100%{ opacity:0; transform:none; } }

/* ===========================================================
   8. QUIZ
   -----------------------------------------------------------
   This is the part that has to feel like a game, and the part
   where a slow animation literally costs points. Nothing here
   sits between a tap and its answer.
   =========================================================== */

/* ---- the countdown --------------------------------------- */
.df-motion .df-countdown{ animation:df-tick 800ms var(--df-ease-out) infinite; }
@keyframes df-tick{ 0%{ opacity:.25; transform:scale(.78); } 45%{ opacity:1; transform:scale(1); } 100%{ opacity:.85; transform:scale(1); } }

/* ---- progress, 1/3 → 3/3 ---------------------------------
   The segments were solid blocks that swapped colour. They now
   fill, which is the same information arriving as a movement.
   The flat .done background is overridden back to the track so
   the ::after fill is the only thing that paints. */
.df-qsegs i{ position:relative; overflow:hidden; }
.df-qsegs i.done,.df-qsegs i.now{ background:var(--df-track); }
.df-qsegs i::after{
  content:""; position:absolute; inset:0; border-radius:3px; background:var(--df-yellow);
  transform:scaleX(0); transform-origin:left center;
  transition:transform var(--df-t-lg) var(--df-ease-out), opacity var(--df-t) var(--df-ease);
}
.df-qsegs i.now::after { transform:scaleX(.4); opacity:.55; }
.df-qsegs i.done::after{ transform:scaleX(1);  opacity:1; }
.df-qsegs i.done{ box-shadow:0 0 12px -2px rgba(255,222,0,.5); }

/* The elapsed dial already animates its arc; give the number the
   same beat when the bonus tier steps down, so the change is
   noticed without a banner. */
.df-dial b{ transition:color var(--df-t) var(--df-ease); }

/* ---- the question ---------------------------------------- */
.df-motion .df-question.df-enter{ animation-name:df-slide-r; animation-duration:var(--df-t-lg); }

/* ---- the options ----------------------------------------- */
.df-opt{ transition:background var(--df-t-sm), border-color var(--df-t-sm), opacity var(--df-t-sm), transform var(--df-t-xs); }
.df-opt:hover:not(:disabled){ transform:translateX(3px); }
.df-opt:active:not(:disabled){ transform:scale(.99); }
.df-opt-k{ transition:background var(--df-t-sm), color var(--df-t-sm), border-color var(--df-t-sm), transform var(--df-t-sm) var(--df-ease-back); }
.df-opt.is-right .df-opt-k,.df-opt.is-wrong .df-opt-k{ transform:scale(1.08); }

/* Right. A short pop, a ring off the edge, and the tick draws
   itself rather than appearing already drawn. */
.df-motion .df-opt.is-right{ animation:df-right 460ms var(--df-ease-back); }
@keyframes df-right{ 0%{ transform:scale(1); } 38%{ transform:scale(1.022); } 100%{ transform:scale(1); } }
.df-motion .df-opt.is-right::after{
  content:""; position:absolute; inset:-2px; border-radius:16px; pointer-events:none;
  border:2px solid var(--df-ok); animation:df-seal 760ms var(--df-ease-out) both;
}
.df-motion .df-opt-mk{ animation:df-pop 320ms var(--df-ease-back) both; }
.df-motion .df-opt.is-right .df-opt-mk svg path{
  stroke-dasharray:30; stroke-dashoffset:30;
  animation:df-draw 380ms var(--df-ease-out) 90ms forwards;
}
@keyframes df-draw{ to{ stroke-dashoffset:0; } }

/* Wrong. The existing shake, softened — this is a three-question
   quiz for a partner programme, not a punishment. */
.df-opt.is-wrong{ animation:df-shake 320ms var(--df-ease) !important; }
@keyframes df-shake{ 22%{ transform:translateX(-5px); } 48%{ transform:translateX(4px); } 74%{ transform:translateX(-2px); } 100%{ transform:none; } }

/* The options that were neither: they step back rather than blink out. */
.df-opt.is-mute{ transform:scale(.985); }

/* ---- the verdict and the way forward --------------------- */
.df-motion .df-verdict{ animation:df-rise var(--df-t-lg) var(--df-ease-out) both; }
.df-motion .df-verdict--ok .df-verdict-mk{ animation:df-ping 1.6s ease-out 3; }

/* ===========================================================
   9. POINTS
   -----------------------------------------------------------
   The count-up itself is in motion.js — a number has to land on
   its exact value, and only script can promise that. This is
   the packaging around it.
   =========================================================== */
.df-scorechip{ position:relative; transition:border-color var(--df-t), background var(--df-t); }
.df-motion .df-scorechip.is-up{ animation:df-bump 420ms var(--df-ease-back); border-color:var(--df-yellow); }
@keyframes df-bump{ 0%{ transform:scale(1); } 40%{ transform:scale(1.07); } 100%{ transform:scale(1); } }

/* The points leaving the answer and landing on the total. */
.df-gain{
  position:absolute; left:50%; top:0; z-index:5; pointer-events:none; white-space:nowrap;
  font-family:"Archivo Black",sans-serif; font-size:1rem; color:var(--df-ink-accent);
  text-shadow:0 2px 12px rgba(0,0,0,.45);
  animation:df-gain 1s var(--df-ease-out) both;
}
.df-gain--bad{ color:var(--df-ink-bad); }
@keyframes df-gain{
  0%  { opacity:0; transform:translate(-50%,6px)   scale(.85); }
  25% { opacity:1; transform:translate(-50%,-8px)  scale(1); }
  100%{ opacity:0; transform:translate(-50%,-34px) scale(1); }
}

/* A perfect run, or a bonus tier that landed: the row is lit
   once and then left alone. */
.df-motion .df-tier.is-hit{ animation:df-pop 420ms var(--df-ease-back) both; }
.df-motion .df-total.is-perfect{ animation:df-glow 2.4s var(--df-ease) 2; }
@keyframes df-glow{
  0%,100%{ box-shadow:0 0 0 0 rgba(255,222,0,0); }
  50%    { box-shadow:0 0 0 6px rgba(255,222,0,.16); }
}

/* "Unlocked", against the speed bonus. */
.df-motion .df-unlock{ animation:df-pop 420ms var(--df-ease-back) both; }

/* ===========================================================
   10. THE BADGE
   -----------------------------------------------------------
   The one moment the brief calls memorable, and the one place
   the export has to be protected from the animation.

   html2canvas re-renders [data-df-badge] — the certificate
   itself — at 1080x1920. So nothing inside that node moves.
   The reveal is on .df-cert-fit, its wrapper, which the export
   never sees, and the shine is a pseudo-element of the wrapper
   for the same reason. badge() sets a scale transform on the
   card; that is why the card is not what animates here.
   =========================================================== */
.df-motion .df-cert-fit{ animation:df-badge var(--df-t-xl) var(--df-ease-back) both; }
@keyframes df-badge{
  0%  { opacity:0; transform:scale(.9) translate3d(0,14px,0); }
  100%{ opacity:1; transform:none; }
}
/* The shine. One pass, 1.1s after the card lands, and never again. */
.df-motion .df-cert-fit::after{
  content:""; position:absolute; inset:0; z-index:3; pointer-events:none;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.55) 50%,transparent 62%);
  transform:translateX(-130%);
  animation:df-shine 1.5s var(--df-ease-out) 1.1s both;
}
@keyframes df-shine{ to{ transform:translateX(130%); } }

/* ===========================================================
   11. CELEBRATION
   =========================================================== */
.df-motion .df-rosette{ animation:df-badge var(--df-t-xl) var(--df-ease-back) both; }
.df-motion .df-rosette-in svg path{
  stroke-dasharray:30; stroke-dashoffset:30;
  animation:df-draw 520ms var(--df-ease-out) 420ms forwards;
}

/* ===========================================================
   12. LEADERBOARD
   -----------------------------------------------------------
   Rows arrive staggered, but motion.js caps --df-i, so a board
   of two hundred names does not end with someone waiting ten
   seconds for the bottom of the list.
   =========================================================== */
.df-lrow{ transition:background var(--df-t), border-color var(--df-t), transform var(--df-t-xs), box-shadow var(--df-t); }
.df-lrow:hover{ transform:translateX(3px); box-shadow:var(--df-shadow-1); }
.df-lrow-p b{ transition:color var(--df-t); }

/* The participant's own row, lit twice on arrival and then still. */
.df-motion .df-lrow.is-me.is-mine{ animation:df-glow 2.2s var(--df-ease) 2; }

.df-pod{ transition:transform var(--df-t); }
.df-pod:hover{ transform:translateY(-3px); }
.df-motion .df-pod-crown{ animation:df-float 3.4s ease-in-out infinite; }

/* First place gets a slow pass of foil. Second and third do not,
   which is the point of first place. */
.df-pod--1 .df-pod-plinth{ position:relative; overflow:hidden; }
.df-motion .df-pod--1 .df-pod-plinth::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(105deg,transparent 40%,rgba(255,255,255,.4) 50%,transparent 60%);
  transform:translateX(-130%); animation:df-shine 4.5s var(--df-ease-out) 1s infinite;
}
.df-motion .df-pod--1 .df-pod-av{ animation:df-glow 3.6s var(--df-ease) infinite; }

/* Tabs swap the board underneath them; the new board fades rather
   than snapping, because the two look very alike. */
.df-motion .df-board-swap{ animation:df-fade var(--df-t) var(--df-ease) both; }

/* ===========================================================
   13. CARDS AND SURFACES
   =========================================================== */
.df-card--hover:hover{ transform:translateY(var(--df-lift)); }
.df-step-n{ transition:transform var(--df-t) var(--df-ease-back); }
.df-card--hover:hover .df-step-n{ transform:scale(1.1) rotate(-4deg); }
.df-notice{ animation:df-rise var(--df-t-lg) var(--df-ease-out) both; }

/* ===========================================================
   14. REDUCED MOTION
   -----------------------------------------------------------
   The real switch is upstream: .df-motion is never added, so
   none of the .df-motion-scoped rules above exist at all and
   every revealed element is simply visible.

   This block covers the rest — the ambient loops and the
   transitions, which are not scoped to that class — and stands
   as the backstop if a future rule forgets the prefix. It ends
   states rather than removing them: a filled progress segment
   stays filled, it just gets there instantly.
   =========================================================== */
@media (prefers-reduced-motion:reduce){
  .df-shimmer,
  .df-play,
  .df-track i::after,
  .df-pod-crown,
  .df-pod--1 .df-pod-plinth::after,
  .df-pod--1 .df-pod-av,
  .df-welcome-badge img,
  .df-countdown,
  .df-verdict-mk,
  .df-cert-fit::after{ animation:none !important; }

  .df-opt.is-wrong{ animation:none !important; }

  [data-df-reveal],
  .df-enter,
  .df-main,
  .df-cert-fit,
  .df-rosette{ opacity:1 !important; transform:none !important; animation:none !important; }

  .df-gain{ display:none; }

  *,*::before,*::after{
    transition-duration:1ms !important;
    animation-iteration-count:1 !important;
  }
}
