/* deploy/site.css — generated once from the 5 public pages' previously-duplicated
   inline <style> blocks (SPA router migration, Phase 0). Not auto-regenerated —
   edit this file directly going forward, same as any other stylesheet.
   NOTE: body-class-scoped selectors (body.page-index &c.) are unreliable here — the
   SPA router swaps #page-content without rewriting <body class>, so the class reflects
   whichever page was loaded FIRST, not the one on screen. Scope by element/section
   instead. The one such rule that existed (.rm-labels at <=820px) was unscoped after
   it visibly broke on mobile; see the @media (max-width:820px) block. */

@font-face{
    font-family:'DBHelvethaica';
    src:url('/fonts/DBHelvethaica-Medium.ttf') format('truetype');
    font-weight:500;
    font-display:swap;
  }

@font-face{
    font-family:'DBHelvethaica';
    src:url('/fonts/DBHelvethaica-Bold.ttf') format('truetype');
    font-weight:700;
    font-display:swap;
  }

/* Underwater World theme (2026-08-29). Ocean blue / cyan / turquoise depth,
   coral pink + lavender accents, pearl-white surfaces. The legacy var names
   are kept on purpose so the ~150 places that reference --sand/--sun/--coral*
   shift to the new hues automatically instead of needing 150 edits.
   Semantic colors that must NOT re-theme (form errors, rank up/down) keep
   their own untouched tokens below. Two new tokens added for this pass:
   --lavender (secondary accent) and --reef (footer coral-reef silhouette). */
:root{
    --sky-top:#f6f7fb;
    --sky-mid:#eef8f7;
    --sea-deep:#3d405b;
    --sea-mid:#6cc3cf;
    --sea-light:#8bd3dd;
    --sand:#eef8f7;
    --sand-line:#c9ecec;
    --sun:#ffe0a3;
    --coral:#ffc7dd;
    --coral-deep:#f582ae;
    --card:#ffffff;
    --card-line:#dceeee;
    --ink:#3d405b;
    --ink-soft:#6b6e8c;
    --shadow: 0 14px 38px -18px rgba(61,64,91,0.35);
    --body-bg-a:#f6f7fb;
    --body-bg-b:#d9f2f2;
    --body-bg-c:#bfe8e8;

    /* new tokens introduced by this theme */
    --radius-lg:30px;
    --radius-md:22px;
    --radius-sm:14px;
    --glass:rgba(255,255,255,0.74);
    --glass-line:rgba(255,255,255,0.9);
    --shadow-soft: 0 10px 30px -16px rgba(61,64,91,0.28);
    --shadow-lift: 0 22px 46px -22px rgba(61,64,91,0.45);
    --accent-grad:var(--sea-light);
    --track-bg:#d9f2f2;
    --danger:#c2596e;
    --success:#4d9078;
    --heading-track:0.06em;
    --lavender:#c9bdf2;
  }

:root[data-theme="dark"]{
    --sky-top:#14152b;
    --sky-mid:#191c34;
    --sea-deep:#9fe0e6;
    --sea-mid:#3f8f92;
    --sea-light:#1f4a4d;
    --sand:#181b33;
    --sand-line:#282c4a;
    --sun:#d9b877;
    --coral:#ffc2dd;
    --coral-deep:#ff8fb8;
    --card:#1c1e38;
    --card-line:#2c2f4d;
    --ink:#eef0f7;
    --ink-soft:#a2a5c4;
    --shadow: 0 14px 38px -18px rgba(0,0,0,0.7);
    --body-bg-a:#101226;
    --body-bg-b:#15182e;
    --body-bg-c:#1a1e38;

    --glass:rgba(21,24,44,0.74);
    --glass-line:rgba(255,255,255,0.06);
    --shadow-soft: 0 10px 30px -16px rgba(0,0,0,0.6);
    --shadow-lift: 0 22px 46px -22px rgba(0,0,0,0.75);
    --accent-grad:linear-gradient(135deg,#5fc2c9,#ff8fb8);
    --track-bg:#20233f;
    --danger:#e08a9c;
    --success:#7cc0a6;
    --lavender:#c3aef2;
  }

/* max(14px, env(safe-area-inset-*)) instead of a plain 14px (2026-09-10, alongside aquarium.html
   gaining viewport-fit=cover below) — env() only ever returns nonzero on a page that opted into
   viewport-fit=cover; everywhere else it's 0, so max(14px,0)=14px, unchanged. Without this, once
   that one page bleeds under the notch/rounded-corner safe area, this sitewide corner button
   would sit right under it in landscape instead of clear of it — safe to apply everywhere since
   it's a no-op on every other page. */
.theme-toggle{
    position:fixed; top:max(14px, env(safe-area-inset-top)); right:max(14px, env(safe-area-inset-right)); z-index:60;
    width:44px; height:44px; border-radius:50%;
    border:1px solid var(--card-line); background:var(--card); color:var(--ink);
    font-size:1.3rem; line-height:1; display:flex; align-items:center; justify-content:center;
    cursor:pointer; box-shadow:var(--shadow); padding:0;
  }

/* Ambient-sound toggle (2026-09-02, site-wide) — stacked directly under the theme
   toggle, same look/size, just offset down. See initAmbientSound() in render.js.
   calc() off the theme toggle's own safe-area-aware top (2026-09-10) keeps the same 52px gap
   between them whether or not env(safe-area-inset-top) is contributing anything. */
.ambient-toggle{top:calc(max(14px, env(safe-area-inset-top)) + 52px);}

/* Gacha reveal-SFX toggle (2026-09-17, gacha.html only — #gachaSfxBtn doesn't exist on any
   other page, so this rule is a no-op elsewhere). Stacked one more slot below the ambient
   toggle, same 52px rhythm. See initGachaSfxToggle()/gachaPlay*Sound() in render.js. */
.gacha-sfx-toggle{top:calc(max(14px, env(safe-area-inset-top)) + 104px);}

/* ============================================================
   PET MASCOT — its own page at /mascot.html. Admin on/off via content.pet.enabled,
   name via content.pet.name. The character is real user-supplied pixel art (PET_ART
   in render.js, 4 poses on R2); the CSS "blob" below is only the fallback for when no
   art url is configured.
   See renderMascot()/doPetAction() in render.js and api/pet.js for the shared
   server-side stats this reads from. ============================================ */

.mascot-card{
    max-width:520px; margin:0 auto; padding:18px;
    background:var(--card); border:1px solid var(--card-line); border-radius:22px;
    box-shadow:0 18px 38px -20px rgba(0,0,0,.35);
  }

.mascot-name{
    font-weight:800; color:var(--sea-deep); font-size:var(--fs-petName, 1.375rem);
    text-align:center; margin:0 0 14px;
  }

.mascot-empty{text-align:center; color:var(--ink-soft); padding:28px 12px;}

.pet-blob{
    position:relative; width:120px; height:120px;
    background:linear-gradient(160deg,var(--sea-light),var(--coral-deep));
    border-radius:48% 52% 45% 55% / 55% 45% 58% 42%;
    box-shadow:var(--shadow-lift, 0 12px 24px -10px rgba(0,0,0,.4)), inset 0 0 0 2px rgba(255,255,255,.35);
    display:flex; align-items:center; justify-content:center; gap:12px;
  }

.pet-eye{width:12px; height:12px; border-radius:50%; background:#2b1c14; animation:petBlink 5s ease-in-out infinite;}

.pet-mouth{
    position:absolute; bottom:34px; left:50%; transform:translateX(-50%);
    width:24px; height:12px; border-radius:0 0 12px 12px; background:#2b1c14;
  }

/* Mood lives on the card root so the mouth/idle speed follow one class. */
.mascot-card.mood-neutral .pet-mouth{height:3px; border-radius:2px; bottom:40px;}

.mascot-card.mood-sad .pet-mouth{border-radius:12px 12px 0 0; bottom:28px;}

@keyframes petBlink{
    0%,90%,100%{transform:scaleY(1);}
    95%{transform:scaleY(.1);}
  }

.pet-stats{display:flex; flex-direction:column; gap:9px; margin-bottom:16px;}

.pet-stat{display:flex; align-items:center; gap:10px;}

.pet-stat-ic{font-size:18px; flex:none; width:24px; text-align:center;}

.pet-bar{flex:1; height:14px; border-radius:999px; background:var(--body-bg-a, #eaf6fb); overflow:hidden; border:1px solid var(--card-line);}

.pet-bar-fill{height:100%; border-radius:999px; transition:width .5s ease-out; background:var(--sea-light);}

.pet-bar-fill.fun{background:var(--coral-deep);}

.pet-bar-fill.energy{background:var(--sun, #ffd54f);}

/* The "room" the pet lives in — soft tinted floor, sized so the 400px-tall source art
   lands around 260px on screen (roughly 1.5x the old floating panel). The custom
   grab-hand cursor is desktop-only by nature (no effect on touch, which is most of this
   site's traffic) — `pointer` is the real fallback for everyone else, kept after it in
   the cursor list. See .pet-pat below for the touch-friendly equivalent (a stamp that
   appears at the tap point on every pet, desktop and mobile alike). */
.pet-stage{
    position:relative; height:300px; margin-bottom:14px;
    cursor:url('https://pub-d2631cc08f5a4694bdeb93749787782b.r2.dev/assets/pet/hand-cursor-64-v2.png') 20 14, pointer;
    border-radius:18px; border:1px solid var(--card-line);
    background:var(--body-bg-a, #eaf6fb); overflow:hidden;
    display:flex; align-items:flex-end; justify-content:center;
  }

/* One-shot "pat" stamp — the same real hand-pat art, placed wherever the pointer is and
   faded/scaled out. Sits alongside the floating hearts in petPetBurst() (render.js), not
   replacing them. Reference: Mamegoma 2 (Pinterest link the user sent) — a hand pats in a
   quick down-up-down rhythm. bindPetStageDrag() in render.js fires one of these on press
   and then repeats on an interval for as long as the pointer is held/dragged, each stamp
   using this one keyframe's own built-in double-bounce so a hold reads as continuous
   patting rather than separate taps. */
.pet-pat{
    position:absolute; width:64px; height:64px; margin:-32px 0 0 -32px;
    background:url('https://pub-d2631cc08f5a4694bdeb93749787782b.r2.dev/assets/pet/hand-pat-120-v2.png') center/contain no-repeat;
    pointer-events:none; z-index:3;
    animation:petPatStamp .5s ease-out forwards;
  }

@keyframes petPatStamp{
    0%{opacity:0; transform:translateY(-16px) scale(.7);}
    25%{opacity:1; transform:translateY(4px) scale(1.02);}
    55%{opacity:1; transform:translateY(-6px) scale(.96);}
    80%{opacity:.8; transform:translateY(2px) scale(1);}
    100%{opacity:0; transform:translateY(-10px) scale(.85);}
  }

/* Press-and-drag petting: while the pointer is held down on .pet-stage, this tracks its
   position (JS sets left/top in bindPetStageDrag() in render.js) so the hand visibly
   follows the mouse/finger instead of only stamping a fixed spot. The stage's own
   `cursor:url(...)` hand is hidden during the drag (`.pet-stage.dragging`) so there's one
   hand, not two. */
.pet-hand-follow{
    position:absolute; width:64px; height:64px; margin:-32px 0 0 -32px;
    background:url('https://pub-d2631cc08f5a4694bdeb93749787782b.r2.dev/assets/pet/hand-pat-120-v2.png') center/contain no-repeat;
    pointer-events:none; z-index:5; opacity:0; transform:translateY(0) scale(.85);
    transition:opacity .12s ease; left:0; top:0;
  }

.pet-hand-follow.active{opacity:1; animation:petHandBounce .3s ease-in-out infinite;}

@keyframes petHandBounce{
    0%,100%{transform:translateY(0) scale(1);}
    50%{transform:translateY(7px) scale(.93);}
  }

.pet-stage.dragging{cursor:none;}

/* The happy cloud+rainbow thought bubble from the same reference clip — plain emoji
   (☁️🌈), not illustrated art, matching the site's existing convention of using real
   emoji for small incidental icons (stat bars etc.) rather than code-drawn shapes. One
   per pet, floating up above the character and fading — see petPet() in render.js. */
.pet-thought{
    position:absolute; left:50%; top:4%; transform:translateX(-50%);
    font-size:2.125rem; line-height:1; pointer-events:none; z-index:4;
    animation:petThoughtFloat 1.6s ease-out forwards;
  }

@keyframes petThoughtFloat{
    0%{opacity:0; transform:translateX(-50%) translateY(14px) scale(.5);}
    20%{opacity:1; transform:translateX(-50%) translateY(-4px) scale(1.05);}
    75%{opacity:1; transform:translateX(-50%) translateY(-10px) scale(1);}
    100%{opacity:0; transform:translateX(-50%) translateY(-26px) scale(.9);}
  }

.pet-stage-glow{
    position:absolute; left:50%; top:46%; width:320px; height:320px; transform:translate(-50%,-50%);
    border-radius:50%; background:var(--sea-light); opacity:.18; pointer-events:none;
  }

.pet-shadow{
    position:absolute; bottom:32px; left:50%; transform:translateX(-50%);
    width:150px; height:22px; border-radius:50%; background:rgba(0,0,0,.16);
    filter:blur(4px); pointer-events:none;
  }

.pet-char{
    position:relative; z-index:1; margin-bottom:36px;
    display:flex; align-items:flex-end; justify-content:center;
    animation:petIdle 3.4s ease-in-out infinite;
  }

.pet-art{max-height:var(--pet-art-size, 260px); max-width:100%; object-fit:contain; display:block;}

/* .pet-art carries its own `display`, which beats the UA's [hidden]{display:none} at
   equal specificity — so hiding it from JS needs an explicit rule, or the placeholder
   blob keeps rendering right next to the real art.
   Same trap, second time (2026-09-16): .header-shortcut-pill is `display:inline-flex`, so
   the GACHA pill stayed fully visible (measured 89x38) while `hidden` was set on it —
   which would have linked straight to a page that isn't open yet. Any element on this site
   with its own `display` needs a rule like this before `hidden` means anything. */
.pet-art[hidden],.pet-blob[hidden],.header-shortcut-pill[hidden]{display:none;}

/* The real art has its own cushion drawn in, so the CSS contact shadow would double up. */
.mascot-card.has-art .pet-shadow{display:none;}

@keyframes petIdle{
    0%,100%{transform:translateY(0) scale(1,1);}
    50%{transform:translateY(-8px) scale(1.02,.98);}
  }

.pet-char.reacting{animation:petWiggle .5s ease-in-out;}

@keyframes petWiggle{
    0%,100%{transform:rotate(0) scale(1);}
    25%{transform:rotate(-5deg) scale(1.05);}
    75%{transform:rotate(5deg) scale(1.05);}
  }

.pet-heart{
    position:absolute; bottom:100px; font-size:24px; pointer-events:none; z-index:2;
    animation:petHeart 1.1s ease-out forwards;
  }

@keyframes petHeart{
    0%{opacity:0; transform:translateY(0) scale(.6);}
    25%{opacity:1;}
    100%{opacity:0; transform:translateY(-92px) scale(1.15);}
  }

.pet-hint{font-size:var(--fs-petHint, 0.8125rem); color:var(--ink-soft); text-align:center; opacity:.75; margin:0 0 6px;}

.pet-msg{font-size:var(--fs-petMsg, 1.0625rem); color:var(--ink); text-align:center; min-height:1.4em; margin:0 0 16px; font-weight:700;}

.pet-actions{display:flex; gap:10px; justify-content:center;}

.pet-action-btn{
    flex:1; display:flex; flex-direction:column; align-items:center; gap:5px;
    padding:14px 6px; border-radius:16px; border:1px solid var(--card-line);
    background:var(--body-bg-a, #f6f7fb); cursor:pointer;
    transition:transform .15s ease, background .15s ease;
  }

.pet-action-ic{font-size:28px; line-height:1;}

.pet-action-lb{font-size:var(--fs-petActionLabel, 0.875rem); color:var(--ink-soft);}

.pet-action-btn:hover{transform:translateY(-2px); background:var(--card);}

.pet-action-btn:active{transform:scale(.94);}

@media(max-width:600px){
    .pet-stage{height:240px;}
    .pet-art{max-height:var(--pet-art-size, 200px);}
    .pet-stage-glow{width:240px; height:240px;}
    .pet-action-ic{font-size:24px;}
    .pet-action-lb{font-size:var(--fs-petActionLabel, 0.8125rem);}
  }

:root[data-theme="dark"] .cta-panel,
  :root[data-theme="dark"] .qr-box,
  :root[data-theme="dark"] .opc-box,
  :root[data-theme="dark"] .support-panel,
  :root[data-theme="dark"] .social-link,
  :root[data-theme="dark"] .countdown-grid div,
  :root[data-theme="dark"] .rewards,
  :root[data-theme="dark"] .rank-row.top1,
  :root[data-theme="dark"] .rank-row.top2,
  :root[data-theme="dark"] .rank-row.top3,
  :root[data-theme="dark"] .cur-event-stats .stat:not(.hero),
  :root[data-theme="dark"] .share-modal{
    background:var(--card);
  }

:root[data-theme="dark"] .support-btn{
    color:#0c3d55;
  }

:root[data-theme="dark"] .share-modal-icon{
    color:#8bd3dd;
  }

:root[data-theme="dark"] .header-side h2,
:root[data-theme="dark"] .rank-hero h1,
:root[data-theme="dark"] .event-hero h1,
:root[data-theme="dark"] .bottle-hero h1,
:root[data-theme="dark"] .past-events h2{
    text-shadow:none;
  }

*{box-sizing:border-box;}

html{scroll-behavior:smooth;}

body{
    margin:0;
    /* The global UI-scale slider applies CSS zoom here; at scales below 1 the
       body would otherwise stop short of the fold and leak the blank <html>
       behind the gradient. */
    min-height:100vh;
    background:var(--body-bg-a);
    color:var(--ink);
    font-family:'DBHelvethaica','Kanit', sans-serif;
    font-size:1.25rem;
    -webkit-font-smoothing:antialiased;
  }

.gingham{
    height:26px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='22' viewBox='0 0 44 22'%3E%3Cpath d='M0 11 Q11 0 22 11 T44 11 V22 H0 Z' fill='%231f93c4'/%3E%3C/svg%3E");
    background-repeat:repeat-x;
    background-size:44px 22px;
    position:relative;
  }

.wrap{max-width:1080px; margin:0 auto; padding:0 20px 60px;}

header{
    padding:50px 0 30px;
    position:relative;
    overflow:hidden;
  }

/* light from the surface — a soft glow behind the header, underwater-world touch */
header::before{
    content:"";
    position:absolute;
    top:-60px; left:50%;
    width:560px; height:340px;
    transform:translateX(-50%);
    background:radial-gradient(ellipse at top, rgba(255,255,255,.6), rgba(255,255,255,0) 70%);
    pointer-events:none;
    z-index:0;
  }

/* Dark mode drops the glow entirely. A white radial wash reads as a soft "light from
   the surface" on the pale light-mode background, but over the near-black dark body it
   turns into a harsh grey spotlight/haze behind the title instead. */
:root[data-theme="dark"] header::before{display:none;}

.sparkle{position:absolute; opacity:.7; color:var(--sun); font-size:1.625rem; pointer-events:none;}

.deco{position:absolute; pointer-events:none; user-select:none; line-height:1;}

/* Scattered particle decorations retired (2026-08-30) in favour of the ambient
   bubble field further down — the sparkles/star/shell read as stray specks rather
   than decoration. Hidden rather than deleted because the markup is duplicated
   across all nine page files and render.js still fills .sparkle with the
   admin-configured glyph; hiding here turns them off everywhere from one place.
   Clouds joined them on the same pass — the ambient bubbles are the only
   background motion now. */
.sparkle,.deco-star,.deco-shell,.deco-cloud1,.deco-cloud2{display:none;}

.deco-sun{font-size:54px; top:10px; left:10px; filter:drop-shadow(0 0 10px rgba(255,213,79,.6)); animation:sunSpin 12s linear infinite;}

.deco-cloud1{font-size:34px; top:6px; right:8%; animation:cloudDrift 18s ease-in-out infinite;}

.deco-cloud2{font-size:24px; top:36px; right:26%; animation:cloudDrift 14s ease-in-out infinite reverse;}

.deco-palm{font-size:56px; bottom:-8px; right:0px; transform:rotate(-4deg); animation:gentleFloat 5s ease-in-out infinite;}

.deco-shell{font-size:22px; bottom:2px; left:4%; animation:gentleFloat 4s ease-in-out infinite;}

.deco-star{font-size:20px; top:60%; left:14%; animation:sparkleFloat 3.4s ease-in-out infinite;}

.deco-fish1{font-size:26px; top:22%; left:9%; animation:fishSwim 22s ease-in-out infinite;}

.deco-fish2{font-size:18px; top:48%; right:11%; animation:fishSwim 17s ease-in-out infinite reverse;}

.deco-fish3{font-size:14px; top:68%; left:32%; animation:fishSwim 26s ease-in-out infinite;}

/* hidden per user feedback 2026-08-29 — header felt too busy/cluttered ("รก") */
.deco-fish1,.deco-fish2,.deco-fish3{display:none;}

/* AQUARIUM nav pill hidden per user request 2026-08-31 — catalog only has 4 fish species and one
   (clownfish) is noticeably blurrier than the rest; hiding the entry point until more/better fish
   art is ready. Page, admin, and backend all stay intact — only the nav link disappears, same
   soft-hide approach as the fish emoji above. Selector targets the href since the pill carries no
   dedicated class (unlike teacup/bottle/gold/coral, which do). */
.nav-pill[href="/aquarium.html"]{display:none;}

@keyframes fishSwim{
    0%,100%{transform:translateX(0) translateY(0);}
    50%{transform:translateX(16px) translateY(-6px);}
  }

.svgicon{width:1em; height:1em; display:inline-block; vertical-align:-0.15em; flex-shrink:0;}

.deco-sun{color:var(--sun);}

.deco-cloud1,.deco-cloud2{color:#ffffff;}

.deco-palm{color:#2f9e5c;}

.deco-shell{color:var(--sand-line);}

.deco-star{color:var(--sun);}

.nav-pill .svgicon{width:1.05em; height:1.05em;}

header .header-grid{
    display:grid;
    grid-template-columns:1fr auto 1fr;
    gap:28px;
    align-items:center;
    position:relative;
    z-index:1;
  }

.title-block img.logo{
    width:100%;
    max-width:420px;
    display:block;
    filter:drop-shadow(2px 3px 0 #fff) drop-shadow(-1px -1px 0 var(--card-line));
    transform:rotate(-2deg);
  }

.title-block p.sub{
    margin:10px 0 0;
    font-weight:600;
    color:var(--ink-soft);
    font-size:1.09375rem;
  }

.avatar{
    width:150px; height:150px; border-radius:50%;
    background:linear-gradient(150deg,#d9f2f2,#8bd3dd);
    border:6px solid #fff;
    box-shadow:var(--shadow), 0 0 0 4px var(--sea-light);
    display:flex; align-items:center; justify-content:center;
    overflow:hidden;
    font-size:4.125rem;
    color:#fff;
    text-shadow:0 2px 6px rgba(0,0,0,.15);
  }

.avatar img{
    width:100%; height:100%; object-fit:cover; object-position:center 25%;
    display:block;
  }

.header-side{
    text-align:right;
    position:relative;
    z-index:1;
  }

.header-side h2{
    margin:0 0 10px;
    font-size:1.3rem;
    font-weight:700;
    color:var(--sea-deep);
    line-height:1.6;
    text-shadow:none;
  }

.rate-pill{
    display:inline-flex; align-items:center; gap:8px;
    margin-top:10px;
    background:var(--sand);
    border:2px dashed var(--sand-line);
    padding:9px 18px;
    border-radius:10px;
    font-size:1rem;
    font-weight:700;
    color:var(--ink);
  }

.cta-panel{
    background:linear-gradient(160deg,#fff 0%,#eaf9ff 55%,#dff2ff 100%);
    border:3px solid var(--sea-light);
    border-radius:var(--radius-lg);
    padding:28px 30px 24px;
    margin:8px 0 30px;
    box-shadow:var(--shadow);
    position:relative;
  }

.cta-grid{
    display:grid; grid-template-columns:1fr 1.15fr; gap:30px; align-items:center;
  }

.cta-qr h4{color:var(--sea-deep); margin:0 0 14px; font-size:1.05rem; font-weight:700; text-align:center;}

.cta-support{text-align:center;}

.cta-support h3{color:var(--coral-deep); font-size:1.5rem; margin:6px 0 8px;}

.cta-support p{font-size:0.96875rem; color:var(--ink-soft); font-weight:600; margin:0 0 18px;}

.progress-card{
    background:var(--card);
    border:1px solid var(--card-line);
    border-radius:var(--radius-lg);
    padding:30px 36px;
    box-shadow:var(--shadow);
    display:grid;
    grid-template-columns:180px 1fr 160px;
    gap:26px;
    align-items:center;
  }

.tier-now{
    text-align:center;
    border-right:2px dashed var(--card-line);
    padding-right:20px;
  }

.tier-now .label{font-size:0.96875rem; color:var(--ink-soft); font-weight:600;}

.tier-now .tier{font-size:2.1rem; font-weight:700; color:var(--sea-deep); margin-top:8px;}

.tier-now .heart{font-size:1.625rem;}

.bar-block .nums{
    display:flex; align-items:baseline; gap:10px; margin-bottom:12px;
  }

.bar-block .nums .cur{font-size:2rem; font-weight:700; color:var(--sea-deep);}

.bar-block .nums .goal{font-size:1rem; color:var(--ink-soft); font-weight:600;}

.track{
    position:relative;
    height:24px;
    background:#eaf6fb;
    border-radius:999px;
    overflow:hidden;
    border:1px solid var(--card-line);
  }

.fill{
    height:100%;
    width:0%;
    border-radius:999px;
    background:linear-gradient(90deg,#8bd3dd,#3d405b);
    display:flex; align-items:center; justify-content:flex-end;
    padding-right:10px;
    color:#fff; font-size:0.8125rem; font-weight:700;
    transition:width 1.2s ease-out;
  }

.track-caption{
    margin-top:8px; font-size:0.9375rem; color:var(--ink-soft); font-weight:600;
  }

.goal-block{text-align:center; border-left:2px dashed var(--card-line); padding-left:20px;}

.goal-block .label{font-size:0.96875rem; color:var(--ink-soft); font-weight:600;}

.goal-block .num{font-size:1.6rem; font-weight:700; color:var(--sea-deep); margin-top:8px;}

.roadmap{margin:52px 0 20px; position:relative;}

.rm-track{
    position:relative;
    display:flex; justify-content:space-between;
    align-items:center;
    padding:0 14px;
    height:26px;
  }

.rm-track::before{
    content:"";
    position:absolute; left:14px; right:14px; top:50%;
    height:0; border-top:4px dashed var(--sea-light);
    transform:translateY(-50%);
    z-index:0;
  }

.rm-dot{
    position:relative; z-index:1;
    width:20px; height:20px; border-radius:50%;
    background:#fff;
    border:4px solid var(--sea-mid);
    box-shadow:0 0 0 3px #fff, 0 4px 8px -2px rgba(14,111,158,.4);
  }

.rm-labels{
    display:flex; justify-content:space-between;
    padding:0 14px;
    margin-top:12px;
  }

.rm-label{
    text-align:center;
    font-size:1.0625rem; color:var(--sea-deep); font-weight:800;
    line-height:1.3;
  }

.tiers{
    display:grid;
    grid-template-columns:repeat(6,1fr);
    gap:14px;
  }

.tier-card{
    background:var(--card);
    border:1px solid var(--card-line);
    border-radius:var(--radius-md);
    padding:16px 10px 18px;
    text-align:center;
  }

.tier-card.active{
    border-color:var(--sun);
    box-shadow:var(--shadow);
  }

.tier-card .badge{
    display:inline-block; background:var(--sea-mid); color:#fff;
    font-weight:700; font-size:0.95rem; padding:8px 16px; border-radius:8px; margin-bottom:12px;
    letter-spacing:.3px;
  }

.tier-card.active .badge{background:var(--coral-deep);}

.tier-card .icon{font-size:2.5rem; margin-bottom:10px;}

.tier-card .baht{font-weight:700; color:var(--sea-deep); font-size:1.3rem;}

.tier-card .range{font-size:0.90625rem; color:var(--ink-soft); margin-top:8px; font-weight:600;}

.tier-card.complete{background:#f0faf1; border-color:#8fd19e;}

.tier-card .tier-complete{font-weight:700; color:#2e7d32; font-size:0.9375rem; line-height:1.6;}

.rewards{
    margin:40px 0;
    background:linear-gradient(180deg,#fff8e6,#ffedbd);
    border:3px dashed var(--sand-line);
    border-radius:var(--radius-lg);
    padding:26px 30px 22px;
    text-align:center;
  }

.rewards h3{
    color:var(--sea-deep); font-size:1.6rem; font-weight:700; margin:0 0 14px;
  }

.rewards p.rw-sub{margin:0 0 24px; font-size:1.125rem; color:var(--ink-soft); font-weight:600;}

.reward-row{display:grid; grid-template-columns:repeat(5,1fr); gap:14px;}

.reward-item{}

.reward-item .tok{font-size:1.0625rem; color:var(--ink-soft); font-weight:700; margin-bottom:10px;}

.card-sr{
    width:78px; height:100px; margin:0 auto 10px;
    background:linear-gradient(160deg,#e8ac5c,#8b5a2b);
    border-radius:10px;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    color:#fff; font-weight:700; font-size:1.15rem; gap:8px;
    box-shadow:0 6px 14px -6px rgba(139,90,43,.6), inset 0 0 0 3px rgba(255,213,79,.5);
    border:2px solid #fff;
    position:relative;
  }

.card-sr.locked{
    background:linear-gradient(160deg,#aab1bb,#6b7280);
    box-shadow:0 6px 14px -6px rgba(60,66,75,.55), inset 0 0 0 3px rgba(255,255,255,.25);
  }

.card-sr.locked span{opacity:0;}

.card-sr .lock-overlay{position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:#fff;}

.card-sr .lock-overlay .svgicon{width:1.75rem; height:1.75rem;}

.reward-item.locked .tok, .reward-item.locked .caption{opacity:.55;}

.reward-item .caption{font-size:1rem; color:var(--sea-deep); font-weight:700;}

.rewards .note{margin-top:18px; font-size:0.9375rem; color:var(--ink-soft);}

.bottom-grid-2{
    display:grid; grid-template-columns:1fr 1fr; gap:18px; margin-top:10px;
  }

.panel{
    background:var(--card);
    border:1px solid var(--card-line);
    border-radius:var(--radius-md);
    padding:20px;
    box-shadow:var(--shadow);
  }

.panel h4{
    color:var(--sea-deep); margin:0 0 14px; font-size:1.3125rem; font-weight:700;
  }

/* Home-page shortcuts to Bottle and Playlist (2026-09-04) — moved off the top nav on
   request ("รู้สึก tap ข้างบนมันรกเกิน", the nav bar felt cluttered with 8 tabs). First try
   put these as full cards at the bottom of the page; the user pointed out the empty gap
   under .rate-pill instead (.header-side is only as tall as its own content, while its
   grid row is set by the taller .avatar circle next to it) and asked to put them there —
   small pills matching .rate-pill's own look fit that spot far better than another card. */
.header-shortcuts{
    display:flex; flex-direction:column; align-items:flex-end; gap:8px;
    margin-top:12px;
  }

.header-shortcut-pill{
    display:inline-flex; align-items:center; gap:7px;
    background:var(--sand); border:2px dashed var(--sand-line);
    padding:7px 16px; border-radius:999px;
    font-size:0.875rem; font-weight:700; color:var(--ink);
    text-decoration:none;
    transition:transform .15s ease, background .15s ease;
  }

.header-shortcut-pill:hover{transform:translateY(-2px); background:var(--card);}

.header-shortcut-pill .svgicon{width:1.1rem; height:1.1rem;}

@media(max-width:600px){
    .header-shortcuts{align-items:center;}
  }

.qr-strip{
    background:var(--sea-mid); color:#fff; text-align:center; padding:10px; border-radius:10px;
    font-size:1rem; font-weight:700; margin-bottom:16px;
  }

.qr-row{display:flex; gap:12px; margin-bottom:16px;}

.qr-box{
    flex:1; background:#fdfdfd; border:1px solid var(--card-line); border-radius:var(--radius-sm);
    padding:10px; text-align:center; font-size:1rem; color:var(--ink-soft); font-weight:700;
  }

/* "บันทึก QR" — injected by render.js:initQrSaveButtons(). Someone reading the site on
   their phone cannot scan a QR shown on that same phone, so this hands them the image. */
.qr-save-btn{
    display:inline-flex; align-items:center; justify-content:center; gap:6px;
    width:100%; margin-top:8px; padding:8px 10px;
    border:1px solid var(--card-line); border-radius:999px;
    background:var(--card); color:var(--sea-deep);
    font-family:inherit; font-size:0.9375rem; font-weight:700; cursor:pointer;
    transition:transform .15s ease, background .15s ease;
  }

.qr-save-btn .svgicon{width:16px; height:16px; flex:none;}

.qr-save-btn:hover{transform:translateY(-1px); background:var(--body-bg-b);}

.qr-save-btn:disabled{opacity:.6; cursor:default; transform:none;}

.qr-row .qr-box:nth-child(1){transform:rotate(-1.5deg);}

.qr-row .qr-box:nth-child(2){transform:rotate(1.5deg);}

.qr-box img{width:100%; border-radius:8px; margin-bottom:8px; border:1px solid var(--card-line);}

.opc-box{
    background:#eaf9ff; border-radius:var(--radius-sm); padding:14px; text-align:center;
    font-size:0.96875rem; color:var(--sea-deep); font-weight:700;
  }

.support-panel{
    text-align:center;
    background:linear-gradient(180deg,#fff,#fff5f9);
  }

.support-panel h3{
    color:var(--coral-deep); font-size:1.625rem; margin:6px 0 8px;
  }

.support-panel p{
    font-size:0.96875rem; color:var(--ink-soft); font-weight:600; margin:0 0 20px;
  }

.support-btn{
    display:inline-flex; align-items:center; gap:8px;
    background:linear-gradient(90deg,#ffd54f,#ff9f43);
    color:var(--ink); border:3px solid #fff; padding:14px 34px; border-radius:var(--radius-sm);
    font-weight:700; font-size:1.05rem; cursor:pointer;
    box-shadow:0 10px 24px -8px rgba(255,159,67,.7);
    transition:transform .15s ease;
    text-decoration:none;
  }

.support-btn:hover{transform:translateY(-2px) scale(1.02);}

.trust-row{
    display:flex; justify-content:space-around; margin-top:24px;
    font-size:0.90625rem; color:var(--ink-soft); font-weight:600;
  }

.trust-row div{display:flex; flex-direction:column; align-items:center; gap:6px;}

.trust-row .ic{font-size:1.46875rem;}

a.qr-box{cursor:pointer; text-decoration:none; color:inherit; display:block;}

.social-block{margin-top:22px; padding-top:18px; border-top:2px dashed var(--card-line);}

.social-title{font-size:0.9375rem; color:var(--ink-soft); font-weight:700; margin-bottom:12px;}

.social-row{display:flex; justify-content:center; gap:10px; flex-wrap:wrap;}

.social-link{
    display:inline-flex; align-items:center; gap:6px;
    background:#fff; border:1px solid var(--card-line); border-radius:999px;
    padding:8px 16px; font-weight:700; font-size:0.875rem; color:var(--sea-deep);
    text-decoration:none; transition:transform .2s ease, box-shadow .2s ease;
  }

.social-link:hover{transform:translateY(-3px); box-shadow:0 8px 16px -8px rgba(14,111,158,.35);}

.social-link .ic{font-size:1.1rem;}

.side-panels{display:flex; flex-direction:column; gap:18px;}

.countdown-grid{
    display:grid; grid-template-columns:repeat(4,1fr); gap:6px; margin:10px 0 6px;
  }

.countdown-grid div{
    background:#eaf9ff; border:1px solid var(--card-line); border-radius:var(--radius-sm);
    padding:12px 4px; text-align:center;
  }

.countdown-grid .cnum{font-weight:700; font-size:var(--fs-countdownNum, 2rem); color:var(--sea-deep); display:block; margin-bottom:6px;}

.countdown-grid .clabel{font-size:var(--fs-countdownLabel, 0.9375rem); color:var(--ink-soft); font-weight:600;}

.panel .small{font-size:0.9375rem; color:var(--ink-soft); font-weight:600; text-align:center;}

/* the date under the countdown grid, scoped so the other .panel .small blocks keep theirs */
#countdown-date-label{font-size:var(--fs-countdownDate, 1.0625rem);}

.update-num{font-size:1.7rem; font-weight:700; color:var(--sea-deep); margin:12px 0 8px;}

footer{
    text-align:center; padding:26px 0 6px; font-size:0.875rem; color:var(--ink-soft);
  }

@keyframes fadeInUp{
    from{opacity:0; transform:translateY(18px);}
    to{opacity:1; transform:translateY(0);}
  }

@keyframes sparkleFloat{
    0%,100%{transform:translateY(0) scale(1); opacity:.6;}
    50%{transform:translateY(-10px) scale(1.2); opacity:1;}
  }

@keyframes heartBeat{
    0%,100%{transform:scale(1);}
    15%{transform:scale(1.25);}
    30%{transform:scale(1);}
    45%{transform:scale(1.18);}
    60%{transform:scale(1);}
  }

@keyframes gentleFloat{
    0%,100%{transform:translateY(0) rotate(0deg);}
    50%{transform:translateY(-6px) rotate(1deg);}
  }

@keyframes cloudDrift{
    0%,100%{transform:translateX(0);}
    50%{transform:translateX(-14px);}
  }

/* sunSpin / btnPulseGlow / ctaGlow / shimmer / stripeMove all died with the
   powder-blue retheme (spinning sun hidden, orange glow and barber-pole stripes
   dropped). bubbleUp moved to the MOTION LAYER at the end of this file, where it
   is finally wired to something — it had never been referenced at all. */

header,.cta-panel,.progress-card,.roadmap,.rewards,.bottom-grid-2{
    animation:fadeInUp .7s ease-out both;
  }

.cta-panel{animation-delay:.06s;}

.progress-card{animation-delay:.14s;}

.roadmap{animation-delay:.2s;}

.rewards{animation-delay:.26s;}

.bottom-grid-2{animation-delay:.32s;}

.sparkle{animation:sparkleFloat 3s ease-in-out infinite;}

.sparkle:nth-of-type(2){animation-delay:.6s;}

.sparkle:nth-of-type(3){animation-delay:1.2s;}

.avatar{animation:gentleFloat 4.5s ease-in-out infinite;}

.tier-now .heart{display:inline-block; animation:heartBeat 2.4s ease-in-out infinite;}

.support-btn:hover{transform:translateY(-2px) scale(1.03);}

.qr-box{transition:transform .25s ease, box-shadow .25s ease;}

.qr-box:hover{transform:translateY(-5px) scale(1.03) rotate(0deg) !important; box-shadow:0 10px 20px -10px rgba(14,111,158,.35);}

.tier-card{transition:transform .2s ease, box-shadow .2s ease;}

.tier-card:hover{transform:translateY(-4px);}

.fill{position:relative; overflow:hidden;}

.card-sr{transition:transform .2s ease;}

.reward-item:hover .card-sr{transform:translateY(-4px) rotate(-2deg);}

.site-nav{
    display:flex; justify-content:center; align-items:center; gap:14px;
    padding:16px 0 0; flex-wrap:wrap;
  }

.nav-pill{
    display:inline-flex; align-items:center; gap:7px;
    background:var(--sea-mid); color:#fff; text-decoration:none;
    padding:11px 24px; border-radius:999px; font-weight:800; font-size:0.9375rem;
    box-shadow:var(--shadow), 0 0 0 3px #fff; transition:transform .15s ease, box-shadow .15s ease;
  }

.nav-pill:hover{transform:translateY(-2px) scale(1.04); box-shadow:var(--shadow), 0 0 0 3px #fff, 0 8px 18px -6px rgba(0,0,0,.25);}

/* Per-pill gradient identity colors removed (2026-08-29, on user request) — the
   later, real .nav-pill.active rule below now owns background/color for the
   current-page pill; .gold/.coral/.bottle/.teacup fall back to the plain base look. */

.nav-pill.active{outline:3px solid #fff; outline-offset:-3px;}

.rank-hero{
    text-align:center; padding:20px 0 8px; position:relative;
  }

.rank-hero h1{
    font-size:var(--fs-rankPageTitle, 2.1rem); font-weight:700; color:var(--sea-deep); margin:0 0 8px; text-shadow:none;
  }

.rank-hero p{
    font-size:var(--fs-rankPageSubtitle, 1.1rem); color:var(--ink-soft); font-weight:600; margin:0;
  }

.rank-list{
    display:flex; flex-direction:column; gap:12px; margin:26px 0;
  }

.rank-row{
    display:flex; flex-wrap:wrap; align-items:center; gap:16px;
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-md);
    padding:14px 20px; box-shadow:var(--shadow);
    animation:fadeInUp .6s ease-out both;
  }

/* Cat-collection icons (2026-09-02 fix): a real bug, reported with a live screenshot —
   .rank-name only ever gets whatever width is left after medal/avatar/score/chest/share
   claim theirs (measured as little as 76px on an actual phone), nowhere near enough for a
   multi-icon collection to wrap into a grid rather than a tall single-file column. Moved
   out of .rank-name into its own row-level child with flex-basis:100%, which — combined
   with .rank-row's new flex-wrap above — forces it onto its own line with the row's FULL
   width available, regardless of how little room the name itself has. Only present at all
   for a person who actually owns cats (render.js), so most rows are unaffected. */
.rank-row-cats{flex-basis:100%; margin-top:2px;}

/* Two copies of the cat markup exist per row now (see paintRankList() in render.js) —
   .rank-name's own inline copy (right after .rank-name-text, hugs it tightly) and
   .rank-row-cats' full-width copy (below the whole row, guaranteed room on a narrow
   phone). Mobile-first: show only the full-width one, matching the original bug-fix
   default. Above 700px — a real desktop screenshot confirmed a .rank-row this wide has
   room to spare, no more starved .rank-name — swap to the inline one instead so the cats
   sit directly against the name instead of dropping to their own line below it. */
.rank-name > .name-emoji-group{display:none;}

@media(min-width:700px){
    .rank-name > .name-emoji-group{display:inline-flex;}
    .rank-row-cats{display:none;}
  }

.rank-row.top1{border-color:#ffd54f; background:linear-gradient(90deg,#fff8e6,#ffffff);}

.rank-row.top2{border-color:#c9d6e3; background:linear-gradient(90deg,#f3f7fa,#ffffff);}

.rank-row.top3{border-color:#e3a86b; background:linear-gradient(90deg,#fbeee0,#ffffff);}

.rank-medal{
    width:52px; height:52px; min-width:52px; border-radius:50%;
    display:flex; align-items:center; justify-content:center;
    font-weight:700; font-size:var(--fs-rankMedal, 1.5rem); color:#fff;
    background:var(--sea-mid);
    box-shadow:0 4px 10px -4px rgba(14,111,158,.5);
  }

.rank-row.top1 .rank-medal{background:linear-gradient(160deg,#ffe08a,#e0a02c); font-size:calc(var(--fs-rankMedal, 1.5rem) * 1.23);}

.rank-row.top2 .rank-medal{background:linear-gradient(160deg,#eef3f7,#a9bac8); font-size:calc(var(--fs-rankMedal, 1.5rem) * 1.15);}

.rank-row.top3 .rank-medal{background:linear-gradient(160deg,#e8b988,#a86a34); font-size:calc(var(--fs-rankMedal, 1.5rem) * 1.08);}

.rank-name{
    flex:1; min-width:0; display:flex; flex-wrap:wrap; align-items:center;
    font-size:var(--fs-rankName, 1.45rem); font-weight:700; color:var(--ink);
  }

/* Name text and the cat-icon group (if any) used to share one nowrap+ellipsis box, so a
   long name would silently clip the tail of someone's cat collection along with it (RWM
   report 2026-09-02, screenshot showed "...” eating extra cats). Split into its own child
   so only the name text ellipsizes — the icon group is a flex:none sibling that always
   shows in full. Same split applied to .tc-row-name below for the identical reason. */
.rank-name-text{overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;}

.rank-name-text:empty::before{content:"— ยังไม่ระบุ —"; color:var(--ink-soft); font-weight:600; font-size:0.95rem;}

/* Purchasable pixel-cat badge appended after a name (2026-08-30) — shared by .rank-name,
   .tc-name, and .tc-row-name, so sized in em to scale with whichever name font-size var
   is active on the page/element it lands in, rather than a fixed px. */
.name-emoji{
    width:1.3em; height:1.3em; margin-left:4px; vertical-align:-0.3em;
    object-fit:contain; image-rendering:pixelated;
    animation:nameEmojiPulse 1.4s ease-in-out infinite;
  }

@keyframes nameEmojiPulse{
    0%,100%{transform:scale(1);}
    50%{transform:scale(1.18);}
  }

/* Sparkle (2026-09-02, per-species) — admin picks, per cat color, how many of a fan's
   copies of that species get this treatment. History: code-drawn drop-shadow pulse →
   user-supplied real pixel-sparkle GIF → that GIF's busy multi-point burst read as "รก"
   (cluttered) once seen live, asked for plain white/yellow instead → a soft radial-glow
   circle (plain CSS) → user was shown 6 CSS-only style candidates side by side (a small
   throwaway `_tmp_sparkle_preview.html` mockup, not a real page) and picked "B — ดาว
   กะพริบมุม" (twinkle cross), landing here: two small 4-point star crosses at opposite
   corners, staggered so they never twinkle in sync. Built with `::before`/`::after` on
   this element itself (a plain <span>, unlike the <img> icon it sits behind — which is
   exactly why nameEmojiHtml() wraps a sparkly icon in .name-emoji-wrap in the first
   place: an <img> can't take generated-content decoration, this span can). Percentage
   sizing (not px) so the stars scale with whichever em-based icon size is active in
   context (Ranking vs Teacup vs the full-card view all use different font sizes).
   Yellow/gold (`--sun`-family) over white, same reasoning as the glow version it
   replaced — reads as a reward/achievement accent, matching rank #1's gold elsewhere. */
.name-emoji-wrap{position:relative; display:inline-flex; align-items:center; justify-content:center;}

.name-emoji-sparkle-fx{position:absolute; inset:-35%; z-index:0; pointer-events:none;}

.name-emoji-sparkle-fx::before,
.name-emoji-sparkle-fx::after{
    content:''; position:absolute; width:38%; height:38%;
    background:
      linear-gradient(#ffd54f,#ffd54f) 50% 0/2px 100% no-repeat,
      linear-gradient(90deg,#ffd54f,#ffd54f) 0 50%/100% 2px no-repeat;
    clip-path:polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%);
    animation:catSparkleTwinkle 1.6s ease-in-out infinite;
  }

.name-emoji-sparkle-fx::before{top:0; left:8%;}

.name-emoji-sparkle-fx::after{bottom:6%; right:0; width:24%; height:24%; animation-delay:.5s;}

@keyframes catSparkleTwinkle{
    0%,100%{opacity:.15; transform:scale(.6) rotate(0deg);}
    50%{opacity:1; transform:scale(1) rotate(15deg);}
  }

.name-emoji-wrap .name-emoji{position:relative; z-index:1;}

/* Clickable wrapper around a fan's whole cat row (2026-09-02) — opens #catModalBackdrop.
   flex:none keeps it out of the name's ellipsis box entirely (see .rank-name-text above).
   nameEmojiHtml() renders every icon a person owns, no truncation — max-width:100% just
   means "don't exceed whatever room the flex parent (.rank-name/.tc-row-name/.tc-name)
   actually gives you", so a wide card lines every icon up on one row while a narrow one
   naturally gets less room per line. flex-wrap:wrap lets the group itself split across
   extra internal lines rather than overflow when that room really is small. The real
   narrow-screen safety net is one level up, on .rank-name/.tc-row-name themselves
   (flex-wrap:wrap there) — that's what stops a big collection from squeezing
   .rank-name-text/.tc-row-name-text down to nothing (a real bug hit once during this
   feature's build, see render.js's own history): the icon group as a whole drops to its
   own line under the name instead, rather than this group's own width being artificially
   capped below its parent's actual available space the way an earlier fixed 160px was. */
.name-emoji-group{display:inline-flex; flex-wrap:wrap; max-width:100%; align-items:center; flex:none; cursor:pointer; transition:transform .15s ease;}

.name-emoji-group:hover{transform:scale(1.08);}

.rank-score{
    font-size:var(--fs-rankScore, 1.6rem); font-weight:700; color:var(--sea-deep); white-space:nowrap;
  }

.rank-score .unit{font-size:var(--fs-rankUnit, 0.85rem); color:var(--ink-soft); font-weight:600; margin-left:4px;}

/* .card-sr and .support-btn were re-declared here verbatim — leftovers from
   site.css being generated by concatenating the pages' inline <style> blocks.
   Both are defined once, higher up. Only .card-sr .star was unique. */
.card-sr .star{font-size:0.9375rem;}

.rank-hero .rank-updated{
    font-size:var(--fs-rankUpdated, 0.9rem); font-weight:600; margin:6px 0 0;
  }

.rank-hero .rank-updated:empty{display:none;}

.rank-avatar{
    width:44px; height:44px; min-width:44px; border-radius:50%; object-fit:cover;
    border:1px solid var(--card-line); box-shadow:0 2px 6px -2px rgba(14,111,158,.4); flex:none;
  }

.rank-row.top1 .rank-avatar{border-color:#e0a02c;}

.rank-row.top2 .rank-avatar{border-color:#a9bac8;}

.rank-row.top3 .rank-avatar{border-color:#a86a34;}

.rank-move{font-size:var(--fs-rankMove, 0.85rem); font-weight:700; padding:3px 9px; border-radius:999px; white-space:nowrap; flex:none;}

.rank-move.up{color:#1e8f4e; background:#e3f7ea;}

.rank-move.down{color:#c23b3b; background:#fbe6e6;}

.rank-move.new{color:#a8790a; background:#fff3d6;}

.rank-share-btn{
    border:none; background:linear-gradient(90deg,#8bd3dd,#3d405b); color:#fff;
    font-weight:700; font-size:var(--fs-rankShareBtn, 0.9rem); padding:8px 16px; border-radius:999px; cursor:pointer;
    white-space:nowrap; flex:none; transition:transform .15s ease; font-family:inherit;
  }

.rank-share-btn:hover{transform:translateY(-2px) scale(1.03);}

.rank-share-btn:disabled{opacity:.6; cursor:default; transform:none;}

.share-modal-backdrop{
    display:none; position:fixed; inset:0; background:rgba(12,42,58,.55);
    z-index:9999; align-items:flex-start; justify-content:center; padding:40px 20px;
    overflow-y:auto;
  }

.share-modal-backdrop.open{display:flex;}

.share-modal{
    position:relative; background:#fff; border-radius:var(--radius-lg); padding:34px 28px 28px;
    max-width:380px; width:100%; text-align:center; box-shadow:0 20px 60px -12px rgba(12,42,58,.4);
    border:3px solid #bfe6f5;
  }

.share-modal-close{
    position:absolute; top:14px; right:16px; border:none; background:#eef8f7; color:#3d405b;
    width:32px; height:32px; border-radius:50%; font-size:1rem; cursor:pointer; font-weight:700;
  }

.share-modal-icon{font-size:2.6rem; margin-bottom:6px; color:var(--sea-mid);}

.emoji-ic{font-family:'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji',sans-serif;}

.share-modal h3{margin:0 0 16px; color:var(--ink); font-size:1.25rem;}

.share-modal-preview{
    width:100%; border-radius:16px; margin-bottom:18px; display:block;
    box-shadow:0 8px 24px -8px rgba(12,42,58,.35); border:1px solid #e2eef3;
  }

.share-modal-btns{display:flex; flex-direction:column; gap:10px;}

.share-plat-btn{
    display:block; text-decoration:none; padding:12px 18px; border-radius:var(--radius-sm); font-weight:700;
    font-size:0.95rem; color:#fff; transition:transform .15s ease; font-family:inherit;
  }

.share-plat-btn:hover{transform:translateY(-2px);}

.share-plat-btn.tw{background:linear-gradient(90deg,#1d9bf0,#0d7ec9);}

.share-modal-dl{
    border:none; background:#f2f8f7; color:#3d405b; font-weight:700; font-size:0.85rem;
    padding:10px 16px; border-radius:var(--radius-sm); cursor:pointer; font-family:inherit; width:100%;
  }

.share-modal-dl:hover{background:#e3f1f8;}

.cur-event-donate{
    position:relative; overflow:hidden;
    margin:26px 0; background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-lg);
    box-shadow:var(--shadow); padding:26px 30px; text-align:center;
    animation:fadeInUp .6s ease-out both;
  }

.cur-event-donate .donate-deco-1{font-size:22px; top:10px; left:16px; animation:gentleFloat 4s ease-in-out infinite; color:var(--sand-line);}

.cur-event-donate .donate-deco-2{font-size:18px; bottom:14px; right:20px; animation:sparkleFloat 3.4s ease-in-out infinite; color:var(--sun);}

.cur-event-donate-title{font-size:var(--fs-donateTitle, 1.3rem); color:var(--sea-deep); font-weight:700; margin-bottom:14px; position:relative;}

.cur-event-donate .qr-strip{
    max-width:var(--donate-qr-width, 520px); margin:0 auto 16px; background:var(--sea-mid); color:#fff;
    padding:11px; border-radius:10px; font-size:var(--fs-donateStrip, 1.05rem); font-weight:700;
  }

.cur-event-donate .qr-row{max-width:var(--donate-qr-width, 520px); margin:0 auto;}

.cur-event-donate .qr-box{font-size:var(--fs-donateLabel, 1.1rem);}

.cur-event-donate-note{margin-top:16px; font-size:var(--fs-donateNote, 0.95rem); color:var(--ink-soft); font-weight:600;}

.event-hero{
    text-align:center; padding:20px 0 8px; position:relative;
  }

.event-hero h1{
    font-size:var(--fs-eventPageTitle, 1.8rem); font-weight:700; color:var(--sea-deep); margin:0 0 8px; text-shadow:none;
  }

.event-hero p{
    font-size:var(--fs-eventPageSubtitle, 1rem); color:var(--ink-soft); font-weight:600; margin:0;
  }

.event-hero .header-shortcuts{align-items:center; margin-top:10px;}

.cur-event{margin:26px 0;}

.cur-event-card{
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-lg);
    box-shadow:var(--shadow); overflow:hidden; position:relative;
    animation:fadeInUp .6s ease-out both;
  }

/* Confetti (render.js's fireConfetti()/startContinuousConfetti()) — falls continuously for
   as long as the goal stays at 100%. Layer sits inside the card so .cur-event-card's own
   overflow:hidden contains the fall. */
.confetti-layer{position:absolute; inset:0; pointer-events:none; z-index:5; overflow:hidden;}

.confetti-piece{
    position:absolute; top:-14px; width:8px; height:14px; border-radius:2px;
    transform:rotate(var(--rot,0deg));
    animation-name:confettiFall; animation-timing-function:ease-in; animation-fill-mode:forwards;
  }

@keyframes confettiFall{
    0%{transform:translateY(0) rotate(var(--rot,0deg)); opacity:1;}
    100%{transform:translateY(460px) rotate(calc(var(--rot,0deg) + 260deg)); opacity:0;}
  }

/* No max-height here on purpose (2026-09-04 fix, reported with a screenshot: a square
   1000x1000 upload was rendering as a small letterboxed strip). `max-height` combined with
   `height:auto` on a replaced element like <img> makes the browser recompute *width* too
   (CSS2.1 10.4) to preserve the intrinsic ratio — so a height cap here was silently shrinking
   the width as well for any image taller-than-wide, instead of just capping height as
   intended. Capping `max-width` on the img instead sidesteps that bug entirely (width is the
   directly-constrained dimension here, nothing to recompute) while still keeping a very tall
   square/portrait upload from turning into an 890px+ tall hero on a wide desktop screen —
   the cap only ever engages above 560px, so it's a no-op on every mobile/tablet width, which
   is most of this site's traffic anyway. */
.cur-event-img{background:#eaf6fb; display:flex; align-items:center; justify-content:center; overflow:hidden;}

.cur-event-img img{width:100%; max-width:560px; height:auto; object-fit:contain; display:block;}

.cur-event-body{padding:26px 30px; position:relative;}

.live-badge{
    display:inline-flex; align-items:center; gap:7px;
    background:linear-gradient(90deg,#ff6f91,#e0447e); color:#fff;
    padding:7px 18px; border-radius:999px; font-size:var(--fs-curEventBadge, 1.0625rem); font-weight:800;
    margin-bottom:12px; box-shadow:0 4px 10px -4px rgba(224,68,126,.6);
    animation:fadeInUp .6s ease-out both;
  }

.live-badge .dot{
    width:8px; height:8px; border-radius:50%; background:#fff; flex:none;
    animation:livePulse 1.4s ease-in-out infinite;
  }

@keyframes livePulse{
    0%,100%{opacity:1; transform:scale(1);}
    50%{opacity:.35; transform:scale(1.6);}
  }

.cur-event-body h2{color:var(--sea-deep); font-size:var(--fs-curEventTitle, 1.5rem); margin:0 0 10px; font-weight:700;}

.cur-event-body .desc{color:var(--ink-soft); font-weight:600; font-size:var(--fs-curEventDesc, 0.96875rem); margin:0 0 20px; white-space:pre-line;}

.cur-event-progress{margin-bottom:18px;}

.cur-event-bar-track{
    position:relative; height:30px; background:#eaf6fb; border-radius:999px;
    overflow:hidden; border:1px solid var(--card-line);
    box-shadow:inset 0 2px 6px rgba(14,111,158,.15);
  }

.cur-event-bar-fill{
    position:relative; height:100%; width:0%; border-radius:999px; overflow:hidden;
    background:linear-gradient(90deg,#8bd3dd,#3d405b); transition:width 1.2s ease-out;
  }

/* Periodic light sweep, distinct from the continuous "water" ripple already painted on this
   element (see the MOTION LAYER block near the end of this file) — a long cycle with a pause
   so it reads as a highlight, not a loading spinner. Reuses rankSweep's keyframes as-is. */
.cur-event-bar-fill::after{
    content:"";
    position:absolute; top:0; bottom:0; left:0; width:42%;
    background:linear-gradient(100deg, transparent, rgba(255,255,255,.75), transparent);
    animation:rankSweep 4.4s ease-in-out infinite;
    pointer-events:none;
  }

/* Flag markers at fixed 25/50/75% of the goal (this page has no per-activity milestone data
   the way the home page's tiers do) — populated by render.js's renderEvent(), dim until
   reached then pop+glow once via .reached. */
.cur-event-milestones{position:absolute; inset:0; pointer-events:none;}

.cur-event-milestone{
    position:absolute; top:50%; width:18px; height:18px; line-height:18px; text-align:center;
    transform:translate(-50%,-50%) scale(.7); font-size:12px;
    background:rgba(255,255,255,.9); border-radius:50%; box-shadow:0 0 0 2px rgba(255,255,255,.5);
    opacity:.4; filter:grayscale(1);
    transition:opacity .3s ease, filter .3s ease;
  }

.cur-event-milestone.reached{
    opacity:1; filter:none;
    animation:milestonePop .5s ease backwards;
  }

@keyframes milestonePop{
    0%{transform:translate(-50%,-50%) scale(.3);}
    60%{transform:translate(-50%,-50%) scale(1.35);}
    100%{transform:translate(-50%,-50%) scale(1);}
  }

.cur-event-pct{
    margin-top:8px; font-size:calc(var(--fs-curEventPct, 0.875rem) * 1.5); color:var(--sea-deep); font-weight:800; text-align:right;
    text-shadow:1px 1px 0 #fff;
  }

/* Optional per-activity countdown (render.js's startEventCountdownOnce()) — hidden entirely
   unless admin sets cur.deadline. Reuses the home page's .countdown-grid/.cnum/.clabel look
   for visual consistency, own wrapper + element ids so the two never collide. */
.cur-event-countdown{margin-bottom:18px; text-align:center;}

.cur-event-countdown .cec-label{font-weight:700; color:var(--sea-deep); margin-bottom:8px; font-size:0.9375rem;}

.cur-event-countdown .countdown-grid{max-width:360px; margin:0 auto;}

/* Stretch-goal marker: lives inside the same .cur-event-milestones layer as the 25/50/75%
   flags but styled distinctly (bigger, gold pulsing ring) since it marks a real past event —
   the original goal, now surpassed — not a generic quarter-mark. Only rendered by render.js
   once the stretch goal has actually kicked in. */
.cur-event-stretch-marker{
    position:absolute; top:50%; width:24px; height:24px; line-height:24px; text-align:center;
    transform:translate(-50%,-50%); font-size:15px;
    background:#fff8e1; border-radius:50%; box-shadow:0 0 0 3px #ffd166, 0 2px 8px -2px rgba(0,0,0,.35);
    animation:stretchMarkerPulse 1.8s ease-in-out infinite;
  }

@keyframes stretchMarkerPulse{
    0%,100%{box-shadow:0 0 0 3px #ffd166, 0 2px 8px -2px rgba(0,0,0,.35);}
    50%{box-shadow:0 0 0 6px rgba(255,209,102,.35), 0 2px 8px -2px rgba(0,0,0,.35);}
  }

/* Stretch-goal banner text: plain centered note before the bonus unlocks ("pending"), a
   celebratory gold pill once it does ("unlocked"). Collapses to nothing when render.js leaves
   it empty (no stretch goal configured at all). */
.cur-event-stretch-note{
    text-align:center; font-weight:700; font-size:0.9375rem; margin-top:10px;
    color:var(--sea-deep); transition:opacity .3s ease;
  }

.cur-event-stretch-note:empty{display:none; margin-top:0;}

.cur-event-stretch-note.pending{opacity:.85;}

.cur-event-stretch-note.unlocked{
    color:#a06b00; background:#fff3c4; border-radius:999px; padding:8px 14px;
    width:fit-content; margin:10px auto 0;
    animation:stretchNotePop .5s ease backwards;
  }

@keyframes stretchNotePop{
    0%{transform:scale(.85); opacity:0;}
    100%{transform:scale(1); opacity:1;}
  }

.cur-event-stats{display:grid; grid-template-columns:1.25fr 1fr 1fr; gap:12px; margin-bottom:14px;}

.cur-event-stats.no-goal{grid-template-columns:1fr; max-width:300px; margin-left:auto; margin-right:auto;}

.cur-event-stats .stat{
    background:#eaf9ff; border:1px solid var(--card-line); border-radius:var(--radius-sm);
    padding:12px 8px; text-align:center; transition:transform .2s ease;
  }

.cur-event-stats .stat:hover{transform:translateY(-3px);}

.cur-event-stats .stat.hero{
    background:linear-gradient(160deg,#8bd3dd,#3d405b);
    border:2px solid #fff;
    box-shadow:0 10px 22px -10px rgba(14,111,158,.6), inset 0 0 0 2px rgba(255,255,255,.3);
    padding:14px 8px;
  }

.cur-event-stats .stat.hero:hover{transform:translateY(-4px) scale(1.02);}

/* When an activity has no fixed goal (.no-goal), the hero "ยอดที่ได้ตอนนี้" box is the ONLY
   thing left in this section — the sweep/milestones/confetti above all live inside
   #curEventProgress, which is display:none in this state. Without its own flourish this box
   reads as completely static. Breathing glow + a couple of floating sparkle/heart decorations,
   scoped to .no-goal only so goal-having activities (which already have plenty going on) are
   untouched. .hero-deco spans exist in the markup unconditionally but stay display:none until
   .no-goal is toggled on by renderEvent(). */
.cur-event-stats.no-goal .stat.hero{position:relative; animation:heroGlow 2.4s ease-in-out infinite;}

.hero-deco{display:none; position:absolute; font-size:16px; pointer-events:none;}

.cur-event-stats.no-goal .hero-deco{display:block;}

.cur-event-stats.no-goal .hero-deco-1{top:-8px; left:14%; animation:sparkleFloat 2.6s ease-in-out infinite;}

.cur-event-stats.no-goal .hero-deco-2{bottom:-6px; right:14%; font-size:14px; animation:gentleFloat 3.4s ease-in-out infinite;}

@keyframes heroGlow{
    0%,100%{box-shadow:0 10px 22px -10px rgba(14,111,158,.6), inset 0 0 0 2px rgba(255,255,255,.3), 0 0 0 0 rgba(255,255,255,0);}
    50%{box-shadow:0 10px 22px -10px rgba(14,111,158,.6), inset 0 0 0 2px rgba(255,255,255,.3), 0 0 22px 4px rgba(255,255,255,.45);}
  }

.cur-event-stats .stat-label{display:block; font-size:var(--fs-curEventStatLabel, 0.78125rem); color:var(--ink-soft); font-weight:700; margin-bottom:4px;}

.cur-event-stats .stat.hero .stat-label{color:rgba(255,255,255,.9);}

.cur-event-stats .stat-val{display:block; font-size:var(--fs-curEventStatVal, 1.1rem); color:var(--sea-deep); font-weight:700;}

.cur-event-stats .stat.hero .stat-val{
    color:#fff; font-size:calc(var(--fs-curEventStatVal, 1.1rem) * 1.4); font-weight:800;
    text-shadow:1px 2px 0 rgba(0,0,0,.15);
  }

.cur-event-updated{font-size:var(--fs-curEventUpdated, 0.8125rem); color:var(--ink-soft); font-weight:600;}

/* Always-on pulsing dot next to "อัปเดตล่าสุด" — purely decorative "this is a live tracker"
   signal, not tied to an actual real-time connection. Reuses .live-badge .dot's livePulse
   keyframes (color-agnostic opacity/scale pulse) with its own color for this plain-background
   context, since .dot's white fill only reads against .live-badge's colored gradient. */
.updated-dot{
    display:inline-block; width:6px; height:6px; border-radius:50%;
    background:var(--success); margin-right:5px; vertical-align:1px;
    animation:livePulse 1.4s ease-in-out infinite;
  }

/* Brief landing-bounce once a count-up settles (render.js's popOnce()). */
.num-pop{animation:numPop .45s ease;}

@keyframes numPop{
    0%{transform:scale(1);}
    40%{transform:scale(1.18);}
    100%{transform:scale(1);}
  }

.past-events{margin:44px 0 20px;}

.past-events h2{color:var(--sea-deep); font-size:var(--fs-pastEventsTitle, 1.4rem); font-weight:700; margin:0 0 18px; text-align:center; text-shadow:none;}

.past-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:18px;}

.past-card{
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-md);
    overflow:hidden; box-shadow:var(--shadow); transition:transform .25s ease, box-shadow .25s ease;
    animation:fadeInUp .6s ease-out both;
    text-decoration:none; color:inherit; display:block;
  }

.past-card:hover{transform:translateY(-6px) scale(1.015); box-shadow:0 16px 30px -14px rgba(14,111,158,.45);}

.past-card-img{aspect-ratio:4/3; background:#eaf6fb; position:relative;}

.past-card-img img{
    width:100%; height:100%; object-fit:cover; display:block;
    filter:saturate(78%); transition:filter .3s ease;
  }

.past-card:hover .past-card-img img{filter:saturate(105%);}

.past-badge{
    display:inline-flex; align-items:center; gap:4px;
    background:linear-gradient(90deg,#5bc98a,#2f9e5c); color:#fff;
    padding:4px 11px; border-radius:999px; font-size:0.7rem; font-weight:800;
    box-shadow:0 3px 8px -2px rgba(47,158,92,.5);
  }

.past-card-img .past-badge{position:absolute; top:9px; left:9px; z-index:2;}

.past-card-body{padding:14px 16px;}

.past-card-body > .past-badge{margin-bottom:9px;}

.past-card-title{font-weight:700; color:var(--ink); font-size:var(--fs-pastCardTitle, 1rem); margin-bottom:6px;}

.past-card-amount{color:var(--sea-deep); font-weight:700; font-size:var(--fs-pastCardAmount, 0.9375rem);}

.past-card-note{color:var(--ink-soft); font-size:var(--fs-pastCardNote, 0.8125rem); font-weight:600; margin-top:2px;}

.past-empty{text-align:center; color:var(--ink-soft); font-weight:600; grid-column:1/-1;}

:root[data-theme="dark"] .cta-panel,
:root[data-theme="dark"] .qr-box,
:root[data-theme="dark"] .opc-box,
:root[data-theme="dark"] .support-panel,
:root[data-theme="dark"] .social-link,
:root[data-theme="dark"] .countdown-grid div,
:root[data-theme="dark"] .rewards,
:root[data-theme="dark"] .rank-row.top1,
:root[data-theme="dark"] .rank-row.top2,
:root[data-theme="dark"] .rank-row.top3,
:root[data-theme="dark"] .cur-event-stats .stat:not(.hero),
:root[data-theme="dark"] .share-modal{
    background:var(--card);
  }

:root[data-theme="dark"] .bottle-tag,
  :root[data-theme="dark"] .bottle-modal,
  :root[data-theme="dark"] .bottle-modal-close,
  :root[data-theme="dark"] .share-modal,
  :root[data-theme="dark"] .aq-tag{
    background:var(--card);
  }

:root[data-theme="dark"] .bottle-hero h1{
    text-shadow:none;
  }

.bottle-hero{text-align:center; padding:20px 0 8px; position:relative;}

.bottle-hero h1{font-size:var(--fs-bottlePageTitle, 2.1rem); font-weight:700; color:var(--sea-deep); margin:0 0 8px; text-shadow:none;}

.bottle-hero p{font-size:var(--fs-bottlePageSubtitle, 1.1rem); color:var(--ink-soft); font-weight:600; margin:0;}

/* mascot.html reuses .bottle-hero for its own title/subtitle but needs its own font-size
   vars (not --fs-bottlePageTitle/Subtitle) so the Bottle admin's slider can't bleed into
   it — id selectors here beat the .bottle-hero class rules above on specificity alone,
   independent of source order. */
#mascotPageTitle{font-size:var(--fs-mascotPageTitle, 2.1rem);}

#mascotPageSubtitle{font-size:var(--fs-mascotPageSubtitle, 1.1rem);}

/* aquarium.html, same reuse-and-decouple deal as mascot above. */
#aqPageTitle{font-size:var(--fs-aqPageTitle, 2.1rem);}

#aqPageSubtitle{font-size:var(--fs-aqPageSubtitle, 1.1rem);}

.bottle-sea{
    position:relative; margin:26px 0; min-height:360px; padding:36px 20px 24px;
    background:linear-gradient(180deg,#eaf9ff 0%,#bdeeff 45%,#5cc3ea 100%);
    border:1px solid var(--card-line); border-radius:var(--radius-lg); box-shadow:var(--shadow);
    display:flex; flex-wrap:wrap; align-content:flex-start; justify-content:center; gap:22px 20px; overflow:hidden;
    animation:fadeInUp .6s ease-out both;
  }

.bottle-item{
    border:none; background:none; cursor:pointer; padding:6px; color:var(--sea-deep);
    display:flex; flex-direction:column; align-items:center; gap:6px;
    animation:bottleDrift 5.2s cubic-bezier(0.45,0,0.55,1) infinite;
    transition:transform .15s ease; font-family:inherit; will-change:transform;
    content-visibility:auto; contain-intrinsic-size:150px 215px;
  }

.bottle-item:hover .bottle-art{transform:scale(1.1);}

.bottle-art{
    width:var(--bottle-art-size, 92px); height:auto; filter:drop-shadow(0 6px 8px rgba(14,111,158,.35));
    animation:bottleBob 3.6s cubic-bezier(0.45,0,0.55,1) infinite; transition:transform .15s ease;
    will-change:transform;
  }

.bottle-tag{
    max-width:150px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    background:#fffdf6; color:var(--ink); font-weight:700; font-size:var(--fs-bottleTag, 0.9rem);
    padding:3px 10px; border-radius:999px; border:1.5px solid var(--card-line);
    box-shadow:0 3px 6px -2px rgba(14,111,158,.3);
  }

@keyframes bottleBob{
    0%,100%{transform:translateY(0) rotate(-4deg);}
    50%{transform:translateY(-14px) rotate(4deg);}
  }

@keyframes bottleDrift{
    0%,100%{transform:translateX(0);}
    50%{transform:translateX(10px);}
  }

.bottle-empty{text-align:center; color:var(--ink-soft); font-weight:600; margin:20px auto;}

/* ---- Song request playlist (2026-08-30) --------------------------------
   Admin-curated list, same spirit as the Bottle sea above but a plain
   scannable list instead of floating icons — the point here is "which songs
   got asked for the most", so rank order (via requesters.length, sorted in
   render.js) carries real information and deserves a visible rank number. */
.pl-empty{text-align:center; color:var(--ink-soft); font-weight:600; margin:20px auto;}

.pl-list{margin-bottom:20px;}

.pl-song{
    display:flex; gap:12px; align-items:flex-start;
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-md);
    padding:16px; margin-bottom:12px; box-shadow:var(--shadow-soft);
  }

.pl-song.pl-top{border-color:var(--coral-deep); box-shadow:var(--shadow-lift);}

.pl-rank{
    flex:none; width:26px; padding-top:15px; text-align:center;
    font-size:1rem; font-weight:700; color:var(--ink-soft); font-variant-numeric:tabular-nums;
  }

.pl-song.pl-top .pl-rank{font-size:1.4rem; color:var(--coral-deep);}

/* "Now playing" equalizer bars, top song only (render.js only emits .pl-eq when isTop). */
.pl-eq{display:inline-flex; align-items:flex-end; justify-content:center; gap:2px; width:100%; height:14px; margin-top:4px;}

.pl-eq span{width:3px; background:var(--coral-deep); border-radius:1px; animation:eqBounce .9s ease-in-out infinite;}

.pl-eq span:nth-child(1){animation-delay:-0.6s;}
.pl-eq span:nth-child(2){animation-delay:-0.3s;}
.pl-eq span:nth-child(3){animation-delay:0s;}

@keyframes eqBounce{
    0%,100%{height:4px;}
    50%{height:14px;}
  }

.pl-art{
    flex:none; width:52px; height:52px; border-radius:14px;
    display:flex; align-items:center; justify-content:center; font-size:1.4rem;
    background:var(--accent-grad); color:#fff;
  }

.pl-meta{flex:1; min-width:0; padding-top:2px;}

.pl-row1{display:flex; align-items:flex-start; justify-content:space-between; gap:10px;}

.pl-title{margin:0; font-size:1.3rem; font-weight:700; color:var(--ink);}

.pl-artist{margin:2px 0 0; font-size:1.05rem; color:var(--ink-soft); font-weight:600;}

.pl-score{
    flex:none; display:flex; align-items:center; gap:4px; padding:5px 12px; border-radius:999px;
    background:var(--sky-mid); color:var(--sea-deep); font-size:0.92rem; font-weight:700;
    font-variant-numeric:tabular-nums; white-space:nowrap;
  }

.pl-song.pl-top .pl-score{background:var(--coral-deep); color:#fff;}

/* Groups the ▲▼NEW movement badge (reuses .rank-move from the Ranking page as-is) next to
   the request-count badge, same row. */
.pl-badges{flex:none; display:flex; align-items:center; gap:6px;}

.pl-score.hot{animation:scoreGlow 1.8s ease-in-out infinite;}

@keyframes scoreGlow{
    0%,100%{box-shadow:0 0 0 0 var(--coral-deep);}
    50%{box-shadow:0 0 10px 3px var(--coral-deep);}
  }

.pl-from{margin-top:9px; font-size:1.05rem; color:var(--ink-soft); font-weight:600;}

.pl-name{color:var(--ink); font-weight:700;}

.pl-name-times{color:var(--coral-deep); font-weight:700; font-size:0.85em; margin-left:2px;}

.pl-note{
    margin:8px 0 0; padding:9px 12px; background:var(--sand); border-radius:var(--radius-sm);
    font-size:1.0rem; color:var(--ink); line-height:1.5; font-style:italic;
  }

/* height defaults to `auto`, in which case aspect-ratio drives it exactly as before this
   existed — the admin's tank-height slider (--aq-tank-height, applied by renderAquarium())
   overrides it with an explicit px height instead, added 2026-09-06 so a tank stocked with
   a lot of fish (all positioned by top/left % — see renderAquarium()) has more vertical room
   to spread into instead of everything cramming into the same ~4:3 box on a narrow phone. */
.aq-tank{
    position:relative; margin:26px 0 18px; border-radius:var(--radius-lg); overflow:hidden;
    box-shadow:var(--shadow); animation:fadeInUp .6s ease-out both;
    height:var(--aq-tank-height, auto); aspect-ratio:4/3;
  }

/* Aquarium World zones (2026-09-10, RWM-67, item F) — .aq-water is now the fixed-size
   *viewport* window (scrollable, not overflow:hidden), and .aq-water-world is the actual
   %-of-itself coordinate space fish are positioned in (see render.js's aqZoneLeftToWorld()) —
   3x the viewport width, one screen-width per zone. Splitting these into two elements (instead
   of just widening .aq-water itself) is what lets the toolbar/goal-pill/catalog-overlay — all
   positioned relative to .aq-tank, not .aq-water — stay pinned to the viewport corners while
   the water scrolls underneath them, exactly like they already did before zones existed.
   scroll-snap makes a swipe/drag settle on a whole zone rather than a half-scrolled one; the
   scrollbar itself is hidden since the zone-nav pills below are the intended way to see "where
   am I" — a visible native scrollbar across the bottom of a photo tank would look out of place. */
.aq-water{
    position:absolute; inset:0; overflow-x:auto; overflow-y:hidden;
    scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
    scrollbar-width:none; cursor:grab;
    /* Scopes layout/paint work to this box (2026-09-10, added alongside the drag-jank fix
       above) — tells the browser nothing outside .aq-water's own bounds can be affected by
       what happens inside it (already true — overflow-x:auto already clips it — this just lets
       the render pipeline skip re-checking that against the rest of the page on every scroll/
       animation frame instead of taking it on faith implicitly). */
    contain:layout paint;
  }
.aq-water::-webkit-scrollbar{display:none;}
.aq-water.aq-dragging{cursor:grabbing; scroll-snap-type:none;}

.aq-water-world{position:relative; width:300%; height:100%;}

/* Same ocean-bg.webp photo as before RWM-67 — tiled once per zone (background-size:33.34% 100%,
   one tile = one viewport-width) rather than inventing separate zone photography, per the
   project's standing rule against drawing new scene art. Per-zone identity comes entirely from
   the color-wash tint divs below, on top of this same base photo — same "light-touch" treatment
   RWM-50/63 already gave this page's chrome, not new art. */
.aq-water-world{
    background-image:url('https://pub-d2631cc08f5a4694bdeb93749787782b.r2.dev/assets/aquarium/ocean-bg.webp');
    background-repeat:repeat-x; background-size:33.34% 100%; background-position:0 0;
  }

.aq-zone-tint{position:absolute; top:0; bottom:0; width:33.34%; pointer-events:none;}
.aq-zone-tint:nth-child(1){left:0; background:linear-gradient(180deg, rgba(255,214,153,.35), rgba(120,214,190,.25));}
.aq-zone-tint:nth-child(2){left:33.33%; background:none;} /* main ocean = the base photo's own natural look, no tint needed */
.aq-zone-tint:nth-child(3){left:66.66%; background:linear-gradient(180deg, rgba(6,20,46,.35), rgba(2,8,24,.65));}

/* Fish live in this layer, not .aq-water-world directly — renderAquarium() replaces its
   innerHTML wholesale on every render, which would otherwise wipe the 3 static .aq-zone-tint
   divs above out along with it. inset:0 makes it exactly overlay .aq-water-world, so every
   fish's %-based left/top still resolves identically to before this layer existed. */
#aqFishLayer{position:absolute; inset:0;}

/* Zone quick-nav — bottom-center pill row, out of the way of the top-right toolbar. Scrolling
   .aq-water is what actually moves between zones (drag/swipe/wheel all just work natively via
   overflow-x:auto + scroll-snap); these buttons are the non-drag path (keyboard/a11y, and a
   visible "you are here" indicator via .active, kept in sync with real scroll position by a
   scroll listener in render.js). */
.aq-zone-nav{
    position:absolute; left:50%; bottom:10px; transform:translateX(-50%); z-index:6;
    display:flex; gap:6px; padding:5px; border-radius:999px; background:rgba(0,0,0,.35);
  }
.aq-zone-btn{
    border:none; background:none; color:rgba(255,255,255,.75); font-size:var(--fs-aqZoneBtn, 0.72rem); font-weight:700;
    padding:6px 10px; border-radius:999px; cursor:pointer; white-space:nowrap; font-family:inherit;
    transition:background .15s ease, color .15s ease;
  }
.aq-zone-btn:hover{color:#fff;}
.aq-zone-btn.active{background:rgba(255,255,255,.9); color:#0d3348;}

/* .aq-mini-goal (below) only ever shows in fullscreen, and sits at this exact same bottom-
   center spot — stack the zone-nav above it there instead of overlapping. */
/* + env(safe-area-inset-bottom) (2026-09-10, alongside aquarium.html gaining viewport-fit=cover
   below) — only meaningful once the fullscreen tank is truly edge-to-edge; harmless elsewhere
   since env() is 0 on any page that didn't opt into cover mode. Keeps this clear of the home
   indicator bar on a notched iPhone in landscape instead of sitting right on top of it. */
.aq-tank.is-fullscreen .aq-zone-nav{bottom:calc(58px + env(safe-area-inset-bottom));}

/* Tank toolbar: fullscreen toggle (2026-09-06, on request) — a CSS-only "fill the
   viewport" overlay rather than the real Fullscreen API: iOS Safari has no/poor support
   for requestFullscreen() on an arbitrary element, and this site's traffic is mostly
   mobile, so the API route would silently fail for a big chunk of visitors. Fish positions
   are already stored as top/left percentages (renderAquarium() in render.js), so blowing
   the tank up to fill the screen needs no extra math — they redistribute automatically.
   Catalog and hide-name buttons joined it the same day, for the same reason: both were
   asked for specifically because they're needed *inside* fullscreen, where the page's own
   catalog section below the tank isn't reachable. */
.aq-tank-toolbar{position:absolute; top:10px; right:10px; z-index:6; display:flex; gap:8px;}

.aq-tank-toggle{
    width:38px; height:38px; border-radius:50%; border:1px solid rgba(255,255,255,.4);
    background:rgba(0,0,0,.35); color:#fff; font-size:1.15rem; line-height:1;
    display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0;
    transition:background .15s ease; flex:none;
  }

.aq-tank-toggle:hover{background:rgba(0,0,0,.55);}

.aq-tank-toggle .svgicon{width:1.05rem; height:1.05rem;}

/* 2026-09-10, real bug reported live (buttons off-center / pushed below the visible screen in
   fullscreen): `inset:0` on a `position:fixed` element already fills the viewport exactly by
   itself — the `width:100vw;height:100vh` alongside it was redundant, and actively wrong in two
   ways: `100vw` traditionally counts the vertical scrollbar's own width in many browsers (so it's
   wider than the actually-visible area, throwing off anything centered with `left:50%`), and
   `100vh` can momentarily diverge from the true viewport height during a real browser fullscreen
   transition (confirmed live: `getBoundingClientRect()` measured the tank ~113px taller than
   `window.innerHeight` right after entering fullscreen). Dropped both, `inset:0` alone is the
   more accurate source of truth either way. */
.aq-tank.is-fullscreen{
    position:fixed; inset:0; z-index:200;
    aspect-ratio:auto; border-radius:0; margin:0;
  }

/* Real bug reported with a screenshot (2026-09-07): the tank's own toolbar sits top-right
   of the tank same as it always has, but once the tank is fullscreen that's the exact same
   viewport corner as the site-wide theme/ambient-sound buttons (.theme-toggle, z-index:60,
   fixed to the viewport) — those sit on top and hid the tank's 📖/🏷️/⛶ buttons completely,
   which is why the catalog looked unreachable in fullscreen. Moving the toolbar to the
   opposite corner only while fullscreen sidesteps the collision instead of fighting it with
   z-index (stacking them at the same spot would just make two rows of circular buttons
   visually collide instead of one hiding the other). */
/* max()'d against env(safe-area-inset-*) for the same reason as .aq-zone-nav above — keeps this
   clear of the notch/rounded-corner cutout once the tank is truly edge-to-edge, no-op elsewhere. */
.aq-tank.is-fullscreen .aq-tank-toolbar{
    left:max(10px, env(safe-area-inset-left)); right:auto;
    top:max(10px, env(safe-area-inset-top));
  }

/* Landscape hint (2026-09-10, on request — fullscreen on a phone in portrait only shows about a
   third of the 300%-wide zone world per screen; landscape shows much more at once). Hidden by
   default; the media query is the only thing that ever shows it, so it needs no dismiss button or
   JS rotation-listener — rotating the phone to landscape makes the query stop matching and the
   hint disappears on its own, same as it would for a resize on desktop. Text-shadow instead of a
   background pill (unlike .aq-tank-toggle) since this sits front-and-center over the fish, not
   tucked in a corner — a solid pill there would block more of the view than the hint is worth. */
.aq-rotate-hint{display:none;}
@media (orientation:portrait) and (max-width:768px){
  .aq-tank.is-fullscreen .aq-rotate-hint{
      display:block; position:absolute; top:max(10px, env(safe-area-inset-top)); left:50%; transform:translateX(-50%);
      z-index:6; color:#fff; font-size:0.8rem; font-weight:600; text-align:center;
      text-shadow:0 1px 4px rgba(0,0,0,.7); pointer-events:none; max-width:80%;
    }
}

/* Locks background scroll while the tank overlay is open — without this, touch-dragging
   on a phone would scroll the page underneath the fixed-position tank. */
body.aq-fullscreen-lock{overflow:hidden;}

/* In-tank catalog overlay — the same species/price data as the page's own #aqCatalog
   below the tank (renderAquariumCatalog() in render.js now paints both from one function),
   just reachable while the tank is fullscreen and the real one is scrolled out of view. */
.aq-catalog-overlay{
    position:absolute; inset:0; z-index:7; background:rgba(10,20,30,.72);
    display:none; align-items:center; justify-content:center; padding:16px; overflow:auto;
  }

.aq-catalog-overlay.open{display:flex;}

.aq-catalog-overlay-inner{
    background:#fdf3df; border-radius:18px; padding:16px; max-width:520px; width:100%;
    max-height:100%; overflow:auto; box-shadow:0 20px 50px -20px rgba(0,0,0,.6);
  }

.aq-catalog-overlay-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:10px;}

.aq-catalog-overlay-head h3{margin:0; font-size:1.05rem; color:#5a4426;}

.aq-catalog-overlay-head .aq-tank-toggle{
    position:static; background:#e7c78f; color:#5a4426; border-color:#d8b06a; width:32px; height:32px;
  }

.aq-catalog-overlay-head .aq-tank-toggle:hover{background:#dcb87a;}

/* Payment QR inside the overlay (2026-09-06, on request — pay without leaving fullscreen).
   Reuses .qr-row/.qr-box from the home page's own CTA panel for visual consistency; this
   compact variant just drops .qr-box's normal label/strip since the overlay is small. */
.aq-overlay-qr-title{margin:14px 0 8px; text-align:center; font-size:0.85rem; font-weight:700; color:#5a4426;}

.aq-overlay-qr-row .qr-box{padding:8px;}

.aq-overlay-qr-row .qr-box img{width:100%; display:block; border-radius:6px;}

.aq-fish{
    position:absolute; padding:0; margin:0; cursor:pointer;
    will-change:transform;
  }

/* Idle bob (RWM-66) lives on this inner wrapper, not .aq-fish itself, so the CSS keyframe
   animation (transform: translateY/rotate) and the free-swim hop's own transform (also
   translate, applied to the OUTER .aq-fish by startAquariumFreeSwim() in render.js) don't fight
   over the same property on the same element — nested elements' transforms compose visually
   instead. position:relative so children (.aq-tag, .aq-rare-crown, .aq-school) keep anchoring
   exactly as before, one level deeper. */
.aq-fish-bob{
    position:relative;
    animation-name:aqBob; animation-timing-function:ease-in-out; animation-iteration-count:infinite;
  }

.aq-fish img{
    display:block; width:56px; height:auto; image-rendering:pixelated; filter:drop-shadow(0 3px 3px rgba(0,0,0,.2));
    scale:var(--aq-size,1);
  }

.aq-fish.facing-left img, .aq-fish.facing-left .aq-fish-sprite.aq-flippable{transform:scaleX(-1);}

/* Grouped-display school (2026-09-10, RWM-61/62) — .aq-fish-group is still .aq-fish (same
   free-swim/bob mechanics apply, see startAquariumFreeSwim() in render.js), it just contains a
   small cluster of member sprites instead of one. .aq-school is a zero-size relatively-positioned
   anchor at the wrapper's own top-left; each member is absolutely positioned at that same point
   and then nudged out by its own translate() offset (render.js), so the cluster spreads around
   one shared anchor rather than each member carrying real layout size. */
.aq-school{position:relative;}
.aq-school-member{position:absolute; left:0; top:0;}

.aq-fish-sprite{
    display:block; background-repeat:no-repeat; image-rendering:pixelated;
    filter:drop-shadow(0 3px 3px rgba(0,0,0,.2)); animation-iteration-count:infinite;
    scale:var(--aq-size,1);
  }

/* Per-species frame size / sheet size / step count — background-image + animation-name/duration/
   delay/timing-function for flip species are set inline per-fish in render.js (see AQUARIUM_SPECIES),
   since those vary per fish instance (swim duration) or are data-driven (sprite URL). */
.aq-fish-sprite.sp-clown{width:64px; height:47px; background-size:256px 47px; animation-name:aqFrame-clown; animation-duration:0.8s; animation-timing-function:steps(4);}
.aq-fish-sprite.sp-jelly{width:60px; height:60px; background-size:240px 60px; animation-name:aqFrame-jelly; animation-duration:1.6s; animation-timing-function:steps(4);}
.aq-fish-sprite.sp-turtle{width:66px; height:47px; background-size:264px 47px; animation-name:aqFrame-turtle; animation-duration:1.0s; animation-timing-function:steps(4);}
.aq-fish-sprite.sp-shark{width:70px; height:48px; background-size:280px 48px; animation-name:aqFrame-shark; animation-duration:0.6s; animation-timing-function:steps(4);}
.aq-fish-sprite.sp-whale{width:88px; height:97px; background-size:352px 97px; animation-name:aqFrame-whale; animation-duration:2.0s; animation-timing-function:steps(4);}
.aq-fish-sprite.sp-mermaid{width:96px; height:65px; background-size:384px 65px; animation-name:aqFrame-mermaid; animation-duration:1.4s; animation-timing-function:steps(4);}

@keyframes aqFrame-clown{from{background-position:0 0;} to{background-position:-256px 0;}}
@keyframes aqFrame-jelly{from{background-position:0 0;} to{background-position:-240px 0;}}
@keyframes aqFrame-turtle{from{background-position:0 0;} to{background-position:-264px 0;}}
@keyframes aqFrame-shark{from{background-position:0 0;} to{background-position:-280px 0;}}
@keyframes aqFrame-whale{from{background-position:0 0;} to{background-position:-352px 0;}}
@keyframes aqFrame-mermaid{from{background-position:0 0;} to{background-position:-384px 0;}}

/* Names show by default now (2026-09-06, on request — "โชว์ชื่อคนที่ซื้อปลาตลอด").
   .aq-water.names-hidden is the opt-out toggle (aqNamesToggle button, render.js) — the only
   way to control name visibility now. A per-fish tap-to-reveal-just-this-one override used
   to layer on top of the hidden state (.showing class + a click handler in
   bindAquariumInteractionsOnce()), but that was removed (2026-09-10, on request) — fish
   drift to a new spot every couple of seconds regardless of interaction, so a second tap
   meant to dismiss one name often missed the fish (it had already moved) and left the name
   stuck showing, reported as "แมงกะพรุนเวลาจะปิดชื่อ มันไม่ยอมปิด". Simpler to drop the
   feature than to keep patching around fish that won't hold still. */
/* Scales per species (2026-09-10, on request — first pass just tied this to --aq-size, the
   admin's manual size slider, but that left a whale/mermaid's tag the same size as a clownfish's
   whenever no admin had touched that species' slider — "ให้ป้ายตรงนี้ปรับขนาดของแต่ละชนิดได้":
   their sprite reads bigger purely from its own larger fixed pixel dimensions (.sp-whale/
   .sp-mermaid in this file), not from --aq-size, so the tag needs its own scale that accounts for
   that too). --aq-tag-scale (set inline on .aq-fish in render.js, alongside --aq-size) is that
   species' relative sprite size (the same AQ_SCHOOL_SPECIES_SCALE table a school's own member
   spread already uses) times the admin's size slider — so a whale/mermaid tag now reads bigger by
   default, and still grows further if an admin bumps that species' size slider on top. Falls back
   to --aq-size for any element that somehow doesn't get --aq-tag-scale set. `scale` is its own
   independent transform-list property (CSS Transforms Level 2), so it composes with this
   element's own translate() below the same way it already does on .aq-fish img/.aq-fish-sprite. */
.aq-tag{
    position:absolute; left:50%; bottom:100%; transform:translate(-50%,-2px);
    scale:var(--aq-tag-scale, var(--aq-size,1));
    max-width:110px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    background:#fffdf6; color:var(--ink); font-weight:700; font-size:0.78rem;
    padding:2px 9px; border-radius:999px; border:1.5px solid var(--card-line);
    box-shadow:0 3px 6px -2px rgba(14,111,158,.3); opacity:1; pointer-events:none;
    transition:opacity .15s ease, transform .15s ease; margin-bottom:2px;
  }

.aq-water.names-hidden .aq-tag{opacity:0; transform:translate(-50%,4px);}

/* Grouped-school label (2026-09-10, RWM-61/62) — same pill, just bolder + a touch wider so
   "Name ×N" has room to keep its count visible instead of ellipsis-truncating it away, which
   would defeat the point of the label. Must come after .aq-tag above to win the cascade (same
   specificity, single class each) — a first attempt placed this rule earlier in the file and
   the extra font-weight silently lost to .aq-tag's own, caught via getComputedStyle in testing. */
.aq-tag-group{font-weight:800; max-width:140px;}

/* Per-species pastel label colors (2026-09-10, RWM-63, on request — "small and cute" labels
   with a color accent per fish type instead of one uniform off-white pill). Attribute selectors
   ([data-aq-species=...], set in render.js) rather than a plain class per species — this gives
   each rule (0,2,0) specificity, strictly higher than the plain .aq-tag/.aq-tag-group base rules
   above (0,1,0 each), so these always win regardless of source order (unlike the .aq-tag-group
   font-weight mistake right above this comment — no repeat of that bug needed here). Colors kept
   in the tank's own separate palette (plain hex, not --pc-* tokens) — same "light-touch, own
   sub-theme" treatment RWM-50 already gave the tank/catalog, not the sitewide Pink Cute retheme. */
/* Same selector list also covers .aq-search-breakdown's per-species pills (RWM-64) — one
   shared palette for "this species" wherever it shows up on the page, not two color systems
   to keep in sync by hand. */
.aq-tag[data-aq-species="clown"], .aq-search-breakdown li[data-aq-species="clown"]{background:#fff1e0; border-color:#f3b673; color:#b5650a;}
.aq-tag[data-aq-species="jelly"], .aq-search-breakdown li[data-aq-species="jelly"]{background:#efe7ff; border-color:#c6b4f7; color:#6b46c1;}
.aq-tag[data-aq-species="turtle"], .aq-search-breakdown li[data-aq-species="turtle"]{background:#e5f7ea; border-color:#a9dfba; color:#1f8a4c;}
.aq-tag[data-aq-species="shark"], .aq-search-breakdown li[data-aq-species="shark"]{background:#e3f1ff; border-color:#a9cdf5; color:#1f66b5;}
.aq-tag[data-aq-species="whale"], .aq-search-breakdown li[data-aq-species="whale"]{background:#e0fbff; border-color:#a6e6f0; color:#1291a8;}
.aq-tag[data-aq-species="mermaid"], .aq-search-breakdown li[data-aq-species="mermaid"]{background:#ffe6f2; border-color:#f7b8d8; color:#c93a82;}

:root[data-theme="dark"] .aq-tag[data-aq-species="clown"], :root[data-theme="dark"] .aq-search-breakdown li[data-aq-species="clown"]{background:#4a2f12; border-color:#c98a3c; color:#ffcf94;}
:root[data-theme="dark"] .aq-tag[data-aq-species="jelly"], :root[data-theme="dark"] .aq-search-breakdown li[data-aq-species="jelly"]{background:#2c2149; border-color:#8f6fe0; color:#d9c9ff;}
:root[data-theme="dark"] .aq-tag[data-aq-species="turtle"], :root[data-theme="dark"] .aq-search-breakdown li[data-aq-species="turtle"]{background:#163a24; border-color:#4caf6f; color:#a8e8bd;}
:root[data-theme="dark"] .aq-tag[data-aq-species="shark"], :root[data-theme="dark"] .aq-search-breakdown li[data-aq-species="shark"]{background:#16273f; border-color:#4f8fd1; color:#b7d9ff;}
:root[data-theme="dark"] .aq-tag[data-aq-species="whale"], :root[data-theme="dark"] .aq-search-breakdown li[data-aq-species="whale"]{background:#123238; border-color:#4fb9c9; color:#b0eef5;}
:root[data-theme="dark"] .aq-tag[data-aq-species="mermaid"], :root[data-theme="dark"] .aq-search-breakdown li[data-aq-species="mermaid"]{background:#3c1830; border-color:#d3689f; color:#ffc2e3;}

/* Fish/school detail card (2026-09-10, RWM-63) — content styling only; the card chrome itself
   (backdrop, rounded card, close button, Pink-Cute-retheme + dark-mode overrides) all comes free
   from reusing .bottle-overlay/.bottle-modal as-is (see aquarium.html). .aq-detail-modal just
   trims that component's default padding for a smaller "detail card" feel, per the request. */
/* Real bug reported from a phone (2026-09-10, "กดที่ตัวปลาไม่ขึ้นอะไรเลย" — tapping a fish did
   nothing): .bottle-overlay's shared z-index:100 loses to .aq-tank.is-fullscreen's z-index:200,
   so inside fullscreen the card was actually opening (pointer-events:auto, opacity:1) — just
   painted underneath the fullscreen tank, invisible. Scoped to this one id, not .bottle-overlay
   itself, so the real message-in-a-bottle modal elsewhere on the site is untouched. */
#aqDetailOverlay{z-index:201;}
.aq-detail-modal{padding:22px 20px 20px;}

/* Donor leaderboard tab (2026-09-12, on request) — same z-index reason as #aqDetailOverlay just
   above: without it, .bottle-overlay's shared z-index:100 loses to .aq-tank.is-fullscreen's
   z-index:200 and the tab would look like it does nothing while fullscreen is open. */
#aqLeaderboardOverlay{z-index:201;}
.aq-leaderboard-modal{max-height:80vh; padding:22px 20px 20px; display:flex; flex-direction:column;}
.aq-leaderboard-title{margin:0 0 14px; font-size:var(--fs-aqLeaderboardTitle, 1.2rem); color:var(--ink);}
.aq-leaderboard-list{list-style:none; margin:0; padding:0 2px 0 0; overflow-y:auto; text-align:left;}
.aq-leader-row{display:flex; align-items:center; gap:10px; padding:9px 4px; border-bottom:1px solid var(--card-line);}
.aq-leader-row:last-child{border-bottom:none;}
.aq-leader-row.top1 .aq-leader-rank{color:#e0a52c;}
.aq-leader-rank{width:22px; min-width:22px; text-align:center; font-weight:700; font-size:0.85rem; color:var(--ink-soft);}
.aq-leader-name{flex:1; font-weight:700; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.aq-leader-amt{font-weight:700; font-size:0.9rem; color:var(--sea-deep); white-space:nowrap;}
.aq-leader-empty{padding:18px 0; text-align:center; color:var(--ink-soft);}
.aq-detail-art{margin-bottom:8px;}
.aq-detail-art img{width:64px; height:64px; object-fit:contain; image-rendering:pixelated;}
.aq-detail-owner{margin:0 0 4px; font-size:var(--fs-aqDetailOwner, 1.15rem); color:var(--ink);}
.aq-detail-species{color:var(--ink-soft); font-weight:700; font-size:var(--fs-aqDetailSpecies, 0.85rem);}
.aq-detail-qty{margin-top:6px; color:var(--sea-deep); font-weight:700; font-size:var(--fs-aqDetailQty, 0.85rem);}
.aq-detail-message{
    margin-top:14px; padding-top:14px; border-top:1.5px dashed var(--card-line);
    white-space:pre-line; color:var(--ink); font-weight:600; font-size:0.95rem; line-height:1.6;
  }

/* Rare species crown + glow (2026-09-10, on request — "ถ้าอยากให้ตัวแพงๆเด่นๆ ควรมีอะไร
   เสริมมั้ย"). Threshold lives in render.js's aquariumIsRare() (currently 1000฿+, so only
   mermaid qualifies today). ::before instead of ::after since ::after would be free for a
   future use; painted with no z-index so it just falls into normal paint order behind this
   fish's own sprite/tag but in front of earlier fish in the water — no need to fight
   stacking contexts for a decorative glow. */
.aq-fish-rare::before{
    content:"";
    position:absolute;
    inset:-10px;
    border-radius:50%;
    background:radial-gradient(circle, rgba(255,209,102,.55), rgba(255,209,102,0) 70%);
    animation:aqRareGlow 2.4s ease-in-out infinite;
    pointer-events:none;
  }

@keyframes aqRareGlow{
    0%,100%{opacity:.55; transform:scale(1);}
    50%{opacity:.9; transform:scale(1.12);}
  }

.aq-rare-crown{
    position:absolute; top:-4px; right:2px;
    font-size:14px; line-height:1; pointer-events:none;
    filter:drop-shadow(0 1px 2px rgba(0,0,0,.35));
    animation:aqCrownBob 2.2s ease-in-out infinite;
  }

@keyframes aqCrownBob{
    0%,100%{transform:translateY(0) rotate(-6deg);}
    50%{transform:translateY(-3px) rotate(6deg);}
  }

@keyframes aqBob{
    0%{transform:translateY(0) rotate(0deg);}
    25%{transform:translateY(-7px) rotate(-4deg);}
    50%{transform:translateY(0) rotate(0deg);}
    75%{transform:translateY(7px) rotate(4deg);}
    100%{transform:translateY(0) rotate(0deg);}
  }

/* Owner search / Find My Fish / Share My Fish (2026-09-10, RWM-64). Base (light-mode) rules
   here; Pink Cute companions live in the AQUARIUM PAGE retheme section further down — this
   card sits directly between the goal card (.aq-goal-card, reuses .cur-event-card, already
   Pink-Cute-styled per RWM-58/47) and the tank frame (Pink-Cute-styled per RWM-63), so it needs
   the same comic-border treatment to not look like a mismatched leftover between them — this is
   page chrome, not tank/fish art, so RWM-50's "leave the tank's own sub-theme alone" rule
   doesn't apply here the way it does to .aq-tag's colors. */
.aq-search-card{
    margin-bottom:18px; padding:16px 18px; border-radius:var(--radius-lg);
    background:#fffdf6; border:1.5px solid var(--card-line); box-shadow:var(--shadow);
  }
.aq-search-row{display:flex; gap:8px;}
.aq-search-input{
    flex:1; min-width:0; font:inherit; font-size:var(--fs-aqSearchInput, 0.95rem); padding:10px 14px;
    border-radius:var(--radius-sm); border:1px solid var(--card-line);
    background:var(--body-bg-b); color:var(--ink);
  }
.aq-search-input:focus{outline:2px solid var(--sea-light);}
.aq-search-empty{margin-top:8px; color:var(--ink-soft); font-size:0.85rem;}
.aq-search-result{display:none; margin-top:14px; padding-top:14px; border-top:1.5px dashed var(--card-line);}
.aq-search-result.show{display:block;}
.aq-search-owner{margin:0 0 2px; font-size:var(--fs-aqSearchOwner, 1.1rem); color:var(--sea-deep);}
.aq-search-total{color:var(--ink-soft); font-weight:700; font-size:var(--fs-aqSearchTotal, 0.85rem); margin-bottom:10px;}
.aq-search-breakdown{list-style:none; margin:0 0 14px; padding:0; display:flex; flex-wrap:wrap; gap:6px;}
.aq-search-breakdown li{font-size:var(--fs-aqSearchBreakdown, 0.8rem); font-weight:700; padding:3px 10px; border-radius:999px; border:1.5px solid transparent;}
.aq-search-actions{display:flex; gap:8px; flex-wrap:wrap;}
/* Shared by ค้นหา/หาปลาฉัน/แชร์ปลาฉัน (all .rank-share-btn, reused as-is from Ranking's share
   button) — scoped to this card specifically so the admin's size choice can't leak onto
   Ranking's own share button, which uses the exact same class outside this page. */
#aqSearchCard .rank-share-btn{font-size:var(--fs-aqSearchBtn, 0.9rem);}

/* Find My Fish pulse (RWM-64) — there's no pan/zoom camera to focus yet (see render.js's
   aqFindMyFish() comment), so this is the whole "focus" effect: scrollIntoView() gets the tank
   on screen, this makes the matching fish impossible to miss once it is. drop-shadow (not
   box-shadow) since .aq-fish has no own background/border box to outline — the glow needs to
   hug the sprite's actual (transparent-background) pixel shape instead. */
@keyframes aqFindPulse{
    0%, 100%{filter:drop-shadow(0 0 0 rgba(255,196,0,0));}
    50%{filter:drop-shadow(0 0 14px rgba(255,196,0,.95));}
  }
.aq-fish.aq-find-highlight{animation:aqFindPulse .9s ease-in-out 4; z-index:5;}

.aq-count{margin:0 0 10px; text-align:center; font-size:var(--fs-aqCount, 0.9rem); font-weight:700; color:var(--sea-deep);}

.aq-empty{text-align:center; color:var(--ink-soft); font-weight:600; margin:0 0 26px;}

.aq-book{
    position:relative; background:#c98d4f; border-radius:22px; padding:14px; margin:0 0 26px;
    box-shadow:var(--shadow);
  }

.aq-pages{
    display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:12px;
    background:#fdf3df; border-radius:16px; padding:20px 16px;
  }

.aq-slot{
    background:#fffdf6; border:3px solid #f3b64f; border-radius:14px; padding:12px 6px 10px;
    display:flex; flex-direction:column; align-items:center; text-align:center;
  }

.aq-slot-locked{background:#fffaf0; border-color:#e7c78f;}

/* Catalog-side rare marker — same threshold/reasoning as .aq-fish-rare above, styled in the
   .aq-slot/.aq-book "old book" palette (gold/amber) already established on this sub-theme
   instead of the site's Pink Cute pink, per this page's own retheme note that the book look
   is deliberately left as its own coherent thing. Static (no bob/glow animation) since the
   catalog card doesn't move the way the tank sprite does. */
.aq-slot-rare{
    position:relative;
    border-color:#f0a500;
    box-shadow:0 0 0 3px rgba(240,165,0,.25), 0 6px 14px -8px rgba(240,165,0,.6);
  }

.aq-slot-rare-crown{
    position:absolute; top:-10px; right:-6px; font-size:20px; line-height:1;
    filter:drop-shadow(0 1px 2px rgba(0,0,0,.3));
    animation:none;
  }

.aq-slot-art{width:64px; height:64px; display:flex; align-items:center; justify-content:center; margin-bottom:6px;}

.aq-slot-art img{max-width:100%; max-height:100%; image-rendering:pixelated;}

.aq-slot-locked .aq-slot-art span{font-size:26px; font-weight:700; color:#d8c6a3;}

.aq-slot-name{font-size:var(--fs-aqSlotName, 0.82rem); font-weight:700; color:#5a4426;}

.aq-slot-locked .aq-slot-name{color:#c9b98f; font-weight:400;}

.aq-slot-price{
    margin-top:4px; font-size:var(--fs-aqSlotPrice, 0.76rem); font-weight:700; color:var(--coral-deep);
    background:#fbeaf0; padding:2px 10px; border-radius:999px;
  }

/* Remaining-stock line (2026-09-07, on request — "บอกด้วยได้ไหมว่าเหลือกี่ตัวแล้วในแต่ละชนิด").
   Only rendered when the admin actually sets a stock target for that species (aquariumStock()
   in render.js) — a species with no cap set shows no line here at all, same opt-in shape as
   speciesPrice/speciesTuning already have. */
.aq-slot-stock{margin-top:3px; font-size:var(--fs-aqSlotStock, 0.7rem); font-weight:700; color:var(--ink-soft);}

.aq-slot-stock.sold-out{color:#c0392b;}

.aq-catalog-hint{margin:0 0 14px; color:var(--ink-soft); font-weight:600; font-size:var(--fs-aqCatalogHint, 0.95rem); text-align:center;}

#aqCatalogTitle{font-size:var(--fs-aqCatalogTitle, 1.5rem);}

@media (max-width:600px){
    .aq-fish img{width:44px;}
    .aq-fish-sprite{scale:calc(var(--aq-size,1) * 0.78);}
    .aq-slot-art{width:52px; height:52px;}
    /* font-size no longer hardcoded here (was 0.66rem) — the admin's --fs-aqZoneBtn choice
       (or its 0.72rem default) now applies at every width, padding still tightens on mobile. */
    .aq-zone-btn{padding:5px 8px;}
  }

.bottle-submit-card{
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-lg);
    padding:24px 26px 22px; margin:0 0 26px; box-shadow:var(--shadow);
  }

.bottle-submit-card h3{
    margin:0 0 6px; color:var(--sea-deep); font-size:var(--fs-bottleSubmitTitle, 1.5rem); font-weight:700;
    display:flex; align-items:center; gap:8px;
  }

.bottle-submit-hint{margin:0 0 16px; color:var(--ink-soft); font-weight:600; font-size:var(--fs-bottleSubmitHint, 1.125rem);}

#bottleSubmitForm{display:flex; flex-direction:column; gap:12px;}

#bottleSubmitForm input[type=text], #bottleSubmitForm textarea{
    font:inherit; font-size:var(--fs-bottleSubmitField, 1.1875rem); padding:12px 16px; border-radius:var(--radius-sm);
    border:1px solid var(--card-line); background:var(--body-bg-b); color:var(--ink); resize:vertical;
  }

#bottleSubmitForm input[type=text]:focus, #bottleSubmitForm textarea:focus{outline:2px solid var(--sea-light);}

.bottle-slip-label{
    display:flex; align-items:center; gap:8px; padding:12px 16px; border-radius:var(--radius-sm);
    border:1px solid var(--card-line); color:var(--ink-soft); font-weight:600; cursor:pointer; font-size:var(--fs-bottleSubmitField, 1.1875rem);
  }

.bottle-slip-label.has-file{color:var(--sea-deep); border-color:var(--sea-light); border-style:solid;}

.bottle-slip-label svg{flex-shrink:0;}

.bottle-hp{position:absolute; left:-9999px; width:1px; height:1px; opacity:0;}

#bsSubmitBtn{
    align-self:flex-start; background:linear-gradient(90deg,#8bd3dd,#3d405b); color:#fff; border:none;
    padding:13px 30px; border-radius:999px; font-weight:700; font-size:var(--fs-bottleSubmitBtn, 1.1875rem); cursor:pointer; font-family:inherit;
  }

#bsSubmitBtn:disabled{opacity:.6; cursor:default;}

.bottle-submit-status{font-weight:600; font-size:var(--fs-bottleSubmitHint, 1.125rem); min-height:1.2em;}

.bottle-submit-status.ok{color:var(--sea-deep);}

.bottle-submit-status.err{color:var(--coral-deep);}

.bottle-overlay{
    position:fixed; inset:0; background:rgba(12,61,85,.6); display:flex; align-items:center; justify-content:center;
    z-index:100; opacity:0; pointer-events:none; transition:opacity .25s ease; padding:20px;
  }

.bottle-overlay.open{opacity:1; pointer-events:auto;}

.bottle-modal{
    position:relative; width:min(92vw, 460px); background:#fffdf6;
    border-radius:var(--radius-md); overflow:hidden; box-shadow:0 16px 34px -14px rgba(14,111,158,.6);
    padding:34px 28px 28px; text-align:center;
  }

.bottle-modal-close{
    position:absolute; top:-14px; right:-14px; z-index:3; width:34px; height:34px; border-radius:50%;
    background:#fff; border:1px solid var(--card-line); color:var(--ink); font-weight:700; cursor:pointer;
    box-shadow:var(--shadow); font-family:inherit;
  }

.bottle-modal-icon{margin-bottom:10px;}

.bottle-modal-icon .bottle-art{width:calc(var(--bottle-art-size, 92px) * 1.15); height:auto; filter:drop-shadow(0 6px 8px rgba(14,111,158,.3));}

.bottle-message-text{
    white-space:pre-line; color:var(--ink); font-weight:600; font-size:var(--fs-bottleMessage, 1.2rem); line-height:1.7;
  }

.bottle-message-from{
    margin-top:14px; color:var(--ink-soft); font-weight:700; font-size:var(--fs-bottleFrom, 1rem);
  }

.bottle-share-btn{
    margin-top:20px; border:none; background:linear-gradient(90deg,#8bd3dd,#3d405b); color:#fff;
    font-weight:700; font-size:0.875rem; padding:10px 20px; border-radius:999px; cursor:pointer;
    font-family:inherit; transition:transform .15s ease;
  }

.bottle-share-btn:hover{transform:translateY(-2px) scale(1.03);}

.bottle-share-btn:disabled{opacity:.6; cursor:default; transform:none;}

@media (max-width:820px){
    header .header-grid{grid-template-columns:1fr; text-align:center;}
    .avatar{margin:0 auto;}
    .title-block img.logo{margin:0 auto;}
    .header-side{text-align:center;}
    .progress-card{grid-template-columns:1fr; text-align:center;}
    .tier-now, .goal-block{border:none; padding:0;}
    .tiers{grid-template-columns:repeat(3,1fr);}
    .reward-row{grid-template-columns:repeat(3,1fr); row-gap:16px;}
    .rm-track{display:none;}
    /* Was scoped to body.page-index, which broke under the SPA router: the router swaps
       #page-content but never rewrites <body class>, so landing on /ranking.html (body
       class empty) and then navigating HOME left these labels visible — a bare row of
       "xxxxx tokens" with no track above it, since .rm-track is hidden right here. The
       labels only ever render on the index roadmap, so the scope bought nothing. */
    .rm-labels{display:none;}
    .cta-grid{grid-template-columns:1fr;}
    .bottom-grid-2{grid-template-columns:1fr;}
    .deco-sun{font-size:38px;}
    .deco-palm{font-size:40px;}
    /* the clouds are hidden at every width now, so the mobile-only hide that used to
       live here was redundant and has been dropped rather than left as dead CSS */
  }

@media (max-width:600px){
    /* Five pills wrap to three rows at phone widths at the full desktop size, pushing the
       hero most of a screen down. Tightening only the padding/gap (not the pill shape) gets
       it back to two rows without touching the desktop nav. */
    .site-nav{gap:8px;}
    .nav-pill{padding:9px 15px; font-size:0.84rem; gap:5px;}
    .rank-row{padding:12px 14px; gap:10px; flex-wrap:wrap;}
    .rank-medal{width:42px; height:42px; min-width:42px; font-size:var(--fs-rankMedal, 1.25rem);}
    .rank-avatar{width:36px; height:36px; min-width:36px;}
    .rank-name{font-size:var(--fs-rankName, 1.15rem);}
    .rank-score{font-size:var(--fs-rankScore, 1.25rem);}
    .rank-share-btn{padding:7px 12px; font-size:var(--fs-rankShareBtn, 0.8rem); margin-left:auto;}
    .bottle-item .bottle-art{width:var(--bottle-art-size, 74px);}
  }

@media (max-width:700px){
    .cur-event-stats{grid-template-columns:1fr;}
    .past-grid{grid-template-columns:repeat(2,1fr);}
  }

@media (max-width:480px){
    .past-grid{grid-template-columns:1fr;}
  }

/* ====================================================================
   POWDER-BLUE THEME LAYER (2026-08-22)
   Everything above is the original structure, with borders thinned and
   radii moved onto --radius-* tokens. This block restyles the pieces
   that still carried the old cream/coral/saturated-cyan look.
   Kept as one labelled section instead of 40 scattered edits so the
   theme can be iterated on — or lifted back out — in one place.
   Touches no markup, no ids, and none of the --fs-* size variables the
   admin sliders drive.
   ==================================================================== */

/* ---- page ground ---- */
body{
    background:var(--body-bg-a);
  }

html{background:var(--body-bg-b);}

.wrap{max-width:1120px; padding:0 24px 72px;}

header{padding:44px 0 26px;}

/* was a saturated cyan scallop strip; now a soft white wave */
.gingham{
    height:38px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='38' viewBox='0 0 120 38' preserveAspectRatio='none'%3E%3Cpath d='M0 20 C 20 4, 40 4, 60 20 S 100 36, 120 20 V38 H0 Z' fill='%23ffffff'/%3E%3C/svg%3E");
    background-size:120px 38px;
  }

/* ---- surfaces: hairline + soft diffuse shadow instead of thick frames ---- */
.cta-panel,.progress-card,.panel,.rewards,.tier-card,.rank-row,
.cur-event-card,.cur-event-donate,.past-card,.bottle-submit-card,
.share-modal,.bottle-sea{
    box-shadow:var(--shadow-soft);
  }

/* these three kept warm/heavy values that the sweep above could not reach:
   .cta-panel framed itself with --sea-light rather than --card-line,
   .support-panel had a pink wash, and the donate-box .qr-strip outranks the
   plain .qr-strip rule on specificity */
.cta-panel{
    background:var(--card);
    border:1px solid var(--card-line);
    padding:34px 36px 30px;
  }

.support-panel{background:var(--card);}

.cur-event-donate .qr-strip{background:var(--accent-grad);}

.progress-card{padding:34px 40px;}

.panel{border-radius:var(--radius-md); padding:24px;}

.theme-toggle{border:1px solid var(--card-line); box-shadow:var(--shadow-soft);}

/* ---- typography: airier headings, no hard white outline ---- */
.header-side h2,.rank-hero h1,.event-hero h1,.bottle-hero h1,
.past-events h2,.rewards h3,.panel h4,.cur-event-body h2,
.bottle-submit-card h3,.cta-support h3,.support-panel h3,.cta-qr h4{
    color:var(--sea-deep);
    letter-spacing:var(--heading-track);
  }

.rank-hero h1,.event-hero h1,.bottle-hero h1{letter-spacing:0.1em;}

.title-block img.logo{
    filter:drop-shadow(0 6px 14px rgba(74,111,165,.22));
    transform:none;
  }

.avatar{
    border:5px solid #fff;
    background:var(--sea-light);
    box-shadow:var(--shadow-soft), 0 0 0 1px var(--card-line);
  }

/* ---- dividers that used to be dashed ---- */
.tier-now{border-right:1px solid var(--card-line);}

.goal-block{border-left:1px solid var(--card-line);}

.social-block{border-top:1px solid var(--card-line);}

.rm-track::before{border-top:2px dashed var(--sea-light);}

.rm-dot{border:3px solid var(--sea-mid); box-shadow:0 0 0 3px var(--body-bg-b), var(--shadow-soft);}

/* ---- progress bars ---- */
.track,.cur-event-bar-track{
    background:var(--track-bg);
    border:1px solid var(--card-line);
    box-shadow:none;
    height:26px;
  }

.fill,.cur-event-bar-fill{background:var(--accent-grad);}

.fill::after{opacity:.45;}

/* the diagonal barber-pole stripes read as noise in this palette */
.cur-event-pct{text-shadow:none; color:var(--sea-deep);}

/* ---- buttons ---- */
.support-btn,#bsSubmitBtn,.rank-share-btn,.bottle-share-btn,.share-plat-btn{
    background:var(--accent-grad);
    color:#fff;
    border:none;
    box-shadow:var(--shadow-soft);
    letter-spacing:.02em;
  }

.support-btn{
    border-radius:999px;
    padding:15px 38px;
    animation:none; /* the orange pulse-glow does not belong in this palette */
  }

.support-btn:hover{transform:translateY(-2px) scale(1.02); box-shadow:var(--shadow-lift);}

.share-plat-btn.tw{background:linear-gradient(135deg,#5b9bd5,#3d7ab8);}

.share-modal-dl{background:var(--track-bg); color:var(--sea-deep);}

.share-modal-dl:hover{background:var(--sea-light);}

/* ---- nav ---- */
.site-nav{gap:10px; padding:20px 0 0;}

.nav-pill{
    background:var(--glass);
    color:var(--sea-deep);
    border:1px solid var(--card-line);
    box-shadow:var(--shadow-soft);
    letter-spacing:.04em;
    -webkit-backdrop-filter:blur(8px);
    backdrop-filter:blur(8px);
  }

.nav-pill:hover{transform:translateY(-2px); box-shadow:var(--shadow-lift);}

/* All pills share the plain white/glass look now (2026-08-29, on user request) —
   only the current page's pill stands out, in a fixed pink not tied to the admin
   theme-color picker (which can set --accent-grad/--sea-light close to white and
   make a background:var(--accent-grad); color:#fff; pill unreadable). */
.nav-pill.active{
    background:#f582ae;
    color:#fff;
    outline:3px solid #fff;
    outline-offset:-3px;
    box-shadow:var(--shadow-lift);
  }

/* ---- rewards / SR cards ---- */
.rewards{background:var(--glass); border:1px solid var(--card-line);}

.card-sr{
    background:linear-gradient(160deg,#a8c6e8,#5b7fb5);
    border:2px solid #fff;
    box-shadow:var(--shadow-soft), inset 0 0 0 2px rgba(255,255,255,.35);
  }

.card-sr.locked{
    background:linear-gradient(160deg,#d5dee8,#9aa8b8);
    box-shadow:var(--shadow-soft), inset 0 0 0 2px rgba(255,255,255,.25);
  }

/* ---- tiers ---- */
.tier-card{background:var(--card);}

.tier-card.active{border-color:var(--sea-mid); box-shadow:var(--shadow-lift); animation:none;}

.tier-card .badge,.tier-card.active .badge{background:var(--accent-grad); border-radius:999px;}

.tier-card.complete{background:var(--body-bg-a); border-color:var(--sea-light);}

.tier-card .tier-complete{color:var(--success);}

/* ---- ranking ---- */
/* Fixed gradient, not var(--accent-grad) — same bug the nav-pill.active comment
   above describes: the admin theme-color picker can push --accent-grad close to
   white, which erases the white rank number on top of it. */
.rank-medal{background:linear-gradient(160deg,#6cc3cf,#3d405b); box-shadow:var(--shadow-soft);}

/* top-3 hierarchy reads through depth of ocean colour — deepest/most
   saturated at rank 1, fading toward the surface by rank 3 */
.rank-row.top1{background:linear-gradient(90deg,#e3f7fb,#ffffff); border-color:#a3dcec;}

.rank-row.top2{background:linear-gradient(90deg,#ecf9fb,#ffffff); border-color:#c3e8f0;}

.rank-row.top3{background:linear-gradient(90deg,#f2fbfc,#ffffff); border-color:#dbf0f4;}

.rank-row.top1 .rank-medal{background:linear-gradient(160deg,#8bd3dd,#3d405b);}

.rank-row.top2 .rank-medal{background:linear-gradient(160deg,#8bd3dd,#6cc3cf);}

.rank-row.top3 .rank-medal{background:linear-gradient(160deg,#bfeaf2,#6fc0d8);}

.rank-row.top1 .rank-avatar{border-color:#8bd3dd;}

.rank-row.top2 .rank-avatar{border-color:#6fc0d8;}

.rank-row.top3 .rank-avatar{border-color:#a3dcec;}

.rank-move.up{color:var(--success); background:#e7f2ee;}

.rank-move.down{color:var(--danger); background:#f7e9ec;}

.rank-move.new{color:var(--sea-deep); background:#e9f0fa;}

/* ---- event ---- */
.live-badge{background:var(--accent-grad); box-shadow:var(--shadow-soft);}

.cur-event-stats .stat{background:var(--card);}

.cur-event-stats .stat.hero{
    background:var(--accent-grad);
    border:none;
    box-shadow:var(--shadow-soft);
  }

.cur-event-stats .stat.hero .stat-val{text-shadow:none;}

.cur-event-img,.past-card-img{background:var(--body-bg-a);}

.past-badge{background:var(--accent-grad); box-shadow:var(--shadow-soft);}

.past-card-img img{filter:saturate(88%);}

.past-card:hover{box-shadow:var(--shadow-lift);}

/* ---- donate / QR ---- */
.qr-strip{background:var(--accent-grad);}

.qr-box{background:var(--card);}

/* the playful tilt reads as clutter against straight glass cards */
.qr-row .qr-box:nth-child(1),.qr-row .qr-box:nth-child(2){transform:none;}

.qr-box:hover{transform:translateY(-4px) !important; box-shadow:var(--shadow-lift);}

.opc-box{background:var(--body-bg-a); color:var(--sea-deep);}

.rate-pill{background:var(--body-bg-a); border:1px solid var(--card-line); border-radius:999px;}

.countdown-grid div{background:var(--body-bg-a);}

.social-link{background:var(--card); color:var(--sea-deep);}

.social-link:hover{box-shadow:var(--shadow-soft);}

/* ---- bottle ---- */
.bottle-sea{background:linear-gradient(180deg,#eafcff 0%,#bdeaf5 55%,#7fd0e8 100%);}

.bottle-tag{background:var(--card); border:1px solid var(--card-line); box-shadow:var(--shadow-soft);}

.bottle-modal{background:var(--card);}

.bottle-modal-close{background:var(--card); border:1px solid var(--card-line);}

.bottle-submit-status.ok{color:var(--success);}

.bottle-submit-status.err{color:var(--danger);}

#bottleSubmitForm input[type=text],#bottleSubmitForm textarea{background:var(--body-bg-b);}

.bottle-slip-label{border:1px dashed var(--card-line);}

.bottle-slip-label.has-file{border-style:solid; border-color:var(--sea-mid);}

/* ---- share modal ---- */
.share-modal{border:1px solid var(--card-line); border-radius:var(--radius-lg);}

.share-modal-close{background:var(--body-bg-a); color:var(--sea-deep);}

.share-modal-icon{color:var(--sea-mid);}

.share-modal-backdrop{background:rgba(51,80,122,.45);}

.bottle-overlay{background:rgba(51,80,122,.5);}

/* ---- decorative emoji: were sun-yellow / palm-green ---- */
/* .deco-sun (☀️) and .deco-palm (🌴) are literal emoji in the markup, not
   entries in icons.svg — `color` cannot touch them, and at full saturation the
   yellow/green fight the whole palette. Hidden rather than faded: a beige blob
   reads as a rendering fault, not as decoration. Delete these two lines to get
   them back. The cloud/shell/star/sparkle decorations ARE svg and are recoloured
   to the blue family just below. */
.deco-sun,.deco-palm{display:none;}

.deco-star,.sparkle{color:var(--sea-light);}

.deco-shell{color:var(--sand-line);}

/* ---- dark mode: re-ground everything this block hardcoded to light ---- */
:root[data-theme="dark"] .cta-panel,
:root[data-theme="dark"] .rewards,
:root[data-theme="dark"] .tier-card.complete,
:root[data-theme="dark"] .rank-row.top1,
:root[data-theme="dark"] .rank-row.top2,
:root[data-theme="dark"] .rank-row.top3,
:root[data-theme="dark"] .cur-event-stats .stat:not(.hero),
:root[data-theme="dark"] .countdown-grid div,
:root[data-theme="dark"] .opc-box,
:root[data-theme="dark"] .rate-pill,
:root[data-theme="dark"] .qr-box,
:root[data-theme="dark"] .social-link,
:root[data-theme="dark"] .cur-event-img,
:root[data-theme="dark"] .past-card-img,
:root[data-theme="dark"] .bottle-tag,
:root[data-theme="dark"] .bottle-modal,
:root[data-theme="dark"] .bottle-modal-close,
:root[data-theme="dark"] .share-modal{
    background:var(--card);
  }

:root[data-theme="dark"] .nav-pill{
    background:var(--card);
    color:var(--sea-deep);
  }

/* Same fixed active-pink as light mode, re-declared after the dark .nav-pill
   base rule above so it still wins (same specificity, later in cascade). */
:root[data-theme="dark"] .nav-pill.active{background:#f582ae; color:#fff;}

:root[data-theme="dark"] .support-btn{
    background:var(--accent-grad);
    color:#fff;
  }

:root[data-theme="dark"] .bottle-sea{
    background:linear-gradient(180deg,#0d2333 0%,#123449 55%,#164a63 100%);
  }

:root[data-theme="dark"] .avatar{
    background:linear-gradient(150deg,#123a52,#1f7fa0);
  }

:root[data-theme="dark"] .share-modal-close{background:var(--track-bg);}

:root[data-theme="dark"] .avatar{border-color:var(--card);}

:root[data-theme="dark"] .gingham{opacity:.14;}

:root[data-theme="dark"] .title-block img.logo{filter:drop-shadow(0 6px 14px rgba(0,0,0,.5));}

:root[data-theme="dark"] .rm-dot{box-shadow:0 0 0 3px var(--body-bg-b), var(--shadow-soft);}

:root[data-theme="dark"] .rank-move.up{background:rgba(124,192,166,.15);}

:root[data-theme="dark"] .rank-move.down{background:rgba(224,138,156,.15);}

:root[data-theme="dark"] .rank-move.new{background:rgba(158,198,239,.15);}

:root[data-theme="dark"] .card-sr{background:linear-gradient(160deg,#5b7fb5,#31507c); border-color:var(--card-line);}

:root[data-theme="dark"] .card-sr.locked{background:linear-gradient(160deg,#3a4a5c,#26313d);}

/* ====================================================================
   MOTION LAYER (2026-08-22)
   The retheme stripped the old flair (pulsing button glow, barber-pole
   stripes, tilted cards, spinning sun) and left the page reading flat.
   These five effects put movement back in the palette's own language.
   All of them are compositor-only (transform / opacity / background-
   position) — no layout thrash — and all are disabled under
   prefers-reduced-motion at the bottom of this block.
   ==================================================================== */

/* ---- 1. layered drifting waves ----------------------------------
   .gingham was a single static scallop strip. Now three wave layers at
   different speeds and opacities. Each layer is 200% wide with a tile
   sized to 50% of that, so translateX(-50%) advances exactly one tile
   and loops seamlessly; the paths start and end at the same y for the
   same reason. */
.gingham{
    height:58px;
    position:relative;
    overflow:hidden;
    background:none;
  }

/* Top strip (right after the theme toggle, before .wrap) reads as crests
   poking up into the nav bar — flipped per user request so the crests point
   down into the page instead. #ambientSoundBtn is the reliable anchor (not
   #themeToggleBtn — the ambient-sound button was added between them later,
   so a #themeToggleBtn + .gingham selector never actually matched anything,
   the flip silently never applied); the footer .gingham (.wrap + .gingham,
   reef silhouette) is untouched. transform is paint-only so it doesn't
   disturb the element's layout box or its background-position:bottom
   animations underneath. */
#ambientSoundBtn + .gingham{transform:scaleY(-1);}

.gingham::before,.gingham::after,.gingham > i{display:none;}

.gingham::before,.gingham::after{
    content:"";
    display:block;
    position:absolute;
    left:0; bottom:0;
    width:200%; height:100%;
    background-repeat:repeat-x;
    background-size:50% 100%;
    will-change:transform;
  }

/* Back layer — slowest, faintest. NOTE this one scrolls its background rather
   than transforming, because .gingham is also the clipping box: translating the
   element itself slid the whole strip left and opened a gap down the right-hand
   side of the page. Only the ::before/::after layers may use transform — they
   are 200% wide and sit *inside* the overflow:hidden box. */
.gingham{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 40' preserveAspectRatio='none'%3E%3Cpath d='M0 18 C 15 6, 35 6, 50 18 S 85 30, 100 18 V40 H0 Z' fill='%23ffffff' opacity='0.38'/%3E%3C/svg%3E");
    background-repeat:repeat-x;
    background-size:560px 100%;
    background-position:0 bottom;
    animation:waveScroll 26s linear infinite;
  }

@keyframes waveScroll{
    from{background-position:0 bottom;}
    to{background-position:-560px bottom;}
  }

.gingham::before{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 40' preserveAspectRatio='none'%3E%3Cpath d='M0 22 C 20 34, 30 8, 50 22 S 80 34, 100 22 V40 H0 Z' fill='%23ffffff' opacity='0.62'/%3E%3C/svg%3E");
    animation:waveDrift 17s linear infinite reverse;
  }

.gingham::after{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 40' preserveAspectRatio='none'%3E%3Cpath d='M0 27 C 18 15, 32 39, 50 27 S 82 15, 100 27 V40 H0 Z' fill='%23ffffff'/%3E%3C/svg%3E");
    animation:waveDrift 11s linear infinite;
  }

@keyframes waveDrift{
    from{transform:translateX(0);}
    to{transform:translateX(-50%);}
  }

/* ---- 2. water inside the progress bars --------------------------
   Replaces the diagonal barber-pole stripes the retheme removed. Painted
   as a second background layer on .fill itself rather than a pseudo-
   element, so the "%" label inside stays on top without a z-index fight. */
.fill,.cur-event-bar-fill{
    background-image:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='26' viewBox='0 0 120 26' preserveAspectRatio='none'%3E%3Cpath d='M0 13 C 15 5, 45 21, 60 13 S 105 5, 120 13 V26 H0 Z' fill='%23ffffff' opacity='0.24'/%3E%3C/svg%3E"),
      var(--accent-grad);
    background-repeat:repeat-x, no-repeat;
    background-size:120px 100%, 100% 100%;
    animation:waterSlide 3.6s linear infinite;
  }

@keyframes waterSlide{
    from{background-position:0 0, 0 0;}
    to{background-position:-120px 0, 0 0;}
  }

/* the old shimmer sweep across the whole bar is gone — it fought the water */
.fill::after{display:none;}

/* ---- pixel heart HP bar (2026-09-09) ----------------------------
   Replaces the water-wave fill above with a flat retro look per the
   reference mockup: cream track, solid red fill with a thin orange top
   highlight band, thick black border. The heart marker is ::after on
   .fill itself, anchored to its own right edge — since render.js already
   animates .fill's width for the % progress, the heart rides the fill/
   empty boundary for free, no JS changes needed. Both .track's and
   .fill's overflow have to open up to visible so the heart (which floats
   above the bar) isn't clipped by the pill's own bounding box. */
.track,.cur-event-bar-track{
    background:#ffe1c8;
    border:3px solid #1c1c1c;
    box-shadow:none;
    overflow:visible;
  }

.fill,.cur-event-bar-fill{
    background-image:none;
    background:linear-gradient(#ffb6d9 0 22%, #fb5393 22% 100%);
    animation:none;
    overflow:visible;
  }

.fill::after,.cur-event-bar-fill::after{
    content:"";
    display:block;
    position:absolute;
    top:-15px; right:-11px; left:auto; bottom:auto;
    width:22px; height:19px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 9' shape-rendering='crispEdges'%3E%3Cg fill='%231c1c1c'%3E%3Crect x='2' y='0' width='1' height='1'/%3E%3Crect x='3' y='0' width='1' height='1'/%3E%3Crect x='6' y='0' width='1' height='1'/%3E%3Crect x='7' y='0' width='1' height='1'/%3E%3Crect x='1' y='1' width='1' height='1'/%3E%3Crect x='4' y='1' width='1' height='1'/%3E%3Crect x='5' y='1' width='1' height='1'/%3E%3Crect x='8' y='1' width='1' height='1'/%3E%3Crect x='0' y='2' width='1' height='1'/%3E%3Crect x='9' y='2' width='1' height='1'/%3E%3Crect x='0' y='3' width='1' height='1'/%3E%3Crect x='9' y='3' width='1' height='1'/%3E%3Crect x='0' y='4' width='1' height='1'/%3E%3Crect x='9' y='4' width='1' height='1'/%3E%3Crect x='1' y='5' width='1' height='1'/%3E%3Crect x='8' y='5' width='1' height='1'/%3E%3Crect x='2' y='6' width='1' height='1'/%3E%3Crect x='7' y='6' width='1' height='1'/%3E%3Crect x='3' y='7' width='1' height='1'/%3E%3Crect x='6' y='7' width='1' height='1'/%3E%3Crect x='4' y='8' width='1' height='1'/%3E%3Crect x='5' y='8' width='1' height='1'/%3E%3C/g%3E%3Cg fill='%23ffb6d9'%3E%3Crect x='2' y='1' width='1' height='1'/%3E%3Crect x='3' y='1' width='1' height='1'/%3E%3Crect x='6' y='1' width='1' height='1'/%3E%3Crect x='7' y='1' width='1' height='1'/%3E%3Crect x='1' y='2' width='1' height='1'/%3E%3Crect x='2' y='2' width='1' height='1'/%3E%3Crect x='7' y='2' width='1' height='1'/%3E%3Crect x='8' y='2' width='1' height='1'/%3E%3C/g%3E%3Cg fill='%23fb5393'%3E%3Crect x='3' y='2' width='1' height='1'/%3E%3Crect x='4' y='2' width='1' height='1'/%3E%3Crect x='5' y='2' width='1' height='1'/%3E%3Crect x='6' y='2' width='1' height='1'/%3E%3Crect x='1' y='3' width='1' height='1'/%3E%3Crect x='2' y='3' width='1' height='1'/%3E%3Crect x='3' y='3' width='1' height='1'/%3E%3Crect x='4' y='3' width='1' height='1'/%3E%3Crect x='5' y='3' width='1' height='1'/%3E%3Crect x='6' y='3' width='1' height='1'/%3E%3Crect x='7' y='3' width='1' height='1'/%3E%3Crect x='8' y='3' width='1' height='1'/%3E%3Crect x='1' y='4' width='1' height='1'/%3E%3Crect x='2' y='4' width='1' height='1'/%3E%3Crect x='3' y='4' width='1' height='1'/%3E%3Crect x='4' y='4' width='1' height='1'/%3E%3Crect x='5' y='4' width='1' height='1'/%3E%3Crect x='6' y='4' width='1' height='1'/%3E%3Crect x='7' y='4' width='1' height='1'/%3E%3Crect x='8' y='4' width='1' height='1'/%3E%3Crect x='2' y='5' width='1' height='1'/%3E%3Crect x='3' y='5' width='1' height='1'/%3E%3Crect x='4' y='5' width='1' height='1'/%3E%3Crect x='5' y='5' width='1' height='1'/%3E%3Crect x='6' y='5' width='1' height='1'/%3E%3Crect x='7' y='5' width='1' height='1'/%3E%3Crect x='3' y='6' width='1' height='1'/%3E%3Crect x='4' y='6' width='1' height='1'/%3E%3Crect x='5' y='6' width='1' height='1'/%3E%3Crect x='6' y='6' width='1' height='1'/%3E%3Crect x='4' y='7' width='1' height='1'/%3E%3Crect x='5' y='7' width='1' height='1'/%3E%3C/g%3E%3C/svg%3E");
    background-size:100% 100%;
    background-repeat:no-repeat;
    animation:none;
    pointer-events:none;
  }

/* ลายทางเดินขบวน (2026-09-09) — replaces the light-sweep วิ้งๆ per user's pick from the
   Bar Motion Lab comparison (option 4 of 6: sweep/pulse/heartbeat/stripes/sparkle/static).
   A diagonal repeating-linear-gradient tiles seamlessly on its own, so marching it exactly
   one tile-period sideways via transform loops with no visible seam — no need to double
   the element width the way a finite gradient sweep does. ::before instead of ::after since
   ::after is already the heart marker; confined to .fill's own box (background never
   overflows an element's own border box regardless of the overflow:visible set above), so
   it stays clipped to the pill shape without fighting the heart's visible overflow.
   transform-only, same as the sweep it replaces — compositor-only, no per-frame repaint. */
.fill::before,.cur-event-bar-fill::before{
    content:"";
    position:absolute; inset:0;
    border-radius:inherit;
    background-image:repeating-linear-gradient(120deg, rgba(255,255,255,.4) 0 9px, transparent 9px 18px);
    animation:pixelMarch 1s linear infinite;
    pointer-events:none;
    will-change:transform;
  }

@keyframes pixelMarch{
    from{transform:translateX(0);}
    to{transform:translateX(18px);}
  }

/* ---- 3. scroll reveal -------------------------------------------
   fadeInUp already existed on 7 section rules but fired on page load, so
   anything below the fold finished animating before it was ever seen.
   render.js adds .has-reveal (only when IntersectionObserver exists, so
   a no-JS visitor still gets everything visible) and tags each section. */
html.has-reveal .reveal{
    opacity:0;
    transform:translateY(22px);
    transition:opacity .65s ease-out, transform .65s ease-out;
    animation:none;
  }

html.has-reveal .reveal.is-in{
    opacity:1;
    transform:none;
  }

/* ---- 4. rising bubbles ------------------------------------------
   @keyframes bubbleUp had been sitting in this file unused since it was
   written — nothing ever referenced it. Rewritten to take its travel
   distance from --b-rise so one keyframe serves both the short header
   band and the tall bottle sea. Fields are injected by render.js. */
.bubble-field{
    position:absolute;
    inset:0;
    overflow:hidden;
    pointer-events:none;
    z-index:0;
  }

.bubble{
    position:absolute;
    bottom:-16px;
    width:var(--b-size,8px);
    height:var(--b-size,8px);
    border-radius:50%;
    /* White is correct HERE: the only remaining host for this base style is
       .bottle-sea, whose blue gradient makes white bubbles read well. The page-wide
       field overrides these colours below, because white-on-cream is invisible. */
    background:radial-gradient(circle at 32% 28%, rgba(255,255,255,.95), rgba(255,255,255,.3) 58%, rgba(255,255,255,.06));
    border:1px solid rgba(255,255,255,.55);
    opacity:0;
    animation:bubbleUp 9s ease-in infinite;
    will-change:transform, opacity;
  }

@keyframes bubbleUp{
    0%{transform:translateY(0) scale(.55); opacity:0;}
    12%{opacity:.8;}
    82%{opacity:.45;}
    100%{transform:translateY(calc(var(--b-rise,180px) * -1)) scale(1); opacity:0;}
  }

/* Ambient page-wide bubbles (2026-08-30), replacing the retired particle specks.
   Fixed to the viewport so they drift behind the entire document instead of only
   inside the header band. z-index:0 and NOT -1 on purpose: a negative index would
   drop the layer behind body's opaque background and hide it completely, so the
   layer stays at 0 and .wrap is lifted to 1 below to sit on top of it. */
.bubble-field-page{position:fixed; inset:0; z-index:0;}

/* Tinted, not white: the page background is a near-white cream (#fefef6), so a white
   bubble over it is literally invisible — this is why the 32 bubbles that were already
   rendering on /ranking.html could never be seen. Hardcoded rather than taken from
   --sea-*, which are admin-editable and could be set to something that vanishes again. */
.bubble-field-page .bubble{
    animation-name:bubbleUpPage;
    background:radial-gradient(circle at 32% 28%, rgba(255,255,255,.92), rgba(125,205,216,.42) 58%, rgba(108,195,207,.2));
    border-color:rgba(96,186,199,.62);
  }

/* dark theme: the page is near-black, so white reads best again */
:root[data-theme="dark"] .bubble-field-page .bubble{
    background:radial-gradient(circle at 32% 28%, rgba(255,255,255,.9), rgba(255,255,255,.28) 58%, rgba(255,255,255,.06));
    border-color:rgba(255,255,255,.5);
  }

@keyframes bubbleUpPage{
    0%{transform:translateY(0) scale(.5); opacity:0;}
    10%{opacity:.75;}
    88%{opacity:.5;}
    100%{transform:translateY(-104vh) scale(1); opacity:0;}
  }

/* Tier celebration (render.js:burstBubbles). Unlike the ambient layer this one sits ABOVE
   the content — it is a deliberate, one-off moment — but stays under .theme-toggle (60)
   and is pointer-events:none via .bubble-field, so it never eats a tap. */
.bubble-burst{z-index:55;}

.tier-toast{
    position:fixed; left:50%; bottom:26px; transform:translateX(-50%);
    z-index:56; max-width:calc(100vw - 32px);
    padding:12px 20px; border-radius:999px;
    background:var(--coral-deep); color:#fff;
    font-weight:700; font-size:1rem; text-align:center;
    box-shadow:var(--shadow-lift);
    animation:tierToastIn .45s ease-out both;
  }

.tier-toast.out{animation:tierToastOut .6s ease-in both;}

@keyframes tierToastIn{
    from{opacity:0; transform:translateX(-50%) translateY(14px);}
    to{opacity:1; transform:translateX(-50%) translateY(0);}
  }

@keyframes tierToastOut{
    from{opacity:1; transform:translateX(-50%) translateY(0);}
    to{opacity:0; transform:translateX(-50%) translateY(14px);}
  }

/* bubbles must sit behind the actual bottles/content of their host */
.bottle-item,.header-grid,.site-nav{position:relative; z-index:1;}

/* every page wraps its nav + main + footer in .wrap, so lifting this one node puts
   all real content above the fixed bubble layer */
.wrap{position:relative; z-index:1;}

/* ---- 5. a single sweep across rank 1 ----------------------------
   Gives the leaderboard a focal point now that gold/silver/bronze are
   gone, without reinstating a warm colour. Rank 1 only, on a long cycle
   with a pause — a constant sweep reads as a loading state. */
.rank-row.top1{position:relative; overflow:hidden;}

.rank-row.top1::after{
    content:"";
    position:absolute;
    top:0; bottom:0; left:0;
    width:42%;
    background:linear-gradient(100deg, transparent, rgba(255,255,255,.6), transparent);
    animation:rankSweep 5.2s ease-in-out infinite;
    pointer-events:none;
  }

:root[data-theme="dark"] .rank-row.top1::after{
    background:linear-gradient(100deg, transparent, rgba(158,198,239,.18), transparent);
  }

@keyframes rankSweep{
    0%{transform:translateX(-150%);}
    45%,100%{transform:translateX(320%);}
  }

/* ---- honour the OS "reduce motion" setting ---------------------- */
@media (prefers-reduced-motion:reduce){
    .gingham,.gingham::before,.gingham::after,
    .bubble-field,
    .fill,.cur-event-bar-fill,.fill::before,.cur-event-bar-fill::before,.cur-event-bar-fill::after,.cur-event-milestone.reached,
    .cur-event-stretch-marker,.cur-event-stretch-note.unlocked,
    .pet-eye,.pet-char,.pet-char.reacting,.pet-heart,.pet-pat,.pet-thought,.pet-hand-follow.active,
    .updated-dot,.pl-eq span,.pl-score.hot,
    .cur-event-stats.no-goal .stat.hero,.cur-event-stats.no-goal .hero-deco-1,.cur-event-stats.no-goal .hero-deco-2,
    .bubble,.rank-row.top1::after,
    .bottle-item,.bottle-art,.avatar,.tier-now .heart,.live-badge .dot,
    .deco-fish1,.deco-fish2,.deco-fish3,.name-emoji,.aq-fish,.aq-fish img,.aq-fish-sprite,
    .name-emoji-sparkle-fx,.name-emoji-sparkle-fx::before,.name-emoji-sparkle-fx::after{
      animation:none !important;
    }
    .bubble{display:none;}
    .tier-toast{animation:none;}
    html.has-reveal .reveal{opacity:1; transform:none; transition:none;}
  }

/* ============================================================
   TREASURE CHEST (2026-08-23) — /ranking.html
   Art is inline SVG from render.js:chestSvg(). The lid pivots on its own
   bbox corner (transform-box:fill-box + 0% 100% = the hinge), so the rotation
   is independent of the viewBox and stays correct at any rendered size.
   Palette stays cool on purpose — the powder-blue retheme replaced every gold
   accent on this page, including the top-3 medals.
   ============================================================ */
.chest{width:100%; height:100%; display:block; overflow:visible;}
.chest .chest-box{fill:#5b86b8;}
.chest .chest-lid{fill:#6f9ccb;}
.chest .chest-metal{fill:#d8e6f5;}
.chest .chest-lock{fill:#3e6392;}
.chest .chest-spark{fill:#9ad4ea; opacity:0; transform-box:fill-box; transform-origin:center;}
.chest .chest-lid-g{transform-box:fill-box; transform-origin:0% 100%;}

.rank-chest-btn{
    width:42px; height:42px; padding:7px; border-radius:50%; flex:none; cursor:pointer;
    background:var(--glass, rgba(255,255,255,.6)); border:1px solid var(--card-line);
    display:flex; align-items:center; justify-content:center;
    transition:transform .15s ease, box-shadow .15s ease;
  }

.rank-chest-btn:hover{transform:translateY(-2px) scale(1.06); box-shadow:var(--shadow-soft, 0 4px 10px -4px rgba(14,111,158,.4));}

.rank-chest-btn .chest{width:34px; height:34px;}

.rank-row.has-chest{cursor:pointer;}

.rank-row.has-chest:hover .rank-chest-btn .chest .chest-lid-g{animation:chestPeek .6s ease;}

@keyframes chestPeek{50%{transform:rotate(-26deg);}}

.chest-overlay{
    position:fixed; inset:0; background:rgba(12,61,85,.6); padding:20px; z-index:120;
    display:flex; align-items:center; justify-content:center;
    opacity:0; pointer-events:none; transition:opacity .25s ease;
  }

.chest-overlay.open{opacity:1; pointer-events:auto;}

.chest-modal{
    position:relative; width:min(92vw, 420px); max-height:88vh; overflow-y:auto;
    background:var(--card); color:var(--ink); border:1px solid var(--card-line);
    border-radius:var(--radius-md, 18px); box-shadow:0 18px 38px -16px rgba(14,111,158,.65);
    padding:30px 24px 26px; text-align:center;
  }

.chest-close{
    position:absolute; top:10px; right:10px; z-index:3; width:34px; height:34px; border-radius:50%;
    background:var(--card); border:1px solid var(--card-line); color:var(--ink); font-weight:700;
    cursor:pointer; font-family:inherit; display:flex; align-items:center; justify-content:center;
  }

.chest-art{width:150px; height:138px; margin:0 auto 4px;}

.chest-title{margin:0; font-size:1.15rem; color:var(--sea-deep);}

.chest-owner{margin:4px 0 18px; font-weight:700; color:var(--ink-soft);}

.chest-owner:empty{display:none; }

.chest-stats{display:flex; flex-direction:column; gap:8px; text-align:left;}

.chest-stat{
    display:flex; align-items:center; gap:10px; padding:9px 14px; border-radius:999px;
    background:var(--glass, rgba(255,255,255,.55)); border:1px solid var(--card-line); font-weight:600;
  }

.chest-stat .svgicon{width:19px; height:19px; color:var(--sea-mid); flex:none;}

.chest-stat-label{color:var(--ink-soft); font-size:.92rem;}

.chest-stat-val{margin-left:auto; font-weight:800; color:var(--sea-deep); white-space:nowrap;}

.chest-badges{display:flex; flex-wrap:wrap; gap:8px; justify-content:center; margin-top:16px;}

.chest-badge{
    display:inline-flex; align-items:center; gap:6px; padding:6px 13px; border-radius:999px;
    background:var(--accent-grad, linear-gradient(90deg,#8bd3dd,#3d405b)); color:#fff;
    font-weight:700; font-size:.86rem;
  }

.chest-badge .svgicon{width:16px; height:16px;}

.chest-badge.empty{background:none; color:var(--ink-soft); border:1px dashed var(--card-line); font-weight:600;}

/* The RESTING state of the modal chest is already open, and the keyframes run
   *from* closed. An engine that never ticks CSS animations then shows an opened
   chest rather than one frozen shut — the same "degrade to visible" rule the
   scroll-reveal code follows, and it matters for the same reason (this site's
   audience is largely the LINE in-app WebView). For that to hold, the stat/badge
   entrance animates transform only: an opacity fade with a fill mode would leave
   the card's actual content invisible if it never ran. */
.chest-overlay .chest-art .chest-lid-g{transform:rotate(-60deg);}

.chest-overlay .chest-art .chest-spark{opacity:.8;}

.chest-overlay.open .chest-art .chest-lid-g{animation:chestOpen .75s cubic-bezier(.34,1.35,.5,1) .1s both;}

.chest-overlay.open .chest-art .chest-spark{animation:chestSpark .9s ease .4s;}

.chest-overlay.open .chest-art .chest-spark:nth-of-type(2){animation-delay:.52s;}

.chest-overlay.open .chest-art .chest-spark:nth-of-type(3){animation-delay:.64s;}

.chest-overlay.open .chest-stat,
.chest-overlay.open .chest-badge{animation:chestPop .45s ease both; animation-delay:calc(.35s + var(--d, 0ms));}

@keyframes chestOpen{0%{transform:rotate(0);} 65%{transform:rotate(-68deg);} 100%{transform:rotate(-60deg);}}

@keyframes chestSpark{0%{opacity:0; transform:scale(.4);} 45%{opacity:1; transform:scale(1.15);} 100%{opacity:.8; transform:scale(1);}}

/* Cat collection popup (2026-09-02) — same overlay/modal skeleton as .chest-overlay/
   .chest-modal just above, reused rather than duplicated as a new pattern since both are
   "click something in a row, see a small profile-card popup" modals. */
.cat-modal-backdrop{
    position:fixed; inset:0; background:rgba(12,61,85,.6); padding:20px; z-index:120;
    display:flex; align-items:center; justify-content:center;
    opacity:0; pointer-events:none; transition:opacity .25s ease;
  }

.cat-modal-backdrop.open{opacity:1; pointer-events:auto;}

.cat-modal{
    position:relative; width:min(92vw, 440px); max-height:88vh; overflow-y:auto;
    background:var(--card); color:var(--ink); border:1px solid var(--card-line);
    border-radius:var(--radius-md, 18px); box-shadow:0 18px 38px -16px rgba(14,111,158,.65);
    padding:30px 24px 26px; text-align:center;
  }

.cat-modal-close{
    position:absolute; top:10px; right:10px; z-index:3; width:34px; height:34px; border-radius:50%;
    background:var(--card); border:1px solid var(--card-line); color:var(--ink); font-weight:700;
    cursor:pointer; font-family:inherit; display:flex; align-items:center; justify-content:center;
  }

.cat-modal-title{margin:0 0 4px; font-size:1.1rem; color:var(--sea-deep);}

.cat-modal-count{margin:0 0 16px; font-weight:700; color:var(--ink-soft); font-size:.92rem;}

.cat-modal-preview-wrap{
    position:relative; width:100%; aspect-ratio:1200/630; border-radius:14px; overflow:hidden;
    background:var(--track-bg); margin-bottom:16px;
  }

.cat-modal-spinner{
    position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
    font-weight:700; color:var(--ink-soft); font-size:.9rem;
  }

.cat-modal-preview{
    position:relative; z-index:1; width:100%; height:100%; object-fit:contain;
    background:var(--card);
  }

.cat-modal-preview:not([src]){display:none;}

.cat-modal-btns{display:flex; flex-direction:column; gap:10px;}

.cat-modal-tw,.cat-modal-dl{
    width:100%; padding:12px; border-radius:999px; border:none; cursor:pointer;
    font-family:inherit; font-weight:700; font-size:.95rem;
  }

/* <a> not <button> (it's a real link to the OG-image share page, opened in a new tab —
   same reasoning/markup as #shareTwitterBtn on the donate share-card modal) so box-sizing
   needs display:block explicitly instead of inheriting a button's default. */
.cat-modal-tw{display:block; text-decoration:none; text-align:center; box-sizing:border-box; background:linear-gradient(90deg,#1d9bf0,#0d7ec9); color:#fff;}

.cat-modal-tw .svgicon{width:16px; height:16px; vertical-align:-3px; margin-right:4px;}

.cat-modal-dl{background:var(--track-bg); color:var(--ink);}

@keyframes chestPop{from{transform:translateY(9px);} to{transform:none;}}

@media (prefers-reduced-motion:reduce){
    .chest-overlay.open .chest-art .chest-lid-g,
    .chest-overlay.open .chest-art .chest-spark,
    .chest-overlay.open .chest-stat,
    .chest-overlay.open .chest-badge,
    .rank-row.has-chest:hover .rank-chest-btn .chest .chest-lid-g{animation:none;}
  }

@media (max-width:600px){
    .rank-chest-btn{width:36px; height:36px; padding:5px;}
    .rank-chest-btn .chest{width:30px; height:30px;}
    .chest-art{width:126px; height:116px;}
  }

/* ====================================================================
   TEACUP BOARD (2026-08-28, RWM-19) — /teacup.html
   Everything here is painted from the live sheet, so the whole block has to
   survive being re-rendered every 60s: no entry animations, no state that
   only exists on first paint.
   ==================================================================== */

.tc-live{color:var(--sea-deep);}

/* IDs (not the shared .rank-hero h1/p rules) so Teacup's own admin sliders don't
   also move Ranking's title/subtitle — the two pages happen to reuse .rank-hero
   for layout but each needs an independently adjustable font size. */
#teacupTitle{font-size:var(--fs-tcTitle, 2.1rem);}

#teacupSubtitle{font-size:var(--fs-tcSubtitle, 1.1rem);}

#teacupUpdated{font-size:var(--fs-tcUpdated, 0.9rem);}

.tc-stats{
    display:grid; grid-template-columns:repeat(3,1fr); gap:12px; margin:22px 0 18px;
  }

.tc-stat{
    display:flex; flex-direction:column; align-items:center; gap:3px;
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-md);
    box-shadow:var(--shadow-soft); padding:16px 10px;
  }

.tc-stat .svgicon{width:22px; height:22px; color:var(--sea-deep);}

.tc-stat-emoji{font-size:22px; line-height:1;}

.tc-stat b{font-size:var(--fs-tcStatValue, 1.6rem); font-weight:800; color:var(--sea-deep); line-height:1.15;}

.tc-stat span{font-size:var(--fs-tcStatLabel, 0.85rem); font-weight:600; color:var(--ink-soft); text-align:center;}

.tc-controls{display:flex; flex-wrap:wrap; gap:10px; margin:0 0 12px;}

/* No appearance:none on the select — the native chevron is the only affordance
   telling a phone user it opens a list, and there is no icon in icons.svg for it. */
.tc-input,.tc-select{
    flex:1 1 190px; min-width:0; font:inherit; font-weight:600;
    color:var(--ink); background:var(--card);
    border:1px solid var(--card-line); border-radius:999px;
    padding:11px 18px; box-shadow:var(--shadow-soft);
  }

.tc-input:focus-visible,.tc-select:focus-visible{outline:2px solid var(--sea-mid); outline-offset:2px;}

/* Segmented ย่อ/เต็ม switch. Sits in the same row as the other controls and shrinks
   last, so on a phone it ends up alone on its own line rather than squeezing the search box. */
.tc-view{
    display:flex; flex:0 0 auto; padding:3px; gap:3px;
    background:var(--track-bg); border:1px solid var(--card-line);
    border-radius:999px; box-shadow:var(--shadow-soft);
  }

.tc-view-btn{
    display:flex; align-items:center; justify-content:center;
    font:inherit; color:var(--ink-soft);
    background:none; border:none; border-radius:999px; padding:9px;
    cursor:pointer; transition:color .15s ease, background .15s ease;
  }

.tc-view-btn .svgicon{width:18px; height:18px;}

.tc-view-btn.active{background:var(--card); color:var(--sea-deep); box-shadow:var(--shadow-soft);}

.tc-result{
    margin:0 0 14px; text-align:center;
    font-size:var(--fs-tcResult, 0.9rem); font-weight:600; color:var(--ink-soft);
  }

.tc-list{
    display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr));
    gap:14px; margin:0 0 30px;
  }

/* ~110 cards, each with up to 27 chips. content-visibility keeps the off-screen
   ones out of layout entirely (same fix as .bottle-item in RWM-14); the `auto`
   keyword makes the browser remember each card's real height once measured, so
   scrolling back up doesn't jump. */
.tc-card{
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-md);
    box-shadow:var(--shadow); padding:14px 16px 16px;
    content-visibility:auto; contain-intrinsic-size:auto 190px;
  }

.tc-head{display:flex; align-items:baseline; gap:10px;}

.tc-name{
    flex:1; margin:0; font-size:var(--fs-tcCardName, 1.05rem); font-weight:800; color:var(--ink);
    overflow-wrap:anywhere;
  }

.tc-count{font-weight:800; color:var(--sea-deep); white-space:nowrap;}

.tc-count b{font-size:var(--fs-tcCardCount, 1.25rem);}

.tc-of{font-size:var(--fs-tcCardOf, 0.8rem); font-weight:600; color:var(--ink-soft);}

.tc-bar{
    height:7px; margin:10px 0 12px; border-radius:999px;
    background:var(--track-bg); overflow:hidden;
  }

.tc-bar span{display:block; height:100%; border-radius:999px; background:var(--accent-grad);}

.tc-chips{display:flex; flex-wrap:wrap; gap:6px;}

/* Explicit "share the cat collection" button (2026-09-02) — only rendered (by
   catShareBtnHtml() in render.js) for a person who actually owns any cats. Lives in the
   row's expanded detail area / card body rather than next to the name, matching where
   Ranking's own always-visible share button gets its room. */
.tc-cat-share-btn{
    display:inline-flex; align-items:center; gap:6px; margin-top:10px; padding:7px 14px;
    border:none; border-radius:999px; background:var(--accent-grad); color:#fff;
    font-family:inherit; font-weight:700; font-size:.82rem; cursor:pointer;
  }

.tc-cat-share-btn .svgicon{width:14px; height:14px;}

.tc-chip{
    font-size:var(--fs-tcChip, 0.8rem); font-weight:600; color:var(--ink);
    background:var(--lavender); border:1px solid var(--card-line);
    border-radius:999px; padding:4px 10px;
  }

.tc-chip.on{background:var(--accent-grad); color:#fff; border-color:transparent;}

:root[data-theme="dark"] .tc-chip{color:#1a1330;}

/* ---- compact view: 3-column card grid, tap a card to expand its chips in place ----
   Replaced the original one-line-per-person list (2026-08-28) with this grid on user
   request 2026-08-29, matching a reference mockup: name + a decorative badge + an
   "N กิจกรรม" count pill + a chevron that rotates open. Each row starts collapsed
   (bar + chips hidden) and expands via a CSS grid-template-rows 0fr→1fr transition —
   no JS height math, and content-visibility still applies to the whole `.tc-row` so
   collapsed off-screen rows cost nothing, same fix as the old one-line rows had. */
.tc-list.compact{grid-template-columns:1fr; gap:10px;}

.tc-row{
    background:var(--card); border:1px solid var(--card-line); border-radius:var(--radius-md);
    box-shadow:var(--shadow-soft); overflow:hidden;
    content-visibility:auto; contain-intrinsic-size:auto 54px;
  }

.tc-row-toggle{
    display:flex; align-items:center; gap:10px; width:100%;
    border:none; background:none; cursor:pointer; font:inherit; color:inherit; text-align:left;
    padding:11px 14px; -webkit-tap-highlight-color:transparent;
  }

.tc-row-badge{
    flex:none; width:30px; height:30px; border-radius:50%;
    background:var(--coral); display:flex; align-items:center; justify-content:center;
    font-size:0.85rem; font-weight:800; color:#fff; line-height:1;
  }

.tc-row-name{
    flex:1; min-width:0; display:flex; flex-wrap:wrap; align-items:center;
    font-size:var(--fs-tcRowName, 0.95rem); font-weight:700; color:var(--ink);
  }

.tc-row-name-text{overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;}

.tc-row-count{
    flex:none; font-size:var(--fs-tcRowCount, 0.78rem); font-weight:700; color:var(--sea-deep);
    background:var(--track-bg); padding:4px 10px; border-radius:999px; white-space:nowrap;
  }

.tc-row-chevron{
    flex:none; font-size:1.15rem; font-weight:700; line-height:1; color:var(--ink-soft);
    transition:transform .2s ease;
  }

.tc-row.open .tc-row-chevron{transform:rotate(90deg);}

.tc-row-detail{
    display:grid; grid-template-rows:0fr; transition:grid-template-rows .2s ease;
  }

.tc-row.open .tc-row-detail{grid-template-rows:1fr;}

.tc-row-detail-inner{min-height:0; overflow:hidden; padding:0 14px;}

.tc-row.open .tc-row-detail-inner{padding-bottom:14px;}

.tc-row-detail-inner .tc-bar{margin:2px 0 10px;}

.tc-none{font-size:0.85rem; font-weight:600; color:var(--ink-soft);}

.tc-note{
    grid-column:1/-1; margin:0; padding:26px 0; text-align:center;
    font-size:0.9rem; font-weight:600; color:var(--ink-soft);
  }

@media (max-width:600px){
    .tc-stats{gap:8px; margin:18px 0 14px;}
    .tc-stat{padding:13px 6px;}
    .tc-stat b{font-size:1.3rem;}
    .tc-stat span{font-size:0.72rem;}
    .tc-list{grid-template-columns:1fr;}
    .tc-list.compact{grid-template-columns:1fr;}
    .tc-view{flex:1 1 100%; justify-content:center;}
    .tc-view-btn{flex:1;}
  }

/* ====================================================================
   REEF SILHOUETTE (2026-08-29, Underwater World)
   The .gingham strip right after .wrap closes is the page's bottom-most
   divider on every page. Give that one instance (only) a static coral
   silhouette as its own background, leaving its ::before/::after wave
   foam layers animating on top — same technique as the existing wave
   strips, just a second visual role for an element that already exists
   on all 9 pages. Not touched: the .gingham before .wrap (page top). */
.wrap + .gingham{
    height:84px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 40' preserveAspectRatio='none'%3E%3Cpath d='M0 40 V26 L4 20 L7 27 L11 15 L15 25 L19 12 L23 26 L27 17 L31 28 L36 14 L40 27 L45 19 L50 29 L55 16 L60 26 L64 13 L68 25 L73 18 L77 28 L82 15 L86 26 L90 20 L94 27 L100 22 V40 Z' fill='%23f582ae'/%3E%3C/svg%3E");
    background-repeat:repeat-x;
    background-size:520px 100%;
    background-position:0 bottom;
    opacity:.55;
    animation:none;
  }

:root[data-theme="dark"] .wrap + .gingham{opacity:.3;}

/* ====================================================================
   CUTE PINK PASS (2026-08-29)
   --coral/--coral-deep got re-tinted to a candy pink, but every place that
   used to read those tokens (tier badge, star sparkle) was already flattened
   to the uniform ocean-blue glass style by the powder-blue retheme above —
   same specificity, later in the cascade, so the pink never actually
   rendered. This rule wins back the current-tier badge and the header's star
   sparkle, without touching the deliberately-uniform ocean headings elsewhere.
   (.nav-pill.coral used to be re-won here too, but as of the 2026-08-29 nav
   pill fix it has its own fixed, always-visible color earlier in this file —
   no longer tied to the admin-editable --coral-deep, which could go muddy or
   low-contrast depending on what the admin picks.) */
.tier-card.active .badge{background:var(--coral-deep);}

.deco-star{color:var(--coral);}

/* ====================================================================
   MANGA PANEL — PINK CUTE RETHEME: HOME PAGE ONLY (2026-09-09)
   First page of a page-by-page conversion (user's explicit scope choice
   this session, over converting the whole site in one pass). Mockup:
   https://claude.ai/code/artifact/6fd18a4d-002d-46dd-9787-37e45bd2d7aa

   Scoping: this file's own top-of-file note says body-class scoping
   (body.page-index) is unreliable — router.js's SPA swap replaces
   #page-content wholesale on internal nav, but never touches <body>, so
   a body class only ever reflects whichever page loaded first. Elements
   INSIDE #page-content are safe to scope by class, though, since the
   whole subtree — not just its innerHTML — gets replaced on every nav
   (confirmed by reading router.js: `liveContent.replaceWith(freshContent)`).
   So every selector below targets markup that only exists inside
   index.html's own #page-content (.cta-panel/.progress-card/.roadmap/
   .tiers/.rewards/.bottom-grid-2/.panel/#siteFooter and friends — verified
   via grep that none of these class names appear in any other real page's
   markup); the couple of names event.html now also uses
   (.header-shortcuts/.header-shortcut-pill, .qr-strip, .qr-box,
   .countdown-grid) are additionally scoped through an index-only ancestor
   (.header-side, .cta-panel, .bottom-grid-2) so this can't leak onto
   event.html. One real markup change was needed for this: <header> in
   index.html gained a `ph-header` class so the old Underwater-World deco
   sprites (sun/clouds/palm/shell/star/fish, all sharing the base `.deco`
   class site-wide) can be hidden on Home without hiding them on every
   other still-unconverted page too.

   Not touched: <body>'s own background/font (genuinely global, never
   swapped, so it can't be changed for one page only — the polka-dot cream
   background from the mockup has to wait until every page converts and
   the global body rule can finally change); the top .site-nav bar (also
   global chrome, stays Underwater-World-styled until its own turn); the
   pixel-heart .track/.fill bar (2026-09-09, above) — the mockup explicitly
   already matches it, nothing to change there.

   New tokens are namespaced --pc-* rather than reusing --sea-*, --coral-*,
   --sand (which the admin's "ธีมสี" 5-swatch picker writes to via an
   injected :root override, see RWM-21) — this retheme is a deliberate,
   different visual identity, not meant to be admin-recolorable the way
   the rest of the site's blue/coral palette is.

   Every rule here is a plain, single-purpose selector appended after
   every earlier pass in this file (Underwater World → glass-flatten →
   pixel-heart-bar → this), so per this file's own established
   same-specificity-later-wins convention it simply wins outright — no
   :root[data-theme="dark"] gating needed, since --pc-* itself already
   resolves differently per theme. ==================================================================== */

:root{
  --pc-ink:#231920;
  --pc-paper:#fff7fa;
  --pc-panel:#ffffff;
  --pc-pink:#ff5da8;
  --pc-pink-deep:#c81871;
  --pc-pink-soft:#ffd9ec;
  --pc-pink-soft2:#ffeef6;
  --pc-cream:#ffe9d1;
  --pc-text-soft:#7a6570;
}

:root[data-theme="dark"]{
  --pc-ink:#f6ecf1;
  --pc-paper:#1c1520;
  --pc-panel:#261c29;
  --pc-pink:#ff74b6;
  --pc-pink-deep:#ffa8d5;
  --pc-pink-soft:#3d2536;
  --pc-pink-soft2:#2f1e2c;
  --pc-cream:#4a2f28;
  --pc-text-soft:#c7b3bf;
}

/* ---- header: retire the old ocean decorations, restyle avatar/wordmark/rate-pill/shortcuts ---- */
.ph-header .deco,.ph-header .sparkle{display:none;}

.avatar{
    border:5px solid var(--pc-ink);
    background:var(--pc-pink-soft2);
    box-shadow:5px 5px 0 var(--pc-ink);
    animation:none;
  }

.title-block img.logo{filter:none; transform:none;}

.title-block p.sub{color:var(--pc-text-soft); font-weight:700;}

.header-side h2{color:var(--pc-pink-deep);}

.rate-pill{
    background:var(--pc-cream);
    border:2.5px solid var(--pc-ink);
    color:var(--pc-ink);
  }

.header-side .header-shortcuts .header-shortcut-pill{
    background:var(--pc-panel);
    border:2.5px solid var(--pc-ink);
    color:var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.header-side .header-shortcuts .header-shortcut-pill:hover{
    background:var(--pc-pink-soft2);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

/* ---- CTA panel: QR strip + support button + trust row + socials ---- */
.cta-panel{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:7px 7px 0 var(--pc-ink);
  }

.cta-qr h4{color:var(--pc-pink-deep);}

.cta-support h3{color:var(--pc-pink-deep);}

.cta-support p{color:var(--pc-text-soft);}

.cta-panel .qr-strip{background:var(--pc-pink); color:#fff;}

.cta-panel .qr-box{
    background:var(--pc-pink-soft2);
    border:2.5px solid var(--pc-ink);
    color:var(--pc-text-soft);
  }

.cta-panel .qr-box img{border-color:var(--pc-ink);}

.opc-box{background:var(--pc-pink-soft2); color:var(--pc-pink-deep); border:2px dashed var(--pc-ink);}

.support-btn{
    background:var(--pc-pink);
    color:#fff;
    border:2.5px solid var(--pc-ink);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

.support-btn:hover{box-shadow:6px 6px 0 var(--pc-ink);}

.trust-row{color:var(--pc-text-soft);}

.social-block{border-top:2px dashed var(--pc-ink);}

.social-link{
    background:var(--pc-panel);
    border:2px solid var(--pc-ink);
    color:var(--pc-pink-deep);
  }

/* ---- progress card: tier-now / bar-block / goal-block (the pixel-heart bar itself is untouched) ---- */
.progress-card{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:7px 7px 0 var(--pc-ink);
  }

.tier-now{border-right:2px dashed var(--pc-ink);}

.tier-now .label{color:var(--pc-text-soft);}

.tier-now .tier{color:var(--pc-pink-deep);}

.bar-block .nums .cur{color:var(--pc-pink-deep);}

.bar-block .nums .goal{color:var(--pc-text-soft);}

.track-caption{color:var(--pc-text-soft);}

.goal-block{border-left:2px dashed var(--pc-ink);}

.goal-block .label{color:var(--pc-text-soft);}

.goal-block .num{color:var(--pc-pink-deep);}

/* ---- roadmap timeline + tier cards ---- */
.rm-track::before{border-top:3px dashed var(--pc-ink);}

.rm-dot{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:0 0 0 3px var(--pc-cream);
  }

.rm-label{color:var(--pc-pink-deep);}

.tier-card{
    background:var(--pc-panel);
    border:2px solid var(--pc-ink);
  }

.tier-card.active{
    border-color:var(--pc-ink);
    box-shadow:5px 5px 0 var(--pc-ink);
  }

.tier-card .badge{background:var(--pc-pink-soft2); color:var(--pc-pink-deep);}

.tier-card.active .badge{background:var(--pc-pink); color:#fff;}

.tier-card .baht{color:var(--pc-pink-deep);}

.tier-card .range{color:var(--pc-text-soft);}

.tier-card.complete{background:var(--pc-pink-soft2); border-color:var(--pc-pink);}

.tier-card .tier-complete{color:var(--pc-pink-deep);}

/* ---- rewards ---- */
.rewards{
    background:var(--pc-pink-soft2);
    border:3px dashed var(--pc-ink);
  }

.rewards h3{color:var(--pc-pink-deep);}

.rewards p.rw-sub{color:var(--pc-text-soft);}

.reward-item .tok{color:var(--pc-text-soft);}

/* ---- bottom grid: countdown + last-updated panels ---- */
.panel{
    background:var(--pc-panel);
    border:2px solid var(--pc-ink);
    box-shadow:5px 5px 0 var(--pc-ink);
  }

.panel h4{color:var(--pc-pink-deep);}

.bottom-grid-2 .countdown-grid div{background:var(--pc-pink-soft2); border-color:var(--pc-ink);}

.countdown-grid .cnum{color:var(--pc-pink-deep);}

.countdown-grid .clabel{color:var(--pc-text-soft);}

.panel .small{color:var(--pc-text-soft);}

.update-num{color:var(--pc-pink-deep);}

#siteFooter{color:var(--pc-text-soft);}

/* ---- dark-mode counterparts -------------------------------------
   This file turns out to have THREE separate earlier dark-mode passes
   (grep for :root[data-theme="dark"] .selector — groups at ~line 324,
   ~1563, and ~2373), each gating a handful of the same selectors above
   at specificity (0,3,0)/(0,3,1) — higher than this retheme's plain
   single-class rules (0,1,0)/(0,1,1). Missed on the first pass (a grep
   for the block-nesting form `:root[data-theme="dark"]{` only caught
   the token-definition block, not this space-before-selector form) and
   caught afterward by comparing computed styles against intended
   values in dark mode. Every plain rule above still needs its own
   plain declaration (light mode has no such gate), and every selector
   below is one that DOES collide with an existing dark-gated rule —
   selectors from this pass with no entry in that grep (.tier-now,
   .goal-block, .social-block, .rm-track::before, .tier-card/.active/
   .badge, .tier-card .tier-complete, .panel, .rm-label, header-shortcut
   pills, #siteFooter, etc.) were confirmed dark-safe as plain rules,
   no duplicate needed. */
:root[data-theme="dark"] .cta-panel{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:7px 7px 0 var(--pc-ink);
  }

:root[data-theme="dark"] .cta-panel .qr-box{
    background:var(--pc-pink-soft2);
    border:2.5px solid var(--pc-ink);
    color:var(--pc-text-soft);
  }

:root[data-theme="dark"] .opc-box{background:var(--pc-pink-soft2); color:var(--pc-pink-deep); border:2px dashed var(--pc-ink);}

:root[data-theme="dark"] .social-link{
    background:var(--pc-panel);
    border:2px solid var(--pc-ink);
    color:var(--pc-pink-deep);
  }

:root[data-theme="dark"] .bottom-grid-2 .countdown-grid div{background:var(--pc-pink-soft2); border-color:var(--pc-ink);}

:root[data-theme="dark"] .rewards{
    background:var(--pc-pink-soft2);
    border:3px dashed var(--pc-ink);
  }

:root[data-theme="dark"] .support-btn{
    background:var(--pc-pink);
    color:#fff;
    border:2.5px solid var(--pc-ink);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

:root[data-theme="dark"] .header-side h2{color:var(--pc-pink-deep);}

:root[data-theme="dark"] .avatar{
    border:5px solid var(--pc-ink);
    background:var(--pc-pink-soft2);
    box-shadow:5px 5px 0 var(--pc-ink);
  }

:root[data-theme="dark"] .title-block img.logo{filter:none;}

:root[data-theme="dark"] .rm-dot{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:0 0 0 3px var(--pc-cream);
  }

:root[data-theme="dark"] .rate-pill{
    background:var(--pc-cream);
    border:2.5px solid var(--pc-ink);
    color:var(--pc-ink);
  }

:root[data-theme="dark"] .tier-card.complete{background:var(--pc-pink-soft2); border-color:var(--pc-pink);}

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: RANKING PAGE (2026-09-09)
   Second page of the page-by-page conversion (RWM-45 did Home). Same
   method: scope by markup unique to ranking.html (.rank-list/.rank-row/
   .rank-medal/.rank-name/.rank-score/.rank-avatar/.rank-share-btn/
   .rank-chest-btn all confirmed via grep to not exist on any other real
   page); .rank-hero IS shared with teacup.html's own header, so that one
   needs the .ph-header ancestor (added to ranking.html's own <header>,
   same convention as index.html) same as the header-shortcuts scoping
   trick from the Home pass. .rank-move up/down/new colors are left
   untouched on purpose - semantic state color, not brand identity.
   Chest/share/cat popup modals also left untouched this pass - out of
   scope, only visible after a click, lower priority than the always-
   visible list. Checked every selector below against the master list of
   dark-mode-gated selectors found during the Home pass; only
   .rank-hero h1 and .rank-row.top1/.top2/.top3 needed an explicit
   :root[data-theme="dark"] companion (specificity race), everything
   else here had no competing dark rule. ==================================================================== */

.ph-header .rank-hero h1{color:var(--pc-pink-deep);}

.ph-header .rank-hero p{color:var(--pc-text-soft);}

.ph-header .rank-hero .rank-updated{color:var(--pc-text-soft);}

.rank-row{
    background:var(--pc-pink-soft2);
    border:2.5px solid var(--pc-ink);
    box-shadow:none;
  }

.rank-row.top1{
    border-color:var(--pc-ink);
    background:var(--pc-pink-soft2);
    box-shadow:5px 5px 0 var(--pc-ink);
  }

.rank-row.top2{
    border-color:var(--pc-ink);
    background:var(--pc-pink-soft);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

.rank-row.top3{
    border-color:var(--pc-ink);
    background:var(--pc-cream);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.rank-medal{
    background:var(--pc-pink-soft2);
    color:var(--pc-ink);
    box-shadow:none;
    border:2.5px solid var(--pc-ink);
  }

.rank-row.top1 .rank-medal{background:var(--pc-pink); color:#fff;}

.rank-row.top2 .rank-medal{background:var(--pc-panel); color:var(--pc-ink);}

.rank-row.top3 .rank-medal{background:var(--pc-pink-soft); color:var(--pc-ink);}

.rank-name{color:var(--pc-ink);}

.rank-score{color:var(--pc-pink-deep);}

.rank-avatar{border:2.5px solid var(--pc-ink); box-shadow:none;}

.rank-row.top1 .rank-avatar{border-color:var(--pc-pink);}

.rank-row.top2 .rank-avatar{border-color:var(--pc-ink);}

.rank-row.top3 .rank-avatar{border-color:var(--pc-ink);}

.rank-share-btn{
    background:var(--pc-pink);
    color:#fff;
    border:2px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.rank-share-btn:hover{box-shadow:4px 4px 0 var(--pc-ink);}

.rank-chest-btn{border:2px solid var(--pc-ink);}

/* ---- dark-mode companions ---- */
:root[data-theme="dark"] .ph-header .rank-hero h1{color:var(--pc-pink-deep);}

:root[data-theme="dark"] .rank-row.top1{
    border-color:var(--pc-ink);
    background:var(--pc-pink-soft2);
    box-shadow:5px 5px 0 var(--pc-ink);
  }

:root[data-theme="dark"] .rank-row.top2{
    border-color:var(--pc-ink);
    background:var(--pc-pink-soft);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

:root[data-theme="dark"] .rank-row.top3{
    border-color:var(--pc-ink);
    background:var(--pc-cream);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: EVENT PAGE (2026-09-09)
   Fourth page. Same method as RWM-45/46: every .cur-event-, .event-hero,
   .past-events, .past-grid class confirmed unique to event.html via grep;
   .qr-strip/.qr-box/.countdown-grid div ARE shared (index, and index's
   own .header-shortcut-pill share this page's markup too) so those stay
   scoped through this page's own existing ancestors (.cur-event-donate,
   .cur-event-countdown) - same scoping convention the page's pre-existing
   CSS already used for those exact selectors, not something new. Added
   ph-header to event.html's own <header> (hides old decos, matches every
   prior page). Milestone/stretch-marker/confetti flourishes left as-is -
   decorative, conditional states, not core identity.
   Dark companions needed (checked against the master list from RWM-45):
   .event-hero h1, .past-events h2, .cur-event-img, .cur-event-donate
   .qr-strip/.qr-box, .cur-event-countdown .countdown-grid div,
   .cur-event-stats .stat:not(.hero). Everything else here (.cur-event-
   card, .live-badge, .cur-event-body h2/.desc, .cur-event-pct, .stat.hero,
   .past-card and children) had no competing dark rule. ==================================================================== */

.event-hero h1{color:var(--pc-pink-deep);}

.event-hero p{color:var(--pc-text-soft);}

.cur-event-card{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:7px 7px 0 var(--pc-ink);
  }

.cur-event-img{background:var(--pc-pink-soft2);}

.live-badge{
    background:var(--pc-pink);
    border:2px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.cur-event-body h2{color:var(--pc-pink-deep);}

.cur-event-body .desc{color:var(--pc-text-soft);}

.cur-event-pct{color:var(--pc-pink-deep); text-shadow:none;}

.cur-event-countdown .cec-label{color:var(--pc-pink-deep);}

.cur-event-countdown .countdown-grid div{background:var(--pc-pink-soft2); border:2px solid var(--pc-ink);}

.cur-event-stretch-note{color:var(--pc-pink-deep);}

.cur-event-stats .stat{background:var(--pc-pink-soft2); border:2px solid var(--pc-ink);}

.cur-event-stats .stat.hero{
    background:var(--pc-pink);
    border:2px solid var(--pc-ink);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

.cur-event-donate-title{color:var(--pc-pink-deep);}

.cur-event-donate .qr-strip{background:var(--pc-pink); color:#fff;}

.cur-event-donate .qr-box{background:var(--pc-pink-soft2); border:2.5px solid var(--pc-ink); color:var(--pc-text-soft);}

.cur-event-donate-note{color:var(--pc-text-soft);}

.past-events h2{color:var(--pc-pink-deep);}

.past-card{
    background:var(--pc-panel);
    border:2.5px solid var(--pc-ink);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

.past-card-img{background:var(--pc-pink-soft2);}

.past-badge{background:var(--pc-pink); color:#fff;}

.past-card-title{color:var(--pc-ink);}

.past-card-amount{color:var(--pc-pink-deep);}

.past-card-note{color:var(--pc-text-soft);}

.past-empty{color:var(--pc-text-soft); text-align:center;}

/* ---- dark-mode companions ---- */
:root[data-theme="dark"] .event-hero h1{color:var(--pc-pink-deep);}

:root[data-theme="dark"] .past-events h2{color:var(--pc-pink-deep);}

:root[data-theme="dark"] .cur-event-img{background:var(--pc-pink-soft2);}

:root[data-theme="dark"] .cur-event-stats .stat:not(.hero){background:var(--pc-pink-soft2); border-color:var(--pc-ink);}

:root[data-theme="dark"] .cur-event-donate .qr-strip{background:var(--pc-pink); color:#fff;}

:root[data-theme="dark"] .cur-event-donate .qr-box{background:var(--pc-pink-soft2); border-color:var(--pc-ink); color:var(--pc-text-soft);}

:root[data-theme="dark"] .cur-event-countdown .countdown-grid div{background:var(--pc-pink-soft2); border-color:var(--pc-ink);}

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: TEACUP PAGE (2026-09-09)
   Fifth page. Every .tc- class confirmed unique to teacup.html (per this
   file's own comments, they always were - the two exceptions are the
   two rules just above this one on purpose). Reuses .rank-hero for its
   own header same as ranking.html, so adding ph-header here also means
   the RWM-46 rule for .ph-header .rank-hero h1/p/.rank-updated now
   covers this page's title/subtitle/updated-line too, no new rule
   needed for those three. Only one dark companion needed this pass:
   .tc-chip's color is dark-gated on its own (line ~3072, an old
   contrast fix for --lavender's background in dark mode) - since this
   pass gives chips their own background+color anyway, added a matching
   companion rather than relying on that old rule's now-irrelevant fix.
   Compact-view accordion mechanics (grid-template-rows transition) and
   the share-cat-collection button flow are untouched, colors only. ==================================================================== */

.tc-stat{
    background:var(--pc-panel);
    border:2px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.tc-stat b{color:var(--pc-pink-deep);}

.tc-input,.tc-select{
    background:var(--pc-panel);
    border:2px solid var(--pc-ink);
    color:var(--pc-ink);
  }

.tc-view{background:var(--pc-pink-soft2); border:2px solid var(--pc-ink);}

.tc-view-btn.active{background:var(--pc-pink); color:#fff;}

.tc-card{
    background:var(--pc-panel);
    border:2.5px solid var(--pc-ink);
    box-shadow:4px 4px 0 var(--pc-ink);
  }

.tc-name{color:var(--pc-ink);}

.tc-count{color:var(--pc-pink-deep);}

.tc-bar{background:var(--pc-cream);}

.tc-bar span{background:var(--pc-pink);}

.tc-chip{
    background:var(--pc-pink-soft2);
    border:1.5px solid var(--pc-ink);
    color:var(--pc-ink);
  }

.tc-chip.on{background:var(--pc-pink); color:#fff; border-color:var(--pc-ink);}

.tc-cat-share-btn{background:var(--pc-pink); color:#fff;}

.tc-row{
    background:var(--pc-panel);
    border:2.5px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.tc-row-badge{background:var(--pc-pink); color:#fff;}

.tc-row-name{color:var(--pc-ink);}

.tc-row-count{background:var(--pc-pink-soft2); color:var(--pc-pink-deep);}

/* ---- dark-mode companion ---- */
:root[data-theme="dark"] .tc-chip{color:var(--pc-ink);}

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: AQUARIUM PAGE (2026-09-09)
   Sixth page. Deliberately light-touch: reuses .bottle-hero for its own
   header (shared with bottle.html, same convention as .rank-hero across
   ranking/teacup), so adding ph-header here covers title/subtitle for
   both pages at once. Fish sprites/tank background art and the whole
   catalog "book" sub-theme (.aq-slot/.aq-book, its own warm parchment
   palette, #5a4426 etc.) are deliberately left untouched - standing
   project rule against code-drawn fish/animal styling, and the book
   look already reads as a coherent, separate visual choice rather than
   a clash worth fixing. Only the tank's own frame (comic border+shadow,
   matching every other page's photo/card frames) and the plain text
   bits (.aq-count/.aq-empty/.aq-catalog-hint/footer) actually changed.
   Dark companion needed: .bottle-hero h1 (shared master-list entry from
   the Home pass audit, also used by bottle.html next). */

.bottle-hero h1{color:var(--pc-pink-deep);}

.bottle-hero p{color:var(--pc-text-soft);}

.aq-tank{border:3px solid var(--pc-ink); box-shadow:6px 6px 0 var(--pc-ink);}

.aq-count{color:var(--pc-pink-deep);}

.aq-empty{color:var(--pc-text-soft);}

.aq-catalog-hint{color:var(--pc-text-soft);}

#aqCatalogTitle{color:var(--pc-pink-deep);}

/* Owner search card (RWM-64, added after this pass — see the base .aq-search-* rules further
   up for why this needs the same treatment as .aq-goal-card/.aq-tank right next to it). */
.aq-search-card{background:var(--pc-panel); border:3px solid var(--pc-ink); box-shadow:6px 6px 0 var(--pc-ink);}

.aq-search-owner{color:var(--pc-pink-deep);}

.aq-search-input{background:var(--pc-panel); border:2px solid var(--pc-ink); color:var(--pc-ink);}

/* ---- dark-mode companion ---- */
:root[data-theme="dark"] .bottle-hero h1{color:var(--pc-pink-deep);}
:root[data-theme="dark"] .aq-search-card{background:var(--pc-panel); border:3px solid var(--pc-ink); box-shadow:6px 6px 0 var(--pc-ink);}
:root[data-theme="dark"] .aq-search-input{background:var(--pc-panel); border:2px solid var(--pc-ink); color:var(--pc-ink);}

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: BOTTLE PAGE (2026-09-09)
   Seventh page. .bottle-hero h1/p already covered by the Aquarium pass
   above (shared header). .bottle-sea (the ocean scene messages float
   in) and .bottle-art (the real bottle image) are deliberately left
   untouched, same standing reasons as Aquarium's fish - real art/scene,
   not chrome. Everything below is the submit-form card and the
   read-a-message modal, both plain chrome. Dark companions needed:
   .bottle-modal and .bottle-modal-close (both already dark-gated,
   confirmed via grep). #bsSubmitBtn/.bottle-share-btn's existing plain
   rule (grouped with .support-btn/.rank-share-btn/.share-plat-btn) has
   no competing dark gate of its own, so no companion needed there. ==================================================================== */

.bottle-submit-card{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:6px 6px 0 var(--pc-ink);
  }

.bottle-submit-card h3{color:var(--pc-pink-deep);}

.bottle-submit-hint{color:var(--pc-text-soft);}

#bottleSubmitForm input[type=text], #bottleSubmitForm textarea{
    background:var(--pc-panel);
    border:2px solid var(--pc-ink);
    color:var(--pc-ink);
  }

.bottle-slip-label{border:2px dashed var(--pc-ink); color:var(--pc-text-soft);}

.bottle-slip-label.has-file{color:var(--pc-pink-deep); border-color:var(--pc-pink); border-style:solid;}

#bsSubmitBtn{
    background:var(--pc-pink);
    border:2px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.bottle-submit-status.ok{color:var(--pc-pink-deep);}

.bottle-empty{color:var(--pc-text-soft);}

.bottle-modal{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:6px 6px 0 var(--pc-ink);
  }

.bottle-modal-close{background:var(--pc-panel); border:2px solid var(--pc-ink); color:var(--pc-ink);}

.bottle-message-text{color:var(--pc-ink);}

.bottle-message-from{color:var(--pc-text-soft);}

.bottle-share-btn{background:var(--pc-pink); border:2px solid var(--pc-ink);}

/* ---- dark-mode companions ---- */
:root[data-theme="dark"] .bottle-modal{
    background:var(--pc-panel);
    border:3px solid var(--pc-ink);
    box-shadow:6px 6px 0 var(--pc-ink);
  }

:root[data-theme="dark"] .bottle-modal-close{background:var(--pc-panel); border:2px solid var(--pc-ink); color:var(--pc-ink);}

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: PLAYLIST PAGE (2026-09-09)
   Eighth and last page of this pass. Also reuses .bottle-hero (already
   covered by RWM-50, ph-header added here too for the deco-hide). Every
   .pl- class confirmed unique to playlist.html via grep, and none of
   them had any competing dark-gated rule at all - checked with a direct
   grep for "dark" near ".pl-" this time instead of relying purely on
   the master list, since this page wasn't part of the audit that built
   it. .pl-art is just a decorative music-note emoji swatch, not real
   supplied art, so recoloring its background is fine (unlike the
   Aquarium/Bottle art exceptions). .rank-move (the up/down/NEW movement
   badge, reused as-is from Ranking) stays untouched - semantic color. ==================================================================== */

.pl-song{
    background:var(--pc-panel);
    border:2.5px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

.pl-song.pl-top{
    border-color:var(--pc-ink);
    background:var(--pc-pink-soft2);
    box-shadow:5px 5px 0 var(--pc-ink);
  }

.pl-rank{color:var(--pc-text-soft);}

.pl-song.pl-top .pl-rank{color:var(--pc-pink-deep);}

.pl-eq span{background:var(--pc-pink);}

.pl-art{background:var(--pc-pink); color:#fff;}

.pl-title{color:var(--pc-ink);}

.pl-artist{color:var(--pc-text-soft);}

.pl-score{background:var(--pc-pink-soft2); color:var(--pc-pink-deep);}

.pl-song.pl-top .pl-score{background:var(--pc-pink); color:#fff;}

.pl-from{color:var(--pc-text-soft);}

.pl-name{color:var(--pc-ink);}

.pl-name-times{color:var(--pc-pink-deep);}

.pl-note{background:var(--pc-pink-soft2); color:var(--pc-ink);}

.pl-empty{color:var(--pc-text-soft);}

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: GLOBAL BODY BACKGROUND (2026-09-09)
   Last step of the retheme, now that all 7 real pages (RWM-45/46/47/48/
   50/51) use the new palette. This is the one piece that genuinely
   could not move earlier - <body> is shared markup the SPA router
   never swaps (see RWM-45's note on router.js), so changing it while
   any page still looked like Underwater World would have put the new
   background behind old blue/coral content. Matches the mockup's own
   body rule exactly: a soft pink polka-dot pattern instead of the flat
   --body-bg-a page tint. --pc-paper added to the token blocks above
   (light/dark) for this. Does not touch --body-bg-a/--body-bg-b
   themselves - those tokens are still read by several not-yet-touched
   admin-editable pieces (.opc-box, .tier-card.complete, etc.), so
   redefining them would have reached further than intended; a direct
   body{} override is the narrower, correct-scoped change. .gingham's
   wave decorations are unaffected on purpose - they're translucent
   white shapes already, they read fine over the new dotted ground
   without any change needed. */
body{
    background:var(--pc-paper);
  }

html{background:var(--pc-paper);}

/* Polka dots stay dark-mode-only (2026-09-09, on request) — light mode read as too busy/
   "yellow-adjacent" for the reward card sitting on top of it, so light keeps a flat --pc-paper
   ground and only dark gets the dotted texture. Needs its own :root[data-theme="dark"] rule
   since the plain `body{}` above already claims the property at the same specificity tier
   and this file's convention is append-to-win, not selector-strength. */
:root[data-theme="dark"] body{
    background-image:radial-gradient(var(--pc-pink-soft) 1.3px, transparent 1.3px);
    background-size:11px 11px;
  }

/* ====================================================================
   MANGA PANEL - PINK CUTE RETHEME: TOP NAV BAR (2026-09-09)
   Truly last piece - .site-nav/.nav-pill is identical global markup on
   all 8 pages (not swapped per-page like the content sections), so it
   was always going to be a single one-time change rather than part of
   the page-by-page pass. Safe now that RWM-52 already moved the shared
   body background over. The .gold/.coral/.bottle/.teacup pill classes
   in every page's markup are dead weight, not touched here either -
   a 2026-08-29 change (see comment a few hundred lines up) already
   removed their per-pill colors in favor of one flat look for all non-
   active pills, this pass just reskins that same flat look. Dark
   companions needed for both .nav-pill and .nav-pill.active - already
   dark-gated from that same 2026-08-29 pass. */

.nav-pill{
    background:var(--pc-panel);
    color:var(--pc-ink);
    border:2.5px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
    backdrop-filter:none;
    -webkit-backdrop-filter:none;
  }

.nav-pill:hover{box-shadow:4px 4px 0 var(--pc-ink);}

.nav-pill.active{
    background:var(--pc-pink);
    color:#fff;
    border-color:var(--pc-ink);
    outline:none;
    box-shadow:3px 3px 0 var(--pc-ink);
  }

/* ---- dark-mode companions ---- */
:root[data-theme="dark"] .nav-pill{
    background:var(--pc-panel);
    color:var(--pc-ink);
    border:2.5px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

:root[data-theme="dark"] .nav-pill.active{
    background:var(--pc-pink);
    color:#fff;
    border-color:var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
  }

/* ---- "ใกล้แล้ว!" callout (2026-09-09, on request) ---------------
   Speech-bubble badge pinned to a top-right corner (moved there 2026-09-09
   per user's circled screenshot — it started out anchored next to the bar
   itself, but that read as tied to the moving fill rather than a standalone
   callout). On the event page it's anchored to .cur-event-body specifically,
   not .cur-event-card (2026-09-10 fix) — an activity with an admin-uploaded
   cur.image renders that image as the card's first child, so anchoring to
   the whole card put the badge on top of someone else's artwork instead of
   near the live-badge pill; .cur-event-body always exists right where the
   pill is, image or no image. .bar-block (home page tier bar) gets
   position:relative here since it had none. render.js toggles the .show
   class; default hidden so a pre-JS/no-JS load never flashes it. */
.bar-block{position:relative;}

.near-goal-badge{
    display:none;
    position:absolute;
    top:14px; right:14px;
    background:var(--pc-pink);
    color:#fff;
    font-weight:700;
    font-size:.78rem;
    line-height:1;
    padding:7px 14px;
    border-radius:999px;
    border:3px solid var(--pc-ink);
    box-shadow:3px 3px 0 var(--pc-ink);
    white-space:nowrap;
    z-index:6;
  }

.near-goal-badge::after{
    content:"";
    position:absolute;
    bottom:-9px; left:22px;
    width:12px; height:12px;
    background:var(--pc-pink);
    border-right:3px solid var(--pc-ink);
    border-bottom:3px solid var(--pc-ink);
    transform:rotate(45deg);
  }

.near-goal-badge.show{display:inline-block; animation:nearGoalPop .4s ease-out;}

@keyframes nearGoalPop{
    0%{transform:scale(.5); opacity:0;}
    70%{transform:scale(1.08); opacity:1;}
    100%{transform:scale(1);}
  }

/* ---- aquarium fundraising goal card (2026-09-09) ---------------------------------------
   Reuses the event page's .cur-event-card/.cur-event-progress/.cur-event-stats components
   as-is — same markup shape dropped onto aquarium.html, so it inherits that component's full
   styling (incl. the Pink Cute dark-mode pass already done for those classes) with zero new
   rules needed beyond this page's own top/bottom spacing, since aquarium.html has no .cur-event
   wrapper section to inherit that margin from. */
.aq-goal-card{margin:26px 0 22px;}

/* Compact goal restatement inside the tank itself, fullscreen-only — see render.js's
   aqMiniGoal comment for why the full card above can't be reached from fullscreen.
   Same dark glass-pill look as .aq-tank-toggle for visual consistency with the tank's own
   floating chrome, bottom-center so it never collides with the top toolbar or the in-tank
   catalog toggle. */
.aq-mini-goal{
    display:none;
    /* + env(safe-area-inset-bottom) (2026-09-10) — only ever shown in fullscreen (see the
       display:flex override below), where the tank is truly edge-to-edge, so this is never
       displayed in a context where env() would be meaningless. */
    position:absolute; bottom:calc(14px + env(safe-area-inset-bottom)); left:50%; transform:translateX(-50%);
    z-index:6; align-items:center; gap:8px;
    background:rgba(0,0,0,.45); border:1px solid rgba(255,255,255,.35);
    border-radius:999px; padding:6px 14px;
    color:#fff; font-size:.78rem; font-weight:700; white-space:nowrap;
  }

.aq-tank.is-fullscreen .aq-mini-goal{display:flex;}

.aq-mini-goal-track{
    position:relative; width:90px; height:8px; border-radius:999px;
    background:rgba(255,255,255,.25); overflow:hidden; flex:none;
  }

.aq-mini-goal-fill{
    height:100%; border-radius:999px; background:var(--pc-pink);
    transition:width 1.2s ease-out;
  }

/* ====================================================================
   STRAWBERRY TEA PARTY RETHEME (2026-09-25) — global chrome + Home
   User-picked mockup "A2 Strawberry Tea Party" (artifact
   Mj4jfpkjDe4WzV1jd5PK8J): pink gingham tablecloth ground, pink/white
   striped shop awning behind the nav, white pill nav, soft rose-edged
   cards with a flat rose drop instead of the manga ink outline, a
   polaroid avatar, strawberry-seed progress fill. Font stays
   DBHelvethaica (user asked to keep the original font).
   Same approach as the Pink Cute pass above: new --st-* tokens (not the
   admin-recolorable --sea-* and --coral-* ones), appended last so it wins
   on source order. Every rule is written as a pair
   `.x, :root[data-theme="dark"] .x` so it also beats this file's older
   dark-gated rules for the same selector, with the dark look coming from
   the token swap alone.
   Global pieces (body ground, awning, nav) reach every page; content
   rules are Home-only selectors (.cta-panel, .progress-card, .roadmap,
   .tier-card, .rewards, .bottom-grid-2 ... none exist on other pages),
   so the other pages keep the Pink Cute panels until their own turn.
   gacha.html is unaffected by the ground (its body uses !important) and
   has no .gingham, so it never gets the awning. ================== */
:root{
  --st-cream:#fffaf5;
  --st-panel:#ffffff;
  --st-check:rgba(255,111,145,.11);
  --st-straw:#ff6f91;
  --st-straw-deep:#d9466b;
  --st-rose:#ffc2d1;
  --st-rose-soft:#fff0f4;
  --st-ink:#5a2a3a;
  --st-text-soft:#8a5a68;
  --st-seed:#ffe9a8;
  --st-awn:#ffb3c6;
  --st-awn2:#ffffff;
  --st-gold:#f2c14e;
  --st-gold-soft:#fff5d6;
}

:root[data-theme="dark"]{
  --st-cream:#2b1620;
  --st-panel:#3b2030;
  --st-check:rgba(255,134,164,.055);
  --st-straw:#ff86a4;
  --st-straw-deep:#ffa3bb;
  --st-rose:#6e3a4d;
  --st-rose-soft:#4a2838;
  --st-ink:#ffe6ec;
  --st-text-soft:#d9b3bf;
  --st-seed:#ffe9a8;
  --st-awn:#c9587a;
  --st-awn2:#3b2030;
  --st-gold:#f2c14e;
  --st-gold-soft:#4a3a1c;
}

/* ---- ground: pink gingham tablecloth (both themes) ---- */
html,:root[data-theme="dark"] html{background:var(--st-cream);}

body,:root[data-theme="dark"] body{
    background-color:var(--st-cream);
    background-image:
      linear-gradient(90deg, var(--st-check) 50%, transparent 50%),
      linear-gradient(var(--st-check) 50%, transparent 50%);
    background-size:36px 36px;
    color:var(--st-ink);
  }

/* ---- shop awning: the top .gingham strip becomes it; bottom wave strip retires ---- */
#ambientSoundBtn + .gingham{
    position:absolute; top:0; left:0; right:0;
    height:64px;
    z-index:0;
    overflow:visible;
    transform:none;
    animation:none;
    background:repeating-linear-gradient(90deg, var(--st-awn) 0 44px, var(--st-awn2) 44px 88px);
    pointer-events:none;
  }

#ambientSoundBtn + .gingham::before{display:none;}

/* phones: the 4 nav pills wrap to 2 rows, so the awning grows to sit behind both */
@media (max-width:600px){
  #ambientSoundBtn + .gingham{height:104px;}
}

#ambientSoundBtn + .gingham::after{
    content:"";
    display:block;
    position:absolute;
    left:0; right:0; top:auto; bottom:-20px;
    width:auto; height:22px;
    transform:none;
    animation:none;
    background:
      radial-gradient(circle at 22px 0, var(--st-awn) 21px, transparent 22px) 0 0/88px 22px repeat-x,
      radial-gradient(circle at 66px 0, var(--st-awn2) 21px, transparent 22px) 0 0/88px 22px repeat-x;
    filter:drop-shadow(0 3px 0 rgba(255,111,145,.28));
  }

/* bottom wave strip — not always right after .wrap (bottle.html has overlays in between) */
.gingham:not(#ambientSoundBtn + .gingham){display:none;}

/* ---- every other page: recolour the Pink Cute pass instead of rewriting it ----
   Ranking/Event/Teacup/Bottle/Playlist/Aquarium were all built on --pc-* tokens
   with a black-ink outline + hard offset shadow. Re-pointing the tokens gives them
   the strawberry palette (--pc-ink becomes the berry text colour), and the two
   rule groups below swap every ink outline for rose and every hard "Xpx Ypx 0 ink"
   shadow for the flat rose drop, keeping each rule's original depth. Selector
   lists were generated from every rule in this file that used var(--pc-ink) in a
   border or box-shadow. Home's own rules further down still win (later). */
:root{
  --pc-ink:#5a2a3a;
  --pc-paper:#fffaf5;
  --pc-panel:#ffffff;
  --pc-pink:#ff6f91;
  --pc-pink-deep:#d9466b;
  --pc-pink-soft:#ffc2d1;
  --pc-pink-soft2:#fff0f4;
  --pc-cream:#fff5d6;
  --pc-text-soft:#8a5a68;
}

:root[data-theme="dark"]{
  --pc-ink:#ffe6ec;
  --pc-paper:#2b1620;
  --pc-panel:#3b2030;
  --pc-pink:#ff86a4;
  --pc-pink-deep:#ffa3bb;
  --pc-pink-soft:#6e3a4d;
  --pc-pink-soft2:#4a2838;
  --pc-cream:#4a3a1c;
  --pc-text-soft:#d9b3bf;
}

.rate-pill,.header-side .header-shortcuts .header-shortcut-pill,.cta-panel .qr-box,.cta-panel .qr-box img,.opc-box,.social-block,.social-link,.tier-now,.goal-block,.rm-track::before,.rm-dot,.rewards,.panel,.bottom-grid-2 .countdown-grid div,
.rank-row,.rank-medal,.rank-avatar,.rank-share-btn,.rank-chest-btn,
.cur-event-card,.live-badge,.cur-event-countdown .countdown-grid div,.cur-event-stats .stat,.cur-event-donate .qr-box,.past-card,
.tc-stat,.tc-input,.tc-select,.tc-view,.tc-card,.tc-chip,.tc-row,
.aq-tank,.aq-search-card,.aq-search-input,
.bottle-submit-card,#bottleSubmitForm input[type=text],#bottleSubmitForm textarea,.bottle-slip-label,#bsSubmitBtn,.bottle-modal,.bottle-modal-close,.bottle-share-btn,
.pl-song,.near-goal-badge,.near-goal-badge::after,
:root[data-theme="dark"] .rate-pill,:root[data-theme="dark"] .header-side .header-shortcuts .header-shortcut-pill,:root[data-theme="dark"] .cta-panel .qr-box,:root[data-theme="dark"] .opc-box,:root[data-theme="dark"] .social-link,:root[data-theme="dark"] .rm-dot,:root[data-theme="dark"] .rewards,:root[data-theme="dark"] .bottom-grid-2 .countdown-grid div,
:root[data-theme="dark"] .rank-row,:root[data-theme="dark"] .rank-medal,:root[data-theme="dark"] .rank-avatar,:root[data-theme="dark"] .rank-share-btn,:root[data-theme="dark"] .rank-chest-btn,
:root[data-theme="dark"] .cur-event-card,:root[data-theme="dark"] .live-badge,:root[data-theme="dark"] .cur-event-countdown .countdown-grid div,:root[data-theme="dark"] .cur-event-stats .stat,:root[data-theme="dark"] .cur-event-donate .qr-box,:root[data-theme="dark"] .past-card,
:root[data-theme="dark"] .tc-stat,:root[data-theme="dark"] .tc-input,:root[data-theme="dark"] .tc-select,:root[data-theme="dark"] .tc-view,:root[data-theme="dark"] .tc-card,:root[data-theme="dark"] .tc-chip,:root[data-theme="dark"] .tc-row,
:root[data-theme="dark"] .aq-tank,:root[data-theme="dark"] .aq-search-card,:root[data-theme="dark"] .aq-search-input,
:root[data-theme="dark"] .bottle-submit-card,:root[data-theme="dark"] #bottleSubmitForm input[type=text],:root[data-theme="dark"] #bottleSubmitForm textarea,:root[data-theme="dark"] .bottle-slip-label,:root[data-theme="dark"] #bsSubmitBtn,:root[data-theme="dark"] .bottle-modal,:root[data-theme="dark"] .bottle-modal-close,:root[data-theme="dark"] .bottle-share-btn,
:root[data-theme="dark"] .pl-song{
    border-color:var(--st-rose);
  }

/* higher-specificity variants from the same list (would otherwise keep the ink edge) */
.cur-event-stats .stat.hero,.cur-event-stats .stat:not(.hero),.rank-row.top2 .rank-avatar,.rank-row.top3 .rank-avatar,
:root[data-theme="dark"] .cur-event-stats .stat.hero,:root[data-theme="dark"] .cur-event-stats .stat:not(.hero),:root[data-theme="dark"] .rank-row.top2 .rank-avatar,:root[data-theme="dark"] .rank-row.top3 .rank-avatar{
    border-color:var(--st-rose);
  }

/* the podium rows and the top song keep a stronger strawberry edge */
.rank-row.top1,.rank-row.top2,.rank-row.top3,.pl-song.pl-top,.tc-chip.on,
:root[data-theme="dark"] .rank-row.top1,:root[data-theme="dark"] .rank-row.top2,:root[data-theme="dark"] .rank-row.top3,:root[data-theme="dark"] .pl-song.pl-top,:root[data-theme="dark"] .tc-chip.on{
    border-color:var(--st-straw);
  }

.rank-row.top3,.rank-share-btn,.live-badge,.tc-stat,.tc-row,#bsSubmitBtn,.pl-song,.header-side .header-shortcuts .header-shortcut-pill,
:root[data-theme="dark"] .rank-row.top3,:root[data-theme="dark"] .rank-share-btn,:root[data-theme="dark"] .live-badge,:root[data-theme="dark"] .tc-stat,:root[data-theme="dark"] .tc-row,:root[data-theme="dark"] #bsSubmitBtn,:root[data-theme="dark"] .pl-song{
    box-shadow:0 3px 0 var(--st-rose);
  }

.rank-row.top2,.rank-share-btn:hover,.cur-event-stats .stat.hero,.past-card,.tc-card,
:root[data-theme="dark"] .rank-row.top2,:root[data-theme="dark"] .rank-share-btn:hover,:root[data-theme="dark"] .cur-event-stats .stat.hero,:root[data-theme="dark"] .past-card,:root[data-theme="dark"] .tc-card{
    box-shadow:0 4px 0 var(--st-rose);
  }

.rank-row.top1,.pl-song.pl-top,.panel,
:root[data-theme="dark"] .rank-row.top1,:root[data-theme="dark"] .pl-song.pl-top,:root[data-theme="dark"] .panel{
    box-shadow:0 5px 0 var(--st-rose);
  }

.aq-tank,.aq-search-card,.bottle-submit-card,.bottle-modal,
:root[data-theme="dark"] .aq-tank,:root[data-theme="dark"] .aq-search-card,:root[data-theme="dark"] .bottle-submit-card,:root[data-theme="dark"] .bottle-modal{
    box-shadow:0 6px 0 var(--st-rose);
  }

.cur-event-card,:root[data-theme="dark"] .cur-event-card{
    box-shadow:0 6px 0 var(--st-rose);
  }

.rank-row.top1,.rank-row.top2,.rank-row.top3,.pl-song.pl-top{box-shadow:0 5px 0 var(--st-straw);}

:root[data-theme="dark"] .rank-row.top1,:root[data-theme="dark"] .rank-row.top2,:root[data-theme="dark"] .rank-row.top3,:root[data-theme="dark"] .pl-song.pl-top{box-shadow:0 5px 0 var(--st-straw);}

/* big cards get the rounder strawberry corners */
.cur-event-card,.past-card,.tc-card,.aq-search-card,.bottle-submit-card,.bottle-modal{border-radius:22px;}

/* ---- nav: white pills sitting on the awning ---- */
.site-nav{position:relative; z-index:2;}

.nav-pill,:root[data-theme="dark"] .nav-pill{
    background:var(--st-panel);
    color:var(--st-ink);
    border:0;
    box-shadow:0 3px 0 rgba(255,111,145,.35);
  }

.nav-pill:hover,:root[data-theme="dark"] .nav-pill:hover{box-shadow:0 5px 0 rgba(255,111,145,.4);}

.nav-pill.active,:root[data-theme="dark"] .nav-pill.active{
    background:var(--st-straw);
    color:#fff;
    border:0;
    outline:none;
    box-shadow:0 3px 0 var(--st-straw-deep);
  }

:root[data-theme="dark"] .nav-pill.active{color:#2b1620;}

/* ================= Home page ================= */

/* ---- header: polaroid avatar, strawberry headings ---- */
.avatar,:root[data-theme="dark"] .avatar{
    /* width stays admin-controlled (render.js sets it inline from the avatar-size
       slider, and a matching height); the polaroid grows to fit its caption instead */
    height:auto !important;
    border-radius:4px;
    border:0;
    background:#fff;
    padding:10px 10px 36px;
    box-shadow:0 10px 24px rgba(90,42,58,.18);
    transform:rotate(-3deg);
    overflow:visible;
    position:relative;
    animation:none;
  }

.avatar img{border-radius:2px; height:auto; aspect-ratio:5/6;}

.avatar::before{
    content:"🍓";
    position:absolute; top:-16px; right:-12px;
    font-size:2.2rem; line-height:1;
    transform:rotate(18deg);
    font-family:'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif;
  }

.avatar::after{
    content:"Micha ♡ GE6";
    position:absolute; left:0; right:0; bottom:6px;
    text-align:center;
    font-size:1.05rem; font-weight:700;
    color:#ff6f91;
    text-shadow:none;
  }

.title-block p.sub{color:var(--st-text-soft);}

.header-side h2,:root[data-theme="dark"] .header-side h2{color:var(--st-straw);}

.rate-pill,:root[data-theme="dark"] .rate-pill{
    background:var(--st-rose);
    border:0;
    color:var(--st-ink);
    box-shadow:none;
  }

.header-side .header-shortcuts .header-shortcut-pill{
    background:var(--st-panel);
    border:2px solid var(--st-rose);
    color:var(--st-ink);
    box-shadow:none;
  }

.header-side .header-shortcuts .header-shortcut-pill:hover{
    background:var(--st-rose-soft);
    box-shadow:0 3px 0 var(--st-rose);
  }

/* ---- shared card look for every Home section ---- */
.cta-panel,:root[data-theme="dark"] .cta-panel,
.progress-card,:root[data-theme="dark"] .progress-card,
.bottom-grid-2 .panel,:root[data-theme="dark"] .bottom-grid-2 .panel{
    background:var(--st-panel);
    border:3px solid var(--st-rose);
    border-radius:22px;
    box-shadow:0 6px 0 var(--st-rose);
  }

/* scalloped lace edge along the bottom of the support panel */
.cta-panel,:root[data-theme="dark"] .cta-panel{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='12'%3E%3Cpath d='M0 12 Q5 0 10 12 Q15 0 20 12' fill='none' stroke='%23ffc2d1' stroke-width='2'/%3E%3C/svg%3E");
    background-position:bottom;
    background-size:20px 12px;
    background-repeat:repeat-x;
  }

.cta-qr h4,.cta-support h3,.bottom-grid-2 .panel h4{color:var(--st-straw);}

.cta-support p,.trust-row{color:var(--st-text-soft);}

.cta-panel .qr-strip{background:var(--st-straw); color:#fff;}

.cta-panel .qr-box,:root[data-theme="dark"] .cta-panel .qr-box{
    background:var(--st-rose-soft);
    border:2px solid var(--st-rose);
    color:var(--st-text-soft);
  }

.cta-panel .qr-box img{border-color:var(--st-rose);}

.opc-box,:root[data-theme="dark"] .opc-box{
    background:var(--st-rose-soft);
    color:var(--st-straw-deep);
    border:2px dashed var(--st-rose);
  }

.support-btn,:root[data-theme="dark"] .support-btn{
    background:var(--st-straw);
    color:#fff;
    border:0;
    box-shadow:0 4px 0 var(--st-straw-deep);
  }

:root[data-theme="dark"] .support-btn{color:#2b1620; box-shadow:0 4px 0 #c9587a;}

.support-btn:hover{box-shadow:0 6px 0 var(--st-straw-deep);}

.social-block{border-top:2px dashed var(--st-rose);}

.social-link,:root[data-theme="dark"] .social-link{
    background:var(--st-panel);
    border:2px solid var(--st-rose);
    color:var(--st-straw);
  }

/* ---- progress card + strawberry bar (Home only — event's bar keeps the pixel heart) ---- */
.tier-now{border-right:2px dashed var(--st-rose);}
.goal-block{border-left:2px dashed var(--st-rose);}

.tier-now .label,.goal-block .label,.bar-block .nums .goal,.track-caption{color:var(--st-text-soft);}

.tier-now .tier,.goal-block .num,.bar-block .nums .cur{color:var(--st-straw);}

/* (the event page's donation bar gets the same strawberry bar) */
.progress-card .track,.cur-event-bar-track{
    background:var(--st-rose-soft);
    border:3px solid var(--st-straw);
    border-radius:999px;
  }

.progress-card .fill,.cur-event-bar-fill{
    background:
      radial-gradient(ellipse 1.5px 2.5px, var(--st-seed) 90%, transparent) 0 0/12px 10px,
      var(--st-straw);
    border-radius:999px;
  }

.progress-card .fill::before,.cur-event-bar-fill::before{display:none;}

.progress-card .fill::after,.cur-event-bar-fill::after{
    content:"🍓";
    background:none;
    width:auto; height:auto;
    top:50%; right:-14px;
    transform:translateY(-50%);
    font-size:1.5rem; line-height:1;
    font-family:'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif;
  }

.near-goal-badge{background:var(--st-straw); border:0; box-shadow:0 3px 0 var(--st-straw-deep);}
.near-goal-badge::after{background:var(--st-straw); border:0;}

/* ---- roadmap + tier cards ---- */
.rm-track::before{border-top:3px dashed var(--st-rose);}

.rm-dot,:root[data-theme="dark"] .rm-dot{
    background:var(--st-panel);
    border:3px solid var(--st-straw);
    box-shadow:0 0 0 3px var(--st-rose-soft);
  }

.rm-label{color:var(--st-straw);}

.tier-card,:root[data-theme="dark"] .tier-card{
    background:var(--st-panel);
    border:2.5px solid var(--st-rose);
    border-radius:20px;
    box-shadow:0 4px 0 var(--st-rose);
  }

.tier-card.active,:root[data-theme="dark"] .tier-card.active{
    background:var(--st-gold-soft);
    border-color:var(--st-gold);
    box-shadow:0 5px 0 var(--st-gold);
  }

.tier-card.complete,:root[data-theme="dark"] .tier-card.complete{
    background:var(--st-rose-soft);
    border-color:var(--st-straw);
    box-shadow:0 4px 0 var(--st-straw);
  }

.tier-card .badge,.tier-card.active .badge{background:var(--st-rose); color:var(--st-ink);}

.tier-card.complete .badge{background:var(--st-straw); color:#fff;}

.tier-card .baht,.tier-card .tier-complete{color:var(--st-straw);}

.tier-card .range{color:var(--st-text-soft);}

/* ---- rewards ---- */
.rewards,:root[data-theme="dark"] .rewards{
    background:var(--st-rose-soft);
    border:3px dashed var(--st-straw);
    border-radius:22px;
    box-shadow:none;
  }

.rewards h3{color:var(--st-straw);}

.rewards p.rw-sub,.reward-item .tok{color:var(--st-text-soft);}

/* ---- countdown + last update ---- */
.bottom-grid-2 .countdown-grid div,:root[data-theme="dark"] .bottom-grid-2 .countdown-grid div{
    background:var(--st-rose-soft);
    border:0;
    border-radius:12px;
  }

.bottom-grid-2 .countdown-grid .cnum,.update-num{color:var(--st-straw);}

.bottom-grid-2 .countdown-grid .clabel,.bottom-grid-2 .panel .small{color:var(--st-text-soft);}

#siteFooter{color:var(--st-straw);}

@media (prefers-reduced-motion: reduce){
  .avatar{transform:none;}
}

/* ====================================================================
   STICKER SEA SITEWIDE (2026-09-26) — replaces the Strawberry look above.
   User: "ทำ ui ทั้งเว็บน่ารักกว่านี้ได้มั้ย รู้สึกมันรกๆไปหน่อย" → comparison
   artifacts (strawberry calm/cute, then sea) → picked "Sticker Sea"
   (artifact 4bqCH2XFyPGiHuASfRqwTB tab 1), i.e. the gacha page's look for
   the whole site: bright blue water, white sticker cards with a flat blue
   drop, pink accents, one face sticker per big card (/stickers/*.svg).
   Declutter: gingham + awning gone, one-row scrollable nav on phones.
   Works by re-pointing the Strawberry block's --st-* and the Pink Cute
   --pc-* tokens (so every page follows), then overriding the pieces that
   need a different shape (card edges/drops, ground, nav, on-ground text).
   Same `.x, :root[data-theme="dark"] .x` pairing as the block above.
   Dark = the same sea at night. Delete this block to get Strawberry back. */
:root{
  --st-cream:#4a98ee; --st-panel:#ffffff; --st-check:transparent;
  --st-straw:#f06a9b; --st-straw-deep:#d9558a;
  --st-rose:#dbe7fb; --st-rose-soft:#f2f7ff;
  --st-ink:#34508f; --st-text-soft:#6f86b8; --st-seed:#ffffff;
  --st-gold:#f2b544; --st-gold-soft:#fff6dc;
  --pc-ink:#34508f; --pc-paper:#f2f7ff; --pc-panel:#ffffff;
  --pc-pink:#f06a9b; --pc-pink-deep:#d9558a; --pc-pink-soft:#dbe7fb; --pc-pink-soft2:#f2f7ff;
  --pc-cream:#fff6dc; --pc-text-soft:#6f86b8;
  --ss-edge:#ffffff; --ss-drop:rgba(40,90,170,.24); --ss-btn:#ff8fb8; --ss-btn-deep:#e46a98; --ss-blue:#5b8ff0;
  --ss-ground:linear-gradient(180deg,#4a98ee 0,#68adf4 30%,#8fc5f9 65%,#c9e3ff 100%);
  --ss-ontext:#ffffff; --ss-onshadow:0 2px 0 rgba(40,90,170,.35);
}
:root[data-theme="dark"]{
  --st-cream:#0f2250; --st-panel:#1f3566;
  --st-straw:#ff9cc2; --st-straw-deep:#ffb8d3;
  --st-rose:#2f4f8f; --st-rose-soft:#26427d;
  --st-ink:#e6efff; --st-text-soft:#9fb6e6;
  --st-gold:#f2c14e; --st-gold-soft:#4a3f1c;
  --pc-ink:#e6efff; --pc-paper:#26427d; --pc-panel:#1f3566;
  --pc-pink:#ff9cc2; --pc-pink-deep:#ffb8d3; --pc-pink-soft:#2f4f8f; --pc-pink-soft2:#26427d;
  --pc-cream:#4a3f1c; --pc-text-soft:#9fb6e6;
  --ss-edge:#2f4f8f; --ss-drop:rgba(0,8,30,.45); --ss-btn:#ff9cc2; --ss-btn-deep:#c9678f; --ss-blue:#7fa8f5;
  --ss-ground:linear-gradient(180deg,#0f2250 0,#173674 45%,#24509a 100%);
  --ss-ontext:#f2f7ff; --ss-onshadow:0 2px 0 rgba(0,8,30,.5);
}

/* ---- ground: blue water + a few white stars (fixed, so they sit still while scrolling) ---- */
html,:root[data-theme="dark"] html{background:var(--st-cream);}
body,:root[data-theme="dark"] body{
    background-color:var(--st-cream);
    background-image:
      url("/stickers/star.svg"),url("/stickers/star.svg"),url("/stickers/star.svg"),
      url("/stickers/star.svg"),url("/stickers/star.svg"),url("/stickers/star.svg"),
      var(--ss-ground);
    background-position:5% 140px,93% 90px,90% 46%,4% 58%,95% 82%,8% 90%,0 0;
    background-size:22px,16px,20px,14px,18px,16px,100% 100%;
    background-repeat:no-repeat;
    background-attachment:fixed;
  }

/* ---- no awning ---- */
#ambientSoundBtn + .gingham{display:none;}

/* ---- nav: white sticker pills; one scrollable row on phones ---- */
.nav-pill,:root[data-theme="dark"] .nav-pill{
    background:var(--st-panel); color:var(--st-ink);
    border:3px solid var(--ss-edge); box-shadow:0 3px 0 var(--ss-drop);
  }
.nav-pill:hover,:root[data-theme="dark"] .nav-pill:hover{box-shadow:0 5px 0 var(--ss-drop);}
.nav-pill.active,:root[data-theme="dark"] .nav-pill.active{
    background:var(--ss-btn); color:#fff; border:3px solid var(--ss-edge);
    box-shadow:0 3px 0 var(--ss-btn-deep);
  }
:root[data-theme="dark"] .nav-pill.active{color:#0f2250;}
@media (max-width:600px){
  /* the row stops short of the fixed theme/sound buttons (margin, not padding) so pills scroll
     under nothing */
  .site-nav{flex-wrap:nowrap; justify-content:flex-start; overflow-x:auto; scrollbar-width:none;
    padding:14px 8px 8px 2px; margin-right:52px; gap:8px; -webkit-overflow-scrolling:touch;}
  .site-nav::-webkit-scrollbar{display:none;}
  .site-nav .nav-pill{flex:none;}
}
.theme-toggle,:root[data-theme="dark"] .theme-toggle{
    background:var(--st-panel); border:3px solid var(--ss-edge); box-shadow:0 3px 0 var(--ss-drop); color:var(--st-ink);
  }

/* ---- text that sits straight on the water goes white ---- */
#headerTitle,.header-side h2,#subHeader,.title-block p.sub,.rm-label,
#rankTitle,#rankSubtitle,#eventPageTitle,#eventPageSubtitle,#pastEventsTitle,.past-empty,
#bottlePageTitle,#bottlePageSubtitle,.bottle-empty,#playlistPageTitle,#playlistPageSubtitle,.pl-empty,
#aqPageTitle,#aqPageSubtitle,.aq-empty,#aqCatalogTitle,.aq-catalog-hint,
#teacupTitle,#teacupSubtitle,#teacupResult,#teacupFooter,#teacupUpdated,.tc-list > .tc-note,
:root[data-theme="dark"] #teacupTitle,:root[data-theme="dark"] #teacupSubtitle,:root[data-theme="dark"] #teacupResult,
#siteFooter,#rankFooter,#eventFooter,#bottleFooter,#playlistFooter,#aqFooter,
:root[data-theme="dark"] #headerTitle,:root[data-theme="dark"] .header-side h2,:root[data-theme="dark"] #subHeader,:root[data-theme="dark"] .rm-label,
:root[data-theme="dark"] #rankTitle,:root[data-theme="dark"] #rankSubtitle,:root[data-theme="dark"] #eventPageTitle,:root[data-theme="dark"] #eventPageSubtitle,
:root[data-theme="dark"] #pastEventsTitle,:root[data-theme="dark"] #bottlePageTitle,:root[data-theme="dark"] #bottlePageSubtitle,
:root[data-theme="dark"] #playlistPageTitle,:root[data-theme="dark"] #playlistPageSubtitle,:root[data-theme="dark"] #aqPageTitle,:root[data-theme="dark"] #aqPageSubtitle,
:root[data-theme="dark"] #aqCatalogTitle,:root[data-theme="dark"] .aq-catalog-hint{
    color:var(--ss-ontext) !important; -webkit-text-fill-color:var(--ss-ontext);
    background:none; -webkit-text-stroke:0; text-shadow:var(--ss-onshadow);
  }

/* ---- header chips on the water: white stickers ---- */
.rate-pill,.header-side .header-shortcuts .header-shortcut-pill,
:root[data-theme="dark"] .rate-pill,:root[data-theme="dark"] .header-side .header-shortcuts .header-shortcut-pill{
    background:var(--st-panel); color:var(--st-ink); border:3px solid var(--ss-edge); box-shadow:0 3px 0 var(--ss-drop);
  }
.rate-pill,:root[data-theme="dark"] .rate-pill{color:var(--st-straw);}
.header-side .header-shortcuts .header-shortcut-pill:hover{background:var(--st-rose-soft); box-shadow:0 5px 0 var(--ss-drop);}

/* ---- polaroid = sticker photo with a turtle buddy ---- */
.avatar,:root[data-theme="dark"] .avatar{
    background:#fff; box-shadow:0 0 0 4px #fff, 0 9px 0 4px var(--ss-drop);
  }
.avatar::before{
    content:""; top:-26px; right:-34px; width:78px; height:64px;
    background:url("/stickers/turtle.svg") center/contain no-repeat;
    transform:rotate(8deg); filter:drop-shadow(0 3px 0 rgba(40,90,170,.2));
  }
.avatar::after{color:#e46a98;}

/* ---- every big card: white sticker edge + flat blue drop ---- */
.cta-panel,.progress-card,.bottom-grid-2 .panel,.rewards,
.cur-event-card,.past-card,.tc-card,.tc-view,.aq-search-card,.aq-tank,.bottle-submit-card,.bottle-modal,.rank-row,.pl-song,
:root[data-theme="dark"] .cta-panel,:root[data-theme="dark"] .progress-card,:root[data-theme="dark"] .bottom-grid-2 .panel,:root[data-theme="dark"] .rewards,
:root[data-theme="dark"] .cur-event-card,:root[data-theme="dark"] .past-card,:root[data-theme="dark"] .tc-card,:root[data-theme="dark"] .tc-view,
:root[data-theme="dark"] .aq-search-card,:root[data-theme="dark"] .aq-tank,:root[data-theme="dark"] .bottle-submit-card,:root[data-theme="dark"] .bottle-modal,
:root[data-theme="dark"] .rank-row,:root[data-theme="dark"] .pl-song{
    border-color:var(--ss-edge); box-shadow:0 5px 0 var(--ss-drop);
  }
.cta-panel,.progress-card,.bottom-grid-2 .panel,.cur-event-card,.aq-search-card,.bottle-submit-card,
:root[data-theme="dark"] .cta-panel,:root[data-theme="dark"] .progress-card,:root[data-theme="dark"] .bottom-grid-2 .panel,
:root[data-theme="dark"] .cur-event-card,:root[data-theme="dark"] .aq-search-card,:root[data-theme="dark"] .bottle-submit-card{
    border-width:4px; border-style:solid; border-radius:26px; box-shadow:0 6px 0 var(--ss-drop);
  }
.cta-panel,:root[data-theme="dark"] .cta-panel{background-image:none;}
.rewards,:root[data-theme="dark"] .rewards{border:4px solid var(--ss-edge); background:var(--st-panel); border-radius:26px;}
/* podium + top song keep a pink edge */
.rank-row.top1,.rank-row.top2,.rank-row.top3,.pl-song.pl-top,
:root[data-theme="dark"] .rank-row.top1,:root[data-theme="dark"] .rank-row.top2,:root[data-theme="dark"] .rank-row.top3,:root[data-theme="dark"] .pl-song.pl-top{
    border-color:var(--ss-btn); box-shadow:0 5px 0 var(--ss-btn-deep);
  }

/* ---- one face sticker per big card ---- */
.cta-panel,.progress-card,.rewards,.bottom-grid-2 .panel:first-child,.cur-event-card,.bottle-submit-card,.aq-search-card{position:relative;}
.cta-panel::after,.progress-card::after,.rewards::after,.bottom-grid-2 .panel:first-child::after,
.cur-event-card::after,.bottle-submit-card::after,.aq-search-card::after{
    content:""; position:absolute; z-index:2; pointer-events:none;
    background:center/contain no-repeat; filter:drop-shadow(0 3px 0 rgba(40,90,170,.18));
    animation:ssBob 3.4s ease-in-out infinite;
  }
.cta-panel::after,.cur-event-card::after{background-image:url("/stickers/turtle.svg"); width:86px; height:70px; top:-34px; right:22px;}
/* .cur-event-card clips its overflow, so its turtle sits inside the top-right corner instead */
.cur-event-card::after{top:12px; right:12px; width:64px; height:52px;}
/* sub-page headers had a white radial glow box behind the title — a hard-edged rectangle on the water */
.ph-header::before,:root[data-theme="dark"] .ph-header::before{display:none;}
.progress-card::after,.bottle-submit-card::after{background-image:url("/stickers/starfish.svg"); width:54px; height:54px; top:-24px; left:-14px; rotate:-12deg;}
.rewards::after,.aq-search-card::after{background-image:url("/stickers/jelly.svg"); width:44px; height:62px; top:-34px; right:-8px; animation-delay:-1.2s;}
.bottom-grid-2 .panel:first-child::after{background-image:url("/stickers/fish.svg"); width:58px; height:38px; bottom:-14px; right:-10px; animation-delay:-2s;}
@keyframes ssBob{50%{translate:0 -5px;}}
@media (max-width:600px){
  .cta-panel::after,.cur-event-card::after{width:64px; height:52px; top:-26px;}
  .progress-card::after,.bottle-submit-card::after{width:42px; height:42px;}
}

/* ---- buttons: pink sticker buttons ---- */
.support-btn,:root[data-theme="dark"] .support-btn{
    background:var(--ss-btn); color:#fff; border:3px solid #fff; box-shadow:0 5px 0 var(--ss-btn-deep);
  }
:root[data-theme="dark"] .support-btn{color:#0f2250; border-color:var(--ss-edge);}
.support-btn:hover{box-shadow:0 7px 0 var(--ss-btn-deep);}
.cta-panel .qr-strip{background:var(--ss-blue);}
.social-link,:root[data-theme="dark"] .social-link{color:var(--st-ink); background:var(--st-rose-soft); border-color:var(--st-rose-soft);}

/* ---- progress bars: pink candy stripes + a swimming fish ---- */
.progress-card .track,.cur-event-bar-track{background:var(--st-rose-soft); border:3px solid var(--st-rose);}
.progress-card .fill,.cur-event-bar-fill{background:repeating-linear-gradient(-45deg,#ffa9c8 0 8px,#ffc3d8 8px 16px);}
.progress-card .fill::after,.cur-event-bar-fill::after{
    content:""; width:40px; height:26px; right:-20px;
    background:url("/stickers/fish.svg") center/contain no-repeat;
  }
.near-goal-badge,.near-goal-badge::after{background:var(--ss-btn);}
.near-goal-badge{box-shadow:0 3px 0 var(--ss-btn-deep);}

/* ---- tier cards ---- */
.tier-card,:root[data-theme="dark"] .tier-card{border:3px solid var(--st-rose); box-shadow:0 4px 0 var(--st-rose);}
.tier-card.complete,:root[data-theme="dark"] .tier-card.complete{background:var(--st-rose-soft); border-color:var(--ss-blue); box-shadow:0 4px 0 var(--ss-blue);}
.tier-card.complete .badge{background:var(--ss-blue);}
.rm-dot,:root[data-theme="dark"] .rm-dot{border-color:#fff; background:var(--ss-btn); box-shadow:0 3px 0 var(--ss-drop);}
.rm-track::before{border-top-color:rgba(255,255,255,.8);}

@media (prefers-reduced-motion: reduce){
  .cta-panel::after,.progress-card::after,.rewards::after,.bottom-grid-2 .panel:first-child::after,
  .cur-event-card::after,.bottle-submit-card::after,.aq-search-card::after{animation:none;}
}

/* ---- Teacup: "teacup grid" full view + activity bottom sheet (2026-09-26, RWM-104) ----
   Built by tcApplyState()/tcCupSVG()/tcOpenSheet() in render.js. */
.tc-stat-emoji img{width:34px; height:auto; display:block; margin:0 auto 2px; filter:drop-shadow(0 2px 0 rgba(40,90,170,.18));}
.tc-list.cups{display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:14px;}
@media (max-width:600px){ .tc-list.cups{grid-template-columns:1fr 1fr; gap:10px;} }
.tc-cup,:root[data-theme="dark"] .tc-cup{
    position:relative; display:flex; flex-direction:column; align-items:center; gap:2px; min-width:0;
    padding:12px 8px 12px; border:4px solid var(--ss-edge); border-radius:24px;
    background:var(--st-panel); color:var(--st-ink); box-shadow:0 5px 0 var(--ss-drop);
    font:inherit; cursor:pointer; transition:transform .15s;
  }
.tc-cup:hover{transform:translateY(-3px);}
.tc-cup:active{transform:translateY(2px);}
.tc-cup:focus-visible{outline:3px solid var(--ss-btn); outline-offset:2px;}
.tc-cup-art{width:88px; height:auto; display:block;}
.tc-cup-name{display:flex; flex-direction:column; align-items:center; gap:2px; max-width:100%; font-weight:700; font-size:1.05rem; line-height:1.15;}
.tc-cup-name-text{max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.tc-cup-name .name-emoji-group{max-width:100%; justify-content:center;}
.tc-cup-count{font-size:.9rem; color:var(--st-text-soft);}
.tc-cup-count b{font-size:1.3rem; color:var(--st-straw);}
.tc-cup-medal{position:absolute; top:8px; left:8px; width:30px; height:30px; border-radius:50%; display:grid; place-items:center;
    font-weight:800; font-size:.95rem; color:#fff; box-shadow:0 0 0 3px #fff;}
.tc-cup-medal.m1{background:#ffd36b; text-shadow:0 1px 0 #c98f1d;}
.tc-cup-medal.m2{background:#b9c6dc; text-shadow:0 1px 0 #7d8ca8;}
.tc-cup-medal.m3{background:#f3b98a; text-shadow:0 1px 0 #b87a4a;}

.tc-sheet-wrap{position:fixed; inset:0; z-index:80; display:none;}
.tc-sheet-wrap.show{display:block;}
.tc-sheet-backdrop{position:absolute; inset:0; background:rgba(20,50,110,.4);}
.tc-sheet{
    position:absolute; left:50%; bottom:0; transform:translateX(-50%); width:min(560px,100%); max-height:78vh; overflow-y:auto;
    background:var(--st-panel); color:var(--st-ink); border-radius:28px 28px 0 0;
    padding:18px 18px calc(22px + env(safe-area-inset-bottom,0px)); box-shadow:0 -8px 30px rgba(20,50,110,.3);
    animation:tcSheetIn .25s ease-out;
  }
@keyframes tcSheetIn{from{transform:translate(-50%,40px); opacity:0;}}
.tc-sheet-close{position:absolute; top:12px; right:14px; width:36px; height:36px; border-radius:50%; border:0;
    background:var(--st-rose-soft); color:var(--st-ink); font-size:1rem; cursor:pointer;}
.tc-sheet-head{display:flex; align-items:center; gap:12px; padding-right:40px;}
.tc-sheet-head .tc-cup-art{width:70px; flex:none;}
.tc-sheet-head h3{margin:0; font-size:1.4rem; line-height:1.15; color:var(--st-ink); overflow-wrap:anywhere;}
.tc-sheet-head p{margin:2px 0 0; color:var(--st-text-soft);}
.tc-sheet-head p b{color:var(--st-straw); font-size:1.2rem;}
.tc-sheet-body h4{margin:14px 0 6px; font-size:1rem; color:var(--st-text-soft);}
.tc-sheet-body .tc-chips{display:flex; flex-wrap:wrap; gap:6px;}
.tc-sheet-body .tc-chip{border:0; background:var(--st-rose-soft); color:var(--st-ink);}
.tc-sheet-body .tc-chip.miss{background:transparent; box-shadow:inset 0 0 0 2px var(--st-rose); color:var(--st-text-soft);}
.tc-sheet-body .tc-chip.on{background:var(--ss-btn); color:#fff;}
.tc-sheet-actions{display:flex; flex-wrap:wrap; gap:8px; margin-top:16px;}
.tc-sheet-actions .tc-cat-share-btn{margin-top:0;}
.tc-card-share-btn{display:inline-flex; align-items:center; gap:6px; padding:7px 14px; border:0; border-radius:999px; cursor:pointer;
    background:var(--ss-blue); color:#fff; font:inherit; font-weight:700; font-size:.82rem; box-shadow:0 3px 0 #3f6fd0;}
.tc-card-share-btn .svgicon{width:1em; height:1em;}
html.tc-sheet-open,html.tc-sheet-open body{overflow:hidden;}
@media (prefers-reduced-motion: reduce){ .tc-sheet{animation:none;} .tc-cup{transition:none;} }

/* ---- Ranking: one sticker sheet (2026-09-26, RWM-105) — markup from renderRanking() ---- */
.rank-list.rk-sheet{
    display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:22px 10px; max-width:640px; margin:0 auto;
    padding:22px 14px 20px; border:4px solid var(--ss-edge); border-radius:26px; box-shadow:0 6px 0 var(--ss-drop);
    background:radial-gradient(circle,rgba(91,143,240,.12) 1.5px,transparent 2px) 0 0/14px 14px,var(--st-panel);
  }
.rk-stk{position:relative; display:flex; flex-direction:column; align-items:center; gap:3px; min-width:0; text-align:center;}
.rk-stk.top1{grid-column:1/-1; margin-bottom:4px;}
.rk-pic{position:relative;}
.rk-av{display:block; width:84px; height:84px; border-radius:50%; object-fit:cover; background:var(--st-rose-soft);
    box-shadow:0 0 0 5px #fff,0 0 0 7px var(--st-rose),0 6px 0 7px var(--ss-drop);}
.rk-av-empty{display:grid; place-items:center;} .rk-av-empty img{width:70%; height:auto;}
.top1 .rk-av{width:128px; height:128px; box-shadow:0 0 0 6px #fff,0 0 0 10px #ffd36b,0 7px 0 10px rgba(200,150,40,.28);}
.top2 .rk-av{box-shadow:0 0 0 5px #fff,0 0 0 8px #b9c6dc,0 6px 0 8px var(--ss-drop);}
.top3 .rk-av{box-shadow:0 0 0 5px #fff,0 0 0 8px #f3b98a,0 6px 0 8px var(--ss-drop);}
.rk-no{position:absolute; top:-6px; right:-8px; width:28px; height:28px; border-radius:50%; display:grid; place-items:center;
    background:var(--ss-blue); color:#fff; font-weight:800; font-size:.9rem; box-shadow:0 0 0 3px #fff;}
.top1 .rk-no{width:38px; height:38px; font-size:1.15rem; background:#ffc94d; text-shadow:0 1px 0 #c98f1d; top:-4px; right:-4px;}
.top2 .rk-no{background:#b9c6dc;} .top3 .rk-no{background:#f3b98a;}
.rk-name{display:inline-block; max-width:100%; margin-top:10px; padding:2px 10px 0; rotate:-2deg; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    background:repeating-linear-gradient(45deg,#ffd0e0 0 7px,#ffe6ef 7px 14px); color:#7a2e52; font-weight:700; font-size:.95rem;}
.top1 .rk-name{font-size:1.25rem; padding:3px 18px 0;}
.rk-score{font-weight:800; color:var(--st-straw); font-size:1rem; line-height:1.1;}
.top1 .rk-score{font-size:1.5rem;}
.rk-meta{display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:4px;}
.rk-meta .rank-move{position:static; margin:0;}
.rk-cats{display:inline-flex !important; align-items:center; gap:1px; max-width:none !important; padding:1px 7px 1px 4px; border-radius:999px;
    background:var(--st-rose-soft); cursor:pointer; flex-wrap:nowrap !important;}
.rk-cats .name-emoji{width:18px !important; height:18px !important; margin:0 !important;}
.rk-cats b{margin-left:3px; font-size:.78rem; color:var(--st-straw);}
.rk-share,:root[data-theme="dark"] .rk-share{
    align-self:center; margin:2px auto 0; padding:2px 12px 0; border:0; border-radius:999px; font:inherit; font-size:.8rem; font-weight:700;
    background:var(--ss-btn); color:#fff; box-shadow:0 3px 0 var(--ss-btn-deep); cursor:pointer;
  }
:root[data-theme="dark"] .rk-av{box-shadow:0 0 0 5px #2c4c8c,0 0 0 7px var(--st-rose),0 6px 0 7px var(--ss-drop);}
:root[data-theme="dark"] .rk-name{color:#7a2e52;}
@media (max-width:420px){
  .rank-list.rk-sheet{gap:18px 4px; padding:18px 8px;}
  .rk-av{width:72px; height:72px;} .top1 .rk-av{width:112px; height:112px;}
  .rk-name{font-size:.85rem; padding:2px 7px 0;}
}

/* ---- Event: life-ring progress + compact stats (RWM-105) — ring built by eventLifeRing() ---- */
.cur-event-progress.ring-mode .cur-event-bar-track,.cur-event-progress.ring-mode .cur-event-pct{display:none;}
.cur-event-stats.has-ring{grid-template-columns:150px 1fr; grid-template-rows:auto auto auto; gap:8px 14px; align-items:center;}
.cur-event-stats.has-ring .cur-event-ring{grid-row:1/4; position:relative; width:150px;}
.cur-event-ring svg{display:block; width:100%; height:auto; filter:drop-shadow(0 4px 0 rgba(40,90,170,.18));}
.cur-event-ring .cer-arc{transition:stroke-dasharray 1.2s ease-out;}
.cer-pct{position:absolute; inset:0; display:grid; place-items:center; font-weight:800; font-size:1.7rem; color:var(--st-straw);}
.cur-event-stats.has-ring .stat,:root[data-theme="dark"] .cur-event-stats.has-ring .stat{
    padding:6px 12px 4px; text-align:left; border:0; box-shadow:none; background:var(--st-rose-soft); color:var(--st-ink);
  }
.cur-event-stats.has-ring .stat.hero,:root[data-theme="dark"] .cur-event-stats.has-ring .stat.hero{background:var(--ss-btn); color:#fff; box-shadow:0 4px 0 var(--ss-btn-deep);}
.cur-event-stats.has-ring .stat .stat-label{font-size:.85rem;}
.cur-event-stats.has-ring .stat .stat-val{font-size:1.2rem;}
.cur-event-stats.has-ring .stat.hero .stat-val{font-size:1.6rem;}
@media (max-width:420px){
  .cur-event-stats.has-ring{grid-template-columns:120px 1fr; gap:6px 10px;}
  .cur-event-stats.has-ring .cur-event-ring{width:120px;}
  .cer-pct{font-size:1.4rem;}
}

/* ---- Event: past events = 3-column polaroid album (RWM-105) ---- */
.past-grid.past-album{grid-template-columns:repeat(3,minmax(0,1fr)); gap:14px;}
.past-card.past-pol,:root[data-theme="dark"] .past-card.past-pol{
    background:#fff; border:0; border-radius:8px; padding:7px 7px 9px; box-shadow:0 5px 0 var(--ss-drop); overflow:visible;
  }
.past-card.past-pol:nth-child(odd){rotate:-1.5deg;} .past-card.past-pol:nth-child(even){rotate:1.5deg;}
.past-pol .past-card-img{aspect-ratio:1; border-radius:4px; overflow:hidden; background:#eaf4ff;}
.past-pol .past-card-img img{width:100%; height:100%; object-fit:cover; display:block;}
.past-pol .past-card-img img.past-pol-empty{object-fit:contain; padding:18%;}
.past-pol .past-card-body{padding:6px 2px 0;}
.past-pol .past-card-title{font-size:.9rem; line-height:1.2; color:#34508f; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;}
.past-pol .past-card-amount{font-size:.85rem; color:#e46a98; font-weight:700;}
.past-pol .past-card-note{font-size:.72rem; color:#6f86b8;}
.past-grid.past-album:not(.show-all) .past-more{display:none;}
.past-more-btn{display:block; margin:16px auto 0; padding:5px 22px 3px; border:3px solid var(--ss-edge); border-radius:999px; font:inherit; font-weight:700;
    background:var(--st-panel); color:var(--st-ink); box-shadow:0 3px 0 var(--ss-drop); cursor:pointer;}
@media (max-width:420px){ .past-grid.past-album{gap:10px 8px;} .past-pol .past-card-title{font-size:.8rem;} }

/* ==== RWM-108 (2026-09-27): time-of-day sea, waving turtle, cork pop, rank hop, tier islands ==== */
/* time of day (class set by applyTimeOfDay() in render.js; dark theme untouched). With no saved
   theme choice, index.html's boot script already picks dark for 19:00-05:59, so .tod-night only
   shows for someone who chose light at night. */
:root.tod-evening:not([data-theme="dark"]){
  --st-cream:#ff9fb8;
  --ss-ground:linear-gradient(180deg,#ff97b5 0,#ffb4a2 32%,#ffd09e 58%,#a9d4fb 100%);
  --ss-onshadow:0 2px 0 rgba(196,84,120,.45);
}
:root.tod-night:not([data-theme="dark"]){
  --st-cream:#0f2250;
  --ss-ground:linear-gradient(180deg,#0f2250 0,#173674 45%,#24509a 100%);
  --ss-onshadow:0 2px 0 rgba(0,8,30,.5);
}

/* Micha's polaroid: tap → turtle waves, heart + message pop */
.avatar{cursor:pointer;}
.avatar::before{transform-origin:30% 90%;}
.avatar.wave::before{animation:avWave .8s ease;}
@keyframes avWave{0%,100%{transform:rotate(8deg);} 25%{transform:rotate(-14deg) translateY(-6px);} 50%{transform:rotate(14deg);} 75%{transform:rotate(-8deg) translateY(-3px);}}
.avatar-msg{position:absolute; left:50%; top:-14px; z-index:5; white-space:nowrap; transform:translate(-50%,-100%);
  background:#fff; color:#e46a98; font-weight:700; font-size:1rem; padding:3px 14px 1px; border-radius:14px;
  box-shadow:0 0 0 3px #ffd0e1,0 4px 0 3px rgba(40,90,170,.18); animation:avMsg 1.8s ease forwards; pointer-events:none;}
@keyframes avMsg{0%{opacity:0; transform:translate(-50%,-80%) scale(.6);} 15%{opacity:1; transform:translate(-50%,-100%) scale(1.05);} 80%{opacity:1;} 100%{opacity:0; transform:translate(-50%,-120%);}}
.avatar-heart{position:absolute; right:10px; top:40%; z-index:5; color:#ff8fb8; font-size:2rem; text-shadow:0 0 0 #fff,0 2px 0 #fff;
  animation:avHeart 1.2s ease-out forwards; pointer-events:none;}
@keyframes avHeart{from{opacity:1; transform:scale(.5);} to{opacity:0; transform:translateY(-90px) scale(1.3);}}

/* Bottle: cork pops with a "ป๊อก!" before the note opens; the note is old paper */
.bottle-item{position:relative;}
.bottle-item.popping .bottle-art{animation:bottleShake .45s ease;}
@keyframes bottleShake{25%{transform:rotate(-12deg);} 50%{transform:rotate(10deg) translateY(-6px);} 75%{transform:rotate(-6deg);}}
.bottle-cork{position:absolute; left:50%; top:4px; pointer-events:none;}
.bottle-cork i{position:absolute; left:-8px; top:0; width:16px; height:14px; border-radius:4px; background:#c99a6a; box-shadow:inset 0 -3px 0 #a87a4c, 0 0 0 2px #fff;
  animation:corkFly .9s cubic-bezier(.2,.7,.4,1) forwards;}
.bottle-cork b{position:absolute; left:10px; top:-6px; white-space:nowrap; font-size:1rem; color:#fff; text-shadow:0 2px 0 #e46a98;
  animation:corkTxt .9s ease-out forwards;}
@keyframes corkFly{to{transform:translate(46px,-110px) rotate(400deg); opacity:0;}}
@keyframes corkTxt{0%{opacity:0; transform:scale(.4);} 25%{opacity:1; transform:scale(1.2) translateY(-10px);} 100%{opacity:0; transform:translateY(-44px);}}
.bottle-modal,:root[data-theme="dark"] .bottle-modal{
  background:repeating-linear-gradient(180deg,transparent 0 31px,rgba(200,160,110,.18) 31px 32px),linear-gradient(160deg,#fffaf0,#fbeed6);
  color:#6b4e2e; rotate:-1deg;
}
.bottle-modal #bottleMessageText{color:#5a4028;}
.bottle-modal #bottleMessageFrom{color:#8a6a48;}

/* Ranking: moved-up stickers hop in, newcomers get a NEW! tag */
.rk-stk.rk-up .rk-pic{animation:rkHop .55s ease .4s 2;}
@keyframes rkHop{50%{transform:translateY(-14px) scale(1.04,.96);}}
.rk-stk.rk-up .rk-pic::after{content:'✦'; position:absolute; left:-10px; top:6px; color:#ffd36b; font-size:1.3rem; text-shadow:0 1px 0 #fff;
  animation:rkSpark 1.4s ease .4s both;}
@keyframes rkSpark{0%{opacity:0; transform:scale(.3);} 40%{opacity:1; transform:scale(1.3);} 100%{opacity:0; transform:scale(.8) translateY(-10px);}}
.rk-newtag{position:absolute; left:-10px; bottom:2px; rotate:-12deg; background:#ffd36b; color:#7a5a10; font-weight:800; font-size:.75rem;
  padding:1px 7px 0; border-radius:6px; box-shadow:0 0 0 2px #fff,0 3px 0 2px rgba(40,90,170,.18); animation:rkNew .5s cubic-bezier(.3,1.6,.5,1) .5s both;}
@keyframes rkNew{from{opacity:0; transform:scale(2.2);}}

/* Home: Tier roadmap as islands on the sea (the tier cards restyled; admin emoji stays the creature) */
.roadmap .rm-track,.roadmap .rm-labels{display:none;}
.roadmap .tiers{position:relative; padding:18px 6px 10px; border-radius:26px;
  background:radial-gradient(circle,rgba(255,255,255,.35) 1.5px,transparent 2.5px) 0 0/22px 22px,linear-gradient(180deg,#8fc5f9,#5aa3f0);
  box-shadow:0 0 0 4px var(--ss-edge),0 6px 0 4px var(--ss-drop);}
:root[data-theme="dark"] .roadmap .tiers{background:radial-gradient(circle,rgba(255,255,255,.18) 1.5px,transparent 2.5px) 0 0/22px 22px,linear-gradient(180deg,#24509a,#173674);}
.roadmap .tier-card,:root[data-theme="dark"] .roadmap .tier-card,
.roadmap .tier-card.active,:root[data-theme="dark"] .roadmap .tier-card.active,
.roadmap .tier-card.complete,:root[data-theme="dark"] .roadmap .tier-card.complete{
  position:relative; background:none; border:0; box-shadow:none; padding:4px 2px 8px; overflow:visible;
  display:flex; flex-direction:column; align-items:center;
}
/* markup order is badge → icon → info; islands read icon (on its island) → badge → info */
.roadmap .tier-card .icon{order:1;} .roadmap .tier-card .badge{order:2;} .roadmap .tier-card > :not(.icon):not(.badge){order:3;}
.roadmap .tier-card:nth-child(even){translate:0 16px;}
.roadmap .tier-card .icon{position:relative; z-index:1; margin:0 0 -14px; font-size:2.6rem; line-height:1; filter:drop-shadow(0 3px 0 rgba(40,90,170,.2));}
.roadmap .tier-card .icon::after{content:''; display:block; width:84px; max-width:100%; height:30px; margin:-6px auto 0; border-radius:50%;
  background:radial-gradient(ellipse at 50% 30%,#fff3d6,#f3dcae); box-shadow:0 0 0 3px #fff,0 4px 0 3px rgba(40,90,170,.2); position:relative; z-index:-1;}
.roadmap .tier-card .badge,.roadmap .tier-card.active .badge,.roadmap .tier-card.complete .badge{
  position:relative; z-index:2; margin-top:18px; background:#fff; color:#34508f; box-shadow:0 3px 0 rgba(40,90,170,.2);}
.roadmap .tier-card .baht,.roadmap .tier-card .range,.roadmap .tier-card .tier-complete{color:#fff; text-shadow:0 1px 0 rgba(40,90,170,.5);}
.roadmap .tier-card.complete::before{content:'🚩'; position:absolute; top:-6px; right:18%; font-size:1.3rem; z-index:3;
  font-family:'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif;}
.roadmap .tier-card.active .badge{background:#ff8fb8; color:#fff; box-shadow:0 3px 0 #e46a98;}
.roadmap .tier-card.active::after{content:''; position:absolute; right:-4px; top:26px; width:46px; height:38px; z-index:3;
  background:url("/stickers/turtle.svg") center/contain no-repeat; filter:drop-shadow(0 3px 0 rgba(40,90,170,.2)); animation:ssBob 3s ease-in-out infinite;}
.roadmap .tier-card:not(.active):not(.complete) .icon{filter:grayscale(.6) opacity(.7) drop-shadow(0 3px 0 rgba(40,90,170,.2));}

@media (prefers-reduced-motion: reduce){
  .avatar.wave::before,.avatar-msg,.avatar-heart,.bottle-cork i,.bottle-cork b,.rk-stk.rk-up .rk-pic,.rk-newtag,.roadmap .tier-card.active::after{animation:none;}
}

/* ==== RWM-110 (2026-09-28): thank-you wall "ทะเลดาว" under the Ranking — markup from renderThanksWall() ==== */
.tw-wall{max-width:640px; margin:28px auto 0; padding:18px 14px 16px; display:flex; flex-direction:column; gap:12px;
  border:4px solid var(--ss-edge); border-radius:26px; box-shadow:0 6px 0 var(--ss-drop); background:var(--st-panel); color:var(--st-ink);}
.tw-wall[hidden]{display:none;}
.tw-head{text-align:center;}
.tw-title,:root[data-theme="dark"] .tw-title{margin:0; font-size:var(--fs-twTitle,1.45rem); font-weight:700; color:var(--st-ink); text-shadow:none;}
.tw-sub{margin:2px 0 0; font-size:.95rem; color:var(--st-text-soft);}
.tw-sub b{color:var(--st-straw);}
.tw-find{display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:4px 10px;}
.tw-find input{font:inherit; font-size:1rem; width:min(280px,100%); padding:7px 16px 5px; border:3px solid var(--st-rose); border-radius:999px;
  background:var(--st-rose-soft); color:var(--st-ink);}
.tw-find input:focus-visible{outline:3px solid var(--ss-btn); outline-offset:2px;}
.tw-hint{font-size:.85rem; color:var(--st-text-soft);}
.tw-hint:empty{display:none;}
.tw-sea{position:relative; max-height:440px; overflow:auto; display:flex; flex-wrap:wrap; gap:8px; align-content:flex-start; justify-content:center;
  padding:16px 12px; border-radius:18px;
  background:radial-gradient(circle,rgba(255,255,255,.35) 1.5px,transparent 2.5px) 0 0/22px 22px,linear-gradient(180deg,#8fc5f9,#5aa3f0);}
:root[data-theme="dark"] .tw-sea{background:radial-gradient(circle,rgba(255,255,255,.18) 1.5px,transparent 2.5px) 0 0/22px 22px,linear-gradient(180deg,#24509a,#173674);}
.tw-star{display:inline-flex; align-items:center; gap:5px; max-width:100%; overflow-wrap:anywhere; padding:3px 12px 1px 8px; border-radius:999px;
  background:#fff; color:#34508f; font-weight:600; font-size:var(--fs-twName,.95rem); box-shadow:0 3px 0 rgba(20,60,140,.3);
  animation:twBob 3.6s ease-in-out infinite; animation-delay:var(--d);}
.tw-star::before{content:'★'; color:#f2b544; font-size:.85em;}
@keyframes twBob{50%{transform:translateY(-3px);}}
.tw-star.hit{position:relative; z-index:1; background:#ff8fb8; color:#fff; animation:none; transform:scale(1.15);
  box-shadow:0 0 0 3px #fff,0 0 16px 5px rgba(255,255,255,.85);}
.tw-star.hit::before{color:#fff;}
.tw-cta{display:flex; align-items:center; justify-content:space-between; gap:8px 12px; padding:10px 12px 10px 16px; border-radius:18px;
  background:var(--st-rose-soft); font-size:.95rem; line-height:1.4;}
.tw-cta-btn,:root[data-theme="dark"] .tw-cta-btn{flex:none; padding:5px 16px 3px; border-radius:999px; background:var(--ss-btn); color:#fff; font-weight:700;
  text-decoration:none; box-shadow:0 3px 0 var(--ss-btn-deep);}
@media (max-width:420px){ .tw-cta{flex-direction:column; text-align:center;} }
@media (prefers-reduced-motion: reduce){ .tw-star{animation:none;} }
