#click-ready-screen{position:fixed;inset:0;z-index:10000000;background:#000;display:flex;align-items:center;justify-content:center;opacity:1;transition:opacity 0.5s ease;cursor:pointer;}
#click-ready-inner{display:flex;flex-direction:column;align-items:center;gap:20px;}
#click-ready-text{font-size:28px;font-weight:700;color:#fff;font-family:sans-serif;letter-spacing:0.08em;animation:click-ready-pulse 1.8s ease-in-out infinite;}
@keyframes click-ready-pulse{0%,100%{opacity:1;transform:scale(1);}50%{opacity:0.55;transform:scale(1.08);}}
#splash-screen{position:fixed;inset:0;z-index:9999999;background:#000;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity 0.6s ease;}
#splash-inner{display:flex;flex-direction:column;align-items:center;gap:20px;}
#splash-title{font-size:31px;font-weight:700;color:#8ed8a0;font-family:sans-serif;letter-spacing:0.12em;text-transform:uppercase;}
/* ── Intro screen polish (#13) ─────────────────────────────────────────────
   Each animation only runs when intro.js stamps a `.intro-cinematic` class
   on the inner wrapper, which fires the moment that screen becomes visible
   so timing matches its audio cue. */
#splash-inner.intro-cinematic svg { animation: introLogoPop 900ms cubic-bezier(0.34,1.6,0.5,1) both, copeFaceBounce 1.2s ease-in-out 900ms infinite; transform-origin: center; }
@keyframes copeFaceBounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-12px); }
}
#splash-inner.intro-cinematic #splash-title { animation: introTitleSpread 1100ms ease-out 200ms both; }
/* Slow 15% grow across the screen's visible duration (~2.8s) — replaces the
   old ring pulse as the prolonged effect. Container scales so SVG and title
   grow together; entry pop on the SVG compounds cleanly with the parent. */
#splash-inner.intro-cinematic { animation: introScreenGrow 2800ms ease-out forwards; transform-origin: center; }
@keyframes introLogoPop {
  0%   { transform: scale(0.32); opacity: 0; }
  60%  { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}
@keyframes introTitleSpread {
  0%   { letter-spacing: -0.04em; opacity: 0; }
  100% { letter-spacing: 0.12em;  opacity: 1; }
}
@keyframes introScreenGrow {
  0%   { transform: scale(1); }
  100% { transform: scale(1.15); }
}
#splash-inner { position: relative; }
#publisher-screen{position:fixed;inset:0;z-index:9999998;background:#000;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity 0.6s ease;}
#publisher-inner{display:flex;flex-direction:column;align-items:center;gap:0;}
/* Baby-blue rounded rectangle around the face. Padding is asymmetric so the
   smiley sits off-centre toward the right edge of the frame; horizontal
   padding is reduced ~60% so the rectangle is 40% less long than before. */
#publisher-face{font-size:72px;color:#fff;font-family:monospace;letter-spacing:0.05em;user-select:none;text-shadow:0 0 18px rgba(190,210,255,0.35),0 0 40px rgba(120,160,255,0.18);display:inline-block;border:2px solid #3d7ea8;border-radius:14px;background:#3d7ea8;padding:12px 14px 18px 32px;line-height:1;margin-top:15px;}
#publisher-tagline{margin-top:18px;font-size:20px;font-weight:700;color:#9ec8e8;font-family:sans-serif;letter-spacing:0.1em;text-transform:uppercase;}
/* Eyes: a fixed-width inline box holding two absolutely-positioned dot
   spans. Splitting the colon into independent elements is what lets a
   single eye wink while the other stays open. */
#publisher-eyes{display:inline-block;position:relative;width:0.6ch;height:1em;vertical-align:top;}
.pub-eye{position:absolute;left:50%;width:0.18em;height:0.18em;background:currentColor;border-radius:50%;transform:translateX(-50%);box-shadow:0 0 12px rgba(190,210,255,0.4),0 0 24px rgba(120,160,255,0.22);transition:width 0.06s ease,height 0.06s ease,border-radius 0.06s ease;}
.pub-eye-top{top:0.30em;}
.pub-eye-bot{top:0.62em;}
/* Wink: dot collapses to a thin horizontal line at the same spot. */
.pub-eye.winking{width:0.34em;height:0.05em;border-radius:0.04em;}
/* Slow 15% grow across the publisher screen's visible duration (~6.5s).
   Replaces publisherBreath so total scale stays within the 15% target. */
#publisher-inner.intro-cinematic { animation: introScreenGrow 6500ms ease-out forwards; transform-origin: center; }
#publisher-inner.intro-cinematic #publisher-face { animation: publisherGlow 3.2s ease-in-out infinite; }
@keyframes publisherGlow {
  0%, 100% { text-shadow: 0 0 18px rgba(190,210,255,0.35), 0 0 40px rgba(120,160,255,0.18); }
  50%      { text-shadow: 0 0 24px rgba(210,230,255,0.55), 0 0 56px rgba(140,180,255,0.32); }
}
#loading-screen{position:fixed;inset:0;z-index:999999;background:#1a0a0f;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity 0.4s ease;}
#shader-screen{position:fixed;inset:0;z-index:999999;background:#1a0a0f;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity 0.4s ease;}
#loading-screen::after,#shader-screen::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:-1;background:radial-gradient(ellipse at center,transparent 35%,rgba(0,0,0,0.55) 75%,rgba(0,0,0,0.9) 100%);}
#shader-inner{display:flex;flex-direction:column;align-items:center;gap:14px;}
#shader-title{font-size:32px;font-weight:700;color:#f0c0d0;font-family:sans-serif;letter-spacing:0.06em;}
#shader-logo-canvas{animation:shaderEggBounce 0.9s ease-in-out infinite alternate;}
@keyframes shaderEggBounce{0%{transform:translateY(0);}100%{transform:translateY(-10px);}}
#shader-inner.intro-cinematic #shader-title {
  background: linear-gradient(90deg,#f0c0d0 0%,#ffe6f4 30%,#c0a0ff 50%,#ffe6f4 70%,#f0c0d0 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: shaderTitleSweep 3.2s linear infinite;
}
@keyframes shaderTitleSweep {
  0%   { background-position: 100% 50%; }
  100% { background-position: -120% 50%; }
}
#shader-bar-wrap{width:260px;height:10px;background:#2a1020;border-radius:6px;overflow:hidden;border:1px solid #4a2035;}
#shader-bar-fill{height:100%;width:0%;background:linear-gradient(to right,#8040c0,#e060c0);border-radius:6px;transition:width 0.1s linear;}
#loading-inner{display:flex;flex-direction:column;align-items:center;gap:14px;}
#loading-logo{font-size:72px;animation:loadingPulse 1s ease-in-out infinite;}
@keyframes loadingPulse{0%,100%{transform:scale(1);}50%{transform:scale(1.12);}}
#loading-title{font-size:32px;font-weight:700;color:#f0c0d0;font-family:sans-serif;letter-spacing:0.06em;}
#loading-msg{font-size:17px;font-weight:700;color:#8a5070;font-family:sans-serif;min-width:260px;text-align:center;}
#loading-bar-wrap{width:260px;height:10px;background:#2a1020;border-radius:6px;overflow:hidden;border:1px solid #4a2035;}
#loading-bar-fill{height:100%;width:0%;background:linear-gradient(to right,#8040c0,#e060c0);border-radius:6px;transition:width 0.1s linear;}
#loading-pct,#shader-pct{font-size:15px;font-weight:700;color:#5a3050;font-family:sans-serif;}
html,body{background:#0d0507;overflow:hidden;cursor:none;}
/* Global default font: replace the browser fallback (Times New Roman on
   Windows) with Calibri-bold, 10% larger than the 16px UA default. Only
   affects elements that do NOT set their own font-family / font-weight /
   font-size — explicit overrides (e.g. Georgia in casino games, the various
   modals with declared px sizes) still win via normal CSS cascade. */
html,body{font-family:Calibri,"Calibri Light","Segoe UI",Arial,sans-serif;font-weight:700;font-size:18px;}
#scanline-overlay{--sl-gap:2.5px;--sl-line-a:3.25px;--sl-line-b:3.75px;--sl-end:5px;--sl-alpha:0.081;position:fixed;inset:0;pointer-events:none;z-index:10000003;background:repeating-linear-gradient(to bottom,transparent 0px,transparent var(--sl-gap),rgba(0,0,0,var(--sl-alpha)) var(--sl-line-a),rgba(0,0,0,var(--sl-alpha)) var(--sl-line-b),transparent var(--sl-end));}
@media (min-height:900px){#scanline-overlay{--sl-gap:3.5px;--sl-line-a:5px;--sl-line-b:5px;--sl-end:6px;}}
@media (min-height:1400px){#scanline-overlay{--sl-gap:5px;--sl-line-a:7px;--sl-line-b:8px;--sl-end:9px;--sl-alpha:0.099;}}
*{cursor:none!important;}
body.system-cursor,body.system-cursor *{cursor:auto!important;}
/* Scrollbar theming — 50% thicker than the previous 8px, plus explicit
   chevron buttons (30% larger than the 8px-bar default). Cursor is forced
   to none across every scrollbar pseudo-element so the OS pointer doesn't
   reassert during hover or drag — the emoji cursor (#global-cursor)
   stays in place via JS. */
::-webkit-scrollbar{width:12px;height:12px;cursor:none!important;}
::-webkit-scrollbar-track{background:#1a0a0f;border-radius:4px;cursor:none!important;}
::-webkit-scrollbar-thumb{background:#5a2040;border-radius:4px;border:1px solid #3a1525;cursor:none!important;}
::-webkit-scrollbar-thumb:hover{background:#8a3060;}
::-webkit-scrollbar-corner{background:#1a0a0f;cursor:none!important;}
/* Chevron buttons at the ends of the scrollbar — vertical track gets up/down
   triangles, horizontal track gets left/right. Triangle SVG is sized so it
   reads ~30% larger than the un-themed default. */
::-webkit-scrollbar-button{background:#1a0a0f;background-repeat:no-repeat;background-position:center;cursor:none!important;}
::-webkit-scrollbar-button:vertical:single-button:start{height:18px;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><polygon points='6,3 10,9 2,9' fill='%238a3060'/></svg>");}
::-webkit-scrollbar-button:vertical:single-button:end{height:18px;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><polygon points='2,3 10,3 6,9' fill='%238a3060'/></svg>");}
::-webkit-scrollbar-button:horizontal:single-button:start{width:18px;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><polygon points='3,6 9,2 9,10' fill='%238a3060'/></svg>");}
::-webkit-scrollbar-button:horizontal:single-button:end{width:18px;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><polygon points='3,2 9,6 3,10' fill='%238a3060'/></svg>");}
::-webkit-scrollbar-button:hover{background-color:#2a1218;}
*{scrollbar-width:auto;scrollbar-color:#5a2040 #1a0a0f;}
/* PERF: position now lives entirely in `transform: translate3d(...)` written
   by JS each rAF tick — left/top stay at 0,0 forever. `will-change:transform`
   keeps the cursor on its own composited layer so every per-frame move is
   pure compositor work (no glyph paint). The initial transform sits at
   off-screen (-9999) so the cursor doesn't flash at 0,0 before the first
   mousemove writes its real position. */
#global-cursor{position:fixed;pointer-events:none;z-index:2000001;font-size:28px;line-height:1;transform:translate3d(-9999px,-9999px,0) translate(-14px,0px) scale(var(--cursor-pulse,1));top:0;left:0;will-change:transform;}
/* Mobile/touch-only: hide the floating cursor emoji until the user's first
   touch. squeeze.js adds .mobile-touch-ui on detection and .mobile-touch-seen
   after the first touch, so the cursor shows up right where the player tapped
   (the element is pointer-events:none, so the tap still activates whatever
   is underneath). */
body.mobile-touch-ui #global-cursor{display:none!important;}
body.mobile-touch-ui.mobile-touch-seen #global-cursor{display:block!important;}
*{box-sizing:border-box;margin:0;padding:0;}
#root{width:100%;height:100vh;display:flex;flex-direction:column;background:#1a0a0f;color:#f0e0e8;font-family:'Exo 2',Calibri,sans-serif;position:relative;}
#banner{width:100%;background:linear-gradient(to right,#1e0a13 0%,#3a1525 50%,#1e0a13 100%);padding:0 20px;height:49px;display:flex;align-items:center;gap:10px;flex-shrink:0;overflow:hidden;box-shadow:0 2px 14px rgba(0,0,0,0.45);position:relative;z-index:51;}
#banner-left{display:flex;align-items:center;gap:6px;flex:1;min-width:0;overflow-x:clip;overflow-y:visible;}
/* Dropped bTitleSweatPulse — animating `filter: drop-shadow()` forces a
   repaint of the title + shadow region every frame. Static filter instead;
   the pulse was subtle and not load-bearing. */
.b-title{font-size:18px;font-weight:700;background:transparent;white-space:nowrap;margin-right:4px;overflow:hidden;text-overflow:ellipsis;min-width:0;filter:var(--cyl-emoji-filter,hue-rotate(120deg) saturate(1.4)) drop-shadow(0 0 12px var(--cyl-glow-color,rgba(235,75,175,0.9))) drop-shadow(0 0 26px var(--cyl-glow-color-soft,rgba(235,75,175,0.55)));}
@keyframes bTitleSweatPulse{
  0%,100%{filter:hue-rotate(120deg) saturate(1.35) drop-shadow(0 0 10px rgba(220,60,160,0.85)) drop-shadow(0 0 22px rgba(220,60,160,0.5));transform:scale(1);}
  50%{filter:hue-rotate(120deg) saturate(1.5) drop-shadow(0 0 16px rgba(255,90,190,1)) drop-shadow(0 0 32px rgba(255,90,190,0.65));transform:scale(1.08);}
}
.b-currency{display:inline-flex;align-items:center;gap:5px;background:#3a1525;border:0.5px solid #6a3050;border-radius:20px;padding:3px 10px;white-space:nowrap;min-width:0;box-shadow:inset 0 1px 2px rgba(0,0,0,0.4),inset 0 -1px 1px rgba(255,160,200,0.06);height:24px;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;}
.b-currency span:first-child{font-size:17px;flex-shrink:0;}
.b-val{font-size:14px;font-weight:700;color:#f0d0dc;overflow:hidden;text-overflow:ellipsis;min-width:0;}
.b-lbl{font-size:12px;font-weight:700;color:#c090a8;margin-left:1px;overflow:hidden;text-overflow:ellipsis;min-width:0;}
#banner-right{display:flex;align-items:center;gap:6px;flex-shrink:0;}
#radio-controls{display:flex;align-items:center;gap:4px;flex-shrink:0;margin-right:2px;}
.radio-btn{background:#3a1525;border:0.5px solid #6a3050;border-radius:6px;width:26px;height:26px;display:flex;align-items:center;justify-content:center;font-size:10px;color:#f0c0d0;cursor:pointer;transition:background 0.15s;padding:0;line-height:1;}
.radio-btn:hover{background:#6a2545;}
#radio-track-name{font-size:13px;font-weight:700;color:#c090a8;white-space:nowrap;max-width:100px;overflow:hidden;text-overflow:ellipsis;margin-left:4px;}
#radio-wave{flex-shrink:0;margin-left:4px;display:block;}
#radio-wave .rw-bar{fill:#c090a8;transform-origin:10px 7px;transform-box:view-box;animation:radioWave 1.1s ease-in-out infinite;}
#radio-wave .rw-b1{transform-origin:2px 7px;animation-delay:0s;}
#radio-wave .rw-b2{transform-origin:6px 7px;animation-delay:0.14s;}
#radio-wave .rw-b3{transform-origin:10px 7px;animation-delay:0.28s;}
#radio-wave .rw-b4{transform-origin:14px 7px;animation-delay:0.21s;}
#radio-wave .rw-b5{transform-origin:18px 7px;animation-delay:0.07s;}
#radio-wave.radio-wave-paused .rw-bar{animation-play-state:paused;}
@keyframes radioWave{0%,100%{transform:scaleY(0.35);}50%{transform:scaleY(1);}}
#radio-vol-slider{-webkit-appearance:none;appearance:none;width:52px;height:3px;border-radius:2px;background:#6a3050;outline:none;cursor:pointer;margin-left:5px;vertical-align:middle;flex-shrink:0;}
#radio-vol-slider::-webkit-slider-thumb{-webkit-appearance:none;width:9px;height:9px;border-radius:50%;background:#f0c0d0;cursor:pointer;}
#radio-vol-slider::-moz-range-thumb{width:9px;height:9px;border-radius:50%;background:#f0c0d0;cursor:pointer;border:none;}
/* PERF: dropped box-shadow + filter from the transition list. The button has
   ~30 instances on screen; sweeping the cursor across them used to fire 30
   overlapping 200ms shadow-paint animations. Snapping the hover state (no
   transition on shadow) drops that to one paint per actual enter/leave. */
.b-btn{background:linear-gradient(135deg,#4a1a30,#250c18);border:1.5px solid #8a3060;border-radius:6px;padding:2.8px 10px 5px 10px;font-size:14px;font-weight:700;color:#f0c0d0;cursor:pointer;white-space:nowrap;letter-spacing:0.02em;box-shadow:0 0 8px 2px rgba(160,60,100,0.28),0 0 16px 4px rgba(160,60,100,0.13);transition:background 0.15s,border-color 0.15s,color 0.15s,transform 0.15s;}
.b-btn:hover{background:linear-gradient(135deg,#6a2545,#380f22);transform:translateY(-1px);box-shadow:0 0 12px 3px rgba(200,80,130,0.45),0 0 24px 6px rgba(160,60,100,0.25);}
.b-btn:active{transform:translateY(0);filter:brightness(0.96);}
.b-btn.vip{background:linear-gradient(135deg,#3a1560,#1a0830);border-color:#7a50c0;color:#d0b0f0;}
.b-btn.vip:hover{background:linear-gradient(135deg,#5a25a0,#2e0f55);}
.b-btn.vip.active-vip{background:linear-gradient(135deg,#5a30a0,#2e0f55);border-color:#c0a0ff;color:#f0e0ff;}
/* VIP glow now handled by the static, non-pulsing rule in ui_polish.css. */
.b-btn.shop-btn{background:linear-gradient(135deg,#7a5800,#c08c10);border:1.5px solid #f0c030;color:#fff0a0;}
.b-btn.shop-btn:hover{background:linear-gradient(135deg,#9a7000,#e0aa18);}
.b-btn.convert-btn{background:linear-gradient(135deg,#102a5a,#081428);border-color:#3060c0;color:#90c0ff;}
.b-btn.convert-btn:hover{background:linear-gradient(135deg,#1a3a7a,#0c1e3c);}
.b-btn.convert-btn{margin-right:50px;}
.b-btn{position:relative;}
.notif-dot{position:absolute;top:0px;right:0px;width:8px;height:8px;background:#ff2020;border-radius:50%;animation:pulseDot 1.2s ease-in-out infinite;pointer-events:none;z-index:3;}
@keyframes pulseDot{0%,100%{opacity:1;transform:scale(1);box-shadow:0 0 3px 1px rgba(255,32,32,0.4),0 0 6px 2px rgba(255,32,32,0.2);}50%{opacity:0.8;transform:scale(1.4);box-shadow:0 0 5px 2px rgba(255,32,32,0.7),0 0 10px 4px rgba(255,60,60,0.35);}}
#daily-btn .notif-dot,#plinko-btn .notif-dot,#lootbox-btn .notif-dot{top:0;right:0;left:0;bottom:0;width:auto;height:auto;background:transparent;border-radius:inherit;border:1.5px solid #ff2020;box-sizing:border-box;animation:pulseInsetBorder 1.2s ease-in-out infinite;}
@keyframes pulseInsetBorder{0%,100%{border-color:rgba(255,32,32,0.55);box-shadow:inset 0 0 4px 1px rgba(255,32,32,0.35);}50%{border-color:rgba(255,90,90,1);box-shadow:inset 0 0 9px 2px rgba(255,80,80,0.7);}}
/* Desktop layout: 5-col × 2-row grid. The two 12px tracks (cols 2 and 4) are
   where column_resize.js's dividers live — they replace the old flex `gap`
   that used to sit between the columns. Row 1 sizes to the minigame bar's
   own height (collapses to 0 when the bar is .ui-locked / display:none);
   row 2 fills the rest. #left-col spans both rows so it reaches up under
   the banner, while #minigame-bar spans only the middle column, the M↔R
   divider, and the right column. Mobile reverts to flex-column via
   mobile.css. */
#main{display:grid;grid-template-columns:minmax(0,37fr) 14px minmax(0,22fr) 14px minmax(0,41fr);grid-template-rows:auto auto 1fr;flex:1;min-height:0;gap:0;padding:12px;background:#110509;overflow:hidden;}
/* Dividers live in row 2 only — the minigame-bar in row 1 owns its full
   width above the cols, and a divider on top of the bar would block clicks
   on the bar's buttons (divider z-index:6 > bar z-index:2). */
#col-divider-lm{grid-column:2;grid-row:3;align-self:stretch;}
#col-divider-mr{grid-column:4;grid-row:3;align-self:stretch;}
#main:has(>#mid-col.ui-locked) >#col-divider-lm,
#main:has(>#right-col.ui-locked) >#col-divider-mr{display:none;}
/* While both unlocked-later columns are still .ui-locked, collapse their
   grid tracks (and the divider tracks) to 0 so #left-col stretches across
   the full width. As each column unlocks the matching :has() rule stops
   matching and the default `37fr 14px 22fr 14px 41fr` template returns. */
#main:has(>#right-col.ui-locked){grid-template-columns:minmax(0,37fr) 14px minmax(0,22fr) 0 0;}
#main:has(>#mid-col.ui-locked):has(>#right-col.ui-locked){grid-template-columns:1fr 0 0 0 0;}
#left-col{grid-column:1;grid-row:2 / span 2;flex:4 1 0;min-width:20%;display:flex;flex-direction:column;align-items:center;background:radial-gradient(ellipse at 50% 65%,#2a0d1e 0%,#1a0a0f 70%);position:relative;overflow:hidden;border-radius:14px;border:1px solid #3a1a2c;box-shadow:0 6px 24px rgba(0,0,0,0.5),inset 0 1px 0 rgba(255,180,220,0.06),inset 0 0 0 1px rgba(255,160,200,0.025);transform:translateZ(0);contain:layout paint;isolation:isolate;}
/* Once the prestige bar unlocks (sits flush against the left-col's top-right
   corner), drop that one corner back to a square edge and remove the top/right
   borders that would otherwise cut a seam between them. */
#main:has(>#prestige-bar-wrap:not(.ui-locked)) >#left-col{border-top-right-radius:0;border-right:none;}
#left-col::before{content:'';position:absolute;left:50%;top:62%;width:240px;height:360px;transform:translate(-50%,-50%);background:radial-gradient(ellipse at center,rgba(192,80,168,0.2) 0%,rgba(140,30,160,0.09) 45%,transparent 72%);border-radius:50%;pointer-events:none;z-index:0;animation:cylAmbientPulse 3.5s ease-in-out infinite;}
@keyframes cylAmbientPulse{0%,100%{opacity:0.7;transform:translate(-50%,-50%) scale(1);}50%{opacity:1;transform:translate(-50%,-50%) scale(1.14);}}
#prestige-bar-wrap{grid-column:1 / span 5;grid-row:1;width:100%;height:15px;background:#2a1020;flex-shrink:0;cursor:pointer;border-radius:14px 14px 0 0;overflow:hidden;position:relative;z-index:4;box-shadow:inset 0 -1px 0 rgba(200,100,160,0.18);}
#prestige-bar-fill{height:100%;position:relative;background:linear-gradient(135deg,#a8d8ff,#e0f0ff,#c0e8ff,#80c8ff,#a8d8ff);width:0%;transition:width 0.4s ease;border-radius:0 3px 3px 0;box-shadow:0 0 8px rgba(168,216,255,0.7),0 0 18px rgba(120,180,240,0.4);overflow:hidden;}
#prestige-bar-fill::before{content:'';position:absolute;top:-40%;bottom:-40%;left:-70%;width:55%;background:linear-gradient(90deg,transparent 0%,transparent 20%,rgba(255,255,255,0.55) 50%,transparent 80%,transparent 100%);animation:prestigeBarSheen 5s ease-in-out infinite;pointer-events:none;transform:skewX(-22deg);}
@keyframes prestigeBarSheen{0%{left:-70%;opacity:0;}8%{opacity:1;}55%{left:130%;opacity:1;}55.01%,100%{left:130%;opacity:0;}}
#prestige-info{width:100%;padding:11px 14px 0;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;flex-shrink:0;position:relative;z-index:51;}
/* Spendable prestige skill points — sits under the Prestige Level line, pulses
   to draw the eye, and is hidden entirely (via inline display:none toggled in
   updatePrestigeBar) when the player has 0 unspent points. */
#prestige-skill-points{margin-top:2px;font-size:19px;font-weight:700;cursor:pointer;letter-spacing:0.01em;background:linear-gradient(135deg,#a8d8ff,#e0f0ff,#c0e8ff,#80c8ff,#a8d8ff);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;text-shadow:0 0 10px rgba(168,216,255,0.4);transform-origin:left center;animation:skillPointsPulse 1.6s ease-in-out infinite;}
@keyframes skillPointsPulse{0%,100%{opacity:0.8;transform:scale(1);}50%{opacity:1;transform:scale(1.06);}}
#fps-display{width:100%;padding:2px 14px 0;font-size:17px;color:#8060a0;opacity:0.75;flex-shrink:0;text-align:left;}
/* PERF: filter:brightness on hover promoted a layer each time. Swapped to a
   plain color change — paint-only, no compositor churn. The .prestige-text
   child uses background-clip:text so its color comes from the gradient; the
   parent color shift still tints adjacent non-gradient glyphs. */
#prestige-label{font-size:19px;font-weight:700;color:#c090a8;cursor:pointer;transition:color 0.15s;}
#prestige-label .prestige-text{background:linear-gradient(135deg,#a8d8ff,#e0f0ff,#c0e8ff,#80c8ff,#a8d8ff);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;text-shadow:0 0 10px rgba(168,216,255,0.4);}
#prestige-label:hover{color:#e8c0d8;}
/* Dropped starGlow — drop-shadow filter pulse, static glow is fine. */
.prestige-star{display:inline-block;filter:hue-rotate(165deg) saturate(0.85) brightness(1.45) drop-shadow(0 0 3px #a8d8ff) drop-shadow(0 0 8px rgba(168,216,255,0.5));}
@keyframes starGlow{0%,100%{filter:hue-rotate(165deg) saturate(0.85) brightness(1.45) drop-shadow(0 0 2px #a8d8ff) drop-shadow(0 0 5px rgba(168,216,255,0.4))}50%{filter:hue-rotate(165deg) saturate(0.85) brightness(1.45) drop-shadow(0 0 5px #a8d8ff) drop-shadow(0 0 12px rgba(168,216,255,0.6))}}
/* Bottom padding kept modest now that #left-stats is anchored absolutely
   to the bottom of #left-col below — the wrap no longer needs to reserve
   space for the stats line in its flex flow. */
#left-game-wrap{flex:1;display:flex;flex-direction:column;align-items:center;padding:16px 0 8px;}
/* Whole-UI responsive scaling — zooms #root so every column, the banner,
   modals and the game-area shrink together on narrow / short windows
   (phones, split-screen, small laptops). Takes the stricter of width vs
   height so nothing is ever clipped, and replaces the older
   #left-game-wrap-only height zoom which left the banner & side columns
   overflowing on mobile. */
:root{--w-zoom:1;--h-zoom:1;}
@media (max-width: 1280px) { :root { --w-zoom: 0.93; } }
@media (max-width: 1180px) { :root { --w-zoom: 0.86; } }
@media (max-width: 1080px) { :root { --w-zoom: 0.79; } }
@media (max-width: 980px)  { :root { --w-zoom: 0.72; } }
@media (max-width: 880px)  { :root { --w-zoom: 0.65; } }
@media (max-width: 780px)  { :root { --w-zoom: 0.58; } }
@media (max-width: 680px)  { :root { --w-zoom: 0.51; } }
@media (max-width: 580px)  { :root { --w-zoom: 0.44; } }
@media (max-width: 480px)  { :root { --w-zoom: 0.37; } }
@media (max-width: 400px)  { :root { --w-zoom: 0.32; } }
@media (max-width: 340px)  { :root { --w-zoom: 0.28; } }
@media (max-height: 1060px) { :root { --h-zoom: 0.93; } }
@media (max-height: 980px)  { :root { --h-zoom: 0.86; } }
@media (max-height: 900px)  { :root { --h-zoom: 0.79; } }
@media (max-height: 820px)  { :root { --h-zoom: 0.72; } }
@media (max-height: 740px)  { :root { --h-zoom: 0.65; } }
@media (max-height: 660px)  { :root { --h-zoom: 0.58; } }
@media (max-height: 580px)  { :root { --h-zoom: 0.51; } }
@media (max-height: 500px)  { :root { --h-zoom: 0.44; } }
@media (max-height: 420px)  { :root { --h-zoom: 0.37; } }
@media (max-height: 360px)  { :root { --h-zoom: 0.32; } }
@media (max-height: 300px)  { :root { --h-zoom: 0.28; } }
#root{--z:min(var(--w-zoom),var(--h-zoom));zoom:var(--z);width:calc(100vw / var(--z));height:calc(100vh / var(--z));}
/* Up-scale the cylinder + balls (and everything inside #game-area: the
   cylinder & kiwi shadows, hit-zone, start-zone, tutorial overlay) on roomy
   viewports. zoom (not transform) so layout reflows, #left-stats below stays
   in flow, and getBoundingClientRect()-driven squeeze hitboxes / particle
   origins follow the scaled rect automatically. Only kicks in when the
   downscale system above is at 1 — i.e. the screen is already big enough. */
:root{--game-zoom:1;}
@media (min-width:1500px) and (min-height:1100px){:root{--game-zoom:1.10;}}
@media (min-width:1700px) and (min-height:1200px){:root{--game-zoom:1.18;}}
@media (min-width:1900px) and (min-height:1300px){:root{--game-zoom:1.25;}}
#game-area{position:relative;width:220px;height:780px;user-select:none;touch-action:none;cursor:none;flex-shrink:0;overflow:visible;zoom:var(--game-zoom);}
#cylinder-wrap{position:absolute;left:50%;transform:translateX(-50%);top:330px;width:95px;height:386px;z-index:2;will-change:transform;}
/* Soft elliptical contact shadow under the cylinder, falling onto the kiwis.
   Lives on cylinder-wrap so it scales/translates with the cylinder during
   drag squashes. opacity ramps slightly with --arousal so the shadow gets
   denser as the cylinder reddens — feels like more pressure on the balls. */
/* Tall pill-shaped sun shadow — projects downward from the cylinder. Size:
   33% bigger than a half-height cylinder pill (95→126 wide, 193→257 tall).
   Top edge of the shadow at idle (scaleY 1) sits 18px below cylinder-wrap's
   bottom — same screen Y as the old short ellipse-shadow's top edge.
   Animates over 65s: 60s sun-arc (right→down→left, long→short→long — the
   shadow stretches at sunrise/sunset and shortens at noon) then 5s fade-out
   before instantly respawning at the start position. */
/* Sun shadow moved out of #cylinder-wrap (whose transform changes every frame
   during a squeeze, which was forcing the 11px blur filter to re-raster).
   Now lives as #game-area::after — a sibling that's never transformed during
   the squeeze — and the filter:blur(11px) is replaced with a pre-baked
   radial-gradient pill that approximates the original blurred shape.
   Box is 130x244 (vs original 92x206) to provide room for the soft falloff
   the blur used to add; all transform-origin Y values in cylSunShadow are
   offset +19px to compensate (the new box starts 19px higher). */
#game-area::after{content:"";position:absolute;left:50%;top:715px;width:130px;height:244px;pointer-events:none;z-index:0;background:radial-gradient(ellipse 50% 50% at 50% 50%,rgba(0,0,0,0.56) 0%,rgba(0,0,0,0.55) 40%,rgba(0,0,0,0.42) 70%,rgba(0,0,0,0.18) 88%,rgba(0,0,0,0) 100%);transform-origin:50% 65px;animation:cylSunShadow 320s linear infinite;}
/* 40s cycle, matched to the sun arc: rotation runs 5s-25s; opacity fades in
   over the first 10° of arc (15.28% of the cycle, ~1.1s) and fades out over
   the final 10° (~1.1s before the rotation ends), so the shadow appears /
   disappears as the sun crosses the horizon rather than holding at the
   endpoints. 10s invisible idle pads the loop to #sun's 40s arc. */
/* Full 180° arc using rotate (skewX cannot reach 90° — tan(90) is infinite).
   transform-origin is 50% 46px — the centre of the pill's rounded top tip
   (border-radius 46px) — so the pill pivots around the cap's centre rather
   than its very edge: at 90° it lays flat to the right, at -90° flat to the
   left, at 0° straight down. */
/* --game-fade is 0 until <body> gets .game-active (added by startGame), then
   transitions smoothly to 1. Multiplying every visible keyframe's opacity by
   it makes the sun + shadow effects fade in on game start instead of popping. */
@property --game-fade{syntax:"<number>";initial-value:0;inherits:true;}
:root{transition:--game-fade 1.5s ease-out;}
body.game-active{--game-fade:1;}
/* Ray length tracks the sun's arc position: short at start/end (when sun is
   near horizon), full at apex (37.5% of the 40s sunArc cycle). All four ray
   layers' masks reference --ray-tip and a calc'd mid stop. */
@property --ray-tip{syntax:"<percentage>";initial-value:50%;inherits:true;}
/* scaleY peaks at the horizon angles (1.7 — 70% longer at sunrise/sunset)
   and bottoms out at noon (0.7), interpolated linearly across the full arc.
   scaleX narrows to 0.3 (70% less wide) only inside the final 35° on either
   end of the arc — between user-angles 0°-35° and 145°-180° — and stays at
   1 across the rest of the sweep. The 22.22% / 52.78% stops are exactly
   where the linear rotation hits those 35° / 145° thresholds (12.5% +
   35°/180° · 50%). */
/* transform-origin shifts UP by 40px at the horizon angles (Y=6px instead
   of the static 46px cap-centre) and eases back to the cap centre at noon.
   Effect: at sunrise/sunset the shadow's pivot anchors well above the cap,
   making the long horizontal shadow read as if cast from higher on the
   cylinder; at noon the pivot returns to the cap centre so the short
   straight-down shadow stays anchored under the cylinder. Linear interp
   between angles: shiftPx = 40 · (1 − userAngle/90) on the way up, mirrored
   on the way down — pre-computed at every keyframe below. */
@keyframes cylSunShadow{
  0%       {transform-origin:50% 25px;     transform:translateX(-50%) rotate(90deg)  scaleX(0.3) scaleY(1.7);    opacity:0;}
  1.5625%  {transform-origin:50% 25px;     transform:translateX(-50%) rotate(90deg)  scaleX(0.3) scaleY(1.7);    opacity:0;}
  1.91%    {transform-origin:50% 29px;  transform:translateX(-50%) rotate(80deg)  scaleX(0.5) scaleY(1.5889); opacity:var(--game-fade,0);}
  16.45%   {transform-origin:50% 41px;  transform:translateX(-50%) rotate(55deg)  scaleX(1)   scaleY(1.3111); opacity:var(--game-fade,0);}
  48.44%   {transform-origin:50% 65px;     transform:translateX(-50%) rotate(0deg)   scaleX(1)   scaleY(0.7);    opacity:var(--game-fade,0);}
  80.42%   {transform-origin:50% 41px;  transform:translateX(-50%) rotate(-55deg) scaleX(1)   scaleY(1.3111); opacity:var(--game-fade,0);}
  94.97%   {transform-origin:50% 29px;  transform:translateX(-50%) rotate(-80deg) scaleX(0.5) scaleY(1.5889); opacity:var(--game-fade,0);}
  95.3125% {transform-origin:50% 25px;     transform:translateX(-50%) rotate(-90deg) scaleX(0.3) scaleY(1.7);    opacity:0;}
  100%     {transform-origin:50% 25px;     transform:translateX(-50%) rotate(-90deg) scaleX(0.3) scaleY(1.7);    opacity:0;}
}
/* Matching drop shadows under each kiwi — same gold tint layer + the same
   40s cycle as the cylinder shadow so all three breathe in sync with the sun. */
#kiwi-left-wrap::after,#kiwi-right-wrap::after{content:"";position:absolute;left:50%;bottom:-42px;transform:translateX(-50%);width:128px;height:27px;border-radius:50%;background-color:rgba(0,0,0,0.83);filter:blur(11px);pointer-events:none;z-index:0;animation:kiwiShadowBreathe 320s linear infinite;}
@keyframes kiwiShadowBreathe{
  0%       {opacity:0;}
  1.5625%  {opacity:0;}
  1.91%    {opacity:var(--game-fade,0);}
  94.97%   {opacity:var(--game-fade,0);}
  95.3125% {opacity:0;}
  100%     {opacity:0;}
}
/* ── Sun effect ─────────────────────────────────────────────────────────────
   Mostly off-screen above the viewport. Swings opposite to the cylinder's
   sun-shadow over the same 30s cycle: starts up-LEFT (when shadow leans
   down-right at +70°), passes overhead at midcycle, ends up-RIGHT (when
   shadow leans down-left at -70°). pointer-events:none + low alpha so it
   never interferes with input or readability. */
/* Lives inside #left-col (overflow:hidden naturally clips it). z-index:1 sits
   above the column's ::before ambient pulse but below all gameplay UI (z 2+).
   100% bigger than the original — 880x880 box with a 240x240 core. top:0
   parks the sun-core near mid-column by default so the arc keyframes mirror
   the shadow's rotation through the cylinder pivot. */
/* offset-path traces a real SVG semicircle (radius 680) instead of using
   keyframes to fake one. Path coords are relative to the element's static
   border-box top-left; the arc centre is at the element's centre (440, 440)
   in box-local px, so endpoints sit at (1120, 440) and (-240, 440) and the
   apex at (440, -240). sweep-flag 0 = upper semicircle in SVG y-down.
   offset-rotate:0 keeps the sun upright (default would rotate it along the
   tangent). offset-distance is animated 0% → 100% for smooth uniform sweep. */
#sun{position:absolute;top:440px;left:50%;width:880px;height:880px;margin-left:-440px;pointer-events:none;z-index:1;opacity:0;offset-path:path("M 1920 440 A 1480 1480 0 0 0 -1040 440");offset-rotate:0deg;transform:scale(3);transform-origin:center;--ray-tip:64%;animation:sunArc 320s linear infinite;}
/* Sun & Shadow option (Off): hide the rotating arc sun and both shadow
   pseudos. Kills the animations entirely so they don't burn cycles either. */
body.no-sun-shadow #sun{display:none !important;}
body.no-sun-shadow #game-area::after,
body.no-sun-shadow #kiwi-left-wrap::after,
body.no-sun-shadow #kiwi-right-wrap::after{display:none !important;animation:none !important;}
#sun .sun-core{position:absolute;inset:calc(50% - 0.5px);border-radius:50%;background:none;}
/* Multiple ray layers, each rotating around the sun centre at different
   rates / directions. Non-uniform keyframes simulate random speed-ups and
   slow-downs; pulse keyframes modulate opacity so the rays breathe. */
#sun .sun-rays{position:absolute;inset:0;pointer-events:none;will-change:transform,opacity;transform-origin:center;}
/* Collapsed from 4 ray layers to 2 and dropped the per-layer radial mask:
   each masked conic layer forced an extra GPU compositing pass every frame.
   The remaining two layers keep the spin+pulse; rays are faint enough (alpha
   ~0.04) to read fine without the mask. Layers 3 & 4 are hidden (kept in the
   DOM so markup is untouched). */
#sun .sun-rays-1{background:repeating-conic-gradient(from 0deg,rgba(255,210,90,0.048) 0deg 8deg,transparent 8deg 18deg);animation:sunRaysSpinA 38s linear infinite,sunRaysPulseA 6.3s ease-in-out infinite;}
#sun .sun-rays-2{background:repeating-conic-gradient(from 9deg,rgba(255,200,80,0.036) 0deg 6deg,transparent 6deg 24deg);animation:sunRaysSpinB 61s linear infinite,sunRaysPulseB 8.7s ease-in-out infinite;}
#sun .sun-rays-3,#sun .sun-rays-4{display:none;}
/* Sun arcs through the upper hemisphere of a circle of radius 680 around
   the rotation midpoint (the sun's default-position centre). Eleven evenly-
   spaced keyframes (every 18° of arc = 16.67%/10 = 6.67% of cycle) make the
   linear-interpolated polygon close enough to a smooth circle to read as
   one. Visible arc 16.67% → 83.33% (= 20s); 0–16.67% fade-in, 83.33–100%
   fade-out at the endpoints. */
@keyframes sunArc{
  0%       {offset-distance:0%;     opacity:0;}
  1.5625%  {offset-distance:0%;     opacity:0;}
  /* Near the horizon endpoints the rays are only half as opaque as at the
     apex (48.44% — offset-distance 50%, the arc's midpoint), so the sun reads
     hazier when low and brightest overhead. */
  1.91%    {offset-distance:5.56%;  opacity:calc(var(--game-fade,0) * 0.5);}
  48.44%   {offset-distance:50%;    opacity:var(--game-fade,0);}
  94.97%   {offset-distance:94.44%; opacity:calc(var(--game-fade,0) * 0.5);}
  95.3125% {offset-distance:100%;   opacity:0;}
  100%     {offset-distance:100%;   opacity:0;}
}
/* --ray-tip is the MID stop of the radial mask (where alpha drops to 0.18).
   Pinned to 64% on #sun so the alpha curve — and therefore the rays' visual
   profile — stays consistent across the entire arc cycle. */
/* Non-uniform rotation rates: keyframes with uneven angle/time deltas
   read as random speed-ups and slow-downs over the cycle. A/C are
   clockwise, B/D counter-clockwise, all four periods coprime so they
   never realign. Pulse animations modulate opacity asymmetrically. */
@keyframes sunRaysSpinA{
  0%{transform:rotate(0deg);}
  18%{transform:rotate(75deg);}
  37%{transform:rotate(110deg);}
  58%{transform:rotate(225deg);}
  82%{transform:rotate(295deg);}
  100%{transform:rotate(360deg);}
}
@keyframes sunRaysSpinB{
  0%{transform:rotate(0deg);}
  22%{transform:rotate(-55deg);}
  46%{transform:rotate(-180deg);}
  71%{transform:rotate(-245deg);}
  100%{transform:rotate(-360deg);}
}
@keyframes sunRaysSpinC{
  0%{transform:rotate(0deg);}
  12%{transform:rotate(25deg);}
  33%{transform:rotate(165deg);}
  61%{transform:rotate(220deg);}
  86%{transform:rotate(335deg);}
  100%{transform:rotate(360deg);}
}
@keyframes sunRaysSpinD{
  0%{transform:rotate(0deg);}
  27%{transform:rotate(-105deg);}
  52%{transform:rotate(-145deg);}
  78%{transform:rotate(-285deg);}
  100%{transform:rotate(-360deg);}
}
@keyframes sunRaysPulseA{0%,100%{opacity:0.7;}50%{opacity:1.1;}}
@keyframes sunRaysPulseB{0%,100%{opacity:0.55;}30%{opacity:1;}70%{opacity:0.65;}}
@keyframes sunRaysPulseC{0%,100%{opacity:0.85;}40%{opacity:0.45;}80%{opacity:1.05;}}
@keyframes sunRaysPulseD{0%,100%{opacity:0.6;}25%{opacity:1;}65%{opacity:0.5;}}
/* Default-skin gradient is driven by CSS vars so the drag hot path only has
   to write 5 vars (--c1..--c4, --bc) instead of rebuilding the gradient
   string + borderColor each frame. applySkin() clears inline background
   when switching to the default skin so this rule actually takes effect;
   for custom skins it sets an inline background that overrides this rule. */
/* Dropped cylGlowShift — a 5s infinite box-shadow animation on the game's
   central element forces a per-frame repaint of the shadow region every
   animation tick. Static shadow instead; the subtle color cycle was barely
   visible anyway. */
#cylinder{position:absolute;inset:0;border-radius:45px;border:2px solid var(--bc,#c080a8);transition:background 0.08s ease,border-color 0.08s ease;box-shadow:0 0 32px rgba(192,128,168,0.28),0 0 10px rgba(192,128,168,0.14),inset 0 0 36px rgba(0,0,0,0.32);background:linear-gradient(to right,var(--c1,#d4a0c0),var(--c2,#f5cce0),var(--c3,#e8b5d0),var(--c4,#c890b0));}
/* Stack (bottom→top): bg gradient · veins (z1) · arousal (z2) · shine/shine-r (z3) · tint (z4) · bulge (z5). */
#cylinder-shine{position:absolute;left:13px;top:22px;width:18px;height:78%;border-radius:10px;background:linear-gradient(to bottom,rgba(255,255,255,0.38) 0%,rgba(255,255,255,0.1) 60%,rgba(255,255,255,0.02) 100%);pointer-events:none;z-index:3;}
/* Right-edge rim light — mirrors #cylinder-shine at lower opacity so the
   cylinder reads as a 3D round shaft instead of a flat painted pill. */
#cylinder-shine-r{position:absolute;right:11px;top:30px;width:8px;height:74%;border-radius:6px;background:linear-gradient(to bottom,rgba(255,255,255,0.16) 0%,rgba(255,255,255,0.05) 70%,rgba(255,255,255,0) 100%);pointer-events:none;z-index:3;}
/* Vein/texture overlay — three faint gradients read as skin grain rather
   than smooth latex. mix-blend-mode:overlay so it reacts to the underlying
   skin color. */
#cylinder-veins{position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:1;mix-blend-mode:overlay;opacity:0.55;background:repeating-linear-gradient(82deg,rgba(255,255,255,0.06) 0px,rgba(0,0,0,0.05) 1px,transparent 2px,transparent 9px),repeating-linear-gradient(98deg,rgba(0,0,0,0.04) 0px,transparent 1px,transparent 14px),radial-gradient(ellipse at 30% 60%,rgba(255,180,200,0.08),transparent 60%);}
#cylinder-tint{position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:rgba(180,20,20,1);opacity:0;z-index:4;}
/* Arousal flush — slow rosy build-up driven by --arousal (0..1). Independent
   of the drag-flash tint so non-default skins don't fight it. */
#cylinder-arousal{position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:2;mix-blend-mode:soft-light;background:radial-gradient(ellipse at 50% 70%,rgba(255,80,110,0.85) 0%,rgba(220,40,80,0.55) 55%,rgba(160,20,60,0.25) 100%);opacity:calc(var(--arousal,0) * 0.75);transition:opacity 220ms linear;}
/* Tip differentiation — shifts hue toward magenta/red so the glans reads
   distinct from the shaft. Uses an inline mix() of the c2/c3 vars by
   layering a translucent rose gradient on top of the existing shaft gradient. */
#tip{display:none;position:absolute;left:50%;transform:translateX(-50%);width:54px;height:31px;top:331px;border-radius:27px 27px 0 0;border:2px solid var(--bc,#b07898);border-bottom:none;transition:background 0.08s ease,border-color 0.08s ease;z-index:2;box-shadow:0 -6px 20px rgba(192,128,168,0.22);background:linear-gradient(to bottom,rgba(220,80,110,0.42),rgba(220,80,110,0.12) 70%,transparent),linear-gradient(to right,var(--c1,#d4a0c0),var(--c2,#f5cce0),var(--c3,#e8b5d0));}
#hit-zone{position:absolute;left:50%;transform:translateX(-50%);width:132px;top:240px;height:523px;z-index:8;cursor:none;}
#start-zone{position:absolute;left:50%;transform:translateX(-50%);width:94px;height:140px;border-radius:45px 45px 0 0;top:270px;z-index:2;}
#kiwi-wrap{position:absolute;left:50%;transform:translateX(-50%);top:588px;display:flex;gap:4px;align-items:flex-end;justify-content:center;pointer-events:none;user-select:none;z-index:1;will-change:transform;}
#kiwi-left-wrap{position:relative;width:128px;height:128px;flex-shrink:0;pointer-events:auto;cursor:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'><text y='28' font-size='28'>☝️</text></svg>") 8 0, pointer;}
#kiwi-right-wrap{position:relative;width:128px;height:128px;flex-shrink:0;pointer-events:auto;cursor:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'><text y='28' font-size='28'>☝️</text></svg>") 8 0, pointer;}
#kiwi-left{width:128px;height:128px;display:block;transform:rotate(90deg);}
#kiwi-right{width:128px;height:128px;display:block;transform:rotate(-90deg);}
@keyframes kiwiBounce{0%{transform:translateY(0) scale(1);}20%{transform:translateY(-18px) scale(0.93,1.08);}45%{transform:translateY(0) scale(1.06,0.94);}65%{transform:translateY(-8px) scale(0.97,1.04);}80%{transform:translateY(0) scale(1.02,0.98);}100%{transform:translateY(0) scale(1);}}
#kiwi-left-wrap.kiwi-bounce,#kiwi-right-wrap.kiwi-bounce{animation:kiwiBounce 0.55s cubic-bezier(0.36,0.07,0.19,0.97) both;}
/* Idle sway — independent durations + offsets so L/R drift out of sync.
   Tiny amplitude so it reads as "settled" rather than nervous. Overridden
   by .kiwi-bounce / .kiwi-(left|right)-jiggle (those use !important). */
@keyframes kiwiSwayLeft{
  0%   {transform:translateY(0)    rotate(0deg);}
  50%  {transform:translateY(-1.6px) rotate(-0.9deg);}
  100% {transform:translateY(0)    rotate(0deg);}
}
@keyframes kiwiSwayRight{
  0%   {transform:translateY(0)    rotate(0deg);}
  50%  {transform:translateY(-1.4px) rotate(0.8deg);}
  100% {transform:translateY(0)    rotate(0deg);}
}
#kiwi-left-wrap{animation:kiwiSwayLeft 3.2s ease-in-out infinite;}
#kiwi-right-wrap{animation:kiwiSwayRight 2.7s ease-in-out -0.6s infinite;}
#hand-cursor{position:absolute;pointer-events:none;transform:translate(-50%,-50%) rotate(-90deg) scale(var(--cursor-pulse,1));display:none;font-size:61px;line-height:1;z-index:10;transition:font-size 0.08s,filter 0.08s;}
/* Hand-cursor glow — moved from stacked filter:drop-shadow() (which re-rasters
   the emoji every frame as the cursor shakes/scales during squeeze) to a
   ::before radial-gradient halo controlled by --hc-g1a/--hc-g2a alphas.
   Opacity-only paint, no blur kernel, compositor-friendly. */
@property --hc-g1a{syntax:'<number>';initial-value:0;inherits:true;}
@property --hc-g2a{syntax:'<number>';initial-value:0;inherits:true;}
#hand-cursor::before{content:"";position:absolute;left:50%;top:50%;width:1.35em;height:1.35em;transform:translate(-50%,-50%);pointer-events:none;z-index:-1;background:radial-gradient(circle at 50% 50%,rgba(255,200,80,var(--hc-g1a,0)) 0%,rgba(255,200,80,calc(var(--hc-g1a,0)*0.55)) 22%,rgba(255,200,80,0) 50%),radial-gradient(circle at 50% 50%,rgba(255,160,40,calc(var(--hc-g2a,0)*0.85)) 0%,rgba(255,160,40,0) 72%);}
#hand-cursor.gripping{font-size:127px;filter:brightness(var(--hc-bright,0.82)) saturate(var(--hc-sat,1.5));}
@keyframes hc-trigger-pulse-kf {
  0%,100% { --hc-g1a:0.40; --hc-g2a:0.25; }
  50%     { --hc-g1a:0.95; --hc-g2a:0.65; }
}
#hand-cursor.in-start-zone:not(.gripping){font-size:140px;}
/* One-shot "armed" pulse — fires when the player reaches the end of a
   down-stroke. Bigger scale + bright shiny flash, then settles back. */
@keyframes hc-armed-pulse-kf {
  0%   { transform: translate(-50%,-50%) rotate(-90deg) scale(1);    filter: none;            --hc-g1a:0;    --hc-g2a:0; }
  28%  { transform: translate(-50%,-50%) rotate(-90deg) scale(1.38); filter: brightness(1.7); --hc-g1a:0.95; --hc-g2a:0.75; }
  58%  { transform: translate(-50%,-50%) rotate(-90deg) scale(1.15); filter: brightness(1.3); --hc-g1a:0.55; --hc-g2a:0; }
  100% { transform: translate(-50%,-50%) rotate(-90deg) scale(1);    filter: none;            --hc-g1a:0;    --hc-g2a:0; }
}
#hand-cursor.hc-armed-pulse{animation:hc-armed-pulse-kf 520ms ease-out;}
/* PERF: drives --cursor-pulse (read by the JS-written transform string)
   instead of overwriting `transform` directly. The previous version clobbered
   the per-frame translate3d position written by JS, snapping the cursor
   to the origin during the pulse. */
@keyframes gc-armed-pulse-kf {
  0%   { --cursor-pulse: 1;    filter: none; }
  28%  { --cursor-pulse: 1.55; filter: brightness(1.7); }
  58%  { --cursor-pulse: 1.2;  filter: brightness(1.3); }
  100% { --cursor-pulse: 1;    filter: none; }
}
#global-cursor.hc-armed-pulse{animation:gc-armed-pulse-kf 520ms ease-out;}
@property --cursor-pulse{syntax:'<number>';initial-value:1;inherits:false;}
@keyframes cursor-btn-pulse-kf{0%,100%{--cursor-pulse:1;}50%{--cursor-pulse:1.1;}}
#global-cursor.btn-hover-pulse,#hand-cursor.btn-hover-pulse{animation:cursor-btn-pulse-kf 700ms ease-in-out infinite;}
.hand-tint{display:inline-block;filter:hue-rotate(290deg) saturate(0.54) brightness(0.94);}
.harem-tint{display:inline-block;filter:hue-rotate(280deg) saturate(0.55) brightness(1.02);}
#shelf-items-helper_hand .shelf-item,#shelf-items-double_fist .shelf-item,#shelf-items-mega .shelf-item,#shelf-items-godhand .shelf-item,#shelf-items-void .shelf-item{filter:hue-rotate(290deg) saturate(0.54) brightness(0.94);}
#ghost-hand{position:absolute;pointer-events:none;transform:translate(-50%,-50%) rotate(-90deg);font-size:135px;line-height:1;z-index:9;opacity:0;}
#stars-canvas{position:fixed;top:0;left:0;pointer-events:none;z-index:2;}
#droplets-canvas-wrap{position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:50;}
#droplets-canvas{display:block;}
#cosmetics-btn-wrap{position:absolute;top:8px;right:10px;z-index:55;display:flex;gap:6px;transition:top 0.3s ease;}
#cosmetics-btn-wrap.prestige-revealed{top:28px;}
/* Banner 2.0 stack — cash pill + $ Buy, sits below the Themes button on the
   right edge. Both right-aligned. Cash pill is 20% larger than .b-currency. */
#cash-buy-wrap{position:absolute;top:52px;right:10px;z-index:55;display:flex;flex-direction:row-reverse;align-items:center;gap:9px;transition:top 0.3s ease;}
#cash-buy-wrap.prestige-revealed{top:72px;}
#cash-buy-wrap #currency-bucks{height:32px;border-radius:24px;padding:3.96px 13px;gap:7px;}
#cash-buy-wrap #currency-bucks span:first-child{font-size:22px;transform:translateY(-4px);display:inline-block;}
#cash-buy-wrap #currency-bucks .b-val{font-size:19px;transform:translateY(-1px);display:inline-block;}
#cash-buy-wrap #currency-bucks .b-lbl{font-size:17px;color:#51b85f;transform:translateY(0);display:inline-block;}
#cash-buy-wrap #sell-btn{font-size:17px;padding:3.39px 12px 3.61px 12px;border-radius:8px;visibility:hidden;}
#cash-buy-wrap #currency-bucks{transform:translateY(4px);}
/* PERF: dropped box-shadow from transition — hover doesn't change the shadow
   anyway so transitioning it was wasted budget. */
#cosmetics-btn,#themes-btn{background:linear-gradient(135deg,rgba(160,90,220,0.45),rgba(80,40,150,0.32));border:0.5px solid #b080e8;border-radius:8px;padding:5px 18px 9px;font-size:17px;font-weight:700;color:#f0e0ff;cursor:pointer;transition:background 0.15s;box-shadow:inset 0 1px 0 rgba(255,255,255,0.18),inset 0 -1px 0 rgba(0,0,0,0.25),0 0 12px 3px rgba(160,90,220,0.55),0 0 24px 6px rgba(112,64,192,0.3);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);text-shadow:0 1px 2px rgba(0,0,0,0.45);position:relative;}
#cosmetics-btn:hover,#themes-btn:hover{background:linear-gradient(135deg,rgba(200,130,255,0.6),rgba(120,70,200,0.48));}
/* Tooltips for skins/themes buttons — handled by universal JS tooltip (js/tooltip.js) */
#cosmetics-btn[data-tooltip]::after,#themes-btn[data-tooltip]::after{display:none!important;}
#stroke-style-btn-wrap{position:static;display:flex;gap:6px;margin-top:4px;}
/* Mirror #cock-fight-btn's shape/size/font/glow but in a bright blue palette. */
/* PERF: dropped box-shadow from transition (banner button, hover triggers
   a paint-heavy dual-shadow swap). Snap. */
#drug-market-btn{background:linear-gradient(135deg,#1a4a22,#0a2410);border:1.5px solid #6acc6a;border-radius:8px;padding:6px 14px;font-size:14px;font-weight:700;color:#d8ffd0;cursor:pointer;letter-spacing:0.02em;white-space:nowrap;transition:background 0.15s,border-color 0.15s,color 0.15s;box-shadow:0 0 10px 2px rgba(80,200,80,0.5),0 0 20px 4px rgba(80,200,80,0.25);}
#drug-market-btn:hover{background:linear-gradient(135deg,#236830,#0e3018);border-color:#9aff9a;color:#f0ffe0;box-shadow:0 0 14px 3px rgba(120,220,120,0.65),0 0 28px 6px rgba(80,200,80,0.3);}
/* Stroke Style button — matches the Hired Cylinders button styling */
/* PERF: dropped box-shadow from transition. Snap. */
#stroke-style-btn{background:linear-gradient(135deg,#2a1040,#1a0828);border:1.5px solid #5a2080;border-radius:8px;color:#c090e0;font-size:14px;font-weight:700;padding:6px 14px;cursor:pointer;transition:background 0.15s,border-color 0.15s,color 0.15s;letter-spacing:0.02em;white-space:nowrap;box-shadow:0 0 10px 2px rgba(200,100,220,0.35),0 0 20px 4px rgba(200,100,220,0.18);}
#stroke-style-btn:hover{background:linear-gradient(135deg,#3a1860,#240a3c);border-color:#8040c0;color:#e0b0ff;box-shadow:0 0 14px 3px rgba(230,130,255,0.55),0 0 28px 6px rgba(200,100,220,0.3);}
/* Anchored to the bottom of #left-col (which is overflow:hidden) so the
   "X per second" line is always at least ~30 actual screen pixels above
   the viewport bottom regardless of how the cylinder above flows. The
   bottom calc divides by --z so the post-zoom value stays a constant 30
   actual pixels — without that, on heavily zoomed-down screens (e.g.
   z=0.4) a flat 30px would render as only 12 actual pixels and the text
   could still be clipped. left:0/right:0 with text-align:center centres
   horizontally without needing a transform that would create a stacking
   context conflict with the cylinder above. */
#left-stats{text-align:center;position:absolute;left:0;right:0;bottom:calc(70px / var(--z, 1));z-index:3;pointer-events:none;opacity:0.9;}
#left-stats > *{pointer-events:auto;}
#droplet-count-wrap{display:flex;align-items:baseline;justify-content:center;gap:12px;}
#droplet-count{font-size:42px;font-weight:700;color:#f0c0d0;}
#droplet-label{font-size:41px;font-weight:700;color:#89cff0;font-family:'Favorite Nurse','Cinzel Decorative',sans-serif;letter-spacing:0.04em;padding-left:5px;}
#squeeze-per-click{font-size:21px;font-weight:700;color:#c090a8;margin-top:4px;}
#dps-display{font-size:23px;font-weight:700;color:#c090a8;margin-top:4px;}
#dps-sparkline{display:block;margin:3px auto 0;width:160px;height:28px;opacity:0.85;}
#msg{font-size:14px;color:#a07088;margin-top:3px;min-height:15px;}
#ghost-card-timer{font-size:11px;font-weight:600;color:#8060a0;margin-left:6px;opacity:0.85;}
#prestige-xp-tooltip{position:absolute;left:50%;transform:translateX(-50%);top:64px;background:#1a0830;border:1px solid #7040c0;border-radius:8px;padding:7px 14px;font-size:12px;font-weight:bold;color:#d0b0ff;z-index:200;pointer-events:none;white-space:nowrap;text-align:center;line-height:1.7;display:none;box-shadow:0 4px 18px rgba(80,30,160,0.45);}
#tutorial-overlay{position:absolute;inset:0;pointer-events:none;z-index:11;transition:opacity 0.5s ease;}
#tutorial-overlay.tut-hidden{opacity:0;pointer-events:none;}
#tut-text{position:absolute;left:50%;top:235px;font-size:31px;font-weight:700;color:rgba(255,245,252,0.97);white-space:nowrap;text-shadow:0 0 10px rgba(255,160,210,0.9),0 0 22px rgba(220,100,160,0.6);animation:tutPulse 2.2s ease-in-out infinite;}#tut-hand{position:absolute;left:50%;top:645px;font-size:109px;line-height:1;opacity:0.64;}
#tut-arrow{position:absolute;left:50%;transform:translateX(-50%);bottom:155px;display:block;pointer-events:none;overflow:visible;}
#tut-start{position:absolute;left:calc(50% - 15px);top:750px;font-size:35px;text-transform:uppercase;color:rgba(255,245,252,0.95);white-space:nowrap;text-shadow:0 0 8px rgba(255,160,210,0.8);animation:tutStartPulse 1.1s ease-in-out infinite;}
/* After a failed drag, swap the two tutorial text positions so "Start here"
   jumps to where "Click and drag" was and vice versa. */
#tutorial-overlay.tut-swapped #tut-text{top:750px;left:calc(50% - 20px);}
#tutorial-overlay.tut-swapped #tut-start{top:235px;left:calc(50% - 215px);}
@keyframes tutDrag{0%{transform:translateX(-50%) translateY(0) rotate(-90deg);opacity:0.64;}10%{opacity:0.68;}65%{transform:translateX(-50%) translateY(-340px) rotate(-90deg);opacity:0.48;}78%{transform:translateX(-50%) translateY(-360px) rotate(-90deg);opacity:0;}90%{transform:translateX(-50%) translateY(0) rotate(-90deg);opacity:0;}100%{transform:translateX(-50%) translateY(0) rotate(-90deg);opacity:0.64;}}
@keyframes tutStartPulse{0%,100%{opacity:0.55;transform:scale(1);}50%{opacity:1;transform:scale(1.1);}}
@keyframes tutPulse{0%,100%{opacity:0.7;transform:translateX(-50%) scale(1);}50%{opacity:1;transform:translateX(-50%) scale(1.06);}}
#mid-col{grid-column:3;grid-row:3;flex:3 1 0;min-width:20%;display:flex;flex-direction:column;background:linear-gradient(to bottom,#15090e 0%,#11070b 100%);overflow:hidden;min-height:0;border-radius:14px;border:1px solid #341828;box-shadow:0 6px 24px rgba(0,0,0,0.5),inset 0 1px 0 rgba(255,180,220,0.05),inset 0 0 0 1px rgba(255,160,200,0.02);position:relative;transform:translateZ(0);contain:layout paint;isolation:isolate;}
#bookshelf{flex:1 1 0;display:flex;flex-direction:column;padding:6px;min-height:0;overflow-y:auto;}
#bookshelf-ghost{flex-shrink:0;}
#bookshelf-split{display:flex;gap:4px;flex:1;}
.bookshelf-col-wrap{flex:1;display:flex;flex-direction:column;min-width:0;}
.bookshelf-col-header{font-size:15px;font-weight:700;color:#7a4060;text-align:center;padding:6px 0 3px;letter-spacing:0.04em;border-bottom:1px solid #3d1a28;margin-bottom:2px;}
.shelf-row{flex:0 0 auto;border-bottom:6px solid #3d1a28;background:linear-gradient(to bottom,#2a1018,#221016);padding:5px 8px 6px;position:relative;min-height:50px;display:flex;flex-direction:column;border-radius:7px 7px 0 0;}
.shelf-label{display:inline-flex;align-items:center;justify-content:space-between;align-self:flex-start;max-width:100%;font-size:14px;font-weight:800;color:#e0a0ff;letter-spacing:0.10em;text-transform:uppercase;padding:2px 6px 2px 8px;margin-bottom:4px;border-radius:3px;border:none;background:transparent;box-shadow:none;text-shadow:none;}
.shelf-label .shelf-label-text{display:inline-block;}
.shelf-label .shelf-pin-btn{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;margin-left:6px;padding:0;border:none;background:transparent;font-size:10px;font-weight:700;color:#c080f0;cursor:pointer;opacity:0.6;line-height:1;transition:opacity 0.15s,transform 0.15s;}
.shelf-label .shelf-pin-btn:hover{opacity:1;transform:scale(1.25);}
.shelf-label.shelf-label-locked{background:transparent;color:#5a3848;border:none;text-shadow:none;box-shadow:none;}
.shelf-label.shelf-label-locked .shelf-pin-btn{display:none;}
.shelf-label-spoiler{position:relative;display:inline-block;width:116px;height:26px;vertical-align:middle;background:transparent;}
.shelf-label-spoiler .shelf-label-blur{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:2px;font-size:22px;letter-spacing:2px;filter:blur(2.5px);opacity:0.5;line-height:1;pointer-events:none;background:transparent;}
.shelf-label-spoiler .shelf-label-qmark{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:28px;z-index:2;line-height:1;pointer-events:none;background:transparent;}
.shelf-row.shelf-pinned .shelf-label{background:transparent;border:none;box-shadow:none;}
.shelf-row.shelf-pinned .shelf-label .shelf-pin-btn{opacity:1;color:#f0c0ff;}
.shelf-row.shelf-ascended .shelf-wood{background:linear-gradient(to bottom,#e8c840 0%,#a07820 100%);box-shadow:0 0 8px rgba(232,200,64,0.6);}
.shelf-row.shelf-ascended .shelf-label .shelf-label-text::before{content:'✨ ';}
.shelf-items{display:flex;flex-wrap:wrap;gap:2px;align-content:flex-start;flex:1;align-items:center;}
.shelf-items.spread{justify-content:space-evenly;}
.shelf-filler{visibility:hidden;pointer-events:none;}
/* will-change:transform removed: shelf-items don't animate themselves
   (only the row via shelfThunk, and the separate .shelf-flight-ghost clone).
   On a full bookshelf this promoted hundreds of emoji to their own GPU layers. */
.shelf-item{font-size:25px;line-height:1;display:inline-block;}
.shelf-item.shelf-milestone-10{text-shadow:0 0 2px rgba(255,200,80,0.7);}
.shelf-item.shelf-milestone-25{text-shadow:0 0 3px rgba(255,150,200,0.85);}
.shelf-item.shelf-milestone-100{text-shadow:0 0 5px rgba(120,200,255,0.95);}
.shelf-milestone-notch{display:inline-block;width:2px;height:18px;margin:0 3px;background:linear-gradient(to bottom,#c0a060,#604018);border-radius:1px;box-shadow:0 0 3px rgba(255,200,80,0.4);align-self:center;}
.shelf-milestone-notch[data-tier="25"]{background:linear-gradient(to bottom,#e890c0,#7a2050);box-shadow:0 0 3px rgba(255,150,200,0.5);height:20px;}
.shelf-milestone-notch[data-tier="100"]{background:linear-gradient(to bottom,#80c8ff,#2050a0);box-shadow:0 0 5px rgba(120,200,255,0.7);height:22px;width:3px;}
.shelf-wood{position:absolute;bottom:-6px;left:0;right:0;height:6px;background:#5a2535;border-radius:0 0 5px 5px;}
.shelf-contrib-bar{position:absolute;left:0;right:0;bottom:0;height:3px;background:rgba(0,0,0,0.45);overflow:hidden;pointer-events:none;border-radius:0 0 0 0;}
.shelf-contrib-fill{height:100%;width:0%;background:linear-gradient(to right,#7040c0,#e060c0);transition:width 0.4s ease;box-shadow:0 0 6px rgba(192,80,200,0.5);}
.shelf-row.shelf-pinned .shelf-contrib-fill{background:linear-gradient(to right,#c89030,#ffe896);box-shadow:0 0 6px rgba(232,200,64,0.6);}
#shelf-row-cylinder_army .shelf-contrib-fill{display:none !important;}
@keyframes shelfThunk{0%{transform:translateY(0);}25%{transform:translateY(2px);}55%{transform:translateY(-1px);}80%{transform:translateY(0.5px);}100%{transform:translateY(0);}}
.shelf-row.shelf-thunk{animation:shelfThunk 0.32s ease-out;}
.shelf-flight-ghost{position:fixed;pointer-events:none;z-index:999998;font-size:21px;line-height:1;will-change:transform,opacity;transition:transform 0.48s cubic-bezier(0.34,1.2,0.5,1),opacity 0.48s ease;transform-origin:50% 50%;}
/* Pin buttons temporarily hidden */
.shelf-label .shelf-pin-btn{display:none !important;}
/* Perk-count pill bar beside shelf titles — temporarily hidden (the same
   bar lives at the top of right-column ability cards as .upg-perk-bar). */
.shelf-perk-bar{display:none !important;}
/* Shelf-row corner wrapper: top-right group for Harem (Hired Cyl + Expedition)
   and Double Fist (Stroke Style) buttons */
.shelf-row-corner{position:absolute;top:6px;right:8px;display:flex;gap:6px;align-items:center;z-index:2;}
.shelf-row-corner > *{margin:0 !important;}
/* 10px breathing room between the Harem title and its hired cylinders */
#shelf-label-cylinder_army{margin-bottom:14px;}
/* Harem-specific label: 10% bigger, pinker, brighter than the default shelf label */
#shelf-label-cylinder_army .shelf-label-text{font-size:16px;color:#ff9ad8;text-shadow:0 0 10px rgba(255,140,210,0.7),0 0 18px rgba(255,100,190,0.35);}
/* Fading horizontal divider under the Harem title — fully gone by 30% across */
#shelf-row-cylinder_army::before{content:'';position:absolute;left:8px;right:0;top:30px;height:1px;background:linear-gradient(to right,rgba(255,160,220,0.85) 0%,transparent 50%);pointer-events:none;z-index:1;}
/* Move Training/Manage/Expedition buttons from the top-right corner to the
   bottom of the harem shelf row (full width, right-aligned). */
#shelf-row-cylinder_army .shelf-row-corner{position:absolute;top:6px;right:8px;display:flex;align-items:center;gap:6px;z-index:2;}
/* Segmented perk-count bar beside each shelf title */
.shelf-perk-bar{display:inline-flex;align-items:stretch;margin-left:8px;width:144px;height:12px;background:#1a0818;border:1px solid #3a1828;border-radius:4px;padding:2px;gap:2px;vertical-align:middle;flex-shrink:0;}
.shelf-perk-seg{flex:1;border-radius:2px;background:transparent;transition:background 0.25s;}
.shelf-perk-seg.filled{background:linear-gradient(180deg,#e070ff,#7030a8);box-shadow:0 0 5px rgba(200,90,220,0.6);}
.shelf-label.shelf-label-locked .shelf-perk-bar{display:none;}
/* Per-card perk bar — sits inline to the right of each right-column ability
   card's name (double_fist, cylinder_army/harem, ghost, helper_hand,
   cock_magic, …). Matches the look of the original .shelf-perk-bar. */
/* Perk row — sits across the top of each ability card. Holds the perk
   bar (flex:1) on the left and the "+" perk-buy button (110px) on the
   right, aligned with the Buy x1 button below it. */
.upg-perk-row{display:flex;align-items:stretch;width:100%;gap:6px;padding:4px 6px;border-bottom:1px solid #3a1828;background:#150810;box-sizing:border-box;}
.upg-card.hidden-card .upg-perk-row{display:none;}
/* Perk pill bar — fills the leftover space inside .upg-perk-row. */
.upg-perk-bar{display:flex;align-items:stretch;flex:1 1 auto;min-width:0;height:22px;background:#1a0818;border:1px solid #3a1828;border-radius:5px;padding:2px;gap:2px;flex-shrink:1;cursor:pointer;transition:border-color 0.15s,box-shadow 0.15s;box-sizing:border-box;}
.upg-perk-bar:hover{border-color:#a060d0;box-shadow:0 0 8px rgba(192,80,220,0.45);}
.upg-perk-seg{flex:1;border-radius:2px;background:transparent;transition:background 0.25s;min-width:2px;}
.upg-perk-seg.filled{background:linear-gradient(180deg,#e070ff,#7030a8);box-shadow:0 0 5px rgba(200,90,220,0.6);}
.upg-card.hidden-card .upg-perk-bar{display:none;}
/* "+" perk-buy button — same 110px width as the Buy x1 button beneath it,
   so the two right-edge controls line up vertically. Gray when the next
   perk is unaffordable, magenta + soft pulse when affordable. */
.upg-perk-plus{flex:0 0 55px;width:55px;height:22px;padding:0;background:#1a0818;border:1px solid #3a1828;border-radius:6px;color:#7a5070;font-weight:900;font-size:15px;line-height:1;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:border-color 0.15s,background 0.15s,color 0.15s,box-shadow 0.15s;box-sizing:border-box;}
.upg-perk-plus:hover{border-color:#7040a0;color:#a070c0;}
/* Unpurchasable + non-maxed: fade the "+" by 50% so it reads as inactive. */
.upg-perk-plus:not(.upg-perk-plus-affordable):not(.upg-perk-plus-maxed){color:rgba(122,80,112,0.3);}
.upg-perk-plus:not(.upg-perk-plus-affordable):not(.upg-perk-plus-maxed):hover{color:rgba(150,104,170,0.45);}
.upg-perk-plus.upg-perk-plus-affordable{position:relative;background:linear-gradient(180deg,#3a1648,#220c2c);border-color:#e060ff;color:#f0c0ff;}
/* Affordable: keep the existing outer halo AND add a pulsing INSET glow so
   the button itself looks "alive" from the inside. The shared upgAffordPulse
   animation handles the opacity ramp. */
.upg-perk-plus.upg-perk-plus-affordable::before{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;box-shadow:inset 0 0 8px rgba(255,180,255,0.75),inset 0 0 16px rgba(220,120,255,0.4),0 0 12px rgba(220,90,255,0.85),0 0 22px rgba(180,60,220,0.5);animation:upgAffordPulse 1.4s ease-in-out infinite;will-change:opacity;}
.upg-perk-plus.upg-perk-plus-affordable:hover{background:linear-gradient(180deg,#4a2058,#2a103a);border-color:#ff80ff;}
.upg-perk-plus.upg-perk-plus-maxed{background:linear-gradient(180deg,#2c1c08,#1a1004);border-color:#a07820;color:#ffd860;cursor:default;}
.upg-perk-plus[disabled]{cursor:default;}
.upg-card.hidden-card .upg-perk-plus{display:none;}
@keyframes upgPerkPlusPulse{
  0%,100%{box-shadow:0 0 4px rgba(220,90,255,0.4);}
  50%    {box-shadow:0 0 12px rgba(220,90,255,0.85),0 0 22px rgba(180,60,220,0.5);}
}
#right-col{grid-column:5;grid-row:3;flex:3 1 0;min-width:20%;display:flex;flex-direction:column;background:linear-gradient(to bottom,#1f0e16 0%,#180a10 100%);overflow:hidden;min-height:0;border-radius:14px;border:1px solid #381a2a;box-shadow:0 6px 24px rgba(0,0,0,0.5),inset 0 1px 0 rgba(255,180,220,0.05),inset 0 0 0 1px rgba(255,160,200,0.02);position:relative;transform:translateZ(0);contain:layout paint;isolation:isolate;}
#shop-header{padding:8px 12px 8px 12px;border-bottom:1px solid #2a1020;font-size:22px;font-weight:700;color:#d39eb9;letter-spacing:0.05em;flex-shrink:0;display:flex;align-items:center;justify-content:flex-start;gap:8px;}
#shop-header .shop-header-title{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
#shop-header #drug-market-btn-wrap{flex:0 0 auto;margin-left:auto;}
/* Pin-upper toggle: square icon button between the Shop title and the
   Drug Market button. Two stacked rectangles with a divider line — when
   pinned, the upper rectangle brightens and the upper section of the
   right column stops scrolling (only the lower section scrolls). */
#pin-upper-btn{flex:0 0 auto;margin-left:8px;background:transparent;border:1px solid #4a2035;border-radius:6px;padding:3px;width:26px;height:26px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:0;transition:border-color 0.15s,background 0.15s,box-shadow 0.15s;}
#pin-upper-btn:hover{border-color:#a060d0;background:rgba(160,80,200,0.12);}
#pin-upper-btn .pus-rect{fill:#a07088;transition:fill 0.18s,filter 0.18s;}
#pin-upper-btn .pus-line{stroke:#a07088;stroke-width:1;transition:stroke 0.18s;}
#pin-upper-btn.pus-on{border-color:#e060ff;background:rgba(220,90,255,0.12);box-shadow:0 0 8px rgba(220,90,255,0.35);}
#pin-upper-btn.pus-on .pus-rect-upper{fill:#ff90f0;filter:drop-shadow(0 0 3px rgba(255,140,255,0.85));}
#pin-upper-btn.pus-on .pus-rect-lower{fill:#5a3050;}
#pin-upper-btn.pus-on .pus-line{stroke:#e060ff;}
/* Pinned-mode layout: upper section frozen at the top, only the lower
   (Squeeze + Passive split) section scrolls. */
#right-col.pin-upper #upgrades-scroll{overflow:hidden;}
#right-col.pin-upper #upgrades-ghost{flex-shrink:0;}
#right-col.pin-upper #upgrades-split{flex:1 1 0;min-height:0;overflow-y:auto;}
#upgrades-list{padding:8px;display:flex;flex-direction:column;gap:6px;flex:1 1 0;min-height:0;overflow:hidden;}
#upgrades-scroll{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:6px;}
#upgrades-ghost{flex-shrink:0;}
#upg-card-ghost{margin-top:5px;position:relative;z-index:1;}
#upg-card-cylinder_army{margin-top:5px;position:relative;z-index:1;}
#upg-card-double_fist{position:relative;z-index:1;}
#upg-card-helper_hand{position:relative;z-index:1;}
/* Stroke Streak tucks up behind Double Fist; Squeeze Coordination tucks up
   behind Cylinder Army — each shifted up 8px so they visually read as part
   of the card above. They stay clickable because the upper card's z-index
   is raised, but only the overlapping top edge is obscured. */
#upg-card-stroke_streak{margin-top:-8px;margin-left:8px;margin-right:8px;position:relative;z-index:0;}
#upg-card-coord_burst{margin-top:-8px;margin-left:8px;margin-right:8px;position:relative;z-index:0;}
#upg-card-ghost_army{margin-top:-8px;margin-left:8px;margin-right:8px;position:relative;z-index:0;}
#upg-card-hand_job{margin-top:-8px;margin-left:8px;margin-right:8px;position:relative;z-index:0;}
#upg-btn-stroke_streak,#upg-btn-coord_burst,#upg-btn-ghost_army,#upg-btn-hand_job{width:102px !important;min-width:102px !important;max-width:102px !important;flex:0 0 102px !important;}
#upg-card-pump{margin-top:2px;}
#upg-card-helper{margin-top:2px;}
#upgrades-split{display:flex;gap:5px;align-items:flex-start;}
.upgrades-col-wrap{flex:1;display:flex;flex-direction:column;min-width:0;}
.upgrades-col-header{font-size:15px;font-weight:700;color:#b07b96;text-align:center;padding:6px 0 5px;letter-spacing:0.05em;border-bottom:1px solid #2a1020;flex-shrink:0;}
.upgrades-col{display:flex;flex-direction:column;gap:5px;padding:5px 2px 130px;}
.upgrades-col .upg-card-top{padding:7px 8px;gap:7px;}
.upgrades-col .upg-icon{font-size:22px;width:28px;filter:none;}
.upgrades-col .upg-info{padding-right:36px;}
.upgrades-col .upg-name{font-size:13px;}
.upgrades-col .upg-desc{font-size:11px;}
.upgrades-col .upg-owned{font-size:11px;}
.upgrades-col .upg-cost{font-size:14px;font-weight:700;}
.upgrades-col .upg-btn-droplet{font-size:16px;}
.upgrades-col .upg-buy-wrap{font-size:13px;}
.upgrades-col .upg-stack{font-size:17px;color:#c080f0;font-weight:700;top:6px;right:7px;}
/* Uniform card height across the Squeeze/Passive columns so the two sides line
   up in every state. Without this the cards floor at their content height, which
   measured 105px (squeeze) vs 103px (passive). min-height only raises shorter
   cards, so taller content (e.g. ASCENDED badge) is never clipped. The explicit
   hidden-card rule re-applies it over the global .upg-card.hidden-card{72px} so
   locked "???" teasers match too. (Global *{box-sizing:border-box} → 105 total.) */
.upgrades-col .upg-card{min-height:105px;}
.upgrades-col .upg-card.hidden-card{min-height:105px;}
#perk-section{background:#1a0c12;border-top:1px solid #281018;flex-shrink:0;}
.upg-card{background:linear-gradient(160deg,#2d1220,#1e0d16);border:1.5px solid #4a2035;border-radius:10px;overflow:hidden;display:flex;flex-direction:column;transition:background 0.15s,border-color 0.15s;position:relative;}
.upg-card-top{display:flex;gap:10px;align-items:center;padding:10px 12px;position:relative;}
.upg-card.affordable{border-color:#c060a0;border-left:3px solid #e060ff;background:linear-gradient(to right,rgba(192,64,224,0) 0%,rgba(192,64,224,0.15) 20%,rgba(192,64,224,0.45) 80%,rgba(192,64,224,0.6) 100%),linear-gradient(160deg,#2e1028,#1e0d18);box-shadow:0 0 8px rgba(160,60,220,0.5);cursor:pointer;}
.upg-card.ghost-card{border-color:#4a2035;}
.upg-card.ghost-card.affordable{border-color:#c060a0;background:linear-gradient(to right,rgba(192,64,224,0) 0%,rgba(192,64,224,0.15) 20%,rgba(192,64,224,0.45) 80%,rgba(192,64,224,0.6) 100%),#2e1220;box-shadow:0 0 8px rgba(160,60,220,0.5);cursor:pointer;}
/* ── Upgrade card family modifiers ───────────────────────────────────────────
   Tint background + a thin top accent stripe (via inset box-shadow so the
   border layout doesn't shift) per family. Affordability still owns the left
   edge with its existing 3px magenta border, so family vs affordability read
   as orthogonal cues. Ghost cards get an iridescent border ring via ::before
   for a holographic feel. Locked .hidden-card overwrites className entirely
   (see upgrades.js:894), so these only apply once revealed. */
.upg-card.upg-squeeze{background:linear-gradient(160deg,#2e1218,#1f0c12);box-shadow:inset 0 2px 0 rgba(220,72,112,0.55);}
.upg-card.upg-squeeze.affordable{background:linear-gradient(to right,rgba(192,64,224,0) 0%,rgba(192,64,224,0.15) 20%,rgba(192,64,224,0.45) 80%,rgba(192,64,224,0.6) 100%),linear-gradient(160deg,#341222,#1f0d14);box-shadow:0 0 8px rgba(160,60,220,0.5),inset 0 2px 0 rgba(255,108,148,0.9);cursor:pointer;}
.upg-card.upg-passive{background:linear-gradient(160deg,#2e1218,#1f0c12);box-shadow:inset 0 2px 0 rgba(220,72,112,0.55);}
.upg-card.upg-passive.affordable{background:linear-gradient(to right,rgba(192,64,224,0) 0%,rgba(192,64,224,0.15) 20%,rgba(192,64,224,0.45) 80%,rgba(192,64,224,0.6) 100%),linear-gradient(160deg,#341222,#1f0d14);box-shadow:0 0 8px rgba(160,60,220,0.5),inset 0 2px 0 rgba(255,108,148,0.9);cursor:pointer;}
.upg-card.ghost-card{background:linear-gradient(160deg,#1e1228,#150b1c);}
/* Unaffordable ghost cards: apply the grayscale/sepia treatment to the
   ENTIRE card so the left content area and the right buy-side share the
   same look. Previously the filter only covered .upg-card-top-ghost, which
   left the buy-side rendering at full purple saturation and produced a
   visible vertical "split" between the two halves at the cost area. */
/* Unpurchaseable styling depends on whether the player owns any of this
   upgrade yet. Fresh (0 owned) → gray treatment (regardless of section).
   Owned (≥1) + unaffordable → red theme (lower-card style) even for ghost
   cards, since the player has already invested and knows what it is. */
.upg-card.ghost-card.upg-fresh:not(.affordable){opacity:0.9;filter:grayscale(0.9) sepia(0.3) brightness(0.92);}
.upg-card.ghost-card:not(.upg-fresh):not(.affordable){background:linear-gradient(160deg,#2e1218,#1f0c12);box-shadow:inset 0 2px 0 rgba(220,72,112,0.55);}
.upg-card.upg-squeeze.upg-fresh:not(.affordable),
.upg-card.upg-passive.upg-fresh:not(.affordable){opacity:0.9;filter:grayscale(0.9) sepia(0.3) brightness(0.92);}
.upg-card.ghost-card::before{content:'';position:absolute;inset:0;border-radius:inherit;padding:1px;background:linear-gradient(135deg,#a060ff,#60d0ff,#ff60c0,#a060ff);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none;opacity:0.45;}
.upg-card.ghost-card.affordable::before{opacity:0.85;}
.upg-card.hidden-card{background:#1a0c14;border-color:#2a1025;position:relative;overflow:hidden;min-height:72px;}
.hidden-blur{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:6px;font-size:22px;letter-spacing:4px;filter:blur(4px);opacity:0.5;pointer-events:none;}
.hidden-lock{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:28px;z-index:2;}
.upg-stack{position:absolute;top:8px;right:10px;font-size:13px;color:#6a4060;font-weight:700;}
.upg-icon{font-size:31px;flex-shrink:0;width:38px;text-align:center;filter:var(--cyl-emoji-filter,none);}
.upg-card.upg-teaser .upg-icon{filter:brightness(0) saturate(0);opacity:0.55;}
.upg-card.upg-teaser .upg-name,.upg-card.upg-teaser .upg-desc{color:#6a4060;letter-spacing:2px;}
/* Ability/ghost upgrade card layout: flex-row with info on the left and buy button
   on the right. Buy-wrap is a natural flex child (align-items:stretch) so it fills
   the full card height — mirrors the lower-upgrade .upg-bottom-row pattern. */
.upg-card-ghost-row{display:flex;align-items:stretch;width:100%;position:relative;}
.upg-card-top-ghost{display:flex;gap:10px;align-items:center;padding:6px 12px 10px;flex:1 1 auto;min-width:0;}
.upg-card-top-ghost .upg-info{padding-right:0;flex:1 1 auto;min-width:0;}
/* Non-ghost (squeeze/passive) cards: ensure .upg-card-top fills the row so
   .upg-info's flex:1 can push the inline buy-wrap to the right edge — same
   alignment behaviour as the ghost/ability cards above them. */
.upg-card-ghost-row > .upg-card-top{flex:1 1 auto;min-width:0;}
.upg-card .upg-stack.upg-stack-inline,.upgrades-col .upg-stack.upg-stack-inline{position:static;font-size:17px;color:#c080f0;font-weight:700;margin-right:6px;flex-shrink:0;text-align:right;min-width:18px;top:auto;right:auto;}
.upg-buy-wrap.upg-buy-side{width:110px!important;min-width:110px!important;max-width:110px!important;flex:0 0 110px!important;border-radius:0 10px 10px 0;margin:0;align-self:stretch;box-sizing:border-box;overflow:hidden;}
/* Inline cost — replaces the side-mounted .upg-buy-side. Sits inside
   .upg-card-top, flush right via margin-left:auto, and never paints its own
   panel background — only the cost text + droplet show. State coloring still
   comes from .can-buy / .ghost-can / .cannot-buy on the wrap, applied to the
   cost / label / droplet glyphs the same way it always was. */
.upg-buy-wrap.upg-buy-inline{margin-left:auto;flex:0 0 auto;width:auto;min-width:0;background:transparent!important;border:none!important;opacity:1!important;align-self:center;display:flex;align-items:center;pointer-events:none;}
.upg-buy-wrap.upg-buy-inline .upg-buy-main{display:flex!important;align-items:baseline!important;justify-content:flex-end!important;flex-wrap:nowrap!important;white-space:nowrap!important;padding:0 0 0 12px!important;line-height:1.2!important;background:transparent!important;flex:0 0 auto;}
.upg-buy-wrap.upg-buy-inline .upg-cost-group{display:inline-flex!important;align-items:baseline!important;flex-wrap:nowrap!important;white-space:nowrap!important;font-size:14px!important;margin:0!important;gap:5px!important;}
.upg-buy-wrap.upg-buy-inline .upg-cost{font-size:16px;font-weight:700;}
.upg-buy-wrap.upg-buy-inline .upg-buy-label{display:inline-block!important;white-space:nowrap!important;font-size:12px!important;}
/* Evolution upgrade — its own bordered sibling card to the right of the
   parent ability card (Circle Jerk → Harem, Hand Job → Helper Hand,
   Stroke Streak → Double Fist, Ghost Army → Ghost). The two cards sit
   inside .upg-pair-row with an 8px gap; each carries its own border so
   they read as mechanically separate upgrades. */
.upg-pair-row{display:flex;align-items:stretch;gap:8px;}
.upg-pair-row > .upg-card{flex:1 1 auto;min-width:0;}
/* When the parent card lives inside .upg-pair-row, its own margin-top
   would push only the parent inside the row (misaligning the evo card).
   Move the spacing onto the row instead. */
.upg-pair-row > #upg-card-cylinder_army,
.upg-pair-row > #upg-card-ghost,
.upg-pair-row > #upg-card-double_fist,
.upg-pair-row > #upg-card-helper_hand{margin-top:0;}
#upgrades-ghost > .upg-pair-row{margin-top:5px;}
.upg-buy-wrap.upg-buy-evo{width:170px!important;min-width:170px!important;max-width:170px!important;flex:0 0 170px!important;border:0.5px solid #4a2035;border-radius:10px;margin:0;align-self:stretch;box-sizing:border-box;overflow:hidden;display:flex;flex-direction:column;justify-content:stretch;background:linear-gradient(160deg,#2d1220,#1e0d16);}
.upg-buy-wrap.upg-buy-evo:hover{border-color:#7a3060;}
/* Compact perk-row + bar + plus button so the perk tree feature fits inside
   the evolution ability cards (170px) above the buy button. */
.upg-buy-evo .upg-perk-row-evo{padding:3px 3px;gap:3px;}
.upg-buy-evo .upg-perk-bar-evo{height:18px;padding:1px;gap:1px;}
.upg-buy-evo .upg-perk-plus-evo{flex:0 0 34px;width:34px;height:18px;font-size:13px;}
/* Two-row layout: icon + title on top (left-aligned), droplet cost pinned to
   the bottom-right edge so the button reads as "[icon] name / cost". Title
   matches the shop's other card titles (.upg-name); cost matches the other
   inline buy costs. */
.upg-buy-evo .upg-buy-main{display:flex!important;flex-direction:column;align-items:stretch;justify-content:space-between!important;padding:5px 7px 6px!important;text-align:left!important;line-height:1.15!important;width:100%;flex:1;box-sizing:border-box;}
.upg-buy-evo .upg-evo-title{display:block;font-size:16px;font-weight:700;color:#f0c0d0;margin-bottom:2px;line-height:1.15;white-space:nowrap;}
/* Evolution button header — evolution upgrade icon (🔥 Stroke Streak,
   👻 Ghost Army, ✋💼 Hand Job, ✊✊ Circle Jerk) sits to the LEFT of the
   single-line title. */
.upg-buy-evo .upg-evo-header{display:flex;align-items:center;justify-content:flex-start;gap:4px;width:100%;}
.upg-buy-evo .upg-evo-icon{display:inline-flex;align-items:center;font-size:14px;line-height:1;flex:0 0 auto;}
.upg-buy-evo .upg-cost-group{display:block!important;font-size:16px!important;text-align:right;}
.upg-buy-evo .upg-cost{font-size:16px;font-weight:700;}
/* Keep pointer-events alive so tooltips still fire on unaffordable/owned states. */
.upg-buy-wrap.upg-buy-evo.cannot-buy{pointer-events:auto;}
.upg-buy-wrap.upg-buy-evo.cannot-buy .upg-buy-main{cursor:default;}
/* Affordable state — mirrors the parent ability card's affordable gradient
   (.upg-card.ghost-card.affordable) so the sibling evolution button reads at
   the same brightness/transparency as the parent's right-side buy area. */
.upg-buy-wrap.upg-buy-evo.ghost-can{background:linear-gradient(to right,rgba(192,64,224,0.45) 0%,rgba(192,64,224,1) 100%),#2e1220!important;border-color:#c060a0;box-shadow:0 0 8px rgba(160,60,220,0.5);}
/* Owned (purchased) state — keeps the title visible; the cost slot is emptied
   in renderUpgrades(), so the purple background is the only "owned" cue. */
.upg-buy-wrap.upg-buy-evo.upg-evo-owned{background:linear-gradient(135deg,#3a2050,#2a1838);border:0.5px solid #6a4090;color:#e0c0ff;box-shadow:inset 0 0 6px rgba(160,100,220,0.35);cursor:default;pointer-events:auto;}
.upg-buy-wrap.upg-buy-evo.upg-evo-owned .upg-buy-main{cursor:default;}
.upg-card-ghost-row .upg-buy-wrap.upg-buy-side .upg-buy-main{display:block!important;padding:8px 10px 10px!important;text-align:center!important;line-height:1.2!important;}
#upg-card-stroke_streak .upg-buy-wrap.upg-buy-side .upg-buy-main,
#upg-card-coord_burst   .upg-buy-wrap.upg-buy-side .upg-buy-main,
#upg-card-hand_job      .upg-buy-wrap.upg-buy-side .upg-buy-main{padding-top:12px!important;}
.upg-card-ghost-row .upg-buy-wrap.upg-buy-side .upg-buy-main{display:flex!important;align-items:baseline!important;justify-content:flex-end!important;flex-wrap:nowrap!important;white-space:nowrap!important;}
/* x1 / x10 / x100 label now lives INSIDE .upg-cost-group as the first child,
   so it tracks the cost number's width — gap keeps a one-space rhythm between
   the label, cost, and droplet emoji regardless of how wide the cost grows. */
.upg-card-ghost-row .upg-buy-wrap.upg-buy-side .upg-cost-group{display:inline-flex!important;align-items:baseline!important;flex-wrap:nowrap!important;white-space:nowrap!important;font-size:14px!important;margin:0!important;gap:5px!important;}
.upg-card-ghost-row .upg-cost{font-size:16px;font-weight:700;}
.upg-card-ghost-row .upg-buy-wrap.upg-buy-side .upg-buy-label{display:inline-block!important;white-space:nowrap!important;font-size:12px!important;}
/* Squeeze/passive (bottom-section) buy buttons: x1 + cost + 💧 on one line, right-aligned, bottom-aligned. */
.upg-card.upg-squeeze .upg-buy-wrap.upg-buy-side .upg-buy-main,
.upg-card.upg-passive .upg-buy-wrap.upg-buy-side .upg-buy-main{display:flex!important;align-items:flex-end!important;justify-content:flex-end!important;flex-wrap:nowrap!important;white-space:nowrap!important;padding:0 8px 6px!important;line-height:1!important;height:100%;width:100%;box-sizing:border-box;}
/* Squeeze/passive cost group: same one-space rhythm as the ghost variant —
   x1 sits as the first flex child so it follows the cost number's width. */
.upg-card.upg-squeeze .upg-buy-wrap.upg-buy-side .upg-cost-group,
.upg-card.upg-passive .upg-buy-wrap.upg-buy-side .upg-cost-group{display:inline-flex!important;align-items:baseline!important;flex-wrap:nowrap!important;white-space:nowrap!important;line-height:1!important;margin:0!important;gap:5px!important;}
.upg-card.upg-squeeze .upg-buy-wrap.upg-buy-side .upg-buy-label,
.upg-card.upg-passive .upg-buy-wrap.upg-buy-side .upg-buy-label{display:inline-block!important;white-space:nowrap!important;line-height:1!important;}
.upg-card.upg-squeeze .upg-buy-wrap.upg-buy-side .upg-cost,
.upg-card.upg-passive .upg-buy-wrap.upg-buy-side .upg-cost,
.upg-card.upg-squeeze .upg-buy-wrap.upg-buy-side .upg-btn-droplet,
.upg-card.upg-passive .upg-buy-wrap.upg-buy-side .upg-btn-droplet{line-height:1!important;vertical-align:baseline!important;}
.upg-info{flex:1;min-width:0;padding-right:44px;min-width:0;}
.upg-name{font-size:16px;font-weight:700;color:#f0c0d0;}
.upg-desc{font-size:13px;color:#a07088;margin-top:2px;}
.upg-desc-bigstat{font-size:130%;color:#c080e0;font-weight:600;white-space:nowrap;}
/* "per squeeze" / "every Xs" suffix — full size by default; the JS in
   renderUpgrades adds .upg-bigstat-tight whenever the line would overflow
   .upg-desc's width (large fmt() numbers like "1.20K" / "1.234B") so the
   trailing words shrink only when they have to in order to stay on one row. */
.upg-desc-bigstat.upg-bigstat-tight .upg-stat-suffix{font-size:0.72em;font-weight:500;letter-spacing:0.01em;}
/* Third line on Squeeze/Passive cards: this upgrade's per-copy effect on harem
   members. Muted so it reads as secondary to the main per-squeeze/per-sec stat;
   empty (no line) on cards that grant no harem bonus. */
.upg-harem-line{font-size:13.2px;color:#c080e0;margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.upg-harem-line:empty{display:none;}
.upg-harem-line .upg-harem-v{color:#fff;font-weight:600;}
.upgrades-col .upg-harem-line{font-size:12px;}
.upg-owned{font-size:13px;color:#c090a8;margin-top:2px;}
.upg-bottom-row{display:flex;align-items:stretch;width:100%;}
.upg-cost{font-size:17px;font-weight:700;color:#95d4f2;}
.upg-buy-wrap.can-buy .upg-cost,.upg-buy-wrap.ghost-can .upg-cost{color:#95d4f2;}
.upg-btn-droplet{font-size:16px;}
.upg-buy-wrap.can-buy .upg-btn-droplet,.upg-buy-wrap.ghost-can .upg-btn-droplet{filter:brightness(1.3);}
.upg-buy-wrap{display:flex;flex:1;min-height:32px;cursor:pointer;transition:box-shadow 0.15s,filter 0.15s;border:none;user-select:none;}
.upg-buy-main{flex:1;display:flex;align-items:center;justify-content:space-between;padding:6px 10px;font-weight:500;transition:background 0.15s;}
/* Buy-area-only hover removed — clicks land anywhere on the affordable card,
   so per-area highlight would re-introduce visual separation. */
/* Buy-area-only active flash removed — _upgPulse(card) flashes the whole card. */
@keyframes buyPulse{0%{filter:brightness(1);}40%{filter:brightness(1.7);}100%{filter:brightness(1);}}
.upg-buy-wrap.buy-pulse,.upg-card.buy-pulse{animation:buyPulse 0.35s ease-out;}
.upg-cost-group{display:flex;align-items:center;gap:3px;}
.upg-buy-x10{width:44px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#fff;border-left:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.12);transition:background 0.15s;}
.upg-buy-wrap.can-buy .upg-buy-x10:hover,.upg-buy-wrap.ghost-can .upg-buy-x10:hover{background:rgba(255,255,255,0.25);}
.upg-buy-wrap.can-buy .upg-buy-x10:active,.upg-buy-wrap.ghost-can .upg-buy-x10:active{filter:brightness(1.5);}
.upg-buy-x10.x10-cannot{background:rgba(0,0,0,0.45);border-left-color:rgba(255,255,255,0.07);pointer-events:none;}
.x10-tooltip{position:fixed;transform:translate(-50%,-100%);background:#1a0a0f;border:1px solid #6a3060;border-radius:6px;padding:4px 8px;font-size:12px;font-weight:600;color:#f0c0d0;white-space:nowrap;pointer-events:none;z-index:999999;}
.x10-tooltip::after{content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:#6a3060;}
.x10-tooltip.tip-below{transform:translate(-50%,0);}
.x10-tooltip.tip-below::after{top:auto;bottom:100%;border-top-color:transparent;border-bottom-color:#6a3060;}
.upg-buy-wrap.can-buy,.upg-buy-wrap.ghost-can{background:transparent;color:#fff;}
.upg-buy-wrap.cannot-buy{background:#3a0f25;border:0.5px solid #6a3050;color:#c090a8;opacity:0.5;pointer-events:none;}
/* Side buy buttons (Squeeze/Passive + ghost ability cards) should not paint
   their own background/border when unaffordable — the card's main surface
   shows through behind the cost + "x1" text instead of a separate panel. */
/* Side-mounted unaffordable buy wraps share the parent card's gradient: no
   own background, no border, and no extra opacity — the parent ghost-card's
   :not(.affordable) filter+opacity already handles the dim treatment, so a
   second opacity layer here would create a visible stacking-context seam. */
.upg-buy-wrap.upg-buy-side.cannot-buy{background:transparent;border:none;opacity:1;}
/* Bulk-buy modifier (Ctrl/Shift) is held but the x10/x100 total is unaffordable —
   dim the whole upgrade card, not just the buy-button section. */
.upg-card.bulk-cannot-buy{opacity:0.5;filter:brightness(0.7) grayscale(0.4);}
.upg-card.bulk-cannot-buy .upg-buy-wrap.cannot-buy{opacity:1;}
/* ── Right-column ability/shop cards: full-width single-line titles ──────────
   The ability name (Adult Magazine, Street Corner, …) now gets the entire top
   row to itself, stretching all the way to the card's right edge instead of
   wrapping in the cramped space left of the cost. The droplet cost drops to the
   bottom-right corner to make that room. Each card is a query container so the
   title font-size scales down with the card (and therefore the right column's)
   width — keeping it on one line as the column narrows. */
#right-col .upg-card{container-type:inline-size;}
#right-col .upg-card-top{position:relative;}
#right-col .upg-card-top > .upg-info{padding-right:0;}
#right-col .upg-card-top .upg-name{white-space:nowrap;font-size:clamp(9px,6cqi,16px);}
/* Reserve room on the desc line so its text wraps before the cost instead of
   running underneath it. */
#right-col .upg-card-top .upg-desc{padding-right:66px;}
#right-col .upg-card-top .upg-harem-line{padding-right:66px;}
/* Cost group out of the horizontal flow, parked bottom-right and nudged down. */
#right-col .upg-card-top > .upg-buy-wrap.upg-buy-inline{position:absolute;right:12px;bottom:8px;margin-left:0;align-self:auto;}
@keyframes popIn{from{transform:scale(0) rotate(-20deg);opacity:0;}to{transform:scale(1) rotate(0);opacity:1;}}
@keyframes fadeInScale{from{transform:scale(0.85);opacity:0;}to{transform:scale(1);opacity:1;}}
@keyframes rainbowPulse{0%{border-color:#ff0080;box-shadow:0 0 18px 4px rgba(255,0,128,0.7);}14%{border-color:#ff6600;box-shadow:0 0 18px 4px rgba(255,102,0,0.7);}28%{border-color:#ffee00;box-shadow:0 0 18px 4px rgba(255,238,0,0.7);}42%{border-color:#00dd00;box-shadow:0 0 18px 4px rgba(0,221,0,0.7);}57%{border-color:#0088ff;box-shadow:0 0 18px 4px rgba(0,136,255,0.7);}71%{border-color:#8800ff;box-shadow:0 0 18px 4px rgba(136,0,255,0.7);}85%{border-color:#ff00cc;box-shadow:0 0 18px 4px rgba(255,0,204,0.7);}100%{border-color:#ff0080;box-shadow:0 0 18px 4px rgba(255,0,128,0.7);}}
@keyframes etherealKiwi{0%{filter:hue-rotate(0deg) saturate(2.5) brightness(1.2) contrast(1.1);}25%{filter:hue-rotate(90deg) saturate(2.5) brightness(1.3) contrast(1.1);}50%{filter:hue-rotate(180deg) saturate(2.5) brightness(1.2) contrast(1.1);}75%{filter:hue-rotate(270deg) saturate(2.5) brightness(1.3) contrast(1.1);}100%{filter:hue-rotate(360deg) saturate(2.5) brightness(1.2) contrast(1.1);}}
.kiwi-ethereal{animation:etherealKiwi 3s linear infinite !important;}
@keyframes goldPulse{0%{box-shadow:0 0 0 3px rgba(255,210,50,0);border-color:#6a3060;}40%{box-shadow:0 0 0 5px rgba(255,210,50,0.9);border-color:#ffd032;}100%{box-shadow:0 0 0 3px rgba(255,210,50,0);border-color:#6a3060;}}
@keyframes goldPulseLoop{0%{box-shadow:0 0 0 3px rgba(255,210,50,0);border-color:#6a3060;}40%{box-shadow:0 0 0 6px rgba(255,210,50,0.95);border-color:#ffd032;}100%{box-shadow:0 0 0 3px rgba(255,210,50,0);border-color:#6a3060;}}
@keyframes milestoneFloat{0%{opacity:0;transform:translate(-50%,0);}15%{opacity:1;}70%{opacity:1;}100%{opacity:0;transform:translate(-50%,-120px);}}
.milestone-toast{position:absolute;left:50%;top:280px;z-index:100;font-size:41px;font-weight:700;color:#ffd700;text-shadow:0 0 12px rgba(255,215,0,0.8),0 0 24px rgba(255,180,0,0.5);pointer-events:none;white-space:nowrap;animation:milestoneFloat 2s ease-out forwards;font-family:Calibri,sans-serif;}
@keyframes dropletFloat{0%{opacity:0;transform:translate(-50%,0) scale(0.8);}8%{opacity:0.7;transform:translate(-50%,-6px) scale(0.84);}16%{opacity:1;transform:translate(-50%,-13px) scale(0.88);}24%{opacity:1;transform:translate(-50%,-20px) scale(0.92);}32%{opacity:1;transform:translate(-50%,-27px) scale(0.96);}40%{opacity:1;transform:translate(-50%,-34px) scale(0.98);}50%{opacity:1;transform:translate(-50%,-42px) scale(1);}60%{opacity:1;transform:translate(-50%,-50px) scale(1);}70%{opacity:0.85;transform:translate(-50%,-58px) scale(0.96);}80%{opacity:0.6;transform:translate(-50%,-66px) scale(0.92);}90%{opacity:0.3;transform:translate(-50%,-74px) scale(0.88);}100%{opacity:0;transform:translate(-50%,-82px) scale(0.84);}}
.droplet-toast{position:absolute;left:50%;top:-8px;z-index:100;font-size:26px;font-weight:800;color:#fff;text-shadow:0 0 8px rgba(255,255,255,0.45),0 0 18px rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.6);pointer-events:none;white-space:nowrap;animation:dropletFloat 1.1s linear forwards;will-change:transform,opacity;}
@keyframes megaPaydayFloat{0%{opacity:0;transform:translate(-50%,20px) scale(0.6);}12%{opacity:1;transform:translate(-50%,0) scale(1.15);}22%{transform:translate(-50%,-8px) scale(1);}80%{opacity:1;transform:translate(-50%,-100px) scale(1);}100%{opacity:0;transform:translate(-50%,-130px) scale(0.9);}}
.mega-payday-toast{position:absolute;left:50%;top:60px;z-index:102;font-size:34px;font-weight:900;color:#fff6a8;text-shadow:0 0 10px rgba(255,210,60,0.95),0 0 22px rgba(255,160,40,0.7),0 0 38px rgba(255,120,30,0.35);pointer-events:none;white-space:nowrap;letter-spacing:0.06em;animation:megaPaydayFloat 2s ease-out forwards;font-family:Calibri,sans-serif;}
@keyframes ghostJobRise{0%{opacity:0;transform:translateX(-50%) translateY(0);}12%{opacity:0.8;}80%{opacity:0.6;}100%{opacity:0;transform:translateX(-50%) translateY(-150px);}}
@keyframes ghostLetterWave{0%,100%{transform:translateY(0);}50%{transform:translateY(-11px);}}
.ghost-job-toast{position:absolute;left:50%;top:240px;font-size:43px;font-weight:800;color:rgba(210,235,255,0.78);text-shadow:0 0 18px rgba(140,200,255,0.75),0 0 36px rgba(100,170,255,0.35);pointer-events:none;white-space:nowrap;z-index:101;letter-spacing:0.04em;animation:ghostJobRise 2.4s ease-out forwards;font-family:Euphemia,sans-serif;}
.ghost-job-toast span{display:inline-block;animation:ghostLetterWave 0.45s ease-in-out infinite;}
/* ── Mobile-only floating-UI sizing ─────────────────────────────────────────
   These three widgets are appended directly to document.body, so the #root
   zoom ladder never reaches them. Shrink them explicitly on touch-only
   devices (body.mobile-touch-ui is added by the detect block in squeeze.js).
   Desktop/tablet-with-mouse behaviour is unchanged. */
/* Drippy's speech bubble: 30% smaller than desktop (desktop uses scale(1.3),
   so 1.3 × 0.7 ≈ 0.91). transform-origin inherits from the base rule. */
body.mobile-touch-ui #coach-wrap{transform:scale(0.91);}
/* UI-unlock panels: 30% less tall, 30% wider (400px → 520px). Width set
   via the layout property (not scaleX) so text reflows instead of stretching.
   Anchor to the top-right since the stack sits flush against the bottom-right corner. */
body.mobile-touch-ui .ui-unlock-panel{width:416px;transform:scaleY(0.7);transform-origin:top right;}
/* Achievement popups: 20% smaller overall. Anchor to top-center so the
   stack stays aligned under its fixed container. */
body.mobile-touch-ui .ach-popup{transform:scale(0.8);transform-origin:top center;}
