/* ============================================
   BLIPBLURB — FRONT END STYLES
   ============================================ */

.blip {
    position: relative;
    overflow: hidden;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    min-height: var(--blip-height, 40px);
    font-size: var(--blip-font-size, 14px);
}

/* A modest, unconditional reduction at mobile widths — applies
   regardless of Lite/Pro, same as Floating's own auto-halving below.
   Height and font-size both still respect whatever value the site set
   (via the custom properties above), this just trims each down a bit
   rather than rendering the exact desktop size on a small screen.
   Deliberately mild: shrinking font-size too far would undermine the
   whole point of an announcement bar's legibility.

   --blip-height-mobile / --blip-font-size-mobile (Pro-only, set inline
   only when that field's Mobile value isn't left blank) take priority
   as var()'s first argument — a blank Mobile field means the custom
   property is never output at all, not set to an empty value, so it
   falls through cleanly to this auto-reduction as the fallback rather
   than to the bare desktop value. */
@media screen and (max-width: 600px) {
    .blip {
        min-height: var(--blip-height-mobile, calc(var(--blip-height, 40px) * 0.82));
        font-size: var(--blip-font-size-mobile, calc(var(--blip-font-size, 14px) * 0.9));
    }
}

.blip[data-sticky="1"] {
    position: fixed;
    left: 0;
    right: 0;
    z-index: 999999;
    /* iOS Safari/Edge (both WebKit) compute a fixed element's position
       against the layout viewport, but prioritize smooth scrolling
       over keeping that viewport in sync while the address bar
       collapses/expands mid-scroll — the well-documented cause of a
       fixed bar visibly jumping a fixed amount at the first scroll of
       a session. Promoting the bar to its own GPU compositing layer
       is the standard mitigation: it stops the browser from
       recomputing the bar's position relative to the ambiguous
       transitioning viewport each frame. Not a full guarantee on
       every iOS version — this is a genuinely long-standing WebKit
       quirk — but it's the documented fix and doesn't cost anything
       on browsers that don't need it. */
    transform: translate3d(0, 0, 0);
    -webkit-transform: translate3d(0, 0, 0);
}

/* --blip-extra-offset (set inline per the Extra Offset setting,
   Bar Settings) is the manual escape hatch for a theme's own sticky
   header — there's no generic way to detect one automatically, since
   every theme implements it differently (or not at all). Combined via
   calc(), not overridden, so it still applies on top of the admin-bar
   offset below when both are relevant at once. */
.blip[data-sticky="1"][data-position="top"] {
    top: calc(var(--blip-extra-offset, 0px));
}

.blip[data-sticky="1"][data-position="bottom"] {
    bottom: calc(var(--blip-extra-offset, 0px));
}

/* WordPress's own admin toolbar (#wpadminbar) is fixed at the very top
   for logged-in visitors, and WP itself adds body.admin-bar with a
   height it controls (32px desktop, 46px at WP's own 782px mobile
   breakpoint — matched here, not this file's unrelated 600px one
   below). #wpadminbar's z-index (99999) is one order of magnitude
   below this bar's (999999), so without this a Sticky, top-positioned
   bar renders on top of it instead of below it. Only applies to
   top-positioned bars — the admin bar is always top-pinned regardless
   of this setting. blip-rotator.js's space-reservation math measures
   the bar's actual rendered position, so it adapts to this offset with
   no JS changes needed. */
body.admin-bar .blip[data-sticky="1"][data-position="top"] {
    top: calc(32px + var(--blip-extra-offset, 0px));
}

@media screen and (max-width: 782px) {
    body.admin-bar .blip[data-sticky="1"][data-position="top"] {
        top: calc(46px + var(--blip-extra-offset, 0px));
    }
}

/* Floating: inset from every side by the same distance, so it reads as
   a card detached from the page rather than one glued flush against
   whatever content it happens to sit next to. Works whether Sticky is
   on (margin still applies normally to a fixed element's offsets) or
   off (normal-flow spacing) — a site that wants the bar closer to its
   own header/footer than this can just adjust that element's own
   padding/margin instead of this being a separate setting. The
   distance itself comes from the --blip-floating-distance custom
   property (set inline per the Distance setting); the fallback here
   only matters if that's somehow missing. */
.blip[data-floating="1"] {
    margin: var(--blip-floating-distance, 25px);
}

/* --blip-floating-distance-mobile (Pro-only, set inline only when
   that field's Mobile value isn't left blank) takes priority as
   var()'s first argument, same fallback-chaining pattern as height/
   font-size-mobile above — a blank Mobile field falls through to the
   existing auto-halving rather than to the bare desktop distance. */
@media screen and (max-width: 600px) {
    .blip[data-floating="1"] {
        margin: var(--blip-floating-distance-mobile, calc(var(--blip-floating-distance, 25px) / 2));
    }
}

/* The admin preview renders the same markup/attributes as the live bar —
   never let Sticky pin it to the viewport inside wp-admin. Higher
   specificity than the rules above so it always wins regardless of
   data-position. */
#blip-preview-wrapper .blip[data-sticky="1"] {
    position: relative;
    top: auto;
    bottom: auto;
    left: auto;
    right: auto;
    z-index: auto;
}

/* Floating's edge-inset spacing doesn't mean anything inside the small
   preview box — suppress it the same way Sticky's fixed positioning is
   suppressed above. Corner Radius (set inline, not here) still shows,
   since that's a purely visual effect that previews fine regardless. */
#blip-preview-wrapper .blip[data-floating="1"] {
    margin: 0;
}

/* --------------------------------------------
   MESSAGE LAYOUT
   -------------------------------------------- */

/*
 * All messages share the same grid cell (grid stacks them, like the old
 * position:absolute approach, but sizes the row to the TALLEST of them —
 * including ones not currently shown). That way a blip that wraps to two
 * lines (e.g. on mobile) grows the bar for every blip, instead of the bar
 * jumping in height each time rotation lands on a taller one.
 */
.blip-message {
    grid-column: 1;
    grid-row: 1;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    text-align: center;
    /* All messages stack in the same grid cell (for equal-height
       rotation) — border-box keeps a per-blip Border from growing
       that cell's size relative to messages without one. */
    box-sizing: border-box;
    /* ::before (the background fill) has no border-radius of its own —
       without this, it stays square-cornered and pokes past a rounded
       per-blip Border at the corner instead of matching its curve. */
    overflow: hidden;
}

.blip-message.is-active {
    z-index: 2;
}

/*
 * Both the incoming and outgoing message stay visible (opacity:1) during a
 * transition — otherwise the outgoing box disappears the instant it stops
 * being .is-active (the base rule's opacity:0 has no transition to animate
 * it), which exposes the page background instead of the outgoing blip's
 * own background/text mid-animation.
 */
.blip-message.is-active,
.blip-message.is-exiting {
    opacity: 1;
}

.blip-message::before {
    content: "";
    position: absolute;
    inset: 0;
    background-color: var(--blip-bg, #222222);
    z-index: 0;
    opacity: 1;
    /* Blur lives on the shared .blip::before base layer instead (see
       above) — this is just each blip's own color wash on top of that
       already-blurred base, so it needs no backdrop-filter of its own.
       Still needs its own radius: the parent's overflow:hidden clips a
       plain background fine on its own, but doesn't retroactively round
       this layer's own corners for anything checking its shape (e.g. a
       per-blip Border drawn on the same box). Matches whatever radius
       class-blip-render.php put on .blip-message (Floating's corner
       radius, or the Border's own radius). */
    border-radius: inherit;
}

.blip-message-inner {
    position: relative;
    z-index: 1;
    padding: 5px 10px;
    line-height: 1.5;
    color: var(--blip-text, #ffffff);
    transition:
        opacity 0.6s ease,
        transform 0.6s ease,
        filter 0.6s ease;
    will-change: opacity, transform, filter;
}

.blip-message.is-exiting {
    z-index: 1;
}

.blip-message a {
    text-decoration: none;
}

/* ============================================
   BACKGROUND ANIMATIONS
   ============================================ */

/* None */
.blip[data-background="none"] .blip-message::before {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
}
.blip[data-background="none"] .blip-message.is-exiting::before {
    opacity: 1 !important;
    transform: none !important;
}

/*
 * Fade / Slide backgrounds: the outgoing message stays static and fully
 * opaque underneath — only the incoming message animates, on top of it
 * (see z-index above). This guarantees a blip color is visible for the
 * entire transition; nothing ever dips through to the page background.
 */

/* Fade */
.blip[data-background="fade"] .blip-message::before {
    opacity: 0;
    transition: opacity 1s ease-out;
}
.blip[data-background="fade"] .blip-message.is-active::before {
    opacity: 1;
}
.blip[data-background="fade"] .blip-message.is-exiting::before {
    opacity: 1;
}

/*
 * Slides reveal ::before (the background layer) via an animated clip-path
 * — never the whole .blip-message box, so Text Animation stays fully
 * independent of whatever Background Animation is doing, and never a
 * transform on ::before itself.
 *
 * This used to animate ::before with `transform: translate*()` instead —
 * sliding the actual filled/blurred layer across the screen. That's a
 * known cross-browser bug trigger: a `backdrop-filter` element clipped by
 * an ANCESTOR's overflow:hidden can leak blur past its own rounded corner
 * while the filtered element itself is mid-transform (the compositor
 * resamples the backdrop at a shifting position out of sync with the
 * clip), showing a brief dark seam during every crossfade. Revealing a
 * layer that never moves — its full-size box stays put; only how much of
 * it is clipped changes — produces the same left/right/up/down sweep
 * visually without ever putting a transform on the filtered element, so
 * there's nothing for that bug to trigger on.
 */

/* Slide Up — incoming background rises up and covers the outgoing one */
.blip[data-background="slide-up"] .blip-message::before {
    clip-path: inset(100% 0 0 0);
    transition: clip-path 1s cubic-bezier(0.16, 1, 0.3, 1);
}
.blip[data-background="slide-up"] .blip-message.is-active::before,
.blip[data-background="slide-up"] .blip-message.is-exiting::before {
    clip-path: inset(0 0 0 0);
}

/* Slide Down — incoming background drops down and covers the outgoing one */
.blip[data-background="slide-down"] .blip-message::before {
    clip-path: inset(0 0 100% 0);
    transition: clip-path 1s cubic-bezier(0.16, 1, 0.3, 1);
}
.blip[data-background="slide-down"] .blip-message.is-active::before,
.blip[data-background="slide-down"] .blip-message.is-exiting::before {
    clip-path: inset(0 0 0 0);
}

/* Slide Left — incoming background enters from the right, covers the outgoing one */
.blip[data-background="slide-left"] .blip-message::before {
    clip-path: inset(0 0 0 100%);
    transition: clip-path 1s cubic-bezier(0.16, 1, 0.3, 1);
}
.blip[data-background="slide-left"] .blip-message.is-active::before,
.blip[data-background="slide-left"] .blip-message.is-exiting::before {
    clip-path: inset(0 0 0 0);
}

/* Slide Right — incoming background enters from the left, covers the outgoing one */
.blip[data-background="slide-right"] .blip-message::before {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 1s cubic-bezier(0.16, 1, 0.3, 1);
}
.blip[data-background="slide-right"] .blip-message.is-active::before,
.blip[data-background="slide-right"] .blip-message.is-exiting::before {
    clip-path: inset(0 0 0 0);
}

/* ============================================
   TEXT ANIMATIONS
   ============================================ */

/* None */
.blip[data-transition="none"] .blip-message-inner {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    transition: none !important;
}
.blip[data-transition="none"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
}

/* Fade In — pure opacity crossfade, no movement */
.blip[data-transition="fade-in"] .blip-message-inner {
    opacity: 0;
}
.blip[data-transition="fade-in"]
    .blip-message.is-active
    .blip-message-inner {
    opacity: 1;
}
.blip[data-transition="fade-in"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 0;
}

/* Slide + Fade (Up) */
.blip[data-transition="slide-fade-up"] .blip-message-inner {
    transform: translateY(20px);
    opacity: 0;
}
.blip[data-transition="slide-fade-up"]
    .blip-message.is-active
    .blip-message-inner {
    opacity: 1;
    transform: translateY(0);
}
.blip[data-transition="slide-fade-up"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 0;
    transform: translateY(-20px);
}

/* Slide + Fade (Down) */
.blip[data-transition="slide-fade-down"] .blip-message-inner {
    transform: translateY(-20px);
    opacity: 0;
}
.blip[data-transition="slide-fade-down"]
    .blip-message.is-active
    .blip-message-inner {
    opacity: 1;
    transform: translateY(0);
}
.blip[data-transition="slide-fade-down"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 0;
    transform: translateY(20px);
}

/* Slide + Fade (Left) */
.blip[data-transition="slide-fade-left"] .blip-message-inner {
    transform: translateX(20px);
    opacity: 0;
}
.blip[data-transition="slide-fade-left"]
    .blip-message.is-active
    .blip-message-inner {
    opacity: 1;
    transform: translateX(0);
}
.blip[data-transition="slide-fade-left"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 0;
    transform: translateX(-20px);
}

/* Slide + Fade (Right) */
.blip[data-transition="slide-fade-right"] .blip-message-inner {
    transform: translateX(-20px);
    opacity: 0;
}
.blip[data-transition="slide-fade-right"]
    .blip-message.is-active
    .blip-message-inner {
    opacity: 1;
    transform: translateX(0);
}
.blip[data-transition="slide-fade-right"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 0;
    transform: translateX(20px);
}

/* Scale Up */
.blip[data-transition="scale-up"] .blip-message-inner {
    transform: scale(0.97);
    opacity: 0;
}
.blip[data-transition="scale-up"]
    .blip-message.is-active
    .blip-message-inner {
    opacity: 1;
    transform: scale(1);
}
.blip[data-transition="scale-up"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 0;
    transform: scale(0.97);
}

/* Blur In */
.blip[data-transition="blur-in"] .blip-message-inner {
    filter: blur(4px);
    opacity: 0;
}
.blip[data-transition="blur-in"]
    .blip-message.is-active
    .blip-message-inner {
    opacity: 1;
    filter: blur(0);
}
.blip[data-transition="blur-in"]
    .blip-message.is-exiting
    .blip-message-inner {
    opacity: 0;
    filter: blur(4px);
}
