/* ══════════════════════════════════════════════════════════════════════════
   Mission: Hunt, Motion Layer
   Loaded last. Owns every transition, easing curve and micro-interaction so
   the timing language stays consistent across the whole app.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Easing curves */
  --ease-out:      cubic-bezier(0.22, 1, 0.36, 1);     /* quint, default exit-to-rest */
  --ease-expo:     cubic-bezier(0.16, 1, 0.30, 1);     /* long, decisive slides */
  --ease-spring:   cubic-bezier(0.34, 1.42, 0.52, 1);  /* slight overshoot */
  --ease-pop:      cubic-bezier(0.18, 1.6, 0.42, 1);   /* strong overshoot, small moves */
  --ease-inout:    cubic-bezier(0.45, 0.05, 0.25, 1);
  --ease-in:       cubic-bezier(0.55, 0, 0.85, 0.4);

  /* Durations */
  --t-instant: 90ms;
  --t-fast:    160ms;
  --t-base:    260ms;
  --t-slow:    380ms;
  --t-page:    460ms;
}

/* ══ 1. Press feedback ═════════════════════════════════════════════════════
   Every interactive surface reacts to touch. Scale is subtle and the release
   uses a springier curve than the press, which is what makes it feel alive
   rather than mechanical. */

.btn,
.login-btn,
.role-btn,
.boundary-type-btn,
.customize-btn,
.sub-tab,
.nav-item,
.feedback-btn,
.btn-icon,
.btn-icon-more,
.friends-load-more,
.action-menu button,
.lb-row,
.friend-item,
.stat-box,
.faceid-login-btn,
#ground-btn,
.avatar-wrapper,
.profile-view-close {
  transition:
    transform var(--t-base) var(--ease-spring),
    background-color var(--t-base) var(--ease-out),
    border-color var(--t-base) var(--ease-out),
    box-shadow var(--t-base) var(--ease-out),
    color var(--t-base) var(--ease-out),
    opacity var(--t-fast) linear;
  will-change: transform;
}

.btn:active,
.login-btn:active,
.customize-btn:active,
.faceid-login-btn:active,
.friends-load-more:active {
  transform: scale(0.968);
  transition-duration: var(--t-instant);
  transition-timing-function: var(--ease-in);
}

.role-btn:active,
.boundary-type-btn:active,
.feedback-btn:active,
.sub-tab:active,
.action-menu button:active {
  transform: scale(0.955);
  transition-duration: var(--t-instant);
}

.btn-icon:active,
.btn-icon-more:active,
.profile-view-close:active {
  transform: scale(0.86);
  transition-duration: var(--t-instant);
}

.lb-row:active,
.friend-item:active {
  transform: scale(0.985);
  transition-duration: var(--t-instant);
}

.avatar-wrapper:active { transform: scale(0.94); }

.nav-item:active .nav-icon { transform: scale(0.84); }

/* ══ 2. Ripple ════════════════════════════════════════════════════════════
   Injected by motion.js on pointerdown. Grows from the touch point and fades,
   so a tap has a visible origin instead of a flat state flip. */

.mh-ripple-host { position: relative; overflow: hidden; }

.mh-ripple {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0);
  background: radial-gradient(circle, currentColor 0%, transparent 72%);
  opacity: 0.26;
  animation: mhRipple 620ms var(--ease-expo) forwards;
  mix-blend-mode: screen;
}

@keyframes mhRipple {
  from { transform: translate(-50%, -50%) scale(0);   opacity: 0.3; }
  to   { transform: translate(-50%, -50%) scale(1);   opacity: 0; }
}

/* ══ 3. Primary button sheen ══════════════════════════════════════════════
   A light band sweeps across the primary action on press, reads as "this is
   the button that does the thing". */

.btn-primary::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -60%;
  width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.16), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
  opacity: 0;
}

.btn-primary:active::after,
.btn-primary.mh-sheen::after {
  animation: mhSheen 720ms var(--ease-out);
}

@keyframes mhSheen {
  0%   { left: -60%; opacity: 0; }
  18%  { opacity: 1; }
  100% { left: 115%; opacity: 0; }
}

/* Idle breathing glow on the main call to action. Done as an opacity fade on
   an overlay rather than an animated box-shadow: opacity is composited, so the
   loop costs no repaint, and there can be several of these on screen. */
.btn-primary::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 150% at 50% 125%, rgba(0, 229, 255, 0.26), transparent 62%);
  pointer-events: none;
  opacity: 0.15;
  animation: mhPrimaryIdle 4.6s ease-in-out infinite;
}
@keyframes mhPrimaryIdle {
  0%, 100% { opacity: 0.12; }
  50%      { opacity: 0.55; }
}
.btn-primary:active::before { animation: none; opacity: 0.7; }

/* ══ 4. Screen + card entrances ═══════════════════════════════════════════ */

@keyframes mhCardIn {
  from { opacity: 0; transform: translateY(16px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@keyframes mhCardInLeft {
  from { opacity: 0; transform: translateX(26px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}

@keyframes mhCardInRight {
  from { opacity: 0; transform: translateX(-26px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}

@keyframes mhCardOut {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-10px) scale(0.985); }
}

.mh-in       { animation: mhCardIn      var(--t-slow) var(--ease-out) both; }
.mh-in-left  { animation: mhCardInLeft  var(--t-slow) var(--ease-expo) both; }
.mh-in-right { animation: mhCardInRight var(--t-slow) var(--ease-expo) both; }
.mh-out      { animation: mhCardOut     var(--t-fast) var(--ease-in)  both; }

/* Play-tab cards get the directional treatment when navigating the flow */
#tab-play .card.mh-nav-in {
  animation: mhCardInLeft var(--t-page) var(--ease-expo) both;
}
#tab-play .card.mh-nav-back {
  animation: mhCardInRight var(--t-page) var(--ease-expo) both;
}

/* Header + menu appear on first paint of the app shell */
#app-shell:not(.hidden) .app-header { animation: mhCardIn 520ms var(--ease-out) both; }

/* ══ 5. Staggered list entrances ══════════════════════════════════════════
   Applied by motion.js only when a list goes from empty → populated, so the
   5-second community poll never re-triggers it. */

.mh-stagger > * {
  animation: mhRowIn 420ms var(--ease-out) both;
}
@keyframes mhRowIn {
  from { opacity: 0; transform: translateY(10px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}
.mh-stagger > *:nth-child(1)  { animation-delay: 0ms;   }
.mh-stagger > *:nth-child(2)  { animation-delay: 34ms;  }
.mh-stagger > *:nth-child(3)  { animation-delay: 68ms;  }
.mh-stagger > *:nth-child(4)  { animation-delay: 100ms; }
.mh-stagger > *:nth-child(5)  { animation-delay: 130ms; }
.mh-stagger > *:nth-child(6)  { animation-delay: 158ms; }
.mh-stagger > *:nth-child(7)  { animation-delay: 184ms; }
.mh-stagger > *:nth-child(8)  { animation-delay: 208ms; }
.mh-stagger > *:nth-child(9)  { animation-delay: 230ms; }
.mh-stagger > *:nth-child(n+10) { animation-delay: 250ms; }

/* ══ 6. Bottom nav ════════════════════════════════════════════════════════ */

.bottom-nav {
  background: rgba(17, 24, 39, 0.86);
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
}

.nav-item {
  position: relative;
  overflow: visible;
  transition: color var(--t-base) var(--ease-out);
}

.nav-icon {
  transition: transform var(--t-slow) var(--ease-pop);
}

.nav-item.active .nav-icon {
  transform: translateY(-2px) scale(1.12);
  filter: drop-shadow(0 0 8px rgba(0, 229, 255, 0.55));
}

.nav-item span {
  transition: transform var(--t-slow) var(--ease-out), opacity var(--t-base) var(--ease-out);
  opacity: 0.75;
}
.nav-item.active span { opacity: 1; }

/* Sliding indicator bar, position driven by --mh-nav-x / --mh-nav-w */
.bottom-nav::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  height: 2px;
  width: var(--mh-nav-w, 33.33%);
  transform: translateX(var(--mh-nav-x, 0px));
  background: linear-gradient(90deg, transparent, var(--accent), #00ff88, transparent);
  box-shadow: 0 0 12px rgba(0, 229, 255, 0.6);
  transition: transform var(--t-slow) var(--ease-spring), width var(--t-slow) var(--ease-spring);
  pointer-events: none;
  border-radius: 2px;
}
.bottom-nav.mh-nav-ready::before { opacity: 1; }
.bottom-nav::before { opacity: 0; }

/* ══ 7. Sub-tabs, sliding pill ═══════════════════════════════════════════ */

.sub-tabs { position: relative; }

.sub-tabs::before {
  content: '';
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: var(--mh-sub-w, 0px);
  transform: translateX(var(--mh-sub-x, 0px));
  background: var(--accent-dim);
  border-radius: 8px;
  transition: transform var(--t-slow) var(--ease-spring), width var(--t-slow) var(--ease-spring), opacity var(--t-fast);
  pointer-events: none;
  opacity: 0;
}
.sub-tabs.mh-sub-ready::before { opacity: 1; }

/* The pill replaces the old flat background so the highlight can travel */
.sub-tabs.mh-sub-ready .sub-tab.active { background: transparent; }
.sub-tab { position: relative; z-index: 1; }

/* ══ 8. Tab + section transitions ═════════════════════════════════════════ */

.tab-content.active {
  animation: mhTabIn var(--t-base) var(--ease-out) both;
}
@keyframes mhTabIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* Suppressed while a swipe drives the transition, the drag is the animation */
#app-shell.mh-swiping .tab-content.active,
.tab-content.active.mh-no-anim { animation: none; }

.community-section.active {
  animation: mhCardIn 340ms var(--ease-out) both;
}
.community-section.active.mh-no-anim { animation: none; }

/* Layer used by the swipe pager for the incoming screen */
.mh-slide-layer {
  position: fixed !important;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 500;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  background: var(--bg-dark);
  display: block !important;
  animation: none !important;
  will-change: transform;
}
.mh-slide-layer::before { content: none; }

/* A community section used as a slide layer sits outside its .container, so
   it has to carry that container's gutters itself. */
.community-section.mh-slide-layer {
  padding-left:  calc(env(safe-area-inset-left, 0px) + 16px);
  padding-right: calc(env(safe-area-inset-right, 0px) + 16px);
  padding-bottom: 20px;
}

.mh-slide-out { will-change: transform, opacity; }

#app-shell.mh-swiping { overscroll-behavior-x: none; }

/* ══ 9. Toggle switches ═══════════════════════════════════════════════════ */

.toggle-slider {
  transition: background-color var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out);
}
.toggle-slider:before {
  transition:
    transform var(--t-slow) var(--ease-pop),
    width var(--t-fast) var(--ease-out),
    background-color var(--t-base) var(--ease-out);
}
/* Squash while pressed, the knob stretches toward its destination */
.toggle-switch:active .toggle-slider:before { width: 27px; }
.toggle-switch input:checked:active + .toggle-slider:before { transform: translateX(19px); }

.toggle-group {
  transition: border-color var(--t-base) var(--ease-out), background-color var(--t-base) var(--ease-out);
}
.toggle-group:has(input:checked) {
  border-color: rgba(0, 229, 255, 0.32);
}

/* ══ 10. Role selector ════════════════════════════════════════════════════ */

.role-btn {
  transition:
    transform var(--t-base) var(--ease-spring),
    border-color var(--t-base) var(--ease-out),
    background-color var(--t-base) var(--ease-out),
    color var(--t-base) var(--ease-out),
    box-shadow var(--t-slow) var(--ease-out);
}
.role-btn.active { animation: mhRoleSnap 460ms var(--ease-pop); }
@keyframes mhRoleSnap {
  0%   { transform: scale(0.94); }
  55%  { transform: scale(1.035); }
  100% { transform: scale(1); }
}

/* ══ 11. Inputs ═══════════════════════════════════════════════════════════ */

.form-group input,
.auth-input,
.search-bar input,
.profile-bio-card textarea,
.settings-select,
.leaderboard-sort select {
  transition:
    border-color var(--t-base) var(--ease-out),
    box-shadow var(--t-base) var(--ease-out),
    background-color var(--t-base) var(--ease-out),
    transform var(--t-base) var(--ease-out);
}
.form-group input:focus,
.search-bar input:focus,
.profile-bio-card textarea:focus {
  transform: translateY(-1px);
}

/* Label lifts with its focused field */
.form-group { transition: transform var(--t-base) var(--ease-out); }
.form-group label { transition: color var(--t-base) var(--ease-out); }
.form-group:focus-within label { color: var(--accent); }

/* ══ 12. Cards ════════════════════════════════════════════════════════════ */

.card {
  transition: border-color var(--t-slow) var(--ease-out), box-shadow var(--t-slow) var(--ease-out);
}
.card::before {
  transform-origin: center;
  animation: mhCardScan 5.5s ease-in-out infinite;
}
@keyframes mhCardScan {
  0%, 100% { opacity: 0.55; transform: scaleX(0.7); }
  50%      { opacity: 1;    transform: scaleX(1);   }
}

/* Boundary editor open/close instead of a hard display flip */
.boundary-editor {
  display: block;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  border-width: 0;
  transition:
    max-height var(--t-page) var(--ease-expo),
    opacity var(--t-base) var(--ease-out),
    padding var(--t-page) var(--ease-expo),
    margin var(--t-page) var(--ease-expo),
    border-width var(--t-fast) linear;
}
.boundary-editor.active {
  max-height: 620px;
  opacity: 1;
  padding-top: 14px;
  padding-bottom: 14px;
  margin-top: 12px;
  border-width: 1px;
}

/* ══ 13. Modals ═══════════════════════════════════════════════════════════ */

.crop-modal {
  background: rgba(4, 7, 14, 0.72);
  backdrop-filter: blur(10px) saturate(0.8);
  -webkit-backdrop-filter: blur(10px) saturate(0.8);
  animation: mhFadeIn var(--t-base) var(--ease-out) both;
}
.crop-modal.mh-closing { animation: mhFadeOut var(--t-fast) var(--ease-in) both; }

@keyframes mhFadeIn  { from { opacity: 0; } to { opacity: 1; } }
@keyframes mhFadeOut { from { opacity: 1; } to { opacity: 0; } }

.crop-container {
  animation: mhModalIn 420ms var(--ease-spring) both;
}
.crop-modal.mh-closing .crop-container {
  animation: mhModalOut var(--t-fast) var(--ease-in) both;
}
@keyframes mhModalIn {
  from { opacity: 0; transform: translateY(24px) scale(0.94); }
  to   { opacity: 1; transform: none; }
}
@keyframes mhModalOut {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(10px) scale(0.97); }
}

/* Legal page slides up like a native sheet */
.legal-modal { animation: mhSheetIn var(--t-slow) var(--ease-expo) both; }
.legal-modal.mh-closing { animation: mhSheetOut var(--t-base) var(--ease-in) both; }
@keyframes mhSheetIn  { from { transform: translateY(100%); } to { transform: none; } }
@keyframes mhSheetOut { from { transform: none; } to { transform: translateY(100%); } }

/* Action dropdown */
.action-menu { animation: mhMenuIn 220ms var(--ease-spring) both; transform-origin: top right; }
@keyframes mhMenuIn {
  from { opacity: 0; transform: translateY(-6px) scale(0.9); }
  to   { opacity: 1; transform: none; }
}
.action-menu.mh-closing { animation: mhMenuOut 130ms var(--ease-in) both; }
@keyframes mhMenuOut {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-4px) scale(0.94); }
}

/* ══ 14. Toast ════════════════════════════════════════════════════════════ */

.error-toast {
  animation: mhToastIn 480ms var(--ease-spring) both;
  border: 1px solid rgba(255, 255, 255, 0.18);
}
.error-toast.mh-closing { animation: mhToastOut var(--t-base) var(--ease-in) both; }

@keyframes mhToastIn {
  from { transform: translate(-50%, -140%); opacity: 0; }
  to   { transform: translate(-50%, 0);     opacity: 1; }
}
@keyframes mhToastOut {
  from { transform: translate(-50%, 0) scale(1);      opacity: 1; }
  to   { transform: translate(-50%, -60%) scale(0.94); opacity: 0; }
}

/* ══ 15. Loading ══════════════════════════════════════════════════════════ */

#loading { animation: mhCardIn var(--t-slow) var(--ease-out) both; }

.loader {
  border-width: 3px;
  animation: spin 0.9s cubic-bezier(0.6, 0.05, 0.35, 0.95) infinite;
  box-shadow: 0 0 18px rgba(0, 229, 255, 0.18);
}

#loading p { animation: mhBreathe 1.8s ease-in-out infinite; }
@keyframes mhBreathe {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}

/* Skeleton shimmer for lists waiting on the network */
.mh-skeleton {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.mh-skeleton-row {
  height: 60px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background:
    linear-gradient(100deg,
      rgba(255, 255, 255, 0.02) 30%,
      rgba(0, 229, 255, 0.07) 50%,
      rgba(255, 255, 255, 0.02) 70%)
    var(--bg-card);
  background-size: 280% 100%;
  animation: mhShimmer 1.35s var(--ease-inout) infinite;
}
.mh-skeleton-row:nth-child(2) { animation-delay: 0.09s; }
.mh-skeleton-row:nth-child(3) { animation-delay: 0.18s; }
.mh-skeleton-row:nth-child(4) { animation-delay: 0.27s; }
.mh-skeleton-row:nth-child(5) { animation-delay: 0.36s; }
@keyframes mhShimmer {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}

/* ══ 16. Stats ════════════════════════════════════════════════════════════ */

.stat-box {
  transition: transform var(--t-slow) var(--ease-spring), border-color var(--t-base) var(--ease-out);
}
.stat-value { transition: color var(--t-base) var(--ease-out); }
.stat-box.mh-bumped {
  border-color: rgba(0, 229, 255, 0.45);
  animation: mhStatBump 620ms var(--ease-pop);
}
@keyframes mhStatBump {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.07); }
  100% { transform: scale(1); }
}

.stat-row { transition: background-color var(--t-base) var(--ease-out); }
.stat-row:active { background: rgba(0, 229, 255, 0.07); }

/* ══ 17. Leaderboard + friend rows ════════════════════════════════════════ */

.lb-row.medal-1 { animation: mhGoldPulse 3.4s ease-in-out infinite; }
@keyframes mhGoldPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 215, 0, 0);    }
  50%      { box-shadow: 0 0 20px -6px rgba(255, 215, 0, 0.55); }
}

.friend-avatar-sm, .lb-avatar, .avatar, .profile-view-avatar {
  transition: transform var(--t-slow) var(--ease-spring), box-shadow var(--t-base) var(--ease-out);
}

.friend-status-badge { transition: all var(--t-base) var(--ease-out); }

/* ══ 18. Login screen ═════════════════════════════════════════════════════ */

#login-screen:not(.hidden) .login-header { animation: mhCardIn 620ms var(--ease-out) both; }
#login-screen:not(.hidden) .login-card   { animation: mhCardIn 620ms var(--ease-out) 90ms both; }

.login-logo { animation: mhLogoFloat 6s ease-in-out infinite; }
@keyframes mhLogoFloat {
  0%, 100% { transform: translateY(0)    rotate(0deg);   }
  50%      { transform: translateY(-7px) rotate(-1.5deg); }
}

.header-logo-icon { animation: mhLogoFloat 7s ease-in-out infinite; }

/* App shell arrival. Opacity only, deliberately: animating a transform here
   would make #app-shell a containing block for its position:fixed children,
   and the bottom nav would jump to the end of the page mid-animation. */
#app-shell:not(.hidden) { animation: mhShellIn 520ms var(--ease-expo) both; }
@keyframes mhShellIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ══ 19. Splash dismissal ═════════════════════════════════════════════════ */

#splash-screen {
  transition: opacity 480ms var(--ease-out), transform 620ms var(--ease-expo), filter 480ms var(--ease-out);
}
#splash-screen.mh-dismissing {
  opacity: 0;
  transform: scale(1.09);
  filter: blur(6px);
  pointer-events: none;
}

#ground-btn:active { transform: translateY(-50%) scale(0.9); }

/* ══ 20. Rejoin banner ════════════════════════════════════════════════════ */

.rejoin-banner:not(.hidden) { animation: mhBannerIn 560ms var(--ease-spring) both, pulseGlow 2s ease-in-out 560ms infinite; }
@keyframes mhBannerIn {
  from { opacity: 0; transform: translateY(-18px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

/* ══ 21. In-game HUD (game.html) ══════════════════════════════════════════ */

.gameplay-page .game-hud   { animation: mhHudDown 560ms var(--ease-expo) both; }
.gameplay-page .bottom-hud { animation: mhHudUp   560ms var(--ease-expo) 80ms both; }

@keyframes mhHudDown {
  from { opacity: 0; transform: translateY(-24px); }
  to   { opacity: 1; transform: none; }
}
@keyframes mhHudUp {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}

.role-badge      { animation: mhPop 520ms var(--ease-pop) 120ms both; }
.mode-indicator  { animation: mhPop 520ms var(--ease-pop) 220ms both; }
@keyframes mhPop {
  from { opacity: 0; transform: scale(0.7); }
  to   { opacity: 1; transform: none; }
}

/* The timer is the thing players glance at, give it a slow pulse so it reads
   as live rather than as a static label. */
.timer {
  animation: mhTimerPulse 2s ease-in-out infinite;
  transition: color var(--t-base) var(--ease-out);
}
@keyframes mhTimerPulse {
  0%, 100% { text-shadow: 0 0 14px rgba(0, 229, 255, 0.35); }
  50%      { text-shadow: 0 0 26px rgba(0, 229, 255, 0.75); }
}

.status-item {
  transition: background-color var(--t-base) var(--ease-out), color var(--t-base) var(--ease-out);
}

/* Each new catch slides into the list instead of appearing */
.caught-item { animation: mhCaughtIn 420ms var(--ease-spring) both; }
@keyframes mhCaughtIn {
  from { opacity: 0; transform: translateX(-14px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}
/* A row that was already on screen before the list was rebuilt. It has already
   made its entrance, and playing it again on every state tick is what made the
   whole log bounce once a second. */
.caught-item.caught-item-settled { animation: none; }

.tag-btn {
  transition: transform var(--t-base) var(--ease-spring), background-color var(--t-base) var(--ease-out);
  animation: mhTagIdle 3.2s ease-in-out infinite;
}
@keyframes mhTagIdle {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 59, 92, 0);   }
  50%      { box-shadow: 0 0 26px -6px rgba(255, 59, 92, 0.6); }
}
.tag-btn:disabled { animation: none; }
.tag-btn:active:not(:disabled) { transform: scale(0.965); transition-duration: var(--t-instant); }

/* Overlays */
.game-end-overlay,
.connection-overlay { animation: mhFadeIn var(--t-base) var(--ease-out) both; }

.end-card        { animation: mhModalIn 560ms var(--ease-spring) 80ms both; }

/* The buffer banner slides up from the bottom rather than fading over the map */
.buffer-banner { animation: mhBufferIn 460ms var(--ease-spring) both; }
.buffer-banner-timer { animation: mhTimerPulse 1.6s ease-in-out infinite; }
@keyframes mhBufferIn {
  from { opacity: 0; transform: translateY(100%); }
  to   { opacity: 1; transform: translateY(0); }
}

.winners-box { animation: mhCardIn 480ms var(--ease-out) 260ms both; }

.warning-banner {
  animation: mhWarnIn 460ms var(--ease-spring) both, pulse 1s 460ms infinite;
}
@keyframes mhWarnIn {
  from { opacity: 0; transform: translate(-50%, -22px) scale(0.94); }
  to   { opacity: 1; transform: translateX(-50%) scale(1); }
}

/* ══ 21. Reduced motion ═══════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .mh-ripple { display: none; }
}
