/* ═══════════════════════════════════════════════════════════════════
   P65.69 — INSTITUTIONAL MOTION POLISH LAYER
   ─────────────────────────────────────────────────────────────────
   Sits on top of the existing P33 transition system (page-transition,
   fx-fade-up, fx-stagger). This file adds the "production-grade
   responsiveness" feel — every interaction confirms with subtle
   motion, every entrance rolls in, every exit fades out.

   Principles:
     • GPU-only properties (transform, opacity, filter) — no layout shifts
     • 120-280ms range for UI feedback (long enough to feel, short enough
       to never block)
     • Spring/ease-out curves so motion feels physical, not robotic
     • prefers-reduced-motion respected at the bottom (zeroes everything)

   Variables exported here are :root scope so the rest of the app can
   reuse them anywhere (existing code uses var(--transition) — kept
   that for back-compat).
   ═══════════════════════════════════════════════════════════════════ */

:root {
  --motion-fast:   120ms;   /* hover, press, focus rings */
  --motion-base:   220ms;   /* card lift, modal close, generic */
  --motion-slow:   380ms;   /* page transitions, large entrances */

  /* Easing curves — picked to match the iOS/Linear/Stripe feel */
  --ease-out:      cubic-bezier(0.22, 1, 0.36, 1);   /* default exit */
  --ease-spring:   cubic-bezier(0.16, 1, 0.30, 1);   /* entrance with subtle overshoot */
  --ease-emphasis: cubic-bezier(0.65, 0.05, 0.36, 1);
  --ease-in-out:   cubic-bezier(0.65, 0.00, 0.35, 1);
}

/* ─── Smooth scroll everywhere ────────────────────────────────── */

html {
  scroll-behavior: smooth;
}

/* ─── Global press feedback (no opt-in needed) ────────────────── */

/* Every button / .btn / link-as-button gets a subtle press-down
   when active. Disabled buttons opt out. We only animate transform
   so it composites and never causes layout. */
button:not(:disabled):not(.no-press),
.btn:not(:disabled):not(.no-press),
[role="button"]:not(.no-press),
.dropdown-item,
.nav-item,
.sidebar-item,
.tab,
.vote-btn,
.action-btn {
  transition:
    transform var(--motion-fast) var(--ease-spring),
    background-color var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out),
    box-shadow var(--motion-fast) var(--ease-out),
    opacity var(--motion-fast) var(--ease-out);
}
button:not(:disabled):not(.no-press):active,
.btn:not(:disabled):not(.no-press):active,
[role="button"]:not(.no-press):active {
  transform: scale(0.96);
}

/* Don't apply press to icon-only inline buttons that already have
   their own hover/active animation (vote-btn, modal-close, etc).
   Those are claimed via .no-press where needed. */
.modal-close { transition: background var(--motion-fast),
                          border-color var(--motion-fast),
                          color var(--motion-fast),
                          transform 0.18s var(--ease-spring); }

/* ─── Card hover lift ─────────────────────────────────────────── */

/* A single, consistent hover treatment for any "tappable card"-style
   element. Subtle 1-2px lift + soft accent glow + brighter background.
   We DO NOT apply this to every card globally — call sites that want
   it add `.fx-hover-lift`. The post card already has its own hover
   rules; adding the class is opt-in. */
.fx-hover-lift {
  transition:
    transform var(--motion-base) var(--ease-spring),
    box-shadow var(--motion-base) var(--ease-out),
    border-color var(--motion-base) var(--ease-out),
    background var(--motion-base) var(--ease-out);
}
.fx-hover-lift:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25),
              0 0 0 1px rgba(0, 196, 114, 0.18);
}

/* ─── Scroll-triggered entrance ───────────────────────────────── */

/* Items observed by the IntersectionObserver in app.js (S._motionObs)
   start invisible+offset; when they cross into the viewport, the
   observer adds .fx-in which transitions them to opacity:1 and
   translateY:0. Different from .fx-fade-up which animates immediately
   on render — this one animates on scroll arrival.

   Use .fx-watch on any element you want this for. The observer hooks
   it up automatically after every renderMainContent. */
.fx-watch {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity var(--motion-slow) var(--ease-out),
    transform var(--motion-slow) var(--ease-spring);
  will-change: opacity, transform;
}
.fx-watch.fx-in {
  opacity: 1;
  transform: translateY(0);
}

/* ─── Vote button micro-bounce ────────────────────────────────── */

@keyframes voteBounce {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.30); }
  60%  { transform: scale(0.92); }
  100% { transform: scale(1); }
}
.fx-vote-bounce {
  animation: voteBounce 360ms var(--ease-spring);
}

/* Floating "+1 cred" pop above the vote — already exists for some
   pages; defining a generic version other code can share. */
@keyframes floatPlusOne {
  0%   { opacity: 0; transform: translate(-50%, 0)    scale(0.8); }
  20%  { opacity: 1; transform: translate(-50%, -8px) scale(1); }
  90%  { opacity: 1; transform: translate(-50%, -28px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -38px) scale(0.9); }
}
.fx-plus-one {
  position: absolute;
  left: 50%;
  bottom: 100%;
  font-size: 11px;
  font-weight: 800;
  color: var(--accent);
  pointer-events: none;
  animation: floatPlusOne 1100ms var(--ease-out) forwards;
  text-shadow: 0 0 8px rgba(0, 196, 114, 0.6);
  font-family: var(--mono);
  z-index: 5;
}

/* ─── Save / bookmark heart-flutter ───────────────────────────── */

@keyframes saveFlutter {
  0%   { transform: scale(1) rotate(0); }
  25%  { transform: scale(1.35) rotate(-8deg); }
  50%  { transform: scale(0.92) rotate(6deg); }
  75%  { transform: scale(1.08) rotate(-3deg); }
  100% { transform: scale(1) rotate(0); }
}
.fx-save-flutter {
  animation: saveFlutter 520ms var(--ease-spring);
  transform-origin: center;
}

/* ─── Comment send: pop-and-fade ─────────────────────────────── */

@keyframes commentSendPop {
  0%   { transform: translateY(0)   scale(1); }
  40%  { transform: translateY(-3px) scale(1.06); }
  100% { transform: translateY(0)   scale(1); }
}
.fx-comment-send { animation: commentSendPop 280ms var(--ease-spring); }

/* New-comment highlight — flash the background emerald-tinted then
   fade to transparent over 1.5s. Applied AFTER the comment has
   rendered in place (P65.72), so we only animate the background —
   touching opacity/transform here would make the existing visible
   row briefly vanish-and-reappear (a "flash" the user would notice
   as a glitch). The bg-only flash is unmistakable but never feels
   like the row teleported. */
@keyframes newCommentFlash {
  0%   { background-color: rgba(0, 196, 114, 0.22); box-shadow: inset 3px 0 0 var(--accent); }
  60%  { background-color: rgba(0, 196, 114, 0.10); box-shadow: inset 3px 0 0 var(--accent); }
  100% { background-color: transparent;             box-shadow: inset 0 0 0 transparent; }
}
.fx-new-comment {
  animation: newCommentFlash 1.6s var(--ease-out);
}

/* ─── Tab indicator slide ─────────────────────────────────────── */

/* Tabs already use color/border to indicate active. Adding a smooth
   underline-fade so the transition between tabs feels continuous
   instead of a hard swap. */
.tab,
.feed-tab,
.profile-tab {
  position: relative;
}
.tab::after,
.feed-tab::after,
.profile-tab::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 0;
  height: 2px;
  width: 0;
  background: var(--accent);
  border-radius: 1px;
  transform: translateX(-50%);
  transition: width var(--motion-base) var(--ease-spring),
              opacity var(--motion-base) var(--ease-out);
  opacity: 0;
  pointer-events: none;
}
.tab.tab-active::after,
.feed-tab.feed-tab-active::after,
.feed-tab.tab-active::after,
.profile-tab.profile-tab-active::after,
.profile-tab.tab-active::after {
  width: 60%;
  opacity: 1;
  box-shadow: 0 0 8px rgba(0, 196, 114, 0.4);
}

/* ─── Sidebar nav item active glow ────────────────────────────── */

.sidebar-item,
.nav-item {
  position: relative;
}
.sidebar-item.active::before,
.nav-item.active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 60%;
  background: var(--accent);
  border-radius: 0 2px 2px 0;
  box-shadow: 0 0 12px rgba(0, 196, 114, 0.5);
  animation: sidebarGlowIn 220ms var(--ease-spring) both;
}
@keyframes sidebarGlowIn {
  from { transform: translate(-3px, -50%); opacity: 0; }
  to   { transform: translate(0, -50%);    opacity: 1; }
}

/* ─── Search input focus glow + clear-x rotate ───────────────── */

input.search-input,
input.header-search,
input[type="search"] {
  transition:
    border-color var(--motion-fast) var(--ease-out),
    background-color var(--motion-fast) var(--ease-out),
    box-shadow var(--motion-fast) var(--ease-out);
}
input.search-input:focus,
input.header-search:focus,
input[type="search"]:focus {
  box-shadow: 0 0 0 3px rgba(0, 196, 114, 0.18);
}

.search-clear-btn,
.btn-clear,
[data-action="clear-search"] {
  transition: transform var(--motion-fast) var(--ease-spring),
              opacity var(--motion-fast) var(--ease-out);
}
.search-clear-btn:hover,
.btn-clear:hover,
[data-action="clear-search"]:hover {
  transform: rotate(90deg);
}

/* ─── Modal: graceful close ───────────────────────────────────── */

/* Existing rule: .modal { animation: modalIn 0.32s ... }. Adding a
   close-out animation triggered by .closing class that the JS layer
   adds before removing the modal. */
@keyframes modalOut {
  from { opacity: 1; transform: translateY(0)    scale(1); }
  to   { opacity: 0; transform: translateY(8px)  scale(0.97); }
}
.modal.closing {
  animation: modalOut 180ms var(--ease-out) forwards;
}
.modal-overlay.closing {
  transition: opacity 180ms var(--ease-out);
  opacity: 0;
}

/* ─── Toast slide-in / slide-out (existing toasts get refined) ──── */

@keyframes toastInRefined {
  from { opacity: 0; transform: translateY(20px) scale(0.95); }
  to   { opacity: 1; transform: translateY(0)   scale(1); }
}
@keyframes toastOutRefined {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-12px) scale(0.96); }
}
.toast { animation: toastInRefined 240ms var(--ease-spring); }
.toast.closing { animation: toastOutRefined 180ms var(--ease-out) forwards; }

/* ─── Avatar / image hover shimmer (subtle) ───────────────────── */

.avatar.pointer,
.profile-avatar-wrap.pointer,
img.fx-clickable {
  transition: transform var(--motion-fast) var(--ease-spring),
              filter var(--motion-fast) var(--ease-out);
}
.avatar.pointer:hover,
.profile-avatar-wrap.pointer:hover,
img.fx-clickable:hover {
  transform: scale(1.05);
  filter: brightness(1.08);
}

/* ─── Like / heart click ripple ───────────────────────────────── */

@keyframes heartPulse {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.4); filter: drop-shadow(0 0 12px rgba(255, 88, 100, 0.7)); }
  60%  { transform: scale(0.9); }
  100% { transform: scale(1); filter: drop-shadow(0 0 0 transparent); }
}
.fx-heart-pulse { animation: heartPulse 480ms var(--ease-spring); }

/* ─── Skeleton-to-content swap ───────────────────────────────── */

.fx-skeleton-out {
  animation: fxSkeletonOut 240ms var(--ease-out) forwards;
}
@keyframes fxSkeletonOut {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.98); }
}

/* ─── Loader rotating spinner (used by "loading" placeholders) ── */

@keyframes spinnerRotate { to { transform: rotate(360deg); } }
.fx-spinner {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid rgba(0, 196, 114, 0.18);
  border-top-color: var(--accent);
  animation: spinnerRotate 720ms linear infinite;
  display: inline-block;
}

/* ─── Counter rolling animation ───────────────────────────────── */

/* When a counter changes (vote count, follower count), the new value
   slides up while the old one fades out. Apply via .fx-count-up on
   the wrapper containing the number text. */
@keyframes countUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.fx-count-up {
  display: inline-block;
  animation: countUp 240ms var(--ease-spring);
}

/* ─── Reduced motion: hard-disable everything ────────────────── */

@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;
  }
  .fx-watch { opacity: 1 !important; transform: none !important; }
}

/* ─── P65.74: Anti-spam comment lock banner ──────────────────────── */

/* Replaces the comment composer when the user has been locked for
   spamming. Red-tinted (destructive) so the user understands this is
   a sanction, not a soft warning. Lock icon on the left, plain-English
   countdown + reason text on the right. The remaining time updates
   every minute via a render tick (see renderPostDetail). */
.comment-lock-banner {
  display: flex;
  gap: 14px;
  align-items: center;
  padding: 16px 18px;
  margin: 0 16px 14px;
  border-radius: 12px;
  background: linear-gradient(135deg,
    rgba(239, 68, 68, 0.10) 0%,
    rgba(239, 68, 68, 0.04) 100%);
  border: 1px solid rgba(239, 68, 68, 0.35);
  box-shadow: 0 0 18px rgba(239, 68, 68, 0.06);
  animation: commentLockIn 280ms cubic-bezier(0.16, 1, 0.30, 1) both;
}
@keyframes commentLockIn {
  from { opacity: 0; transform: translateY(-6px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0)    scale(1); }
}
.comment-lock-icon {
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: rgba(239, 68, 68, 0.16);
  border: 1px solid rgba(239, 68, 68, 0.40);
  color: #ff8a8a;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.comment-lock-text { flex: 1; min-width: 0; }
.comment-lock-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-0);
  margin-bottom: 4px;
}
.comment-lock-sub {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.5;
}
.comment-lock-sub strong {
  color: #ff8a8a;
  font-family: var(--mono);
  font-weight: 700;
}

/* ─── P65.80: post-card skeleton (Trending page hydrate) ────────── */

/* Shown for ~0-300ms while the initial trending fetch is in flight.
   Shape mirrors the real post card so the layout doesn't jump when
   real content lands. Subtle shimmer animation tells the user "this
   is loading" without pulling visual attention. */
.trending-skeleton {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.skel-post-card {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  position: relative;
  overflow: hidden;
}
/* Single sliding shimmer across all skeleton cards — composited via
   ::after on each card so no extra DOM is needed. */
.skel-post-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 30%,
    rgba(255, 255, 255, 0.04) 50%,
    transparent 70%
  );
  background-size: 200% 100%;
  animation: skelShimmer 1.4s ease-in-out infinite;
  pointer-events: none;
}
@keyframes skelShimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -100% 0; }
}
.skel-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.skel-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  flex-shrink: 0;
}
.skel-text-block {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.skel-line {
  height: 10px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 4px;
}
.skel-line-name  { width: 30%; }
.skel-line-time  { width: 18%; height: 8px; }
.skel-line-title { width: 78%; height: 14px; margin-bottom: 8px; }
.skel-line-body  { width: 96%; height: 10px; margin-bottom: 14px; }
.skel-actions {
  display: flex;
  gap: 12px;
  margin-top: 4px;
}
.skel-chip {
  width: 42px;
  height: 22px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.04);
  display: inline-block;
}

/* ─── P65.85: Token top-up — + button on profile + modal ─────────── */

/* The small "+" button next to the Tokens count on the profile.
   Sits inline with the number, gold-tinted, scales up on hover. */
.profile-token-topup {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(247, 217, 121, 0.14);
  border: 1px solid rgba(247, 217, 121, 0.40);
  color: var(--gold, #f7d979);
  cursor: pointer;
  padding: 0;
  margin-left: 2px;
  transition: background 0.12s ease, border-color 0.12s ease,
              transform 0.18s cubic-bezier(0.16, 1, 0.30, 1),
              box-shadow 0.18s ease;
  flex-shrink: 0;
}
.profile-token-topup:hover {
  background: rgba(247, 217, 121, 0.28);
  border-color: rgba(247, 217, 121, 0.65);
  transform: scale(1.15) rotate(90deg);
  box-shadow: 0 0 12px rgba(247, 217, 121, 0.45);
}
.profile-token-topup:active { transform: scale(1.05) rotate(90deg); }
.profile-token-topup svg { display: block; }

/* ─── Top-up modal layout ────────────────────────────────────────── */

.topup-icon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(247, 217, 121, 0.10);
  border: 1px solid rgba(247, 217, 121, 0.28);
  flex-shrink: 0;
}
.topup-icon svg {
  filter: drop-shadow(0 0 6px rgba(247, 217, 121, 0.45));
}

/* Preset chip strip — 5 buttons in a responsive grid. Each chip shows
   the token amount above and the USD equivalent below. */
.topup-presets {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}
@media (max-width: 460px) {
  .topup-presets { grid-template-columns: repeat(3, 1fr); }
}
.topup-preset-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 10px 6px;
  border-radius: 10px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease,
              transform 0.15s cubic-bezier(0.16, 1, 0.30, 1);
  font-family: inherit;
  text-align: center;
}
.topup-preset-chip:hover {
  background: rgba(247, 217, 121, 0.06);
  border-color: rgba(247, 217, 121, 0.35);
  transform: translateY(-1px);
}
.topup-preset-chip.topup-preset-active {
  background: linear-gradient(180deg,
    rgba(247, 217, 121, 0.20),
    rgba(247, 217, 121, 0.08));
  border-color: rgba(247, 217, 121, 0.65);
  box-shadow: 0 0 0 1px rgba(247, 217, 121, 0.25), 0 4px 14px rgba(247, 217, 121, 0.10);
}
.topup-preset-amount {
  font-family: var(--mono);
  font-weight: 800;
  font-size: 13px;
  color: var(--text-0);
  letter-spacing: -0.01em;
}
.topup-preset-chip.topup-preset-active .topup-preset-amount { color: var(--gold, #f7d979); }
.topup-preset-usd {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  font-weight: 600;
}
.topup-preset-chip.topup-preset-active .topup-preset-usd { color: rgba(247, 217, 121, 0.7); }
.topup-preset-chip:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* P65.224 — refreshed top-up modal layout.  Was: section labels and
   panels styled with inline styles, inconsistent spacing, custom
   amount input bare, "you pay" panel green (clashed with the gold
   theme of the modal).  Now: shared section-label class, custom
   input as a focusable card with mono font, "you pay" reworked as a
   gold-tinted summary chip with a row layout. */
.topup-section-label {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-4);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  margin-bottom: 10px;
}
.topup-section-label-mt { margin-top: 18px; }

/* Custom-amount input — bordered card with focus-within glow that
   matches the gold preset chip language.  Mono font on the value,
   subtle "× ... stars" suffix to read as a calculator input. */
.topup-custom-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.topup-custom-wrap:focus-within {
  border-color: rgba(247, 217, 121, 0.55);
  box-shadow: 0 0 0 3px rgba(247, 217, 121, 0.10);
  background: rgba(247, 217, 121, 0.03);
}
.topup-custom-icon {
  flex-shrink: 0;
  color: var(--text-4);
  opacity: 0.55;
  transition: color 0.15s ease, opacity 0.15s ease;
}
.topup-custom-wrap:focus-within .topup-custom-icon {
  color: rgba(247, 217, 121, 0.90);
  opacity: 1;
}
.topup-custom-input {
  flex: 1;
  background: transparent;
  border: 0;
  outline: 0;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 15px;
  font-weight: 700;
  color: var(--text-0);
  min-width: 0;
  letter-spacing: 0.01em;
  /* Hide the spinner arrows for a cleaner look */
  -moz-appearance: textfield;
}
.topup-custom-input::-webkit-outer-spin-button,
.topup-custom-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* P65.336 — Hide native number-input spinners on every .input field
   site-wide.  The arrows look out of place against our dark UI and
   the user almost never wants to step a value by one — they type.
   Per-input opt-outs (none today) would override this rule. */
.input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.input[type="number"]::-webkit-outer-spin-button,
.input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.topup-custom-input::placeholder {
  color: var(--text-4);
  font-weight: 600;
}
.topup-custom-suffix {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-4);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* You-pay summary card — gold-tinted (was green which clashed with
   the rest of the gold theme).  Two-line layout: label + total on the
   first row, payment note as a small secondary line below. */
.topup-summary-card {
  margin-top: 18px;
  padding: 12px 14px 11px;
  background:
    linear-gradient(180deg,
      rgba(247, 217, 121, 0.08),
      rgba(247, 217, 121, 0.02));
  border: 1px solid rgba(247, 217, 121, 0.28);
  border-radius: 10px;
  box-shadow: inset 0 1px 0 rgba(247, 217, 121, 0.08);
}
.topup-summary-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}
.topup-summary-label {
  font-size: 10.5px;
  font-weight: 700;
  color: rgba(247, 217, 121, 0.85);
  letter-spacing: 0.10em;
  text-transform: uppercase;
}
.topup-summary-total {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 15px;
  font-weight: 800;
  color: var(--text-0);
  letter-spacing: -0.01em;
  text-align: right;
}
.topup-summary-note {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--text-4);
  font-weight: 500;
}

/* ─── P65.91: Token gift flow — mode link + recipient picker + confirm ─── */

/* "Send a gift?" / "Buy more?" toggle in the modal top-right.
   Plain text link styling — explicitly NOT a button, per design intent. */
.topup-mode-link {
  font-size: 12px;
  font-weight: 600;
  color: #f7d979;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: rgba(247, 217, 121, 0.35);
  transition: color 0.12s ease, text-decoration-color 0.12s ease;
  align-self: center;
  flex-shrink: 0;
}
.topup-mode-link:hover {
  color: #fff3ad;
  text-decoration-color: rgba(247, 217, 121, 0.85);
}

/* ═════════════════════════════════════════════════════════════════
   P65.104 — Token modal polish
   • Hide native browser number-spinner buttons on the custom-amount
     input (the chip presets already cover discrete values; the
     default Chrome/Firefox spinners look cheap and clash with the
     dark theme).
   • Tighten preset-chip min-height so topup ($X.XX subtitle) and
     gift (stars subtitle) chips stay perfectly aligned.
   • Subtle right-padding on the title-row already added inline so
     "Send a gift?" / "Buy more?" doesn't collide with the X close.
   ═════════════════════════════════════════════════════════════════ */

/* Cross-browser hide of the up/down arrows on the custom-amount
   inputs in BOTH topup and gift mode. Inputs still accept keyboard
   typing + chip selection. */
input#topUpCustomAmount::-webkit-outer-spin-button,
input#topUpCustomAmount::-webkit-inner-spin-button,
input#giftCustomAmount::-webkit-outer-spin-button,
input#giftCustomAmount::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input#topUpCustomAmount,
input#giftCustomAmount {
  -moz-appearance: textfield;        /* Firefox */
  appearance: textfield;
}

/* Lock chip min-height so all five presets line up at the same
   vertical center regardless of subtitle length (e.g. "$10.00" vs
   "stars"). Without this, the chip text-baseline can drift a pixel
   between rows. */
.topup-presets .topup-preset-chip {
  min-height: 54px;
}

/* ─── P65.104 — Lock token modal dimensions across modes ─────────
   When the user toggles between "Top up tokens" and "Send a gift",
   the modal previously resized to fit each mode's content (the
   "YOU PAY" panel in topup vs the recipient + error in gift). The
   reflow felt cheap and unprofessional. Fix: pin the modal at a
   stable width AND give the body a fixed min-height so both modes
   render at IDENTICAL dimensions. The stricter institutional feel
   matches the rest of the platform's gold-trim modals.

   The `.modal-tokenTopUp` class is added by renderModal() in
   app.js (P65.104) and removed by closeModal() so the rule scopes
   precisely to this one modal type. */
.modal.modal-tokenTopUp {
  max-width: 560px;        /* slightly wider than the global 520 to
                              accommodate 5 preset chips comfortably */
  width: 560px;            /* fixed width — no reflow on toggle */
}
.modal.modal-tokenTopUp .modal-body {
  min-height: 460px;       /* fits the taller of the two modes
                              (compose with recipient pill + amount
                              row + error line). Both modes render
                              at this height now — toggling is just
                              a content swap, not a reflow. */
  display: flex;
  flex-direction: column;
  /* Top-aligned. Whitespace pads the bottom of the shorter mode,
     which is intentional — gives the modal a stable institutional
     frame the way bank-grade interfaces handle multi-step flows. */
}
/* On narrow viewports, drop the fixed width — modal still respects
   its 100% / max-width fallback from the base .modal rule. */
@media (max-width: 600px) {
  .modal.modal-tokenTopUp {
    width: 100%;
    max-width: 100%;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   P65.122 — Profile entrance (minimal)
   ─────────────────────────────────────────────────────────────────
   Earlier (P65.121) attempt was too elaborate — fade + slide + scale
   + blur + stat-grid cascade made the page feel busy and amplified
   the visible repetition when async hydration re-rendered. Now: a
   single 200ms fade with a tiny 4px slide. No stat-grid stagger.
   The count-up animation handles the "numbers loading" feel.
   ═══════════════════════════════════════════════════════════════════ */

.profile-card-enter {
  animation: profileCardEnter 0.22s ease-out both;
}
@keyframes profileCardEnter {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .profile-card-enter { animation-duration: 0.12s; }
  @keyframes profileCardEnter { from { opacity: 0; } to { opacity: 1; } }
}

/* ─── P65.119 — Lock followList modal dimensions ─────────────────
   Followers / Following / Mutuals tabs all render into the same
   modal frame. Different list lengths (1 row vs 50 rows) used to
   make the box snap between heights — felt cheap. Pin a fixed
   width + min-height so the frame is stable; the inner list area
   scrolls when it overflows. */
.modal.modal-followList {
  max-width: 480px;
  width: 480px;
}
.modal.modal-followList .modal-body {
  min-height: 420px;
  max-height: 60vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
@media (max-width: 540px) {
  .modal.modal-followList {
    width: 100%;
    max-width: 100%;
  }
}

/* The "stars" subtitle on gift chips uses the same `.topup-preset-usd`
   class as the topup "$X.XX" subtitle — same font/color/size. But
   when we ALSO want a subtle uppercase letterspacing for the word
   "stars" so it reads as a label rather than a number suffix. */
.topup-preset-usd:not(:empty) {
  text-transform: lowercase;
  letter-spacing: 0.04em;
}

/* Balance pill at the top of the gift form */
.gift-balance {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: linear-gradient(180deg, rgba(247, 217, 121, 0.06), rgba(247, 217, 121, 0.02));
  border: 1px solid rgba(247, 217, 121, 0.20);
  border-radius: 10px;
  margin-bottom: 4px;
}
.gift-balance-label {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-4);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.gift-balance-num {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 16px;
  font-weight: 800;
  color: #fff3ad;
}

/* Recipient search wrap (input + spinner) */
.gift-search-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.gift-search-wrap:focus-within {
  border-color: rgba(247, 217, 121, 0.45);
  box-shadow: 0 0 0 3px rgba(247, 217, 121, 0.10);
}

/* Search results dropdown — list of matching users */
.gift-results {
  margin-top: 8px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  max-height: 240px;
  overflow-y: auto;
  animation: giftResultsIn 180ms cubic-bezier(0.16, 1, 0.30, 1) both;
}
@keyframes giftResultsIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.gift-result {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  transition: background 0.12s ease;
}
.gift-result:last-child { border-bottom: 0; }
.gift-result:hover {
  background: rgba(247, 217, 121, 0.06);
}
.gift-result-meta { flex: 1; min-width: 0; }
.gift-result-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-0);
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gift-result-handle {
  font-size: 11px;
  color: var(--text-4);
  font-family: var(--mono);
  margin-top: 1px;
}
.gift-no-results {
  margin-top: 8px;
  padding: 14px;
  text-align: center;
  font-size: 12px;
  color: var(--text-4);
  background: var(--bg-1);
  border: 1px dashed var(--border);
  border-radius: 8px;
}

/* Selected recipient pill — replaces the search input once chosen */
.gift-recipient-pill {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: linear-gradient(180deg, rgba(247, 217, 121, 0.10), rgba(247, 217, 121, 0.03));
  border: 1px solid rgba(247, 217, 121, 0.40);
  border-radius: 10px;
  animation: giftResultsIn 200ms cubic-bezier(0.16, 1, 0.30, 1) both;
}
.gift-recipient-meta { flex: 1; min-width: 0; }
.gift-recipient-name {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-0);
  letter-spacing: -0.01em;
}
.gift-recipient-handle {
  font-size: 11.5px;
  color: var(--text-3);
  font-family: var(--mono);
  margin-top: 1px;
}
.gift-recipient-clear {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border);
  color: var(--text-3);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.18s cubic-bezier(0.16, 1, 0.30, 1);
}
.gift-recipient-clear:hover {
  background: rgba(239, 68, 68, 0.10);
  border-color: rgba(239, 68, 68, 0.40);
  color: #ff8a8a;
  transform: rotate(90deg);
}

/* Insufficient-balance error inline */
.gift-error {
  font-size: 11.5px;
  color: #ff8a8a;
  margin-top: 6px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* P65.224 — gift confirm step rebuilt.  Was: small icon + paragraph
   of mixed prose with bold spans.  Now: hero icon with pulsing ring,
   bold title, and a "flow" row that visually connects the amount
   chip → arrow → recipient chip — reads as "X stars going to @user"
   in one glance.  Warning becomes a small chip at the bottom. */
.gift-confirm {
  text-align: center;
  padding: 6px 0 14px;
  animation: giftResultsIn 240ms cubic-bezier(0.16, 1, 0.30, 1) both;
}

/* Icon hero with pulsing emerald-gold ring */
.gift-confirm-icon-wrap {
  position: relative;
  width: 84px;
  height: 84px;
  margin: 0 auto 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gift-confirm-icon-pulse {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%,
    rgba(247, 217, 121, 0.30) 0%,
    rgba(247, 217, 121, 0.0) 65%);
  animation: giftConfirmPulse 2.4s ease-in-out infinite;
}
@keyframes giftConfirmPulse {
  0%, 100% { transform: scale(0.95); opacity: 0.7; }
  50%      { transform: scale(1.10); opacity: 1.0; }
}
.gift-confirm-icon {
  position: relative;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 30% 30%,
      rgba(255, 243, 173, 0.18) 0%,
      rgba(247, 217, 121, 0.08) 50%,
      rgba(212, 167, 44, 0.04) 100%);
  border: 1px solid rgba(247, 217, 121, 0.40);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #f7d979;
  filter: drop-shadow(0 0 18px rgba(247, 217, 121, 0.40));
  z-index: 1;
}

.gift-confirm-title {
  font-size: 19px;
  font-weight: 800;
  color: var(--text-0);
  letter-spacing: -0.018em;
  margin-bottom: 18px;
}

/* Flow row: amount chip → arrow → recipient chip.  Wraps on narrow
   widths so the layout never overflows. */
.gift-confirm-flow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 0 16px;
  margin-bottom: 18px;
}
.gift-confirm-amount-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 14px 9px 12px;
  background:
    linear-gradient(180deg,
      rgba(247, 217, 121, 0.16),
      rgba(247, 217, 121, 0.06));
  border: 1px solid rgba(247, 217, 121, 0.45);
  border-radius: 999px;
  box-shadow: 0 0 18px -8px rgba(247, 217, 121, 0.55);
}
.gift-confirm-amount-num {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 16px;
  font-weight: 800;
  color: #fff3ad;
  letter-spacing: -0.01em;
}
.gift-confirm-amount-unit {
  font-size: 11px;
  font-weight: 700;
  color: rgba(247, 217, 121, 0.75);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.gift-confirm-arrow {
  flex-shrink: 0;
  color: var(--text-3);
  opacity: 0.7;
}
.gift-confirm-recipient-chip {
  display: inline-flex;
  align-items: baseline;
  padding: 9px 14px 9px 12px;
  background:
    linear-gradient(180deg,
      rgba(0, 196, 114, 0.14),
      rgba(0, 196, 114, 0.05));
  border: 1px solid rgba(0, 196, 114, 0.40);
  border-radius: 999px;
  box-shadow: 0 0 18px -8px rgba(0, 196, 114, 0.55);
  font-family: var(--mono, ui-monospace, monospace);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.005em;
  color: var(--accent);
}
.gift-confirm-recipient-at {
  opacity: 0.6;
  margin-right: 1px;
}
.gift-confirm-recipient-name {
  color: #b6f3d3;
}

/* Warning chip — replaces the old grey footnote so the irreversible
   nature of the action carries more weight. */
.gift-confirm-warn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px 5px 9px;
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.30);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: #ffb4b4;
  letter-spacing: 0.01em;
}
.gift-confirm-warn svg { flex-shrink: 0; }

/* Reduced-motion: drop the icon pulse */
@media (prefers-reduced-motion: reduce) {
  .gift-confirm-icon-pulse { animation: none; opacity: 0.4; }
}

/* ─── P65.88: Top-up celebration — institutional gold treatment ──
   Replaces the previous "happy birthday" burst with a controlled,
   choreographed reveal:
     • Frosted-glass card material with a thin gold accent line
     • Hero panel (orbit ring + 6 twinkling sparkles + 3D star)
     • Staggered entrance — accent → star → number → sub → CTA
     • Calmer particle pattern (12 around the card, not 36 chaos)
   Reuses .verified-celebration shell from pages.css for the backdrop /
   particle / ring infrastructure; everything below is the gold layer. */

.topup-celebration .vc-card {
  position: relative;
  background:
    radial-gradient(60% 80% at 50% 0%, rgba(247, 217, 121, 0.10), transparent 70%),
    linear-gradient(180deg, rgba(20, 18, 14, 0.92), rgba(8, 8, 10, 0.92));
  border-color: rgba(247, 217, 121, 0.32);
  border-width: 1px;
  border-radius: 22px;
  padding: 32px 36px 26px;
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  box-shadow:
    0 0 0 1px rgba(247, 217, 121, 0.06) inset,
    0 1px 0 rgba(255, 255, 255, 0.04) inset,
    0 0 100px rgba(247, 217, 121, 0.14),
    0 30px 80px rgba(0, 0, 0, 0.7);
  overflow: hidden;
  /* Subtle entrance — scale + lift, slower than verified card */
  animation: topupCardIn 520ms cubic-bezier(0.16, 1, 0.30, 1) both;
}
@keyframes topupCardIn {
  0%   { opacity: 0; transform: translateY(14px) scale(0.94); }
  100% { opacity: 1; transform: translateY(0)    scale(1); }
}

/* Thin animated gold accent line at the very top of the card.
   Sweeps in from off-screen left, then pulses softly while open. */
.topup-card-accent {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(247, 217, 121, 0.4) 20%,
    #f7d979 50%,
    rgba(247, 217, 121, 0.4) 80%,
    transparent 100%
  );
  background-size: 200% 100%;
  filter: drop-shadow(0 0 8px rgba(247, 217, 121, 0.6));
  animation: topupAccentSweep 700ms cubic-bezier(0.22, 1, 0.36, 1) both,
             topupAccentShimmer 4s linear 700ms infinite;
  pointer-events: none;
}
@keyframes topupAccentSweep {
  from { transform: scaleX(0); transform-origin: 0 0; opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}
@keyframes topupAccentShimmer {
  0%   { background-position:   0% 50%; }
  100% { background-position: 200% 50%; }
}

/* Outer rings — gold-tinted, slightly thicker than verified */
.topup-celebration .topup-ring {
  border-color: rgba(247, 217, 121, 0.45) !important;
  border-width: 1.5px !important;
  filter: drop-shadow(0 0 18px rgba(247, 217, 121, 0.30));
}

/* ─── Hero panel: star + orbit ring + sparkles ──────────────── */

.topup-hero {
  position: relative;
  width: 160px;
  height: 160px;
  margin: 0 auto 18px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Thin orbit ring around the star. Counter-rotates slowly. */
.topup-orbit-ring {
  position: absolute;
  inset: 22px;
  border-radius: 50%;
  border: 1px dashed rgba(247, 217, 121, 0.32);
  animation: topupOrbitRotate 14s linear infinite;
  pointer-events: none;
}
@keyframes topupOrbitRotate {
  to { transform: rotate(360deg); }
}

/* Twinkling sparkles — 6 small stars at varying radii, each pulsing
   on its own phase so the group feels alive. CSS-only. */
.topup-sparkles {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.topup-sparkle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #fff3ad;
  box-shadow: 0 0 6px #f7d979, 0 0 12px rgba(247, 217, 121, 0.6);
  /* Each sparkle is positioned at angle --sa, distance --sr from
     center via transform-origin trick + rotation. */
  transform-origin: 0 0;
  transform: rotate(var(--sa)) translate(var(--sr)) rotate(calc(-1 * var(--sa)));
  animation: topupSparkleTwinkle var(--sx, 2.6s) ease-in-out var(--sd, 0s) infinite;
  opacity: 0;
}
@keyframes topupSparkleTwinkle {
  0%, 100% { opacity: 0;   transform: rotate(var(--sa)) translate(var(--sr)) rotate(calc(-1 * var(--sa))) scale(0.6); }
  50%      { opacity: 1;   transform: rotate(var(--sa)) translate(var(--sr)) rotate(calc(-1 * var(--sa))) scale(1.2); }
}

/* Star centerpiece — slightly larger, more pronounced glow,
   refined entrance animation. */
.topup-celebration .topup-star-wrap {
  width: 84px;
  height: 84px;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  filter: drop-shadow(0 0 24px rgba(247, 217, 121, 0.65))
          drop-shadow(0 0 48px rgba(247, 217, 121, 0.25));
  position: relative;
  z-index: 2;
}
.topup-celebration .topup-star {
  animation:
    topupStarPop 800ms cubic-bezier(0.16, 1, 0.30, 1) both,
    topupStarFloat 5s ease-in-out 800ms infinite;
}
@keyframes topupStarPop {
  0%   { transform: scale(0.2) rotate(-45deg); opacity: 0; }
  55%  { transform: scale(1.22) rotate(6deg);  opacity: 1; }
  78%  { transform: scale(0.96) rotate(-2deg); }
  100% { transform: scale(1)    rotate(0);     opacity: 1; }
}
@keyframes topupStarFloat {
  0%, 100% { transform: translateY(0)    rotate(0); }
  50%      { transform: translateY(-3px) rotate(2deg); }
}

/* ─── Typography ────────────────────────────────────────────── */

.topup-celebration .topup-eyebrow {
  color: #f7d979;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-align: center;
  margin-bottom: 8px;
  text-shadow: 0 0 12px rgba(247, 217, 121, 0.55);
  /* Staggered fade-up — comes in after star pops */
  animation: topupItemIn 500ms cubic-bezier(0.22, 1, 0.36, 1) 280ms both;
}

.topup-celebration .topup-title {
  background: linear-gradient(180deg, #fff3ad 0%, #f7d979 45%, #d4a72c 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  font-size: 64px;
  font-weight: 900;
  font-family: var(--mono);
  letter-spacing: -0.025em;
  text-align: center;
  line-height: 1;
  filter: drop-shadow(0 0 24px rgba(247, 217, 121, 0.5));
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: 2px;
  animation: topupItemIn 600ms cubic-bezier(0.22, 1, 0.36, 1) 340ms both;
}
.topup-celebration .topup-title-plus {
  font-size: 0.7em;
  opacity: 0.85;
}
.topup-celebration .topup-title-num {
  /* Tabular numerals so the digits don't shift width during count-up */
  font-variant-numeric: tabular-nums;
}

.topup-celebration .topup-sub {
  color: var(--text-2);
  font-size: 13.5px;
  text-align: center;
  margin-top: 6px;
  letter-spacing: 0.01em;
  animation: topupItemIn 500ms cubic-bezier(0.22, 1, 0.36, 1) 540ms both;
}

@keyframes topupItemIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ─── Receipt link & dismiss button ─────────────────────────── */

.topup-celebration .topup-receipt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 18px auto 0;
  padding: 7px 14px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(247, 217, 121, 0.22);
  border-radius: 999px;
  color: var(--text-3);
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  animation: topupItemIn 500ms cubic-bezier(0.22, 1, 0.36, 1) 700ms both;
}
.topup-celebration .topup-receipt:hover {
  background: rgba(247, 217, 121, 0.10);
  border-color: rgba(247, 217, 121, 0.5);
  color: #f7d979;
}
.topup-celebration .topup-receipt .vc-receipt-label {
  color: var(--text-4);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 9.5px;
}
.topup-celebration .topup-receipt .vc-receipt-sig {
  color: var(--text-1);
  font-weight: 700;
}

.topup-celebration .topup-dismiss {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 18px auto 0;
  padding: 11px 26px;
  background: linear-gradient(180deg, rgba(247, 217, 121, 0.30), rgba(212, 167, 44, 0.18));
  border: 1px solid rgba(247, 217, 121, 0.55);
  border-radius: 999px;
  color: #fff3ad;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition:
    background 0.15s ease,
    border-color 0.15s ease,
    transform 0.18s cubic-bezier(0.16, 1, 0.30, 1),
    box-shadow 0.18s ease;
  box-shadow: 0 4px 16px rgba(247, 217, 121, 0.16);
  animation: topupItemIn 500ms cubic-bezier(0.22, 1, 0.36, 1) 820ms both;
}
.topup-celebration .topup-dismiss:hover {
  background: linear-gradient(180deg, rgba(247, 217, 121, 0.42), rgba(212, 167, 44, 0.28));
  border-color: rgba(247, 217, 121, 0.85);
  transform: translateY(-1px);
  box-shadow: 0 8px 24px rgba(247, 217, 121, 0.30);
}
.topup-celebration .topup-dismiss:active {
  transform: translateY(0) scale(0.98);
}
.topup-celebration .topup-dismiss svg {
  transition: transform 0.18s cubic-bezier(0.16, 1, 0.30, 1);
}
.topup-celebration .topup-dismiss:hover svg {
  transform: translateX(2px);
}

/* Particle entrance — they emerge with a slight scale-up, not just
   "fly out" — feels orchestrated. */
.topup-celebration .vc-particle {
  border-radius: 50%;
}

/* ─── Profile token-stat: pulse highlight while count animates ─── */

.profile-token-stat {
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
.profile-token-stat-celebrating {
  background: radial-gradient(60% 100% at 50% 50%,
    rgba(247, 217, 121, 0.16) 0%,
    rgba(247, 217, 121, 0.04) 60%,
    transparent 100%);
  box-shadow: inset 0 0 24px rgba(247, 217, 121, 0.12);
  animation: profileTokenPulse 1.6s ease-out;
}
@keyframes profileTokenPulse {
  0%, 100% { transform: scale(1); }
  35%      { transform: scale(1.04); }
}
.profile-token-stat-celebrating .profile-token-count {
  color: #f7d979;
  text-shadow: 0 0 12px rgba(247, 217, 121, 0.55);
  transition: color 0.4s ease;
}

/* "+N" floating pip that rises above the count during the animation */
.profile-token-pip {
  position: absolute;
  left: 50%;
  top: -2px;
  transform: translate(-50%, 0);
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 800;
  color: #f7d979;
  text-shadow: 0 0 14px rgba(247, 217, 121, 0.85);
  pointer-events: none;
  white-space: nowrap;
  animation: profileTokenPipRise 1.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  z-index: 5;
}
@keyframes profileTokenPipRise {
  0%   { opacity: 0; transform: translate(-50%, 6px)  scale(0.8); }
  18%  { opacity: 1; transform: translate(-50%, -4px) scale(1.1); }
  60%  { opacity: 1; transform: translate(-50%, -22px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -38px) scale(0.95); }
}

/* ═════════════════════════════════════════════════════════════════
   P65.102 — First-topup cred bonus
   • In-card banner shown inside the topup celebration when the
     server awarded the +N cred bonus on this top-up.
   • Cred-card pulse + floating "+N" pip on the profile-page CRED
     card after the celebration closes (mirrors the token-stat one).
   ═════════════════════════════════════════════════════════════════ */

.topup-celebration .topup-bonus-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px auto 0;
  padding: 10px 14px;
  width: max-content;
  max-width: 92%;
  border-radius: 12px;
  background: linear-gradient(135deg,
    rgba(255, 243, 173, 0.10) 0%,
    rgba(247, 217, 121, 0.16) 50%,
    rgba(212, 167, 44, 0.10) 100%);
  border: 1px solid rgba(247, 217, 121, 0.32);
  box-shadow:
    0 6px 18px rgba(247, 217, 121, 0.10),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  opacity: 0;
  transform: translateY(8px) scale(0.96);
  animation: topupBonusBannerIn 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.85s forwards;
}
@keyframes topupBonusBannerIn {
  0%   { opacity: 0; transform: translateY(8px) scale(0.96); }
  60%  { opacity: 1; transform: translateY(-1px) scale(1.02); }
  100% { opacity: 1; transform: translateY(0)    scale(1); }
}

.topup-celebration .topup-bonus-icon {
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  border-radius: 8px;
  background: linear-gradient(135deg, #fff3ad 0%, #f7d979 50%, #d4a72c 100%);
  color: #2a1a00;
  flex-shrink: 0;
  box-shadow:
    0 4px 12px rgba(247, 217, 121, 0.32),
    inset 0 1px 0 rgba(255, 255, 255, 0.5);
}

.topup-celebration .topup-bonus-text {
  display: flex; flex-direction: column;
  text-align: left;
  line-height: 1.2;
}
.topup-celebration .topup-bonus-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #f7d979;
}
.topup-celebration .topup-bonus-sub {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-0);
  margin-top: 2px;
}

/* Cred-card pulse — same pattern as profile-token-stat-celebrating
   but tinted toward the platform accent (green) so it visually
   reads as "cred earned" rather than "stars added". */
.cred-score-card.profile-cred-celebrating,
.stat-card.profile-cred-celebrating {
  position: relative;
  animation: profileCredPulse 1.8s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes profileCredPulse {
  0%   { box-shadow: 0 0 0 0 rgba(0, 196, 114, 0); }
  18%  { box-shadow: 0 0 0 6px rgba(0, 196, 114, 0.20),
                     0 8px 22px rgba(0, 196, 114, 0.18); }
  60%  { box-shadow: 0 0 0 12px rgba(0, 196, 114, 0.06),
                     0 6px 18px rgba(0, 196, 114, 0.10); }
  100% { box-shadow: 0 0 0 0 rgba(0, 196, 114, 0); }
}

.profile-cred-pip {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translate(-50%, 6px) scale(0.8);
  font-size: 14px;
  font-weight: 800;
  color: #00c472;
  text-shadow: 0 0 10px rgba(0, 196, 114, 0.55),
               0 1px 2px rgba(0, 0, 0, 0.4);
  letter-spacing: -0.015em;
  pointer-events: none;
  opacity: 0;
  animation: profileCredPipRise 1.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  z-index: 5;
}
@keyframes profileCredPipRise {
  0%   { opacity: 0; transform: translate(-50%, 6px)  scale(0.8); }
  18%  { opacity: 1; transform: translate(-50%, -4px) scale(1.1); }
  60%  { opacity: 1; transform: translate(-50%, -22px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -38px) scale(0.95); }
}

/* ═══════════════════════════════════════════════════════════════════
   P65.125 — Smart Money verification celebration
   ─────────────────────────────────────────────────────────────────
   Full-screen overlay that fires when a wallet is confirmed Smart
   Money. Brand palette: purple (#ba83ff → #9b6bff) over green
   (#22c55e → #34d399), reflecting the SM badge's split-color
   aesthetic. Three stacked layers: backdrop blur, particle burst,
   3 concentric rings, central card with hero badge + headline +
   continue button. The hero badge is the element that flies onto
   the profile when the user dismisses (handled in JS via Web
   Animations API).
   ═══════════════════════════════════════════════════════════════════ */

.sm-celebration .vc-card.sm-card {
  background: linear-gradient(180deg,
    rgba(186, 131, 255, 0.10) 0%,
    rgba(34, 197, 94, 0.04) 100%),
    var(--bg-2);
  border: 1px solid rgba(186, 131, 255, 0.32);
  box-shadow:
    0 0 80px rgba(186, 131, 255, 0.20),
    0 0 0 1px rgba(186, 131, 255, 0.08) inset,
    0 18px 60px -10px rgba(0, 0, 0, 0.5);
  position: relative;
  overflow: hidden;
}
.sm-card-accent {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: linear-gradient(90deg,
    transparent 0%,
    #ba83ff 25%,
    #22c55e 75%,
    transparent 100%);
  background-size: 200% 100%;
  animation: smCardAccent 2.4s linear infinite;
}
@keyframes smCardAccent {
  from { background-position: 200% 0; }
  to   { background-position: 0% 0; }
}

/* Concentric purple-tinted rings — same shape as topup but in SM
   palette, faster pulse to read more "verified-energy". */
.sm-celebration .sm-ring {
  border-color: rgba(186, 131, 255, 0.55);
  box-shadow: 0 0 30px rgba(186, 131, 255, 0.25);
}

/* Hero zone: 76px badge + radial glow + orbit ring. */
.sm-hero {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 110px;
  height: 110px;
  margin: 8px auto 16px;
}
.sm-hero-glow {
  position: absolute;
  inset: -12px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(186, 131, 255, 0.40) 0%,
    rgba(34, 197, 94, 0.18) 45%,
    transparent 75%);
  animation: smHeroGlow 2.4s ease-in-out infinite;
  filter: blur(8px);
  z-index: 0;
}
@keyframes smHeroGlow {
  0%, 100% { transform: scale(1);     opacity: 0.85; }
  50%      { transform: scale(1.08);  opacity: 1; }
}
.sm-orbit-ring {
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  border: 1.5px dashed rgba(186, 131, 255, 0.42);
  animation: smOrbitRotate 9s linear infinite;
  z-index: 1;
}
@keyframes smOrbitRotate {
  from { transform: rotate(0deg);   }
  to   { transform: rotate(360deg); }
}

/* The hero SM badge — the element that flies onto the profile on
   dismiss. Slightly bigger + with a sweeping shine. */
.sm-hero-badge {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 76px;
  height: 76px;
  border-radius: 18px;
  background: linear-gradient(135deg, #ba83ff 0%, #9b6bff 50%, #22c55e 100%);
  color: #fff;
  font-family: var(--mono);
  font-weight: 900;
  font-size: 26px;
  letter-spacing: 0.04em;
  overflow: hidden;
  box-shadow:
    0 0 28px rgba(186, 131, 255, 0.55),
    0 12px 32px -6px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.4);
  animation: smHeroBadgePop 0.7s cubic-bezier(0.22, 1.4, 0.36, 1) both;
}
.sm-hero-badge-text {
  position: relative;
  z-index: 1;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.32);
}
.sm-hero-badge-shine {
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg,
    transparent 38%,
    rgba(255, 255, 255, 0.55) 50%,
    transparent 62%);
  animation: smHeroBadgeShine 1.6s linear infinite;
  pointer-events: none;
}
@keyframes smHeroBadgePop {
  0%   { transform: scale(0.4) rotate(-12deg); opacity: 0; }
  60%  { transform: scale(1.10) rotate(2deg);  opacity: 1; }
  100% { transform: scale(1)     rotate(0);    opacity: 1; }
}
@keyframes smHeroBadgeShine {
  from { transform: translateX(-110%); }
  to   { transform: translateX(110%);  }
}

/* Headline + sub copy — purple→green gradient text matches the
   badge palette so the celebration reads as a coherent system. */
.sm-eyebrow {
  color: #ba83ff;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  opacity: 0;
  animation: smCardFadeStep 0.5s ease-out 0.55s forwards;
}
.sm-title {
  background: linear-gradient(135deg, #ba83ff 0%, #ffffff 50%, #22c55e 100%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  font-weight: 900;
  font-size: 30px;
  letter-spacing: -0.02em;
  text-align: center;
  margin: 4px 0 6px;
  opacity: 0;
  animation: smCardFadeStep 0.55s ease-out 0.65s forwards;
}
.sm-sub {
  font-size: 12.5px;
  color: var(--text-2);
  text-align: center;
  line-height: 1.5;
  max-width: 340px;
  margin: 0 auto 18px;
  opacity: 0;
  animation: smCardFadeStep 0.55s ease-out 0.8s forwards;
}
.sm-sub strong { color: var(--text-0); }
@keyframes smCardFadeStep {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Continue button — purple/green gradient matching the brand. */
.sm-dismiss {
  background: linear-gradient(135deg, #ba83ff 0%, #9b6bff 50%, #22c55e 100%) !important;
  color: #fff !important;
  border: none !important;
  font-weight: 800;
  letter-spacing: -0.005em;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 auto;
  padding: 11px 22px;
  border-radius: 10px;
  cursor: pointer;
  box-shadow:
    0 6px 20px rgba(186, 131, 255, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.30);
  opacity: 0;
  animation: smCardFadeStep 0.5s ease-out 0.95s forwards;
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.sm-dismiss:hover {
  transform: translateY(-1px);
  filter: brightness(1.07);
  box-shadow:
    0 8px 24px rgba(186, 131, 255, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.40);
}
.sm-dismiss:active { transform: translateY(0) scale(0.98); }

/* Receive-pulse on the destination SM badge (the one in the profile
   header / dropdown) when the hero badge lands on it. Single 1s
   pulse with a gold-tinted halo so the user's eye is drawn to where
   the badge "stuck." */
.sm-badge-receive {
  position: relative;
  animation: smBadgeReceive 1.0s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes smBadgeReceive {
  0%   { transform: scale(0.6); filter: drop-shadow(0 0 0 rgba(186, 131, 255, 0)); }
  35%  { transform: scale(1.30); filter: drop-shadow(0 0 14px rgba(186, 131, 255, 0.85)); }
  70%  { transform: scale(0.92); filter: drop-shadow(0 0 8px rgba(186, 131, 255, 0.45)); }
  100% { transform: scale(1);    filter: drop-shadow(0 0 0 rgba(186, 131, 255, 0)); }
}

@media (prefers-reduced-motion: reduce) {
  .sm-celebration .sm-ring,
  .sm-card-accent,
  .sm-hero-glow,
  .sm-orbit-ring,
  .sm-hero-badge-shine {
    animation: none !important;
  }
  .sm-hero-badge { animation-duration: 0.18s; }
  .sm-eyebrow, .sm-title, .sm-sub, .sm-dismiss {
    animation-duration: 0.18s;
    animation-delay: 0s;
  }
}

/* ═══════════════════════════════════════════════════════════════════ */
/* P65.196 — Voice Boost (paid comment promotion)                      */
/* ─────────────────────────────────────────────────────────────────── */
/* Three layers of styling work together:                              */
/*   1. .comment-actions / .comment-action-btn — the per-comment       */
/*      Reply / Like / Voice Boost icon row                            */
/*   2. .post-comment-row.is-voice-boosted — emerald aura, shimmering  */
/*      box-shadow, gradient bg                                        */
/*   3. .voice-boost-bubbles — animated rising bubbles inside boosted  */
/*      rows (8 staggered <span>s, GPU-accelerated CSS keyframes)      */
/* ═══════════════════════════════════════════════════════════════════ */

/* The post-comment-row needs position:relative so the bubble overlay
   can be absolutely-positioned inside it.  No layout impact when no
   boost is active (the bubbles div isn't rendered). */
.post-comment-row {
  position: relative;
  transition: background 240ms ease;
}

/* Action row — Reply / Like / Voice Boost */
.comment-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 8px;
  padding-left: 2px;
}
.comment-action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  margin-left: -8px;
  background: transparent;
  border: 0;
  border-radius: 14px;
  cursor: pointer;
  color: var(--text-4);
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: 0.01em;
  transition: color 0.12s ease, background 0.12s ease, transform 0.12s ease;
}
.comment-action-btn svg {
  flex-shrink: 0;
  transition: stroke 0.12s ease, fill 0.12s ease, transform 0.18s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
.comment-action-btn:hover {
  color: var(--text-1);
  background: rgba(255, 255, 255, 0.04);
}
.comment-action-btn:active svg {
  transform: scale(0.85);
}
.comment-action-reply:hover { color: var(--accent); }

/* P65.208 — like state goes emerald to match the rest of the brand
   (was pink/red, which clashed with the green accent system the
   user uses everywhere else — voice boost, accent text, balance UI). */
.comment-action-like.is-liked         { color: var(--accent); }
.comment-action-like.is-liked svg     { fill: var(--accent); stroke: var(--accent); }
.comment-action-like:hover            { color: var(--accent); }

/* P65.214 — Voice Boost button slightly tighter than Reply/Like.
   It carries more text ("Voice Boost" = 11 chars vs "Reply" / "Like"
   = 4-5) and a gold tint, both of which made it visually dominate
   the action row.  Shrinking the font / icon / padding a notch
   brings it back into balance without losing legibility. */
.comment-action-vboost {
  color: #f7d979;
  font-size: 11px;
  padding: 3px 7px;
  gap: 5px;
}
.comment-action-vboost svg { width: 12px; height: 12px; }
.comment-action-vboost:hover {
  color: #fff7d0;
  background: rgba(247, 217, 121, 0.10);
}
/* When the comment IS already boosted, the button shifts to emerald —
   subtle "extend" hint instead of "activate" */
.comment-action-vboost.is-active {
  color: var(--accent);
  background: rgba(0, 196, 114, 0.10);
}

/* ── Boosted comment row ────────────────────────────────────────── */
.post-comment-row.is-voice-boosted {
  background: linear-gradient(
    180deg,
    rgba(0, 196, 114, 0.085) 0%,
    rgba(0, 196, 114, 0.025) 80%,
    rgba(0, 196, 114, 0.0) 100%
  );
  border-left: 2px solid rgba(0, 196, 114, 0.55);
  box-shadow: inset 0 0 0 1px rgba(0, 196, 114, 0.18);
  /* P65.225 — overflow:hidden removed so the kebab dropdown isn't
     clipped to the row.  The bubbles container has its OWN
     overflow:hidden (.voice-boost-bubbles) so they still stay inside
     their layer; only the dropdown (positioned outside the bubble
     layer) escapes correctly now. */
  isolation: isolate;
  animation: voiceBoostShimmer 4.5s ease-in-out infinite;
}
@keyframes voiceBoostShimmer {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(0, 196, 114, 0.18); }
  50%      { box-shadow: inset 0 0 0 1px rgba(0, 196, 114, 0.42),
                          inset 0 0 22px -8px rgba(0, 196, 114, 0.40); }
}
/* Foreground content sits above the bubble layer.  P65.217 — exclude
   .voice-boost-bubbles from the `position: relative` rule above.
   The bubbles container needs to stay `position: absolute` (set in
   its own base rule) so `inset: 0` fills the row.  Without this
   exclusion, the rule `.is-voice-boosted > *` was overwriting the
   container's position to `relative`, collapsing it to zero size
   and hiding all 17 bubbles. */
.post-comment-row.is-voice-boosted > *:not(.voice-boost-bubbles) {
  position: relative;
  z-index: 1;
}
/* Re-assert absolute positioning + sit BEHIND the foreground content. */
.post-comment-row.is-voice-boosted .voice-boost-bubbles {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* "BOOSTED · 53m" pill on a boosted comment header */
.voice-boost-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px 2px 6px;
  background: linear-gradient(135deg, rgba(0, 196, 114, 0.22), rgba(0, 196, 114, 0.10));
  border: 1px solid rgba(0, 196, 114, 0.50);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #b6f3d3;
  text-shadow: 0 0 8px rgba(0, 196, 114, 0.45);
  animation: voiceBoostBadgePulse 2.6s ease-in-out infinite;
}
.voice-boost-badge svg { stroke: #c4f5d8; }
.voice-boost-badge-time {
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: 0.04em;
}
@keyframes voiceBoostBadgePulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(0, 196, 114, 0.0); }
  50%      { box-shadow: 0 0 14px 1px rgba(0, 196, 114, 0.35); }
}

/* ── P65.216 — "Boiling" bubbles inside a boosted comment ────────
   Each bubble is a 3-stop emerald gradient orb with a glossy white
   highlight at the top-left (::before pseudo) for a real "wet"
   3D look.  Animation rises with a sinusoidal wobble (alternating
   ±2-3px translateX on the keyframe stops) and ends with a scale-
   up "pop" + fade at the top of the row, simulating a bubble
   surfacing and bursting.
   Sizes are driven by --vb-size so one keyframe works for all
   bubbles — set per-bubble inline by _voiceBoostBubblesHtml. */
.voice-boost-bubbles {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}
/* P65.218 — bubbles toned WAY down so they read as an ambient
   background texture, not a foreground accent.  Three changes:
     1. Less green: the radial gradient drops from saturated emerald
        to a soft, desaturated sage-grey that barely tints the row.
     2. Lower opacity ceiling: keyframes peak at 0.32 (was 0.88) so
        bubbles never compete with the comment text.
     3. Soft blur + no drop-shadow: removes the bright glow halo
        that made bubbles feel like glowing orbs in front. */
.voice-boost-bubble {
  position: absolute;
  bottom: -16px;
  width: var(--vb-size, 8px);
  height: var(--vb-size, 8px);
  border-radius: 50%;
  background: radial-gradient(
    circle at 32% 28%,
    rgba(220, 235, 225, 0.55) 0%,
    rgba(160, 200, 180, 0.30) 30%,
    rgba(110, 170, 145, 0.15) 65%,
    rgba(110, 170, 145, 0.0)  92%
  );
  filter: blur(0.6px);
  opacity: 0;
  animation: voiceBoostBoil var(--vb-dur, 5s) ease-in-out infinite;
  animation-delay: var(--vb-delay, 0s);
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}
/* Soft inner highlight — kept subtle (was 0.92 white, now 0.30) so
   bubbles read as washed-out background spheres, not glossy beads. */
.voice-boost-bubble::before {
  content: '';
  position: absolute;
  top: 18%;
  left: 22%;
  width: 28%;
  height: 24%;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    rgba(255, 255, 255, 0.30) 0%,
    rgba(255, 255, 255, 0.0)  72%
  );
  pointer-events: none;
}
@keyframes voiceBoostBoil {
  0%   { transform: translate(0,    0)     scale(0.4);  opacity: 0;    }
  6%   { transform: translate(1px,  -8px)  scale(0.7);  opacity: 0.10; }
  16%  { transform: translate(-3px, -28px) scale(0.95); opacity: 0.22; }
  30%  { transform: translate(2px,  -52px) scale(1.0);  opacity: 0.30; }
  45%  { transform: translate(-2px, -80px) scale(1.0);  opacity: 0.32; }
  60%  { transform: translate(2px, -110px) scale(1.0);  opacity: 0.26; }
  75%  { transform: translate(-2px,-140px) scale(1.05); opacity: 0.16; }
  88%  { transform: translate(1px, -170px) scale(1.18); opacity: 0.06; }
  100% { transform: translate(0,   -200px) scale(1.30); opacity: 0;    }
}

/* One-shot "powering up" burst when the boost is freshly activated */
@keyframes voiceBoostBurst {
  0%   { transform: scale(0.92); filter: brightness(1);    }
  35%  { transform: scale(1.04); filter: brightness(1.45); }
  70%  { transform: scale(1.00); filter: brightness(1.15); }
  100% { transform: scale(1.00); filter: brightness(1.00); }
}
.post-comment-row.is-vb-just-activated {
  animation: voiceBoostBurst 900ms cubic-bezier(0.18, 0.89, 0.32, 1.28) both;
}

/* ── Voice Boost confirm modal styling ──────────────────────────── */
.vboost-modal-icon {
  width: 64px;
  height: 64px;
  border-radius: 18px;
  background: radial-gradient(
    circle at 30% 30%,
    rgba(0, 196, 114, 0.30) 0%,
    rgba(0, 196, 114, 0.10) 50%,
    transparent 100%
  );
  border: 1px solid rgba(0, 196, 114, 0.40);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 14px;
  box-shadow: 0 0 32px -8px rgba(0, 196, 114, 0.55);
  animation: voiceBoostBadgePulse 2.6s ease-in-out infinite;
}
.vboost-cost-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 14px 0 16px;
}
.vboost-cost-cell {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}
.vboost-cost-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  color: var(--text-4);
  text-transform: uppercase;
  margin-bottom: 4px;
}
.vboost-cost-value {
  font-size: 16px;
  font-weight: 800;
  color: var(--text-0);
  font-family: var(--mono);
  letter-spacing: -0.01em;
}
.vboost-cost-value-accent { color: var(--accent); text-shadow: 0 0 12px rgba(0, 196, 114, 0.35); }
.vboost-cost-sub {
  font-size: 10.5px;
  color: var(--text-4);
  margin-top: 2px;
  font-weight: 500;
}
.vboost-no-stock {
  margin: 12px 0 16px;
  padding: 12px 14px;
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.30);
  border-radius: 10px;
  font-size: 12.5px;
  color: #ffb4b4;
  text-align: center;
  line-height: 1.6;
}
.vboost-no-stock-cta {
  display: inline-block;
  margin-top: 6px;
  color: #f7d979;
  font-weight: 700;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.btn-vboost {
  background: linear-gradient(135deg, var(--accent), #00d484);
  color: #0a1d12;
  font-weight: 800;
  letter-spacing: 0.01em;
  border: 1px solid rgba(0, 196, 114, 0.65);
  border-radius: 10px;
  padding: 10px 16px;
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  box-shadow: 0 0 18px -4px rgba(0, 196, 114, 0.55);
  transition: background 0.18s ease, box-shadow 0.18s ease, transform 0.12s ease;
}
.btn-vboost:hover {
  background: linear-gradient(135deg, #00d484, var(--accent));
  box-shadow: 0 0 28px -2px rgba(0, 196, 114, 0.75);
}
.btn-vboost:active { transform: translateY(1px); }
.btn-vboost:disabled {
  background: var(--bg-3);
  color: var(--text-4);
  border-color: var(--border);
  box-shadow: none;
  cursor: not-allowed;
}

/* ── P65.200 — Voice Boost qty stepper inside the modal ─────────── */
/* Same surgical-update pattern as the marketplace confirm-buy stepper:
   the +/- buttons mutate just the affected DOM nodes, no re-render,
   no animation flash. */
.vboost-qty {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin: 0 auto 14px;
  padding: 8px 14px;
  background: var(--bg-1);
  border: 1px solid rgba(0, 196, 114, 0.22);
  border-radius: 14px;
  width: fit-content;
  min-width: 200px;
  box-shadow: 0 0 18px -10px rgba(0, 196, 114, 0.45);
}
.vboost-qty-btn {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: rgba(0, 196, 114, 0.10);
  border: 1px solid rgba(0, 196, 114, 0.30);
  color: var(--accent);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.12s ease, color 0.12s ease;
  font-family: inherit;
  flex-shrink: 0;
}
.vboost-qty-btn:hover:not(:disabled) {
  background: rgba(0, 196, 114, 0.20);
  border-color: rgba(0, 196, 114, 0.55);
  transform: scale(1.05);
}
.vboost-qty-btn:active:not(:disabled) { transform: scale(0.92); }
.vboost-qty-btn:disabled {
  opacity: 0.30;
  cursor: not-allowed;
  background: rgba(255, 255, 255, 0.03);
  border-color: var(--border);
  color: var(--text-4);
}
.vboost-qty-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 100px;
  user-select: none;
}
.vboost-qty-num {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 22px;
  font-weight: 800;
  color: var(--text-0);
  line-height: 1.0;
  letter-spacing: -0.02em;
}
.vboost-qty-unit {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-3);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 4px;
}

/* Reduced-motion: disable bubble float, shimmer, and pulse */
@media (prefers-reduced-motion: reduce) {
  .post-comment-row.is-voice-boosted,
  .voice-boost-badge,
  .vboost-modal-icon {
    animation: none !important;
  }
  .voice-boost-bubble {
    animation-duration: 12s;
    opacity: 0.25;
  }
}

/* ═══════════════════════════════════════════════════════════════════ */
/* P65.197 — Post-detail visual refresh                                */
/* ─────────────────────────────────────────────────────────────────── */
/* The post-detail page used to be a stack of edge-to-edge bands       */
/* separated by 1px borders — no clear "this is one post" framing,     */
/* no card boundaries, the composer felt unstyled, the sentiment bar   */
/* dominated when only one side had voted.  This block reframes:       */
/*   • .post-detail-card        — single rounded surface for the post  */
/*   • .post-detail-composer-card — composer as its own focusable card */
/*   • .post-detail-replies-header — "Replies (N)" section label       */
/*   • softer sentiment bar  + better section dividers inside the card */
/* ═══════════════════════════════════════════════════════════════════ */

/* Outer card — hosts the entire post chunk (author / body / media /
   meta / sentiment / actions).  Sits 14px below the sticky header
   with horizontal margin so it doesn't run flush to the edges. */
.post-detail-card {
  margin: 14px 14px 18px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 18px;
  /* No overflow:hidden — the post header dropdown menu is absolutely
     positioned BELOW the kebab button and would get clipped here.
     Media inside the card has its own rounded-with-overflow-hidden
     wrapper so it stays inside the visual bounds. */
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.02) inset,
    0 6px 24px -16px rgba(0, 0, 0, 0.55);
  transition: border-color 0.2s ease;
}
.post-detail-card:hover {
  border-color: rgba(255, 255, 255, 0.10);
}

/* ═══════════════════════════════════════════════════════════════
   POST-DETAIL TEXT BLOCKS (P65.385)
   Title and body inside the post-detail card. Previously rendered
   with inline styles that lacked any wrapping rule — a wall of
   unbroken characters ("fffffffff…") overflowed the card horizon-
   tally and made the page scroll sideways. These classes preserve
   the original typography but add:
     • white-space: pre-wrap      — keep user-entered newlines
     • overflow-wrap: anywhere    — break inside long unbroken
                                    tokens before they blow out
                                    the card width
     • word-wrap: break-word      — legacy alias for older engines
   ═══════════════════════════════════════════════════════════════ */
.post-detail-title-text {
  font-size: 17px;
  color: var(--text-0);
  line-height: 1.45;
  margin-bottom: 6px;
  font-weight: 500;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-wrap: break-word;
}
.post-detail-body-text {
  font-size: 15px;
  color: var(--text-2);
  line-height: 1.55;
  margin-bottom: 10px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-wrap: break-word;
}
.post-detail-body-text a { color: var(--accent); }

/* Inside the card we want softer dividers — the existing inline
   `border-bottom: 1px solid var(--border)` is too heavy against the
   brighter card surface.  Scope under .post-detail-card so feed
   cards (which still want the punchier border) aren't affected. */
.post-detail-card div[style*="border-bottom"] {
  border-bottom-color: rgba(255, 255, 255, 0.06) !important;
}

/* Drop the action-row divider entirely — it sits flush with the
   card's bottom edge, so the card's own border IS the divider. */
.post-detail-card > div:last-child > div[style*="border-bottom"] {
  border-bottom: 0 !important;
}

/* Composer card */
.post-detail-composer-card {
  margin: 0 14px 14px;
  padding: 14px 16px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 16px;
  display: flex;
  gap: 12px;
  align-items: flex-start;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.post-detail-composer-card:focus-within {
  border-color: rgba(0, 196, 114, 0.35);
  box-shadow: 0 0 0 3px rgba(0, 196, 114, 0.08);
}
.post-detail-composer-input {
  width: 100%;
  background: transparent;
  border: 0;
  outline: 0;
  resize: none;
  font-family: inherit;
  font-size: 15px;
  line-height: 1.45;
  color: var(--text-0);
  padding: 6px 0;
  min-height: 40px;
}
.post-detail-composer-input::placeholder {
  /* P65.275 — bumped to text-3 for the same readability reason as
     the global .input::placeholder rule. */
  color: var(--text-3);
  opacity: .85;
}

/* The lock-banner state replaces the composer when anti-spam locks
   the user — give it the same card frame for consistency. */
.comment-lock-banner {
  margin: 0 14px 14px;
  border-radius: 16px;
}

/* P65.198 — Replies card.  Same frame language as .post-detail-card
   above the composer: bg-1 surface, soft border, 18px radius, inset
   highlight + outer drop-shadow.  Holds the replies header + every
   comment row OR the empty state.  No padding of its own — children
   (.post-detail-replies-header, .post-comment-row,
   .post-detail-replies-empty) provide their own. */
.post-detail-replies-card {
  margin: 0 14px 18px;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.02) inset,
    0 6px 24px -16px rgba(0, 0, 0, 0.55);
  transition: border-color 0.2s ease;
  /* P65.225 — overflow:hidden removed so the kebab dropdown on the
     LAST comment isn't clipped to the card's bottom edge.  The
     small visual cost is that the boosted-comment gradient on the
     first/last row may poke past the card's rounded corner by a
     few pixels; that's a worthwhile trade for functional menus. */
}
.post-detail-replies-card:hover {
  border-color: rgba(255, 255, 255, 0.10);
}

/* "Replies" section header — small label + monospace count chip.
   Now sits at the top of the replies card with a divider below it
   that matches the soft dividers inside the post-detail-card. */
.post-detail-replies-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 22px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.post-detail-replies-label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-3);
}
.post-detail-replies-count {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 11px;
  font-weight: 700;
  color: var(--text-4);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 999px;
  padding: 1px 8px;
  letter-spacing: 0.04em;
}

/* "No replies yet" empty state — styled to match the card surface
   instead of floating without any context. */
.post-detail-replies-empty {
  text-align: center;
  padding: 56px 24px;
  color: var(--text-4);
  font-size: 13px;
  letter-spacing: 0.01em;
}

/* Comment rows: soften the heavy edge-to-edge `border-bottom` and
   give a subtle hover state so individual comments feel like
   first-class objects.  The hover bg is scoped with :not so it
   doesn't clobber the .is-voice-boosted gradient. */
.post-comment-row {
  border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
  padding: 14px 22px !important;
  transition: background 180ms ease, border-color 180ms ease;
}
.post-comment-row:not(.is-voice-boosted):hover {
  background: rgba(255, 255, 255, 0.02);
}
.post-comment-row:last-child {
  border-bottom: 0 !important;
}

/* Sentiment bar inside the post-detail-card: slimmer + de-emphasize
   the all-one-side case (when 100% downvotes the bar was screaming
   red across the whole row).  We keep the colors but lower the
   contrast to "subdued" so it informs without dominating. */
.post-detail-card .sentiment-bar {
  background: rgba(255, 255, 255, 0.025);
  border-color: rgba(255, 255, 255, 0.05);
}
.post-detail-card .sentiment-bar-track {
  height: 5px;
  background: rgba(255, 255, 255, 0.04);
}
.post-detail-card .sentiment-bar-fill-up {
  background: linear-gradient(90deg, rgba(0, 196, 114, 0.55), rgba(0, 196, 114, 0.85));
}
.post-detail-card .sentiment-bar-fill-down {
  background: linear-gradient(90deg, rgba(212, 68, 68, 0.85), rgba(212, 68, 68, 0.55));
}

/* On narrow screens, drop the side margins so the card still has
   breathing room without orphaning the content */
@media (max-width: 540px) {
  .post-detail-card,
  .post-detail-composer-card,
  .post-detail-replies-card,
  .comment-lock-banner {
    margin-left: 8px;
    margin-right: 8px;
    border-radius: 14px;
  }
  .post-detail-replies-header {
    padding: 12px 16px 10px;
  }
  .post-comment-row {
    padding: 14px 16px !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════ */
/* P65.207 — Threaded replies (X-style) + inline reply composer        */
/* ─────────────────────────────────────────────────────────────────── */
/* Replies render indented under their parent with a left rail so the */
/* thread structure is visually obvious.  Reply count on the Reply     */
/* button shows engagement at a glance.  Inline composer drops in     */
/* below the parent when the user clicks Reply.                        */
/* ═══════════════════════════════════════════════════════════════════ */

/* Threaded reply row — sits indented under its parent with a left
   accent rail.  Smaller avatar, tighter padding, slightly muted bg
   so the parent stays the visual anchor of the thread. */
.post-comment-row.post-comment-reply {
  padding: 10px 22px 10px 60px !important;
  background: rgba(255, 255, 255, 0.015);
  border-bottom: 1px solid rgba(255, 255, 255, 0.04) !important;
  position: relative;
}
.post-comment-row.post-comment-reply::before {
  content: '';
  position: absolute;
  left: 38px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: linear-gradient(180deg,
    rgba(0, 196, 114, 0.18) 0%,
    rgba(0, 196, 114, 0.06) 100%);
  border-radius: 2px;
}
.post-comment-row.post-comment-reply:hover {
  background: rgba(255, 255, 255, 0.03);
}
/* Voice-boosted threaded reply: keep the emerald gradient but
   preserve the indent + left rail. */
.post-comment-row.post-comment-reply.is-voice-boosted {
  background: linear-gradient(
    180deg,
    rgba(0, 196, 114, 0.10) 0%,
    rgba(0, 196, 114, 0.03) 80%,
    rgba(0, 196, 114, 0.0) 100%
  );
}

/* Inline reply composer — sits below the parent comment, indented
   into the thread so it feels like a child surface. */
.comment-reply-composer {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 22px 12px 60px;
  background: rgba(0, 196, 114, 0.025);
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
  border-left: 2px solid rgba(0, 196, 114, 0.30);
  position: relative;
  animation: replyComposerIn 220ms cubic-bezier(0.16, 1, 0.30, 1) both;
}
@keyframes replyComposerIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.comment-reply-input {
  width: 100%;
  background: transparent;
  border: 0;
  outline: 0;
  resize: none;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.45;
  color: var(--text-0);
  padding: 4px 0;
  min-height: 28px;
}
.comment-reply-input::placeholder {
  color: var(--text-4);
}
.comment-reply-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.comment-reply-actions .btn-sm {
  padding: 5px 14px;
  border-radius: 16px;
  font-size: 12px;
}

/* Reply count badge on the Reply action button — shown when
   replyCount > 0 instead of the plain "Reply" label.  We use the
   existing comment-action-btn typography but style the count digit
   in mono for tabular alignment. */
.comment-action-reply span {
  font-family: var(--mono, ui-monospace, monospace);
  letter-spacing: 0.02em;
}

/* P65.209 — collapsible replies container.  Modern grid-template-rows
   trick: 0fr → 1fr animates the auto-height smoothly.  In both
   directions.  Inner element needs overflow:hidden + min-height:0
   so the grid item can collapse to zero.
   Browsers without grid-row support (older Safari) snap-toggle —
   that's an acceptable fallback. */
.comment-replies-container {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 360ms cubic-bezier(0.16, 1, 0.30, 1),
              opacity 220ms ease 0ms;
  pointer-events: none;
}
.comment-replies-container.is-expanded {
  grid-template-rows: 1fr;
  opacity: 1;
  /* Slight delay on the opacity fade-in so the height starts
     opening BEFORE the rows light up — feels more deliberate. */
  transition: grid-template-rows 360ms cubic-bezier(0.16, 1, 0.30, 1),
              opacity 240ms ease 90ms;
  pointer-events: auto;
}
.comment-replies-inner {
  overflow: hidden;
  min-height: 0;
}

/* Chevron rotates 180° when the toggle is in expanded state.  Single
   SVG, smooth turn — no DOM swap. */
.comment-replies-chev {
  transition: transform 280ms cubic-bezier(0.16, 1, 0.30, 1);
  transform: rotate(0deg);
  flex-shrink: 0;
}
.comment-replies-toggle.is-expanded .comment-replies-chev {
  transform: rotate(-180deg);
}

/* P65.208 — "View N replies" / "Hide replies" toggle.  Sits below
   the parent comment with the same indent as the reply rows so the
   thread reads as one column.  Emerald accent text + chevron makes
   it clearly clickable without dominating the comment list. */
.comment-replies-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 22px 8px 60px;
  background: transparent;
  border: 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
  cursor: pointer;
  color: var(--accent);
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  text-align: left;
  width: 100%;
  position: relative;
  transition: background 0.18s ease, color 0.18s ease;
}
.comment-replies-toggle::before {
  content: '';
  position: absolute;
  left: 38px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: linear-gradient(180deg,
    rgba(0, 196, 114, 0.18) 0%,
    rgba(0, 196, 114, 0.04) 100%);
  border-radius: 2px;
}
.comment-replies-toggle:hover {
  background: rgba(0, 196, 114, 0.05);
  color: #b6f3d3;
}
.comment-replies-toggle:hover::before {
  background: linear-gradient(180deg,
    rgba(0, 196, 114, 0.45) 0%,
    rgba(0, 196, 114, 0.20) 100%);
}
.comment-replies-toggle.is-expanded {
  color: var(--text-3);
  font-weight: 600;
}
.comment-replies-toggle.is-expanded:hover {
  color: var(--accent);
}
.comment-replies-toggle svg {
  opacity: 0.85;
}

/* Mobile alignment matches the indent shrink for replies. */
@media (max-width: 540px) {
  .comment-replies-toggle {
    padding-left: 44px;
  }
  .comment-replies-toggle::before {
    left: 26px;
  }
}

/* P65.209 — reduced-motion users get an instant snap (no height
   animation, no chevron rotation tween).  Keeping the same DOM —
   only the timing functions go to 0. */
@media (prefers-reduced-motion: reduce) {
  .comment-replies-container,
  .comment-replies-container.is-expanded {
    transition: none;
  }
  .comment-replies-chev {
    transition: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════ */
/* P65.210 — Comment kebab menu + inline edit controls                 */
/* ─────────────────────────────────────────────────────────────────── */
/* Kebab sits in the top-right corner of every comment row.  Items     */
/* differ by ownership (owner: edit/copy/delete; viewer: copy/report). */
/* Edit mode swaps the comment body for an inline textarea.            */
/* ═══════════════════════════════════════════════════════════════════ */

.comment-menu {
  flex-shrink: 0;
  align-self: flex-start;
  margin-top: -2px;
}
.comment-menu-btn {
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: transparent;
  border: 0;
  color: var(--text-4);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  transition: background 0.12s ease, color 0.12s ease;
  padding: 0;
}
.comment-menu-btn:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-1);
}
.comment-menu-btn:active {
  background: rgba(255, 255, 255, 0.10);
}
.comment-menu-btn svg {
  flex-shrink: 0;
}

/* Comment-dropdown menu — anchors right of the kebab button.  Re-uses
   the .dropdown-menu base styling (defined in components.css). */
.comment-dropdown {
  /* All sizing/border styles inherited from .dropdown-menu */
}

/* Inline edit-mode controls.  Swaps in for the comment text body. */
.comment-edit-wrap {
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: rgba(0, 196, 114, 0.04);
  border: 1px solid rgba(0, 196, 114, 0.30);
  border-radius: 10px;
  padding: 8px 10px;
  animation: replyComposerIn 220ms cubic-bezier(0.16, 1, 0.30, 1) both;
}
.comment-edit-input {
  width: 100%;
  background: transparent;
  border: 0;
  outline: 0;
  resize: none;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.45;
  color: var(--text-0);
  padding: 0;
  min-height: 36px;
}
.comment-edit-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}
.comment-edit-actions .btn-sm {
  padding: 5px 12px;
  border-radius: 14px;
  font-size: 12px;
}

/* Reduced-motion: drop the edit-wrap slide-in. */
@media (prefers-reduced-motion: reduce) {
  .comment-edit-wrap { animation: none; }
}

/* Note: .avatar-xs is already defined in components.css (24×24).
   Threaded replies use it so child rows feel slightly secondary
   to their parent without sacrificing avatar legibility. */

/* Mobile: shrink the indent so threads still fit. */
@media (max-width: 540px) {
  .post-comment-row.post-comment-reply {
    padding-left: 44px !important;
  }
  .post-comment-row.post-comment-reply::before {
    left: 26px;
  }
  .comment-reply-composer {
    padding-left: 44px;
  }
}

/* Reduced-motion: drop the composer slide-in */
@media (prefers-reduced-motion: reduce) {
  .comment-reply-composer { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════ */
/* P65.226 — Stamina system on posts                                   */
/* ─────────────────────────────────────────────────────────────────── */
/* Three layers:                                                       */
/*   1. .post-action-stamina — the lightning button non-owners click   */
/*   2. .post-stamina-sparks — floating ⚡ animation when post is alive*/
/*   3. .stamina-* classes for the confirm modal                       */
/* ═══════════════════════════════════════════════════════════════════ */

/* P65.227 — Stamina palette: ELECTRIC ENERGY GREEN.  Reads as a
   supercharged version of the brand emerald — bright neon mint
   highlights, glowing core, electric edges.  Six anchor colors:
       --se-mint   : #b6ffe5  (palest highlight, like a bright reflection)
       --se-neon   : #34ffaf  (vibrant electric body)
       --se-core   : #00d484  (core green — slightly brighter than --accent)
       --se-deep   : #009d62  (anchor depth)
       --se-glow   : 52,255,175  (RGB triplet for translucent glows)
       --se-soft   : 0,212,132  (RGB triplet for softer glows)
   These are scoped via :root so all stamina-related elements inherit
   one consistent palette.  Pure CSS — JS is unchanged. */
:root {
  --se-mint: #b6ffe5;
  --se-neon: #34ffaf;
  --se-core: #00d484;
  --se-deep: #009d62;
}

/* The lightning button.  Sits where the owner's promote button would
   be (margin-left:auto pushes it to the right of the action row).
   Tinted electric-green when this user has applied stamina, brighter
   neon-pulsing when the post is in its alive window. */
.post-action-stamina {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 10px;
  background: transparent;
  border: 0;
  border-radius: 14px;
  cursor: pointer;
  color: var(--text-3);
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  transition: color 0.12s ease, background 0.12s ease, transform 0.12s ease;
}
.post-action-stamina svg {
  flex-shrink: 0;
  transition: stroke 0.12s ease, fill 0.12s ease, transform 0.18s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
.post-action-stamina:hover {
  color: var(--se-neon);
  background: rgba(52, 255, 175, 0.10);
}
.post-action-stamina:active svg { transform: scale(0.85); }
.post-action-stamina.has-count {
  color: var(--se-neon);
}
.post-action-stamina.is-alive {
  color: var(--se-mint);
  background: rgba(52, 255, 175, 0.14);
  box-shadow: 0 0 0 1px rgba(52, 255, 175, 0.45);
  animation: postStaminaActionPulse 2.4s ease-in-out infinite;
}
.post-action-stamina.is-alive svg {
  filter: drop-shadow(0 0 6px rgba(52, 255, 175, 0.70));
}
@keyframes postStaminaActionPulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(52, 255, 175, 0.40), 0 0 0 0 rgba(52, 255, 175, 0); }
  50%      { box-shadow: 0 0 0 1px rgba(52, 255, 175, 0.65), 0 0 14px 2px rgba(52, 255, 175, 0.40); }
}
.post-action-stamina .post-action-count {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 11px;
  letter-spacing: 0.02em;
}

/* Post-card alive state — neon green left rail + soft electric frame
   so the card reads as "live energy" at a glance in the feed. */
.post.post-is-stamina-alive {
  position: relative;
  border-color: rgba(52, 255, 175, 0.40) !important;
  box-shadow:
    inset 3px 0 0 rgba(52, 255, 175, 0.65),
    0 0 28px -10px rgba(52, 255, 175, 0.40);
}

/* One-shot pulse fired on the freshly-fueled card (added by JS in
   spendStaminaNow's optimistic path, removed after 900ms). */
.post.is-stamina-just-applied {
  animation: postStaminaApplyBurst 900ms cubic-bezier(0.18, 0.89, 0.32, 1.28) both;
}
@keyframes postStaminaApplyBurst {
  0%   { transform: scale(1.00); filter: brightness(1)    saturate(1);   }
  35%  { transform: scale(1.01); filter: brightness(1.30) saturate(1.20); }
  70%  { transform: scale(1.00); filter: brightness(1.10) saturate(1.08); }
  100% { transform: scale(1.00); filter: brightness(1)    saturate(1);   }
}

/* P65.229 — Spark animation rebuilt as "electric crackle/flash"
   instead of the streaming-rise pattern.  Eight SVG bolts (NOT the
   ⚡ emoji which intrinsically renders yellow) scattered at fixed
   positions across the card.  Each one independently flashes bright,
   wobbles + brightens its glow for ~200ms, then dims and fades
   in-place — reads as electrical surges, not flowing particles.
   Pure SVG so currentColor honors --se-neon (true electric green). */
.post-stamina-sparks {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: inherit;
  z-index: 0;
}
.post-stamina-spark {
  position: absolute;
  /* `top` + `left` set inline per-spark, so each sits at its fixed
     scattered position.  Animation handles the flash/fade. */
  display: block;
  color: var(--se-neon, #34ffaf);
  opacity: 0;
  filter:
    drop-shadow(0 0 3px rgba(52, 255, 175, 0.55))
    drop-shadow(0 0 8px rgba(0, 212, 132, 0.30));
  animation: postStaminaCrackle var(--ss-dur, 4s) ease-out infinite;
  animation-delay: var(--ss-delay, 0s);
  transform-origin: 50% 50%;
  will-change: transform, opacity, filter;
}
@keyframes postStaminaCrackle {
  /* Charge-up: dim + small + slight rotation */
  0%   { transform: scale(0.40) rotate(-18deg); opacity: 0;    filter: drop-shadow(0 0 0 transparent); }
  /* Spark catches */
  9%   { transform: scale(0.85) rotate(-6deg);  opacity: 0.45; }
  /* Peak flash — bright neon, brief glow halo bloom */
  16%  { transform: scale(1.20) rotate(2deg);   opacity: 0.95;
         filter:
           drop-shadow(0 0 6px rgba(182, 255, 229, 0.95))
           drop-shadow(0 0 14px rgba(52, 255, 175, 0.85))
           drop-shadow(0 0 22px rgba(0, 212, 132, 0.45)); }
  /* Settle — wobble down to a quieter glow */
  26%  { transform: scale(0.95) rotate(8deg);   opacity: 0.55;
         filter:
           drop-shadow(0 0 4px rgba(52, 255, 175, 0.65))
           drop-shadow(0 0 10px rgba(0, 212, 132, 0.30)); }
  40%  { transform: scale(1.05) rotate(-4deg);  opacity: 0.32; }
  60%  { transform: scale(1.00) rotate(3deg);   opacity: 0.18; }
  85%  { transform: scale(0.95) rotate(-2deg);  opacity: 0.06; }
  100% { transform: scale(0.85) rotate(0deg);   opacity: 0;    filter: drop-shadow(0 0 0 transparent); }
}
/* Foreground content sits above the sparks layer */
.post.post-is-stamina-alive > .post-inner { position: relative; z-index: 1; }

/* ── Stamina confirm modal ─────────────────────────────────────── */
.stamina-modal-icon {
  width: 64px;
  height: 64px;
  border-radius: 18px;
  /* Layered radial: bright neon-mint core fading through electric
     green to transparent — reads as "energy contained in a panel". */
  background:
    radial-gradient(circle at 30% 30%,
      rgba(182, 255, 229, 0.35) 0%,
      rgba(52, 255, 175, 0.18)  35%,
      rgba(0, 212, 132, 0.08)   65%,
      transparent               100%);
  border: 1px solid rgba(52, 255, 175, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 14px;
  box-shadow:
    0 0 32px -6px rgba(52, 255, 175, 0.55),
    inset 0 0 22px -8px rgba(0, 212, 132, 0.25);
  animation: postStaminaActionPulse 2.6s ease-in-out infinite;
}
.stamina-cost-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 14px 0 16px;
}
/* P65.228 — single-column variant when only the milestone cell is
   shown (no Author Earns cell anymore). */
.stamina-cost-grid-single {
  grid-template-columns: 1fr;
}

/* P65.228 — Stamina qty stepper.  Same surgical-update pattern as the
   Voice Boost stepper (setVoiceBoostQty); pure CSS for layout, JS
   mutates only digits / states without re-rendering the modal. */
.stamina-qty {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin: 0 auto 14px;
  padding: 8px 14px;
  background: var(--bg-1);
  border: 1px solid rgba(52, 255, 175, 0.30);
  border-radius: 14px;
  width: fit-content;
  min-width: 220px;
  box-shadow: 0 0 18px -10px rgba(52, 255, 175, 0.55);
}
.stamina-qty-btn {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: rgba(52, 255, 175, 0.10);
  border: 1px solid rgba(52, 255, 175, 0.40);
  color: var(--se-neon, #34ffaf);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.12s ease, color 0.12s ease;
  font-family: inherit;
  flex-shrink: 0;
}
.stamina-qty-btn:hover:not(:disabled) {
  background: rgba(52, 255, 175, 0.22);
  border-color: rgba(52, 255, 175, 0.65);
  color: var(--se-mint, #b6ffe5);
  transform: scale(1.06);
}
.stamina-qty-btn:active:not(:disabled) { transform: scale(0.92); }
.stamina-qty-btn:disabled {
  opacity: 0.30;
  cursor: not-allowed;
  background: rgba(255, 255, 255, 0.03);
  border-color: var(--border);
  color: var(--text-4);
}
.stamina-qty-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 110px;
  user-select: none;
}
.stamina-qty-num {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 22px;
  font-weight: 800;
  color: var(--se-mint, #b6ffe5);
  line-height: 1.0;
  letter-spacing: -0.02em;
  text-shadow: 0 0 12px rgba(52, 255, 175, 0.45);
}
.stamina-qty-unit {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-3);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 4px;
  font-family: var(--mono, ui-monospace, monospace);
}

/* "You only have N. Buy more or pick fewer." inline hint — shows when
   user steps qty above their inventory count. */
.stamina-no-stock {
  margin: 0 0 14px;
  padding: 10px 12px;
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.30);
  border-radius: 10px;
  font-size: 12px;
  color: #ffb4b4;
  text-align: center;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.stamina-cost-cell {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}
.stamina-cost-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  color: var(--text-4);
  text-transform: uppercase;
  margin-bottom: 4px;
}
.stamina-cost-value {
  font-size: 16px;
  font-weight: 800;
  color: var(--text-0);
  font-family: var(--mono, ui-monospace, monospace);
  letter-spacing: -0.01em;
}
.stamina-cost-value-accent {
  color: var(--se-neon);
  text-shadow: 0 0 14px rgba(52, 255, 175, 0.45);
}
.stamina-cost-sub {
  font-size: 10.5px;
  color: var(--text-4);
  margin-top: 2px;
  font-weight: 500;
}

/* The Spend button — the modal's energy peak.  Bright neon-mint to
   electric-green gradient with a deep emerald edge, dark cocoa text
   for high contrast against the bright surface, plus a glowing halo
   that pulses subtly in the alive state. */
.btn-stamina {
  background: linear-gradient(135deg,
    var(--se-mint) 0%,
    var(--se-neon) 50%,
    var(--se-core) 100%);
  color: #062018;
  font-weight: 800;
  letter-spacing: 0.01em;
  border: 1px solid rgba(52, 255, 175, 0.75);
  border-radius: 10px;
  padding: 10px 16px;
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  box-shadow:
    0 0 22px -4px rgba(52, 255, 175, 0.65),
    inset 0 1px 0 rgba(255, 255, 255, 0.30);
  transition: background 0.18s ease, box-shadow 0.18s ease, transform 0.12s ease, filter 0.18s ease;
  text-shadow: 0 0 1px rgba(255, 255, 255, 0.35);
}
.btn-stamina:hover {
  background: linear-gradient(135deg,
    #d4ffeb 0%,
    var(--se-mint) 35%,
    var(--se-neon) 75%);
  box-shadow:
    0 0 32px -2px rgba(52, 255, 175, 0.85),
    inset 0 1px 0 rgba(255, 255, 255, 0.45);
  filter: brightness(1.05);
}
.btn-stamina:active { transform: translateY(1px); }
.btn-stamina:disabled {
  background: var(--bg-3);
  color: var(--text-4);
  border-color: var(--border);
  box-shadow: none;
  text-shadow: none;
  cursor: not-allowed;
}

/* Reduced-motion: drop the spark rises + action pulse */
@media (prefers-reduced-motion: reduce) {
  .post-stamina-spark,
  .post-action-stamina.is-alive,
  .stamina-modal-icon {
    animation: none !important;
  }
  .post-stamina-spark { opacity: 0.40; }
}
