#pingMap{position:fixed;inset:0;z-index:0}
#pingMap .maplibregl-control-container,#pingMap .mapboxgl-control-container{display:none !important}
.ping-bg-image{
    position:fixed;top:0;left:0;right:0;height:50vh;
    z-index:1;
    background-size:cover;background-position:center top;background-repeat:no-repeat;
    opacity:0.35;
    mask-image:linear-gradient(to bottom,black 30%,transparent 100%);
    -webkit-mask-image:linear-gradient(to bottom,black 30%,transparent 100%);
    pointer-events:none;
    /* will-change hints the compositor to keep the element on its own
       layer, so transitioning opacity doesn't force a layer promote
       mid-animation (which was the most likely cause of the show-side
       transition getting dropped while the hide-side still worked). */
    will-change:opacity;
    transition-property:opacity;
    transition-duration:.9s;
    transition-timing-function:ease;
}
@media(max-height:600px){.ping-bg-image{height:40vh}}
/* When the intro modal flies in, the GamifEYE hero art it uses for its
   own hero image duplicates what .ping-bg-image is already showing as
   the map-surround backdrop. Fading the backdrop to 0 removes that
   redundancy — the intro card becomes the sole carrier of the hero
   imagery and the globe reads cleaner through the gap above/below the
   card. The .9s fade (vs the card's .65s fly-in) means the backdrop
   lingers just long enough to visually "hand off" the art to the card
   as it arrives from above. Toggled directly on the element (not via a
   body class) so there's no ancestor-cascade indirection that browsers
   might optimise away mid-transition. */
.ping-bg-image.is-faded{opacity:0;}

/* The old .ping-top branding card + .ping-lang-wrap rules lived here.
   Removed alongside their markup in ping-landing.blade.php — the intro
   modal (#spinIntroModal) now owns the landing-page headline/CTA/lang
   chooser. Kept .ping-bg-image above because it still backs the map. */

/* ── Spin The Globe button ──
   Visuals live on .spin-globe-pill so the same pill can be dropped into
   any container (currently the intro modal body). The previous
   fixed-positioning rules scoped to #spinGlobeBtn were removed when the
   button moved inside the intro modal — it now lays out inline within
   the modal's CTA row. The id is retained for JS selector compatibility
   (see ping-landing.js: document.getElementById('spinGlobeBtn')). */
.spin-globe-pill{
    display:inline-flex;flex-direction:column;align-items:center;gap:4px;
    padding:18px 38px;
    background:rgba(10,14,30,0.55);
    -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
    border:1.5px solid rgba(0,255,213,0.55);
    border-radius:999px;
    color:#fff;font-family:'Fredoka',sans-serif;font-weight:700;
    cursor:pointer;
    box-shadow:0 10px 40px rgba(0,255,213,0.22),inset 0 0 0 1px rgba(255,255,255,0.05);
    transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .35s ease,box-shadow .25s ease,border-color .25s ease;
    animation:spinGlobePulse 2.6s ease-in-out infinite;
}
.spin-globe-pill:hover{transform:scale(1.05);border-color:rgba(0,255,213,0.9);box-shadow:0 14px 50px rgba(0,255,213,0.38),0 0 0 6px rgba(0,255,213,0.1);}
.spin-globe-pill:active{transform:scale(0.98);}
.spin-globe-pill:focus-visible{outline:2px solid #00ffd5;outline-offset:4px;}
.spin-globe-pill .spin-label{font-size:17px;letter-spacing:1.5px;text-transform:uppercase;text-shadow:0 1px 8px rgba(0,0,0,0.5);}
.spin-globe-pill .spin-sub{font-size:10px;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,0.75);font-weight:500;}
@keyframes spinGlobePulse{
    0%,100%{box-shadow:0 10px 40px rgba(0,255,213,0.22),inset 0 0 0 1px rgba(255,255,255,0.05);}
    50%{box-shadow:0 14px 52px rgba(0,255,213,0.45),inset 0 0 0 1px rgba(255,255,255,0.05);}
}
@media(max-width:500px){
    .spin-globe-pill{padding:14px 26px;}
    .spin-globe-pill .spin-label{font-size:14px;letter-spacing:1.2px;}
    .spin-globe-pill .spin-sub{font-size:9px;letter-spacing:1.4px;}
}

/* ── Spin modals (9:16 story card) ──
   Two dialogs share this card shell so the visual language between the
   intro and the flyin result feels like one system:
     - #spinIntroModal is the landing-page CTA surface.
     - #spinModal is the result modal shown after the globe flies to a place.
   Keep structural rules on combined selectors so they stay in lockstep;
   per-modal overrides (intro body padding, no close/stamp on intro, etc.)
   live in their own blocks further down. */
#spinModal, #spinIntroModal{
    position:fixed;inset:0;
    z-index:40;
    display:none;
    align-items:center;justify-content:center;
    background:rgba(5,8,18,0.58);
    -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
    opacity:0;
    transition:opacity .3s ease;
}
#spinModal.visible, #spinIntroModal.visible{display:flex;opacity:1;}
#spinModal .spin-card, #spinIntroModal .spin-card{
    position:relative;
    width:420px;max-width:90vw;
    aspect-ratio:9/16;
    max-height:92vh;
    border-radius:20px;
    overflow:hidden;
    background:#14182a;
    /* Stacked box-shadows:
         1. ambient drop shadow (same as before, adjusted to sit below the ring)
         2. solid 2px magenta ring — drawn as a shadow (not a CSS `border`)
            so the hero image can still bleed right up to the card edge
            without a content-inset band, and so `overflow:hidden` never
            clips the ring against the rounded corners.
         3. outer magenta glow echoing the .spin-intro-name-hero text. */
    box-shadow:
        0 30px 80px rgba(0,0,0,0.6),
        0 0 0 2px #ff2bd6,
        0 0 28px rgba(255,43,214,0.38);
    transform:scale(0.92) translateY(-120vh);
    opacity:0;
    font-family:'Fredoka',sans-serif;
    display:flex;flex-direction:column;
}
/* When the inline auth panel takes over the card body, release the
   9/16 aspect lock so the card can grow as tall as its content
   (still capped by the existing max-height:92vh). Without this the
   MS-win panel — heading + tabs + form + notice + submit + Spin
   Again — overflows its fixed-ratio body and the last button gets
   clipped on phone-height viewports. :has() is widely supported in
   all evergreen browsers we target. */
#spinModal .spin-card:has(.spin-body.is-authpanel),
#spinIntroModal .spin-card:has(.spin-body.is-authpanel){
    aspect-ratio:auto;
}
#spinModal.visible .spin-card, #spinIntroModal.visible .spin-card{
    animation:spinFlyIn .65s cubic-bezier(.4,0,.2,1) forwards;
}
@keyframes spinFlyIn{
    0%   {transform:scale(0.92) translateY(-120vh);opacity:0;}
    82%  {transform:scale(0.98) translateY(24px);  opacity:1;}
    100% {transform:scale(1)    translateY(0);     opacity:1;}
}
/* ── Spin-modal fly-out ──
   Reverse of spinFlyIn for when the intro modal dismisses itself on a
   Spin-The-Globe click. Scoped selector (.is-flying-out.visible) beats
   the fly-in rule above on specificity so the new animation takes over
   cleanly. .visible is kept ON during the animation so display:flex
   stays put; the JS strips both classes together on animationend.
   Also applied to #spinModal in case a future code path wants to
   dismiss the result modal with the same motion language. */
#spinIntroModal.is-flying-out.visible .spin-card,
#spinModal.is-flying-out.visible .spin-card{
    animation:spinFlyOut .5s cubic-bezier(.4,0,.2,1) forwards;
}
/* Backdrop fades in lock-step. Higher specificity than the .visible
   opacity:1 rule, and the base .3s opacity transition handles the
   easing — no extra transition declaration needed. */
#spinIntroModal.is-flying-out.visible,
#spinModal.is-flying-out.visible{
    opacity:0;
}
@keyframes spinFlyOut{
    0%   {transform:scale(1)    translateY(0);     opacity:1;}
    100% {transform:scale(0.92) translateY(-120vh);opacity:0;}
}
#spinModal .spin-hero-wrap, #spinIntroModal .spin-hero-wrap{
    position:relative;
    width:100%;
    aspect-ratio:16/9;
    flex:0 0 auto;
    overflow:hidden;
    background:#0a0a1a;
}
#spinModal .spin-hero, #spinIntroModal .spin-hero{
    position:absolute;inset:0;
    background-size:cover;background-position:center;
    -webkit-mask-image:linear-gradient(to bottom,black 0%,black 72%,transparent 100%);
            mask-image:linear-gradient(to bottom,black 0%,black 72%,transparent 100%);
}
#spinModal .spin-hero-shade, #spinIntroModal .spin-hero-shade{
    position:absolute;inset:0;
    background:linear-gradient(to bottom,rgba(0,0,0,0) 55%,rgba(20,24,42,0.8) 92%,rgba(20,24,42,1) 100%);
}
#spinModal .spin-body, #spinIntroModal .spin-body{
    flex:1 1 auto;
    padding:20px 24px 24px;
    color:#fff;
    display:flex;flex-direction:column;gap:10px;
    min-height:0;
}
#spinModal .spin-name, #spinIntroModal .spin-name{
    font-size:28px;font-weight:700;line-height:1.1;
    text-shadow:0 2px 14px rgba(0,0,0,0.5);
}
@media(max-width:500px){
    #spinModal .spin-card, #spinIntroModal .spin-card{border-radius:16px;}
    #spinModal .spin-name, #spinIntroModal .spin-name{font-size:24px;}
}

/* ── Intro modal: body layout + CTA row ──
   Overrides that are specific to the intro dialog. The card/hero rules
   above already handle the shell; this block handles the two things the
   intro needs that the result modal doesn't: a roomier description and a
   centered pill CTA anchored to the bottom of the card. */
#spinIntroModal .spin-intro-desc{
    font-size:18px;line-height:1.35;font-weight:700;
    letter-spacing:0.2px;
    color:rgba(255,255,255,0.9);
    text-align:center;
    flex:0 0 auto;
}
#spinIntroModal .spin-intro-cta{
    margin-top:auto;
    padding-top:12px;
    display:flex;justify-content:center;
}
/* Inside the intro modal the pill sits inline in normal flow. The base
   .spin-globe-pill rule already drops the fixed-positioning that the
   previous standalone button used, so nothing else is needed here. */

/* ── Intro modal: title split into preamble + hero line ──
   The title carries the marketing hook, so "Play The Planet" is sized
   up and set bold to become the visual anchor of the card; the
   preamble ("GamifEYE Lets You") sits smaller and lighter above it.
   Using flex-column + gap means the two lines stay visually connected
   without relying on a <br> (easier to restyle per breakpoint). */
#spinIntroModal .spin-intro-name{
    display:flex;flex-direction:column;gap:2px;
    line-height:1.05;
    text-align:center;
}
#spinIntroModal .spin-intro-name-lead{
    font-size:18px;font-weight:700;
    color:rgba(255,255,255,0.82);
    letter-spacing:0.2px;
}
#spinIntroModal .spin-intro-name-hero{
    font-size:44px;font-weight:700;
    line-height:1.0;
    letter-spacing:-0.5px;
    color:#ff2bd6;
    text-shadow:0 2px 18px rgba(255,43,214,0.45),0 2px 16px rgba(0,0,0,0.55);
}
@media(max-width:500px){
    #spinIntroModal .spin-intro-name-lead{font-size:15px;}
    #spinIntroModal .spin-intro-name-hero{font-size:34px;}
    #spinIntroModal .spin-intro-desc{font-size:15px;}
}

/* ── Spin modals: hero corner pills (language + auth) ──
   Two pills live in the top corners of both the intro and result
   modals:
     - top-right: language chooser (.spin-intro-lang) — intro modal only,
       because the result modal's top-right is reserved for .spin-close.
     - top-left:  auth indicator (.spin-intro-auth) — on BOTH modals.
   They share a single glass-on-hero trigger skin so they read as a
   matched pair whenever both are visible. The language-switcher
   component ships its own dropdown menu styles; only the trigger is
   re-skinned here. The auth indicator is a plain <a> with the same
   .nav-lang-btn class so it inherits the trigger styles one-for-one. */
#spinIntroModal .spin-intro-lang,
#spinIntroModal .spin-intro-auth,
#spinModal .spin-intro-auth{
    position:absolute;
    top:12px;
    z-index:2;
}
#spinIntroModal .spin-intro-lang{right:12px;}
#spinIntroModal .spin-intro-auth,
#spinModal .spin-intro-auth{left:12px;}

/* Shared glass pill skin for BOTH corner triggers on BOTH modals.
   Matches the language chooser's look exactly — same background alpha,
   border, radius, typography, padding, and drop-shadow. */
#spinIntroModal .spin-intro-lang .nav-lang-btn,
#spinIntroModal .spin-intro-auth .nav-lang-btn,
#spinModal .spin-intro-auth .nav-lang-btn{
    background:rgba(10,14,30,0.55) !important;
    -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    border:1px solid rgba(255,255,255,0.22) !important;
    border-radius:999px !important;
    color:#fff !important;
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    padding:0.35rem 0.7rem !important;
    box-shadow:0 4px 14px rgba(0,0,0,0.35);
    text-decoration:none;
    line-height:1;
}
#spinIntroModal .spin-intro-lang .nav-lang-btn:hover,
#spinIntroModal .spin-intro-auth .nav-lang-btn:hover,
#spinModal .spin-intro-auth .nav-lang-btn:hover{
    background:rgba(10,14,30,0.75) !important;
    border-color:rgba(255,255,255,0.32) !important;
}
/* Auth pill icon sized to match the flag sprite in the language trigger
   (.nav-lang-flag is 1.1rem). Without this it would render at bootstrap's
   default text size, making the two pills look asymmetric. */
.spin-auth-icon{
    font-size:1.1rem;
    line-height:1;
    display:inline-flex;
    align-items:center;
}
/* Avatar variant of the leading slot. Occupies the same visual footprint
   as .spin-auth-icon (and the flag sprite in the language trigger) so
   swapping between logged-in/out states doesn't cause the pill to
   reflow. object-fit:cover handles non-square source images; the thin
   white ring mirrors the pill's own border so the avatar feels like it
   belongs to the same glass surface. */
.spin-auth-avatar{
    width:1.3rem;
    height:1.3rem;
    border-radius:50%;
    object-fit:cover;
    display:block;
    flex:0 0 auto;
    border:1px solid rgba(255,255,255,0.35);
    background:rgba(255,255,255,0.08);
}
/* When the avatar is itself a click target (authed user, inline
   change-avatar trigger) we set cursor + hover ring to signal that
   clicking it does something distinct from the surrounding pill,
   which opens the menu. The JS on [data-spin-avatar-change] calls
   stopPropagation so the two gestures don't collide. Scoped to
   #spinIntroModal because the result-modal pill's avatar has no
   bound handler (the avatar partial lives only in the intro body)
   and would otherwise show a misleading hover affordance. */
#spinIntroModal .spin-auth-avatar[data-spin-avatar-change]{
    cursor:pointer;
    transition:box-shadow 0.15s ease, border-color 0.15s ease;
}
#spinIntroModal .spin-auth-avatar[data-spin-avatar-change]:hover,
#spinIntroModal .spin-auth-avatar[data-spin-avatar-change]:focus-visible{
    border-color:rgba(255,43,214,0.9);
    box-shadow:0 0 0 2px rgba(255,43,214,0.35);
    outline:none;
}
@media(max-width:500px){
    .spin-auth-avatar{width:1.15rem;height:1.15rem;}
}

/* ── Auth dropdown trigger + menu ──
   Dropdown visuals mirror the language chooser's menu (see inline CSS
   in components/language-switcher.blade.php) so the two pills feel
   like a matched pair: same card background, radius, shadow, option
   hover behaviour, transition timing. Key differences:
     - Menu anchors to the LEFT (not right), because the auth pill sits
       in the top-left of the hero.
     - Option row has a leading icon slot (the language menu uses a
       flag sprite in that slot; we use bi-icons here).
     - The "danger" modifier (logout) tints red on hover. */
.spin-auth-dropdown{
    position:relative;
}
.spin-auth-chevron{
    font-size:0.7rem;
    line-height:1;
    margin-left:0.1rem;
    transition:transform 0.2s ease;
}
.spin-auth-dropdown.open .spin-auth-chevron{
    transform:rotate(180deg);
}
.spin-auth-menu{
    position:absolute;
    top:calc(100% + 0.5rem);
    left:0;
    min-width:180px;
    background:rgba(30,30,35,0.98);
    border:1px solid rgba(255,255,255,0.15);
    border-radius:10px;
    box-shadow:0 8px 32px rgba(0,0,0,0.4);
    padding:0.5rem;
    opacity:0;
    visibility:hidden;
    transform:translateY(-10px);
    transition:opacity 0.2s ease,transform 0.2s ease,visibility 0.2s ease;
    z-index:1000;
    font-family:'Fredoka',sans-serif;
}
.spin-auth-dropdown.open .spin-auth-menu{
    opacity:1;
    visibility:visible;
    transform:translateY(0);
}
.spin-auth-option{
    display:flex;
    align-items:center;
    gap:0.6rem;
    width:100%;
    padding:0.6rem 0.75rem;
    border-radius:6px;
    text-decoration:none;
    color:rgba(255,255,255,0.88);
    font-family:inherit;
    font-size:0.9rem;
    font-weight:500;
    text-align:left;
    line-height:1.2;
    /* Neutralise browser button defaults — this rule is applied to both
       <a> (authed items: Dashboard/Profile/Logout) and <button> (guest
       items: Login/Register, which swap the card body inline instead of
       navigating). Without these resets, <button> inherits a grey fill,
       outset border, and center-aligned system-font text. */
    background:transparent;
    border:none;
    cursor:pointer;
    appearance:none;
    -webkit-appearance:none;
    transition:background 0.15s ease,color 0.15s ease;
    white-space:nowrap;
}
.spin-auth-option:hover,
.spin-auth-option:focus{
    background:rgba(255,255,255,0.1);
    color:#fff;
    outline:none;
}
.spin-auth-option-danger:hover,
.spin-auth-option-danger:focus{
    background:rgba(255,43,214,0.18);
    color:#ff9be7;
}
.spin-auth-option-icon{
    font-size:1rem;
    line-height:1;
    width:1.1rem;
    text-align:center;
    opacity:0.85;
    flex:0 0 auto;
}
@media(max-width:500px){
    #spinIntroModal .spin-intro-lang,
    #spinIntroModal .spin-intro-auth,
    #spinModal .spin-intro-auth{top:10px;}
    #spinIntroModal .spin-intro-lang{right:10px;}
    #spinIntroModal .spin-intro-auth,
    #spinModal .spin-intro-auth{left:10px;}
    #spinIntroModal .spin-intro-lang .nav-lang-btn,
    #spinIntroModal .spin-intro-auth .nav-lang-btn,
    #spinModal .spin-intro-auth .nav-lang-btn{font-size:12px;padding:0.3rem 0.6rem !important;}
    .spin-auth-icon{font-size:1rem;}
}
#spinModal .spin-breadcrumb{
    font-size:10px;letter-spacing:2.5px;text-transform:uppercase;
    color:#00ffd5;font-weight:500;
}
/* .spin-name is styled by the shared #spinModal, #spinIntroModal block
   above; no per-modal override needed. */
#spinModal .spin-extract{
    font-size:13.5px;line-height:1.55;font-weight:400;
    color:rgba(255,255,255,0.88);
    flex:0 1 auto;min-height:0;
    overflow:hidden;
    display:-webkit-box;-webkit-line-clamp:12;-webkit-box-orient:vertical;
}
#spinModal .spin-actions{display:flex;flex-direction:column;gap:8px;margin-top:auto;padding-top:8px;}
#spinModal .spin-actions-row{display:flex;gap:10px;}
#spinModal .spin-btn{
    flex:1;padding:11px 14px;border-radius:12px;
    border:1px solid rgba(255,255,255,0.14);
    background:rgba(255,255,255,0.08);color:#fff;
    font-family:'Fredoka',sans-serif;font-weight:600;font-size:13px;letter-spacing:0.4px;
    cursor:pointer;transition:background .2s,border-color .2s,transform .15s;
}
#spinModal .spin-btn:hover{background:rgba(255,255,255,0.14);border-color:rgba(255,255,255,0.22);}
#spinModal .spin-btn:active{transform:scale(0.98);}
#spinModal .spin-btn-primary{
    background:rgba(0,255,213,0.18);
    border-color:rgba(0,255,213,0.6);
    color:#b9fff1;
}
#spinModal .spin-btn-primary:hover{background:rgba(0,255,213,0.3);border-color:rgba(0,255,213,0.85);color:#fff;}
#spinModal .spin-wiki-link{
    align-self:flex-start;
    font-size:10px;letter-spacing:1.5px;text-transform:uppercase;
    color:rgba(255,255,255,0.6);text-decoration:none;font-weight:500;
}
#spinModal .spin-wiki-link:hover{color:#00ffd5;}
#spinModal .spin-close{
    position:absolute;top:12px;right:12px;
    width:34px;height:34px;border-radius:50%;
    border:1px solid rgba(255,255,255,0.18);
    background:rgba(10,10,26,0.55);
    color:#fff;font-size:18px;line-height:1;cursor:pointer;
    display:flex;align-items:center;justify-content:center;
    -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
    transition:background .2s;
}
#spinModal .spin-close:hover{background:rgba(0,255,213,0.25);border-color:rgba(0,255,213,0.55);}
@media(max-width:500px){
    /* .spin-card border-radius and .spin-name font-size are handled by
       the shared media-query block above. Only the result-modal extract
       override lives here. */
    #spinModal .spin-extract{font-size:12.5px;-webkit-line-clamp:10;}
}

/* ── Minesweeper launch: bounce-off modal animation + accent button ── */
#spinModal .spin-btn-mine{
    background:rgba(233,30,140,0.15);
    border-color:rgba(233,30,140,0.5);
    color:#ffb3d6;
    display:inline-flex;align-items:center;justify-content:center;gap:6px;
}
#spinModal .spin-btn-mine:hover{background:rgba(233,30,140,0.28);border-color:rgba(233,30,140,0.85);color:#fff;}
#spinModal .spin-btn-dungeon{
    background:rgba(138,43,226,0.15);
    border-color:rgba(138,43,226,0.5);
    color:#d7b8ff;
    display:inline-flex;align-items:center;justify-content:center;gap:6px;
}
#spinModal .spin-btn-dungeon:hover{background:rgba(138,43,226,0.28);border-color:rgba(138,43,226,0.85);color:#fff;}
@keyframes spinFlyUp{
    0%{transform:scale(1) translateY(0);opacity:1;}
    18%{transform:scale(0.98) translateY(24px);opacity:1;}
    100%{transform:scale(0.92) translateY(-120vh);opacity:0;}
}
#spinModal.flying-up{pointer-events:none;}
#spinModal.flying-up .spin-card{
    animation:spinFlyUp .65s cubic-bezier(.4,0,.2,1) forwards;
    transition:none;
}

/* ── Territory-claimed "won" state: reuses the spin card as the
   congrats surface. The stamp slams onto the hero, the standard
   action row is hidden and only the "Enter your aura" button is
   shown. The hero edges pick up a cyan glow to signal victory. */
#spinModal .spin-claim-stamp{
    position:absolute;
    top:50%;left:50%;
    transform:translate(-50%,-50%) rotate(-14deg) scale(0.4);
    display:flex;flex-direction:column;align-items:center;gap:2px;
    padding:14px 28px;
    border:4px solid #00ffd5;
    border-radius:10px;
    color:#00ffd5;
    font-family:'JetBrains Mono',ui-monospace,monospace;
    text-transform:uppercase;letter-spacing:2px;
    background:rgba(10,14,30,0.35);
    -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
    box-shadow:0 0 18px rgba(0,255,213,0.45), inset 0 0 12px rgba(0,255,213,0.2);
    text-shadow:0 2px 10px rgba(0,0,0,0.6);
    opacity:0;pointer-events:none;
    white-space:nowrap;
    z-index:3;
}
#spinModal .spin-claim-stamp-top{font-size:10px;letter-spacing:4px;font-weight:500;opacity:0.8;}
#spinModal .spin-claim-stamp-main{font-size:30px;font-weight:800;line-height:1;letter-spacing:4px;}
#spinModal .spin-claim-stamp-bottom{font-size:9px;letter-spacing:3px;font-weight:500;opacity:0.75;}
#spinModal .spin-card.won .spin-claim-stamp{
    animation:spinStampSlam .55s cubic-bezier(.2,.9,.3,1.3) .35s forwards;
}
@keyframes spinStampSlam{
    0%  {opacity:0;transform:translate(-50%,-50%) rotate(-14deg) scale(2.4);}
    60% {opacity:1;transform:translate(-50%,-50%) rotate(-14deg) scale(0.92);}
    80% {opacity:1;transform:translate(-50%,-50%) rotate(-14deg) scale(1.05);}
    100%{opacity:1;transform:translate(-50%,-50%) rotate(-14deg) scale(1);}
}
#spinModal .spin-card.won{
    box-shadow:0 30px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(0,255,213,0.35), 0 0 60px rgba(0,255,213,0.22);
}
#spinModal .spin-card.won .spin-hero-wrap::after{
    content:'';position:absolute;inset:0;
    box-shadow:inset 0 0 60px rgba(0,255,213,0.25);
    pointer-events:none;
}
#spinModal .spin-card.won .spin-close,
#spinModal .spin-card.won .spin-actions-row{display:none !important;}
#spinModal .spin-btn-aura{
    display:none;
    flex:1;
    background:rgba(0,255,213,0.2);
    border-color:rgba(0,255,213,0.65);
    color:#c7fff3;
    gap:6px;align-items:center;justify-content:center;
    font-weight:700;letter-spacing:0.6px;
    box-shadow:0 0 20px rgba(0,255,213,0.25);
}
#spinModal .spin-btn-aura:hover{background:rgba(0,255,213,0.32);border-color:rgba(0,255,213,0.9);color:#fff;}
#spinModal .spin-card.won #spinModalEnterAura{display:inline-flex;}

/* ── Minesweeper HUD ── */
#msHud{
    position:fixed;top:14px;left:50%;transform:translateX(-50%);
    z-index:45;
    display:none;align-items:center;gap:14px;
    padding:8px 18px;border-radius:999px;
    background:rgba(10,14,30,0.7);
    -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
    border:1px solid rgba(233,30,140,0.35);
    color:#fff;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;
    box-shadow:0 10px 30px rgba(0,0,0,0.35);
    pointer-events:none;
}
#msHud.visible{display:inline-flex;}
#msHud .ms-hud-item{display:inline-flex;align-items:center;gap:4px;letter-spacing:0.5px;}
#msHud .ms-hud-icon{font-size:12px;}
#msHud #msHudMines{color:#ff5a5a;font-weight:700;}
#msHud #msHudTimer{color:#00ffd5;font-weight:700;}
#msHud #msHudRevealed{color:#ffa726;font-weight:700;}
#msHud #msHudDemos{color:rgba(255,255,255,0.6);font-weight:500;letter-spacing:1.2px;text-transform:uppercase;font-size:10px;padding-left:6px;border-left:1px solid rgba(255,255,255,0.14);margin-left:2px;}
@media(max-width:500px){
    #msHud{gap:10px;padding:6px 14px;font-size:11px;}
    #msHud #msHudDemos{display:none;}
}

/* ── Minesweeper game-over + auth modal (shared overlay styles) ── */
.ms-overlay{
    position:fixed;inset:0;z-index:50;
    display:none;align-items:center;justify-content:center;
    background:rgba(5,8,18,0.72);
    -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
}
.ms-overlay.visible{display:flex;}
.ms-box{
    background:rgba(20,24,42,0.96);
    border:1px solid rgba(255,255,255,0.1);
    border-radius:16px;
    padding:32px 36px;text-align:center;
    min-width:300px;max-width:calc(100vw - 40px);
    color:#fff;font-family:'Fredoka',sans-serif;
    box-shadow:0 30px 80px rgba(0,0,0,0.6);
}
.ms-box h2{
    margin:0 0 8px;font-size:26px;font-weight:700;
    font-family:'JetBrains Mono',ui-monospace,monospace;letter-spacing:1.5px;
}
.ms-box h2.win{color:#00ffd5;}
.ms-box h2.lose{color:#ff5a5a;}
.ms-box .ms-sub{font-size:14px;color:rgba(255,255,255,0.75);margin-bottom:20px;line-height:1.45;}
.ms-box .ms-btn-row{display:flex;gap:10px;justify-content:center;margin-top:16px;flex-wrap:wrap;}
.ms-btn{
    padding:10px 22px;border-radius:24px;
    font-family:'JetBrains Mono',ui-monospace,monospace;
    font-size:11px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;
    border:1px solid rgba(255,255,255,0.15);
    background:transparent;color:rgba(255,255,255,0.8);
    cursor:pointer;transition:all .2s;
    text-decoration:none;display:inline-flex;align-items:center;justify-content:center;
}
.ms-btn:hover{border-color:rgba(255,255,255,0.35);color:#fff;}
.ms-btn-primary{
    background:rgba(0,255,213,0.18);border-color:rgba(0,255,213,0.6);color:#b9fff1;
}
.ms-btn-primary:hover{background:rgba(0,255,213,0.32);border-color:rgba(0,255,213,0.9);color:#fff;}

/* Inline auth tabs + form (reused inside .spin-auth-panel — the
   retired #msAuthModal "You win!" green modal used to host these
   too, but its markup and id-scoped rules have been removed). */
.ms-auth-tabs{display:flex;gap:0;margin-bottom:18px;border-bottom:1px solid rgba(255,255,255,0.1);}
.ms-auth-tabs button{
    flex:1;padding:10px 0;
    font-family:'JetBrains Mono',ui-monospace,monospace;
    font-size:11px;letter-spacing:1.5px;text-transform:uppercase;
    background:none;border:none;border-bottom:2px solid transparent;
    color:rgba(255,255,255,0.55);cursor:pointer;transition:all .2s;
}
.ms-auth-tabs button.active{color:#00ffd5;border-bottom-color:#00ffd5;}
.ms-auth-form{display:none;}
.ms-auth-form.active{display:block;}
.ms-auth-form input{
    display:block;width:100%;box-sizing:border-box;
    margin-bottom:10px;padding:11px 14px;
    font-family:'Fredoka',sans-serif;font-size:14px;color:#fff;
    background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);
    border-radius:8px;outline:none;transition:border-color .2s;
}
.ms-auth-form input:focus{border-color:#00ffd5;}
.ms-auth-form input::placeholder{color:rgba(255,255,255,0.4);}
.ms-auth-error{color:#ff5a5a;font-size:12px;margin-bottom:10px;min-height:14px;}
.ms-auth-form button[type="submit"]{
    width:100%;margin-top:6px;padding:11px 18px;border-radius:10px;border:none;cursor:pointer;
    font-family:'JetBrains Mono',ui-monospace,monospace;
    font-size:12px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;
    background:#00ffd5;color:#0a0a1a;transition:transform .15s,filter .2s;
}
.ms-auth-form button[type="submit"]:hover{filter:brightness(1.1);}
.ms-auth-form button[type="submit"]:active{transform:scale(0.98);}

/* ── Inline auth panel (intro + spin-result modals) ──
   Rendered once per modal via public/partials/spin-auth-panel.blade.php.
   Hidden by default. When the pill's Login or Register item is clicked,
   JS adds .is-authpanel to the enclosing .spin-body; the rules below
   then hide every native sibling in that body and reveal the panel.

   The forms share structural (.ms-auth-tabs, .ms-auth-form, .ms-auth-
   error) classes with the Minesweeper win-gate so layout + show/hide
   behaviour stay in sync, but every visual property (accent colour,
   font family, spacing) is overridden below so the panel matches the
   magenta/Fredoka brand of the intro modal instead of the win-gate's
   teal/JetBrains-Mono arcade aesthetic. */
.spin-auth-panel{
    display:none;
    flex:1 1 auto;
    text-align:left;
    padding-top:8px;
    gap:14px;
}
/* When the body is in auth-panel mode, every direct child except the
   panel itself is removed from layout. Two body flavours share this
   rule: .spin-intro-body (intro modal) and the result modal's plain
   .spin-body — the selector is intentionally generic so both pick it
   up without per-modal CSS. */
#spinModal .spin-body.is-authpanel > *:not(.spin-auth-panel),
#spinIntroModal .spin-body.is-authpanel > *:not(.spin-auth-panel){
    display:none !important;
}
#spinModal .spin-body.is-authpanel > .spin-auth-panel,
#spinIntroModal .spin-body.is-authpanel > .spin-auth-panel{
    display:flex;flex-direction:column;
}

/* ── Panel-scoped overrides: magenta + Fredoka + more breathing room ──
   Each selector is scoped under .spin-auth-panel so the Minesweeper
   win-gate (which reuses the same .ms-auth-* classes but is themed
   arcade/teal on purpose) is unaffected. */

/* Tabs: wider gap from the card top, Fredoka, magenta active state. */
.spin-auth-panel .ms-auth-tabs{
    margin-top:6px;margin-bottom:22px;
    border-bottom:1px solid rgba(255,255,255,0.12);
}
.spin-auth-panel .ms-auth-tabs button{
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    letter-spacing:0.3px;
    text-transform:none;
    font-weight:500;
    padding:12px 0;
    color:rgba(255,255,255,0.55);
}
.spin-auth-panel .ms-auth-tabs button.active{
    color:#ff2bd6;
    border-bottom-color:#ff2bd6;
    font-weight:600;
}

/* Inputs: taller, Fredoka, magenta focus ring. */
.spin-auth-panel .ms-auth-form input{
    margin-bottom:14px;
    padding:13px 16px;
    font-family:'Fredoka',sans-serif;
    font-size:15px;
    border-radius:10px;
}
.spin-auth-panel .ms-auth-form input:focus{
    border-color:#ff2bd6;
    box-shadow:0 0 0 3px rgba(255,43,214,0.18);
}

/* Paired-input row: Login puts email + password together, Register
   puts password + confirm together. The row owns the vertical cadence
   so its children can drop their individual bottom margins, and
   min-width:0 stops the inputs from overflowing their flex cell on
   narrow cards (flex items default to min-content, which is wider than
   the column when placeholder text is long). Stacks again under 500px
   so the form stays usable on phones. */
.spin-auth-panel .ms-auth-form .spin-auth-row{
    display:flex;
    gap:10px;
    margin-bottom:14px;
}
.spin-auth-panel .ms-auth-form .spin-auth-row > input{
    flex:1 1 0;
    min-width:0;
    margin-bottom:0;
}
@media(max-width:500px){
    .spin-auth-panel .ms-auth-form .spin-auth-row{
        flex-direction:column;
        gap:0;
    }
    .spin-auth-panel .ms-auth-form .spin-auth-row > input{
        margin-bottom:14px;
    }
}

/* Errors: slightly bigger so they don't get lost under the taller
   inputs; Fredoka keeps the whole panel in one typographic voice. */
.spin-auth-panel .ms-auth-error{
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    margin-bottom:12px;
    min-height:16px;
}

/* Registration notice that replaces the password + confirm row. Sets
   the expectation that the server will email a password, in the same
   Fredoka/magenta voice as the rest of the panel. A thin magenta left
   rule makes it read as "informational" rather than a plain paragraph,
   and the soft glass background ties it to the rest of the card. */
.spin-auth-panel .ms-auth-form .spin-auth-panel-notice{
    margin:4px 0 16px;
    padding:12px 14px;
    border-radius:10px;
    border-left:3px solid #ff2bd6;
    background:rgba(255,43,214,0.08);
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    line-height:1.5;
    letter-spacing:0.1px;
    color:rgba(255,255,255,0.88);
}

/* Primary submit: magenta fill with a soft glow, Fredoka, more top
   margin so it doesn't crowd the last input. White text because
   magenta + near-black would be lower contrast than white on magenta. */
.spin-auth-panel .ms-auth-form button[type="submit"]{
    margin-top:10px;
    padding:13px 20px;
    border-radius:12px;
    font-family:'Fredoka',sans-serif;
    font-size:14px;
    letter-spacing:0.4px;
    text-transform:none;
    font-weight:600;
    background:#ff2bd6;
    color:#fff;
    box-shadow:0 6px 20px rgba(255,43,214,0.3);
}
.spin-auth-panel .ms-auth-form button[type="submit"]:hover{
    filter:brightness(1.08);
    box-shadow:0 8px 26px rgba(255,43,214,0.42);
}

/* Back: low-emphasis, Fredoka, roomier click target so it reads as
   navigation rather than a system button. */
.spin-auth-panel-back{
    align-self:center;
    margin-top:10px;
    padding:10px 16px;
    background:none;border:none;cursor:pointer;
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    letter-spacing:0.2px;
    font-weight:500;
    color:rgba(255,255,255,0.55);
    transition:color .15s ease;
}
.spin-auth-panel-back:hover{color:#fff;}
.spin-auth-panel-back > span:first-child{margin-right:6px;}

/* ── Win-mode heading + Spin Again button ──
   Hidden by default; revealed when JS adds .is-mswin to the panel
   root (see showWinAuthModal() in public/js/ping-landing.js). Used
   to give the post-minesweeper-win auth flow context — "Login Or
   Register / To Add [Place] To Your Territories" — and swap the
   subtle Back link for a prominent Spin Again CTA that matches the
   result modal's own primary button styling. */
.spin-auth-panel-winhead{display:none;}
.spin-auth-panel-winagain{display:none;}
.spin-auth-panel.is-mswin .spin-auth-panel-winhead{
    display:block;
    text-align:center;
    margin-top:2px;
    margin-bottom:10px;
}
.spin-auth-panel.is-mswin .spin-auth-panel-wintitle{
    font-family:'Fredoka',sans-serif;
    font-weight:600;
    font-size:17px;
    line-height:1.22;
    color:#ff2bd6;
    margin:0 0 4px;
    letter-spacing:0.2px;
}
.spin-auth-panel.is-mswin .spin-auth-panel-wintitle [data-spin-auth-winplace]{
    color:#fff;
}
.spin-auth-panel.is-mswin .spin-auth-panel-winsub{
    font-family:'Fredoka',sans-serif;
    font-size:12px;
    color:rgba(255,255,255,0.6);
    margin:0;
}
/* In win-mode, compact the inline form so the heading + tabs +
   fields + notice + submit + Spin Again all fit on a ~640px-tall
   body without clipping. Each rule here shaves a handful of pixels
   off the vertical rhythm the non-win panel uses. */
.spin-auth-panel.is-mswin .ms-auth-tabs{
    margin-top:2px;
    margin-bottom:14px;
}
.spin-auth-panel.is-mswin .ms-auth-form input{
    margin-bottom:10px;
}
/* The auto-generated-password notice is redundant in the win-gate
   (the user already has context from the heading and is mid-claim),
   and removing it also tightens the card vertically so the Spin
   Again CTA sits close to Create Account instead of separated by
   a full paragraph block. The notice stays in the pill's normal
   Login/Register flow where the explanation is still useful. */
.spin-auth-panel.is-mswin .ms-auth-form .spin-auth-panel-notice{
    display:none;
}
.spin-auth-panel.is-mswin .ms-auth-form button[type="submit"]{
    margin-top:4px;
}
/* Swap the subtle Back link for the primary Spin Again pill so the
   "walk away without signing up" affordance is first-class rather
   than a footer afterthought. Kept tight to the submit button above
   so the two CTAs read as a paired "finish claim / skip to next
   spin" row rather than two unrelated footer actions. */
.spin-auth-panel.is-mswin .spin-auth-panel-back{display:none;}
.spin-auth-panel.is-mswin .spin-auth-panel-winagain{
    display:block;
    align-self:stretch;
    width:100%;
    margin-top:4px;
}
@media(max-width:500px){
    .spin-auth-panel.is-mswin .spin-auth-panel-wintitle{font-size:16px;}
    .spin-auth-panel.is-mswin .spin-auth-panel-winsub{font-size:11px;}
}

/* ── Inline onboarding prompts (intro modal only) ──
   Two partials can render inside #spinIntroModal .spin-body.spin-intro-body:
     - public/partials/spin-avatar-prompt.blade.php   (always for @auth)
     - public/partials/spin-verify-prompt.blade.php   (unverified or post-verify)
   Both can be in the DOM simultaneously (authed user with an avatar
   who hasn't verified: avatar partial is dormant, verify partial is
   active), so we use two independent body modifiers — one per active
   prompt — rather than a shared one. JS coordinates which is live:
     - .is-avatarprompt → avatar partial takes over the .spin-body
     - .is-verifyprompt → verify partial takes over the .spin-body
   Clicking the avatar image in the pill swaps verify→avatar; Cancel
   on the avatar swaps it back so the verify prompt reappears if the
   user still needs to verify. */
#spinIntroModal .spin-body.is-avatarprompt > *:not(.spin-avatar-prompt){
    display:none !important;
}
#spinIntroModal .spin-body.is-avatarprompt > .spin-avatar-prompt{
    display:flex;
    flex-direction:column;
}
#spinIntroModal .spin-body.is-verifyprompt > *:not(.spin-verify-prompt){
    display:none !important;
}
#spinIntroModal .spin-body.is-verifyprompt > .spin-verify-prompt{
    display:flex;
    flex-direction:column;
}

.spin-avatar-prompt{
    display:none;
    flex:1 1 auto;
    gap:14px;
    padding-top:4px;
    text-align:center;
    font-family:'Fredoka',sans-serif;
}

/* Two-state visibility inside the prompt. Default state shows the
   empty tile; adding .is-cropping from JS flips to the crop stage +
   Apply/Cancel row. We use a class modifier rather than the HTML5
   [hidden] attribute because .spin-avatar-tile and .spin-avatar-crop
   set explicit display values below, which would otherwise win the
   specificity fight and keep all three elements visible at once. */
.spin-avatar-prompt:not(.is-cropping) > .spin-avatar-crop,
.spin-avatar-prompt:not(.is-cropping) > .spin-avatar-actions{
    display:none;
}
.spin-avatar-prompt.is-cropping > .spin-avatar-tile,
.spin-avatar-prompt.is-cropping > .spin-avatar-dismiss-row{
    display:none;
}

/* Opt-in Cancel row. Only rendered when the user already has an avatar
   (server-side gate in the partial); visible only in the empty-state
   tile view — once the user has picked an image, the Apply/Cancel row
   takes over that role, so this row hides to avoid duplication. */
.spin-avatar-dismiss-row{
    display:flex;
    justify-content:center;
    margin-top:4px;
}
.spin-avatar-dismiss-row .spin-avatar-btn{
    min-width:160px;
}

/* Title + description. Title matches the visual weight of the auth
   panel tabs' active state; description is the same quiet grey as
   the auth-panel placeholder/back text. */
.spin-avatar-prompt__head{margin:0 0 4px;}
.spin-avatar-prompt__title{
    margin:0 0 6px;
    font-family:'Fredoka',sans-serif;
    font-size:17px;
    font-weight:600;
    letter-spacing:0.2px;
    color:#ff2bd6;
}
.spin-avatar-prompt__desc{
    margin:0;
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    line-height:1.5;
    color:rgba(255,255,255,0.7);
}

/* Clickable square. Uses aspect-ratio for a perfect square at any
   width, capped so very short viewports don't push the Apply button
   off-screen. min() keeps it responsive without a media query for
   small screens. Dashed magenta border reads as "drop zone" even
   though we only support click-to-pick (keeps the visual language
   familiar without over-promising drag-and-drop). */
.spin-avatar-tile{
    appearance:none;
    display:block;
    width:min(260px, 60%);
    aspect-ratio:1 / 1;
    margin:4px auto 0;
    padding:0;
    background:rgba(255,43,214,0.06);
    border:2px dashed rgba(255,43,214,0.45);
    border-radius:18px;
    cursor:pointer;
    font-family:inherit;
    color:inherit;
    transition:background .15s ease, border-color .15s ease, transform .15s ease;
}
.spin-avatar-tile:hover{
    background:rgba(255,43,214,0.12);
    border-color:rgba(255,43,214,0.75);
    transform:translateY(-1px);
}
.spin-avatar-tile:focus-visible{
    outline:none;
    border-color:#ff2bd6;
    box-shadow:0 0 0 3px rgba(255,43,214,0.25);
}
.spin-avatar-tile__inner{
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:10px;
    width:100%;height:100%;
}
.spin-avatar-tile__icon{
    font-size:44px;
    color:#ff2bd6;
    line-height:1;
}
.spin-avatar-tile__hint{
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    font-weight:500;
    letter-spacing:0.3px;
    color:rgba(255,255,255,0.7);
    padding:0 12px;
}

/* Crop stage: shares the same footprint as the tile so the two
   states feel like one surface transforming. Cropper.js mounts an
   <img> inside .__mount; we constrain that img so the cropper's
   own layout still fills the square cleanly. */
.spin-avatar-crop{
    display:block;
    width:min(260px, 60%);
    aspect-ratio:1 / 1;
    margin:4px auto 0;
    background:rgba(10,14,30,0.55);
    border:1px solid rgba(255,43,214,0.35);
    border-radius:18px;
    overflow:hidden;
}
.spin-avatar-crop__mount{
    width:100%;height:100%;
    display:block;
}
.spin-avatar-crop__mount img{
    display:block;
    max-width:100%;
    max-height:100%;
}

/* Error: Fredoka + a subtle red tint so it reads as "something went
   wrong" without shouting. min-height reserves layout space so a new
   error doesn't jump the Apply button around. */
.spin-avatar-error{
    min-height:16px;
    margin:2px 0 0;
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    color:#ff7a9a;
}
.spin-avatar-error:empty{display:none;}

/* Action row: Apply (magenta fill, mirrors .spin-auth-panel submit)
   and Cancel (ghost, muted). Stacked-on-mobile avoided by default —
   both buttons are short enough to sit on one row down to ~340px. */
.spin-avatar-actions{
    display:flex;
    gap:10px;
    justify-content:center;
    align-items:center;
    margin-top:6px;
    flex-wrap:wrap;
}
.spin-avatar-btn{
    appearance:none;
    padding:12px 20px;
    border-radius:12px;
    font-family:'Fredoka',sans-serif;
    font-size:14px;
    font-weight:600;
    letter-spacing:0.3px;
    cursor:pointer;
    transition:filter .15s ease, box-shadow .15s ease, background .15s ease, color .15s ease;
}
.spin-avatar-btn:disabled{opacity:0.55;cursor:progress;}
.spin-avatar-btn--primary{
    background:#ff2bd6;
    color:#fff;
    border:1px solid #ff2bd6;
    box-shadow:0 6px 20px rgba(255,43,214,0.3);
}
.spin-avatar-btn--primary:hover:not(:disabled){
    filter:brightness(1.08);
    box-shadow:0 8px 26px rgba(255,43,214,0.42);
}
.spin-avatar-btn--ghost{
    background:transparent;
    color:rgba(255,255,255,0.7);
    border:1px solid rgba(255,255,255,0.2);
}
.spin-avatar-btn--ghost:hover:not(:disabled){
    color:#fff;
    border-color:rgba(255,255,255,0.4);
    background:rgba(255,255,255,0.05);
}

/* Shrink the tile a touch on narrow cards so title + tile + buttons
   comfortably fit in a single scroll-free view. */
@media(max-width:500px){
    .spin-avatar-tile,
    .spin-avatar-crop{
        width:min(220px, 72%);
    }
    .spin-avatar-tile__icon{font-size:38px;}
}

/* ── Inline email-verify prompt (intro modal only) ──
   Shares the .is-avatarprompt body modifier with the avatar-upload
   prompt (see rules higher up). The two partials are mutually
   exclusive server-side. Copy-styles mirror the auth/avatar panels
   (Fredoka, generous whitespace, centered) while the button itself
   mirrors the dashboard's .dash-map-verify-badge state machine so the
   verify UX feels identical across surfaces. */
.spin-verify-prompt{
    display:none;
    flex:1 1 auto;
    gap:18px;
    padding-top:4px;
    text-align:center;
    align-items:center;
    justify-content:flex-start;
    font-family:'Fredoka',sans-serif;
}
.spin-verify-prompt__head{margin:0 0 4px;}
.spin-verify-prompt__title{
    margin:0 0 8px;
    font-family:'Fredoka',sans-serif;
    font-weight:600;
    font-size:22px;
    line-height:1.2;
    color:#ff2bd6;
    letter-spacing:0.01em;
}
.spin-verify-prompt__desc{
    margin:0;
    font-family:'Fredoka',sans-serif;
    font-weight:400;
    font-size:14px;
    line-height:1.45;
    color:rgba(255,255,255,0.72);
    max-width:360px;
}

/* The button IS the UI — no secondary actions, mirroring the
   dashboard's badge. Colors/keyframes match .dash-map-verify-badge
   exactly; only the scale is dialed up for modal prominence. */
.spin-verify-btn{
    appearance:none;
    -webkit-appearance:none;
    background:transparent;
    border:1.5px solid transparent;
    cursor:pointer;
    font-family:'Fredoka',sans-serif;
    font-weight:600;
    font-size:15px;
    line-height:1.2;
    letter-spacing:0.04em;
    text-transform:uppercase;
    padding:0.85rem 1.4rem;
    border-radius:12px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    gap:0.55rem;
    transition:background .2s ease, color .2s ease, border-color .2s ease, opacity .2s ease;
    min-width:min(280px, 90%);
    max-width:100%;
}
.spin-verify-btn i{font-size:0.95em;}
.spin-verify-btn:disabled,
.spin-verify-btn[aria-disabled="true"]{cursor:default;}

/* Red pulsing — initial unverified state, matches dashboard. */
.spin-verify-btn.unverified{
    background:rgba(239,68,68,0.25);
    color:#f87171;
    border-color:rgba(248,113,113,0.45);
    animation:spin-verify-pulse 2s ease-in-out infinite;
}
.spin-verify-btn.unverified:hover{
    background:rgba(239,68,68,0.4);
    color:#fca5a5;
}

/* Orange — email sent, awaiting user to click the link. */
.spin-verify-btn.verify-pending{
    background:rgba(245,158,11,0.3);
    color:#fbbf24;
    border-color:rgba(251,191,36,0.45);
}
.spin-verify-btn.verify-pending:not(:disabled):hover{
    background:rgba(245,158,11,0.45);
    color:#fde68a;
}

/* Green — verified. Two rendering paths:
     1. Blade renders a non-interactive <span class="spin-verify-btn verified">
        in the post-verify success state (see spin-verify-prompt.blade.php).
     2. JS flips the button to this class on the rare already-verified
        edge case (other tab verified first) before reloading.
   Neither should look clickable, so cursor is reset. The success pill
   sizes to content (dropping the generous call-to-action min-width) so
   the "Verified" confirmation doesn't look absurdly wide next to a
   short countdown string. */
.spin-verify-btn.verified{
    background:rgba(22,163,74,0.28);
    color:#4ade80;
    border-color:rgba(74,222,128,0.45);
    animation:none;
    cursor:default;
    min-width:0;
    width:auto;
    padding:0.65rem 1.15rem;
    font-size:14px;
}

/* Transient states driven by JS during AJAX. */
.spin-verify-btn.sending{
    opacity:0.72;
    pointer-events:none;
    animation:none;
}
.spin-verify-btn.sent{
    background:rgba(22,163,74,0.22);
    color:#4ade80;
    border-color:rgba(74,222,128,0.4);
    animation:none;
}
.spin-verify-btn.failed{
    background:rgba(239,68,68,0.28);
    color:#fca5a5;
    border-color:rgba(248,113,113,0.55);
    animation:none;
}

@keyframes spin-verify-pulse{
    0%,100%{opacity:1;transform:scale(1);}
    50%{opacity:0.72;transform:scale(1.03);}
}

.spin-verify-error{
    font-family:'Fredoka',sans-serif;
    font-size:13px;
    color:#fca5a5;
    min-height:18px;
    margin-top:-4px;
}

@media(max-width:500px){
    .spin-verify-prompt__title{font-size:19px;}
    .spin-verify-prompt__desc{font-size:13px;}
    .spin-verify-btn{
        font-size:13px;
        padding:0.75rem 1.1rem;
        min-width:0;
        width:100%;
    }
}

/* Transient status toast (demos remaining, errors) */
#msToast{
    position:fixed;left:50%;bottom:38px;transform:translate(-50%,20px);
    z-index:60;
    padding:10px 22px;border-radius:999px;
    background:rgba(10,14,30,0.85);
    -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    border:1px solid rgba(0,255,213,0.4);
    color:#fff;font-family:'Fredoka',sans-serif;font-size:13px;letter-spacing:0.5px;
    opacity:0;pointer-events:none;
    transition:opacity .25s ease,transform .35s cubic-bezier(.22,1,.36,1);
    box-shadow:0 10px 30px rgba(0,0,0,0.4);
}
#msToast.visible{opacity:1;transform:translate(-50%,0);}

/* Small inline spinner used by lazy-load UI (Dungeon button while
   fetching ~110 KB gz of engine, etc.). Colours track the current
   text colour, so it looks right on any button background. */
.ms-dot-spinner{display:inline-block;width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;vertical-align:middle;margin-right:4px;animation:ms-dot-spin 0.8s linear infinite}
@keyframes ms-dot-spin{to{transform:rotate(360deg)}}
