/* ---------------------------------------------------------------------------
   profile-fx.css — the optional "page fx" layer on bio pages.

   Five independent, per-name opt-ins (all premium, all stored on the bio's
   jsonb config):
     1. tap-to-enter gate  (#fx-gate)       — blurred door you tap to open
     2. cursor trail       (body.fx-trail-*) — sparkle/bubble pointer trail
     3. custom cursor      (--fx-cursor)     — your own pointer sprite
     4. badge tint         (--fx-badge-tint) — recolour the badge icons
     5. custom badge       (--fx-badge-icon) — swap in your own badge art
   Plus the site-wide "want your own?" CTA pill (body has .fx-cta).

   Everything here degrades cleanly: no JS and the gate never renders, the
   trail/tint do nothing on their own, and prefers-reduced-motion stills all
   motion. Tokens read the theme's --p-* palette so fx colour-match the skin.
   --------------------------------------------------------------------------- */

/* ---------- 1. tap-to-enter gate ---------- */

/* The page underneath is dimmed + blurred; the gate is a full-bleed veil with
   the "tap anywhere" affordance. Dismissed by ui-4.js (profile-fx wiring).

   This selector is the *element*, and it must never be given to the body. The
   body carries `fx-gated` (note the -d) as its state flag. When it carried
   `fx-gate` by mistake, the whole body became this rule: fixed, z-index max,
   translucent, and — because a backdrop-filter is on the element — the
   containing block for all fixed descendants. The fixed cursor-trail canvas
   is one, so the whole trail rendered offset from the pointer. Keep the two
   names apart. */
.fx-gate {
    position: fixed;
    inset: 0;
    /* Above the trail canvas (99998) and every page layer (the editor tops out
       around 970); still below the global loader at 2147483000. The old
       2147482000 shared the trail's near-int-max range, where compositors can
       lose paint order between the veil, its backdrop-filter and the canvas. */
    z-index: 99999;
    display: grid;
    place-items: center;
    background: rgba(4, 2, 2, .55);
    backdrop-filter: blur(26px) saturate(1.2);
    -webkit-backdrop-filter: blur(26px) saturate(1.2);
    cursor: pointer;
    animation: fx-gate-in .5s ease both;
}

@keyframes fx-gate-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.fx-gate-ring {
    position: absolute;
    width: min(46vmin, 340px);
    height: min(46vmin, 340px);
    border-radius: 50%;
    border: 1px solid var(--p-line-accent, rgba(255, 255, 255, .2));
    animation: fx-gate-pulse 2.6s ease-in-out infinite;
}

@keyframes fx-gate-pulse {
    0%, 100% { transform: scale(.92); opacity: .5; }
    50%      { transform: scale(1.06); opacity: 1; }
}

.fx-gate-mark {
    display: grid;
    place-items: center;
    gap: 1.1rem;
    text-align: center;
}

.fx-gate-logo {
    width: clamp(74px, 16vmin, 116px);
    height: clamp(74px, 16vmin, 116px);
    object-fit: contain;
    filter: drop-shadow(0 0 26px var(--p-accent-glow, rgba(196, 30, 30, .5)));
    animation: fx-gate-breathe 1.9s ease-in-out infinite;
}

@keyframes fx-gate-breathe {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.07); }
}

/* A badge taking the logo's spot. Badge art is square and usually larger
   than the favicon, so it gets its own, roomier frame. */
.fx-gate-badge {
    width: clamp(84px, 18vmin, 132px);
    height: clamp(84px, 18vmin, 132px);
    object-fit: contain;
    filter: drop-shadow(0 0 30px var(--p-accent-glow, rgba(196, 30, 30, .5)));
    animation: fx-gate-breathe 1.9s ease-in-out infinite;
}

.fx-gate-hint {
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .34em;
    text-transform: uppercase;
    color: var(--p-ink);
    padding-left: .34em;
    opacity: .8;
    animation: fx-gate-blink 1.9s ease-in-out infinite;
}

@keyframes fx-gate-blink {
    0%, 100% { opacity: .35; }
    50%      { opacity: .95; }
}

.fx-gate-owner {
    font-size: .7rem;
    color: var(--p-muted);
    letter-spacing: .04em;
}

/* Dismissal: JS adds .is-opening, then removes the node. The veil zooms
   through while the page beneath un-blurs, the ring blooms out, and the mark
   lifts away — the "walk through the door" read rather than a plain fade. */
.fx-gate.is-opening {
    animation: fx-gate-out .7s cubic-bezier(.2, .7, .2, 1) both;
    pointer-events: none;
}

.fx-gate.is-opening .fx-gate-ring {
    animation: fx-gate-ring-bloom .7s cubic-bezier(.2, .7, .2, 1) both;
}

.fx-gate.is-opening .fx-gate-mark {
    animation: fx-gate-mark-lift .7s cubic-bezier(.2, .7, .2, 1) both;
}

@keyframes fx-gate-out {
    from {
        opacity: 1;
        transform: scale(1);
        backdrop-filter: blur(26px) saturate(1.2);
    }
    to {
        opacity: 0;
        transform: scale(1.16);
        backdrop-filter: blur(0) saturate(1);
    }
}

@keyframes fx-gate-ring-bloom {
    from { transform: scale(.92); opacity: .9; }
    to   { transform: scale(2); opacity: 0; }
}

@keyframes fx-gate-mark-lift {
    from { transform: translateY(0) scale(1); opacity: 1; }
    to   { transform: translateY(-26px) scale(1.08); opacity: 0; }
}

/* While the gate is up, the real page sits slightly recessed. */
/* The page sits slightly recessed while the veil is up. Scoped with :has() on
   the veil itself rather than a body class, because the class outlives the
   dismissal and a permanently transformed .bb-main breaks the editor's
   100dvh sizing. Never target .profile-page: a transform there becomes the
   containing block for the owner's fixed Edit pill (same trap as the ui-4
   page animation). */
body:has(> #fx-gate) .bb-main {
    transform: scale(.985);
    transition: transform .55s cubic-bezier(.2, .7, .2, 1);
}

/* The editor outranks the entrance veil: it sits above it and owns the
   viewport while open, so the veil must not paint over (or eat clicks on)
   the owner's controls. Owners still see it in view mode. */
body.dash-on .fx-gate {
    display: none;
}

/* ---------- 2. cursor trail ---------- */

/* A fixed canvas sits above the page (below the loader) and paints the trail.
   Only exists when a trail style is on, so no idle cost. */
.fx-trail {
    position: fixed;
    inset: 0;
    /* The JS sizes the backing store to innerWidth*innerHeight*dpr, but a
       <canvas> with no CSS width/height lays itself out at its intrinsic size
       — the backing store — so on any display with dpr > 1 the canvas box was
       1280*2 CSS px wide on a 1280px viewport. Every particle was then drawn
       at twice its true position (exactly 2x at dpr 2, 1.25x at dpr 1.25),
       which is the "trail is offset from the cursor" bug on HiDPI screens.
       Pinning the box to the viewport here makes the dpr-scaled backing store
       map 1:1 again. */
    width: 100%;
    height: 100%;
    z-index: 99998;
    pointer-events: none;
    /* No `will-change`/`transform` here on purpose. Both force Firefox to give
       the canvas its own compositor layer, and a forced layer is what made the
       stale-frame bug (mozilla 1959815) visible: the canvas kept showing its
       last frame until a resize/DevTools rebuilt the layer. The JS clears under
       the identity transform now, and letting the engine layerise normally
       repaints correctly. */
}

/* While the enter-gate veil is up, its full-screen backdrop-filter: blur(26px)
   throttles this fixed canvas to a handful of frames a second; the trail then
   barely decays and smears where the cursor used to be. The gate sits above the
   canvas (higher z-index) so the trail is hidden anyway; the JS skips emission
   and clears the canvas the moment the gate opens, so it never smears. */

/* A trail sits *beside* the pointer; the native cursor stays visible. Only a
   custom sprite replaces it, because then the sprite *is* the pointer. */
body.fx-cursor-icon,
body.fx-cursor-icon * {
    cursor: var(--fx-cursor) var(--fx-cursor-x, 12) var(--fx-cursor-y, 12), auto;
}

/* ---------- 3. per-badge colour + custom art ---------- */

/* Recolouring is per badge, applied inline on that one <img> (app.py derives
   the hue/sat from the chosen hex), so the Premium badge can go gold while
   Verified stays default. Custom art replaces the image, so it isn't tinted. */
.badge-icon[data-fx-colour] {
    filter: var(--fx-badge-filter, none);
    transition: filter .18s ease;
}

/* Badge hover lift, so a recoloured badge still reads as interactive. */
.profile-badges .badge-icon:hover {
    filter: var(--fx-badge-filter, none) brightness(1.15) drop-shadow(0 3px 12px var(--p-accent-glow, rgba(196, 30, 30, .5)));
    transform: translateY(-1px);
}

.profile-badges .badge-icon {
    transition: filter .18s ease, transform .18s ease;
}

/* ---------- 5. the "want your own?" CTA ---------- */

/* Bottom-centre floating pill on every bio page. Mirrors the owner edit pill
   (bottom-right) so the two never collide. */
.fx-cta {
    position: fixed;
    left: 50%;
    bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    z-index: 960;
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .95rem;
    border-radius: 999px;
    border: 1px solid var(--p-line-accent, rgba(255, 255, 255, .18));
    background: var(--p-btn-bg, rgba(255, 255, 255, .06));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    box-shadow: 0 12px 30px -14px rgba(0, 0, 0, .85);
    color: var(--p-ink);
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .01em;
    text-decoration: none;
    white-space: nowrap;
    transition: transform .18s cubic-bezier(.2, .7, .2, 1), border-color .18s ease;
}

.fx-cta:hover {
    transform: translateX(-50%) translateY(-2px);
    border-color: var(--p-accent, var(--blood-bright));
}

.fx-cta-slash {
    color: var(--p-muted);
    font-weight: 500;
}

.fx-cta-name {
    font-weight: 800;
    letter-spacing: -.01em;
}

.fx-cta-cta {
    color: var(--p-accent, var(--blood-bright));
    font-weight: 700;
}

.fx-cta-logo {
    width: 15px;
    height: 15px;
    object-fit: contain;
}

/* Owners already have the editor — no upsell pill for them, and none while
   the dashboard is open. */
body.owner-view .fx-cta,
body.dash-on .fx-cta {
    display: none;
}

/* ---------- accessibility ---------- */

@media (prefers-reduced-motion: reduce) {
    .fx-gate-ring,
    .fx-gate-logo,
    .fx-gate-badge,
    .fx-gate-hint {
        animation: none;
    }
    .fx-gate,
    .fx-gate.is-opening,
    .fx-gate.is-opening .fx-gate-ring,
    .fx-gate.is-opening .fx-gate-mark {
        animation: none;
    }
    body:has(> #fx-gate) .bb-main {
        transform: none;
        transition: none;
    }
    .fx-cta {
        transition: none;
    }
}

/* Touch devices have no hover cursor — the CTA pill would just sit over the
   owner's edit pill on small screens, so tuck it above the safe area only
   when there is room and drop the hover-lift dependency. */
@media (max-width: 560px) {
    .fx-cta {
        font-size: .7rem;
        padding: .42rem .8rem;
    }
}
