/* ============================================================================
   COOKIE BUBBLE
   ============================================================================

   Ported from githyperplexed/bubbles against its ACTUAL source, not its README.
   The first attempt was built from a prose summary and got most of the numbers
   wrong; every value below now comes from a named constant in that repo, noted
   where it matters so a later edit can tell a deliberate deviation from a drift.

     BUBBLE_SIZE            56
     DISMISS_TARGET_SIZE    112   (BUBBLE_SIZE * 2)
     EDGE_MARGIN            12
     ACTIVE_SHRINK          4px of DIAMETER, not a scale factor

   THE TARGET IS TWICE THE BUBBLE ON PURPOSE. At 64px against a 56px bubble the
   bubble covers the target almost exactly and the X reads as sitting behind it,
   which is exactly what went wrong the first time. At 112px the bubble is
   captured into the middle of a ring that stays visible all the way around it.

   THE TARGET SITS BELOW THE BUBBLE (Z_DISMISS_TARGET 2147483610 against
   Z_BUBBLE_TOP 2147483647). Both are re-expressed on the house ladder here,
   because a raw 2147483647 would sit above the modal, the toasts and the nav.

   Motion lives in the JS: the library runs a spring integrator, and a CSS
   transition cannot express a damping ratio. What stays here is only what is
   genuinely a transition in the original - the press shrink and the target's
   capture scale, both 150ms ease.
   ========================================================================= */

/* ⚠ THE z-index BELONGS HERE, NOT ONLY ON .cb-bubble.
   `position: fixed` creates a stacking context unconditionally, so this element
   made one at `z-index: auto` (≈0) and every descendant's z-index became
   internal to it. .cb-bubble's `--z-float` (10000) was therefore trapped: it
   ordered the bubble against its own siblings and could not lift the bubble
   above anything outside .cb-root, however large the number got.

   The visible symptom was the bubble disappearing behind the Stage doorway on
   the home page and nowhere else - because .stage-doorway is the only home
   section whose wrapper carries a positive z-index (5), and 5 beats auto. The
   tempting fixes both fail: raising .cb-bubble does nothing (still trapped),
   and lowering the doorway just moves the collision to the next section that
   grows a z-index. Promoting the context itself is the fix. */
.cb-root {
    position: fixed;
    z-index: var(--z-float, 10000);
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}

/* ---------------------------------------------------------------------------
   THE BUBBLE
   ------------------------------------------------------------------------ */

.cb-bubble {
    position: fixed;
    z-index: var(--z-float, 10000);
    width: 56px;
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    pointer-events: auto;
    touch-action: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    outline: none;
}

/* The library splits the element from its SURFACE so the press shrink can scale
   the disc without moving the hit area. Keeping that split matters here: scaling
   the button itself would drag the touch target under the 44px floor every time
   somebody pressed it. */
.cb-surface {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    border: 1px solid var(--border, rgba(var(--fg-rgb, 255, 255, 255), 0.10));
    border-radius: var(--radius-circle, 50%);
    background: var(--surface-2, #171b24);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
    outline: 3px solid transparent;
    outline-offset: 3px;
    transition: scale 150ms ease,
                outline-color 150ms ease,
                border-color 150ms ease;
}

.cb-bubble:hover .cb-surface {
    border-color: var(--brand-blue, #3498db);
}

/* ACTIVE_SHRINK is 4px of diameter, so 52/56. Written as that ratio rather than
   a rounder-looking 0.95 because the press is subtle by design and the
   difference is visible side by side. */
.cb-bubble--pressed .cb-surface {
    scale: 0.9286;
}

.cb-bubble:focus-visible .cb-surface {
    outline-color: var(--brand-blue, #3498db);
}

.cb-icon {
    font-size: 1.25rem;
    background: var(--brand-gradient, linear-gradient(135deg, #3498db, #9b59b6));
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* ---------------------------------------------------------------------------
   THE ATTENTION STATE

   Undecided only. Once somebody answers, the bubble is a settings affordance and
   must stop asking - a permanent pulse on a resolved choice is the nag this
   whole design exists to avoid.
   ------------------------------------------------------------------------ */

.cb-bubble--attention .cb-surface::after {
    content: "";
    position: absolute;
    inset: -1px;
    border: 1px solid var(--brand-blue, #3498db);
    border-radius: var(--radius-circle, 50%);
    opacity: 0;
    animation: cb-pulse 2600ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1)) infinite;
    pointer-events: none;
}

.cb-dot {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 9px;
    height: 9px;
    border: 2px solid var(--surface-2, #171b24);
    border-radius: var(--radius-circle, 50%);
    background: var(--brand-blue, #3498db);
}

@keyframes cb-pulse {
    0% {
        opacity: 0.55;
        transform: scale(1);
    }
    70% {
        opacity: 0;
        transform: scale(1.45);
    }
    100% {
        opacity: 0;
        transform: scale(1.45);
    }
}

/* ---------------------------------------------------------------------------
   THE DISMISS TARGET

   112px, below the bubble. POSITION IS OWNED BY THE JS, which writes left/top
   every frame: the target rides up from off-screen when a drag begins, leans
   toward a free cursor on a quadratic falloff, tethers to the cursor once it has
   captured the bubble, and rides back down on release. None of that is
   expressible as a class, which is why the previous version - a static badge
   that faded its opacity - felt nothing like the original.

   So there is no `left: 50%` and no translateX here. A transform would fight the
   left/top the simulation writes, and a `bottom` anchor cannot travel off the
   bottom of the screen, which is exactly the entrance and the exit.

   The ONLY state this stylesheet owns is the capture scale, because that is the
   only thing the library expresses as a transition too. It scales DOWN to 0.85
   and never up - "only ever goes down (crisp)", since scaling a rendered disc up
   softens its edge. It is deliberately NOT recoloured on capture: the original
   communicates capture with scale alone, and a red flash here would be a louder
   signal than the thing it is imitating.
   ------------------------------------------------------------------------ */

.cb-dismiss {
    position: fixed;
    /* One BELOW the bubble, never equal to it. The library orders these
       explicitly (Z_DISMISS_TARGET 2147483610 under Z_BUBBLE_TOP 2147483647) and
       an equal value here is not a tier, it is a tie broken by DOM order (#97) -
       and since the target is appended after the bubble, that tie resolves the
       WRONG way and the target veils the bubble at the moment of capture.
       Measured as a real tie before this was written. */
    z-index: calc(var(--z-float, 10000) - 1);
    width: 112px;
    height: 112px;
    display: none;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.cb-dismiss__surface {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    border: 1px solid var(--border-strong, rgba(var(--fg-rgb, 255, 255, 255), 0.15));
    border-radius: var(--radius-circle, 50%);
    background: rgba(20, 20, 26, 0.82);
    backdrop-filter: blur(var(--glass-blur-md, 12px));
    -webkit-backdrop-filter: blur(var(--glass-blur-md, 12px));
    color: var(--text-secondary, #a0a0ab);
    transition: scale 150ms ease;
}

/* An SVG rather than an icon font: this element is built before any webfont has
   necessarily loaded, and a missing glyph would leave an empty ring at the exact
   moment it has to read as "drop it here". 44px at stroke 1.5, as in the
   original - the 26px version read as a small mark in a large ring. */
.cb-dismiss__surface svg {
    width: 44px;
    height: 44px;
}

/* ---------------------------------------------------------------------------
   MOBILE

   Neither element changes size. The library keeps both at their stock sizes and
   instead caps the capture and attraction RADII against the smaller viewport
   axis (25% and 75%), which the JS does - shrinking the target as well would
   compound the two and make it hard to hit.
   ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
   REDUCED MOTION

   The library's phrase is "fades in place of flights". The bubble still tracks a
   finger, because a draggable control that does not follow the drag is broken
   rather than calm; what stops is the spring, the pulse and the capture glide.
   The JS reads the same query and zeroes its catch-up gap.
   ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
    .cb-bubble--attention .cb-surface::after {
        animation: none;
        opacity: 0.4;
    }

    /* The JS pins the target at rest and fades it with WAAPI instead of riding
       it up from off-screen, so the only thing left to calm is the capture
       scale. */
    .cb-dismiss__surface {
        transition: none;
    }
}

/* ============================================================================
   THE CONSENT DIALOG

   Built on the house modal shell via Modal.mount, so the focus trap, Escape, the
   scroll lock and the enter/exit animation come from one place. What is bespoke
   is the crest and the body: a dialog opened from a cookie button should say
   "cookies" before anybody reads a word, and the first version said nothing at
   all until you reached the title.
   ========================================================================= */

/* Compound, not a bare .cb-dialog: that would TIE with .mh-modal-dialog at one
   class each and be settled by which stylesheet the browser saw last, which is a
   fact about link order rather than about intent (#33). */
.mh-modal-dialog.cb-dialog {
    max-width: 540px;
    padding: 0;
    text-align: left;
    overflow: hidden;
}

/* ---------------------------------------------------------------------- crest

   The MitsuHub monogram baked into a cookie. Same path the nav renders and the
   same brand gradient, so it reads as ours rather than as stock clip-art - and
   the disc, the bite and the chips make the subject legible before a single word
   is read, which is the thing the old header did not do.
   ------------------------------------------------------------------------ */

.cb-crest {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 30px 24px 20px;
    overflow: hidden;
}

/* Brand wash behind the crest. Under everything, takes no clicks. */
.cb-crest::before {
    content: "";
    position: absolute;
    top: -58%;
    left: 50%;
    width: 340px;
    height: 340px;
    transform: translateX(-50%);
    border-radius: var(--radius-circle, 50%);
    background: radial-gradient(circle,
                rgba(52, 152, 219, 0.20) 0%,
                rgba(155, 89, 182, 0.11) 45%,
                transparent 70%);
    pointer-events: none;
}

.cb-crest__inner {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.cb-cookie {
    width: 88px;
    height: 88px;
    display: block;
}

.cb-crest__title {
    margin: 16px 0 0;
    color: var(--text-primary, #ffffff);
    font-size: 1.32rem;
    font-weight: var(--weight-bold, 700);
    letter-spacing: -0.01em;
    text-align: center;
}

.cb-body {
    padding: 0 28px 26px;
}

.cb-intro {
    margin: 0 0 20px;
    color: var(--text-secondary, #a0a0ab);
    font-size: 0.92rem;
    line-height: var(--leading-relaxed, 1.6);
    text-align: center;
}

/* --------------------------------------------------------------- the groups */

.cb-groups {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0 0 20px;
}

.cb-group {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    padding: 12px 14px;
    border: 1px solid var(--border-hairline, rgba(var(--fg-rgb, 255, 255, 255), 0.08));
    border-radius: var(--radius-md, 12px);
    background: var(--surface-1, #12151c);
}

.cb-group__icon {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-circle, 50%);
    background: rgba(52, 152, 219, 0.14);
    color: var(--brand-blue, #3498db);
    font-size: 0.85rem;
}

.cb-group__body {
    min-width: 0;
}

.cb-group__name {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0 0 3px;
    color: var(--text-primary, #ffffff);
    font-size: 0.9rem;
    font-weight: var(--weight-semibold, 600);
}

.cb-group__tag {
    padding: 2px 8px;
    border-radius: var(--radius-pill, 9999px);
    background: rgba(var(--fg-rgb, 255, 255, 255), 0.07);
    color: var(--text-muted, #6b6b76);
    font-size: 0.68rem;
    font-weight: var(--weight-semibold, 600);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
}

.cb-group__tag--required {
    background: rgba(46, 204, 113, 0.14);
    color: var(--success, #2ecc71);
}

.cb-group__desc {
    margin: 0;
    color: var(--text-secondary, #a0a0ab);
    font-size: 0.83rem;
    line-height: 1.55;
}

/* The standing decision, shown only once one exists. Somebody who has never
   answered is not told they are undecided - that is a state, not news, and
   saying it makes the dialog read as a form they failed to fill in. */
.cb-current {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0 0 18px;
    padding: 10px 14px;
    border-radius: var(--radius-md, 12px);
    background: rgba(var(--fg-rgb, 255, 255, 255), 0.04);
    color: var(--text-secondary, #a0a0ab);
    font-size: 0.83rem;
}

.cb-current i {
    color: var(--brand-blue, #3498db);
}

/* The way back out of a standing decision. An inline text button rather than a
   third full-width action: it is the rare path, and giving it equal weight to
   allow/refuse would make a two-way choice look like a three-way one. */
.cb-withdraw {
    padding: 0;
    border: none;
    background: none;
    color: var(--brand-blue, #3498db);
    font: inherit;
    font-weight: var(--weight-semibold, 600);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.cb-withdraw:hover {
    color: var(--text-primary, #ffffff);
}

.cb-withdraw:focus-visible {
    outline: 2px solid var(--brand-blue, #3498db);
    outline-offset: 2px;
    border-radius: var(--radius-xs, 4px);
}

.cb-actions {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

.cb-actions .btn {
    flex: 1 1 160px;
}

@media (max-width: 480px) {
    .cb-crest {
        padding: 26px 18px 16px;
    }

    .cb-body {
        padding: 0 18px 20px;
    }

    .cb-actions {
        flex-direction: column;
    }

    .cb-actions .btn {
        flex: 1 1 auto;
        width: 100%;
    }
}
