/* ── base.css · tokens, reset, intro loader, bottom nav ── */


    /* ══ Design tokens ══════════════════════════════════════
       :root  = base palette — the site's original dark tokens, kept
                byte-for-byte identical in appearance.
       html[data-theme="dark"|"graydark"|"light"] re-map the same
       tokens; "system" resolves to dark/light in the boot script
       (see <head>) and the theme engine (near #themePage).
       ├─ base tokens  — main content surfaces + violet accent
       ├─ --v* tokens  — muted "graphite" layer (hero / profile /
       │                 admin / notifications / auth pages, which
       │                 previously hardcoded these per-scope)
       └─ --f* tokens  — footer "floor" layer (deepest band)
       Hexes are uppercase on purpose: lowercase literals were
       mass-migrated to var() refs, uppercase marks definitions. */
    :root{
      color-scheme: dark;
      --tc:#8A8F98;
      --bg:#08080D; --sur:#131320; --sur2:#191927; /* all-digit hexes: sweep-tools must skip token definition lines */
      --bdr:#26263A; --bdrh:#3A3A56;
      --tx:#F5F4FB; --tx2-rgb:214,212,232; --txd:rgba(var(--tx2-rgb),.62);
      --pg:#8B5CF6; --pb:#B9A3FB; --ps:#C9B8FD; --pgd:#7C3AED;
      --pg-rgb:139,92,246; --pgd-rgb:124,58,237;
      --pdim:rgba(var(--pg-rgb),.16);
      --vbg:#050308; --vsur:#0D0A14; --vsur2:#120E1C;
      --vtx:#F0E8FF; --vtx2-rgb:196,181,253; --vtxd:rgba(var(--vtx2-rgb),.6);
      --vtc:#9A9EA5; --vtc-rgb:154,158,165;
      --vpg:#8A8F98; --vpb:#B8BCC2; --vps:#CFD2D6;
      --fbg:#040406; --fsur:#0B0B13; --fsur2:#101018;
      --fbdr:#1C1C2C; --fbdrh:#2C2C44;
      /* fonts — two families only: Sora (display) + Inter (UI/body).
         --fm keeps its name (96 call sites) but its role is now
         "UI label / eyebrow" rendered in Inter; the mono-era
         tracking on those labels reads as a deliberate style. */
      --fd:'Sora','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
      --fb:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
      --fm:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
      --text-on-accent:#FFFFFF; /* other semantic text tokens: see the *-level alias rule below */

      /* ── Semantic status + role colours ──────────────────────────
         Body-level overlays (report modal, community modals, mini profile
         card) render under the GLOBAL theme, so they can't use the bright
         hexes the dark-scoped panels rely on — those wash out on Light.
         These tokens carry a dark-theme default and are re-mapped for
         Light below, so every theme keeps readable contrast. */
      --danger:#fca5a5;
      /* Brand red — the "Artz" in the logo lockup. Owned by a token so
         the hero highlight and any future red accents stay in step
         with the badge instead of scattering hexes. */
      --brand-red:#EF4444;
      --danger-strong:#dc2626;
      --danger-bg:rgba(220,38,38,.14);
      --danger-bg-hover:rgba(220,38,38,.24);
      --danger-bd:rgba(220,38,38,.45);
      --role-owner:#fbbf24;  --role-owner-bd:rgba(251,191,36,.45);
      --role-admin:#f87171;  --role-admin-bd:rgba(248,113,113,.45);
      --role-srmod:#60a5fa;  --role-srmod-bd:rgba(96,165,250,.45);
      --role-jrmod:#34d399;  --role-jrmod-bd:rgba(52,211,153,.45);

      /* ── Milestone colour system ─────────────────────────────────
         One colour per rank, used flat (no gradient / glow / shadow)
         for BOTH the artist's display name and their milestone ribbon,
         so the two always match — that match IS the system.

           --msN        the tier's identity colour. Fills the ribbon and
                        the milestone badge, and colours the name on the
                        dark themes, where every tier clears 5.2:1.
           --msN-name   the NAME colour only. Identical to --msN on dark;
                        re-mapped below on Light, where the raw hues sit
                        at 1.5–4.2:1 on white and are unreadable. Same
                        hue, dropped in lightness until it clears 4.5:1,
                        so a Legend still reads as gold — just a gold
                        that survives a white background.
           --ms-ink     text ON a filled badge. See the note in the Light
                        block for why this is near-black and not white. */
      --ms1:#BFC5D2;  --ms1-name:#BFC5D2;   /* LVL 5   New Artist      */
      --ms2:#22C55E;  --ms2-name:#22C55E;   /* LVL 10  Sketch Explorer */
      --ms3:#3B82F6;  --ms3-name:#3B82F6;   /* LVL 20  Creative Soul   */
      --ms4:#8B5CF6;  --ms4-name:#8B5CF6;   /* LVL 35  Gallery Artist  */
      --ms5:#F59E0B;  --ms5-name:#F59E0B;   /* LVL 50  Community Star  */
      --ms6:#EF4444;  --ms6-name:#EF4444;   /* LVL 70  Master Creator  */
      --ms7:#06B6D4;  --ms7-name:#06B6D4;   /* LVL 85  Elite Artist    */
      --ms8:#FACC15;  --ms8-name:#FACC15;   /* LVL 100 DigiArtz Legend */
      --ms-ink:#0B0B12;
    }
    /* ── Dark (default) — neutral near-black, monochrome WHITE accent
       (no violet: accent tokens re-mapped to white/off-white). ── */
    html[data-theme="dark"]{
      --tc:#8B8E96;
      --brand-red:#EF4444;
      --bg:#0A0A0E; --sur:#141419; --sur2:#1A1A21;
      --bdr:#262630; --bdrh:#3A3A47;
      --tx:#F4F4F7; --tx2-rgb:219,219,229;
      --pg:#F5F5F7; --pb:#FFFFFF; --ps:#FFFFFF; --pgd:#C9C9D4;
      --pg-rgb:245,245,247; --pgd-rgb:201,201,212;
      --pdim:rgba(var(--pg-rgb),.10);
      --text-on-accent:#0A0A0E;
      --vbg:#060609; --vsur:#101015; --vsur2:#16161C;
      --vtx:#F2F2F5; --vtx2-rgb:212,212,222;
      --fbg:#040406; --fsur:#0B0B10; --fsur2:#101016;
      --fbdr:#1C1C26; --fbdrh:#2C2C3A;
    }
    /* ── Gray Dark — softer graphite, monochrome WHITE accent
       (no violet: accent tokens re-mapped to white/off-white). ── */
    html[data-theme="graydark"]{
      --tc:#94979F;
      --brand-red:#EF4444;
      --bg:#131317; --sur:#1C1C22; --sur2:#23232B;
      --bdr:#30303A; --bdrh:#444450;
      --tx:#F2F2F5; --tx2-rgb:216,216,226;
      --pg:#F4F4F6; --pb:#FFFFFF; --ps:#FFFFFF; --pgd:#C6C6CE;
      --pg-rgb:244,244,246; --pgd-rgb:198,198,206;
      --pdim:rgba(var(--pg-rgb),.10);
      --text-on-accent:#131317;
      --vbg:#0E0E12; --vsur:#17171D; --vsur2:#1E1E25;
      --vtx:#F0F0F4; --vtx2-rgb:208,208,218;
      --fbg:#0B0B0F; --fsur:#14141A; --fsur2:#1A1A21;
      --fbdr:#26262F; --fbdrh:#383844;
    }
    /* ── Light — clean + bright, monochrome BLACK accent
       (no violet: accent tokens re-mapped to near-black). ── */
    html[data-theme="light"]{
      color-scheme:light;
      --tc:#6E6C80;
      --bg:#F6F6F9; --sur:#FFFFFF; --sur2:#F1F0F6;
      --bdr:#E3E2EE; --bdrh:#CBC9DD;
      --tx:#1A1826; --tx2-rgb:62,60,86; --txd:rgba(var(--tx2-rgb),.72);
      --pg:#17151F; --pb:#0F0E15; --ps:#000000; --pgd:#2E2C3A;
      --pg-rgb:23,21,31; --pgd-rgb:46,44,58;
      --pdim:rgba(var(--pg-rgb),.07);
      --text-on-accent:#FFFFFF;
      --vbg:#EFEEF4; --vsur:#FFFFFF; --vsur2:#F5F4F9;
      --vtx:#201E2C; --vtx2-rgb:70,68,94; --vtxd:rgba(var(--vtx2-rgb),.68);
      --vtc:#71747E; --vtc-rgb:113,116,126;
      --vpg:#5F626C; --vpb:#4D505A; --vps:#3D404A;
      --fbg:#E9E8F0; --fsur:#F4F3F8; --fsur2:#EEEDF4;
      --fbdr:#D8D7E4; --fbdrh:#C2C0D4;

      /* Status + role colours darkened for contrast on white surfaces —
         the dark-theme pastels (#fca5a5, #fbbf24, #34d399…) are close to
         unreadable here. */
      --danger:#B3261E;
      --brand-red:#DC2626; /* logo red as-is — plenty of contrast on white */
      --danger-strong:#B3261E;
      --danger-bg:rgba(179,38,30,.08);
      --danger-bg-hover:rgba(179,38,30,.14);
      --danger-bd:rgba(179,38,30,.38);
      --role-owner:#8A5A00;  --role-owner-bd:rgba(138,90,0,.38);
      --role-admin:#B3261E;  --role-admin-bd:rgba(179,38,30,.38);
      --role-srmod:#1D4ED8;  --role-srmod-bd:rgba(29,78,216,.38);
      --role-jrmod:#046B4E;  --role-jrmod-bd:rgba(4,107,78,.38);

      /* Milestone NAME colours darkened for white surfaces. The ribbon
         fills (--ms1..--ms8) are deliberately NOT re-mapped — a filled
         chip carries its own background, so the brand colour stays exact
         in every theme. Only text sitting directly on the page changes.
         Measured on #FFFFFF: raw hues score 1.53:1 (Legend Gold) to
         4.23:1 (Royal Purple); these all clear 4.50:1 (WCAG AA). */
      --ms1-name:#687695;   /* Soft Gray     4.55:1 */
      --ms2-name:#178841;   /* Emerald       4.53:1 */
      --ms3-name:#1E6FF5;   /* Sapphire      4.52:1 */
      --ms4-name:#8655F6;   /* Royal Purple  4.52:1 */
      --ms5-name:#A56A07;   /* Amber Gold    4.50:1 */
      --ms6-name:#EB1515;   /* Crimson       4.52:1 */
      --ms7-name:#048196;   /* Cyan          4.58:1 */
      --ms8-name:#8F7303;   /* Legend Gold   4.54:1 */
    }
    /* ── Semantic text tokens ─────────────────────────────────
       Declared on * (not :root) deliberately: a custom property
       declared only on :root bakes in :root's values — descendants
       inherit the COMPUTED alias, so scoped re-declarations of
       --tx/--pb (profile graphite layer, footer floor) would be
       ignored. Declaring per-element makes each alias re-resolve
       against the tokens the element actually inherits, so these
       are theme-aware AND scope-aware. Trade-off: setting e.g.
       --text-primary directly on a container will not inherit —
       set the underlying token (--tx) instead. ── */
    *{
      --text-primary:var(--tx);   --text-secondary:var(--txd);
      --text-muted:var(--tc);     --heading-color:var(--tx);
      --link-color:var(--pb);     --border-color:var(--bdr);
    }
    /* ── Rendering perf: below-fold sections skip style/layout/paint
       until scrolled near. contain-intrinsic-size's `auto` keyword
       remembers the real size after first render, so the scrollbar
       never jumps. #connect is deliberately NOT listed — it contains
       position:fixed modals whose containing block paint containment
       would hijack. ── */
    #artworks,#subOverview,footer.siteFooter{
      content-visibility:auto;
    }
    #artworks{contain-intrinsic-size:auto 900px;}
    #subOverview{contain-intrinsic-size:auto 700px;}
    footer.siteFooter{contain-intrinsic-size:auto 480px;}

    /* ══ Typography base — applies in every theme; classed styles
       keep precedence, this layer only sets sane defaults. ══ */
    body{line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}
    h1,h2,h3,h4,h5,h6{color:var(--heading-color);line-height:1.15;}
    h1{font-weight:800;letter-spacing:-.02em;}
    h2,h3{font-weight:700;letter-spacing:-.01em;}
    a{color:var(--link-color);}
    kbd,.avZoomPct{font-variant-numeric:tabular-nums;}

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

    html{scroll-behavior:smooth;background:var(--vbg);}
    body{background:var(--bg);color:var(--tx);font-family:var(--fb);overflow-x:hidden;min-height:100vh;}

    /* INTRO — dark/violet theme, matching the Community page
       (--cmBg var(--bg), --cmAccent var(--pg), --cmAccentSoft var(--pb)) */
    /* Loading veil — transparent but input-opaque. The site paints in
       behind it while pointer-events:auto swallows every interaction;
       a faint tint keeps the spinner readable on any content without
       hiding the page loading underneath. Short .3s fade on reveal. */
    #intro{position:fixed;inset:0;z-index:9999;background:color-mix(in srgb,var(--bg) 28%,transparent);display:flex;align-items:center;justify-content:center;flex-direction:column;gap:.9rem;opacity:1;transition:opacity .3s ease;pointer-events:auto;}
    #intro.iHide{opacity:0;pointer-events:none;}
    #intro.iGone{visibility:hidden;}
    .iSpin{width:44px;height:44px;border-radius:50%;border:3px solid rgba(var(--pg-rgb),.18);border-top-color:var(--pg);animation:iSpin .8s linear infinite;}
    @keyframes iSpin{to{transform:rotate(360deg)}}
    .iTxt{font-family:var(--fm);font-size:.72rem;letter-spacing:.3em;color:var(--tx);text-shadow:0 1px 6px rgba(0,0,0,.35);}
    @media(prefers-reduced-motion:reduce){.iSpin{animation-duration:1.6s;}}


    /* =========================================================
       FLOATING BOTTOM NAVIGATION
       ├─ Bar (#bnNav) — near-full-width pill, icon-only
       │   └─ Items (.bnItem) × 5 — circular icon buttons,
       │       Home / Gallery / Comic / Community / Profile,
       │       evenly spaced with justify-content:space-between
       │   z-index sits above every full-page panel (gallery,
       │   comments, comic, subscription) so the bar
       │   stays visible/usable across the whole site, not just
       │   the hero section.
       (AI Assistant button #zeoBtn now lives up top, left of the
        notification icon — see .fpFloat widgets in the hero)
       ========================================================= */
    #bnNav{
      position:fixed;
      left:16px;right:16px;bottom:1.1rem;
      margin:0 auto;
      max-width:460px;
      z-index:1000;
      display:flex;align-items:center;justify-content:space-between;
      padding:.55rem 1rem;
      border-radius:999px;
      border:1px solid rgba(var(--pg-rgb),.22);
      box-shadow:0 10px 32px rgba(0,0,0,.5), inset 0 1px 0 rgba(var(--pg-rgb),.08);
      isolation:isolate;
      /* FIX: promote to its own GPU layer up front (translateZ(0))
         instead of letting the browser create one reactively mid-
         scroll — pairs with the ::before blur decoupling below to
         remove the last residual flicker on iOS Safari. */
      transform:translateZ(0);
      -webkit-transform:translateZ(0);
      will-change:transform;
    }
    /* FIX: background + blur moved off #bnNav itself and onto this
       ::before layer. WebKit's compositor flashes white on fast
       scroll when a position:fixed element ALSO carries
       backdrop-filter, because it has to re-snapshot the blur
       source for the SAME layer it's repositioning every frame.
       Putting the blur on a separate absolutely-positioned layer
       behind the content decouples "track the viewport" from
       "sample + blur the backdrop", which stops the flash. Visual
       result is pixel-identical. */
    #bnNav::before{
      content:'';
      position:absolute;
      inset:0;
      border-radius:inherit;
      background:linear-gradient(160deg,color-mix(in srgb,var(--sur) 92%,transparent) 0%,color-mix(in srgb,var(--bg) 94%,transparent) 100%);
      z-index:-1;
    }
    .bnItem{
      position:relative;
      display:flex;align-items:center;justify-content:center;
      flex:0 0 auto;
      width:58px;height:58px;
      border-radius:50%;
      background:none;border:none;cursor:pointer;
      color:rgba(var(--tx2-rgb),.62);
      transition:color .22s,background .22s,transform .15s;
    }
    .bnItem svg{width:26px;height:26px;display:block;stroke-width:1.8;transition:transform .22s cubic-bezier(.34,1.56,.64,1);}
    .bnItem:hover{color:var(--tx);background:rgba(var(--pg-rgb),.14);}
    .bnItem:active{transform:scale(.92);}
    .bnItem:focus-visible{outline:2px solid var(--pg);outline-offset:2px;}
    .bnItem.bnActive{color:var(--pb);background:rgba(var(--pg-rgb),.18);}
    .bnItem.bnActive svg{transform:translateY(-1px) scale(1.08);}
    .bnItem.bnActive::after{
      content:'';
      position:absolute;
      bottom:5px;left:50%;
      width:4px;height:4px;
      transform:translateX(-50%);
      border-radius:50%;
      background:var(--pg);
      box-shadow:0 0 6px var(--pg);
    }
    /* Profile item hosts the existing login/avatar swap buttons,
       sized to match the other icons exactly, still circular */
    .bnItem.bnProfile{padding:0;}
    .bnItem .nLoginBtn,
    .bnItem .nAvatarBtn{
      width:42px;height:42px;margin:0;border-radius:50%;
    }
    .bnItem .nLoginBtn svg{width:24px;height:24px;stroke-width:1.8;}
    .bnItem .nAvLetter{font-size:1.05rem;}

    /* Text labels removed — icon-only nav; .bnLabel spans are kept
       in markup for screen-reader / SEO continuity but hidden visually */
    .bnLabel{
      position:absolute;width:1px;height:1px;padding:0;margin:-1px;
      overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
    }

    @media(max-width:640px){
      #bnNav{bottom:.8rem;left:10px;right:10px;padding:.45rem .7rem;}
      .bnItem{width:52px;height:52px;}
      .bnItem svg{width:24px;height:24px;}
      .bnItem .nLoginBtn,
      .bnItem .nAvatarBtn{width:38px;height:38px;}
      .bnItem .nLoginBtn svg{width:22px;height:22px;}
    }

    #hero {
      position: relative;
      min-height: 1px;
      overflow: hidden;
      background: var(--bg);
    }

    /* ── Front-page floating widgets — brand badge + notification
       button. Fixed like the bottom nav, but only shown while the
       hero/main section is in view and no full-page overlay panel
       (login, subscription, comic arts, etc.) is open — see the
       hero-only-widgets script near the end of the file for the
       show/hide logic (adds/removes the .heroOut class below).
       Sizing uses clamp() throughout instead of a single breakpoint
       so both widgets scale smoothly across phones, tablets, and
       desktops rather than snapping at one fixed width. ── */
    .fpFloat {
      position: fixed;
      top: clamp(1rem, 2.6vw, 2rem);
      z-index: 1001;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 999px;
      border: 1px solid color-mix(in srgb,var(--vpg) 35%,transparent);
      box-shadow: 0 10px 32px rgba(5,3,8,.35), inset 0 1px 0 color-mix(in srgb,var(--vpg) 15%,transparent);
      color: var(--vtx);
      isolation: isolate;
      /* FIX: promote to its own GPU layer up front, same reasoning
         as #bnNav below. */
      transform: translateZ(0);
      -webkit-transform: translateZ(0);
      will-change: transform;
    }
    /* FIX: same decoupling as #bnNav — blur lives on its own layer
       behind the content instead of on the fixed element itself,
       so WebKit isn't re-snapshotting the backdrop for a layer
       it's simultaneously repositioning every scroll frame. */
    .fpFloat::before {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: inherit;
      background: linear-gradient(160deg,color-mix(in srgb,var(--vpg) 50%,transparent) 0%,color-mix(in srgb,var(--vpg) 30%,transparent) 100%);
      z-index: -1;
    }
    /* Hidden once the hero section has scrolled out of view — these
       widgets (brand badge, notification button, and #zeoBtn below)
       belong to the main/hero page only, not every section. */
    .fpFloat.heroOut,
    #zeoBtn.heroOut {
      opacity: 0;
      transform: translateZ(0);
      pointer-events: none;
    }

    /* ── Brand badge — top-left ── */
    .hBadge {
      left: clamp(1rem, 2.6vw, 2rem);
      width: clamp(112px, 24vw, 156px);
      height: clamp(44px, 8vw, 60px);
      font-family: var(--fd);
      font-weight: 800;
      font-size: clamp(.85rem, 2.6vw, 1.2rem);
      letter-spacing: .01em;
      pointer-events: none;
    }
    .hBadge .hBadgeD { color: #0a0a0a; }
    .hBadge .hBadgeA { color: var(--brand-red); }
    /* Less see-through than the shared .fpFloat::before default —
       flat 70%-opaque fill (only ~30% transparent) so the brand
       pill reads as solidly as the notification button next to it. */
    .hBadge::before { background: color-mix(in srgb,var(--vpg) 70%,transparent); }

    /* ── Notification button — top-right, plain circle ── */
    .hNotifBtn {
      right: clamp(1rem, 2.6vw, 2rem);
      width: clamp(42px, 8vw, 54px);
      height: clamp(42px, 8vw, 54px);
      border-radius: 50%;
      cursor: pointer;
    }
    .hNotifBtn svg { width: clamp(18px, 3.6vw, 22px); height: clamp(18px, 3.6vw, 22px); stroke-width: 1.8; display: block; }
    .hNotifBtn:hover { background: linear-gradient(160deg,color-mix(in srgb,var(--vpg) 50%,transparent) 0%,color-mix(in srgb,var(--vpg) 30%,transparent) 100%); }
    .hNotifBtn:active { transform: scale(.95); }
    .hNotifBtn:focus-visible { outline: 2px solid var(--pg); outline-offset: 2px; }
    /* Unread dot — hidden until an admin notification actually exists */
    .hNotifDot {
      position: absolute;
      top: 6px; right: 6px;
      width: 9px; height: 9px;
      border-radius: 50%;
      background: var(--pg);
      box-shadow: 0 0 6px var(--pg);
      display: none;
    }
    .hNotifBtn.hasUnread .hNotifDot { display: block; }


