/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE POLICY — read before adding a media query to this file
   ───────────────────────────────────────────────────────────────────────────
   This file grew 63 media queries across 15 different breakpoints, which is
   how mobile ended up as desktop-shrunk rather than designed. New work uses
   only these three, matching the tokens in tokens.css:

     ≤768px   phone + small tablet   (primary mobile anchor, --bp-md)
     ≤560px   phone
     ≤400px   narrow phone

   The legacy widths (380/480/600/640/720/840/850/900/1000/1100/1200/1600) are
   left where they are — rewriting them risks regressions for no visible gain —
   but nothing new is added at those widths.

   Prefer the density tokens (--pad-page/--pad-section/--pad-card/--gap-card)
   over a fresh media query: they already retune at 768 and 400 in tokens.css.

   Source order is the override mechanism in this file. Later blocks with equal
   specificity win, which is why the mobile passes live at the bottom.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   FOCUS RING FLOOR — a keyboard user must be able to see where they are
   ───────────────────────────────────────────────────────────────────────────
   :where() makes this (0,0,0) — zero specificity — on purpose, and the purpose
   is worth stating because it looks like a mistake:

     * It must LOSE to every existing focus rule. Four rules in this file already
       draw a ring, and .chat-input-container:focus-within rings the wrapper while
       the textarea inside is `outline: none`. A specific selector here would
       fight all of them; a zero-specificity one cannot.
     * It still BEATS the browser's own ring, because a UA-origin declaration
       loses to any author declaration at any specificity. That is the real reach
       of this block: an audit of the dashboard found 88 visible focusable
       controls, and 57 of them were relying on Chrome's default ring — the right
       behaviour in a wrong colour, and absent in some other engines. Those 57 now
       ring in the product's own colour.
     * It CANNOT rescue the 15 `outline: none` declarations in this file. Those sit
       at (0,1,0) and beat (0,0,0) every time. Each was audited separately by
       forcing :focus-visible and diffing computed style; 14 compensate with a
       border-colour change or a box-shadow ring, which is a perfectly good
       indicator. The one that did not was .glass-btn, fixed explicitly at its own
       rule below — do not try to fix that class of problem by making this block
       more specific.

   The offset is positive so the ring lands on the page ground rather than on the
   control's own fill; see the --focus-ring comment in tokens.css for why that is
   what makes it visible on a brand-coloured button.
   ═══════════════════════════════════════════════════════════════════════════ */
:where(button, a, select, input, textarea, summary, [role="button"], [role="tab"],
       [role="switch"], [role="checkbox"], [contenteditable="true"],
       [tabindex]):focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* Smart search + filter results header */
.results-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 18px;
    margin: 4px 0 16px 0;
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.2px;
    color: var(--text-primary);
    background: linear-gradient(135deg, rgba(110, 142, 251, 0.08), rgba(167, 119, 227, 0.08));
    border: 1px solid rgba(167, 119, 227, 0.22);
    border-radius: var(--border-radius-md);
    backdrop-filter: blur(8px);
    box-shadow: 0 2px 12px rgba(167, 119, 227, 0.08);
}

.results-header>span:first-of-type,
.results-header>span:not(.results-count) {
    background: linear-gradient(135deg, #e2c8ff 0%, #a777e3 60%, #6e8efb 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    text-shadow: 0 1px 12px rgba(167, 119, 227, 0.25);
}

.results-header i {
    color: var(--accent-color);
    font-size: 0.95rem;
    filter: drop-shadow(0 0 6px rgba(167, 119, 227, 0.5));
}

.results-header .results-count {
    margin-left: auto;
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--text-secondary);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--border-color);
    padding: 3px 10px;
    border-radius: var(--border-radius-pill);
    letter-spacing: 0.3px;
}

/* Discover pagination bar */
.pagination-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: 28px 0 40px 0;
    font-family: var(--font-display);
}

.pagination-btn {
    min-width: 38px;
    height: 38px;
    padding: 0 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text-secondary);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}

.pagination-btn:hover:not(:disabled) {
    color: var(--text-primary);
    background: rgba(167, 119, 227, 0.12);
    border-color: rgba(167, 119, 227, 0.45);
    transform: translateY(-1px);
}

.pagination-btn:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

.pagination-btn.active {
    color: var(--on-brand-gradient);
    background: linear-gradient(135deg, #6e8efb 0%, #a777e3 100%);
    border-color: transparent;
    box-shadow: 0 2px 12px rgba(167, 119, 227, 0.35);
    cursor: default;
}

.pagination-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.pagination-btn.arrow {
    min-width: 34px;
    padding: 0 8px;
    font-size: 0.8rem;
}

.pagination-gap {
    padding: 0 4px;
    color: var(--text-secondary);
    font-size: 0.9rem;
    letter-spacing: 1px;
    user-select: none;
}

.pagination-count {
    margin-left: 10px;
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--text-secondary);
    letter-spacing: 0.3px;
}

@media (max-width: 640px) {
    .pagination-bar {
        gap: 4px;
        margin: 20px 0 32px 0;
    }

    .pagination-btn {
        min-width: 34px;
        height: 34px;
        padding: 0 7px;
        font-size: 0.84rem;
    }

    /* On a phone the range readout wraps the bar to two lines for little
       gain — the active page number already communicates position. */
    .pagination-count {
        display: none;
    }
}

:root {
    --bg-main: #09090b;
    --bg-sidebar: rgba(16, 16, 20, 0.4);
    --bg-hover: rgba(255, 255, 255, 0.08);
    --bg-card: rgba(30, 30, 35, 0.5);
    --text-primary: #f8f8f8;
    --text-secondary: #a1a1aa;
    --accent-color: var(--brand-accent, #a777e3);
    --accent-gradient: var(--brand-gradient, linear-gradient(135deg, #6e8efb, #a777e3));
    --border-color: rgba(255, 255, 255, 0.08);
    /* The radius scale. The values live in tokens.css (the only stylesheet all 19
       pages link); these are the local names styles.css and dashboard.js already
       used, kept so the two naming systems stop competing.

       Each carries a literal fallback on purpose. An undefined var() in
       border-radius is invalid at computed-value time, and border-radius does not
       inherit — so the property would resolve to `initial`, i.e. 0. If tokens.css
       ever fails to load, the fallback keeps 428 declarations rounded instead of
       squaring every card, pill and avatar on the dashboard at once.

       ONE INVARIANT HOLDS BELOW THIS LINE, and it is worth keeping: a @media
       border-radius override in this file means the radius GENUINELY differs at
       that width. All 55 that had become no-ops were deleted once the values
       collapsed onto these six tokens, because an override that agrees with its
       base is worse than no override — the next person changes the desktop value,
       sees nothing move on a phone, and goes looking for a bug that isn't there.

       Three shapes of dead override turned up, none of them findable by eye:
         - value-equal to its base after the collapse (the common case)
         - a narrow breakpoint fully covered by a LATER, wider block carrying the
           same value: .diary-input-container had @600 md written at :7904 and
           @768 md at :20550, and the later one wins at every width ≤600
         - a duplicate of a rule that already exists (.shop-hero-section carried the
           same @560 rule twice, and @768 already covered both)
       Deleting them was verified as a no-op by measurement, not by reading: 592
       computed radii across nine surfaces at two viewports, 0 changed.

       radredundant.mjs re-derives the list by simulating the cascade, so if one
       creeps back it can be found again rather than argued about. */
    --border-radius-2xs: var(--radius-2xs, 4px);
    --border-radius-xs: var(--radius-xs, 8px);
    --border-radius-sm: var(--radius-sm, 12px);
    --border-radius-md: var(--radius-md, 16px);
    --border-radius-lg: var(--radius-lg, 24px);
    --border-radius-pill: var(--radius-pill, 999px);

    /* The glass-tile surface. See "THE SURFACE PRIMITIVE" below for why these
       are tokens and not literals in the rule.

       --surface-radius is --border-radius-md (16px) and not the 20px it used to
       be, because 16px is the value that already shipped to every phone: the
       media block below this rule had redefined the token to md at 768px since
       Phase 4.1. So this is a -4px change on desktop and a 0px change on mobile,
       not a change to "every tile" -- and it makes the two agree instead of
       forking. The same argument decided the chat bubble, which was likewise
       already 16px under 768px.

       Not --border-radius-lg (24px): lg is the page-section tier. .shop-hero-
       section, .premium-hero and the four other hero panels are lg, and the
       tiles sit INSIDE them. Keeping the tiles one step tighter than the section
       that contains them is the hierarchy; making both 24px erases it.

       --surface-shadow exists because box-shadow does not compose: a tile that
       wants to add an inset highlight has to restate the resting layers. This
       keeps the resting depth defined once even so. */
    --surface-radius: var(--border-radius-md);
    --surface-shadow:
        0 10px 28px -12px rgba(0, 0, 0, 0.55),
        0 2px 6px -2px rgba(0, 0, 0, 0.4);
    /* Body face. Points at the shared token rather than repeating the literal —
       this file used to be the only place the app pages had any family at all,
       because --font-body was defined in landing.css, which they don't load.
       Kept under its old name so the ~40 var(--font-family) call sites below
       don't all have to change. */
    --font-family: var(--font-body, 'Inter', system-ui, -apple-system, sans-serif);
    --transition-speed: 0.3s;
}

/* The mobile snap that used to live here is gone, and its absence is the point.
   It read:

       @media (max-width: 768px) { :root { --surface-radius: var(--border-radius-md); } }

   ...which is now exactly what the base value is, so the block could only ever
   set the token to the value it already had. It is deleted rather than kept as a
   no-op because a media override that agrees with its base is worse than no
   override: the next person to change the desktop radius changes one line, sees
   no effect on a phone, and goes looking for a bug.

   Worth keeping the history, though, because it explains why the token exists at
   all. The four hand-rolled versions of this snap had drifted to TWO breakpoints:
   .shop-card at 560px, but .quest-card, .achievement-card and .public-panel-card
   at 768px. Between 561px and 768px the same tile was 20px in the shop grid and
   16px in the quest grid. Collapsing the tiles onto one token is what made that
   fork impossible to reintroduce by accident. */

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    height: 100%;
    width: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
}

body {
    position: fixed;
    inset: 0;
    /* `inset: 0` alone sizes the shell to the layout viewport, which on mobile is
       the LARGE viewport — so the bottom of the app (chat composer, bottom tab
       bar) sits behind the browser's URL bar. Releasing `bottom` and giving the
       shell a dvh height re-anchors it to what is actually on screen. The plain
       100% line is the fallback for browsers without dvh. */
    bottom: auto;
    height: 100%;
    height: 100dvh;
    margin: 0;
    padding: 0;
    overflow: hidden;
    overscroll-behavior: none;
    -webkit-text-size-adjust: 100%;
    /* `pan-x pan-y` silently killed pinch-zoom on top of the viewport meta's
       user-scalable=no. `manipulation` keeps the double-tap-zoom delay off
       (the reason this was set) while letting the user pinch to zoom. */
    touch-action: manipulation;
    /* Suppress the translucent grey rectangle Android WebView flashes over every
       tapped element — the single loudest tell that a UI is a web page. The
       property inherits, so this one declaration covers the whole app;
       .mobile-tab-bar .tab-item already set it locally (~19000) for exactly this
       reason, and that line is now redundant rather than wrong. Tap feedback
       comes from the app's own :active states instead, which is the point of
       doing it here rather than with `touch-action` tricks. */
    -webkit-tap-highlight-color: transparent;
    background-color: var(--bg-main);
    color: var(--text-primary);
    font-family: var(--font-family);
}

/* Immersive Background Glow */
body::before {
    content: '';
    position: absolute;
    top: -20%;
    left: -10%;
    width: 70%;
    height: 70%;
    background: radial-gradient(circle, rgba(167, 119, 227, 0.15) 0%, transparent 60%);
    z-index: -1;
    pointer-events: none;
    animation: drift 15s ease-in-out infinite alternate;
}

body::after {
    content: '';
    position: absolute;
    bottom: -20%;
    right: -10%;
    width: 60%;
    height: 60%;
    background: radial-gradient(circle, rgba(110, 142, 251, 0.12) 0%, transparent 60%);
    z-index: -1;
    pointer-events: none;
    animation: drift 20s ease-in-out infinite alternate-reverse;
}

@keyframes drift {
    0% {
        transform: translate(0, 0) scale(1);
    }

    100% {
        transform: translate(5%, 5%) scale(1.1);
    }
}

/* App Layout */
#app {
    display: flex;
    height: 100%;
    width: 100%;
    position: relative;
    overflow: hidden;
}

/* Sidebar with Glassmorphism */
.sidebar {
    width: 260px;
    min-width: 260px;
    flex-shrink: 0;
    background-color: var(--bg-sidebar);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-right: 1px solid var(--border-color);
    display: flex;
    flex-direction: column;
    padding: 20px 18px;
    transition: width var(--transition-speed) ease;
    z-index: 50;
    overflow: hidden;
    animation: sidebarIn 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes sidebarIn {
    from {
        opacity: 0;
        transform: translateX(-16px);
    }

    to {
        opacity: 1;
        transform: translateX(0);
    }
}

.sidebar-header {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    margin-top: 4px;
    margin-bottom: 30px;
    padding-left: 4px;
}

/* ── Animated brand logo ── */
.brand-logo {
    display: inline-flex;
    align-items: baseline;
    gap: 0;
    cursor: pointer;
    user-select: none;
    animation: brandIntro 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes brandIntro {
    from {
        opacity: 0;
        transform: translateY(-6px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.brand-mark {
    display: none;
}

.sidebar-header h2 {
    font-size: 1.4rem;
    /* The wordmark. It had NO font-family and so inherited body Inter, while the
       footer wordmark on the 16 pages that load footer.css writes
       var(--font-heading, var(--font-display)) and resolved to the display face.
       Same brand string, two faces, decided by which page you happened to be on.
       Measured on discover.html: h2 "LoreLoom." -> Inter at 22.4px. */
    font-family: var(--font-display);
    font-weight: 800;
    /* -0.5px (-0.022em) was tracked for a geometric sans. Fraunces is a serif
       with real stroke contrast and needs less negative tracking before the
       serifs start colliding; measured at 22.4px, -0.2px keeps the optical
       tightness without the o/r pair touching. */
    letter-spacing: -0.2px;
    background: linear-gradient(90deg, #f6f1ff 0%, #d9c6ff 50%, #f6f1ff 100%);
    background-size: 200% auto;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    transition: background-position 0.6s ease;
}

.brand-logo:hover h2 {
    animation: brandTextShine 1.6s linear infinite;
}

@keyframes brandTextShine {
    to {
        background-position: 200% center;
    }
}

.brand-dot {
    -webkit-text-fill-color: #a777e3;
    color: #a777e3;
    display: inline-block;
    animation: dotPulse 2.4s ease-in-out infinite;
}

@keyframes dotPulse {

    0%,
    100% {
        opacity: 1;
        transform: translateY(0);
    }

    50% {
        opacity: 0.55;
        transform: translateY(-1px);
    }
}

/* Respect users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {

    .sidebar,
    .brand-logo,
    .brand-mark::after,
    .brand-thread,
    .brand-node,
    .brand-dot,
    .nav-item,
    .account-avatar::before {
        animation: none !important;
    }

    .brand-thread {
        stroke-dashoffset: 0 !important;
    }

    .brand-node {
        transform: scale(1) !important;
    }
}

.icon-btn {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: var(--text-secondary);
    font-size: 1rem;
    cursor: pointer;
    border-radius: 50%;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition:
        background var(--transition-speed),
        color var(--transition-speed),
        transform var(--transition-speed);
}

.icon-btn:hover {
    background: rgba(255, 255, 255, 0.1);
    color: var(--text-primary);
    transform: scale(1.05);
}

.create-btn {
    display: block;
    background: var(--accent-gradient);
    color: #fff;
    text-decoration: none;
    padding: 14px;
    border-radius: var(--border-radius-pill);
    text-align: center;
    font-weight: 600;
    margin-bottom: 32px;
    border: none;
    transition: box-shadow var(--transition-speed), transform var(--transition-speed);
    box-shadow: 0 4px 15px rgba(167, 119, 227, 0.3);
    position: relative;
    overflow: hidden;
}

.create-btn::after {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 50%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
    animation: shine 3s infinite;
}

.super-create-btn {
    background: linear-gradient(135deg, #a777e3 0%, #7c5ce7 50%, #6e8efb 100%);
    background-size: 200% 200%;
    animation: superGradientShift 6s ease infinite;
    color: white;
    border: none;
    border-radius: var(--border-radius-md);
    padding: 13px 18px;
    font-weight: 700;
    font-size: 0.95rem;
    letter-spacing: 0.3px;
    box-shadow:
        0 4px 16px rgba(167, 119, 227, 0.35),
        0 0 0 1px rgba(167, 119, 227, 0.15),
        inset 0 1px 0 rgba(255, 255, 255, 0.15);
    transition:
        box-shadow 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 8px;
    position: relative;
    overflow: hidden;
}

.super-create-btn::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 60%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
    animation: shine 4s ease-in-out infinite;
}

.super-create-btn:hover {
    transform: translateY(-2px) scale(1.02);
    box-shadow:
        0 8px 28px rgba(167, 119, 227, 0.5),
        0 0 0 1px rgba(167, 119, 227, 0.3),
        inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.super-create-btn:active {
    transform: translateY(0) scale(0.98);
    box-shadow:
        0 2px 10px rgba(167, 119, 227, 0.3),
        0 0 0 1px rgba(167, 119, 227, 0.2);
}

/* Premium Create Dropdown styling */
.create-dropdown-wrapper {
    position: relative;
    margin-bottom: 28px;
    z-index: 110 !important;
}

.super-create-btn .create-btn-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    background: rgba(255, 255, 255, 0.18);
    border-radius: var(--border-radius-xs);
    font-size: 0.75rem;
    flex-shrink: 0;
    transition: background 0.3s ease, transform 0.3s ease;
}

.super-create-btn:hover .create-btn-icon {
    background: rgba(255, 255, 255, 0.28);
    transform: rotate(90deg);
}

.super-create-btn .create-chevron {
    font-size: 0.7em;
    margin-left: auto;
    opacity: 0.7;
    transition: transform var(--transition-speed) cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.super-create-btn.active .create-chevron {
    transform: rotate(180deg);
}

.create-dropdown {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    right: 0;
    background: rgba(18, 16, 26, 0.95);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid rgba(167, 119, 227, 0.2);
    border-radius: var(--border-radius-md);
    z-index: 200;
    padding: 8px;
    box-shadow:
        0 16px 40px rgba(0, 0, 0, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.04),
        0 0 30px rgba(167, 119, 227, 0.08);
    display: none;
    animation: dropdownReveal 0.3s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
    transform-origin: top center;
}

@keyframes dropdownReveal {
    from {
        opacity: 0;
        transform: translateY(-8px) scale(0.95);
    }

    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

.create-dropdown .dropdown-item {
    padding: 12px 14px;
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 14px;
    color: var(--text-primary);
    font-weight: 500;
    font-size: 0.9rem;
    transition: background 0.25s ease;
    position: relative;
}

.create-dropdown .dropdown-item .dropdown-icon-wrap {
    width: 38px;
    height: 38px;
    border-radius: var(--border-radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background 0.3s ease, border-color 0.3s ease, transform 0.3s ease;
}

.create-dropdown .dropdown-item .dropdown-icon-wrap.purple {
    background: rgba(167, 119, 227, 0.12);
    border: 1px solid rgba(167, 119, 227, 0.2);
}

.create-dropdown .dropdown-item .dropdown-icon-wrap.purple i {
    color: #c9a0f5;
    font-size: 1rem;
}

.create-dropdown .dropdown-item .dropdown-text {
    display: flex;
    align-items: center;
    min-width: 0;
}

.create-dropdown .dropdown-item .dropdown-text .dropdown-label {
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--text-primary);
    transition: color 0.25s ease;
}

.create-dropdown .dropdown-item:hover {
    background: rgba(167, 119, 227, 0.1);
}

.create-dropdown .dropdown-item:hover .dropdown-icon-wrap.purple {
    background: rgba(167, 119, 227, 0.22);
    border-color: rgba(167, 119, 227, 0.4);
    transform: scale(1.05);
}

.create-dropdown .dropdown-item:hover .dropdown-label {
    color: #e2d0ff;
}

.create-dropdown .dropdown-item .dropdown-arrow {
    margin-left: auto;
    font-size: 0.7rem;
    color: rgba(255, 255, 255, 0.15);
    transition: color 0.25s ease, transform 0.25s ease;
}

.create-dropdown .dropdown-item:hover .dropdown-arrow {
    color: rgba(167, 119, 227, 0.6);
    transform: translateX(3px);
}

@keyframes superGradientShift {
    0% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }

    100% {
        background-position: 0% 50%;
    }
}

@keyframes shine {
    100% {
        left: 200%;
    }
}

.create-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(167, 119, 227, 0.5);
}

.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 32px;
}

.nav-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 16px;
    text-decoration: none;
    color: var(--text-secondary);
    border-radius: var(--border-radius-sm);
    font-weight: 500;
    border: 1px solid transparent;
    overflow: hidden;
    transition: color 0.25s ease, background 0.25s ease, transform 0.25s cubic-bezier(0.34, 1.4, 0.64, 1), border-color 0.25s ease;
    /* staggered entrance */
    animation: navItemIn 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.sidebar-nav .nav-item:nth-child(1) {
    animation-delay: 0.06s;
}

.sidebar-nav .nav-item:nth-child(2) {
    animation-delay: 0.12s;
}

.sidebar-nav .nav-item:nth-child(3) {
    animation-delay: 0.18s;
}

@keyframes navItemIn {
    from {
        opacity: 0;
        transform: translateX(-10px);
    }

    to {
        opacity: 1;
        transform: translateX(0);
    }
}

/* Animated left indicator bar */
.nav-item::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 0;
    border-radius: 0 var(--border-radius-2xs) var(--border-radius-2xs) 0;
    background: linear-gradient(180deg, #a777e3, #6e8efb);
    box-shadow: 0 0 10px rgba(167, 119, 227, 0.6);
    transition: height 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.nav-item i {
    font-size: 1.1rem;
    width: 20px;
    text-align: center;
    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.25s ease;
}

.nav-item.active {
    background: linear-gradient(100deg, rgba(167, 119, 227, 0.16), rgba(110, 142, 251, 0.05));
    color: var(--text-primary);
    border-color: rgba(167, 119, 227, 0.22);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.nav-item.active::before {
    height: 58%;
}

.nav-item.active i {
    color: #c9a4ff;
    transform: scale(1.1);
    filter: drop-shadow(0 0 6px rgba(167, 119, 227, 0.5));
}

.nav-item:hover:not(.active) {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-primary);
    transform: translateX(5px);
}

.nav-item:hover:not(.active)::before {
    height: 36%;
}

.nav-item:hover:not(.active) i {
    transform: scale(1.15) rotate(-6deg);
    color: #c9a4ff;
}

.recent-chats {
    flex-grow: 1;
    overflow-y: auto;
    overflow-x: hidden;
    padding-right: 2px;
}

.recent-chats::-webkit-scrollbar {
    width: 5px;
}

.recent-chats::-webkit-scrollbar-thumb {
    background: rgba(167, 119, 227, 0.28);
    border-radius: var(--border-radius-xs);
}

.recent-chats::-webkit-scrollbar-thumb:hover {
    background: rgba(167, 119, 227, 0.5);
}

.recent-chats h3 {
    display: flex;
    align-items: center;
    gap: 8px;
    /* Body face on purpose, and stated rather than inherited. It is an h3, so the
       obvious "fix" is to point it at --font-display — don't. At 11.5px, uppercase,
       +1.2px tracked, this is an eyebrow label in the UI voice, and a display serif
       at that size on a dark panel goes muddy. Same call as .ai-tip-title in
       create_character.html. */
    font-family: var(--font-body);
    font-size: 0.72rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 1.2px;
    font-weight: 700;
    margin-bottom: 14px;
    padding-left: 2px;
}

.recent-chats h3 i {
    font-size: 0.72rem;
    color: #b98cf0;
    opacity: 0.9;
}

.chat-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.sidebar-chat-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 10px 10px 10px 12px;
    border-radius: var(--border-radius-md);
    cursor: pointer;
    transition: transform 0.18s cubic-bezier(0.34, 1.4, 0.64, 1), background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
    border: 1px solid transparent;
    margin-bottom: 3px;
    overflow: hidden;
    /* Entrance animation */
    animation: sidebar-chat-in 0.35s ease both;
}

@keyframes sidebar-chat-in {
    from {
        opacity: 0;
        transform: translateX(-8px);
    }

    to {
        opacity: 1;
        transform: translateX(0);
    }
}

/* Accent bar that grows on hover / active */
.sidebar-chat-item::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 0;
    border-radius: 0 var(--border-radius-2xs) var(--border-radius-2xs) 0;
    background: linear-gradient(180deg, #a777e3, #6e8efb);
    transition: height 0.22s ease;
}

.sidebar-chat-item:hover {
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(255, 255, 255, 0.07);
    transform: translateX(2px);
}

.sidebar-chat-item:hover::before {
    height: 40%;
}

.sidebar-chat-item.active {
    background: linear-gradient(100deg, rgba(167, 119, 227, 0.16), rgba(110, 142, 251, 0.06));
    border-color: rgba(167, 119, 227, 0.3);
    box-shadow: 0 4px 16px rgba(167, 119, 227, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

.sidebar-chat-item.active::before {
    height: 62%;
}

.sidebar-chat-item img {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid rgba(167, 119, 227, 0.35);
    transition: border-color 0.2s ease, transform 0.2s ease;
}

.sidebar-chat-item:hover img,
.sidebar-chat-item.active img {
    border-color: rgba(167, 119, 227, 0.7);
}

.sidebar-chat-item:hover img {
    transform: scale(1.05);
}

.chat-item-info {
    flex: 1;
    min-width: 0;
}

.chat-item-name {
    font-weight: 700;
    font-size: 0.85rem;
    color: #f2ecfa;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    letter-spacing: 0.01em;
}

.sidebar-chat-item.active .chat-item-name {
    color: #e2ccff;
}

.chat-item-last {
    font-size: 0.74rem;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-top: 2px;
    opacity: 0.85;
}

.delete-chat-btn {
    transform: scale(0.85);
    transition: opacity 0.2s ease, transform 0.2s ease, color 0.2s ease !important;
}

.delete-chat-btn:hover {
    transform: scale(1.15);
    color: #ff6b6b !important;
}

/* Sidebar Account Shell */
.sidebar-account-shell {
    position: relative;
    margin-top: 12px;
}

.sidebar-account-toggle {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 12px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.035);
    border-radius: var(--border-radius-md);
    padding: 12px 12px;
    cursor: pointer;
    color: var(--text-primary);
    transition:
        background 0.22s ease,
        border-color 0.22s ease,
        box-shadow 0.22s ease,
        transform 0.22s ease;
    text-align: left;
}

.sidebar-account-toggle:hover {
    background: rgba(167, 119, 227, 0.09);
    border-color: rgba(167, 119, 227, 0.22);
    transform: translateY(-1px);
}

.account-avatar {
    position: relative;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    color: var(--on-brand-gradient);
    font-weight: 800;
    box-shadow: 0 8px 20px rgba(167, 119, 227, 0.25);
    flex-shrink: 0;
    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Rotating conic glow ring around the avatar */
.account-avatar::before {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    background: conic-gradient(from 0deg, #a777e3, #6e8efb, #a777e3, #6e8efb, #a777e3);
    z-index: -1;
    opacity: 0;
    transition: opacity 0.3s ease;
    animation: avatarRing 4s linear infinite;
}

@keyframes avatarRing {
    to {
        transform: rotate(360deg);
    }
}

.sidebar-account-toggle:hover .account-avatar {
    transform: scale(1.06);
}

.sidebar-account-toggle:hover .account-avatar::before {
    opacity: 0.9;
}

/* Loom Plus users get a diamond badge with a premium gradient */
.account-avatar.plus-avatar {
    background: linear-gradient(135deg, #7f5bff, #b06cff);
    box-shadow: 0 8px 20px rgba(176, 108, 255, 0.4);
}

.account-avatar.plus-avatar i {
    font-size: 1rem;
    color: #fff;
}

/* =====================================================================
   LOOM PLUS — Premium account card treatment
   ===================================================================== */

/* Avatar wrapper so the crown can sit on the avatar corner */
.account-avatar-wrap {
    position: relative;
    flex-shrink: 0;
    display: grid;
    place-items: center;
}

/* Gold crown badge — sits on the avatar's corner, hidden until .plus */
.account-plus-crown {
    display: none;
    position: absolute;
    right: -3px;
    bottom: -3px;
    width: 19px;
    height: 19px;
    border-radius: 50%;
    place-items: center;
    background: linear-gradient(135deg, #ffe27a 0%, #ffb43d 45%, #ff8f1f 100%);
    box-shadow: 0 2px 7px rgba(255, 150, 30, 0.55), 0 0 0 2px rgba(24, 18, 10, 0.95);
    z-index: 3;
}

.account-plus-crown i {
    font-size: 0.55rem;
    color: #3a2600;
}

.sidebar-account-shell.plus .account-plus-crown {
    display: grid;
    animation: plusCrownPop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes plusCrownPop {
    from {
        opacity: 0;
        transform: scale(0) rotate(-25deg);
    }

    to {
        opacity: 1;
        transform: scale(1) rotate(0);
    }
}

/* Premium gold-tinted toggle: animated gradient border + soft aura */
.sidebar-account-shell.plus .sidebar-account-toggle {
    position: relative;
    background:
        linear-gradient(rgba(24, 18, 10, 0.92), rgba(24, 18, 10, 0.92)) padding-box,
        linear-gradient(135deg, #ffd970, #b06cff, #6e8efb, #ffd970) border-box;
    background-size: auto, 300% 300%;
    border: 1px solid transparent;
    box-shadow:
        0 8px 26px rgba(176, 108, 255, 0.22),
        0 0 0 1px rgba(255, 215, 120, 0.12),
        inset 0 1px 0 rgba(255, 255, 255, 0.06);
    animation: plusBorderFlow 6s ease infinite;
}

@keyframes plusBorderFlow {
    0% {
        background-position: 0% 50%, 0% 50%;
    }

    50% {
        background-position: 0% 50%, 100% 50%;
    }

    100% {
        background-position: 0% 50%, 0% 50%;
    }
}

.sidebar-account-shell.plus .sidebar-account-toggle:hover {
    transform: translateY(-1px);
    box-shadow:
        0 12px 32px rgba(176, 108, 255, 0.35),
        0 0 18px rgba(255, 200, 90, 0.18),
        inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

/* Slow-drifting aura behind the whole card */
.sidebar-account-shell.plus .sidebar-account-toggle::after {
    content: '';
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    background: radial-gradient(120% 140% at 15% 0%, rgba(255, 210, 110, 0.16), transparent 55%);
    pointer-events: none;
    opacity: 0.9;
    z-index: 0;
}

.sidebar-account-shell.plus .account-avatar-wrap,
.sidebar-account-shell.plus .account-copy,
.sidebar-account-shell.plus .account-chevron {
    position: relative;
    z-index: 1;
}

/* Plus avatar: keep the original diamond gradient/texture */
.sidebar-account-shell.plus .account-avatar {
    background: linear-gradient(135deg, #7f5bff, #b06cff);
    box-shadow: 0 8px 20px rgba(176, 108, 255, 0.4);
}

.sidebar-account-shell.plus .account-avatar::before {
    background: conic-gradient(from 0deg, #ffd970, #b06cff, #6e8efb, #ffd970, #ffd970);
    opacity: 0.55;
    inset: -2px;
}

.sidebar-account-shell.plus .sidebar-account-toggle:hover .account-avatar::before {
    opacity: 1;
}

.sidebar-account-shell.plus .account-avatar i {
    animation: plusGemShine 2.6s ease-in-out infinite;
}

@keyframes plusGemShine {

    0%,
    100% {
        transform: scale(1);
        filter: drop-shadow(0 0 0 rgba(255, 220, 130, 0));
    }

    50% {
        transform: scale(1.12);
        filter: drop-shadow(0 0 6px rgba(255, 220, 130, 0.8));
    }
}

/* "Loom Plus" balance text gets a subtle gold shimmer */
.sidebar-account-shell.plus .account-copy .balance {
    background: linear-gradient(90deg, #ffe9a8, #ffc65c, #ffe9a8);
    background-size: 200% auto;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    font-weight: 700;
    animation: plusBalanceShimmer 3s linear infinite;
}

@keyframes plusBalanceShimmer {
    to {
        background-position: 200% center;
    }
}

@media (prefers-reduced-motion: reduce) {

    .sidebar-account-shell.plus .sidebar-account-toggle,
    .sidebar-account-shell.plus .account-avatar i,
    .sidebar-account-shell.plus .account-copy .balance,
    .account-plus-crown {
        animation: none !important;
    }
}


.account-copy {
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* Pin this two-line stack to the avatar's height and center its contents so
       the name + balance read as vertically centered against the avatar. Without
       this the inherited line-height makes the stack as tall as (or taller than)
       the 42px avatar, leaving the glyphs looking low. */
    justify-content: center;
    gap: 2px;
    flex: 1;
    min-height: 42px;
}

.account-copy .username {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1.2;
    color: #fff;
}

.account-copy .balance {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.78rem;
    line-height: 1.2;
    color: var(--text-secondary);
}

.account-chevron {
    margin-left: auto;
    font-size: 0.75rem;
    opacity: 0.65;
    transition: transform 0.22s ease;
}

.sidebar-account-shell.open .account-chevron {
    transform: rotate(180deg);
}

.sidebar-account-menu {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% + 10px);
    background: rgba(18, 16, 26, 0.97);
    border: 1px solid rgba(167, 119, 227, 0.18);
    border-radius: var(--border-radius-md);
    padding: 10px;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    display: grid;
    gap: 6px;
    opacity: 0;
    transform: translateY(8px) scale(0.98);
    pointer-events: none;
    transition: opacity 0.22s ease, transform 0.22s ease;
    z-index: 120;
}

.sidebar-account-shell.open .sidebar-account-menu {
    opacity: 1;
    transform: translateY(0) scale(1);
    pointer-events: auto;
}

.account-menu-header {
    padding: 6px 8px 10px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    margin-bottom: 4px;
}

.account-menu-title {
    font-size: 0.72rem;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--text-secondary);
    font-weight: 700;
}

.account-menu-subtitle {
    margin-top: 4px;
    color: #d6b4ff;
    font-size: 0.88rem;
    font-weight: 600;
}

.account-menu-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 11px 12px;
    border-radius: var(--border-radius-sm);
    border: 1px solid transparent;
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.2s ease, border-color 0.2s ease;
    font-size: 0.92rem;
    font-weight: 600;
}

.account-menu-item i {
    width: 18px;
    text-align: center;
    color: #c9a0f5;
}

.account-menu-item:hover {
    background: rgba(167, 119, 227, 0.1);
    border-color: rgba(167, 119, 227, 0.18);
}

.account-menu-item.danger i,
.account-menu-item.danger {
    color: #ff8aa0;
}

.account-menu-item.danger:hover {
    background: rgba(var(--color-danger-rgb), 0.08);
    border-color: rgba(var(--color-danger-rgb), 0.16);
}

/* Public Profile Page - Redesigned Premium Responsive Layout */
.public-profile-page {
    overflow-y: auto !important;
}

.public-profile-shell {
    min-height: 100%;
    display: flex;
    flex-direction: column;
    padding-bottom: 40px;
    background: #09090b;
}

/* Widescreen cover banner */
.public-profile-banner {
    position: relative;
    height: 220px;
    overflow: hidden;
    background: linear-gradient(180deg, rgba(20, 16, 28, 0.95), rgba(9, 9, 11, 1));
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.public-profile-banner-bg {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
}

.hero-glow {
    position: absolute;
    border-radius: 50%;
    filter: blur(80px);
    opacity: 0.35;
    animation: publicProfileDrift 15s ease-in-out infinite alternate;
}

.hero-glow-a {
    width: 320px;
    height: 320px;
    background: rgba(167, 119, 227, 0.3);
    top: -120px;
    left: -60px;
}

.hero-glow-b {
    width: 260px;
    height: 260px;
    background: rgba(110, 142, 251, 0.25);
    right: 5%;
    bottom: -100px;
    animation-delay: -5s;
}

@keyframes publicProfileDrift {
    from {
        transform: translate(0, 0) scale(1);
    }

    to {
        transform: translate(30px, -20px) scale(1.1);
    }
}

/* The public-profile back button. Every `!important` here is load-bearing and none of them
   is habit: dashboard.html ships this element as `class="settings-back-btn public-back-btn"`
   (:1459, :1655), and `.settings-back-btn` at styles.css:4367 is a later rule at equal
   specificity (0,1,0) that declares a different value for all five of these properties --
   white translucent fill, pill radius, smaller asymmetric padding. Drop any one flag and
   that property silently falls back to the Settings treatment, so the purple glass button
   turns into a white pill one property at a time.
   The real fix is to stop two components sharing one element, or to move this rule after
   :4367; both are source-order surgery on a 22k-line file and deliberately out of scope. */
.public-back-btn {
    position: absolute;
    top: 24px;
    left: 32px;
    z-index: 50;
    background: rgba(167, 119, 227, 0.1) !important;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(167, 119, 227, 0.25) !important;
    color: #e2d0ff !important;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    font-family: var(--font-display);
    border-radius: var(--border-radius-sm) !important;
    padding: 10px 20px !important;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
    transition:
        background 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        border-color 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        box-shadow 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        color 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.public-back-btn:hover {
    background: rgba(167, 119, 227, 0.2) !important;
    border-color: rgba(167, 119, 227, 0.5) !important;
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(167, 119, 227, 0.25);
    color: #ffffff !important;
}

.public-back-btn i {
    transition: transform 0.3s ease;
}

.public-back-btn:hover i {
    transform: translateX(-4px);
}

/* Two-Column Grid */
.public-profile-grid {
    display: grid;
    /* minmax(0, …) rather than a bare 1fr: `.public-mini-title` and
       `.up-char-card-name` are `white-space: nowrap`, so a real character
       name gives the column a min-content width in the hundreds of px, and
       an `auto` minimum lets the track grow to it. Measured at 360 with two
       seeded cards the column went to 600 and the overflow was silently
       clipped by `.main-content`'s `overflow: hidden` — no scrollbar, just
       missing card. Same reason for min-width: 0 on both columns below. */
    grid-template-columns: 340px minmax(0, 1fr);
    gap: 32px;
    padding: 0 32px;
    margin-top: -80px;
    position: relative;
    z-index: 10;
}

/* Left Sidebar Column */
.public-profile-sidebar {
    display: flex;
    flex-direction: column;
    gap: 24px;
    min-width: 0;
}

/* Profile Identity Card */
.public-profile-sidebar-card {
    background: linear-gradient(180deg, rgba(20, 16, 28, 0.8) 0%, rgba(14, 11, 20, 0.95) 100%);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    border: 1px solid rgba(167, 119, 227, 0.15);
    border-radius: var(--border-radius-lg);
    padding: 36px 28px 28px;
    box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
    text-align: center;
    position: relative;
    overflow: hidden;
    transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.4s ease;
}

.public-profile-sidebar-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: radial-gradient(circle at 50% 0%, rgba(167, 119, 227, 0.15) 0%, transparent 60%);
    pointer-events: none;
}

.public-profile-sidebar-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 35px 60px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 0 20px rgba(167, 119, 227, 0.15);
    border-color: rgba(167, 119, 227, 0.3);
}

.public-profile-avatar-container {
    display: flex;
    justify-content: center;
    margin-bottom: 24px;
    position: relative;
    z-index: 1;
}

.public-profile-avatar-ring {
    width: 128px;
    height: 128px;
    border-radius: 50%;
    padding: 4px;
    background: linear-gradient(135deg, #a777e3, #6e8efb, #ff71a2);
    background-size: 200% 200%;
    animation: avatarRingGradient 6s ease infinite;
    box-shadow: 0 12px 35px rgba(167, 119, 227, 0.35);
    transition: transform 0.4s ease;
}

.public-profile-sidebar-card:hover .public-profile-avatar-ring {
    transform: scale(1.05);
}

@keyframes avatarRingGradient {
    0% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }

    100% {
        background-position: 0% 50%;
    }
}

.public-profile-avatar {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    color: white;
    font-weight: 800;
    font-size: 2.8rem;
    font-family: var(--font-display);
    letter-spacing: -1px;
    text-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
    border: 4px solid #14101c;
    box-sizing: border-box;
}

.public-profile-info {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.public-profile-name {
    font-size: 1.8rem;
    font-weight: 800;
    color: #fff;
    letter-spacing: -0.5px;
    line-height: 1.2;
    margin-bottom: 6px;
}

.public-profile-handle-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 16px;
}

.public-profile-handle {
    color: #c9a0f5;
    font-weight: 700;
    font-size: 0.95rem;
}

.public-profile-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 3px 10px;
    border-radius: var(--border-radius-pill);
    font-size: 0.75rem;
    font-weight: 700;
    color: #fff;
    background: rgba(167, 119, 227, 0.16);
    border: 1px solid rgba(167, 119, 227, 0.28);
}

.public-profile-bio {
    color: var(--text-secondary);
    line-height: 1.6;
    font-size: 0.92rem;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    width: 100%;
}

/* Stats 2x2 Grid */
.public-profile-stats-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
}

.public-stat-card {
    padding: 16px 12px;
    border-radius: var(--border-radius-md);
    background: rgba(18, 14, 27, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.05);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    text-align: center;
    transition:
        background 0.25s cubic-bezier(0.4, 0, 0.2, 1),
        border-color 0.25s cubic-bezier(0.4, 0, 0.2, 1),
        box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1),
        transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}

.public-stat-card:hover {
    transform: translateY(-3px);
    background: rgba(167, 119, 227, 0.08);
    border-color: rgba(167, 119, 227, 0.25);
    box-shadow: 0 12px 28px rgba(167, 119, 227, 0.15);
}

.public-stat-value {
    display: block;
    font-size: 1.4rem;
    font-weight: 800;
    color: #fff;
    line-height: 1;
}

.public-stat-label {
    display: block;
    margin-top: 6px;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-secondary);
    font-weight: 700;
}

/* Right Main Column */
.public-profile-main {
    display: flex;
    flex-direction: column;
    gap: 24px;
    padding-top: 16px;
    min-width: 0;
}

/* Tab Bar Capsule */
.public-profile-tabs {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    background: rgba(18, 14, 27, 0.5);
    backdrop-filter: blur(15px);
    -webkit-backdrop-filter: blur(15px);
    border: 1px solid rgba(255, 255, 255, 0.05);
    padding: 8px;
    border-radius: var(--border-radius-md);
    align-self: start;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}

.public-profile-tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--text-secondary);
    padding: 8px 16px;
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    font-weight: 700;
    font-size: 0.88rem;
    transition:
        background 0.22s ease,
        border-color 0.22s ease,
        box-shadow 0.22s ease,
        color 0.22s ease;
}

.public-profile-tab:hover {
    color: #fff;
    background: rgba(255, 255, 255, 0.04);
}

.public-profile-tab.active {
    color: #fff;
    background: rgba(167, 119, 227, 0.16);
    border-color: rgba(167, 119, 227, 0.25);
    box-shadow: 0 4px 12px rgba(167, 119, 227, 0.1);
}

.public-profile-panels {
    width: 100%;
}

.public-profile-panel {
    display: none;
    animation: fadeTabIn 0.35s ease forwards;
}

.public-profile-panel.active {
    display: block;
}

.public-panel-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 20px;
}

/* ═════════════════════════════════════════════════════════════════════
   THE SURFACE PRIMITIVE

   Five classes were five hand-rolled versions of one glass tile:
   .public-panel-card, .shop-card, .qm-card and -- because dashboard.js
   writes class="shop-card quest-card" and class="shop-card
   achievement-card" -- .quest-card and .achievement-card. So .shop-card
   was already the de-facto primitive for three of the five. This makes
   that explicit and gives the other two the same surface.

   The four legacy names are in the selector list rather than replaced by
   .surface because dashboard.js emits them into markup; .surface is here
   so the next tile gets the surface without a sixth hand-rolled copy.

   What had actually drifted, measured at 1440/768/390 by
   llshots/cardsurf.mjs (which injects synthetic nodes carrying the exact
   class strings dashboard.js emits -- the real cards come from Firestore
   and are absent from the DOM when signed out):

     .qm-card   was 22px where its four siblings were 20px, had no
                resting shadow at all, and was the one class the mobile
                snap never covered -- so on a phone it sat at 22px
                against 16px neighbours. It now joins the system, which
                is the one intentional visible change in this step.

     radius     was declared nine times across five base rules and four
                media blocks, at two different breakpoints. Now twice:
                --surface-radius, and its one @media redefinition.

   .public-panel-card keeps its own flatter, wider shadow and is not in
   the depth group below: it is a content panel that happens to be a
   tile, it has no hover state, and it does not clip its children. Do
   not "finish the job" by adding overflow:hidden to it.
   ═════════════════════════════════════════════════════════════════════ */
.surface,
.public-panel-card,
.shop-card,
.qm-card {
    border-radius: var(--surface-radius);
}

.surface,
.shop-card,
.qm-card {
    position: relative;
    overflow: hidden;
    box-shadow: var(--surface-shadow);
    transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.35s ease;
}

.public-panel-card {
    background: rgba(18, 14, 27, 0.6);
    border: 1px solid var(--border-color);
    padding: 24px;
    box-shadow: 0 12px 34px rgba(0, 0, 0, 0.22);
}

.public-panel-title {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 20px;
    color: #fff;
    font-weight: 800;
    font-size: 1.1rem;
}

.public-panel-title i {
    color: #a777e3;
}

.public-card-list,
.public-activity-list {
    display: grid;
    gap: 12px;
}

.public-card-list--grid {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}

.public-mini-card {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    border-radius: var(--border-radius-md);
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.05);
    min-width: 0;
    transition: background 0.22s ease, border-color 0.22s ease, transform 0.22s ease;
}

.public-mini-card:hover {
    transform: translateY(-2px);
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(167, 119, 227, 0.2);
}

.public-mini-avatar {
    width: 44px;
    height: 44px;
    border-radius: var(--border-radius-sm);
    flex-shrink: 0;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.18), rgba(110, 142, 251, 0.12));
    border: 1px solid rgba(167, 119, 227, 0.18);
    display: grid;
    place-items: center;
    color: #fff;
    font-weight: 800;
    font-size: 1.1rem;
    background-size: cover;
    background-position: center;
}

.public-mini-copy {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.public-mini-title {
    color: #fff;
    font-weight: 700;
    font-size: 0.98rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.public-mini-subtitle {
    color: var(--text-secondary);
    font-size: 0.82rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.empty-mini-state {
    padding: 24px;
    border-radius: var(--border-radius-md);
    background: rgba(255, 255, 255, 0.015);
    color: var(--text-secondary);
    text-align: center;
    font-size: 0.9rem;
    border: 1px dashed rgba(255, 255, 255, 0.08);
}

.public-about-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

.public-about-item {
    padding: 16px 18px;
    border-radius: var(--border-radius-md);
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.05);
}

.public-about-label {
    display: block;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    font-size: 0.68rem;
    color: var(--text-secondary);
    font-weight: 700;
    margin-bottom: 6px;
}

.public-about-value {
    color: #fff;
    font-weight: 700;
    font-size: 0.96rem;
}

.public-bio-block {
    margin: 0;
    color: var(--text-secondary);
    line-height: 1.7;
    font-size: 0.98rem;
}

/* ═══════════════════════════════════════════════════════════════
   USER PROFILE — Follow Button & Character Grid
   ═══════════════════════════════════════════════════════════════ */

/* Follow Button Container */
.up-follow-container {
    width: 100%;
    margin-top: 16px;
    display: flex;
    justify-content: center;
}

.up-follow-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 12px 32px;
    border: none;
    border-radius: var(--border-radius-pill);
    font-size: 0.95rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    transition:
        background 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    overflow: hidden;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    color: var(--on-brand-gradient);
    box-shadow: 0 4px 20px rgba(167, 119, 227, 0.35);
}

.up-follow-btn::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), transparent);
    opacity: 0;
    transition: opacity 0.3s;
}

.up-follow-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 28px rgba(167, 119, 227, 0.5);
}

.up-follow-btn:hover::before {
    opacity: 1;
}

.up-follow-btn:active {
    transform: translateY(0) scale(0.97);
}

/* Following state */
.up-follow-btn.following {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.12);
    color: #a1a1aa;
    box-shadow: none;
}

.up-follow-btn.following:hover {
    background: rgba(var(--color-danger-rgb), 0.12);
    border-color: rgba(var(--color-danger-rgb), 0.35);
    color: var(--color-danger);
    box-shadow: 0 4px 20px rgba(var(--color-danger-rgb), 0.15);
}

/* Own profile state */
.up-follow-btn.own-profile {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: var(--text-secondary);
    cursor: default;
    box-shadow: none;
    pointer-events: none;
}

/* Follow sparkle animation */
@keyframes followSparkle {
    0% {
        transform: scale(0) rotate(0deg);
        opacity: 1;
    }

    50% {
        transform: scale(1.2) rotate(180deg);
        opacity: 0.8;
    }

    100% {
        transform: scale(0) rotate(360deg);
        opacity: 0;
    }
}

.up-follow-btn.sparkle::after {
    content: '✨';
    position: absolute;
    font-size: 1.2rem;
    animation: followSparkle 0.6s ease-out forwards;
    pointer-events: none;
}

/* Character Grid for User Profile */
.up-char-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
}

.up-char-card {
    background: rgba(18, 14, 27, 0.7);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-md);
    overflow: hidden;
    transition:
        border-color 0.28s cubic-bezier(0.4, 0, 0.2, 1),
        box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1),
        transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
    cursor: pointer;
    position: relative;
}

.up-char-card::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.08), transparent);
    opacity: 0;
    transition: opacity 0.3s;
    pointer-events: none;
    z-index: 1;
}

.up-char-card:hover {
    transform: translateY(-4px);
    border-color: rgba(167, 119, 227, 0.3);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4), 0 0 20px rgba(167, 119, 227, 0.08);
}

.up-char-card:hover::before {
    opacity: 1;
}

.up-char-card-avatar {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    display: block;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.15), rgba(110, 142, 251, 0.1));
}

.up-char-card-avatar-placeholder {
    width: 100%;
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.15), rgba(110, 142, 251, 0.1));
    font-size: 2.5rem;
    font-weight: 800;
    color: rgba(255, 255, 255, 0.5);
}

.up-char-card-body {
    padding: 14px 16px;
    position: relative;
    z-index: 2;
}

.up-char-card-name {
    font-weight: 700;
    color: #fff;
    font-size: 0.95rem;
    margin-bottom: 4px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.up-char-card-tagline {
    color: var(--text-secondary);
    font-size: 0.8rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-bottom: 8px;
}

.up-char-card-stats {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.75rem;
    color: var(--text-secondary);
}

.up-char-card-stats i {
    color: #a777e3;
    font-size: 0.7rem;
}

/* Clickable author link */
.card-creator-link,
.char-profile-author-link {
    cursor: pointer;
    transition: color 0.2s, text-decoration-color 0.2s;
    text-decoration: underline;
    text-decoration-color: transparent;
    text-underline-offset: 3px;
}

.card-creator-link:hover,
.char-profile-author-link:hover {
    color: #c9a0f5 !important;
    text-decoration-color: #c9a0f5;
}

/* Social card in followers/following lists */
.up-social-card {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    border-radius: var(--border-radius-md);
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.05);
    min-width: 0;
    transition: background 0.22s ease, border-color 0.22s ease, transform 0.22s ease;
    cursor: pointer;
}

.up-social-card:hover {
    transform: translateY(-2px);
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(167, 119, 227, 0.2);
}

/* Responsiveness Media Queries */
@media (max-width: 1100px) {
    .public-profile-grid {
        grid-template-columns: minmax(0, 1fr);
        margin-top: -60px;
        gap: 24px;
    }

    .public-profile-sidebar {
        max-width: 600px;
        width: 100%;
        margin: 0 auto;
    }

    .public-profile-main {
        padding-top: 0;
    }

    .public-profile-tabs {
        align-self: center;
    }
}

@media (max-width: 720px) {
    .public-profile-banner {
        height: 160px;
    }

    .public-profile-grid {
        padding: 0 16px;
        margin-top: -50px;
    }

    .public-back-btn {
        top: 16px;
        left: 16px;
    }

    .public-panel-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .public-about-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}


.main-content {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    position: relative;
    /* The one place the top/side insets are consumed for the whole app shell.
       This is a full-height border-box flex column whose last child is
       .mobile-tab-bar, so top padding shifts every view down out of the status
       bar while the tab bar's bottom edge stays pinned to the viewport — the
       bottom inset stays the tab bar's own job (~19000).

       Doing it here rather than per-view is deliberate: .top-nav is
       display:none on chat, diary and character-profile (~2147), so padding it
       would leave exactly those three views unprotected, and any view added
       later would start out unprotected too. All four values are 0 in a
       browser, so the live site is unchanged. */
    padding-top: var(--safe-top);
    padding-left: var(--safe-left);
    padding-right: var(--safe-right);
    overflow: hidden;
}

/* Above the mobile breakpoint .mobile-tab-bar is display:none, so nothing is
   left to absorb the bottom inset — on a tablet running the packaged app the
   last row of a view would sit under the navigation bar. --safe-bottom is 0 in
   a browser, so desktop is untouched. */
@media (min-width: 769px) {
    .main-content {
        padding-bottom: var(--safe-bottom);
    }
}

.top-nav {
    padding: 24px 32px;
    display: flex;
    align-items: center;
    background: linear-gradient(to bottom, rgba(9, 9, 11, 0.8), transparent);
    z-index: 10;
}

.search-container {
    background: rgba(255, 255, 255, 0.03);
    backdrop-filter: blur(10px);
    border-radius: var(--border-radius-pill);
    padding: 12px 20px;
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: 600px;
    width: 100%;
    border: 1px solid rgba(255, 255, 255, 0.1);
    transition: background 0.3s, border-color 0.3s, box-shadow 0.3s;
}

/* The search only applies to browsing souls, so it lives on Discover alone.
   Every other page (Shop, Achievements, Collection, Quests, Profile, ...)
   drops the field. */
.main-content:not(:has(#page-discover.active)) .search-container {
    display: none !important;
}

/* Hide Top Nav when chat, diary, or character profile is active to avoid pushing the UI down */
.main-content:has(#page-chat.active) .top-nav,
.top-nav:has(~ #page-chat.active),
.main-content:has(#page-diary.active) .top-nav,
.top-nav:has(~ #page-diary.active),
.main-content:has(#page-character-profile.active) .top-nav,
.top-nav:has(~ #page-character-profile.active) {
    display: none !important;
}

/* On desktop the strip holds nothing but the search, so once the search is
   gone the whole bar goes too rather than leaving an empty padded strip.
   Mobile keeps it — the hamburger lives there. */
@media (min-width: 769px) {
    .main-content:not(:has(#page-discover.active)) .top-nav {
        display: none !important;
    }

    /* These pages relied on the strip for their top gap and have no top
       padding of their own, so give it back. The profile pages are excluded:
       their banner is full-bleed by design. */
    #page-shop.active,
    #page-collection.active,
    #page-achievements.active,
    #page-quests.active {
        padding-top: 28px;
    }
}

.search-container:focus-within {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(167, 119, 227, 0.5);
    box-shadow: 0 0 20px rgba(167, 119, 227, 0.2);
}

.search-container i {
    color: var(--text-secondary);
}

.search-container input {
    background: transparent;
    border: none;
    color: var(--text-primary);
    width: 100%;
    outline: none;
    font-size: 1rem;
    font-family: var(--font-family);
}

/* Pages */
.page {
    display: none;
    flex-grow: 1;
    overflow-y: auto;
    padding: 0 32px 32px;
}

.page.active {
    display: flex;
    flex-direction: column;
    animation: fadeIn 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(20px) scale(0.98);
    }

    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

/* Discover Page - Premium UI */
.premium-hero {
    position: relative;
    padding: 30px 48px;
    border-radius: var(--border-radius-lg);
    background: rgba(20, 15, 30, 0.6);
    backdrop-filter: blur(20px);
    border: 1px solid rgba(167, 119, 227, 0.2);
    overflow: hidden;
    margin-bottom: 40px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
    flex-shrink: 0;
}

.hero-bg-elements {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
}

.hero-orb {
    position: absolute;
    border-radius: 50%;
    filter: blur(60px);
    opacity: 0.5;
    animation: floatOrb 10s ease-in-out infinite alternate;
}

/* The three hero orbs render at opacity 0.38 under blur(70px) — they are
   atmosphere, not accent. Atmosphere that leaves the brand hue family reads as
   a rendering bug, which is exactly what .orb-secondary was doing: it used to
   be radial-gradient(#ff416c, #ff4b2b) and threw a brick-red wash across the
   lower-right of the Discover hero, behind "STORIES TO WEAVE". .orb-accent was
   radial-gradient(#00f2fe, #4facfe), a third unrelated family. Both are now
   depth grades of the same violet: deep (secondary), mid (primary), light
   (accent). */
.orb-primary {
    width: 300px;
    height: 300px;
    background: radial-gradient(circle, var(--brand-accent), var(--brand-accent-2));
    top: -100px;
    left: -50px;
}

.orb-secondary {
    width: 250px;
    height: 250px;
    background: radial-gradient(circle, #7c5cc4, #3f4a9e);
    bottom: -120px;
    right: 10%;
    animation-delay: -5s;
}

.orb-accent {
    width: 150px;
    height: 150px;
    background: radial-gradient(circle, #c9a7f0, var(--brand-accent));
    top: 20%;
    left: 40%;
    animation-delay: -2s;
}

@keyframes floatOrb {
    0% {
        transform: translate(0, 0) scale(1);
    }

    100% {
        transform: translate(30px, -30px) scale(1.2);
    }
}

.hero-content {
    position: relative;
    z-index: 1;
    max-width: 600px;
}

.hero-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: rgba(167, 119, 227, 0.15);
    border: 1px solid rgba(167, 119, 227, 0.4);
    padding: 6px 14px;
    border-radius: var(--border-radius-pill);
    font-size: 0.8rem;
    font-weight: 700;
    color: #d6b4ff;
    margin-bottom: 20px;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.hero-badge i {
    color: #fff;
}

.welcome-back-header {
    font-size: 3.5rem;
    font-family: var(--font-display);
    font-weight: 800;
    line-height: 1.25;
    padding-top: 10px;
    margin-bottom: 16px;
    background: linear-gradient(to right, #fff, #a777e3, #6e8efb);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    letter-spacing: -1px;
}

/* The hero subtitle is declared ONCE, at line ~13858, for both class names it goes
   by. See the comment there; this rule used to be the first of two competing
   `.hero-subtitle` blocks and every declaration in it except line-height was
   already dead. */

.hero-actions {
    display: flex;
    gap: 16px;
}

.glass-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 24px;
    border-radius: var(--border-radius-sm);
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    transition:
        background 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        border-color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    border: none;
    outline: none;
}

.glass-btn.primary {
    background: var(--action-gradient);
    color: var(--on-brand-gradient);
    box-shadow: 0 4px 15px rgba(167, 119, 227, 0.4);
}

.glass-btn.primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(167, 119, 227, 0.6);
}

.glass-btn.secondary {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: #fff;
    backdrop-filter: blur(10px);
}

.glass-btn.secondary:hover {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.2);
    transform: translateY(-2px);
}

/* The only control on the dashboard that showed NOTHING on keyboard focus, and it took a
   forced-:focus-visible sweep to find it rather than a grep: .glass-btn declares both
   `border: none` and `outline: none` above, so it has no border to recolour and no ring to
   draw, and it has no :focus rule of its own. Nine shipping instances — the diary setup,
   unlock and settings save/cancel buttons in dashboard.html, the message-edit Save/Cancel
   pair, the achievement claim button and the achievement unlock banner — were all invisible
   to a keyboard.
   This has to be its own rule rather than left to the zero-specificity floor at the top of
   the file: `outline: none` in .glass-btn is (0,1,0) and beats the floor's (0,0,0). At (0,2,0)
   this wins. Offset is 3px rather than the floor's 2px because .glass-btn.primary carries
   `box-shadow: 0 4px 15px rgba(167,119,227,0.4)`, and a 2px ring sits inside that glow. */
.glass-btn:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
}

.hero-stats {
    position: relative;
    z-index: 1;
    display: flex;
    gap: 32px;
    background: rgba(0, 0, 0, 0.3);
    padding: 24px 32px;
    /* No border-radius here: a SECOND `.hero-stats` rule at :13544 sets 18px and,
       being later with equal specificity, wins. The 20px that used to sit on this
       line never rendered. Same story for padding (22px 30px wins), background
       (0.28 wins) and border (0.07 wins) — this rule now only contributes
       z-index, display and backdrop-filter. See the note on .filter-chip below:
       16 selectors in this region exist twice. */
    border: 1px solid rgba(255, 255, 255, 0.08);
    backdrop-filter: blur(10px);
}

.stat-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.stat-value {
    font-size: 1.8rem;
    font-weight: 800;
    font-family: var(--font-display);
    color: #fff;
}

.stat-label {
    font-size: 0.85rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 1px;
    font-weight: 600;
}

/* Premium Section Headers */
.premium-section-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    margin-bottom: 24px;
    padding-bottom: 12px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    flex-shrink: 0;
}

.premium-section-header h2 {
    font-size: 1.5rem;
    font-weight: 700;
    color: #fff;
    display: flex;
    align-items: center;
    gap: 12px;
}

.premium-section-header h2 i {
    color: #a777e3;
    font-size: 1.2rem;
}

.view-all-link {
    color: var(--text-secondary);
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 8px;
    transition: color 0.3s;
}

.view-all-link:hover {
    color: #a777e3;
}

/* Category Filters */
.category-filters {
    display: flex;
    gap: 12px;
}

.filter-chip {
    padding: 8px 16px;
    /* No border-radius here. `.filter-chip` is declared a SECOND time at :13635
       with the identical bare selector, so that later rule wins every property it
       repeats — including the radius, which is a full pill there, not the 20px
       this line used to claim. The chips have been pills the whole time.

       This is not an isolated slip: 27 top-level selectors in this file are
       declared twice, and 16 of them are one region written once around
       :2365-:3177 and again around :13469-:14069. Anything edited in the earlier
       copy silently does nothing. */
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: var(--text-secondary);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition:
        background 0.3s,
        border-color 0.3s,
        box-shadow 0.3s,
        color 0.3s,
        opacity 0.3s,
        transform 0.3s;
}

.filter-chip:hover {
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
}

.filter-chip.active {
    background: rgba(167, 119, 227, 0.2);
    border-color: rgba(167, 119, 227, 0.5);
    color: #d6b4ff;
    box-shadow: 0 0 15px rgba(167, 119, 227, 0.2);
}

@media (max-width: 900px) {
    .premium-hero {
        flex-direction: column;
        padding: 20px 24px;
        gap: 20px;
    }

    .hero-stats {
        width: 100%;
        justify-content: space-between;
    }

    .welcome-back-header {
        font-size: 2.5rem;
    }
}

/* ===== DISCOVER GRID — CSS Grid Layout ===== */
.character-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 18px;
    justify-content: start;
    margin-top: 20px;
    padding: 0 2px;
    /* Prevent box-shadow clipping on edges */
}

@media (max-width: 1600px) {
    .character-grid {
        grid-template-columns: repeat(4, 1fr);
        gap: 16px;
    }
}

@media (max-width: 1200px) {
    .character-grid {
        grid-template-columns: repeat(3, 1fr);
        gap: 16px;
    }
}

@media (max-width: 1000px) {
    .character-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 14px;
    }
}

/* Mobile: keep a dense 2-column grid like janitor.ai instead of collapsing to 1 */
@media (max-width: 600px) {
    .character-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
    }
}

/* ===== DISCOVER CARD — Rich Info Card ===== */
.character-card {
    background: linear-gradient(168deg, #140c1f 0%, #0e0816 100%);
    border-radius: var(--border-radius-md);
    display: flex;
    flex-direction: column;
    cursor: pointer;
    border: 1px solid rgba(167, 119, 227, 0.25);
    transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
        border-color 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
        box-shadow 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    position: relative;
    overflow: hidden;
    animation: cardReveal 0.5s ease backwards;
    box-shadow:
        0 0 8px rgba(167, 119, 227, 0.06),
        0 4px 20px rgba(0, 0, 0, 0.4);
}

/* Accent glow line at the top of the card. Both ends must be the same colour
   for the 200%-wide background-position slide to tile without a visible seam;
   the old gradient did that with #d946ef (Tailwind fuchsia-500), which is not a
   brand colour and appeared nowhere else on the card. Brand violet now bookends
   it instead. */
.character-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: linear-gradient(90deg, #a777e3, #6e8efb, #c9a7f0, #a777e3);
    background-size: 200% 100%;
    z-index: 5;
    opacity: 0.7;
    animation: neonShift 4s linear infinite;
}

@keyframes neonShift {
    0% {
        background-position: 0% 0%;
    }

    100% {
        background-position: 200% 0%;
    }
}

@keyframes cardReveal {
    from {
        opacity: 0;
        transform: translateY(16px) scale(0.97);
    }

    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

.character-card:hover {
    transform: translateY(-5px) scale(1.015);
    border-color: rgba(167, 119, 227, 0.5);
    box-shadow:
        0 0 20px rgba(167, 119, 227, 0.2),
        0 0 40px rgba(167, 119, 227, 0.08),
        0 16px 40px rgba(0, 0, 0, 0.5);
}

.character-card:hover::before {
    opacity: 1;
    height: 3px;
}

/* --- Card Title Bar --- */
.card-title-bar {
    padding: 10px 14px;
    font-family: var(--font-display);
    font-size: 0.9rem;
    font-weight: 800;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background: rgba(0, 0, 0, 0.35);
    border-bottom: 1px solid rgba(167, 119, 227, 0.1);
    letter-spacing: -0.3px;
    line-height: 1.3;
}

.card-title-bar span {
    font-family: 'Inter', sans-serif;
    font-weight: 400;
    font-size: 0.78rem;
    opacity: 0.45;
}

/* The wrapper the mobile marquee puts around the tagline text so the row above
   it can hold still and clip (see the <=560px block). Kept outside that media
   query on purpose: the selector above matches this nested span too, so if the
   resets dropped out at a wider width — a tablet rotating past 560px with the
   wrapper already in the DOM — the tagline would take a second 0.45 opacity
   (0.45 x 0.45 = barely visible) and snap back to 0.78rem. Neutral at every
   width; only the animation that moves it is breakpoint-scoped. */
.card-title-bar .card-title-text .card-title-slide {
    display: inline-block;
    font: inherit;
    opacity: 1;
    will-change: transform;
}

/* --- Card Hero Image Area — compact 5:6 ratio --- */
.card-hero-wrap {
    position: relative;
    width: 100%;
    aspect-ratio: 5 / 6;
    overflow: hidden;
    background: linear-gradient(135deg, #0d0815, #14091f);
}

.card-hero-wrap img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.character-card:hover .card-hero-wrap img {
    transform: scale(1.06);
}

/* Gradient overlay on bottom of image */
.card-hero-wrap::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 55%;
    background: linear-gradient(to top, #0e0816 5%, rgba(14, 8, 22, 0.7) 35%, transparent 100%);
    pointer-events: none;
}

/* Stats badge overlaid on image — pink tinted like reference */
.card-stats-overlay {
    position: absolute;
    top: 8px;
    right: 8px;
    display: flex;
    gap: 6px;
    z-index: 2;
}

.card-stat-badge {
    background: rgba(167, 119, 227, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(167, 119, 227, 0.35);
    border-radius: var(--border-radius-xs);
    padding: 4px 9px;
    font-family: var(--font-display);
    font-size: 0.7rem;
    font-weight: 700;
    color: #fff;
    display: flex;
    align-items: center;
    gap: 5px;
    letter-spacing: 0.3px;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

.card-stat-badge i {
    font-size: 0.6rem;
    color: #e2b3ff;
}

/* --- Owner action toolbar (edit / delete) — frosted group, reveals on hover --- */
.card-owner-actions {
    position: absolute;
    top: 8px;
    left: 8px;
    display: flex;
    gap: 6px;
    padding: 4px;
    z-index: 6;
    border-radius: var(--border-radius-sm);
    background: rgba(10, 6, 18, 0.55);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
    opacity: 0;
    transform: translateY(-6px) scale(0.96);
    transform-origin: top left;
    transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    pointer-events: none;
}

.character-card:hover .card-owner-actions,
.character-card:focus-within .card-owner-actions {
    opacity: 1;
    transform: translateY(0) scale(1);
    pointer-events: auto;
}

.card-owner-btn {
    width: 30px;
    height: 30px;
    border-radius: var(--border-radius-xs);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 0.75rem;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: #f5f0ff;
    transition: transform 0.18s ease, background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}

.card-owner-btn:hover {
    transform: translateY(-2px);
}

.card-owner-btn:active {
    transform: translateY(0) scale(0.92);
}

.card-owner-btn.edit:hover {
    background: rgba(167, 119, 227, 0.9);
    border-color: rgba(167, 119, 227, 0.6);
    color: #fff;
    box-shadow: 0 4px 14px rgba(167, 119, 227, 0.45);
}

.card-owner-btn.delete {
    color: #ff8a8a;
}

.card-owner-btn.delete:hover {
    background: rgba(220, 38, 38, 0.92);
    border-color: rgba(255, 74, 74, 0.6);
    color: #fff;
    box-shadow: 0 4px 14px rgba(220, 38, 38, 0.45);
}

/* Touch devices have no hover — keep owner actions visible but subtle */
@media (hover: none) {
    .card-owner-actions {
        opacity: 1;
        transform: none;
        pointer-events: auto;
    }
}

/* --- Card Body (below image) --- */
.card-body {
    padding: 14px 14px 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    position: relative;
    z-index: 1;
    margin-top: -24px;
    flex: 1;
}

.card-creator {
    font-family: 'Inter', sans-serif;
    font-size: 0.76rem;
    color: #c084fc;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 5px;
    letter-spacing: 0.2px;
}

.card-creator i {
    font-size: 0.55rem;
    color: #38bdf8;
}

/* Framed creator avatar chip (Discover cards) — carries the author's equipped
   frame so cosmetics show up in the browse grid. position:relative lets the
   .avatar-frame--* ::after ring anchor to it. */
.card-creator-avatar {
    position: relative;
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background-size: cover;
    background-position: center;
    background-color: #2c2740;
}

.card-creator-avatar--initial {
    display: grid;
    place-items: center;
    font-size: 0.6rem;
    font-weight: 800;
    color: var(--on-brand-gradient);
    background-image: linear-gradient(135deg, #a777e3, #6e8efb);
}

/* Frame rings on a 20px chip need a tighter inset than the default -3px. */
.card-creator-avatar[class*="avatar-frame--"]::after {
    inset: -2px;
}

/* Keep the tiny title badge on a card compact. */
.card-creator-title {
    font-size: 0.62rem;
    padding: 1px 6px;
    margin-left: 2px;
}

/* Description snippet — expands on hover */
.card-snippet {
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.62);
    font-style: normal;
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: 0.1px;
    max-height: 4.5em;
    /* ~3 lines default height */
    overflow: hidden;
    transition: max-height 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
        color 0.3s ease;
    position: relative;
}

.card-snippet-highlight {
    color: #ffffff;
    font-weight: 600;
    text-shadow: 0 0 12px rgba(255, 255, 255, 0.1);
}

/* Fade mask at bottom of truncated text */
.card-snippet::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 1.8em;
    background: linear-gradient(to top, #0e0816 0%, transparent 100%);
    pointer-events: none;
    transition: opacity 0.3s ease;
    opacity: 1;
}

/* On hover: expand description and remove fade */
.character-card:hover .card-snippet {
    max-height: 20em;
    /* Show much more text */
    color: rgba(255, 255, 255, 0.82);
}

.character-card:hover .card-snippet::after {
    opacity: 0;
}

/* --- Tags Section --- */
.card-tags-section {
    display: flex;
    flex-direction: column;
    gap: 7px;
    margin-top: auto;
    padding-top: 8px;
    border-top: 1px solid rgba(167, 119, 227, 0.08);
}

.card-tags-header {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.0rem;
    color: rgba(255, 255, 255, 0.0);
    text-transform: uppercase;
    letter-spacing: 1.5px;
    font-weight: 700;
    height: 0;
    overflow: hidden;
}

.card-tags-header i {
    font-size: 0;
    color: var(--brand-accent);
}

.card-tags-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.card-tag {
    font-family: var(--font-display);
    font-size: 0.66rem;
    font-weight: 600;
    padding: 3px 8px;
    border-radius: var(--border-radius-2xs);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.55);
    transition: border-color 0.25s ease;
    display: flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
    letter-spacing: 0.3px;
}

.character-card:hover .card-tag {
    border-color: rgba(255, 255, 255, 0.14);
}

.card-tag i {
    font-size: 0.55rem;
}

/* Tag color variants — more vivid to match reference */
.card-tag.tag-gender {
    border-color: rgba(79, 172, 254, 0.35);
    color: #7dd3fc;
    background: rgba(79, 172, 254, 0.1);
}

.card-tag.tag-genre {
    border-color: rgba(217, 70, 239, 0.35);
    color: #f0abfc;
    background: rgba(217, 70, 239, 0.1);
}

.card-tag.tag-pov {
    border-color: rgba(74, 222, 128, 0.35);
    color: #86efac;
    background: rgba(74, 222, 128, 0.1);
}

.card-tag.tag-setting {
    border-color: rgba(250, 204, 21, 0.35);
    color: #fde68a;
    background: rgba(250, 204, 21, 0.1);
}

.card-tag.tag-tone {
    border-color: rgba(255, 65, 108, 0.35);
    color: #fda4af;
    background: rgba(255, 65, 108, 0.1);
}

.card-tag.tag-misc {
    border-color: rgba(167, 119, 227, 0.3);
    color: #d8b4fe;
    background: rgba(167, 119, 227, 0.08);
}

/* --- Card Footer: Token Count --- */
.card-footer-bar {
    padding: 9px 14px;
    border-top: 1px solid rgba(167, 119, 227, 0.1);
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-family: var(--font-display);
    font-size: 0.7rem;
    color: rgba(255, 255, 255, 0.3);
    font-weight: 500;
    letter-spacing: 0.3px;
}

.card-footer-bar i {
    font-size: 0.6rem;
    margin-right: 4px;
    color: rgba(167, 119, 227, 0.5);
}

.card-play-hint {
    color: rgba(167, 119, 227, 0.55);
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 5px;
    transition: color 0.35s ease, gap 0.35s ease;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.8px;
}

.character-card:hover .card-play-hint {
    color: #e2b3ff;
    gap: 7px;
}

/* --- Legacy card classes kept for compatibility --- */
.card-header {
    display: flex;
    gap: 16px;
    align-items: center;
    position: relative;
}

.card-header img {
    width: 64px;
    height: 64px;
    border-radius: var(--border-radius-md);
    object-fit: cover;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.card-info .name {
    font-size: 1.2rem;
    font-weight: 600;
    margin-bottom: 4px;
}

.card-info .author {
    font-size: 0.85rem;
    color: var(--text-secondary);
}

.card-desc {
    font-size: 0.95rem;
    color: var(--text-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-clamp: 2;
    overflow: hidden;
    line-height: 1.5;
    position: relative;
}

.card-footer {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--text-secondary);
    font-size: 0.85rem;
    margin-top: auto;
    font-weight: 500;
}

/* Chat Page Specifics */
#page-chat {
    padding: 0;
    display: none;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    position: relative;
    /* Prevent the page itself from scrolling — only .chat-messages scrolls */
    /* position: relative also keeps the composer's overlays inside the page */
}

/* Floating "jump to latest" button — shown only when scrolled up from the bottom.
   Positioned against `.chat-input-area`, not the page: the composer grows with
   the macro row, a multi-line draft and the safe-area inset, and the three
   hardcoded `bottom:` values this replaced (140 / 160 / 118px) were each a guess
   at one of those states. */
#scroll-to-bottom-btn {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 6px);
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #a777e3, #6e3de7);
    color: #fff;
    font-size: 1rem;
    border: none;
    cursor: pointer;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
    opacity: 0;
    transform: translateX(-50%) translateY(12px) scale(0.9);
    pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
    z-index: 20;
}

#scroll-to-bottom-btn.visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1);
    pointer-events: auto;
}

#scroll-to-bottom-btn:hover {
    filter: brightness(1.08);
    transform: translateX(-50%) translateY(-2px) scale(1.05);
}

#page-chat.active {
    display: flex;
}

.chat-header {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 12px 32px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    background: rgba(9, 9, 11, 0.7);
    backdrop-filter: blur(16px);
    z-index: 10;
    flex-shrink: 0;
    /* Never let header get crushed */
}

.chat-character-info {
    display: flex;
    align-items: center;
    gap: 16px;
}

.chat-character-info img {
    width: 54px;
    height: 54px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid rgba(167, 119, 227, 0.55);
    box-shadow: 0 0 0 3px rgba(167, 119, 227, 0.15), 0 4px 14px rgba(0, 0, 0, 0.4);
    flex-shrink: 0;
    transition: box-shadow 0.3s ease, transform 0.3s ease;
}

.chat-character-info img:hover {
    box-shadow: 0 0 0 4px rgba(167, 119, 227, 0.3), 0 6px 20px rgba(167, 119, 227, 0.25);
    transform: scale(1.05);
}

.chat-character-info .details .name {
    font-weight: 700;
    font-size: 1.15rem;
    letter-spacing: 0.01em;
    color: #f4eefc;
    line-height: 1.2;
}

.chat-character-info .details .by {
    font-size: 0.8rem;
    color: var(--text-secondary);
}

/* Live status badge under the chat title */
.chat-status {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 3px;
}

.chat-status .status-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #34d399;
    box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.6);
    animation: status-pulse 2s ease-in-out infinite;
    flex-shrink: 0;
}

@keyframes status-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.55);
    }

    70% {
        box-shadow: 0 0 0 6px rgba(52, 211, 153, 0);
    }

    100% {
        box-shadow: 0 0 0 0 rgba(52, 211, 153, 0);
    }
}

.chat-status .status-text {
    font-size: 0.76rem;
    font-weight: 600;
    color: rgba(201, 178, 240, 0.75);
    letter-spacing: 0.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 320px;
}

.chat-messages {
    flex: 1 1 0;
    min-height: 0;
    /* Critical: allows flex child to shrink below content size so scrolling works */
    padding: 32px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 24px;
    /* Deliberately NO scroll-behavior: smooth. It made every programmatic
       scrollTop assignment animate — and a full renderMessages() rebuild wipes
       innerHTML first, which drops scrollTop to 0, so restoring the position
       animated visibly from the top down to the newest message. Smooth scrolling
       is now opt-in per call site (scrollTo({behavior:'smooth'}) on the
       jump-to-latest button), where it is actually wanted. */
}

.message-wrapper {
    display: flex;
    gap: 16px;
    max-width: 85%;
    animation: popIn 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    opacity: 0;
    transform-origin: bottom left;
}

.message-wrapper.user {
    align-self: flex-end;
    flex-direction: row-reverse;
    transform-origin: bottom right;
}

@keyframes popIn {
    0% {
        opacity: 0;
        transform: scale(0.8) translateY(20px);
    }

    100% {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

/* A full list rebuild re-creates every bubble, which replays popIn on the whole
   conversation — the entire chat fades and scales in again on any re-render
   (error notice, cosmetic change, remote sync). Rebuilt nodes get .instant so
   only genuinely NEW messages animate. Must restore opacity/transform too,
   since .message-wrapper starts at opacity:0 and relies on the animation's
   forwards fill to become visible. */
.message-wrapper.instant {
    animation: none;
    opacity: 1;
    transform: none;
}

.message-avatar {
    width: 52px;
    height: 52px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.message-avatar img,
.message-avatar .avatar {
    width: 52px;
    height: 52px;
    border-radius: var(--border-radius-md);
    object-fit: cover;
    object-position: center top;
    border: 2px solid rgba(167, 119, 227, 0.35);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
    transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.3s ease, border-radius 0.3s ease;
    flex-shrink: 0;
    display: block;
}

/* Text-based avatars (user initial) need a filled background and centered
   glyph — otherwise the letter floats at the top of an empty rounded box. */
.message-avatar .avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #6e8efb, #a777e3);
    color: #fff;
    font-weight: 700;
    font-size: 1.25rem;
    line-height: 1;
    text-transform: uppercase;
    box-sizing: border-box;
}


.message-wrapper:hover .message-avatar img,
.message-wrapper:hover .message-avatar .avatar {
    transform: scale(1.12) rotate(4deg);
    box-shadow: 0 6px 18px rgba(167, 119, 227, 0.3);
}

.message-content {
    background: var(--bg-card);
    backdrop-filter: blur(10px);
    padding: 16px 20px;
    border-radius: var(--border-radius-md);
    border-top-left-radius: var(--border-radius-2xs);
    font-size: 1rem;
    line-height: 1.75;
    border: 1px solid rgba(255, 255, 255, 0.05);
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

/* Keep bot prose at a comfortable reading measure (~68ch) rather than
   stretching across the full width of wide screens. */
.message-wrapper.bot .message-content {
    max-width: 68ch;
}

.message-wrapper:hover .message-content {
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
}

.message-wrapper.user .message-content {
    background: var(--accent-gradient);
    border: none;
    border-top-left-radius: var(--border-radius-md);
    border-top-right-radius: var(--border-radius-2xs);
    color: var(--on-brand-gradient);
    box-shadow: 0 4px 15px rgba(167, 119, 227, 0.3);
}

.message-wrapper.user:hover .message-content {
    box-shadow: 0 8px 25px rgba(167, 119, 227, 0.6);
}

/* System Error Message Styling */
.message-wrapper.system-error {
    align-self: center;
    max-width: 90%;
    margin: 20px 0;
    transform-origin: center;
    animation: systemPop 0.7s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}

.message-wrapper.system-error .message-content {
    background: linear-gradient(135deg, rgba(220, 53, 69, 0.10), rgba(167, 119, 227, 0.07));
    border: 1px solid rgba(220, 53, 69, 0.28);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
    text-align: center;
    border-radius: var(--border-radius-md);
    color: #cbd5e1;
    font-size: 0.92rem;
    line-height: 1.5;
}

/* Retry affordance on transient error notices */
.message-wrapper.system-error .system-error-actions {
    display: flex;
    justify-content: center;
    margin-top: 10px;
}

.message-wrapper.system-error .system-error-retry {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 18px;
    border-radius: var(--border-radius-pill);
    border: 1px solid rgba(167, 119, 227, 0.45);
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.18), rgba(110, 142, 251, 0.14));
    color: #e2e8f0;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.message-wrapper.system-error .system-error-retry:hover {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.30), rgba(110, 142, 251, 0.24));
    box-shadow: 0 4px 16px rgba(167, 119, 227, 0.3);
    transform: translateY(-1px);
}

.message-wrapper.system-error .system-error-retry:active {
    transform: translateY(0);
}

.message-wrapper.system-error .message-content h3 {
    color: var(--color-danger-deep);
    margin-bottom: 8px;
    font-size: 1.3rem;
}

.message-wrapper.system-error .system-icon {
    background: linear-gradient(135deg, var(--color-danger), var(--color-danger-deep));
    color: white;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 1.1rem;
    box-shadow: 0 4px 15px rgba(var(--color-danger-rgb), 0.4);
}

@keyframes systemPop {
    0% {
        opacity: 0;
        transform: scale(0.8) translateY(20px);
    }

    50% {
        transform: scale(1.02) translateY(-5px);
    }

    100% {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

/* Inline Message Editing UI */
.edit-message-container {
    display: flex;
    flex-direction: column;
    gap: 14px;
    width: 750px;
    max-width: 100%;
    margin-top: 4px;
}

.edit-message-textarea {
    width: 100%;
    min-height: 150px;
    background: rgba(10, 10, 12, 0.7);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid rgba(167, 119, 227, 0.45);
    border-radius: var(--border-radius-md);
    color: var(--text-primary);
    padding: 16px;
    font-family: var(--font-family);
    font-size: 1rem;
    line-height: 1.6;
    resize: vertical;
    outline: none;
    transition: border-color 0.25s ease, box-shadow 0.25s ease;
    box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5);
}

.edit-message-textarea:focus {
    border-color: rgba(167, 119, 227, 0.85);
    box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 14px rgba(167, 119, 227, 0.4);
}

.edit-actions-row {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
}

.edit-actions-row .glass-btn {
    padding: 10px 22px;
    font-size: 0.9rem;
    border-radius: var(--border-radius-sm);
}

.chat-input-area {
    position: relative;
    /* containing block for #scroll-to-bottom-btn */
    padding: 24px 32px 32px;
    padding-bottom: max(32px, var(--safe-bottom));
    /* gesture bar / home indicator */
    background: linear-gradient(transparent, rgba(9, 9, 11, 0.95) 30%);
    flex-shrink: 0;
    /* Never let this area get crushed by flex */
}

.chat-input-container {
    background: rgba(30, 30, 35, 0.7);
    backdrop-filter: blur(16px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    /* Deliberately NOT on the radius scale, and deliberately not a pill.

       A GROWING BOX MUST NOT USE A HEIGHT-RELATIVE RADIUS. This container wraps
       an auto-growing textarea: it is 64px tall with one line of input and keeps
       going as the user types. At 64px a 30px radius reads as a pill, which is
       what makes the empty composer look like an input; at 140px the same pill
       would render as a 70px sausage with the text floating in the middle of a
       blob. --border-radius-pill would do exactly that, and every numeric token
       is either visibly tighter than what ships (md = 16px) or wrong at the top
       of the range (lg = 24px still bows on a tall box).

       So 30px is a value chosen against the ONE-LINE height, and it stops being
       meaningful the moment the box changes height -- which is the definition of
       a value that does not belong on a scale. Same reasoning applies to
       .diary-input-container below.

       Of everything in this file this is the radius to be most careful with: a
       wrong radius on a shop card is invisible, a wrong radius on the composer
       is the first thing anyone sees on the chat screen. */
    border-radius: 30px;
    display: flex;
    align-items: center;
    padding: 10px 10px 10px 24px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
    transition: border-color 0.3s, box-shadow 0.3s;
}

.chat-input-container:focus-within {
    border-color: rgba(167, 119, 227, 0.5);
    box-shadow: 0 10px 30px rgba(167, 119, 227, 0.2);
}

.chat-input-container textarea {
    flex-grow: 1;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 1.05rem;
    outline: none;
    font-family: var(--font-family);
    resize: none;
    padding-top: 10px;
    padding-bottom: 10px;
    max-height: 180px;
    overflow-y: auto;
}

/* The rotating composer hint (dashboard.js, chatHintSet) cross-fades by
   toggling .ph-swapping on the container while it swaps the placeholder text.
   A placeholder only renders when the field is empty and the rotator only runs
   then, so fading this can never hide something the user typed.

   It fades `color` rather than `opacity` because ::placeholder is specified to
   accept the properties that apply to ::first-line, and opacity is not one of
   them — Chrome honours it anyway (measured), but color is the portable choice
   and needs no fallback. Fading to `transparent` interpolates premultiplied, so
   the grey does not darken on its way out. */
.chat-input-container textarea::placeholder {
    transition: color 0.25s ease;
}

.chat-input-container.ph-swapping textarea::placeholder {
    color: transparent;
}

/* Primary send action — echoes the brand accent so it clearly reads as
   the main action rather than a neutral grey circle. */
#send-btn {
    background: var(--accent-gradient);
    border: none;
    color: #fff;
    width: 42px;
    height: 42px;
    font-size: 1.05rem;
    flex-shrink: 0;
    box-shadow: 0 4px 14px rgba(167, 119, 227, 0.35);
}

#send-btn:hover {
    background: var(--accent-gradient);
    color: #fff;
    transform: scale(1.08);
    box-shadow: 0 6px 20px rgba(167, 119, 227, 0.55);
}

/* RP Macros Bar */
.rp-macros {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
    padding: 0 4px;
}

.macro-btn {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--border-radius-pill);
    padding: 6px 14px;
    color: var(--text-secondary);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.2s, border-color 0.2s, color 0.2s, transform 0.2s;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.macro-btn:hover {
    background: rgba(167, 119, 227, 0.15);
    color: #e2b3ff;
    border-color: rgba(167, 119, 227, 0.4);
    transform: translateY(-2px);
}

.chat-input-container .icon-btn {
    background: var(--text-primary);
    color: var(--bg-main);
    width: 44px;
    height: 44px;
}

.chat-input-container .icon-btn:hover {
    background: var(--accent-color);
    color: #fff;
}

.char-counter {
    font-size: 0.7rem;
    color: var(--text-secondary);
    opacity: 0;
    transition: opacity 0.2s;
    white-space: nowrap;
    margin-right: 8px;
    user-select: none;
}

.char-counter.visible {
    opacity: 0.6;
}

.char-counter.warning {
    color: #f59e0b;
    opacity: 1;
}

.char-counter.limit {
    color: #ef4444;
    opacity: 1;
}

.voice-notice {
    text-align: center;
    font-size: 0.72rem;
    color: var(--text-secondary);
    margin-top: 10px;
    opacity: 0.45;
}

/* Scrollbars */
::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.1);
    border-radius: var(--border-radius-sm);
}

::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.2);
}

/* Typings animation for Bot */
.typing-dots {
    display: flex;
    gap: 4px;
    padding: 8px 4px;
}

.typing-dots span {
    width: 6px;
    height: 6px;
    background: var(--text-secondary);
    border-radius: 50%;
    animation: typingBot 1.4s infinite;
}

.typing-dots span:nth-child(2) {
    animation-delay: 0.2s;
}

.typing-dots span:nth-child(3) {
    animation-delay: 0.4s;
}

@keyframes typingBot {

    0%,
    100% {
        opacity: 0.3;
        transform: translateY(0);
    }

    50% {
        opacity: 1;
        transform: translateY(-4px);
    }
}

/* Markdown Rich Text Styling for Chat */
.markdown-body {
    font-family: var(--font-body);
    line-height: 1.5;
}

.markdown-body p {
    margin-bottom: 0.7em;
}

.markdown-body p:last-child {
    margin-bottom: 0;
}

.markdown-body strong {
    color: var(--primary-accent);
    font-weight: 600;
}

/* Bot Actions (Asterisks) */
.message-wrapper.bot .markdown-body em {
    color: #b9a3d9;
    font-style: italic;
    /* Narration/action reads as flowing prose — no box, no border. */
    background: transparent;
    padding: 0;
    border: none;
}

/* Spoken dialogue — the line the eye should land on first. */
.message-wrapper.bot .markdown-body .rp-say {
    color: #f4eefe;
    font-style: normal;
    font-weight: 500;
}

.message-wrapper.user .markdown-body .rp-say {
    color: #ffffff;
    font-style: normal;
    font-weight: 500;
}

/* User Actions (Asterisks) */
.message-wrapper.user .markdown-body em {
    color: #ffe9b8;
    font-style: italic;
    background: transparent;
    padding: 0;
    opacity: 0.9;
}

.markdown-body code {
    background: rgba(0, 0, 0, 0.4);
    padding: 2px 6px;
    border-radius: var(--border-radius-2xs);
    font-family: monospace;
    font-size: 0.9em;
    color: #ffd700;
}

.markdown-body pre {
    background: rgba(10, 10, 12, 0.8);
    border: 1px solid rgba(255, 255, 255, 0.1);
    padding: 12px;
    border-radius: var(--border-radius-xs);
    overflow-x: auto;
    margin-bottom: 0.7em;
}

.markdown-body pre code {
    background: transparent;
    padding: 0;
    color: #e2e8f0;
}

.markdown-body blockquote {
    border-left: 3px solid var(--primary-accent);
    padding-left: 10px;
    margin: 10px 0;
    color: var(--text-secondary);
    background: rgba(167, 119, 227, 0.05);
    border-radius: 0 var(--border-radius-2xs) var(--border-radius-2xs) 0;
}

/* Scene Setter Ultimate UI */
.scene-setter-card {
    background: rgba(15, 10, 25, 0.95);
    border: 1px solid rgba(167, 119, 227, 0.3);
    border-radius: var(--border-radius-md);
    margin: 20px auto;
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5), 0 0 30px rgba(167, 119, 227, 0.1) inset;
    backdrop-filter: blur(10px);
    text-align: left;
    animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}

.scene-setter-header {
    text-align: center;
    margin-bottom: 24px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    padding-bottom: 16px;
}

.scene-setter-header h3 {
    font-size: 1.5rem;
    color: #fff;
    margin-bottom: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
}

.scene-setter-header h3 i {
    color: var(--primary-accent);
}

.scene-setter-header p {
    font-size: 0.9rem;
    color: var(--text-secondary);
}

.scene-form-group {
    margin-bottom: 16px;
}

/* `.scene-form-group label` is declared again ~480 lines below, at equal specificity
   and later in the file, so it won every property this block used to set. Only the
   surviving rule is kept. */

.scene-select,
.scene-textarea {
    width: 100%;
    background: rgba(0, 0, 0, 0.4);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--border-radius-xs);
    color: #fff;
    font-family: var(--font-family);
    font-size: 0.95rem;
    padding: 12px 14px;
    outline: none;
    transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}

.scene-select {
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23e2b3ff%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
    background-repeat: no-repeat;
    background-position: right 14px top 50%;
    background-size: 10px auto;
    padding-right: 30px;
}

.scene-select:hover,
.scene-textarea:hover {
    border-color: rgba(167, 119, 227, 0.3);
}

.scene-select:focus,
.scene-textarea:focus {
    background: rgba(0, 0, 0, 0.6);
    border-color: var(--primary-accent);
    box-shadow: 0 0 15px rgba(167, 119, 227, 0.2);
}

.scene-textarea {
    resize: none;
    height: 100px;
    line-height: 1.5;
}

.scene-select option {
    background: #1a1525;
    color: #fff;
    padding: 10px;
}

/* Premium Back Button */
#back-to-discover.glass-back-btn {
    background: rgba(20, 15, 25, 0.6);
    border: 1px solid rgba(255, 255, 255, 0.05);
    color: var(--text-secondary);
    border-radius: var(--border-radius-pill);
    padding: 8px 16px 8px 14px;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.9rem;
    font-weight: 600;
    transition:
        background 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        border-color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    margin-right: 16px;
    cursor: pointer;
    backdrop-filter: blur(10px);
}

#back-to-discover.glass-back-btn i {
    font-size: 0.85rem;
    transition: transform 0.3s ease;
}

#back-to-discover.glass-back-btn:hover {
    background: rgba(167, 119, 227, 0.15);
    color: #fff;
    border-color: rgba(167, 119, 227, 0.4);
    box-shadow: 0 8px 20px rgba(167, 119, 227, 0.2);
    transform: translateY(-2px);
}

#back-to-discover.glass-back-btn:hover i {
    transform: translateX(-3px);
    color: var(--primary-accent);
}

/* Ultimate Scene Setter Button */
#start-scene-btn {
    background: linear-gradient(135deg, #a777e3, #6e3de7);
    color: white;
    border: none;
    border-radius: var(--border-radius-sm);
    font-size: 1.1rem;
    font-weight: 700;
    letter-spacing: 0.5px;
    padding: 16px;
    width: 100%;
    cursor: pointer;
    transition:
        background 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        box-shadow 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    position: relative;
    overflow: hidden;
    box-shadow: 0 8px 20px rgba(110, 61, 231, 0.3);
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    margin-top: 15px;
}

#start-scene-btn::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 50%;
    height: 100%;
    background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0) 100%);
    transform: skewX(-25deg);
    animation: shimmer 3s infinite;
}

@keyframes shimmer {
    0% {
        left: -100%;
    }

    20% {
        left: 200%;
    }

    100% {
        left: 200%;
    }
}

#start-scene-btn:hover {
    transform: translateY(-3px) scale(1.02);
    box-shadow: 0 12px 25px rgba(110, 61, 231, 0.5);
    background: linear-gradient(135deg, #b88bf2, #7a4aeb);
}

/* System Scenario Chat Bubble */
.message-wrapper.system-scenario {
    align-self: center;
    max-width: 90%;
    margin: 30px 0;
}

.message-wrapper.system-scenario .message-content {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.1), rgba(110, 61, 231, 0.05));
    border: 1px solid rgba(167, 119, 227, 0.4);
    text-align: center;
    color: rgba(210, 185, 255, 0.85);
    box-shadow: 0 8px 32px rgba(167, 119, 227, 0.1);
}

/* --- Premium Profile Settings UI --- */
/* ===== SEAMLESS PROFILE SETTINGS REDESIGN ===== */
.settings-wrapper {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    overflow-y: auto;
    padding: 0;
}

/* Hero Card — contains avatar, name, badges all in ONE element. No gap. */
.profile-hero-card {
    position: relative;
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 64px 40px 32px;
    background: linear-gradient(135deg, rgba(88, 28, 180, 0.35) 0%, rgba(30, 10, 60, 0.5) 100%);
    border-bottom: 1px solid rgba(167, 119, 227, 0.15);
    flex-shrink: 0;
    overflow: hidden;
    width: 100%;
    box-sizing: border-box;
}

/* Animated background blobs */
.hero-bg-orbs {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
}

.orb {
    position: absolute;
    border-radius: 50%;
    filter: blur(60px);
    opacity: 0.25;
    animation: orbFloat 8s ease-in-out infinite alternate;
}

.orb1 {
    width: 300px;
    height: 300px;
    background: #7b2ff7;
    top: -80px;
    left: -60px;
    animation-delay: 0s;
}

.orb2 {
    width: 200px;
    height: 200px;
    background: #a777e3;
    bottom: -80px;
    right: 20px;
    animation-delay: 2s;
}

@keyframes orbFloat {
    from {
        transform: translate(0, 0) scale(1);
    }

    to {
        transform: translate(20px, 15px) scale(1.1);
    }
}

.hero-avatar {
    width: 90px;
    height: 90px;
    border-radius: 50%;
    background: rgba(167, 119, 227, 0.12);
    border: 2px solid rgba(167, 119, 227, 0.4);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.2rem;
    color: #a777e3;
    flex-shrink: 0;
    box-shadow: 0 0 24px rgba(167, 119, 227, 0.3);
    position: relative;
    z-index: 2;
}

.hero-info {
    position: relative;
    z-index: 2;
    flex: 1;
}

/* Premium animated back button */
.settings-back-btn {
    position: absolute;
    top: 14px;
    left: 20px;
    z-index: 10;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.12);
    color: rgba(255, 255, 255, 0.75);
    padding: 7px 16px 7px 10px;
    border-radius: var(--border-radius-pill);
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    letter-spacing: 0.5px;
    backdrop-filter: blur(10px);
    transition:
        background 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        border-color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    text-transform: uppercase;
    overflow: hidden;
}

.settings-back-btn .back-arrow-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    background: rgba(167, 119, 227, 0.25);
    border-radius: 50%;
    font-size: 0.7rem;
    transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
    flex-shrink: 0;
}

.settings-back-btn:hover {
    background: rgba(167, 119, 227, 0.2);
    border-color: rgba(167, 119, 227, 0.4);
    color: #fff;
    transform: translateX(0);
    box-shadow: 0 4px 20px rgba(167, 119, 227, 0.2);
}

.settings-back-btn:hover .back-arrow-icon {
    transform: translateX(-4px);
    background: rgba(167, 119, 227, 0.5);
}

.back-label {
    letter-spacing: 1px;
}

.hero-name {
    font-size: 1.8rem;
    font-weight: 800;
    background: linear-gradient(90deg, #fff 40%, #c9a0f5);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    margin: 0 0 10px 0;
    line-height: 1;
}

.hero-badges {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.hero-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 12px;
    border-radius: var(--border-radius-pill);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.5px;
}

.hero-badge.purple {
    background: rgba(167, 119, 227, 0.2);
    border: 1px solid rgba(167, 119, 227, 0.35);
    color: #e2b3ff;
}

.hero-badge.dark {
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: var(--text-secondary);
}

/* Two-column area directly below the hero — no gap */
.settings-edit-area {
    display: flex;
    gap: 20px;
    padding: 24px 28px;
    flex: 1;
    min-height: 0;
    width: 100%;
    box-sizing: border-box;
}

/* Left panel: quick preview info + AI note */
.settings-left-sidebar {
    flex: 0 0 230px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.settings-section-title {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--text-secondary);
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
}

.info-chips {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.info-chip {
    display: flex;
    align-items: center;
    gap: 10px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: var(--border-radius-sm);
    padding: 10px 14px;
    font-size: 0.9rem;
    color: var(--text-secondary);
}

.ai-note {
    margin-top: auto;
    background: rgba(167, 119, 227, 0.07);
    border: 1px solid rgba(167, 119, 227, 0.2);
    border-radius: var(--border-radius-sm);
    padding: 16px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 0.82rem;
    line-height: 1.6;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.ai-note strong {
    color: #c9a0f5;
}

/* Right main form */
.settings-main-form {
    flex: 1;
    overflow-y: auto;
}

/* Shared glass panel */
.glass-panel {
    background: rgba(25, 20, 35, 0.6);
    backdrop-filter: blur(20px);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-md);
    padding: 26px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}

.creation-section {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent);
    border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: var(--border-radius-md);
    padding: 24px;
    margin-bottom: 24px;
    position: relative;
    overflow: hidden;
    backdrop-filter: blur(10px);
}

.creation-section-title {
    font-size: 1.1rem;
    font-weight: 700;
    color: #fff;
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
    padding-bottom: 12px;
}

/* Input grid */
.input-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 22px;
}

/* ===== PREMIUM FORM GROUP ===== */
.scene-form-group {
    margin-bottom: 0;
    position: relative;
}

/* ---- Form labels: one system ---------------------------------------------------
   The product had two label idioms for the same job, and the difference was
   typography, not markup:

     .stg-label (settings)          12.8px  w600  rgba(255,255,255,.75)  ls 0.2px  sentence case
     .scene-form-group label (here) 11.5px  w600  rgba(201,160,245,.85)  ls 1.6px  UPPERCASE

   Settings' system wins, on two grounds. It is the more legible of the two -- 12.8px
   near-white beats 11.5px in a purple tint at the same weight -- and uppercase micro
   labels with wide tracking and an accent tint on EVERY field is one of the specific
   tells the UI review called out. Kept from the old treatment: the 3px accent bar
   (::before below) and the focus-within colour shift, which are this form's own
   character and do not fight the shared type.

   What did NOT need fixing, contrary to the plan: these are real <label> elements,
   every one of them, so the placeholders ("e.g. Luna, Atlas, Kai...") are examples
   rather than labels and the vanish-on-input accessibility objection does not apply.
   They stay. */
.scene-form-group label {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 10px;
    font-size: 0.8rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.75);
    letter-spacing: 0.2px;
    position: relative;
    padding-left: 10px;
}

.scene-form-group label::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 14px;
    background: linear-gradient(180deg, #a777e3, #6e3de7);
    border-radius: var(--border-radius-2xs);
    opacity: 0.7;
    transition: box-shadow 0.3s ease, height 0.3s ease, opacity 0.3s ease;
}

.scene-form-group:focus-within label {
    color: #e2b3ff;
}

.scene-form-group:focus-within label::before {
    opacity: 1;
    height: 18px;
    box-shadow: 0 0 8px rgba(167, 119, 227, 0.5);
}

/* ===== PREMIUM INPUT FIELDS ===== */
.premium-input {
    width: 100%;
    background: rgba(15, 10, 25, 0.7);
    border: 1px solid rgba(167, 119, 227, 0.12);
    color: #f0eaf8;
    padding: 14px 16px;
    border-radius: var(--border-radius-sm);
    font-size: 0.93rem;
    font-family: 'Inter', sans-serif;
    transition:
        background 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        border-color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
        opacity 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    box-sizing: border-box;
    box-shadow:
        inset 0 1px 3px rgba(0, 0, 0, 0.3),
        0 1px 0 rgba(255, 255, 255, 0.03);
    letter-spacing: 0.2px;
}

.premium-input::placeholder {
    color: rgba(161, 161, 170, 0.4);
    font-style: italic;
    font-weight: 400;
    letter-spacing: 0.3px;
}

.premium-input:hover {
    border-color: rgba(167, 119, 227, 0.25);
    background: rgba(18, 12, 30, 0.8);
    box-shadow:
        inset 0 1px 3px rgba(0, 0, 0, 0.3),
        0 0 0 1px rgba(167, 119, 227, 0.06);
}

.premium-input:focus {
    outline: none;
    border-color: rgba(167, 119, 227, 0.55);
    background: rgba(20, 14, 35, 0.95);
    box-shadow:
        0 0 0 3px rgba(167, 119, 227, 0.1),
        0 0 20px rgba(167, 119, 227, 0.08),
        inset 0 1px 3px rgba(0, 0, 0, 0.2);
    color: #fff;
}

.premium-input.textarea {
    min-height: 120px;
    resize: vertical;
    line-height: 1.65;
    scrollbar-width: thin;
    scrollbar-color: rgba(167, 119, 227, 0.3) transparent;
}

.premium-input.textarea::-webkit-scrollbar {
    width: 5px;
}

.premium-input.textarea::-webkit-scrollbar-track {
    background: transparent;
}

.premium-input.textarea::-webkit-scrollbar-thumb {
    background: rgba(167, 119, 227, 0.25);
    border-radius: var(--border-radius-sm);
}

.premium-input.select {
    appearance: none;
    background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a777e3' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right 1rem center;
    background-size: 1em;
    cursor: pointer;
    padding-right: 42px;
}

.premium-input.select:hover {
    background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23c9a0f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
}

.premium-input.select option {
    background: #1a1228;
    color: #f0eaf8;
    padding: 8px;
}

/* Date input special styling */
.premium-input[type="date"] {
    color-scheme: dark;
    position: relative;
}

.premium-input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(0.6) sepia(1) saturate(3) hue-rotate(230deg);
    cursor: pointer;
    opacity: 0.6;
    transition: opacity 0.2s ease;
}

.premium-input[type="date"]:hover::-webkit-calendar-picker-indicator {
    opacity: 1;
}

/* ===== PREMIUM SAVE BUTTON ===== */
.ultimate-save-btn {
    width: 100%;
    background: linear-gradient(135deg, #a777e3 0%, #7b44d4 50%, #6e3de7 100%);
    background-size: 200% 200%;
    color: white;
    border: none;
    border-radius: var(--border-radius-md);
    padding: 18px 24px;
    font-size: 1rem;
    font-weight: 700;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    transition:
        box-shadow 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    box-shadow:
        0 8px 25px rgba(110, 61, 231, 0.3),
        inset 0 1px 0 rgba(255, 255, 255, 0.15);
    display: flex;
    justify-content: center;
    align-items: center;
    letter-spacing: 0.5px;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    animation: btnGradientShift 4s ease infinite;
    font-family: 'Inter', sans-serif;
}

@keyframes btnGradientShift {

    0%,
    100% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }
}

.ultimate-save-btn::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: var(--border-radius-md);
    border: 1px solid rgba(255, 255, 255, 0.1);
    pointer-events: none;
}

.ultimate-save-btn:hover {
    transform: translateY(-3px) scale(1.01);
    box-shadow:
        0 14px 35px rgba(110, 61, 231, 0.45),
        0 0 40px rgba(167, 119, 227, 0.15),
        inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.ultimate-save-btn:active {
    transform: translateY(-1px) scale(0.99);
    box-shadow:
        0 6px 18px rgba(110, 61, 231, 0.35),
        inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.ultimate-save-btn .btn-content {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 2px;
}

.ultimate-save-btn::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 60%;
    height: 100%;
    background: linear-gradient(to right,
            transparent 0%,
            rgba(255, 255, 255, 0.08) 30%,
            rgba(255, 255, 255, 0.2) 50%,
            rgba(255, 255, 255, 0.08) 70%,
            transparent 100%);
    transform: skewX(-25deg);
    z-index: 1;
    transition: none;
}

.ultimate-save-btn:hover::before {
    animation: premiumShimmer 2s ease infinite;
}

@keyframes premiumShimmer {
    0% {
        left: -100%;
    }

    100% {
        left: 200%;
    }
}

/* ===== ENHANCED GLASS PANEL FOR FORM ===== */
.settings-main-form.glass-panel {
    background: linear-gradient(170deg,
            rgba(30, 22, 48, 0.65) 0%,
            rgba(20, 15, 35, 0.7) 100%);
    backdrop-filter: blur(24px);
    border: 1px solid rgba(167, 119, 227, 0.1);
    border-radius: var(--border-radius-md);
    padding: 30px;
    box-shadow:
        0 8px 32px rgba(0, 0, 0, 0.35),
        0 1px 0 rgba(167, 119, 227, 0.06) inset;
    position: relative;
}

.settings-main-form.glass-panel::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg,
            transparent,
            rgba(167, 119, 227, 0.2) 30%,
            rgba(167, 119, 227, 0.3) 50%,
            rgba(167, 119, 227, 0.2) 70%,
            transparent);
    border-radius: var(--border-radius-md) var(--border-radius-md) 0 0;
}

/* Success toast */
.success-toast {
    margin-top: 18px;
    padding: 14px 18px;
    background: linear-gradient(135deg, rgba(46, 204, 113, 0.08), rgba(39, 174, 96, 0.12));
    border: 1px solid rgba(46, 204, 113, 0.25);
    color: #2ecc71;
    border-radius: var(--border-radius-sm);
    text-align: center;
    font-weight: 600;
    font-size: 0.9rem;
    display: none;
    animation: slideUpFade 0.4s ease forwards;
    backdrop-filter: blur(10px);
    box-shadow: 0 4px 16px rgba(46, 204, 113, 0.08);
}

@keyframes slideUpFade {
    from {
        opacity: 0;
        transform: translateY(8px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ===== SETTINGS TAB NAVIGATION ===== */
.settings-tab-nav {
    display: flex;
    gap: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    background: rgba(15, 12, 25, 0.5);
    padding: 0 28px;
    flex-shrink: 0;
}

.settings-tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 14px 22px;
    border: none;
    background: transparent;
    color: var(--text-secondary);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
    font-family: var(--font-family);
    letter-spacing: 0.3px;
    position: relative;
}

.settings-tab:hover {
    color: #e2b3ff;
    background: rgba(167, 119, 227, 0.05);
}

.settings-tab.active {
    color: #e2b3ff;
    border-bottom-color: #a777e3;
}

.settings-tab .orb-icon-svg {
    flex-shrink: 0;
}

/* Tab content panels */
.settings-tab-content {
    display: none;
    animation: fadeTabIn 0.35s ease forwards;
}

.settings-tab-content.active {
    display: block;
}

@keyframes fadeTabIn {
    from {
        opacity: 0;
        transform: translateY(6px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ===== ORB BADGE IN HERO ===== */
.hero-badge.orb-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: var(--border-radius-pill);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.5px;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.2), rgba(110, 61, 231, 0.15));
    border: 1px solid rgba(167, 119, 227, 0.35);
    color: #e2b3ff;
}

.hero-badge.gold {
    background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 165, 0, 0.15));
    border: 1px solid rgba(255, 215, 0, 0.4);
    color: #ffd700;
}

.orb-icon-svg {
    display: inline-block;
    vertical-align: middle;
}

/* Orb pulse animation */
.orb-pulse {
    animation: orbPulse 2.5s ease-in-out infinite;
}

@keyframes orbPulse {

    0%,
    100% {
        filter: drop-shadow(0 0 4px rgba(167, 119, 227, 0.4));
        transform: scale(1);
    }

    50% {
        filter: drop-shadow(0 0 12px rgba(167, 119, 227, 0.7));
        transform: scale(1.05);
    }
}

/* ===== SUBSCRIPTIONS GRID ===== */
.sub-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
    padding: 24px 28px;
}

/* Base subscription card */
.sub-card {
    background: rgba(25, 20, 35, 0.6);
    backdrop-filter: blur(20px);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-md);
    padding: 26px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.sub-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
}

/* --- Orb Balance Card --- */
.orb-balance-card {
    grid-column: 1 / -1;
    background: linear-gradient(135deg, rgba(30, 20, 50, 0.8), rgba(15, 10, 30, 0.9));
    border: 1px solid rgba(167, 119, 227, 0.15);
}

.sub-card-header {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 20px;
}

.orb-hero-icon {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: rgba(167, 119, 227, 0.1);
    border: 1px solid rgba(167, 119, 227, 0.3);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.sub-card-title {
    font-size: 1.15rem;
    font-weight: 700;
    color: #fff;
    margin: 0;
}

.sub-card-subtitle {
    font-size: 0.82rem;
    color: var(--text-secondary);
    margin: 2px 0 0 0;
}

.orb-balance-display {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 20px;
}

.orb-balance-number {
    font-size: 3rem;
    font-weight: 800;
    background: linear-gradient(135deg, #e2b3ff, #a777e3);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    line-height: 1;
}

.orb-balance-label {
    font-size: 0.9rem;
    color: var(--text-secondary);
    font-weight: 500;
}

/* Quota section */
.orb-quota-section {
    background: rgba(0, 0, 0, 0.2);
    border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: var(--border-radius-sm);
    padding: 16px;
}

.quota-label-row {
    display: flex;
    justify-content: space-between;
    font-size: 0.82rem;
    color: var(--text-secondary);
    margin-bottom: 10px;
    font-weight: 500;
}

.quota-bar {
    width: 100%;
    height: 8px;
    background: rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-sm);
    overflow: hidden;
    margin-bottom: 10px;
}

.quota-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, #a777e3, #6e8efb);
    border-radius: var(--border-radius-sm);
    transition: width 0.6s cubic-bezier(0.25, 0.8, 0.25, 1);
}

.quota-hint {
    font-size: 0.78rem;
    color: rgba(161, 161, 170, 0.7);
    margin: 0;
    line-height: 1.5;
}

/* --- Plan Card --- */
.plan-card {
    text-align: center;
}

.plan-card-icon {
    width: 52px;
    height: 52px;
    margin: 0 auto 16px;
    border-radius: 50%;
    background: rgba(110, 142, 251, 0.12);
    border: 1px solid rgba(110, 142, 251, 0.3);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
    color: #6e8efb;
}

.sub-plan-name {
    font-size: 1.3rem;
    font-weight: 700;
    color: #fff;
    margin: 0 0 8px 0;
}

.sub-plan-desc {
    font-size: 0.85rem;
    color: var(--text-secondary);
    line-height: 1.5;
    margin: 0 0 16px 0;
}

.plan-features {
    list-style: none;
    padding: 0;
    margin: 0 0 20px 0;
    text-align: left;
}

.plan-features li {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
    font-size: 0.88rem;
    color: var(--text-secondary);
    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}

.plan-features li:last-child {
    border-bottom: none;
}

.plan-features li i {
    color: #2ecc71;
    font-size: 0.8rem;
    width: 18px;
    text-align: center;
    flex-shrink: 0;
}

.plan-features li.muted {
    opacity: 0.45;
}

.plan-features li.muted i {
    color: var(--color-danger);
}

.sub-upgrade-btn {
    width: 100%;
    background: linear-gradient(135deg, #ffd700, #ff8c00);
    color: #1a1525;
    border: none;
    border-radius: var(--border-radius-sm);
    padding: 14px;
    font-size: 1rem;
    font-weight: 700;
    cursor: pointer;
    transition: box-shadow 0.3s ease, transform 0.3s ease;
    box-shadow: 0 6px 20px rgba(255, 215, 0, 0.25);
    font-family: var(--font-family);
}

.sub-upgrade-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 30px rgba(255, 215, 0, 0.4);
}

/* --- Premium Preview Card --- */
.premium-preview-card {
    position: relative;
    overflow: hidden;
    border: 1px solid rgba(255, 215, 0, 0.15);
    background: linear-gradient(160deg, rgba(40, 30, 10, 0.6), rgba(25, 18, 35, 0.8));
}

.premium-glow {
    position: absolute;
    top: -40px;
    right: -40px;
    width: 180px;
    height: 180px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255, 215, 0, 0.15) 0%, transparent 70%);
    pointer-events: none;
    animation: premiumGlowPulse 4s ease-in-out infinite alternate;
}

@keyframes premiumGlowPulse {
    from {
        opacity: 0.5;
        transform: scale(0.9);
    }

    to {
        opacity: 1;
        transform: scale(1.1);
    }
}

.premium-card-inner {
    position: relative;
    z-index: 2;
}

.premium-card-header {
    text-align: center;
    margin-bottom: 16px;
}

.premium-crown-icon {
    font-size: 2rem;
    color: #ffd700;
    margin-bottom: 8px;
    display: block;
    filter: drop-shadow(0 0 8px rgba(255, 215, 0, 0.4));
}

.premium-card-header h3 {
    font-size: 1.3rem;
    font-weight: 700;
    color: #ffd700;
    margin: 0 0 4px 0;
}

.premium-price {
    font-size: 0.9rem;
    color: rgba(255, 215, 0, 0.6);
    margin: 0;
    font-weight: 500;
}

.premium-features li i {
    color: #ffd700;
}


/* ===== Custom Confirm Modal ===== */
.confirm-overlay {
    position: fixed;
    inset: 0;
    background: rgba(8, 5, 12, 0.7);
    backdrop-filter: blur(10px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease;
}

.confirm-overlay.active {
    opacity: 1;
    visibility: visible;
}

.confirm-modal {
    background: linear-gradient(160deg, rgba(30, 22, 50, 0.95), rgba(18, 14, 28, 0.98));
    border: 1px solid rgba(167, 119, 227, 0.25);
    border-radius: var(--border-radius-md);
    padding: 36px 32px 28px;
    max-width: 400px;
    width: 90%;
    text-align: center;
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6), 0 0 40px rgba(167, 119, 227, 0.15);
    transform: scale(0.9) translateY(15px);
    transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.confirm-overlay.active .confirm-modal {
    transform: scale(1) translateY(0);
}

.confirm-icon-wrap {
    width: 56px;
    height: 56px;
    margin: 0 auto 18px;
    border-radius: 50%;
    background: rgba(167, 119, 227, 0.12);
    border: 1px solid rgba(167, 119, 227, 0.3);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    color: #c9a0f5;
    transition:
        background 0.3s ease,
        border-color 0.3s ease,
        box-shadow 0.3s ease,
        height 0.3s ease,
        padding 0.3s ease,
        width 0.3s ease;
}

.confirm-icon-wrap.has-avatar {
    width: 86px;
    height: 86px;
    background: linear-gradient(135deg, #a777e3 0%, #6c5ce7 100%);
    padding: 3px;
    border: none;
    box-shadow: 0 0 25px rgba(167, 119, 227, 0.4), 0 0 50px rgba(167, 119, 227, 0.15);
    animation: modalPop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    overflow: visible;
    position: relative;
    box-sizing: border-box;
}

.modal-avatar-img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    border: 2px solid #140e20;
    box-sizing: border-box;
}

.confirm-icon-wrap.has-avatar::after {
    content: '\f00c';
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    position: absolute;
    bottom: -2px;
    right: -2px;
    width: 26px;
    height: 26px;
    background: linear-gradient(135deg, #00b894, #00decb);
    border: 2px solid #140e20;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    color: white;
    box-shadow: 0 4px 10px rgba(0, 184, 148, 0.4);
    animation: badgePop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.2s both;
}

@keyframes modalPop {
    from {
        transform: scale(0.7);
        opacity: 0;
    }

    to {
        transform: scale(1);
        opacity: 1;
    }
}

@keyframes badgePop {
    from {
        transform: scale(0);
        opacity: 0;
    }

    to {
        transform: scale(1);
        opacity: 1;
    }
}

.confirm-modal h3 {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 800;
    color: #fff;
    margin: 0 0 12px;
}

.confirm-modal p {
    font-size: 0.92rem;
    color: var(--text-secondary);
    line-height: 1.6;
    margin: 0 0 28px;
}

.confirm-modal .highlight-name {
    color: #d6b4ff;
    font-weight: 700;
    text-shadow: 0 0 8px rgba(214, 180, 255, 0.2);
}

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

.confirm-btn {
    flex: 1;
    padding: 13px 18px;
    border-radius: var(--border-radius-sm);
    font-size: 0.92rem;
    font-weight: 700;
    cursor: pointer;
    border: none;
    transition:
        background 0.25s cubic-bezier(0.25, 0.8, 0.25, 1),
        box-shadow 0.25s cubic-bezier(0.25, 0.8, 0.25, 1),
        color 0.25s cubic-bezier(0.25, 0.8, 0.25, 1),
        transform 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}

.confirm-btn:active {
    transform: scale(0.97) translateY(0);
}

.confirm-btn.cancel {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: var(--text-secondary);
}

.confirm-btn.cancel:hover {
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
}

.confirm-btn.danger {
    background: linear-gradient(135deg, #e74c3c, #c0392b);
    color: white;
    box-shadow: 0 4px 16px rgba(231, 76, 60, 0.3);
}

.confirm-btn.danger:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(231, 76, 60, 0.5);
}

.confirm-btn.primary {
    background: var(--action-gradient);
    color: var(--on-brand-gradient);
    box-shadow: 0 4px 16px rgba(167, 119, 227, 0.35);
}

.confirm-btn.primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(167, 119, 227, 0.5);
}

/* ===== Featured Soul character picker ===== */
.feature-pick-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 18px 0 4px;
    max-height: 280px;
    overflow-y: auto;
    text-align: left;
}

.feature-pick-item {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 10px 12px;
    border-radius: var(--border-radius-sm);
    border: 1px solid rgba(167, 119, 227, 0.2);
    background: rgba(167, 119, 227, 0.06);
    color: #ece8f5;
    font-size: 0.95rem;
    cursor: pointer;
    transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.feature-pick-item:hover {
    background: rgba(167, 119, 227, 0.14);
    border-color: rgba(167, 119, 227, 0.45);
    transform: translateY(-1px);
}

.feature-pick-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
}

.feature-pick-name {
    flex: 1 1 auto;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.feature-pick-item i {
    color: rgba(201, 160, 245, 0.7);
    font-size: 0.85rem;
}

/* ===== Scene Shift Feature ===== */
.scene-shift-macro {
    border: 1px solid rgba(255, 193, 7, 0.2) !important;
}

.scene-shift-macro:hover {
    background: rgba(255, 193, 7, 0.1) !important;
    border-color: rgba(255, 193, 7, 0.4) !important;
    color: #ffc107 !important;
}

.her-turn-macro {
    border: 1px solid rgba(236, 72, 153, 0.2) !important;
}

.her-turn-macro:hover {
    background: rgba(236, 72, 153, 0.1) !important;
    border-color: rgba(236, 72, 153, 0.4) !important;
    color: #f472b6 !important;
}

.scene-shift-modal {
    max-width: 520px;
}

.scene-chips-section {
    text-align: left;
    margin-top: 16px;
}

.chip-label {
    display: block;
    font-size: 0.75rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 1.2px;
    margin-bottom: 10px;
}

.chip-label i {
    color: #a777e3;
    margin-right: 4px;
}

.scene-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.scene-chip {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: var(--text-secondary);
    padding: 7px 14px;
    border-radius: var(--border-radius-pill);
    font-size: 0.82rem;
    cursor: pointer;
    transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, color 0.2s ease;
    font-family: 'Inter', sans-serif;
}

.scene-chip:hover {
    background: rgba(167, 119, 227, 0.1);
    border-color: rgba(167, 119, 227, 0.3);
    color: #e2b3ff;
}

.scene-chip.active {
    background: rgba(167, 119, 227, 0.2);
    border-color: rgba(167, 119, 227, 0.5);
    color: #fff;
    box-shadow: 0 0 12px rgba(167, 119, 227, 0.2);
}

/* ===== Quick Action Split-Button ===== */
.action-btn-wrapper {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0;
    border-radius: var(--border-radius-sm);
    overflow: visible;
}

.action-main-btn {
    border-top-right-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
}

.action-toggle-btn {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-left: none;
    color: var(--text-secondary);
    padding: 0 8px;
    height: 100%;
    cursor: pointer;
    border-top-right-radius: var(--border-radius-sm);
    border-bottom-right-radius: var(--border-radius-sm);
    transition: background 0.2s ease, color 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}

.action-toggle-btn i {
    transition: transform 0.25s ease;
    font-size: 0.7rem;
}

.action-toggle-btn.menu-open i {
    transform: rotate(180deg);
}

.action-toggle-btn:hover,
.action-toggle-btn.menu-open {
    background: rgba(167, 119, 227, 0.18);
    color: #c9a0f5;
}

.quick-action-hover-menu {
    position: absolute;
    bottom: calc(100% + 12px);
    left: 0;
    background: linear-gradient(160deg, rgba(28, 19, 42, 0.96), rgba(16, 11, 26, 0.96));
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border: 1px solid rgba(167, 119, 227, 0.32);
    border-radius: var(--border-radius-md);
    padding: 12px 12px 14px;
    width: 300px;
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(167, 119, 227, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.05);
    opacity: 0;
    visibility: hidden;
    transform: translateY(12px) scale(0.94);
    transform-origin: bottom left;
    transition: opacity 0.25s ease, transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1), visibility 0.25s;
    z-index: 200;
    pointer-events: none;
}

.quick-action-hover-menu.menu-open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
    pointer-events: auto;
}

/* Header */
.qa-menu-header {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: #d9c2ff;
    padding: 0 4px 10px;
    margin-bottom: 4px;
    border-bottom: 1px solid rgba(167, 119, 227, 0.16);
}

.qa-menu-header i {
    font-size: 0.85rem;
    color: #b98cf0;
    filter: drop-shadow(0 0 6px rgba(167, 119, 227, 0.6));
    animation: qa-sparkle 2.4s ease-in-out infinite;
}

@keyframes qa-sparkle {

    0%,
    100% {
        transform: scale(1) rotate(0deg);
        opacity: 0.9;
    }

    50% {
        transform: scale(1.15) rotate(10deg);
        opacity: 1;
    }
}

.qa-menu-scroll {
    max-height: 320px;
    overflow-y: auto;
    overflow-x: hidden;
    padding-right: 4px;
    margin-right: -4px;
}

.qa-menu-scroll::-webkit-scrollbar {
    width: 6px;
}

.qa-menu-scroll::-webkit-scrollbar-thumb {
    background: rgba(167, 119, 227, 0.35);
    border-radius: var(--border-radius-xs);
}

.qa-group-label {
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: rgba(201, 160, 245, 0.6);
    font-weight: 700;
    padding: 8px 4px 6px;
}

.qa-chip-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 6px;
}

.hover-chip {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--border-radius-sm);
    height: 40px;
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 0 10px;
    cursor: pointer;
    color: #ece4f8;
    line-height: 1;
    text-align: left;
    overflow: hidden;
    position: relative;
    transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
    /* Staggered entrance */
    opacity: 0;
    transform: translateY(6px) scale(0.96);
}

.menu-open .hover-chip {
    animation: qa-chip-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

@keyframes qa-chip-in {
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

/* Cascade delay across the whole tray */
.menu-open .hover-chip:nth-child(1) {
    animation-delay: 0.02s;
}

.menu-open .hover-chip:nth-child(2) {
    animation-delay: 0.05s;
}

.menu-open .hover-chip:nth-child(3) {
    animation-delay: 0.08s;
}

.menu-open .hover-chip:nth-child(4) {
    animation-delay: 0.11s;
}

.menu-open .hover-chip:nth-child(5) {
    animation-delay: 0.14s;
}

.menu-open .hover-chip:nth-child(6) {
    animation-delay: 0.17s;
}

.menu-open .hover-chip:nth-child(7) {
    animation-delay: 0.20s;
}

.menu-open .hover-chip:nth-child(8) {
    animation-delay: 0.23s;
}

.qa-emoji {
    font-size: 1.15rem;
    line-height: 1;
    flex-shrink: 0;
    transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.qa-label {
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hover-chip:hover,
.hover-chip:active,
.hover-chip:focus-visible {
    background: rgba(167, 119, 227, 0.22);
    border-color: rgba(167, 119, 227, 0.55);
    transform: translateY(-2px) scale(1.03);
    box-shadow: 0 6px 18px rgba(167, 119, 227, 0.3);
    outline: none;
}

.hover-chip:hover .qa-emoji {
    transform: scale(1.25) rotate(-6deg);
}

/* Little shine sweep on hover */
.hover-chip::after {
    content: '';
    position: absolute;
    top: 0;
    left: -70%;
    width: 45%;
    height: 100%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.14), transparent);
    transform: skewX(-18deg);
    transition: left 0.5s ease;
    pointer-events: none;
}

.hover-chip:hover::after {
    left: 130%;
}

/* Tap feedback ripple when inserted */
.hover-chip.qa-inserted {
    animation: qa-pop 0.4s ease;
}

@keyframes qa-pop {
    0% {
        transform: scale(1);
    }

    40% {
        transform: scale(0.9);
        background: rgba(167, 119, 227, 0.5);
    }

    100% {
        transform: scale(1);
    }
}

/* ===== System Scenario Banner =====
   The four flags left below are load-bearing; the four that were removed were not, and the
   difference is worth recording because it is not guessable from this rule alone. This is a
   variant class on .message-content, so two other rules compete for the same element:

     .message-wrapper.system-scenario .message-content  (styles.css:4265) is (0,3,0) against
       this rule's (0,1,0) and declares its own background, border and color. It wins on
       specificity, so those three stay flagged.
     .message-content inside @media (max-width: 768px) declares line-height: 1.5 and sits
       later in the file, so it wins on source order below 768px. That one stays flagged too.

   border-radius, font-family and font-size had no rival that outranks this rule at 1440,
   1024, 768 or 390 -- .message-content and .markdown-body both declare them but sit earlier,
   so this rule already wins on order. color is shadowed by the (0,3,0) rule above, which
   sets the identical rgba(210, 185, 255, 0.85); it is kept unflagged as an explicit default
   rather than deleted, so the banner still has a colour if that wrapper rule ever moves. */
.scenario-banner {
    background: linear-gradient(135deg, rgba(110, 61, 231, 0.12), rgba(167, 119, 227, 0.08)) !important;
    border: 1px solid rgba(167, 119, 227, 0.35) !important;
    border-left: 3px solid rgba(167, 119, 227, 0.7) !important;
    border-radius: var(--border-radius-sm);
    font-family: 'Georgia', 'Times New Roman', serif;
    color: rgba(210, 185, 255, 0.85);
    font-size: 0.88rem;
    line-height: 1.7 !important;
    letter-spacing: 0.02em;
}

/* Headings inside the banner */
.scenario-banner h1,
.scenario-banner h2,
.scenario-banner h3,
.scenario-banner strong {
    color: #e2d0ff !important;
    font-family: 'Inter', sans-serif !important;
    font-weight: 600 !important;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-size: 0.75rem !important;
}

/* The ⏩ SCENE SHIFT title specifically */
.scenario-banner p:first-child strong,
.scenario-banner>p>strong:first-child {
    font-size: 0.8rem !important;
}

/* ===== Mobile Responsive ===== */

/* Hamburger toggle — hidden on desktop */
.mobile-menu-btn {
    display: none;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: var(--text-secondary);
    font-size: 1.1rem;
    width: 40px;
    height: 40px;
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    align-items: center;
    justify-content: center;
    transition: background 0.2s, color 0.2s;
    flex-shrink: 0;
}

.mobile-menu-btn:hover {
    background: rgba(167, 119, 227, 0.15);
    color: #c9a0f5;
}

/* Sidebar Overlay Backdrop — hidden on desktop */
.sidebar-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(4px);
    z-index: 99;
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
}

.sidebar-backdrop.active {
    opacity: 1;
    pointer-events: auto;
}

@media (max-width: 768px) {

    /* ---- Sidebar Drawer ---- */
    .sidebar {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        width: 280px;
        min-width: unset;
        transform: translateX(-100%);
        transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
        z-index: 100;
        box-shadow: none;
        /* Cancel the desktop `sidebarIn` intro animation. Its `both` fill-mode
           otherwise pins transform at translateX(0), overriding the drawer's
           translateX(-100%) and leaving the sidebar parked over the chat. */
        animation: none;
    }

    .sidebar.mobile-open {
        transform: translateX(0);
        box-shadow: 0 0 50px rgba(0, 0, 0, 0.6);
    }

    .sidebar-backdrop {
        display: block;
    }

    .mobile-menu-btn {
        display: flex;
    }

    /* ---- Top Nav ---- */
    .top-nav {
        padding: 14px 16px;
        gap: 12px;
    }

    /* Hide Top Nav exclusively when in chat UI to save major vertical space */
    .main-content:has(#page-chat.active) .top-nav,
    .top-nav:has(~ #page-chat.active) {
        display: none !important;
    }

    .search-container {
        padding: 10px 16px;
        border-radius: var(--border-radius-md);
    }

    .search-container input {
        font-size: 0.9rem;
    }

    /* ---- Pages ---- */
    .page {
        padding: 0 16px 16px;
    }

    /* ---- Discover Page ---- */
    .hero-section {
        padding: 10px 0 12px;
    }

    .greeting h1 {
        font-size: 1.3rem;
        margin-bottom: 2px;
    }

    .greeting p {
        font-size: 0.82rem;
    }

    .category-tags {
        gap: 8px;
        padding-bottom: 12px;
        /* Prevent tags from being cut off at edges */
        padding-left: 2px;
        padding-right: 2px;
    }

    .tag {
        padding: 7px 16px;
        font-size: 0.82rem;
        border-radius: var(--border-radius-pill);
    }

    .character-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
        margin-top: 4px;
    }

    /* Stay 2-up even on the narrowest phones, matching janitor.ai */
    @media (max-width: 400px) {
        .character-grid {
            gap: 8px;
        }
    }

    .character-card {
        border-radius: var(--border-radius-sm);
    }

    .character-card:hover {
        transform: none;
        box-shadow:
            0 0 8px rgba(167, 119, 227, 0.06),
            0 4px 20px rgba(0, 0, 0, 0.4);
    }

    /* Compact card internals so two fit cleanly side by side */
    .card-title-bar {
        padding: 8px 10px;
        font-size: 0.82rem;
    }

    /* Touch has no hover, so overflowing titles auto-slide to reveal the tagline.
       Ping-pong: hold at start, glide to reveal the end, hold, glide back. The
       shift distance is measured per-card in JS and set via --marquee-shift.

       Two selectors because the sliding element differs by width: above 560px
       the whole "name | tagline" line slides; at or below it the name is on its
       own line and only the tagline span moves, so the name stays put. JS picks
       which one gets the class. */
    .card-title-text.card-title-auto-marquee,
    .card-title-bar .card-title-auto-marquee {
        animation-name: cardTitleMarquee;
        animation-timing-function: ease-in-out;
        animation-iteration-count: infinite;
        animation-delay: 1.2s;
    }

    @keyframes cardTitleMarquee {

        0%,
        18% {
            transform: translateX(0);
        }

        50%,
        68% {
            transform: translateX(var(--marquee-shift, 0));
        }

        100% {
            transform: translateX(0);
        }
    }

    .card-body {
        padding: 10px 10px 10px;
        gap: 7px;
        margin-top: -20px;
    }

    .card-creator {
        font-size: 0.7rem;
    }

    .card-snippet {
        font-size: 0.78rem;
        line-height: 1.45;
        max-height: 4.35em;
    }

    .card-tag {
        font-size: 0.62rem;
        padding: 2px 6px;
    }

    .card-stat-badge {
        font-size: 0.64rem;
        padding: 3px 7px;
    }

    .card-footer-bar {
        padding: 7px 10px;
        font-size: 0.6rem;
        gap: 6px;
    }

    /* Tighten the CONNECT hint so it doesn't crowd the token count on narrow cards */
    .card-play-hint {
        font-size: 0.6rem;
        letter-spacing: 0.4px;
        gap: 4px;
        flex-shrink: 0;
    }

    .card-footer-bar>span:first-child {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    /* No hover-to-expand on touch — show a stable clamped snippet instead */
    .character-card:hover .card-snippet {
        max-height: 4.35em;
        color: rgba(255, 255, 255, 0.62);
    }

    .character-card:hover .card-snippet::after {
        opacity: 1;
    }

    .character-card:hover .card-hero-wrap img {
        transform: none;
    }

    .card-header {
        gap: 12px;
    }

    .card-header img {
        width: 44px;
        height: 44px;
        border-radius: var(--border-radius-sm);
    }

    .card-info .name {
        font-size: 1rem;
    }

    .card-info .author {
        font-size: 0.78rem;
    }

    .card-desc {
        font-size: 0.82rem;
        line-height: 1.4;
    }

    .card-footer {
        font-size: 0.78rem;
    }

    /* ---- Chat Page ---- */
    #page-chat {
        height: 100%;
        max-height: 100%;
        overflow: hidden;
    }

    .main-content {
        height: 100%;
        max-height: 100%;
        overflow: hidden;
    }

    .chat-header {
        padding: 10px 12px;
        gap: 8px;
        flex-shrink: 0;
    }

    .chat-header .glass-back-btn {
        padding: 6px 10px 6px 8px;
        font-size: 0.78rem;
        gap: 4px;
        margin-right: 4px;
    }

    .chat-header .glass-back-btn span {
        display: none;
        /* Hide "Hub" text on mobile to save space */
    }

    .chat-character-info img {
        width: 44px;
        height: 44px;
    }

    .chat-character-info .details .name {
        font-size: 0.9rem;
    }

    .chat-character-info .details .by {
        font-size: 0.72rem;
    }

    .chat-messages {
        padding: 10px 12px;
        gap: 8px;
        flex: 1 1 0;
        min-height: 0;
    }

    .message-wrapper {
        max-width: 88%;
        gap: 6px;
    }

    .message-content {
        padding: 8px 12px;
        font-size: 0.84rem;
        line-height: 1.5;
    }


    .message-avatar img,
    .message-avatar .avatar {
        width: 36px;
        height: 36px;
    }

    /* Disable hover effects on mobile */
    .message-wrapper:hover .message-content {
        transform: none;
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
    }

    .message-wrapper:hover .message-avatar img,
    .message-wrapper:hover .message-avatar .avatar {
        transform: none;
    }

    .character-card:hover {
        transform: none;
    }

    /* ---- Chat Input Area ---- */
    .chat-input-area {
        padding: 8px 10px;
        padding-bottom: max(10px, var(--safe-bottom));
        background: var(--bg-main);
        z-index: 50;
        flex-shrink: 0;
    }

    /* Anchored to the composer now, so no `bottom` per breakpoint — only the
       tap target, which was 38px against --tap-min: 44px. */
    #scroll-to-bottom-btn {
        right: auto;
        width: var(--tap-min);
        height: var(--tap-min);
    }

    .rp-macros {
        gap: 6px;
        flex-wrap: nowrap;
        overflow: visible;
        overflow-x: auto;
        overflow-y: visible;
        padding-bottom: 4px;
        margin-bottom: 4px;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }

    .rp-macros::-webkit-scrollbar {
        display: none;
    }

    .macro-btn,
    .action-btn-wrapper {
        font-size: 0.75rem;
        border-radius: var(--border-radius-md);
        flex-shrink: 0;
    }

    .macro-btn {
        padding: 6px 10px;
    }

    .action-main-btn {
        padding: 6px 10px;
    }

    .action-toggle-btn {
        padding: 0 8px;
        min-width: 28px;
    }

    .chat-input-container {
        padding: 6px 6px 6px 14px;
        /* Off the scale on purpose -- see the long note on the base rule. The
           mobile composer is 52px with one line, so its one-line radius is 18px
           rather than 30px. It is not var(--border-radius-md) despite being 2px
           away from it: this number tracks a height, not a tier, and putting it
           on the scale would invite someone to "fix the inconsistency" by
           snapping the desktop 30px too. */
        border-radius: 18px;
    }

    .chat-input-container textarea {
        font-size: 16px !important;
        /* Unconditionally force 16px to prevent ALL mobile browsers from auto-zooming */
        padding-top: 6px;
        padding-bottom: 6px;
    }

    .chat-input-container .icon-btn {
        width: 36px;
        height: 36px;
    }

    .voice-notice {
        font-size: 0.65rem;
        margin-top: 4px;
    }

    /* ---- Quick Action Hover Menu ---- */
    .quick-action-hover-menu {
        position: fixed;
        bottom: auto;
        left: 10px;
        right: 10px;
        width: auto;
        max-width: none;
        padding: 12px;
        z-index: 9000;
    }

    .qa-menu-scroll {
        max-height: 42vh;
    }

    .hover-chip {
        height: 42px;
        /* Larger touch targets on mobile */
    }

    .qa-emoji {
        font-size: 1.25rem;
    }

    .qa-label {
        font-size: 0.8rem;
    }

    /* ---- Modals ---- */
    .confirm-modal {
        max-width: 92vw !important;
        /* padding moved to the PHASE 5 — MODALS block at the end of this
           file. It was `24px 20px !important` here, which no later rule of
           any specificity could beat, so the memory-manager modals could
           not be tightened past it. The width cap stays: it is what
           actually governs modal width on a phone (92vw = 331 at 360). */
        margin: 0 12px;
    }

    #companion-select-overlay .confirm-modal {
        padding: 0 !important;
    }

    .scene-shift-modal {
        max-width: 92vw;
    }

    .scene-chips {
        gap: 6px;
    }

    .scene-chip {
        padding: 6px 12px;
        font-size: 0.78rem;
    }

    .confirm-actions {
        gap: 10px;
    }

    .confirm-btn {
        padding: 12px 18px;
        font-size: 0.88rem;
    }

    /* ---- Settings Page ---- */
    .settings-wrapper {
        height: auto;
        overflow-y: visible;
    }

    .profile-hero-card {
        padding: 52px 20px 24px;
        gap: 16px;
        flex-direction: column;
        align-items: center;
        text-align: center;
    }

    .hero-avatar {
        width: 72px;
        height: 72px;
        font-size: 1.8rem;
    }

    .hero-name {
        font-size: 1.4rem;
    }

    .hero-badges {
        justify-content: center;
    }

    .settings-edit-area {
        flex-direction: column;
        padding: 16px;
        gap: 16px;
        flex: unset;
    }

    .settings-left-sidebar {
        flex: unset;
        width: 100%;
    }

    .info-chips {
        flex-direction: row;
        flex-wrap: wrap;
    }

    .ai-note {
        margin-top: 8px;
    }

    .settings-main-form {
        overflow-y: visible;
    }

    .glass-panel {
        padding: 20px 16px;
    }

    .input-grid {
        grid-template-columns: 1fr !important;
    }

    .settings-back-btn {
        top: 10px;
        left: 12px;
        padding: 6px 12px 6px 8px;
        font-size: 0.75rem;
    }

    .settings-tab-nav {
        padding: 0 12px;
        overflow-x: auto;
    }

    .settings-tab {
        padding: 12px 16px;
        font-size: 0.82rem;
        white-space: nowrap;
    }

    .sub-grid {
        grid-template-columns: 1fr;
        padding: 16px;
        gap: 16px;
    }

    .orb-balance-number {
        font-size: 2.2rem;
    }

    .orb-hero-icon {
        width: 44px;
        height: 44px;
    }

    .orb-hero-icon .orb-icon-svg {
        width: 36px;
        height: 36px;
    }

    .scene-setter-card {
        margin: 12px;
    }

    .scene-setter-header h3 {
        font-size: 1.2rem;
    }

    /* ---- System Scenario Banners ---- */
    .message-wrapper.system-scenario {
        max-width: 100%;
    }

    .scenario-banner {
        font-size: 0.82rem !important;
    }

    /* ---- Back Button (Chat) ---- */
    #back-to-discover.glass-back-btn {
        padding: 6px 10px 6px 8px;
        font-size: 0.78rem;
        gap: 4px;
        margin-right: 4px;
    }

    /* ---- Chat textarea height limit ---- */
    .chat-input-container textarea {
        max-height: 80px;
        /* Prevent textarea from growing too tall on mobile */
    }

    /* ---- Hide voice notice on very small screens ---- */
    .voice-notice {
        display: none;
    }

    /* ---- Narrator blocks (inline styled green text) ---- */
    .message-content div[style*="4ade80"] {
        font-size: 0.82rem !important;
        padding: 8px 12px !important;
        margin: 10px 0 !important;
        line-height: 1.5 !important;
    }

    /* ---- Markdown inside messages ---- */
    .message-content p {
        margin-bottom: 0.3em;
        font-size: inherit;
    }

    .message-content p:last-child {
        margin-bottom: 0;
    }

    /* ---- Bot action text (italic highlighted) ---- */
    .message-content em {
        font-size: 0.82rem;
    }

    /* ---- Regenerate button row ---- */
    .regen-btn-row {
        margin-top: 2px;
    }

    .regen-btn-row button {
        font-size: 0.72rem !important;
        padding: 4px 10px !important;
    }
}

/* ===== Extra-small screens (< 400px) ===== */
@media (max-width: 400px) {
    .chat-header {
        padding: 8px 10px;
    }

    .chat-messages {
        padding: 6px 8px;
        gap: 6px;
    }

    .message-content {
        padding: 7px 10px;
        font-size: 0.8rem;
    }

    .chat-input-area {
        padding: 6px 8px;
        padding-bottom: max(8px, var(--safe-bottom));
    }

    .macro-btn {
        padding: 5px 8px;
        font-size: 0.7rem;
    }

    .greeting h1 {
        font-size: 1.4rem;
    }
}

/* ===== AI Refine Buttons ===== */
.ai-refine-btn {
    background: rgba(167, 119, 227, 0.15);
    border: 1px solid rgba(167, 119, 227, 0.3);
    color: #c9a0f5;
    padding: 6px 14px;
    border-radius: var(--border-radius-pill);
    font-size: 0.75rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.3s ease, opacity 0.3s ease, transform 0.3s ease;
}

.ai-refine-btn:hover {
    background: rgba(167, 119, 227, 0.25);
    transform: scale(1.05);
}

.ai-refine-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

/* Discover Carousel */
.featured-carousel {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    padding-bottom: 16px;
    scroll-snap-type: x mandatory;
    scrollbar-width: thin;
    scrollbar-color: rgba(167, 119, 227, 0.5) transparent;
    margin-bottom: 32px;
}

.featured-carousel::-webkit-scrollbar {
    height: 6px;
}

.featured-carousel::-webkit-scrollbar-track {
    background: transparent;
}

.featured-carousel::-webkit-scrollbar-thumb {
    background-color: rgba(167, 119, 227, 0.5);
    border-radius: var(--border-radius-sm);
}

.featured-card {
    min-width: 280px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    scroll-snap-align: start;
    background: rgba(30, 30, 35, 0.6);
    border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: var(--border-radius-md);
    overflow: hidden;
    position: relative;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    cursor: pointer;
}

.featured-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
    border-color: rgba(167, 119, 227, 0.4);
}

.featured-image {
    width: 100%;
    height: 160px;
    object-fit: cover;
}

.featured-info {
    padding: 16px;
}

.featured-title {
    font-weight: 700;
    font-size: 1.2rem;
    color: #fff;
    margin-bottom: 4px;
}

.featured-desc {
    font-size: 0.9rem;
    color: var(--text-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.section-header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
    font-size: 1.3rem;
    font-weight: 700;
    color: #fff;
}

.section-header i {
    color: #a777e3;
}

.choice-btn:hover {
    background: rgba(74, 222, 128, 0.1) !important;
    border-color: #4ade80 !important;
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.choice-btn:active {
    transform: translateY(0);
}

/* ===== AI DIARY STYLES ===== */
#page-diary {
    position: relative;
    padding: 0;
    display: none;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    background:
        radial-gradient(120% 90% at 50% -10%, rgba(167, 119, 227, 0.14), transparent 55%),
        radial-gradient(90% 70% at 85% 110%, rgba(110, 142, 251, 0.10), transparent 60%);
}

#page-diary.active {
    display: flex;
}

.diary-view {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Deliberately NOT justify-content: center. The card is centred with
       `margin: auto` on itself instead (see below), because auto margins
       collapse to 0 once the card is taller than the view, whereas
       justify-content: center keeps overflowing it in *both* directions and
       puts the top half permanently out of reach of the scroll. */
    justify-content: flex-start;
    padding: 32px;
    overflow-y: auto;
}

.diary-setup-card,
.diary-lock-card {
    position: relative;
    background:
        linear-gradient(180deg, rgba(30, 22, 44, 0.82), rgba(16, 12, 24, 0.9));
    backdrop-filter: blur(28px);
    -webkit-backdrop-filter: blur(28px);
    border: 1px solid rgba(167, 119, 227, 0.22);
    border-radius: var(--border-radius-lg);
    padding: 52px 44px;
    max-width: 440px;
    width: 100%;
    text-align: center;
    box-shadow:
        0 30px 80px rgba(0, 0, 0, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.03),
        inset 0 1px 0 rgba(255, 255, 255, 0.06);
    animation: diaryCardRise 0.6s cubic-bezier(0.16, 1, 0.3, 1);
    /* Clips the ::before aurora to the rounded corners — it is deliberately
       oversized (top: -40%, width: 140%) — so this cannot simply be dropped,
       which is why the shrink has to be stopped instead. */
    overflow: hidden;
    /* Both of these are load-bearing, not tidying.

       This card is a flex item in a column flex container, where flex-shrink
       applies to HEIGHT — and it defaults to 1. So on any viewport shorter than
       the form, the card silently shrank and the overflow: hidden above ate
       whatever no longer fitted. Nothing scrolled, because as far as layout was
       concerned the item fitted its parent. Measured at 360x660: 720px of
       content squeezed into 541.7px, the PIN row cut in half, and the "Create
       Diary" button 128px past the bottom edge with no way to reach it — the
       diary could not be set up on a phone at all. Not mobile-only either; a
       768px-tall laptop clips it too. It only escapes notice above ~560px wide,
       where the card gets wide enough for the text to stop wrapping.

       margin: auto replaces the old justify-content: center on .diary-view: it
       still centres the card while there is spare room, and resolves to 0 when
       there is not, so the view's own overflow-y: auto can finally scroll. */
    flex-shrink: 0;
    margin: auto;
}

/* Soft aurora glow bleeding from the top of the card */
.diary-setup-card::before,
.diary-lock-card::before {
    content: '';
    position: absolute;
    top: -40%;
    left: 50%;
    transform: translateX(-50%);
    width: 140%;
    height: 60%;
    background: radial-gradient(50% 60% at 50% 50%, rgba(167, 119, 227, 0.22), transparent 70%);
    pointer-events: none;
    z-index: 0;
}

.diary-setup-card>*,
.diary-lock-card>* {
    position: relative;
    z-index: 1;
}

@keyframes diaryCardRise {
    from {
        opacity: 0;
        transform: translateY(18px) scale(0.98);
    }

    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

.diary-setup-icon,
.diary-lock-icon {
    position: relative;
    width: 84px;
    height: 84px;
    border-radius: 50%;
    background: linear-gradient(135deg, #b98cf0, #6e8efb);
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 26px;
    font-size: 2.1rem;
    color: white;
    box-shadow:
        0 12px 40px rgba(167, 119, 227, 0.45),
        inset 0 2px 4px rgba(255, 255, 255, 0.25);
    animation: floatOrb 6s ease-in-out infinite alternate;
}

/* Pulsing halo ring around the setup/lock icon */
.diary-setup-icon::after,
.diary-lock-icon::after {
    content: '';
    position: absolute;
    inset: -8px;
    border-radius: 50%;
    border: 1px solid rgba(167, 119, 227, 0.35);
    animation: diaryHalo 3s ease-in-out infinite;
}

@keyframes diaryHalo {

    0%,
    100% {
        opacity: 0.5;
        transform: scale(1);
    }

    50% {
        opacity: 0;
        transform: scale(1.18);
    }
}

.diary-setup-card h2,
.diary-lock-card h2 {
    font-family: var(--font-display);
    font-size: 1.9rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    margin-bottom: 12px;
    background: linear-gradient(135deg, #ffffff 20%, #d6b4ff 90%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

.diary-setup-desc {
    color: var(--text-secondary);
    font-size: 0.95rem;
    line-height: 1.65;
    margin-bottom: 32px;
    max-width: 34ch;
    margin-left: auto;
    margin-right: auto;
}

.diary-lock-subtitle {
    color: var(--text-secondary);
    font-size: 0.9rem;
    margin-bottom: 28px;
}

.pin-input-group {
    display: flex;
    gap: 10px;
    justify-content: center;
}

.pin-box {
    width: 48px;
    height: 58px;
    background: rgba(255, 255, 255, 0.04);
    border: 1.5px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--border-radius-md);
    text-align: center;
    font-size: 1.5rem;
    font-weight: 700;
    color: white;
    font-family: var(--font-display);
    outline: none;
    transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease, transform 0.15s ease;
    -webkit-text-security: disc;
}

.pin-box:focus {
    border-color: #a777e3;
    box-shadow: 0 0 0 3px rgba(167, 119, 227, 0.18), 0 6px 20px rgba(167, 119, 227, 0.28);
    background: rgba(167, 119, 227, 0.1);
    transform: translateY(-2px);
}

/* Filled PIN cell gets a subtle accent tint */
.pin-box:not(:placeholder-shown),
.pin-box.filled {
    border-color: rgba(167, 119, 227, 0.5);
    background: rgba(167, 119, 227, 0.08);
}

.pin-box.error {
    border-color: var(--color-danger);
    animation: pinShake 0.4s ease;
}

@keyframes pinShake {

    0%,
    100% {
        transform: translateX(0);
    }

    20%,
    60% {
        transform: translateX(-6px);
    }

    40%,
    80% {
        transform: translateX(6px);
    }
}

#diary-main {
    justify-content: flex-start;
    padding: 0;
    overflow: hidden;
}

.diary-header-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 32px;
    border-bottom: 1px solid rgba(167, 119, 227, 0.1);
    background: linear-gradient(180deg, rgba(20, 15, 30, 0.85), rgba(12, 10, 18, 0.7));
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    flex-shrink: 0;
    width: 100%;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
    z-index: 5;
}

.diary-header-info {
    display: flex;
    align-items: center;
    gap: 14px;
}

/* The name + memory pill stack. min-width: 0 is the whole reason this has a
   class: flex items default to min-width: auto, which refuses to shrink below
   the content's intrinsic width, and that refusal does not stop at one level —
   .diary-header-info already allows shrinking, but this div sitting between it
   and the pill did not, so the pill's own `max-width: 100%` resolved against an
   already-too-wide parent and its ellipsis never fired. The text simply ran on
   under the gear button. Every level of the chain has to opt in. */
.diary-header-text {
    min-width: 0;
    flex: 1 1 auto;
}

/* Was an inline style, which no media query could reach — and this group needs
   to be sized per breakpoint. flex-shrink: 0 so the actions keep their full
   size and the text yields instead; margin-left guarantees a gutter that the
   pill can never close, whatever it is set to. */
.diary-header-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    margin-left: 10px;
}

/* Mobile-only exit from the diary. Display: none here rather than inside a
   min-width query because the desktop sidebar is always on screen there, so the
   arrow would be a second, redundant way to do the same thing. The ≤768 block
   turns it on. */
.diary-back-btn {
    display: none;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: var(--text-secondary);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    font-size: 0.95rem;
    transition: background 0.2s ease, color 0.2s ease, transform 0.15s ease;
}

.diary-back-btn:active {
    transform: scale(0.92);
    background: rgba(167, 119, 227, 0.18);
    color: #fff;
}

.diary-agent-avatar {
    position: relative;
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: linear-gradient(135deg, #b98cf0, #6e8efb);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.15rem;
    color: white;
    overflow: hidden;
    box-shadow: 0 6px 18px rgba(167, 119, 227, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.25);
}

.diary-agent-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
}

.diary-header-info h2 {
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: white;
    line-height: 1.2;
}

.diary-subtitle {
    font-size: 0.78rem;
    color: var(--text-secondary);
    display: block;
    margin-top: 1px;
}

/* Reach-of-memory pill. Sits where the old static subtitle was, because the
   promise it makes ("nothing gets trimmed to save space") is the single most
   valuable thing this header can say. Deliberately quiet — it should read as a
   fact about the product, not a badge. */
.diary-memory-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    font-size: 0.74rem;
    color: #c9aee8;
    /* It is a button now — tapping it opens what the diary remembers. Keeping
       the flat look means the header still reads as a subtitle, not a toolbar. */
    padding: 2px 0;
    background: transparent;
    border: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: color 0.16s ease;
}

.diary-memory-pill:hover,
.diary-memory-pill:focus-visible {
    color: #e8d5ff;
    text-decoration: underline;
    text-decoration-color: rgba(167, 119, 227, 0.5);
    text-underline-offset: 3px;
}

/* ── What it remembers ─────────────────────────────────────────
   The receipt for the pill's promise. Rows are quiet by default: this is a
   list of someone's own life, and it should read like a page, not a database
   table. Pinned rows are the one thing allowed to stand out. */
.diary-memory-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 11px 13px;
    background: rgba(0, 0, 0, 0.22);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-left: 2px solid rgba(167, 119, 227, 0.35);
    border-radius: var(--border-radius-xs);
}

.diary-memory-item.pinned {
    background: rgba(167, 119, 227, 0.1);
    border-left-color: #a777e3;
}

.diary-memory-item-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.diary-memory-item-meta {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    color: #b28ce0;
}

.diary-memory-item-text {
    font-size: 0.86rem;
    line-height: 1.5;
    color: var(--text-primary, #f2eaff);
    overflow-wrap: anywhere;
}

.diary-memory-item-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
}

.diary-memory-act {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    color: var(--text-secondary);
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--border-radius-xs);
    cursor: pointer;
    transition: color 0.16s ease, background 0.16s ease, border-color 0.16s ease;
}

.diary-memory-act:hover {
    color: #e8d5ff;
    background: rgba(167, 119, 227, 0.16);
    border-color: rgba(167, 119, 227, 0.4);
}

.diary-memory-act.danger:hover {
    color: #ff9db4;
    background: rgba(224, 113, 156, 0.16);
    border-color: rgba(224, 113, 156, 0.4);
}

.diary-memory-item.pinned .diary-memory-act[data-act="pin"] {
    color: #d7bdf5;
    background: rgba(167, 119, 227, 0.2);
    border-color: rgba(167, 119, 227, 0.45);
}

.diary-memory-tabs .memory-tab.active {
    color: #e8d5ff;
    border-bottom-color: #a777e3 !important;
}

.diary-memory-pill i {
    font-size: 0.72rem;
    color: #a777e3;
    flex-shrink: 0;
}

/* "Load earlier entries" — older months live in their own Firestore documents
   and load on demand, so this is the way back into them. */
.diary-load-earlier {
    align-self: center;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 4px auto 14px;
    padding: 7px 16px;
    font-family: var(--font-display);
    font-size: 0.74rem;
    font-weight: 600;
    color: #d7bdf5;
    background: rgba(167, 119, 227, 0.1);
    border: 1px solid rgba(167, 119, 227, 0.24);
    border-radius: var(--border-radius-pill);
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease;
}

.diary-load-earlier:hover:not(:disabled) {
    background: rgba(167, 119, 227, 0.18);
    border-color: rgba(167, 119, 227, 0.4);
}

.diary-load-earlier:disabled {
    opacity: 0.6;
    cursor: default;
}

/* A relationship change, shown in place in the history. Softer than a date
   divider: past entries were written to whoever the diary was then, and the
   marker records that without redecorating them. */
.diary-marker-note .diary-day-divider-pill {
    text-transform: none;
    letter-spacing: 0.2px;
    font-weight: 600;
    color: #e8d5ff;
    border-color: rgba(167, 119, 227, 0.32);
    background: rgba(45, 30, 62, 0.9);
}

.diary-marker-note .diary-day-divider-pill i {
    color: #e0719c;
}

.diary-entries-feed {
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    padding: 28px 32px 80px !important;
    display: flex;
    flex-direction: column;
    gap: 28px;
    width: 100%;
    align-items: center;
    scrollbar-width: thin;
    scrollbar-color: rgba(167, 119, 227, 0.3) transparent;
}

.diary-entries-feed::-webkit-scrollbar {
    width: 8px;
}

.diary-entries-feed::-webkit-scrollbar-thumb {
    background: rgba(167, 119, 227, 0.28);
    border-radius: var(--border-radius-pill);
}

.diary-entries-feed::-webkit-scrollbar-thumb:hover {
    background: rgba(167, 119, 227, 0.45);
}

.diary-empty-state {
    text-align: center;
    padding: 72px 20px;
    color: var(--text-secondary);
    margin: auto 0;
    animation: fadeIn 0.6s ease;
}

.diary-empty-state i {
    font-size: 2.6rem;
    color: rgba(167, 119, 227, 0.55);
    margin-bottom: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 90px;
    height: 90px;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 40%, rgba(167, 119, 227, 0.18), rgba(167, 119, 227, 0.04));
    border: 1px solid rgba(167, 119, 227, 0.2);
    animation: floatOrb 5s ease-in-out infinite alternate;
}

.diary-empty-state h3 {
    font-family: var(--font-display);
    font-size: 1.45rem;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.85);
    margin-bottom: 10px;
}

.diary-empty-state p {
    font-size: 0.92rem;
    line-height: 1.65;
    max-width: 36ch;
    margin: 0 auto;
}

/* ===== AI DIARY CHAT STYLES ===== */

/* Centered date-divider pill between days */
.diary-day-divider {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    max-width: 860px;
    margin: 6px auto;
    position: relative;
}

.diary-day-divider::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(167, 119, 227, 0.18), transparent);
    z-index: 0;
}

.diary-day-divider-pill {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 16px;
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.8px;
    text-transform: uppercase;
    color: #d7bdf5;
    background: rgba(26, 20, 38, 0.9);
    border: 1px solid rgba(167, 119, 227, 0.2);
    border-radius: var(--border-radius-pill);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}

.diary-day-divider-pill i {
    color: #a777e3;
    font-size: 0.78rem;
}

/* Exchange is a transparent group holding a user + AI row */
.diary-exchange {
    display: flex;
    flex-direction: column;
    gap: 14px;
    width: 100%;
    max-width: 860px;
    margin: 0 auto;
    flex-shrink: 0;
}

/* A single chat row (user right, AI left) */
.diary-row {
    position: relative;
    display: flex;
    gap: 12px;
    align-items: flex-end;
    max-width: 100%;
    animation: diaryBubbleIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes diaryBubbleIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.diary-row-user {
    flex-direction: row;
    justify-content: flex-end;
    align-items: flex-end;
}

.diary-row-ai {
    flex-direction: row;
    justify-content: flex-start;
}

/* The bubble itself */
.diary-bubble {
    position: relative;
    padding: 13px 17px;
    font-size: 0.95rem;
    line-height: 1.65;
    border-radius: var(--border-radius-md);
    max-width: min(74%, 620px);
    word-wrap: break-word;
    overflow-wrap: break-word;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22);
}

/* User bubble — accent gradient, right side */
.diary-bubble-user {
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    color: var(--on-brand-gradient);
    border-bottom-right-radius: var(--border-radius-2xs);
    box-shadow: 0 8px 24px rgba(110, 142, 251, 0.32);
}

.diary-entry-user-text {
    white-space: pre-wrap;
    color: #fff;
}

/* AI bubble — glass, left side */
.diary-bubble-ai {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.92);
    border-bottom-left-radius: var(--border-radius-2xs);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}

.diary-ai-col {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
    max-width: calc(100% - 50px);
    align-items: flex-start;
}

.diary-entry-ai-avatar {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: linear-gradient(135deg, #b98cf0, #6e8efb);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    color: white;
    flex-shrink: 0;
    overflow: hidden;
    box-shadow: 0 5px 14px rgba(110, 142, 251, 0.3), inset 0 1px 2px rgba(255, 255, 255, 0.25);
}

.diary-entry-ai-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
}

.diary-entry-ai-name {
    color: #9fb0ff;
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.5px;
    padding-left: 4px;
}

.diary-entry-ai-text {
    color: rgba(255, 255, 255, 0.92);
    font-size: 0.94rem;
    line-height: 1.75;
}

.diary-entry-ai-text p {
    margin-bottom: 10px;
}

.diary-entry-ai-text p:last-child {
    margin-bottom: 0;
}

.diary-time-tag {
    color: var(--text-secondary);
    font-size: 0.68rem;
    font-weight: 500;
    opacity: 0.6;
    white-space: nowrap;
    flex-shrink: 0;
    padding: 0 2px 2px;
}

.diary-row-ai .diary-time-tag {
    padding-left: 4px;
}

/* Hover delete button, sits outside the user bubble */
.diary-exchange-delete-btn {
    background: rgba(var(--color-danger-rgb), 0.1);
    border: 1px solid rgba(var(--color-danger-rgb), 0.25);
    color: var(--color-danger);
    width: 30px;
    height: 30px;
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.74rem;
    align-self: center;
    opacity: 0;
    transform: scale(0.9);
    transition:
        background 0.2s cubic-bezier(0.165, 0.84, 0.44, 1),
        border-color 0.2s cubic-bezier(0.165, 0.84, 0.44, 1),
        opacity 0.2s cubic-bezier(0.165, 0.84, 0.44, 1),
        transform 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);
    flex-shrink: 0;
}

.diary-row-user:hover .diary-exchange-delete-btn {
    opacity: 1;
    transform: scale(1);
}

.diary-exchange-delete-btn:hover {
    background: rgba(var(--color-danger-rgb), 0.3);
    border-color: var(--color-danger);
    transform: scale(1.08);
}

.diary-input-area {
    padding: 16px 32px 26px;
    border-top: 1px solid rgba(167, 119, 227, 0.1);
    background: linear-gradient(0deg, rgba(20, 15, 30, 0.85), rgba(12, 10, 18, 0.55));
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    flex-shrink: 0;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.diary-input-container {
    display: flex;
    align-items: flex-end;
    gap: 10px;
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.1);
    /* Off the scale, same reason as .chat-input-container: this wraps a textarea
       that grows, and `align-items: flex-end` means it is BUILT to grow. The
       diary is the surface where the user writes the long turn, so this box gets
       taller here than anywhere else in the app. A height-relative radius is
       wrong by construction. */
    border-radius: 18px;
    padding: 10px 12px;
    transition: border-color 0.3s ease, box-shadow 0.3s ease, background 0.3s ease;
    width: 100%;
    max-width: 860px;
    position: relative;
}

.diary-input-container:focus-within {
    border-color: rgba(167, 119, 227, 0.45);
    background: rgba(167, 119, 227, 0.05);
    box-shadow: 0 0 0 3px rgba(167, 119, 227, 0.12), 0 8px 28px rgba(167, 119, 227, 0.15);
}

#diary-entry-input {
    flex: 1;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-family: var(--font-family);
    font-size: 0.95rem;
    resize: none;
    outline: none;
    min-height: 40px;
    max-height: 180px;
    line-height: 1.5;
    padding: 9px 0;
    box-sizing: border-box;
}

#diary-entry-input::placeholder {
    color: rgba(255, 255, 255, 0.32);
}

/* Elevated send button inside the diary composer */
.diary-input-container #diary-send-btn {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: linear-gradient(135deg, #b98cf0, #6e8efb);
    border: none;
    color: white;
    flex-shrink: 0;
    box-shadow: 0 4px 14px rgba(167, 119, 227, 0.4);
    transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}

.diary-input-container #diary-send-btn:hover {
    transform: scale(1.08);
    box-shadow: 0 6px 20px rgba(167, 119, 227, 0.55);
    filter: brightness(1.08);
}

.diary-input-container #diary-send-btn:active {
    transform: scale(0.96);
}

/* Emoji toggle button inside the diary composer */
.diary-input-container .diary-emoji-btn {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.65);
    flex-shrink: 0;
    font-size: 1.1rem;
    transition: transform 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.diary-input-container .diary-emoji-btn:hover {
    color: #f5c451;
    background: rgba(245, 196, 81, 0.12);
    transform: scale(1.08);
}

.diary-input-container .diary-emoji-btn[aria-expanded="true"] {
    color: #f5c451;
    background: rgba(245, 196, 81, 0.15);
}

/* Emoji picker popover */
.diary-emoji-panel {
    position: absolute;
    bottom: calc(100% + 10px);
    left: 8px;
    width: min(320px, calc(100vw - 48px));
    max-height: 240px;
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 2px;
    padding: 10px;
    background: rgba(24, 18, 34, 0.98);
    border: 1px solid rgba(167, 119, 227, 0.28);
    border-radius: var(--border-radius-md);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    z-index: 40;
}

.diary-emoji-panel[hidden] {
    display: none;
}

.diary-emoji-item {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 1.25rem;
    line-height: 1;
    padding: 6px 0;
    border-radius: var(--border-radius-xs);
    transition: background 0.15s ease, transform 0.15s ease;
}

.diary-emoji-item:hover {
    background: rgba(167, 119, 227, 0.22);
    transform: scale(1.15);
}

/* Privacy note under the diary composer */
.diary-input-area .voice-notice {
    opacity: 0.5;
    margin-top: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}

.diary-input-area .voice-notice::before {
    content: '\f023';
    /* fa-lock */
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    font-size: 0.68rem;
    color: #a777e3;
    opacity: 0.8;
}

/* Fix last message cut-off */
.diary-entries-feed::after {
    content: '';
    display: block;
    min-height: 20px;
    flex-shrink: 0;
}

/* Diary Settings Button */
.diary-settings-btn {
    background: none;
    border: 1px solid transparent;
    color: var(--text-secondary);
    font-size: 1.05rem;
    cursor: pointer;
    width: 38px;
    height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--border-radius-sm);
    transition: color 0.25s ease, background 0.25s ease, border-color 0.25s ease, transform 0.2s ease;
    margin-right: 2px;
}

.diary-settings-btn:hover {
    color: #d6b4ff;
    background: rgba(167, 119, 227, 0.12);
    border-color: rgba(167, 119, 227, 0.25);
    transform: rotate(30deg);
}

.diary-settings-btn:active {
    transform: rotate(30deg) scale(0.94);
}

/* Diary Settings Modal */
.diary-settings-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.7);
    backdrop-filter: blur(8px);
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

.diary-settings-overlay.active {
    opacity: 1;
    pointer-events: all;
}

.diary-settings-modal {
    background: rgba(20, 15, 30, 0.95);
    backdrop-filter: blur(24px);
    border: 1px solid rgba(167, 119, 227, 0.25);
    border-radius: var(--border-radius-lg);
    padding: 36px 32px;
    max-width: 480px;
    width: 90%;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
    animation: fadeIn 0.35s ease;
    max-height: 85vh;
    overflow-y: auto;
}

.diary-settings-modal h2 {
    font-family: var(--font-display);
    font-size: 1.4rem;
    font-weight: 700;
    color: white;
    margin-bottom: 24px;
    display: flex;
    align-items: center;
    gap: 10px;
}

.diary-settings-modal h2 i {
    color: #a777e3;
}

.diary-avatar-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-bottom: 24px;
}

.diary-avatar-preview {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    color: white;
    overflow: hidden;
    border: 3px solid rgba(167, 119, 227, 0.4);
    box-shadow: 0 8px 24px rgba(167, 119, 227, 0.3);
    cursor: pointer;
    transition: border-color 0.3s ease, transform 0.3s ease;
    margin-bottom: 10px;
    position: relative;
}

.diary-avatar-preview:hover {
    border-color: #a777e3;
    transform: scale(1.05);
}

.diary-avatar-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.diary-avatar-preview .avatar-hover-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 0.25s;
    font-size: 1rem;
    border-radius: 50%;
}

.diary-avatar-preview:hover .avatar-hover-overlay {
    opacity: 1;
}

.diary-avatar-hint {
    font-size: 0.78rem;
    color: var(--text-secondary);
}

.diary-settings-actions {
    display: flex;
    gap: 10px;
    margin-top: 24px;
}

.diary-settings-actions button {
    flex: 1;
}

@media (max-width: 600px) {

    /* --- Diary Setup & Lock Cards --- */
    .diary-view {
        padding: 20px 16px;
    }

    .diary-setup-card,
    .diary-lock-card {
        /* No border-radius (and, in truth, this whole rule is dead). It sits in a
           `@media (max-width: 600px)` block, but a LATER `@media (max-width:
           768px)` block at :20487 declares the same two selectors with equal
           specificity — and 768 covers everything 600 covers. Later same-width-
           or-wider media block wins, so the 20px here never applied at any
           viewport. The card is 18px below 768px, full stop. */
        padding: 32px 20px;
    }

    .diary-setup-icon,
    .diary-lock-icon {
        width: 64px;
        height: 64px;
        font-size: 1.6rem;
        margin-bottom: 20px;
    }

    .diary-setup-card h2,
    .diary-lock-card h2 {
        font-size: 1.45rem;
    }

    .diary-setup-desc {
        font-size: 0.88rem;
        margin-bottom: 22px;
    }

    .diary-lock-subtitle {
        font-size: 0.85rem;
        margin-bottom: 18px;
    }

    /* PIN boxes — scale to fit mobile screens */
    .pin-input-group {
        gap: 8px;
    }

    .pin-box {
        width: 42px;
        height: 50px;
        font-size: 1.2rem;
        border-radius: var(--border-radius-sm);
    }

    /* --- Diary Settings Modal --- */
    .diary-settings-modal {
        padding: 28px 20px;
    }

    /* --- Diary Main View --- */
    .diary-header-bar {
        padding: 14px 16px;
    }

    .diary-agent-avatar {
        width: 40px;
        height: 40px;
        font-size: 1rem;
    }

    .diary-header-info {
        gap: 12px;
    }

    .diary-header-info h2 {
        font-size: 1.05rem;
    }

    .diary-entries-feed {
        padding: 16px 16px 80px !important;
    }

    .diary-input-area {
        padding: 12px 16px 20px;
    }

    .diary-input-container {
        padding: 10px 14px;
    }

    #diary-entry-input {
        font-size: 0.9rem;
    }

    /* --- Chat bubbles --- */
    .diary-bubble {
        max-width: 82%;
        padding: 11px 14px;
        font-size: 0.9rem;
    }

    .diary-day-divider-pill {
        font-size: 0.68rem;
        padding: 6px 13px;
    }

    .diary-entry-ai-avatar {
        width: 32px;
        height: 32px;
        font-size: 0.75rem;
    }

    .diary-ai-col {
        max-width: calc(100% - 44px);
    }

    .diary-entry-ai-name {
        font-size: 0.72rem;
    }

    .diary-entry-ai-text {
        font-size: 0.88rem;
    }

    .diary-exchange-delete-btn {
        opacity: 1;
        transform: scale(1);
        width: 28px;
        height: 28px;
        font-size: 0.7rem;
    }

    /* --- Empty state --- */
    .diary-empty-state {
        padding: 60px 16px;
    }

    .diary-empty-state i {
        font-size: 2.4rem;
        margin-bottom: 16px;
    }

    .diary-empty-state h3 {
        font-size: 1.2rem;
    }

    .diary-empty-state p {
        font-size: 0.85rem;
    }
}

/* Extra small screens (very narrow phones) */
@media (max-width: 380px) {

    .diary-setup-card,
    .diary-lock-card {
        padding: 28px 16px;
    }

    .pin-input-group {
        gap: 6px;
    }

    .pin-box {
        width: 36px;
        height: 44px;
        font-size: 1.05rem;
        border-radius: var(--border-radius-xs);
    }

    .diary-setup-card h2,
    .diary-lock-card h2 {
        font-size: 1.3rem;
    }

    .diary-day-divider-pill {
        font-size: 0.64rem;
        padding: 5px 11px;
    }

    .diary-bubble {
        max-width: 86%;
        padding: 10px 13px;
    }
}

/* ============================================================
   CHARACTER PROFILE PAGE
   ============================================================ */

.char-profile-wrapper {
    width: 100%;
    max-width: 100%;
    min-height: 100%;
    display: flex;
    flex-direction: column;
}

/* Hero Banner */
.char-profile-hero {
    position: relative;
    width: 100%;
    min-height: 420px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    padding: 40px 32px 48px;
    overflow: hidden;
}

.char-profile-hero-bg {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center top;
    background-repeat: no-repeat;
    filter: blur(28px) brightness(0.45) saturate(1.4);
    transform: scale(1.15);
    z-index: 0;
}

.char-profile-hero-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg,
            rgba(10, 10, 14, 0.3) 0%,
            rgba(10, 10, 14, 0.6) 50%,
            rgba(10, 10, 14, 0.95) 85%,
            #0a0a0e 100%);
    z-index: 1;
}

.char-profile-hero-content {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 12px;
    animation: profileFadeUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes profileFadeUp {
    from {
        opacity: 0;
        transform: translateY(24px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.char-profile-avatar-ring {
    width: 140px;
    height: 140px;
    min-height: 140px;
    border-radius: 50%;
    padding: 4px;
    background: linear-gradient(135deg, #a777e3, #6e8efb, #a777e3);
    background-size: 300% 300%;
    animation: avatarRingGlow 4s ease-in-out infinite, profileFadeUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
    box-shadow: 0 0 40px rgba(167, 119, 227, 0.35), 0 0 80px rgba(110, 142, 251, 0.12);
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    aspect-ratio: 1 / 1;
}


@keyframes avatarRingGlow {

    0%,
    100% {
        background-position: 0% 50%;
        box-shadow: 0 0 40px rgba(167, 119, 227, 0.35), 0 0 80px rgba(110, 142, 251, 0.12);
    }

    50% {
        background-position: 100% 50%;
        box-shadow: 0 0 55px rgba(167, 119, 227, 0.5), 0 0 100px rgba(110, 142, 251, 0.2);
    }
}

.char-profile-avatar-ring img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    object-position: center top;
    border: 3px solid #0a0a0e;
}


.char-profile-name {
    font-family: var(--font-display);
    font-size: 2.6rem;
    font-weight: 800;
    letter-spacing: -0.5px;
    background: linear-gradient(135deg, #ffffff 0%, #e2d0ff 50%, #a777e3 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    line-height: 1.2;
    margin: 0;
}

.char-profile-tagline {
    font-size: 1.05rem;
    color: rgba(255, 255, 255, 0.55);
    font-style: italic;
    max-width: 500px;
    line-height: 1.5;
    margin: 0;
}

.char-profile-meta {
    display: flex;
    align-items: center;
    gap: 20px;
    margin-top: 4px;
}

.char-profile-author {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    color: #c9a0f5;
    font-weight: 600;
}

.char-profile-author i {
    font-size: 0.7rem;
}

.char-profile-stat {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.82rem;
    color: rgba(255, 255, 255, 0.45);
}

.char-profile-stat i {
    font-size: 0.7rem;
}

.char-profile-tags {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin-top: 8px;
}

.char-profile-tags .profile-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 14px;
    border-radius: var(--border-radius-pill);
    font-size: 0.75rem;
    font-weight: 600;
    background: rgba(167, 119, 227, 0.1);
    border: 1px solid rgba(167, 119, 227, 0.2);
    color: #d6b4ff;
    letter-spacing: 0.3px;
}

.char-profile-tags .profile-tag i {
    font-size: 0.65rem;
}

/* Profile Body */
.char-profile-body {
    width: 100%;
    max-width: 760px;
    margin: 0 auto;
    padding: 8px 24px 48px;
    display: flex;
    flex-direction: column;
    gap: 24px;
}

.char-profile-section {
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-md);
    padding: 28px;
    transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.char-profile-section:hover {
    border-color: rgba(167, 119, 227, 0.15);
    box-shadow: 0 4px 30px rgba(167, 119, 227, 0.05);
}

.char-profile-section-title {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 1rem;
    font-weight: 700;
    color: #fff;
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-family: var(--font-display);
}

.char-profile-short-lore {
    font-size: 0.95rem;
    color: rgba(255, 255, 255, 0.7);
    line-height: 1.75;
    white-space: pre-wrap;
    word-break: break-word;
    margin: 0;
}

.char-profile-backstory {
    font-size: 0.92rem;
    color: rgba(255, 255, 255, 0.65);
    line-height: 1.85;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ── Sectioned biography ──
   Markup comes from formatLoreSections() in lore-format.js, which turns
   ALL-CAPS lines in the stored plain text into headings. The container keeps
   `pre-wrap` so hand-placed line breaks inside a paragraph survive; the
   heading and rule elements below reset it so their own margins do the
   spacing instead of stray newlines. */
.lore-para {
    margin: 0 0 14px;
}

.lore-para:last-child {
    margin-bottom: 0;
}

/* Divider above every top-level heading. The diamond is a pseudo-element so
   the text content stays copy-pasteable and screen readers skip it. */
.lore-rule {
    position: relative;
    height: 1px;
    margin: 30px auto 22px;
    width: min(180px, 55%);
    white-space: normal;
    background: linear-gradient(90deg,
            transparent, rgba(110, 142, 251, 0.55), transparent);
}

.lore-rule::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 5px;
    height: 5px;
    transform: translate(-50%, -50%) rotate(45deg);
    background: rgba(167, 119, 227, 0.9);
}

.lore-body > .lore-rule:first-child,
.char-profile-backstory > .lore-rule:first-child {
    margin-top: 4px;
}

.lore-head {
    margin: 0 0 16px;
    text-align: center;
    white-space: normal;
    font-size: 0.86rem;
    font-weight: 800;
    letter-spacing: 1.4px;
    text-transform: uppercase;
}

/* Hues cycle in order of appearance — see HEADING_HUES in lore-format.js. */
.lore-head.is-rose {
    color: #ff5c8a;
    text-shadow: 0 0 18px rgba(255, 92, 138, 0.35);
}

.lore-head.is-amber {
    color: #f0932b;
    text-shadow: 0 0 18px rgba(240, 147, 43, 0.3);
}

.lore-head.is-violet {
    color: #a777e3;
    text-shadow: 0 0 18px rgba(167, 119, 227, 0.35);
}

.lore-head.is-cyan {
    color: #34d8d8;
    text-shadow: 0 0 18px rgba(52, 216, 216, 0.3);
}

/* Subheading — an ALL-CAPS line ending in a colon, e.g. "INTRO 1:". Reads as
   a labelled block so a list of scene openers doesn't blur into one wall. */
.lore-sub {
    margin: 18px 0 8px;
    padding: 7px 14px;
    white-space: normal;
    border-left: 3px solid #6e8efb;
    border-radius: 0 var(--border-radius-xs) var(--border-radius-xs) 0;
    background: rgba(110, 142, 251, 0.08);
    color: #34d8d8;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 1px;
}

.char-profile-edit-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    padding: 14px;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--border-radius-md);
    color: var(--text-secondary);
    font-family: var(--font-family);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}

.char-profile-edit-btn:hover {
    background: rgba(167, 119, 227, 0.1);
    border-color: rgba(167, 119, 227, 0.3);
    color: #d6b4ff;
}

.char-profile-start-btn {
    width: 100%;
    padding: 20px;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    background-size: 200% 200%;
    animation: superGradientShift 6s ease infinite;
    border: none;
    border-radius: var(--border-radius-md);
    color: var(--on-brand-gradient);
    font-family: var(--font-display);
    font-size: 1.15rem;
    font-weight: 700;
    cursor: pointer;
    transition:
        box-shadow 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    box-shadow: 0 8px 30px rgba(167, 119, 227, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.15);
    position: relative;
    overflow: hidden;
    letter-spacing: 0.3px;
}

.char-profile-start-btn::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 60%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
    animation: shine 4s ease-in-out infinite;
}

.char-profile-start-btn:hover {
    transform: translateY(-3px) scale(1.01);
    box-shadow: 0 14px 45px rgba(167, 119, 227, 0.5), 0 0 0 1px rgba(167, 119, 227, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.char-profile-start-btn:active {
    transform: translateY(0) scale(0.98);
}

/* Profile Particles */
.char-profile-particles {
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: 1;
    pointer-events: none;
}

.char-profile-particles .particle {
    position: absolute;
    width: 3px;
    height: 3px;
    background: #fff;
    border-radius: 50%;
    opacity: 0.3;
    animation: floatParticle 8s infinite ease-in-out alternate;
}

.char-profile-particles .particle:nth-child(1) {
    top: 20%;
    left: 10%;
    width: 4px;
    height: 4px;
    animation-duration: 12s;
}

.char-profile-particles .particle:nth-child(2) {
    top: 60%;
    left: 80%;
    width: 2px;
    height: 2px;
    animation-delay: -2s;
    opacity: 0.5;
}

.char-profile-particles .particle:nth-child(3) {
    top: 40%;
    left: 30%;
    width: 5px;
    height: 5px;
    animation-duration: 15s;
    background: #a777e3;
}

.char-profile-particles .particle:nth-child(4) {
    top: 80%;
    left: 20%;
    width: 3px;
    height: 3px;
    animation-delay: -5s;
}

.char-profile-particles .particle:nth-child(5) {
    top: 10%;
    left: 70%;
    width: 6px;
    height: 6px;
    animation-duration: 10s;
    background: #6e8efb;
}

.char-profile-particles .particle:nth-child(6) {
    top: 90%;
    left: 90%;
    width: 3px;
    height: 3px;
    animation-delay: -7s;
}

@keyframes floatParticle {
    0% {
        transform: translateY(0) translateX(0);
    }

    100% {
        transform: translateY(-40px) translateX(20px);
    }
}

/* Stats Row */
.char-profile-stats-row {
    display: flex;
    gap: 16px;
    width: 100%;
}

.profile-stat-card {
    flex: 1;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-md);
    padding: 16px;
    display: flex;
    align-items: center;
    gap: 14px;
    transition: background 0.3s ease, border-color 0.3s ease, transform 0.3s ease;
}

.profile-stat-card:hover {
    background: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.1);
    transform: translateY(-2px);
}

.profile-stat-icon {
    width: 44px;
    height: 44px;
    border-radius: var(--border-radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    background: rgba(167, 119, 227, 0.12);
    border: 1px solid rgba(167, 119, 227, 0.25);
    color: #a777e3;
    flex-shrink: 0;
}

.profile-stat-info {
    display: flex;
    flex-direction: column;
}

.profile-stat-value {
    font-size: 1.25rem;
    font-weight: 700;
    color: #fff;
    font-family: var(--font-display);
    line-height: 1.2;
}

.profile-stat-label {
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.5);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 600;
}

/* Traits Grid */
.char-profile-traits-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
}

.profile-trait-chip {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: var(--border-radius-sm);
    transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.profile-trait-chip:hover {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.1);
    transform: translateX(4px);
}

.profile-trait-chip i {
    font-size: 1.2rem;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}

.trait-text {
    display: flex;
    flex-direction: column;
}

.trait-label {
    font-size: 0.9rem;
    font-weight: 700;
    color: #fff;
}

.trait-desc {
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.5);
}

@media (max-width: 768px) {
    .char-profile-hero {
        min-height: 360px;
        padding: 32px 20px 36px;
    }

    .char-profile-avatar-ring {
        width: 110px;
        height: 110px;
        min-height: 110px;
    }


    .char-profile-name {
        font-size: 2rem;
    }

    .char-profile-tagline {
        font-size: 0.92rem;
    }

    .char-profile-body {
        padding: 8px 16px 40px;
    }

    .char-profile-section {
        padding: 20px;
    }

    .char-profile-start-btn {
        padding: 16px;
        font-size: 1.05rem;
    }
}

/* ==========================================
   UPGRADE TO LOOM+ BUTTON STYLING
   ========================================== */
.sidebar-upgrade-container {
    margin-top: 12px;
    margin-bottom: 12px;
    padding-top: 16px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.sidebar-upgrade-btn {
    width: 100%;
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.12) 0%, rgba(110, 142, 251, 0.12) 100%);
    border: 1px solid rgba(167, 119, 227, 0.25);
    border-radius: var(--border-radius-md);
    padding: 13px 16px;
    color: #fff;
    font-family: var(--font-family);
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    overflow: hidden;
    transition:
        background 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        border-color 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        box-shadow 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.2),
        inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

/* Iridescent shimmer overlay */
.sidebar-upgrade-btn::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg,
            transparent 0%,
            rgba(255, 255, 255, 0) 30%,
            rgba(255, 255, 255, 0.15) 50%,
            rgba(255, 255, 255, 0) 70%,
            transparent 100%);
    z-index: 1;
    transition: none;
}

.sidebar-upgrade-btn:hover::before {
    left: 100%;
    transition: left 1.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.sidebar-upgrade-btn:hover {
    transform: translateY(-2px);
    border-color: rgba(167, 119, 227, 0.6);
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.22) 0%, rgba(110, 142, 251, 0.22) 100%);
    box-shadow:
        0 8px 24px rgba(167, 119, 227, 0.3),
        0 0 16px rgba(110, 142, 251, 0.15),
        inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.sidebar-upgrade-btn:active {
    transform: translateY(0) scale(0.98);
    box-shadow:
        0 2px 8px rgba(167, 119, 227, 0.2),
        inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

/* Pulsing background radial glow */
.upgrade-btn-glow {
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at 50% 50%, rgba(167, 119, 227, 0.4), transparent 75%);
    opacity: 0;
    transition: opacity 0.4s ease;
    z-index: 0;
    pointer-events: none;
}

.sidebar-upgrade-btn:hover .upgrade-btn-glow {
    opacity: 1;
    animation: upgradePulse 2s infinite alternate;
}

@keyframes upgradePulse {
    0% {
        transform: scale(0.95);
        opacity: 0.4;
    }

    100% {
        transform: scale(1.2);
        opacity: 0.8;
    }
}

/* Upgrade Icon Styles with gold/purple gradient and subtle breathing animation */
.upgrade-icon {
    font-size: 1rem;
    background: linear-gradient(135deg, #ffd700, #ff8c00);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 2px 4px rgba(255, 215, 0, 0.3));
    transition: filter 0.3s ease, transform 0.3s ease;
    z-index: 2;
    animation: iconFloat 3s ease-in-out infinite;
}

@keyframes iconFloat {

    0%,
    100% {
        transform: translateY(0);
    }

    50% {
        transform: translateY(-2px);
    }
}

.sidebar-upgrade-btn:hover .upgrade-icon {
    transform: scale(1.2) rotate(12deg);
    filter: drop-shadow(0 4px 8px rgba(255, 215, 0, 0.6));
    animation: none;
    /* stop float on hover since hover has its own scale/rotate */
}

.upgrade-text {
    background: linear-gradient(90deg, #ffffff, #e2d0ff);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    z-index: 2;
    letter-spacing: 0.3px;
    font-weight: 700;
    white-space: nowrap;
}

.sidebar-upgrade-btn:hover .upgrade-text {
    background: linear-gradient(90deg, #ffffff, #ffd700);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.upgrade-arrow {
    margin-left: auto;
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.35);
    transition:
        color 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    z-index: 2;
}

.sidebar-upgrade-btn:hover .upgrade-arrow {
    color: #ffd700;
    transform: translateX(4px);
}


/* =====================================================================
   LORELOOM+ UPGRADE MODAL — Premium Subscription UI
   ===================================================================== */

/* Body scroll-lock when modal is open */
body.ll-modal-open {
    overflow: hidden !important;
}

/* ── Fullscreen Overlay ── */
.ll-upgrade-overlay {
    position: fixed;
    inset: 0;
    z-index: 99999;
    background: rgba(0, 0, 0, 0.75);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    display: flex;
    /* flex-start + `margin:auto` on the modal, NOT `align-items:center`.
       A centered flex item taller than its container overflows in BOTH
       directions, and the overflow above the top edge can't be scrolled
       to — that's what clipped the heading on mobile. Auto margins
       collapse to 0 when there's no free space, so the modal pins to the
       top and this overlay scrolls instead. */
    align-items: flex-start;
    justify-content: center;
    overflow-y: auto;
    overscroll-behavior: contain;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.35s ease, visibility 0.35s ease;
}

.ll-upgrade-overlay.ll-active {
    opacity: 1;
    visibility: visible;
}

/* ── Modal Container ── */
.ll-upgrade-modal {
    position: relative;
    width: 95vw;
    max-width: 1060px;
    max-height: 92vh;
    /* Vertically centered while it fits, top-anchored once it's taller than
       the viewport (see the overlay's align-items note). */
    margin: auto;
    overflow-y: auto;
    overflow-x: hidden;
    background: linear-gradient(168deg, #15121e 0%, #0e0c14 40%, #110f19 100%);
    border: 1px solid rgba(167, 119, 227, 0.12);
    border-radius: var(--border-radius-lg);
    box-shadow:
        0 40px 100px rgba(0, 0, 0, 0.7),
        0 0 80px rgba(167, 119, 227, 0.08),
        inset 0 1px 0 rgba(255, 255, 255, 0.04);
    transform: scale(0.92) translateY(20px);
    opacity: 0;
    transition: transform 0.4s cubic-bezier(0.165, 0.84, 0.44, 1), opacity 0.35s ease;
}

.ll-upgrade-overlay.ll-active .ll-upgrade-modal {
    transform: scale(1) translateY(0);
    opacity: 1;
}

/* Custom scrollbar for the modal */
.ll-upgrade-modal::-webkit-scrollbar {
    width: 6px;
}

.ll-upgrade-modal::-webkit-scrollbar-track {
    background: transparent;
}

.ll-upgrade-modal::-webkit-scrollbar-thumb {
    background: rgba(167, 119, 227, 0.25);
    border-radius: var(--border-radius-pill);
}

/* ── Close Button ── */
.ll-upgrade-close {
    position: absolute;
    top: 20px;
    right: 20px;
    z-index: 50;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(30, 28, 40, 0.7);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: rgba(255, 255, 255, 0.6);
    font-size: 1.15rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition:
        background 0.25s ease,
        border-color 0.25s ease,
        color 0.25s ease,
        transform 0.25s ease;
}

.ll-upgrade-close:hover {
    background: rgba(167, 119, 227, 0.2);
    border-color: rgba(167, 119, 227, 0.4);
    color: #fff;
    transform: rotate(90deg);
}

/* ── Banner Section ── */
.ll-upgrade-banner {
    position: relative;
    width: 100%;
    min-height: 240px;
    overflow: hidden;
    border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
}


.ll-upgrade-castle-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center top;
    display: block;
}

.ll-upgrade-banner-fade {
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg,
            #15121e 0%,
            rgba(21, 18, 30, 0.92) 15%,
            rgba(21, 18, 30, 0.55) 40%,
            rgba(21, 18, 30, 0.15) 65%,
            transparent 100%);
    z-index: 2;
}

/* Additional bottom fade for seamless transition */
.ll-upgrade-banner-artwork::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 50%;
    background: linear-gradient(to top, #15121e 0%, rgba(21, 18, 30, 0.45) 50%, transparent 100%);
    z-index: 3;
    pointer-events: none;
}

.ll-upgrade-banner-content {
    position: relative;
    z-index: 10;
    padding: 44px 48px 36px;
}

.ll-upgrade-logo {
    font-size: 1.5rem;
    font-weight: 800;
    color: #fff;
    letter-spacing: -0.5px;
    margin-bottom: 18px;
    display: block;
    line-height: 1;
}

.ll-upgrade-heading {
    font-family: var(--font-display);
    font-size: 2.8rem;
    font-weight: 800;
    color: #fff;
    letter-spacing: -1.2px;
    line-height: 1.1;
    margin-bottom: 10px;
}

.ll-upgrade-heading-accent {
    background: linear-gradient(135deg, #c084fc 0%, #a855f7 40%, #9333ea 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}

.ll-upgrade-subtitle {
    font-size: 1.05rem;
    color: rgba(255, 255, 255, 0.45);
    font-weight: 400;
    letter-spacing: 0.2px;
}

/* ── Body: Two-Column Layout ── */
.ll-upgrade-body {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 28px;
    padding: 8px 40px 40px;
}

/* ── LEFT COLUMN: Feature Table ── */
.ll-upgrade-features-col {
    min-width: 0;
}

.ll-upgrade-table {
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-lg);
    padding: 20px 24px;
    overflow: hidden;
}

.ll-upgrade-table-header {
    display: grid;
    grid-template-columns: 1fr 60px 90px;
    align-items: center;
    padding-bottom: 14px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    margin-bottom: 4px;
}

.ll-table-label {
    font-weight: 700;
    font-size: 0.95rem;
    color: #fff;
}

.ll-table-plan {
    text-align: center;
    font-size: 0.78rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.4);
}

.ll-table-plan-plus {
    color: #c084fc;
}

/* Shortened plan label swapped in on narrow screens (see mobile pass) */
.ll-plan-name-short {
    display: none;
}

/* Desktop keeps the column head "Features"; the mobile pass drops the Free /
   Loom+ columns entirely and swaps in the list-style label instead. */
.ll-table-label-short {
    display: none;
}

/* ── Table Rows ── */
.ll-upgrade-table-row {
    display: grid;
    grid-template-columns: 1fr 60px 90px;
    align-items: center;
    padding: 13px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.03);
    transition: background 0.2s ease;
}

.ll-upgrade-table-row:last-child {
    border-bottom: none;
}

.ll-upgrade-table-row:hover {
    background: rgba(167, 119, 227, 0.04);
    border-radius: var(--border-radius-sm);
    margin: 0 -8px;
    padding-left: 8px;
    padding-right: 8px;
}

.ll-table-feature {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 0.9rem;
    color: rgba(255, 255, 255, 0.8);
    font-weight: 500;
}

.ll-feature-icon {
    font-size: 0.95rem;
    width: 20px;
    text-align: center;
    flex-shrink: 0;
}

.ll-table-check {
    text-align: center;
    font-size: 0.9rem;
}

.ll-table-check.free i {
    color: rgba(255, 255, 255, 0.5);
}

.ll-table-check.plus i {
    color: #c084fc;
    filter: drop-shadow(0 0 4px rgba(192, 132, 252, 0.4));
}

.ll-table-dash {
    text-align: center;
    font-size: 0.95rem;
    color: rgba(255, 255, 255, 0.18);
    font-weight: 600;
}

/* ── RIGHT COLUMN: Plans ── */
.ll-upgrade-plans-col {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

/* The notice+CTA wrapper is transparent here: `display: contents` generates no box,
   so both children stay flex items of .ll-upgrade-plans-col and desktop is unchanged.
   The (max-width: 560px) block turns it into a pinned pay dock. */
.ll-upgrade-cta-dock {
    display: contents;
}

/* Price inside the CTA is a phone-only affordance — the plan cards already carry it
   on desktop, where there is no fold to fight. */
.ll-continue-price {
    display: none;
}

/* ── Plan Cards ── */
.ll-plan-card {
    position: relative;
    border-radius: var(--border-radius-md);
    padding: 2px;
    cursor: pointer;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.08);
    transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}

.ll-plan-card:hover {
    border-color: rgba(167, 119, 227, 0.3);
    background: rgba(167, 119, 227, 0.06);
}

/* Active plan card: glowing purple border */
.ll-plan-card.ll-plan-card--active {
    border-color: transparent;
    background: linear-gradient(135deg, #9333ea, #7c3aed, #a855f7) padding-box,
        linear-gradient(135deg, #9333ea, #7c3aed, #a855f7) border-box;
    border: 2px solid transparent;
    background-clip: padding-box;
    background-origin: border-box;
    box-shadow:
        0 0 20px rgba(147, 51, 234, 0.2),
        0 0 40px rgba(147, 51, 234, 0.1),
        inset 0 0 0 999px #15121e;
    animation: planGlow 3s ease-in-out infinite alternate;
}

@keyframes planGlow {
    0% {
        box-shadow: 0 0 20px rgba(147, 51, 234, 0.15), 0 0 40px rgba(147, 51, 234, 0.08), inset 0 0 0 999px #15121e;
    }

    100% {
        box-shadow: 0 0 28px rgba(147, 51, 234, 0.3), 0 0 60px rgba(147, 51, 234, 0.12), inset 0 0 0 999px #15121e;
    }
}

.ll-plan-card-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 22px 24px;
    border-radius: var(--border-radius-md);
}

.ll-plan-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.ll-plan-label {
    font-weight: 700;
    font-size: 1.05rem;
    color: #fff;
    letter-spacing: 0.2px;
}

.ll-plan-price-row {
    display: flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: wrap;
}

.ll-plan-price {
    font-family: var(--font-display);
    font-size: 2rem;
    font-weight: 800;
    color: #fff;
    letter-spacing: -0.5px;
}

.ll-plan-period {
    font-size: 0.95rem;
    color: rgba(255, 255, 255, 0.4);
    font-weight: 500;
}

.ll-plan-original-price {
    font-size: 0.9rem;
    color: rgba(255, 255, 255, 0.3);
    text-decoration: line-through;
    font-weight: 500;
}

.ll-plan-save {
    font-size: 0.82rem;
    color: #a855f7;
    font-weight: 600;
    margin-top: 2px;
}

/* ── Plan Radio Indicator ── */
.ll-plan-radio {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.15);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background 0.25s ease, border-color 0.25s ease;
    background: transparent;
}

.ll-plan-card--active .ll-plan-radio {
    border-color: #a855f7;
    background: rgba(168, 85, 247, 0.15);
}

.ll-plan-radio-circle {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: transparent;
    transition:
        background 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        box-shadow 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transform: scale(0);
}

.ll-plan-card--active .ll-plan-radio-circle {
    background: linear-gradient(135deg, #a855f7, #7c3aed);
    transform: scale(1);
    box-shadow: 0 0 10px rgba(168, 85, 247, 0.5);
}

/* ── 20% OFF Badge ── */
.ll-plan-badge {
    position: absolute;
    top: -10px;
    right: 18px;
    background: linear-gradient(135deg, #7c3aed, #9333ea);
    color: #fff;
    font-size: 0.72rem;
    font-weight: 800;
    padding: 5px 14px;
    border-radius: var(--border-radius-pill);
    letter-spacing: 0.8px;
    box-shadow: 0 4px 16px rgba(147, 51, 234, 0.4);
    z-index: 10;
    display: flex;
    align-items: center;
    gap: 5px;
    text-transform: uppercase;
}

.ll-plan-badge i {
    font-size: 0.65rem;
}

/* ── Renewal Notice ── */
.ll-renewal-notice {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.82rem;
    color: rgba(255, 255, 255, 0.3);
    padding: 4px 0;
}

.ll-renewal-notice i {
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.2);
}

/* ── Continue Button ── */
.ll-upgrade-continue-btn {
    position: relative;
    width: 100%;
    padding: 18px 32px;
    border: none;
    border-radius: var(--border-radius-md);
    background: linear-gradient(135deg, #9333ea 0%, #7c3aed 40%, #a855f7 100%);
    background-size: 200% 200%;
    animation: continueGradient 4s ease infinite;
    color: #fff;
    font-family: 'Inter', sans-serif;
    font-size: 1.1rem;
    font-weight: 700;
    letter-spacing: 0.5px;
    cursor: pointer;
    overflow: hidden;
    transition: box-shadow 0.3s ease, transform 0.3s ease;
    box-shadow:
        0 8px 30px rgba(147, 51, 234, 0.35),
        0 2px 8px rgba(0, 0, 0, 0.3);
    margin-top: 4px;
}

@keyframes continueGradient {
    0% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }

    100% {
        background-position: 0% 50%;
    }
}

.ll-upgrade-continue-btn:hover {
    transform: translateY(-2px);
    box-shadow:
        0 12px 40px rgba(147, 51, 234, 0.5),
        0 4px 12px rgba(0, 0, 0, 0.4);
}

.ll-upgrade-continue-btn:active {
    transform: translateY(0) scale(0.98);
}

/* Shine sweep on Continue */
.ll-continue-glow {
    position: absolute;
    top: 0;
    left: -100%;
    width: 60%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.18), transparent);
    animation: continueSweep 3.5s ease-in-out infinite;
    pointer-events: none;
}

@keyframes continueSweep {
    0% {
        left: -100%;
    }

    100% {
        left: 200%;
    }
}

.ll-continue-text {
    position: relative;
    z-index: 2;
}


/* =====================================================================
   LORELOOM+ UPGRADE MODAL — RESPONSIVE
   ===================================================================== */

@media (max-width: 840px) {
    .ll-upgrade-modal {
        max-width: 96vw;
        max-height: 95vh;
        /* dvh tracks the *visible* viewport, so the modal shrinks instead of
           hiding under the URL/nav bars. Ordered after the vh line as a
           fallback for browsers without dvh support. */
        max-height: 95dvh;
    }

    .ll-upgrade-banner {
        min-height: 200px;
    }


    .ll-upgrade-banner-content {
        padding: 32px 28px 28px;
    }

    .ll-upgrade-heading {
        font-size: 2rem;
    }

    .ll-upgrade-subtitle {
        font-size: 0.92rem;
    }

    .ll-upgrade-body {
        grid-template-columns: 1fr;
        padding: 0 24px 32px;
        gap: 20px;
    }
}

@media (max-width: 560px) {
    .ll-upgrade-modal {
        max-width: 100vw;
        width: 100vw;
        max-height: 100vh;
        /* Same dvh fix as the 840px breakpoint — with plain 100vh the modal is
           taller than what Android Chrome actually shows and the banner
           heading gets cut off past the top edge. */
        max-height: 100dvh;
        border-radius: 0;
    }

    .ll-upgrade-banner {
        min-height: 170px;
    }


    .ll-upgrade-banner-content {
        padding: 24px 20px 22px;
    }

    .ll-upgrade-logo {
        height: 24px;
        margin-bottom: 12px;
    }

    .ll-upgrade-heading {
        font-size: 1.6rem;
        letter-spacing: -0.8px;
    }

    .ll-upgrade-subtitle {
        font-size: 0.85rem;
    }

    .ll-upgrade-body {
        padding: 0 16px 28px;
        gap: 16px;
    }

    .ll-upgrade-table {
        padding: 16px 16px;
    }

    .ll-upgrade-table-header {
        grid-template-columns: 1fr 48px 80px;
    }

    .ll-upgrade-table-row {
        grid-template-columns: 1fr 48px 80px;
        padding: 11px 0;
    }

    .ll-table-feature {
        font-size: 0.82rem;
        gap: 8px;
    }

    .ll-plan-card-inner {
        padding: 18px 18px;
    }

    .ll-plan-price {
        font-size: 1.7rem;
    }

    .ll-upgrade-continue-btn {
        padding: 16px 24px;
        font-size: 1rem;
    }

    .ll-upgrade-close {
        /* Pushed below the notch/status bar on the full-bleed mobile modal.
           --safe-top is 0 in a plain browser tab, so desktop is unaffected. */
        top: calc(12px + var(--safe-top));
        right: 12px;
        width: 38px;
        height: 38px;
        font-size: 1rem;
    }
}


/* ===== FIXED FULL-BLEED UPGRADE BANNER ===== */

.ll-upgrade-banner {
    position: relative;
    width: 100%;
    min-height: 240px;
    overflow: hidden;
    border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
    background: #15121e;
}

.ll-upgrade-castle-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center top;
    display: block;
    transform: scale(1.03);
    z-index: 1;
}

.ll-upgrade-banner-fade {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;

    background: linear-gradient(90deg,
            rgba(21, 18, 30, 0.96) 0%,
            rgba(21, 18, 30, 0.92) 18%,
            rgba(21, 18, 30, 0.70) 35%,
            rgba(21, 18, 30, 0.25) 60%,
            rgba(21, 18, 30, 0.04) 78%,
            transparent 100%);
}

.ll-upgrade-banner::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 48%;
    z-index: 3;
    pointer-events: none;

    background: linear-gradient(to top,
            rgba(21, 18, 30, 0.96) 0%,
            rgba(21, 18, 30, 0) 100%);
}

.ll-upgrade-banner-content {
    position: relative;
    z-index: 4;
    max-width: min(58%, 620px);
    padding: 44px 48px 36px;
}

/* remove old split layout */
.ll-upgrade-banner-artwork {
    display: none !important;
}

@media (max-width: 840px) {

    .ll-upgrade-banner {
        min-height: 200px;
    }

    .ll-upgrade-banner-content {
        max-width: 72%;
        padding: 32px 28px 28px;
    }
}

@media (max-width: 560px) {

    .ll-upgrade-banner {
        min-height: 170px;
        border-radius: 0;
    }

    .ll-upgrade-banner-content {
        max-width: 100%;
        /* Top padding clears the status-bar/notch inset (--safe-top resolves to
           0 in a normal browser tab, so this is a no-op on desktop). Right
           padding reserves the close button's 38px + 12px offset so the
           heading no longer runs underneath it. */
        padding: calc(24px + var(--safe-top)) 62px 22px 20px;
    }
}

/* ===================================================================
   ORB SHOP PAGE — REDESIGNED
   =================================================================== */

#page-shop {
    position: relative;
    overflow-y: auto;
    padding: 0 28px 60px;
}

/* --- Floating Particles --- */
.shop-particles {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    overflow: hidden;
}

.shop-particle {
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
    opacity: 0;
    animation: shopParticleFloat linear infinite;
}

@keyframes shopParticleFloat {
    0% {
        opacity: 0;
        transform: translateY(100vh) scale(0.3);
    }

    10% {
        opacity: 1;
    }

    90% {
        opacity: 1;
    }

    100% {
        opacity: 0;
        transform: translateY(-10vh) scale(1);
    }
}

/* ============ SHOP HERO ============ */
.shop-hero-section {
    position: relative;
    z-index: 1;
    border-radius: var(--border-radius-lg);
    padding: 44px 48px;
    margin-bottom: 32px;
    overflow: hidden;
    background: linear-gradient(160deg, rgba(30, 20, 55, 0.8) 0%, rgba(18, 12, 38, 0.95) 100%);
    border: 1px solid rgba(167, 119, 227, 0.18);
    box-shadow:
        0 20px 60px rgba(0, 0, 0, 0.45),
        0 0 80px rgba(167, 119, 227, 0.06),
        inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

.shop-hero-mesh {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(ellipse 60% 80% at 10% 20%, rgba(167, 119, 227, 0.2) 0%, transparent 60%),
        radial-gradient(ellipse 50% 70% at 90% 80%, rgba(110, 142, 251, 0.15) 0%, transparent 60%);
    pointer-events: none;
    animation: heroMeshDrift 12s ease-in-out infinite alternate;
}

/* Layered over .shop-hero-mesh, which is brand violet + brand blue. This third
   layer used to be rgba(255, 65, 108, 0.08) — the same foreign red as the old
   .orb-secondary, drifting on its own 15s cycle. Now a light-lilac highlight,
   so the hero has three depths of one hue instead of two hues plus a bruise. */
.shop-hero-mesh-2 {
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse 40% 50% at 70% 30%, rgba(226, 179, 255, 0.1) 0%, transparent 60%);
    pointer-events: none;
    animation: heroMeshDrift 15s ease-in-out infinite alternate-reverse;
}

@keyframes heroMeshDrift {
    0% {
        opacity: 0.7;
        transform: scale(1) translate(0, 0);
    }

    100% {
        opacity: 1;
        transform: scale(1.08) translate(10px, -8px);
    }
}

.shop-hero-noise {
    position: absolute;
    inset: 0;
    opacity: 0.03;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
    pointer-events: none;
}

.shop-hero-section::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(167, 119, 227, 0.4) 30%, rgba(110, 142, 251, 0.5) 50%, rgba(167, 119, 227, 0.4) 70%, transparent);
}

.shop-hero-content-wrap {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 32px;
}

.shop-hero-left {
    max-width: 480px;
}

.shop-hero-title {
    font-family: var(--font-display);
    font-size: 3rem;
    font-weight: 800;
    letter-spacing: -1px;
    background: linear-gradient(135deg, #e2b3ff 0%, #a777e3 40%, #6e8efb 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    margin: 0 0 8px;
    filter: drop-shadow(0 0 30px rgba(167, 119, 227, 0.25));
}

/* --- Balance Pill (inside hero) --- */
.shop-hero-balance-pill {
    position: relative;
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 24px 32px;
    background: linear-gradient(160deg, rgba(40, 28, 70, 0.7), rgba(20, 14, 40, 0.9));
    border: 1px solid rgba(167, 119, 227, 0.22);
    border-radius: var(--border-radius-md);
    backdrop-filter: blur(20px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(167, 119, 227, 0.08);
    overflow: hidden;
    min-width: 320px;
}

.shop-hero-balance-glow {
    position: absolute;
    top: -40px;
    left: 50%;
    transform: translateX(-50%);
    width: 200px;
    height: 200px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(167, 119, 227, 0.18) 0%, transparent 70%);
    pointer-events: none;
    animation: shopBalanceGlow 4s ease-in-out infinite alternate;
}

@keyframes shopBalanceGlow {
    from {
        opacity: 0.6;
        transform: translateX(-50%) scale(0.9);
    }

    to {
        opacity: 1;
        transform: translateX(-50%) scale(1.15);
    }
}

.shop-hero-orb-visual {
    position: relative;
    width: 72px;
    height: 72px;
    flex-shrink: 0;
}

.shop-hero-orb-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 0 10px rgba(167, 119, 227, 0.32));
    /* animation: shopOrbSpin 12s linear infinite; */
}

@keyframes shopOrbSpin {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

.shop-hero-orb-ring {
    position: absolute;
    inset: -6px;
    border-radius: 50%;
    border: 2px solid rgba(167, 119, 227, 0.25);
    animation: shopRingPulse 2.5s ease-in-out infinite;
    pointer-events: none;
}

.shop-hero-orb-ring.ring-2 {
    inset: -14px;
    border-color: rgba(110, 142, 251, 0.15);
    animation-delay: -1.2s;
}

@keyframes shopRingPulse {

    0%,
    100% {
        transform: scale(1);
        opacity: 0.5;
    }

    50% {
        transform: scale(1.18);
        opacity: 0;
    }
}

.shop-hero-balance-data {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* ── Contrast pass (WCAG 1.4.3) ────────────────────────────────────────────────
   The three muted colours in this pill all failed measured contrast, and the fix is
   recorded here once because the same three values recur across the file.
   Measured, not estimated: a probe composited each rgba() down its real ancestor
   background stack and scored the result against the ground. That step is the whole
   point — the raw triplet rgb(161,161,170) is a perfectly readable grey, and it is
   only the alpha over a near-black ground that made it a failure. An earlier audit
   pass reported this site clean on contrast, which was a bug in the audit (it parsed
   colour strings with a bare number regex, so every rgba() became NaN and every
   comparison silently came out false) and not a result.
     .shop-hero-balance-label  a .7  ->  4.25:1, needs 4.5
     .shop-hero-balance-unit   a .65 ->  3.12:1, needs 4.5
     .shop-hero-balance-sub    a .5  ->  2.69:1, needs 4.5
   Alpha was raised rather than the hue repicked, so "this is secondary text" survives
   as an intent — only the amount of it changes. The values are solved, not guessed:
   the minimum alpha that clears the threshold, rounded up to the next 0.05. The
   headroom was meant to cover the fact that .shop-hero-balance-pill carries a
   background GRADIENT and the composited solid the probe measured is only an
   approximation of it.

   The headroom was not enough, and the approximation was the problem, not the
   rounding. That probe walks ancestors looking for an opaque background-color and
   never reads background-image at all, so it scored this text against the near-black
   page ground while the pill actually paints a much lighter violet on top. Measured
   from rendered pixels instead (two screenshots of each element, the second with
   color:transparent, so the glyph pixels' true ground is read rather than modelled):
     .shop-hero-balance-label  a .8  ->  4.05:1 on rgb(50,36,78),  needs 4.5
     .shop-hero-balance-unit   a .9  ->  3.61:1 on rgb(54,38,83),  needs 4.5
     .shop-hero-balance-sub    a .8  ->  4.23:1 on rgb(46,32,73),  needs 4.5
   So all three moved in the right direction and stopped short. The fix is the same
   idea taken to its end: alpha 1. That is not a new colour -- rgb(161,161,170) is the
   value that was always intended, and removing the alpha is what stops it being
   washed out. Label and sub reach 5.47 and 5.77 that way.
   .shop-hero-balance-unit is the one exception: at alpha 1 its violet reaches only
   4.10, so its lightness is raised with hue and saturation held exactly --
   #a777e3 -> #b187e6, hsl 267/66% in both, L* 58.9 -> 63.7. That is a +4.8 L* move,
   at the "slightly different shade" end of the perceptual scale, and it is the whole
   change: the violet is still the brand violet.
   Note the label and sub deliberately land on the SAME value. They were .7 and .5,
   which reads like a hierarchy but was drift: both needed the same treatment, so one
   shared value is the fix rather than an extra change on top of it. Size and
   letter-spacing already separate them. ---------------------------------------- */
.shop-hero-balance-label {
    font-size: 0.7rem;
    color: #a1a1aa;   /* was rgba(161,161,170,.8) -> 4.05:1 on pixels; see the note above */
    font-weight: 600;
    letter-spacing: 1.8px;
}

.shop-hero-balance-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.shop-hero-balance-num {
    font-family: var(--font-display);
    font-size: 2.6rem;
    font-weight: 800;
    background: linear-gradient(135deg, #e2b3ff, #a777e3, #6e8efb);
    background-size: 200% 200%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    line-height: 1;
    animation: shopBalanceShimmer 4s ease infinite;
}

@keyframes shopBalanceShimmer {

    0%,
    100% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }
}

.shop-hero-balance-unit {
    font-size: 1rem;
    font-weight: 600;
    color: #b187e6;   /* was rgba(167,119,227,.9) -> 3.61:1 on pixels; see the note above */
}

.shop-hero-balance-sub {
    font-size: 0.72rem;
    color: #a1a1aa;   /* was rgba(161,161,170,.8) -> 4.23:1 on pixels; see the note above */
    margin-top: 2px;
}

/* ============ SHOP TABS ============ */
.shop-tabs-bar {
    position: relative;
    z-index: 1;
    display: flex;
    gap: 6px;
    margin-bottom: 28px;
    padding: 5px;
    background: rgba(20, 15, 35, 0.6);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-md);
    width: fit-content;
    backdrop-filter: blur(12px);
}

.shop-tab {
    padding: 10px 22px;
    border: none;
    border-radius: var(--border-radius-sm);
    background: transparent;
    /* was .7 -> 4.20:1 at 1440 and 4.14:1 at 390, both short of 4.5. This is the
       INACTIVE tab label, which WCAG does not exempt: an inactive tab is still an
       operable control, unlike a :disabled one. Same value as the balance label so
       the two muted greys stay one value; see the contrast note above that rule. */
    color: rgba(161, 161, 170, 0.8);
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
    display: flex;
    align-items: center;
    gap: 8px;
}

.shop-tab:hover {
    color: #fff;
    background: rgba(167, 119, 227, 0.08);
}

.shop-tab.active {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.2), rgba(110, 142, 251, 0.15));
    color: #e2b3ff;
    border: 1px solid rgba(167, 119, 227, 0.25);
    box-shadow: 0 4px 16px rgba(167, 119, 227, 0.1);
}

.shop-tab.active i {
    color: #c9a0f5;
}

.shop-tab-panel {
    display: none;
    animation: shopPanelFadeIn 0.35s ease;
}

.shop-tab-panel.active {
    display: block;
}

@keyframes shopPanelFadeIn {
    from {
        opacity: 0;
        transform: translateY(8px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ============ SHOP GRID ============ */
.shop-grid {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 22px;
}

/* ============ SHOP CARD ============ */
/* Surface (radius, clip, resting depth, hover transition) comes from the
   primitive near the top of the file. Only the layout is local. */
.shop-card {
    display: flex;
}

.shop-card:hover {
    transform: translateY(-6px) scale(1.02);
    box-shadow:
        0 26px 60px -14px rgba(0, 0, 0, 0.6),
        0 0 44px rgba(167, 119, 227, 0.16);
}

.shop-card-glow {
    position: absolute;
    top: -40px;
    right: -40px;
    width: 160px;
    height: 160px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(167, 119, 227, 0.1) 0%, transparent 70%);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s ease;
}

.shop-card:hover .shop-card-glow {
    opacity: 1;
}

.shop-card-border-anim {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.12), rgba(110, 142, 251, 0.08), rgba(167, 119, 227, 0.12));
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
    transition: background 0.4s ease;
}

.shop-card:hover .shop-card-border-anim {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.35), rgba(110, 142, 251, 0.25), rgba(226, 179, 255, 0.3));
    animation: shopBorderRotate 3s linear infinite;
    background-size: 300% 300%;
}

@keyframes shopBorderRotate {
    0% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }

    100% {
        background-position: 0% 50%;
    }
}

/* --- Popularity Badge --- */
.shop-card-popularity {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 5;
    padding: 5px 12px;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(135deg, rgba(255, 149, 0, 0.2), rgba(255, 94, 0, 0.15));
    border: 1px solid rgba(255, 149, 0, 0.35);
    color: #ffb347;
    font-size: 0.72rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 5px;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    backdrop-filter: blur(8px);
}

.shop-card-popularity i {
    font-size: 0.68rem;
}

/* --- Premium Tier Ribbon --- */
.shop-card-tier-ribbon {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 5;
    padding: 5px 12px;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.2), rgba(110, 61, 231, 0.18));
    border: 1px solid rgba(167, 119, 227, 0.4);
    color: #d6b4ff;
    font-size: 0.72rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 5px;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    backdrop-filter: blur(8px);
}

.shop-card-inner {
    position: relative;
    z-index: 2;
    flex: 1;
    width: 100%;
    /* Layered surface: a soft top sheen fading into a deep base, so the
       glass tile catches light at the top edge and grounds toward the
       footer. Reads richer than a flat two-stop fill. */
    background:
        radial-gradient(140% 90% at 50% -10%, rgba(167, 119, 227, 0.14) 0%, transparent 55%),
        linear-gradient(168deg, rgba(38, 28, 60, 0.82) 0%, rgba(22, 16, 36, 0.92) 46%, rgba(15, 11, 24, 0.95) 100%);
    backdrop-filter: blur(22px);
    /* Track the parent rather than restating its number. This used to be a
       literal 20px with a second literal 16px in a @media -- which silently
       became wrong the moment the parent's snap moved breakpoint. */
    border-radius: inherit;
    padding: 28px 24px 22px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 12px;
    /* Crisp inner top-edge highlight + soft inner floor shadow = real depth. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.10),
        inset 0 -20px 40px -24px rgba(0, 0, 0, 0.6);
}

.shop-card-icon-wrap {
    position: relative;
    width: 68px;
    height: 68px;
    border-radius: var(--border-radius-md);
    background:
        radial-gradient(120% 120% at 30% 20%, color-mix(in srgb, var(--icon-color, #a777e3) 30%, transparent), transparent 70%),
        var(--icon-bg, rgba(167, 119, 227, 0.12));
    border: 1px solid var(--icon-border, rgba(167, 119, 227, 0.3));
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.7rem;
    color: var(--icon-color, #a777e3);
    margin-bottom: 2px;
    box-shadow:
        0 8px 22px -8px color-mix(in srgb, var(--icon-color, #a777e3) 55%, transparent),
        inset 0 1px 0 rgba(255, 255, 255, 0.12);
    filter: drop-shadow(0 0 10px color-mix(in srgb, var(--icon-color, #a777e3) 35%, transparent));
    transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease;
}

/* Soft halo behind the icon tile for extra depth */
.shop-card-icon-wrap::after {
    content: '';
    position: absolute;
    inset: -18px;
    border-radius: 50%;
    background: radial-gradient(circle, color-mix(in srgb, var(--icon-color, #a777e3) 22%, transparent) 0%, transparent 68%);
    opacity: 0.6;
    z-index: -1;
    pointer-events: none;
    transition: opacity 0.35s ease;
}

.shop-card:hover .shop-card-icon-wrap {
    transform: translateY(-3px) scale(1.08) rotate(-4deg);
    box-shadow:
        0 14px 30px -8px color-mix(in srgb, var(--icon-color, #a777e3) 70%, transparent),
        inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

.shop-card:hover .shop-card-icon-wrap::after {
    opacity: 1;
}

.shop-card-title {
    font-family: var(--font-display);
    font-size: 1.18rem;
    font-weight: 700;
    letter-spacing: -0.3px;
    color: #fff;
    margin: 0;
    text-shadow: 0 1px 12px rgba(167, 119, 227, 0.18);
}

.shop-card-desc {
    font-size: 0.82rem;
    color: rgba(190, 190, 205, 0.82);
    line-height: 1.55;
    margin: 0;
    max-width: 30ch;
}

/* --- Card Tags --- */
.shop-card-tags {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: center;
    margin: 4px 0;
}

/* .shop-tag's grey is the same grey as the balance labels and it failed for the same
   reason, on a different surface: the chip's own rgba(255,255,255,.04) sits on a card
   that is itself translucent, so the ground under the text measures rgb(43,38,54) --
   lighter than the page, which is what the modelled probe assumed. 4.21:1 at alpha .8.
   Same fix, same reasoning as the note at .shop-hero-balance-label: alpha 1 is the
   colour that was always meant, and it lands at 5.72:1. -------------------------- */
.shop-tag {
    padding: 4px 10px;
    border-radius: var(--border-radius-xs);
    font-size: 0.7rem;
    font-weight: 600;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: #a1a1aa;
    display: flex;
    align-items: center;
    gap: 4px;
}

.shop-tag i {
    font-size: 0.6rem;
    opacity: 0.7;
}

.shop-tag.tag-value {
    background: rgba(255, 215, 0, 0.08);
    border-color: rgba(255, 215, 0, 0.2);
    color: #ffd700;
}

.shop-tag.tag-premium {
    background: rgba(167, 119, 227, 0.1);
    border-color: rgba(167, 119, 227, 0.25);
    color: #c9a0f5;
}

/* --- Card Footer (cost + button side by side) --- */
.shop-card-footer {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    margin-top: auto;
    padding-top: 8px;
}

.shop-card-cost {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    background:
        linear-gradient(135deg, rgba(167, 119, 227, 0.14), rgba(110, 142, 251, 0.08));
    border: 1px solid rgba(167, 119, 227, 0.22);
    border-radius: var(--border-radius-sm);
    flex-shrink: 0;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

.shop-cost-orb-icon {
    width: 18px;
    height: 18px;
    object-fit: contain;
    filter: drop-shadow(0 0 6px rgba(167, 119, 227, 0.4));
}

.shop-cost-value {
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    background: linear-gradient(135deg, #e2b3ff, #a777e3);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* --- Buy Button --- */
.shop-buy-btn {
    position: relative;
    flex: 1;
    padding: 12px 18px;
    border: none;
    border-radius: var(--border-radius-sm);
    background: var(--action-gradient);
    background-size: 200% 200%;
    color: var(--on-brand-gradient);
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-weight: 700;
    cursor: pointer;
    overflow: hidden;
    transition:
        background 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        border-color 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        box-shadow 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        color 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        filter 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        opacity 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    box-shadow: 0 6px 20px rgba(110, 61, 231, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15);
    letter-spacing: 0.4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    animation: btnGradientShift 4s ease infinite;
}

.shop-buy-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 30px rgba(110, 61, 231, 0.45), 0 0 30px rgba(167, 119, 227, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.shop-buy-btn:active {
    transform: translateY(0) scale(0.98);
}

.shop-buy-btn-glow {
    position: absolute;
    top: 0;
    left: -100%;
    width: 60%;
    height: 100%;
    background: linear-gradient(to right, transparent 0%, rgba(255, 255, 255, 0.08) 30%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.08) 70%, transparent 100%);
    transform: skewX(-25deg);
    pointer-events: none;
}

.shop-buy-btn:hover .shop-buy-btn-glow {
    animation: premiumShimmer 2s ease infinite;
}

/* --- Premium Card Variant --- */
.shop-card--premium .shop-card-border-anim {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.18), rgba(255, 65, 108, 0.1), rgba(167, 119, 227, 0.18));
}

.shop-card--premium:hover .shop-card-border-anim {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.4), rgba(255, 65, 108, 0.25), rgba(226, 179, 255, 0.35));
}

/* --- "Best Value" ribbon variant (gold, sits on the flagship) --- */
.shop-card-tier-ribbon.tier-ribbon--value {
    background: linear-gradient(135deg, rgba(255, 215, 0, 0.22), rgba(255, 165, 0, 0.16));
    border-color: rgba(255, 215, 0, 0.45);
    color: #ffe27a;
}

/* --- Flagship card: the single "hero" offer that anchors the value story.
   Persistent glow + subtle lift so it reads as the recommended pick even
   before hover. Only one flagship per shop (marketing: primary-action). --- */
.shop-card--flagship {
    transform: translateY(-4px);
}

.shop-card--flagship .shop-card-inner {
    background: linear-gradient(170deg, rgba(44, 32, 66, 0.85) 0%, rgba(22, 16, 38, 0.92) 100%);
}

.shop-card--flagship .shop-card-border-anim {
    padding: 1.5px;
    background: linear-gradient(135deg, rgba(255, 215, 0, 0.4), rgba(167, 119, 227, 0.35), rgba(46, 204, 113, 0.3));
    background-size: 300% 300%;
    animation: shopBorderRotate 4s linear infinite;
}

.shop-card--flagship .shop-card-glow {
    opacity: 0.7;
    background: radial-gradient(circle, rgba(255, 215, 0, 0.14) 0%, transparent 70%);
}

.shop-card--flagship:hover {
    transform: translateY(-10px) scale(1.02);
    box-shadow: 0 26px 60px rgba(0, 0, 0, 0.55), 0 0 46px rgba(255, 215, 0, 0.14);
}

.shop-card--flagship:hover .shop-card-glow {
    opacity: 1;
}

/* --- Locked / Disabled --- */
.shop-card.locked .shop-buy-btn {
    background: rgba(255, 255, 255, 0.06);
    color: rgba(161, 161, 170, 0.5);
    box-shadow: none;
    cursor: not-allowed;
    animation: none;
    border: 1px solid rgba(255, 255, 255, 0.06);
}

.shop-card.locked .shop-buy-btn:hover {
    transform: none;
    box-shadow: none;
}

.shop-card.locked .shop-buy-btn .shop-buy-btn-glow {
    display: none;
}

.shop-card.locked {
    opacity: 0.6;
    filter: grayscale(0.3);
}

.shop-card.locked:hover {
    transform: none;
    box-shadow: none;
}

/* --- Purchase Flash --- */
.shop-card.purchased .shop-card-inner {
    animation: shopPurchaseFlash 0.6s ease;
}

@keyframes shopPurchaseFlash {
    0% {
        box-shadow: inset 0 0 0 rgba(46, 204, 113, 0);
    }

    30% {
        box-shadow: inset 0 0 40px rgba(46, 204, 113, 0.15);
    }

    100% {
        box-shadow: inset 0 0 0 rgba(46, 204, 113, 0);
    }
}

/* --- Toast --- */
.shop-toast {
    position: fixed;
    bottom: 40px;
    left: 50%;
    transform: translateX(-50%) translateY(100px);
    z-index: 9999;
    opacity: 0;
    transition:
        opacity 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
        transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
    pointer-events: none;
}

.shop-toast.visible {
    transform: translateX(-50%) translateY(0);
    opacity: 1;
    pointer-events: auto;
}

.shop-toast-inner {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 28px;
    background: linear-gradient(135deg, rgba(46, 204, 113, 0.12), rgba(39, 174, 96, 0.18));
    border: 1px solid rgba(46, 204, 113, 0.35);
    border-radius: var(--border-radius-md);
    backdrop-filter: blur(20px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4), 0 0 20px rgba(46, 204, 113, 0.1);
    color: #2ecc71;
    font-weight: 600;
    font-size: 0.9rem;
    white-space: nowrap;
}

.shop-toast-inner i {
    font-size: 1.1rem;
    filter: drop-shadow(0 0 6px rgba(46, 204, 113, 0.4));
}

.shop-toast.error .shop-toast-inner {
    background: linear-gradient(135deg, rgba(var(--color-danger-rgb), 0.12), rgba(var(--color-danger-rgb), 0.18));
    border-color: rgba(var(--color-danger-rgb), 0.35);
    color: var(--color-danger);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4), 0 0 20px rgba(var(--color-danger-rgb), 0.1);
}

/* --- Responsive --- */
@media (max-width: 900px) {
    .shop-hero-content-wrap {
        flex-direction: column;
        align-items: flex-start;
    }

    .shop-hero-balance-pill {
        min-width: unset;
        width: 100%;
    }

    /* When the hero stacks, let the level widget sit full-width and left-aligned
       under the heading instead of clinging to a 300px min-width at the edge. */
    .achievements-hero-section .shop-hero-right {
        min-width: 0;
        width: 100%;
        justify-content: flex-start;
    }
}

@media (max-width: 768px) {
    #page-shop {
        padding: 0 16px 40px;
    }

    .shop-hero-section {
        padding: 28px 22px;
        border-radius: var(--border-radius-md);
    }

    .shop-hero-title {
        font-size: 2.2rem;
    }

    .shop-hero-balance-pill {
        padding: 18px 20px;
        gap: 14px;
    }

    .shop-hero-orb-visual {
        width: 56px;
        height: 56px;
    }

    .shop-hero-balance-num {
        font-size: 2rem;
    }

    .shop-grid {
        grid-template-columns: 1fr;
        gap: 16px;
    }

    .shop-card-inner {
        padding: 24px 20px 20px;
    }

    .shop-tabs-bar {
        width: 100%;
    }

    .shop-tab {
        flex: 1;
        justify-content: center;
        font-size: 0.8rem;
        padding: 10px 14px;
    }
}

@media (max-width: 480px) {
    .shop-hero-title {
        font-size: 1.8rem;
    }

    .shop-hero-balance-pill {
        flex-direction: column;
        text-align: center;
        align-items: center;
    }

    .shop-hero-balance-data {
        align-items: center;
    }

    .shop-hero-balance-num {
        font-size: 1.8rem;
    }

    .shop-card-footer {
        flex-direction: column;
    }
}

/* ============================================================
   SHOP — UX ENHANCEMENTS (a11y, affordability, focus, motion)
   ============================================================ */

/* Tabular figures keep balances & prices from shifting width */
.shop-hero-balance-num,
.shop-cost-value,
.shop-card-meter-label {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* --- Keyboard focus rings (only for keyboard users) --- */
.shop-tab:focus-visible,
.shop-buy-btn:focus-visible,
.reward-chip:focus-visible,
.shop-hero-balance-pill:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
    border-radius: var(--border-radius-sm);
}

/* --- Affordability meter (shown only on locked cards) --- */
.shop-card-meter {
    display: none;
    width: 100%;
    flex-direction: column;
    gap: 5px;
    margin: 2px 0 4px;
}

.shop-card.locked .shop-card-meter {
    display: flex;
}

.shop-card-meter-track {
    position: relative;
    height: 6px;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.07);
    overflow: hidden;
}

.shop-card-meter-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 0;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(90deg, #6e8efb, #a777e3);
    box-shadow: 0 0 12px rgba(167, 119, 227, 0.45);
    transition: width 0.6s cubic-bezier(0.34, 1.2, 0.64, 1);
}

.shop-card-meter-label {
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.3px;
    color: rgba(201, 160, 245, 0.85);
    align-self: flex-end;
}

.shop-card-meter-label .meter-need {
    color: rgba(161, 161, 170, 0.7);
    font-weight: 500;
}

/* --- Affordable cards get a quiet "ready" cue --- */
.shop-card.affordable .shop-card-cost {
    border-color: rgba(46, 204, 113, 0.3);
    background: rgba(46, 204, 113, 0.08);
}

.shop-card.affordable .shop-cost-value {
    background: linear-gradient(135deg, #7bffb0, #2ecc71);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* Locked buy button — disabled state clarity */
.shop-buy-btn:disabled {
    cursor: not-allowed;
    opacity: 0.75;
}

/* --- Balance count-up flash --- */
.shop-hero-balance-num.counting {
    filter: drop-shadow(0 0 14px rgba(167, 119, 227, 0.55));
}

/* --- Respect reduced-motion: calm the ambient animations --- */
@media (prefers-reduced-motion: reduce) {

    .shop-hero-mesh,
    .shop-hero-mesh-2,
    .shop-hero-balance-glow,
    .shop-hero-orb-ring,
    .shop-hero-balance-num,
    .shop-buy-btn,
    .shop-card:hover .shop-card-border-anim,
    .shop-buy-btn:hover .shop-buy-btn-glow,
    .shop-tab-panel.active {
        animation: none !important;
    }

    .shop-card,
    .shop-card:hover,
    .shop-buy-btn,
    .shop-buy-btn:hover,
    .shop-card-icon-wrap,
    .shop-card:hover .shop-card-icon-wrap,
    .shop-card-meter-fill,
    .reward-chip,
    .reward-chip:hover {
        transition-duration: 0.01ms !important;
        transform: none !important;
    }
}

/* =====================================================================
   SETTINGS V2 — Premium SaaS Redesign (Linear / Discord / Arc / Notion)
   ===================================================================== */

/* ---- Ambient Page Background & Glows ---- */
#page-settings {
    position: relative !important;
    background: #06040a !important;
    padding: 40px 24px !important;
    box-sizing: border-box !important;
    display: none;
    /* Controlled by JS flex/none */
    align-items: center !important;
    justify-content: center !important;
    overflow: hidden !important;
    width: 100% !important;
    height: 100% !important;
}

#page-settings::before {
    content: '';
    position: absolute;
    top: -10%;
    right: -10%;
    width: 600px;
    height: 600px;
    background: radial-gradient(circle, rgba(167, 119, 227, 0.08) 0%, transparent 70%);
    filter: blur(120px);
    pointer-events: none;
    z-index: 0;
}

#page-settings::after {
    content: '';
    position: absolute;
    bottom: -10%;
    left: -10%;
    width: 600px;
    height: 600px;
    background: radial-gradient(circle, rgba(110, 142, 251, 0.06) 0%, transparent 70%);
    filter: blur(120px);
    pointer-events: none;
    z-index: 0;
}

/* ---- Floating Premium Glass Container ---- */
.settings-wrapper-v2 {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 1100px;
    height: 100%;
    max-height: 700px;
    background: linear-gradient(135deg, rgba(20, 15, 30, 0.45) 0%, rgba(10, 8, 15, 0.75) 100%);
    backdrop-filter: blur(30px);
    -webkit-backdrop-filter: blur(30px);
    border: 1px solid rgba(167, 119, 227, 0.12);
    border-radius: var(--border-radius-lg);
    overflow: hidden;
    position: relative;
    box-shadow:
        0 24px 64px rgba(0, 0, 0, 0.7),
        0 0 100px rgba(167, 119, 227, 0.02),
        inset 0 1px 0 rgba(255, 255, 255, 0.05);
    z-index: 1;
    animation: settingsScaleIn 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes settingsScaleIn {
    from {
        opacity: 0;
        transform: scale(0.97) translateY(10px);
    }

    to {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

/* ---- Premium Top Bar ---- */
.stg-topbar {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 20px 32px;
    border-bottom: 1px solid rgba(167, 119, 227, 0.08);
    flex-shrink: 0;
    background: rgba(14, 11, 20, 0.25);
    z-index: 5;
}

.stg-back-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.06);
    color: #e2d0ff;
    padding: 8px 16px;
    border-radius: var(--border-radius-xs);
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0.3px;
    cursor: pointer;
    transition:
        background 0.2s cubic-bezier(0.16, 1, 0.3, 1),
        border-color 0.2s cubic-bezier(0.16, 1, 0.3, 1),
        box-shadow 0.2s cubic-bezier(0.16, 1, 0.3, 1),
        color 0.2s cubic-bezier(0.16, 1, 0.3, 1),
        transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
    font-family: var(--font-display);
}

.stg-back-btn:hover {
    background: rgba(167, 119, 227, 0.1);
    color: #ffffff;
    border-color: rgba(167, 119, 227, 0.25);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(167, 119, 227, 0.15);
}

.stg-back-btn i {
    font-size: 0.8rem;
    transition: transform 0.2s ease;
}

.stg-back-btn:hover i {
    transform: translateX(-2px);
}

.stg-topbar-title {
    font-size: 1.1rem;
    font-weight: 800;
    font-family: var(--font-display);
    color: #e2b3ff;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    text-shadow: 0 0 10px rgba(167, 119, 227, 0.6);
    margin-left: auto;
}

/* ---- Two-Column Layout ---- */
.stg-layout {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

/* ---- Left Sidebar ---- */
.stg-sidebar {
    width: 260px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    padding: 24px 20px;
    border-right: 1px solid rgba(167, 119, 227, 0.08);
    overflow-y: auto;
    gap: 20px;
    background: rgba(10, 8, 15, 0.15);
}

/* ---- Premium Profile Card ---- */
.stg-profile-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px;
    border-radius: var(--border-radius-sm);
    background: rgba(255, 255, 255, 0.015);
    border: 1px solid rgba(255, 255, 255, 0.04);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
    margin-bottom: 8px;
    position: relative;
    overflow: hidden;
    transition:
        background 0.25s ease,
        border-color 0.25s ease,
        box-shadow 0.25s ease,
        transform 0.25s ease;
}

.stg-profile-card:hover {
    background: rgba(255, 255, 255, 0.03);
    border-color: rgba(167, 119, 227, 0.25);
    transform: translateY(-1px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.stg-avatar {
    width: 40px;
    height: 40px;
    border-radius: var(--border-radius-sm);
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.8) 0%, rgba(110, 142, 251, 0.8) 100%);
    padding: 1px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    box-shadow: 0 4px 12px rgba(167, 119, 227, 0.25);
}

.stg-avatar i {
    width: 100%;
    height: 100%;
    background: #110e19;
    border-radius: calc(var(--border-radius-sm) - 1px);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.95rem;
    color: #e2d0ff;
    transition:
        background 0.25s ease,
        color 0.25s ease,
        filter 0.25s ease,
        opacity 0.25s ease,
        text-shadow 0.25s ease,
        transform 0.25s ease;
}

.stg-profile-card:hover .stg-avatar i {
    background: transparent;
    color: #ffffff;
}

.stg-profile-info {
    flex: 1;
    min-width: 0;
}

.stg-profile-name {
    font-size: 0.92rem;
    font-family: var(--font-display);
    font-weight: 600;
    color: #ffffff;
    margin: 0 0 4px 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.stg-profile-meta {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.stg-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px;
    border-radius: var(--border-radius-xs);
    font-size: 0.65rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0.2px;
}

.stg-badge--plan {
    background: rgba(167, 119, 227, 0.08);
    border: 1px solid rgba(167, 119, 227, 0.2);
    color: #d1b9ff;
}

.stg-badge--plan.gold {
    background: rgba(255, 215, 0, 0.08);
    border-color: rgba(255, 215, 0, 0.25);
    color: #ffd700;
}

.stg-badge--orbs {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.06);
    color: #d1d1d6;
}

.stg-badge--orbs svg {
    margin-right: 1px;
}

/* ---- Sidebar Nav ---- */
.stg-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.stg-nav-label {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 1px;
    /* was .35 -> 3.08:1. Four white-alpha text colours in this file failed contrast
       (.35 here, .4 on .stg-field-hint, .3 on .dob-trigger-value.placeholder, .42 on
       .alw-cap) and all four solve to the same .5, so they now share one value. Note
       what is deliberately NOT changed: rgba(255,255,255,0.25) on
       .stg-save-btn:disabled measures 2.27:1, the worst number in the audit, and is
       exempt — WCAG 1.4.3 does not apply to an inactive control, and lifting it would
       make a disabled button look enabled. */
    color: rgba(255, 255, 255, 0.5);
    font-weight: 600;
    padding: 12px 10px 6px;
}

.stg-nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: none;
    background: transparent;
    color: #a1a1aa;
    font-size: 0.82rem;
    font-weight: 500;
    cursor: pointer;
    border-radius: var(--border-radius-xs);
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
    font-family: 'Inter', sans-serif;
    text-align: left;
    width: 100%;
}

.stg-nav-item i {
    width: 16px;
    text-align: center;
    font-size: 0.85rem;
    opacity: 0.6;
    transition:
        background 0.2s ease,
        color 0.2s ease,
        filter 0.2s ease,
        opacity 0.2s ease,
        text-shadow 0.2s ease,
        transform 0.2s ease;
}

.stg-nav-item:hover {
    background: rgba(255, 255, 255, 0.03);
    color: #ffffff;
}

.stg-nav-item:hover i {
    opacity: 0.9;
    color: #ffffff;
}

.stg-nav-item.active {
    background: rgba(167, 119, 227, 0.08);
    border: 1px solid rgba(167, 119, 227, 0.15);
    color: #e2b3ff;
}

.stg-nav-item.active i {
    opacity: 1;
    color: #a777e3;
}

/* ---- Sidebar Note ---- */
.stg-sidebar-note {
    margin-top: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px;
    border-radius: var(--border-radius-xs);
    background: rgba(167, 119, 227, 0.03);
    border: 1px solid rgba(167, 119, 227, 0.08);
}

.stg-sidebar-note i {
    color: #a777e3;
    font-size: 0.85rem;
    margin-top: 1px;
    flex-shrink: 0;
}

.stg-sidebar-note p {
    margin: 0;
    font-size: 0.72rem;
    color: #8e8e93;
    line-height: 1.45;
}

.stg-sidebar-note strong {
    color: #c9a0f5;
}

/* ---- Right Content Panel ---- */
.stg-content {
    flex: 1;
    overflow-y: auto;
    padding: 24px 32px 90px;
    min-height: 0;
}

.settings-tab-content {
    max-width: 680px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
}

/* ---- Section Header ---- */
.stg-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0;
}

.stg-section-title {
    font-size: 1.6rem;
    font-weight: 700;
    font-family: var(--font-display);
    color: #fff;
    margin: 0;
    letter-spacing: -0.02em;
}

.stg-section-desc {
    font-size: 0.88rem;
    color: rgba(255, 255, 255, 0.5);
    margin: 6px 0 0 0;
}

/* ---- Divider ---- */
.stg-divider {
    height: 1px;
    background: linear-gradient(90deg, rgba(167, 119, 227, 0.15) 0%, transparent 100%);
    margin: 16px 0 20px;
}

/* ---- Quick Info Chips ---- */
.stg-quick-info {
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
}

.stg-quick-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 16px;
    border-radius: var(--border-radius-pill);
    background: rgba(167, 119, 227, 0.05);
    border: 1px solid rgba(167, 119, 227, 0.15);
    font-size: 0.78rem;
    color: #e2b3ff;
    font-weight: 500;
}

.stg-quick-chip i {
    color: #a777e3;
    font-size: 0.8rem;
}

/* ---- Field Groups ---- */
.stg-field-group {
    margin-bottom: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.stg-field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
}

.stg-field {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.stg-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.75);
    letter-spacing: 0.2px;
}

.stg-field-hint {
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.5);   /* was .4 -> 3.71:1; see .stg-nav-label above */
    margin: 0 0 4px 0;
    line-height: 1.5;
}

/* ---- Input Styles ---- */
.stg-input {
    width: 100%;
    background-color: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.05);
    color: #f0eaf8;
    padding: 12px 16px;
    border-radius: var(--border-radius-sm);
    font-size: 0.88rem;
    font-family: 'Inter', sans-serif;
    transition:
        background 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
    box-sizing: border-box;
    outline: none;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
}

.stg-input::placeholder {
    color: rgba(255, 255, 255, 0.2);
}

/* Locked (permanent) field — e.g. username after signup */
.stg-input--locked {
    cursor: not-allowed;
    color: rgba(240, 234, 248, 0.65);
    background-color: rgba(255, 255, 255, 0.015);
    padding-right: 38px;
}

.stg-input--locked:hover {
    border-color: rgba(255, 255, 255, 0.05);
    background-color: rgba(255, 255, 255, 0.015);
}

.stg-input-wrapper .stg-lock-badge {
    position: absolute;
    right: 14px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.8rem;
    color: rgba(167, 119, 227, 0.6);
    pointer-events: none;
}

.stg-username-lock-note {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
    font-size: 0.74rem;
    color: rgba(255, 255, 255, 0.35);
}

.stg-username-lock-note i {
    font-size: 0.68rem;
    color: rgba(167, 119, 227, 0.55);
}

.stg-input:hover {
    border-color: rgba(167, 119, 227, 0.25);
    background-color: rgba(255, 255, 255, 0.03);
}

.stg-input:focus {
    border-color: rgba(167, 119, 227, 0.5);
    background-color: rgba(255, 255, 255, 0.04);
    box-shadow: 0 0 0 2px rgba(167, 119, 227, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.02);
    transform: translateY(-1px);
}

/* ---- Select ---- */
.stg-select {
    background-image: none !important;
    appearance: auto !important;
    -webkit-appearance: menulist !important;
    cursor: pointer;
    padding-right: 36px;
}

.stg-select:hover,
.stg-select:focus {
    background-image: none !important;
}

.stg-select option {
    background: #100b18;
    color: #f0eaf8;
    padding: 12px;
}

/* ---- Hide native select, show custom animated dropdown ---- */
.stg-select--native {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.pronoun-select {
    position: relative;
    width: 100%;
}

.pronoun-trigger {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    background-color: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.05);
    color: #f0eaf8;
    padding: 12px 16px;
    border-radius: var(--border-radius-sm);
    font-size: 0.88rem;
    font-family: 'Inter', sans-serif;
    cursor: pointer;
    text-align: left;
    box-sizing: border-box;
    outline: none;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
    transition:
        background 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}

.pronoun-trigger:hover {
    border-color: rgba(167, 119, 227, 0.25);
    background-color: rgba(255, 255, 255, 0.03);
}

.pronoun-select.open .pronoun-trigger {
    border-color: rgba(167, 119, 227, 0.5);
    background-color: rgba(255, 255, 255, 0.04);
    box-shadow: 0 0 0 2px rgba(167, 119, 227, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.02);
}

.pronoun-trigger:focus-visible {
    border-color: rgba(167, 119, 227, 0.6);
    box-shadow: 0 0 0 2px rgba(167, 119, 227, 0.18);
}

.pronoun-chevron {
    font-size: 0.7rem;
    color: rgba(167, 119, 227, 0.7);
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), color 0.2s ease;
    flex-shrink: 0;
}

.pronoun-select.open .pronoun-chevron {
    transform: rotate(180deg);
    color: #a777e3;
}

.pronoun-menu {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: 0;
    z-index: 50;
    background: rgba(20, 14, 30, 0.96);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border: 1px solid rgba(167, 119, 227, 0.18);
    border-radius: var(--border-radius-md);
    padding: 6px;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.02);
    opacity: 0;
    transform: translateY(-8px) scale(0.98);
    transform-origin: top center;
    pointer-events: none;
    transition: opacity 0.22s ease, transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}

.pronoun-select.open .pronoun-menu {
    opacity: 1;
    transform: translateY(0) scale(1);
    pointer-events: auto;
}

.pronoun-option {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    background: transparent;
    border: none;
    color: rgba(240, 234, 248, 0.75);
    padding: 11px 12px;
    border-radius: var(--border-radius-xs);
    font-size: 0.86rem;
    font-family: 'Inter', sans-serif;
    text-align: left;
    cursor: pointer;
    transition: background 0.18s ease, color 0.18s ease, transform 0.12s ease;
}

.pronoun-option+.pronoun-option {
    margin-top: 2px;
}

.pronoun-option:hover {
    background: rgba(167, 119, 227, 0.12);
    color: #f0eaf8;
}

.pronoun-option:active {
    transform: scale(0.985);
}

.pronoun-option i {
    font-size: 0.72rem;
    color: #a777e3;
    opacity: 0;
    transform: scale(0.6);
    transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
}

.pronoun-option.selected {
    background: rgba(167, 119, 227, 0.1);
    color: #f0eaf8;
}

.pronoun-option.selected i {
    opacity: 1;
    transform: scale(1);
}

.pronoun-select.open .pronoun-option {
    animation: pronounOptionIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.pronoun-select.open .pronoun-option:nth-child(1) {
    animation-delay: 0.02s;
}

.pronoun-select.open .pronoun-option:nth-child(2) {
    animation-delay: 0.05s;
}

.pronoun-select.open .pronoun-option:nth-child(3) {
    animation-delay: 0.08s;
}

.pronoun-select.open .pronoun-option:nth-child(4) {
    animation-delay: 0.11s;
}

.pronoun-select.open .pronoun-option:nth-child(5) {
    animation-delay: 0.14s;
}

@keyframes pronounOptionIn {
    from {
        opacity: 0;
        transform: translateY(-4px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {

    .pronoun-menu,
    .pronoun-chevron,
    .pronoun-option,
    .pronoun-option i {
        transition: none;
    }

    .pronoun-select.open .pronoun-option {
        animation: none;
    }
}

/* ===== Custom Date of Origin (Birthday) picker ===== */
.stg-input--native-date {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.dob-picker {
    position: relative;
    width: 100%;
}

.dob-trigger {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 12px;
    background-color: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.05);
    color: #f0eaf8;
    padding: 12px 16px;
    border-radius: var(--border-radius-sm);
    font-size: 0.88rem;
    font-family: 'Inter', sans-serif;
    cursor: pointer;
    text-align: left;
    box-sizing: border-box;
    outline: none;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
    transition:
        background 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
        box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}

.dob-trigger:hover {
    border-color: rgba(167, 119, 227, 0.25);
    background-color: rgba(255, 255, 255, 0.03);
}

.dob-picker.open .dob-trigger {
    border-color: rgba(167, 119, 227, 0.5);
    background-color: rgba(255, 255, 255, 0.04);
    box-shadow: 0 0 0 2px rgba(167, 119, 227, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.02);
}

.dob-trigger:focus-visible {
    border-color: rgba(167, 119, 227, 0.6);
    box-shadow: 0 0 0 2px rgba(167, 119, 227, 0.18);
}

.dob-trigger-icon {
    color: #a777e3;
    font-size: 0.95rem;
    flex-shrink: 0;
}

.dob-trigger-value {
    flex: 1;
}

.dob-trigger-value.placeholder {
    color: rgba(255, 255, 255, 0.5);   /* was .3 -> 2.59:1; see .stg-nav-label above */
}

.dob-chevron {
    font-size: 0.7rem;
    color: rgba(167, 119, 227, 0.7);
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
    flex-shrink: 0;
}

.dob-picker.open .dob-chevron {
    transform: rotate(180deg);
    color: #a777e3;
}

.dob-pop {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: 60;
    width: 300px;
    max-width: 100%;
    background: rgba(20, 14, 30, 0.97);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border: 1px solid rgba(167, 119, 227, 0.18);
    border-radius: var(--border-radius-md);
    padding: 14px;
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(255, 255, 255, 0.02);
    opacity: 0;
    transform: translateY(-8px) scale(0.98);
    transform-origin: top left;
    pointer-events: none;
    transition: opacity 0.22s ease, transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}

.dob-picker.open .dob-pop {
    opacity: 1;
    transform: translateY(0) scale(1);
    pointer-events: auto;
}

.dob-pop-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 12px;
}

.dob-pop-title {
    display: flex;
    gap: 6px;
    flex: 1;
    justify-content: center;
}

.dob-sel {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.06);
    color: #f0eaf8;
    border-radius: var(--border-radius-xs);
    padding: 6px 8px;
    font-size: 0.82rem;
    font-family: 'Inter', sans-serif;
    cursor: pointer;
    outline: none;
    transition: border-color 0.2s ease, background 0.2s ease;
}

.dob-sel:hover,
.dob-sel:focus {
    border-color: rgba(167, 119, 227, 0.4);
    background: rgba(255, 255, 255, 0.05);
}

.dob-sel option {
    background: #140e1e;
    color: #f0eaf8;
}

.dob-nav {
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.06);
    color: rgba(240, 234, 248, 0.8);
    border-radius: var(--border-radius-xs);
    cursor: pointer;
    font-size: 0.72rem;
    transition:
        background 0.18s ease,
        border-color 0.18s ease,
        color 0.18s ease,
        transform 0.18s ease;
    flex-shrink: 0;
}

.dob-nav:hover {
    background: rgba(167, 119, 227, 0.15);
    color: #f0eaf8;
    border-color: rgba(167, 119, 227, 0.35);
}

.dob-nav:active {
    transform: scale(0.92);
}

.dob-weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
    margin-bottom: 6px;
}

.dob-weekdays span {
    text-align: center;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    /* was .65 -> 3.10:1. Same value and same fix as .shop-hero-balance-unit, the only
       other place this violet-alpha failed. A weekday header is not decoration — it is
       the only thing telling you which column is which. */
    color: rgba(167, 119, 227, 0.9);
    text-transform: uppercase;
    padding: 4px 0;
}

.dob-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}

.dob-day {
    aspect-ratio: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: rgba(240, 234, 248, 0.85);
    font-size: 0.8rem;
    font-family: 'Inter', sans-serif;
    border-radius: var(--border-radius-xs);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease;
}

.dob-day:hover {
    background: rgba(167, 119, 227, 0.15);
}

.dob-day:active {
    transform: scale(0.9);
}

.dob-day.muted {
    color: rgba(255, 255, 255, 0.18);
}

.dob-day.today {
    box-shadow: inset 0 0 0 1px rgba(167, 119, 227, 0.5);
}

.dob-day.selected {
    background: linear-gradient(135deg, #a777e3, #7b5cc4);
    color: #fff;
    font-weight: 600;
    box-shadow: 0 4px 12px rgba(167, 119, 227, 0.4);
}

.dob-pop-foot {
    display: flex;
    justify-content: space-between;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid rgba(255, 255, 255, 0.05);
}

.dob-foot-btn {
    background: transparent;
    border: none;
    color: rgba(240, 234, 248, 0.6);
    font-size: 0.78rem;
    font-family: 'Inter', sans-serif;
    cursor: pointer;
    padding: 4px 8px;
    border-radius: var(--border-radius-xs);
    transition: color 0.18s ease, background 0.18s ease;
    /* "Clear" and "Today" measured 46.3x22.5 and 51.9x22.5 — the only two controls on the
       dashboard under WCAG 2.2 SC 2.5.8's 24x24 minimum, and both only at desktop widths:
       the mobile pass already sets min-height: 32px on this class. min-height rather than
       more padding because the shortfall is 1.5px of line-box, and padding maths that
       depends on the font's line-height is exactly what produced 22.5 in the first place.
       inline-flex + centring so the extra height goes evenly above and below the label
       instead of leaving the text sitting at the top of a taller box. */
    min-height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.dob-foot-btn:hover {
    color: #f0eaf8;
    background: rgba(255, 255, 255, 0.04);
}

.dob-foot-btn.dob-today {
    color: #a777e3;
    font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {

    .dob-pop,
    .dob-chevron,
    .dob-day,
    .dob-nav {
        transition: none;
    }
}


/* ---- Textarea ---- */
.stg-textarea {
    height: 100px;
    resize: none;
    line-height: 1.6;
    scrollbar-width: thin;
    scrollbar-color: rgba(167, 119, 227, 0.2) transparent;
}

.stg-textarea::-webkit-scrollbar {
    width: 6px;
}

.stg-textarea::-webkit-scrollbar-track {
    background: transparent;
}

.stg-textarea::-webkit-scrollbar-thumb {
    background: rgba(167, 119, 227, 0.2);
    border-radius: var(--border-radius-sm);
}

/* ---- Date Input ---- */
.stg-input[type="date"] {
    color-scheme: dark;
}

.stg-input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(0.5) sepia(1) saturate(2) hue-rotate(230deg);
    cursor: pointer;
    opacity: 0.5;
    transition: opacity 0.2s ease;
}

.stg-input[type="date"]:hover::-webkit-calendar-picker-indicator {
    opacity: 0.8;
}

/* ---- Floating Integrated Save Bar ---- */
.stg-save-bar {
    position: absolute;
    bottom: 28px;
    left: calc(260px + 40px);
    /* Aligns nicely outside sidebar */
    right: 40px;
    /* 0.98, not the 0.75 this shipped at. The read-through was real and it was
       measured, not guessed: with a section heading scrolled under the bar's top
       edge, the luminance spread along a scanline through the bar was

         alpha 0.75  ->  range 24.0 / 255   (1.27:1 -- visible mottling)
         alpha 0.90  ->  range  9.8         (1.09:1)
         alpha 0.95  ->  range  5.1         (1.04:1 -- at the threshold)
         alpha 0.98  ->  range  2.0         (1.01:1 -- in the noise)
         alpha 1.00  ->  range  0.0

       0.98 is the lowest candidate whose ghost lands below perception, so it is
       the one taken; 0.95 (the value first proposed) still leaves a fifth of the
       original spread. The blur stays: 2% still transmits, and the blur is what
       makes that 2% a soft wash instead of legible letterforms.
       The ≤768px block further down already goes fully opaque and drops the blur,
       for cost reasons that only apply on a phone. */
    background: rgba(18, 14, 28, 0.98);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid rgba(167, 119, 227, 0.18);
    border-radius: var(--border-radius-md);
    padding: 16px 28px;
    z-index: 10;
    box-shadow:
        0 16px 40px rgba(0, 0, 0, 0.5),
        inset 0 1px 0 rgba(255, 255, 255, 0.05);
    /* No transition, on purpose. `.has-changes` is the only state this bar has,
       and it changes nothing on the bar itself — only two descendants (the dot
       at .stg-save-bar.has-changes .stg-save-status i and the label at
       #stg-save-label), each of which carries its own transition. So the
       `transition: all 0.3s` that used to be here had no state to animate.

       It was not inert, though: this rule sets `left`, `right` and `bottom`,
       and four @media blocks below override them, so `all` meant dragging a
       window across 768px slid the bar to its new position over 0.3s instead of
       laying out at the new width. Nothing asked for that. */
}

/* Elevate the active tab content above the save changes bar when a custom dropdown is open */
.settings-wrapper-v2:has(.dob-picker.open) .settings-tab-content.active,
.settings-wrapper-v2:has(.pronoun-select.open) .settings-tab-content.active {
    position: relative;
    z-index: 20;
}

.stg-save-bar-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    max-width: 680px;
    margin: 0 auto;
}

.stg-save-status {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.82rem;
    color: rgba(255, 255, 255, 0.5);
    font-weight: 500;
}

.stg-save-status i {
    font-size: 0.55rem;
    color: rgba(255, 255, 255, 0.25);
    transition:
        background 0.2s ease,
        color 0.2s ease,
        filter 0.2s ease,
        opacity 0.2s ease,
        text-shadow 0.2s ease,
        transform 0.2s ease;
}

.stg-save-bar.has-changes .stg-save-status i {
    color: #ffd700;
    text-shadow: 0 0 8px rgba(255, 215, 0, 0.6);
}

.stg-save-bar.has-changes #stg-save-label {
    color: #ffffff;
}

.stg-save-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 22px;
    border: none;
    border-radius: var(--border-radius-sm);
    background: var(--action-gradient);
    color: var(--on-brand-gradient);
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition:
        background 0.25s ease,
        box-shadow 0.25s ease,
        color 0.25s ease,
        transform 0.25s ease;
    font-family: 'Inter', sans-serif;
    box-shadow: 0 4px 15px rgba(167, 119, 227, 0.3);
}

.stg-save-btn:hover:not(:disabled) {
    background: linear-gradient(135deg, #b88bf2 0%, #8c4fed 100%);
    box-shadow: 0 6px 20px rgba(167, 119, 227, 0.45);
    transform: translateY(-1px);
}

.stg-save-btn:active:not(:disabled) {
    transform: translateY(0);
}

.stg-save-btn:disabled {
    background: rgba(255, 255, 255, 0.05);
    color: rgba(255, 255, 255, 0.25);
    box-shadow: none;
    cursor: not-allowed;
}

/* ---- Save Toast ---- */
.stg-save-toast {
    display: none;
    position: absolute;
    top: -50px;
    left: 50%;
    transform: translateX(-50%);
    padding: 10px 20px;
    background: rgba(46, 204, 113, 0.1);
    border: 1px solid rgba(46, 204, 113, 0.25);
    color: #2ecc71;
    border-radius: var(--border-radius-sm);
    text-align: center;
    font-weight: 600;
    font-size: 0.8rem;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
    z-index: 100;
    white-space: nowrap;
    animation: stgToastIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.stg-save-toast i {
    margin-right: 6px;
}

@keyframes stgToastIn {
    from {
        opacity: 0;
        transform: translate(-50%, 4px);
    }

    to {
        opacity: 1;
        transform: translate(-50%, 0);
    }
}

/* ---- Empty State ---- */
.stg-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 80px 20px;
    text-align: center;
    gap: 16px;
}

.stg-empty-state i {
    font-size: 2.2rem;
    color: rgba(167, 119, 227, 0.2);
}

.stg-empty-state p {
    font-size: 0.88rem;
    color: rgba(255, 255, 255, 0.4);
    margin: 0;
}

/* ---- Tab Content Transitions ---- */
.settings-wrapper-v2 .settings-tab-content {
    display: none;
    animation: stgFadeIn 0.3s ease forwards;
}

.settings-wrapper-v2 .settings-tab-content.active {
    display: flex;
}

@keyframes stgFadeIn {
    from {
        opacity: 0;
        transform: translateY(6px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ---- Mobile Navigation Toggle ---- */
.stg-mobile-nav-toggle {
    display: none;
}

/* =====================================================================
   SETTINGS V2 — Responsive Breakpoints
   ===================================================================== */

@media (max-width: 900px) {
    #page-settings {
        padding: 24px 16px !important;
    }

    .stg-sidebar {
        width: 230px;
        padding: 24px 16px;
    }

    .stg-content {
        padding: 32px 32px 120px;
    }

    .stg-save-bar {
        left: calc(230px + 32px);
        right: 32px;
        bottom: 24px;
        padding: 12px 20px;
    }
}

@media (max-width: 768px) {
    #page-settings {
        padding: 0 !important;
    }

    .settings-wrapper-v2 {
        max-width: 100%;
        height: 100%;
        max-height: 100%;
        border: none;
        border-radius: 0;
        box-shadow: none;
    }

    .stg-layout {
        flex-direction: column;
    }

    .stg-sidebar {
        width: 100%;
        flex-direction: row;
        flex-wrap: wrap;
        gap: 12px;
        padding: 16px;
        border-right: none;
        border-bottom: 1px solid rgba(167, 119, 227, 0.08);
        overflow-x: auto;
        overflow-y: hidden;
    }

    .stg-profile-card {
        width: 100%;
        margin-bottom: 0;
    }

    .stg-nav {
        flex-direction: row;
        gap: 6px;
        width: 100%;
        overflow-x: auto;
        scrollbar-width: none;
    }

    .stg-nav::-webkit-scrollbar {
        display: none;
    }

    .stg-nav-label {
        display: none;
    }

    .stg-nav-item {
        white-space: nowrap;
        padding: 8px 16px;
        font-size: 0.8rem;
        width: auto;
    }

    .stg-sidebar-note {
        display: none;
    }

    .stg-content {
        padding: 32px 20px 140px;
    }

    .stg-field-row {
        grid-template-columns: 1fr;
        gap: 20px;
    }

    .stg-save-bar {
        left: 16px !important;
        right: 16px !important;
        bottom: 16px !important;
        width: auto;
    }
}

@media (max-width: 480px) {
    .stg-topbar {
        padding: 16px;
    }

    .stg-topbar-title {
        display: none;
    }

    .stg-section-title {
        font-size: 1.3rem;
    }

    .stg-quick-info {
        flex-direction: column;
        gap: 8px;
        margin-bottom: 24px;
    }

    .stg-quick-chip {
        width: fit-content;
    }

    .stg-save-bar {
        padding: 12px 16px;
    }

    .stg-save-status {
        display: none;
    }
}

/* --- Search Hide Logic on Settings View --- */
.main-content:has(#page-settings[style*='display: flex']) .search-container {
    display: none !important;
}

/* Hide the entire top nav strip on the Settings view so no empty bar shows */
.main-content.settings-active .top-nav {
    display: none !important;
}

/* =====================================================================
   QUOTA MODAL — "Continue Your Journey" Premium Glassmorphism
   ===================================================================== */

/* ---- Overlay ---- */
.qm-overlay {
    position: fixed;
    inset: 0;
    z-index: 99999;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(4, 2, 10, 0.0);
    backdrop-filter: blur(0px);
    -webkit-backdrop-filter: blur(0px);
    transition: background 0.4s ease, backdrop-filter 0.4s ease;
    padding: 24px;
}

.qm-overlay.active {
    display: flex;
}

.qm-overlay.visible {
    background: rgba(4, 2, 10, 0.75);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
}

/* ---- Modal Container ---- */
.qm-modal {
    position: relative;
    width: 100%;
    max-width: 680px;
    background: linear-gradient(165deg, rgba(22, 16, 40, 0.92) 0%, rgba(10, 8, 18, 0.98) 100%);
    border: 1px solid rgba(167, 119, 227, 0.15);
    border-radius: var(--border-radius-lg);
    padding: 44px 40px 40px;
    overflow: hidden;
    box-shadow:
        0 32px 80px rgba(0, 0, 0, 0.8),
        0 0 120px rgba(167, 119, 227, 0.06),
        inset 0 1px 0 rgba(255, 255, 255, 0.06);
    opacity: 0;
    transform: scale(0.92) translateY(24px);
    transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1),
        transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.qm-overlay.visible .qm-modal {
    opacity: 1;
    transform: scale(1) translateY(0);
}

/* ---- Decorative Glow Orbs ---- */
.qm-glow {
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
    filter: blur(80px);
}

.qm-glow-1 {
    top: -60px;
    right: -40px;
    width: 220px;
    height: 220px;
    background: radial-gradient(circle, rgba(167, 119, 227, 0.18) 0%, transparent 70%);
    animation: qmGlowPulse1 5s ease-in-out infinite alternate;
}

.qm-glow-2 {
    bottom: -40px;
    left: -30px;
    width: 180px;
    height: 180px;
    background: radial-gradient(circle, rgba(110, 142, 251, 0.12) 0%, transparent 70%);
    animation: qmGlowPulse2 6s ease-in-out infinite alternate;
}

.qm-glow-3 {
    top: 40%;
    left: 50%;
    width: 140px;
    height: 140px;
    transform: translateX(-50%);
    background: radial-gradient(circle, rgba(226, 179, 255, 0.06) 0%, transparent 70%);
    animation: qmGlowPulse1 7s ease-in-out infinite alternate-reverse;
}

@keyframes qmGlowPulse1 {
    from {
        opacity: 0.5;
        transform: scale(0.9);
    }

    to {
        opacity: 1;
        transform: scale(1.2);
    }
}

@keyframes qmGlowPulse2 {
    from {
        opacity: 0.4;
        transform: scale(1);
    }

    to {
        opacity: 0.9;
        transform: scale(1.15);
    }
}

/* ---- Close Button ---- */
.qm-close {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 10;
    width: 36px;
    height: 36px;
    border-radius: var(--border-radius-sm);
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.04);
    color: rgba(255, 255, 255, 0.5);
    font-size: 1rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition:
        background 0.25s ease,
        border-color 0.25s ease,
        color 0.25s ease,
        transform 0.25s ease;
}

.qm-close:hover {
    background: rgba(var(--color-danger-rgb), 0.12);
    border-color: rgba(var(--color-danger-rgb), 0.3);
    color: var(--color-danger);
    transform: rotate(90deg);
}

/* ---- Header ---- */
.qm-header {
    text-align: center;
    margin-bottom: 32px;
    position: relative;
    z-index: 2;
}

.qm-header-icon {
    width: 64px;
    height: 64px;
    margin: 0 auto 18px;
    border-radius: var(--border-radius-md);
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.15), rgba(110, 142, 251, 0.1));
    border: 1px solid rgba(167, 119, 227, 0.25);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    color: #c9a0f5;
    box-shadow: 0 8px 32px rgba(167, 119, 227, 0.15);
    animation: qmIconFloat 3s ease-in-out infinite;
}

@keyframes qmIconFloat {

    0%,
    100% {
        transform: translateY(0);
    }

    50% {
        transform: translateY(-6px);
    }
}

.qm-title {
    font-family: var(--font-display);
    font-size: 1.7rem;
    font-weight: 800;
    background: linear-gradient(135deg, #ffffff 0%, #e2b3ff 40%, #a777e3 70%, #6e8efb 100%);
    background-size: 200% 200%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    margin: 0 0 8px;
    animation: qmTitleShimmer 4s ease infinite;
}

@keyframes qmTitleShimmer {

    0%,
    100% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }
}

.qm-subtitle {
    font-family: 'Inter', sans-serif;
    font-size: 0.88rem;
    color: rgba(161, 161, 170, 0.8);
    line-height: 1.55;
    margin: 0;
    max-width: 440px;
    margin-left: auto;
    margin-right: auto;
}

/* ---- Option Cards Container ---- */
.qm-cards {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
    position: relative;
    z-index: 2;
}

/* ---- Individual Card ----
   The whole body of this rule was absorbed by the surface primitive; the only
   thing that was ever local to it was the 22px radius, and that was drift, not
   a decision. Gaining a resting shadow also fixes the hover: it used to
   interpolate out of `none`, which is not a real starting value. */

.qm-card:hover {
    transform: translateY(-6px) scale(1.02);
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5),
        0 0 40px rgba(167, 119, 227, 0.12);
}

/* Card glow */
.qm-card-glow {
    position: absolute;
    top: -40px;
    right: -40px;
    width: 160px;
    height: 160px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(167, 119, 227, 0.1) 0%, transparent 70%);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s ease;
}

.qm-card:hover .qm-card-glow {
    opacity: 1;
}

/* Card animated border */
.qm-card-border {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.12), rgba(110, 142, 251, 0.08), rgba(167, 119, 227, 0.12));
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
    transition: background 0.4s ease;
}

.qm-card:hover .qm-card-border {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.4), rgba(110, 142, 251, 0.3), rgba(226, 179, 255, 0.35));
    background-size: 300% 300%;
    animation: shopBorderRotate 3s linear infinite;
}

/* Card inner */
.qm-card-inner {
    position: relative;
    z-index: 2;
    background: linear-gradient(170deg, rgba(30, 22, 48, 0.75) 0%, rgba(18, 14, 30, 0.95) 100%);
    backdrop-filter: blur(20px);
    /* Tracks the parent — see .shop-card-inner. */
    border-radius: inherit;
    padding: 28px 22px 24px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    min-height: 260px;
}

/* ---- Badge ---- */
.qm-card-badge {
    padding: 4px 14px;
    border-radius: var(--border-radius-pill);
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    display: flex;
    align-items: center;
    gap: 5px;
}

.qm-badge-free {
    background: linear-gradient(135deg, rgba(46, 204, 113, 0.15), rgba(39, 174, 96, 0.1));
    border: 1px solid rgba(46, 204, 113, 0.35);
    color: #2ecc71;
}

.qm-badge-orbs {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.15), rgba(110, 61, 231, 0.1));
    border: 1px solid rgba(167, 119, 227, 0.35);
    color: #c9a0f5;
}

.qm-badge-orb-icon {
    width: 14px;
    height: 14px;
    object-fit: contain;
    filter: drop-shadow(0 0 4px rgba(167, 119, 227, 0.5));
}

/* ---- Icon Wrap ---- */
.qm-card-icon-wrap {
    width: 62px;
    height: 62px;
    border-radius: var(--border-radius-md);
    background: var(--qm-icon-bg, rgba(167, 119, 227, 0.12));
    border: 1px solid var(--qm-icon-border, rgba(167, 119, 227, 0.3));
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.qm-card:hover .qm-card-icon-wrap {
    transform: scale(1.1) rotate(-5deg);
    box-shadow: 0 0 24px rgba(167, 119, 227, 0.2);
}

.qm-card-emoji {
    font-size: 1.8rem;
    line-height: 1;
}

/* ---- Card Title ---- */
.qm-card-title {
    font-family: var(--font-display);
    font-size: 1.15rem;
    font-weight: 700;
    color: #fff;
    margin: 0;
}

/* ---- Reward Line ---- */
.qm-card-reward {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    font-weight: 600;
    color: #2ecc71;
}

.qm-card-reward i {
    font-size: 0.75rem;
    filter: drop-shadow(0 0 6px rgba(46, 204, 113, 0.4));
}

/* ---- Cost Display ---- */
.qm-card-cost {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    background: rgba(167, 119, 227, 0.08);
    border: 1px solid rgba(167, 119, 227, 0.15);
    border-radius: var(--border-radius-sm);
}

.qm-cost-orb-icon {
    width: 16px;
    height: 16px;
    object-fit: contain;
    filter: drop-shadow(0 0 6px rgba(167, 119, 227, 0.4));
}

.qm-orb-cost {
    font-family: var(--font-display);
    font-size: 0.85rem;
    font-weight: 700;
    background: linear-gradient(135deg, #e2b3ff, #a777e3);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* ---- Card Buttons ---- */
.qm-card-btn {
    position: relative;
    width: 100%;
    margin-top: auto;
    padding: 13px 20px;
    border: none;
    border-radius: var(--border-radius-md);
    font-family: 'Inter', sans-serif;
    font-size: 0.88rem;
    font-weight: 700;
    cursor: pointer;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    letter-spacing: 0.4px;
    transition:
        box-shadow 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275),
        transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

/* Sibling of .qm-btn-orbs (brand violet). This used to be
   linear-gradient(#4facfe, #00f2fe) — a cyan that belonged to no other part of
   the product. It still has to read as the *other* choice next to the violet
   button, so it takes the brand's second accent instead of a third hue. */
.qm-btn-ad {
    background: linear-gradient(135deg, var(--brand-accent-2) 0%, #4a63d8 100%);
    color: #fff;
    box-shadow: 0 6px 24px rgba(110, 142, 251, 0.3),
        inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.qm-btn-ad:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 36px rgba(110, 142, 251, 0.45),
        0 0 30px rgba(110, 142, 251, 0.15),
        inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.qm-btn-orbs {
    background: linear-gradient(135deg, #a777e3 0%, #7b44d4 50%, #6e3de7 100%);
    background-size: 200% 200%;
    color: #fff;
    box-shadow: 0 6px 24px rgba(110, 61, 231, 0.3),
        inset 0 1px 0 rgba(255, 255, 255, 0.15);
    animation: btnGradientShift 4s ease infinite;
}

.qm-btn-orbs:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 36px rgba(110, 61, 231, 0.45),
        0 0 30px rgba(167, 119, 227, 0.15),
        inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.qm-card-btn:active {
    transform: translateY(0) scale(0.97);
}

/* Shimmer glow on buttons */
.qm-btn-glow {
    position: absolute;
    top: 0;
    left: -100%;
    width: 60%;
    height: 100%;
    background: linear-gradient(to right,
            transparent 0%,
            rgba(255, 255, 255, 0.08) 30%,
            rgba(255, 255, 255, 0.2) 50%,
            rgba(255, 255, 255, 0.08) 70%,
            transparent 100%);
    transform: skewX(-25deg);
    pointer-events: none;
}

.qm-card-btn:hover .qm-btn-glow {
    animation: premiumShimmer 2s ease infinite;
}

/* ---- Sub-Modals (Ad Simulation & Not Enough Orbs) ---- */
.qm-submodal {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(10, 8, 18, 0.85);
    backdrop-filter: blur(12px);
    border-radius: var(--border-radius-lg);
}

.qm-submodal.active {
    display: flex;
}

.qm-submodal-card {
    background: linear-gradient(170deg, rgba(30, 22, 48, 0.95) 0%, rgba(18, 14, 30, 0.98) 100%);
    border: 1px solid rgba(167, 119, 227, 0.2);
    border-radius: var(--border-radius-md);
    padding: 32px 28px;
    text-align: center;
    max-width: 340px;
    width: 100%;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
    animation: qmSubSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes qmSubSlideIn {
    from {
        opacity: 0;
        transform: scale(0.9) translateY(12px);
    }

    to {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

.qm-submodal-icon {
    width: 52px;
    height: 52px;
    margin: 0 auto 16px;
    border-radius: var(--border-radius-md);
    background: rgba(167, 119, 227, 0.12);
    border: 1px solid rgba(167, 119, 227, 0.25);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
    color: #c9a0f5;
}

.qm-submodal-text {
    font-family: 'Inter', sans-serif;
    font-size: 0.95rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.85);
    margin: 0 0 20px;
    line-height: 1.5;
}

.qm-submodal-actions {
    display: flex;
    gap: 10px;
}

.qm-sub-btn {
    flex: 1;
    padding: 12px 18px;
    border: none;
    border-radius: var(--border-radius-sm);
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-weight: 700;
    cursor: pointer;
    transition:
        background 0.25s ease,
        border-color 0.25s ease,
        box-shadow 0.25s ease,
        color 0.25s ease,
        transform 0.25s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
}

.qm-sub-primary {
    background: linear-gradient(135deg, #a777e3 0%, #7b44d4 100%);
    color: #fff;
    box-shadow: 0 6px 20px rgba(110, 61, 231, 0.3);
}

.qm-sub-primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 30px rgba(110, 61, 231, 0.4);
}

.qm-sub-cancel {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: rgba(255, 255, 255, 0.6);
}

.qm-sub-cancel:hover {
    background: rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.9);
    border-color: rgba(255, 255, 255, 0.2);
}

/* ---- Responsive: Mobile Stacked ---- */
@media (max-width: 640px) {
    .qm-overlay {
        padding: 16px;
        align-items: flex-end;
    }

    .qm-modal {
        padding: 32px 22px 28px;
        max-height: 90vh;
        overflow-y: auto;
    }

    .qm-cards {
        grid-template-columns: 1fr;
        gap: 14px;
    }

    .qm-card-inner {
        min-height: auto;
        padding: 22px 18px 20px;
    }

    .qm-title {
        font-size: 1.35rem;
    }

    .qm-subtitle {
        font-size: 0.82rem;
    }

    .qm-header-icon {
        width: 52px;
        height: 52px;
        font-size: 1.3rem;
    }
}

/* =====================================================================
   PREMIUM ACCOUNT SETTINGS STYLES
   ===================================================================== */
.stg-account-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    width: 100%;
    margin-top: 10px;
}

@media (max-width: 850px) {
    .stg-account-grid {
        grid-template-columns: 1fr;
        gap: 20px;
    }
}

/* Privacy & ads card. A full-width third item in the account grid rather than a
   sibling below it, so it inherits the grid's gap instead of carrying its own
   margin, and stacks with the other two cards at <=850px for free. */
.stg-privacy-card {
    grid-column: 1 / -1;
}

.stg-privacy-card .stg-section-desc {
    margin: 0 0 16px;
}

.stg-privacy-card .stg-save-btn {
    justify-content: center;
}

@media (max-width: 850px) {
    .stg-privacy-card .stg-save-btn {
        width: 100%;
    }
}

.stg-acc-card {
    background: rgba(14, 11, 22, 0.45);
    backdrop-filter: blur(25px);
    -webkit-backdrop-filter: blur(25px);
    border: 1px solid rgba(167, 119, 227, 0.12);
    border-radius: var(--border-radius-md);
    padding: 24px;
    position: relative;
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.05);
    transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.stg-acc-card:hover {
    border-color: rgba(167, 119, 227, 0.25);
    box-shadow: 0 20px 48px rgba(0, 0, 0, 0.7), 0 0 30px rgba(167, 119, 227, 0.03), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.stg-acc-card-title {
    font-size: 1.1rem;
    font-weight: 700;
    color: #e2b3ff;
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 0;
    font-family: var(--font-display);
    text-shadow: 0 0 10px rgba(167, 119, 227, 0.3);
}

.stg-input-wrapper {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
}

.stg-input-icon {
    position: absolute;
    left: 14px;
    color: rgba(255, 255, 255, 0.3);
    font-size: 0.88rem;
    pointer-events: none;
    transition: color 0.3s ease;
}

.stg-input-wrapper .stg-input {
    padding-left: 42px;
}

.stg-input-wrapper.password-wrapper .stg-input {
    padding-right: 42px;
    position: relative;
    z-index: 1;
}

.toggle-password {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    z-index: 5;
    background: none;
    border: none;
    color: rgba(255, 255, 255, 0.4);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.92rem;
    width: 32px;
    height: 32px;
    border-radius: var(--border-radius-xs);
    transition: color 0.2s ease, background 0.2s ease;
    padding: 0;
}

.toggle-password:hover {
    color: #e2b3ff;
    background: rgba(167, 119, 227, 0.1);
}

.stg-input-wrapper:focus-within .stg-input-icon {
    color: #a777e3;
}

.stg-acc-avatar-wrapper {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 100px;
    height: 100px;
    margin: 0 auto 24px;
}

.stg-acc-avatar-glow {
    position: absolute;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(167, 119, 227, 0.22) 0%, transparent 70%);
    filter: blur(8px);
    z-index: 1;
    top: -10px;
    left: -10px;
    pointer-events: none;
}

.stg-acc-avatar-ring {
    width: 90px;
    height: 90px;
    border-radius: 50%;
    padding: 3px;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 20px rgba(167, 119, 227, 0.4);
    z-index: 2;
    position: relative;
}

.stg-acc-avatar {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background: #110e19;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.2rem;
    font-family: var(--font-display);
    font-weight: 700;
    color: #fff;
    border: 2px solid #09090b;
    overflow: hidden;
}

.stg-acc-avatar-edit-btn {
    position: absolute;
    bottom: 0;
    right: 0;
    background: #a777e3;
    border: 2px solid #09090b;
    border-radius: 50%;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: #fff;
    font-size: 0.75rem;
    transition: background 0.2s ease, transform 0.2s ease;
    z-index: 3;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
}

.stg-acc-avatar-edit-btn:hover {
    background: #6e8efb;
    transform: scale(1.1);
}

.stg-acc-tier-row {
    display: flex;
    justify-content: space-between;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: var(--border-radius-sm);
    padding: 12px 16px;
    margin-bottom: 20px;
}

.stg-acc-tier-info,
.stg-acc-orbs-info {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.stg-acc-tier-label {
    font-size: 0.68rem;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.4);
    font-weight: 600;
    letter-spacing: 0.5px;
}

.stg-acc-tier-badge {
    font-size: 0.85rem;
    font-weight: 700;
    color: #a777e3;
    display: flex;
    align-items: center;
    gap: 6px;
    text-shadow: 0 0 10px rgba(167, 119, 227, 0.3);
}

.stg-acc-tier-badge.premium {
    color: #ffd700;
    text-shadow: 0 0 10px rgba(255, 215, 0, 0.4);
}

.stg-acc-orbs-value {
    font-size: 0.88rem;
    font-weight: 700;
    color: #e2b3ff;
}

.stg-email-status-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 8px;
}

.stg-verify-email-btn {
    background: none;
    border: none;
    color: #a777e3;
    font-size: 0.75rem;
    font-weight: 600;
    cursor: pointer;
    text-decoration: underline;
    transition: color 0.2s ease;
}

.stg-verify-email-btn:hover {
    color: #6e8efb;
}

.stg-badge--unverified {
    background: rgba(255, 74, 74, 0.08);
    border: 1px solid rgba(255, 74, 74, 0.2);
    color: #ff4a4a;
    padding: 3px 8px;
    border-radius: var(--border-radius-xs);
    font-size: 0.65rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.stg-badge--verified {
    background: rgba(46, 204, 113, 0.08);
    border: 1px solid rgba(46, 204, 113, 0.2);
    color: #2ecc71;
    padding: 3px 8px;
    border-radius: var(--border-radius-xs);
    font-size: 0.65rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.stg-security-meta-row {
    display: flex;
    justify-content: space-between;
    margin-top: 8px;
}

.stg-security-meta {
    font-size: 0.72rem;
    color: rgba(255, 255, 255, 0.4);
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.pw-strength-container {
    margin-top: 8px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.pw-strength-bar {
    width: 100%;
    height: 4px;
    background: rgba(255, 255, 255, 0.05);
    border-radius: var(--border-radius-pill);
    overflow: hidden;
}

.pw-strength-fill {
    width: 0%;
    height: 100%;
    border-radius: var(--border-radius-pill);
    transition: width 0.3s ease, background-color 0.3s ease;
}

.pw-strength-text {
    font-size: 0.7rem;
    font-weight: 600;
    text-align: right;
    color: rgba(255, 255, 255, 0.3);
}

/* Memory Manager Styles */
.memory-manager-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.8);
    backdrop-filter: blur(8px);
    z-index: 10000;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px;
}

.memory-manager-panel {
    background: var(--surface-light);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius-md);
    width: 100%;
    max-width: 900px;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}

.memory-manager-header {
    padding: 20px;
    border-bottom: 1px solid var(--border-color);
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
}

.memory-header-info h2 {
    margin: 0 0 5px 0;
    font-size: 1.5rem;
}

.memory-subtitle {
    color: var(--text-secondary);
    margin: 0 0 10px 0;
    font-size: 0.95rem;
}

.memory-note {
    font-size: 0.8rem;
    color: #a777e3;
    margin: 0;
    display: flex;
    align-items: center;
    gap: 6px;
}

.memory-header-actions {
    display: flex;
    gap: 10px;
}

.memory-search-bar {
    padding: 15px 20px;
    border-bottom: 1px solid var(--border-color);
    display: flex;
    align-items: center;
    gap: 10px;
    background: rgba(0, 0, 0, 0.2);
}

.memory-search-bar input {
    flex-grow: 1;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 0.95rem;
    outline: none;
}

.memory-bulk-actions {
    padding: 10px 20px;
    background: rgba(var(--color-danger-rgb), 0.1);
    border-bottom: 1px solid var(--border-color);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.memory-buckets-container {
    padding: 20px;
    overflow-y: auto;
    flex-grow: 1;
}

.memory-bucket {
    margin-bottom: 30px;
}

.memory-bucket-header {
    font-size: 1.1rem;
    color: var(--text-primary);
    margin-bottom: 15px;
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}

.memory-bucket-header .bucket-count {
    background: rgba(255, 255, 255, 0.1);
    padding: 2px 8px;
    border-radius: var(--border-radius-sm);
    font-size: 0.8rem;
}

.memory-cards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 15px;
}

.memory-card {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius-sm);
    padding: 15px;
    position: relative;
    transition: background 0.2s, border-color 0.2s, transform 0.2s;
    display: flex;
    flex-direction: column;
}

.memory-card:hover {
    border-color: rgba(255, 255, 255, 0.2);
    transform: translateY(-2px);
}

.memory-card.locked {
    border-color: #ffd700;
    background: rgba(255, 215, 0, 0.05);
}

.memory-card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
    font-size: 0.8rem;
}

.memory-badge {
    padding: 2px 6px;
    border-radius: var(--border-radius-2xs);
    font-weight: 600;
}

.memory-badge.ai {
    background: rgba(110, 142, 251, 0.2);
    color: #6e8efb;
}

.memory-badge.user {
    background: rgba(46, 204, 113, 0.2);
    color: #2ecc71;
}

.memory-card-value {
    font-size: 0.95rem;
    line-height: 1.4;
    margin-bottom: 15px;
    flex-grow: 1;
    word-break: break-word;
}

.memory-card-meta {
    font-size: 0.75rem;
    color: var(--text-secondary);
    display: flex;
    justify-content: space-between;
    margin-bottom: 12px;
}

.memory-card-actions {
    display: flex;
    gap: 8px;
    border-top: 1px solid rgba(255, 255, 255, 0.05);
    padding-top: 12px;
}

.memory-card-actions button {
    flex-grow: 1;
    background: rgba(255, 255, 255, 0.05);
    border: none;
    color: var(--text-primary);
    padding: 6px;
    border-radius: var(--border-radius-xs);
    cursor: pointer;
    /* `transition: 0.2s` was here. That is a duration with no property, and an
       omitted transition-property defaults to `all` — so this was a 76th
       `transition: all` that no text search for "all" could ever have found.
       The browser's CSSOM was the only thing that saw it. */
    transition: background 0.2s, color 0.2s;
    font-size: 0.8rem;
}

.memory-card-actions button:hover {
    background: rgba(255, 255, 255, 0.1);
}

.memory-card-actions button.lock-btn:hover {
    color: #ffd700;
}

.memory-card-actions button.delete-btn:hover {
    color: var(--color-danger);
    background: rgba(var(--color-danger-rgb), 0.1);
}

.memory-checkbox {
    margin-right: 10px;
    cursor: pointer;
}

.memory-edit-panel {
    background: var(--surface-light);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius-sm);
    padding: 24px;
    width: 100%;
    max-width: 500px;
}

/* .memory-bucket-content held a lone `transition: all 0.3s ease` and nothing
   else. Removed: there is no state rule for it anywhere in this file, and no JS
   in the repo touches `.memory-bucket*` at all, so nothing was ever animated.
   (.memory-bucket-header does set `cursor: pointer`, i.e. the collapse this
   transition was written for was designed but never wired up. Left as found —
   that is a behaviour gap, not a stylesheet one.) */

/* ===== Memory Manager Modal specific styling ===== */
.memory-manager-modal {
    max-width: 700px !important;
    text-align: left !important;
    /* Moved off the two inline style="" attributes in dashboard.html (2432,
       2537) so the mobile block at the end of this file can reach it — an
       inline declaration can only be overridden with !important. Later in
       source order than `.confirm-modal { padding: 36px 32px 28px }` (5072),
       so it wins at equal specificity without one.
       NOTE: the max-width !important above also overrides the 640px the
       diary variant asks for inline; pre-existing, left as-is. */
    padding: 24px;
}

/* ===== Memory tab rail =====
   Shared by the RP memory manager and the diary's "what it remembers". Every
   declaration down to `scrollbar-width` was inline on the two containers in
   dashboard.html; it moved here for three separate reasons:
     · an inline `overflow-x` cannot be corrected by a stylesheet at any
       specificity, and it needed correcting (see below);
     · the mobile block at the end of this file could not reach the row at all;
     · `renderDiaryMemoryTabs()` un-hides the diary rail with
       `wrap.style.display = ''`, which deletes the inline `display: flex`
       along with the `none` — so that rail stopped being a flex container
       after its first render. */
.memory-tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 16px;
    padding-bottom: 6px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);

    /* Sideways only — and BOTH axes have to say so. Per CSS Overflow, a
       `visible` on one axis computes to `auto` when the other axis is not
       visible, so authoring `overflow-x: auto` on its own quietly made this row
       a scroll container vertically as well. It then clipped its own 44px tabs
       and grew a second scrollbar to prove it. */
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;

    /* The load-bearing line. A scroll container's automatic minimum size is 0
       instead of its content height, so this row was the one child of the
       column-flex modal that COULD be squeezed to nothing — and therefore the
       one that absorbed the entire deficit when the modal hit its max-height.
       Measured at 360x740 before this: 11px of client height holding 50px of
       tabs, i.e. not one glyph of any label was on screen. #memory-entries-list
       is the only child that may give up height. */
    flex: 0 0 auto;

    /* A native bar on a 44px row is all cost and no signal; the fade below is
       the affordance instead. */
    scrollbar-width: none;

    /* Zero-width stops = no fade. The state classes below widen them. */
    --mt-fade-l: 0px;
    --mt-fade-r: 0px;
}

.memory-tabs::-webkit-scrollbar {
    display: none;
}

.diary-memory-tabs {
    margin-bottom: 14px;
}

/* ---- The rail has to say it scrolls, and stop saying so at each end ----
   Eight tabs need 588px and the rail measures 304 at 360dp, so four of them sat
   off the right edge with nothing on screen to suggest they existed.

   mask-image is the right device here for the same reason it was the wrong one
   for `.public-profile-tabs` (see the note at that rule): this row is a bare
   transparent strip, not a filled capsule with its own border and radius, so a
   mask spends nothing to buy the text fade. A sticky ::after would instead have
   to repaint the modal's own 160deg gradient to disappear against it.

   What is different from the two static fades in this file: these are gated on
   classes `syncMemoryRail()` (dashboard.js) sets from scrollLeft, so neither
   edge keeps claiming there is more to see once you are at that end. Declaring
   the mask only on those classes also means a rail that fits — the 700px
   desktop modal, where all eight tabs are visible — is never composited.

   It does fade the last 32px of the 1px bottom border along with the tabs. At
   rgba(255,255,255,0.05) that is not a visible cost. */
.memory-tabs.mt-more-left,
.memory-tabs.mt-more-right {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--mt-fade-l), #000 calc(100% - var(--mt-fade-r)), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--mt-fade-l), #000 calc(100% - var(--mt-fade-r)), transparent 100%);
}

.memory-tabs.mt-more-left {
    --mt-fade-l: 26px;
}

.memory-tabs.mt-more-right {
    --mt-fade-r: 32px;
}

.memory-tab {
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
    border-bottom: 2px solid transparent;
    /* Never let a label shrink or wrap to make the rail fit — the rail scrolls
       instead. `nowrap` is inline on every tab, including the ones
       renderDiaryMemoryTabs() writes. */
    flex: 0 0 auto;
    scroll-snap-align: start;
}

.memory-tab:hover {
    color: #e2b3ff !important;
    background: rgba(167, 119, 227, 0.05) !important;
}

.memory-tab.active {
    color: #e2b3ff !important;
    border-bottom-color: #a777e3 !important;
}

.memory-entry-card {
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

.memory-entry-card:hover {
    background: rgba(255, 255, 255, 0.05) !important;
    border-color: rgba(167, 119, 227, 0.2) !important;
}

.lock-toggle-btn {
    transition: color 0.2s ease;
}

.lock-toggle-btn.locked {
    color: #ffd700 !important;
}

.lock-toggle-btn:hover {
    color: #e2b3ff !important;
}

/* Both flags below are load-bearing. dashboard.js renders these two buttons with an inline
   `style="color: var(--text-secondary); ..."` (:4143, :4146), and an inline declaration
   outranks every author rule that is not important -- so without the flag the hover colour
   never appears at all, on any pointer. Note it is the inline style that forces this, not
   `.icon-btn:hover` at :515, which these rules already beat on source order. Removing them
   would mean moving the two colours out of the JS and into the stylesheet. */
.edit-entry-btn:hover {
    color: #6e8efb !important;
}

.delete-entry-btn:hover {
    color: var(--color-danger) !important;
}

/* ═══════════════════════════════════════════════════════════════════
   MEMORY MANAGER — CAST TAB
   Every control on this tab used to be styled from dashboard.js with
   `el.style.cssText = ...`. An inline declaration outranks every author
   rule that is not !important, so for as long as the styling lived in
   the JS these controls could not have a hover, a focus ring or a
   pressed state AT ALL -- not because nobody wrote them, but because
   there is nowhere to write them: there is no inline :hover. Moving the
   look here is what buys the states below.

   Two colour systems run through the tab and they mean different
   things, so they are driven off one custom property per ledger rather
   than hard-coded per rule:
     --cast-tint       violet for "knows", danger red for "does not know"
     --cast-tint-text  the readable text colour on this app's near-black
   A secret ledger is red because acting on something a character was
   never told is the failure this whole feature exists to prevent.
   ═══════════════════════════════════════════════════════════════════ */

/* ---- "Add a character" ---- */
.cast-add-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    min-height: var(--tap-min);
    margin-bottom: 12px;
    padding: 11px 16px;
    border: none;
    border-radius: var(--border-radius-sm);
    background: var(--action-gradient);
    /* Near-black on the gradient, never white -- measured 5.44:1 against
       the violet end and 5.89:1 against the blue. See --on-brand-gradient. */
    color: var(--on-brand-gradient);
    font-family: inherit;
    font-size: 0.86rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(110, 142, 251, 0.18);
    transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1),
                box-shadow 0.18s cubic-bezier(0.16, 1, 0.3, 1),
                filter 0.18s ease;
}

.cast-add-btn i {
    font-size: 0.9em;
    opacity: 0.85;
}

.cast-add-btn:hover {
    transform: translateY(-1px);
    filter: brightness(1.06);
    box-shadow: 0 6px 20px rgba(110, 142, 251, 0.28);
}

.cast-add-btn:active {
    transform: translateY(0);
    box-shadow: 0 2px 8px rgba(110, 142, 251, 0.2);
}

/* ---- The card ---- */
.cast-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    /* Extra left padding clears the 2px presence rail below. */
    padding: 14px 14px 14px 16px;
    margin-bottom: 10px;
    /* A 2px left rail, drawn as a background layer rather than a border so
       it can fade down the card instead of boxing it in. It is the only
       thing that reads "in the room" from across the modal, and it costs
       no horizontal space next to the name field. */
    background:
        linear-gradient(to bottom, rgba(167, 119, 227, 0.32), rgba(167, 119, 227, 0)) left / 2px 100% no-repeat,
        rgba(255, 255, 255, 0.028);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--border-radius-sm);
    transition: border-color 0.2s ease, background-color 0.2s ease;
}

.cast-card.is-present {
    background:
        linear-gradient(to bottom, rgba(var(--color-ok-rgb), 0.5), rgba(var(--color-ok-rgb), 0)) left / 2px 100% no-repeat,
        rgba(255, 255, 255, 0.028);
}

.cast-card:hover {
    border-color: rgba(167, 119, 227, 0.22);
}

.cast-card-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.cast-field-row {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

/* ---- Labels ---- */
.cast-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #e2b3ff;
}

/* "Right now" is the one field on the card that expires, so it is the one
   label in a different hue -- amber reads as temporary next to the violet
   permanent rows without needing a second badge. */
.cast-label--state {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
    color: #ffd28a;
}

.cast-label--state small {
    font-size: 0.68rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: rgba(255, 255, 255, 0.35);
}

/* ---- Text fields ----
   These keep .stg-input so the <=768px anti-zoom rule (16px, ~line 19625)
   still reaches them; .cast-field only trims the desktop padding down to
   something a dense card can carry, and drops the focus nudge, which on a
   card of six stacked fields reads as a jolt rather than a lift. */
.cast-field {
    padding: 9px 12px;
    font-size: 0.84rem;
    border-radius: var(--border-radius-xs);
    background-color: rgba(0, 0, 0, 0.22);
}

.cast-field::placeholder {
    color: rgba(255, 255, 255, 0.24);
}

.cast-field:hover {
    background-color: rgba(0, 0, 0, 0.3);
    border-color: rgba(167, 119, 227, 0.28);
}

.cast-field:focus {
    transform: none;
    background-color: rgba(0, 0, 0, 0.34);
    border-color: rgba(167, 119, 227, 0.55);
    box-shadow: 0 0 0 3px rgba(167, 119, 227, 0.14);
}

.cast-field--name {
    flex: 1 1 140px;
    width: auto;
    font-size: 0.92rem;
    font-weight: 600;
}

.cast-field--fact {
    flex: 1 1 100%;
}

.cast-field--by {
    flex: 1 1 110px;
    width: auto;
}

.cast-field--day {
    flex: 0 0 74px;
    width: auto;
    text-align: center;
    /* The spinners are 20px of chrome on a field that holds at most three
       digits, and they push the number off centre. */
    -moz-appearance: textfield;
}

.cast-field--day::-webkit-outer-spin-button,
.cast-field--day::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* ---- Provenance select ----
   Kept native on purpose: this ships as a Capacitor Android app, where the
   native picker is a thumb-sized material dialog that no custom listbox
   beats. The defect worth fixing was that the popup was rendering in the
   OS LIGHT theme -- white rows, black text -- against a near-black modal.
   `color-scheme: dark` is what tells the platform to draw the dark popup;
   the option colours cover the engines that ignore it. */
.cast-select {
    flex: 1 1 150px;
    width: auto;
    padding: 9px 30px 9px 12px;
    font-family: inherit;
    font-size: 0.8rem;
    color: #e8dcff;
    background-color: rgba(0, 0, 0, 0.26);
    border: 1px solid rgba(167, 119, 227, 0.2);
    border-radius: var(--border-radius-xs);
    cursor: pointer;
    color-scheme: dark;
    /* Own chevron, so the control keeps its shape across browsers. */
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23c2a4ee' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 11px 7px;
    transition: border-color 0.2s ease, background-color 0.2s ease;
}

.cast-select:hover {
    background-color: rgba(0, 0, 0, 0.34);
    border-color: rgba(167, 119, 227, 0.45);
}

/* No `outline: none` here, unlike .stg-input: this select does not carry that
   class, so leaving the outline alone is what lets the global :focus-visible
   ring (it is a :where() rule, so anything wins over it) still reach a
   keyboard user. The box-shadow below is the pointer-focus affordance. */
.cast-select:focus {
    border-color: rgba(167, 119, 227, 0.65);
    box-shadow: 0 0 0 3px rgba(167, 119, 227, 0.14);
}

.cast-select option {
    background-color: #15101f;
    color: #f0eaf8;
}

/* ---- Present / away toggle ---- */
.cast-present-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: none;
    padding: 7px 12px;
    font-family: inherit;
    font-size: 0.74rem;
    font-weight: 600;
    white-space: nowrap;
    border-radius: var(--border-radius-pill);
    cursor: pointer;
    transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.cast-present-toggle.is-present {
    color: var(--color-ok);
    background: rgba(var(--color-ok-rgb), 0.12);
    border: 1px solid rgba(var(--color-ok-rgb), 0.35);
}

.cast-present-toggle.is-present:hover {
    background: rgba(var(--color-ok-rgb), 0.2);
    border-color: rgba(var(--color-ok-rgb), 0.5);
}

.cast-present-toggle.is-away {
    color: rgba(255, 255, 255, 0.45);
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.12);
}

.cast-present-toggle.is-away:hover {
    color: rgba(255, 255, 255, 0.7);
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(255, 255, 255, 0.22);
}

/* ---- Ledgers ---- */
.cast-ledger {
    --cast-tint: 167, 119, 227;
    --cast-tint-text: #ddc3ff;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-top: 10px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.cast-ledger--secret {
    --cast-tint: var(--color-danger-rgb);
    --cast-tint-text: #ffaec0;
}

.cast-ledger .cast-label {
    color: var(--cast-tint-text);
}

.cast-ledger-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* The per-row "+ Add". Ghost by default so it never competes with the
   card's own primary action, tinted to whichever ledger it sits in. */
.cast-row-add {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: none;
    min-height: 30px;
    padding: 5px 11px;
    font-family: inherit;
    font-size: 0.73rem;
    font-weight: 600;
    color: var(--cast-tint-text, #ddc3ff);
    background: rgba(var(--cast-tint, 167, 119, 227), 0.09);
    border: 1px solid rgba(var(--cast-tint, 167, 119, 227), 0.28);
    border-radius: var(--border-radius-pill);
    cursor: pointer;
    transition: background-color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}

.cast-row-add i {
    font-size: 0.85em;
}

.cast-row-add:hover {
    background: rgba(var(--cast-tint, 167, 119, 227), 0.18);
    border-color: rgba(var(--cast-tint, 167, 119, 227), 0.5);
    transform: translateY(-1px);
}

.cast-row-add:active {
    transform: translateY(0);
}

.cast-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 8px;
    background: rgba(0, 0, 0, 0.16);
    border-left: 2px solid rgba(var(--cast-tint, 167, 119, 227), 0.35);
    border-radius: var(--border-radius-xs);
    transition: background-color 0.2s ease, border-color 0.2s ease;
}

.cast-row:hover {
    background: rgba(0, 0, 0, 0.24);
    border-left-color: rgba(var(--cast-tint, 167, 119, 227), 0.65);
}

/* ---- Empty states ---- */
.cast-empty {
    font-size: 0.76rem;
    font-style: italic;
    color: rgba(255, 255, 255, 0.32);
}

.cast-empty-state {
    margin: 0;
    padding: 1.75rem 1rem;
    text-align: center;
    font-size: 0.85rem;
    font-style: italic;
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.34);
}

/* ---- Icon buttons (delete member, remove row) ---- */
.cast-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 32px;
    height: 32px;
    padding: 0;
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.4);
    background: transparent;
    border: none;
    border-radius: var(--border-radius-xs);
    cursor: pointer;
    transition: color 0.18s ease, background-color 0.18s ease;
}

.cast-icon-btn:hover {
    color: var(--color-danger);
    background: rgba(var(--color-danger-rgb), 0.12);
}

.cast-icon-btn:active {
    background: rgba(var(--color-danger-rgb), 0.2);
}

@media (max-width: 768px) {

    /* Both of these are below --tap-min on desktop because they sit inside a
       dense row; on a phone they are the two things most likely to be
       mis-tapped, so they get the extra height a finger needs. */
    .cast-row-add {
        min-height: 38px;
        padding: 8px 14px;
    }

    .cast-icon-btn {
        width: 38px;
        height: 38px;
    }
}

#confirm-modal-overlay {
    z-index: 10005 !important;
}

/* =====================================================================
   MOBILE HARDENING LAYER  (appended — overrides earlier rules safely)
   Goals: kill horizontal scroll/overflow, tighten sidebar drawer,
   enforce comfortable tap targets, prevent iOS input zoom.
   Scoped to phones/tablets so desktop is untouched.
   ===================================================================== */

/* --- 1. Global horizontal-overflow guard (all viewports) --- */
html,
body {
    max-width: 100%;
    overflow-x: hidden;
}

#app,
.main-content,
.page {
    max-width: 100%;
    min-width: 0;
    /* allow flex/grid children to shrink instead of forcing scroll */
}

/* Media never pushes the layout wider than its container */
.page img,
.page video,
.message-content img {
    max-width: 100%;
    height: auto;
}

@media (max-width: 768px) {

    /* --- 2. Long words / URLs in chat wrap instead of overflowing --- */
    .message-content,
    .card-desc,
    .card-info .name,
    .chat-character-info .details .name {
        overflow-wrap: anywhere;
        word-break: break-word;
    }

    /* --- 3. Fixed-width offenders that caused sideways scroll --- */
    .shop-hero-balance-pill {
        min-width: 0;
        width: 100%;
        padding: 16px 18px;
        flex-wrap: wrap;
    }

    .shop-hero-section,
    .shop-hero-content-wrap {
        flex-direction: column;
        align-items: flex-start;
        gap: 16px;
    }

    /* Card / grid layouts: never demand more than one column width */
    .featured-card {
        min-width: 78vw;
        max-width: 78vw;
    }

    .public-profile-grid,
    .public-panel-grid,
    .public-about-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Any auto-fill grid: drop its min track so it can't overflow */
    .character-grid,
    .premium-grid,
    [class*="-grid"] {
        min-width: 0;
    }

    /* Horizontal scroll rows (chips, macros, carousels): contain them */
    .category-tags,
    .category-filters,
    .rp-macros,
    .featured-carousel {
        max-width: 100%;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        overscroll-behavior-x: contain;
    }

    .category-tags::-webkit-scrollbar,
    .category-filters::-webkit-scrollbar,
    .rp-macros::-webkit-scrollbar,
    .featured-carousel::-webkit-scrollbar {
        display: none;
    }

    .featured-carousel {
        scroll-snap-type: x mandatory;
        scroll-padding-left: 16px;
        gap: 12px;
        padding-bottom: 12px;
    }

    /* Momentum-scroll rows shouldn't lift/scale their children on tap */
    .featured-card:hover {
        transform: none;
        box-shadow: none;
    }

    .featured-card:active {
        transform: scale(0.98);
    }

    /* --- 4. Sidebar drawer polish --- */
    .sidebar {
        width: min(86vw, 320px);
        max-width: 86vw;
        height: 100%;
        height: 100dvh;
        padding-top: max(20px, var(--safe-top));
        padding-bottom: max(20px, var(--safe-bottom));
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }

    /* Recent chats list shouldn't force the drawer taller than the screen */
    .recent-chats {
        min-height: 0;
    }

    /* --- 5. Comfortable tap targets (>=44px) --- */
    .nav-item,
    .sidebar-account-toggle,
    .account-menu-item,
    .super-create-btn,
    .sidebar-upgrade-btn,
    .filter-chip,
    .tag {
        min-height: 44px;
    }

    .mobile-menu-btn {
        width: 44px;
        height: 44px;
    }

    /* --- 6. Prevent iOS auto-zoom: inputs must be >=16px --- */
    input,
    textarea,
    select,
    .search-container input,
    .chat-input-area textarea {
        font-size: 16px;
    }

    /* --- 7. Chat header: keep avatar visible + all action icons reachable ---
       The header was a single non-wrapping row; the wide model selector plus
       admin-only buttons (bug + key) pushed Memory and Restart off-screen.
       Fix: let the header wrap, drop the model selector to its own full-width
       row, give the character info room to shrink, and enlarge the avatar. */
    .chat-header {
        flex-wrap: wrap;
        row-gap: 8px;
        column-gap: 6px;
        padding: 10px 12px;
    }

    .chat-header .glass-back-btn {
        order: 1;
        flex-shrink: 0;
    }

    /* Character identity: bigger avatar, name truncates instead of overflowing */
    .chat-character-info {
        order: 2;
        flex: 1 1 auto;
        min-width: 0;
        gap: 10px;
    }

    .chat-character-info img {
        width: 42px;
        height: 42px;
        flex-shrink: 0;
    }

    .message-avatar,
    .message-avatar img,
    .message-avatar .avatar {
        width: 40px;
        height: 40px;
    }


    .chat-character-info .details {
        min-width: 0;
    }

    .chat-character-info .details .name {
        font-size: 0.95rem;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 42vw;
    }

    /* The empty flex spacer must not consume a whole wrap row */
    .chat-header>div[style*="flex-grow: 1"] {
        display: none !important;
    }

    /* Action icons (memory + restart, plus admin bug/key) sit together on the
       top row, right-aligned, and stay compact so all fit even for admins */
    .chat-header .icon-btn {
        order: 3;
        width: 38px;
        height: 38px;
        margin-right: 0 !important;
        margin-left: 2px !important;
        flex-shrink: 0;
    }

    /* Push the whole action cluster to the right edge regardless of how many
       dev buttons are visible. The character info takes all spare space, so
       every icon-btn that follows it lands hard against the right side. */
    .chat-character-info {
        margin-right: auto;
    }

    /* Model selector drops to its own full-width row below — no longer steals
       horizontal space from the Memory/Restart icons */
    #chat-model-selector {
        order: 4;
        flex: 1 1 100%;
        width: 100% !important;
        max-width: 100%;
        height: 36px !important;
        margin-right: 0 !important;
        font-size: 0.8rem !important;
    }
}

/* ============================================================
   DISCOVER PAGE — UI REFINEMENTS (appended; overrides above)
   ============================================================ */

/* --- Hero: calmer, more intentional --- */
.premium-hero {
    padding: 36px 44px;
    margin-bottom: 28px;
    gap: 40px;
    border: 1px solid rgba(167, 119, 227, 0.18);
    background:
        radial-gradient(120% 140% at 0% 0%, rgba(167, 119, 227, 0.10), transparent 55%),
        radial-gradient(120% 140% at 100% 100%, rgba(110, 142, 251, 0.10), transparent 55%),
        rgba(16, 11, 24, 0.66);
}

.hero-orb {
    opacity: 0.38;
    filter: blur(70px);
}

.welcome-back-header {
    font-size: 3rem;
    line-height: 1.1;
    padding-top: 4px;
    margin-bottom: 14px;
    letter-spacing: -1.5px;
    background: linear-gradient(105deg, #ffffff 18%, #d9bcff 60%, #9db4ff 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* ---- Hero subtitle: one rule, both class names ---------------------------------
   Four pages ship the same hero pattern and, measured at 1440, no two of them
   agreed on the subtitle:

     shop          .hero-subtitle        17.3px  lh 27.6  rgba(255,255,255,.66)  501px
     achievements  .shop-hero-subtitle   16px    lh norm  rgb(248,248,248)       none
     quests        .hero-subtitle        17.3px  lh 27.6  rgba(255,255,255,.66)  501px
     collection    .shop-hero-subtitle   16.3px  lh 25.3  rgba(255,255,255,.66)  494px

   Two causes, both invisible from reading any single rule. `.hero-subtitle` was
   declared TWICE at top level (here and ~11,000 lines earlier), so its computed
   style was a blend: this block won font-size/colour/margin, the earlier one
   supplied the line-height nothing here declared. And `.shop-hero-subtitle` had
   NO base rule anywhere in the file -- Achievements' subtitle was therefore not
   styled at all, just inherited body type, which is why it is the odd one out on
   every axis including having no measure at all.

   So: one rule, both names. The per-page blocks below keep only their real
   layout differences (margins), not their own copies of the type. */
.hero-subtitle,
.shop-hero-subtitle {
    font-size: 1.08rem;
    line-height: 1.6;
    max-width: 46ch;
    color: rgba(255, 255, 255, 0.66);
    margin-bottom: 24px;
}

/* Hero primary CTA */
.hero-cta-row {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.hero-cta-primary {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 13px 24px;
    border: none;
    border-radius: var(--border-radius-md);
    font-family: var(--font-display);
    font-size: 0.98rem;
    font-weight: 700;
    letter-spacing: 0.2px;
    color: var(--on-brand-gradient);
    cursor: pointer;
    background: var(--action-gradient);
    box-shadow: 0 8px 24px rgba(110, 90, 220, 0.35);
    transition: transform 0.25s cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 0.25s ease;
}

.hero-cta-primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 30px rgba(110, 90, 220, 0.5);
}

.hero-cta-primary:active {
    transform: translateY(0);
}

.hero-cta-primary i {
    font-size: 0.9rem;
}

/* Hero stats: tighter, with a divider rule */
.hero-stats {
    align-items: stretch;
    gap: 24px;
    padding: 22px 30px;
    background: rgba(0, 0, 0, 0.28);
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: var(--border-radius-md);
}

.stat-divider {
    width: 1px;
    align-self: stretch;
    background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.14), transparent);
}

.stat-item {
    justify-content: center;
    gap: 6px;
}

.stat-value {
    font-size: 2rem;
    background: linear-gradient(135deg, #ffffff, #d9bcff);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

.stat-label {
    font-size: 0.72rem;
    letter-spacing: 1.2px;
    color: rgba(255, 255, 255, 0.5);
}

/* --- Section header + scrollable filter rail --- */
.premium-section-header {
    align-items: center;
    flex-wrap: wrap;
    gap: 16px 24px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.premium-section-header h2 {
    font-family: var(--font-display);
    letter-spacing: -0.3px;
}

/* Replaces an inline `style="margin-top: 32px"` on the Explore Souls header.
   Inline styles can't be overridden by a media query, which is why the mobile
   pass at the bottom of this file could never tighten this gap. */
.section-header-gap {
    margin-top: 32px;
}

/* Filter chips wrap onto their own full-width row so every genre is visible */
.category-filters {
    position: relative;
    flex: 1 1 100%;
    width: 100%;
    flex-wrap: wrap;
    gap: 9px;
    padding: 4px 0 2px;
    margin-top: 4px;
}

.category-filters::-webkit-scrollbar {
    display: none;
}

/* Mobile: chip filters become a single-line snap slider instead of stacking
   into many wrapped rows. Negative margins let it bleed to the screen edges,
   and an edge mask fades chips as they scroll off — a polished "more here" cue.
   Declared after the desktop wrap rule above so it wins by source order. */
@media (max-width: 768px) {
    .category-filters {
        flex-wrap: nowrap;
        scroll-snap-type: x proximity;
        gap: 8px;
        padding: 4px 16px 6px;
        margin-left: -16px;
        margin-right: -16px;
        width: calc(100% + 32px);
        -webkit-mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 20px), transparent 100%);
        mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 20px), transparent 100%);
    }

    .filter-chip {
        scroll-snap-align: start;
    }
}

.filter-chip {
    flex: 0 0 auto;
    scroll-snap-align: start;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 15px;
    font-family: var(--font-display);
    font-size: 0.86rem;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.09);
    transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.filter-chip i {
    font-size: 0.78rem;
    opacity: 0.85;
}

.filter-chip:hover {
    background: rgba(255, 255, 255, 0.07);
    transform: translateY(-1px);
}

.filter-chip.active {
    background: linear-gradient(135deg, rgba(167, 119, 227, 0.28), rgba(110, 142, 251, 0.22));
    border-color: rgba(167, 119, 227, 0.6);
    color: #fff;
    box-shadow: 0 4px 16px rgba(167, 119, 227, 0.22);
}

.filter-chip.active i {
    opacity: 1;
    color: #e9d4ff;
}

/* Count badge on genre filter chips (souls-per-genre) */
.filter-chip .chip-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    margin-left: 2px;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.1);
    color: var(--text-secondary, #a1a1aa);
}

.filter-chip.active .chip-count {
    background: rgba(255, 255, 255, 0.22);
    color: #fff;
}

/* --- Accessible focus states (keyboard nav) --- */
.filter-chip:focus-visible,
.hero-cta-primary:focus-visible,
.character-card:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
}

/* ─────────────────────────────────────────────────────────────────────────
   Intent discovery: mood row + facet panel.

   Every chip in here is a plain `.filter-chip`, so `.active`, `.chip-count`
   and the `:focus-visible` rule above all apply verbatim — this block only
   adds the LAYOUT the two new surfaces need, not a second chip style.

   The layout split is deliberate:
     • .mood-row      — one line, scrollable. Its chips write a phrase into the
                        search box, so it must read as a rail of suggestions.
     • .facet-panel   — must WRAP. It holds ~102 chips across 7 groups; a snap
                        slider would hide most of them behind a scroll nobody
                        performs. It therefore does NOT inherit the mobile
                        .category-filters nowrap rule.
   ───────────────────────────────────────────────────────────────────────── */

/* Mood row — the primary teaching surface for what you can type. */
.mood-row {
    display: flex;
    flex: 1 1 100%;
    width: 100%;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 2px 0 4px;
}

.mood-row .mood-label {
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.8px;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.38);
    margin-right: 2px;
    flex: 0 0 auto;
}

/* Mood chips sit a shade warmer than facet chips: they perform a search
   rather than toggling a filter, so they should not look toggleable. */
.mood-row .mood-chip {
    cursor: pointer;
    background: rgba(167, 119, 227, 0.09);
    border-color: rgba(167, 119, 227, 0.22);
}

.mood-row .mood-chip:hover {
    background: rgba(167, 119, 227, 0.18);
    border-color: rgba(167, 119, 227, 0.45);
}

@media (max-width: 768px) {
    .mood-row {
        flex-wrap: nowrap;
        overflow-x: auto;
        scroll-snap-type: x proximity;
        padding: 2px 16px 6px;
        margin-left: -16px;
        margin-right: -16px;
        width: calc(100% + 32px);
        -webkit-mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 20px), transparent 100%);
        mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 20px), transparent 100%);
    }

    .mood-row::-webkit-scrollbar {
        display: none;
    }
}

/* Panel toggle, sitting inline with the mode chips. */
.filter-toggle-chip {
    cursor: pointer;
}

.filter-toggle-chip .facet-active-count {
    display: none;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    color: var(--on-brand-gradient);
}

.filter-toggle-chip.has-active .facet-active-count {
    display: inline-flex;
}

/* The panel itself. */
.facet-panel {
    flex: 1 1 100%;
    width: 100%;
    margin-top: 6px;
    padding: 14px 16px 8px;
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: var(--border-radius-md);
    background: rgba(0, 0, 0, 0.22);
}

.facet-panel[hidden] {
    display: none;
}

.facet-panel-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.facet-search {
    flex: 1 1 200px;
    min-width: 0;
    padding: 8px 13px;
    font-family: var(--font-display);
    font-size: 0.85rem;
    color: #fff;
    border-radius: var(--border-radius-pill);
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(255, 255, 255, 0.04);
}

.facet-search::placeholder {
    color: rgba(255, 255, 255, 0.35);
}

.facet-search:focus {
    outline: none;
    border-color: rgba(167, 119, 227, 0.55);
    background: rgba(255, 255, 255, 0.06);
}

.facet-clear {
    flex: 0 0 auto;
    padding: 7px 14px;
    font-family: var(--font-display);
    font-size: 0.8rem;
    color: var(--text-secondary, #a1a1aa);
    cursor: pointer;
    border-radius: var(--border-radius-pill);
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: transparent;
    transition: color 0.2s ease, border-color 0.2s ease;
}

.facet-clear:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.28);
}

.facet-clear[hidden] {
    display: none;
}

/* One <details> per group. Native disclosure means the open/closed state is
   owned by the DOM element, so it survives every count update — the reason
   updateFacetCounts() may only ever touch textContent and classList. */
.facet-group {
    border-top: 1px solid rgba(255, 255, 255, 0.05);
    padding: 8px 0;
}

.facet-group:first-of-type {
    border-top: none;
}

.facet-group[hidden] {
    display: none;
}

.facet-group>summary {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 5px 2px;
    font-family: var(--font-display);
    font-size: 0.86rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.82);
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.facet-group>summary::-webkit-details-marker {
    display: none;
}

.facet-group>summary:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
    border-radius: var(--border-radius-xs);
}

.facet-group>summary i:first-child {
    font-size: 0.8rem;
    color: #c9a6ff;
    opacity: 0.9;
}

.facet-group .fg-count {
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 1;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    color: var(--on-brand-gradient);
}

.facet-group.has-active .fg-count {
    display: inline-flex;
}

.facet-group .fg-caret {
    margin-left: auto;
    font-size: 0.7rem;
    opacity: 0.45;
    transition: transform 0.2s ease;
}

.facet-group[open]>summary .fg-caret {
    transform: rotate(90deg);
}

.facet-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 2px 4px;
}

.facet-chip {
    cursor: pointer;
}

.facet-chip[hidden] {
    display: none;
}

/* A zero-count chip is still clickable (the count is for the CURRENT result
   set, and clearing the search brings it back), so it is dimmed rather than
   disabled — greying out something that works is its own kind of lie. */
.facet-chip.is-empty {
    opacity: 0.38;
}

.facet-chip.is-empty.active {
    opacity: 1;
}

.facet-more {
    flex: 0 0 auto;
    padding: 8px 14px;
    font-family: var(--font-display);
    font-size: 0.8rem;
    color: #c9a6ff;
    cursor: pointer;
    border-radius: var(--border-radius-pill);
    border: 1px dashed rgba(167, 119, 227, 0.4);
    background: transparent;
}

.facet-more:hover {
    background: rgba(167, 119, 227, 0.12);
}

.facet-more[hidden] {
    display: none;
}

/* "Related: [Stepmom] [Older Woman]" beside the results header — the canonical
   tag taught at the highest-intent moment there is. */
.related-facets {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 12px;
    font-size: 0.8rem;
    color: var(--text-secondary, #a1a1aa);
}

.related-facets[hidden] {
    display: none;
}

.related-facets .related-label {
    font-weight: 600;
    letter-spacing: 0.4px;
    opacity: 0.75;
}

.related-facets .filter-chip {
    padding: 6px 13px;
    font-size: 0.8rem;
    cursor: pointer;
}

/* "Did you mean" chips inside the empty state. */
.empty-suggests {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}

.empty-suggests .filter-chip {
    cursor: pointer;
}

/* --- Empty / no-results state --- */
.character-grid .discover-empty {
    grid-column: 1 / -1;
    text-align: center;
    padding: 56px 20px;
    color: var(--text-secondary);
}

.character-grid .discover-empty .empty-icon {
    font-size: 2.4rem;
    margin-bottom: 16px;
    opacity: 0.35;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    opacity: 0.6;
}

.character-grid .discover-empty .empty-title {
    font-family: var(--font-display);
    font-size: 1.15rem;
    font-weight: 700;
    color: #fff;
    margin-bottom: 6px;
}

.character-grid .discover-empty .empty-sub {
    font-size: 0.88rem;
    opacity: 0.7;
}

/* --- Card polish --- */
.card-title-bar {
    font-size: 0.92rem;
    padding: 11px 14px;
    background: rgba(0, 0, 0, 0.4);
}

.card-stat-badge {
    background: rgba(0, 0, 0, 0.5);
    border: 1px solid rgba(167, 119, 227, 0.3);
}

.card-play-hint {
    color: rgba(226, 179, 255, 0.7);
}

/* --- Responsive hero --- */
@media (max-width: 900px) {
    .premium-hero {
        padding: 24px 22px;
        gap: 22px;
    }

    .welcome-back-header {
        font-size: 2.3rem;
    }

    .hero-stats {
        width: 100%;
        justify-content: space-around;
    }
}

/* --- Respect reduced motion --- */
@media (prefers-reduced-motion: reduce) {

    .hero-orb,
    .character-card::before,
    .card-title-text.card-title-auto-marquee,
    .card-title-bar .card-title-auto-marquee,
    body::before,
    body::after {
        animation: none !important;
    }

    .character-card,
    .filter-chip,
    .hero-cta-primary,
    .card-hero-wrap img {
        transition: none !important;
    }
}

/* ── Shop Rewards section (watch-ad-to-unlock, SFW page) ───────────── */
/* ── Reward chip (compact hero trigger) ───────────────────────────── */
.reward-chip {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    margin-top: 14px;
    padding: 12px 16px;
    cursor: pointer;
    text-align: left;
    border-radius: var(--border-radius-md);
    border: 1px solid rgba(167, 119, 227, 0.30);
    background:
        radial-gradient(120% 160% at 0% 0%, rgba(110, 142, 251, 0.14), transparent 60%),
        rgba(255, 255, 255, 0.03);
    color: #fff;
    transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}

.reward-chip:hover {
    transform: translateY(-2px);
    border-color: rgba(167, 119, 227, 0.55);
    box-shadow: 0 12px 28px -14px rgba(110, 142, 251, 0.8);
}

.reward-chip-icon {
    flex: 0 0 38px;
    width: 38px;
    height: 38px;
    display: grid;
    place-items: center;
    border-radius: var(--border-radius-sm);
    font-size: 1.05rem;
    color: #fff;
    background: linear-gradient(135deg, #6e8efb, #a777e3);
    box-shadow: 0 6px 16px -6px rgba(167, 119, 227, 0.75), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.reward-chip-text {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
    font-weight: 700;
    font-size: 0.95rem;
}

.reward-chip-sub {
    font-weight: 500;
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.55);
}

.reward-chip-arrow {
    margin-left: auto;
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.5);
    transition: transform 0.2s ease;
}

.reward-chip:hover .reward-chip-arrow {
    transform: translateX(3px);
    color: #c9b6ff;
}

/* ── Earn Orbs chip — sits above the reward chip, routes to Quests ── */
.earn-orbs-chip .reward-chip-icon {
    background: linear-gradient(135deg, #f7b733, #fc4a1a);
    box-shadow: 0 6px 16px -6px rgba(247, 183, 51, 0.75), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.earn-orbs-chip {
    border-color: rgba(247, 183, 51, 0.30);
    background:
        radial-gradient(120% 160% at 0% 0%, rgba(247, 183, 51, 0.14), transparent 60%),
        rgba(255, 255, 255, 0.03);
}

.earn-orbs-chip:hover {
    border-color: rgba(247, 183, 51, 0.55);
    box-shadow: 0 12px 28px -14px rgba(247, 183, 51, 0.8);
}

.earn-orbs-chip:hover .reward-chip-arrow {
    color: #ffd479;
}

/* ═══ Quests page ══════════════════════════════════════════════════════
   Extends the glass/orb shop-card language. Per-card accent is passed via the
   inline --quest-accent custom property so each quest gets its own hue while
   sharing one structural style. */

/* The Quests hero reuses the shop hero shell but carries less content, so it
   gets a tighter, shorter frame instead of the full marketing-size block. */
.quests-hero-section {
    padding: 30px 40px;
    margin-bottom: 24px;
}

/* Title size comes from the shared `.shop-hero-title` (3rem, with five breakpoint
   overrides below it). This block used to re-declare it as clamp(2rem, 4vw, 2.9rem),
   which put the quests hero at 46.4px against the shop's 48px -- a 1.6px difference
   nobody can see and everybody has to maintain. The clamp could not simply move to
   the base rule either: the shared 768px override is 2.2rem, so a 4vw base would
   have made the title JUMP UP as the window narrowed past 769px. */
.quests-hero-section .shop-hero-title {
    margin: 8px 0 0;
}

/* Two-class selector so this beats `.shop-tab-panel.active { display:block }`
   — otherwise the flex gap between Daily and Weekly never applies. */
.shop-tab-panel.quests-board {
    display: block;
}

.quests-section {
    position: relative;
}

/* A following section (Weekly after Daily) breaks away with a divider so the
   two groups read as distinct. Owns the full inter-section spacing on its
   own — no flex gap stacking on top. */
.quests-section+.quests-section {
    margin-top: 48px;
    padding-top: 44px;
    border-top: 1px solid rgba(167, 119, 227, 0.12);
}

/* Hero summary chips */
.quests-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 18px;
}

.quests-summary-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border-radius: var(--border-radius-pill);
    font-size: 0.82rem;
    font-weight: 600;
    color: rgba(230, 230, 245, 0.9);
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    backdrop-filter: blur(6px);
}

.quests-summary-chip i {
    font-size: 0.85rem;
}

.quests-summary-chip.is-ready {
    color: #fff;
    background: linear-gradient(135deg, rgba(124, 58, 237, 0.32), rgba(167, 139, 250, 0.22));
    border-color: rgba(167, 139, 250, 0.5);
    box-shadow: 0 6px 18px -8px rgba(124, 58, 237, 0.7);
    animation: quests-ready-pulse 2.4s ease-in-out infinite;
}

.quests-summary-chip.is-ready i {
    color: #d7c4ff;
}

@keyframes quests-ready-pulse {

    0%,
    100% {
        box-shadow: 0 6px 18px -8px rgba(124, 58, 237, 0.7);
    }

    50% {
        box-shadow: 0 6px 26px -6px rgba(167, 139, 250, 0.95);
    }
}

/* Section headers */
.quests-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

.quests-section-title {
    display: flex;
    align-items: center;
    gap: 14px;
}

.quests-section-title h2 {
    font-family: var(--font-display);
    font-size: 1.32rem;
    font-weight: 800;
    letter-spacing: -0.4px;
    color: #fff;
    margin: 0;
}

.quests-section-title p {
    font-size: 0.82rem;
    color: rgba(190, 190, 205, 0.72);
    margin: 2px 0 0;
}

.quests-section-icon {
    width: 46px;
    height: 46px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border-radius: var(--border-radius-md);
    font-size: 1.15rem;
    color: #fff;
}

.quests-section-icon--daily {
    background: linear-gradient(135deg, #7c3aed, #a78bfa);
    box-shadow: 0 8px 22px -8px rgba(124, 58, 237, 0.85), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.quests-section-icon--weekly {
    background: linear-gradient(135deg, #0ea5e9, #34d399);
    box-shadow: 0 8px 22px -8px rgba(14, 165, 233, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.quests-reset-pill {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 13px;
    border-radius: var(--border-radius-pill);
    font-size: 0.76rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: rgba(210, 210, 225, 0.82);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.09);
}

.quests-reset-pill i {
    opacity: 0.75;
}

.quests-grid {
    align-items: stretch;
}

/* ── Quest card ─────────────────────────────────────────────────────── */
/* Quest cards reuse .shop-card's frame/glow but not its .shop-card-inner
   wrapper, so the glass surface + padding are applied here directly. */
.quest-card {
    /* .shop-card is also on this element (dashboard.js), so radius, clip and
       resting depth arrive from the surface primitive. position and overflow
       come with them; z-index does not, and this needs one to sit above the
       section's own decoration. */
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 22px 22px 20px;
    background:
        radial-gradient(140% 90% at 50% -10%, color-mix(in srgb, var(--quest-accent, #a78bfa) 12%, transparent) 0%, transparent 55%),
        linear-gradient(168deg, rgba(38, 28, 60, 0.82) 0%, rgba(22, 16, 36, 0.92) 46%, rgba(15, 11, 24, 0.95) 100%);
    backdrop-filter: blur(22px);
    /* box-shadow replaces, it does not compose — so adding the inset highlight
       means restating the resting layers. var() keeps them defined once. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.10),
        var(--surface-shadow);
}

.quest-card-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

.quest-card-icon {
    width: 48px;
    height: 48px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border-radius: var(--border-radius-md);
    font-size: 1.25rem;
    color: #fff;
    background: linear-gradient(135deg, var(--quest-accent, #a78bfa), color-mix(in srgb, var(--quest-accent, #a78bfa) 55%, #000));
    box-shadow: 0 8px 20px -9px color-mix(in srgb, var(--quest-accent, #a78bfa) 90%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.28);
}

.quest-reward-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 6px 11px 6px 7px;
    border-radius: var(--border-radius-pill);
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 0.92rem;
    font-variant-numeric: tabular-nums;
    color: #ffe9a8;
    background: linear-gradient(135deg, rgba(247, 183, 51, 0.18), rgba(252, 74, 26, 0.12));
    border: 1px solid rgba(247, 183, 51, 0.35);
    white-space: nowrap;
}

/* Orb Multiplier active: the badge already shows the boosted payout, so tint it
   so the bigger number reads as "the perk is working" rather than a typo. */
.quest-reward-badge.is-boosted {
    color: #d8ccff;
    background: linear-gradient(135deg, rgba(155, 122, 255, 0.22), rgba(94, 200, 255, 0.14));
    border-color: rgba(155, 122, 255, 0.45);
    box-shadow: 0 0 12px rgba(155, 122, 255, 0.18);
}

.quest-reward-orb {
    width: 18px;
    height: 18px;
    filter: drop-shadow(0 1px 3px rgba(247, 183, 51, 0.6));
}

/* Progress bar */
.quest-progress {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 2px;
}

.quest-progress-track {
    flex: 1;
    height: 8px;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.08);
    overflow: hidden;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
}

.quest-progress-fill {
    height: 100%;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(90deg, var(--quest-accent, #a78bfa), color-mix(in srgb, var(--quest-accent, #a78bfa) 60%, #fff));
    box-shadow: 0 0 10px -1px color-mix(in srgb, var(--quest-accent, #a78bfa) 85%, transparent);
    transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

.quest-progress-label {
    font-size: 0.76rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: rgba(210, 210, 225, 0.9);
    white-space: nowrap;
}

.quest-card-hint {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.74rem;
    color: rgba(185, 185, 205, 0.7);
}

.quest-card-hint i {
    color: var(--quest-accent, #a78bfa);
    font-size: 0.72rem;
}

.quest-card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: auto;
    padding-top: 4px;
}

.quest-claim-btn {
    width: 100%;
    min-height: 44px;
    justify-content: center;
    font-variant-numeric: tabular-nums;
}

/* ── Card states ────────────────────────────────────────────────────── */
/* Locked: goal not yet met — muted, non-glowing. */
.quest-card.is-locked .quest-claim-btn {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.12);
    color: rgba(215, 215, 230, 0.75);
    cursor: default;
    box-shadow: none;
}

/* Claimable: goal met — the card lifts and the button glows to pull the eye. */
.quest-card.is-claimable {
    border-color: color-mix(in srgb, var(--quest-accent, #a78bfa) 55%, transparent);
    box-shadow: 0 14px 34px -18px color-mix(in srgb, var(--quest-accent, #a78bfa) 90%, transparent);
}

.quest-card.is-claimable .quest-claim-btn {
    background: linear-gradient(135deg, var(--quest-accent, #a78bfa), color-mix(in srgb, var(--quest-accent, #a78bfa) 60%, #000));
    border: 1px solid transparent;
    color: #fff;
    cursor: pointer;
    box-shadow: 0 8px 22px -8px color-mix(in srgb, var(--quest-accent, #a78bfa) 90%, transparent);
    animation: quest-claim-breathe 2.2s ease-in-out infinite;
}

.quest-card.is-claimable .quest-claim-btn:hover {
    transform: translateY(-2px);
    filter: brightness(1.08);
}

@keyframes quest-claim-breathe {

    0%,
    100% {
        box-shadow: 0 8px 22px -8px color-mix(in srgb, var(--quest-accent, #a78bfa) 90%, transparent);
    }

    50% {
        box-shadow: 0 8px 30px -6px color-mix(in srgb, var(--quest-accent, #a78bfa) 100%, transparent);
    }
}

/* Claimed: done for the period — dimmed, checkmark, green button. */
.quest-card.is-claimed {
    opacity: 0.72;
}

.quest-card.is-claimed .quest-card-icon {
    filter: saturate(0.7);
}

.quest-card.is-claimed .quest-claim-btn {
    background: rgba(52, 211, 153, 0.14);
    border: 1px solid rgba(52, 211, 153, 0.4);
    color: #6ee7b7;
    cursor: default;
    box-shadow: none;
}

/* Claim celebration flash */
.quest-card.quest-just-claimed {
    animation: quest-claimed-flash 0.9s ease-out;
}

@keyframes quest-claimed-flash {
    0% {
        transform: scale(1);
    }

    30% {
        transform: scale(1.03);
        box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.5);
    }

    100% {
        transform: scale(1);
    }
}

/* Orb burst confetti */
.quest-burst {
    position: absolute;
    top: 32px;
    left: 32px;
    width: 0;
    height: 0;
    pointer-events: none;
    z-index: 3;
}

.quest-burst-orb {
    position: absolute;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, #ffe9a8, #f7b733 60%, #fc4a1a);
    box-shadow: 0 0 8px rgba(247, 183, 51, 0.8);
    animation: quest-burst-fly 0.85s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}

@keyframes quest-burst-fly {
    0% {
        transform: translate(0, 0) scale(0.6);
        opacity: 1;
    }

    100% {
        transform: translate(var(--bx, 0), var(--by, 0)) scale(1);
        opacity: 0;
    }
}

/* Skeleton loading */
.quest-card--skeleton {
    pointer-events: none;
}

.quest-skeleton-icon,
.quest-skeleton-line,
.quest-skeleton-bar {
    border-radius: var(--border-radius-xs);
    background: linear-gradient(90deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.05));
    background-size: 200% 100%;
    animation: quest-skeleton-shimmer 1.4s ease-in-out infinite;
}

.quest-skeleton-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--border-radius-md);
}

.quest-skeleton-line {
    height: 14px;
    width: 70%;
    margin-top: 6px;
}

.quest-skeleton-line.short {
    width: 45%;
}

.quest-skeleton-bar {
    height: 8px;
    width: 100%;
    margin-top: 10px;
    border-radius: var(--border-radius-pill);
}

@keyframes quest-skeleton-shimmer {
    0% {
        background-position: 200% 0;
    }

    100% {
        background-position: -200% 0;
    }
}

.quests-empty {
    grid-column: 1 / -1;
    text-align: center;
    color: rgba(190, 190, 205, 0.7);
    font-size: 0.9rem;
    padding: 28px 12px;
}

@media (prefers-reduced-motion: reduce) {

    .quests-summary-chip.is-ready,
    .quest-card.is-claimable .quest-claim-btn,
    .quest-card.quest-just-claimed,
    .quest-progress-fill,
    .quest-skeleton-icon,
    .quest-skeleton-line,
    .quest-skeleton-bar {
        animation: none;
        transition: none;
    }
}

/* ── Reward modal (ad plays here) ─────────────────────────────────── */
.reward-modal {
    max-width: 460px;
    text-align: center;
}

.reward-modal-icon {
    background: linear-gradient(135deg, #6e8efb, #a777e3) !important;
    color: #fff !important;
    box-shadow: 0 10px 26px -8px rgba(167, 119, 227, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.reward-modal-actions {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 20px;
}

.reward-watch-btn,
.reward-return-btn {
    position: relative;
    border: none;
    cursor: pointer;
    border-radius: var(--border-radius-md);
    padding: 15px 26px;
    font-size: 1rem;
    font-weight: 700;
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    overflow: hidden;
    transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease, filter 0.2s ease;
}

/* [hidden] must be restored for both buttons, and it needs !important for the
   same reason .reward-fallback[hidden] below does: the display: inline-flex in
   the rule above beats the [hidden] attribute's UA rule, so `btn.hidden = true`
   silently did nothing. Both writers of that flag were affected — the no-network
   gate that hides Watch on the website, and the reward-earned path that swaps
   Watch for "return to chat" — so a hidden button stayed on screen, live and
   clickable, next to the panel that replaced it. */
.reward-watch-btn[hidden],
.reward-return-btn[hidden] {
    display: none !important;
}

.reward-watch-btn {
    background: linear-gradient(135deg, #6e8efb, #a777e3);
    box-shadow: 0 10px 26px -8px rgba(110, 142, 251, 0.8);
}

.reward-watch-btn:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 16px 34px -8px rgba(110, 142, 251, 0.95);
}

.reward-watch-btn:active:not(:disabled) {
    transform: translateY(0) scale(0.98);
}

/* Sheen sweep on hover */
.reward-btn-shine {
    position: absolute;
    top: 0;
    left: -120%;
    width: 60%;
    height: 100%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.35), transparent);
    transform: skewX(-18deg);
    transition: left 0.6s ease;
    pointer-events: none;
}

.reward-watch-btn:hover:not(:disabled) .reward-btn-shine {
    left: 130%;
}

.reward-return-btn {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.14);
    color: rgba(255, 255, 255, 0.85);
    font-weight: 600;
}

.reward-return-btn:hover {
    transform: translateY(-2px);
    background: rgba(255, 255, 255, 0.09);
    border-color: rgba(255, 255, 255, 0.25);
}

.reward-watch-btn:disabled {
    filter: saturate(0.6) brightness(0.85);
    cursor: default;
    box-shadow: none;
}

/* ── No-fill fallback ─────────────────────────────────────────────────
   Shown instead of a reward when the ad network has nothing to serve — or,
   on the website, when there is no ad network at all. It pays out nothing;
   these are the two ways out that are not an ad.

   [hidden] first, and !important: a display value from the class beats the
   [hidden] attribute's UA rule, so without this the panel would sit open on
   a page that has not even tried to load an ad yet. */
.reward-fallback[hidden] {
    display: none !important;
}

.reward-fallback {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid rgba(255, 255, 255, 0.10);
}

.reward-fallback-msg {
    margin: 0 0 4px;
    font-size: 0.9rem;
    line-height: 1.45;
    color: rgba(255, 255, 255, 0.68);
}

.reward-fallback-btn {
    position: relative;
    cursor: pointer;
    border-radius: var(--border-radius-md);
    padding: 13px 22px;
    font-size: 0.94rem;
    font-weight: 650;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.reward-fallback-btn:hover:not(:disabled) {
    transform: translateY(-2px);
}

.reward-fallback-btn:active:not(:disabled) {
    transform: translateY(0) scale(0.98);
}

.reward-fallback-btn:disabled {
    filter: saturate(0.6) brightness(0.85);
    cursor: default;
    transform: none;
}

.reward-fallback-orbs {
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.9);
}

.reward-fallback-orbs:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.28);
}

.reward-fallback-plus {
    border: none;
    color: #fff;
    background: linear-gradient(135deg, #6e8efb, #a777e3);
    box-shadow: 0 10px 26px -10px rgba(110, 142, 251, 0.75);
}

.reward-fallback-plus:hover:not(:disabled) {
    box-shadow: 0 16px 34px -10px rgba(110, 142, 251, 0.9);
}

@media (prefers-reduced-motion: reduce) {
    .reward-btn-shine {
        display: none;
    }
}

/* ── Settings toggle rows (mature-content prefs) ──────────────────── */
.stg-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 16px;
    margin-top: 10px;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
}

.stg-toggle-text {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.stg-toggle-title {
    font-weight: 600;
    font-size: 0.95rem;
    color: #fff;
}

.stg-toggle-sub {
    font-size: 0.82rem;
    color: rgba(255, 255, 255, 0.55);
    line-height: 1.4;
}

.stg-switch {
    position: relative;
    flex: 0 0 auto;
    width: 46px;
    height: 26px;
}

.stg-switch input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
}

.stg-switch-slider {
    position: absolute;
    inset: 0;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.15);
    transition: background 0.25s ease;
}

.stg-switch-slider::before {
    content: '';
    position: absolute;
    top: 3px;
    left: 3px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    transition: transform 0.25s ease;
}

.stg-switch input:checked+.stg-switch-slider {
    background: linear-gradient(135deg, #6e8efb, #a777e3);
}

.stg-switch input:checked+.stg-switch-slider::before {
    transform: translateX(20px);
}

.stg-switch input:disabled {
    cursor: not-allowed;
}

.stg-switch input:disabled+.stg-switch-slider {
    opacity: 0.4;
}

/* =====================================================================
   TABLET TAP-TARGET COVERAGE  (769px–1024px)
   The mobile hardening layer enforces >=44px tap targets only at
   <=768px, so tablets and landscape phones kept desktop sizing. This
   fills that gap with the SAME token-driven minimum (--tap-min), and
   only in the tablet band so desktop (>=1025px) is untouched.
   ===================================================================== */
@media (min-width: 769px) and (max-width: 1024px) {

    .nav-item,
    .sidebar-account-toggle,
    .account-menu-item,
    .super-create-btn,
    .sidebar-upgrade-btn,
    .filter-chip,
    .tag,
    .icon-btn,
    .glass-back-btn,
    .mobile-menu-btn {
        min-height: var(--tap-min, 44px);
    }

    .icon-btn,
    .glass-back-btn,
    .mobile-menu-btn {
        min-width: var(--tap-min, 44px);
    }
}

/* =====================================================================
   PLUS UPGRADE CELEBRATION  —  full-screen "Welcome to Loom Plus"
   Shown once when the dashboard loads with ?upgraded=1 after a
   successful Razorpay payment. Confetti + a glassy welcome card.
   ===================================================================== */
.plus-celebrate {
    position: fixed;
    inset: 0;
    z-index: 100000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: radial-gradient(circle at 50% 40%, rgba(167, 119, 227, 0.28), rgba(10, 8, 20, 0.82) 70%);
    backdrop-filter: blur(10px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.5s ease, visibility 0.5s ease;
}

.plus-celebrate.visible {
    opacity: 1;
    visibility: visible;
}

/* The welcome card */
.plus-celebrate-card {
    position: relative;
    z-index: 2;
    max-width: 440px;
    width: 100%;
    text-align: center;
    padding: 44px 36px 40px;
    border-radius: var(--border-radius-lg);
    background: linear-gradient(160deg, rgba(38, 30, 60, 0.92), rgba(22, 18, 38, 0.94));
    border: 1px solid rgba(167, 119, 227, 0.45);
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55), 0 0 60px rgba(167, 119, 227, 0.25);
    transform: scale(0.8) translateY(20px);
    opacity: 0;
    transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.5s ease;
}

.plus-celebrate.visible .plus-celebrate-card {
    transform: scale(1) translateY(0);
    opacity: 1;
}

.plus-celebrate-badge {
    width: 88px;
    height: 88px;
    margin: 0 auto 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.4rem;
    color: #fff;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    box-shadow: 0 0 0 8px rgba(167, 119, 227, 0.12), 0 12px 34px rgba(110, 142, 251, 0.45);
    animation: plusBadgePop 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) 0.15s both;
}

@keyframes plusBadgePop {
    0% {
        transform: scale(0) rotate(-45deg);
    }

    100% {
        transform: scale(1) rotate(0);
    }
}

.plus-celebrate-eyebrow {
    display: inline-block;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #c9a9f0;
    margin-bottom: 10px;
}

.plus-celebrate-title {
    font-family: var(--font-display);
    font-size: clamp(1.7rem, 1.2rem + 2vw, 2.3rem);
    font-weight: 800;
    line-height: 1.15;
    margin: 0 0 12px;
    background: linear-gradient(135deg, #fff, #d8bbff 55%, #6e8efb);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: #d8bbff;
}

.plus-celebrate-sub {
    font-size: 0.98rem;
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.78);
    margin: 0 0 28px;
}

.plus-celebrate-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 13px 32px;
    border: none;
    border-radius: var(--border-radius-pill);
    cursor: pointer;
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--on-brand-gradient);
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    box-shadow: 0 10px 28px rgba(110, 142, 251, 0.4);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.plus-celebrate-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 14px 34px rgba(110, 142, 251, 0.55);
}

/* Confetti layer */
.plus-celebrate-confetti {
    position: absolute;
    inset: 0;
    z-index: 1;
    overflow: hidden;
    pointer-events: none;
}

.plus-confetti-piece {
    position: absolute;
    top: -12px;
    width: 10px;
    height: 14px;
    border-radius: var(--border-radius-2xs);
    opacity: 0;
    animation: plusConfettiFall linear forwards;
}

@keyframes plusConfettiFall {
    0% {
        transform: translateY(-20px) rotate(0deg);
        opacity: 1;
    }

    100% {
        transform: translateY(102vh) rotate(720deg);
        opacity: 0.9;
    }
}

@media (prefers-reduced-motion: reduce) {

    .plus-celebrate,
    .plus-celebrate .plus-celebrate-card {
        transition: opacity 0.3s ease, visibility 0.3s ease;
    }

    .plus-celebrate-card {
        transform: none;
    }

    .plus-celebrate-badge {
        animation: none;
    }

    .plus-confetti-piece {
        display: none;
    }
}

/* ===== SUBSCRIPTION TAB ===== */
.sub-plan-head {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.sub-plan-icon {
    width: 52px;
    height: 52px;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--border-radius-md);
    font-size: 1.4rem;
    color: #a1a1aa;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.08);
}

.sub-plan-card.is-plus .sub-plan-icon {
    color: #ffcf5c;
    background: rgba(255, 199, 92, 0.12);
    border-color: rgba(255, 199, 92, 0.3);
}

.sub-plan-titles {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.sub-plan-name {
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--text-primary);
}

.sub-plan-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--text-secondary);
}

.sub-plan-status i {
    font-size: 0.5rem;
    color: #4ade80;
}

.sub-plan-status.expired i {
    color: #a1a1aa;
}

.sub-renew-btn {
    justify-content: center;
    white-space: nowrap;
}

.sub-detail-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
    margin-top: 22px;
}

.sub-detail-item {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px 16px;
    border-radius: var(--border-radius-sm);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(167, 119, 227, 0.1);
}

.sub-detail-label {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}

.sub-detail-value {
    font-size: 0.98rem;
    font-weight: 600;
    color: var(--text-primary);
}

.sub-expiry-bar-wrap {
    margin-top: 18px;
}

.sub-expiry-bar {
    height: 8px;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.08);
    overflow: hidden;
}

.sub-expiry-bar-fill {
    height: 100%;
    width: 0;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(135deg, #6e8efb, #a777e3);
    transition: width 0.4s ease;
}

.sub-expiry-bar-fill.low {
    background: linear-gradient(135deg, #fbbf24, #f97316);
}

.sub-plan-note {
    margin-top: 16px;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--text-secondary);
}

.sub-perks-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 14px;
}

.sub-perk-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border-radius: var(--border-radius-sm);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(167, 119, 227, 0.1);
}

.sub-perk-item i {
    color: #a777e3;
    font-size: 1rem;
    flex: 0 0 auto;
}

.sub-perk-name {
    font-weight: 600;
    color: var(--text-primary);
    font-size: 0.9rem;
}

.sub-perk-time {
    margin-left: auto;
    font-size: 0.8rem;
    color: var(--text-secondary);
    white-space: nowrap;
}

.sub-perks-empty {
    font-size: 0.85rem;
    color: var(--text-secondary);
    margin-top: 14px;
}

@media (max-width: 640px) {
    .sub-detail-grid {
        grid-template-columns: 1fr;
    }

    .sub-renew-btn {
        margin-left: 0 !important;
        width: 100%;
    }

    .sub-plan-head {
        gap: 12px;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   Cosmetics — avatar frames + title badges (bought with Orbs)
   Frames are pure CSS applied via .avatar-frame--<id> on the avatar
   WRAPPER (.account-avatar, .public-profile-avatar, .message-avatar).
   Each frame draws its ring with ::after so it never clashes with the
   sidebar avatar's existing ::before glow. border-radius:inherit keeps
   the ring matching whatever shape the surface uses (circle or rounded
   rect).
   ═══════════════════════════════════════════════════════════════════ */

[class*="avatar-frame--"] {
    position: relative;
}

[class*="avatar-frame--"]::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: inherit;
    pointer-events: none;
    z-index: 2;
}

/* The message-avatar wrapper is a rounded rect, so its ring needs rounded corners
   rather than the circle the other two wrappers get.

   This CANNOT be `border-radius: inherit` like the generic rule above, and that
   is the trap: .message-avatar itself declares no radius at all -- it is a bare
   52x52 flex box, and the rounding lives on its `img` / `.avatar` CHILD. So
   `inherit` here resolves to 0 and squares the ring. (.account-avatar and
   .public-profile-avatar are both 50%, which is why inherit is right for them.)

   It is --border-radius-md because that is what the child is. It used to be a
   literal 20px against a child of 18px, i.e. the ring was 2px rounder than the
   square it was ringing -- visible as a hairline gap at each corner on any
   avatar wearing a purchased frame. */
.message-avatar[class*="avatar-frame--"]::after {
    border-radius: var(--border-radius-md);
}

/* ─────────────────────────────────────────────────────────────────────
   Avatar frames — pure-CSS rings drawn on the ::after (and, for the showier
   tiers, ::before) of any wrapper carrying `.avatar-frame--<id>`. Ordered by
   tier: calm static rings at the bottom, motion-forward auras at the top.
   Motion is frozen under prefers-reduced-motion (see block at end).
   ───────────────────────────────────────────────────────────────────── */

/* Ancient Guardian (common) — weathered brushed steel, a faint living sheen. */
.avatar-frame--slate-ring::after {
    box-shadow: 0 0 0 2px #8a93a6, 0 0 0 4px rgba(199, 206, 219, 0.45), inset 0 0 6px rgba(199, 206, 219, 0.25);
}

/* Tidecaller (rare) — deep-sea cyan halo that breathes. */
.avatar-frame--aqua-pulse::after {
    box-shadow: 0 0 0 2px #39d3ff, 0 0 10px 2px rgba(57, 211, 255, 0.55);
    animation: cosmeticAquaPulse 2.6s ease-in-out infinite;
}

@keyframes cosmeticAquaPulse {

    0%,
    100% {
        box-shadow: 0 0 0 2px #39d3ff, 0 0 8px 1px rgba(57, 211, 255, 0.4);
    }

    50% {
        box-shadow: 0 0 0 2px #6e8efb, 0 0 16px 4px rgba(57, 211, 255, 0.7);
    }
}

/* Cinderborn (rare) — embers that never quite die, a warm flicker. */
.avatar-frame--ember-aura::after {
    box-shadow: 0 0 0 2px #ff7a45, 0 0 12px 3px rgba(255, 122, 69, 0.55);
    animation: cosmeticEmber 3s ease-in-out infinite;
}

@keyframes cosmeticEmber {

    0%,
    100% {
        box-shadow: 0 0 0 2px #ff7a45, 0 0 9px 2px rgba(255, 122, 69, 0.45);
    }

    50% {
        box-shadow: 0 0 0 2px #ffb347, 0 0 18px 5px rgba(255, 159, 71, 0.7);
    }
}

/* Forest Spirit (epic) — a living emerald conic ring in slow rotation. */
.avatar-frame--verdant-vine::after {
    inset: -4px;
    background: conic-gradient(from 0deg, #37d67a, #a8e063, #37d67a, #1f9d5a, #37d67a);
    padding: 3px;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    filter: drop-shadow(0 0 6px rgba(55, 214, 122, 0.45));
    animation: cosmeticSpin 6s linear infinite;
}

/* Sovereign (epic) — polished gold with a sweeping specular glint. */
.avatar-frame--gold-ring::after {
    inset: -4px;
    background: conic-gradient(from 0deg, #b8860b, #f6c453, #fff4cf, #f6c453, #b8860b);
    padding: 3px;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    filter: drop-shadow(0 0 7px rgba(246, 196, 83, 0.55));
    animation: cosmeticSpin 5.5s linear infinite;
}

/* Void Walker (legendary) — rotating aurora conic ring + a soft outer glow.
   ::after is the crisp masked ring; ::before is the diffuse bloom behind it.
   The bloom is suppressed on .account-avatar, whose own ::before drives the
   sidebar hover glow — the crisp ::after ring still shows there. */
.avatar-frame--astral-halo:not(.account-avatar)::before {
    content: '';
    position: absolute;
    inset: -8px;
    border-radius: inherit;
    pointer-events: none;
    z-index: 1;
    background: conic-gradient(from 0deg, #a777e3, #6e8efb, #39d3ff, #a777e3);
    filter: blur(9px);
    opacity: 0.55;
    animation: cosmeticSpin 5s linear infinite;
}

.avatar-frame--astral-halo::after {
    inset: -4px;
    background: conic-gradient(from 0deg, #a777e3, #6e8efb, #39d3ff, #a777e3);
    padding: 3px;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    animation: cosmeticSpin 5s linear infinite;
}

/* Chrono Rift (mythic) — the apex. A hue-drifting aurora ring, a diffuse
   outer bloom, AND an orbiting spark dot on ::before. Three layers of motion,
   deliberately the busiest thing anyone can wear. The ::before spark/bloom is
   suppressed on the sidebar .account-avatar (see Void Walker note). */
.avatar-frame--chrono-rift::after {
    inset: -4px;
    background: conic-gradient(from 0deg, #ff5edb, #a777e3, #39d3ff, #ffcf5c, #ff5edb);
    padding: 3px;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    filter: drop-shadow(0 0 8px rgba(255, 94, 219, 0.5));
    animation: cosmeticSpin 4.5s linear infinite, cosmeticHueDrift 8s linear infinite;
}

.avatar-frame--chrono-rift:not(.account-avatar)::before {
    content: '';
    position: absolute;
    inset: -7px;
    border-radius: inherit;
    pointer-events: none;
    z-index: 3;
    /* A single bright spark riding the ring edge, plus a faint outer bloom. */
    background:
        radial-gradient(circle at 50% 0%, #fff 0, #ffcf5c 2px, transparent 4px),
        conic-gradient(from 0deg, transparent, rgba(255, 94, 219, 0.4), transparent 40%);
    filter: blur(0.4px) drop-shadow(0 0 6px rgba(255, 207, 92, 0.9));
    animation: cosmeticSpin 3s linear infinite reverse;
}

/* Shared rotation keyframe reused by every rotating-ring frame. */
@keyframes cosmeticSpin {
    to {
        transform: rotate(360deg);
    }
}

/* Mythic-only hue rotation layered on top of the spin. */
@keyframes cosmeticHueDrift {
    to {
        filter: drop-shadow(0 0 8px rgba(255, 94, 219, 0.5)) hue-rotate(360deg);
    }
}

/* ── Lottie-animated avatar frames (registry-driven) ─────────────────────────
   Any frame declaring a `lottie` path in cosmetics-config.js renders through
   frame-lottie.js instead of a CSS ::after ring. frameClass() tags those avatars
   with the shared `.avatar-frame--lottie` marker, so every rule below is written
   ONCE and covers every present and future animated frame. The only per-frame
   value is the glow colour, passed in as --frame-lottie-glow from the config.

   The ::after pseudo is suppressed so the injected .frame-lottie-overlay div owns
   the visual layer entirely. The overlay SVG (z-index:2) is a ring/wreath whose
   CENTER is transparent, so it sits behind the avatar face — but only if the
   avatar content itself forms a stacking context above it. The rules below lift
   the avatar's own content (img / initial letter / icon) above the overlay on
   every surface it appears on, otherwise the ring would paint over the
   face/letter and the avatar would look blank. */
.avatar-frame--lottie::after {
    display: none;
}

.frame-lottie-overlay {
    position: absolute;
    inset: -100%;
    pointer-events: none;
    z-index: 2;
    filter: drop-shadow(0 0 7px var(--frame-lottie-glow, rgba(255, 94, 0, 0.65)));
}

.frame-lottie-overlay svg {
    width: 100% !important;
    height: 100% !important;
}

/* Tighter inset on the small chips, where -100% would overflow the row. */
.account-avatar.avatar-frame--lottie .frame-lottie-overlay,
.card-creator-avatar.avatar-frame--lottie .frame-lottie-overlay {
    inset: -75%;
}

/* Lift the avatar's own content above the ring overlay so the face / initial /
   icon stays visible. Covers every surface an animated frame can appear on:
   chat (.message-avatar), shop preview (.cosmetic-sample-avatar), My Collection
   live preview (.inv-preview-avatar), sidebar/account (.account-avatar), and the
   small card-creator chip (.card-creator-avatar). z-index:3 > the overlay's 2. */
.avatar-frame--lottie > img,
.avatar-frame--lottie > .avatar,
.avatar-frame--lottie > .avatar > i,
.avatar-frame--lottie > i {
    position: relative;
    z-index: 3;
}

/* The My Collection preview holds either an <img> or a wrapped initial span. */
.avatar-frame--lottie.inv-preview-avatar {
    z-index: 0;
}

.avatar-frame--lottie.cosmetic-sample-avatar {
    position: relative;
}
.avatar-frame--lottie.inv-preview-avatar > img,
.avatar-frame--lottie.inv-preview-avatar > .avatar,
.avatar-frame--lottie.inv-preview-avatar > .inv-preview-initial {
    position: relative;
    z-index: 3;
}

/* ── Earned-only level frames (granted at Level 5 / Level 10) ──
   Pure CSS rings — no images, no JS animation renderer. Reuse the shared
   cosmeticSpin keyframe so they rotate like the other animated frames. */

/* Level 5 — Ascending Halo: a calm twin-tone teal→blue ring. */
.avatar-frame--level-5::after {
    inset: -4px;
    background: conic-gradient(from 0deg, #7fd4c1, #6e8efb, #7fd4c1);
    padding: 3px;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    filter: drop-shadow(0 0 7px rgba(110, 142, 251, 0.5));
    animation: cosmeticSpin 6s linear infinite;
}
.avatar-frame--level-5:not(.account-avatar)::before {
    content: '';
    position: absolute;
    inset: -7px;
    border-radius: inherit;
    pointer-events: none;
    z-index: 3;
    background: radial-gradient(circle at 50% 0%, #fff 0, #7fd4c1 2px, transparent 4px);
    filter: blur(0.4px) drop-shadow(0 0 5px rgba(127, 212, 193, 0.9));
    animation: cosmeticSpin 4s linear infinite reverse;
}

/* Level 10 — Apex Crown: a radiant prismatic ring with a brighter bloom. */
.avatar-frame--level-10::after {
    inset: -5px;
    background: conic-gradient(from 0deg, #ffcf5c, #ff5edb, #39d3ff, #ffcf5c);
    padding: 3px;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    filter: drop-shadow(0 0 9px rgba(255, 94, 219, 0.55));
    animation: cosmeticSpin 4s linear infinite, cosmeticHueDrift 8s linear infinite;
}
.avatar-frame--level-10:not(.account-avatar)::before {
    content: '';
    position: absolute;
    inset: -8px;
    border-radius: inherit;
    pointer-events: none;
    z-index: 3;
    background:
        radial-gradient(circle at 50% 0%, #fff 0, #ffcf5c 2px, transparent 4px),
        conic-gradient(from 0deg, transparent, rgba(255, 207, 92, 0.45), transparent 40%);
    filter: blur(0.4px) drop-shadow(0 0 7px rgba(255, 207, 92, 0.95));
    animation: cosmeticSpin 2.5s linear infinite reverse;
}
.account-avatar.avatar-frame--level-5::after,
.card-creator-avatar.avatar-frame--level-5::after,
.account-avatar.avatar-frame--level-10::after,
.card-creator-avatar.avatar-frame--level-10::after {
    inset: -3px;
}

/* ── Title badge chip (next to a username) ── */
.user-title-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    vertical-align: middle;
    margin-left: 6px;
    padding: 2px 9px;
    border-radius: var(--border-radius-pill);
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1.4;
    letter-spacing: 0.2px;
    color: var(--title-color, #c9a0f5);
    background: color-mix(in srgb, var(--title-color, #c9a0f5) 16%, transparent);
    border: 1px solid color-mix(in srgb, var(--title-color, #c9a0f5) 45%, transparent);
    white-space: nowrap;
}

.user-title-badge i {
    font-size: 0.7em;
}

/* If a gradient username-style clips text on the name container, the badge is a
   child and would inherit the transparent fill — restore its own solid color. */
.user-title-badge {
    -webkit-text-fill-color: var(--title-color, #c9a0f5);
}

.user-title-badge--lg {
    font-size: 0.9rem;
    padding: 5px 14px;
    margin-left: 0;
}

/* ── Cosmetics shop tab ── */
.shop-cosmetics-intro {
    text-align: center;
    max-width: 620px;
    margin: 0 auto 22px;
}

.shop-cosmetics-intro h3 {
    font-size: 1.35rem;
    font-weight: 800;
    color: #fff;
    margin-bottom: 6px;
}

.shop-cosmetics-intro p {
    color: rgba(255, 255, 255, 0.65);
    font-size: 0.95rem;
}

.shop-cosmetics-heading {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 1.05rem;
    font-weight: 700;
    color: #fff;
    margin: 26px 4px 14px;
    padding-bottom: 8px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.shop-cosmetics-heading i {
    color: #a777e3;
}

.shop-grid--cosmetics {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
}

/* Per-tier accent tokens — one source of truth shared by cosmetic cards,
   tier ribbons and inventory swatches so a rarity reads consistently. */
.cosmetic-tint--common {
    --tier-accent: #a9b2c4;
    --tier-glow: rgba(169, 178, 196, 0.35);
}

.cosmetic-tint--rare {
    --tier-accent: #6e8efb;
    --tier-glow: rgba(110, 142, 251, 0.45);
}

.cosmetic-tint--epic {
    --tier-accent: #c9a0f5;
    --tier-glow: rgba(167, 119, 227, 0.5);
}

.cosmetic-tint--legendary {
    --tier-accent: #ffcf5c;
    --tier-glow: rgba(246, 196, 83, 0.55);
}

.cosmetic-tint--mythic {
    --tier-accent: #ff5edb;
    --tier-glow: rgba(255, 94, 219, 0.6);
}

/* Mythic reads as iridescent, not flat pink: the ribbon + card wash shift hue. */
.cosmetic-tint--mythic .cosmetic-tier {
    background: linear-gradient(90deg, rgba(255, 94, 219, 0.25), rgba(57, 211, 255, 0.25), rgba(255, 207, 92, 0.25));
    color: #fff;
    animation: cosmeticHueSpin 6s linear infinite;
}

@keyframes cosmeticHueSpin {
    to {
        filter: hue-rotate(360deg);
    }
}

/* ── Cosmetic card (redesigned) ──
   One calm, tier-tinted card: big preview → title → desc → a SINGLE footer
   affordance. No competing cost pill + meter + lock button. */
.shop-card--cosmetic {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--border-radius-md);
    background:
        radial-gradient(120% 80% at 50% 0%, color-mix(in srgb, var(--tier-accent, #a777e3) 14%, transparent), transparent 60%),
        linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.015));
    overflow: hidden;
    transition: transform 0.35s cubic-bezier(0.34, 1.4, 0.64, 1), border-color 0.35s ease, box-shadow 0.35s ease;
}

.shop-card--cosmetic:hover {
    transform: translateY(-4px);
    border-color: color-mix(in srgb, var(--tier-accent, #a777e3) 55%, transparent);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px color-mix(in srgb, var(--tier-accent, #a777e3) 25%, transparent);
}

.shop-card--cosmetic .shop-card-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 4px;
    padding: 18px 16px 16px;
    flex: 1;
}

.shop-card--cosmetic .shop-card-title {
    margin-top: 2px;
}

.shop-card--cosmetic .shop-card-desc {
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.55);
    line-height: 1.45;
    margin-bottom: 4px;
    min-height: 2.3em;
}

.shop-card--cosmetic.is-equipped-card {
    border-color: rgba(55, 214, 122, 0.5);
    box-shadow: 0 0 0 1px rgba(55, 214, 122, 0.25), 0 14px 34px rgba(0, 0, 0, 0.4);
}

.shop-card--cosmetic.cosmetic-locked {
    opacity: 0.72;
}

/* Preview stage — a tier-tinted pedestal for the frame/title sample. */
.cosmetic-preview {
    display: grid;
    place-items: center;
    width: 100%;
    min-height: 118px;
    margin-bottom: 8px;
    border-radius: var(--border-radius-md);
    background:
        radial-gradient(80% 80% at 50% 40%, color-mix(in srgb, var(--tier-accent, #a777e3) 18%, transparent), transparent 70%);
}

.cosmetic-sample-avatar {
    width: 68px;
    height: 68px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, #4a4266, #2c2740);
    color: rgba(255, 255, 255, 0.85);
    font-size: 1.6rem;
}

.cosmetic-title-preview {
    transform: scale(1.05);
}

/* Rarity ribbon */
.cosmetic-tier {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 3;
    padding: 3px 10px;
    border-radius: var(--border-radius-pill);
    font-size: 0.6rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.7px;
    color: var(--tier-accent, #c9a0f5);
    background: color-mix(in srgb, var(--tier-accent, #c9a0f5) 18%, rgba(0, 0, 0, 0.35));
    border: 1px solid color-mix(in srgb, var(--tier-accent, #c9a0f5) 40%, transparent);
}

/* Equipped flag (top-left, green) */
.cosmetic-equipped-flag {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 10px;
    border-radius: var(--border-radius-pill);
    font-size: 0.62rem;
    font-weight: 800;
    color: #7ef0a8;
    background: rgba(55, 214, 122, 0.16);
    border: 1px solid rgba(55, 214, 122, 0.45);
}

/* ── Footer: exactly one action ── */
.cosmetic-card-footer {
    width: 100%;
    margin-top: auto;
}

.cosmetic-action-btn {
    position: relative;
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 11px 14px;
    border-radius: var(--border-radius-sm);
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.3px;
    cursor: pointer;
    border: 1px solid transparent;
    overflow: hidden;
    transition: transform 0.25s ease, box-shadow 0.3s ease, background 0.3s ease, opacity 0.2s ease;
}

/* Buy: price + label, no separate cost pill. Label swaps to a lock when
   unaffordable (the .cosmetic-locked ancestor drives it). */
.cosmetic-buy-btn {
    justify-content: space-between;
    color: #fff;
    background: linear-gradient(135deg, #a777e3 0%, #7b44d4 55%, #6e3de7 100%);
    box-shadow: 0 6px 18px rgba(110, 61, 231, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.15);
}

.cosmetic-buy-btn:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 12px 28px rgba(110, 61, 231, 0.45);
}

.cosmetic-buy-btn:disabled {
    cursor: not-allowed;
    background: rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.5);
    box-shadow: none;
}

.cosmetic-buy-price {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-variant-numeric: tabular-nums;
    font-weight: 800;
}

.cosmetic-buy-orb {
    width: 15px;
    height: 15px;
    object-fit: contain;
}

.cosmetic-buy-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.cosmetic-lock-ico {
    display: none;
}

.shop-card--cosmetic.cosmetic-locked .cosmetic-buy-ico {
    display: none;
}

.shop-card--cosmetic.cosmetic-locked .cosmetic-lock-ico {
    display: inline;
}

/* Equip / Equipped */
.cosmetic-action-btn.is-equip {
    color: #d9c8ff;
    background: color-mix(in srgb, var(--tier-accent, #a777e3) 16%, transparent);
    border-color: color-mix(in srgb, var(--tier-accent, #a777e3) 45%, transparent);
}

.cosmetic-action-btn.is-equip:hover {
    transform: translateY(-2px);
    background: color-mix(in srgb, var(--tier-accent, #a777e3) 26%, transparent);
}

.cosmetic-action-btn.is-equipped {
    color: #7ef0a8;
    background: rgba(55, 214, 122, 0.14);
    border-color: rgba(55, 214, 122, 0.45);
    cursor: default;
}

/* ═══════════════════════════════════════════════════════════════════
   My Collection (inventory) tab
   ═══════════════════════════════════════════════════════════════════ */
.inv-layout {
    display: grid;
    grid-template-columns: 300px 1fr;
    gap: 24px;
    align-items: start;
}

/* Live preview card (sticky on wide screens) */
.inv-preview-card {
    position: sticky;
    top: 12px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 12px;
    padding: 28px 22px;
    border-radius: var(--border-radius-md);
    border: 1px solid rgba(167, 119, 227, 0.2);
    background:
        radial-gradient(120% 70% at 50% 0%, rgba(167, 119, 227, 0.14), transparent 65%),
        linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.015));
}

.inv-preview-stage {
    display: grid;
    place-items: center;
    padding: 14px;
}

.inv-preview-avatar {
    width: 116px;
    height: 116px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 3rem;
    font-weight: 800;
    color: #fff;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
    overflow: visible;
}

.inv-preview-avatar img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
}

.inv-preview-name {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px;
    font-size: 1.2rem;
    font-weight: 800;
    color: #fff;
}

.inv-preview-hint {
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.5);
    line-height: 1.5;
    max-width: 240px;
}

/* Right column: grouped collections */
.inv-collection {
    display: flex;
    flex-direction: column;
    gap: 26px;
}

.inv-group-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.inv-group-head h4 {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 1.05rem;
    font-weight: 700;
    color: #fff;
}

.inv-group-head h4 i {
    color: #a777e3;
}

.inv-count {
    font-size: 0.78rem;
    color: rgba(255, 255, 255, 0.45);
    font-weight: 600;
}

.inv-swatch-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: 12px;
}

/* One selectable item */
.inv-swatch {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 9px;
    padding: 16px 10px 12px;
    border-radius: var(--border-radius-md);
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.025);
    cursor: pointer;
    transition: transform 0.22s ease, border-color 0.25s ease, background 0.25s ease, box-shadow 0.25s ease;
}

.inv-swatch:hover {
    transform: translateY(-3px);
    border-color: color-mix(in srgb, var(--tier-accent, #a777e3) 55%, transparent);
    background: rgba(255, 255, 255, 0.05);
}

.inv-swatch.is-active {
    border-color: rgba(55, 214, 122, 0.6);
    background: rgba(55, 214, 122, 0.08);
    box-shadow: 0 0 0 1px rgba(55, 214, 122, 0.35);
}

.inv-swatch-visual {
    display: grid;
    place-items: center;
    height: 56px;
}

.inv-swatch-avatar {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    color: rgba(255, 255, 255, 0.85);
    background: linear-gradient(135deg, #4a4266, #2c2740);
    font-size: 1.2rem;
}

.inv-swatch-none-visual {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    border: 2px dashed rgba(255, 255, 255, 0.25);
    color: rgba(255, 255, 255, 0.4);
    font-size: 1.1rem;
}

.inv-swatch-label {
    font-size: 0.76rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.75);
    line-height: 1.2;
    text-align: center;
}

.inv-swatch.is-active .inv-swatch-label {
    color: #fff;
}

.inv-swatch-check {
    position: absolute;
    top: 6px;
    right: 6px;
    color: #7ef0a8;
    font-size: 0.85rem;
}

.inv-empty {
    grid-column: 1 / -1;
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.5);
    padding: 8px 2px;
}

.inv-empty strong {
    color: #c9a0f5;
}

/* Responsive: stack preview above the collection on narrow screens */
@media (max-width: 720px) {
    .inv-layout {
        grid-template-columns: 1fr;
    }

    .inv-preview-card {
        position: static;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   Username styles — a gradient (and optional animation) painted on the
   display name via `.username-style--<id>`. Gradient text uses
   background-clip:text; the color falls back to the accent if clip is
   unsupported. Applied wherever a name renders (sidebar, profile, chat,
   followers, previews). Motion frozen under prefers-reduced-motion.
   ═══════════════════════════════════════════════════════════════════ */
[class*="username-style--"] {
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    background-repeat: no-repeat;
    /* Slightly heavier weight helps thin gradient text stay legible. */
    font-weight: 800;
}

.username-style--oceanic {
    background-image: linear-gradient(90deg, #39d3ff, #6e8efb);
}

.username-style--sunset {
    background-image: linear-gradient(90deg, #ff7a45, #ffb347);
}

.username-style--emerald {
    background-image: linear-gradient(90deg, #37d67a, #a8e063);
}

.username-style--amethyst {
    background-image: linear-gradient(90deg, #a777e3, #c9a0f5);
}

.username-style--ashen {
    background-image: linear-gradient(90deg, #b8c0cc, #6d7688);
}

.username-style--rose-gold {
    background-image: linear-gradient(90deg, #ffb3a7, #f6c453);
    /* Elegant italic serif to match the soft, luxe blush-gold theme. */
    font-family: 'Playfair Display', Georgia, serif;
    font-style: italic;
    font-weight: 700;
    letter-spacing: 0.3px;
}

.username-style--toxic {
    background-image: linear-gradient(90deg, #9bff5c, #37d67a);
    /* Techy, radioactive-console feel. */
    font-family: 'Orbitron', var(--font-display);
    font-weight: 800;
    letter-spacing: 0.5px;
}

.username-style--crimson {
    background-image: linear-gradient(90deg, #ff5e7a, #a71d31);
    /* Bold classical serif — regal, dramatic. */
    font-family: 'Cinzel', Georgia, serif;
    font-weight: 700;
    letter-spacing: 0.4px;
}

/* Aurora (legendary, animated) — a wide gradient that drifts across the name. */
.username-style--aurora {
    background-image: linear-gradient(90deg, #39d3ff, #a777e3, #ff5edb, #39d3ff);
    background-size: 300% 100%;
    /* Tile the wrapping gradient so the 0→300% scroll loops seamlessly (no pop). */
    background-repeat: repeat;
    animation: cosmeticAuroraName 4.5s linear infinite;
    will-change: background-position;
}

@keyframes cosmeticAuroraName {
    to {
        background-position: 300% 0;
    }
}

/* Spectrum (legendary, animated) — a wide rainbow that flows across the name. */
.username-style--spectrum {
    background-image: linear-gradient(90deg, #ff5e5e, #ffcf5c, #37d67a, #39d3ff, #a777e3, #ff5e5e);
    background-size: 300% 100%;
    background-repeat: repeat;
    animation: cosmeticAuroraName 4.5s linear infinite;
    will-change: background-position;
    /* Wide retro-futuristic face — reads as a marquee sign. Second slot is the
       token, not a literal: Audiowide loads non-blocking (print-media swap in
       the page head) so this is the face the cosmetic wears for the first few
       hundred ms. It used to name 'Outfit', which no page loads any more. */
    font-family: 'Audiowide', var(--font-display);
    font-weight: 400;
    letter-spacing: 0.5px;
}

/* Prismatic (mythic, animated) — bright iridescent light that bends across the name. */
.username-style--prismatic {
    background-image: linear-gradient(90deg, #ff5edb, #39d3ff, #9bff5c, #ffcf5c, #ff5edb);
    background-size: 300% 100%;
    background-repeat: repeat;
    animation: cosmeticAuroraName 4s linear infinite;
    filter: saturate(1.25);
    will-change: background-position;
    /* Sci-fi display face to sell the mythic capstone. */
    font-family: 'Orbitron', var(--font-display);
    font-weight: 800;
    letter-spacing: 0.6px;
}

/* ═══════════════════════════════════════════════════════════════════
   Message bubble skins — theme the user's OWN chat bubbles via
   `.bubble-skin--<id>` on .message-content. Bot bubbles are untouched.
   Kept motion-light so long chats stay readable.
   ═══════════════════════════════════════════════════════════════════ */
.bubble-skin--parchment {
    background: linear-gradient(160deg, #efe3c6, #d9c69a) !important;
    color: #3a2f1a !important;
    border: 1px solid #cbb892 !important;
    box-shadow: 0 2px 8px rgba(120, 96, 48, 0.25) !important;
}

.bubble-skin--neon {
    background: rgba(20, 26, 48, 0.92) !important;
    color: #eaf6ff !important;
    border: 1px solid #39d3ff !important;
    box-shadow: 0 0 12px rgba(57, 211, 255, 0.5), inset 0 0 8px rgba(57, 211, 255, 0.18) !important;
}

.bubble-skin--ember {
    background: linear-gradient(160deg, #ff8a4b, #d1521e) !important;
    color: #fff6ee !important;
    border: 1px solid #ffb347 !important;
    box-shadow: 0 2px 12px rgba(209, 82, 30, 0.4) !important;
}

.bubble-skin--holographic {
    background: linear-gradient(120deg, rgba(167, 119, 227, 0.9), rgba(57, 211, 255, 0.9), rgba(255, 94, 219, 0.9)) !important;
    background-size: 200% 200% !important;
    color: #fff !important;
    border: 1px solid rgba(255, 255, 255, 0.4) !important;
    box-shadow: 0 2px 14px rgba(167, 119, 227, 0.45) !important;
    animation: cosmeticHoloShift 8s ease infinite !important;
}

@keyframes cosmeticHoloShift {

    0%,
    100% {
        background-position: 0% 50%;
    }

    50% {
        background-position: 100% 50%;
    }
}

/* Slate (common) — clean flat graphite, no motion. */
.bubble-skin--slate {
    background: linear-gradient(160deg, #404a63, #2b3346) !important;
    color: #e6ecf7 !important;
    border: 1px solid rgba(160, 175, 205, 0.28) !important;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35) !important;
}

/* Royal (epic) — regal purple with a gilded hairline edge. */
.bubble-skin--royal {
    background: linear-gradient(160deg, #5b3fa0, #2c2f7a) !important;
    color: #f3ecff !important;
    border: 1px solid #f6c453 !important;
    box-shadow: 0 2px 12px rgba(91, 63, 160, 0.45) !important;
}

/* Prism (mythic, animated) — a living iridescent sweep, richer and faster than Holographic. */
.bubble-skin--prism {
    background: linear-gradient(120deg, rgba(255, 94, 219, 0.92), rgba(57, 211, 255, 0.92), rgba(155, 255, 92, 0.92), rgba(255, 207, 92, 0.92)) !important;
    background-size: 300% 300% !important;
    color: #fff !important;
    border: 1px solid rgba(255, 255, 255, 0.55) !important;
    box-shadow: 0 2px 16px rgba(255, 94, 219, 0.5) !important;
    animation: cosmeticHoloShift 5s ease infinite !important;
}

/* ── Previews for the new cosmetic types (shop cards + inventory swatches) ── */
.cosmetic-username-preview {
    display: grid;
    place-items: center;
    width: 100%;
}

.cosmetic-username-sample {
    font-size: 1.35rem;
    font-weight: 800;
    letter-spacing: 0.3px;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cosmetic-bubble-preview {
    display: grid;
    place-items: center;
    width: 100%;
}

.cosmetic-bubble-sample,
.inv-preview-bubble {
    display: inline-block;
    padding: 9px 14px;
    border-radius: var(--border-radius-md) var(--border-radius-md) var(--border-radius-2xs) var(--border-radius-md);
    font-size: 0.9rem;
    font-weight: 600;
    /* Neutral base so an unskinned sample still looks like a bubble. */
    background: linear-gradient(135deg, #6e8efb, #5a7af0);
    color: var(--on-brand-gradient);
    max-width: 90%;
}

.inv-preview-bubble {
    margin-top: 2px;
}

/* Inventory swatch visuals for the two new types. */
.inv-swatch-visual--username .cosmetic-username-sample {
    font-size: 0.95rem;
}

.inv-swatch-visual--bubble .cosmetic-bubble-sample {
    padding: 6px 10px;
    font-size: 0.72rem;
    border-radius: var(--border-radius-sm) var(--border-radius-sm) var(--border-radius-2xs) var(--border-radius-sm);
}

/* ═══════════════════════════════════════════════════════════════════
   Accessibility — freeze all cosmetic motion for users who ask for it.
   Frames, animated usernames, holographic/prism bubbles and mythic ribbons
   all settle to a static first frame.
   ═══════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {

    [class*="avatar-frame--"]::before,
    [class*="avatar-frame--"]::after,
    [class*="username-style--"],
    .bubble-skin--holographic,
    .bubble-skin--prism,
    .cosmetic-tint--mythic .cosmetic-tier {
        animation: none !important;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   My Collection — standalone page shell
   Mirrors the shop page container padding and the tighter quests-style
   hero so the destination reads as a sibling of Discover/Diary/Shop.
   ═══════════════════════════════════════════════════════════════════ */
#page-collection {
    position: relative;
    overflow-y: auto;
    padding: 0 28px 60px;
}

/* Shorter hero than the shop's marketing block (it carries less content). */
.collection-hero-section {
    padding: 30px 40px;
    margin-bottom: 24px;
}

.collection-hero-section .shop-hero-title {
    margin: 8px 0 0;
}

/* Type comes from the shared subtitle rule; only the vertical rhythm differs here,
   because this hero's title uses `margin: 8px 0 0` rather than the shop's. */
.collection-hero-section .shop-hero-subtitle {
    margin: 12px 0 0;
}

.collection-body {
    position: relative;
    z-index: 1;
}

/* "Unlock more" hero link → deep-links into the Cosmetics shop */
.collection-shop-link {
    display: inline-flex;
    align-items: center;
    gap: 14px;
    padding: 16px 20px;
    border-radius: var(--border-radius-md);
    text-decoration: none;
    border: 1px solid rgba(167, 119, 227, 0.25);
    background: linear-gradient(160deg, rgba(40, 28, 70, 0.7), rgba(20, 14, 40, 0.9));
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(167, 119, 227, 0.08);
    transition: transform 0.22s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

.collection-shop-link:hover {
    transform: translateY(-2px);
    border-color: rgba(167, 119, 227, 0.5);
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4), 0 0 40px rgba(167, 119, 227, 0.12);
}

.collection-shop-link-icon {
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    flex-shrink: 0;
    border-radius: var(--border-radius-sm);
    font-size: 1.1rem;
    color: #fff;
    background: linear-gradient(135deg, #a777e3, #6e8efb);
}

.collection-shop-link-text {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
}

.collection-shop-link-text strong {
    font-size: 0.98rem;
    font-weight: 700;
    color: #fff;
}

.collection-shop-link-text small {
    font-size: 0.78rem;
    color: rgba(255, 255, 255, 0.55);
}

.collection-shop-link>.fa-chevron-right {
    color: rgba(255, 255, 255, 0.4);
    font-size: 0.8rem;
    transition: transform 0.22s ease, color 0.25s ease;
}

.collection-shop-link:hover>.fa-chevron-right {
    transform: translateX(3px);
    color: #c9a0f5;
}

/* On narrow screens the hero stacks; let the link sit full-width beneath. */
@media (max-width: 720px) {
    .collection-shop-link {
        width: 100%;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   ACHIEVEMENTS / LEVELS PAGE
   Permanent milestones + XP/level widget. Reuses the shop hero/particle
   backdrop for visual continuity with the rest of the orb economy.
   ═══════════════════════════════════════════════════════════════════ */

.achievements-hero-section .shop-hero-right {
    min-width: 300px;
    display: flex;
    justify-content: flex-end;
}

/* Level widget — sits in the hero's right column. A conic "prestige ring"
   wraps the level number so progress reads at a glance. */
.achievements-level-widget {
    position: relative;
    width: 100%;
    max-width: 300px;
    padding: 22px 22px 18px;
    border-radius: var(--border-radius-lg);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    background:
        radial-gradient(120% 90% at 50% 0%, rgba(255, 94, 219, 0.14), transparent 62%),
        linear-gradient(160deg, rgba(167, 119, 227, 0.22), rgba(110, 142, 251, 0.08));
    border: 1px solid rgba(167, 119, 227, 0.34);
    box-shadow: 0 16px 44px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.1);
    backdrop-filter: blur(14px);
}
/* Prestige ring — the conic gradient sweeps the XP arc around the level no. */
.alw-ring-wrap { display: flex; justify-content: center; }
.alw-ring {
    --xp-pct: 0;
    position: relative;
    width: 132px;
    height: 132px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background:
        conic-gradient(from -90deg,
            #ffcf5c 0%,
            #ff5edb calc(var(--xp-pct) * 1%),
            rgba(255, 255, 255, 0.07) calc(var(--xp-pct) * 1%),
            rgba(255, 255, 255, 0.07) 100%);
    box-shadow: 0 0 28px rgba(255, 94, 219, 0.22), inset 0 0 0 1px rgba(255, 255, 255, 0.04);
    transition: background 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.alw-ring::before {
    /* blurred halo that traces only the filled arc — glow grows with progress */
    content: '';
    position: absolute;
    inset: -6px;
    border-radius: 50%;
    background: conic-gradient(from -90deg,
        rgba(255, 207, 92, 0.4), rgba(255, 94, 219, 0.4) calc(var(--xp-pct) * 1%),
        transparent calc(var(--xp-pct) * 1%));
    filter: blur(10px);
    opacity: 0.75;
    z-index: -1;
}
.alw-ring-inner {
    width: 104px;
    height: 104px;
    border-radius: 50%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    background: radial-gradient(120% 120% at 50% 0%, rgba(40, 28, 66, 0.96), rgba(16, 11, 32, 0.98));
    box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.alw-label {
    font-size: 0.62rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.5);
    font-weight: 700;
}
.alw-level {
    font-size: 2.5rem;
    font-weight: 800;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    background: linear-gradient(120deg, #ffcf5c, #ff5edb);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    filter: drop-shadow(0 0 12px rgba(255, 94, 219, 0.4));
}
.alw-cap {
    /* was .42 -> 4.04:1; see .stg-nav-label above. This one carries a nested <span> with
       no colour of its own, so it inherits — the audit reported "/ 10" and "10" as two
       failures and they are one rule. */
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.5);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
/* The slim linear bar under the ring. renderAchievements() sets a width on
   `.alw-xp-fill` (dashboard.js:9688, "drives both the slim linear bar and the
   conic prestige ring") and the reduced-motion block below already names it,
   but neither element had any CSS — so the track rendered at 0px and the bar
   was invisible on every screen. */
.alw-xp-track {
    width: 100%;
    height: 6px;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.09);
    border: 1px solid rgba(255, 255, 255, 0.06);
    overflow: hidden;
}
.alw-xp-fill {
    height: 100%;
    width: 0;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(90deg, #ffcf5c, #ff5edb);
    box-shadow: 0 0 10px rgba(255, 94, 219, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.35);
    transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.alw-xp-text {
    font-size: 0.76rem;
    color: rgba(255, 255, 255, 0.62);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.achievements-body {
    padding: 4px clamp(16px, 4vw, 40px) 64px;
}

/* Toolbar — section title on the left, status chips on the right. Gives the
   page a clear "Milestones" heading instead of cards floating in a void. */
.achievements-toolbar {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px 20px;
    margin: 6px 0 26px;
    padding-bottom: 16px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.achievements-toolbar-title h2 {
    margin: 0;
    font-size: 1.35rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: #fff;
}
.achievements-toolbar-title p {
    margin: 4px 0 0;
    font-size: 0.84rem;
    color: rgba(255, 255, 255, 0.5);
}
.achievements-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    min-height: 28px;
}
.ach-summary-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 6px 13px;
    border-radius: var(--border-radius-pill);
    font-size: 0.8rem;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.78);
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.08);
}
.ach-summary-chip i { color: #ffcf5c; }
.ach-summary-chip--ready {
    color: #fff;
    background: linear-gradient(120deg, rgba(167, 119, 227, 0.35), rgba(255, 94, 219, 0.25));
    border-color: rgba(255, 94, 219, 0.5);
    box-shadow: 0 0 16px rgba(255, 94, 219, 0.25);
}
.ach-summary-chip--ready i { color: #ff5edb; }

/* The grid is now a vertical stack of family sections, each with its own
   responsive card row — so the three journeys read as distinct groups. */
.achievements-grid {
    display: flex;
    flex-direction: column;
    gap: 40px;
}
.achievements-empty {
    text-align: center;
    color: rgba(255, 255, 255, 0.5);
    padding: 48px 0;
    font-size: 0.95rem;
}

/* ── Family section header ── */
.ach-family { --fam: #a777e3; }
.ach-family-head {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 18px;
    padding-bottom: 14px;
}
/* Fading hairline under each family header, tinted to that journey's accent —
   visually groups the cards beneath it without a heavy divider. */
.ach-family-head::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 1px;
    background: linear-gradient(90deg, color-mix(in srgb, var(--fam) 55%, transparent), rgba(255, 255, 255, 0.06) 55%, transparent);
}
.ach-family-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    flex: 0 0 auto;
    border-radius: var(--border-radius-sm);
    font-size: 1.05rem;
    color: color-mix(in srgb, var(--fam) 78%, #fff);
    background: linear-gradient(150deg, color-mix(in srgb, var(--fam) 26%, transparent), color-mix(in srgb, var(--fam) 8%, rgba(255, 255, 255, 0.03)));
    border: 1px solid color-mix(in srgb, var(--fam) 40%, transparent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 0 16px color-mix(in srgb, var(--fam) 22%, transparent);
}
.ach-family-copy { flex: 1 1 auto; min-width: 0; }
.ach-family-label {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: #fff;
}
.ach-family-blurb {
    margin: 2px 0 0;
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.48);
}
.ach-family-count {
    flex: 0 0 auto;
    font-size: 0.78rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    padding: 4px 11px;
    border-radius: var(--border-radius-pill);
    color: color-mix(in srgb, var(--fam) 70%, #fff);
    background: color-mix(in srgb, var(--fam) 12%, rgba(255, 255, 255, 0.04));
    border: 1px solid color-mix(in srgb, var(--fam) 30%, transparent);
}
/* Each journey gets its own hue so the three groups feel distinct at a glance:
   words spoken = emerald, souls met = cyan, souls created = violet. */
.ach-family:nth-of-type(1) { --fam: #37d67a; }
.ach-family:nth-of-type(2) { --fam: #38bdf8; }
.ach-family:nth-of-type(3) { --fam: #c084fc; }
.ach-family-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 16px;
}

/* Achievement card — built on .shop-card so it inherits the glass surface. */
.achievement-card {
    --ach-accent: #a78bfa;
    /* radius, clip, resting depth and hover transition come from the surface
       primitive via .shop-card, which dashboard.js also puts on this element. */
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 20px;
    /* Explicit glass surface so cards read as tiles on the dark page, not as
       outlines floating in the void. */
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.014));
    /* Was a bare rgba(255,255,255,0.07) against .public-panel-card's 0.06 and
       the file's own --border-color at 0.08 — three hairlines nobody could tell
       apart, drifting independently. One token. */
    border: 1px solid var(--border-color);
    /* Staggered entrance — cards cascade in per family row. We keep the
       RESTING state visible (opacity:1) and use `backwards` fill so the hidden
       start-frame only applies during the per-card delay; once the animation
       ends there is no `forwards` fill pinning transform/opacity, so :hover
       lift still works (an animated fill would otherwise override it). */
    opacity: 1;
    transform: none;
    animation: achCardIn 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards;
    animation-delay: calc(var(--ach-i, 0) * 45ms);
}
@keyframes achCardIn {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
}
.achievement-card::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: radial-gradient(120% 70% at 50% -20%, color-mix(in srgb, var(--ach-accent) 20%, transparent), transparent 65%);
    pointer-events: none;
    transition: opacity 0.3s ease;
    opacity: 0.85;
}
.achievement-card:hover::before { opacity: 1; }
/* Thin accent line along the top edge — gives each card its own identity and
   a crisp "lit" edge that lifts it off the dark page. */
.achievement-card::after {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--ach-accent), transparent);
    opacity: 0.55;
    transition: opacity 0.3s ease, height 0.3s ease;
    pointer-events: none;
}
.achievement-card:hover::after { opacity: 1; }
.achievement-card-head {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.achievement-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--border-radius-sm);
    font-size: 1.15rem;
    background: color-mix(in srgb, var(--ach-accent) 14%, rgba(255, 255, 255, 0.03));
    border: 1px solid color-mix(in srgb, var(--ach-accent) 38%, transparent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 0 18px color-mix(in srgb, var(--ach-accent) 26%, transparent);
}
.achievement-xp-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.03em;
    padding: 4px 9px;
    border-radius: var(--border-radius-pill);
    color: #ffd877;
    background: rgba(255, 207, 92, 0.12);
    border: 1px solid rgba(255, 207, 92, 0.28);
}
.achievement-xp-badge i { font-size: 0.62rem; }
.achievement-title {
    position: relative;
    margin: 0;
    font-size: 1.06rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: #fff;
}
.achievement-desc {
    position: relative;
    margin: -4px 0 0;
    font-size: 0.83rem;
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.55);
    min-height: 2.5em;
}
.achievement-progress {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top:2px;
}
.achievement-progress-track {
    height: 8px;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid rgba(255, 255, 255, 0.05);
    overflow: hidden;
}
.achievement-progress-fill {
    height: 100%;
    border-radius: var(--border-radius-pill);
    background: linear-gradient(90deg, var(--ach-accent), color-mix(in srgb, var(--ach-accent) 55%, #fff));
    box-shadow: 0 0 10px color-mix(in srgb, var(--ach-accent) 45%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.4);
    transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.achievement-progress-text {
    font-size: 0.72rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: rgba(255, 255, 255, 0.5);
    align-self: flex-end;
}
.achievement-rewards {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.achievement-rewards > span {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 0.72rem;
    font-weight: 700;
    padding: 4px 9px;
    border-radius: var(--border-radius-pill);
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.8);
}
.ach-reward-orb { color: #d9c4ff; }
.ach-reward-orb i { color: #a777e3; font-size: 0.6rem; }
.ach-reward-cosmetic {
    color: #ffc2ec;
    background: rgba(255, 94, 219, 0.1);
    border-color: rgba(255, 94, 219, 0.28);
}
.ach-reward-cosmetic i { color: #ff5edb; }
.achievement-claim-btn {
    position: relative;
    margin-top: auto;
    padding: 11px 18px;
    font-size: 0.9rem;
    width: 100%;
    justify-content: center;
}

/* Locked cards: calm, not a wall of gray. A faint frosted diagonal texture
   keeps the surface alive, and the icon/top-edge dim to read as "dormant"
   rather than broken. The button stays a quiet ghost. */
.achievement-card.is-locked {
    border-color: rgba(255, 255, 255, 0.06);
    background:
        repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.02) 0 2px, transparent 2px 9px),
        linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
}
.achievement-card.is-locked::after { opacity: 0.2; }
.achievement-card.is-locked .achievement-icon {
    filter: grayscale(0.45) brightness(0.85);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.achievement-card.is-locked:hover {
    transform: translateY(-3px);
    box-shadow: 0 16px 38px -16px rgba(0, 0, 0, 0.6);
}
.achievement-card.is-locked .achievement-claim-btn {
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: rgba(255, 255, 255, 0.4);
    box-shadow: none;
    cursor: not-allowed;
}
.achievement-card.is-locked .achievement-progress-fill { opacity: 0.7; }

/* State variants. */
.achievement-card.is-claimable {
    border-color: color-mix(in srgb, var(--ach-accent) 55%, transparent);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--ach-accent) 35%, transparent),
        0 14px 40px -10px color-mix(in srgb, var(--ach-accent) 40%, transparent);
    animation: achCardIn 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards,
        achievementPulse 2.4s ease-in-out 0.6s infinite;
}
.achievement-card.is-claimable .achievement-claim-btn {
    background: linear-gradient(135deg, var(--ach-accent), color-mix(in srgb, var(--ach-accent) 50%, #6e8efb));
    box-shadow: 0 6px 20px color-mix(in srgb, var(--ach-accent) 45%, transparent);
}
@keyframes achievementPulse {
    0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--ach-accent) 40%, transparent), 0 10px 30px color-mix(in srgb, var(--ach-accent) 18%, transparent); }
    50% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--ach-accent) 65%, transparent), 0 10px 40px color-mix(in srgb, var(--ach-accent) 34%, transparent); }
}
.achievement-card.is-claimed {
    /* "Earned" reads through the green edge + border + button; the body is
       gently settled (no whole-card desaturate, which looked like dead gray). */
    border-color: rgba(55, 214, 122, 0.28);
    background: linear-gradient(180deg, rgba(55, 214, 122, 0.06), rgba(255, 255, 255, 0.012));
}
.achievement-card.is-claimed::before { opacity: 0.4; }
.achievement-card.is-claimed::after {
    background: linear-gradient(90deg, transparent, rgba(55, 214, 122, 0.85), transparent);
    opacity: 0.6;
}
.achievement-card.is-claimed .achievement-icon {
    filter: saturate(0.6) brightness(0.95);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.achievement-card.is-claimed .achievement-title { color: rgba(255, 255, 255, 0.82); }
.achievement-card.is-claimed .achievement-claim-btn {
    background: rgba(55, 214, 122, 0.14);
    border: 1px solid rgba(55, 214, 122, 0.34);
    color: #7fe6a8;
    box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
    /* Entrance animation off → cards must be forced visible (base is opacity:0). */
    .achievement-card {
        opacity: 1;
        transform: none;
        animation: none;
    }
    .achievement-card.is-claimable { animation: none; }
    .alw-xp-fill,
    .alw-ring,
    .achievement-progress-fill { transition: none; }
}

/* ── Public profile level badge (next to the display name) ── */
.profile-level-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    vertical-align: middle;
    margin-left: 8px;
    padding: 2px 9px;
    border-radius: var(--border-radius-pill);
    font-size: 0.72rem;
    font-weight: 800;
    line-height: 1.4;
    color: #ffcf5c;
    background: linear-gradient(120deg, rgba(255, 207, 92, 0.16), rgba(255, 94, 219, 0.14));
    border: 1px solid rgba(255, 207, 92, 0.4);
    box-shadow: 0 0 12px rgba(255, 207, 92, 0.25);
}
.profile-level-badge i { font-size: 0.66rem; }

/* ── Achievement unlock banner (top of screen, mid-chat nudge) ── */
.ach-unlock-banner {
    position: fixed;
    top: 16px;
    left: 50%;
    transform: translate(-50%, -140%);
    z-index: 9999;
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: min(440px, calc(100vw - 24px));
    padding: 12px 14px 12px 14px;
    border-radius: var(--border-radius-md);
    background: linear-gradient(135deg, rgba(30, 22, 48, 0.96), rgba(44, 28, 70, 0.96));
    border: 1px solid rgba(255, 94, 219, 0.45);
    box-shadow: 0 14px 44px rgba(0, 0, 0, 0.5), 0 0 24px rgba(167, 119, 227, 0.35);
    backdrop-filter: blur(14px);
    opacity: 0;
    pointer-events: none;
    transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.35s ease;
}
.ach-unlock-banner.visible {
    transform: translate(-50%, 0);
    opacity: 1;
    pointer-events: auto;
}
.ach-unlock-banner-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: var(--border-radius-sm);
    font-size: 1.1rem;
    color: #ffcf5c;
    background: rgba(255, 207, 92, 0.14);
    border: 1px solid rgba(255, 207, 92, 0.35);
}
.ach-unlock-banner-copy {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}
.ach-unlock-banner-copy strong {
    font-size: 0.92rem;
    color: #fff;
}
.ach-unlock-banner-copy span {
    font-size: 0.78rem;
    color: rgba(255, 255, 255, 0.62);
}
.ach-unlock-banner-btn {
    flex: 0 0 auto;
    padding: 8px 14px;
}
.ach-unlock-banner-close {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border: none;
    border-radius: var(--border-radius-xs);
    background: rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.6);
    cursor: pointer;
    transition: background 0.2s, color 0.2s;
}
.ach-unlock-banner-close:hover {
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
}
@media (max-width: 480px) {
    .ach-unlock-banner-copy span { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   ORB SHOP — MOBILE PASS
   Appended last on purpose: the shop rules live at ~9000 and ~14700,
   and several later blocks (12687, 14747) re-declare the same
   selectors. Source order here is what makes these win.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* The hero's right column shrink-wraps its content once the wrap goes
       vertical, so the balance card and both chips rendered narrower than
       the paragraph above them. Make the column own the full row. */
    #page-shop .shop-hero-right {
        width: 100%;
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: stretch;
    }

    #page-shop .shop-hero-left {
        max-width: none;
    }

    /* Decorative layers are the main jank source on a phone GPU: two
       animated mesh gradients, fixed-position particles, and a 22px
       backdrop-filter on every card. None of it survives being scrolled. */
    .shop-particles {
        display: none;
    }

    .shop-hero-mesh,
    .shop-hero-mesh-2 {
        animation: none;
    }

    .shop-hero-balance-glow {
        animation: none;
        opacity: 0.7;
    }

    .shop-card-inner {
        backdrop-filter: none;
    }

    /* Hover-only flourishes never fire on touch but still cost paint. */
    .shop-card-border-anim {
        display: none;
    }

    .shop-card:hover {
        transform: none;
    }

    /* Press feedback instead — touch has no hover to lean on. */
    .shop-card:active {
        transform: scale(0.985);
    }

    .shop-buy-btn {
        animation: none;
    }

    /* Long toast copy hit `white-space: nowrap` and ran off both edges. */
    .shop-toast {
        left: 12px;
        right: 12px;
        bottom: 20px;
        transform: translateY(100px);
    }

    .shop-toast.visible {
        transform: translateY(0);
    }

    .shop-toast-inner {
        white-space: normal;
        justify-content: center;
        padding: 12px 18px;
        text-align: left;
    }
}

@media (max-width: 560px) {

    #page-shop {
        padding: 0 12px 32px;
    }

    .shop-hero-section {
        padding: 22px 16px;
        margin-bottom: 20px;
    }

    .shop-hero-title {
        font-size: 1.75rem;
        letter-spacing: -0.5px;
    }

    #page-shop .hero-subtitle {
        font-size: 0.9rem;
        line-height: 1.5;
    }

    #page-shop .hero-badge {
        font-size: 0.66rem;
    }

    /* ---- Balance card: row, not a 250px-tall centred column ----
       The 480px rule stacked the orb above the numbers, which pushed the
       first actual product below the fold. Back to a compact row. */
    #page-shop .shop-hero-balance-pill {
        flex-direction: row;
        flex-wrap: nowrap;
        align-items: center;
        text-align: left;
        gap: 14px;
        padding: 14px 16px;
    }

    #page-shop .shop-hero-balance-data {
        align-items: flex-start;
        min-width: 0;
    }

    #page-shop .shop-hero-orb-visual {
        width: 44px;
        height: 44px;
    }

    /* the outer pulse ring reads as clutter at this size */
    #page-shop .shop-hero-orb-ring.ring-2 {
        display: none;
    }

    #page-shop .shop-hero-balance-num {
        font-size: 1.75rem;
    }

    #page-shop .shop-hero-balance-label {
        font-size: 0.62rem;
        letter-spacing: 1.2px;
    }

    #page-shop .shop-hero-balance-sub {
        font-size: 0.68rem;
        line-height: 1.35;
    }

    /* ---- Reward chips: real 48px touch targets ---- */
    .reward-chip {
        /* margin-top, min-height, padding and border-radius are all dead here: a
           SECOND `@media (max-width: 560px)` block at :18899 declares
           `.reward-chip` again with equal specificity and later source order, so
           it wins all four. The chip is 13px / 44px-min-height on mobile, not the
           14px / 48px this rule claims. Only `gap` still comes from here. */
        gap: 10px;
    }

    .reward-chip-icon {
        flex: 0 0 34px;
        width: 34px;
        height: 34px;
        font-size: 0.95rem;
    }

    .reward-chip-text {
        font-size: 0.88rem;
        min-width: 0;
    }

    .reward-chip-sub {
        font-size: 0.7rem;
    }

    /* ---- Tabs: three across 336px without a slider ---- */
    .shop-tabs-bar {
        width: 100%;
        gap: 3px;
        padding: 4px;
        margin-bottom: 18px;
    }

    .shop-tab {
        flex: 1 1 0;
        min-width: 0;
        min-height: 44px;
        justify-content: center;
        gap: 5px;
        padding: 9px 6px;
        font-size: 0.74rem;
        white-space: nowrap;
    }

    /* ---- Perk / orb-pack cards ---- */
    .shop-grid {
        grid-template-columns: 1fr;
        gap: 12px;
    }

    .shop-card-inner {
        padding: 20px 16px 16px;
        gap: 9px;
    }

    .shop-card-icon-wrap {
        width: 54px;
        height: 54px;
        font-size: 1.35rem;
    }

    .shop-card-title {
        font-size: 1.05rem;
    }

    .shop-card-desc {
        font-size: 0.8rem;
        line-height: 1.5;
        max-width: 34ch;
    }

    /* Footer stayed a row at 480px and squeezed the price against the
       button; the 480px rule stacked it but left the button full-width
       under a full-width pill. Price left, action right, both tappable. */
    #page-shop .shop-card-footer {
        flex-direction: row;
        align-items: stretch;
        gap: 8px;
        padding-top: 6px;
    }

    #page-shop .shop-card-cost {
        padding: 8px 12px;
        border-radius: var(--border-radius-sm);
    }

    #page-shop .shop-buy-btn {
        min-height: 44px;
        padding: 11px 14px;
        font-size: 0.82rem;
        border-radius: var(--border-radius-sm);
    }

    /* Badges shouldn't crowd the icon on a narrow card */
    .shop-card-popularity,
    .shop-card-tier-ribbon {
        top: 10px;
        right: 10px;
        padding: 4px 9px;
        font-size: 0.64rem;
    }

    /* ---- Cosmetics: 2-up, it's a preview browse grid ---- */
    #page-shop .shop-grid--cosmetics {
        grid-template-columns: 1fr 1fr;
        gap: 10px;
    }

    #page-shop .shop-grid--cosmetics .shop-card-inner {
        padding: 14px 11px 12px;
        gap: 7px;
    }

    .cosmetic-preview {
        min-height: 92px;
        margin-bottom: 5px;
        border-radius: var(--border-radius-sm);
    }

    .cosmetic-sample-avatar {
        width: 52px;
        height: 52px;
        font-size: 1.25rem;
    }

    #page-shop .shop-grid--cosmetics .shop-card-title {
        font-size: 0.9rem;
    }

    /* no room for the blurb in a half-width tile */
    #page-shop .shop-grid--cosmetics .shop-card-desc {
        display: none;
    }

    .cosmetic-action-btn {
        min-height: 42px;
        padding: 9px 10px;
        font-size: 0.76rem;
        gap: 5px;
    }

    /* price + label side by side won't fit — stack them tight */
    .cosmetic-buy-btn {
        flex-direction: column;
        justify-content: center;
        gap: 1px;
        line-height: 1.2;
    }

    .cosmetic-tier,
    .cosmetic-equipped-flag {
        top: 7px;
        right: 7px;
        padding: 2px 7px;
        font-size: 0.55rem;
    }

    .cosmetic-equipped-flag {
        left: 7px;
        right: auto;
    }

    .shop-cosmetics-heading {
        font-size: 0.95rem;
        margin: 20px 2px 12px;
    }

    .shop-cosmetics-intro {
        margin-bottom: 16px;
    }

    .shop-cosmetics-intro h3 {
        font-size: 1.15rem;
    }

    .shop-cosmetics-intro p {
        font-size: 0.86rem;
    }
}

/* Narrow phones: drop the tab icons so the three labels keep their room */
@media (max-width: 400px) {
    .shop-tab {
        font-size: 0.72rem;
        padding: 9px 4px;
    }

    .shop-tab i {
        display: none;
    }

    #page-shop .shop-hero-balance-num {
        font-size: 1.6rem;
    }

    #page-shop .shop-grid--cosmetics {
        gap: 8px;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   SIDEBAR DRAWER — MOBILE PASS
   The drawer inherits desktop spacing (30px header margin, 32px nav
   margin, 8px item gaps) which pushes Recent Chats and the account
   row toward the fold. Also strips hover-only effects that stick
   after a tap on touch, and the always-on GPU work.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* 320px over a ~390px screen left a ~70px backdrop sliver — hard to
       hit and it reads as a rendering glitch rather than a way out. */
    .sidebar {
        width: min(82vw, 300px);
        max-width: 82vw;
        /* drawer sits on an opaque backdrop; the blur is pure GPU cost */
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* ---- Reclaim vertical space so Recent Chats gets real room ---- */
    .sidebar-header {
        margin-bottom: 18px;
    }

    .sidebar-nav {
        gap: 4px;
        margin-bottom: 18px;
    }

    .nav-item {
        padding: 11px 14px;
        gap: 12px;
        /* staggered entrance replays on every drawer open, which reads as
           lag when you're just trying to tap through */
        animation: none;
    }

    .recent-chats h3 {
        margin-bottom: 10px;
    }

    .sidebar-upgrade-container {
        margin-top: 10px;
        margin-bottom: 10px;
        padding-top: 12px;
    }

    .sidebar-upgrade-btn {
        padding: 12px 14px;
        font-size: 0.86rem;
        gap: 10px;
    }

    .sidebar-account-shell {
        margin-top: 10px;
    }

    .sidebar-account-toggle {
        padding: 10px;
        gap: 10px;
    }

    .account-avatar {
        width: 38px;
        height: 38px;
    }

    /* ---- Touch: hover styles latch after a tap on mobile browsers ---- */
    .nav-item:hover:not(.active) {
        background: transparent;
        color: var(--text-secondary);
        transform: none;
    }

    .nav-item:hover:not(.active)::before {
        height: 0;
    }

    .nav-item:hover:not(.active) i {
        transform: none;
        color: inherit;
    }

    .nav-item:active:not(.active) {
        background: rgba(255, 255, 255, 0.05);
    }

    .sidebar-account-toggle:hover,
    .sidebar-chat-item:hover {
        transform: none;
    }

    .sidebar-upgrade-btn:hover {
        transform: none;
    }

    .super-create-btn:hover {
        transform: none;
    }

    /* ---- Always-on animation the phone pays for either way ---- */
    .account-avatar::before {
        animation: none;
    }

    .super-create-btn {
        animation: none;
    }

    .sidebar-chat-item {
        animation: none;
    }

    /* The account menu opens upward inside a scrollable drawer, so a long
       menu could land above the scroll origin and be unreachable. Anchor
       it to the toggle and let it size to content. */
    .sidebar-account-menu {
        max-height: 60vh;
        overflow-y: auto;
    }

    .account-menu-item {
        min-height: 44px;
    }
}

/* Short viewports (landscape phones): the drawer must stay scrollable
   rather than clipping the account row off the bottom. */
@media (max-width: 768px) and (max-height: 560px) {
    .sidebar-header {
        margin-bottom: 12px;
    }

    .sidebar-nav {
        margin-bottom: 12px;
    }

    /* below this height Recent Chats can't earn its space */
    .recent-chats {
        flex-grow: 0;
    }
}

/* ============================================================
   CHARACTER PROFILE + RP CHAT — MOBILE PASS
   Appended last so these win on source order over the earlier
   .char-profile 768px block (8043) and the chat blocks at
   5547 / 6110 / 6175 / 12687 without editing them in place.
   ============================================================ */

@media (max-width: 768px) {

    /* ---- Root: kill sideways overflow ----
       Any child wider than the viewport makes mobile Chrome widen the layout
       viewport, which drags the absolutely-positioned Back button off the left
       edge and clips the stats row on both sides. Contain it here. */
    #page-character-profile {
        overflow-x: clip;
    }

    .char-profile-wrapper,
    .char-profile-body,
    .char-profile-hero {
        max-width: 100%;
        min-width: 0;
    }

    /* ---- Hero ---- */
    .char-profile-hero {
        min-height: 0;
        /* content decides the height instead of a 360px floor */
        padding: 64px 16px 28px;
        /* top padding reserves the row the Back button floats in */
    }

    .char-profile-hero-bg {
        /* 28px blur over a full-bleed image is the most expensive paint on
           this page; halve it and drop the upscale */
        filter: blur(16px) brightness(0.45) saturate(1.3);
        transform: scale(1.08);
    }

    .char-profile-particles {
        display: none;
    }

    .char-profile-hero-content {
        gap: 10px;
        width: 100%;
    }

    .char-profile-avatar-ring {
        width: 96px;
        height: 96px;
        min-height: 96px;
        animation: profileFadeUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
        /* drops the 4s infinite gradient+shadow loop */
    }

    .char-profile-name {
        font-size: clamp(1.55rem, 7vw, 2rem);
        max-width: 100%;
        overflow-wrap: break-word;
    }

    .char-profile-tagline {
        font-size: 0.88rem;
        line-height: 1.45;
        max-width: 100%;
    }

    .char-profile-meta {
        gap: 14px;
        flex-wrap: wrap;
        justify-content: center;
    }

    /* ---- Back to Hub button ----
       Was absolute at left: 20px with a 10px override; pin it inside the
       clipped hero and shrink the pill so the label always fits. */
    #page-character-profile .settings-back-btn {
        top: 14px;
        left: 14px;
        padding: 6px 13px 6px 7px;
        font-size: 0.7rem;
        gap: 6px;
        letter-spacing: 0.3px;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: rgba(16, 12, 24, 0.72);
        max-width: calc(100% - 28px);
    }

    #page-character-profile .settings-back-btn .back-arrow-icon {
        width: 20px;
        height: 20px;
        font-size: 0.62rem;
    }

    #page-character-profile .settings-back-btn .back-label {
        letter-spacing: 0.5px;
        white-space: nowrap;
    }

    #page-character-profile .settings-back-btn:hover {
        transform: none;
        box-shadow: none;
        background: rgba(16, 12, 24, 0.72);
        border-color: rgba(255, 255, 255, 0.12);
        color: rgba(255, 255, 255, 0.75);
    }

    #page-character-profile .settings-back-btn:hover .back-arrow-icon {
        transform: none;
        background: rgba(167, 119, 227, 0.25);
    }

    #page-character-profile .settings-back-btn:active {
        background: rgba(167, 119, 227, 0.24);
    }

    /* ---- Body ---- */
    .char-profile-body {
        padding: 4px 14px 36px;
        gap: 14px;
    }

    /* ---- Stats row: 3-up compact, no horizontal scroll ----
       The flex row put a 44px icon beside a 1.25rem number in each of three
       cards, which needs ~330px of content + 32px of gaps and overflowed.
       Stack icon over value/label so all three fit any phone width. */
    .char-profile-stats-row {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 8px;
        width: 100%;
        min-width: 0;
    }

    .profile-stat-card {
        flex-direction: column;
        align-items: center;
        justify-content: flex-start;
        text-align: center;
        gap: 7px;
        padding: 12px 6px;
        min-width: 0;
    }

    .profile-stat-card:hover {
        background: rgba(255, 255, 255, 0.02);
        border-color: rgba(255, 255, 255, 0.06);
        transform: none;
    }

    .profile-stat-icon {
        width: 34px;
        height: 34px;
        font-size: 0.9rem;
    }

    .profile-stat-info {
        align-items: center;
        min-width: 0;
        width: 100%;
    }

    .profile-stat-value {
        font-size: 1.02rem;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 100%;
    }

    .profile-stat-label {
        font-size: 0.6rem;
        letter-spacing: 0.3px;
    }

    /* ---- Sections ---- */
    .char-profile-section {
        padding: 16px 15px;
    }

    .char-profile-section:hover {
        border-color: rgba(255, 255, 255, 0.06);
        box-shadow: none;
    }

    .char-profile-section-title {
        font-size: 0.82rem;
        margin-bottom: 12px;
        padding-bottom: 10px;
        gap: 8px;
    }

    .char-profile-short-lore {
        font-size: 0.88rem;
        line-height: 1.65;
    }

    .char-profile-backstory {
        font-size: 0.86rem;
        line-height: 1.7;
    }

    .char-profile-traits-grid {
        grid-template-columns: 1fr 1fr;
        gap: 8px;
    }

    .profile-trait-chip {
        padding: 10px 12px;
        gap: 9px;
        min-width: 0;
    }

    .profile-trait-chip:hover {
        transform: none;
        background: rgba(255, 255, 255, 0.03);
        border-color: rgba(255, 255, 255, 0.05);
    }

    /* ---- CTAs: real tap targets, no animated gradient ---- */
    .char-profile-edit-btn {
        padding: 13px;
        min-height: 46px;
        font-size: 0.86rem;
    }

    .char-profile-edit-btn:hover {
        background: rgba(255, 255, 255, 0.03);
        border-color: rgba(255, 255, 255, 0.1);
        color: var(--text-secondary);
    }

    .char-profile-edit-btn:active {
        background: rgba(167, 119, 227, 0.14);
    }

    .char-profile-start-btn {
        padding: 15px;
        min-height: 52px;
        font-size: 1rem;
        animation: none;
        background: linear-gradient(135deg, #a777e3, #6e8efb);
    }

    .char-profile-start-btn::before {
        display: none;
        /* removes the 4s infinite shine sweep */
    }

    .char-profile-start-btn:hover {
        transform: none;
    }

    .char-profile-start-btn:active {
        transform: scale(0.985);
    }

    /* ============================================================
       RP CHAT
       ============================================================ */

    /* ---- Header ----
       The 12687 block already wraps the header and drops the model selector to
       its own row. Keep that, but let the selector share the top row when
       there's room so the chat doesn't lose 44px of message space. */
    .chat-header {
        gap: 8px;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: rgba(9, 9, 11, 0.94);
    }

    #chat-model-selector {
        flex: 0 1 auto;
        width: auto !important;
        max-width: 34vw;
        min-width: 88px;
        height: 34px !important;
        font-size: 0.74rem !important;
        padding: 4px 8px !important;
        margin-right: 6px !important;
        text-overflow: ellipsis;
    }

    .chat-character-info .details .name {
        max-width: 38vw;
    }

    .chat-status .status-text {
        max-width: 38vw;
        font-size: 0.68rem;
    }

    .chat-character-info img:hover {
        transform: none;
        box-shadow: 0 0 0 3px rgba(167, 119, 227, 0.15), 0 4px 14px rgba(0, 0, 0, 0.4);
    }

    #back-to-discover.glass-back-btn {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        min-height: var(--tap-min);
    }

    #back-to-discover.glass-back-btn:hover {
        transform: none;
        box-shadow: none;
        background: rgba(20, 15, 25, 0.6);
        border-color: rgba(255, 255, 255, 0.05);
        color: var(--text-secondary);
    }

    #back-to-discover.glass-back-btn:hover i {
        transform: none;
        color: inherit;
    }

    #back-to-discover.glass-back-btn:active {
        background: rgba(167, 119, 227, 0.18);
    }

    /* ---- Messages ---- */
    .chat-messages {
        padding: 12px 12px 4px;
        gap: 12px;
    }

    .message-wrapper {
        max-width: 92%;
        gap: 8px;
    }

    /* Per-bubble backdrop-filter means one extra blur layer per message; on a
       long RP log that's the main scroll-jank source. */
    .message-content {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: rgba(26, 22, 34, 0.92);
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
        min-width: 0;
        overflow-wrap: break-word;
    }

    .message-wrapper.user .message-content {
        background: var(--accent-gradient);
        box-shadow: 0 2px 10px rgba(167, 119, 227, 0.25);
    }

    .message-wrapper.bot .message-content {
        max-width: 100%;
    }

    /* Narration is the bulk of an RP reply — give it a touch more contrast at
       small sizes so it doesn't grey out against the bubble. */
    .message-wrapper.bot .markdown-body em {
        color: #c2addf;
    }

    /* ---- Macro row: wrap instead of scroll ----
       Only two controls live here, so a scroll container was never needed. */
    .rp-macros {
        flex-wrap: wrap;
        overflow-x: visible;
        row-gap: 6px;
        margin-bottom: 6px;
        padding: 0 2px;
    }

    .macro-btn {
        min-height: 38px;
        padding: 7px 13px;
    }

    .macro-btn:hover {
        transform: none;
        background: rgba(255, 255, 255, 0.05);
        color: var(--text-secondary);
        border-color: rgba(255, 255, 255, 0.1);
    }

    .macro-btn:active {
        background: rgba(167, 119, 227, 0.18);
        color: #e2b3ff;
    }

    .action-main-btn {
        min-height: 38px;
    }

    .action-toggle-btn {
        min-height: 38px;
        min-width: 34px;
    }

    /* ---- Input ---- */
    .chat-input-container {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: rgba(30, 30, 35, 0.96);
        padding: 5px 5px 5px 14px;
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
    }

    .chat-input-container .icon-btn,
    #send-btn {
        width: 40px;
        height: 40px;
        flex-shrink: 0;
    }

    #send-btn:hover {
        transform: none;
        box-shadow: 0 4px 14px rgba(167, 119, 227, 0.35);
    }

    #send-btn:active {
        transform: scale(0.94);
    }

    /* Quick-action sheet: cap the height so the emoji grid can't run past the
       top of the screen on short phones. */
    .quick-action-hover-menu {
        max-height: 62vh;
        overflow-y: auto;
        overscroll-behavior: contain;
    }
}

/* ---- Narrow phones (< 400px) ---- */
@media (max-width: 400px) {
    .char-profile-hero {
        padding: 60px 12px 24px;
    }

    .char-profile-body {
        padding: 4px 12px 32px;
    }

    .profile-stat-card {
        padding: 10px 4px;
        gap: 6px;
    }

    .profile-stat-icon {
        width: 30px;
        height: 30px;
        font-size: 0.8rem;
    }

    .profile-stat-value {
        font-size: 0.94rem;
    }

    .profile-stat-label {
        font-size: 0.55rem;
    }

    .char-profile-traits-grid {
        grid-template-columns: 1fr;
    }

    /* At this width the selector plus two icon buttons no longer fit beside
       the avatar — give it back its own row rather than squeezing the name. */
    #chat-model-selector {
        max-width: 100%;
        flex: 1 1 100%;
        margin-right: 0 !important;
    }

    .message-wrapper {
        max-width: 94%;
        gap: 6px;
    }

    .message-avatar,
    .message-avatar img,
    .message-avatar .avatar {
        width: 32px;
        height: 32px;
    }

    .message-avatar .avatar {
        font-size: 0.95rem;
    }

    .message-content {
        font-size: 0.82rem;
    }

    .chat-character-info .details .name {
        max-width: 34vw;
    }
}

/* ============================================================
   UPGRADE TO LOOM+ MODAL — MOBILE PASS
   Appended last so it wins over the 840px / 560px blocks at
   8786 / 8819 and the banner re-declaration at 8966 / 8978.
   ============================================================ */

@media (max-width: 840px) {

    /* ---- Order: price before the comparison table ----
       Single-column stacking put an 8-row feature table above the plans, so
       the price and Continue button both landed below the fold — you had to
       scroll past every feature to find out what it costs. Flip them: decide
       first, justify second.

       This still holds from 561px to 840px, where the CTA is in normal flow.
       Phones reverse it in the 560px block below, but only because the dock
       pins the CTA there — the fold argument above is answered by the pinning,
       not by the ordering. Do not widen that reversal to this query without
       widening the dock with it. */
    .ll-upgrade-plans-col {
        order: 1;
    }

    .ll-upgrade-features-col {
        order: 2;
    }

    .ll-upgrade-body {
        padding-top: 18px;
    }
}

@media (max-width: 560px) {

    /* ---- Overlay + shell ----
       100vh is the *large* viewport height on mobile, so a centred modal at
       max-height: 100vh overflows behind the URL bar and clips its own top
       edge (the "LoreLoom." wordmark). dvh tracks the visible height; top
       alignment keeps the banner anchored while the bar shows/hides. */
    .ll-upgrade-overlay {
        align-items: flex-start;
        justify-content: flex-start;
        background: rgba(0, 0, 0, 0.9);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        /* full-bleed modal covers it anyway — the blur was pure GPU cost */
    }

    .ll-upgrade-modal {
        height: 100vh;
        height: 100dvh;
        max-height: 100vh;
        max-height: 100dvh;
        /* no scale on mobile — scaling a full-bleed sheet shows the backdrop */

        /* The 14px entrance slide moves via `top`, NOT `transform`, and the base rule's
           transform is explicitly cancelled — including in the .ll-active state below,
           whose `scale(1) translateY(0)` is an identity transform but still counts.

           Reason: this element is the scroll container, and any transform here makes it
           the containing block for the fixed pay dock further down. A fixed box anchored
           to a transformed *scroller* behaves like an absolute one and scrolls away with
           the content — measured at 268px of drift on a 320px-wide screen, i.e. the dock
           left the screen entirely. With no transform in the chain (the overlay's
           backdrop-filter is also off at this width) the dock anchors to the viewport
           and stays put.

           `top` on an already-relative box paints the identical 14px offset, creates no
           containing block, and costs no layout — unlike a margin, which would push the
           100dvh sheet past the overlay and make it briefly scrollable mid-animation. */
        transform: none;
        top: 14px;
        transition: top 0.4s cubic-bezier(0.165, 0.84, 0.44, 1), opacity 0.35s ease;
        overscroll-behavior: contain;
        padding-bottom: var(--safe-bottom);
    }

    .ll-upgrade-overlay.ll-active .ll-upgrade-modal {
        transform: none;
        top: 0;
    }

    /* ---- Banner ----
       The fade is a left-to-right gradient, so at phone widths the heading's
       right half ("Loom+", and the subtitle's tail) sat on the bright part of
       the castle and lost contrast. Go vertical instead: dark at the text,
       clear at the top. */
    .ll-upgrade-banner {
        min-height: 0;
        padding-top: 4px;
    }

    .ll-upgrade-banner-fade {
        background: linear-gradient(180deg,
                rgba(21, 18, 30, 0.42) 0%,
                rgba(21, 18, 30, 0.66) 42%,
                rgba(21, 18, 30, 0.88) 74%,
                rgba(21, 18, 30, 0.97) 100%);
    }

    .ll-upgrade-banner::after {
        height: 60%;
    }

    .ll-upgrade-banner-content {
        /* Later duplicate of the ~9372 rule, so it wins and must carry the
           status-bar inset itself — otherwise the heading rides up under the
           notch on the full-bleed sheet. */
        padding: calc(16px + var(--safe-top)) 16px 14px;
        /* keep the heading clear of the close button */
        padding-right: 62px;
    }

    .ll-upgrade-logo {
        font-size: 1.12rem;
        height: auto;
        margin-bottom: 6px;
    }

    .ll-upgrade-heading {
        font-size: clamp(1.5rem, 7.2vw, 1.9rem);
        letter-spacing: -0.6px;
        margin-bottom: 5px;
    }

    .ll-upgrade-subtitle {
        font-size: 0.82rem;
        line-height: 1.4;
        color: rgba(255, 255, 255, 0.55);
        /* lifted from 0.45 — over artwork the lighter grey read as noise */
    }

    .ll-upgrade-close {
        /* This is the SECOND @media (max-width:560px) block for this selector;
           being later it wins on source order over the inset-aware rule ~9278.
           So the status-bar inset must be re-applied HERE or it is silently
           lost — without it 34 of these 42px sit under the notch, leaving an
           8px sliver as the only touch target. --safe-top is 0 in a plain
           browser tab, so desktop keeps the flat 14px. */
        top: calc(14px + var(--safe-top));
        right: 14px;
        width: 42px;
        height: 42px;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: rgba(18, 15, 26, 0.82);
    }

    .ll-upgrade-close:hover {
        transform: none;
        background: rgba(18, 15, 26, 0.82);
        border-color: rgba(255, 255, 255, 0.1);
        color: rgba(255, 255, 255, 0.6);
    }

    .ll-upgrade-close:active {
        background: rgba(167, 119, 227, 0.24);
        color: #fff;
    }

    /* ---- Body ---- */
    .ll-upgrade-body {
        /* Bottom padding clears the pinned dock so the last plan card can scroll fully
           clear of it. --ll-dock-h is measured and set by upgrade-modal.js; the 122px
           fallback covers the window before that runs. Folded into the shorthand
           deliberately: two later rules re-declare `padding` on this selector at these
           widths, so a separate padding-bottom here would be dead code. */
        padding: 14px 16px calc(26px + var(--ll-dock-h, 122px));
        gap: 16px;
    }

    /* ---- Plan cards ---- */
    .ll-upgrade-plans-col {
        gap: 12px;
    }


    .ll-plan-card:hover {
        border-color: rgba(255, 255, 255, 0.08);
        background: rgba(255, 255, 255, 0.06);
    }

    .ll-plan-card.ll-plan-card--active {
        animation: none;
        /* 3s alternating box-shadow loop, always on, behind a full-bleed sheet */
        box-shadow: 0 0 22px rgba(147, 51, 234, 0.22), inset 0 0 0 999px #15121e;
    }

    .ll-plan-card-inner {
        padding: 15px 16px;
        gap: 12px;
    }

    .ll-plan-info {
        min-width: 0;
    }

    .ll-plan-label {
        font-size: 0.92rem;
    }

    /* The price is what the sheet is asking about, so on a phone it is the
       largest thing on it after the heading. */
    .ll-plan-price {
        font-size: 1.75rem;
    }

    .ll-plan-period {
        font-size: 0.84rem;
    }

    .ll-plan-original-price {
        font-size: 0.8rem;
    }

    .ll-plan-save {
        font-size: 0.74rem;
    }

    .ll-plan-radio {
        width: 24px;
        height: 24px;
    }

    .ll-plan-radio-circle {
        width: 12px;
        height: 12px;
    }

    .ll-plan-badge {
        top: -9px;
        right: 14px;
        font-size: 0.64rem;
        padding: 4px 11px;
        letter-spacing: 0.5px;
    }

    /* ---- Continue, then the disclosure ----
       Reordered rather than reworded: the renewal terms sat between the last
       price and the button, so the phone read "₹7,499 … auto renews … Continue"
       and the CTA lost its adjacency to the thing it commits to. Below the
       button it still precedes the charge and now doubles as reassurance,
       which is where every subscription sheet puts it. `order` only applies
       inside the flex column, so the desktop grid is untouched. */
    .ll-upgrade-continue-btn {
        order: 1;
        padding: 15px 20px;
        font-size: 0.98rem;
        min-height: 52px;
        margin-top: 0;
        animation: none;
        background: linear-gradient(135deg, #9333ea 0%, #7c3aed 50%, #a855f7 100%);
        box-shadow: 0 6px 22px rgba(147, 51, 234, 0.32);
    }

    .ll-renewal-notice {
        order: 2;
        justify-content: center;
        font-size: 0.74rem;
        padding: 0;
    }

    /* ---- Order: value before price (phones only) ----
       Sell first, price second: the "Everything in Free, plus:" list leads, then the
       plan cards. This reverses the 840px block above, which leads with the price to
       keep the CTA above the fold. That trade-off is answered here by the dock below,
       which pins the CTA at every scroll position — so the fold no longer has to
       dictate the reading order. The reversal is scoped to this query for exactly that
       reason: it is only safe where the dock is. */
    .ll-upgrade-features-col {
        order: 1;
    }

    .ll-upgrade-plans-col {
        order: 2;
    }

    /* ---- Pinned pay dock ----
       The feature list now leads, which puts the CTA at the end of 929px of content in
       a 913px sheet here — and far below the fold on a 640px-tall phone. So the
       notice+CTA pair leaves the flow and pins to the bottom of the screen instead of
       letting the fold dictate the reading order.

       This anchors to the viewport, and that is load-bearing rather than incidental: it
       only works because nothing between here and the viewport creates a containing
       block for fixed positioning. Two things had to be true for that, and both are
       one property away from breaking —

         * .ll-upgrade-modal must stay `transform: none` at this width (see the note on
           it above). It is the scroll container, so a transform there re-parents this
           dock to it and the dock scrolls off with the content.
         * .ll-upgrade-overlay must keep `backdrop-filter: none` at this width. It is not
           the scroller, so that one only shifts the anchor from the screen to the
           overlay box — harmless today, but it would silently change what `bottom: 0`
           and the safe-area padding below are measured from.

       Since `bottom: 0` is the true screen edge, the dock carries the safe-area inset
       itself; the modal's own padding-bottom does not apply out here. */
    .ll-upgrade-cta-dock {
        display: flex;
        flex-direction: column;
        gap: 7px;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 6;
        padding: 11px 16px calc(11px + var(--safe-bottom, 0px));
        background: rgba(14, 11, 26, 0.94);
        backdrop-filter: blur(16px) saturate(150%);
        -webkit-backdrop-filter: blur(16px) saturate(150%);
        border-top: 1px solid rgba(168, 85, 247, 0.18);
        box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.5);
    }

    /* The price rides in the button so the commitment is legible at the moment of the
       tap, without the plan cards having to be on screen. Text comes from the selected
       card's rendered price, so it follows the currency the pricing endpoint returned. */
    .ll-continue-price {
        display: inline;
        opacity: 0.92;
        font-weight: 600;
    }

    .ll-continue-price::before {
        content: " · ";
        opacity: 0.55;
    }

    .ll-continue-glow {
        display: none;
        /* 3.5s infinite sweep */
    }

    .ll-upgrade-continue-btn:hover {
        transform: none;
        box-shadow: 0 6px 22px rgba(147, 51, 234, 0.32);
    }

    .ll-upgrade-continue-btn:active {
        transform: scale(0.985);
    }

    /* ---- Feature table: collapse to a single-column Plus list ----
       The old 3-column comparison pushed 8 rows onto a 360px sheet where
       every row bar one is Plus-only — the Free column did nothing but take
       width. The reference sheet instead opens the list with "Everything in
       Free, plus:" and shows just the additions, each with a leading check.
       Do the same: drop the Free/Loom+ columns and the one row Free also
       gets (Basic chat models), put the Plus check in front as the marker.
       The per-feature icons go too — a check plus a crown/gift/star before
       the same word read as noise at this width.

       This also retires the `.ll-plan-name-full` / `--short` swap, which
       existed only in this block to fit "LoreLoom+" into a 58px column head —
       a column head that is now hidden. Above 560px the head is back to its
       full 90px, where the long label always fitted, so `.ll-plan-name-short`
       is now unrendered at every width. Both spans stay in the markup because
       the short one is display:none by default: nothing changes on the
       website, and a future narrow comparison has its hook already. */
    .ll-upgrade-table {
        padding: 14px 16px;
        border-radius: var(--border-radius-md);
    }

    .ll-upgrade-table-header {
        grid-template-columns: 1fr;
        padding-bottom: 8px;
    }

    /* the Free / Loom+ column heads have no place in a single column */
    .ll-upgrade-table-header .ll-table-plan {
        display: none;
    }

    .ll-table-label-full {
        display: none;
    }

    .ll-table-label {
        font-size: 0.88rem;
    }

    .ll-table-label-short {
        display: inline;
        font-size: 0.95rem;
    }

    .ll-upgrade-table-row {
        display: flex;
        align-items: center;
        gap: 11px;
        padding: 12px 0;
    }

    /* The one row Free gets too is not a Plus addition, so it leaves the list.
       This MUST come after the `display: flex` above: both selectors are a
       single class, so specificity ties and source order decides. Placed
       before it, the row stayed visible — measured, not assumed. */
    .ll-upgrade-table-row--in-free {
        display: none;
    }

    /* Touch has no hover, and the negative margins made rows twitch on tap */
    .ll-upgrade-table-row:hover {
        background: transparent;
        margin: 0;
        padding-left: 0;
        padding-right: 0;
    }

    /* the two comparison columns vanish; only the Plus check survives */
    .ll-upgrade-table-row .ll-table-check.free,
    .ll-upgrade-table-row .ll-table-dash {
        display: none;
    }

    /* the Plus check moves in front of the text as the list marker */
    .ll-upgrade-table-row .ll-table-check.plus {
        order: -1;
        display: flex;
        align-items: center;
        flex-shrink: 0;
    }

    .ll-table-feature {
        font-size: 0.9rem;
        gap: 0;
        min-width: 0;
        line-height: 1.3;
    }

    /* Two classes on purpose: Font Awesome's .fa-solid also sets display and
       loads after styles.css, so a bare .ll-feature-icon loses the tie. */
    .ll-table-feature .ll-feature-icon {
        display: none;
    }
}

/* ---- Narrow phones (< 400px) ---- */
@media (max-width: 400px) {
    .ll-upgrade-banner-content {
        padding: 18px 14px 14px;
        padding-right: 58px;
    }

    .ll-upgrade-body {
        /* Same dock allowance as the 560px block — this rule re-declares the shorthand
           and would otherwise drop it at narrow widths. */
        padding: 12px 14px calc(24px + var(--ll-dock-h, 122px));
    }

    .ll-plan-price {
        font-size: 1.6rem;
    }

    .ll-plan-card-inner {
        padding: 14px 14px;
    }

    .ll-upgrade-table {
        padding: 12px 14px;
    }

    /* The 3-column grid this block used to narrow is gone at 560px (the list
       collapse above); all that is left to trim is the row rhythm. */
    .ll-upgrade-table-row {
        padding: 11px 0;
        gap: 10px;
    }

    .ll-table-feature {
        font-size: 0.86rem;
    }
}

/* Short viewports: the sheet is already full-height, so trim the banner
   rather than letting it eat the space the plan cards need. */
@media (max-width: 560px) and (max-height: 700px) {
    .ll-upgrade-banner-content {
        padding-top: 16px;
        padding-bottom: 14px;
    }

    .ll-upgrade-logo {
        margin-bottom: 6px;
    }

    .ll-upgrade-heading {
        font-size: clamp(1.35rem, 6.4vw, 1.65rem);
    }

    .ll-upgrade-subtitle {
        font-size: 0.78rem;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   MOBILE DENSITY PASS — page heroes, chrome and the discover grid
   Appended last on purpose: the discover hero lives at ~2268/~13330,
   the shop-family hero at ~9466/~16975, and several later blocks
   re-declare the same selectors. Source order is what makes these
   win. Where the rule being overridden is ID-prefixed, the override
   repeats the ID (#page-shop …) instead of reaching for !important.

   What this fixes: on a 360px viewport every page opened with a hero
   that filled the entire first screen — eyebrow badge + display-size
   heading + 3-line paragraph + CTA + a stats/balance card — so the
   content the page actually exists for (souls grid, shop tabs) began
   below the fold everywhere. Phones get the same hero built for a
   360px column, with the decorative layers (uppercase badge, 70px orb
   blurs, stacked backdrop-filters) dropped rather than merely shrunk.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* ---- Chrome above the fold ----
       Search is Discover-only, so on every other page the top strip was
       spending ~70px of the first screen to present one 40px hamburger. */
    .top-nav {
        padding: 10px 14px 8px;
    }

    .main-content:not(:has(#page-discover.active)) .top-nav {
        padding: 8px 12px 2px;
    }

    /* ---- Discover hero ---- */
    .premium-hero {
        padding: 18px 16px;
        border-radius: var(--border-radius-md);
        margin-bottom: 16px;
        gap: 14px;
        /* A 20px backdrop-filter sitting under two blurred 300px orbs is
           the most expensive paint on the page, for an effect no one can
           make out at this width. */
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        box-shadow: 0 10px 26px rgba(0, 0, 0, 0.3);
    }

    .hero-orb {
        filter: blur(40px);
        opacity: 0.3;
        animation: none;
    }

    .hero-content {
        max-width: none;
        width: 100%;
    }

    .welcome-back-header {
        font-size: clamp(1.5rem, 6.6vw, 1.95rem);
        line-height: 1.15;
        padding-top: 0;
        margin-bottom: 6px;
        letter-spacing: -0.6px;
    }

    .premium-hero .hero-subtitle {
        font-size: 0.86rem;
        line-height: 1.45;
        max-width: none;
        margin-bottom: 14px;
        /* Two lines is the whole paragraph on most phones — the third was
           always a stray word or two carried over. */
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    .hero-cta-row {
        gap: 10px;
    }

    .hero-cta-primary {
        width: 100%;
        justify-content: center;
        min-height: 46px;
        padding: 12px 18px;
        font-size: 0.92rem;
    }

    /* Stats read as a strip instead of a 120px-tall card.
       auto-fit with a REAL minimum, not `1fr 1fr` and not `minmax(0, 1fr)`:
         - `1fr 1fr` was the old value, and the hero carries one stat since the
           infinity glyph was removed — a fixed second track left the surviving
           number pinned to the left half with a dead column beside it, which
           reads as a stat that failed to load.
         - `minmax(0, 1fr)` fixes the look and is still wrong: a zero minimum
           means auto-fit can fit unlimited tracks, so at 390px it generated 35
           of them (measured) — the first 314px wide, the other 34 at 0px. A
           second stat added later would land in a 0px track and vanish.
       120px is wide enough to hold "Souls in the Loom" at the mobile label size,
       so it bounds the track count at 1 here and 2 once a sibling returns. */
    .premium-hero .hero-stats {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
        gap: 8px;
        width: 100%;
        padding: 10px;
        border-radius: var(--border-radius-md);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .premium-hero .stat-item {
        align-items: center;
        text-align: center;
        gap: 2px;
        min-width: 0;
    }

    .premium-hero .stat-value {
        font-size: 1.15rem;
    }

    .premium-hero .stat-label {
        font-size: 0.58rem;
        letter-spacing: 0.6px;
        line-height: 1.25;
    }

    /* ---- Section header + the gap above it ----
       (was an inline margin-top: 32px in the markup, now .section-header-gap
       so a media query can reach it at all) ---- */
    .premium-section-header {
        margin-bottom: 12px;
        padding-bottom: 8px;
        gap: 8px 14px;
    }

    .section-header-gap {
        margin-top: 16px;
    }

    .premium-section-header h2 {
        font-size: 1.18rem;
        gap: 9px;
    }

    .premium-section-header h2 i {
        font-size: 1rem;
    }

    /* ---- Results banner ("Trending this week · 215 found") ---- */
    .results-header {
        padding: 9px 12px;
        margin: 0 0 10px;
        font-size: 0.84rem;
        gap: 8px;
        border-radius: var(--border-radius-sm);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .results-header i {
        font-size: 0.82rem;
    }

    /* Long queries ellipsis rather than wrapping the banner to three lines. */
    .results-header>span:not(.results-count) {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .results-header .results-count {
        font-size: 0.68rem;
        padding: 2px 8px;
        flex-shrink: 0;
    }

    /* ---- Owner actions: chrome over the art, not a slab on the face ----
       @media (hover: none) pins these permanently visible, which at 30px +
       padding covered ~45% of a 159px card's width. */
    .card-owner-actions {
        top: 6px;
        left: 6px;
        gap: 4px;
        padding: 3px;
        background: rgba(8, 5, 14, 0.68);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
    }

    .card-owner-btn {
        width: 26px;
        height: 26px;
        font-size: 0.66rem;
    }

    /* One backdrop-filter per card, ~24 cards per page, for a blur behind a
       10px badge. Opaque fill keeps the count legible without the cost. */
    .card-stat-badge {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: rgba(26, 14, 44, 0.78);
    }
}

@media (max-width: 560px) {

    /* The uppercase eyebrow badge is decoration that costs ~34px at the very
       top of every page. Dropped on phones, kept from 561px up. */
    .premium-hero .hero-badge,
    .shop-hero-section .hero-badge {
        display: none;
    }

    /* ---- Shop / Collection / Achievements / Quests share this hero ---- */
    .shop-hero-section {
        padding: 16px 14px;
        margin-bottom: 14px;
    }

    .shop-hero-content-wrap {
        gap: 12px;
    }

    .shop-hero-title {
        font-size: 1.5rem;
        letter-spacing: -0.4px;
        margin-bottom: 4px;
    }

    #page-shop .hero-subtitle,
    .shop-hero-subtitle {
        font-size: 0.82rem;
        line-height: 1.45;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    #page-shop .shop-hero-balance-pill {
        padding: 12px 14px;
        gap: 12px;
        border-radius: var(--border-radius-md);
    }

    #page-shop .shop-hero-orb-visual {
        width: 40px;
        height: 40px;
    }

    #page-shop .shop-hero-balance-num {
        font-size: 1.5rem;
    }

    /* "Earn more through daily logins & activity" duplicates the Earn Orbs
       chip sitting directly beneath it. */
    #page-shop .shop-hero-balance-sub {
        display: none;
    }

    .reward-chip {
        margin-top: 8px;
        min-height: 44px;
        padding: 9px 12px;
        border-radius: var(--border-radius-sm);
    }

    .reward-chip-icon {
        flex: 0 0 30px;
        width: 30px;
        height: 30px;
        font-size: 0.85rem;
    }

    .reward-chip-text {
        font-size: 0.84rem;
    }

    .reward-chip-sub {
        font-size: 0.66rem;
    }

    /* ---- Chip rails: more chips per swipe ---- */
    .filter-chip {
        padding: 7px 13px;
        font-size: 0.8rem;
        gap: 6px;
    }

    .filter-chip i {
        font-size: 0.72rem;
    }

    /* ---- Discover cards ----
       Three tries at the title bar, so the reasoning is worth keeping:

       1. Name + tagline on one nowrap line made a 155px bar slide the whole
          string, name included, which reads as a rendering bug.
       2. Hiding the tagline fixed the motion but read as missing content —
          it is the character's pitch, and the name alone says nothing.
       3. Wrapping it to two clamped lines showed everything but turned the
          header into a paragraph competing with the snippet under the image.

       So: the tagline gets ONE line of its own under the name. The name never
       moves, the bar costs ~16px instead of ~32px, and when the tagline is too
       long for the card the marquee block in renderCharactersHome() slides it
       to reveal the tail — the PC hover reveal, running by itself because
       touch has no hover.

       The !important is load-bearing, not defensive: the card template sets
       style="white-space:nowrap" inline on .card-title-text, and no media
       query can out-rank an attribute. */
    .card-title-bar {
        padding: 7px 10px;
        font-size: 0.8rem;
        white-space: normal;
        /* Reserve the subtitle row even when a character has no tagline, so a
           card without one doesn't start its hero image higher than the card
           beside it. 3.75em clears the natural height of a bar that has one,
           so every bar in a row is identical. A name long enough to wrap does
           still push its own bar taller — accepted deliberately, because
           truncating a character's name is worse than a 16px offset. */
        min-height: 3.75em;
    }

    .card-title-bar .card-title-text {
        display: block !important;
        white-space: normal !important;
    }

    /* The "|" divider only makes sense between an inline pair. */
    .card-title-bar .card-title-text > span:first-of-type {
        display: none;
    }

    /* The subtitle row. This element does NOT move — it is the window: it clips
       the overflow and fades the cut edge. The fade cannot live on an ancestor,
       which is the mistake the first pass made: .card-title-text spans both
       rows, so its mask bled onto the name and ate the tail of longer ones
       ("Grandmother Ember") even though the name was not overflowing at all.
       It cannot live on .card-title-bar either — that has a background the mask
       would fade along with the text.

       The child combinator is required, not stylistic. :last-of-type is scoped
       to each element's own parent, so a plain descendant selector also matched
       .card-title-slide inside this span — and at (0,3,1) it out-specified the
       (0,3,0) rule below, handing the slider this row's display:block, mask and
       overflow. The slider then filled the row instead of hugging its text, so
       every short tagline measured as overflowing by exactly the +14 fudge and
       jittered 14px for nothing.

       The inline style owns colour, weight and font-size (0.9em of 0.8rem =
       11.5px). opacity overrides the 0.45 on .card-title-bar span, which was
       tuned for a trailing fragment after a "|" and is too dim to read as a
       subtitle line of its own. */
    .card-title-bar .card-title-text > span:last-of-type {
        display: block;
        overflow: hidden;
        white-space: nowrap;
        line-height: 1.35;
        margin-top: 1px;
        opacity: 0.72;
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%);
        mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%);
    }

    /* The part that actually slides is .card-title-slide, wrapped around the
       tagline text by the marquee block so the window above can stay still. Its
       resets live with .card-title-bar span near line 2627, outside this query,
       for the reason documented there. transform is what moves it — rather than
       animating text-indent — so the slide composites on the GPU instead of
       reflowing the bar every frame.

       Once a row is actually sliding, its start scrolls out of the window too,
       and a hard cut mid-word directly under the name reads as broken text. So
       soften that edge as well — but only while it slides, which is what :has
       buys: a resting subtitle keeps its first letter at full strength instead
       of being dimmed for a reveal that never happens. Browsers without :has
       simply keep the right-only fade. */
    .card-title-bar .card-title-text > span:last-of-type:has(> .card-title-auto-marquee) {
        -webkit-mask-image: linear-gradient(to right, transparent 0, #000 9px, #000 calc(100% - 16px), transparent 100%);
        mask-image: linear-gradient(to right, transparent 0, #000 9px, #000 calc(100% - 16px), transparent 100%);
    }

    /* Repeat the :hover selector — the 768px block sets a taller clamp on it
       and would otherwise out-specify a bare .card-snippet here. */
    .card-snippet,
    .character-card:hover .card-snippet {
        font-size: 0.76rem;
        line-height: 1.42;
        /* three lines: 3 × 1.42 */
        max-height: 4.26em;
    }

    /* Traits ran to 6-10 chips on a 159px column — three rows of grey pills
       under every card was most of what made the grid feel congested. */
    .card-tags-grid .card-tag:nth-child(n+4) {
        display: none;
    }

    .card-body {
        padding: 9px 10px;
        gap: 6px;
    }

    .card-footer-bar {
        padding: 6px 10px;
    }
}

/* ---- Narrow phones (≤400px) ---- */
@media (max-width: 400px) {

    /* 12px gutters buy each card 4px of width back. */
    .page {
        padding: 0 12px 16px;
    }

    /* Edge-bleeding rails have to track the new gutter or they push the page
       into horizontal overflow. */
    .category-filters,
    .mood-row {
        padding-left: 12px;
        padding-right: 12px;
        margin-left: -12px;
        margin-right: -12px;
        width: calc(100% + 24px);
        -webkit-mask-image: linear-gradient(to right, transparent 0, #000 12px, #000 calc(100% - 16px), transparent 100%);
        mask-image: linear-gradient(to right, transparent 0, #000 12px, #000 calc(100% - 16px), transparent 100%);
    }

    .premium-hero {
        padding: 16px 14px;
    }

    .welcome-back-header {
        font-size: 1.45rem;
    }

    .premium-hero .stat-label {
        font-size: 0.54rem;
        letter-spacing: 0.4px;
    }

    .shop-hero-title {
        font-size: 1.38rem;
    }

    .card-tags-grid .card-tag:nth-child(n+3) {
        display: none;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   MOBILE INPUT SIZING — the iOS focus-zoom trap
   Safari zooms the page in whenever a focused text field computes to
   under 16px, and the viewport meta used to carry user-scalable=no, so
   the user could not pinch back out. The meta is fixed now; this closes
   the other half.

   There is already an anti-zoom block at ~13262, but its selector is
   bare `input, textarea, select` — specificity (0,0,1). Every field in
   this app is class-styled, so `.stg-input` (0,1,0) and `.premium-input`
   (0,1,0) beat it no matter how late it sits. Hence an explicit list at
   matching specificity, appended last. The chat composer solved this
   long ago (~6064) and is the pattern being copied.

   Measured before this rule, at 360px: .premium-input 14.9px,
   .stg-input 14.1px, #diary-entry-input 14.4px, .scene-textarea 15.2px,
   .memory-search-bar input 15.2px, .facet-search 13.6px.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    .premium-input,
    .scene-textarea,
    .stg-input,
    #diary-entry-input,
    .memory-search-bar input,
    .facet-search,
    .edit-message-textarea {
        /* 16px exactly — not 1rem, which a future root-size change would
           quietly drop below the threshold. */
        font-size: 16px;
    }

    /* Selects are exempt: a native picker never zooms the page, and
       forcing 16px on #chat-model-selector would push the chat header
       into a third row. Its legibility is handled in the type sweep. */
}

/* ═══════════════════════════════════════════════════════════════════
   MOBILE PAINT BUDGET (≤768px)
   The app ships 119 backdrop-filters, 102 filter: blur()s and 81
   `infinite` animations. On desktop that is a look; on a mid-range
   phone it is the reason scrolling stutters, because the two drifting
   radial layers on body::before/::after sit UNDER most of those
   backdrop-filters — so every frame of their transform forces every
   blur above them to re-rasterise.

   Three deliberate lines are drawn here:
     · Free decoration stops (drifting orbs, particle fields, shine
       sweeps, gradient-shifting buttons).
     · Meaning keeps moving — the chat status dot, typing dots, loading
       skeletons and the claimable/ready pulses on quests and
       achievements are all telling the user something.
     · Purchased cosmetics keep moving. Animated avatar frames, username
       styles and bubble skins are what someone paid for; quietly
       freezing them on the device most people use is not a
       performance decision, it is a refund request.

   Explicit selector lists, never a blanket `*`, so what was turned off
   is auditable and nothing gets caught by accident.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* ---- A. The two page-wide drifting layers ----
       Kept visible (they are the app's background character) but frozen,
       which is what makes every backdrop-filter below static instead of
       re-blurring on every animation frame. */
    body::before,
    body::after {
        animation: none;
    }

    /* ---- B. Decorative orb / mesh / particle layers ---- */
    .hero-orb,
    .orb,
    .orb-pulse,
    .hero-glow,
    .premium-glow,
    .shop-hero-mesh,
    .shop-hero-mesh-2,
    .shop-hero-balance-glow,
    .shop-particle,
    .char-profile-particles .particle,
    .diary-lock-icon,
    .diary-lock-icon::after,
    .diary-empty-state i,
    .qm-glow-1,
    .qm-glow-2,
    .qm-glow-3,
    .ll-continue-glow {
        animation: none;
    }

    /* Particles are pure ornament and there can be dozens of them, each
       its own composited layer. */
    .shop-particle,
    .char-profile-particles .particle {
        display: none;
    }

    /* ---- C. Shine / shimmer sweeps and gradient-shifting buttons ----
       A gradient that animates its background-position repaints the whole
       button forever. None of them communicate state. */
    .create-btn::after,
    .super-create-btn,
    .super-create-btn::before,
    .brand-logo:hover h2,
    .brand-dot,
    .account-avatar::before,
    .sidebar-account-shell.plus .sidebar-account-toggle,
    .sidebar-account-shell.plus .account-avatar i,
    .sidebar-account-shell.plus .account-copy .balance,
    .sidebar-upgrade-btn:hover .upgrade-btn-glow,
    .upgrade-icon,
    #start-scene-btn::before,
    .ultimate-save-btn,
    .ultimate-save-btn:hover::before,
    .qa-menu-header i,
    .character-card::before,
    .public-profile-avatar-ring,
    .shop-hero-orb-ring,
    .shop-hero-balance-num,
    .shop-buy-btn,
    .shop-buy-btn:hover .shop-buy-btn-glow,
    .shop-card:hover .shop-card-border-anim,
    .shop-card--flagship .shop-card-border-anim,
    .qm-header-icon,
    .qm-title,
    .qm-btn-orbs,
    .qm-card:hover .qm-card-border,
    .qm-card-btn:hover .qm-btn-glow,
    .ll-plan-card.ll-plan-card--active,
    .ll-upgrade-continue-btn,
    .plus-celebrate {
        animation: none;
    }

    /* ---- D. Blur on surfaces that sit on the page background ----
       These are the ones that repeat: a card grid or a message list can
       put 20+ stacked blurs on screen at once. Each already carries its
       own translucent fill over an opaque #09090b page, so removing the
       blur costs a texture nobody can resolve at this width. */
    .card-stat-badge,
    .card-owner-actions,
    .card-owner-btn,
    .results-header,
    .hero-stats,
    .search-container,
    .message-content,
    .message-wrapper,
    .edit-message-textarea,
    .scene-setter-card,
    .glass-panel,
    .glass-btn.secondary,
    .creation-section,
    .sub-card,
    .quest-card,
    .quests-summary-chip,
    .shop-card-inner,
    .shop-card-popularity,
    .shop-card-tier-ribbon,
    .shop-hero-balance-pill,
    .qm-card-inner,
    .stg-acc-card,
    .public-stat-card,
    .public-profile-sidebar-card,
    .public-back-btn,
    .diary-bubble-ai,
    .diary-lock-card,
    .achievements-level-widget,
    .ach-unlock-banner,
    .settings-wrapper-v2,
    .settings-main-form.glass-panel,
    .settings-back-btn,
    #back-to-discover.glass-back-btn,
    .success-toast,
    .shop-toast-inner {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* ---- E. Chrome that floats OVER content ----
       These cannot simply lose the blur: at 0.4-0.75 alpha the blur is the
       only thing stopping the message list / card grid reading straight
       through them. Blur off AND fill opaque, which is both cheaper and
       more legible than a frosted panel at this size. */
    .sidebar {
        background: #101014;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .chat-header {
        background: #0b0b0e;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .chat-input-container {
        background: #1c1c21;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .diary-header-bar {
        background: linear-gradient(180deg, #140f1e, #0c0a12);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .diary-input-area {
        background: linear-gradient(0deg, #140f1e, #0c0a12);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .shop-tabs-bar {
        background: #141023;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .public-profile-tabs {
        background: #120e1b;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .stg-save-bar {
        background: #120e1c;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* Already 0.9 alpha — only the blur needs to go. */
    .diary-day-divider-pill {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* ---- E2. Popovers and dropdowns ----
       Same rule as E, but these were originally grouped with D, and that was
       wrong in a way you only see on a device: D's reasoning is "each already
       carries a translucent fill over an opaque page background", which is true
       of a card or a message bubble and false of a menu that opens on top of
       other UI. Measured on the emulator, .sidebar-account-menu at 0.97 over the
       "Upgrade to Loom+" button showed the button's label straight through its
       Log out row — 3% of white on a dark fill is still legible text, and with
       the blur gone there was nothing smearing it. Hit-testing was never wrong,
       so the menu worked; it just looked broken.

       Every one of these is position: absolute over sibling content at 0.85-0.98
       alpha, so every one had the same defect at ≤768px. Opaque fills, matched to
       what each frosted panel resolved to. */
    .sidebar-account-menu,
    .create-dropdown {
        background: #12101a;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .pronoun-menu,
    .dob-pop {
        background: #140e1e;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .diary-emoji-panel {
        background: #181222;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .quick-action-hover-menu {
        background: linear-gradient(160deg, #1c132a, #100b1a);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* ---- F. Full-screen overlays ----
       The most expensive blur in the app (it samples the whole viewport) for
       the least benefit: each already darkens the page to 0.6-0.8, and the
       thing the user is meant to read sits in the modal above it. Alpha goes
       up a little to carry the separation the blur was providing. */
    .confirm-overlay,
    .qm-overlay,
    .qm-overlay.visible,
    .memory-manager-overlay,
    .diary-settings-overlay,
    .ll-upgrade-overlay,
    .qm-submodal,
    .sidebar-backdrop {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .confirm-overlay {
        background: rgba(8, 5, 12, 0.86);
    }

    /* Scrim over the memory-manager card rather than over the page, so it belongs
       here and not with the popovers above: the card is meant to stay visible
       behind it. 0.85 was tuned against a 12px blur, and reads as a thin wash
       without one. */
    .qm-submodal {
        background: rgba(10, 8, 18, 0.94);
    }

    /* .qm-overlay animates up from alpha 0, so only the visible state is set. */
    .qm-overlay.visible {
        background: rgba(4, 2, 10, 0.88);
    }

    .memory-manager-overlay,
    .diary-settings-overlay,
    .ll-upgrade-overlay {
        background: rgba(0, 0, 0, 0.88);
    }

    .sidebar-backdrop {
        background: rgba(0, 0, 0, 0.72);
    }
}


/* ═══════════════════════════════════════════════════════════════════════
   MOBILE BOTTOM TAB BAR  (≤768px)
   ───────────────────────────────────────────────────────────────────────
   Discover · Chats · Diary · Shop · Me. The markup lives at the end of
   <main class="main-content"> in dashboard.html, i.e. as a normal flex
   child AFTER .page — not position: fixed. .main-content is
   `display:flex; flex-direction:column; overflow:hidden` and .page is
   `flex-grow:1; overflow-y:auto`, so a flex-shrink:0 bar takes its own
   height out of the scroll area. Nothing anywhere needs a compensating
   padding-bottom, and no page can hide content behind the bar.

   The buttons carry `class="nav-item" data-page="..."`, which is what the
   sidebar drawer uses, so the existing click handler drives them. That
   also means every sidebar .nav-item rule applies here and has to be
   undone: the 14px gap, the 12/16 padding, the left indicator rail
   (::before), the translateX(5px) hover and the staggered entrance.
   ═══════════════════════════════════════════════════════════════════════ */
.mobile-tab-bar {
    display: none;
}

@media (max-width: 768px) {
    .mobile-tab-bar {
        display: flex;
        flex: none;
        align-items: stretch;
        /* The bar's own box is --tab-bar-h; the home-indicator inset is added
           on top so the buttons never sit under the iOS gesture area. */
        height: calc(var(--tab-bar-h) + var(--safe-bottom));
        padding-bottom: var(--safe-bottom);
        background: #0d0b14;
        border-top: 1px solid rgba(255, 255, 255, 0.08);
        /* No backdrop-filter: this is a permanently visible surface over a
           scrolling list, which is the single most expensive place to put one
           on a phone. Opaque fill instead — same reasoning as the paint
           budget block above. */
        box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.45);
        z-index: 40;
    }

    /* Reset the sidebar geometry these buttons inherit from .nav-item */
    .mobile-tab-bar .tab-item {
        flex: 1 1 0;
        min-width: 0;
        flex-direction: column;
        justify-content: center;
        gap: 3px;
        padding: 6px 2px;
        min-height: var(--tap-min);
        border: none;
        border-radius: 0;
        background: none;
        box-shadow: none;
        animation: none;
        transition: color 0.18s ease;
        -webkit-tap-highlight-color: transparent;
    }

    /* The sidebar's left indicator rail becomes a top indicator here. */
    .mobile-tab-bar .tab-item::before {
        left: 50%;
        top: 0;
        transform: translateX(-50%);
        width: 26px;
        height: 2px;
        border-radius: 0 0 var(--border-radius-2xs) var(--border-radius-2xs);
        opacity: 0;
        transition: opacity 0.18s ease;
    }

    .mobile-tab-bar .tab-item.active::before {
        height: 2px;
        opacity: 1;
    }

    .mobile-tab-bar .tab-item i {
        font-size: 1.05rem;
        width: auto;
        transition: transform 0.18s ease, color 0.18s ease;
    }

    .mobile-tab-bar .tab-item span {
        /* 0.68rem would read better at this width but the type floor for this
           refactor is 11px, and a nav label is not the place to break it. */
        font-size: 0.6875rem;
        font-weight: 600;
        letter-spacing: 0.01em;
        line-height: 1;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 100%;
    }

    /* Active = colour, not a filled pill: a tinted block repeated five times
       across the bottom edge reads as noise. */
    .mobile-tab-bar .tab-item.active {
        background: none;
        border-color: transparent;
        box-shadow: none;
        color: #cbaaff;
    }

    .mobile-tab-bar .tab-item.active i {
        transform: none;
        color: #cbaaff;
    }

    /* Kill the sidebar hover shift — on a touch device :hover sticks after a
       tap, so the bar would visibly slide sideways and stay there. */
    .mobile-tab-bar .tab-item:hover:not(.active),
    .mobile-tab-bar .tab-item:hover:not(.active) i {
        transform: none;
        background: none;
    }

    .mobile-tab-bar .tab-item:hover:not(.active)::before {
        height: 2px;
        opacity: 0;
    }

    .mobile-tab-bar .tab-item:active i {
        transform: scale(0.9);
    }

    /* Chat needs every pixel of height, and it has its own back button and
       composer — same reasoning (and same :has pattern) as the top nav above. */
    .main-content:has(#page-chat.active) .mobile-tab-bar {
        display: none;
    }

    /* The diary's entry feed is the same kind of surface: a header, a scrolling
       transcript and a composer, where the bar was costing 56px of the 700 the
       phone has and sitting under the keyboard the moment you started writing.

       Scoped to the "main" stage only, via the data-stage that showDiaryView()
       stamps on the page (dashboard.js:8298). Setup and lock are not the chat
       window — the user asked for it gone from the chat window — and they have
       no back button of their own, so the bar is their only way out. The feed
       stage does have one: .diary-back-btn, which this pass added for exactly
       this reason (the top nav is already hidden here by line 2149, so without
       it hiding the bar would strand the page). */
    .main-content:has(#page-diary.active[data-stage="main"]) .mobile-tab-bar {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .mobile-tab-bar .tab-item,
    .mobile-tab-bar .tab-item i,
    .mobile-tab-bar .tab-item::before {
        transition: none;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   QUESTS — MOBILE PASS (≤768px / ≤560px)
   The Quests page shipped 31 base rules and not one media query, so a
   360px phone got the desktop board verbatim. Measured at 360x740
   (page height 630 with the top nav and tab bar in place):

     hero section          504   <- 80% of the viewport before quest one
       back chip            50   (+16 margin)
       title                40   at 32px
       subtitle             83   at 17.3px, four lines
       summary chips        34   (+18 margin), wrapped onto two rows
       balance pill        167
     section head           93   x2
     quest card        262-288   at 22px padding, 48px icon

   Two specificity traps are why the existing shop-hero mobile rules did
   not already cover this page, and both are fixed by selector, not by
   another late override:
     · `.quests-hero-section` (0,1,0) is declared AFTER the 768px
       `.shop-hero-section` rule, so at equal specificity the base rule
       wins and the hero keeps `padding: 30px 40px`.
     · `.quests-hero-section .shop-hero-title` (0,2,0) outranks the
       560px `.shop-hero-title` (0,1,0), so the title stayed at
       clamp(2rem, 4vw, 2.9rem) — 32px — on every phone.
     · The 560px pass scoped its balance-pill and subtitle rules to
       `#page-shop`, which never matched here.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* ---- Hero ---- */
    .quests-hero-section {
        padding: 20px 16px;
        margin-bottom: var(--gap-card);
    }

    /* ID-scoped so it beats `.quests-hero-section .shop-hero-title`. */
    #page-quests .shop-hero-title {
        font-size: 1.6rem;
        letter-spacing: -0.4px;
    }

    #page-quests .hero-subtitle {
        font-size: 0.84rem;
        line-height: 1.45;
        max-width: none;
        margin-bottom: 0;
        /* Two lines is enough to explain the board; the quests themselves
           are the content. */
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    /* 50px tall with a 16px margin, above a hero that is already the
       tallest thing on the page. Floored at --tap-min in the final sweep:
       38 was the one back button on the app that a thumb could miss, and
       #page-quests here outranks the sweep block's bare ID selector. */
    #page-quests .quests-back-chip {
        min-height: var(--tap-min);
        margin-bottom: 10px !important;
        padding: 7px 12px;
    }

    #page-quests .quests-back-chip .reward-chip-icon {
        width: 24px;
        height: 24px;
        /* This is the ONE instance of .reward-chip-icon that is 24px square, and
           it needs its own radius because of that. The others are 38px, 34px and
           30px, where --border-radius-sm (12px) reads as a rounded square. At
           24px, 12px IS half the box, so sm renders a perfect circle -- the badge
           stops matching the three other reward chips in the same UI.

           It had been getting away with inheriting the base value at 11px (just
           under the 12px circle threshold), which is exactly the kind of accident
           a token scale is supposed to end: the shape was correct by 1px, and any
           change to the base radius anywhere would have silently rounded it off. */
        border-radius: var(--border-radius-xs);
        font-size: 0.75rem;
    }

    /* The two chips measure 157px + 198px against a 330px content box, so
       they always wrap to two rows. One masked scroll rail instead — the
       same pattern as .mood-row and .category-filters on Discover. */
    .quests-summary {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
        margin-top: 12px;
        padding-bottom: 2px;
        -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 20px), transparent 100%);
        mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 20px), transparent 100%);
    }

    .quests-summary::-webkit-scrollbar {
        display: none;
    }

    .quests-summary-chip {
        flex: 0 0 auto;
        padding: 7px 12px;
        font-size: 0.78rem;
    }

    /* The balance is not shown anywhere else on this page — the top nav has
       only the menu button and search — so the pill stays. What goes is its
       shape: the ≤480 rule stacks the orb above the figure, which measured
       119px tall and 140px wide. Back to a compact full-width row, the same
       fix `#page-shop` got at 560px (styles.css:17041). */
    #page-quests .shop-hero-right {
        width: 100%;
    }

    #page-quests .shop-hero-balance-pill {
        flex-direction: row;
        flex-wrap: nowrap;
        align-items: center;
        text-align: left;
        padding: 12px 14px;
        gap: 12px;
        border-radius: var(--border-radius-md);
    }

    #page-quests .shop-hero-balance-data {
        align-items: flex-start;
        min-width: 0;
    }

    #page-quests .shop-hero-balance-label {
        font-size: 0.62rem;
        letter-spacing: 1.2px;
    }

    /* the outer pulse ring reads as clutter at this size */
    #page-quests .shop-hero-orb-ring.ring-2 {
        display: none;
    }

    #page-quests .shop-hero-orb-visual {
        width: 40px;
        height: 40px;
    }

    #page-quests .shop-hero-balance-num {
        font-size: 1.5rem;
    }

    #page-quests .shop-hero-balance-sub {
        display: none;
    }

    /* ---- Section heads ---- */
    .quests-section-head {
        gap: 10px;
        margin-bottom: 12px;
    }

    .quests-section-title {
        gap: 10px;
    }

    .quests-section-icon {
        width: 36px;
        height: 36px;
        border-radius: var(--border-radius-sm);
        font-size: 0.95rem;
        box-shadow: none;
    }

    .quests-section-title h2 {
        font-size: 1.08rem;
    }

    .quests-section-title p {
        font-size: 0.76rem;
    }

    .quests-reset-pill {
        padding: 6px 11px;
        font-size: 0.72rem;
    }

    /* 48px of margin plus 44px of padding — 92px of empty space between
       Daily and Weekly, on a screen 630px tall. The rule still reads as a
       break; it just costs a quarter of what it did. */
    .quests-section+.quests-section {
        margin-top: 24px;
        padding-top: 20px;
    }

    /* ---- Quest cards ---- */
    .quest-card {
        padding: var(--pad-card);
        gap: 10px;
    }

    .quest-card-icon {
        width: 38px;
        height: 38px;
        border-radius: var(--border-radius-sm);
        font-size: 1rem;
    }

    .quest-card-top {
        gap: 10px;
    }

    .quest-reward-badge {
        font-size: 0.85rem;
        padding: 5px 10px 5px 6px;
    }

    .quest-reward-orb {
        width: 16px;
        height: 16px;
    }

    .quest-card-hint {
        font-size: 0.72rem;
    }

    .quest-card-footer {
        padding-top: 2px;
    }

    /* Hover lift and brightness stick after a tap on touch, leaving one
       card permanently raised. */
    .quest-card.is-claimable .quest-claim-btn:hover {
        transform: none;
        filter: none;
    }
}

@media (max-width: 560px) {

    /* The ≤560 `.shop-hero-section` rule sits earlier in the file than the
       768px block above, so restate its padding here or the wider band's
       value would win on source order at the narrowest width. */
    .quests-hero-section {
        padding: 16px 14px;
    }

    #page-quests .shop-hero-title {
        font-size: 1.4rem;
    }

    .quests-section-title h2 {
        font-size: 1rem;
    }

    .quests-section-icon {
        width: 32px;
        height: 32px;
        font-size: 0.85rem;
    }

    /* The countdown pill and the section title compete for one row at this
       width, and the pill wraps under the icon rather than under the text,
       which reads as broken. Stack it under the title block instead. */
    .quests-section-head {
        flex-direction: column;
        align-items: flex-start;
        gap: 8px;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   ACHIEVEMENTS — MOBILE PASS (≤768px / ≤560px)
   44 base rules, one media query (the unlock banner's). The page inherits
   the shared `.shop-hero-section` mobile rules, so the hero was already
   sane; everything below it was desktop-scale. Measured at 360x740:

     hero                    333   (level widget 217 of it)
     toolbar                 120   1.35rem h2 + blurb + chips + 26 margin
     family head              67   (+18 margin), 42px icon
     achievement card    287-296   x10, at 20px padding and 12px gaps
     body total             3562

   The card is 60px of internal gap and 40px of padding around ~185px of
   content, which is what makes ten milestones a 3.5k-pixel scroll.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* clamp(16px, 4vw, 40px) bottoms out at 16px, and the 64px tail was
       written before the tab bar existed to hold the bottom edge. */
    .achievements-body {
        padding: 4px var(--pad-page) 24px;
    }

    /* The ring is the hero's centrepiece, so it keeps its scale; the box
       around it does not need 22px of padding at this width. */
    .achievements-level-widget {
        padding: 14px 14px 12px;
        border-radius: var(--border-radius-md);
        gap: 10px;
        max-width: none;
    }

    .achievements-hero-section .shop-hero-right {
        min-width: 0;
        width: 100%;
    }

    .alw-ring {
        width: 108px;
        height: 108px;
    }

    .alw-ring-inner {
        width: 86px;
        height: 86px;
    }

    .alw-level {
        font-size: 2rem;
    }

    /* ---- Toolbar ---- */
    .achievements-toolbar {
        margin: 2px 0 14px;
        padding-bottom: 12px;
        gap: 10px;
    }

    .achievements-toolbar-title h2 {
        font-size: 1.12rem;
    }

    .achievements-toolbar-title p {
        font-size: 0.76rem;
        margin-top: 2px;
    }

    .ach-summary-chip {
        padding: 5px 11px;
        font-size: 0.74rem;
    }

    /* ---- Family sections ---- */
    /* 40px between families on a 630px-tall viewport reads as three
       unrelated pages rather than one board. */
    .achievements-grid {
        gap: 22px;
    }

    .ach-family-head {
        gap: 10px;
        margin-bottom: 12px;
        padding-bottom: 10px;
    }

    .ach-family-icon {
        width: 34px;
        height: 34px;
        font-size: 0.9rem;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
    }

    .ach-family-label {
        font-size: 1rem;
    }

    .ach-family-blurb {
        font-size: 0.74rem;
    }

    .ach-family-count {
        font-size: 0.72rem;
        padding: 3px 9px;
    }

    .ach-family-grid {
        gap: var(--gap-card);
    }

    /* ---- Cards ---- */
    .achievement-card {
        padding: var(--pad-card);
        gap: 9px;
        /* The entrance cascade delays the last card by 45ms x 9. On a phone
           the later families are off-screen anyway, so the stagger only makes
           the first paint feel unfinished. */
        animation-delay: 0ms;
    }

    .achievement-icon {
        width: 38px;
        height: 38px;
        font-size: 1rem;
    }

    .achievement-title {
        font-size: 0.98rem;
    }

    /* `min-height: 2.5em` reserves a third line to keep desktop card tops
       aligned in a multi-column grid. At one column per row there is
       nothing to align to, so the reserve is just empty space. */
    .achievement-desc {
        font-size: 0.8rem;
        min-height: 0;
        margin-top: -2px;
    }

    /* 40px measured, against --tap-min: 44px. */
    .achievement-claim-btn {
        min-height: var(--tap-min);
        padding: 11px 16px;
    }

    /* Hover lift sticks after a tap on touch. */
    .achievement-card:hover,
    .achievement-card.is-locked:hover {
        transform: none;
    }
}

@media (max-width: 560px) {
    .achievements-toolbar-title h2 {
        font-size: 1.05rem;
    }

    .alw-ring {
        width: 96px;
        height: 96px;
    }

    .alw-ring-inner {
        width: 76px;
        height: 76px;
    }

    .alw-level {
        font-size: 1.75rem;
    }

    .ach-family-icon {
        width: 30px;
        height: 30px;
        font-size: 0.82rem;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   MY COLLECTION — MOBILE PASS (≤768px / ≤560px)
   12 base rules, one media query (the 720px stack). Measured at 360x740:

     hero                    243   title 32px, subtitle 16.3px
       "Unlock more" link     87   at 16px 20px
     preview card            357   116px avatar in 28px 22px padding
     swatch grid           2 col   143px cells, 110px tall, 12px gap
     inv-layout total       1547

   Same specificity trap as Quests: `.collection-hero-section
   .shop-hero-title` and `... .shop-hero-subtitle` are (0,2,0) and beat the
   shared ≤560 `.shop-hero-title` / `.shop-hero-subtitle` rules, so this
   page kept desktop hero type on every phone.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    .collection-hero-section {
        padding: 20px 16px;
        margin-bottom: var(--gap-card);
    }

    #page-collection .shop-hero-title {
        font-size: 1.6rem;
        letter-spacing: -0.4px;
        margin-top: 0;
    }

    #page-collection .shop-hero-subtitle {
        font-size: 0.84rem;
        line-height: 1.45;
        max-width: none;
        margin-top: 6px;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    /* Already full-width from the 720px rule; this is the density. */
    .collection-shop-link {
        padding: 12px 14px;
        gap: 10px;
    }

    .collection-shop-link-icon {
        width: 38px;
        height: 38px;
        font-size: 1rem;
    }

    .collection-shop-link-text strong {
        font-size: 0.9rem;
    }

    .collection-shop-link-text small {
        font-size: 0.72rem;
    }

    .inv-layout {
        gap: var(--gap-card);
    }

    /* A preview of yourself is worth having, but not 357px of it before the
       first item you can equip. */
    .inv-preview-card {
        padding: 14px;
        gap: 8px;
    }

    .inv-preview-stage {
        padding: 2px;
    }

    .inv-preview-avatar {
        width: 84px;
        height: 84px;
        font-size: 2.1rem;
    }

    .inv-preview-name {
        font-size: 1.05rem;
    }

    .inv-preview-hint {
        font-size: 0.74rem;
        max-width: none;
    }

    .inv-collection {
        gap: 18px;
    }

    .inv-group-head {
        margin-bottom: 10px;
        padding-bottom: 7px;
    }

    .inv-group-head h4 {
        font-size: 0.95rem;
        gap: 7px;
    }

    .inv-count {
        font-size: 0.72rem;
    }

    /* minmax(104px, 1fr) yields exactly two 143px cells at this width — wide
       cells, tall grids, and four groups to scroll through. Three narrower
       cells fit the same 52px visual and cut each grid by a third. */
    .inv-swatch-grid {
        grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
        gap: 10px;
    }

    .inv-swatch {
        padding: 11px 7px 9px;
        gap: 7px;
        border-radius: var(--border-radius-sm);
    }

    /* Hover lift sticks after a tap on touch, leaving a swatch raised as if
       it were the equipped one — which `.is-active` is supposed to signal. */
    .inv-swatch:hover {
        transform: none;
    }

    .inv-swatch-visual {
        height: 48px;
    }

    .inv-swatch-avatar,
    .inv-swatch-none-visual {
        width: 44px;
        height: 44px;
    }

    .inv-swatch-label {
        font-size: 0.72rem;
    }
}

@media (max-width: 560px) {

    /* Restated for the same reason as the Quests hero: the shared ≤560
       `.shop-hero-section` padding is declared earlier in the file than the
       768px block above. */
    .collection-hero-section {
        padding: 16px 14px;
    }

    #page-collection .shop-hero-title {
        font-size: 1.4rem;
    }

    .inv-preview-avatar {
        width: 76px;
        height: 76px;
        font-size: 1.9rem;
    }
}

/* ============================================================
   PHASE 4 — CHAT SHELL DENSITY & TAP TARGETS (appended last)
   ============================================================
   Measured at a true 360x740 layout viewport (script-stripped dashboard.html
   with the openChat header template and three bubbles hand-built):

     .chat-header          151px   -> three wrapped rows
     .chat-input-area      110px
     .chat-messages        479px   -> 65% of the viewport for the actual chat

   Why the header wrapped to three rows: `.chat-character-info` is
   `flex: 1 1 auto`, so its hypothetical size (244px: 42px avatar + a 150px
   details column) is what the wrap algorithm places. back(32) + info(244) +
   two 38px action icons overflows the 336px content box, so the icons broke to
   their own line, and the model selector already had a third
   (styles.css:13354 / 17934). Giving the info column `flex-basis: 0` lets it be
   *sized by the leftover space* instead of forcing the break, so back + name +
   both icons share one row and only the selector keeps its own.

   Tap targets in the same pass: every control here was 34-40px against
   --tap-min: 44px.
   ============================================================ */

@media (max-width: 768px) {

    /* ---- Header: two rows instead of three ---- */
    .chat-header {
        padding: 8px 12px;
        row-gap: 6px;
        column-gap: 6px;
    }

    /* flex-basis: 0 is the whole fix — see the note above. min-width: 0 is what
       lets the name ellipsis instead of pushing the icons out again. */
    .chat-character-info {
        flex: 1 1 0;
        min-width: 0;
    }

    .chat-header .glass-back-btn {
        min-width: var(--tap-min);
        min-height: var(--tap-min);
        justify-content: center;
        padding: 6px;
        margin-right: 0;
    }

    .chat-header .icon-btn {
        width: var(--tap-min);
        height: var(--tap-min);
    }

    /* Its own full-width row below 400px (13354 / 17934), so height is the only
       thing left to fix: a 34px native select is the shortest control in the
       app's tightest chrome. Raised from 40 to the full --tap-min in the final
       sweep — it has a row to itself, so the 4px lands on the header once and
       not on anything that has to fit beside it. */
    #chat-model-selector {
        min-height: var(--tap-min);
        height: var(--tap-min) !important;
    }

    /* ---- Composer ---- */
    /* The two composer macros (Action + Undo) are real tap targets. The
       identical `.macro-btn` inside a bubble's action row is NOT raised: those
       sit in the scroll area, 8px apart, and inflating them adds ~6px to every
       bot message. Same spacing-exception call as the Forge trait chips. */
    .rp-macros .macro-btn,
    .rp-macros .action-main-btn {
        min-height: var(--tap-min);
    }

    .rp-macros .action-toggle-btn {
        min-height: var(--tap-min);
        min-width: 38px;
    }

    #send-btn,
    .chat-input-container .icon-btn {
        width: var(--tap-min);
        height: var(--tap-min);
    }
}

@media (max-width: 560px) {

    /* The selector inline (34vw, styles.css:17743) leaves the name column under
       120px on a phone — 38px, three characters, measured at 420 — so it takes
       its own full-width row here, the same trade the 400px block already made.
       Above 560 there is room for one row: 61px total, measured at 520 and 700. */
    #chat-model-selector {
        order: 4;
        flex: 1 1 100%;
        width: 100% !important;
        max-width: 100%;
        margin-right: 0 !important;
    }
}

/* ============================================================
   PHASE 4 — DIARY DENSITY & TAP TARGETS (appended last)
   ============================================================
   The diary's only mobile pass lives at a legacy `max-width: 600px`
   (styles.css:7712) plus four rules at 380 — 30 rules against 107 base ones —
   and it tuned type more than structure. Measured at a true 360x740 viewport
   (tab bar 56 -> 684px page, feed hand-built from renderDiaryEntries):

     .diary-header-bar     82   ->  actions 38 / 36, avatar 40
     .diary-entries-feed   480  ->  padding-bottom 80 (+ a 20px ::after)
     .diary-input-area     122  ->  send 40, emoji 40, notice at 12px margin
     day pills             10.2px    time tags 10.9px    delete btn 28px

   The 80px feed padding predates the composer being a static flex sibling
   (styles.css:7394 `flex-shrink: 0`) — nothing overlaps the feed any more, and
   `.diary-entries-feed::after` (7556) already reserves 20px, so on a phone it
   is 80px of dead scroll under the newest entry.
   ============================================================ */

@media (max-width: 768px) {

    /* ---- Header ---- */
    .diary-header-bar {
        padding: 10px 14px;
    }

    .diary-header-info {
        gap: 10px;
        min-width: 0;
    }

    /* 34 wide, not 44. The bar has to fit an exit, a 38px avatar, the name, the
       memory pill and two 44px actions inside 332px at 360 — a square target
       here would come straight out of the pill, which is the one line in the
       header that carries a promise. A back arrow is the standard place to spend
       that trade: full 44px on the vertical axis, narrow horizontally, which is
       how the platform's own back affordances are shaped. */
    .diary-back-btn {
        display: flex;
        width: 34px;
        height: var(--tap-min);
        font-size: 1rem;
    }

    .diary-agent-avatar {
        width: 38px;
        height: 38px;
        font-size: 0.95rem;
        box-shadow: 0 4px 12px rgba(167, 119, 227, 0.35);
    }

    .diary-header-info h2 {
        font-size: 1rem;
    }

    /* The pill is the header's only long line; it must ellipsis rather than
       wrap, or the whole bar grows a row. */
    .diary-memory-pill {
        font-size: 0.72rem;
        display: block;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* 36, not 44. Two square --tap-min buttons plus their gap took 92px out of a
       362px bar, and every pixel of it came off the memory pill — the one line in
       this header that says what the feature is for. 36 still clears WCAG 2.5.8's
       24px comfortably, and the header is not where a mis-tap is costly: the gear
       opens a panel and the lock is undone by typing the PIN.

       Sized and shaped together because they read as one control group and did
       not look like one: .icon-btn is a filled 50%-radius circle and
       .diary-settings-btn is a bare transparent icon, so the lock sat there as a
       heavy grey orb beside a naked gear. Both go quiet — the header is meant to
       read as a subtitle, not a toolbar (see the pill's comment at 6785), and the
       back arrow is the only one that keeps a fill, because it is the only one
       you need to find without looking. */
    .diary-settings-btn,
    .diary-header-bar .icon-btn {
        width: 36px;
        height: 36px;
        border-radius: var(--border-radius-sm);
        background: none;
        border-color: transparent;
        font-size: 0.95rem;
        margin-right: 0;
    }

    .diary-settings-btn:active,
    .diary-header-bar .icon-btn:active {
        background: rgba(167, 119, 227, 0.16);
        color: #e8d5ff;
    }

    /* ---- Feed ---- */
    .diary-entries-feed {
        padding: 14px var(--pad-page) 20px !important;
        gap: 20px;
    }

    .diary-exchange {
        gap: 10px;
    }

    /* 10.2px uppercase at 0.8px tracking was the smallest text in the app.
       0.7rem is the floor used across this pass. */
    .diary-day-divider-pill {
        font-size: 0.7rem;
        padding: 5px 12px;
        letter-spacing: 0.5px;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        box-shadow: none;
    }

    .diary-day-divider-pill i {
        font-size: 0.72rem;
    }

    .diary-day-divider {
        margin: 2px auto;
    }

    /* ---- Bubbles: leading and measure ----
       Two rules were fighting and legibility lost. .diary-bubble asks for 1.65;
       its own child .diary-entry-ai-text asks for 1.75 and wins, and the 600px
       block below only ever lowered their font-sizes — never the leading. So on a
       phone the reply rendered at 14.08px on 24.64px: leading has to grow *with*
       the measure, and this is the opposite, ~41 characters a line spaced as if
       they were 75. Six lines of a four-sentence reply, each floating clear of
       the next, which is what reads as stretched. Chat solved this for its own
       bubbles at line 5957 by dropping to 1.5 on mobile; match that number, and
       state it on both selectors so the child stops overriding the parent. */
    .diary-bubble,
    .diary-entry-ai-text {
        line-height: 1.5;
    }

    /* Paragraph spacing was tuned against the loose leading above. At 1.5 the
       gap between paragraphs has to be re-separated from it or they merge. */
    .diary-entry-ai-text p {
        margin-bottom: 9px;
    }

    /* The percentage cap is charged twice for one visual job. .diary-ai-col has
       already set aside 44px for the avatar, so the bubble is *inside* an
       already-inset column and its 82/86% takes another slice for asymmetry the
       column is providing anyway: 41px at 360 (292 -> 251), on a screen where the
       reply had 223px of text width to work with. Full width within the column
       instead — the avatar still does the offsetting, and the line goes from ~41
       to ~48 characters, which drops a six-line reply to five. */
    .diary-bubble-ai {
        max-width: 100%;
    }

    /* The user bubble is not capped by its max-width on a phone at all — it is
       flex-shrunk by what sits beside it in the row (36px delete + time tag), so
       it measured 224.6px at 360 against the AI's 251. The gap is the only part
       of that worth reclaiming; 8px still separates them clearly at this size. */
    .diary-row {
        gap: 8px;
    }

    .diary-time-tag {
        font-size: 0.7rem;
    }

    /* Touch has no hover, so the 600px block already unhid this. It stays under
       --tap-min on purpose: it deletes an entry, it sits in the row beside the
       bubble (every pixel of width comes out of the text), and 36px clears the
       24px WCAG 2.5.8 minimum with room to spare. */
    .diary-exchange-delete-btn {
        opacity: 1;
        transform: scale(1);
        width: 36px;
        height: 36px;
        font-size: 0.78rem;
    }

    /* ---- Composer ---- */
    .diary-input-area {
        padding: 10px var(--pad-page) 12px;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .diary-input-container {
        padding: 8px 10px;
        gap: 8px;
        border-radius: var(--border-radius-md);
    }

    .diary-input-container #diary-send-btn,
    .diary-input-container .diary-emoji-btn {
        width: var(--tap-min);
        height: var(--tap-min);
    }

    /* `.diary-input-area .voice-notice` is (0,2,0) and beats the blanket
       `.voice-notice { display: none }` at 6315, so this notice IS on screen on
       a phone. It stays — "private & encrypted with your PIN" is worth its line
       at the moment of writing — but at a third of the margin. */
    .diary-input-area .voice-notice {
        margin-top: 4px;
        font-size: 0.7rem;
    }

    /* ---- Setup / lock / empty state ---- */
    .diary-setup-card,
    .diary-lock-card {
        padding: 24px 18px;
        border-radius: var(--border-radius-md);
    }

    .diary-setup-icon,
    .diary-lock-icon {
        width: 52px;
        height: 52px;
        font-size: 1.35rem;
        margin-bottom: 14px;
    }

    .diary-setup-card h2,
    .diary-lock-card h2 {
        font-size: 1.3rem;
    }

    .diary-setup-desc {
        font-size: 0.86rem;
        margin-bottom: 16px;
        /* The base rule narrows this to 34ch, a measure tuned for the 440px
           desktop card. Inside a 284px phone card that is 218px — it wraps to
           three lines inside a box that had room for two, so it reads as a
           column of text floating in its own margins. Full width here. */
        max-width: none;
    }

    /* The card is text-align: center for its icon / title / description, and
       the form groups inherit it. Their labels don't care (they are flex rows
       with a left accent bar) and the PIN row centres itself, but the two
       helper paragraphs — "Changes how they talk to you…", "This PIN locks
       your diary…" — do, so each sat centred directly under a hard-left label.
       Both are inline-styled in dashboard.html, which fixes their size and
       colour but leaves alignment to CSS, so this reaches them. */
    .diary-setup-card .scene-form-group,
    .diary-lock-card .scene-form-group {
        text-align: left;
    }

    /* Pull each label onto its field. 10px between them and 18px between the
       groups reads as evenly spaced rows rather than four label+field pairs. */
    .diary-setup-card .scene-form-group label {
        margin-bottom: 7px;
    }

    .diary-empty-state {
        padding: 32px 16px;
    }

    .diary-empty-state i {
        font-size: 2.1rem;
        margin-bottom: 12px;
    }

    .diary-empty-state h3 {
        font-size: 1.1rem;
    }

    /* Settings modal: 28px of padding on a 360px screen leaves the fields
       narrower than the labels above them. */
    .diary-settings-modal {
        padding: 20px 16px;
        border-radius: var(--border-radius-md);
    }
}

/* ============================================================
   PHASE 5 — SETTINGS DENSITY & TAP TARGETS (appended last)

   Settings has a *structural* mobile pass already (11774: the two-column
   layout stacks, the sidebar becomes a horizontal rail, the nav labels
   drop). What it never got is a density pass, so at 360x740 the page is
   32% chrome before a single field:

     .stg-topbar   66   (a 33px back button in 16px of padding)
     .stg-sidebar  152  (74px profile card + a 33px nav rail)
     .stg-content  466  of which 172 is padding — `32px 20px 140px`

   The 140px bottom is clearance for .stg-save-bar, which is absolutely
   positioned and permanently visible (dashboard.js:2675 only toggles its
   .has-changes class, never its display), so it cannot simply be cut —
   it is retuned against the bar's measured mobile height instead.

   Two legacy rules are deliberately overridden from here rather than
   edited in place:
     · 11837 `.stg-content { padding: 32px 20px 140px }`
     · 11854's `@media (max-width: 480px) .stg-quick-info{flex-direction:column}`
       — there are only two chips (gender, birthday) and they measure 226px
       together, so a wrapping row fits 360px and saves a whole row.

   Type floor: `.stg-badge`, `.stg-badge--verified/--unverified` (10.4px)
   and `.stg-acc-tier-label` (10.9px) come up to 11.2px. The two 0.5-0.55rem
   sizes in this cluster stay: `.stg-save-status i` and `.sub-plan-status i`
   are fa-circle status *dots*, not text, and scaling them to 11px would
   turn a 7px indicator into a bullet.
   ============================================================ */

@media (max-width: 768px) {

    /* ---- Top bar ---- */
    .stg-topbar {
        padding: 8px var(--pad-page);
        gap: 10px;
    }

    /* 33 -> 44. The bar does not get shorter (44 + 16 of padding ~= the old
       33 + 32) but the only control on it becomes a real target. */
    .stg-back-btn {
        min-height: var(--tap-min);
        padding: 8px 14px;
    }

    /* ---- Sidebar rail ---- */
    .stg-sidebar {
        padding: 10px var(--pad-page);
        gap: 10px;
    }

    /* 74 -> ~52: the plan/orbs badges are worth keeping in reach on this
       page, so the card is compacted rather than hidden. */
    .stg-profile-card {
        padding: 8px 10px;
        gap: 10px;
        margin-bottom: 0;
    }

    .stg-avatar {
        width: 34px;
        height: 34px;
    }

    .stg-avatar i {
        font-size: 0.82rem;
    }

    .stg-profile-name {
        font-size: 0.86rem;
        margin: 0 0 2px 0;
    }

    .stg-badge,
    .stg-badge--unverified,
    .stg-badge--verified {
        font-size: 0.7rem;
        padding: 3px 7px;
    }

    /* The rail at 11813 is a row with `overflow-x: auto`, but there are only
       three tabs and at 360 they measured 350 against a 336 container — a
       14px overflow reads as a clipping bug, not as something scrollable.
       Sharing the row instead: 3 items at 6px side padding is 278 + gaps,
       so they fit and grow to fill. `white-space: nowrap` (11830) keeps the
       labels on one line. */
    .stg-nav-item {
        min-height: var(--tap-min);
        padding: 8px 6px;
        flex: 1 1 auto;
        min-width: 0;
        justify-content: center;
    }

    /* ---- Content panel ---- */
    /* Bottom clearance is measured, not guessed: the mobile save bar is
       12 (offset) + 10+10 (padding) + 44 (button) + 2 (border) = 78. */
    .stg-content {
        padding: 16px var(--pad-page) 88px;
    }

    .stg-section-title {
        font-size: 1.25rem;
    }

    .stg-section-desc {
        font-size: 0.82rem;
        margin-top: 4px;
    }

    .stg-divider {
        margin: 12px 0 14px;
    }

    /* Overrides the 480px column rule — see the note above. */
    .stg-quick-info {
        flex-direction: row;
        flex-wrap: wrap;
        gap: 8px;
        margin-bottom: 14px;
    }

    /* Non-interactive info chips (divs), so no tap minimum applies. */
    .stg-quick-chip {
        padding: 6px 12px;
        font-size: 0.74rem;
    }

    .stg-field-group {
        margin-bottom: 14px;
    }

    .stg-field-row {
        gap: 14px;
    }

    .stg-field,
    .stg-field-group {
        gap: 6px;
    }

    /* 32 -> 40. Not 44: it sits inside a 45px-tall field (16px text +
       12px padding), where a 44px button would touch both borders. */
    .toggle-password {
        width: 40px;
        height: 40px;
    }

    .stg-empty-state {
        padding: 48px 16px;
        gap: 12px;
    }

    /* ---- Save bar ---- */
    .stg-save-bar {
        left: 12px !important;
        right: 12px !important;
        bottom: 12px !important;
        padding: 10px 12px;
    }

    .stg-save-btn {
        min-height: var(--tap-min);
        padding: 10px 18px;
    }

    /* ---- Account tab ---- */
    .stg-account-grid {
        gap: 14px;
    }

    .stg-acc-card {
        padding: var(--pad-card);
        box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.05);
    }

    .stg-acc-card-title {
        font-size: 1rem;
    }

    .stg-acc-avatar-wrapper {
        width: 84px;
        height: 84px;
        margin: 0 auto 16px;
    }

    .stg-acc-avatar-glow {
        width: 100px;
        height: 100px;
        top: -8px;
        left: -8px;
    }

    .stg-acc-avatar-ring {
        width: 76px;
        height: 76px;
    }

    .stg-acc-avatar {
        font-size: 1.9rem;
    }

    /* 28 -> 34. A 44px badge would cover a third of a 76px avatar and hide
       the thing it edits; 34 still clears the 24px WCAG 2.5.8 minimum. */
    .stg-acc-avatar-edit-btn {
        width: 34px;
        height: 34px;
        font-size: 0.8rem;
    }

    .stg-acc-tier-row {
        padding: 12px 14px;
        margin-bottom: 14px;
    }

    .stg-acc-tier-label {
        font-size: 0.7rem;
    }

    /* ---- Subscription tab ---- */
    .sub-plan-icon {
        width: 44px;
        height: 44px;
        border-radius: var(--border-radius-sm);
        font-size: 1.15rem;
    }

    .sub-plan-name {
        font-size: 1.1rem;
    }

    .sub-detail-grid {
        gap: 10px;
        margin-top: 16px;
    }

    .sub-detail-item {
        padding: 12px 14px;
        gap: 4px;
    }

    .sub-detail-value {
        font-size: 0.92rem;
    }

    .sub-expiry-bar-wrap {
        margin-top: 14px;
    }

    .sub-plan-note {
        margin-top: 12px;
    }

    /* Inline `style="margin-top:20px"` in the markup (dashboard.html:2270),
       which a media query can only reach with !important. */
    #sub-perks-card {
        margin-top: 14px !important;
    }

    .sub-perks-list {
        gap: 8px;
        margin-top: 10px;
    }

    .sub-perk-item {
        padding: 10px 12px;
        gap: 10px;
    }

    /* ---- Mature-content toggle row ---- */
    .stg-toggle-row {
        padding: 12px 14px;
        gap: 12px;
    }

    .stg-toggle-title {
        font-size: 0.9rem;
    }

    .stg-toggle-sub {
        font-size: 0.78rem;
    }
}

@media (max-width: 560px) {

    .stg-content {
        padding: 14px var(--pad-page) 84px;
    }

    .stg-section-title {
        font-size: 1.15rem;
    }


    /* Only the button is left in the bar below 480 (.stg-save-status is
       display:none there, 11881), and a lone left-aligned button in a
       full-width bar reads as a layout bug. Full width instead. */
    .stg-save-bar-inner {
        justify-content: stretch;
    }

    .stg-save-btn:not(.sub-renew-btn) {
        width: 100%;
        justify-content: center;
    }
}

/* ============================================================
   PHASE 5 — MODALS: DENSITY, HEIGHT AND THE INLINE-STYLE TRAPS

   `.confirm-modal` is the shared shell for eight overlays (confirm,
   reauth, OTP, admin debug, memory manager, diary memory, …). Its base
   padding is `36px 32px 28px` on a `width: 90%` box: at 360px that is
   324 wide with 64 of it padding, which is why the reauth password field
   measured 254px and the memory-manager add row collapsed its text input
   to **34px** — a `flex: 1` input losing every pixel to two fixed-width
   selects and an Add button on the same row.

   Three things here can only be reached with !important, because they are
   inline `style=""` attributes in dashboard.html and a stylesheet rule of
   any specificity loses to those:
     · `.memory-add-form` `display: flex` (2479)
     · `#memory-add-input` `flex: 1` + `font-size: 0.85rem` (2482) — the
       last surviving iOS focus-zoom trap in the app; the Phase 0 16px
       block cannot beat an inline declaration.
     · `#memory-entries-list` `max-height: 40vh` (2516)
   The fourth, `padding: 24px` on the two memory modals, was instead moved
   off the markup onto `.memory-manager-modal` (13071) so this block can
   reach it normally.

   Height: every long modal gets a dvh cap so it can't run off a phone
   screen — vh first as the fallback line, matching the pattern at 9243.

   `#debug-payload-content` keeps its 12.8px inline font: it is a readonly
   admin-only JSON dump where 16px would wrap every line, and it is not a
   field anyone types into.
   ============================================================ */

@media (max-width: 768px) {

    .confirm-modal {
        padding: 24px 18px 20px;
    }

    /* No modal may exceed the *visible* viewport. overscroll-behavior stops
       a flick inside the modal from scrolling the page behind it. */
    .confirm-modal,
    .qm-modal,
    .memory-manager-panel,
    .diary-settings-modal {
        max-height: 92vh;
        max-height: 88dvh;
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    .confirm-icon-wrap {
        width: 46px;
        height: 46px;
        margin: 0 auto 14px;
        font-size: 1.15rem;
    }

    .confirm-icon-wrap.has-avatar {
        width: 72px;
        height: 72px;
    }

    .confirm-modal h3 {
        font-size: 1.25rem;
        margin-bottom: 10px;
    }

    .confirm-modal p {
        font-size: 0.88rem;
        margin-bottom: 20px;
    }

    .confirm-actions {
        gap: 8px;
    }

    .confirm-btn {
        min-height: var(--tap-min);
        padding: 12px 14px;
        font-size: 0.88rem;
    }

    /* ---- Memory manager / diary memory ---- */
    /* Later than the `.confirm-modal` rule above at equal specificity, so
       these two keep their own (tighter) padding. */
    .memory-manager-modal {
        padding: 16px 14px;
        max-height: 92vh;
        max-height: 88dvh;
    }

    .memory-manager-modal .memory-manager-header {
        margin-bottom: 12px !important;
        padding-bottom: 10px !important;
    }

    .memory-manager-modal .confirm-icon-wrap {
        width: 40px;
        height: 40px;
        font-size: 1rem;
    }

    .memory-manager-modal h3 {
        font-size: 1.15rem !important;
    }

    /* Two rows instead of one: the text field gets the full width and the
       axis/confidence selects and Add button share the second. */
    .memory-add-form {
        flex-wrap: wrap !important;
        gap: 8px !important;
        padding: 10px !important;
        margin-bottom: 12px !important;
    }

    #memory-add-input {
        flex: 1 1 100% !important;
        /* 16px exactly, for the same reason as the Phase 0 input block —
           and with !important because the size it beats is inline. */
        font-size: 16px !important;
        padding: 10px 12px !important;
    }

    #memory-add-btn {
        min-height: var(--tap-min);
        margin-left: auto !important;
    }

    #memory-entries-list {
        /* dvh so the list shrinks with the keyboard open rather than
           pushing the Add row off screen. */
        max-height: 40dvh !important;
        /* Now that the tab rail refuses to shrink, the list is the only child
           left to absorb a too-short viewport — and it is a scroll container, so
           its automatic minimum is 0 and it will absorb its way to invisible.
           Landscape at 740x360 is where that bites: 88dvh leaves less than the
           fixed chrome needs. The floor (the same 120px the diary list asks for
           inline) makes the modal scroll instead of showing an empty list. It is
           a no-op in portrait, where there are ~134px to give. */
        min-height: 120px;
    }

    .memory-tab {
        min-height: var(--tap-min);
        padding: 8px 12px !important;
    }

    .memory-manager-modal .memory-manager-footer {
        margin-top: 12px !important;
        padding-top: 10px !important;
    }

    /* ---- Orb / quest modal ---- */
    /* Bottom-sheet corners: the modal is anchored to the bottom edge below 768
       (.qm-overlay switches to align-items: flex-end), so the top corners stay at
       the section tier and the bottom two tighten as they approach the screen edge.

       This is the only remaining rung, and that is the fix. The ladder used to read
       28px base -> 20px @768 -> 24px @640, i.e. the modal got ROUNDER as the screen
       got narrower — non-monotonic, which no responsive ladder should ever be, and
       invisible unless you happen to resize through the 600s. Both mobile rungs
       became lg/lg/md/md in the token pass, which made the @640 copy identical to
       this one, so it is gone. */
    .qm-modal {
        border-radius: var(--border-radius-lg) var(--border-radius-lg) var(--border-radius-md) var(--border-radius-md);
    }
}

@media (max-width: 400px) {

    .confirm-modal {
        padding: 20px 14px 16px;
    }

    .confirm-modal h3 {
        font-size: 1.15rem;
    }

    /* .confirm-actions is deliberately NOT stacked here: at 360 the modal is
       346 wide, so two buttons plus the 8px gap still get 155px each — more
       than the longest pair ("Cancel" / "Confirm") needs — and the debug
       modal's buttons carry an inline width: 50% that stacking would leave
       looking half-drawn. */

    .memory-manager-modal {
        padding: 14px 12px;
    }
}

/* ═══════════════════════════════════════════════════════════════════════
   PHASE 5 — PUBLIC PROFILE (mobile density)
   Both `#page-profile` (your own) and `#page-user-profile` (someone
   else's) share the `.public-*` / `.up-*` selectors, so one pass covers
   both. Prior coverage was two structural queries only — 1100px collapses
   the two-column grid, 720px shortens the banner and un-grids the panels
   — and nothing touched density. Measured at 360x740 before this block:

     .public-profile-banner        160  (grid pulls up 50, so 110 of
                                        decorative gradient above the card)
     .public-profile-sidebar-card  378, padding 36px 28px 28px
     .public-profile-avatar-ring   128
     .public-profile-name          28.8px
     .public-profile-tabs          136  <- five tabs wrapped over FOUR rows
     .public-profile-tab           34   (below --tap-min)
     .public-panel-card            251, padding 24px
     .public-stat-label            10.88px (below the 11px floor)
     grid total                    1272

   The tab capsule is the headline: `flex-wrap: wrap` (1711) turns five
   tabs into a 136px block that pushes the panels below the fold. Five
   labels need ~600px in one row, so at phone widths this becomes a
   horizontal rail — the same treatment the quest/forge chip rows got —
   rather than a taller and taller stack.
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* ---- Shell & cover banner ---- */
    .public-profile-shell {
        /* The tab bar is a flex sibling of .page, not an overlay, so the
           only thing this padding buys is breathing room after the last
           card. 40 -> 20. */
        padding-bottom: 20px;
    }

    .public-profile-banner {
        /* 160 -> 120; with the grid's -56 pull that leaves a 64px cover
           strip, enough to read as a banner and to hold the back button. */
        height: 120px;
    }

    .public-back-btn {
        top: 12px;
        left: var(--pad-page);
        /* 10px 20px !important at 1390 — the shorthand has to be beaten
           with one of its own. */
        padding: 10px 16px !important;
    }

    /* ---- Grid & sidebar ---- */
    .public-profile-grid {
        padding: 0 var(--pad-page);
        margin-top: -56px;
        gap: var(--gap-card);
    }

    .public-profile-sidebar {
        gap: var(--gap-card);
    }

    .public-profile-sidebar-card {
        padding: 20px var(--pad-card) 16px;
        border-radius: var(--border-radius-md);
        box-shadow: 0 12px 28px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .08);
    }

    .public-profile-avatar-container {
        margin-bottom: 14px;
    }

    .public-profile-avatar-ring {
        width: 96px;
        height: 96px;
        padding: 3px;
    }

    .public-profile-avatar {
        font-size: 2.1rem;
        border-width: 3px;
    }

    .public-profile-name {
        font-size: 1.4rem;
        margin-bottom: 4px;
    }

    .public-profile-handle-row {
        margin-bottom: 10px;
        flex-wrap: wrap;
        justify-content: center;
    }

    .public-profile-handle {
        font-size: 0.88rem;
    }

    .public-profile-bio {
        font-size: 0.86rem;
        margin-top: 12px;
        padding-top: 12px;
    }

    /* ---- Stat cards (2x2) ---- */
    .public-profile-stats-grid {
        gap: 10px;
    }

    .public-stat-card {
        padding: 12px 8px;
        box-shadow: 0 6px 16px rgba(0, 0, 0, .18);
    }

    .public-stat-value {
        font-size: 1.2rem;
    }

    .public-stat-label {
        /* 0.68rem = 10.88px, under the 11px floor. */
        font-size: 0.7rem;
        letter-spacing: 0.6px;
        margin-top: 4px;
    }

    /* ---- Tab capsule: rail, not wrap ---- */
    .public-profile-main {
        gap: var(--gap-card);
    }

    .public-profile-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        /* `align-self: center` (2159) would shrink-wrap the rail and clip it
           mid-tab; it needs the full column width to scroll inside. */
        align-self: stretch;
        justify-content: flex-start;
        padding: 6px;
        /* The right gutter moves off the container and onto the last tab (below).
           A flex item is laid out in the CONTENT box, but overflow clips to the
           PADDING box -- so with 6px of padding-right the fade below could not
           reach the last 6px, and glyphs scrolled through that strip at full
           brightness. Measured: the ramp ran 100/88/90/35/16% inward and then
           jumped back to 100% in the final 6px. A fade that un-fades reads as a
           rendering fault, not as a hint. */
        padding-right: 0;
        gap: 6px;
    }

    /* Puts back the trailing gutter that padding-right: 0 gave up, so the last tab
       still ends with breathing room when the rail is scrolled fully right.
       `:last-child` still matches the last tab: the ::after below is a flex item
       but not a child, so it does not take the match away. */
    .public-profile-tab:last-child {
        margin-right: 6px;
    }

    /* ---- The rail has to SAY it scrolls ----
       Measured at 390: this capsule is 364px wide holding 598px of tabs, so
       234px -- roughly two of the five tabs -- sit off the right edge with
       nothing on screen indicating they exist. It was the only overflowing row
       in the product without an affordance (`.category-filters` and
       `.quests-summary` already fade; at 1440 nothing overflows at all).

       This is a pseudo-element painting OVER the tabs rather than a mask-image
       like the five fades further up this file, and the reason is measured. Those
       five are bare transparent strips; this one is a capsule with its own fill,
       a rgba(255,255,255,0.05) border, a 16px radius and a drop shadow -- and
       mask-image applies to an element's ENTIRE rendering, not just its text. So
       a mask here buys its text fade by spending the capsule's own edge. Peak
       glyph luminance stepping in from the right edge, against the vertical
       border stroke at mid-height, one tab parked under the fade:

                        40px  32px  16px  10px   6px      right border
         no fade         100%  100%  100%  100%  100%      27.7
         mask -> 0.35    100%   95%   67%   55%   48%      14.9   (-46%)
         mask -> 0       100%   93%   48%   32%   21%       8.2   (-70%, = the
                                                                   page ground:
                                                                   the stroke that
                                                                   closes the pill
                                                                   is simply gone)
         this ::after    100%   88%   36%   16%   11%      27.7   (unchanged, and
                                                                  so are both
                                                                  horizontal edges)

       It gives the strongest signal of the four AND costs the chrome nothing,
       because a child cannot paint on its parent's border. Three details make it
       work:
         * ::after on a flex container is a flex item, so `flex: 0 0 34px` with an
           equal negative margin (plus the 6px gap it would otherwise add) nets
           zero layout space -- it overlays the last tab rather than extending the
           rail.
         * `position: sticky`, not absolute. An absolutely positioned child of an
           overflow-x container resolves against the scrollable overflow area, so
           it SCROLLS WITH the content and drifts off screen; sticky pins it to
           the scrollport's right edge instead.
         * it paints in #120e1b, this breakpoint's own capsule fill (19711), so
           the tab text disappears into the capsule rather than into a grey wash.
       Known limitation, shared with the two existing static fades: it stays
       visible at the end of the scroll. A scroll-position-aware fade needs
       animation-timeline: scroll(), which no other row here uses. */
    .public-profile-tabs::after {
        content: '';
        position: sticky;
        right: 0;
        flex: 0 0 34px;
        /* 34px of fade + the 6px flex gap that precedes it = zero net width. */
        margin-left: -40px;
        align-self: stretch;
        pointer-events: none;
        background: linear-gradient(to left, #120e1b 20%, rgba(18, 14, 27, 0) 100%);
    }

    .public-profile-tabs::-webkit-scrollbar {
        display: none;
    }

    .public-profile-tab {
        flex: 0 0 auto;
        min-height: var(--tap-min);
        padding: 8px 14px;
        font-size: 0.84rem;
        white-space: nowrap;
    }

    /* ---- Panels ---- */
    .public-panel-card {
        padding: var(--pad-card);
        box-shadow: 0 8px 22px rgba(0, 0, 0, .22);
    }

    .public-panel-title {
        font-size: 1rem;
        gap: 8px;
        margin-bottom: 12px;
    }

    .public-card-list,
    .public-activity-list {
        gap: 10px;
    }

    /* The 220px min track fits at 328 but leaves a single column with a
       stretched card; explicit minmax(0, 1fr) says the same thing and keeps
       the nowrap card titles from inflating the track. */
    .public-card-list--grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .public-mini-card {
        padding: 10px 12px;
        gap: 10px;
    }

    .up-social-card {
        padding: 10px 12px;
        gap: 10px;
    }

    .public-mini-avatar {
        width: 40px;
        height: 40px;
        font-size: 1rem;
    }

    .public-mini-title {
        font-size: 0.92rem;
    }

    .public-mini-subtitle {
        font-size: 0.78rem;
    }

    .empty-mini-state {
        padding: 18px 14px;
        font-size: 0.85rem;
    }

    /* ---- About panel ---- */
    /* 720 (2182) already made this one column; two short label/value pairs
       per row read better than four full-width ones on a phone. */
    .public-about-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
    }

    .public-about-item {
        padding: 10px 12px;
        border-radius: var(--border-radius-sm);
    }

    .public-about-label {
        font-size: 0.7rem;
        letter-spacing: 0.6px;
        margin-bottom: 4px;
    }

    .public-about-value {
        font-size: 0.88rem;
    }

    .public-bio-block {
        font-size: 0.9rem;
        line-height: 1.6;
    }

    /* ---- Other user's profile ---- */
    .up-follow-container {
        margin-top: 12px;
    }

    .up-follow-btn {
        /* 12px 32px gave 43px — one short of the tap minimum. Full width
           too: it is the only action on the card. */
        width: 100%;
        justify-content: center;
        min-height: var(--tap-min);
        padding: 11px 20px;
        font-size: 0.9rem;
    }

    .up-char-grid {
        /* minmax(200px, 1fr) collapses to one column inside a 300px panel,
           which makes each character a full-width banner. Two per row keeps
           the square avatars reading as a gallery. */
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
    }

    .up-char-card,
    .up-char-card::before {
        border-radius: var(--border-radius-md);
    }

    .up-char-card-body {
        padding: 10px 12px;
    }

    .up-char-card-name {
        font-size: 0.88rem;
    }

    .up-char-card-tagline {
        font-size: 0.75rem;
        margin-bottom: 6px;
    }

    .up-char-card-stats {
        font-size: 0.72rem;
    }

    .up-char-card-avatar-placeholder {
        font-size: 1.8rem;
    }
}

@media (max-width: 400px) {

    .public-profile-banner {
        height: 104px;
    }

    .public-profile-grid {
        margin-top: -48px;
    }

    .public-profile-sidebar-card {
        padding: 18px 12px 14px;
    }

    .public-profile-avatar-ring {
        width: 84px;
        height: 84px;
    }

    .public-profile-avatar {
        font-size: 1.85rem;
    }

    .public-profile-name {
        font-size: 1.28rem;
    }

    .public-stat-value {
        font-size: 1.1rem;
    }

    .public-panel-title {
        font-size: 0.95rem;
    }

    /* Four one-line pairs stacked is 4 x 58; two columns of 145 keep the
       longest value ("Unspecified") on one line, so the pairing holds. */
    .public-about-value {
        font-size: 0.84rem;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 5 — FINAL MOBILE SWEEP (type floor + tap targets)
   ═══════════════════════════════════════════════════════════════════════════
   The last item of the mobile plan: a pass over the two things the per-surface
   passes each left a little of behind — text under 11px and hit boxes under
   --tap-min. Both lists came from measuring, not reading: a scan of every
   computed font-size across 11 dashboard pages at 360x740, and an iframe
   harness that reports every visible interactive element whose box is under
   44px in either axis.

   This block is LAST in the file on purpose. Several offenders are set inside
   earlier `@media (max-width: 560px)` / `(400px)` blocks; at 360 those queries
   and this one all match, so source order decides and a plain 768 rule here
   wins. Where the offender is ID-scoped (#page-shop, #page-quests) the rule
   below carries the same ID — specificity outranks order.

   Two carve-outs, applied consistently and deliberately:
     - font-size under 11px is fine on an <i> that renders a dot, arrow or
       glyph rather than text (.card-tag i, .stg-save-status i, list ::before
       bullets). Those are icons at their intended size, not small copy.
     - a hit box may stop short of 44px when the control sits in a dense
       overlay or beside a bigger primary action, provided it still clears
       WCAG 2.5.8's 24px floor. Each such stop is noted at the rule.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {

    /* ---- 1. Type floor: nothing under 11px (0.7rem) ----------------------
       0.7rem is the floor rather than a scale step because most of these are
       uppercase micro-labels with letter-spacing, where 11.2px is already the
       point the tracking starts to fall apart. Ordered as they were measured,
       smallest first, so the diff reads against the audit. */

    /* 8.64px at <=400, 9.28px at <=768 — the smallest text in the app, and it
       labels the Plus hero's stat numbers. */
    .premium-hero .stat-label {
        font-size: 0.7rem;
    }

    /* 8.8px at <=560 on a flag that has to be legible over cosmetic art. */
    .cosmetic-equipped-flag {
        font-size: 0.7rem;
    }

    .profile-stat-label,
    .card-footer-bar,
    .card-play-hint,
    .cosmetic-tier,
    .achievement-rewards > span,
    .card-creator-avatar--initial,
    .card-creator-title,
    .card-tag,
    .alw-label,
    .achievement-xp-badge,
    .card-stat-badge,
    .diary-day-divider-pill,
    .shop-card-tier-ribbon,
    .shop-card-popularity,
    .ll-plan-badge,
    .qa-group-label,
    .voice-notice,
    .dob-weekdays span,
    .profile-level-badge,
    .card-owner-btn,
    .reward-chip-sub,
    .diary-time-tag,
    .shop-card-meter-label,
    .qm-card-badge,
    .facet-group .fg-count,
    .chat-status .status-text,
    .results-header .results-count {
        font-size: 0.7rem;
    }

    /* Same floor, ID-matched: these live under #page-shop / #page-quests
       rules that a bare class selector cannot outrank. */
    #page-shop .shop-hero-balance-label,
    #page-quests .shop-hero-balance-label,
    #page-shop .shop-hero-balance-sub,
    #page-shop .hero-badge {
        font-size: 0.7rem;
    }
}

@media (max-width: 768px) {

    /* ---- 2. Tap targets -------------------------------------------------
       Measured with a harness that walks every button / a[href] / input /
       select / [role] on the page and reports any box under 44px in either
       axis, deduped by selector. What follows is that list. Three groups from
       the raw output are NOT bugs: `.account-menu-item` reported 43 only
       because the closed account menu carries `transform: scale(0.98)`
       (styles.css:1241) — 44 x 0.98 — and the same holds for everything
       inside the closed `.pronoun-menu` / `.dob-pop` (10926 / 11128), which
       is why re-measuring the popover controls below returns 43 / 36 / 31
       rather than 44 / 36 / 32. `.nav-item` / `.filter-chip` were already
       raised at 13277. */

    /* PIN entry: six boxes in a ~300px modal. The 380px block (7884) shrank
       them to 36x44 to make the row fit; flexing instead lets them fill the
       row exactly — 42px each at 360, measured, which is as wide as six
       boxes and five gaps can be at this width — and capped so they never
       look like text fields on a tablet. Width is the only axis that stays
       under 44 and it is set by the row, not by a guess. */
    .pin-input-group {
        gap: 6px;
    }

    .pin-box {
        flex: 1 1 0;
        width: auto;
        min-width: 0;
        max-width: 48px;
        height: 48px;
    }

    /* The composer textarea measured 32px tall: 16px text with 6px of padding
       (6114). It is the single most-tapped control in the app, so it gets the
       full 44 and the container gives up 2px of its own padding to pay for
       part of it. `min-height` also survives the JS auto-grow, which writes
       an inline `height`. */
    .chat-input-container textarea {
        min-height: var(--tap-min);
    }

    .chat-input-container {
        padding: 3px 5px 3px 14px;
    }

    /* Diary composer, same reasoning (base min-height was 40 + 9px padding). */
    #diary-entry-input {
        min-height: var(--tap-min);
    }

    /* .glass-btn measured 42.7 tall at 360 on the diary unlock screen, and the
       arithmetic says it always will: 12px + 12px of padding (2381) around an
       18.7px line box from 16px text is 42.7, so it is short by the 1.3px that
       the line-height happens not to supply. Same one-pixel argument as the
       "All" chip below — not a usability problem by itself, worth a line because
       it is the last sub-44 control on the diary screen and because
       `min-height` costs nothing on the nine other .glass-btn instances
       (diary setup/unlock/settings, memory edit save/cancel, achievement claim,
       unlock banner), all of which already clear it.

       min-height rather than more padding: the button is already
       `align-items: center`, so this re-centres the icon/label pair for free
       and cannot shift any button that already passes. */
    .glass-btn {
        min-height: var(--tap-min);
    }

    /* The discover search field measured 20px tall — the visual pill is 40,
       but all of that is `.search-container` padding, which does not focus the
       input. Moving the height into the input makes the whole pill tappable
       and leaves the pill the same size. */
    .search-container {
        padding: 0 16px;
    }

    .search-container input {
        min-height: var(--tap-min);
        align-self: stretch;
    }

    /* Back buttons. All three are absolutely positioned over a hero, so
       growing them reflows nothing; the character-profile one needs its ID
       back to outrank 17563. */
    #quests-back-btn,
    .settings-back-btn,
    #page-character-profile .settings-back-btn {
        min-height: var(--tap-min);
    }

    /* Selects and single-line fields in the Forge and Settings: 43px, one
       short, because 12px padding plus a 16px line rounds down. */
    .premium-input,
    .pronoun-trigger,
    .dob-trigger {
        min-height: var(--tap-min);
    }

    .pronoun-option {
        min-height: var(--tap-min);
    }

    /* ---- Dense popovers: documented stops short of 44 -------------------
       The DOB picker is three selects, two arrows and two footer buttons
       inside a ~300px popover that already has to fit above the keyboard.
       Sizing every control to 44 makes the popover taller than the field it
       is attached to. These clear WCAG 2.5.8's 24px minimum with room, which
       is the same call made for `.toggle-password` (40) and
       `.diary-exchange-delete-btn` (36). */
    .dob-nav {
        width: 32px;
        height: 32px;
    }

    .dob-sel {
        min-height: 36px;
    }

    .dob-foot-btn {
        min-height: 32px;
        padding: 6px 12px;
    }

    /* The diary header's memory pill measured 18px tall: 0.72rem text with
       `padding: 2px 0` and no background, i.e. a bare text link. It stays a
       text link — a filled 44px button would compete with the header's real
       actions — but the padding takes the box to 32, past WCAG's 24. */
    .diary-memory-pill {
        padding: 9px 0;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   TOUCH INPUT — feel, not layout
   Keyed on the pointer CAPABILITY, not the viewport width. That distinction
   matters: a desktop browser dragged to 700px wide matches
   `max-width: 768px` but still has a real mouse, and width-keyed hover
   resets take its hover states away for no reason. The two existing
   width-based resets (~5790, ~6000) are deliberately left alone — they are
   part of the mobile layout pass, and narrowing their condition now would
   be a behaviour change unrelated to touch.

   Both `hover: none` and `pointer: coarse` are listed because a device that
   misreports one usually still reports the other, and this block going
   silently inert is invisible — nothing breaks, the app just keeps feeling
   like a web page. A desktop mouse is `hover: hover` + `pointer: fine` and
   matches neither, so nothing is taken from it. (Emulator behaviour is worth
   checking rather than assuming: an API 36 Pixel 6 AVD driven by the host
   mouse reports `pointer: coarse` + `hover: none`, so this block IS live and
   measurable there — verified via matchMedia over CDP. Older AVDs have been
   seen to report fine/hover instead, which makes the block inert; if a touch
   rule appears to do nothing, read matchMedia first and reach for
   Emulation.setEmulatedMedia only if the AVD really is reporting a mouse.)

   Everything here ships to loreloom.chat as well as the packaged app; a
   phone browser matches this block exactly as the WebView does.
   ═══════════════════════════════════════════════════════════════════ */

@media (hover: none), (pointer: coarse) {

    /* A tap leaves :hover applied until the next tap lands somewhere else, so
       every lift/scale below would freeze mid-air — a card stays raised, a
       button stays nudged, and neither returns until you tap elsewhere. Only
       `transform` is reset: the base values here are multi-layer box-shadows,
       and a slightly-too-elevated shadow is invisible next to a frozen 2px
       lift, so resetting those too would be a much larger diff for no gain.

       Reveal-on-hover rules are NOT in this list — they set `opacity`
       alongside `transform`, and zeroing only the transform would leave the
       revealed control at its hidden offset. The codebase already handles
       those separately (`.card-owner-actions` ~2790,
       `.diary-exchange-delete-btn` ~7760/~20010).

       Tap feedback comes from the existing `:active` states instead, which is
       why this can be a blanket reset rather than a per-element judgement. */
    #back-to-discover.glass-back-btn:hover,
    #back-to-discover.glass-back-btn:hover i,
    #scroll-to-bottom-btn:hover,
    #send-btn:hover,
    #start-scene-btn:hover,
    .achievement-card.is-locked:hover,
    .ai-refine-btn:hover,
    .card-owner-btn:hover,
    .char-profile-start-btn:hover,
    .character-card:hover,
    .character-card:hover .card-hero-wrap img,
    .chat-character-info img:hover,
    .choice-btn:hover,
    .collection-shop-link:hover,
    .collection-shop-link:hover>.fa-chevron-right,
    .confirm-btn.danger:hover,
    .confirm-btn.primary:hover,
    .cosmetic-action-btn.is-equip:hover,
    .cosmetic-buy-btn:hover:not(:disabled),
    .create-btn:hover,
    .create-dropdown .dropdown-item:hover .dropdown-arrow,
    .create-dropdown .dropdown-item:hover .dropdown-icon-wrap.purple,
    .delete-chat-btn:hover,
    .diary-avatar-preview:hover,
    .diary-emoji-item:hover,
    .diary-exchange-delete-btn:hover,
    .diary-input-container #diary-send-btn:hover,
    .diary-input-container .diary-emoji-btn:hover,
    .diary-settings-btn:hover,
    .feature-pick-item:hover,
    .featured-card:hover,
    .filter-chip:hover,
    .glass-btn.primary:hover,
    .glass-btn.secondary:hover,
    .hero-cta-primary:hover,
    .hover-chip:hover .qa-emoji,
    .icon-btn:hover,
    .inv-swatch:hover,
    .ll-upgrade-close:hover,
    .ll-upgrade-continue-btn:hover,
    .macro-btn:hover,
    .memory-card:hover,
    .message-wrapper.system-error .system-error-retry:hover,
    .message-wrapper:hover .message-avatar .avatar,
    .message-wrapper:hover .message-content,
    .nav-item:hover:not(.active),
    .nav-item:hover:not(.active) i,
    .pagination-btn:hover:not(:disabled),
    .plus-celebrate-btn:hover,
    .profile-stat-card:hover,
    .profile-trait-chip:hover,
    .public-back-btn:hover,
    .public-back-btn:hover i,
    .public-mini-card:hover,
    .public-profile-sidebar-card:hover,
    .public-profile-sidebar-card:hover .public-profile-avatar-ring,
    .public-stat-card:hover,
    .qm-btn-ad:hover,
    .qm-btn-orbs:hover,
    .qm-card:hover,
    .qm-card:hover .qm-card-icon-wrap,
    .qm-close:hover,
    .qm-sub-primary:hover,
    .quest-card.is-claimable .quest-claim-btn:hover,
    .reward-chip:hover,
    .reward-chip:hover .reward-chip-arrow,
    .reward-return-btn:hover,
    .reward-watch-btn:hover:not(:disabled),
    .settings-back-btn:hover,
    .settings-back-btn:hover .back-arrow-icon,
    .shop-buy-btn:hover,
    .shop-card--cosmetic:hover,
    .shop-card--flagship:hover,
    .shop-card:hover,
    .shop-card:hover .shop-card-icon-wrap,
    .sidebar-account-shell.plus .sidebar-account-toggle:hover,
    .sidebar-account-toggle:hover,
    .sidebar-account-toggle:hover .account-avatar,
    .sidebar-chat-item:hover,
    .sidebar-chat-item:hover img,
    .sidebar-upgrade-btn:hover,
    .sidebar-upgrade-btn:hover .upgrade-arrow,
    .sidebar-upgrade-btn:hover .upgrade-icon,
    .stg-acc-avatar-edit-btn:hover,
    .stg-back-btn:hover,
    .stg-back-btn:hover i,
    .stg-profile-card:hover,
    .stg-save-btn:hover:not(:disabled),
    .sub-card:hover,
    .sub-upgrade-btn:hover,
    .super-create-btn:hover,
    .super-create-btn:hover .create-btn-icon,
    .ultimate-save-btn:hover,
    .up-char-card:hover,
    .up-follow-btn:hover,
    .up-social-card:hover {
        transform: none;
    }

    /* ---- Exception: the Upgrade to Loom+ button ----
       Its hover does not just deepen the dark shadow. It swaps the resting
       drop for a 24px violet glow plus a 16px rgba(110, 142, 251, …) BLUE
       ring, brightens the border from 0.25 to 0.6 alpha, and fades a
       full-opacity radial overlay in. Latched after a tap, all of that paints a
       visible blue halo around the button; on the emulator, driven by a real
       mouse yet reporting hover:none, it is on almost permanently. This is the
       "extra layer of blue around the upgrade button" from the first report.

       Measured on device by driving CDP Input.dispatchMouseEvent — a synthetic
       mouseover does NOT change :hover, that state comes from the compositor's
       hit test. Just outside the left border the surround went from 3/255 off
       the sidebar background at rest to 82/255 with hover held, blue-excess
       (B − max(R, G)) climbing +8 → +20 across the halo and reaching +49 at
       the border itself. The account card immediately below measures 0/255 on
       both sides — that is the control this is compared against.

       Every hovered property is held at its resting value (base ~8449-8600).
       :not(:active) keeps the press feedback at ~8508 alive: on touch :hover
       and :active are both true during a press, so without it this rule would
       suppress the one state that should paint. */
    .sidebar-upgrade-btn:hover:not(:active) {
        border-color: rgba(167, 119, 227, 0.25);
        background-image: linear-gradient(135deg, rgba(167, 119, 227, 0.12) 0%, rgba(110, 142, 251, 0.12) 100%);
    }

    /* The resting outer drop shadow goes too — at rest and latched from one
       declaration, so the two can never disagree. On a #101014 sidebar a 16px
       black shadow reads as a faint dark smudge (measured 3-7/255) that the
       account card does not have; it is the second, much fainter half of the
       same "extra layer around the button" report. The inset highlight stays:
       it is inside the border box and cannot halo. So do the gradient fill and
       the violet border — those are what mark this as the premium CTA. */
    .sidebar-upgrade-btn,
    .sidebar-upgrade-btn:hover:not(:active) {
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
    }

    /* The children latch their own gold/glow treatment. Unlike the button
       itself these reset even while pressed, so a tap shows only the violet
       :active glow instead of flashing a whole hover state.

       `animation: none` is load-bearing, not tidiness: upgradePulse animates
       opacity 0.4 → 0.8, and an animation origin outranks a normal author
       declaration, so opacity:0 alone would lose to it. The ~18882 perf block
       already stops it below 768px, which is why the phone would have looked
       fixed while a touch tablet still glowed. */
    .sidebar-upgrade-btn:hover .upgrade-btn-glow {
        opacity: 0;
        animation: none;
    }

    .sidebar-upgrade-btn:hover .upgrade-icon {
        filter: drop-shadow(0 2px 4px rgba(255, 215, 0, 0.3));
    }

    /* background-IMAGE, never the `background` shorthand: the label and the gem
       are gradient-clipped text (`-webkit-background-clip: text` +
       `-webkit-text-fill-color: transparent`, ~8540/~8570), and the shorthand
       resets background-clip to border-box. Doing that here painted the
       gradient across the whole box with the glyphs still transparent — a solid
       white-to-lilac bar where the label should be. Computed style looked
       correct because only the clip had changed; the screenshot caught it. */
    .sidebar-upgrade-btn:hover .upgrade-text {
        background-image: linear-gradient(90deg, #ffffff, #e2d0ff);
    }

    .sidebar-upgrade-btn:hover .upgrade-arrow {
        color: rgba(255, 255, 255, 0.35);
    }

    /* ---- Exception: the three controls that latch a COLOURED glow ----
       Same reasoning as the Upgrade button, same defect the user reported.
       The blanket `transform: none` above deliberately leaves shadows alone,
       and that holds for every control whose hover only deepens an existing
       dark drop — invisible on a dark ground, not worth the diff. It stops
       holding the moment a rule swaps in a NEW HUE, because then the latched
       state is a coloured halo sitting on the element until you tap elsewhere.

       Measured live on device with driven Input.dispatchMouseEvent (a
       synthetic mouseover cannot move :hover — that state comes from the
       compositor's hit test):

         .reward-chip      rest none  → hover 0 12px 28px -14px rgba(110,142,251,.8)
         .earn-orbs-chip   rest none  → hover 0 12px 28px -14px rgba(247,183,51,.8)
         .public-stat-card rest 0 6px 16px rgba(0,0,0,.18)   (mobile, ~20843)
                                      → hover 0 12px 28px rgba(167,119,227,.15)

       Only these three of the ~60 guarded selectors qualified; the rest
       (.shop-card ×3 views, .achievement-card) really do just deepen a dark
       shadow, so they stay as they are. Each property is pinned to its own
       resting value from the base rules (~1593-1599, ~13976-13979,
       ~14036-14039) — checking for a narrower mobile override first, which
       .public-stat-card turned out to have and the other two did not.

       :not(:active) for the same reason as above — on touch :hover and
       :active are both true during a press, and the :active states are what
       paint tap feedback. */
    .reward-chip:hover:not(:active),
    .earn-orbs-chip:hover:not(:active) {
        box-shadow: none;
    }

    .reward-chip:hover:not(:active) {
        border-color: rgba(167, 119, 227, 0.30);
    }

    .earn-orbs-chip:hover:not(:active) {
        border-color: rgba(247, 183, 51, 0.30);
    }

    .public-stat-card:hover:not(:active) {
        background: rgba(18, 14, 27, 0.5);
        border-color: rgba(255, 255, 255, 0.05);
        /* NOT the desktop base 0 8px 20px/.15 (~1599): a mobile override at
           ~20843 resets this to 0 6px 16px/.18, and THAT is the resting value a
           phone shows, so the held-hover must match it or the two disagree by a
           couple of px. Verified on device: rest and hover both read
           rgba(0,0,0,0.18) 0px 6px 16px after this. */
        box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
    }

    /* Long-pressing app chrome pops iOS/Android's "copy link / save image"
       sheet over the UI, and a slightly-long tap on a nav item selects its
       label text — both read as "this is a web page", not an app.

       Scoped to chrome ONLY, by listing the chrome rather than putting this on
       body with exceptions: message bodies, diary entries and code blocks must
       stay selectable, because copying a reply is something people actually
       do and this is the easy way to break it silently. No input or textarea
       appears below either — user-select:none on a field breaks caret drag
       selection inside it. */
    .mobile-tab-bar,
    .tab-item,
    .nav-item,
    .icon-btn,
    .glass-btn,
    .glass-back-btn,
    .mobile-menu-btn,
    .filter-chip,
    .chat-header,
    .super-create-btn,
    .create-btn,
    .sidebar-account-toggle {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        user-select: none;
    }

    /* Tap targets, measured on an API 36 device against --tap-min: 44px.
       The chip's 8px/16px padding already makes it 44 TALL, and every chip with a
       real word in it clears 44 wide on its own -- so this fixes exactly one chip,
       "All", which at two characters came out 43x44. One pixel is not a usability
       problem by itself; it is worth a line because min-width costs nothing on the
       chips that already pass, and because a filter row where one chip is a
       different size than its neighbours is the kind of thing that gets "fixed"
       later by padding the label asymmetrically. */
    .filter-chip {
        min-width: var(--tap-min, 44px);
    }

    /* Discover's pagination row — the last sub-minimum control on any public page.
       Measured on an API 36 device (411x914): all seven buttons came back 34x34
       against --tap-min: 44px, and 34 is what the max-width:640px block above
       deliberately picked, so this must grow the hit area without growing the
       graphic.

       Why not simply `min-width/height: var(--tap-min)`: seven 44px buttons plus
       six gaps needs 332px, and the bar only measures 373.7px at 411dp -- fine
       here, but a 360dp phone leaves ~322px and the row wraps to two lines. The
       ::after keeps the drawn size and costs 298px instead.

       The ::after is sized rather than inset by a fixed negative amount, because
       the drawn size is not constant: 34px under 640px, 38px above it (a coarse-
       pointer tablet). Centring a `min-width/min-height: --tap-min` region on the
       button lands the right overhang either way -- 5px per side at 34, 3px at 38.

       The gap bump is NOT cosmetic and has to ship in the same block: at the
       existing 4px gap, two neighbours' expanded regions would overlap by 6px, and
       the topmost one wins -- so tapping page 3 could select page 2, which is
       worse than a 34px button. 10px is 2x the widest overhang, so the regions
       meet exactly and every pixel between two buttons belongs to one of them.

       position:relative is needed here (unlike .close-btn/.pw-toggle in
       landing.css, which are already absolutely positioned and so are their own
       containing blocks). No z-index: all seven siblings are positioned, so they
       paint in DOM order and the regions no longer overlap anyway. */
    .pagination-bar {
        gap: 10px;
    }

    .pagination-btn {
        position: relative;
    }

    .pagination-btn::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        height: 100%;
        min-width: var(--tap-min, 44px);
        min-height: var(--tap-min, 44px);
    }
}
