@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .px-1 {
    padding-left: 0.25rem;
    padding-right: 0.25rem;
  }

  /* Landing-page splash text overrides. The dark-background splash
     partials (landing_pages/backgrounds/{_blobs,_circles,_gradient}) force
     white text via the .lp-splash scope wrapped around the splash content.
     Each compound selector has specificity 0,0,2,0 — beats Tailwind's
     single-class text utilities (.text-heading etc., specificity 0,0,1,0) —
     so no !important is needed. Previously these rules were duplicated
     inline in all 3 background partials with !important; consolidated here
     2026-05-24. */
  .lp-splash { text-shadow: 0 1px 10px rgba(0, 0, 0, 0.45); }
  .lp-splash .text-heading   { color: #ffffff; }
  .lp-splash .text-body      { color: rgba(255, 255, 255, 0.92); }
  .lp-splash .text-secondary { color: rgba(255, 255, 255, 0.86); }
  .lp-splash .text-muted     { color: rgba(255, 255, 255, 0.72); }
  .lp-splash .card { box-shadow: 0 24px 55px -15px rgba(0, 0, 0, 0.6); }

}

/* Navbar responsive sizing. Promoted from layouts/_navbar.html.erb's
   inline style block 2026-05-24 — these rules fight Tailwind text,
   gap, and sizing utilities applied to the same elements. Lives outside
   any @layer (Tailwind's PostCSS pipeline doesn't accept @media inside
   @layer); unlayered rules outrank any @layer in the cascade, so the
   !important workarounds the inline block needed are dropped. */
.user-nav-col { width: 14rem; }
.nav-title    { display: flex; gap: 0.25em; align-items: baseline; }
@media (min-width: 400px) { .user-nav-col { width: 15rem; } }
@media (min-width: 768px) { .user-nav-col { width: 20rem; } }
@media (max-width: 767px) {
  .nav-title { font-size: 1.25rem; flex-direction: column; gap: 0; line-height: 1.15; transition: font-size 0.3s; }
  .nav-title span:first-child { margin-bottom: -4px; }
  .nav-title span:last-child  { font-size: 1.5rem; transition: font-size 0.3s; }
  .nav-logo-link { gap: 0.5rem; }
  .is-scrolled .nav-title { font-size: 1rem; }
  .is-scrolled .nav-title span:last-child { font-size: 1.15rem; }
  .is-scrolled .nav-logo  { width: 2.5rem; height: 2.5rem; }
}
@media (max-width: 399px) {
  .nav-logo-link { gap: 0.25rem; }
  .nav-title { font-size: 1.1rem; transition: font-size 0.3s; }
  .nav-title span:last-child { font-size: 1.3rem; transition: font-size 0.3s; }
  .is-scrolled .nav-title { font-size: 0.9rem; }
  .is-scrolled .nav-title span:last-child { font-size: 1rem; }
}

/* Toast z-index defaults from studio-engine (60/55) sit below our
   sticky navbar (z-[125] from layouts/_navbar.html.erb) and the modal
   backdrop (z-[120]). Override via the engine's CSS custom properties
   so toasts (200/199) surface above everything — including any open
   modal — no !important needed since studio-engine 0.4.10. */
:root {
  --studio-toast-z: 200;
  --studio-toast-blur-z: 199;
}

/* Theme CSS custom properties are now engine-generated via studio_theme_css_tag */

@layer components {
  .card {
    @apply bg-surface rounded-xl border border-subtle;
  }
  .card-hover {
    @apply bg-surface rounded-xl border border-subtle hover:border-primary-700/50 hover:shadow-lg hover:shadow-primary-900/10 transition;
  }
  .badge {
    @apply inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border;
  }
  .input-field {
    @apply w-full bg-surface-alt border border-strong rounded-lg px-4 py-3 text-heading focus:border-primary focus:outline-none;
  }
  .empty-state {
    @apply bg-surface rounded-xl border border-subtle p-8 text-center;
  }
  .json-debug {
    @apply bg-inset rounded-xl border border-subtle p-4 overflow-x-auto;
  }
  .label-upper {
    @apply text-xs text-muted uppercase tracking-wider;
  }

  /* -- Button System (standardized across all Studio apps) -- */
  .btn {
    @apply inline-flex items-center justify-center font-bold rounded-lg transition px-6 py-2 text-sm disabled:opacity-50 disabled:cursor-not-allowed;
  }
  .btn-primary {
    @apply text-white;
    background-color: var(--color-cta);
  }
  .btn-primary:hover {
    background-color: var(--color-cta-hover);
  }
  .btn-secondary {
    @apply bg-violet hover:bg-violet-600 text-white;
  }
  .btn-outline {
    @apply bg-transparent border text-heading;
    border-color: var(--color-cta);
  }
  .btn-outline:hover {
    @apply text-white;
    background-color: var(--color-cta);
  }
  .btn-danger {
    @apply text-white;
    background-color: var(--color-danger);
  }
  .btn-danger:hover {
    filter: brightness(0.9);
  }
  .btn-warning {
    @apply text-white;
    background-color: var(--color-warning);
  }
  .btn-warning:hover {
    filter: brightness(0.9);
  }
  .btn-success {
    @apply text-white;
    background-color: var(--color-success);
  }
  .btn-success:hover {
    filter: brightness(0.9);
  }
  .btn-google {
    @apply bg-white border border-strong shadow-sm text-gray-700;
  }
  .btn-google:hover {
    @apply bg-gray-50;
  }
  /* Neutral filled "panel" button (Helius-style) — a soft-bordered surface
     tile for non-primary auth options (Google / Solana / wallet picker) so
     only the green primary CTA pops. Theme-aware via surface tokens; subtle
     lighten on hover (same filter pattern as the status buttons above). */
  .btn-neutral {
    @apply bg-surface-alt border border-strong text-heading;
  }
  .btn-neutral:hover {
    filter: brightness(1.3);
  }
  .btn-sm {
    @apply px-3 py-1.5 text-xs;
  }
  .btn-lg {
    @apply px-8 py-3 text-base rounded-xl shadow;
  }

  .backdrop-overlay {
    backdrop-filter: blur(2px) brightness(0.7);
    -webkit-backdrop-filter: blur(2px) brightness(0.7);
    background: rgb(var(--color-primary-900-rgb, 58 51 89) / 0.2);
  }

  .seeds-invite-row .card {
    margin-bottom: 0;
    height: 100%;
  }

  /* ── Hold Button Component ── */
  .hold-btn {
    --duration: 2000ms;
    --color: #F6F8FF;
    --bg: var(--color-cta);
    --shadow: rgba(0, 61, 9, 0.2);
    --shadow-active: rgba(0, 61, 9, 0.32);
    --progress-border: var(--color-inset, #3a3359);
    --progress-active: #F6F8FF;
    --progress-success: #06D6A0;
    --tick-stroke: var(--progress-active);

    position: relative;
    font-size: 14px;
    font-weight: 600;
    line-height: 19px;
    padding: 14px 32px;
    width: 100%;
    border: 0;
    border-radius: 12px;
    outline: none;
    user-select: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    backface-visibility: hidden;
    color: var(--color);
    background: var(--bg);
    transition: transform .3s, box-shadow .3s, background .3s, border-color .3s;
    transform: scale(var(--scale, 1));
    border: 1px solid rgb(var(--color-primary-rgb) / 0.5);
    box-shadow: 0 0 20px rgb(var(--color-primary-rgb) / 0.25);
  }
  .hold-btn.nudge { animation: hold-nudge 0.5s ease-in-out 1; }
  .hold-btn.nudge-soft { animation: hold-nudge-soft 0.4s ease-in-out 1; }

  /* Progress circle */
  .hold-btn > .hold-icon {
    position: absolute; top: 50%; left: 14px;
    width: 20px; height: 20px; margin-top: -10px;
    border-radius: 50%; background: var(--progress-border);
    opacity: var(--icon-o, 0);
    transform: translateX(var(--icon-x, -4px));
    transition: transform .3s, opacity .2s;
  }
  .hold-btn > .hold-icon::before {
    content: ''; width: 16px; height: 16px; left: 2px; top: 2px; z-index: 1;
    position: absolute; background: var(--bg); border-radius: inherit;
    transform: scale(var(--bg-scale, 1)); transition: transform .32s ease;
  }
  .hold-btn > .hold-icon svg { display: block; fill: none; width: 20px; height: 20px; }
  .hold-btn > .hold-icon svg.progress {
    transform: rotate(-90deg) scale(var(--progress-scale, 1)); transition: transform .5s ease;
  }
  .hold-btn > .hold-icon svg.progress circle {
    stroke-dashoffset: 1; stroke-dasharray: var(--progress-array, 0) 52;
    stroke-width: 16; stroke: var(--progress-active);
    transition: stroke-dasharray var(--duration) linear;
  }
  .hold-btn > .hold-icon svg.tick {
    position: absolute; left: 0; top: 0; stroke-width: 3;
    stroke-linecap: round; stroke-linejoin: round; stroke: var(--tick-stroke);
    transition: stroke .3s ease .7s;
  }
  .hold-btn > .hold-icon svg.tick polyline {
    stroke-dasharray: 18 18 18; stroke-dashoffset: var(--tick-offset, 18);
    transition: stroke-dashoffset .4s ease .7s;
  }

  /* Sliding text */
  .hold-btn .hold-text {
    margin: 0; padding: 0; list-style: none; text-align: center;
    pointer-events: none; position: relative; backface-visibility: hidden;
    transition: transform .3s; transform: translate3d(var(--ul-x, 0), 0, 0);
  }
  .hold-btn .hold-text li {
    backface-visibility: hidden; transform: translateY(var(--ul-y, 0));
    transition: transform .3s ease .16s, opacity .2s ease .16s;
  }
  .hold-btn .hold-text li:not(:first-child) { position: absolute; left: 0; right: 0; }
  .hold-btn .hold-text li:nth-child(1) { top: 0; opacity: var(--ul-o-1, 1); }
  .hold-btn .hold-text li:nth-child(2) { top: 100%; opacity: var(--ul-o-2, 0); }
  .hold-btn .hold-text li:nth-child(3) { top: 200%; opacity: var(--ul-o-3, 0); }
  .hold-btn .hold-text li:nth-child(4) { top: 300%; opacity: var(--ul-o-4, 0); }

  /* State: hover */
  .hold-btn:hover:not(.process):not(.success) {
    --shadow-color: var(--shadow-active);
    filter: brightness(0.92);
    border-color: rgb(var(--color-primary-rgb) / 0.7);
    box-shadow: 0 0 30px rgb(var(--color-primary-rgb) / 0.4);
  }
  .hold-btn:active:not(.success) { --scale: .97; }
  .hold-btn.process, .hold-btn.success { animation: none; }

  /* State: process (holding) */
  .hold-btn.process {
    --icon-x: 0; --ul-y: -100%; --ul-o-1: 0; --ul-o-2: 1; --ul-o-3: 0;
    --bg: var(--color-page, #1e1b35);
    border-color: rgb(var(--color-primary-rgb) / 0.7);
    box-shadow: 0 0 30px rgb(var(--color-primary-rgb) / 0.45), 0 0 80px rgb(var(--color-primary-rgb) / 0.25),
                0 0 120px rgb(var(--color-primary-rgb) / 0.1), inset 0 0 40px rgb(var(--color-primary-rgb) / 0.1);
    transition: transform .3s, box-shadow 2.5s ease-out, border-color 1s, background .5s;
  }
  .hold-btn.process, .hold-btn.success {
    --ul-x: 8px; --icon-o: 1; --progress-array: 52;
  }

  /* State: success */
  .hold-btn.success {
    --scale: 1.03; --icon-x: 6px; --progress-border: none; --progress-scale: .11;
    --tick-stroke: var(--progress-success); --bg-scale: 0; --tick-offset: 36;
    --ul-y: -200%; --ul-o-1: 0; --ul-o-2: 0; --ul-o-3: 1;
    border-color: #06D6A0;
    background: linear-gradient(135deg, #025e40, #06D6A0);
    --color: #fff;
    box-shadow: 0 0 40px rgba(6, 214, 160, 0.7), 0 0 100px rgba(6, 214, 160, 0.4),
                0 0 160px rgba(6, 214, 160, 0.15);
    animation: hold-glow-pulse .8s ease-in-out 1;
  }
  .hold-btn.success > .hold-icon svg.progress { animation: hold-tick .3s linear forwards .4s; }

  /* State: error */
  .hold-btn.error {
    --ul-x: 0; --icon-o: 0; --ul-y: -300%;
    --ul-o-1: 0; --ul-o-2: 0; --ul-o-3: 0; --ul-o-4: 1;
    --bg: var(--color-danger, #EF4444);
    border-color: rgba(239, 68, 68, 0.7);
    box-shadow: 0 0 30px rgba(239, 68, 68, 0.4);
    animation: none; cursor: default;
  }

  /* State: loading — hold completed, submission in flight, waiting for
     server confirmation. Keeps the green glow from .process so the user
     sees continuity, but swaps the filled progress ring for a spinning
     arc so the visual reads as "still working" rather than "done". */
  .hold-btn.loading {
    --icon-x: 0; --ul-y: -100%; --ul-o-1: 0; --ul-o-2: 1; --ul-o-3: 0;
    --bg: var(--color-page, #1e1b35);
    --ul-x: 8px; --icon-o: 1;
    border-color: rgb(var(--color-primary-rgb) / 0.7);
    box-shadow: 0 0 30px rgb(var(--color-primary-rgb) / 0.45), 0 0 80px rgb(var(--color-primary-rgb) / 0.25),
                0 0 120px rgb(var(--color-primary-rgb) / 0.1), inset 0 0 40px rgb(var(--color-primary-rgb) / 0.1);
    cursor: default;
  }
  .hold-btn.loading > .hold-icon { background: transparent; }
  .hold-btn.loading > .hold-icon::before { display: none; }
  .hold-btn.loading > .hold-icon svg.progress {
    animation: hold-spinner 0.8s linear infinite;
    transition: none;
  }
  .hold-btn.loading > .hold-icon svg.progress circle {
    stroke-width: 3;
    stroke-dasharray: 30 21;
    stroke-dashoffset: 0;
    stroke: rgb(var(--color-primary-rgb));
    transition: none;
  }
  @keyframes hold-spinner {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
  }

  /* Debug nudge countdown */
  .nudge-debug {
    position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
    width: 24px; height: 24px; pointer-events: none; display: none;
  }
  .dev-mode .nudge-debug { display: block; }
  .nudge-debug svg { width: 24px; height: 24px; transform: rotate(-90deg); }
  .nudge-debug svg circle.track { fill: none; stroke: rgba(255,255,255,0.15); stroke-width: 3; }
  .nudge-debug svg circle.fill {
    fill: none; stroke: var(--color-cta, #8E82FE); stroke-width: 3;
    stroke-linecap: round; stroke-dasharray: 56.55; stroke-dashoffset: 0;
    transition: stroke-dashoffset 0.3s linear;
  }
  .nudge-debug .countdown-num {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    font-size: 9px; font-weight: 800; color: #F6F8FF; font-family: ui-monospace, monospace;
  }
  .hold-btn.process .nudge-debug, .hold-btn.success .nudge-debug { opacity: 0; }

  /* ── Level Badges (1–10) ──
     Progressive visual treatments for the user's level pill. Tiers:
       L1   : outline starter
       L2–3 : filled green progression
       L4–6 : metallic (bronze, silver, gold)
       L7–9 : gemstones (emerald, sapphire, ruby)
       L10  : holographic legendary
     Preview gallery at /admin/level_badges. */
  .level-badge {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 2px 10px; border-radius: 9999px;
    font-size: 10px; font-weight: 700; line-height: 1.4; letter-spacing: 0.02em;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    white-space: nowrap;
    border: 1px solid transparent;
  }
  .level-badge-1 {
    background: rgb(var(--color-primary-500-rgb) / 0.15);
    color: var(--color-cta);
    border-color: rgb(var(--color-primary-500-rgb) / 0.3);
  }
  .level-badge-2 {
    background: linear-gradient(180deg, rgb(var(--color-primary-500-rgb) / 0.55), rgb(var(--color-primary-500-rgb) / 0.35));
    color: #fff;
    border-color: rgb(var(--color-primary-500-rgb) / 0.6);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
  }
  /* L3 — L2 fill plus inset top highlight + inset bottom shadow line
     for a "polished pin" feel (no outer drop shadow yet). */
  .level-badge-3 {
    background: linear-gradient(180deg, rgb(var(--color-primary-500-rgb) / 0.6), rgb(var(--color-primary-500-rgb) / 0.4));
    color: #fff;
    border-color: rgb(var(--color-primary-500-rgb) / 0.7);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.4),
      inset 0 -1px 0 rgba(0, 0, 0, 0.15);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
  }
  /* L4 — L2 fill plus an outer drop shadow so the badge reads as
     lifted off the card surface. */
  .level-badge-4 {
    background: linear-gradient(180deg, rgb(var(--color-primary-500-rgb) / 0.6), rgb(var(--color-primary-500-rgb) / 0.4));
    color: #fff;
    border-color: rgb(var(--color-primary-500-rgb) / 0.7);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
  }
  /* L5 — L4 fill with a static medium green glow (8/16px) around the
     perimeter. Wider than a rim, tighter than a wide halo. */
  .level-badge-5 {
    background: linear-gradient(180deg, rgb(var(--color-primary-500-rgb) / 0.6), rgb(var(--color-primary-500-rgb) / 0.4));
    color: #fff;
    border-color: rgb(var(--color-primary-500-rgb) / 0.85);
    box-shadow:
      0 0 8px rgb(var(--color-primary-500-rgb) / 0.7),
      0 0 16px rgb(var(--color-primary-500-rgb) / 0.4);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
  }
  /* L6 — L5's medium glow plus a beveled-coin treatment: bumped fill
     saturation, top white inset highlight, bottom dark inset shadow,
     and a tight drop shadow for the sculpted-pin feel. */
  .level-badge-6 {
    background: linear-gradient(180deg, rgb(var(--color-primary-500-rgb) / 0.7), rgb(var(--color-primary-500-rgb) / 0.45));
    color: #fff;
    border-color: rgb(var(--color-primary-500-rgb) / 0.85);
    box-shadow:
      inset 0 2px 0 rgba(255, 255, 255, 0.5),
      inset 0 -2px 0 rgba(0, 0, 0, 0.25),
      0 1px 2px rgba(0, 0, 0, 0.35),
      0 0 8px rgb(var(--color-primary-500-rgb) / 0.7),
      0 0 16px rgb(var(--color-primary-500-rgb) / 0.4);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
  }
  /* ── Badge Sheen Sweep (reusable utility) ────────────────────────
     Wraps any pill-shaped element to fire a one-shot diagonal white
     sheen on initial render. Built so the seeds-card level badge can
     "wink" alongside the seeds bar's existing per-segment shimmer.
     Delay is driven by a CSS custom property (--sheen-delay) so the
     caller can sync to whatever else is animating nearby.
     Source: extracted from the 7d Sheen Sweep candidate 2026-05-24. */
  .badge-with-sheen {
    position: relative;
    display: inline-block;
    border-radius: 9999px;
  }
  .badge-with-sheen::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, transparent 35%, rgba(255, 255, 255, 0.45) 50%, transparent 65%);
    background-size: 250% 100%;
    background-repeat: no-repeat;
    background-position: 200% 0;
    border-radius: inherit;
    pointer-events: none;
    animation: badge-sheen-sweep 2.5s ease-in-out var(--sheen-delay, 0s) 1 both;
  }
  @keyframes badge-sheen-sweep {
    from { background-position:  200% 0; }
    to   { background-position: -100% 0; }
  }

  /* Reference: classic metal-tier treatments — the original Silver / Gold
     concept for L5 / L6 before we settled on green-family glows. Kept
     for future use and shown in the Reference section of the preview. */
  .level-badge-classic-5 {
    background: linear-gradient(135deg, #f5f5f5, #9a9a9a 55%, #d8d8d8);
    color: #2a2a2a;
    border-color: #b8b8b8;
    box-shadow: 0 0 12px rgba(220, 220, 220, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.85), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.65);
  }
  .level-badge-classic-6 {
    background: linear-gradient(135deg, #FFE34D, #B8860B 55%, #FFC107);
    color: #4a2e00;
    border-color: #e8b500;
    box-shadow: 0 0 14px rgba(255, 215, 0, 0.6), 0 0 28px rgba(255, 215, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.8), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
    font-weight: 800;
  }
  /* L7 — L6's beveled face with the fill swapped to a diagonal
     primary → mint gradient. Adds chromatic depth without animation. */
  .level-badge-7 {
    background: linear-gradient(135deg, rgb(var(--color-primary-500-rgb) / 0.8), rgba(6, 214, 160, 0.7));
    color: #fff;
    border-color: rgb(var(--color-primary-500-rgb) / 0.85);
    box-shadow:
      inset 0 2px 0 rgba(255, 255, 255, 0.5),
      inset 0 -2px 0 rgba(0, 0, 0, 0.25),
      0 1px 2px rgba(0, 0, 0, 0.35),
      0 0 8px rgb(var(--color-primary-500-rgb) / 0.7),
      0 0 16px rgb(var(--color-primary-500-rgb) / 0.4);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
  }
  /* L8 — Full transition to mint. Same beveled face as L6/L7 but
     the fill is all mint (no green half), so this is where the
     green-family arc lands on its destination color. */
  .level-badge-8 {
    background: linear-gradient(135deg, #16E5B6, #06D6A0 60%, #028A6F);
    color: #fff;
    border-color: rgba(6, 214, 160, 0.85);
    box-shadow:
      inset 0 2px 0 rgba(255, 255, 255, 0.5),
      inset 0 -2px 0 rgba(0, 0, 0, 0.25),
      0 1px 2px rgba(0, 0, 0, 0.35),
      0 0 10px rgba(6, 214, 160, 0.7),
      0 0 20px rgba(6, 214, 160, 0.4);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
    font-weight: 800;
  }
  /* L9 — Animated green ↔ mint gradient. Symmetric 3-stop palette so
     the level-shimmer background-position sweep slides the mint peak
     across the badge and back without a hard reset. */
  .level-badge-9 {
    background: linear-gradient(135deg,
      rgb(var(--color-primary-500-rgb) / 0.9),
      rgba(6, 214, 160, 0.9) 50%,
      rgb(var(--color-primary-500-rgb) / 0.9));
    background-size: 200% 200%;
    color: #fff;
    border-color: rgba(6, 214, 160, 0.85);
    box-shadow:
      inset 0 2px 0 rgba(255, 255, 255, 0.5),
      inset 0 -2px 0 rgba(0, 0, 0, 0.25),
      0 1px 2px rgba(0, 0, 0, 0.35),
      0 0 10px rgba(6, 214, 160, 0.7),
      0 0 20px rgba(6, 214, 160, 0.4);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
    animation: level-shimmer 2.5s ease-in-out infinite;
    font-weight: 800;
  }
  @keyframes level-shimmer {
    0%, 100% { background-position: 0% 50%; }
    50%      { background-position: 100% 50%; }
  }
  .level-badge-10 {
    background: linear-gradient(135deg, #ff0080, #ff8c00, #ffd700, #06D6A0, #4BAF50, #00d4ff, #8E82FE, #ff0080);
    background-size: 400% 400%;
    color: #fff;
    border-color: rgba(255, 255, 255, 0.5);
    box-shadow: 0 0 24px rgba(255, 255, 255, 0.55), 0 0 48px rgba(255, 215, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.5);
    text-shadow: 0 0 6px rgba(255, 255, 255, 0.85), 0 1px 2px rgba(0, 0, 0, 0.5);
    animation: level-holographic 4s ease infinite;
    font-weight: 900;
    letter-spacing: 0.04em;
  }
  @keyframes level-holographic {
    0%, 100% { background-position: 0% 50%; }
    50%      { background-position: 100% 50%; }
  }

  /* ── Dev Mode Debug Colors ──
     `!important` is intentional here: these are diagnostic overlays
     applied via `.dm-{color}` classes on arbitrary elements, and they
     must override whatever the element's own background is. Scoped to
     `.dev-mode` (toggled by Alpine $store.devMode) so production users
     never see them. Don't refactor — `!important` is the right tool for
     "diagnostic paint on top of everything." */
  .dev-mode .dm-blue     { background: rgba(100, 149, 237, 0.75) !important; } /* cornflowerblue */
  .dev-mode .dm-green    { background: rgba(144, 238, 144, 0.75) !important; } /* lightgreen */
  .dev-mode .dm-orange   { background: rgba(244, 164, 96, 0.75) !important; } /* sandybrown */
  .dev-mode .dm-salmon   { background: rgba(250, 128, 114, 0.75) !important; } /* lightsalmon */
  .dev-mode .dm-purple   { background: rgba(128, 0, 128, 0.75) !important; } /* purple */
  .dev-mode .dm-coral    { background: rgba(240, 128, 128, 0.75) !important; } /* lightcoral */
  .dev-mode .dm-yellow   { background: rgba(240, 230, 140, 0.75) !important; } /* khaki */
  .dev-mode .dm-teal     { background: rgba(175, 238, 238, 0.75) !important; } /* paleturquoise */

  /* Nav level-up bounce (shared by _user_nav and _navbar_seeds_bar) */
  .nav-level-pop { animation: navLevelPop 0.8s cubic-bezier(0.34, 1.56, 0.64, 1); }

  .matchup-selected {
    outline: 3px solid rgb(var(--color-primary-rgb));
    outline-offset: -3px;
    background-color: rgb(var(--color-primary-rgb) / 0.05);
    box-shadow: 0 0 20px 4px rgb(var(--color-primary-rgb) / 0.35);
  }

  /* Theme transition (applied during toggle) */
  .theme-transition *,
  .theme-transition *::before,
  .theme-transition *::after {
    transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease, box-shadow 200ms ease !important;
  }
}

@keyframes navLevelPop {
  0% { transform: scale(1); }
  15% { transform: scale(1.5); }
  35% { transform: scale(1.2); }
  50% { transform: scale(1.4); }
  70% { transform: scale(1.1); }
  100% { transform: scale(1); }
}
@keyframes card-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-3px) rotate(-1deg); }
  30% { transform: translateX(3px) rotate(1deg); }
  45% { transform: translateX(-2px); }
  60% { transform: translateX(2px); }
  75% { transform: translateX(-1px); }
}
.shake { animation: card-shake 0.4s ease-out; }

/* Hold button animations */
@keyframes hold-tick {
  100% { transform: rotate(-90deg) translate(0, -5px) scale(var(--progress-scale)); }
}
@keyframes hold-glow-pulse {
  50% { box-shadow: 0 0 60px rgba(6, 214, 160, 0.9), 0 0 140px rgba(6, 214, 160, 0.5), 0 0 200px rgba(6, 214, 160, 0.2); }
}
@keyframes hold-nudge {
  0%, 100% { transform: scale(1) rotate(0deg); }
  15% { transform: scale(1.06) rotate(-2deg); }
  30% { transform: scale(1.06) rotate(2deg); }
  45% { transform: scale(1.04) rotate(-1.5deg); }
  60% { transform: scale(1.03) rotate(1deg); }
  75% { transform: scale(1.02) rotate(-0.5deg); }
  90% { transform: scale(1.01) rotate(0.3deg); }
}
@keyframes hold-nudge-soft {
  0%, 100% { transform: scale(1) rotate(0deg); }
  20% { transform: scale(1.03) rotate(-1deg); }
  40% { transform: scale(1.03) rotate(1deg); }
  60% { transform: scale(1.02) rotate(-0.5deg); }
  80% { transform: scale(1.01) rotate(0.5deg); }
}

/* Seeds bar animations */
@keyframes seedsBarGlow {
  0%, 100% { box-shadow: 0 0 6px rgba(var(--color-primary-500-rgb), 0.3); }
  50% { box-shadow: 0 0 14px rgba(var(--color-primary-500-rgb), 0.6); }
}
@keyframes seedsShimmer {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(200%); }
}
/* Pulse variant: shine happens in first 25% of duration, then idles. animation-duration controls the cycle interval. */
@keyframes seedsShimmerPulse {
  0%   { transform: translateX(-100%); }
  25%  { transform: translateX(200%); }
  100% { transform: translateX(200%); }
}
/* Lobby button countdown — drains a fill from full to empty over animation-duration */
@keyframes lobby-drain {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}
/* Auth wizard submit-button text shimmer — applied while a network attempt
   is in flight (props.submitting truthy). The text becomes the loader: a
   bright gradient sweeps left-to-right across the label, no spinner glyph
   needed. Uses background-clip:text so the underlying button fill stays
   unchanged. */
.auth-text-shimmer {
  background: linear-gradient(
    90deg,
    rgba(255,255,255,0.55) 0%,
    rgba(255,255,255,1)   50%,
    rgba(255,255,255,0.55) 100%
  );
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: auth-text-shimmer 1.4s linear infinite;
}
@keyframes auth-text-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}
/* Registered custom property — makes --bar-progress interpolatable so a single transition drives all 5 segment widths in lockstep (one ease curve, not 5 chained). */
@property --bar-progress {
  syntax: '<number>';
  initial-value: 0;
  inherits: true;
}
.seeds-bar-continuous {
  --bar-progress: 0;
  transition: --bar-progress 1.2s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes levelPop {
  0% { transform: scale(1) rotate(0deg); }
  12% { transform: scale(3.2) rotate(-4deg); }
  28% { transform: scale(2.4) rotate(3deg); }
  42% { transform: scale(2.8) rotate(-1.5deg); }
  60% { transform: scale(2.2) rotate(1deg); }
  78% { transform: scale(1.3) rotate(0deg); }
  100% { transform: scale(1) rotate(0deg); }
}
@keyframes levelGlow {
  0% { box-shadow: 0 0 0 0 rgba(var(--color-primary-500-rgb), 0.9); }
  25% { box-shadow: 0 0 40px 16px rgba(var(--color-primary-500-rgb), 0.7); }
  50% { box-shadow: 0 0 25px 10px rgba(var(--color-primary-500-rgb), 0.4); }
  100% { box-shadow: 0 0 0 0 rgba(var(--color-primary-500-rgb), 0); }
}
.level-up-pop {
  animation: levelPop 1.1s cubic-bezier(0.34, 1.56, 0.64, 1), levelGlow 1.4s ease-out;
  z-index: 10;
}

/* Pick slot attention animations */
@keyframes pick-pulse {
  0%, 100% { border-color: rgb(var(--color-primary-rgb) / 0.2); box-shadow: 0 0 0 0 rgb(var(--color-primary-rgb) / 0); }
  50% { border-color: rgb(var(--color-primary-rgb) / 0.9); box-shadow: 0 0 14px 3px rgb(var(--color-primary-rgb) / 0.3); }
}
.pick-pulse {
  animation: pick-pulse 2s ease-in-out infinite;
  border-style: solid;
}

@keyframes pick-shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
.pick-pulse-shimmer {
  animation: pick-pulse 2s ease-in-out infinite, pick-shimmer 3s ease-in-out infinite;
  border-style: solid;
  background-size: 200% 100%;
  background-image: linear-gradient(
    120deg,
    transparent 25%,
    rgb(var(--color-primary-rgb) / 0.1) 45%,
    rgb(var(--color-primary-rgb) / 0.2) 50%,
    rgb(var(--color-primary-rgb) / 0.1) 55%,
    transparent 75%
  );
}

@keyframes pick-pulse-urgent {
  0%, 100% { border-color: rgb(var(--color-primary-rgb) / 0.3); box-shadow: 0 0 0 0 rgb(var(--color-primary-rgb) / 0); transform: scale(1); }
  50% { border-color: rgb(var(--color-primary-rgb) / 1); box-shadow: 0 0 20px 6px rgb(var(--color-primary-rgb) / 0.45); transform: scale(1.04); }
}
@keyframes pick-shimmer-fast {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
.pick-pulse-urgent {
  animation: pick-pulse-urgent 1s ease-in-out infinite, pick-shimmer-fast 1.5s ease-in-out infinite;
  border-style: solid;
  background-size: 200% 100%;
  background-image: linear-gradient(
    120deg,
    transparent 20%,
    rgb(var(--color-primary-rgb) / 0.15) 40%,
    rgb(var(--color-primary-rgb) / 0.3) 50%,
    rgb(var(--color-primary-rgb) / 0.15) 60%,
    transparent 80%
  );
}

/* === Modal host (studio/modals/_host.html.erb) =========================
   Promoted from the local override partial's inline <style> block
   2026-05-24 — these rules are page-static and were being emitted on
   every HTML response. */

/* Scroll lock applied by $store.modals._sync() when the stack is non-empty. */
body.modal-open { overflow: hidden; }

/* Drain-bar keyframe used by studio/modals/blocks/_success_card when
   cta_drain is set — kept available for any future caller that re-enables
   that option. */
@keyframes studio-modal-drain {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}

/* === Loading dots =====================================================
   Three primary-colored dots that bounce in sequence. Used by
   modals/blocks/_card_header when spinner: true so async/in-flight
   states (auth modal's tokens-confirming, onchain-tx processing) share
   one visual idiom for "we're working on it" with the rest of the
   modal family. Sizing matches the icon pills (32px square footprint)
   so the card_header layout stays aligned across spinner / icon /
   emoji modes. */
.loading-dots {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.loading-dots span {
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  background: var(--color-primary);
  animation: loading-dots-bounce 1.2s ease-in-out infinite both;
}
.loading-dots span:nth-child(1) { animation-delay: -0.32s; }
.loading-dots span:nth-child(2) { animation-delay: -0.16s; }
.loading-dots span:nth-child(3) { animation-delay: 0s; }
@keyframes loading-dots-bounce {
  0%, 80%, 100% { transform: translateY(0)    scale(0.85); opacity: 0.45; }
  40%           { transform: translateY(-6px) scale(1);    opacity: 1;    }
}
@media (prefers-reduced-motion: reduce) {
  .loading-dots span { animation: none; opacity: 0.85; }
}

/* === Modal mount / unmount animations =================================
   Used by studio/modals/_host.html.erb via the .modal-card-{mount,unmount}
   and .modal-backdrop-{mount,unmount} classes the host binds. The card
   open/close use multi-keyframe spring-y curves so the modal POPS in
   with a small overshoot and POPS out with a tiny anticipation windup
   before falling — much more present than a plain ease. Backdrop stays
   on a clean fade (a bouncing backdrop reads as a bug, not polish).

   Durations: card-out stays at 220ms to match CLOSE_ANIM_MS in the
   host's store — that's the timeout before the entry is spliced off
   the stack. Bump CLOSE_ANIM_MS if you lengthen this. */

@keyframes modal-card-in {
  0%   { opacity: 0; transform: scale(0.86) translateY(12px); }
  55%  { opacity: 1; transform: scale(1.04) translateY(-3px); } /* overshoot */
  80%  {              transform: scale(0.99) translateY(1px);  } /* small undershoot */
  100% { opacity: 1; transform: scale(1) translateY(0);        } /* settle */
}
@keyframes modal-card-out {
  0%   { opacity: 1; transform: scale(1) translateY(0);         }
  25%  { opacity: 1; transform: scale(1.03) translateY(-2px);   } /* windup */
  100% { opacity: 0; transform: scale(0.92) translateY(4px);    } /* fall */
}
@keyframes modal-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modal-backdrop-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Bounce ease the spring; the keyframes already do most of the work,
   the easing just smooths the segments between them. */
.modal-card-mount     { animation: modal-card-in     320ms cubic-bezier(0.34, 1.4, 0.5, 1) both; }
.modal-card-unmount   { animation: modal-card-out    220ms cubic-bezier(0.6, -0.2, 0.7, 0.4) forwards; }
.modal-backdrop-mount { animation: modal-backdrop-in 180ms ease-out both; }
.modal-backdrop-unmount { animation: modal-backdrop-out 200ms ease-in forwards; }

/* Inter-modal swap animations — directional slide for forward-flow feel.
   The leaving modal slides off to the right + fades; the new one enters
   from the left. Pairs with $store.modals.swap() (or open(_, _, { replace
   true })) in studio/modals/_host.html.erb — the store flips _swappingOut
   on the leaving entry, waits CLOSE_ANIM_MS, then flips _swappingIn on
   the entering entry. Total swap duration ≈ 440ms (220 out + 220 in). */
@keyframes modal-card-slide-out-right {
  from { opacity: 1; transform: translateX(0) scale(1); }
  to   { opacity: 0; transform: translateX(48px) scale(0.98); }
}
@keyframes modal-card-slide-in-left {
  from { opacity: 0; transform: translateX(-48px) scale(0.98); }
  to   { opacity: 1; transform: translateX(0) scale(1); }
}
.modal-card-swap-out { animation: modal-card-slide-out-right 220ms cubic-bezier(0.4, 0, 1, 1) forwards; }
.modal-card-swap-in  { animation: modal-card-slide-in-left   220ms cubic-bezier(0, 0, 0.2, 1) both; }

/* Back-direction pair — current step slides off stage-LEFT, next slides
   in from the RIGHT. Mirror of the forward pair above; together they
   give wizard nav a directional language (forward = →, back = ←) so
   the user's spatial model of "where the previous step lives" is
   preserved. Trigger: $store.modals.advance(patch, { direction: 'back' })
   or swap(id, props, { direction: 'back' }). */
@keyframes modal-card-slide-out-left {
  from { opacity: 1; transform: translateX(0) scale(1); }
  to   { opacity: 0; transform: translateX(-48px) scale(0.98); }
}
@keyframes modal-card-slide-in-right {
  from { opacity: 0; transform: translateX(48px) scale(0.98); }
  to   { opacity: 1; transform: translateX(0) scale(1); }
}
.modal-card-swap-out-back { animation: modal-card-slide-out-left 220ms cubic-bezier(0.4, 0, 1, 1) forwards; }
.modal-card-swap-in-back  { animation: modal-card-slide-in-right 220ms cubic-bezier(0, 0, 0.2, 1) both; }

/* Honor reduced-motion preferences — drop the spring/slide motion,
   just fade fast. Vestibular safety. */
@media (prefers-reduced-motion: reduce) {
  .modal-card-mount,
  .modal-card-unmount,
  .modal-card-swap-out,
  .modal-card-swap-in,
  .modal-card-swap-out-back,
  .modal-card-swap-in-back,
  .modal-backdrop-mount,
  .modal-backdrop-unmount {
    animation-duration: 80ms;
    animation-timing-function: linear;
  }
  @keyframes modal-card-in  { from { opacity: 0; } to { opacity: 1; } }
  @keyframes modal-card-out { from { opacity: 1; } to { opacity: 0; } }
  @keyframes modal-card-slide-out-right { from { opacity: 1; } to { opacity: 0; } }
  @keyframes modal-card-slide-in-left   { from { opacity: 0; } to { opacity: 1; } }
  @keyframes modal-card-slide-out-left  { from { opacity: 1; } to { opacity: 0; } }
  @keyframes modal-card-slide-in-right  { from { opacity: 0; } to { opacity: 1; } }
}

/* === Entry-token badge (components/_entry_token_badge.html.erb) ========
   Punch animation fired by window.animateFreeEntryBadge() after a
   token-funded entry confirms. We intentionally do NOT set `display:` on
   .free-entry-badge so Tailwind's `hidden` class wins when the user has
   no tokens. */
.free-entry-badge { transform-origin: center; }
.free-entry-punch { animation: free-entry-punch 0.65s cubic-bezier(.36,.07,.19,.97) both; }
@keyframes free-entry-punch {
  0%   { transform: scale(1)    rotate(0deg); }
  20%  { transform: scale(1.45) rotate(-15deg); }
  40%  { transform: scale(1.2)  rotate(12deg); }
  60%  { transform: scale(1.3)  rotate(-6deg); }
  100% { transform: scale(1)    rotate(0deg); }
}

/* === Contest chat panel (contests/_chat_panel.html.erb) ================ */
.chat-admin-only { display: none; }
.chat-admin .chat-admin-only { display: inline-block; }

/* New messages slide + fade in as they prepend to the list. */
@keyframes chat-message-in {
  from { opacity: 0; transform: translateY(-0.85rem) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.chat-message-in { animation: chat-message-in 0.34s cubic-bezier(0.22, 1, 0.36, 1); }

/* Keyboard-shortcut keycaps shown inside the Send button. */
.chat-kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.1rem; height: 1.1rem; padding: 0 0.3rem;
  background: rgba(0, 0, 0, 0.18);
  border-radius: 0.3rem;
  font-size: 0.72rem; line-height: 1;
}

/* Pulsing "Live" indicator — a radar-ping ring behind the solid dot. */
@keyframes chat-live-ping {
  0%        { transform: scale(1);   opacity: 0.6; }
  70%, 100% { transform: scale(2.6); opacity: 0; }
}
.chat-live-ping { animation: chat-live-ping 1.8s cubic-bezier(0, 0, 0.2, 1) infinite; }

/* Soften the bottom edge of the message list — content fades out. */
.chat-scroll-fade {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2.5rem), transparent);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 2.5rem), transparent);
}
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *


 */

.wallet-badge {
  top: 20px;
  font-size: 8px;
}
