body { margin: 0; min-height: 100vh; box-sizing: border-box; padding: var(--wb-space-5) var(--wb-space-4) var(--wb-space-7); }
[hidden] { display: none !important; }

.top { display: flex; align-items: center; flex-wrap: wrap; gap: var(--wb-space-3); max-width: 560px; margin: 0 auto var(--wb-space-5); }
.home { display: flex; align-items: center; gap: var(--wb-space-3); color: inherit; text-decoration: none; }
.prefs { display: flex; align-items: center; flex-wrap: wrap; gap: var(--wb-space-2); margin-left: auto; }
.theme, .icon-btn.wb-btn--sm { width: var(--wb-size-control-sm); padding: 0; }
.icon-btn { width: var(--wb-size-control); padding: 0; flex: none; }
.grow { flex: 1; }
.theme svg { display: none; }
.theme[data-state="auto"] .i-auto, .theme[data-state="light"] .i-light, .theme[data-state="dark"] .i-dark { display: block; }
.lang { width: auto; height: var(--wb-size-control-sm); padding-block: 0; }
.logo-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logo-light { display: none; }
  :root:not([data-theme="light"]) .logo-dark { display: inline; }
}
:root[data-theme="dark"] .logo-light { display: none; }
:root[data-theme="dark"] .logo-dark { display: inline; }
.brand { font: 600 var(--wb-text-lg)/1 var(--wb-font-mono); }
.brand-app { color: var(--wb-accent); }

main.wb-card { max-width: 560px; margin: 0 auto; box-sizing: border-box; }
.stack { display: flex; flex-direction: column; gap: var(--wb-space-4); }
.stack > p { margin: 0; }
.field { display: flex; flex-direction: column; gap: var(--wb-space-2); }
.row { display: flex; gap: var(--wb-space-3); flex-wrap: wrap; }
.row > .wb-input { flex: 1; min-width: 0; }
.join { margin-top: var(--wb-space-5); padding-top: var(--wb-space-5); border-top: 1px solid var(--wb-border); }
.mono { font-family: var(--wb-font-mono); font-size: var(--wb-text-sm); }
.keys { margin-top: var(--wb-space-3); }
summary { cursor: pointer; font-size: var(--wb-text-sm); }

.drop {
  display: flex; flex-direction: column; gap: var(--wb-space-1); align-items: center; text-align: center;
  padding: var(--wb-space-6) var(--wb-space-4); border: 2px dashed var(--wb-border); border-radius: var(--wb-radius-card);
  cursor: pointer; transition: border-color 0.15s, background-color 0.15s;
}
.drop:hover, .drop.is-over { border-color: var(--wb-accent); background: color-mix(in srgb, var(--wb-accent) 8%, transparent); }
.drop:focus-within { outline: 2px solid var(--wb-fg); outline-offset: 2px; }
.drop.is-disabled { cursor: not-allowed; opacity: 0.55; }
.drop-title { font: 600 var(--wb-text-md)/1.3 var(--wb-font-mono); }
.xfer { display: flex; flex-direction: column; gap: var(--wb-space-2); }
.xfer-head { display: flex; gap: var(--wb-space-2); align-items: baseline; min-width: 0; }
.xfer-name { font-weight: 600; }
.xfer-foot { display: flex; justify-content: space-between; align-items: center; gap: var(--wb-space-3); font-size: var(--wb-text-sm); }
.xfer-track { display: flex; position: relative; }
.xfer progress { width: 100%; height: 8px; appearance: none; border: 0; border-radius: var(--wb-radius-pill); overflow: hidden; background: var(--wb-input-bg); box-shadow: inset 0 0 0 1px var(--wb-border); }
.xfer progress::-webkit-progress-bar { background: transparent; }
.xfer progress::-webkit-progress-value { background: var(--wb-accent); border-radius: var(--wb-radius-pill); }
.xfer progress::-moz-progress-bar { background: var(--wb-accent); border-radius: var(--wb-radius-pill); }
.spell { display: none; }

.history { gap: var(--wb-space-2); }
.history h2 { margin: 0; }
.history-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--wb-space-2); }
.hist { display: flex; align-items: center; gap: var(--wb-space-2); min-height: var(--wb-size-control-sm); font-size: var(--wb-text-sm); }
.hist-name { flex: 1; min-width: 0; font-weight: 600; }
.hist-state { color: var(--wb-muted); }
.hist--done .hist-state { color: var(--wb-success-fg); }
.hist--failed .hist-state { color: var(--wb-danger); }
.hist-check { display: none; }

.status-row { display: flex; align-items: center; gap: var(--wb-space-3); }
.status-row > .wb-alert { flex: 1; min-width: 0; }

.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

.dialog { max-width: min(440px, calc(100vw - 2 * var(--wb-space-4))); box-sizing: border-box; color: var(--wb-fg); }
.dialog::backdrop { background: color-mix(in srgb, var(--wb-bg) 60%, transparent); }
.room-info { font-size: var(--wb-text-sm); }
.room-info-row { align-items: center; }
.room-info-row > .room-info { margin: 0; }
.hint { margin: 0; font-size: var(--wb-text-sm); }
.room-hints { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--wb-space-1); font-size: var(--wb-text-sm); }
#set-room.wb-alert { gap: var(--wb-space-2); }
#set-room.wb-alert .room-hints { font-size: inherit; list-style: disc; padding-left: 1.25em; margin: 0; }
#set-room-lead[hidden] + .room-hints { list-style: none; padding-left: 0; }
.dialog h3.wb-label { margin: 0; }
.notify-card { display: flex; flex-direction: column; gap: var(--wb-space-3); }
.notify-card p { margin: 0; }
.check { display: flex; align-items: center; gap: var(--wb-space-2); cursor: pointer; }
.check input { accent-color: var(--wb-accent); width: 1.125rem; height: 1.125rem; margin: 0; flex: none; }
.qr-box { display: flex; flex-direction: column; align-items: center; gap: var(--wb-space-3); }
.invite-qr svg { display: block; width: 224px; height: 224px; max-width: 100%; border-radius: var(--wb-radius-card); }
.qr-box:not(.is-open) { display: none; }
.scan-video { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--wb-radius-card); background: #000; }
.offer-name { font-weight: 600; overflow-wrap: anywhere; }

/* Legal pages (T-026) and the footer that links to them: long-form reading width and rhythm. */
main.wb-card.legal, .top.legal-top { max-width: 680px; }
.legal h1 { margin: 0 0 var(--wb-space-2); }
.legal p { margin: 0 0 var(--wb-space-4); line-height: 1.6; }
.legal h2 { margin: var(--wb-space-6) 0 var(--wb-space-2); font: 600 var(--wb-text-xl)/1.2 var(--wb-font-heading); letter-spacing: -0.015em; }
.legal h3 { margin: var(--wb-space-5) 0 var(--wb-space-2); font: 600 var(--wb-text-md)/1.3 var(--wb-font-heading); }
.legal .effective { margin-bottom: var(--wb-space-5); font-size: var(--wb-text-sm); }
.legal ul, .legal ol { margin: 0 0 var(--wb-space-4); padding-left: var(--wb-space-5); }
.legal li { margin-bottom: var(--wb-space-2); line-height: 1.6; }
.legal li:last-child { margin-bottom: 0; }
.legal a { color: var(--wb-link); text-underline-offset: 3px; }
.legal-footer { display: flex; flex-wrap: wrap; gap: var(--wb-space-4); max-width: 560px; margin: var(--wb-space-5) auto 0; font-size: var(--wb-text-sm); }
.legal-footer a { color: var(--wb-muted); text-underline-offset: 3px; }
.legal-footer a:hover { color: var(--wb-fg); }
.legal-note { margin: 0; font-size: var(--wb-text-sm); }
.legal-note a { color: inherit; }
.lang-link { font-size: var(--wb-text-sm); }

/* Fun mode (D-053..D-057): everything under .fun on <html>; one-shot classes come from burst() in fun.ts. */
/* D-090: a dragged file charges the drop zone (marching ants drawn as four animated background strips, since a
   dashed border cannot move its dashes) and a send fires sparks down toward the sending bar. */
.fun .drop { position: relative; }
.fun .drop.is-over {
  border-color: transparent;
  background-color: color-mix(in srgb, var(--wb-accent) 10%, transparent);
  background-image:
    repeating-linear-gradient(90deg, var(--wb-accent) 0 8px, transparent 8px 16px),
    repeating-linear-gradient(90deg, var(--wb-accent) 0 8px, transparent 8px 16px),
    repeating-linear-gradient(0deg, var(--wb-accent) 0 8px, transparent 8px 16px),
    repeating-linear-gradient(0deg, var(--wb-accent) 0 8px, transparent 8px 16px);
  background-size: 100% 2px, 100% 2px, 2px 100%, 2px 100%;
  background-position: 0 0, 0 100%, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-origin: border-box;
  background-clip: border-box;
  box-shadow: 0 0 0 2px var(--wb-accent), 0 0 26px color-mix(in srgb, var(--wb-accent) 70%, transparent);
  animation: fun-ants 0.45s linear infinite, fun-charge 1s ease-in-out infinite alternate;
}
.fun .drop.is-fire::after {
  content: ""; position: absolute; left: 50%; bottom: 10px; width: 4px; height: 4px; margin-left: -2px;
  border-radius: 50%; background: var(--wb-accent); pointer-events: none;
  box-shadow: 0 0 6px 2px var(--wb-accent), 6px 7px 0 -0.5px var(--hot), -7px 5px 0 -1px var(--wb-accent), 3px 13px 0 -0.5px var(--hot), -3px 17px 0 -1px var(--wb-accent), 9px 19px 0 -1.5px var(--hot);
  animation: fun-fire 0.6s cubic-bezier(0.35, 0.8, 0.5, 1) forwards;
}
.fun .xfer progress { box-shadow: inset 0 0 0 1px var(--wb-border), 0 0 10px color-mix(in srgb, var(--wb-accent) 60%, transparent); }
/* Warm particle color: warning-fg is a bright peach on dark but a brown text color on light. */
.fun { --hot: var(--wb-danger); }
@media (prefers-color-scheme: dark) { :root.fun:not([data-theme="light"]) { --hot: var(--wb-warning-fg); } }
:root.fun[data-theme="dark"] { --hot: var(--wb-warning-fg); }
/* D-113, D-115: a spell beam from a wand dot to the fill's edge: white core, twisting coils, a small sparking impact
   orb. It stays still while the offer waits for the receiver's answer. */
.fun .spell { display: block; position: absolute; left: 0; right: 0; top: 50%; height: 28px; margin-top: -14px; pointer-events: none; }
.fun .xfer.is-waiting .spell { display: none; }
.fun .spell-beam { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; clip-path: inset(-20px calc((1 - var(--p, 0)) * 100%) -20px 0); }
.fun .spell-core { stroke: #fff; stroke-width: 2.4; fill: none; filter: drop-shadow(0 0 2px #fff) drop-shadow(0 0 4px var(--wb-accent)); }
.fun .spell-coil { fill: none; stroke-width: 1.4; stroke-linecap: round; animation: spell-flow 0.9s linear infinite; }
.fun .spell-coil.a { stroke: color-mix(in srgb, var(--wb-accent) 55%, #fff); }
.fun .spell-coil.b { stroke: var(--hot); opacity: 0.85; animation-duration: 0.7s; }
.fun .spell-coil.c { stroke: #fff; opacity: 0.7; stroke-width: 0.9; animation-duration: 1.2s; }
.fun .spell-wand { position: absolute; left: -3px; top: 50%; width: 6px; height: 6px; margin-top: -3px; border-radius: 50%; background: #fff; box-shadow: 0 0 4px 1px #fff, 0 0 10px 3px var(--wb-accent); }
.fun .spell-head { position: absolute; left: calc(var(--p, 0) * 100%); top: 50%; width: 13px; height: 13px; margin: -6.5px 0 0 -6.5px; border-radius: 50%;
  background: radial-gradient(circle, #fff 0 22%, color-mix(in srgb, var(--wb-accent) 70%, #fff) 40%, transparent 70%);
  box-shadow: 0 0 9px 3px color-mix(in srgb, var(--wb-accent) 70%, transparent); animation: spell-pulse 0.6s ease-in-out infinite alternate; }
.fun .spell-head::before, .fun .spell-head::after { content: ""; position: absolute; left: 50%; top: 50%; width: 3px; height: 3px; margin: -1.5px; border-radius: 50%; background: #fff;
  box-shadow: 6px -5px 0 0 var(--hot), 8px 3px 0 -0.5px #fff, 4px 6px 0 0 var(--wb-accent), -3px -8px 0 -0.5px #fff, 10px -1px 0 0.5px var(--hot);
  animation: spell-sparks 0.72s steps(3) infinite; }
.fun .spell-head::after { box-shadow: 7px 6px 0 0 #fff, 10px -4px 0 -0.5px var(--hot), 3px -8px 0 0 var(--wb-accent), 12px 2px 0 -0.5px #fff; animation-delay: -0.36s; }
.fun .xfer.is-done .spell, .fun .xfer.is-fizzle .spell { display: none; }
@keyframes spell-flow { to { transform: translateX(20px); } }
@keyframes spell-pulse { from { transform: scale(0.9); } to { transform: scale(1.08); } }
@keyframes spell-sparks { 0% { transform: translate(0, 0) scale(1); opacity: 1; } 50% { transform: translate(3px, -1.5px) scale(1.2); } 100% { transform: translate(5px, 1px) scale(0.4); opacity: 0; } }
/* D-089: Accept keeps its click synchronous; the package zips into the receiving bar instead of animating the dialog. */
.fun .xfer.is-zip { animation: fun-zip 0.45s cubic-bezier(0.2, 0.9, 0.3, 1.1); }
.fun .xfer.is-done { animation: fun-shake 0.55s ease-out; }
.fun .xfer.is-done progress { box-shadow: inset 0 0 0 1px var(--wb-accent), 0 0 22px var(--wb-accent); }
.fun .xfer.is-fizzle { animation: fun-fizzle 0.7s ease-in forwards; }
.fun .xfer.is-fizzle progress { box-shadow: inset 0 0 0 1px var(--wb-border); }
.fun .xfer.is-fizzle progress::-webkit-progress-value { background: var(--wb-muted); }
.fun .xfer.is-fizzle progress::-moz-progress-bar { background: var(--wb-muted); }
.fun main.is-pop { animation: fun-pop 0.45s cubic-bezier(0.3, 1.6, 0.5, 1); }
.fun main.is-boom { animation: fun-boom 0.7s ease-out; }
/* D-089, D-091: the offer slides in; each finished file lands in the history, a done check drawing itself. */
.fun dialog.is-arrive { animation: fun-arrive 0.55s cubic-bezier(0.22, 0.9, 0.32, 1.1); }
.fun .hist-check { display: block; width: 1rem; height: 1rem; flex: none; stroke-width: 2.5; color: var(--wb-success-fg); overflow: visible; }
.fun .hist-check path { stroke-dasharray: 24; stroke-dashoffset: 0; }
.fun .hist.is-land { animation: fun-land 0.5s cubic-bezier(0.3, 1.3, 0.5, 1); }
.fun .hist--done.is-land { animation-name: fun-land-pop; }
.fun .hist--done.is-land .hist-check path { animation: fun-check 0.42s ease-out; }
.fun .hist--declined.is-land { animation-name: fun-land-toss; }
.fun .hist--cancelled.is-land, .fun .hist--failed.is-land { animation-name: fun-land-grey; }

/* Entering a room and your own Leave: a quick swell with a few particles, no blur (D-093). */
.fun main.is-assemble, .fun main.is-bye { position: relative; }
.fun main.is-assemble::before, .fun main.is-bye::before {
  content: ""; position: absolute; top: 50%; left: 50%; width: 4px; height: 4px; margin: -2px; border-radius: 50%; pointer-events: none;
}
.fun main.is-assemble { animation: fun-swell 0.35s ease-out; }
.fun main.is-assemble > section { animation: fun-materialize 0.35s ease-out; }
.fun main.is-assemble::before { animation: fun-gather 0.35s ease-out forwards; }
.fun main.is-bye { animation: fun-settle 0.35s ease-in-out; }
.fun main.is-bye > section { animation: fun-dim 0.35s ease-in-out; }
.fun main.is-bye::before { animation: fun-drift 0.35s ease-out forwards; }
@keyframes fun-materialize { 0%, 35% { opacity: 0; transform: scale(0.96); } 100% { opacity: 1; transform: none; } }
@keyframes fun-gather {
  0% { opacity: 0; box-shadow: 180px -40px 0 1px var(--wb-accent), 140px 90px 0 0 var(--hot), -120px 120px 0 1px var(--wb-accent), -190px -50px 0 0 var(--hot), 20px -160px 0 1px var(--wb-accent); }
  25% { opacity: 1; }
  100% { opacity: 0; box-shadow: 24px -18px 0 0 var(--wb-accent), -16px 22px 0 0 var(--hot), 12px 20px 0 0 var(--wb-accent), -28px -12px 0 0 var(--hot), 4px -24px 0 0 var(--wb-accent); }
}
@keyframes fun-swell { 0% { transform: scale(0.98); } }
@keyframes fun-settle { 40% { transform: scale(0.99); } }
@keyframes fun-dim { 40% { opacity: 0.55; } }
@keyframes fun-drift {
  0% { opacity: 0; box-shadow: -80px -60px 0 1px var(--wb-accent), 90px 40px 0 0 var(--wb-muted), -60px 90px 0 1px var(--wb-accent), 70px -80px 0 0 var(--wb-muted); }
  20% { opacity: 0.9; }
  100% { opacity: 0; box-shadow: -140px -120px 0 0 var(--wb-accent), 150px 90px 0 0 var(--wb-muted), -110px 150px 0 0 var(--wb-accent), 130px -140px 0 0 var(--wb-muted); }
}

/* Copy Link confetti (D-084), the connection path (D-085) and the theme swap (D-086). */
.fun #copy.is-confetti { position: relative; }
.fun #copy.is-confetti::before, .fun #copy.is-confetti::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px;
  border-radius: 1px; background: var(--wb-accent); pointer-events: none;
  animation: fun-confetti-a 0.7s ease-out forwards;
}
.fun #copy.is-confetti::after { animation-name: fun-confetti-b; animation-duration: 0.85s; animation-delay: 0.04s; }
@keyframes fun-confetti-a {
  0% { opacity: 0; transform: translate(0, 0); box-shadow: 0 0 0 0 var(--wb-accent), 0 0 0 0 var(--hot), 0 0 0 0 var(--wb-success-fg), 0 0 0 0 var(--wb-accent), 0 0 0 0 var(--hot), 0 0 0 0 var(--wb-accent); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: translate(0, 24px); box-shadow: -34px -20px 0 0 var(--wb-accent), 27px -31px 0 1px var(--hot), -16px -40px 0 0 var(--wb-success-fg), 12px -35px 0 1px var(--wb-accent), 39px -13px 0 0 var(--hot), -41px -7px 0 1px var(--wb-accent); }
}
@keyframes fun-confetti-b {
  0% { opacity: 0; transform: translate(0, 0); box-shadow: 0 0 0 0 var(--hot), 0 0 0 0 var(--wb-accent), 0 0 0 0 var(--wb-success-fg), 0 0 0 0 var(--hot), 0 0 0 0 var(--wb-accent); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: translate(0, 30px); box-shadow: 31px -24px 0 0 var(--hot), -29px -35px 0 1px var(--wb-accent), 8px -43px 0 0 var(--wb-success-fg), -7px -19px 0 1px var(--hot), 21px -41px 0 0 var(--wb-accent); }
}

.fun .theme.is-swap { animation: fun-turn 0.45s cubic-bezier(0.4, 0, 0.2, 1); }
.fun .theme.is-swap svg { animation: fun-turn-in 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
@keyframes fun-turn { 50% { transform: rotate(90deg); } 100% { transform: none; } }
@keyframes fun-turn-in { 0% { opacity: 0; transform: rotate(-150deg) scale(0.4); } 70% { opacity: 1; } 100% { opacity: 1; transform: none; } }

/* Decoration only: #conn-path and #conn-detail above it stay the accessible content. */
.conn-graph { display: none; width: 100%; height: auto; color: var(--wb-muted); }
.fun .conn-graph.is-direct, .fun .conn-graph.is-relay { display: block; }
.conn-graph .conn-device, .conn-graph .conn-relay { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.conn-graph .conn-relay { display: none; }
.conn-graph.is-relay .conn-relay { display: inline; transform-box: fill-box; transform-origin: center; animation: fun-relay 0.45s ease-out 0.6s both; }
.conn-graph .conn-wire { fill: none; stroke: var(--wb-accent); stroke-width: 2.5; stroke-linecap: round; stroke-dasharray: 200; stroke-dashoffset: 200; animation: fun-draw 0.7s ease-out 0.1s forwards; }
.conn-graph.is-lan .conn-wire { stroke: var(--wb-success-fg); }
.conn-graph.is-relay .conn-wire { stroke: var(--wb-warning-fg); }
.conn-graph .conn-dot { fill: var(--wb-accent); animation: fun-travel 1.15s ease-in-out 0.55s both; }
.conn-graph.is-lan .conn-dot { fill: var(--wb-success-fg); }
.conn-graph.is-relay .conn-dot { fill: var(--wb-warning-fg); }
@keyframes fun-draw { to { stroke-dashoffset: 0; } }
@keyframes fun-travel { 0% { transform: translateX(0); opacity: 0; } 12% { opacity: 1; } 85% { opacity: 1; } 100% { transform: translateX(188px); opacity: 0; } }
@keyframes fun-relay { 0% { opacity: 0; transform: scale(0.4); } 100% { opacity: 1; transform: none; } }

/* D-116: big tabular MM:SS digits beside the status; they turn red and pulse as the room's time runs out. */
.fun .countdown { flex: none; font: 700 1.75rem/1 var(--wb-font-mono); font-variant-numeric: tabular-nums; letter-spacing: 0.02em; color: var(--wb-fg); }
.fun .countdown.is-scared { color: var(--wb-danger); text-shadow: 0 0 10px color-mix(in srgb, var(--wb-danger) 70%, transparent); }
.fun .countdown.is-tick { animation: fun-tick 0.4s ease-out; }
.fun .countdown.is-defused { animation: fun-countdown-out 0.9s ease-out forwards; }

/* Waiting pulse and reconnect plug (T-020, D-087, D-088): one glyph at a time, Fun mode only. */
.waiting { display: none; flex: none; width: 1.75rem; height: 1.75rem; }
.fun .waiting { display: block; position: relative; background: radial-gradient(circle at 50% 50%, var(--wb-accent) 0 3px, transparent 3.5px); }
.fun .waiting::before, .fun .waiting::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 12px; height: 12px; margin: -6px; box-sizing: border-box;
  border: 2px solid var(--wb-accent); border-radius: 50%; opacity: 0; animation: fun-listen 1.8s ease-out infinite;
}
.fun .waiting::after { animation-delay: -0.9s; }
.plug { display: none; flex: none; width: 2.5rem; height: 2.5rem; overflow: visible; }
.fun .plug { display: block; transform-origin: 25% 60%; animation: fun-plug-wobble 0.6s ease-in-out infinite; }
.plug-cord, .plug-prong { fill: none; stroke: var(--wb-muted); stroke-width: 1.6; stroke-linecap: round; }
.plug-body { fill: var(--wb-fg); }
.plug-spark { fill: var(--hot); transform-box: fill-box; transform-origin: center; animation: fun-plug-spark 0.7s steps(2) infinite; }

@keyframes fun-shake {
  15% { transform: translateX(-5px) rotate(-1deg); } 30% { transform: translateX(5px) rotate(1deg); }
  45% { transform: translateX(-3px); } 60% { transform: translateX(3px); } 80% { transform: translateX(-1px); }
}
@keyframes fun-fizzle {
  20% { transform: translateX(-3px) rotate(-0.5deg); } 40% { transform: translateX(2px); }
  60% { transform: translateX(-1px); opacity: 0.7; } 100% { transform: none; opacity: 0.25; filter: grayscale(1); }
}
@keyframes fun-pop { 0% { transform: scale(0.97); } 60% { transform: scale(1.015); } 100% { transform: none; } }
@keyframes fun-boom {
  0% { box-shadow: 0 0 0 0 var(--wb-danger); }
  10% { transform: translate(-6px, 3px) rotate(-0.6deg); } 25% { transform: translate(5px, -3px) rotate(0.5deg); }
  40% { transform: translate(-3px, 2px); } 60% { transform: translate(2px, -1px); }
  100% { transform: none; box-shadow: 0 0 0 28px transparent; }
}
@keyframes fun-tick {
  30% { transform: scale(calc(1.1 + var(--panic, 0) * 0.35)) rotate(calc(var(--panic, 0) * -10deg)); }
  65% { transform: rotate(calc(var(--panic, 0) * 8deg)); }
}
@keyframes fun-fade { 50% { opacity: 0.6; } }
@keyframes fun-countdown-out {
  0% { opacity: 1; transform: scale(1); }
  35% { opacity: 1; transform: scale(1.08); }
  100% { opacity: 0; transform: scale(0.9); }
}
@keyframes fun-listen { 0% { transform: scale(0.35); opacity: 0.9; } 100% { transform: scale(1.9); opacity: 0; } }
@keyframes fun-plug-wobble { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg) translateX(1px); } }
@keyframes fun-plug-spark { 50% { opacity: 0.15; transform: scale(0.5); } }
@keyframes fun-ants { to { background-position: 16px 0, -16px 100%, 0 -16px, 100% 16px; } }
@keyframes fun-charge {
  from { box-shadow: 0 0 0 1px color-mix(in srgb, var(--wb-accent) 45%, transparent), 0 0 8px color-mix(in srgb, var(--wb-accent) 50%, transparent); }
  to { box-shadow: 0 0 0 2px var(--wb-accent), 0 0 26px color-mix(in srgb, var(--wb-accent) 70%, transparent); }
}
@keyframes fun-fire {
  0% { opacity: 0; transform: translateY(-8px) scale(0.5); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translateY(52px) scale(1.2); }
}
@keyframes fun-zip {
  0% { opacity: 0; transform: translateY(-10px) scale(0.96); filter: brightness(1.5); }
  60% { opacity: 1; }
  100% { transform: none; filter: none; }
}
@keyframes fun-arrive {
  0% { opacity: 0; transform: translateY(-26px) scale(0.94) rotate(-1.2deg); }
  55% { opacity: 1; transform: translateY(4px) scale(1.01) rotate(0.7deg); }
  75% { transform: translateY(-2px) rotate(-0.35deg); }
  100% { transform: none; }
}
@keyframes fun-check { from { stroke-dashoffset: 24; } to { stroke-dashoffset: 0; } }
@keyframes fun-land {
  0% { opacity: 0; transform: translateY(-8px); }
  100% { transform: none; }
}
@keyframes fun-land-pop {
  0% { opacity: 0; transform: translateY(-10px) scale(0.96); }
  60% { opacity: 1; transform: translateY(1px) scale(1.02); }
  100% { transform: none; }
}
@keyframes fun-land-grey {
  0% { opacity: 0; transform: translateX(-16px); filter: grayscale(1); }
  60% { opacity: 1; }
  100% { transform: none; filter: none; }
}
@keyframes fun-land-toss {
  0% { opacity: 0; transform: translate(-18px, -14px) rotate(-8deg); filter: grayscale(1); }
  60% { opacity: 1; transform: translate(3px, 2px) rotate(1.5deg); }
  100% { transform: none; filter: none; }
}

/* D-056: no shake, flicker or movement; glow and the danger color stay, and a short fade still ends each burst. */
@media (prefers-reduced-motion: reduce) {
  .drop { transition: none; }
  .fun .xfer progress { animation: none; }
  .fun .spell-coil, .fun .spell-head { animation: none; }
  .fun .spell-head::before, .fun .spell-head::after { content: none; }
  .fun .xfer.is-done, .fun .xfer.is-fizzle, .fun main.is-pop, .fun main.is-boom, .fun main.is-assemble, .fun main.is-bye { animation: fun-fade 0.3s; }
  .fun .countdown.is-tick, .fun .countdown.is-defused { animation: fun-fade 0.3s; }
  .fun main.is-assemble::before, .fun main.is-bye::before { content: none; }
  .fun main.is-assemble > section, .fun main.is-bye > section { animation: none; }
  .fun #copy.is-confetti::before, .fun #copy.is-confetti::after { content: none; }
  .fun .theme.is-swap, .fun .theme.is-swap svg { animation: fun-fade 0.3s; }
  .fun .conn-graph .conn-wire { stroke-dashoffset: 0; animation: none; }
  .fun .conn-graph .conn-dot { display: none; }
  .fun .conn-graph.is-relay .conn-relay { animation: none; }
  .fun .waiting::before, .fun .waiting::after { content: none; }
  .fun .plug, .plug-spark { animation: none; }
  .fun .drop.is-over, .fun .hist--done.is-land .hist-check path { animation: none; }
  .fun .drop.is-fire::after, .fun .xfer.is-zip, .fun dialog.is-arrive, .fun .hist.is-land { animation: fun-fade 0.3s; }
}
