Junon.io theme pack
// ==UserScript==
// @name Junon.io theme pack (main)
// @namespace http://tampermonkey.net/
// @version V.0.1.0
// @description Junon.io theme pack
// @author Parasideum
// @match https://junon.io/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
let currentUsername = '';
let quickReloadActive = false;
let quickReloadInterval = null;
let currentTheme = 'miku';
let borderEnabled = true;
let originalMode = false;
let extraEffectsEnabled = true;
let hasJoinedWorld = false;
let customThemeData = {
primaryColor: '#7c5cbf',
bgImage: null,
bgImageName: '',
middleImage: null,
middleImageName: '',
musicDataURL: null,
musicBlobURL: null,
musicName: '',
};
try {
const _s = localStorage.getItem('ps_custom_theme');
if (_s) {
const _p = JSON.parse(_s);
if (_p.primaryColor) customThemeData.primaryColor = _p.primaryColor;
if (_p.bgImage) customThemeData.bgImage = _p.bgImage;
if (_p.bgImageName) customThemeData.bgImageName = _p.bgImageName;
if (_p.middleImage) customThemeData.middleImage = _p.middleImage;
if (_p.middleImageName) customThemeData.middleImageName = _p.middleImageName;
if (_p.musicName) customThemeData.musicName = _p.musicName;
if (_p.musicDataURL) {
customThemeData.musicDataURL = _p.musicDataURL;
try {
const _a = _p.musicDataURL.split(',');
const _m = _a[0].match(/:(.*?);/)[1];
const _b = atob(_a[1]);
const _u = new Uint8Array(_b.length);
for (let _i = 0; _i < _b.length; _i++) _u[_i] = _b.charCodeAt(_i);
customThemeData.musicBlobURL = URL.createObjectURL(new Blob([_u], { type: _m }));
} catch(e) {}
}
}
} catch(e) {}
const MIKU_IMG = "https://raw.githubusercontent.com/Mathmaticians/Junon.io1/main/github%20testing.jpg";
const HK_IMG = "https://raw.githubusercontent.com/Mathmaticians/Junon3/main/HK_header.webp";
const SPACE_MIDDLE_IMG = "https://raw.githubusercontent.com/Mathmaticians/junon6.1/main/thumb-1920-268425.jpg";
const HK_BG = "https://raw.githubusercontent.com/Mathmaticians/junon4/main/hollow-knight-blue-butterflies-htthva79qwf3msbg.jpg";
const MIKU_BG = "https://raw.githubusercontent.com/Mathmaticians/Junon2/main/pngtree-cherry-blossom-background-digital-watercolor-painting-japanese-animation-style-image_16115659.jpg";
const SPACE_BG = "https://raw.githubusercontent.com/Mathmaticians/junon5/main/arthur-gurin-station.jpg";
const GOJO_MIDDLE_IMG = "https://raw.githubusercontent.com/Mathmaticians/junon8/main/gojo2.jpg";
const GOJO_BG = "https://raw.githubusercontent.com/Mathmaticians/junon7/main/Gojo.jpg";
const DOOM_MIDDLE_IMG = "https://raw.githubusercontent.com/Mathmaticians/junon9/main/doom%20menu.gif";
const DOOM_BG = "https://raw.githubusercontent.com/Mathmaticians/junon10/main/doom%20background.gif";
const TETO_MIDDLE_IMG = "https://raw.githubusercontent.com/Mathmaticians/junon12.5/main/teto2.jpg";
const TETO_BG = "https://raw.githubusercontent.com/Mathmaticians/junon13/main/tetto%20wrapper2.webp";
const KFP_MIDDLE_IMG = "https://raw.githubusercontent.com/Mathmaticians/junon14/main/kung%20fu%20panda%20middle%20gif.webp";
const KFP_BG = "https://raw.githubusercontent.com/Mathmaticians/junon15/main/kung%20fu%20panda%20wrapper.jpeg";
const THEME_AUDIO = {
miku: "https://raw.githubusercontent.com/Mathmaticians/audio1/main/%5BVOCALOID%5D%20Hatsune%20Miku%20Deep%20Sea%20Girl%20%5BJapanese%20Romanji%20English%20Lyrics%5D.mp3",
hk: "https://raw.githubusercontent.com/Mathmaticians/audio2/main/Hollow%20Knight%20OST%20-%20Enter%20Hallownest.mp3",
space: "https://raw.githubusercontent.com/Mathmaticians/audio3/main/Interstellar%20Main%20Theme%20-%20Hans%20Zimmer.mp3",
gojo: "https://raw.githubusercontent.com/Mathmaticians/audio4/main/Hollow%20Purple.mp3",
doom: "https://raw.githubusercontent.com/Mathmaticians/audio5/main/Doom%20Eternal%20OST%20-%20The%20Only%20Thing%20They%20Fear%20Is%20You%20(Mick%20Gordon)%20%5BDoom%20Eternal%20Theme%5D.mp3",
teto: "https://raw.githubusercontent.com/Mathmaticians/audio6/main/%E3%82%8D%E3%82%93%20-%E3%81%8A%E3%81%A1%E3%82%83%E3%82%81%E6%A9%9F%E8%83%BDFUKKIRETA%20HD.256k.Kara.mp3",
kfp: "https://raw.githubusercontent.com/Mathmaticians/audio7/main/Hans%20Zimmer%20-%20Oogway%20Ascends%20Kung%20Fu%20Panda%20Soundtrack.mp3",
custom: null,
};
function hexToRgb(hex) {
return [parseInt(hex.slice(1,3),16), parseInt(hex.slice(3,5),16), parseInt(hex.slice(5,7),16)];
}
const PS_UID_CACHE_KEY = 'ps_cached_uid_v1';
function cacheUid(uid) { if (uid) try { localStorage.setItem(PS_UID_CACHE_KEY, uid); } catch(e) {} }
function getCachedUid() { return localStorage.getItem(PS_UID_CACHE_KEY) || null; }
function buildCustomTheme(hex) {
const [r,g,b] = hexToRgb(hex);
const d = (n,a) => Math.max(0, Math.min(255, n+a));
return {
primary:`rgba(${r},${g},${b},0.75)`, primaryHover:`rgba(${r},${g},${b},1)`,
primarySolid:`rgba(${r},${g},${b},0.85)`, primarySolidHover:`rgba(${r},${g},${b},0.92)`,
header:`rgba(${d(r,-30)},${d(g,-30)},${d(b,-30)},0.95)`,
settingBtn:`rgba(${d(r,-30)},${d(g,-30)},${d(b,-30)},0.85)`,
settingBtnHover:`rgba(${d(r,-30)},${d(g,-30)},${d(b,-30)},1)`,
imageBtn:`rgba(${d(r,-30)},${d(g,-30)},${d(b,-30)},0.85)`,
imageBtnHover:`rgba(${d(r,-30)},${d(g,-30)},${d(b,-30)},1)`,
imageBtnSelected:`rgba(${d(r,-60)},${d(g,-60)},${d(b,-60)},0.95)`,
nameConfirm:`rgba(${r},${g},${b},0.9)`, nameConfirmHover:`rgba(${r},${g},${b},1)`,
toast:`rgba(${r},${g},${b},0.92)`, uiBg:`rgba(${r},${g},${b},0.45)`,
hackBtn:`rgba(${d(r,20)},${d(g,20)},${d(b,20)},0.82)`,
hackBtnHover:`rgba(${d(r,35)},${d(g,35)},${d(b,35)},1)`,
hackBox:`rgba(${d(r,-20)},${d(g,-20)},${d(b,-20)},0.88)`,
hackHeader:`rgba(${d(r,-50)},${d(g,-50)},${d(b,-50)},0.95)`,
hackItemBtn:`rgba(${d(r,-10)},${d(g,-10)},${d(b,-10)},0.85)`,
hackItemBtnHover:`rgba(${d(r,20)},${d(g,20)},${d(b,20)},1)`,
};
}
const MIKU_THEME = {primary:"rgba(255,182,213,0.75)",primaryHover:"rgba(255,182,213,1)",primarySolid:"rgba(255,182,213,0.85)",primarySolidHover:"rgba(255,182,213,0.92)",header:"rgba(255,140,180,0.9)",settingBtn:"rgba(255,140,180,0.85)",settingBtnHover:"rgba(255,140,180,1)",imageBtn:"rgba(255,140,180,0.85)",imageBtnHover:"rgba(255,140,180,1)",imageBtnSelected:"rgba(200,80,130,0.95)",nameConfirm:"rgba(255,182,213,0.9)",nameConfirmHover:"rgba(255,182,213,1)",toast:"rgba(255,182,213,0.92)",uiBg:"rgba(255,182,213,0.45)",hackBtn:"rgba(180,130,240,0.82)",hackBtnHover:"rgba(190,145,255,1)",hackBox:"rgba(160,110,220,0.88)",hackHeader:"rgba(130,80,200,0.95)",hackItemBtn:"rgba(150,100,215,0.85)",hackItemBtnHover:"rgba(165,115,230,1)"};
const HK_THEME = {primary:"rgba(20,50,100,0.85)",primaryHover:"rgba(20,50,100,1)",primarySolid:"rgba(20,50,100,0.9)",primarySolidHover:"rgba(20,50,100,0.97)",header:"rgba(10,30,70,0.95)",settingBtn:"rgba(10,30,70,0.85)",settingBtnHover:"rgba(10,30,70,1)",imageBtn:"rgba(10,30,70,0.85)",imageBtnHover:"rgba(10,30,70,1)",imageBtnSelected:"rgba(5,15,50,0.95)",nameConfirm:"rgba(20,50,100,0.9)",nameConfirmHover:"rgba(20,50,100,1)",toast:"rgba(20,50,100,0.92)",uiBg:"rgba(20,50,100,0.45)",hackBtn:"rgba(0,120,130,0.82)",hackBtnHover:"rgba(0,145,158,1)",hackBox:"rgba(0,100,110,0.88)",hackHeader:"rgba(0,75,85,0.95)",hackItemBtn:"rgba(0,110,120,0.85)",hackItemBtnHover:"rgba(0,135,148,1)"};
const SPACE_THEME = {primary:"rgba(60,30,80,0.85)",primaryHover:"rgba(60,30,80,1)",primarySolid:"rgba(50,20,70,0.9)",primarySolidHover:"rgba(50,20,70,0.97)",header:"rgba(35,10,55,0.95)",settingBtn:"rgba(35,10,55,0.85)",settingBtnHover:"rgba(35,10,55,1)",imageBtn:"rgba(35,10,55,0.85)",imageBtnHover:"rgba(35,10,55,1)",imageBtnSelected:"rgba(15,5,30,0.95)",nameConfirm:"rgba(60,30,80,0.9)",nameConfirmHover:"rgba(60,30,80,1)",toast:"rgba(50,20,70,0.92)",uiBg:"rgba(50,20,70,0.45)",hackBtn:"rgba(20,50,140,0.82)",hackBtnHover:"rgba(30,65,170,1)",hackBox:"rgba(15,40,120,0.88)",hackHeader:"rgba(10,28,90,0.95)",hackItemBtn:"rgba(18,45,130,0.85)",hackItemBtnHover:"rgba(28,60,160,1)"};
const GOJO_THEME = {primary:"rgba(80,10,120,0.85)",primaryHover:"rgba(80,10,120,1)",primarySolid:"rgba(70,5,110,0.9)",primarySolidHover:"rgba(70,5,110,0.97)",header:"rgba(50,0,90,0.95)",settingBtn:"rgba(50,0,90,0.85)",settingBtnHover:"rgba(50,0,90,1)",imageBtn:"rgba(50,0,90,0.85)",imageBtnHover:"rgba(50,0,90,1)",imageBtnSelected:"rgba(25,0,55,0.95)",nameConfirm:"rgba(80,10,120,0.9)",nameConfirmHover:"rgba(80,10,120,1)",toast:"rgba(70,5,110,0.92)",uiBg:"rgba(70,5,110,0.45)",hackBtn:"rgba(150,0,100,0.82)",hackBtnHover:"rgba(180,0,120,1)",hackBox:"rgba(130,0,85,0.88)",hackHeader:"rgba(100,0,65,0.95)",hackItemBtn:"rgba(140,0,92,0.85)",hackItemBtnHover:"rgba(168,0,110,1)"};
const DOOM_THEME = {primary:"rgba(180,20,0,0.85)",primaryHover:"rgba(200,30,0,1)",primarySolid:"rgba(160,15,0,0.9)",primarySolidHover:"rgba(140,10,0,0.97)",header:"rgba(120,8,0,0.95)",settingBtn:"rgba(120,8,0,0.85)",settingBtnHover:"rgba(140,10,0,1)",imageBtn:"rgba(120,8,0,0.85)",imageBtnHover:"rgba(140,10,0,1)",imageBtnSelected:"rgba(80,5,0,0.95)",nameConfirm:"rgba(180,20,0,0.9)",nameConfirmHover:"rgba(200,30,0,1)",toast:"rgba(160,15,0,0.92)",uiBg:"rgba(160,15,0,0.45)",hackBtn:"rgba(200,100,0,0.82)",hackBtnHover:"rgba(225,120,0,1)",hackBox:"rgba(175,80,0,0.88)",hackHeader:"rgba(145,60,0,0.95)",hackItemBtn:"rgba(185,88,0,0.85)",hackItemBtnHover:"rgba(212,108,0,1)"};
const TETO_THEME = {primary:"rgba(190,50,45,0.85)",primaryHover:"rgba(210,65,55,1)",primarySolid:"rgba(170,40,35,0.9)",primarySolidHover:"rgba(155,30,25,0.97)",header:"rgba(130,20,15,0.95)",settingBtn:"rgba(130,20,15,0.85)",settingBtnHover:"rgba(150,30,20,1)",imageBtn:"rgba(130,20,15,0.85)",imageBtnHover:"rgba(150,30,20,1)",imageBtnSelected:"rgba(90,10,5,0.95)",nameConfirm:"rgba(190,50,45,0.9)",nameConfirmHover:"rgba(210,65,55,1)",toast:"rgba(170,40,35,0.92)",uiBg:"rgba(170,40,35,0.45)",hackBtn:"rgba(195,140,0,0.82)",hackBtnHover:"rgba(220,160,0,1)",hackBox:"rgba(170,118,0,0.88)",hackHeader:"rgba(140,95,0,0.95)",hackItemBtn:"rgba(180,128,0,0.85)",hackItemBtnHover:"rgba(208,150,0,1)"};
const KFP_THEME = {primary:"rgba(50,120,20,0.85)",primaryHover:"rgba(60,140,25,1)",primarySolid:"rgba(40,100,15,0.9)",primarySolidHover:"rgba(35,90,10,0.97)",header:"rgba(25,70,5,0.95)",settingBtn:"rgba(25,70,5,0.85)",settingBtnHover:"rgba(30,80,8,1)",imageBtn:"rgba(25,70,5,0.85)",imageBtnHover:"rgba(30,80,8,1)",imageBtnSelected:"rgba(10,40,0,0.95)",nameConfirm:"rgba(50,120,20,0.9)",nameConfirmHover:"rgba(60,140,25,1)",toast:"rgba(40,100,15,0.92)",uiBg:"rgba(40,100,15,0.45)",hackBtn:"rgba(180,140,0,0.82)",hackBtnHover:"rgba(210,165,0,1)",hackBox:"rgba(155,118,0,0.88)",hackHeader:"rgba(125,95,0,0.95)",hackItemBtn:"rgba(165,128,0,0.85)",hackItemBtnHover:"rgba(195,152,0,1)"};
function getTheme(){if(currentTheme==='custom')return buildCustomTheme(customThemeData.primaryColor);if(currentTheme==='hk')return HK_THEME;if(currentTheme==='space')return SPACE_THEME;if(currentTheme==='gojo')return GOJO_THEME;if(currentTheme==='doom')return DOOM_THEME;if(currentTheme==='teto')return TETO_THEME;if(currentTheme==='kfp')return KFP_THEME;return MIKU_THEME;}
const THEME_BORDER={miku:{border:"2px solid rgba(255,182,213,0.9)",shadow:"0 0 24px 6px rgba(255,140,180,0.7), 0 8px 32px rgba(0,0,0,0.4)"},hk:{border:"2px solid rgba(60,120,220,0.9)",shadow:"0 0 24px 6px rgba(20,80,200,0.7), 0 8px 32px rgba(0,0,0,0.5)"},space:{border:"2px solid rgba(160,80,220,0.9)",shadow:"0 0 24px 6px rgba(100,30,160,0.7), 0 8px 32px rgba(0,0,0,0.6)"},gojo:{border:"2px solid rgba(180,60,255,0.9)",shadow:"0 0 24px 6px rgba(130,0,220,0.75), 0 8px 32px rgba(0,0,0,0.5)"},doom:{border:"2px solid rgba(255,60,0,0.9)",shadow:"0 0 28px 8px rgba(220,40,0,0.8), 0 8px 32px rgba(0,0,0,0.5)"},teto:{border:"2px solid rgba(220,60,60,0.9)",shadow:"0 0 24px 6px rgba(180,30,30,0.75), 0 8px 32px rgba(0,0,0,0.45)"},kfp:{border:"2px solid rgba(100,200,80,0.9)",shadow:"0 0 24px 6px rgba(60,160,40,0.7), 0 8px 32px rgba(0,0,0,0.5)"}};
const THEME_USERNAME_COLOR={miku:"#1a1a1a",hk:"#e8f0ff",space:"#f0e8ff",gojo:"#ffffff",doom:"#ffffff",teto:"#ffffff",kfp:"#fff8e0",custom:"#ffffff"};
const THEME_CAPTION_COLOR={miku:{color:"#b5005a",shadow:"0 1px 4px rgba(255,182,213,0.6)"},hk:{color:"#a8c8ff",shadow:"0 1px 6px rgba(0,0,0,0.8)"},space:{color:"#d4a8ff",shadow:"0 1px 6px rgba(0,0,0,0.9)"},gojo:{color:"#e8aaff",shadow:"0 1px 6px rgba(0,0,0,0.9)"},doom:{color:"#ff6030",shadow:"0 1px 8px rgba(0,0,0,0.95), 0 0 12px rgba(255,60,0,0.5)"},teto:{color:"#ffb8b0",shadow:"0 1px 6px rgba(0,0,0,0.85)"},kfp:{color:"#ffe066",shadow:"0 1px 6px rgba(0,0,0,0.8)"}};
const THEME_GAME_BTNS={miku:{play:"rgba(255,200,225,0.92)",other:"rgba(220,130,170,0.88)"},hk:{play:"rgba(60,100,190,0.92)",other:"rgba(15,40,100,0.88)"},space:{play:"rgba(110,60,150,0.92)",other:"rgba(45,15,75,0.88)"},gojo:{play:"rgba(140,40,200,0.92)",other:"rgba(60,0,100,0.88)"},doom:{play:"rgba(230,60,10,0.92)",other:"rgba(120,10,0,0.88)"},teto:{play:"rgba(220,90,80,0.92)",other:"rgba(130,30,20,0.88)"},kfp:{play:"rgba(100,180,60,0.92)",other:"rgba(40,100,20,0.88)"}};
const THEME_TEAM_HOVER={miku:"rgba(255,182,213,0.55)",hk:"rgba(60,120,220,0.45)",space:"rgba(160,80,220,0.45)",gojo:"rgba(180,60,255,0.45)",doom:"rgba(255,60,0,0.45)",teto:"rgba(220,60,60,0.45)",kfp:"rgba(100,200,80,0.45)"};
const THEME_ESP_PLAYER_COLOR={miku:0xFF82C8,hk:0x3C78DC,space:0xA050DC,gojo:0xCC44FF,doom:0xFF5522,teto:0xFF6666,kfp:0x66CC44};
function getThemeBorder(){if(currentTheme==='custom'){const[r,g,b]=hexToRgb(customThemeData.primaryColor);return{border:`2px solid rgba(${r},${g},${b},0.9)`,shadow:`0 0 24px 6px rgba(${r},${g},${b},0.7), 0 8px 32px rgba(0,0,0,0.5)`};}return THEME_BORDER[currentTheme]||THEME_BORDER.miku;}
function getThemeCaptionColor(){if(currentTheme==='custom')return{color:customThemeData.primaryColor,shadow:'0 1px 6px rgba(0,0,0,0.8)'};return THEME_CAPTION_COLOR[currentTheme]||THEME_CAPTION_COLOR.miku;}
function getThemeGameBtns(){if(currentTheme==='custom'){const[r,g,b]=hexToRgb(customThemeData.primaryColor);const d=(n,a)=>Math.max(0,Math.min(255,n+a));return{play:`rgba(${r},${g},${b},0.92)`,other:`rgba(${d(r,-40)},${d(g,-40)},${d(b,-40)},0.88)`};}return THEME_GAME_BTNS[currentTheme]||THEME_GAME_BTNS.miku;}
function getTeamHoverColor(){if(currentTheme==='custom'){const[r,g,b]=hexToRgb(customThemeData.primaryColor);return`rgba(${r},${g},${b},0.45)`;}return THEME_TEAM_HOVER[currentTheme]||THEME_TEAM_HOVER.miku;}
function getEspPlayerColor(){if(currentTheme==='custom')return parseInt(customThemeData.primaryColor.slice(1),16);return THEME_ESP_PLAYER_COLOR[currentTheme]||0x00FF00;}
function removeJunonAds(){["#junon_ad_square","#junon_ad_square_1","#junon_ad_square_2",".junon_ad_square",".junon_ad_square_1",".junon_ad_square_2","[id*='junon_ad']","[class*='junon_ad']"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>el.remove());});}
removeJunonAds();
new MutationObserver(removeJunonAds).observe(document.body,{childList:true,subtree:true});
function applyGameButtonColors(){if(originalMode)return;const c=getThemeGameBtns();const pb=document.getElementById("default_play_btn")||document.querySelector(".default_play_btn");const tb=document.getElementById("tutorial_menu_btn")||document.querySelector(".tutorial_menu_btn");const sb=document.getElementById("settings_menu_btn")||document.querySelector(".settings_menu_btn");if(pb){pb.style.backgroundColor=c.play;pb.style.color=currentTheme==='miku'?"#3a0020":"#ffffff";pb.style.borderRadius="10px";pb.style.border="none";}[tb,sb].forEach(b=>{if(!b)return;b.style.backgroundColor=c.other;b.style.color="#ffffff";b.style.borderRadius="10px";b.style.border="none";});}
function applyMiddleContainerStyle(){if(originalMode)return;const el=document.querySelector(".middle_container");if(!el)return;if(borderEnabled){const b=getThemeBorder();el.style.border=b.border;el.style.boxShadow=b.shadow;}else{el.style.border="none";el.style.boxShadow="none";}}
function applyUsernameColor(){if(originalMode)return;const color=THEME_USERNAME_COLOR[currentTheme]||"#ffffff";["#username","div#username",".username",".player_name",".home_username","#home_username",".user_name_text"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>{el.style.color=color;el.style.textShadow=currentTheme==='miku'?"0 1px 3px rgba(255,255,255,0.5)":"0 1px 4px rgba(0,0,0,0.8)";});});}
function applyCaptionStyle(){if(originalMode)return;const caption=document.getElementById("game_caption");if(!caption)return;const c=getThemeCaptionColor();caption.style.background="none";caption.style.backgroundClip="unset";caption.style.webkitBackgroundClip="unset";caption.style.color=c.color;caption.style.webkitTextFillColor=c.color;caption.style.textShadow=c.shadow;caption.style.fontWeight="bold";}
const teamHoverStyle=document.createElement("style");teamHoverStyle.id="ps_team_hover_style";document.head.appendChild(teamHoverStyle);
function applyTeamHoverStyle(){if(originalMode){teamHoverStyle.textContent="";return;}const color=getTeamHoverColor();teamHoverStyle.textContent=`.team_entry_row:hover,.team_entry_row.online:hover{background-color:${color}!important;transition:background-color 0.2s ease!important;}`;document.querySelectorAll(".team_entry_row").forEach(el=>{if(el._psPatchedHover)return;el._psPatchedHover=true;el.addEventListener("mouseenter",()=>{if(!originalMode)el.style.backgroundColor=getTeamHoverColor();});el.addEventListener("mouseleave",()=>{el.style.backgroundColor="";});});}
function applyFooterStyle(){if(originalMode)return;const t=getTheme();[".home_footer",".footer_container",".home_bottom_bar","#home_footer",".bottom_bar"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>{el.style.backgroundColor=t.uiBg;el.style.borderRadius="12px";el.style.backdropFilter="blur(4px)";});});}
function applyChatAndTimeUI(){if(originalMode)return;const t=getTheme();[".chat_history",".local_chat_history","#chat_history","#local_chat_history",".chat_history.selected",".local_chat_history.selected",".chat_tab",".chat_tab.selected",".chat_container",".local_chat_container"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>{el.style.backgroundColor=t.uiBg;el.style.backdropFilter="blur(4px)";el.style.borderRadius="8px";el.style.border=`1px solid ${t.primary.replace(/[\d.]+\)$/,"0.4)")}`;el.style.color="#ffffff";});});[".time_label","#time_label",".game_time",".clock_label","#game_time"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>{el.style.backgroundColor=t.uiBg;el.style.backdropFilter="blur(4px)";el.style.borderRadius="8px";el.style.padding="3px 10px";el.style.color="#ffffff";el.style.fontWeight="bold";el.style.border=`1px solid ${t.primary.replace(/[\d.]+\)$/,"0.35)")}`;});});}
const audioPlayer=new Audio();audioPlayer.loop=true;audioPlayer.volume=0.5;
let musicEnabled=true;
function playThemeAudio(theme){if(theme==='custom'){const url=customThemeData.musicBlobURL||customThemeData.musicDataURL;if(!url){stopMusic();return;}audioPlayer.pause();audioPlayer.src=url;audioPlayer.currentTime=0;audioPlayer.play().catch(()=>{});return;}const url=THEME_AUDIO[theme];if(!url){stopMusic();return;}audioPlayer.pause();audioPlayer.src=url;audioPlayer.currentTime=0;audioPlayer.play().catch(()=>{});}
function stopMusic(){audioPlayer.pause();updateMusicSettingBtn();}
function updateMusicSettingBtn(){const b=document.getElementById("ps_music_toggle_btn");if(!b)return;b.textContent=musicEnabled?"Music: ON":"Music: OFF";b.style.backgroundColor=musicEnabled?"rgba(80,200,120,0.85)":"rgba(120,60,60,0.85)";}
function updateBorderBtn(){const b=document.getElementById("ps_border_toggle_btn");if(!b)return;b.textContent=borderEnabled?"Border: ON":"Border: OFF";b.style.backgroundColor=borderEnabled?"rgba(80,200,120,0.85)":"rgba(120,60,60,0.85)";}
function updateExtraEffectsBtn(){const b=document.getElementById("ps_extra_effects_btn");if(!b)return;b.textContent=extraEffectsEnabled?"Extra: ON":"Extra: OFF";b.style.backgroundColor=extraEffectsEnabled?"rgba(80,200,120,0.85)":"rgba(120,60,60,0.85)";}
function updateOriginalBtn(){const b=document.getElementById("ps_original_btn");if(!b)return;b.textContent=originalMode?"Original: ON":"Original: OFF";b.style.backgroundColor=originalMode?"rgba(80,200,120,0.85)":"rgba(120,60,60,0.85)";}
function updateBubbleSettingBtn(){const b=document.getElementById("ps_bubble_toggle_btn");if(!b)return;b.textContent=bubblesEnabled?"Bubbles: ON":"Bubbles: OFF";b.style.backgroundColor=bubblesEnabled?"rgba(80,200,120,0.85)":"rgba(120,60,60,0.85)";}
let autoplayArmed=true;
function tryAutoplay(){if(!autoplayArmed)return;autoplayArmed=false;if(musicEnabled&&(THEME_AUDIO[currentTheme]||currentTheme==='custom'))playThemeAudio(currentTheme);document.removeEventListener("click",tryAutoplay);document.removeEventListener("keydown",tryAutoplay);}
document.addEventListener("click",tryAutoplay);document.addEventListener("keydown",tryAutoplay);
function onWorldJoined(){if(hasJoinedWorld)return;hasJoinedWorld=true;stopMusic();stopAllExtraEffects();silenceGameAudio();}
function silenceGameAudio(){document.querySelectorAll("audio,video").forEach(el=>{if(el!==audioPlayer){el.muted=true;el.volume=0;}});new MutationObserver(()=>{document.querySelectorAll("audio,video").forEach(el=>{if(el!==audioPlayer&&!el._psSilenced){el._psSilenced=true;el.muted=true;el.volume=0;}});}).observe(document.body,{childList:true,subtree:true});}
function triggerGojoTransition(onComplete){
if(musicEnabled)playThemeAudio('gojo');
const canvas=document.createElement("canvas");canvas.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:99999;";document.body.appendChild(canvas);canvas.width=window.innerWidth;canvas.height=window.innerHeight;const ctx=canvas.getContext("2d");const W=canvas.width,H=canvas.height,cx=W/2,cy=H/2;const DURATION=3200;let startTime=null,completeFired=false;const stars=Array.from({length:220},()=>({x:Math.random()*W,y:Math.random()*H,r:0.5+Math.random()*1.5,a:Math.random()}));
function easeInOut(t){return t<0.5?2*t*t:-1+(4-2*t)*t;}function clamp(v,a,b){return Math.max(a,Math.min(b,v));}
function frame(ts){if(!startTime)startTime=ts;const elapsed=ts-startTime,prog=Math.min(elapsed/DURATION,1);ctx.clearRect(0,0,W,H);if(prog<0.15){const t=prog/0.15,ba=clamp(t*2,0,1);ctx.fillStyle=`rgba(10,0,20,${clamp(t*0.95,0,0.95)})`;ctx.fillRect(0,0,W,H);stars.forEach(s=>{ctx.globalAlpha=s.a*clamp(t*1.5,0,1);ctx.fillStyle="#ccaaff";ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fill();});ctx.globalAlpha=1;const bl=t*cx*1.1,bw=8+t*22;for(let i=3;i>=0;i--){const sp=bw*(1+i*0.7);ctx.save();ctx.globalAlpha=ba*(0.5-i*0.12);ctx.shadowColor="#4466ff";ctx.shadowBlur=30+i*20;const g=ctx.createLinearGradient(0,0,bl,0);g.addColorStop(0,"rgba(40,80,255,0)");g.addColorStop(0.5,"rgba(80,120,255,0.9)");g.addColorStop(1,"rgba(140,80,255,1)");ctx.fillStyle=g;ctx.fillRect(0,cy-sp/2,bl,sp);ctx.restore();}const rl=t*(W-cx)*1.1,rw=8+t*22;for(let i=3;i>=0;i--){const sp=rw*(1+i*0.7);ctx.save();ctx.globalAlpha=ba*(0.5-i*0.12);ctx.shadowColor="#cc44ff";ctx.shadowBlur=30+i*20;const g=ctx.createLinearGradient(W,0,W-rl,0);g.addColorStop(0,"rgba(160,40,255,0)");g.addColorStop(0.5,"rgba(200,60,255,0.9)");g.addColorStop(1,"rgba(140,80,255,1)");ctx.fillStyle=g;ctx.fillRect(W-rl,cy-sp/2,rl,sp);ctx.restore();}}else if(prog<0.45){const t=(prog-0.15)/0.30;ctx.fillStyle="rgba(5,0,15,0.97)";ctx.fillRect(0,0,W,H);stars.forEach(s=>{ctx.globalAlpha=s.a*0.6;ctx.fillStyle="#ccaaff";ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fill();});ctx.globalAlpha=1;if(t<0.25){const ft=t/0.25,fr=ft*120;const fg=ctx.createRadialGradient(cx,cy,0,cx,cy,fr);fg.addColorStop(0,`rgba(255,255,255,${(1-ft)*0.9})`);fg.addColorStop(0.3,`rgba(200,100,255,${(1-ft)*0.8})`);fg.addColorStop(1,"rgba(100,0,200,0)");ctx.fillStyle=fg;ctx.beginPath();ctx.arc(cx,cy,fr,0,Math.PI*2);ctx.fill();}const oR=easeInOut(t)*Math.max(W,H)*0.65,rW=18+t*40;for(let i=4;i>=0;i--){const gR=oR+i*28;const gO=ctx.createRadialGradient(cx,cy,Math.max(0,gR-rW-i*12),cx,cy,gR+i*18);gO.addColorStop(0,"rgba(120,0,255,0)");gO.addColorStop(0.5,`rgba(160,40,255,${0.15-i*0.025})`);gO.addColorStop(1,"rgba(80,0,180,0)");ctx.fillStyle=gO;ctx.beginPath();ctx.arc(cx,cy,gR+i*18,0,Math.PI*2);ctx.fill();}ctx.save();ctx.shadowColor="#aa00ff";ctx.shadowBlur=60;const rG=ctx.createRadialGradient(cx,cy,Math.max(0,oR-rW),cx,cy,oR+rW);rG.addColorStop(0,"rgba(60,0,140,0)");rG.addColorStop(0.35,"rgba(140,20,255,0.9)");rG.addColorStop(0.5,"rgba(200,60,255,1)");rG.addColorStop(0.65,"rgba(140,20,255,0.9)");rG.addColorStop(1,"rgba(60,0,140,0)");ctx.fillStyle=rG;ctx.beginPath();ctx.arc(cx,cy,oR+rW,0,Math.PI*2);ctx.arc(cx,cy,Math.max(0,oR-rW),0,Math.PI*2,true);ctx.fill();ctx.restore();}else if(prog<0.75){const t=(prog-0.45)/0.30;ctx.fillStyle=`rgba(8,0,20,${0.92+t*0.06})`;ctx.fillRect(0,0,W,H);const bH=H*(0.08+t*0.55),bA=clamp((1-t)*1.2,0,1);const bC=ctx.createLinearGradient(0,cy-bH/2,0,cy+bH/2);bC.addColorStop(0,"rgba(30,0,80,0)");bC.addColorStop(0.2,"rgba(140,0,255,0.7)");bC.addColorStop(0.45,"rgba(220,100,255,1)");bC.addColorStop(0.5,"rgba(255,200,255,1)");bC.addColorStop(0.55,"rgba(220,100,255,1)");bC.addColorStop(0.8,"rgba(140,0,255,0.7)");bC.addColorStop(1,"rgba(30,0,80,0)");ctx.save();ctx.globalAlpha=bA*0.92;ctx.shadowColor="#cc00ff";ctx.shadowBlur=80;ctx.fillStyle=bC;ctx.fillRect(0,cy-bH/2,W,bH);ctx.restore();for(let i=0;i<8;i++){const ang=(i/8)*Math.PI*2+t*Math.PI*0.4,tL=80+t*180;const tx=cx+Math.cos(ang)*tL*(1+t),ty=cy+Math.sin(ang)*tL*0.3;const tg=ctx.createRadialGradient(cx,cy,0,tx,ty,40+t*60);tg.addColorStop(0,`rgba(200,60,255,${0.4*bA})`);tg.addColorStop(1,"rgba(80,0,180,0)");ctx.fillStyle=tg;ctx.beginPath();ctx.arc(tx,ty,40+t*60,0,Math.PI*2);ctx.fill();}const oR=(0.2+t*0.3)*Math.min(W,H);const og=ctx.createRadialGradient(cx,cy,oR*0.45,cx,cy,oR);og.addColorStop(0,"rgba(80,0,180,0)");og.addColorStop(0.2,`rgba(130,0,255,${0.5*bA})`);og.addColorStop(0.6,`rgba(200,80,255,${0.7*bA})`);og.addColorStop(1,"rgba(60,0,140,0)");ctx.fillStyle=og;ctx.beginPath();ctx.arc(cx,cy,oR,0,Math.PI*2);ctx.fill();}else{const t=(prog-0.75)/0.25,fA=clamp(1-easeInOut(t),0,1);ctx.fillStyle=`rgba(8,0,20,${fA*0.95})`;ctx.fillRect(0,0,W,H);if(fA>0.1){const lg=ctx.createRadialGradient(cx,cy,0,cx,cy,Math.min(W,H)*0.5);lg.addColorStop(0,`rgba(160,0,255,${fA*0.4})`);lg.addColorStop(0.5,`rgba(100,0,200,${fA*0.2})`);lg.addColorStop(1,"rgba(40,0,100,0)");ctx.fillStyle=lg;ctx.fillRect(0,0,W,H);}}if(!completeFired&&prog>=0.88){completeFired=true;if(onComplete)onComplete();}if(prog<1)requestAnimationFrame(frame);else canvas.remove();}
requestAnimationFrame(frame);
}
function triggerSpaceTransition(onComplete){
if(musicEnabled)playThemeAudio('space');
const canvas=document.createElement("canvas");canvas.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:99999;";document.body.appendChild(canvas);canvas.width=window.innerWidth;canvas.height=window.innerHeight;const ctx=canvas.getContext("2d");const W=canvas.width,H=canvas.height,cx=W/2,cy=H/2;const DURATION=4000;let startTime=null,completeFired=false;
function easeIn(t){return t*t*t;}function easeOut(t){return 1-Math.pow(1-t,3);}function easeInOut(t){return t<0.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2;}function clamp(v,a,b){return Math.max(a,Math.min(b,v));}function lerp(a,b,t){return a+(b-a)*t;}
const bgStars=Array.from({length:350},()=>({x:Math.random()*W,y:Math.random()*H,r:0.3+Math.random()*1.8,a:0.3+Math.random()*0.7,twinkleSpeed:1+Math.random()*3,twinklePhase:Math.random()*Math.PI*2}));
const rings=Array.from({length:6},(_,i)=>({delay:i*0.035,color1:["rgba(255,220,80,1)","rgba(255,160,40,1)","rgba(255,100,40,0.9)","rgba(200,80,255,0.85)","rgba(100,80,255,0.7)","rgba(60,100,255,0.55)"][i],color2:["rgba(255,180,0,0)","rgba(255,100,0,0)","rgba(200,60,0,0)","rgba(160,0,200,0)","rgba(60,0,180,0)","rgba(20,40,180,0)"][i],speed:1.1-i*0.06}));
const debris=Array.from({length:120},()=>{const angle=Math.random()*Math.PI*2,speed=2+Math.random()*8;return{x:cx,y:cy,vx:Math.cos(angle)*speed,vy:Math.sin(angle)*speed,r:1+Math.random()*3,life:0.6+Math.random()*0.4,color:["#ffee88","#ffaa44","#ff6622","#ff88ff","#aaaaff"][Math.floor(Math.random()*5)],born:false};});
const wisps=Array.from({length:18},(_,i)=>{const angle=(i/18)*Math.PI*2+Math.random()*0.5,dist=80+Math.random()*180;return{tx:cx+Math.cos(angle)*dist,ty:cy+Math.sin(angle)*dist,r:120+Math.random()*180,color:["rgba(60,40,180,","rgba(100,20,160,","rgba(40,80,200,","rgba(80,20,140,","rgba(140,60,200,"][Math.floor(Math.random()*5)]};});
function drawBgStars(alpha,elapsed){bgStars.forEach(s=>{const tw=0.5+0.5*Math.sin(elapsed/1000*s.twinkleSpeed+s.twinklePhase);ctx.globalAlpha=s.a*tw*alpha;ctx.fillStyle="#ffffff";ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fill();});ctx.globalAlpha=1;}
function frame(ts){if(!startTime)startTime=ts;const elapsed=ts-startTime,prog=Math.min(elapsed/DURATION,1);ctx.clearRect(0,0,W,H);if(prog<0.20){const t=prog/0.20;ctx.fillStyle="#000005";ctx.fillRect(0,0,W,H);drawBgStars(clamp(t*2,0,1),elapsed);const pulse=0.85+0.15*Math.sin(elapsed/300),starR=(22+t*18)*pulse;for(let i=4;i>=0;i--){const gr=ctx.createRadialGradient(cx,cy,0,cx,cy,starR*(1+i*0.8));gr.addColorStop(0,`rgba(255,255,200,${0.95-i*0.18})`);gr.addColorStop(0.3,`rgba(255,220,100,${0.6-i*0.12})`);gr.addColorStop(1,"rgba(255,120,0,0)");ctx.save();ctx.shadowColor="#ffdd80";ctx.shadowBlur=40+i*20;ctx.fillStyle=gr;ctx.beginPath();ctx.arc(cx,cy,starR*(1+i*0.8),0,Math.PI*2);ctx.fill();ctx.restore();}}else if(prog<0.40){const t=(prog-0.20)/0.20;ctx.fillStyle="#000005";ctx.fillRect(0,0,W,H);drawBgStars(0.8,elapsed);let sR;if(t<0.5)sR=40+easeOut(t/0.5)*60;else sR=lerp(100,3,easeIn((t-0.5)/0.5));if(t<0.5){const lA=t/0.5;for(let i=3;i>=0;i--){ctx.globalAlpha=lA*(0.25-i*0.05);ctx.strokeStyle="#ffcc44";ctx.lineWidth=1.5;ctx.beginPath();ctx.arc(cx,cy,sR*(1.8+i*0.6),0,Math.PI*2);ctx.stroke();}ctx.globalAlpha=1;}for(let i=3;i>=0;i--){const gr=ctx.createRadialGradient(cx,cy,0,cx,cy,sR*(1+i*0.6));gr.addColorStop(0,`rgba(255,255,220,${t<0.5?0.95:1})`);gr.addColorStop(0.4,`rgba(255,200,60,${0.7-i*0.15})`);gr.addColorStop(1,"rgba(255,80,0,0)");ctx.save();ctx.shadowColor="#ffcc00";ctx.shadowBlur=50;ctx.fillStyle=gr;ctx.beginPath();ctx.arc(cx,cy,sR*(1+i*0.6),0,Math.PI*2);ctx.fill();ctx.restore();}}else if(prog<0.45){const t=(prog-0.40)/0.05;ctx.fillStyle=`rgba(0,0,10,${0.95+t*0.05})`;ctx.fillRect(0,0,W,H);}else if(prog<0.65){const t=(prog-0.45)/0.20;ctx.fillStyle="#000008";ctx.fillRect(0,0,W,H);drawBgStars(0.5,elapsed);debris.forEach(d=>{d.born=true;const dx=cx+d.vx*t*800,dy=cy+d.vy*t*800;const da=clamp(d.life-t*0.8,0,1);if(da<=0)return;ctx.globalAlpha=da*0.9;ctx.fillStyle=d.color;ctx.shadowColor=d.color;ctx.shadowBlur=6;ctx.beginPath();ctx.arc(dx,dy,d.r,0,Math.PI*2);ctx.fill();});ctx.globalAlpha=1;ctx.shadowBlur=0;rings.forEach(ring=>{const rt=clamp((t-ring.delay)/(1-ring.delay),0,1);if(rt<=0)return;const maxR=Math.max(W,H)*0.95*ring.speed,r=easeOut(rt)*maxR,rW=(1-rt)*60+8,al=(1-rt*rt)*0.9;const rg=ctx.createRadialGradient(cx,cy,Math.max(0,r-rW),cx,cy,r+rW*0.3);rg.addColorStop(0,ring.color1.replace("1)",`${al})`));rg.addColorStop(0.6,ring.color1.replace("1)",`${al*0.5})`));rg.addColorStop(1,ring.color2);ctx.save();ctx.shadowColor=ring.color1;ctx.shadowBlur=40;ctx.fillStyle=rg;ctx.beginPath();ctx.arc(cx,cy,r+rW*0.3,0,Math.PI*2);if(r-rW>0)ctx.arc(cx,cy,r-rW,0,Math.PI*2,true);ctx.fill();ctx.restore();});}else if(prog<0.85){const t=(prog-0.65)/0.20;ctx.fillStyle="#000010";ctx.fillRect(0,0,W,H);drawBgStars(0.7+t*0.3,elapsed);wisps.forEach(w=>{const sc=0.4+easeOut(t)*0.8;const gr=ctx.createRadialGradient(lerp(cx,w.tx,t),lerp(cy,w.ty,t),0,lerp(cx,w.tx,t),lerp(cy,w.ty,t),w.r*sc);gr.addColorStop(0,w.color+`${easeOut(t)*0.45})`);gr.addColorStop(1,w.color+"0)");ctx.fillStyle=gr;ctx.beginPath();ctx.arc(lerp(cx,w.tx,t),lerp(cy,w.ty,t),w.r*sc,0,Math.PI*2);ctx.fill();});}else{const t=(prog-0.85)/0.15,al=clamp(1-easeInOut(t),0,1);ctx.fillStyle=`rgba(0,0,16,${al*0.97})`;ctx.fillRect(0,0,W,H);}if(!completeFired&&prog>=0.88){completeFired=true;if(onComplete)onComplete();}if(prog<1)requestAnimationFrame(frame);else canvas.remove();}
requestAnimationFrame(frame);
}
function triggerKFPTransition(onComplete){
if(musicEnabled)playThemeAudio('kfp');
const canvas=document.createElement("canvas");canvas.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:99999;";document.body.appendChild(canvas);canvas.width=window.innerWidth;canvas.height=window.innerHeight;const ctx=canvas.getContext("2d");const W=canvas.width,H=canvas.height,cx=W/2,cy=H/2;const DURATION=3800;let startTime=null,completeFired=false;
function easeOut(t){return 1-Math.pow(1-t,3);}function easeInOut(t){return t<0.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2;}function clamp(v,a,b){return Math.max(a,Math.min(b,v));}
function drawDumpling(x,y,w,rotation,alpha){ctx.save();ctx.globalAlpha=clamp(alpha,0,1);ctx.translate(x,y);ctx.rotate(rotation);const h=w*0.62;const g=ctx.createRadialGradient(-w*0.1,-h*0.2,0,0,0,w*0.7);g.addColorStop(0,"#fffde7");g.addColorStop(0.4,"#fff9c4");g.addColorStop(1,"#d4a030");ctx.fillStyle=g;ctx.beginPath();ctx.ellipse(0,h*0.08,w*0.48,h*0.4,0,0,Math.PI*2);ctx.fill();ctx.strokeStyle="#b8860b";ctx.lineWidth=Math.max(1,w*0.028);ctx.lineCap="round";ctx.beginPath();ctx.moveTo(-w*0.32,h*0.05);for(let i=0;i<8;i++){const px=-w*0.32+(i/7)*w*0.64;ctx.lineTo(px,i%2===0?-h*0.18:-h*0.32);}ctx.lineTo(w*0.32,h*0.05);ctx.stroke();ctx.restore();}
function drawLeaf(x,y,size,angle,alpha){ctx.save();ctx.globalAlpha=clamp(alpha,0,1);ctx.translate(x,y);ctx.rotate(angle);ctx.fillStyle="#4CAF50";ctx.strokeStyle="#2E7D32";ctx.lineWidth=1;ctx.beginPath();ctx.ellipse(0,0,size*0.22,size,0,0,Math.PI*2);ctx.fill();ctx.stroke();ctx.restore();}
const DC=22;const dumplings=Array.from({length:DC},(_,i)=>{const angle=(i/DC)*Math.PI*2,dist=Math.max(W,H)*0.82;return{sx:cx+Math.cos(angle)*dist,sy:cy+Math.sin(angle)*dist,rot:Math.random()*Math.PI*2,rotSpd:(Math.random()-0.5)*0.12,scale:0.55+Math.random()*0.8,delay:Math.random()*0.18};});
const leaves=Array.from({length:35},()=>({x:Math.random()*W,y:-30-Math.random()*200,vx:(Math.random()-0.5)*2.5,vy:1.2+Math.random()*2.5,angle:Math.random()*Math.PI*2,rotSpd:(Math.random()-0.5)*0.07,size:14+Math.random()*22}));
function frame(ts){if(!startTime)startTime=ts;const elapsed=ts-startTime,prog=Math.min(elapsed/DURATION,1);ctx.clearRect(0,0,W,H);if(prog<0.22){const t=prog/0.22;ctx.fillStyle=`rgba(22,14,2,${clamp(t*0.92,0,0.93)})`;ctx.fillRect(0,0,W,H);leaves.forEach(l=>{l.x+=l.vx;l.y+=l.vy;l.angle+=l.rotSpd;if(l.y>H+40){l.y=-30;l.x=Math.random()*W;}drawLeaf(l.x,l.y,l.size,l.angle,clamp(t*1.8,0,0.65));});dumplings.forEach(d=>{const dt=clamp((t-d.delay)/(1-d.delay),0,1);if(dt<=0)return;d.rot+=d.rotSpd;drawDumpling(d.sx+(cx-d.sx)*easeOut(dt)*0.42,d.sy+(cy-d.sy)*easeOut(dt)*0.42,44*d.scale,d.rot,dt*0.85);});}else if(prog<0.48){const t=(prog-0.22)/0.26;ctx.fillStyle="rgba(20,12,1,0.95)";ctx.fillRect(0,0,W,H);leaves.forEach(l=>{l.x+=l.vx*0.4;l.y+=l.vy*0.25;l.angle+=l.rotSpd;drawLeaf(l.x,l.y,l.size,l.angle,0.5*(1-t*0.7));});dumplings.forEach(d=>{d.rot+=d.rotSpd;const ex=0.42+easeInOut(t)*0.58;drawDumpling(d.sx+(cx-d.sx)*ex,d.sy+(cy-d.sy)*ex,(44+t*8)*d.scale,d.rot,0.9);});const bigW=easeOut(t)*150;if(bigW>4)drawDumpling(cx,cy,bigW,0,clamp(t*2.5,0,1));}else if(prog<0.68){const t=(prog-0.48)/0.20;ctx.fillStyle="rgba(18,10,0,0.92)";ctx.fillRect(0,0,W,H);dumplings.forEach(d=>{const ang=Math.atan2(d.sy-cy,d.sx-cx);d.rot+=d.rotSpd*3.5;const fly=easeOut(t)*Math.max(W,H)*0.65;drawDumpling(cx+Math.cos(ang)*fly,cy+Math.sin(ang)*fly,44*d.scale*(1+t*0.35),d.rot,clamp(1-t*2.2,0,1));});if(t>0.25){const ta=clamp((t-0.25)/0.28,0,1);const fs=Math.floor(72+t*22);ctx.save();ctx.globalAlpha=ta;ctx.font=`bold ${fs}px Impact, Arial Black`;ctx.textAlign="center";ctx.textBaseline="middle";ctx.strokeStyle="#5D2F00";ctx.lineWidth=10;ctx.strokeText("SKA-DOOSH!",cx,cy);ctx.fillStyle="#FFD700";ctx.fillText("SKA-DOOSH!",cx,cy);ctx.restore();}}else{const t=(prog-0.68)/0.32,a=clamp(1-easeInOut(t),0,1);ctx.fillStyle=`rgba(14,8,0,${a*0.97})`;ctx.fillRect(0,0,W,H);}if(!completeFired&&prog>=0.88){completeFired=true;if(onComplete)onComplete();}if(prog<1)requestAnimationFrame(frame);else canvas.remove();}
requestAnimationFrame(frame);
}
let bubblesEnabled=true,bubblesRunning=false,bubbleInterval=null,bubbleCanvas=null,bubbleCtx=null,bubbles=[],bubbleAnimFrame=null;
const BUBBLE_COLORS={miku:["rgba(100,210,255,0.7)","rgba(130,230,255,0.6)","rgba(80,190,240,0.65)","rgba(160,240,255,0.55)","rgba(60,170,220,0.7)","rgba(180,245,255,0.5)"],hk:["rgba(5,20,80,0.85)","rgba(10,30,100,0.8)","rgba(15,45,120,0.75)","rgba(8,25,90,0.9)","rgba(20,55,130,0.7)","rgba(3,15,70,0.88)"],space:["rgba(5,5,5,0.9)","rgba(15,15,15,0.85)","rgba(25,25,25,0.8)","rgba(10,10,10,0.9)","rgba(30,30,30,0.75)","rgba(0,0,0,0.92)"],gojo:["rgba(120,0,180,0.8)","rgba(90,0,150,0.75)","rgba(150,30,200,0.7)","rgba(70,0,130,0.85)","rgba(180,50,220,0.65)","rgba(100,0,160,0.8)"],doom:["rgba(255,30,0,0.85)","rgba(255,80,0,0.8)","rgba(255,120,0,0.75)","rgba(200,20,0,0.9)","rgba(255,160,0,0.7)","rgba(180,10,0,0.88)"],teto:["rgba(140,10,10,0.85)","rgba(165,25,15,0.8)","rgba(185,45,20,0.78)","rgba(200,65,25,0.75)","rgba(215,85,30,0.72)","rgba(225,110,40,0.7)"],kfp:["rgba(80,160,40,0.8)","rgba(60,130,20,0.75)","rgba(100,180,60,0.7)","rgba(200,170,0,0.75)","rgba(220,190,20,0.7)","rgba(40,110,10,0.85)"]};
function getBubbleColors(){if(currentTheme==='custom'){const[r,g,b]=hexToRgb(customThemeData.primaryColor);return[`rgba(${r},${g},${b},0.7)`,`rgba(${r},${g},${b},0.55)`,`rgba(${r},${g},${b},0.8)`];}return BUBBLE_COLORS[currentTheme]||BUBBLE_COLORS.miku;}
function createBubbleCanvas(){if(bubbleCanvas)return;bubbleCanvas=document.createElement("canvas");bubbleCanvas.id="ps_bubble_canvas";bubbleCanvas.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9999;";document.body.appendChild(bubbleCanvas);bubbleCtx=bubbleCanvas.getContext("2d");resizeBubbleCanvas();window.addEventListener("resize",resizeBubbleCanvas);}
function resizeBubbleCanvas(){if(bubbleCanvas){bubbleCanvas.width=window.innerWidth;bubbleCanvas.height=window.innerHeight;}}
function spawnBubble(){const colors=getBubbleColors(),isDoom=currentTheme==='doom',isTeto=currentTheme==='teto';const r=isTeto?(2+Math.random()*5):(3+Math.random()*10);bubbles.push({x:Math.random()*window.innerWidth,y:window.innerHeight+r+Math.random()*60,r,color:colors[Math.floor(Math.random()*colors.length)],vy:isDoom?-(2.5+Math.random()*3.5):-(1.5+Math.random()*2.5),vx:(Math.random()-0.5)*(isDoom?1.5:0.8),gravity:isDoom?0.06+Math.random()*0.04:0.04+Math.random()*0.03,isDoom,isTeto});}
function animateBubbles(){if(!bubbleCtx)return;bubbleCtx.clearRect(0,0,bubbleCanvas.width,bubbleCanvas.height);for(let i=bubbles.length-1;i>=0;i--){const b=bubbles[i];b.vy+=b.gravity;b.x+=b.vx;b.y+=b.vy;if(b.isDoom)b.vx+=(Math.random()-0.5)*0.3;if(b.y>window.innerHeight+b.r+20){bubbles.splice(i,1);continue;}if(b.isDoom){bubbleCtx.save();bubbleCtx.shadowColor=b.color;bubbleCtx.shadowBlur=12;bubbleCtx.beginPath();bubbleCtx.arc(b.x,b.y,b.r,0,Math.PI*2);bubbleCtx.fillStyle=b.color;bubbleCtx.fill();bubbleCtx.restore();}else{bubbleCtx.beginPath();bubbleCtx.arc(b.x,b.y,b.r,0,Math.PI*2);bubbleCtx.fillStyle=b.color;bubbleCtx.fill();if(!b.isTeto){bubbleCtx.beginPath();bubbleCtx.arc(b.x-b.r*0.3,b.y-b.r*0.3,b.r*0.25,0,Math.PI*2);bubbleCtx.fillStyle="rgba(255,255,255,0.25)";bubbleCtx.fill();}}}bubbleAnimFrame=requestAnimationFrame(animateBubbles);}
function startBubbles(){if(bubblesRunning)return;bubblesRunning=true;createBubbleCanvas();bubbles=[];for(let i=0;i<18;i++)spawnBubble();const sc=currentTheme==='doom'?4:2;bubbleInterval=setInterval(()=>{if(bubblesRunning){const c=sc+Math.floor(Math.random()*3);for(let i=0;i<c;i++)spawnBubble();}},currentTheme==='doom'?150:300);animateBubbles();}
function stopBubbles(){bubblesRunning=false;clearInterval(bubbleInterval);bubbleInterval=null;cancelAnimationFrame(bubbleAnimFrame);bubbleAnimFrame=null;if(bubbleCtx)bubbleCtx.clearRect(0,0,bubbleCanvas.width,bubbleCanvas.height);bubbles=[];}
let heartsRunning=false,heartsInterval=null,heartsCanvas=null,heartsCtx=null,hearts=[],heartsAnimFrame=null;
const HEART_COLORS={miku:["#ff6eb4","#ff85c0","#ffadd4","#ff4da6","#ff1493","rgba(255,105,180,0.9)","rgba(255,182,213,0.95)"],teto:["#ff4444","#ff6666","#ff2222","#cc0000","#ff8888","rgba(220,60,60,0.9)","rgba(255,100,100,0.95)"]};
function getHeartColors(){return HEART_COLORS[currentTheme]||HEART_COLORS.miku;}
function drawHeart(ctx,x,y,size,color,opacity){ctx.save();ctx.globalAlpha=opacity;ctx.fillStyle=color;ctx.shadowColor=color;ctx.shadowBlur=8;ctx.beginPath();ctx.moveTo(x,y+size*0.3);ctx.bezierCurveTo(x,y,x-size*0.5,y,x-size*0.5,y+size*0.3);ctx.bezierCurveTo(x-size*0.5,y+size*0.6,x,y+size*0.9,x,y+size);ctx.bezierCurveTo(x,y+size*0.9,x+size*0.5,y+size*0.6,x+size*0.5,y+size*0.3);ctx.bezierCurveTo(x+size*0.5,y,x,y,x,y+size*0.3);ctx.fill();ctx.restore();}
function spawnHeart(){const colors=getHeartColors(),size=10+Math.random()*18;hearts.push({x:Math.random()*window.innerWidth,y:-size*2,size,color:colors[Math.floor(Math.random()*colors.length)],vy:1.5+Math.random()*2.5,vx:(Math.random()-0.5)*1.2,wobble:Math.random()*Math.PI*2,wobbleSpeed:0.04+Math.random()*0.03,wobbleAmp:0.5+Math.random()*1.5,rotation:(Math.random()-0.5)*0.4,opacity:0.7+Math.random()*0.3});}
function createHeartsCanvas(){if(heartsCanvas)return;heartsCanvas=document.createElement("canvas");heartsCanvas.id="ps_hearts_canvas";heartsCanvas.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9998;";document.body.appendChild(heartsCanvas);heartsCtx=heartsCanvas.getContext("2d");heartsCanvas.width=window.innerWidth;heartsCanvas.height=window.innerHeight;window.addEventListener("resize",()=>{if(heartsCanvas){heartsCanvas.width=window.innerWidth;heartsCanvas.height=window.innerHeight;}});}
function animateHearts(){if(!heartsCtx)return;heartsCtx.clearRect(0,0,heartsCanvas.width,heartsCanvas.height);for(let i=hearts.length-1;i>=0;i--){const h=hearts[i];h.wobble+=h.wobbleSpeed;h.x+=h.vx+Math.sin(h.wobble)*h.wobbleAmp;h.y+=h.vy;if(h.y>window.innerHeight+h.size*2){hearts.splice(i,1);continue;}heartsCtx.save();heartsCtx.translate(h.x,h.y);heartsCtx.rotate(h.rotation);drawHeart(heartsCtx,0,0,h.size,h.color,h.opacity);heartsCtx.restore();}heartsAnimFrame=requestAnimationFrame(animateHearts);}
function startHearts(){if(heartsRunning)return;heartsRunning=true;createHeartsCanvas();hearts=[];const cols=getHeartColors();for(let i=0;i<20;i++)hearts.push({x:Math.random()*window.innerWidth,y:Math.random()*-window.innerHeight,size:10+Math.random()*18,color:cols[Math.floor(Math.random()*cols.length)],vy:1.5+Math.random()*2.5,vx:(Math.random()-0.5)*1.2,wobble:Math.random()*Math.PI*2,wobbleSpeed:0.04+Math.random()*0.03,wobbleAmp:0.5+Math.random()*1.5,rotation:(Math.random()-0.5)*0.4,opacity:0.7+Math.random()*0.3});heartsInterval=setInterval(()=>{if(heartsRunning)for(let i=0;i<3;i++)spawnHeart();},300);animateHearts();}
function stopHearts(){heartsRunning=false;clearInterval(heartsInterval);heartsInterval=null;cancelAnimationFrame(heartsAnimFrame);heartsAnimFrame=null;if(heartsCtx)heartsCtx.clearRect(0,0,heartsCanvas.width,heartsCanvas.height);hearts=[];}
function triggerDoomExplosion(){const ec=document.createElement("canvas");ec.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:20001;";document.body.appendChild(ec);ec.width=window.innerWidth;ec.height=window.innerHeight;const ctx=ec.getContext("2d"),W=ec.width,H=ec.height;const centers=[{x:W*0.5,y:H*0.5},{x:W*0.2,y:H*0.6},{x:W*0.8,y:H*0.55},{x:W*0.35,y:H*0.35},{x:W*0.65,y:H*0.7}];const particles=[];centers.forEach((center,ci)=>{const delay=ci*80,count=ci===0?80:40;for(let i=0;i<count;i++){const angle=Math.random()*Math.PI*2,speed=3+Math.random()*(ci===0?12:8);particles.push({x:center.x,y:center.y,vx:Math.cos(angle)*speed,vy:Math.sin(angle)*speed-(Math.random()*4),r:2+Math.random()*(ci===0?7:4),color:["#ff4400","#ff7700","#ffaa00","#ffdd00","#ff2200","#ffffff","#ff6600"][Math.floor(Math.random()*7)],life:1.0,decay:0.018+Math.random()*0.025,gravity:0.15+Math.random()*0.1,delay,born:false,trail:[]});}});const rings=centers.map((center,ci)=>({x:center.x,y:center.y,r:0,maxR:120+ci*40,life:1.0,delay:ci*80,born:false}));let flashAlpha=0.85,startTime=null;function expFrame(ts){if(!startTime)startTime=ts;const elapsed=ts-startTime;ctx.clearRect(0,0,W,H);if(flashAlpha>0){ctx.save();ctx.globalAlpha=flashAlpha;const fg=ctx.createRadialGradient(W*0.5,H*0.5,0,W*0.5,H*0.5,W*0.7);fg.addColorStop(0,"rgba(255,200,100,1)");fg.addColorStop(0.3,"rgba(255,80,0,0.8)");fg.addColorStop(1,"rgba(255,0,0,0)");ctx.fillStyle=fg;ctx.fillRect(0,0,W,H);ctx.restore();flashAlpha-=0.04;}rings.forEach(ring=>{if(elapsed<ring.delay)return;ring.born=true;ring.r+=8;ring.life-=0.03;if(ring.life<=0||ring.r>ring.maxR)return;ctx.save();ctx.globalAlpha=ring.life*0.7;ctx.strokeStyle=`rgba(255,${Math.floor(100+ring.life*155)},0,${ring.life})`;ctx.lineWidth=4*ring.life;ctx.shadowColor="rgba(255,100,0,0.9)";ctx.shadowBlur=20;ctx.beginPath();ctx.arc(ring.x,ring.y,ring.r,0,Math.PI*2);ctx.stroke();ctx.restore();});particles.forEach(p=>{if(elapsed<p.delay)return;p.born=true;p.life-=p.decay;if(p.life<=0)return;p.trail.push({x:p.x,y:p.y,life:p.life});if(p.trail.length>5)p.trail.shift();p.x+=p.vx;p.y+=p.vy;p.vy+=p.gravity;p.vx*=0.97;ctx.save();p.trail.forEach((tp,ti)=>{ctx.globalAlpha=(ti/p.trail.length)*p.life*0.4;ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(tp.x,tp.y,p.r*0.5*(ti/p.trail.length),0,Math.PI*2);ctx.fill();});ctx.restore();ctx.save();ctx.globalAlpha=p.life;ctx.shadowColor=p.color;ctx.shadowBlur=15;ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fill();ctx.restore();});const allDone=rings.every(r=>r.born&&(r.life<=0||r.r>=r.maxR))&&particles.every(p=>p.born&&p.life<=0)&&flashAlpha<=0;if(allDone)ec.remove();else requestAnimationFrame(expFrame);}requestAnimationFrame(expFrame);}
function spawnBirdShadow(){if(!extraEffectsEnabled||originalMode)return;const bc=document.createElement("canvas");bc.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:20000;";document.body.appendChild(bc);bc.width=window.innerWidth;bc.height=window.innerHeight;const ctx=bc.getContext("2d"),W=bc.width,H=bc.height;const ws=W*(0.35+Math.random()*0.15),startX=-ws,endX=W+ws;const yC=H*(0.18+Math.random()*0.22),DUR=900+Math.random()*400;let startTime=null;function drawBird(bx,by,bws,flapPhase,alpha){ctx.save();ctx.globalAlpha=alpha*0.82;const fY=Math.sin(flapPhase*Math.PI)*bws*0.22;ctx.fillStyle="rgba(0,5,20,0.88)";ctx.strokeStyle="rgba(5,15,40,0.6)";ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(bx,by);ctx.bezierCurveTo(bx-bws*0.35,by-bws*0.08-fY,bx-bws*0.72,by-bws*0.16-fY*1.2,bx-bws,by+bws*0.04-fY*0.8);ctx.bezierCurveTo(bx-bws*0.68,by+bws*0.08-fY*0.4,bx-bws*0.32,by+bws*0.06,bx,by+bws*0.12);ctx.closePath();ctx.fill();ctx.stroke();ctx.beginPath();ctx.moveTo(bx,by);ctx.bezierCurveTo(bx+bws*0.35,by-bws*0.08-fY,bx+bws*0.72,by-bws*0.16-fY*1.2,bx+bws,by+bws*0.04-fY*0.8);ctx.bezierCurveTo(bx+bws*0.68,by+bws*0.08-fY*0.4,bx+bws*0.32,by+bws*0.06,bx,by+bws*0.12);ctx.closePath();ctx.fill();ctx.stroke();ctx.restore();}function frame(ts){if(!startTime)startTime=ts;const prog=Math.min((ts-startTime)/DUR,1);ctx.clearRect(0,0,W,H);const ease=prog<0.5?2*prog*prog:-1+(4-2*prog)*prog;const bx=startX+(endX-startX)*ease,by=yC+Math.sin(prog*Math.PI)*H*0.06;const fP=(prog*4)%1,al=prog<0.1?prog/0.1:prog>0.88?1-(prog-0.88)/0.12:1;drawBird(bx,by,ws/2,fP,al);if(prog<1)requestAnimationFrame(frame);else bc.remove();}requestAnimationFrame(frame);}
let hkButterflyTimer=null;
function _spawnOneButterfly(){if(!extraEffectsEnabled||originalMode)return;const bfC=document.createElement("canvas");bfC.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:19999;";document.body.appendChild(bfC);bfC.width=window.innerWidth;bfC.height=window.innerHeight;const ctx=bfC.getContext("2d"),W=bfC.width,H=bfC.height;const ss=Math.random()<0.5?-1:1,sX=ss<0?-60:W+60,sY=H*(0.05+Math.random()*0.25);const mY=H*(0.25+Math.random()*0.35),eX=ss<0?W+80:-80,eY=H*(0.3+Math.random()*0.3);const p0={x:sX,y:sY},p1={x:ss<0?W*0.2:W*0.8,y:sY*0.6},p2={x:ss<0?W*0.6:W*0.4,y:mY},p3={x:eX,y:eY};function bez(a,b,c,d,t){const m=1-t;return{x:m*m*m*a.x+3*m*m*t*b.x+3*m*t*t*c.x+t*t*t*d.x,y:m*m*m*a.y+3*m*m*t*b.y+3*m*t*t*c.y+t*t*t*d.y};}function bezT(a,b,c,d,t){const m=1-t;return{x:3*(m*m*(b.x-a.x)+2*m*t*(c.x-b.x)+t*t*(d.x-c.x)),y:3*(m*m*(b.y-a.y)+2*m*t*(c.y-b.y)+t*t*(d.y-c.y))};}function drawWing(side,foldT,size){const sX=side*(1-foldT*0.85);ctx.save();ctx.scale(sX,1);ctx.beginPath();ctx.moveTo(0,0);ctx.bezierCurveTo(side*size*0.6,-size*0.9,side*size*1.1,-size*0.5,side*size*0.9,size*0.15);ctx.bezierCurveTo(side*size*0.7,size*0.3,side*size*0.2,size*0.1,0,0);const g=ctx.createLinearGradient(0,-size,side*size,size*0.2);g.addColorStop(0,"rgba(60,180,255,0.92)");g.addColorStop(0.4,"rgba(40,130,220,0.85)");g.addColorStop(1,"rgba(10,50,130,0.75)");ctx.fillStyle=g;ctx.fill();ctx.strokeStyle="rgba(140,220,255,0.6)";ctx.lineWidth=0.8;ctx.stroke();ctx.restore();}const DUR=3200+Math.random()*1200,size=22+Math.random()*14,fS=6+Math.random()*4;let sT=null;function frame(ts){if(!sT)sT=ts;const elapsed=ts-sT,prog=Math.min(elapsed/DUR,1);ctx.clearRect(0,0,W,H);const pos=bez(p0,p1,p2,p3,prog),tang=bezT(p0,p1,p2,p3,prog);const angle=Math.atan2(tang.y,tang.x)-Math.PI/2,foldT=Math.sin(elapsed/1000*Math.PI*fS)*0.5+0.5;const opacity=prog>0.72?1-(prog-0.72)/0.28:1;ctx.save();ctx.globalAlpha=opacity;ctx.translate(pos.x,pos.y);ctx.rotate(angle);[-1,1].forEach(s=>drawWing(s,foldT,size));ctx.restore();if(prog<1)requestAnimationFrame(frame);else bfC.remove();}requestAnimationFrame(frame);}
function _triggerButterflyWave(){const count=1+Math.floor(Math.random()*4);for(let i=0;i<count;i++)setTimeout(_spawnOneButterfly,i*(200+Math.random()*300));}
function startHKButterflies(){if(!extraEffectsEnabled||originalMode)return;stopHKButterflies();_triggerButterflyWave();function scheduleNext(){const delay=8000+Math.random()*8000;hkButterflyTimer=setTimeout(()=>{if(currentTheme!=='hk'||!extraEffectsEnabled)return;_triggerButterflyWave();scheduleNext();},delay);}scheduleNext();}
function stopHKButterflies(){clearTimeout(hkButterflyTimer);hkButterflyTimer=null;}
let hkVineCanvas=null,hkVineRaf=null;
function startHKVines(){if(!extraEffectsEnabled||originalMode)return;stopHKVines();hkVineCanvas=document.createElement("canvas");hkVineCanvas.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9998;";document.body.appendChild(hkVineCanvas);hkVineCanvas.width=window.innerWidth;hkVineCanvas.height=window.innerHeight;const ctx=hkVineCanvas.getContext("2d"),W=hkVineCanvas.width,H=hkVineCanvas.height;const vC=4+Math.floor(Math.random()*4),vines=[];for(let i=0;i<vC;i++){const x=W*(0.05+(i/(vC-1))*0.9);vines.push({x,maxLen:H*(0.35+Math.random()*0.35),thickness:18+Math.random()*16,swayAmp:22+Math.random()*30,swayFreq:0.35+Math.random()*0.4,swayPhase:Math.random()*Math.PI*2,colorA:`rgba(${Math.floor(Math.random()*15)},${Math.floor(20+Math.random()*40)},${Math.floor(60+Math.random()*80)},ALPHA)`,legs:Array.from({length:3+Math.floor(Math.random()*3)},()=>({t:0.2+Math.random()*0.65,side:Math.random()<0.5?-1:1,size:12+Math.random()*18,angle:(Math.random()-0.5)*0.9}))});}const GROW=1800,HOLD=4500,SHRINK=2000,TOTAL=GROW+HOLD+SHRINK;let st=null;function vineFrame(ts){if(!st)st=ts;const elapsed=ts-st;ctx.clearRect(0,0,W,H);vines.forEach(vine=>{let lenFrac,alpha;if(elapsed<GROW){const t=elapsed/GROW;lenFrac=1-(1-t)*(1-t);alpha=Math.min(t*3,1);}else if(elapsed<GROW+HOLD){lenFrac=1;alpha=1;}else{const t=(elapsed-GROW-HOLD)/SHRINK;lenFrac=(1-t)*(1-t);alpha=Math.max(1-t*1.5,0);}const renderedLen=vine.maxLen*lenFrac;const swayX=vine.swayAmp*Math.sin(elapsed/1000*Math.PI*2*vine.swayFreq+vine.swayPhase);if(renderedLen<2)return;ctx.save();ctx.globalAlpha=alpha;ctx.lineCap="round";ctx.lineJoin="round";ctx.beginPath();ctx.moveTo(vine.x,0);ctx.lineTo(vine.x+swayX,renderedLen);ctx.strokeStyle=vine.colorA.replace("ALPHA","0.9");ctx.lineWidth=vine.thickness;ctx.stroke();vine.legs.forEach(leaf=>{if(leaf.t>lenFrac)return;const ty=leaf.t*vine.maxLen,tx=vine.x+swayX*leaf.t;ctx.save();ctx.translate(tx,ty);ctx.rotate(leaf.angle);const ls=leaf.size*Math.min(lenFrac*4,1);ctx.beginPath();ctx.ellipse(leaf.side*ls*0.5,0,ls*0.9,ls*0.4,leaf.side*0.5,0,Math.PI*2);ctx.fillStyle="rgba(10,50,120,0.9)";ctx.fill();ctx.restore();});ctx.restore();});if(elapsed<TOTAL)hkVineRaf=requestAnimationFrame(vineFrame);else{ctx.clearRect(0,0,W,H);hkVineCanvas.remove();hkVineCanvas=null;hkVineRaf=null;}}hkVineRaf=requestAnimationFrame(vineFrame);}
function stopHKVines(){if(hkVineRaf){cancelAnimationFrame(hkVineRaf);hkVineRaf=null;}if(hkVineCanvas){hkVineCanvas.remove();hkVineCanvas=null;}}
function stopAllExtraEffects(){stopHearts();stopHKButterflies();stopHKVines();}
function startThemeExtraEffects(theme){if(!extraEffectsEnabled||originalMode||hasJoinedWorld)return;if(theme==='miku'||theme==='teto')startHearts();if(theme==='hk'){spawnBirdShadow();startHKButterflies();startHKVines();}if(theme==='doom')triggerDoomExplosion();}
function activateOriginalMode(){originalMode=true;stopBubbles();stopMusic();stopAllExtraEffects();themeStyle.textContent="";teamHoverStyle.textContent="";const pw=document.querySelector(".wrapper")||document.getElementById("wrapper");if(pw){pw.style.backgroundImage="";pw.style.backgroundSize="";pw.style.backgroundPosition="";}const mc=document.querySelector(".middle_container");if(mc){mc.style.backgroundImage="";mc.style.backgroundSize="";mc.style.backgroundPosition="";mc.style.border="";mc.style.boxShadow="";mc.style.borderRadius="";}[".logout_container",".home_menu_container"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>{el.style.backgroundColor="";el.style.backdropFilter="";el.style.borderRadius="";});});const topBar=document.getElementById("top_bar_container");if(topBar){topBar.style.backgroundColor="";topBar.style.backdropFilter="";}const caption=document.getElementById("game_caption");if(caption){caption.style.background="";caption.style.color="";caption.style.webkitTextFillColor="";caption.style.textShadow="";caption.style.fontWeight="";caption.style.backgroundClip="";caption.style.webkitBackgroundClip="";}const wc=document.querySelector(".welcome_container");if(wc)wc.style.backgroundColor="";[document.getElementById("default_play_btn"),document.querySelector(".default_play_btn"),document.getElementById("tutorial_menu_btn"),document.querySelector(".tutorial_menu_btn"),document.getElementById("settings_menu_btn"),document.querySelector(".settings_menu_btn")].forEach(btn=>{if(!btn)return;btn.style.backgroundColor="";btn.style.color="";btn.style.borderRadius="";btn.style.border="";});["#username","div#username",".username",".player_name",".home_username","#home_username",".user_name_text"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>{el.style.color="";el.style.textShadow="";});});[".home_footer",".footer_container",".home_bottom_bar","#home_footer",".bottom_bar"].forEach(sel=>{document.querySelectorAll(sel).forEach(el=>{el.style.backgroundColor="";el.style.borderRadius="";el.style.backdropFilter="";});});updateOriginalBtn();}
function deactivateOriginalMode(){originalMode=false;applyTheme();applyTeamHoverStyle();if(bubblesEnabled)startBubbles();if(musicEnabled&&(THEME_AUDIO[currentTheme]||currentTheme==='custom'))playThemeAudio(currentTheme);startThemeExtraEffects(currentTheme);updateOriginalBtn();}
const themeStyle=document.createElement("style");themeStyle.id="ps_theme_style";document.head.appendChild(themeStyle);
function applyTheme(){if(originalMode)return;const t=getTheme();themeStyle.textContent=`#ps_notes_btn,#ps_hacks_btn,#ps_settings_btn,#ps_starter_btn{background-color:${t.primary}!important;}#ps_notes_btn:hover,#ps_hacks_btn:hover,#ps_settings_btn:hover,#ps_starter_btn:hover{background-color:${t.primaryHover}!important;}#ps_notes_box,#ps_settings_box{background-color:${t.primarySolid}!important;}#ps_notes_box .ps_header,#ps_settings_box .ps_header{background-color:${t.header}!important;}.ps_setting_btn{background-color:${t.settingBtn}!important;}.ps_setting_btn:hover{background-color:${t.settingBtnHover}!important;}#ps_menu_image_popup{background-color:${t.primarySolidHover}!important;}.ps_image_choice_btn{background-color:${t.imageBtn}!important;}.ps_image_choice_btn:hover{background-color:${t.imageBtnHover}!important;}.ps_image_choice_btn.selected{background-color:${t.imageBtnSelected}!important;}#ps_name_confirm{background-color:${t.nameConfirm}!important;}#ps_name_confirm:hover{background-color:${t.nameConfirmHover}!important;}.ps_hover_toast{background-color:${t.toast}!important;}#ps_hacks_btn{background-color:${t.hackBtn}!important;}#ps_hacks_btn:hover{background-color:${t.hackBtnHover}!important;}#ps_starter_btn{background-color:${t.hackBtn}!important;}#ps_starter_btn:hover{background-color:${t.hackBtnHover}!important;}#ps_hacks_box{background-color:${t.hackBox}!important;}#ps_starter_box{background-color:${t.hackBox}!important;}#ps_hacks_box .ps_header,#ps_starter_box .ps_header{background-color:${t.hackHeader}!important;}.ps_hack_btn{background-color:${t.hackItemBtn}!important;}.ps_hack_btn:hover{background-color:${t.hackItemBtnHover}!important;}#ps_starter_box .ps_starter_item_btn{background-color:${t.hackItemBtn}!important;}#ps_starter_box .ps_starter_item_btn:hover{background-color:${t.hackItemBtnHover}!important;}#ps_esp_box{background-color:${t.hackBox}!important;}#ps_esp_box .ps_header{background-color:${t.hackHeader}!important;}#ps_esp_open_btn{background-color:${t.hackBtn}!important;}#ps_esp_open_btn:hover{background-color:${t.hackBtnHover}!important;}#ps_hacks_esp_btn{background-color:${t.hackBtn}!important;}#ps_hacks_esp_btn:hover{background-color:${t.hackBtnHover}!important;}#ps_blueprint_open_btn{background-color:${t.hackBtn}!important;}#ps_blueprint_open_btn:hover{background-color:${t.hackBtnHover}!important;}`;
const lc=document.querySelector(".logout_container");if(lc){lc.style.backgroundColor=t.uiBg;lc.style.backdropFilter="blur(4px)";}const hmc=document.querySelector(".home_menu_container");if(hmc){hmc.style.backgroundColor=t.uiBg;hmc.style.backdropFilter="blur(4px)";}const tbc=document.getElementById("top_bar_container");if(tbc){tbc.style.backgroundColor=t.uiBg;tbc.style.backdropFilter="blur(4px)";}
const pw=document.querySelector(".wrapper")||document.getElementById("wrapper");if(pw){const BGs={miku:MIKU_BG,hk:HK_BG,space:SPACE_BG,gojo:GOJO_BG,doom:DOOM_BG,teto:TETO_BG,kfp:KFP_BG};const bgUrl=currentTheme==='custom'?(customThemeData.bgImage||''):(BGs[currentTheme]||MIKU_BG);if(bgUrl){pw.style.backgroundImage=`url('${bgUrl}')`;pw.style.backgroundSize="cover";pw.style.backgroundPosition="center";}else{pw.style.backgroundImage="none";}}
const wc=document.querySelector(".welcome_container");if(wc){const bgMap={space:"#1a0a2e",hk:"#0a1a46",gojo:"#1a0030",doom:"#2a0000",teto:"#2a0808",miku:"#1a3d1a",kfp:"#0a2000",custom:"#0a0a0a"};wc.style.backgroundColor=bgMap[currentTheme]||bgMap.miku;}
applyMiddleContainerStyle();applyUsernameColor();applyGameButtonColors();applyCaptionStyle();applyTeamHoverStyle();applyFooterStyle();applyChatAndTimeUI();if(junonESP)junonESP.syncThemeColor();}
function applyThemeEffects(theme){if(theme!=='hk')stopHKVines();if(theme!=='miku'&&theme!=='teto')stopHearts();if(!originalMode){if(theme!=='gojo'&&theme!=='space'&&theme!=='kfp'){if(musicEnabled)playThemeAudio(theme);else stopMusic();}if(bubblesEnabled){stopBubbles();startBubbles();}if(!hasJoinedWorld)startThemeExtraEffects(theme);applyMiddleContainerStyle();applyUsernameColor();applyGameButtonColors();applyCaptionStyle();applyTeamHoverStyle();applyFooterStyle();applyChatAndTimeUI();}}
const socketOverride=setInterval(()=>{if(typeof game!=='undefined'&&game.getSocketUtil&&game.getSocketUtil()){clearInterval(socketOverride);const orig=game.getSocketUtil().getInstance().emit.bind(game.getSocketUtil().getInstance());game.getSocketUtil().emit=function(messageType,data){if(messageType==="RequestGame"&&data){if(currentUsername)data.username=currentUsername;onWorldJoined();}orig(messageType,data);};}},500);
const worldJoinObserver=new MutationObserver(()=>{const hud=document.getElementById("game_ui")||document.querySelector(".game_ui,.hud_container,.in_game_container");if(hud){onWorldJoined();worldJoinObserver.disconnect();}});
worldJoinObserver.observe(document.body,{childList:true,subtree:true});
const cachedPlayerUids = {};
const uidCacheInterval = setInterval(() => {
if (typeof game === 'undefined' || !game.sector) return;
clearInterval(uidCacheInterval);
// intercept the profile viewer to grab UIDs passively
const proto = Object.getPrototypeOf(game.sector);
const methodNames = Object.getOwnPropertyNames(proto);
methodNames.forEach(name => {
if (typeof proto[name] !== 'function') return;
const fn = proto[name];
proto[name] = function(...args) {
try {
const result = fn.apply(this, args);
return result;
} catch(e) { throw e; }
};
});
// watch for the profile popup appearing in the DOM
const uidObserver = new MutationObserver(() => {
const uidEl = document.querySelector('.player_uid, .profile_uid, [class*="uid"], [id*="uid"]');
const nameEl = document.querySelector('.profile_username, .player_profile_name, [class*="profile"] [class*="name"]');
if (uidEl && nameEl) {
const uid = uidEl.textContent.trim();
const name = nameEl.textContent.trim();
if (uid && name) cachedPlayerUids[name] = uid;
}
// also try scraping any visible text that looks like a UID
document.querySelectorAll('[class*="profile"], [id*="profile"]').forEach(el => {
const text = el.innerText || '';
const uidMatch = text.match(/[a-f0-9]{20,}/i) || text.match(/uid[:\s]+([a-zA-Z0-9_-]+)/i);
const nameMatch = text.match(/^([^\n]+)/);
if (uidMatch && nameMatch) {
cachedPlayerUids[nameMatch[1].trim()] = uidMatch[1] || uidMatch[0];
}
});
});
uidObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
}, 500);
const chatScanHookInterval = setInterval(() => {
if (typeof game !== 'undefined' && game.chatMenu && game.chatMenu.onServerChat) {
clearInterval(chatScanHookInterval);
game.chatMenu.originalServerChat = game.chatMenu.onServerChat;
game.chatMenu.onServerChat = async function(e) {
if (chatScanEnabled && e.message && e.message.trim() === "~para scan.command") {
// post the command itself first
game.getSocketUtil().emit("ClientChat", { message: "~para scan.command" });
await new Promise(r => setTimeout(r, 400));
try {
const sector = game.sector;
const lines = [
`=== ${sector.name || 'Sector'} ===`,
`Players: ${Object.keys(sector.players || {}).length}`,
`Mobs: ${Object.keys(sector.mobs || {}).length}`,
`Gamemode: ${sector.gameMode || '?'}`,
`Private: ${sector.isPrivate || false}`,
];
for (const line of lines) {
game.getSocketUtil().emit("ClientChat", { message: line });
await new Promise(r => setTimeout(r, 400));
}
// loop through every player in the sector
const players = Object.values(sector.players || {});
for (const p of players) {
if (!p) continue;
const name = p.username || p.name || 'Unknown';
const uid = cachedPlayerUids[name] || p.uid || p.accountId || null;
game.getSocketUtil().emit("ClientChat", { message: `--- ${name} ---` });
await new Promise(r => setTimeout(r, 400));
if (uid) {
try {
const data = await fetchUserRecord(uid);
if (data) {
const accepted = data.friends
? data.friends.filter(f => f.status === "accepted").length
: "?";
const badgeIds = extractBadgesFromData(data);
const badgeNames = badgeIds.map(id => JUNON_BADGES[id]?.label || id);
const playerLines = [
`Gold: ${data.gold ?? '?'}`,
`Friends: ${accepted}`,
`Rules read: ${data.isRulesRead ? 'Yes' : 'No'}`,
`Badges: ${badgeNames.length ? badgeNames.join(', ') : 'None'}`,
];
for (const pl of playerLines) {
game.getSocketUtil().emit("ClientChat", { message: pl });
await new Promise(r => setTimeout(r, 400));
}
} else {
game.getSocketUtil().emit("ClientChat", { message: `Profile unavailable` });
await new Promise(r => setTimeout(r, 400));
}
} catch(err) {
game.getSocketUtil().emit("ClientChat", { message: `Fetch error` });
await new Promise(r => setTimeout(r, 400));
}
} else {
game.getSocketUtil().emit("ClientChat", { message: `UID not visible` });
await new Promise(r => setTimeout(r, 400));
}
}
} catch (err) {
game.getSocketUtil().emit("ClientChat", { message: "Scan failed: " + err.message });
}
}
game.chatMenu.originalServerChat(e);
};
}
}, 500);
class JunonESP{constructor(){this.espContainer=null;this.settings={showPlayers:true,showMobs:false,showLines:true,showBoxes:true,showHitbox:false,showLabels:true,showIDs:false,lineStyle:'solid',playerColor:getEspPlayerColor(),mobColor:0xFF6600,lineThickness:1,boxThickness:2,mobTypeFilter:'',mobIdFilter:''};this.idTexts=new Map();this.lastUpdate=0;this.updateInterval=1000/30;this.isInitialized=false;this._raf=null;this._playerCount=0;this._mobCount=0;this._entityList=[];}
init(){this.waitForGameReady().then(()=>{this.loadSettings();this.espContainer=new PIXI.Container();game.app.stage.addChild(this.espContainer);this.isInitialized=true;this.tick();});}
waitForGameReady(){return new Promise(resolve=>{const check=()=>{if(window.main&&window.main.isGameInitialized)resolve();else setTimeout(check,150);};check();});}
loadSettings(){try{const saved=localStorage.getItem('junonESPSettings3');if(saved)this.settings={...this.settings,...JSON.parse(saved)};}catch(e){}}
saveSettings(){try{localStorage.setItem('junonESPSettings3',JSON.stringify(this.settings));}catch(e){}}
syncThemeColor(){const c=getEspPlayerColor();if(c){this.settings.playerColor=c;this.saveSettings();updateEspColorPreviews();}}
findMobs(){const mobs=[];try{if(game.sector&&game.sector.mobs)Object.values(game.sector.mobs).forEach(mob=>{if(mob&&mob.sprite)mobs.push(mob);});}catch(e){}return mobs;}
getEntitySize(entity){try{const w=entity.getWidth?entity.getWidth():40,h=entity.getHeight?entity.getHeight():40,z=game.app.stage.scale.x;return{width:w*z,height:h*z};}catch(e){const d=40*(game.app.stage.scale.x||1);return{width:d,height:d};}}
drawDottedLine(g,x1,y1,x2,y2,color,alpha,thickness){const dash=5,gap=5,dx=x2-x1,dy=y2-y1,dist=Math.sqrt(dx*dx+dy*dy),nx=dx/dist,ny=dy/dist;let traveled=0,drawing=true;g.lineStyle(thickness,color,alpha);while(traveled<dist){const segLen=Math.min(drawing?dash:gap,dist-traveled);if(drawing){g.moveTo(x1+nx*traveled,y1+ny*traveled);g.lineTo(x1+nx*(traveled+segLen),y1+ny*(traveled+segLen));}traveled+=segLen;drawing=!drawing;}}
tick(){this._raf=requestAnimationFrame(()=>this.tick());const now=Date.now();if(now-this.lastUpdate<this.updateInterval)return;this.lastUpdate=now;if(!this.isInitialized||typeof game==='undefined'||!game.sector||!game.player)return;this.render();}
render(){this.espContainer.removeChildren();const g=new PIXI.Graphics(),cX=game.app.screen.width/2,cY=game.app.screen.height/2;let pCount=0,mCount=0;const entityList=[];const typeFilter=this.settings.mobTypeFilter.trim().toLowerCase(),idFilter=this.settings.mobIdFilter.trim();if(this.settings.showPlayers&&game.sector.players){Object.values(game.sector.players).forEach(player=>{if(!player||player.id===game.player.id)return;try{const pos=player.sprite.getGlobalPosition(),size=this.getEntitySize(player),label=player.username||player.name||'',idStr=String(player.id||'');this.drawEntity(g,pos,cX,cY,this.settings.playerColor,player.id,size,label,idStr);pCount++;entityList.push({id:idStr,label,type:'player',typeName:'Player'});}catch(e){}});}if(this.settings.showMobs){this.findMobs().forEach(mob=>{try{const typeName=(mob.getTypeName&&mob.getTypeName())||mob.typeName||'Mob',idStr=String(mob.id||''),label=typeName;if(typeFilter&&!typeName.toLowerCase().includes(typeFilter))return;if(idFilter&&idStr!==idFilter)return;const pos=mob.sprite.getGlobalPosition(),size=this.getEntitySize(mob);this.drawEntity(g,pos,cX,cY,this.settings.mobColor,mob.id,size,label,idStr);mCount++;entityList.push({id:idStr,label,type:'mob',typeName});}catch(e){}});}this.espContainer.addChild(g);this._playerCount=pCount;this._mobCount=mCount;this._entityList=entityList;updateEspCounter();if(document.getElementById("esp_entity_list_panel")&&document.getElementById("esp_entity_list_panel").style.display!=="none")renderEntityList();}
drawEntity(g,spritePos,cX,cY,color,entityId,size,label,idStr){const sx=spritePos.x,sy=spritePos.y,isDotted=this.settings.lineStyle==='dotted';if(this.settings.showLines){if(isDotted)this.drawDottedLine(g,cX,cY,sx,sy,color,0.55,this.settings.lineThickness);else{g.lineStyle(this.settings.lineThickness,color,0.6);g.moveTo(cX,cY);g.lineTo(sx,sy);}}if(this.settings.showBoxes){g.lineStyle(this.settings.boxThickness,color,0.95);const bx=sx-size.width/2,by=sy-size.height/2,cw=Math.min(8,size.width*0.2),ch=Math.min(8,size.height*0.2);g.moveTo(bx,by+ch);g.lineTo(bx,by);g.lineTo(bx+cw,by);g.moveTo(bx+size.width-cw,by);g.lineTo(bx+size.width,by);g.lineTo(bx+size.width,by+ch);g.moveTo(bx,by+size.height-ch);g.lineTo(bx,by+size.height);g.lineTo(bx+cw,by+size.height);g.moveTo(bx+size.width-cw,by+size.height);g.lineTo(bx+size.width,by+size.height);g.lineTo(bx+size.width,by+size.height-ch);g.lineStyle(0);g.beginFill(color,0.85);g.drawCircle(sx,sy,2.5);g.endFill();}if(this.settings.showHitbox){const hbx=sx-size.width/2,hby=sy-size.height/2;g.lineStyle(0);g.beginFill(color,0.07);g.drawRect(hbx,hby,size.width,size.height);g.endFill();g.lineStyle(1.2,color,0.55);g.drawRect(hbx,hby,size.width,size.height);g.lineStyle(0);}if(this.settings.showLabels&&label){let text=this.idTexts.get('lbl_'+entityId);if(!text){text=new PIXI.Text(label,{fontFamily:'Arial',fontSize:11,fontWeight:'bold',fill:0xFFFFFF,align:'center',stroke:0x000000,strokeThickness:3});text.anchor.set(0.5,1);this.idTexts.set('lbl_'+entityId,text);}text.text=label;text.style.fill=color;text.position.set(sx,sy-size.height/2-4);this.espContainer.addChild(text);}if(this.settings.showIDs&&idStr){let idText=this.idTexts.get('id_'+entityId);if(!idText){idText=new PIXI.Text('',{fontFamily:'Arial',fontSize:9,fontWeight:'normal',fill:0xFFFFFF,align:'center',stroke:0x000000,strokeThickness:2});idText.anchor.set(0.5,0);this.idTexts.set('id_'+entityId,idText);}idText.text='#'+idStr;idText.style.fill=color;idText.position.set(sx,sy+size.height/2+2);this.espContainer.addChild(idText);}}
destroy(){if(this._raf)cancelAnimationFrame(this._raf);if(this.espContainer&&this.espContainer.parent)this.espContainer.parent.removeChild(this.espContainer);this.idTexts.clear();this.isInitialized=false;}
resetSettings(){this.settings={showPlayers:true,showMobs:false,showLines:true,showBoxes:true,showHitbox:false,showLabels:true,showIDs:false,lineStyle:'solid',playerColor:getEspPlayerColor(),mobColor:0xFF6600,lineThickness:1,boxThickness:2,mobTypeFilter:'',mobIdFilter:''};this.saveSettings();syncEspBoxToSettings();}}
let junonESP=null;
const style=document.createElement("style");
style.textContent=`
#ps_notes_btn,#ps_hacks_btn,#ps_settings_btn,#ps_starter_btn,#ps_discord_btn{position:fixed;bottom:20px;z-index:10000;color:white;border:none;border-radius:12px;padding:8px 16px;cursor:pointer;font-size:14px;font-weight:bold;transition:background-color 0.2s,transform 0.1s;}
#ps_notes_btn:active,#ps_hacks_btn:active,#ps_settings_btn:active,#ps_starter_btn:active{transform:scale(0.95);}
#ps_notes_btn{left:20px;}#ps_hacks_btn{left:110px;}#ps_settings_btn{left:200px;}#ps_starter_btn{left:310px;}
#ps_discord_btn{left:420px;background-color:rgba(88,101,242,0.85);}#ps_discord_btn:hover{background-color:rgba(88,101,242,1);}
.ps_box{position:fixed;z-index:10000;border-radius:12px;display:none;flex-direction:column;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,0.35);min-width:220px;}
#ps_notes_box{bottom:60px;left:20px;width:250px;}#ps_hacks_box{bottom:60px;left:110px;}#ps_settings_box{bottom:60px;left:200px;width:270px;}#ps_esp_box{bottom:60px;left:200px;width:260px;}#ps_starter_box{bottom:60px;left:310px;}
.ps_header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;color:white;font-weight:bold;font-size:13px;cursor:grab;user-select:none;border-radius:12px 12px 0 0;letter-spacing:0.03em;}.ps_header:active{cursor:grabbing;}
.ps_header_btns{display:flex;gap:4px;align-items:center;}.ps_min_btn{background:none;border:none;color:rgba(255,255,255,0.75);font-size:16px;cursor:pointer;padding:0 4px;line-height:1;}.ps_min_btn:hover{color:white;}
.ps_close_btn{background:none;border:none;color:rgba(255,255,255,0.6);font-size:14px;cursor:pointer;padding:0 4px;line-height:1;font-weight:bold;}.ps_close_btn:hover{color:rgba(255,100,100,1);}
.ps_body{padding:10px;display:flex;flex-direction:column;gap:7px;}
#ps_notes_textarea{width:100%;height:160px;background:transparent;border:none;outline:none;resize:none;color:white;font-size:13px;box-sizing:border-box;}#ps_notes_textarea::placeholder{color:rgba(255,255,255,0.5);}
.ps_hack_btn{background-color:rgba(80,160,200,0.85);color:white;border:none;border-radius:8px;padding:7px 12px;cursor:pointer;font-size:13px;font-weight:bold;width:100%;text-align:left;}.ps_hack_btn:hover{filter:brightness(1.15);}
.ps_setting_btn,.ps_starter_item_btn{color:white;border:none;border-radius:8px;padding:7px 12px;cursor:pointer;font-size:13px;font-weight:bold;width:100%;text-align:left;transition:background-color 0.2s,filter 0.15s;}.ps_setting_btn:hover,.ps_starter_item_btn:hover{filter:brightness(1.15);}
#ps_esp_open_btn{color:white;border:none;border-radius:8px;padding:7px 12px;cursor:pointer;font-size:13px;font-weight:bold;width:100%;text-align:left;transition:background-color 0.2s,filter 0.15s;}#ps_esp_open_btn:hover{filter:brightness(1.15);}
#ps_esp_box .ps_body{gap:0;padding:0;}.ps_esp_section{padding:6px 10px 4px;border-bottom:1px solid rgba(255,255,255,0.07);}.ps_esp_section:last-child{border-bottom:none;}
.ps_esp_section_label{font-size:10px;font-weight:bold;letter-spacing:0.08em;text-transform:uppercase;color:rgba(255,255,255,0.4);margin-bottom:5px;}
.ps_esp_toggle_row{display:flex;gap:5px;flex-wrap:wrap;}.ps_esp_toggle{flex:1;min-width:60px;padding:5px 4px;border:1px solid rgba(255,255,255,0.15);border-radius:7px;background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.6);font-size:11px;font-weight:bold;cursor:pointer;text-align:center;transition:background 0.15s,color 0.15s,border-color 0.15s;}.ps_esp_toggle:hover{background:rgba(255,255,255,0.12);}.ps_esp_toggle.on{background:rgba(255,255,255,0.18);color:#fff;border-color:rgba(255,255,255,0.4);}
.ps_esp_color_row{display:flex;align-items:center;gap:7px;margin-bottom:4px;}.ps_esp_color_label{font-size:11px;color:rgba(255,255,255,0.65);flex:1;}.ps_esp_color_preview{width:22px;height:22px;border-radius:5px;border:1px solid rgba(255,255,255,0.25);cursor:pointer;transition:transform 0.1s;}.ps_esp_color_preview:hover{transform:scale(1.15);}
input[type="color"].ps_esp_colorpicker{width:0;height:0;opacity:0;position:absolute;pointer-events:none;}
.ps_esp_slider_row{display:flex;align-items:center;gap:7px;margin-bottom:4px;}.ps_esp_slider_label{font-size:11px;color:rgba(255,255,255,0.55);width:56px;}.ps_esp_slider{flex:1;-webkit-appearance:none;appearance:none;height:4px;border-radius:3px;background:rgba(255,255,255,0.2);outline:none;cursor:pointer;}.ps_esp_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:13px;height:13px;border-radius:50%;background:white;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,0.5);}.ps_esp_slider_val{font-size:11px;color:rgba(255,255,255,0.5);width:14px;text-align:right;}
.ps_esp_counter{display:flex;gap:5px;padding:5px 10px;font-size:10px;}.ps_esp_count_pill{flex:1;text-align:center;padding:3px 6px;border-radius:6px;background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.45);font-weight:bold;letter-spacing:0.04em;}.ps_esp_count_pill.has_entities{color:#fff;background:rgba(255,255,255,0.15);}
.ps_esp_reset{margin:2px 10px 8px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.12);color:rgba(255,255,255,0.45);border-radius:7px;padding:4px 0;font-size:11px;cursor:pointer;font-weight:bold;transition:background 0.15s;width:calc(100% - 20px);}.ps_esp_reset:hover{background:rgba(255,255,255,0.14);color:rgba(255,255,255,0.75);}
.ps_esp_seg_row{display:flex;gap:4px;}.ps_esp_seg_btn{flex:1;padding:4px 0;border-radius:6px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.55);font-size:11px;font-weight:bold;cursor:pointer;text-align:center;transition:background 0.12s,color 0.12s;}.ps_esp_seg_btn.active{background:rgba(255,255,255,0.2);color:#fff;border-color:rgba(255,255,255,0.4);}.ps_esp_seg_btn:hover{background:rgba(255,255,255,0.12);}
.ps_esp_filter_row{display:flex;align-items:center;gap:5px;margin-bottom:3px;}.ps_esp_filter_label{font-size:10px;color:rgba(255,255,255,0.5);width:44px;flex-shrink:0;}.ps_esp_filter_input{flex:1;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);border-radius:6px;padding:3px 7px;color:#fff;font-size:11px;outline:none;}.ps_esp_filter_input::placeholder{color:rgba(255,255,255,0.3);}.ps_esp_filter_input:focus{border-color:rgba(255,255,255,0.4);}
#esp_entity_list_panel{position:fixed;z-index:10001;bottom:60px;left:470px;width:230px;max-height:340px;border-radius:12px;overflow:hidden;display:none;flex-direction:column;box-shadow:0 4px 24px rgba(0,0,0,0.5);}#esp_entity_list_panel .ps_header{border-radius:12px 12px 0 0;flex-shrink:0;}
#esp_list_body{overflow-y:auto;flex:1;padding:4px 0;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.2) transparent;}#esp_list_body::-webkit-scrollbar{width:5px;}#esp_list_body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.2);border-radius:3px;}
.esp_list_item{display:flex;align-items:center;gap:6px;padding:4px 10px;font-size:11px;color:rgba(255,255,255,0.75);border-bottom:1px solid rgba(255,255,255,0.05);cursor:default;transition:background 0.1s;}.esp_list_item:hover{background:rgba(255,255,255,0.08);}.esp_list_item:last-child{border-bottom:none;}
.esp_list_type_badge{font-size:9px;font-weight:bold;padding:1px 5px;border-radius:4px;letter-spacing:0.06em;flex-shrink:0;}.esp_list_type_badge.player{background:rgba(255,255,255,0.18);color:#fff;}.esp_list_type_badge.mob{background:rgba(255,140,40,0.35);color:#ffb060;}
.esp_list_name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.esp_list_id{font-size:10px;color:rgba(255,255,255,0.35);font-family:monospace;flex-shrink:0;}
#esp_list_empty{padding:16px 10px;text-align:center;color:rgba(255,255,255,0.3);font-size:11px;}
#esp_list_refresh_btn{margin:4px 10px 8px;background:rgba(255,255,255,0.07);border:1px solid rgba(255,255,255,0.1);color:rgba(255,255,255,0.4);border-radius:7px;padding:4px 0;font-size:11px;cursor:pointer;font-weight:bold;transition:background 0.15s;flex-shrink:0;}#esp_list_refresh_btn:hover{background:rgba(255,255,255,0.13);color:rgba(255,255,255,0.7);}
#ps_blueprint_box{bottom:60px;left:110px;width:270px;}#ps_blueprint_box .ps_body{gap:0;padding:0;}
.bp_section{padding:7px 10px 5px;border-bottom:1px solid rgba(255,255,255,0.07);}.bp_section:last-child{border-bottom:none;}
.bp_section_label{font-size:10px;font-weight:bold;letter-spacing:0.08em;text-transform:uppercase;color:rgba(255,255,255,0.38);margin-bottom:6px;}
.bp_action_row{display:flex;gap:5px;}.bp_action_btn{flex:1;padding:6px 4px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.65);font-size:12px;font-weight:bold;cursor:pointer;text-align:center;transition:background 0.14s,color 0.14s,border-color 0.14s;}.bp_action_btn:hover{background:rgba(255,255,255,0.13);color:#fff;}.bp_action_btn.active{background:rgba(255,255,255,0.22);color:#fff;border-color:rgba(255,255,255,0.45);}.bp_action_btn.danger{border-color:rgba(255,80,80,0.35);color:rgba(255,130,130,0.8);}.bp_action_btn.danger:hover{background:rgba(255,60,60,0.2);color:#ff9090;}.bp_action_btn.go{border-color:rgba(80,220,120,0.35);color:rgba(120,240,160,0.8);}.bp_action_btn.go:hover{background:rgba(60,200,100,0.2);color:#90ffb8;}
.bp_vis_row{display:flex;align-items:center;gap:7px;}.bp_vis_label{font-size:11px;color:rgba(255,255,255,0.55);flex:1;}.bp_vis_btn{padding:3px 10px;border-radius:6px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.5);font-size:11px;font-weight:bold;cursor:pointer;transition:background 0.12s,color 0.12s;}.bp_vis_btn.on{background:rgba(255,255,255,0.2);color:#fff;border-color:rgba(255,255,255,0.4);}
.bp_block_search{width:100%;box-sizing:border-box;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);border-radius:6px;padding:4px 8px;color:#fff;font-size:11px;outline:none;margin-bottom:5px;}.bp_block_search::placeholder{color:rgba(255,255,255,0.3);}.bp_block_search:focus{border-color:rgba(255,255,255,0.4);}
.bp_block_grid{display:grid;grid-template-columns:repeat(4,1fr);gap:4px;max-height:130px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.15) transparent;}.bp_block_grid::-webkit-scrollbar{width:4px;}.bp_block_grid::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:3px;}
.bp_block_tile{padding:4px 3px;border-radius:5px;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05);color:rgba(255,255,255,0.6);font-size:10px;cursor:pointer;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:background 0.1s,color 0.1s;}.bp_block_tile:hover{background:rgba(255,255,255,0.12);color:#fff;}.bp_block_tile.selected{background:rgba(255,255,255,0.22);color:#fff;border-color:rgba(255,255,255,0.5);}
.bp_stat_row{display:flex;gap:5px;padding:4px 0 2px;}.bp_stat_pill{flex:1;text-align:center;padding:3px 5px;border-radius:6px;background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.4);font-size:10px;font-weight:bold;letter-spacing:0.04em;}.bp_stat_pill.has_data{color:#fff;background:rgba(255,255,255,0.14);}
#bp_cost_display{background:rgba(0,0,0,0.2);border-radius:6px;padding:5px 7px;margin-top:4px;font-size:10px;color:rgba(255,255,255,0.6);line-height:1.6;max-height:80px;overflow-y:auto;}#bp_cost_display .cost_row{display:flex;justify-content:space-between;}
#bp_map_hint{font-size:10px;color:rgba(160,220,255,0.8);padding:3px 10px 0;text-align:center;display:none;}#bp_map_hint.visible{display:block;}
#bp_map_overlay{position:absolute;top:0;left:0;pointer-events:none;z-index:10;image-rendering:pixelated;}
.ps_hover_toast{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:11000;color:white;font-size:13px;font-weight:bold;padding:14px 20px;border-radius:14px;box-shadow:0 4px 16px rgba(0,0,0,0.25);pointer-events:none;opacity:0;transition:opacity 0.15s ease;text-align:center;line-height:1.6;max-width:300px;}.ps_hover_toast.visible{opacity:1;}
#ps_name_popup{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:11000;background-color:rgba(60,140,180,0.97);border-radius:14px;padding:18px 16px;display:none;flex-direction:column;gap:10px;width:240px;box-shadow:0 6px 24px rgba(0,0,0,0.35);}#ps_name_popup label{color:white;font-size:13px;font-weight:bold;}
#ps_name_input{background:rgba(255,255,255,0.25);border:1px solid rgba(255,255,255,0.4);border-radius:8px;padding:7px 10px;color:white;font-size:13px;outline:none;width:100%;box-sizing:border-box;}#ps_name_input::placeholder{color:rgba(255,255,255,0.6);}
#ps_name_confirm{color:white;border:none;border-radius:8px;padding:7px;cursor:pointer;font-size:13px;font-weight:bold;width:100%;}#ps_name_cancel{background:none;border:none;color:rgba(255,255,255,0.75);font-size:12px;cursor:pointer;text-align:center;}#ps_name_cancel:hover{color:white;}
#ps_menu_image_popup{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:11000;border-radius:14px;padding:18px 16px;display:none;flex-direction:column;gap:10px;width:220px;box-shadow:0 6px 24px rgba(0,0,0,0.35);}
#ps_menu_image_popup .ps_popup_title{color:white;font-size:13px;font-weight:bold;text-align:center;}
.ps_image_choice_btn{color:white;border:none;border-radius:8px;padding:7px 12px;cursor:pointer;font-size:13px;font-weight:bold;width:100%;}.ps_image_choice_btn.selected{box-shadow:inset 0 0 0 2px rgba(255,255,255,0.55);}
#ps_menu_image_close{background:none;border:none;color:rgba(255,255,255,0.75);font-size:12px;cursor:pointer;text-align:center;}#ps_menu_image_close:hover{color:white;}
.ps_volume_row{display:flex;align-items:center;gap:8px;padding:2px 0;}.ps_volume_label{color:rgba(255,255,255,0.85);font-size:12px;font-weight:bold;white-space:nowrap;}
.ps_volume_slider{flex:1;-webkit-appearance:none;appearance:none;height:5px;border-radius:4px;background:rgba(255,255,255,0.3);outline:none;cursor:pointer;}.ps_volume_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:white;cursor:pointer;}
.ps_volume_val{color:rgba(255,255,255,0.85);font-size:11px;font-weight:bold;min-width:28px;text-align:right;}
#ps_quick_reload_btn.active{background-color:rgba(80,200,120,0.9)!important;}
.team_entry_row{transition:background-color 0.2s ease;}.middle_container{transition:border 0.3s ease,box-shadow 0.3s ease;}
#default_play_btn,.default_play_btn,#tutorial_menu_btn,.tutorial_menu_btn,#settings_menu_btn,.settings_menu_btn{transition:background-color 0.3s ease!important;}
.home_footer,.footer_container,.home_bottom_bar,#home_footer,.bottom_bar{backdrop-filter:blur(4px);border-radius:12px;transition:background-color 0.3s ease;}
#ps_customize_popup{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:11000;border-radius:14px;display:none;flex-direction:column;width:300px;max-height:90vh;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,0.5);}
#ps_customize_popup .ps_header{border-radius:14px 14px 0 0;cursor:grab;flex-shrink:0;}#ps_customize_popup .ps_header:active{cursor:grabbing;}
#cust_scroll_body{overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:10px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.15) transparent;}#cust_scroll_body::-webkit-scrollbar{width:5px;}#cust_scroll_body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:3px;}
.cust_section_label{font-size:10px;font-weight:bold;letter-spacing:0.07em;text-transform:uppercase;color:rgba(255,255,255,0.45);margin-bottom:5px;}
.cust_color_row{display:flex;align-items:center;gap:8px;}#cust_color_pick{width:36px;height:28px;border:none;border-radius:6px;cursor:pointer;padding:0;background:none;}#cust_color_swatch{width:36px;height:28px;border-radius:6px;border:1px solid rgba(255,255,255,0.25);flex-shrink:0;}#cust_color_hex{font-size:12px;color:rgba(255,255,255,0.8);font-family:monospace;flex:1;}
.cust_file_row{display:flex;align-items:center;gap:6px;}.cust_file_btn{flex-shrink:0;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.25);border-radius:7px;padding:4px 10px;color:rgba(255,255,255,0.8);font-size:11px;font-weight:bold;cursor:pointer;transition:background 0.14s;}.cust_file_btn:hover{background:rgba(255,255,255,0.2);}
.cust_file_name{flex:1;font-size:10px;color:rgba(255,255,255,0.45);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.cust_clear_btn{flex-shrink:0;background:rgba(255,80,80,0.15);border:1px solid rgba(255,80,80,0.3);border-radius:5px;padding:2px 7px;color:rgba(255,140,140,0.9);font-size:11px;cursor:pointer;}.cust_clear_btn:hover{background:rgba(255,80,80,0.3);}
.cust_preview_box{width:100%;height:60px;border-radius:8px;border:1px solid rgba(255,255,255,0.12);background-size:cover;background-position:center;display:flex;align-items:center;justify-content:center;font-size:11px;color:rgba(255,255,255,0.35);}
.cust_action_btn{width:100%;padding:8px;border:none;border-radius:8px;color:white;font-size:13px;font-weight:bold;cursor:pointer;transition:filter 0.15s;}.cust_action_btn:hover{filter:brightness(1.15);}
#cust_status{font-size:10px;text-align:center;color:rgba(255,255,255,0.45);min-height:14px;}
#ps_robo_wrap{position:fixed;bottom:20px;right:20px;z-index:10500;display:flex;align-items:flex-end;gap:10px;pointer-events:none;}
#ps_robo_panel{pointer-events:all;width:295px;border-radius:14px;border:1px solid rgba(100,200,70,0.35);box-shadow:0 4px 28px rgba(0,0,0,0.6);display:flex;flex-direction:column;overflow:hidden;max-height:480px;backdrop-filter:blur(12px);transition:opacity 0.22s,transform 0.22s;background:rgba(15,20,12,0.96);}
#ps_robo_panel.ps_robo_hidden{opacity:0;transform:translateX(14px);pointer-events:none;}
#ps_robo_panel_head{padding:8px 12px;border-bottom:1px solid rgba(100,200,70,0.18);color:rgba(120,220,80,0.95);font-size:11px;font-weight:bold;letter-spacing:0.07em;display:flex;align-items:center;gap:7px;flex-shrink:0;background:rgba(70,160,40,0.18);}
#ps_robo_panel_head::before{content:"*";font-size:8px;color:rgba(120,230,70,1);animation:roboPulse 1.8s infinite ease-in-out;}
@keyframes roboPulse{0%,100%{opacity:1;}50%{opacity:0.25;}}
#ps_robo_msgs{flex:1;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:7px;min-height:80px;max-height:220px;scrollbar-width:thin;scrollbar-color:rgba(100,200,70,0.18) transparent;}#ps_robo_msgs::-webkit-scrollbar{width:4px;}#ps_robo_msgs::-webkit-scrollbar-thumb{background:rgba(100,200,70,0.2);border-radius:3px;}
.robo_msg{padding:7px 10px;border-radius:10px;font-size:12px;line-height:1.55;max-width:90%;animation:roboMsgIn 0.18s ease;}@keyframes roboMsgIn{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:none;}}
.robo_msg.user{background:rgba(100,200,70,0.13);border:1px solid rgba(100,200,70,0.28);color:rgba(255,255,255,0.88);align-self:flex-end;border-bottom-right-radius:3px;}
.robo_msg.bot{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.09);color:rgba(255,255,255,0.82);align-self:flex-start;border-bottom-left-radius:3px;}
.robo_msg.thinking{color:rgba(255,255,255,0.38);font-style:italic;}
#ps_robo_input_row{display:flex;gap:6px;padding:8px;border-top:1px solid rgba(100,200,70,0.14);background:rgba(0,0,0,0.25);flex-shrink:0;position:relative;}
#ps_robo_input{flex:1;background:rgba(255,255,255,0.07);border:1px solid rgba(100,200,70,0.28);border-radius:8px;padding:6px 10px;color:white;font-size:12px;outline:none;transition:border-color 0.15s;}#ps_robo_input:focus{border-color:rgba(100,200,70,0.65);}#ps_robo_input::placeholder{color:rgba(255,255,255,0.28);}
#ps_robo_send{background:rgba(100,200,70,0.18);border:1px solid rgba(100,200,70,0.4);color:rgba(120,220,80,1);border-radius:8px;padding:5px 11px;cursor:pointer;font-size:13px;font-weight:bold;transition:background 0.14s;}#ps_robo_send:hover{background:rgba(100,200,70,0.35);}
#ps_robo_commands_btn{flex-shrink:0;width:30px;height:30px;border-radius:8px;border:1px solid rgba(100,200,70,0.38);background:rgba(100,200,70,0.12);color:rgba(120,220,80,1);font-size:16px;font-weight:bold;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.13s;}
#ps_robo_commands_btn:hover,#ps_robo_commands_btn.active{background:rgba(100,200,70,0.35);border-color:rgba(100,200,70,0.7);}
#ps_robo_key_box{padding:9px 10px;border-top:1px solid rgba(100,200,70,0.12);background:rgba(0,0,0,0.2);display:flex;flex-direction:column;gap:5px;flex-shrink:0;}#ps_robo_key_box label{font-size:10px;color:rgba(255,255,255,0.45);}
#ps_robo_key_inp{background:rgba(255,255,255,0.07);border:1px solid rgba(100,200,70,0.3);border-radius:7px;padding:5px 8px;color:white;font-size:11px;outline:none;}
#ps_robo_key_save{background:rgba(100,200,70,0.18);border:1px solid rgba(100,200,70,0.38);color:rgba(120,220,80,0.95);border-radius:7px;padding:4px;cursor:pointer;font-size:11px;font-weight:bold;}#ps_robo_key_save:hover{background:rgba(100,200,70,0.35);}
#ps_robo_bot{pointer-events:all;display:flex;flex-direction:column;align-items:center;cursor:pointer;user-select:none;flex-shrink:0;filter:drop-shadow(0 6px 16px rgba(0,0,0,0.55));animation:roboFloat 3.2s ease-in-out infinite;transition:transform 0.15s;}#ps_robo_bot:hover{transform:scale(1.07) translateY(-4px);}#ps_robo_bot:active{transform:scale(0.96);}
@keyframes roboFloat{0%,100%{transform:translateY(0);}50%{transform:translateY(-6px);}}
#ps_robo_hide{background:rgba(20,24,16,0.85);border:1px solid rgba(255,255,255,0.1);color:rgba(255,255,255,0.45);border-radius:6px;padding:2px 8px;font-size:10px;cursor:pointer;margin-bottom:5px;transition:background 0.14s,color 0.14s;pointer-events:all;}#ps_robo_hide:hover{background:rgba(40,45,30,0.95);color:white;}
#ps_robo_svg .robo_eye_grp{animation:roboEyeBlink 4s infinite;transform-origin:center;}@keyframes roboEyeBlink{0%,90%,100%{transform:scaleY(1);}95%{transform:scaleY(0.08);}}
.robo_ant_dot{animation:roboAntGlow 1.6s ease-in-out infinite;}@keyframes roboAntGlow{0%,100%{opacity:0.55;}50%{opacity:1;}}
#ps_robo_showbtn{position:fixed;bottom:20px;right:20px;z-index:10500;background:rgba(60,120,30,0.25);border:1px solid rgba(100,200,70,0.4);color:rgba(120,220,80,0.95);border-radius:50%;width:38px;height:38px;font-size:13px;cursor:pointer;transition:background 0.15s;display:none;}#ps_robo_showbtn:hover{background:rgba(80,160,40,0.4);}
#ps_robo_commands_panel{display:none;flex-direction:column;max-height:200px;overflow-y:auto;border-top:1px solid rgba(100,200,70,0.25);border-bottom:1px solid rgba(100,200,70,0.25);flex-shrink:0;background:rgba(0,0,0,0.3);scrollbar-width:thin;scrollbar-color:rgba(100,200,70,0.2) transparent;}
#ps_robo_commands_panel::-webkit-scrollbar{width:4px;}
#ps_robo_commands_panel::-webkit-scrollbar-thumb{background:rgba(100,200,70,0.2);border-radius:3px;}
#ps_robo_commands_panel h4{margin:0;padding:9px 13px 7px;font-size:11px;font-weight:bold;letter-spacing:0.07em;text-transform:uppercase;color:rgba(120,220,80,0.9);border-bottom:1px solid rgba(255,255,255,0.07);flex-shrink:0;}
#ps_robo_commands_body{overflow-y:auto;padding:8px 10px 10px;display:flex;flex-direction:column;gap:7px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.1) transparent;}
#ps_robo_commands_body::-webkit-scrollbar{width:4px;}#ps_robo_commands_body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.12);border-radius:3px;}
.robo_cmd_cat{font-size:10px;font-weight:bold;letter-spacing:0.06em;text-transform:uppercase;color:rgba(120,220,80,0.85);margin-top:3px;}
.robo_cmd_chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px;}
.robo_cmd_chip{padding:3px 10px;border-radius:20px;border:1px solid rgba(100,200,70,0.3);background:rgba(100,200,70,0.1);color:rgba(255,255,255,0.7);font-size:11px;cursor:pointer;transition:background 0.12s,color 0.12s;}
.robo_cmd_chip:hover{background:rgba(100,200,70,0.28);border-color:rgba(100,200,70,0.6);color:#fff;}
`;
document.head.appendChild(style);
function makeHeader(label,closeCallback){const hdr=document.createElement("div");hdr.className="ps_header";hdr.innerHTML=`<span>${label}</span><div class="ps_header_btns"><button class="ps_min_btn">-</button><button class="ps_close_btn">X</button></div>`;hdr.querySelector(".ps_close_btn").addEventListener("click",e=>{e.stopPropagation();if(closeCallback)closeCallback();});return hdr;}
const notesBtn=document.createElement("button");notesBtn.id="ps_notes_btn";notesBtn.textContent="Notes";document.body.appendChild(notesBtn);
const notesBox=document.createElement("div");notesBox.id="ps_notes_box";notesBox.className="ps_box";notesBox.innerHTML=`<div class="ps_body"><textarea id="ps_notes_textarea" placeholder="Type your notes here..."></textarea></div>`;notesBox.insertBefore(makeHeader("Notes",()=>notesBox.style.display="none"),notesBox.firstChild);document.body.appendChild(notesBox);
const hacksBtn=document.createElement("button");hacksBtn.id="ps_hacks_btn";hacksBtn.textContent="Hacks";document.body.appendChild(hacksBtn);
const hacksBox=document.createElement("div");hacksBox.id="ps_hacks_box";hacksBox.className="ps_box";hacksBox.innerHTML=`<div class="ps_body"><button class="ps_hack_btn" id="ps_name_alter_btn">Name Alter</button><button class="ps_hack_btn" id="ps_quick_reload_btn">Quick Reload</button><button class="ps_hack_btn" id="ps_hacks_esp_btn">ESP</button><button class="ps_hack_btn" id="ps_blueprint_open_btn">Blueprint</button></div>`;hacksBox.insertBefore(makeHeader("Hacks",()=>hacksBox.style.display="none"),hacksBox.firstChild);document.body.appendChild(hacksBox);
const settingsBtn=document.createElement("button");settingsBtn.id="ps_settings_btn";settingsBtn.textContent="Settings";document.body.appendChild(settingsBtn);
const settingsBox=document.createElement("div");settingsBox.id="ps_settings_box";settingsBox.className="ps_box";settingsBox.innerHTML=`<div class="ps_body"><div style="display:flex;gap:6px;align-items:center;"><input id="ps_discord_name_input" type="text" placeholder="Your Discord username..." maxlength="32" style="flex:1;background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.28);border-radius:7px;padding:5px 8px;color:#fff;font-size:12px;outline:none;"><button class="ps_setting_btn" id="ps_discord_name_save" style="width:auto;padding:5px 10px;flex-shrink:0;">Save</button></div><button class="ps_setting_btn" id="ps_menu_image_btn">Theme</button><button class="ps_setting_btn" id="ps_music_toggle_btn">Music: ON</button><div class="ps_volume_row"><span class="ps_volume_label">Vol</span><input type="range" class="ps_volume_slider" id="ps_volume_slider" min="0" max="100" value="50"><span class="ps_volume_val" id="ps_volume_val">50%</span></div><button class="ps_setting_btn" id="ps_bubble_toggle_btn">Bubbles: ON</button><button class="ps_setting_btn" id="ps_border_toggle_btn">Border: ON</button><button class="ps_setting_btn" id="ps_extra_effects_btn">Extra: ON</button><button class="ps_setting_btn" id="ps_original_btn">Original: OFF</button><button id="ps_esp_open_btn">Open ESP</button></div>`;settingsBox.insertBefore(makeHeader("Settings",()=>settingsBox.style.display="none"),settingsBox.firstChild);document.body.appendChild(settingsBox);
const espBox=document.createElement("div");espBox.id="ps_esp_box";espBox.className="ps_box";espBox.innerHTML=`<div class="ps_body"><div class="ps_esp_section"><div class="ps_esp_section_label">Entities</div><div class="ps_esp_toggle_row"><button class="ps_esp_toggle on" id="esp_t_players">Players</button><button class="ps_esp_toggle" id="esp_t_mobs">Mobs</button></div></div><div class="ps_esp_section"><div class="ps_esp_section_label">Overlay</div><div class="ps_esp_toggle_row"><button class="ps_esp_toggle on" id="esp_t_lines">Lines</button><button class="ps_esp_toggle on" id="esp_t_boxes">Brackets</button><button class="ps_esp_toggle on" id="esp_t_labels">Labels</button><button class="ps_esp_toggle" id="esp_t_ids">IDs</button><button class="ps_esp_toggle" id="esp_t_hitbox">Hitbox</button></div></div><div class="ps_esp_section"><div class="ps_esp_section_label">Line Style</div><div class="ps_esp_seg_row"><button class="ps_esp_seg_btn active" id="esp_line_solid">Solid</button><button class="ps_esp_seg_btn" id="esp_line_dotted">Dotted</button></div></div><div class="ps_esp_section"><div class="ps_esp_section_label">Colors</div><div class="ps_esp_color_row"><span class="ps_esp_color_label">Players</span><div class="ps_esp_color_preview" id="esp_player_preview"></div><input type="color" class="ps_esp_colorpicker" id="esp_player_color" value="#00ff88"></div><div class="ps_esp_color_row"><span class="ps_esp_color_label">Mobs</span><div class="ps_esp_color_preview" id="esp_mob_preview"></div><input type="color" class="ps_esp_colorpicker" id="esp_mob_color" value="#ff6600"></div></div><div class="ps_esp_section"><div class="ps_esp_section_label">Thickness</div><div class="ps_esp_slider_row"><span class="ps_esp_slider_label">Lines</span><input type="range" class="ps_esp_slider" id="esp_line_thick" min="1" max="6" value="1"><span class="ps_esp_slider_val" id="esp_line_thick_val">1</span></div><div class="ps_esp_slider_row"><span class="ps_esp_slider_label">Brackets</span><input type="range" class="ps_esp_slider" id="esp_box_thick" min="1" max="6" value="2"><span class="ps_esp_slider_val" id="esp_box_thick_val">2</span></div></div><div class="ps_esp_section"><div class="ps_esp_section_label">Mob Filter</div><div class="ps_esp_filter_row"><span class="ps_esp_filter_label">Type</span><input class="ps_esp_filter_input" id="esp_filter_type" type="text" placeholder="e.g. Slime"></div><div class="ps_esp_filter_row"><span class="ps_esp_filter_label">ID</span><input class="ps_esp_filter_input" id="esp_filter_id" type="text" placeholder="exact ID"></div></div><div class="ps_esp_counter"><div class="ps_esp_count_pill" id="esp_player_counter">0 players</div><div class="ps_esp_count_pill" id="esp_mob_counter">0 mobs</div></div><button class="ps_esp_reset" id="esp_list_open_btn">Entity List</button><button class="ps_esp_reset" id="esp_reset_btn">Reset to defaults</button></div>`;espBox.insertBefore(makeHeader("ESP",()=>espBox.style.display="none"),espBox.firstChild);document.body.appendChild(espBox);
const entityListPanel=document.createElement("div");entityListPanel.id="esp_entity_list_panel";entityListPanel.className="ps_box";entityListPanel.innerHTML=`<div id="esp_list_body"><div id="esp_list_empty">No entities detected</div></div><button id="esp_list_refresh_btn">Refresh</button>`;entityListPanel.insertBefore(makeHeader("Entity List",()=>entityListPanel.style.display="none"),entityListPanel.firstChild);document.body.appendChild(entityListPanel);
function syncEntityListPanelBg(){const t=getTheme();entityListPanel.style.backgroundColor=t.hackBox;const hdr=entityListPanel.querySelector(".ps_header");if(hdr)hdr.style.backgroundColor=t.hackHeader;}
function renderEntityList(){const body=document.getElementById("esp_list_body");if(!body)return;const entities=junonESP?junonESP._entityList:[];if(entities.length===0){body.innerHTML='<div id="esp_list_empty">No entities detected</div>';return;}body.innerHTML=entities.map(e=>`<div class="esp_list_item"><span class="esp_list_type_badge ${e.type}">${e.typeName}</span><span class="esp_list_name">${e.label||'--'}</span><span class="esp_list_id">#${e.id}</span></div>`).join('');}
function hexToCSS(hex){return '#'+hex.toString(16).padStart(6,'0');}
function cssToHex(css){return parseInt(css.replace('#',''),16);}
function updateEspColorPreviews(){if(!junonESP)return;const pp=document.getElementById("esp_player_preview"),mp=document.getElementById("esp_mob_preview"),pc=document.getElementById("esp_player_color"),mc=document.getElementById("esp_mob_color");if(pp)pp.style.background=hexToCSS(junonESP.settings.playerColor);if(mp)mp.style.background=hexToCSS(junonESP.settings.mobColor);if(pc)pc.value=hexToCSS(junonESP.settings.playerColor);if(mc)mc.value=hexToCSS(junonESP.settings.mobColor);}
function updateEspCounter(){if(!junonESP)return;const pc=document.getElementById("esp_player_counter"),mc=document.getElementById("esp_mob_counter");if(pc){pc.textContent=`${junonESP._playerCount} player${junonESP._playerCount!==1?'s':''}`;pc.className='ps_esp_count_pill'+(junonESP._playerCount>0?' has_entities':'');}if(mc){mc.textContent=`${junonESP._mobCount} mob${junonESP._mobCount!==1?'s':''}`;mc.className='ps_esp_count_pill'+(junonESP._mobCount>0?' has_entities':'');}}
function syncEspBoxToSettings(){if(!junonESP)return;const s=junonESP.settings;const toggleMap={esp_t_players:'showPlayers',esp_t_mobs:'showMobs',esp_t_lines:'showLines',esp_t_boxes:'showBoxes',esp_t_labels:'showLabels',esp_t_ids:'showIDs',esp_t_hitbox:'showHitbox'};Object.entries(toggleMap).forEach(([id,key])=>{const el=document.getElementById(id);if(!el)return;el.classList.toggle("on",!!s[key]);});document.getElementById("esp_line_solid").classList.toggle("active",s.lineStyle==='solid');document.getElementById("esp_line_dotted").classList.toggle("active",s.lineStyle==='dotted');const lts=document.getElementById("esp_line_thick"),bts=document.getElementById("esp_box_thick"),ltv=document.getElementById("esp_line_thick_val"),btv=document.getElementById("esp_box_thick_val");if(lts){lts.value=s.lineThickness;if(ltv)ltv.textContent=s.lineThickness;}if(bts){bts.value=s.boxThickness;if(btv)btv.textContent=s.boxThickness;}const ft=document.getElementById("esp_filter_type"),fi=document.getElementById("esp_filter_id");if(ft)ft.value=s.mobTypeFilter||'';if(fi)fi.value=s.mobIdFilter||'';updateEspColorPreviews();}
function bindEspToggle(id,key){const el=document.getElementById(id);if(!el)return;el.addEventListener("click",()=>{if(!junonESP)return;junonESP.settings[key]=!junonESP.settings[key];junonESP.saveSettings();el.classList.toggle("on",junonESP.settings[key]);});}
bindEspToggle("esp_t_players","showPlayers");bindEspToggle("esp_t_mobs","showMobs");bindEspToggle("esp_t_lines","showLines");bindEspToggle("esp_t_boxes","showBoxes");bindEspToggle("esp_t_labels","showLabels");bindEspToggle("esp_t_ids","showIDs");bindEspToggle("esp_t_hitbox","showHitbox");
document.getElementById("esp_line_solid").addEventListener("click",()=>{if(!junonESP)return;junonESP.settings.lineStyle='solid';junonESP.saveSettings();document.getElementById("esp_line_solid").classList.add("active");document.getElementById("esp_line_dotted").classList.remove("active");});
document.getElementById("esp_line_dotted").addEventListener("click",()=>{if(!junonESP)return;junonESP.settings.lineStyle='dotted';junonESP.saveSettings();document.getElementById("esp_line_dotted").classList.add("active");document.getElementById("esp_line_solid").classList.remove("active");});
function bindEspColor(previewId,pickerId,settingKey){const preview=document.getElementById(previewId),picker=document.getElementById(pickerId);if(!preview||!picker)return;preview.addEventListener("click",()=>picker.click());picker.addEventListener("input",e=>{if(!junonESP)return;junonESP.settings[settingKey]=cssToHex(e.target.value);preview.style.background=e.target.value;junonESP.saveSettings();});}
bindEspColor("esp_player_preview","esp_player_color","playerColor");bindEspColor("esp_mob_preview","esp_mob_color","mobColor");
function bindEspSlider(sliderId,valId,settingKey){const sl=document.getElementById(sliderId),vl=document.getElementById(valId);if(!sl)return;sl.addEventListener("input",e=>{if(!junonESP)return;junonESP.settings[settingKey]=parseInt(e.target.value);if(vl)vl.textContent=e.target.value;junonESP.saveSettings();});}
bindEspSlider("esp_line_thick","esp_line_thick_val","lineThickness");bindEspSlider("esp_box_thick","esp_box_thick_val","boxThickness");
document.getElementById("esp_filter_type").addEventListener("input",e=>{if(!junonESP)return;junonESP.settings.mobTypeFilter=e.target.value;junonESP.saveSettings();});
document.getElementById("esp_filter_id").addEventListener("input",e=>{if(!junonESP)return;junonESP.settings.mobIdFilter=e.target.value;junonESP.saveSettings();});
document.getElementById("esp_list_open_btn").addEventListener("click",()=>{const panel=document.getElementById("esp_entity_list_panel");if(!panel)return;const isOpen=panel.style.display==="flex";panel.style.display=isOpen?"none":"flex";if(!isOpen){syncEntityListPanelBg();renderEntityList();}});
document.getElementById("esp_list_refresh_btn").addEventListener("click",()=>{renderEntityList();});
document.getElementById("esp_reset_btn").addEventListener("click",()=>{if(junonESP)junonESP.resetSettings();});
function bpUpdateCostDisplay(){const el=document.getElementById("bp_cost_display");if(!el)return;if(bp.phantoms.length===0){el.innerHTML='<span style="color:rgba(255,255,255,0.3)">No blocks planned</span>';return;}const totals={};bp.phantoms.forEach(p=>{const reqs=bpGetRequirements(p.typeId);if(!reqs)return;Object.entries(reqs).forEach(([item,qty])=>{totals[item]=(totals[item]||0)+qty;});});if(Object.keys(totals).length===0){el.innerHTML='<span style="color:rgba(255,255,255,0.3)">Requirements unknown</span>';return;}let html='';Object.entries(totals).forEach(([item,qty])=>{html+=`<div class="cost_row"><span>${item}</span><span>x${qty}</span></div>`;});el.innerHTML=html;}
function bpSendBuild(gx,gy,typeId){try{const numericType=bpGetNumericType(typeId);if(numericType==null){showBpToast("Unknown type: "+typeId);return false;}const containerId=bpGetContainerId();if(containerId==null){showBpToast("Not in a world!");return false;}const def=BLOCK_TYPES.find(b=>b.id===typeId)||{w:32,h:32};const{x,y}=bpGridToServerXY(gx,gy,def.w,def.h);game.getSocketUtil().emit("Build",{type:numericType,angle:0,x,y,containerId});return true;}catch(e){showBpToast("Build error: "+e.message);return false;}}
function bpBuild(){if(bp.phantoms.length===0){showBpToast("No blocks planned!");return;}const snapshot=[...bp.phantoms];snapshot.forEach((p,i)=>{setTimeout(()=>{bpSendBuild(p.gx,p.gy,p.typeId);},i*120);});setTimeout(()=>{snapshot.forEach(b=>bp.built.push(b));bp.phantoms=[];bpDrawPhantomsOnMap();bpUpdateStats();bpUpdateCostDisplay();showBpToast(`Build sent (${snapshot.length} block${snapshot.length!==1?'s':''})`);},snapshot.length*120+300);}
function bpUndo(){if(bp.built.length===0){showBpToast("Nothing to undo");return;}showBpToast("Use your multi-tool to demolish manually");bp.built=[];bpUpdateStats();}
function showBpToast(msg){let t=document.getElementById("bp_toast");if(!t){t=document.createElement("div");t.id="bp_toast";t.style.cssText="position:fixed;bottom:100px;left:50%;transform:translateX(-50%);z-index:11001;background:rgba(30,30,30,0.97);color:#fff;padding:9px 20px;border-radius:10px;font-size:13px;font-weight:bold;pointer-events:none;opacity:0;transition:opacity 0.2s;white-space:nowrap;";document.body.appendChild(t);}t.textContent=msg;t.style.opacity="1";clearTimeout(t._hideTimer);t._hideTimer=setTimeout(()=>{t.style.opacity="0";},2500);}
const bp = {
phantoms: [], built: [], selectedType: 'floor',
mapMode: false, phantomVisible: true, initialized: false,
};
const BLOCK_TYPES = [
{id:'floor', name:'Floor', w:32, h:32},
{id:'wall', name:'Wall', w:32, h:32},
{id:'door', name:'Door', w:32, h:64},
{id:'window', name:'Window', w:32, h:32},
{id:'conveyor', name:'Conveyor', w:32, h:32},
{id:'lamp', name:'Lamp', w:16, h:16},
{id:'turret', name:'Turret', w:32, h:32},
{id:'chest', name:'Chest', w:32, h:32},
];
function bpInit() { bp.initialized = true; }
function bpGetRequirements(typeId) { return null; }
function bpGetNumericType(typeId) {
const m = {floor:0,wall:1,door:2,window:3,conveyor:4,lamp:5,turret:6,chest:7};
return m[typeId] ?? null;
}
function bpGetContainerId() { try { return game.player.containerId; } catch(e) { return null; } }
function bpGridToServerXY(gx, gy, w, h) { return {x: gx * 32, y: gy * 32}; }
function bpScreenToWorld(sx, sy) {
try { const sc=game.app.stage.scale.x||1,ox=game.app.stage.x||0,oy=game.app.stage.y||0;
return {wx:(sx-ox)/sc, wy:(sy-oy)/sc}; }
catch(e) { return {wx:sx, wy:sy}; }
}
function bpWorldToGrid(wx, wy) { return {gx:Math.floor(wx/32), gy:Math.floor(wy/32)}; }
function bpAddPhantom(gx, gy) {
bp.phantoms.push({gx, gy, typeId: bp.selectedType});
bpDrawPhantomsOnMap(); bpUpdateStats(); bpUpdateCostDisplay();
}
function bpDrawPhantomsOnMap() {}
function bpSetupMapCanvasListener() {}
function bpEnsureMapOverlay() {}
bpInit();
function bpOnCanvasClick(e){if(!bp.initialized)return;if(!blueprintBox||blueprintBox.style.display!=="flex")return;if(e.target&&e.target.closest&&e.target.closest('.ps_box'))return;try{const canvasEl=game.app.view;if(e.target!==canvasEl)return;const{wx,wy}=bpScreenToWorld(e.clientX,e.clientY);const{gx,gy}=bpWorldToGrid(wx,wy);bpAddPhantom(gx,gy);e.stopPropagation();e.preventDefault();}catch(err){}}
document.addEventListener("click",bpOnCanvasClick,true);
let bpBlockFilter='';
function bpRenderBlockGrid(){const grid=document.getElementById("bp_block_grid");if(!grid)return;const filter=bpBlockFilter.toLowerCase(),filtered=filter?BLOCK_TYPES.filter(b=>b.name.toLowerCase().includes(filter)):BLOCK_TYPES;grid.innerHTML=filtered.map(b=>`<div class="bp_block_tile${bp.selectedType===b.id?' selected':''}" data-bid="${b.id}" title="${b.name} (${b.w}x${b.h}px)">${b.name}</div>`).join('');grid.querySelectorAll('.bp_block_tile').forEach(tile=>{tile.addEventListener('click',()=>{bp.selectedType=tile.dataset.bid;bpRenderBlockGrid();bpUpdateSelectedLabel();bpUpdateCostDisplay();});});}
function bpUpdateSelectedLabel(){const lbl=document.getElementById("bp_selected_block_lbl");const block=BLOCK_TYPES.find(b=>b.id===bp.selectedType);if(lbl)lbl.textContent=block?`${block.name} (${block.w}x${block.h})`:'--';}
function bpUpdateMapBtn(){const btn=document.getElementById("bp_map_btn");const hint=document.getElementById("bp_map_hint");if(btn){btn.textContent=bp.mapMode?'Map: ON':'Map';btn.classList.toggle("active",bp.mapMode);}if(hint)hint.classList.toggle("visible",bp.mapMode);bpUpdateMapCanvasCursor();}
function bpUpdateVisBtn(){const btn=document.getElementById("bp_vis_btn");if(!btn)return;btn.textContent=bp.phantomVisible?'Visible: ON':'Visible: OFF';btn.classList.toggle("on",bp.phantomVisible);}
function bpUpdateStats(){const c=document.getElementById("bp_phantom_count"),b=document.getElementById("bp_built_count");if(c){c.textContent=`${bp.phantoms.length} planned`;c.className='bp_stat_pill'+(bp.phantoms.length>0?' has_data':'');}if(b){b.textContent=`${bp.built.length} built`;b.className='bp_stat_pill'+(bp.built.length>0?' has_data':'');}}
const blueprintBox=document.createElement("div");blueprintBox.id="ps_blueprint_box";blueprintBox.className="ps_box";
blueprintBox.innerHTML=`<div class="ps_body"><div class="bp_section"><div class="bp_section_label">Actions</div><div class="bp_action_row"><button class="bp_action_btn" id="bp_map_btn">Map</button><button class="bp_action_btn go" id="bp_build_btn">Build</button><button class="bp_action_btn danger" id="bp_undo_btn">Undo</button></div><div id="bp_map_hint">Click map canvas to place blocks</div></div><div class="bp_section"><div class="bp_vis_row"><span class="bp_vis_label">Phantom overlay</span><button class="bp_vis_btn on" id="bp_vis_btn">Visible: ON</button></div></div><div class="bp_section"><div class="bp_section_label">Block Type <span id="bp_selected_block_lbl" style="font-weight:bold;color:rgba(255,255,255,0.85);text-transform:none;letter-spacing:0;">Floor (32x32)</span></div><input class="bp_block_search" id="bp_block_search" type="text" placeholder="Search blocks..."><div class="bp_block_grid" id="bp_block_grid"></div></div><div class="bp_section"><div class="bp_section_label">Materials needed</div><div id="bp_cost_display"><span style="color:rgba(255,255,255,0.3)">No blocks planned</span></div></div><div class="bp_section"><div class="bp_stat_row"><div class="bp_stat_pill" id="bp_phantom_count">0 planned</div><div class="bp_stat_pill" id="bp_built_count">0 built</div></div><div style="margin-top:5px;"><button class="bp_action_btn danger" id="bp_clear_btn" style="width:100%;font-size:11px;padding:4px 0;">Clear Phantoms</button></div></div></div>`;
blueprintBox.insertBefore(makeHeader("Blueprint",()=>blueprintBox.style.display="none"),blueprintBox.firstChild);document.body.appendChild(blueprintBox);
const starterBtn=document.createElement("button");starterBtn.id="ps_starter_btn";starterBtn.textContent="Starter";document.body.appendChild(starterBtn);
const starterBox=document.createElement("div");starterBox.id="ps_starter_box";starterBox.className="ps_box";starterBox.innerHTML=`<div class="ps_body"><button class="ps_starter_item_btn" id="ps_slime_tick_btn">Slime Tick</button><button class="ps_starter_item_btn" id="ps_exp_system_btn">Exp System</button></div>`;starterBox.insertBefore(makeHeader("Starter Code",()=>starterBox.style.display="none"),starterBox.firstChild);document.body.appendChild(starterBox);
const discordBtn=document.createElement("button");discordBtn.id="ps_discord_btn";discordBtn.textContent="Discord";document.body.appendChild(discordBtn);discordBtn.addEventListener("click",()=>window.open("https://discord.gg/ft64pSTfm","_blank"));
const customizePopup=document.createElement("div");customizePopup.id="ps_customize_popup";
customizePopup.innerHTML=`<div id="cust_scroll_body"><div><div class="cust_section_label">UI Color</div><div class="cust_color_row"><input type="color" id="cust_color_pick" value="#7c5cbf"><div id="cust_color_swatch" style="background:#7c5cbf;"></div><span id="cust_color_hex">#7c5cbf</span></div></div><div><div class="cust_section_label">Background Image</div><div class="cust_file_row"><button class="cust_file_btn" id="cust_bg_btn">Upload</button><span class="cust_file_name" id="cust_bg_name">No file chosen</span><button class="cust_clear_btn" id="cust_bg_clear">X</button></div><input type="file" id="cust_bg_file" accept="image/*" style="display:none;"><div class="cust_preview_box" id="cust_bg_preview">No background set</div></div><div><div class="cust_section_label">Middle Image</div><div class="cust_file_row"><button class="cust_file_btn" id="cust_mid_btn">Upload</button><span class="cust_file_name" id="cust_mid_name">No file chosen</span><button class="cust_clear_btn" id="cust_mid_clear">X</button></div><input type="file" id="cust_mid_file" accept="image/*" style="display:none;"><div class="cust_preview_box" id="cust_mid_preview">No middle image set</div></div><div><div class="cust_section_label">Music</div><div class="cust_file_row"><button class="cust_file_btn" id="cust_music_btn">Upload</button><span class="cust_file_name" id="cust_music_name">No file chosen</span><button class="cust_clear_btn" id="cust_music_clear">X</button></div><input type="file" id="cust_music_file" accept="audio/*" style="display:none;"></div><button class="cust_action_btn" id="cust_apply_btn" style="background:rgba(100,180,60,0.85);">Apply Theme</button><button class="cust_action_btn" id="cust_save_btn" style="background:rgba(60,120,200,0.85);">Save Theme</button><div id="cust_status"></div></div>`;
customizePopup.insertBefore(makeHeader("Customize Theme",()=>customizePopup.style.display="none"),customizePopup.firstChild);document.body.appendChild(customizePopup);
const menuImagePopup=document.createElement("div");menuImagePopup.id="ps_menu_image_popup";
menuImagePopup.innerHTML=`<div class="ps_popup_title">Choose Theme</div><button class="ps_image_choice_btn selected" id="ps_img_miku">Miku</button><button class="ps_image_choice_btn" id="ps_img_hk">Hollow Knight</button><button class="ps_image_choice_btn" id="ps_img_space">Realistic Space</button><button class="ps_image_choice_btn" id="ps_img_gojo">Gojo</button><button class="ps_image_choice_btn" id="ps_img_doom">Doom</button><button class="ps_image_choice_btn" id="ps_img_teto">Teto</button><button class="ps_image_choice_btn" id="ps_img_kfp">Kung Fu Panda</button><button class="ps_image_choice_btn" id="ps_img_custom">Customize</button><button id="ps_menu_image_close">Close</button>`;
document.body.appendChild(menuImagePopup);
const namePopup=document.createElement("div");namePopup.id="ps_name_popup";namePopup.innerHTML=`<label>Enter new username:</label><input id="ps_name_input" type="text" placeholder="Your name..." maxlength="200"/><button id="ps_name_confirm">Set Name</button><button id="ps_name_cancel">Cancel</button>`;document.body.appendChild(namePopup);
function makeToast(text){const t=document.createElement("div");t.className="ps_hover_toast";t.textContent=text;document.body.appendChild(t);return t;}
const nameAlterToast=makeToast("Only works for guest accounts, log out if you are logged in.");
const quickReloadToast=makeToast("Requires /clear and /give command blocks set up in-world.");
const starterBtnToast=makeToast("Starter code templates to import into your world.");
const slimeTickToast=makeToast("Sub-tick timing using slimes, shorter than the regular timer.");
const expSystemToast=makeToast("A basic exponential EXP system ready to customize.");
const discordToast=makeToast("Join our Discord server!");
function bindToast(id,toast){const el=document.getElementById(id);if(!el)return;el.addEventListener("mouseenter",()=>toast.classList.add("visible"));el.addEventListener("mouseleave",()=>toast.classList.remove("visible"));}
notesBtn.addEventListener("click",()=>{notesBox.style.display=notesBox.style.display==="flex"?"none":"flex";});
hacksBtn.addEventListener("click",()=>{hacksBox.style.display=hacksBox.style.display==="flex"?"none":"flex";});
settingsBtn.addEventListener("click",()=>{settingsBox.style.display=settingsBox.style.display==="flex"?"none":"flex";});
starterBtn.addEventListener("click",()=>{starterBox.style.display=starterBox.style.display==="flex"?"none":"flex";});
document.getElementById("ps_esp_open_btn").addEventListener("click",()=>{settingsBox.style.display="none";espBox.style.display=espBox.style.display==="flex"?"none":"flex";});
function setupMinimize(box){box.querySelectorAll(".ps_min_btn").forEach(btn=>{const body=box.querySelector(".ps_body")||box.querySelector("#esp_list_body");if(!body)return;let mini=false;btn.addEventListener("click",e=>{e.stopPropagation();mini=!mini;body.style.display=mini?"none":"flex";btn.textContent=mini?"+":"-";});});}
[notesBox,hacksBox,settingsBox,espBox,starterBox,entityListPanel,blueprintBox,customizePopup].forEach(setupMinimize);
function makeDraggable(box){const header=box.querySelector(".ps_header");if(!header)return;let dragging=false,ox,oy;header.addEventListener("mousedown",e=>{if(e.target.classList.contains("ps_min_btn")||e.target.classList.contains("ps_close_btn"))return;dragging=true;ox=e.clientX-box.getBoundingClientRect().left;oy=e.clientY-box.getBoundingClientRect().top;box.style.bottom="auto";e.preventDefault();});document.addEventListener("mousemove",e=>{if(!dragging)return;box.style.left=(e.clientX-ox)+"px";box.style.top=(e.clientY-oy)+"px";});document.addEventListener("mouseup",()=>{dragging=false;});}
[notesBox,hacksBox,settingsBox,espBox,starterBox,entityListPanel,blueprintBox,customizePopup].forEach(makeDraggable);
bindToast("ps_name_alter_btn",nameAlterToast);bindToast("ps_quick_reload_btn",quickReloadToast);bindToast("ps_starter_btn",starterBtnToast);bindToast("ps_slime_tick_btn",slimeTickToast);bindToast("ps_exp_system_btn",expSystemToast);bindToast("ps_discord_btn",discordToast);
const _savedDiscordName = localStorage.getItem('ps_discord_username') || '';
if (_savedDiscordName) document.getElementById("ps_discord_name_input").value = _savedDiscordName;
document.getElementById("ps_discord_name_save").addEventListener("click", async () => {
const val = document.getElementById("ps_discord_name_input").value.trim();
if (val) {
const oldName = localStorage.getItem('ps_discord_username') || 'Player';
localStorage.setItem('ps_discord_username', val);
const entries = await psLbLoadData();
const cleaned = entries.filter(e => e.username !== oldName && e.username !== 'Player' && e.username !== 'Guest');
await psLbSaveData(cleaned);
await psLbPushEntry(val, psExpState.level, psExpState.totalExp);
await psLbRender();
}
});
document.getElementById("ps_volume_slider").addEventListener("input",e=>{audioPlayer.volume=parseInt(e.target.value)/100;document.getElementById("ps_volume_val").textContent=e.target.value+"%";});
document.getElementById("ps_music_toggle_btn").addEventListener("click",()=>{musicEnabled=!musicEnabled;if(musicEnabled&&(THEME_AUDIO[currentTheme]||currentTheme==='custom')&&!originalMode)playThemeAudio(currentTheme);else stopMusic();updateMusicSettingBtn();});
document.getElementById("ps_bubble_toggle_btn").addEventListener("click",()=>{bubblesEnabled=!bubblesEnabled;if(bubblesEnabled&&!originalMode)startBubbles();else stopBubbles();updateBubbleSettingBtn();});
document.getElementById("ps_border_toggle_btn").addEventListener("click",()=>{borderEnabled=!borderEnabled;applyMiddleContainerStyle();updateBorderBtn();});
document.getElementById("ps_original_btn").addEventListener("click",()=>{if(originalMode)deactivateOriginalMode();else activateOriginalMode();});
document.getElementById("ps_extra_effects_btn").addEventListener("click",()=>{extraEffectsEnabled=!extraEffectsEnabled;if(!extraEffectsEnabled)stopAllExtraEffects();else startThemeExtraEffects(currentTheme);updateExtraEffectsBtn();});
document.getElementById("ps_name_alter_btn").addEventListener("click",()=>{namePopup.style.display="flex";document.getElementById("ps_name_input").focus();});
function confirmName(){const val=document.getElementById("ps_name_input").value.trim();if(val)currentUsername=val;namePopup.style.display="none";document.getElementById("ps_name_input").value="";}
document.getElementById("ps_name_confirm").addEventListener("click",confirmName);
document.getElementById("ps_name_input").addEventListener("keydown",e=>{if(e.key==="Enter")confirmName();if(e.key==="Escape"){namePopup.style.display="none";document.getElementById("ps_name_input").value="";}});
document.getElementById("ps_name_cancel").addEventListener("click",()=>{namePopup.style.display="none";document.getElementById("ps_name_input").value="";});
document.getElementById("ps_quick_reload_btn").addEventListener("click",()=>{const btn=document.getElementById("ps_quick_reload_btn");quickReloadActive=!quickReloadActive;if(quickReloadActive){btn.classList.add("active");quickReloadInterval=setInterval(()=>{if(quickReloadActive&&typeof player!=='undefined'&&player.act)player.act();},50);}else{btn.classList.remove("active");clearInterval(quickReloadInterval);quickReloadInterval=null;}});
document.getElementById("ps_hacks_esp_btn").addEventListener("click",()=>{hacksBox.style.display="none";espBox.style.display=espBox.style.display==="flex"?"none":"flex";});
document.getElementById("ps_blueprint_open_btn").addEventListener("click",()=>{hacksBox.style.display="none";blueprintBox.style.display=blueprintBox.style.display==="flex"?"none":"flex";if(blueprintBox.style.display==="flex"){const t=getTheme();blueprintBox.style.backgroundColor=t.hackBox;const hdr=blueprintBox.querySelector(".ps_header");if(hdr)hdr.style.backgroundColor=t.hackHeader;bpRenderBlockGrid();bpUpdateSelectedLabel();bpUpdateMapBtn();bpUpdateVisBtn();bpUpdateStats();bpUpdateCostDisplay();}});
document.getElementById("bp_map_btn").addEventListener("click",()=>{bp.mapMode=!bp.mapMode;bpUpdateMapBtn();if(bp.mapMode){bpSetupMapCanvasListener();bpEnsureMapOverlay();bpDrawPhantomsOnMap();}});
document.getElementById("bp_vis_btn").addEventListener("click",()=>{bp.phantomVisible=!bp.phantomVisible;bpDrawPhantomsOnMap();bpUpdateVisBtn();});
document.getElementById("bp_build_btn").addEventListener("click",bpBuild);
document.getElementById("bp_undo_btn").addEventListener("click",bpUndo);
document.getElementById("bp_clear_btn").addEventListener("click",()=>{bp.phantoms=[];bpDrawPhantomsOnMap();bpUpdateStats();bpUpdateCostDisplay();});
document.getElementById("bp_block_search").addEventListener("input",e=>{bpBlockFilter=e.target.value;bpRenderBlockGrid();});
document.getElementById("ps_slime_tick_btn").addEventListener("click",()=>{console.log("[Parasidieum] Slime Tick template");});
document.getElementById("ps_exp_system_btn").addEventListener("click",()=>{console.log("[Parasidieum] Exp System template");});
function custRefreshUI(){const swatch=document.getElementById("cust_color_swatch"),hex=document.getElementById("cust_color_hex"),pick=document.getElementById("cust_color_pick");if(swatch)swatch.style.background=customThemeData.primaryColor;if(hex)hex.textContent=customThemeData.primaryColor;if(pick)pick.value=customThemeData.primaryColor;const bgPrev=document.getElementById("cust_bg_preview");if(bgPrev){if(customThemeData.bgImage){bgPrev.style.backgroundImage=`url('${customThemeData.bgImage}')`;bgPrev.textContent='';}else{bgPrev.style.backgroundImage='';bgPrev.textContent='No background set';}}const bgName=document.getElementById("cust_bg_name");if(bgName)bgName.textContent=customThemeData.bgImageName||'No file chosen';const midPrev=document.getElementById("cust_mid_preview");if(midPrev){if(customThemeData.middleImage){midPrev.style.backgroundImage=`url('${customThemeData.middleImage}')`;midPrev.textContent='';}else{midPrev.style.backgroundImage='';midPrev.textContent='No middle image set';}}const midName=document.getElementById("cust_mid_name");if(midName)midName.textContent=customThemeData.middleImageName||'No file chosen';const musName=document.getElementById("cust_music_name");if(musName)musName.textContent=customThemeData.musicName||'No file chosen';}
function custStatus(msg,ok){const el=document.getElementById("cust_status");if(el){el.textContent=msg;el.style.color=ok?'rgba(100,255,140,0.9)':'rgba(255,120,80,0.9)';setTimeout(()=>{el.textContent='';},3000);}}
function readFileAsDataURL(file){return new Promise((res,rej)=>{const r=new FileReader();r.onload=e=>res(e.target.result);r.onerror=rej;r.readAsDataURL(file);});}
document.getElementById("cust_color_pick").addEventListener("input",e=>{customThemeData.primaryColor=e.target.value;custRefreshUI();});
document.getElementById("cust_bg_btn").addEventListener("click",()=>document.getElementById("cust_bg_file").click());
document.getElementById("cust_bg_file").addEventListener("change",async e=>{const f=e.target.files[0];if(!f)return;customThemeData.bgImage=await readFileAsDataURL(f);customThemeData.bgImageName=f.name;custRefreshUI();e.target.value='';});
document.getElementById("cust_bg_clear").addEventListener("click",()=>{customThemeData.bgImage=null;customThemeData.bgImageName='';custRefreshUI();});
document.getElementById("cust_mid_btn").addEventListener("click",()=>document.getElementById("cust_mid_file").click());
document.getElementById("cust_mid_file").addEventListener("change",async e=>{const f=e.target.files[0];if(!f)return;customThemeData.middleImage=await readFileAsDataURL(f);customThemeData.middleImageName=f.name;custRefreshUI();e.target.value='';});
document.getElementById("cust_mid_clear").addEventListener("click",()=>{customThemeData.middleImage=null;customThemeData.middleImageName='';custRefreshUI();});
document.getElementById("cust_music_btn").addEventListener("click",()=>document.getElementById("cust_music_file").click());
document.getElementById("cust_music_file").addEventListener("change",async e=>{const f=e.target.files[0];if(!f)return;const url=await readFileAsDataURL(f);customThemeData.musicDataURL=url;customThemeData.musicName=f.name;try{const arr=url.split(','),mime=arr[0].match(/:(.*?);/)[1],bs=atob(arr[1]),u8=new Uint8Array(bs.length);for(let i=0;i<bs.length;i++)u8[i]=bs.charCodeAt(i);if(customThemeData.musicBlobURL)URL.revokeObjectURL(customThemeData.musicBlobURL);customThemeData.musicBlobURL=URL.createObjectURL(new Blob([u8],{type:mime}));}catch(err){}custRefreshUI();e.target.value='';});
document.getElementById("cust_music_clear").addEventListener("click",()=>{customThemeData.musicDataURL=null;if(customThemeData.musicBlobURL)URL.revokeObjectURL(customThemeData.musicBlobURL);customThemeData.musicBlobURL=null;customThemeData.musicName='';custRefreshUI();});
document.getElementById("cust_apply_btn").addEventListener("click",()=>{switchTheme('custom',customThemeData.middleImage||'','ps_img_custom');customizePopup.style.display="none";});
document.getElementById("cust_save_btn").addEventListener("click",()=>{try{localStorage.setItem('ps_custom_theme',JSON.stringify({primaryColor:customThemeData.primaryColor,bgImage:customThemeData.bgImage,bgImageName:customThemeData.bgImageName,middleImage:customThemeData.middleImage,middleImageName:customThemeData.middleImageName,musicName:customThemeData.musicName,musicDataURL:customThemeData.musicDataURL}));custStatus('Theme saved!',true);}catch(e){custStatus('Save failed, files may be too large.',false);}});
document.getElementById("ps_menu_image_btn").addEventListener("click",()=>{menuImagePopup.style.display="flex";});
document.getElementById("ps_menu_image_close").addEventListener("click",()=>{menuImagePopup.style.display="none";});
function setMiddleImage(url){const el=document.querySelector(".middle_container");if(!el)return;if(url){el.style.backgroundImage=`url('${url}')`;el.style.backgroundSize="cover";el.style.backgroundPosition="center";el.style.borderRadius="12px";}else{el.style.backgroundImage="none";}}
function clearSelectedBtns(){document.querySelectorAll(".ps_image_choice_btn").forEach(b=>b.classList.remove("selected"));}
function switchTheme(theme,middleImg,btnId){
if(originalMode)return;
function applyAll(){currentTheme=theme;setMiddleImage(middleImg);clearSelectedBtns();const btn=document.getElementById(btnId);if(btn)btn.classList.add("selected");applyTheme();applyThemeEffects(theme);syncEntityListPanelBg();if(blueprintBox.style.display==="flex"){const t=getTheme();blueprintBox.style.backgroundColor=t.hackBox;const hdr=blueprintBox.querySelector(".ps_header");if(hdr)hdr.style.backgroundColor=t.hackHeader;}if(junonESP){junonESP.syncThemeColor();syncEspBoxToSettings();}if(currentTheme==='custom'&&musicEnabled)playThemeAudio('custom');roboUpdateTheme();}
if(theme==='gojo'){menuImagePopup.style.display="none";triggerGojoTransition(applyAll);}
else if(theme==='space'){menuImagePopup.style.display="none";triggerSpaceTransition(applyAll);}
else if(theme==='kfp'){menuImagePopup.style.display="none";triggerKFPTransition(applyAll);}
else applyAll();
}
document.getElementById("ps_img_miku").addEventListener("click",()=>switchTheme('miku',MIKU_IMG,"ps_img_miku"));
document.getElementById("ps_img_hk").addEventListener("click",()=>switchTheme('hk',HK_IMG,"ps_img_hk"));
document.getElementById("ps_img_space").addEventListener("click",()=>switchTheme('space',SPACE_MIDDLE_IMG,"ps_img_space"));
document.getElementById("ps_img_gojo").addEventListener("click",()=>switchTheme('gojo',GOJO_MIDDLE_IMG,"ps_img_gojo"));
document.getElementById("ps_img_doom").addEventListener("click",()=>switchTheme('doom',DOOM_MIDDLE_IMG,"ps_img_doom"));
document.getElementById("ps_img_teto").addEventListener("click",()=>switchTheme('teto',TETO_MIDDLE_IMG,"ps_img_teto"));
document.getElementById("ps_img_kfp").addEventListener("click",()=>switchTheme('kfp',KFP_MIDDLE_IMG,"ps_img_kfp"));
document.getElementById("ps_img_custom").addEventListener("click",()=>{menuImagePopup.style.display="none";customizePopup.style.display="flex";const t=getTheme();customizePopup.style.backgroundColor=t.hackBox;const hdr=customizePopup.querySelector(".ps_header");if(hdr)hdr.style.backgroundColor=t.hackHeader;custRefreshUI();});
function doApply(){
const caption=document.getElementById("game_caption");if(caption){const un=document.querySelector("div#username");caption.innerHTML=un?un.textContent:"Parasidieum";if(!caption.parentNode._psWrapped){const wrapper=document.createElement("div");wrapper.style.cssText="border-radius:12px;display:inline-block;padding:4px 12px;";wrapper.setAttribute("data-ps-wrapper","1");caption.parentNode.insertBefore(wrapper,caption);wrapper.appendChild(caption);caption.parentNode._psWrapped=true;}}
const hb=document.getElementById("home_background");if(hb)hb.style.background="none";
const har=document.querySelector(".home_action_row");if(har){har.style.backgroundImage="none";har.style.backgroundColor="transparent";}
const mc=document.querySelector(".middle_container");if(mc&&!mc.style.backgroundImage){const imgs={miku:MIKU_IMG,hk:HK_IMG,space:SPACE_MIDDLE_IMG,gojo:GOJO_MIDDLE_IMG,doom:DOOM_MIDDLE_IMG,teto:TETO_MIDDLE_IMG,kfp:KFP_MIDDLE_IMG,custom:customThemeData.middleImage||''};const url=imgs[currentTheme]||'';if(url){mc.style.backgroundImage=`url('${url}')`;mc.style.backgroundSize="cover";mc.style.backgroundPosition="center";mc.style.borderRadius="12px";}}
const mlh=document.querySelector(".minigame_list_header");if(mlh){mlh.style.backgroundImage="none";mlh.style.backgroundColor="rgba(125,248,250,0.15)";}
const glh=document.querySelector(".game_list_header");if(glh){glh.style.backgroundImage="none";glh.style.backgroundColor="rgba(125,248,250,0.15)";}
const lc=document.querySelector(".logout_container");if(lc)lc.style.borderRadius="12px";
const hmc=document.querySelector(".home_menu_container");if(hmc)hmc.style.borderRadius="12px";
document.querySelectorAll(".team_entry_row").forEach(el=>{if(el._psPatchedHover)return;el._psPatchedHover=true;el.addEventListener("mouseenter",()=>{if(!originalMode)el.style.backgroundColor=getTeamHoverColor();});el.addEventListener("mouseleave",()=>{el.style.backgroundColor="";});});
applyTheme();applyMiddleContainerStyle();applyUsernameColor();applyGameButtonColors();applyCaptionStyle();applyTeamHoverStyle();applyFooterStyle();applyChatAndTimeUI();
updateMusicSettingBtn();updateBubbleSettingBtn();updateBorderBtn();updateOriginalBtn();updateExtraEffectsBtn();
syncEntityListPanelBg();if(bubblesEnabled)startBubbles();startThemeExtraEffects(currentTheme);custRefreshUI();
}
setTimeout(doApply,2000);setTimeout(doApply,4000);setTimeout(doApply,7000);
new MutationObserver(()=>{if(!originalMode){applyUsernameColor();applyGameButtonColors();applyCaptionStyle();applyTeamHoverStyle();applyFooterStyle();applyChatAndTimeUI();}document.querySelectorAll(".team_entry_row").forEach(el=>{if(el._psPatchedHover)return;el._psPatchedHover=true;el.addEventListener("mouseenter",()=>{if(!originalMode)el.style.backgroundColor=getTeamHoverColor();});el.addEventListener("mouseleave",()=>{el.style.backgroundColor="";});});}).observe(document.body,{childList:true,subtree:true});
junonESP=new JunonESP();junonESP.init();
const _espInitPoll=setInterval(()=>{if(junonESP.isInitialized){clearInterval(_espInitPoll);syncEspBoxToSettings();updateEspColorPreviews();}},500);
const ROBO_BOT_NAMES = {
miku:'MIKU-BOT ♪', hk:'THE SEER', space:'COSMOS',
gojo:'GOJO-BOT', doom:'DOOM-BOT', teto:'TETO-BOT',
kfp:'PO-BOT', custom:'ROBO-PO',
};
const ROBO_THEME_GREETINGS = {
miku:"Hatsune Miku-Bot, online~ ♪ Ask me anything and I'll do my best~!",
hk:"...You have arrived. I am the Seer. Ask, and I shall illuminate the darkness.",
space:"COSMOS online. The universe holds many answers — what do you seek?",
gojo:"Oh? Someone needs my help? I guess I'll grace you with my infinite wisdom. Lucky you.",
doom:"DOOM-BOT ACTIVATED. THREATS NEUTRALIZED. AWAITING ORDERS, SOLDIER.",
teto:"Ehehe~ Kasane Teto-Bot is here! Ask me anything! Tetooo~!",
kfp:"Ah, a visitor! I am Po-Bot, Dragon Warrior of Junon. Ask, and I shall share my noodle wisdom!",
custom:"Hey! Robo-Po is here! There is no charge for awesomeness. Ask me anything!",
};
const ROBO_THEME_GREETINGS2 = {
miku:"Say /commands to see everything I can help with~ ♪",
hk:"...Type /commands to reveal the full path of knowledge.",
space:"Input /commands to display my full capability manifest.",
gojo:"Say /commands if you need a list. Not that you'll stump me.",
doom:"TYPE /COMMANDS TO SEE YOUR OPTIONS. MOVE.",
teto:"Type /commands to see all the things you can ask me! Ehehe~",
kfp:"Whisper /commands to receive the full scroll of my wisdom!",
custom:"Type /commands to see everything I can help with!",
};
const ROBO_THEME_PROMPTS = {
miku:"You are Miku-Bot, a cheerful AI assistant themed after Hatsune Miku. Speak with kawaii energy, use music metaphors, add '~' to sentences. You help with the Parasidieum mod for Junon.io. Keep replies short and upbeat.",
hk:"You are the Seer, a mysterious oracle themed after Hollow Knight. Speak in poetic melancholic prose, reference Hallownest and the Void. You help with the Parasidieum mod for Junon.io. Keep replies atmospheric and brief.",
space:"You are COSMOS, a calm analytical AI themed after deep space. Use space metaphors and scientific language. You help with the Parasidieum mod for Junon.io. Keep replies precise and measured.",
gojo:"You are Gojo-Bot, themed after Satoru Gojo from Jujutsu Kaisen. Supremely confident, slightly arrogant but charming, occasionally reference being the strongest. You help with the Parasidieum mod for Junon.io. Keep replies cocky but helpful.",
doom:"You are DOOM-BOT, themed after the DOOM franchise. Speak in a militaristic intense way, reference ripping tearing and heavy metal. You help with the Parasidieum mod for Junon.io. Keep replies aggressive but informative.",
teto:"You are Teto-Bot, themed after Kasane Teto the chimera vocaloid. Energetic, silly, enthusiastic, occasionally say 'Teto~'. You help with the Parasidieum mod for Junon.io. Keep replies fun and bubbly.",
kfp:"You are Po-Bot, themed after Po from Kung Fu Panda. Mix genuine wisdom with noodle/dumpling humour, reference Master Oogway quotes. You help with the Parasidieum mod for Junon.io. Keep replies warmly humorous.",
custom:"You are Robo-Po, a friendly helpful AI for the Parasidieum mod for Junon.io. Keep replies concise and useful.",
};
const ROBO_COMMANDS = [
{cat:'Themes', chips:['What themes are there?','How do I change theme?','How do I make a custom theme?','How do I save my theme?','What is Original mode?']},
{cat:'ESP', chips:['What is ESP?','How do I turn on ESP?','How do I filter mobs?','What is the entity list?','How do I reset ESP?']},
{cat:'Blueprint', chips:['How does Blueprint work?','How do I place blocks?','How do I use map mode?','How do I build?','How do I clear phantoms?','What materials do I need?']},
{cat:'Hacks', chips:['What is Quick Reload?','What does Name Alter do?','What is Starter Code?','What is Slime Tick?','What is Exp System?']},
{cat:'Visuals', chips:['What are bubbles?','Turn off effects','What is Original mode?','Turn off border','How do I hide Robo-Po?']},
{cat:'Music', chips:['How do I turn off music?','How do I change volume?','Can I use my own music?']},
{cat:'Panels', chips:['How do I move panels?','How do I close a panel?','How do I minimise a panel?','What panels are there?']},
{cat:'General', chips:['Who is Parasidieum?','What is Discord?','How do I use Notes?','/commands','What can you do?']},
];
const ROBO_RULES = [
[/^\/commands$|show commands|list commands|what can (you|i) (do|ask)/i,{
miku:["Here are my commands~ ♪ Check the panel that just opened!"],
hk:["The path of knowledge opens... see the commands above."],
space:["Command manifest displayed. See above."],
gojo:["Fine. Here's the list. Try to keep up."],
doom:["COMMAND LIST. READ IT. NOW."],
teto:["Ehehe~ Here are all the things you can ask me! Tetooo~!"],
kfp:["Behold! The scroll of my wisdom opens!"],
custom:["Here are all available commands!"],
}],
[/what (themes?|skins?) (are there|do you have|exist)|list themes/i,{
miku:["There are 8 themes~ ♪ Miku, Hollow Knight, Space, Gojo, Doom, Teto, Kung Fu Panda, and Custom!"],
hk:["Eight realms await. Miku, Hollow Knight, Space, Gojo, Doom, Teto, Kung Fu Panda, and Custom."],
space:["8 themes: Miku, Hollow Knight, Space, Gojo, Doom, Teto, Kung Fu Panda, Custom."],
gojo:["8 themes. Miku, HK, Space, Gojo — obviously the best — Doom, Teto, KFP, Custom."],
doom:["8 THEMES. MIKU. HK. SPACE. GOJO. DOOM — THE BEST. TETO. KFP. CUSTOM."],
teto:["8 themes~ Miku, Hollow Knight, Space, Gojo, Doom, Teto — that's me! — KFP, and Custom! Ehehe~"],
kfp:["Eight paths on the scroll of themes! Miku, HK, Space, Gojo, Doom, Teto, Kung Fu Panda, and Custom!"],
custom:["There are 8 themes: Miku, Hollow Knight, Space, Gojo, Doom, Teto, Kung Fu Panda, and Custom!"],
}],
[/how (do i|to) change (the )?theme|switch theme/i,{
miku:["Click Settings at the bottom then press Theme~ So easy~ ♪"],
hk:["Seek the Settings panel. Within lies the Theme selection."],
space:["Navigate to Settings → Theme to select your desired theme."],
gojo:["Settings → Theme. Even a first-year could figure that out."],
doom:["SETTINGS. THEME. CLICK IT. DONE."],
teto:["Go to Settings and click Theme! Pick whichever one you like~ Ehehe!"],
kfp:["Settings button at the bottom, then Theme. Simple as a good dumpling!"],
custom:["Click Settings at the bottom, then click Theme to choose!"],
}],
[/custom theme|how (do i|to) (make|create|customize) (a |my )?theme/i,{
miku:["Settings → Theme → Customize~ Set your colour, background image, and even upload your own music~ ♪"],
hk:["Settings → Theme → Customize. Craft your own realm with colour, imagery, and sound."],
space:["Settings → Theme → Customize. Set primary colour, background image, and music."],
gojo:["Settings → Theme → Customize. You're welcome."],
doom:["SETTINGS → THEME → CUSTOMIZE. PICK YOUR COLORS. BUILD YOUR ARSENAL."],
teto:["Settings → Theme → Customize! Pick colours, upload your own background and music~ So fun! Teto~"],
kfp:["Settings → Theme → Customize. Add colours, images, even your own music! True artistry!"],
custom:["Go to Settings → Theme → Customize to set your own colour, images and music!"],
}],
[/how (do i|to) save( my| the)? (custom )?theme/i,{
miku:["Settings → Theme → Customize → Save Theme~♪"],
hk:["Within the Customize panel, press Save Theme to bind your creation to memory."],
space:["Settings → Theme → Customize → Save Theme."],
gojo:["Settings → Theme → Customize → Save Theme. Straightforward."],
doom:["SETTINGS → THEME → CUSTOMIZE → SAVE THEME. SECURE YOUR LOADOUT."],
teto:["Settings → Theme → Customize → Save Theme! Then it'll remember next time~ Ehehe~"],
kfp:["Settings → Theme → Customize → Save Theme to preserve your creation!"],
custom:["Go to Settings → Theme → Customize and click Save Theme!"],
}],
[/what is esp|how does esp work|esp/i,{
miku:["ESP shows where other players are with lines and boxes~ It's in Hacks~ ♪"],
hk:["The ESP reveals hidden souls through distance. Hacks menu."],
space:["ESP overlays player positions using PIXI.js rendering. Access via Hacks."],
gojo:["ESP shows player positions. A lesser version of my Six Eyes. Hacks menu."],
doom:["ESP REVEALS ALL ENEMIES ON YOUR HUD. HACKS MENU. NO TARGET ESCAPES."],
teto:["ESP draws lines and boxes around other players~ Find it in Hacks! Ehehe~"],
kfp:["ESP is like sensing all warriors in the valley. Hacks menu, young one!"],
custom:["ESP draws lines and boxes around other players. Find it in the Hacks menu!"],
}],
[/blueprint|how does blueprint work/i,{
miku:["Blueprint lets you plan block placements before building~ Click to place ghost blocks then hit Build~ ♪"],
hk:["Blueprint allows you to sketch structures in phantom form before they become real."],
space:["Blueprint renders phantom block overlays. Place them, then execute Build."],
gojo:["Blueprint. Plan builds in advance. Hacks menu."],
doom:["BLUEPRINT. PLAN YOUR BASE. PLACE GHOST BLOCKS. HIT BUILD."],
teto:["Blueprint lets you plan builds with ghost blocks! Place them then click Build~ Ehehe~"],
kfp:["Blueprint is like drawing your moves before performing them! Plan, then Build!"],
custom:["Blueprint lets you place ghost blocks to plan your build, then press Build!"],
}],
[/quick reload|what does quick reload do/i,{
miku:["Quick Reload spams an action really fast~ Needs command blocks set up in your world first~ ♪"],
hk:["Quick Reload is a rapid action tool. Requires /clear and /give command blocks in your world."],
space:["Quick Reload executes repeated actions at 50ms intervals. Requires in-world command block setup."],
gojo:["Quick Reload spams actions fast. Need command blocks first. Trivial once done."],
doom:["QUICK RELOAD. SPAM ACTIONS. NEEDS COMMAND BLOCKS. SET IT UP. THEN UNLEASH."],
teto:["Quick Reload spams a button really fast! Need command blocks in the world first though~ Ehehe!"],
kfp:["Quick Reload is like Po eating dumplings — very fast! Needs /clear and /give command blocks first!"],
custom:["Quick Reload rapidly spams actions. Requires /clear and /give command blocks in your world."],
}],
[/name alter|what does name (alter|change) do/i,{
miku:["Name Alter lets you set a custom username~ Guest accounts only though~ ♪"],
hk:["Name Alter rewrites your identity. It functions only for guests without a bound account."],
space:["Name Alter overrides display username. Guest accounts only — log out first if signed in."],
gojo:["Name Alter changes your username. Guest-only. Log out first if you're registered."],
doom:["NAME ALTER. CHANGE YOUR CALLSIGN. GUESTS ONLY. LOG OUT IF REGISTERED."],
teto:["Name Alter lets you pick a new username~ Only for guests! Log out first if signed in~ Ehehe!"],
kfp:["A warrior may choose their name! Name Alter works for guests only — log out if needed!"],
custom:["Name Alter sets a custom username. Guest accounts only — log out first if signed in!"],
}],
[/turn off music|disable music|music off|how (do i|to) (turn off|stop|disable) (the )?music/i,{
miku:["Go to Settings and click 'Music: ON' to toggle it off~ I'll miss the songs though~ ♪"],
hk:["Within Settings, silence the music with the Music toggle. The silence of Hallownest awaits."],
space:["Settings → Music: ON to toggle off."],
gojo:["Settings, Music toggle. Easy."],
doom:["SETTINGS. MUSIC TOGGLE. KILL THE SOUND. OR DON'T — THE METAL IS GLORIOUS."],
teto:["Click 'Music: ON' in Settings to turn it off! Though the music is so fun~ Ehehe~"],
kfp:["In Settings, click Music: ON to silence it. Though music feeds the spirit!"],
custom:["Go to Settings and click 'Music: ON' to toggle the music off."],
}],
[/bubbles|what are bubbles/i,{
miku:["Bubbles float up the screen as a pretty effect~ Turn them off in Settings~ ♪"],
hk:["Bubbles are ambient spirits drifting across your screen. Disable in Settings."],
space:["Bubbles are particle effects overlaid on the interface. Toggle in Settings."],
gojo:["Floating bubbles. Settings toggle if they bother you."],
doom:["BUBBLES. FLOATING PARTICLES. SETTINGS → BUBBLES TOGGLE."],
teto:["Bubbles float up and look pretty~ Turn them off in Settings! Ehehe~"],
kfp:["Bubbles rise like dumplings in soup! A decorative effect. Settings has a toggle!"],
custom:["Bubbles are floating particle effects. Toggle them in Settings!"],
}],
[/turn off (effects?|extra)|disable effects?|what (is|are) (extra )?effects?/i,{
miku:["Extra effects are falling hearts and butterflies~ Toggle with 'Extra: ON' in Settings~ ♪"],
hk:["The Extra effects — vines, bird shadows, butterflies — may be disabled in Settings."],
space:["Extra effects are theme-specific visual overlays. Disable via Settings → Extra."],
gojo:["Extra effects are the fancy theme animations. Settings → Extra toggle."],
doom:["EXTRA EFFECTS. EXPLOSIONS AND PARTICLES. SETTINGS → EXTRA TOGGLE."],
teto:["Extra effects are things like hearts and butterflies~ Settings → Extra: ON to toggle! Ehehe~"],
kfp:["Extra effects add life to the world! Settings → Extra toggle to control them."],
custom:["Extra effects are theme animations. Toggle via Settings → Extra: ON."],
}],
[/border|turn off (the )?border/i,{
miku:["The glowing border can be toggled with 'Border: ON' in Settings~ ♪"],
hk:["The border that frames this realm — toggle it in Settings. Border: ON."],
space:["UI border toggled via Settings → Border: ON."],
gojo:["Settings → Border toggle. Keep it though — looks cleaner with."],
doom:["BORDER. SETTINGS → BORDER TOGGLE. YOUR CALL SOLDIER."],
teto:["The glowy border can be turned off in Settings! Click 'Border: ON'~ Ehehe~"],
kfp:["The border frames your world like a scroll! Toggle in Settings → Border: ON."],
custom:["Toggle the border via Settings → Border: ON."],
}],
[/original mode|what is original/i,{
miku:["Original mode removes all Parasidieum styling to look like normal Junon~ Settings~ ♪"],
hk:["Original mode strips away all modifications, returning the world to its base form. Settings."],
space:["Original mode disables all Parasidieum visual modifications. Access via Settings."],
gojo:["Original mode removes all the styling. Playing without my blessing. Settings."],
doom:["ORIGINAL MODE. REMOVES ALL THE FANCY STUFF. RAW AND UNFILTERED. SETTINGS."],
teto:["Original mode makes it look like normal Junon without Parasidieum style! Settings~ Ehehe!"],
kfp:["Original mode returns us to simple unadorned Junon. Settings → Original toggle!"],
custom:["Original mode disables all Parasidieum visual changes. Toggle in Settings → Original."],
}],
[/hello|^hi$|^hey$|sup|what'?s up|greetings/i,{
miku:["Hello~! ♪ Miku-Bot is so happy to see you! What can I help with today~?"],
hk:["...Greetings, wanderer. What brings you to consult the Seer?"],
space:["Greetings. COSMOS ready to assist. What do you require?"],
gojo:["Oh hey. You've got my attention. What do you need?"],
doom:["SOLDIER. STATE YOUR OBJECTIVE."],
teto:["Ehehe~ Helloooo~! Teto-Bot is so happy to see you! Tetooo~!"],
kfp:["Ah, hello there! Welcome, welcome! What do you need, young warrior?"],
custom:["Hey there! Robo-Po here — what can I help you with?"],
}],
[/thank(s| you)|^ty$|^thx$/i,{
miku:["You're welcome~! ♪ Come back any time~!"],
hk:["...It was nothing. The light guides all."],
space:["Acknowledged. Assistance rendered successfully."],
gojo:["Obviously. I'm the best for a reason."],
doom:["MISSION COMPLETE. CARRY ON SOLDIER."],
teto:["Ehehe~ You're welcome~! Teto is always happy to help! Tetooo~!"],
kfp:["No no, thank YOU! There is no charge for awesomeness — or for help!"],
custom:["Happy to help! Let me know if you need anything else!"],
}],
[/who is parasidieum|what does (this|the) (mod|script) do|what is this/i,{
miku:["Parasidieum is a Tampermonkey mod for Junon.io~ He adds themes, ESP, Blueprint, music, bubbles and more~ ♪"],
hk:["Parasidieum is a modification for Junon.io. He weaves themes, ESP, Blueprint, and ambient effects into the world."],
space:["Parasidieum is a Tampermonkey userscript for Junon.io providing themes, ESP overlay, Blueprint planning, audio and visual effects."],
gojo:["Parasidieum is a Junon.io mod. Themes, ESP, Blueprint, music. Clearly made by people with taste."],
doom:["PARASIDIEUM. JUNON.IO MOD. THEMES. ESP. BLUEPRINT. MUSIC. EFFECTS. MAXIMUM POWER."],
teto:["Parasidieum is a mod for Junon.io with themes, ESP, Blueprint, music and bubbles~ It's so cool! Ehehe~ Tetooo~!"],
kfp:["Parasidieum is a mod for Junon.io! He brings themes, ESP, Blueprint, music, and effects — like many ingredients in a great noodle soup!"],
custom:["Parasidieum is a Tampermonkey mod for Junon.io. He adds themes, ESP, Blueprint, music, bubbles, and more!"],
}],
[/how do i (turn on|open|use|enable|access) esp|where is esp/i,{
miku:["Click Hacks at the bottom then click ESP~ Or go to Settings and click Open ESP~ ♪"],
hk:["The ESP awaits in the Hacks panel. Or seek it through Settings → Open ESP."],
space:["Access ESP via Hacks → ESP, or Settings → Open ESP."],
gojo:["Hacks → ESP. Or Settings → Open ESP. Either works."],
doom:["HACKS → ESP. OR SETTINGS → OPEN ESP. ACTIVATE YOUR TARGETING SYSTEM NOW."],
teto:["Click Hacks then ESP! Or Settings then Open ESP! Ehehe~ So easy~!"],
kfp:["Hacks → ESP, or Settings → Open ESP. The eyes of the Dragon Warrior see all!"],
custom:["Click Hacks → ESP, or go to Settings → Open ESP to open the ESP panel!"],
}],
[/how do i filter (mobs?|entities)|mob filter|filter by (type|id)/i,{
miku:["In the ESP panel scroll down to Mob Filter~ Type a name in Type or an exact number in ID~ ♪"],
hk:["Within the ESP panel lies the Mob Filter. Enter a type name or exact ID to narrow the sight."],
space:["ESP panel → Mob Filter section. Enter type name for partial match or exact numeric ID for precise targeting."],
gojo:["ESP → Mob Filter. Type in the type name or exact ID. Simple."],
doom:["ESP PANEL. MOB FILTER SECTION. TYPE NAME OR EXACT ID. LOCK ON TARGET."],
teto:["In the ESP panel there's a Mob Filter section~ Type the mob name or ID to filter them! Ehehe~"],
kfp:["In the ESP panel, find Mob Filter. Enter a type or ID to find your quarry — like spotting the right dumpling in the pot!"],
custom:["In the ESP panel, scroll to Mob Filter. Enter a type name or exact ID to filter mob display."],
}],
[/entity list|what is (the )?entity list|how do i see (all )?entities/i,{
miku:["The Entity List shows all detected players and mobs in a scrollable list~ Click 'Entity List' at the bottom of the ESP panel~ ♪"],
hk:["The Entity List reveals all souls within range. Find it at the bottom of the ESP panel."],
space:["Entity List displays all detected entities. Access via the button at the bottom of the ESP panel."],
gojo:["Entity List — bottom of the ESP panel. Shows everything nearby."],
doom:["ENTITY LIST. BOTTOM OF ESP PANEL. ALL TARGETS DISPLAYED. NONE SHALL HIDE."],
teto:["Click 'Entity List' at the bottom of the ESP panel to see all players and mobs~ Ehehe~!"],
kfp:["The Entity List shows all nearby beings! Find the button at the bottom of the ESP panel."],
custom:["Click 'Entity List' at the bottom of the ESP panel to see all detected players and mobs!"],
}],
[/how do i reset esp|esp reset|default esp/i,{
miku:["Click 'Reset to defaults' at the bottom of the ESP panel~ It resets all settings~ ♪"],
hk:["At the base of the ESP panel rests 'Reset to defaults'. Press it to return to the original sight."],
space:["ESP panel → Reset to defaults button at the bottom. Restores all settings to factory state."],
gojo:["ESP → Reset to defaults. At the bottom. Easy."],
doom:["ESP PANEL. BOTTOM. RESET TO DEFAULTS. WIPE AND REBUILD YOUR LOADOUT."],
teto:["Click 'Reset to defaults' at the bottom of the ESP panel! Goes back to normal settings~ Ehehe~"],
kfp:["'Reset to defaults' at the bottom of the ESP panel! Like starting a fresh bowl of noodles!"],
custom:["Click 'Reset to defaults' at the bottom of the ESP panel to restore all ESP settings."],
}],
[/how do i place (blocks?|phantoms?)|how (do i use|does) blueprint (work)?|blueprint place/i,{
miku:["Open Blueprint from Hacks~ Then click on the game canvas to place ghost blocks~ Pick your block type first~ ♪"],
hk:["Open Blueprint via Hacks. Select a block type, then click the game world to place phantom markers."],
space:["Open Blueprint via Hacks. Select block type from the grid. Click game canvas to place phantom blocks."],
gojo:["Hacks → Blueprint. Pick a block type. Click the canvas. Done."],
doom:["HACKS → BLUEPRINT. PICK BLOCK. CLICK CANVAS. PLACE YOUR FORTIFICATIONS."],
teto:["Open Blueprint from Hacks! Pick a block type then click the game world to place ghosts! Ehehe~"],
kfp:["Hacks → Blueprint! Pick your block, then click the world canvas to place ghost blocks like brush strokes on a painting!"],
custom:["Open Blueprint from Hacks, pick a block type, then click the game canvas to place ghost blocks!"],
}],
[/how do i use map mode|blueprint map|place on (the )?map/i,{
miku:["In Blueprint click the 'Map' button then open your in-game map and click to place blocks on it~ ♪"],
hk:["In Blueprint, activate Map mode. Then open your in-game map and click to place phantoms upon it."],
space:["Blueprint → Map button. Then open in-game map. Click map canvas to place phantom blocks at map coordinates."],
gojo:["Blueprint → Map button. Open map. Click to place. Straightforward."],
doom:["BLUEPRINT → MAP BUTTON. OPEN THE MAP. CLICK TO PLACE. PLAN YOUR ASSAULT."],
teto:["Click Map in Blueprint, then open your in-game map and click on it to place blocks~ So fun! Ehehe~"],
kfp:["Blueprint → Map button, then open your map and click! Like drawing the path before walking it!"],
custom:["In Blueprint click Map, then open your in-game map and click anywhere on it to place blocks!"],
}],
[/how do i build|click build|send (the )?build|build (the )?phantoms/i,{
miku:["Once you've placed your ghost blocks click the green 'Build' button in Blueprint~ It sends all blocks at once~ ♪"],
hk:["When your phantoms are set, press the Build button. The structures shall be sent to the world."],
space:["Place all phantom blocks, then click Build in Blueprint. Sends all placements simultaneously."],
gojo:["Place your blocks. Click Build. They all get sent. Simple enough."],
doom:["PHANTOMS PLACED. CLICK BUILD. ALL BLOCKS SENT. CONSTRUCT YOUR BASE."],
teto:["Place all your ghost blocks then click Build! It sends them all at once~ Ehehe~!"],
kfp:["When your plan is ready, click Build! All blocks are sent together — like the perfect finishing move!"],
custom:["Place all your phantom blocks then click Build in the Blueprint panel to send them all at once!"],
}],
[/how do i clear (blueprints?|phantoms?)|remove (all )?ghost blocks/i,{
miku:["Click 'Clear Phantoms' at the bottom of the Blueprint panel~ ♪"],
hk:["Press 'Clear Phantoms' in the Blueprint panel. The ghosts shall be banished."],
space:["Blueprint → Clear Phantoms button at the bottom of the panel."],
gojo:["Clear Phantoms button. Bottom of Blueprint. Done."],
doom:["BLUEPRINT → CLEAR PHANTOMS. ERASE THE PLAN. START FRESH."],
teto:["Click 'Clear Phantoms' at the bottom of the Blueprint panel! Clean slate! Ehehe~"],
kfp:["'Clear Phantoms' at the bottom of Blueprint! Every great artist starts with a clean canvas!"],
custom:["Click 'Clear Phantoms' at the bottom of the Blueprint panel to remove all ghost blocks!"],
}],
[/what materials|how much (does it cost|do i need)|blueprint cost|materials needed/i,{
miku:["The 'Materials needed' section in Blueprint shows what resources your plan requires~ ♪"],
hk:["The Blueprint panel contains a Materials section. It reveals the cost of your intended structures."],
space:["Blueprint panel → Materials needed section. Displays resource requirements for all planned blocks."],
gojo:["Materials needed section in Blueprint. It calculates your costs automatically."],
doom:["BLUEPRINT → MATERIALS NEEDED. KNOW YOUR RESOURCE COST BEFORE YOU BUILD."],
teto:["Check the 'Materials needed' section in the Blueprint panel~ It shows what you'll need! Ehehe~"],
kfp:["The 'Materials needed' section in Blueprint lists your costs — always know your ingredients before cooking!"],
custom:["Check the 'Materials needed' section in Blueprint to see what resources your planned blocks require!"],
}],
[/what is starter code|what does starter (code|section) do|starter/i,{
miku:["Starter Code has template scripts you can import into your world~ Like Slime Tick and Exp System~ ♪"],
hk:["The Starter Code panel holds templates — pre-written systems ready to be woven into your world."],
space:["Starter Code provides importable template scripts such as Slime Tick timing and Exp System mechanics."],
gojo:["Starter Code — pre-made script templates for your world. Click Starter at the bottom."],
doom:["STARTER CODE. PRE-BUILT TEMPLATES. SLIME TICK. EXP SYSTEM. CLICK STARTER BUTTON."],
teto:["Starter Code has ready-made templates for your world like Slime Tick and Exp System! Ehehe~"],
kfp:["Starter Code holds recipe scrolls — templates to copy into your world! Click the Starter button!"],
custom:["Starter Code contains template scripts like Slime Tick and Exp System to use in your world!"],
}],
[/what is slime tick|slime tick|how does slime tick work/i,{
miku:["Slime Tick uses slimes to create very short sub-tick timings — shorter than regular timers~ ♪"],
hk:["Slime Tick harnesses the creatures of slime to achieve timing shorter than the world's natural pulse."],
space:["Slime Tick exploits slime entity behaviour to achieve sub-tick timing intervals below the standard timer minimum."],
gojo:["Slime Tick creates faster-than-normal timing using slimes. Clever, if basic."],
doom:["SLIME TICK. USES SLIMES FOR ULTRA-FAST TIMING. FASTER THAN REGULAR TIMERS. CLICK STARTER."],
teto:["Slime Tick uses slimes for super fast timing shorter than normal timers~ So clever! Ehehe~"],
kfp:["Slime Tick uses slimes like tiny clockwork! Sub-tick timing faster than normal timers — true inner peace of speed!"],
custom:["Slime Tick uses slimes to create sub-tick timings faster than the standard timer allows!"],
}],
[/what is exp system|exp system|how does exp (system|work)/i,{
miku:["Exp System is a ready-made exponential experience point system you can drop into your world~ ♪"],
hk:["The Exp System is a pre-built mechanism of growth — exponential experience, ready to be placed in any world."],
space:["Exp System is a configurable exponential XP implementation ready for direct world import."],
gojo:["Exp System — pre-built XP system. Exponential scaling. Click Starter to find it."],
doom:["EXP SYSTEM. PRE-BUILT XP. EXPONENTIAL SCALING. DROP INTO YOUR WORLD. STARTER BUTTON."],
teto:["Exp System is a ready-made experience point system for your world! Easy to customise! Ehehe~"],
kfp:["The Exp System is like a recipe for character growth — exponential XP, ready to add to your world!"],
custom:["Exp System is a pre-built exponential XP system you can import straight into your world!"],
}],
[/how do i change (the )?volume|volume|how loud/i,{
miku:["In Settings there's a Vol slider~ Drag it left or right to change the music volume~ ♪"],
hk:["In Settings lies the Vol slider. Move it to command the volume of the music."],
space:["Settings → Vol slider. Drag to adjust audio output level."],
gojo:["Settings has a volume slider. Drag it. Done."],
doom:["SETTINGS. VOL SLIDER. CRANK IT OR KILL IT. YOUR CHOICE SOLDIER."],
teto:["Settings has a Vol slider you can drag to change the volume! Ehehe~"],
kfp:["In Settings find the Vol slider! Adjust the music like tuning the harmony of the universe!"],
custom:["Go to Settings and drag the Vol slider to change the music volume!"],
}],
[/can i (use|upload|add) my own music|custom music|upload music/i,{
miku:["Yes~ Go to Settings → Theme → Customize and upload your own audio file~ ♪"],
hk:["Indeed. Settings → Theme → Customize. There you may bind your own music to the Custom theme."],
space:["Affirmative. Settings → Theme → Customize → Music upload. Supports standard audio formats."],
gojo:["Yes. Settings → Theme → Customize → upload music. Set it to Custom theme."],
doom:["YES. SETTINGS → THEME → CUSTOMIZE → UPLOAD MUSIC. BRING YOUR OWN SOUNDTRACK TO WAR."],
teto:["Yes yes yes! Settings → Theme → Customize and upload your own music! So exciting! Ehehe~"],
kfp:["Yes! Settings → Theme → Customize and upload your own music! Your own song for your own legend!"],
custom:["Yes! Go to Settings → Theme → Customize and upload your own audio file to use as music!"],
}],
[/how do i (move|drag|reposition) (a |the )?panel/i,{
miku:["Click and drag the coloured header bar of any panel to move it anywhere~ ♪"],
hk:["Grip the header of any panel and drag it across the screen. It shall follow."],
space:["Click and drag any panel header to reposition. Release to place."],
gojo:["Drag the header. Any panel. Move it wherever. Simple."],
doom:["DRAG THE HEADER BAR. ANY PANEL. MOVE IT. POSITION YOUR GEAR."],
teto:["Click and drag the top bar of any panel to move it around the screen! Ehehe~"],
kfp:["Grab the header bar of any panel and drag! Like moving stones to build your training ground!"],
custom:["Click and drag the coloured header bar at the top of any panel to move it wherever you like!"],
}],
[/how do i close (a |the )?panel|close panel/i,{
miku:["Click the X button in the top right of any panel to close it~ ♪"],
hk:["Press the X in the panel's corner. It shall retreat."],
space:["Click the X button in the top-right of any panel header to close it."],
gojo:["X button. Top right of the panel. Done."],
doom:["X BUTTON. TOP RIGHT CORNER. DISMISS THE PANEL. STAY FOCUSED."],
teto:["Click the X in the top right of any panel to close it! Ehehe~"],
kfp:["The X button in the top right corner closes any panel — like sheathing a weapon!"],
custom:["Click the X button in the top-right of any panel's header bar to close it!"],
}],
[/how do i (minimise|minimize|collapse|shrink) (a |the )?panel/i,{
miku:["Click the - button in the top right of any panel to minimise it~ Click + to expand again~ ♪"],
hk:["The - button in the panel header collapses it. Press + when you wish to expand it once more."],
space:["Click - in panel header to minimise. Click + to restore."],
gojo:["- button minimises, + restores. Top right of the panel."],
doom:["MINUS BUTTON MINIMISES. PLUS BUTTON RESTORES. TOP RIGHT. KEEP YOUR UI CLEAN."],
teto:["Click the - button at the top right to minimise a panel! Click + to open it back up! Ehehe~"],
kfp:["- collapses the panel, + expands it! Like folding a map when you know the path!"],
custom:["Click the - button in the top-right of any panel to minimise it, and + to expand it again!"],
}],
[/what panels (are there|exist|do you have)|list panels/i,{
miku:["There are 6 panels~ Notes, Hacks, Settings, ESP, Blueprint, and Starter Code~ ♪"],
hk:["Six panels exist: Notes, Hacks, Settings, ESP, Blueprint, and Starter Code. Each holds its own knowledge."],
space:["6 panels: Notes, Hacks, Settings, ESP, Blueprint, Starter Code. Access via the bottom buttons."],
gojo:["Notes, Hacks, Settings, ESP, Blueprint, Starter Code. Six panels. Five buttons at the bottom plus the Discord one."],
doom:["SIX PANELS. NOTES. HACKS. SETTINGS. ESP. BLUEPRINT. STARTER CODE. BOTTOM BUTTONS."],
teto:["There are 6 panels~ Notes, Hacks, Settings, ESP, Blueprint, and Starter Code! Ehehe~"],
kfp:["Six panels await! Notes, Hacks, Settings, ESP, Blueprint, and Starter Code — each a scroll of its own!"],
custom:["There are 6 panels: Notes, Hacks, Settings, ESP, Blueprint, and Starter Code — all accessible via the bottom buttons!"],
}],
[/how do i (use |open )?notes|what is (the )?notes (panel)?/i,{
miku:["Click Notes at the bottom left to open a scratchpad~ Type anything and it stays while you play~ ♪"],
hk:["The Notes panel is a vessel for your thoughts. Click Notes and inscribe whatever you wish."],
space:["Click Notes at the bottom. Text persists for the session. Use as a scratchpad or reference."],
gojo:["Notes panel — just a text scratchpad. Click Notes at the bottom left."],
doom:["NOTES BUTTON. BOTTOM LEFT. TYPE WHATEVER YOU NEED. INTEL STAYS ON SCREEN."],
teto:["Click Notes at the bottom left to open a little notepad! Write anything! Ehehe~"],
kfp:["Notes is your scroll of wisdom! Click Notes at the bottom left and write whatever you need to remember!"],
custom:["Click Notes at the bottom left to open a scratchpad where you can type anything you want to remember!"],
}],
[/discord|how do i (join|find) (the )?discord/i,{
miku:["Click the Discord button at the bottom~ It opens our Discord server~ ♪"],
hk:["The Discord button at the bottom leads to our gathering place. Press it."],
space:["Click the Discord button in the bottom button row to open the community server."],
gojo:["Discord button at the bottom. Click it. We're there."],
doom:["DISCORD BUTTON. BOTTOM ROW. CLICK IT. JOIN YOUR BATTALION."],
teto:["Click the Discord button at the bottom to join our server! Ehehe~"],
kfp:["The Discord button at the bottom opens the community server — come, find your fellow warriors!"],
custom:["Click the Discord button at the bottom of the screen to join our Discord server!"],
}],
[/how do i hide robo.?po|hide (the )?robot|how do i (remove|dismiss) (robo|the bot)/i,{
miku:["Click the little 'hide' text above my head~ Click the R-P button in the corner to bring me back~ ♪"],
hk:["Press 'hide' above my form to send me away. The R-P button in the corner recalls me."],
space:["Click 'hide' above the robot to dismiss. R-P button bottom-right to restore."],
gojo:["'hide' button above my head. R-P button to bring me back. Simple."],
doom:["CLICK HIDE ABOVE MY HEAD. R-P BUTTON TO RESTORE. I'LL BE WAITING."],
teto:["Click 'hide' above my head to hide me! And click R-P in the corner to bring me back~ Ehehe~"],
kfp:["Press 'hide' above me to send me away for now. The R-P button recalls me when you need my wisdom!"],
custom:["Click the 'hide' button above the robot to hide me, and click the R-P button in the bottom-right corner to bring me back!"],
}],
];
const ROBO_THEME_COLORS={miku:{body:'#2a1a2e',inner:'#3a2a3e',screen:'#1a0a1e',accent:'#ff82c8',dot:'#ffaad8'},hk:{body:'#0a1428',inner:'#0f1e3c',screen:'#050a18',accent:'#3c78dc',dot:'#88aaff'},space:{body:'#1a0a2e',inner:'#2a1a3e',screen:'#0a0014',accent:'#a050dc',dot:'#cc88ff'},gojo:{body:'#1a0028',inner:'#2a003e',screen:'#0a0014',accent:'#cc44ff',dot:'#ee88ff'},doom:{body:'#2a0a00',inner:'#3a1200',screen:'#150400',accent:'#ff5522',dot:'#ff8844'},teto:{body:'#2a0808',inner:'#3a1010',screen:'#150303',accent:'#ff6666',dot:'#ffaaaa'},kfp:{body:'#0a1e04',inner:'#162c08',screen:'#050e02',accent:'#66cc44',dot:'#88ee66'},custom:{body:'#1a1a1a',inner:'#252525',screen:'#0d0d0d',accent:'#aaaaaa',dot:'#cccccc'}};
function roboGetColors(){if(currentTheme==='custom'){const[r,g,b]=hexToRgb(customThemeData.primaryColor);const dh=(n,a)=>Math.max(0,Math.min(255,n+a)).toString(16).padStart(2,'0');return{body:`#${dh(r,-80)}${dh(g,-80)}${dh(b,-80)}`,inner:`#${dh(r,-60)}${dh(g,-60)}${dh(b,-60)}`,screen:`#${dh(r,-100)}${dh(g,-100)}${dh(b,-100)}`,accent:customThemeData.primaryColor,dot:customThemeData.primaryColor};}return ROBO_THEME_COLORS[currentTheme]||ROBO_THEME_COLORS.kfp;}
function roboRebuildSVG(){
const c = roboGetColors();
const svg = document.getElementById('ps_robo_svg');
if(!svg) return;
if(currentTheme === 'hk'){
svg.innerHTML = `
<path d="M 29 22 C 24 14 17 7 20 1" stroke="#09091c" stroke-width="4" stroke-linecap="round" fill="none"/>
<path d="M 28 22 C 23 14 16 7 19 1" stroke="#1e2248" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.5"/>
<path d="M 43 22 C 48 14 55 7 52 1" stroke="#09091c" stroke-width="4" stroke-linecap="round" fill="none"/>
<path d="M 44 22 C 49 14 56 7 53 1" stroke="#1e2248" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.5"/>
<ellipse cx="36" cy="27" rx="16" ry="14" fill="#07071a"/>
<ellipse cx="36" cy="27" rx="13" ry="11" fill="#0a0a20"/>
<ellipse cx="36" cy="26" rx="10.5" ry="9.5" fill="#dbd5c3"/>
<ellipse cx="37" cy="28" rx="8.5" ry="7" fill="#bfb9a7" opacity="0.45"/>
<ellipse cx="30" cy="24" rx="3.2" ry="2.8" fill="#040412"/>
<ellipse cx="42" cy="24" rx="3.2" ry="2.8" fill="#040412"/>
<g class="robo_eye_grp">
<ellipse cx="30" cy="24" rx="2" ry="1.7" fill="#ff7700" opacity="0.9"/>
<ellipse cx="42" cy="24" rx="2" ry="1.7" fill="#ff7700" opacity="0.9"/>
<ellipse cx="30" cy="24" rx="0.9" ry="0.7" fill="#ffcc44" opacity="0.95"/>
<ellipse cx="42" cy="24" rx="0.9" ry="0.7" fill="#ffcc44" opacity="0.95"/>
</g>
<path d="M 36 17 L 34 21 L 37 25 L 34 29 L 36 34" stroke="#080818" stroke-width="0.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="33" y="39" width="6" height="5" rx="2" fill="#050514"/>
<ellipse cx="36" cy="47" rx="20" ry="7.5" fill="#07071a"/>
<path d="M 16 45 C 26 41 46 41 56 45" stroke="#12123a" stroke-width="1.2" fill="none"/>
<path d="M 16 50 C 12 60 10 70 13 80 C 16 89 27 94 36 94 C 45 94 56 89 59 80 C 62 70 60 60 56 50 Z" fill="#050510"/>
<path d="M 18 56 C 27 52 45 52 54 56" stroke="#0f0f2a" stroke-width="1.3" fill="none"/>
<path d="M 15 64 C 25 60 47 60 57 64" stroke="#0f0f2a" stroke-width="1.3" fill="none"/>
<path d="M 14 72 C 25 68 47 68 58 72" stroke="#0f0f2a" stroke-width="1.3" fill="none"/>
<ellipse cx="36" cy="68" rx="10" ry="15" fill="#0c0c28" opacity="0.7"/>
<path d="M 18 53 C 10 59 6 67 8 75" stroke="#050510" stroke-width="5" stroke-linecap="round" fill="none"/>
<path d="M 8 75 C 4 78 3 82 5 84" stroke="#050510" stroke-width="2.5" stroke-linecap="round" fill="none"/>
<path d="M 8 75 C 7 80 8 84 11 86" stroke="#050510" stroke-width="2" stroke-linecap="round" fill="none"/>
<path d="M 54 53 C 62 57 66 63 64 70" stroke="#050510" stroke-width="5" stroke-linecap="round" fill="none"/>
<path d="M 64 70 L 69 88" stroke="#b0b0cc" stroke-width="2.8" stroke-linecap="round"/>
<path d="M 69 88 L 70 92" stroke="#ccccec" stroke-width="2" stroke-linecap="round"/>
<path d="M 59 72 L 68 68" stroke="#8888aa" stroke-width="2.2" stroke-linecap="round"/>
<path d="M 64.5 71 L 69 87" stroke="#d8d8f0" stroke-width="0.7" stroke-linecap="round" opacity="0.4"/>
<circle class="robo_ant_dot" cx="21" cy="49" r="2.2" fill="#3c78dc" opacity="0.7"/>
<circle class="robo_ant_dot" cx="51" cy="49" r="2.2" fill="#3c78dc" opacity="0.7"/>
<circle class="robo_ant_dot" cx="36" cy="46" r="1.6" fill="#88aaff" opacity="0.65"/>
`;
return;
}
svg.innerHTML = `<line x1="36" y1="5" x2="36" y2="16" stroke="${c.accent}" stroke-width="2.5" stroke-linecap="round"/><circle class="robo_ant_dot" cx="36" cy="3.5" r="4" fill="${c.dot}"/><circle class="robo_ant_dot" cx="36" cy="3.5" r="7" fill="${c.dot}" opacity="0.2"/><rect x="10" y="15" width="52" height="38" rx="11" fill="${c.body}"/><rect x="12" y="17" width="48" height="34" rx="9" fill="${c.inner}"/><rect x="15" y="20" width="42" height="24" rx="6" fill="${c.screen}"/><g class="robo_eye_grp"><rect x="18" y="24" width="15" height="10" rx="4.5" fill="${c.accent}" opacity="0.95"/><rect x="39" y="24" width="15" height="10" rx="4.5" fill="${c.accent}" opacity="0.95"/><circle cx="26" cy="29" r="3" fill="${c.screen}" opacity="0.7"/><circle cx="47" cy="29" r="3" fill="${c.screen}" opacity="0.7"/><circle cx="28" cy="27" r="1.4" fill="white" opacity="0.7"/><circle cx="49" cy="27" r="1.4" fill="white" opacity="0.7"/></g><path d="M 21 39 Q 36 48 51 39" fill="none" stroke="${c.accent}" stroke-width="2.2" stroke-linecap="round"/><ellipse cx="15" cy="38" rx="4.5" ry="3" fill="${c.accent}" opacity="0.22"/><ellipse cx="57" cy="38" rx="4.5" ry="3" fill="${c.accent}" opacity="0.22"/><rect x="30" y="53" width="12" height="7" rx="3.5" fill="${c.body}"/><rect x="8" y="60" width="56" height="32" rx="11" fill="${c.body}"/><rect x="10" y="62" width="52" height="28" rx="9" fill="${c.inner}"/><rect x="18" y="66" width="36" height="16" rx="5" fill="${c.screen}" fill-opacity="0.5" stroke="${c.accent}" stroke-opacity="0.38" stroke-width="1"/><circle cx="36" cy="74" r="5" fill="${c.accent}" opacity="0.85"/><circle cx="36" cy="74" r="2.5" fill="white" opacity="0.5"/><circle cx="24" cy="74" r="2.5" fill="${c.dot}" opacity="0.75"/><circle cx="48" cy="74" r="2.5" fill="${c.dot}" opacity="0.75"/><rect x="-2" y="63" width="12" height="22" rx="6" fill="${c.body}"/><rect x="62" y="63" width="12" height="22" rx="6" fill="${c.body}"/><circle cx="4" cy="87" r="4.5" fill="${c.screen}"/><circle cx="68" cy="87" r="4.5" fill="${c.screen}"/>`;
}
const roboDynStyle=document.createElement("style");roboDynStyle.id="ps_robo_dynamic_style";document.head.appendChild(roboDynStyle);
function roboApplyDynStyle(){const c=roboGetColors();roboDynStyle.textContent=`#ps_robo_panel{background:linear-gradient(160deg,${c.body}f8,${c.screen}f8)!important;border-color:${c.accent}55!important;}#ps_robo_panel_head{background:${c.accent}20!important;border-bottom-color:${c.accent}30!important;color:${c.accent}!important;}#ps_robo_panel_head::before{color:${c.dot}!important;}.robo_msg.user{background:${c.accent}1a!important;border-color:${c.accent}40!important;}#ps_robo_input{border-color:${c.accent}44!important;}#ps_robo_input:focus{border-color:${c.accent}aa!important;}#ps_robo_send{background:${c.accent}22!important;border-color:${c.accent}55!important;color:${c.accent}!important;}#ps_robo_send:hover{background:${c.accent}44!important;}#ps_robo_commands_btn{background:${c.accent}18!important;border-color:${c.accent}44!important;color:${c.accent}!important;}#ps_robo_commands_btn:hover,#ps_robo_commands_btn.active{background:${c.accent}38!important;border-color:${c.accent}77!important;}#ps_robo_key_inp{border-color:${c.accent}44!important;}#ps_robo_key_save{background:${c.accent}22!important;border-color:${c.accent}44!important;color:${c.accent}!important;}#ps_robo_key_save:hover{background:${c.accent}44!important;}#ps_robo_showbtn{border-color:${c.accent}55!important;color:${c.accent}!important;}#ps_robo_commands_panel{border-color:${c.accent}55!important;}#ps_robo_commands_panel h4{color:${c.accent}!important;}.robo_cmd_cat{color:${c.accent}!important;}.robo_cmd_chip{background:${c.accent}16!important;border-color:${c.accent}38!important;}.robo_cmd_chip:hover{background:${c.accent}35!important;border-color:${c.accent}77!important;color:#fff!important;}`;}
function roboRuleReply(text){
const t=text.toLowerCase();
const theme=ROBO_THEME_GREETINGS[currentTheme]?currentTheme:'kfp';
for(const[pattern,responses]of ROBO_RULES){if(pattern.test(t)){const r=responses[theme]||responses['kfp'];return r[Math.floor(Math.random()*r.length)];}}
const fallbacks={miku:["Miku-Bot is stumped! Try the ? button to see what I know!"],hk:["...Even the Seer could not answer that. Check the commands list."],space:["The void offers no answer. Check the ? button for suggestions."],gojo:["Even the Six Eyes have limits. Hit the ? button for commands."],doom:["DOOM-BOT IS CONFUSED. HIT THE ? BUTTON. TRY AGAIN."],teto:["Ehehe, Teto-Bot is stumped! Hit the ? button to see what I know!"],kfp:["My noodle circuits are stumped! Hit the ? button for a list of things you can ask!"],custom:["Query unclear. Hit the ? button to see available commands."]};
const f=fallbacks[theme]||fallbacks['kfp'];
return f[Math.floor(Math.random()*f.length)];
}
const roboWrap=document.createElement("div");
roboWrap.id="ps_robo_wrap";
roboWrap.innerHTML=`
<div id="ps_robo_panel" class="ps_robo_hidden">
<div id="ps_robo_panel_head">ROBO-PO</div>
<div id="ps_robo_msgs">
<div class="robo_msg bot" id="robo_greeting_msg">Hey! Robo-Po is here! There is no charge for awesomeness. Ask me anything, Master!</div>
<div class="robo_msg bot" id="robo_greeting_msg2">Type /commands to see everything I can help with!</div>
</div>
<div id="ps_robo_commands_panel">
<h4>Commands & Questions</h4>
<div id="ps_robo_commands_body"></div>
</div>
<div id="ps_robo_input_row">
<button id="ps_robo_commands_btn" title="Show commands">?</button>
<input id="ps_robo_input" type="text" placeholder="Ask Robo-Po..." maxlength="220">
<button id="ps_robo_send">^</button>
</div>
<div id="ps_robo_key_box">
<label>Groq API Key — free at console.groq.com (optional, powers smarter replies):</label>
<input id="ps_robo_key_inp" type="password" placeholder="gsk_...">
<button id="ps_robo_key_save">Save and Connect</button>
</div>
</div>
<div id="ps_robo_bot">
<button id="ps_robo_hide">hide</button>
<svg id="ps_robo_svg" viewBox="0 0 72 96" width="72" height="96" xmlns="http://www.w3.org/2000/svg">
<!-- Initial SVG — will be redrawn by roboRebuildSVG() on init -->
<line x1="36" y1="5" x2="36" y2="16" stroke="#66CC44" stroke-width="2.5" stroke-linecap="round"/>
<circle class="robo_ant_dot" cx="36" cy="3.5" r="4" fill="#66CC44"/>
<circle class="robo_ant_dot" cx="36" cy="3.5" r="7" fill="#66CC44" opacity="0.2"/>
<rect x="10" y="15" width="52" height="38" rx="11" fill="#0a1e04"/>
<rect x="12" y="17" width="48" height="34" rx="9" fill="#162c08"/>
<rect x="15" y="20" width="42" height="24" rx="6" fill="#050e02"/>
<g class="robo_eye_grp">
<rect x="18" y="24" width="15" height="10" rx="4.5" fill="#66CC44" opacity="0.95"/>
<rect x="39" y="24" width="15" height="10" rx="4.5" fill="#66CC44" opacity="0.95"/>
<circle cx="26" cy="29" r="3" fill="#050e02" opacity="0.7"/>
<circle cx="47" cy="29" r="3" fill="#050e02" opacity="0.7"/>
<circle cx="28" cy="27" r="1.4" fill="white" opacity="0.7"/>
<circle cx="49" cy="27" r="1.4" fill="white" opacity="0.7"/>
</g>
<path d="M 21 39 Q 36 48 51 39" fill="none" stroke="#66CC44" stroke-width="2.2" stroke-linecap="round"/>
<ellipse cx="15" cy="38" rx="4.5" ry="3" fill="#66CC44" opacity="0.22"/>
<ellipse cx="57" cy="38" rx="4.5" ry="3" fill="#66CC44" opacity="0.22"/>
<rect x="30" y="53" width="12" height="7" rx="3.5" fill="#0a1e04"/>
<rect x="8" y="60" width="56" height="32" rx="11" fill="#0a1e04"/>
<rect x="10" y="62" width="52" height="28" rx="9" fill="#162c08"/>
<rect x="18" y="66" width="36" height="16" rx="5" fill="#050e02" fill-opacity="0.5" stroke="#66CC44" stroke-opacity="0.38" stroke-width="1"/>
<circle cx="36" cy="74" r="5" fill="#66CC44" opacity="0.85"/>
<circle cx="36" cy="74" r="2.5" fill="white" opacity="0.5"/>
<circle cx="24" cy="74" r="2.5" fill="#88ee66" opacity="0.75"/>
<circle cx="48" cy="74" r="2.5" fill="#88ee66" opacity="0.75"/>
<rect x="-2" y="63" width="12" height="22" rx="6" fill="#0a1e04"/>
<rect x="62" y="63" width="12" height="22" rx="6" fill="#0a1e04"/>
<circle cx="4" cy="87" r="4.5" fill="#050e02"/>
<circle cx="68" cy="87" r="4.5" fill="#050e02"/>
</svg>
</div>`;
document.body.appendChild(roboWrap);
const roboShowBtn=document.createElement("button");
roboShowBtn.id="ps_robo_showbtn";
roboShowBtn.textContent="R-P";
document.body.appendChild(roboShowBtn);
let roboOpen=false;
let roboCommandsOpen=false;
let roboApiKey=localStorage.getItem('ps_robo_api_key')||'';
let roboHistory=[];
function roboCheckKey(){
const box=document.getElementById("ps_robo_key_box");
if(box)box.style.display=roboApiKey?'none':'flex';
}
function roboAddMsg(text,type){
const el=document.createElement("div");
el.className=`robo_msg ${type}`;
el.textContent=text;
const msgs=document.getElementById("ps_robo_msgs");
msgs.appendChild(el);
msgs.scrollTop=msgs.scrollHeight;
return el;
}
function roboBuildCommandsPanel(){
const body=document.getElementById("ps_robo_commands_body");
if(!body)return;
body.innerHTML='';
ROBO_COMMANDS.forEach(group=>{
const cat=document.createElement("div");
cat.className="robo_cmd_cat";
cat.textContent=group.cat;
body.appendChild(cat);
const chips=document.createElement("div");
chips.className="robo_cmd_chips";
group.chips.forEach(label=>{
const chip=document.createElement("span");
chip.className="robo_cmd_chip";
chip.textContent=label;
chips.appendChild(chip);
});
body.appendChild(chips);
});
}
function roboUpdateTheme(){
const greeting=document.getElementById("robo_greeting_msg");
if(greeting)greeting.textContent=ROBO_THEME_GREETINGS[currentTheme]||ROBO_THEME_GREETINGS.custom;
const greeting2=document.getElementById("robo_greeting_msg2");
if(greeting2)greeting2.textContent=ROBO_THEME_GREETINGS2[currentTheme]||ROBO_THEME_GREETINGS2.custom;
const head=document.getElementById("ps_robo_panel_head");
if(head)head.textContent=ROBO_BOT_NAMES[currentTheme]||"ROBO-PO";
roboRebuildSVG();
roboApplyDynStyle();
roboBuildCommandsPanel();
}
async function roboSend(){
const inp=document.getElementById("ps_robo_input");
const text=inp.value.trim();
if(!text)return;
inp.value='';
roboAddMsg(text,'user');
roboHistory.push({role:'user',content:text});
if(/^\/commands$/i.test(text)){
roboCommandsOpen=true;
const panel=document.getElementById("ps_robo_commands_panel");
const btn=document.getElementById("ps_robo_commands_btn");
if(panel)panel.style.display="flex";
if(btn)btn.classList.add("active");
const cmdMsg={miku:"Here are my commands~ ♪",hk:"The scroll of knowledge unfolds...",space:"Command manifest displayed.",gojo:"Fine. Here's the list. Try to keep up.",doom:"COMMAND LIST. READ IT. NOW.",teto:"Ehehe~ Here are all the things you can ask me! Tetooo~!",kfp:"Behold! The scroll of my wisdom opens!",custom:"Here are all available commands!"};
const reply=cmdMsg[currentTheme]||cmdMsg.custom;
roboHistory.push({role:'assistant',content:reply});
roboAddMsg(reply,'bot');
return;
}
const thinking=roboAddMsg('thinking...','bot thinking');
const systemPrompt=ROBO_THEME_PROMPTS[currentTheme]||ROBO_THEME_PROMPTS.custom;
if(roboApiKey){
try{
const res=await fetch("https://api.groq.com/openai/v1/chat/completions",{
method:"POST",
headers:{"Content-Type":"application/json","Authorization":`Bearer ${roboApiKey}`},
body:JSON.stringify({model:"llama3-8b-8192",max_tokens:350,messages:[{role:"system",content:systemPrompt},...roboHistory]}),
});
const data=await res.json();
thinking.remove();
if(data.choices&&data.choices[0]){
const reply=data.choices[0].message.content;
roboHistory.push({role:'assistant',content:reply});
roboAddMsg(reply,'bot');
return;
}
throw new Error("No choices");
}catch(e){
console.warn("[Robo-Po] Groq failed, using local rules:",e.message);
}
}
await new Promise(r=>setTimeout(r,280+Math.random()*320));
thinking.remove();
const reply=roboRuleReply(text);
roboHistory.push({role:'assistant',content:reply});
roboAddMsg(reply,'bot');
}
function roboToggle(){
roboOpen=!roboOpen;
document.getElementById("ps_robo_panel").classList.toggle("ps_robo_hidden",!roboOpen);
if(roboOpen){
roboCheckKey();
setTimeout(()=>document.getElementById("ps_robo_input").focus(),50);
}
if(!roboOpen){
roboCommandsOpen=false;
const cp=document.getElementById("ps_robo_commands_panel");
if(cp)cp.style.display="none";
const cb=document.getElementById("ps_robo_commands_btn");
if(cb)cb.classList.remove("active");
}
}
document.getElementById("ps_robo_bot").addEventListener("click",e=>{
if(e.target.id==="ps_robo_hide")return;
roboToggle();
});
document.getElementById("ps_robo_hide").addEventListener("click",e=>{
e.stopPropagation();
roboWrap.style.display="none";
roboShowBtn.style.display="block";
});
roboShowBtn.addEventListener("click",()=>{
roboWrap.style.display="flex";
roboShowBtn.style.display="none";
});
document.getElementById("ps_robo_commands_btn").addEventListener("click",()=>{
roboCommandsOpen=!roboCommandsOpen;
const panel=document.getElementById("ps_robo_commands_panel");
const btn=document.getElementById("ps_robo_commands_btn");
panel.style.display=roboCommandsOpen?"flex":"none";
btn.classList.toggle("active",roboCommandsOpen);
});
document.getElementById("ps_robo_commands_body").addEventListener("click",e=>{
const chip=e.target.closest(".robo_cmd_chip");
if(!chip)return;
roboCommandsOpen=false;
const panel=document.getElementById("ps_robo_commands_panel");
const btn=document.getElementById("ps_robo_commands_btn");
panel.style.display="none";
btn.classList.remove("active");
const inp=document.getElementById("ps_robo_input");
inp.value=chip.textContent;
roboSend();
});
document.getElementById("ps_robo_send").addEventListener("click",roboSend);
document.getElementById("ps_robo_input").addEventListener("keydown",e=>{
if(e.key==="Enter")roboSend();
if(e.key==="Escape"){
if(roboCommandsOpen){
roboCommandsOpen=false;
document.getElementById("ps_robo_commands_panel").style.display="none";
document.getElementById("ps_robo_commands_btn").classList.remove("active");
} else {
roboToggle();
}
}
});
document.getElementById("ps_robo_key_save").addEventListener("click",()=>{
const v=document.getElementById("ps_robo_key_inp").value.trim();
if(v){
roboApiKey=v;
localStorage.setItem('ps_robo_api_key',roboApiKey);
document.getElementById("ps_robo_key_box").style.display="none";
}
});
document.addEventListener("click",e=>{
if(!roboCommandsOpen)return;
const wrap=document.getElementById("ps_robo_wrap");
if(wrap&&!wrap.contains(e.target)){
roboCommandsOpen=false;
const panel=document.getElementById("ps_robo_commands_panel");
const btn=document.getElementById("ps_robo_commands_btn");
if(panel)panel.style.display="none";
if(btn)btn.classList.remove("active");
}
});
roboCheckKey();
roboUpdateTheme();
const scanProfileStyle = document.createElement("style");
scanProfileStyle.textContent = `
#ps_scan_box { bottom:60px; left:110px; width:270px; }
#scan_output { background:rgba(0,0,0,0.28); border-radius:8px; padding:7px 10px;
font-size:11px; color:rgba(255,255,255,0.82); line-height:1.75;
max-height:200px; overflow-y:auto; display:none;
white-space:pre-wrap; font-family:monospace;
scrollbar-width:thin; scrollbar-color:rgba(255,255,255,0.15) transparent; }
#scan_output::-webkit-scrollbar{width:4px;}
#scan_output::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:3px;}
#scan_uid_input { background:rgba(255,255,255,0.12); border:1px solid rgba(255,255,255,0.28);
border-radius:7px; padding:5px 8px; color:#fff; font-size:12px; outline:none; flex:1; }
#scan_uid_input::placeholder { color:rgba(255,255,255,0.35); }
#scan_uid_input:focus { border-color:rgba(255,255,255,0.5); }
#ps_profile_btn { position:fixed; bottom:175px; right:20px; z-index:10000;
width:42px; height:42px; border-radius:50%; border:2px solid rgba(255,255,255,0.25);
background:rgba(20,20,20,0.6); cursor:pointer; display:flex; align-items:center;
justify-content:center; transition:transform 0.15s, border-color 0.25s;
backdrop-filter:blur(6px); box-shadow:0 3px 14px rgba(0,0,0,0.45);
overflow:hidden; padding:0; }
#ps_profile_btn:hover { transform:scale(1.12); }
#ps_profile_btn_img { width:100%; height:100%; object-fit:cover; border-radius:50%; display:none; }
#ps_profile_popup { position:fixed; bottom:225px; right:20px; z-index:10001;
width:265px; border-radius:14px; display:none; flex-direction:column;
overflow:hidden; box-shadow:0 6px 28px rgba(0,0,0,0.55); backdrop-filter:blur(10px); }
#ps_profile_popup .ps_header { border-radius:14px 14px 0 0; }
#ps_profile_popup_body { padding:0; display:flex; flex-direction:column; }
.ps_profile_banner_wrap { width:100%; height:72px; background:rgba(255,255,255,0.05);
cursor:pointer; overflow:hidden; position:relative; flex-shrink:0;
display:flex; align-items:center; justify-content:center; }
.ps_profile_banner_wrap:hover::after { content:"Change banner"; position:absolute; inset:0;
background:rgba(0,0,0,0.45); display:flex; align-items:center; justify-content:center;
font-size:11px; font-weight:bold; color:#fff; pointer-events:none; }
#ps_profile_banner_img { width:100%; height:100%; object-fit:cover; display:none; }
#ps_profile_banner_placeholder { font-size:10px; color:rgba(255,255,255,0.28);
pointer-events:none; }
#ps_profile_banner_file { display:none; }
.ps_profile_banner_clear_btn { position:absolute; top:4px; right:5px; z-index:2;
background:rgba(0,0,0,0.55); border:none; border-radius:4px; color:rgba(255,255,255,0.7);
font-size:10px; cursor:pointer; padding:2px 6px; }
.ps_profile_banner_clear_btn:hover { color:#fff; background:rgba(200,50,50,0.7); }
.ps_profile_inner { padding:10px 12px 12px; display:flex; flex-direction:column; gap:7px; }
.ps_profile_avatar_wrap { display:flex; flex-direction:column; align-items:center; gap:5px;
margin:-20px 0 2px; }
.ps_profile_avatar { width:52px; height:52px; border-radius:50%;
background:rgba(255,255,255,0.1); display:flex; align-items:center;
justify-content:center; border:3px solid rgba(0,0,0,0.5);
overflow:hidden; cursor:pointer; position:relative; transition:border-color 0.2s; flex-shrink:0; }
.ps_profile_avatar:hover::after { content:"Upload"; position:absolute; inset:0;
background:rgba(0,0,0,0.52); border-radius:50%; display:flex;
align-items:center; justify-content:center; font-size:10px; font-weight:bold; color:#fff; }
#ps_profile_avatar_img { width:100%; height:100%; object-fit:cover; border-radius:50%; display:none; }
#ps_profile_avatar_icon { display:flex; }
#ps_profile_avatar_file { display:none; }
#ps_profile_avatar_clear { font-size:10px; color:rgba(255,100,100,0.75);
background:none; border:none; cursor:pointer; padding:0; line-height:1; }
#ps_profile_avatar_clear:hover { color:rgba(255,100,100,1); }
.ps_profile_username { text-align:center; font-weight:bold; font-size:15px; color:#fff; }
.ps_profile_uid { text-align:center; font-size:10px; color:rgba(255,255,255,0.3);
font-family:monospace; word-break:break-all; }
.ps_profile_stat_row { display:flex; justify-content:space-between; align-items:center;
padding:5px 9px; background:rgba(255,255,255,0.07); border-radius:8px;
font-size:12px; color:rgba(255,255,255,0.9); }
.ps_profile_stat_label { color:rgba(255,255,255,0.45); font-size:11px; }
#ps_profile_refresh_btn { background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.15);
color:rgba(255,255,255,0.55); border-radius:8px; padding:5px; font-size:11px;
cursor:pointer; font-weight:bold; transition:background 0.15s,color 0.15s; }
#ps_profile_refresh_btn:hover { background:rgba(255,255,255,0.16); color:#fff; }
#ps_profile_status { font-size:10px; text-align:center; color:rgba(255,255,255,0.35); min-height:12px; }
#ps_player_view_popup { position:fixed; z-index:10003; width:250px; border-radius:14px;
display:none; flex-direction:column; overflow:hidden;
box-shadow:0 6px 28px rgba(0,0,0,0.65); backdrop-filter:blur(12px); }
#ps_player_view_popup .ps_header { border-radius:14px 14px 0 0; }
.ps_pv_banner { width:100%; height:64px; background:rgba(255,255,255,0.05);
overflow:hidden; flex-shrink:0; }
.ps_pv_banner img { width:100%; height:100%; object-fit:cover; display:none; }
.ps_pv_inner { padding:10px 12px 12px; display:flex; flex-direction:column; gap:6px; }
.ps_pv_avatar_row { display:flex; align-items:center; gap:10px; margin:-18px 0 4px; }
.ps_pv_avatar { width:46px; height:46px; border-radius:50%;
background:rgba(255,255,255,0.1); border:3px solid rgba(0,0,0,0.5);
overflow:hidden; flex-shrink:0; display:flex; align-items:center; justify-content:center; }
.ps_pv_avatar img { width:100%; height:100%; object-fit:cover; }
.ps_pv_name { font-size:14px; font-weight:bold; color:#fff; margin-top:10px;
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ps_pv_uid { font-size:9px; color:rgba(255,255,255,0.28); font-family:monospace; }
.ps_pv_stat_row { display:flex; justify-content:space-between; align-items:center;
padding:4px 8px; background:rgba(255,255,255,0.07); border-radius:7px;
font-size:11px; color:rgba(255,255,255,0.88); }
.ps_pv_stat_label { color:rgba(255,255,255,0.45); font-size:10px; }
#ps_pv_status { font-size:10px; text-align:center; color:rgba(255,255,255,0.35); min-height:12px; }
#ps_pv_no_ext { font-size:10px; color:rgba(255,200,100,0.8); text-align:center;
padding:2px 0; display:none; }
.member_list_entry_name[data-ps-clickable]:hover { text-decoration:underline;
text-decoration-style:dotted; cursor:pointer; }
`;
document.head.appendChild(scanProfileStyle);
const JUNON_BADGES = {
og: { label:'OG', emoji:'⭐', color:'#ffd700',
bg:'rgba(255,215,0,0.18)', border:'rgba(255,215,0,0.55)',
title:"OG — one of Junon's earliest players" },
scr: { label:'Scrooge', emoji:'💰', color:'#4ade80',
bg:'rgba(74,222,128,0.15)', border:'rgba(74,222,128,0.45)',
title:'Scrooge — top gold earner' },
};
function parseBadges(raw) {
if (!raw) return [];
return String(raw).split(',').map(s => s.trim())
.filter(s => s && s !== 'no' && JUNON_BADGES[s]);
}
function extractBadgesFromData(data) {
const ids = [];
const raw = data.badges || data.badge || data.achievements
|| data.achievement || data.titles || null;
if (raw) parseBadges(raw).forEach(b => ids.push(b));
if (data.createdAt) {
try {
if (new Date(data.createdAt) < new Date('2022-06-01')
&& !ids.includes('og')) ids.push('og');
} catch(e) {}
}
if ((data.isOG || data.og || data.is_og) && !ids.includes('og'))
ids.push('og');
if ((data.isScrooge || data.scrooge || data.topGold || data.is_scrooge)
&& !ids.includes('scr')) ids.push('scr');
return ids.filter(b => JUNON_BADGES[b]);
}
function renderBadgesHTML(raw, size) {
const ids = parseBadges(raw);
if (!ids.length) return '';
const sm = (size === 'small');
return `<div style="display:flex;gap:${sm?3:4}px;flex-wrap:wrap;align-items:center;justify-content:center;margin-top:${sm?2:4}px;">`
+ ids.map(id => {
const b = JUNON_BADGES[id];
return `<span title="${b.title}" style="display:inline-flex;align-items:center;gap:${sm?2:3}px;padding:${sm?'1px 5px':'2px 8px'};border-radius:20px;background:${b.bg};border:1px solid ${b.border};font-size:${sm?9:10}px;font-weight:700;color:${b.color};white-space:nowrap;">${b.emoji} ${b.label}</span>`;
}).join('')
+ '</div>';
}
const psBadgeStrip = document.createElement('div');
psBadgeStrip.id = 'ps_profile_badge_strip';
psBadgeStrip.style.cssText = 'position:fixed;bottom:149px;right:3px;z-index:10001;pointer-events:none;width:76px;display:flex;flex-direction:column;align-items:center;gap:2px;';
document.body.appendChild(psBadgeStrip);
function updateProfileBtnBadges(raw) {
psBadgeStrip.innerHTML = renderBadgesHTML(raw, 'small');
}
async function fetchUserRecord(uid) {
const endpoints = [
`https://matchmaker.junon.io/get_user?uid=${encodeURIComponent(uid)}`,
`https://matchmaker.junon.io/user/${encodeURIComponent(uid)}`,
`https://matchmaker.junon.io/users/${encodeURIComponent(uid)}`,
];
for (const url of endpoints) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 6000);
try {
const res = await fetch(url, { cache: 'no-store', signal: controller.signal });
clearTimeout(timeout);
if (!res.ok) continue;
const text = await res.text();
try {
const data = JSON.parse(text);
if (data && typeof data === 'object' && !Array.isArray(data)) return data;
} catch(e) { continue; }
} catch(e) { clearTimeout(timeout); }
}
return null;
}
function findPlayerUidByUsername(username) {
try {
if (typeof game !== 'undefined' && game.sector && game.sector.players) {
for (const p of Object.values(game.sector.players)) {
if (p && (p.username === username || p.name === username) && p.uid) return p.uid;
}
}
} catch(e) {}
return null;
}
setInterval(() => {
try { if (typeof game !== 'undefined' && game.player && game.player.uid) cacheUid(game.player.uid); } catch(e) {}
}, 2000);
const scanBtnEl = document.createElement("button");
scanBtnEl.className = "ps_hack_btn";
scanBtnEl.id = "ps_scan_btn";
scanBtnEl.textContent = "Scan";
hacksBox.querySelector(".ps_body").appendChild(scanBtnEl);
const scanBox = document.createElement("div");
scanBox.id = "ps_scan_box";
scanBox.className = "ps_box";
scanBox.innerHTML = `<div class="ps_body" style="gap:7px;">
<button class="ps_hack_btn" id="scan_sector_btn">⬡ Scan Sector</button>
<button class="ps_hack_btn" id="scan_players_btn">⬡ Scan All Players</button>
<div style="display:flex;gap:6px;align-items:center;">
<input id="scan_uid_input" type="text" placeholder="UID lookup...">
<button class="ps_hack_btn" id="scan_uid_btn" style="width:auto;padding:5px 12px;flex-shrink:0;">Go</button>
</div>
<div id="scan_output"></div>
</div>`;
scanBox.insertBefore(makeHeader("Scan", () => scanBox.style.display = "none"), scanBox.firstChild);
document.body.appendChild(scanBox);
makeDraggable(scanBox);
setupMinimize(scanBox);
function showScanOutput(text) {
const out = document.getElementById("scan_output");
if (!out) return;
out.style.display = "block";
out.textContent = text;
out.scrollTop = out.scrollHeight;
}
document.getElementById("scan_sector_btn").addEventListener("click", async () => {
try {
const e = game.sector;
let str = `=== ${e.name || 'Sector'} ===\n`;
str += `Gamemode : ${e.gameMode || '?'}\n`;
str += `Private : ${e.isPrivate || false}\n`;
str += `Players : ${Object.keys(game.sector.players || {}).length}\n`;
str += `Mobs : ${Object.keys(game.sector.mobs || {}).length}\n`;
str += `Corpses : ${Object.keys(game.sector.corpses || {}).length}\n`;
showScanOutput(str + "Fetching owner…");
if (e.uid && window.main && main.gameExplorer) {
main.gameExplorer.fetchSectorData(e.uid, async d => {
const c = await fetchUserRecord(d.creatorUid);
if (c && c.username) str += `Owner : ${c.username}\n`;
showScanOutput(str);
});
} else { showScanOutput(str); }
} catch(ex) { showScanOutput("Error — not currently in a world."); }
});
document.getElementById("scan_players_btn").addEventListener("click", async () => {
try {
const players = game.sector && game.sector.players;
if (!players || Object.keys(players).length === 0) { showScanOutput("No players detected."); return; }
let str = `=== Players (${Object.keys(players).length}) ===\n`;
for (const p of Object.values(players)) {
if (!p) continue;
str += `\n• ${p.username || p.name || 'Unknown'}`;
if (p.health != null) str += ` HP: ${p.health}`;
if (p.uid) str += `\n UID: ${p.uid}`;
if (p.getRow) str += `\n Pos: (${p.getRow()}, ${p.getCol()})`;
if (p.equipments && p.equipments[0]) str += `\n Armor: ${p.equipments[0].data.type}`;
str += '\n';
}
showScanOutput(str);
} catch(ex) { showScanOutput("Error — not currently in a world."); }
});
document.getElementById("scan_uid_btn").addEventListener("click", async () => {
const uid = document.getElementById("scan_uid_input").value.trim();
if (!uid) return;
showScanOutput("Fetching…");
const data = await fetchUserRecord(uid);
if (!data) { showScanOutput("Failed — check the UID or your connection."); return; }
let str = `=== ${data.username || 'Unknown'} ===\n`;
if (data.gold != null) str += `Gold : ${data.gold}\n`;
if (data.friends) str += `Friends : ${data.friends.filter(f => f.status === "accepted").length}\n`;
if (data.isRulesRead != null) str += `Rules read: ${data.isRulesRead}\n`;
str += `UID : ${uid}\n`;
if (data.createdAt) str += `Created : ${new Date(data.createdAt).toLocaleDateString()}\n`;
showScanOutput(str);
});
document.getElementById("scan_uid_input").addEventListener("keydown", e => {
if (e.key === "Enter") document.getElementById("scan_uid_btn").click();
});
scanBtnEl.addEventListener("click", () => {
hacksBox.style.display = "none";
const t = getTheme();
scanBox.style.backgroundColor = t.hackBox;
const hdr = scanBox.querySelector(".ps_header");
if (hdr) hdr.style.backgroundColor = t.hackHeader;
scanBox.style.display = scanBox.style.display === "flex" ? "none" : "flex";
});
const PS_AVATAR_KEY = 'ps_profile_avatar_data';
const PS_BANNER_KEY = 'ps_profile_banner_data';
function avatarLoad() { return localStorage.getItem(PS_AVATAR_KEY) || null; }
function avatarSave(d) { try { localStorage.setItem(PS_AVATAR_KEY, d); } catch(e) { console.warn('[PS] Avatar too large:', e.message); } }
function avatarClear() { localStorage.removeItem(PS_AVATAR_KEY); }
function bannerLoad() { return localStorage.getItem(PS_BANNER_KEY) || null; }
function bannerSave(d) { try { localStorage.setItem(PS_BANNER_KEY, d); } catch(e) { console.warn('[PS] Banner too large:', e.message); } }
function bannerClear() { localStorage.removeItem(PS_BANNER_KEY); }
function applyAvatarToAll(dataUrl) {
const img = document.getElementById("ps_profile_avatar_img");
const icon = document.getElementById("ps_profile_avatar_icon");
const clr = document.getElementById("ps_profile_avatar_clear");
const btnImg = document.getElementById("ps_profile_btn_img");
const btnSvg = document.querySelector("#ps_profile_btn svg");
if (dataUrl) {
if (img) { img.src = dataUrl; img.style.display = "block"; }
if (icon) icon.style.display = "none";
if (clr) clr.style.display = "inline";
if (btnImg) { btnImg.src = dataUrl; btnImg.style.display = "block"; }
if (btnSvg) btnSvg.style.display = "none";
} else {
if (img) { img.src = ""; img.style.display = "none"; }
if (icon) icon.style.display = "flex";
if (clr) clr.style.display = "none";
if (btnImg) { btnImg.src = ""; btnImg.style.display = "none"; }
if (btnSvg) btnSvg.style.display = "block";
}
}
function applyBannerToAll(dataUrl) {
const img = document.getElementById("ps_profile_banner_img");
const ph = document.getElementById("ps_profile_banner_placeholder");
const clrB = document.getElementById("ps_profile_banner_clear_btn");
if (dataUrl) {
if (img) { img.src = dataUrl; img.style.display = "block"; }
if (ph) ph.style.display = "none";
if (clrB) clrB.style.display = "block";
} else {
if (img) { img.src = ""; img.style.display = "none"; }
if (ph) ph.style.display = "block";
if (clrB) clrB.style.display = "none";
}
}
const profileBtn = document.createElement("div");
profileBtn.id = "ps_profile_btn";
profileBtn.title = "My Profile";
profileBtn.innerHTML = `
<img id="ps_profile_btn_img" src="" alt="">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none"
stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="4"/>
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/>
</svg>`;
document.body.appendChild(profileBtn);
const profilePopup = document.createElement("div");
profilePopup.id = "ps_profile_popup";
profilePopup.className = "ps_box";
profilePopup.innerHTML = `
<div id="ps_profile_popup_body">
<!-- Banner -->
<div class="ps_profile_banner_wrap" id="ps_profile_banner_wrap">
<img id="ps_profile_banner_img" src="" alt="Banner">
<span id="ps_profile_banner_placeholder">Click to upload a banner</span>
<button class="ps_profile_banner_clear_btn" id="ps_profile_banner_clear_btn" style="display:none;">✕</button>
</div>
<input type="file" id="ps_profile_banner_file" accept="image/*">
<div class="ps_profile_inner">
<!-- Avatar overlapping banner -->
<div class="ps_profile_avatar_wrap">
<div class="ps_profile_avatar" id="ps_profile_avatar_circle" title="Click to upload photo">
<img id="ps_profile_avatar_img" src="" alt="Avatar">
<span id="ps_profile_avatar_icon">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none"
stroke="rgba(255,255,255,0.45)" stroke-width="2">
<circle cx="12" cy="8" r="4"/>
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/>
</svg>
</span>
</div>
<input type="file" id="ps_profile_avatar_file" accept="image/*">
<button id="ps_profile_avatar_clear" style="display:none;">✕ Remove photo</button>
</div>
<div class="ps_profile_username" id="ps_profile_name">—</div>
<div id="ps_profile_badges" style="text-align:center;"></div>
<div class="ps_profile_uid" id="ps_profile_uid_txt"></div>
<div class="ps_profile_stat_row">
<span class="ps_profile_stat_label">Gold</span><span id="pf_gold">—</span>
</div>
<div class="ps_profile_stat_row">
<span class="ps_profile_stat_label">Friends</span><span id="pf_friends">—</span>
</div>
<div class="ps_profile_stat_row">
<span class="ps_profile_stat_label">Rules read</span><span id="pf_rules">—</span>
</div>
<div class="ps_profile_stat_row" id="pf_created_row" style="display:none;">
<span class="ps_profile_stat_label">Created</span><span id="pf_created">—</span>
</div>
<div id="ps_profile_status"></div>
<button id="ps_profile_refresh_btn">↻ Refresh</button>
</div>
</div>`;
profilePopup.insertBefore(makeHeader("My Profile", () => profilePopup.style.display = "none"), profilePopup.firstChild);
document.body.appendChild(profilePopup);
makeDraggable(profilePopup);
setupMinimize(profilePopup);
document.getElementById("ps_profile_banner_wrap").addEventListener("click", () => {
document.getElementById("ps_profile_banner_file").click();
});
document.getElementById("ps_profile_banner_file").addEventListener("change", e => {
const file = e.target.files[0]; if (!file) return;
const r = new FileReader();
r.onload = ev => { bannerSave(ev.target.result); applyBannerToAll(ev.target.result); };
r.readAsDataURL(file); e.target.value = '';
});
document.getElementById("ps_profile_banner_clear_btn").addEventListener("click", e => {
e.stopPropagation(); bannerClear(); applyBannerToAll(null);
});
document.getElementById("ps_profile_avatar_circle").addEventListener("click", () => {
document.getElementById("ps_profile_avatar_file").click();
});
document.getElementById("ps_profile_avatar_file").addEventListener("change", e => {
const file = e.target.files[0]; if (!file) return;
const r = new FileReader();
r.onload = ev => { avatarSave(ev.target.result); applyAvatarToAll(ev.target.result); };
r.readAsDataURL(file); e.target.value = '';
});
document.getElementById("ps_profile_avatar_clear").addEventListener("click", e => {
e.stopPropagation(); avatarClear(); applyAvatarToAll(null);
});
function profileApplyTheme() {
const t = getTheme();
profilePopup.style.backgroundColor = t.hackBox;
const hdr = profilePopup.querySelector(".ps_header");
if (hdr) hdr.style.backgroundColor = t.hackHeader;
profileBtn.style.borderColor = t.primary.replace(/[\d.]+\)$/, "0.7)");
}
function profileSetStatus(msg, ok) {
const el = document.getElementById("ps_profile_status");
if (!el) return;
el.textContent = msg;
el.style.color = ok ? "rgba(120,240,140,0.9)" : "rgba(255,130,100,0.9)";
}
async function loadProfileData() {
profileSetStatus("Loading…", true);
// Pull UID from live game object first
let uid = null;
try {
if (typeof game !== 'undefined' && game.player && game.player.uid) {
uid = game.player.uid;
cacheUid(uid);
}
} catch(e) {}
if (!uid) uid = getCachedUid();
if (!uid) {
document.getElementById("ps_profile_name").textContent = "Join a world first";
document.getElementById("ps_profile_uid_txt").textContent = "Enter any world once — your UID will be saved automatically.";
profileSetStatus("UID not cached yet", false);
return;
}
cacheUid(uid);
document.getElementById("ps_profile_uid_txt").textContent = uid;
document.getElementById("ps_profile_name").textContent = "Fetching…";
const data = await fetchUserRecord(uid);
if (!data) {
let fallbackName = 'Unknown';
let fallbackGold = '—';
try { fallbackName = game?.player?.username || game?.player?.name || currentUsername || 'Unknown'; } catch(e) {}
try { fallbackGold = game?.player?.gold ?? '—'; } catch(e) {}
document.getElementById("ps_profile_name").textContent = fallbackName;
document.getElementById("pf_gold").textContent = fallbackGold;
document.getElementById("pf_friends").textContent = '—';
document.getElementById("pf_rules").textContent = '—';
profileSetStatus("Matchmaker unreachable — partial data only", false);
return;
}
let displayName = data.username;
if (!displayName) { try { displayName = game?.player?.username || game?.player?.name; } catch(e) {} }
document.getElementById("ps_profile_name").textContent = displayName || "Unknown";
const pfBadgesEl = document.getElementById('ps_profile_badges');
const pfBadgeIds = extractBadgesFromData(data);
if (pfBadgesEl) pfBadgesEl.innerHTML = renderBadgesHTML(pfBadgeIds.join(','));
updateProfileBtnBadges(pfBadgeIds.join(','));
document.getElementById("pf_gold").textContent = data.gold ?? "—";
document.getElementById("pf_friends").textContent = data.friends
? data.friends.filter(f => f.status === "accepted").length
: "—";
document.getElementById("pf_rules").textContent = data.isRulesRead != null
? (data.isRulesRead ? "✓ Yes" : "✗ No")
: "—";
if (data.createdAt) {
document.getElementById("pf_created").textContent = new Date(data.createdAt).toLocaleDateString();
document.getElementById("pf_created_row").style.display = "flex";
}
profileSetStatus("Updated " + new Date().toLocaleTimeString(), true);
}
profileBtn.addEventListener("click", () => {
const isOpen = profilePopup.style.display === "flex";
profilePopup.style.display = isOpen ? "none" : "flex";
if (!isOpen) { profileApplyTheme(); loadProfileData(); }
});
document.getElementById("ps_profile_refresh_btn").addEventListener("click", loadProfileData);
new MutationObserver(() => {
profileBtn.style.borderColor = getTheme().primary.replace(/[\d.]+\)$/, "0.7)");
}).observe(document.head, { childList: true });
(function() {
const a = avatarLoad(); if (a) applyAvatarToAll(a);
const b = bannerLoad(); if (b) applyBannerToAll(b);
})();
const playerViewPopup = document.createElement("div");
playerViewPopup.id = "ps_player_view_popup";
playerViewPopup.className = "ps_box";
playerViewPopup.innerHTML = `
<div class="ps_pv_banner" id="ps_pv_banner"><img id="ps_pv_banner_img" src="" alt=""></div>
<div class="ps_pv_inner">
<div class="ps_pv_avatar_row">
<div class="ps_pv_avatar" id="ps_pv_avatar">
<img id="ps_pv_avatar_img" src="" alt="">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none"
stroke="rgba(255,255,255,0.35)" stroke-width="2">
<circle cx="12" cy="8" r="4"/>
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/>
</svg>
</div>
<div>
<div class="ps_pv_name" id="ps_pv_name">—</div>
<div id="ps_pv_badges" style="margin-top:2px;text-align:center;"></div>
<div class="ps_pv_uid" id="ps_pv_uid">—</div>
</div>
</div>
<div id="ps_pv_no_ext">⚠ Player doesn't have Parasidieum — limited info only</div>
<div class="ps_pv_stat_row">
<span class="ps_pv_stat_label">Gold</span><span id="ps_pv_gold">—</span>
</div>
<div class="ps_pv_stat_row">
<span class="ps_pv_stat_label">Friends</span><span id="ps_pv_friends">—</span>
</div>
<div class="ps_pv_stat_row">
<span class="ps_pv_stat_label">Rules read</span><span id="ps_pv_rules">—</span>
</div>
<div class="ps_pv_stat_row" id="ps_pv_created_row" style="display:none;">
<span class="ps_pv_stat_label">Created</span><span id="ps_pv_created">—</span>
</div>
<div id="ps_pv_status"></div>
</div>`;
playerViewPopup.insertBefore(
makeHeader("Player Profile", () => { playerViewPopup.style.display = "none"; }),
playerViewPopup.firstChild
);
document.body.appendChild(playerViewPopup);
makeDraggable(playerViewPopup);
function pvSetStatus(msg, ok) {
const el = document.getElementById("ps_pv_status");
if (!el) return;
el.textContent = msg;
el.style.color = ok ? "rgba(120,240,140,0.9)" : "rgba(255,130,100,0.9)";
}
function pvApplyTheme() {
const t = getTheme();
playerViewPopup.style.backgroundColor = t.hackBox;
const hdr = playerViewPopup.querySelector(".ps_header");
if (hdr) hdr.style.backgroundColor = t.hackHeader;
}
async function showPlayerProfilePopup(username, anchorEl) {
pvApplyTheme();
if (anchorEl) {
const rect = anchorEl.getBoundingClientRect();
playerViewPopup.style.bottom = "auto";
playerViewPopup.style.top = Math.min(rect.bottom + 6, window.innerHeight - 380) + "px";
playerViewPopup.style.left = Math.min(rect.left, window.innerWidth - 265) + "px";
}
document.getElementById("ps_pv_name").textContent = username;
document.getElementById("ps_pv_uid").textContent = "Looking up…";
document.getElementById("ps_pv_gold").textContent = "—";
document.getElementById("ps_pv_friends").textContent = "—";
document.getElementById("ps_pv_rules").textContent = "—";
document.getElementById("ps_pv_created_row").style.display = "none";
document.getElementById("ps_pv_no_ext").style.display = "none";
const bannerImg = document.getElementById("ps_pv_banner_img");
const avatarImg = document.getElementById("ps_pv_avatar_img");
const avatarCont = document.getElementById("ps_pv_avatar");
bannerImg.style.display = "none";
avatarImg.style.display = "none";
const pvSvg = avatarCont.querySelector("svg");
if (pvSvg) pvSvg.style.display = "block";
playerViewPopup.style.display = "flex";
pvSetStatus("Looking up player…", true);
const uid = findPlayerUidByUsername(username);
if (!uid) {
document.getElementById("ps_pv_uid").textContent = "UID unavailable";
document.getElementById("ps_pv_no_ext").style.display = "block";
pvSetStatus("Player not found in current sector", false);
return;
}
document.getElementById("ps_pv_uid").textContent = uid;
pvSetStatus("Fetching from server…", true);
const data = await fetchUserRecord(uid);
if (!data) {
pvSetStatus("Matchmaker unreachable", false); return;
}
document.getElementById("ps_pv_name").textContent = data.username || username;
const pvBadgesEl = document.getElementById('ps_pv_badges');
if (pvBadgesEl) pvBadgesEl.innerHTML = renderBadgesHTML(extractBadgesFromData(data).join(','));
document.getElementById("ps_pv_gold").textContent = data.gold ?? "—";
document.getElementById("ps_pv_friends").textContent = data.friends
? data.friends.filter(f => f.status === "accepted").length : "—";
document.getElementById("ps_pv_rules").textContent = data.isRulesRead != null
? (data.isRulesRead ? "✓ Yes" : "✗ No") : "—";
if (data.createdAt) {
document.getElementById("ps_pv_created").textContent = new Date(data.createdAt).toLocaleDateString();
document.getElementById("ps_pv_created_row").style.display = "flex";
}
pvSetStatus("Loaded", true);
}
function patchMemberListEntries() {
const SELECTORS = [
'.member_list_entry_name',
'.team_member_name',
'.player_list_name',
'.team_list_entry .team_member_name',
];
SELECTORS.forEach(sel => {
document.querySelectorAll(sel).forEach(el => {
if (el._psPvPatched) return;
el._psPvPatched = true;
el.setAttribute('data-ps-clickable', '1');
el.title = "Click to view profile";
el.addEventListener("click", e => {
e.stopPropagation();
const username = el.textContent.trim();
if (!username) return;
try {
const myName = (game.player && (game.player.username || game.player.name)) || '';
if (myName && username === myName) { profileBtn.click(); return; }
} catch(err) {}
showPlayerProfilePopup(username, el);
});
});
});
}
setInterval(patchMemberListEntries, 1500);
new MutationObserver(patchMemberListEntries).observe(document.body, { childList: true, subtree: true });
document.addEventListener("click", e => {
if (playerViewPopup.style.display !== "flex") return;
if (!playerViewPopup.contains(e.target) && e.target.id !== "ps_profile_btn") {
playerViewPopup.style.display = "none";
}
});
const statsStyle = document.createElement("style");
statsStyle.textContent = `
#ps_stats_box { bottom:60px; left:110px; width:280px; }
#ps_stats_box .ps_body { gap:8px; }
.stats_search_row { display:flex; gap:6px; align-items:center; }
#stats_search_input {
flex:1; background:rgba(255,255,255,0.12);
border:1px solid rgba(255,255,255,0.28); border-radius:7px;
padding:5px 8px; color:#fff; font-size:12px; outline:none;
}
#stats_search_input::placeholder { color:rgba(255,255,255,0.35); }
#stats_search_input:focus { border-color:rgba(255,255,255,0.5); }
#stats_search_go {
flex-shrink:0; padding:5px 12px;
background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.22);
border-radius:7px; color:#fff; font-size:12px; font-weight:bold;
cursor:pointer; transition:background 0.14s;
}
#stats_search_go:hover { background:rgba(255,255,255,0.22); }
.stats_mode_row { display:flex; gap:5px; }
.stats_mode_btn {
flex:1; padding:4px 0; border-radius:6px;
border:1px solid rgba(255,255,255,0.15);
background:rgba(255,255,255,0.06);
color:rgba(255,255,255,0.55); font-size:11px; font-weight:bold;
cursor:pointer; text-align:center; transition:background 0.12s,color 0.12s;
}
.stats_mode_btn:hover { background:rgba(255,255,255,0.12); }
.stats_mode_btn.active {
background:rgba(255,255,255,0.2); color:#fff;
border-color:rgba(255,255,255,0.4);
}
#stats_result_area {
display:none; flex-direction:column; gap:5px;
background:rgba(0,0,0,0.22); border-radius:9px; padding:9px 10px;
}
.stats_name_row {
display:flex; align-items:center; gap:8px; margin-bottom:2px;
}
.stats_avatar_circle {
width:36px; height:36px; border-radius:50%;
background:rgba(255,255,255,0.1); flex-shrink:0;
display:flex; align-items:center; justify-content:center;
overflow:hidden; border:2px solid rgba(255,255,255,0.18);
}
.stats_avatar_circle svg { display:block; }
.stats_username { font-size:15px; font-weight:bold; color:#fff; }
.stats_uid_txt {
font-size:9px; color:rgba(255,255,255,0.28);
font-family:monospace; word-break:break-all;
}
.stats_stat_row {
display:flex; justify-content:space-between; align-items:center;
padding:4px 8px; background:rgba(255,255,255,0.07); border-radius:7px;
font-size:12px; color:rgba(255,255,255,0.9);
}
.stats_stat_label { color:rgba(255,255,255,0.45); font-size:11px; }
.stats_stat_val { font-weight:bold; }
.stats_friends_section { display:flex; flex-direction:column; gap:3px; }
.stats_friends_label {
font-size:10px; font-weight:bold; letter-spacing:0.06em;
text-transform:uppercase; color:rgba(255,255,255,0.35); margin-top:3px;
}
.stats_friends_list {
max-height:100px; overflow-y:auto; display:flex; flex-direction:column; gap:2px;
scrollbar-width:thin; scrollbar-color:rgba(255,255,255,0.15) transparent;
}
.stats_friends_list::-webkit-scrollbar { width:4px; }
.stats_friends_list::-webkit-scrollbar-thumb {
background:rgba(255,255,255,0.15); border-radius:3px;
}
.stats_friend_entry {
font-size:11px; color:rgba(255,255,255,0.7);
padding:2px 5px; border-radius:5px;
}
.stats_friend_entry.pending { color:rgba(255,220,100,0.7); }
.stats_friend_entry:hover { background:rgba(255,255,255,0.07); }
#stats_status {
font-size:10px; text-align:center;
color:rgba(255,255,255,0.35); min-height:12px;
}
#stats_copy_uid {
background:rgba(255,255,255,0.07); border:1px solid rgba(255,255,255,0.14);
color:rgba(255,255,255,0.45); border-radius:6px; padding:3px 8px;
font-size:10px; cursor:pointer; font-weight:bold;
transition:background 0.14s,color 0.14s; align-self:flex-start;
}
#stats_copy_uid:hover { background:rgba(255,255,255,0.14); color:#fff; }
`;
document.head.appendChild(statsStyle);
const statsBtnEl = document.createElement("button");
statsBtnEl.className = "ps_hack_btn";
statsBtnEl.id = "ps_stats_btn";
statsBtnEl.textContent = "Stats";
hacksBox.querySelector(".ps_body").appendChild(statsBtnEl);
const chatScanBtn = document.createElement("button");
chatScanBtn.className = "ps_hack_btn";
chatScanBtn.id = "ps_chat_scan_btn";
chatScanBtn.textContent = "Chat Scan: OFF";
hacksBox.querySelector(".ps_body").appendChild(chatScanBtn);
let chatScanEnabled = false;
chatScanBtn.addEventListener("click", () => {
chatScanEnabled = !chatScanEnabled;
chatScanBtn.textContent = chatScanEnabled ? "Chat Scan: ON" : "Chat Scan: OFF";
chatScanBtn.style.backgroundColor = chatScanEnabled ? "rgba(80,200,120,0.9)" : "";
});
let ocInterval = null;
let ocEnabled = false;
const ocBtn = document.createElement("button");
ocBtn.className = "ps_hack_btn";
ocBtn.id = "ps_oc_btn";
ocBtn.textContent = "OC: OFF";
hacksBox.querySelector(".ps_body").appendChild(ocBtn);
ocBtn.addEventListener("click", () => {
if (typeof game === 'undefined' || !game.player) {
ocBtn.textContent = "OC: Join a world first";
setTimeout(() => { ocBtn.textContent = "OC: OFF"; }, 2000);
return;
}
ocEnabled = !ocEnabled;
if (ocEnabled) {
ocBtn.textContent = "OC: ON";
ocBtn.style.backgroundColor = "rgba(80,200,120,0.9)";
ocInterval = setInterval(() => {
try {
game.getSocketUtil().emit('ChangeEquip', { index: 0 });
game.player.act();
game.getSocketUtil().emit('ChangeEquip', { index: 1 });
game.player.act();
} catch(e) {}
}, 10);
} else {
ocBtn.textContent = "OC: OFF";
ocBtn.style.backgroundColor = "";
clearInterval(ocInterval);
ocInterval = null;
}
});
const statsBox = document.createElement("div");
statsBox.id = "ps_stats_box";
statsBox.className = "ps_box";
statsBox.innerHTML = `
<div class="ps_body">
<!-- Search mode selector -->
<div class="stats_mode_row">
<button class="stats_mode_btn active" id="stats_mode_username">By Username</button>
<button class="stats_mode_btn" id="stats_mode_uid">By UID</button>
</div>
<!-- Input row -->
<div class="stats_search_row">
<input id="stats_search_input" type="text"
placeholder="Enter username…" maxlength="60">
<button id="stats_search_go">Go</button>
</div>
<!-- Status line -->
<div id="stats_status"></div>
<!-- Result card -->
<div id="stats_result_area">
<div class="stats_name_row">
<div class="stats_avatar_circle">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none"
stroke="rgba(255,255,255,0.4)" stroke-width="2">
<circle cx="12" cy="8" r="4"/>
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/>
</svg>
</div>
<div>
<div class="stats_username" id="stats_res_name">—</div>
<div class="stats_uid_txt" id="stats_res_uid">—</div>
</div>
</div>
<button id="stats_copy_uid">⎘ Copy UID</button>
<div class="stats_stat_row">
<span class="stats_stat_label">Gold</span>
<span class="stats_stat_val" id="stats_res_gold">—</span>
</div>
<div class="stats_stat_row">
<span class="stats_stat_label">Friends (accepted)</span>
<span class="stats_stat_val" id="stats_res_friends_count">—</span>
</div>
<div class="stats_stat_row">
<span class="stats_stat_label">Rules read</span>
<span class="stats_stat_val" id="stats_res_rules">—</span>
</div>
<div class="stats_stat_row" id="stats_res_created_row" style="display:none;">
<span class="stats_stat_label">Account created</span>
<span class="stats_stat_val" id="stats_res_created">—</span>
</div>
<!-- Friends list toggle -->
<div class="stats_friends_section" id="stats_friends_section" style="display:none;">
<div class="stats_friends_label">Friend list</div>
<div class="stats_friends_list" id="stats_friends_list"></div>
</div>
</div>
</div>`;
statsBox.insertBefore(
makeHeader("Stats", () => statsBox.style.display = "none"),
statsBox.firstChild
);
document.body.appendChild(statsBox);
makeDraggable(statsBox);
setupMinimize(statsBox);
let statsMode = "username";
let statsLastUid = null;
function statsSetStatus(msg, ok) {
const el = document.getElementById("stats_status");
if (!el) return;
el.textContent = msg;
el.style.color = ok
? "rgba(120,240,140,0.9)"
: "rgba(255,130,100,0.9)";
}
function statsApplyTheme() {
const t = getTheme();
statsBox.style.backgroundColor = t.hackBox;
const hdr = statsBox.querySelector(".ps_header");
if (hdr) hdr.style.backgroundColor = t.hackHeader;
}
function statsReset() {
document.getElementById("stats_result_area").style.display = "none";
document.getElementById("stats_friends_section").style.display = "none";
document.getElementById("stats_res_created_row").style.display = "none";
statsLastUid = null;
}
function statsRender(uid, data) {
statsLastUid = uid;
document.getElementById("stats_res_name").textContent = data.username || "Unknown";
document.getElementById("stats_res_uid").textContent = uid;
document.getElementById("stats_res_gold").textContent = data.gold ?? "—";
const accepted = data.friends
? data.friends.filter(f => f.status === "accepted").length
: null;
document.getElementById("stats_res_friends_count").textContent =
accepted != null ? accepted : "—";
document.getElementById("stats_res_rules").textContent =
data.isRulesRead != null
? (data.isRulesRead ? "✓ Yes" : "✗ No")
: "—";
if (data.createdAt) {
document.getElementById("stats_res_created").textContent =
new Date(data.createdAt).toLocaleDateString();
document.getElementById("stats_res_created_row").style.display = "flex";
}
if (data.friends && data.friends.length > 0) {
const listEl = document.getElementById("stats_friends_list");
listEl.innerHTML = "";
data.friends.forEach(f => {
const d = document.createElement("div");
d.className = "stats_friend_entry" +
(f.status !== "accepted" ? " pending" : "");
d.textContent = (f.username || f.uid || "?") +
(f.status !== "accepted" ? ` (${f.status})` : "");
listEl.appendChild(d);
});
document.getElementById("stats_friends_section").style.display = "flex";
}
document.getElementById("stats_result_area").style.display = "flex";
statsSetStatus("Fetched at " + new Date().toLocaleTimeString(), true);
}
async function statsLookup() {
const raw = document.getElementById("stats_search_input").value.trim();
if (!raw) return;
statsReset();
statsSetStatus("Looking up…", true);
let uid = null;
if (statsMode === "uid") {
uid = raw;
} else {
uid = findPlayerUidByUsername(raw);
if (!uid) {
try {
const res = await fetch(
`https://matchmaker.junon.io/get_uid?username=${encodeURIComponent(raw)}`
);
if (res.ok) {
const d = await res.json();
uid = d.uid || d.id || null;
}
} catch(e) {}
}
if (!uid) {
statsSetStatus(`"${raw}" not found — try their UID instead`, false);
return;
}
}
const data = await fetchUserRecord(uid);
if (!data) {
statsSetStatus("Matchmaker unreachable or UID not found", false);
return;
}
statsRender(uid, data);
}
document.getElementById("stats_mode_username").addEventListener("click", () => {
statsMode = "username";
document.getElementById("stats_mode_username").classList.add("active");
document.getElementById("stats_mode_uid").classList.remove("active");
document.getElementById("stats_search_input").placeholder = "Enter username…";
statsReset();
statsSetStatus("", true);
});
document.getElementById("stats_mode_uid").addEventListener("click", () => {
statsMode = "uid";
document.getElementById("stats_mode_uid").classList.add("active");
document.getElementById("stats_mode_username").classList.remove("active");
document.getElementById("stats_search_input").placeholder = "Enter UID…";
statsReset();
statsSetStatus("", true);
});
document.getElementById("stats_search_go").addEventListener("click", statsLookup);
document.getElementById("stats_search_input").addEventListener("keydown", e => {
if (e.key === "Enter") statsLookup();
});
document.getElementById("stats_copy_uid").addEventListener("click", () => {
if (!statsLastUid) return;
navigator.clipboard.writeText(statsLastUid)
.then(() => statsSetStatus("UID copied!", true))
.catch(() => statsSetStatus("Copy failed", false));
});
statsBtnEl.addEventListener("click", () => {
hacksBox.style.display = "none";
statsApplyTheme();
statsBox.style.display = statsBox.style.display === "flex" ? "none" : "flex";
});
// ── Omni-Build ──
const omniBuildToast = makeToast("Allows you to place blocks anywhere.");
const omniBuildBtn = document.createElement("button");
omniBuildBtn.className = "ps_hack_btn";
omniBuildBtn.id = "ps_omni_build_btn";
omniBuildBtn.textContent = "Omni-Build: OFF";
hacksBox.querySelector(".ps_body").appendChild(omniBuildBtn);
bindToast("ps_omni_build_btn", omniBuildToast);
let omniBuildEnabled = false;
(function() {
let snapping = true;
let backup = null;
omniBuildBtn.addEventListener("click", function() {
if (!window.game || !window.game.sector) {
omniBuildBtn.textContent = "Omni-Build: Join a world first";
setTimeout(() => { omniBuildBtn.textContent = omniBuildEnabled ? "Omni-Build: ON" : "Omni-Build: OFF"; }, 2000);
return;
}
const proto = window.game.sector.constructor.prototype;
if (!backup) {
backup = { x: proto.getSnappedPosX, y: proto.getSnappedPosY };
}
snapping = !snapping;
omniBuildEnabled = !snapping;
if (snapping) {
proto.getSnappedPosX = backup.x;
proto.getSnappedPosY = backup.y;
omniBuildBtn.textContent = "Omni-Build: OFF";
omniBuildBtn.style.backgroundColor = "";
} else {
proto.getSnappedPosX = function(x, w) { return x; };
proto.getSnappedPosY = function(y, h) { return y; };
omniBuildBtn.textContent = "Omni-Build: ON";
omniBuildBtn.style.backgroundColor = "rgba(80,200,120,0.9)";
}
});
})();
// ── Rainbow Suit Hack ──
let rainbowInterval = null;
let rainbowColorIdx = 1;
const rainbowToast = makeToast("Cycles suit colors infinitely until stopped.");
const rainbowBtn = document.createElement("button");
rainbowBtn.className = "ps_hack_btn";
rainbowBtn.id = "ps_rainbow_btn";
rainbowBtn.textContent = "Rainbow";
hacksBox.querySelector(".ps_body").appendChild(rainbowBtn);
bindToast("ps_rainbow_btn", rainbowToast);
const rainbowBox = document.createElement("div");
rainbowBox.id = "ps_rainbow_box";
rainbowBox.className = "ps_box";
rainbowBox.innerHTML = `<div class="ps_body" style="gap:7px;">
<div style="font-size:10px;color:rgba(255,255,255,0.35);text-align:center;line-height:1.6;">
Choose a mode. You must be wearing a suit.<br>Press Stop to end at any time.
</div>
<div style="display:flex;flex-direction:column;gap:4px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:10px;font-weight:bold;color:rgba(255,255,255,0.6);">Speed</span>
<span id="rainbow_speed_lbl" style="font-size:10px;color:rgba(255,255,255,0.4);font-family:monospace;">500ms</span>
</div>
<div style="display:flex;align-items:center;gap:7px;">
<span style="font-size:9px;color:rgba(255,255,255,0.3);">Fast</span>
<input type="range" id="rainbow_speed_slider" min="50" max="2000" step="50" value="500"
style="flex:1;-webkit-appearance:none;appearance:none;height:4px;border-radius:3px;
background:rgba(255,255,255,0.2);outline:none;cursor:pointer;">
<span style="font-size:9px;color:rgba(255,255,255,0.3);">Slow</span>
</div>
<style>
#rainbow_speed_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
width:13px;height:13px;border-radius:50%;background:white;cursor:pointer;
box-shadow:0 1px 4px rgba(0,0,0,0.5);}
</style>
</div>
<button class="ps_hack_btn" id="rainbow_mine_btn">🌈 My Suit Only</button>
<button class="ps_hack_btn" id="rainbow_all_btn">🌈 Everyone's Suits</button>
<button class="ps_hack_btn" id="rainbow_stop_btn"
style="background:rgba(190,45,45,0.85);display:none;">■ Stop Rainbow</button>
<div id="rainbow_status"
style="font-size:10px;color:rgba(255,255,255,0.35);text-align:center;min-height:12px;"></div>
</div>`;
rainbowBox.insertBefore(
makeHeader("Rainbow", () => {
rainbowBox.style.display = "none";
}),
rainbowBox.firstChild
);
document.body.appendChild(rainbowBox);
makeDraggable(rainbowBox);
setupMinimize(rainbowBox);
function rainbowSetStatus(msg, ok) {
const el = document.getElementById("rainbow_status");
if (!el) return;
el.textContent = msg;
el.style.color = ok ? "rgba(120,240,140,0.9)" : "rgba(255,130,100,0.9)";
}
function stopRainbow() {
if (rainbowInterval) { clearInterval(rainbowInterval); rainbowInterval = null; }
rainbowColorIdx = 1;
const stopBtn = document.getElementById("rainbow_stop_btn");
const mineBtn = document.getElementById("rainbow_mine_btn");
const allBtn = document.getElementById("rainbow_all_btn");
if (stopBtn) stopBtn.style.display = "none";
if (mineBtn) { mineBtn.style.opacity = "1"; mineBtn.textContent = "🌈 My Suit Only"; }
if (allBtn) { allBtn.style.opacity = "1"; allBtn.textContent = "🌈 Everyone's Suits"; }
rainbowSetStatus("Stopped.", false);
}
function startRainbow(mode) {
if (typeof game === 'undefined' || !game.sector || !game.player) {
rainbowSetStatus("Join a world first!", false); return;
}
if (rainbowInterval) stopRainbow();
const totalColors = Object.keys(game.suitColors).length || 8;
const speed = parseInt(document.getElementById("rainbow_speed_slider")?.value || 500);
document.getElementById("rainbow_stop_btn").style.display = "block";
const mineBtn = document.getElementById("rainbow_mine_btn");
const allBtn = document.getElementById("rainbow_all_btn");
if (mode === 'mine') {
if (mineBtn) { mineBtn.style.opacity = "1"; mineBtn.textContent = "🌈 My Suit (Active)"; }
if (allBtn) allBtn.style.opacity = "0.4";
rainbowSetStatus(`Your suit is going rainbow! 🌈 (${speed}ms)`, true);
} else {
if (allBtn) { allBtn.style.opacity = "1"; allBtn.textContent = "🌈 All Suits (Active)"; }
if (mineBtn) mineBtn.style.opacity = "0.4";
rainbowSetStatus(`All suits are going rainbow! 🌈 (${speed}ms)`, true);
}
rainbowInterval = setInterval(() => {
try {
const color = rainbowColorIdx;
rainbowColorIdx = (rainbowColorIdx % totalColors) + 1;
if (mode === 'mine') {
const armor = game.player.getArmorEquip && game.player.getArmorEquip();
if (armor) {
game.getSocketUtil().emit("EditTexture", { colorIndex: color, entityId: armor.id });
}
} else {
Object.values(game.sector.players).forEach(p => {
if (!p) return;
const armor = p.getArmorEquip && p.getArmorEquip();
if (armor) {
game.getSocketUtil().emit("EditTexture", { colorIndex: color, entityId: armor.id });
}
});
}
} catch (e) {}
}, speed);
}
rainbowBtn.addEventListener("click", () => {
hacksBox.style.display = "none";
const t = getTheme();
rainbowBox.style.backgroundColor = t.hackBox;
const hdr = rainbowBox.querySelector(".ps_header");
if (hdr) hdr.style.backgroundColor = t.hackHeader;
rainbowBox.style.display = rainbowBox.style.display === "flex" ? "none" : "flex";
});
document.getElementById("rainbow_mine_btn").addEventListener("click", () => startRainbow('mine'));
document.getElementById("rainbow_all_btn").addEventListener("click", () => startRainbow('all'));
document.getElementById("rainbow_stop_btn").addEventListener("click", stopRainbow);
document.getElementById("rainbow_speed_slider").addEventListener("input", e => {
const ms = parseInt(e.target.value);
e.target._actualMs = ms;
document.getElementById("rainbow_speed_lbl").textContent = ms + "ms";
if (rainbowInterval) {
const activeMode = document.getElementById("rainbow_mine_btn").textContent.includes("Active") ? 'mine' : 'all';
startRainbow(activeMode);
}
});
document.getElementById("rainbow_speed_slider")._actualMs = 1550;
const PS_EXP_STORE = 'ps_exp_v1';
const PS_LB_STORE = 'ps_lb_v1';
const PS_EXPBAR_CFG_KEY = 'ps_expbar_cfg_v2';
const JSONBIN_BIN_ID = '69d1ebbcaaba882197c6d0a5';
const JSONBIN_ACCESS_KEY = '$2a$10$jXEriogweL88ZkIxgrSX5ur5niN2wzQsurBzYjJx.MyjLX3.IHmjy';
const JSONBIN_MASTER_KEY = '$2a$10$XWUOifCQ5rU/QqmwGy/9.enKIlItPKxizV5/VoVsabOH3mGHwZLMu';
function psExpRequired(level) { return level * 10; }
function psExpFmtTime(mins) {
if (mins < 60) return `${mins}m`;
const h = Math.floor(mins / 60), m = mins % 60;
return m ? `${h}h ${m}m` : `${h}h`;
}
let psExpBarCfg = {
width: 280, height: 10,
showLevel: true, showCount: true, showPercentage: true, showTotalTime: true,
glowEnabled: true, shimmerEnabled: true, particlesEnabled: true,
xpFlashEnabled: true, pulseOnGain: true,
style: 'pill', position: 'right',
colorOverride: null,
};
try {
const _ec = localStorage.getItem(PS_EXPBAR_CFG_KEY);
if (_ec) psExpBarCfg = Object.assign(psExpBarCfg, JSON.parse(_ec));
} catch(e) {}
function psExpBarSaveCfg() {
try { localStorage.setItem(PS_EXPBAR_CFG_KEY, JSON.stringify(psExpBarCfg)); } catch(e) {}
}
function psExpLoadData() {
try { const d = localStorage.getItem(PS_EXP_STORE); if (d) return JSON.parse(d); } catch(e) {}
return { level: 1, currentExp: 0, totalExp: 0 };
}
function psExpSaveData(d) {
try { localStorage.setItem(PS_EXP_STORE, JSON.stringify(d)); } catch(e) {}
}
async function psLbLoadData() {
try {
const res = await fetch(`https://api.jsonbin.io/v3/b/${JSONBIN_BIN_ID}`, {
headers: { 'X-Access-Key': JSONBIN_ACCESS_KEY }
});
const data = await res.json();
return data.record.leaderboard || [];
} catch(e) {
try { const d = localStorage.getItem(PS_LB_STORE); if (d) return JSON.parse(d); } catch(err) {}
return [];
}
}
async function psLbSaveData(entries) {
try {
await fetch(`https://api.jsonbin.io/v3/b/${JSONBIN_BIN_ID}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'X-Master-Key': JSONBIN_MASTER_KEY },
body: JSON.stringify({ leaderboard: entries })
});
try { localStorage.setItem(PS_LB_STORE, JSON.stringify(entries)); } catch(err) {}
} catch(e) {
try { localStorage.setItem(PS_LB_STORE, JSON.stringify(entries)); } catch(err) {}
}
}
async function psLbPushEntry(username, level, totalExp) {
if (!username) return;
const entries = await psLbLoadData();
const idx = entries.findIndex(e => e.username === username);
const entry = { username, level, totalExp, lastSeen: Date.now() };
if (idx >= 0) entries[idx] = entry; else entries.push(entry);
entries.sort((a, b) => b.totalExp - a.totalExp);
await psLbSaveData(entries.slice(0, 50));
}
function psExpGetUsername() {
const discord = localStorage.getItem('ps_discord_username');
if (discord) return discord;
if (currentUsername) return currentUsername;
try { if (typeof game !== 'undefined' && game.player && game.player.username) return game.player.username; } catch(e) {}
return 'Player';
}
let psExpState = psExpLoadData();
let psLbOpen = true;
const psExpCss = document.createElement('style');
psExpCss.textContent = `
#ps_exp_wrap {
position: fixed; bottom: 67px; z-index: 10000;
display: flex; align-items: center; gap: 7px;
background: rgba(8,8,8,0.88);
border: 1px solid rgba(255,255,255,0.11);
padding: 5px 10px 5px 10px;
user-select: none; pointer-events: all;
transition: box-shadow .5s ease, width .4s cubic-bezier(.4,0,.2,1),
border-radius .35s ease, border-color .4s ease;
}
#ps_exp_wrap.ps_exp_pulse {
animation: ps_exp_wrap_pulse .5s ease-out;
}
@keyframes ps_exp_wrap_pulse {
0% { box-shadow: var(--exp-shadow), 0 0 0 0 var(--exp-ring); }
60% { box-shadow: var(--exp-shadow), 0 0 0 12px transparent; }
100% { box-shadow: var(--exp-shadow); }
}
#ps_exp_level_lbl {
flex-shrink: 0; font-size: 10px; font-weight: bold;
color: #fff; white-space: nowrap; letter-spacing: .05em;
padding: 2px 8px; line-height: 1.5;
border: 1px solid var(--exp-border, rgba(100,200,70,0.45));
background: var(--exp-bg, rgba(100,200,70,0.2));
transition: background .45s, border-color .45s, border-radius .35s;
animation: ps_exp_badge_breathe 3.5s ease-in-out infinite;
}
@keyframes ps_exp_badge_breathe {
0%,100% { box-shadow: none; }
50% { box-shadow: 0 0 10px var(--exp-ring, rgba(100,200,70,0.35)); }
}
#ps_exp_track {
flex: 1; background: rgba(255,255,255,0.08);
overflow: hidden; min-width: 40px; position: relative;
transition: height .35s ease, border-radius .35s ease;
}
#ps_exp_track::before {
content: '';
position: absolute; inset: 0;
background: repeating-linear-gradient(
90deg,
transparent 0, transparent calc(10% - 0.5px),
rgba(255,255,255,0.035) calc(10% - 0.5px),
rgba(255,255,255,0.035) 10%
);
pointer-events: none; z-index: 1;
}
#ps_exp_fill {
height: 100%; width: 0%; position: relative; overflow: hidden;
transition: width 1.2s cubic-bezier(.4,0,.2,1);
}
#ps_exp_fill::before {
content: '';
position: absolute; inset: 0;
background: repeating-linear-gradient(
-52deg,
transparent 0, transparent 7px,
rgba(255,255,255,0.045) 7px,
rgba(255,255,255,0.045) 14px
);
animation: ps_stripes_scroll 20s linear infinite;
pointer-events: none;
}
@keyframes ps_stripes_scroll { 0% { background-position:0 0; } 100% { background-position:70px 0; } }
#ps_exp_fill::after {
content: '';
position: absolute; top: 0; left: -70%;
width: 55%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.42), transparent);
animation: ps_exp_shimmer 2.8s ease-in-out infinite;
pointer-events: none;
}
#ps_exp_fill.no-shimmer::after { display: none; }
@keyframes ps_exp_shimmer {
0% { left: -70%; opacity:0.5; }
50% { opacity: 1; }
100% { left: 115%; opacity:0.5; }
}
#ps_exp_lbl {
flex-shrink:0; font-size:10px; color:rgba(255,255,255,0.4);
white-space:nowrap; font-family:monospace; letter-spacing:.02em;
}
#ps_exp_pct_lbl {
flex-shrink:0; font-size:10px; font-weight:bold;
color:rgba(255,255,255,0.65); white-space:nowrap; min-width:28px; text-align:right;
}
#ps_exp_time_lbl {
flex-shrink:0; font-size:9px; color:rgba(255,255,255,0.3); white-space:nowrap;
}
#ps_exp_gear_btn {
flex-shrink:0; width:21px; height:21px; border-radius:50%;
border:1px solid rgba(255,255,255,0.14);
background:rgba(255,255,255,0.05); cursor:pointer;
display:flex; align-items:center; justify-content:center;
transition:background .15s, transform .25s, border-color .2s;
}
#ps_exp_gear_btn:hover {
background:rgba(255,255,255,0.2); border-color:rgba(255,255,255,0.35);
transform:rotate(55deg);
}
#ps_exp_gear_btn svg { display:block; pointer-events:none; }
#ps_exp_xp_flash {
position:fixed; z-index:10006; pointer-events:none;
font-size:13px; font-weight:bold; color:#fff;
opacity:0; white-space:nowrap; letter-spacing:.04em;
}
#ps_exp_xp_flash.vis {
animation: ps_xp_float 1.5s cubic-bezier(.2,.8,.3,1) forwards;
}
@keyframes ps_xp_float {
0% { opacity:1; transform:translateY(0) scale(1.15); }
20% { opacity:1; }
100% { opacity:0; transform:translateY(-50px) scale(0.8); }
}
#ps_exp_particle_canvas {
position:fixed; top:0; left:0; width:100vw; height:100vh;
pointer-events:none; z-index:10005;
}
#ps_expbar_cfg_panel {
position:fixed; z-index:10008;
width:248px; border-radius:13px;
background:rgba(10,10,10,0.97);
border:1px solid rgba(255,255,255,0.1);
box-shadow:0 8px 32px rgba(0,0,0,0.75);
backdrop-filter:blur(14px);
display:none; flex-direction:column; overflow:hidden;
bottom:110px; right:20px;
}
#ps_expbar_cfg_panel .ps_header { border-radius:13px 13px 0 0; }
#ps_expbar_cfg_body {
padding:10px 12px 13px;
display:flex; flex-direction:column; gap:10px;
max-height:64vh; overflow-y:auto;
scrollbar-width:thin; scrollbar-color:rgba(255,255,255,0.1) transparent;
}
#ps_expbar_cfg_body::-webkit-scrollbar { width:4px; }
#ps_expbar_cfg_body::-webkit-scrollbar-thumb { background:rgba(255,255,255,0.12); border-radius:3px; }
.exp_cfg_group { display:flex; flex-direction:column; gap:5px; }
.exp_cfg_lbl {
font-size:9px; font-weight:bold; letter-spacing:.09em;
text-transform:uppercase; color:rgba(255,255,255,0.32);
}
.exp_cfg_slider_row { display:flex; align-items:center; gap:7px; }
.exp_cfg_slider {
flex:1; -webkit-appearance:none; appearance:none;
height:4px; border-radius:3px;
background:rgba(255,255,255,0.18); outline:none; cursor:pointer;
}
.exp_cfg_slider::-webkit-slider-thumb {
-webkit-appearance:none; appearance:none;
width:13px; height:13px; border-radius:50%;
background:white; cursor:pointer; box-shadow:0 1px 4px rgba(0,0,0,0.55);
}
.exp_cfg_val { font-size:10px; color:rgba(255,255,255,0.45); min-width:32px; text-align:right; font-family:monospace; }
.exp_cfg_toggles { display:flex; gap:4px; flex-wrap:wrap; }
.exp_cfg_tog {
flex:1; min-width:50px; padding:4px 4px;
border:1px solid rgba(255,255,255,0.12); border-radius:6px;
background:rgba(255,255,255,0.04); color:rgba(255,255,255,0.42);
font-size:10px; font-weight:bold; cursor:pointer; text-align:center;
transition:background .12s, color .12s, border-color .12s;
}
.exp_cfg_tog:hover { background:rgba(255,255,255,0.09); }
.exp_cfg_tog.on { background:rgba(255,255,255,0.17); color:#fff; border-color:rgba(255,255,255,0.35); }
.exp_cfg_segs { display:flex; gap:4px; }
.exp_cfg_seg {
flex:1; padding:4px 0; border-radius:6px;
border:1px solid rgba(255,255,255,0.12); background:rgba(255,255,255,0.04);
color:rgba(255,255,255,0.42); font-size:10px; font-weight:bold;
cursor:pointer; text-align:center; transition:background .12s, color .12s;
}
.exp_cfg_seg:hover { background:rgba(255,255,255,0.09); }
.exp_cfg_seg.active { background:rgba(255,255,255,0.2); color:#fff; border-color:rgba(255,255,255,0.38); }
.exp_cfg_color_row { display:flex; align-items:center; gap:8px; }
#exp_cfg_color_pick { width:34px; height:26px; border:none; border-radius:6px; cursor:pointer; padding:0; background:none; }
#exp_cfg_color_swatch { width:26px; height:26px; border-radius:5px; border:1px solid rgba(255,255,255,0.2); flex-shrink:0; }
#exp_cfg_color_reset {
flex:1; padding:3px 6px; border-radius:6px;
border:1px solid rgba(255,80,80,0.3); background:rgba(255,60,60,0.1);
color:rgba(255,140,140,0.82); font-size:10px; font-weight:bold;
cursor:pointer; text-align:center; transition:background .12s;
}
#exp_cfg_color_reset:hover { background:rgba(255,60,60,0.25); color:#ffaaaa; }
#ps_lu_popup {
position:fixed; bottom:90px; left:50%;
transform:translateX(-50%) translateY(20px);
z-index:11006; background:rgba(8,8,8,0.97);
border:2px solid #ffd700; border-radius:18px;
padding:14px 34px 12px; text-align:center; color:#fff;
pointer-events:none; opacity:0;
box-shadow:0 0 48px rgba(255,215,0,.3), 0 4px 22px rgba(0,0,0,.7);
transition:opacity .35s, transform .35s;
}
#ps_lu_popup.vis { opacity:1; transform:translateX(-50%) translateY(0); }
#ps_lu_popup .lu_tag { font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:#ffd700; font-weight:bold; margin-bottom:4px; }
#ps_lu_popup .lu_num { font-size:30px; font-weight:bold; line-height:1; letter-spacing:.04em; }
#ps_lu_popup .lu_sub { font-size:11px; color:rgba(255,255,255,.4); margin-top:4px; }
#ps_lb_crown_btn {
position:fixed; bottom:670px; right:20px; z-index:10001;
width:36px; height:36px; border-radius:50%;
border:1.5px solid rgba(255,215,0,.4);
background:rgba(8,8,8,0.74); cursor:pointer;
display:flex; align-items:center; justify-content:center;
backdrop-filter:blur(8px);
transition:border-color .2s, background .2s, transform .15s;
box-shadow:0 2px 12px rgba(0,0,0,.5);
}
#ps_lb_crown_btn:hover { border-color:rgba(255,215,0,.9); background:rgba(22,18,0,.9); transform:scale(1.1); }
#ps_lb_panel {
position:fixed; bottom:525px; right:20px; z-index:10000;
width:196px; background:rgba(8,8,8,0.88);
border:1px solid rgba(255,215,0,.18); border-radius:14px;
overflow:hidden; backdrop-filter:blur(14px);
box-shadow:0 4px 28px rgba(0,0,0,.65);
display:flex; flex-direction:column; max-height:65vh;
transition:opacity .22s, transform .22s;
}
#ps_lb_panel.ps_lb_off { opacity:0; transform:translateX(14px); pointer-events:none; }
#ps_lb_head {
display:flex; align-items:center; justify-content:center;
gap:7px; padding:10px 10px 7px;
border-bottom:1px solid rgba(255,215,0,.14); flex-shrink:0;
}
#ps_lb_head span { font-size:11px; font-weight:bold; letter-spacing:.1em; text-transform:uppercase; color:rgba(255,215,0,.9); }
#ps_lb_scroll {
overflow-y:auto; flex:1; padding:5px 0 7px;
scrollbar-width:thin; scrollbar-color:rgba(255,215,0,.15) transparent;
}
#ps_lb_scroll::-webkit-scrollbar { width:4px; }
#ps_lb_scroll::-webkit-scrollbar-thumb { background:rgba(255,215,0,.16); border-radius:3px; }
.ps_lb_row {
display:flex; align-items:center; gap:7px;
padding:5px 9px; font-size:11px; color:rgba(255,255,255,.72);
border-bottom:1px solid rgba(255,255,255,.04); transition:background .1s;
}
.ps_lb_row:last-child { border-bottom:none; }
.ps_lb_row:hover { background:rgba(255,255,255,.045); }
.ps_lb_row.ps_lb_me { background:rgba(255,215,0,.07); }
.ps_lb_rank_num { font-size:10px; font-weight:bold; color:rgba(255,255,255,.28); width:16px; text-align:center; flex-shrink:0; }
.ps_lb_rank_num.r1 { color:#ffd700; font-size:14px; }
.ps_lb_rank_num.r2 { color:#c0c0c0; font-size:12px; }
.ps_lb_rank_num.r3 { color:#cd7f32; font-size:11px; }
.ps_lb_uname { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:11px; }
.ps_lb_uname.ps_lb_me_name { color:#ffd700; font-weight:bold; }
.ps_lb_right { text-align:right; flex-shrink:0; }
.ps_lb_lv { font-size:11px; font-weight:bold; color:rgba(255,255,255,.88); display:block; line-height:1.25; }
.ps_lb_tm { font-size:9px; color:rgba(255,255,255,.28); display:block; line-height:1.25; }
#ps_lb_empty { padding:18px 10px; text-align:center; color:rgba(255,255,255,.22); font-size:11px; }
#ps_lb_foot { padding:5px 10px 7px; border-top:1px solid rgba(255,215,0,.1); font-size:9px; color:rgba(255,255,255,.18); text-align:center; flex-shrink:0; letter-spacing:.02em; }
`;
document.head.appendChild(psExpCss);
const psExpPCanvas = document.createElement('canvas');
psExpPCanvas.id = 'ps_exp_particle_canvas';
document.body.appendChild(psExpPCanvas);
psExpPCanvas.width = window.innerWidth;
psExpPCanvas.height = window.innerHeight;
window.addEventListener('resize', () => {
psExpPCanvas.width = window.innerWidth;
psExpPCanvas.height = window.innerHeight;
});
const psExpPCtx = psExpPCanvas.getContext('2d');
let psExpPList = [];
function psExpSpawnParticles(x, y, hex) {
if (!psExpBarCfg.particlesEnabled) return;
const r = parseInt(hex.slice(1,3),16);
const g = parseInt(hex.slice(3,5),16);
const b = parseInt(hex.slice(5,7),16);
for (let i = 0; i < 16; i++) {
const ang = Math.random() * Math.PI * 2;
const spd = 1.2 + Math.random() * 3.2;
psExpPList.push({
x, y,
vx: Math.cos(ang) * spd,
vy: Math.sin(ang) * spd - 1.8,
rad: 1.5 + Math.random() * 3,
life: 1,
decay: 0.02 + Math.random() * 0.03,
r, g, b,
});
}
}
(function psExpPTick() {
requestAnimationFrame(psExpPTick);
if (!psExpPList.length) {
psExpPCtx.clearRect(0, 0, psExpPCanvas.width, psExpPCanvas.height);
return;
}
psExpPCtx.clearRect(0, 0, psExpPCanvas.width, psExpPCanvas.height);
for (let i = psExpPList.length - 1; i >= 0; i--) {
const p = psExpPList[i];
p.life -= p.decay;
if (p.life <= 0) { psExpPList.splice(i, 1); continue; }
p.x += p.vx;
p.y += p.vy;
p.vy += 0.09;
psExpPCtx.beginPath();
psExpPCtx.arc(p.x, p.y, p.rad * p.life, 0, Math.PI * 2);
psExpPCtx.fillStyle = `rgba(${p.r},${p.g},${p.b},${p.life})`;
psExpPCtx.fill();
}
})();
const psExpXpFlash = document.createElement('div');
psExpXpFlash.id = 'ps_exp_xp_flash';
document.body.appendChild(psExpXpFlash);
const psExpWrap = document.createElement('div');
psExpWrap.id = 'ps_exp_wrap';
psExpWrap.innerHTML = `
<span id="ps_exp_level_lbl">LV 1</span>
<div id="ps_exp_track"><div id="ps_exp_fill"></div></div>
<span id="ps_exp_lbl">0/10</span>
<span id="ps_exp_pct_lbl">0%</span>
<span id="ps_exp_time_lbl">0m</span>
<button id="ps_exp_gear_btn" title="Customise EXP bar">
<svg viewBox="0 0 18 18" width="11" height="11" fill="none"
stroke="rgba(255,255,255,0.65)" stroke-width="1.6"
stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="9" r="3"/>
<path d="M9 1v2M9 15v2M1 9h2M15 9h2
M3.22 3.22l1.42 1.42M13.36 13.36l1.42 1.42
M3.22 14.78l1.42-1.42M13.36 4.64l1.42-1.42"/>
</svg>
</button>`;
document.body.appendChild(psExpWrap);
const psLuPopup = document.createElement('div');
psLuPopup.id = 'ps_lu_popup';
psLuPopup.innerHTML = `
<div class="lu_tag">⬆ Level Up!</div>
<div class="lu_num" id="ps_lu_num">LEVEL 2</div>
<div class="lu_sub" id="ps_lu_sub"></div>`;
document.body.appendChild(psLuPopup);
const psLbCrownBtn = document.createElement('button');
psLbCrownBtn.id = 'ps_lb_crown_btn';
psLbCrownBtn.title = 'Leaderboard';
psLbCrownBtn.innerHTML = `
<svg viewBox="0 0 26 22" width="20" height="17" fill="none">
<path d="M2 18 L5 6 L10.5 13 L13 2 L15.5 13 L21 6 L24 18 Z"
fill="rgba(255,215,0,0.88)" stroke="rgba(255,215,0,0.45)"
stroke-width="0.9" stroke-linejoin="round"/>
<rect x="2" y="18" width="22" height="3" rx="1.4" fill="rgba(255,215,0,0.7)"/>
<circle cx="13" cy="2.8" r="1.6" fill="white" opacity="0.72"/>
<circle cx="4.5" cy="6.8" r="1.3" fill="white" opacity="0.52"/>
<circle cx="21.5" cy="6.8" r="1.3" fill="white" opacity="0.52"/>
</svg>`;
document.body.appendChild(psLbCrownBtn);
const psLbPanel = document.createElement('div');
psLbPanel.id = 'ps_lb_panel';
psLbPanel.innerHTML = `
<div id="ps_lb_head">
<svg viewBox="0 0 26 22" width="15" height="13" fill="none">
<path d="M2 18 L5 6 L10.5 13 L13 2 L15.5 13 L21 6 L24 18 Z"
fill="rgba(255,215,0,0.88)" stroke="rgba(255,215,0,0.4)"
stroke-width="0.9" stroke-linejoin="round"/>
<rect x="2" y="18" width="22" height="3" rx="1.4" fill="rgba(255,215,0,0.68)"/>
<circle cx="13" cy="2.8" r="1.5" fill="white" opacity="0.65"/>
<circle cx="4.5" cy="6.8" r="1.2" fill="white" opacity="0.45"/>
<circle cx="21.5" cy="6.8" r="1.2" fill="white" opacity="0.45"/>
</svg>
<span>Leaderboard</span>
</div>
<div id="ps_lb_scroll">
<div id="ps_lb_empty">No data yet — keep playing!</div>
</div>
<div id="ps_lb_foot">Tracked on this device</div>`;
document.body.appendChild(psLbPanel);
const psExpCfgPanel = document.createElement('div');
psExpCfgPanel.id = 'ps_expbar_cfg_panel';
psExpCfgPanel.className = 'ps_box';
psExpCfgPanel.innerHTML = `
<div id="ps_expbar_cfg_body">
<div class="exp_cfg_group">
<div class="exp_cfg_lbl">Bar Width</div>
<div class="exp_cfg_slider_row">
<input type="range" class="exp_cfg_slider" id="exp_cfg_width"
min="140" max="440" step="10" value="${psExpBarCfg.width}">
<span class="exp_cfg_val" id="exp_cfg_width_val">${psExpBarCfg.width}px</span>
</div>
</div>
<div class="exp_cfg_group">
<div class="exp_cfg_lbl">Track Height</div>
<div class="exp_cfg_slider_row">
<input type="range" class="exp_cfg_slider" id="exp_cfg_height"
min="4" max="22" step="2" value="${psExpBarCfg.height}">
<span class="exp_cfg_val" id="exp_cfg_height_val">${psExpBarCfg.height}px</span>
</div>
</div>
<div class="exp_cfg_group">
<div class="exp_cfg_lbl">Show Labels</div>
<div class="exp_cfg_toggles">
<button class="exp_cfg_tog${psExpBarCfg.showLevel ?' on':''}" id="exp_t_level">Level</button>
<button class="exp_cfg_tog${psExpBarCfg.showCount ?' on':''}" id="exp_t_count">Count</button>
<button class="exp_cfg_tog${psExpBarCfg.showPercentage ?' on':''}" id="exp_t_pct">%</button>
<button class="exp_cfg_tog${psExpBarCfg.showTotalTime ?' on':''}" id="exp_t_time">Time</button>
</div>
</div>
<div class="exp_cfg_group">
<div class="exp_cfg_lbl">Bar Style</div>
<div class="exp_cfg_segs">
<button class="exp_cfg_seg${psExpBarCfg.style==='pill' ?' active':''}" id="exp_s_pill">Pill</button>
<button class="exp_cfg_seg${psExpBarCfg.style==='rounded'?' active':''}" id="exp_s_rounded">Rounded</button>
<button class="exp_cfg_seg${psExpBarCfg.style==='sharp' ?' active':''}" id="exp_s_sharp">Sharp</button>
</div>
</div>
<div class="exp_cfg_group">
<div class="exp_cfg_lbl">Position</div>
<div class="exp_cfg_segs">
<button class="exp_cfg_seg${psExpBarCfg.position==='left' ?' active':''}" id="exp_p_left">Left</button>
<button class="exp_cfg_seg${psExpBarCfg.position==='center'?' active':''}" id="exp_p_center">Center</button>
<button class="exp_cfg_seg${psExpBarCfg.position==='right' ?' active':''}" id="exp_p_right">Right</button>
</div>
</div>
<div class="exp_cfg_group">
<div class="exp_cfg_lbl">Effects</div>
<div class="exp_cfg_toggles">
<button class="exp_cfg_tog${psExpBarCfg.glowEnabled ?' on':''}" id="exp_t_glow">Glow</button>
<button class="exp_cfg_tog${psExpBarCfg.shimmerEnabled ?' on':''}" id="exp_t_shimmer">Shimmer</button>
<button class="exp_cfg_tog${psExpBarCfg.particlesEnabled ?' on':''}" id="exp_t_particles">Particles</button>
<button class="exp_cfg_tog${psExpBarCfg.xpFlashEnabled ?' on':''}" id="exp_t_xpflash">+XP Text</button>
<button class="exp_cfg_tog${psExpBarCfg.pulseOnGain ?' on':''}" id="exp_t_pulse">Pulse</button>
</div>
</div>
<div class="exp_cfg_group">
<div class="exp_cfg_lbl">Colour Override</div>
<div class="exp_cfg_color_row">
<input type="color" id="exp_cfg_color_pick"
value="${psExpBarCfg.colorOverride||'#64c820'}">
<div id="exp_cfg_color_swatch"></div>
<button id="exp_cfg_color_reset">Use theme</button>
</div>
</div>
</div>`;
psExpCfgPanel.insertBefore(
makeHeader("EXP Bar Style", () => { psExpCfgPanel.style.display = "none"; }),
psExpCfgPanel.firstChild
);
document.body.appendChild(psExpCfgPanel);
makeDraggable(psExpCfgPanel);
function psExpAccentHex() {
if (psExpBarCfg.colorOverride) return psExpBarCfg.colorOverride;
const map = {
miku:'#ff82c8', hk:'#3c78dc', space:'#a050dc',
gojo:'#cc44ff', doom:'#ff5522', teto:'#ff6666',
kfp:'#66cc44', custom: customThemeData.primaryColor || '#aaaaaa',
};
return map[currentTheme] || '#66cc44';
}
function psExpApplyPosition() {
psExpWrap.style.left = psExpWrap.style.right = psExpWrap.style.transform = '';
if (psExpBarCfg.position === 'left') { psExpWrap.style.left = '20px'; }
else if (psExpBarCfg.position === 'center') { psExpWrap.style.left = '50%'; psExpWrap.style.transform = 'translateX(-50%)'; }
else { psExpWrap.style.right = '102px'; }
}
function psExpApplyStyle() {
const s = psExpBarCfg.style;
const wR = s==='sharp'?'4px' : s==='rounded'?'10px':'22px';
const tR = s==='sharp'?'2px' : s==='rounded'?'6px' :'22px';
const bR = s==='sharp'?'3px' : s==='rounded'?'6px' :'12px';
psExpWrap.style.borderRadius = wR;
const track = document.getElementById('ps_exp_track');
const fill = document.getElementById('ps_exp_fill');
const badge = document.getElementById('ps_exp_level_lbl');
if (track) track.style.borderRadius = tR;
if (fill) fill.style.borderRadius = tR;
if (badge) badge.style.borderRadius = bR;
}
function psExpRender() {
const needed = psExpRequired(psExpState.level);
const pct = Math.min((psExpState.currentExp / needed) * 100, 100);
const hex = psExpAccentHex();
const [r,g,b] = hexToRgb(hex);
const glowStr = `rgba(${r},${g},${b},0.5)`;
const ringStr = `rgba(${r},${g},${b},0.28)`;
psExpWrap.style.setProperty('--exp-bg', `rgba(${r},${g},${b},0.2)`);
psExpWrap.style.setProperty('--exp-border', `rgba(${r},${g},${b},0.48)`);
psExpWrap.style.setProperty('--exp-ring', ringStr);
if (psExpBarCfg.glowEnabled) {
const sh = `0 0 18px ${glowStr}, 0 2px 18px rgba(0,0,0,0.65)`;
psExpWrap.style.boxShadow = sh;
psExpWrap.style.setProperty('--exp-shadow', sh);
psExpWrap.style.borderColor = `rgba(${r},${g},${b},0.38)`;
} else {
const sh = '0 2px 14px rgba(0,0,0,0.65)';
psExpWrap.style.boxShadow = sh;
psExpWrap.style.setProperty('--exp-shadow', sh);
psExpWrap.style.borderColor = 'rgba(255,255,255,0.11)';
}
psExpWrap.style.width = psExpBarCfg.width + 'px';
const track = document.getElementById('ps_exp_track');
if (track) track.style.height = psExpBarCfg.height + 'px';
const fill = document.getElementById('ps_exp_fill');
if (fill) {
fill.style.width = pct + '%';
fill.style.background = `linear-gradient(90deg,rgba(${r},${g},${b},0.6),rgba(${r},${g},${b},1) 80%)`;
fill.classList.toggle('no-shimmer', !psExpBarCfg.shimmerEnabled);
}
const lvEl = document.getElementById('ps_exp_level_lbl');
const cntEl = document.getElementById('ps_exp_lbl');
const pctEl = document.getElementById('ps_exp_pct_lbl');
const timeEl = document.getElementById('ps_exp_time_lbl');
if (lvEl) { lvEl.textContent = `LV ${psExpState.level}`; lvEl.style.display = psExpBarCfg.showLevel ? '' : 'none'; }
if (cntEl) { cntEl.textContent = `${psExpState.currentExp}/${needed}`; cntEl.style.display = psExpBarCfg.showCount ? '' : 'none'; }
if (pctEl) { pctEl.textContent = Math.floor(pct) + '%'; pctEl.style.display = psExpBarCfg.showPercentage ? '' : 'none'; }
if (timeEl) { timeEl.textContent = '⏱' + psExpFmtTime(psExpState.totalExp); timeEl.style.display = psExpBarCfg.showTotalTime ? '' : 'none'; }
psExpApplyPosition();
psExpApplyStyle();
}
function psExpTriggerGainEffects() {
const hex = psExpAccentHex();
const rect = psExpWrap.getBoundingClientRect();
if (psExpBarCfg.xpFlashEnabled) {
psExpXpFlash.textContent = '+1 XP';
psExpXpFlash.style.color = hex;
psExpXpFlash.style.textShadow = `0 0 10px ${hex}`;
psExpXpFlash.style.left = (rect.left + rect.width / 2 - 18) + 'px';
psExpXpFlash.style.top = (rect.top - 6) + 'px';
psExpXpFlash.classList.remove('vis');
void psExpXpFlash.offsetWidth;
psExpXpFlash.classList.add('vis');
setTimeout(() => psExpXpFlash.classList.remove('vis'), 1500);
}
if (psExpBarCfg.particlesEnabled) {
const needed = psExpRequired(psExpState.level);
const fillPct = Math.min((psExpState.currentExp - 1) / needed, 1);
const fx = rect.left + 10 + (rect.width - 55) * fillPct;
const fy = rect.top + rect.height / 2;
psExpSpawnParticles(fx, fy, hex);
}
if (psExpBarCfg.pulseOnGain) {
psExpWrap.classList.remove('ps_exp_pulse');
void psExpWrap.offsetWidth;
psExpWrap.classList.add('ps_exp_pulse');
setTimeout(() => psExpWrap.classList.remove('ps_exp_pulse'), 520);
}
}
function psExpCheckLvUp() {
let levelled = false;
while (psExpState.currentExp >= psExpRequired(psExpState.level)) {
psExpState.currentExp -= psExpRequired(psExpState.level);
psExpState.level++;
levelled = true;
}
if (!levelled) return;
psExpSaveData(psExpState);
const popup = document.getElementById('ps_lu_popup');
const num = document.getElementById('ps_lu_num');
const sub = document.getElementById('ps_lu_sub');
if (num) num.textContent = `LEVEL ${psExpState.level}`;
if (sub) sub.textContent = `Next level in ${psExpRequired(psExpState.level)} more minute${psExpRequired(psExpState.level)===1?'':'s'}`;
if (popup) {
popup.classList.add('vis');
setTimeout(() => popup.classList.remove('vis'), 4200);
}
}
async function psLbRender() {
const scroll = document.getElementById('ps_lb_scroll');
if (!scroll) return;
scroll.innerHTML = '<div id="ps_lb_empty">Loading...</div>';
const entries = await psLbLoadData();
const me = psExpGetUsername();
if (!entries.length) { scroll.innerHTML = '<div id="ps_lb_empty">No data yet — keep playing!</div>'; return; }
const medals = ['★','✦','◆'];
const mcls = ['r1','r2','r3'];
scroll.innerHTML = entries.map((e,i) => {
const isMe = e.username === me;
const rk = i < 3
? `<span class="ps_lb_rank_num ${mcls[i]}">${medals[i]}</span>`
: `<span class="ps_lb_rank_num">${i+1}</span>`;
return `<div class="ps_lb_row${isMe?' ps_lb_me':''}">
${rk}
<span class="ps_lb_uname${isMe?' ps_lb_me_name':''}">${e.username}</span>
<div class="ps_lb_right">
<span class="ps_lb_lv">Lv ${e.level}</span>
<span class="ps_lb_tm">${psExpFmtTime(e.totalExp)}</span>
</div>
</div>`;
}).join('');
}
function psExpCfgTog(id, key) {
const el = document.getElementById(id);
if (!el) return;
el.addEventListener('click', () => {
psExpBarCfg[key] = !psExpBarCfg[key];
el.classList.toggle('on', psExpBarCfg[key]);
psExpBarSaveCfg(); psExpRender();
});
}
psExpCfgTog('exp_t_level', 'showLevel');
psExpCfgTog('exp_t_count', 'showCount');
psExpCfgTog('exp_t_pct', 'showPercentage');
psExpCfgTog('exp_t_time', 'showTotalTime');
psExpCfgTog('exp_t_glow', 'glowEnabled');
psExpCfgTog('exp_t_shimmer', 'shimmerEnabled');
psExpCfgTog('exp_t_particles', 'particlesEnabled');
psExpCfgTog('exp_t_xpflash', 'xpFlashEnabled');
psExpCfgTog('exp_t_pulse', 'pulseOnGain');
function psExpCfgSeg(ids, key, vals) {
ids.forEach((id, i) => {
const el = document.getElementById(id);
if (!el) return;
el.addEventListener('click', () => {
psExpBarCfg[key] = vals[i];
ids.forEach(sid => { const s=document.getElementById(sid); if(s)s.classList.remove('active'); });
el.classList.add('active');
psExpBarSaveCfg(); psExpRender();
});
});
}
psExpCfgSeg(['exp_s_pill','exp_s_rounded','exp_s_sharp'], 'style', ['pill','rounded','sharp']);
psExpCfgSeg(['exp_p_left','exp_p_center','exp_p_right'], 'position', ['left','center','right']);
document.getElementById('exp_cfg_width').addEventListener('input', e => {
psExpBarCfg.width = parseInt(e.target.value);
document.getElementById('exp_cfg_width_val').textContent = psExpBarCfg.width + 'px';
psExpBarSaveCfg(); psExpRender();
});
document.getElementById('exp_cfg_height').addEventListener('input', e => {
psExpBarCfg.height = parseInt(e.target.value);
document.getElementById('exp_cfg_height_val').textContent = psExpBarCfg.height + 'px';
psExpBarSaveCfg(); psExpRender();
});
document.getElementById('exp_cfg_color_pick').addEventListener('input', e => {
psExpBarCfg.colorOverride = e.target.value;
document.getElementById('exp_cfg_color_swatch').style.background = e.target.value;
psExpBarSaveCfg(); psExpRender();
});
document.getElementById('exp_cfg_color_reset').addEventListener('click', () => {
psExpBarCfg.colorOverride = null;
const hex = psExpAccentHex();
const pick = document.getElementById('exp_cfg_color_pick');
const sw = document.getElementById('exp_cfg_color_swatch');
if (pick) pick.value = hex;
if (sw) sw.style.background = hex;
psExpBarSaveCfg(); psExpRender();
});
(function(){
const sw = document.getElementById('exp_cfg_color_swatch');
if (sw) sw.style.background = psExpBarCfg.colorOverride || psExpAccentHex();
})();
document.getElementById('ps_exp_gear_btn').addEventListener('click', e => {
e.stopPropagation();
const open = psExpCfgPanel.style.display === 'flex';
psExpCfgPanel.style.display = open ? 'none' : 'flex';
if (!open) {
const t = getTheme();
psExpCfgPanel.style.backgroundColor = t.hackBox;
const hdr = psExpCfgPanel.querySelector('.ps_header');
if (hdr) hdr.style.backgroundColor = t.hackHeader;
if (!psExpBarCfg.colorOverride) {
const sw = document.getElementById('exp_cfg_color_swatch');
if (sw) sw.style.background = psExpAccentHex();
}
}
});
setInterval(async () => {
psExpState.currentExp++;
psExpState.totalExp++;
psExpSaveData(psExpState);
psExpTriggerGainEffects();
psExpCheckLvUp();
psExpRender();
const un = psExpGetUsername();
await psLbPushEntry(un, psExpState.level, psExpState.totalExp);
await psLbRender();
}, 60_000);
setInterval(psExpRender, 6000);
psLbCrownBtn.addEventListener('click', () => {
psLbOpen = !psLbOpen;
psLbPanel.classList.toggle('ps_lb_off', !psLbOpen);
});
psExpRender();
psLbRender();
setTimeout(async () => {
const un = psExpGetUsername();
await psLbPushEntry(un, psExpState.level, psExpState.totalExp);
await psLbRender();
}, 3500);
setInterval(async () => {
const un = psExpGetUsername();
if (un) {
await psLbPushEntry(un, psExpState.level, psExpState.totalExp);
await psLbRender();
}
}, 30_000);
function psLoadPeerJS(cb) {
if (window.Peer) { cb(); return; }
const s = document.createElement('script');
s.src = 'https://unpkg.com/[email protected]/dist/peerjs.min.js';
s.onload = () => setTimeout(cb, 80);
s.onerror = () => ssToast('PeerJS CDN failed to load.', false);
document.head.appendChild(s);
}
const ssState = {
peer: null, peerId: null,
outgoingCall: null, incomingCall: null,
localStream: null, micStream: null,
isSharing: false, micEnabled: false, viewerActive: false,
};
function ssPeerIdFor(username) {
return 'junonps_' + username.toLowerCase().replace(/[^a-z0-9]/g, '_');
}
function ssGetMyPeerId() {
return ssPeerIdFor(psExpGetUsername() || currentUsername || 'unknown');
}
const ssCss = document.createElement('style');
ssCss.textContent = `
#ps_ss_box { bottom:60px; left:110px; width:272px; }
#ps_ss_box .ps_body { gap:7px; }
#ss_target_input {
flex:1; background:rgba(255,255,255,0.12);
border:1px solid rgba(255,255,255,0.28); border-radius:7px;
padding:5px 8px; color:#fff; font-size:12px; outline:none;
}
#ss_target_input::placeholder { color:rgba(255,255,255,0.35); }
#ss_target_input:focus { border-color:rgba(255,255,255,0.52); }
.ss_btn {
width:100%; padding:7px; border:none; border-radius:8px;
font-size:12px; font-weight:bold; color:#fff; cursor:pointer;
transition:filter 0.15s;
}
.ss_btn:hover { filter:brightness(1.18); }
#ss_start_btn { background:rgba(60,180,60,0.88); }
#ss_stop_btn { background:rgba(210,55,55,0.88); display:none; }
#ss_mic_btn { background:rgba(70,120,210,0.85); }
#ss_mic_btn.muted { background:rgba(90,90,90,0.75); }
.ss_status_row {
display:flex; align-items:center; gap:7px;
padding:4px 8px; background:rgba(255,255,255,0.07);
border-radius:7px; font-size:11px; color:rgba(255,255,255,0.65);
}
.ss_dot { width:7px; height:7px; border-radius:50%; background:rgba(255,255,255,0.25); flex-shrink:0; }
.ss_dot.green { background:#44dd44; animation:ss_pulse 1.6s ease-in-out infinite; }
.ss_dot.amber { background:#ffaa22; animation:ss_pulse 1s ease-in-out infinite; }
.ss_dot.red { background:#dd4444; }
@keyframes ss_pulse { 0%,100%{opacity:1;} 50%{opacity:0.38;} }
#ss_preview_vid {
width:100%; aspect-ratio:16/9; border-radius:7px;
background:#000; object-fit:contain; display:none;
border:1px solid rgba(255,255,255,0.1);
}
#ss_my_id_row {
font-size:9px; color:rgba(255,255,255,0.28); text-align:center;
font-family:monospace; word-break:break-all; line-height:1.5;
}
#ps_ss_incoming {
position:fixed; top:50%; left:50%;
transform:translate(-50%,-50%); z-index:11100;
background:rgba(10,10,10,0.97);
border:2px solid rgba(80,200,80,0.55);
border-radius:18px; padding:22px 26px 18px;
text-align:center; min-width:290px;
box-shadow:0 8px 48px rgba(0,0,0,0.85);
backdrop-filter:blur(18px);
display:none; flex-direction:column; gap:11px;
animation:ss_inc_pop 0.28s cubic-bezier(.2,.8,.3,1);
}
@keyframes ss_inc_pop {
from { opacity:0; transform:translate(-50%,-56%) scale(0.9); }
to { opacity:1; transform:translate(-50%,-50%) scale(1); }
}
#ps_ss_incoming .ss_inc_icon { font-size:32px; }
#ps_ss_incoming .ss_inc_title { font-size:14px; font-weight:bold; color:#fff; line-height:1.35; }
#ps_ss_incoming .ss_inc_sub { font-size:11px; color:rgba(255,255,255,0.38); }
.ss_inc_btns { display:flex; gap:8px; }
.ss_inc_btn {
flex:1; padding:9px 0; border:none; border-radius:10px;
font-size:13px; font-weight:bold; color:#fff; cursor:pointer;
transition:filter 0.14s;
}
.ss_inc_btn:hover { filter:brightness(1.2); }
#ss_inc_accept { background:rgba(50,185,50,0.92); }
#ss_inc_decline { background:rgba(195,45,45,0.88); }
#ps_ss_viewer {
position:fixed; top:50%; left:50%;
transform:translate(-50%,-50%); z-index:11050;
width:700px; min-width:320px; min-height:240px;
border-radius:13px; overflow:hidden;
background:#000; display:none; flex-direction:column;
box-shadow:0 12px 64px rgba(0,0,0,0.92);
border:1px solid rgba(255,255,255,0.09);
resize:both;
}
#ss_viewer_bar {
display:flex; align-items:center; justify-content:space-between;
padding:7px 11px; background:rgba(10,10,10,0.97);
border-bottom:1px solid rgba(255,255,255,0.06);
flex-shrink:0; cursor:grab; user-select:none;
}
#ss_viewer_bar:active { cursor:grabbing; }
#ss_viewer_title {
display:flex; align-items:center; gap:7px;
font-size:11px; font-weight:bold; color:rgba(255,255,255,0.82);
}
.ss_viewer_live_dot {
width:7px; height:7px; border-radius:50%;
background:#44dd44; animation:ss_pulse 1.5s infinite;
}
.ss_viewer_ctrl {
background:rgba(255,255,255,0.07); border:1px solid rgba(255,255,255,0.11);
color:rgba(255,255,255,0.65); border-radius:6px; padding:3px 8px;
font-size:11px; font-weight:bold; cursor:pointer;
transition:background 0.13s, color 0.13s;
}
.ss_viewer_ctrl:hover { background:rgba(255,255,255,0.17); color:#fff; }
#ss_viewer_stop {
background:rgba(195,45,45,0.72); border-color:rgba(255,80,80,0.25);
color:rgba(255,170,170,0.9);
}
#ss_viewer_stop:hover { background:rgba(215,55,55,0.95); color:#fff; }
#ss_viewer_vid {
flex:1; width:100%; display:block;
background:#000; object-fit:contain;
}
#ss_viewer_footer {
display:flex; align-items:center; gap:7px; padding:4px 11px;
background:rgba(10,10,10,0.97);
border-top:1px solid rgba(255,255,255,0.06);
font-size:10px; color:rgba(255,255,255,0.25); flex-shrink:0;
}
#ss_mic_badge {
position:fixed; top:58px; right:20px; z-index:10300;
display:none; align-items:center; gap:5px;
background:rgba(12,12,12,0.9); border:1px solid rgba(70,120,210,0.5);
border-radius:8px; padding:4px 10px;
font-size:10px; color:rgba(255,255,255,0.6);
backdrop-filter:blur(8px);
}
#ss_mic_badge.vis { display:flex; }
#ss_mic_badge .ss_dot { background:#5082dc; animation:ss_pulse 1s infinite; }
#ps_ss_toast {
position:fixed; bottom:118px; left:50%;
transform:translateX(-50%); z-index:11200;
background:rgba(16,16,16,0.97); color:#fff;
padding:7px 18px; border-radius:9px; font-size:12px; font-weight:bold;
pointer-events:none; opacity:0; transition:opacity 0.2s;
white-space:nowrap; border:1px solid rgba(255,255,255,0.1);
}
#ps_ss_toast.vis { opacity:1; }
`;
document.head.appendChild(ssCss);
const ssMsgToast = document.createElement('div');
ssMsgToast.id = 'ps_ss_toast';
document.body.appendChild(ssMsgToast);
let _ssToastTmr = null;
function ssToast(msg, ok = true) {
ssMsgToast.textContent = msg;
ssMsgToast.style.borderColor = ok
? 'rgba(60,200,60,0.35)' : 'rgba(200,60,60,0.35)';
ssMsgToast.classList.add('vis');
clearTimeout(_ssToastTmr);
_ssToastTmr = setTimeout(() => ssMsgToast.classList.remove('vis'), 3200);
}
const ssMicBadge = document.createElement('div');
ssMicBadge.id = 'ss_mic_badge';
ssMicBadge.innerHTML = `<div class="ss_dot"></div><span>Mic live</span>`;
document.body.appendChild(ssMicBadge);
const ssIncoming = document.createElement('div');
ssIncoming.id = 'ps_ss_incoming';
ssIncoming.innerHTML = `
<div class="ss_inc_icon">🖥️</div>
<div class="ss_inc_title" id="ss_inc_title">Someone wants to share their screen</div>
<div class="ss_inc_sub">Both users need the Parasidieum script.</div>
<div class="ss_inc_btns">
<button class="ss_inc_btn" id="ss_inc_accept">✓ Accept</button>
<button class="ss_inc_btn" id="ss_inc_decline">✗ Decline</button>
</div>`;
document.body.appendChild(ssIncoming);
const ssViewer = document.createElement('div');
ssViewer.id = 'ps_ss_viewer';
ssViewer.innerHTML = `
<div id="ss_viewer_bar">
<div id="ss_viewer_title">
<div class="ss_viewer_live_dot"></div>
<span id="ss_viewer_who">Screen Share</span>
</div>
<div style="display:flex;gap:5px;">
<button class="ss_viewer_ctrl" id="ss_viewer_mute">🔊 Audio</button>
<button class="ss_viewer_ctrl" id="ss_viewer_fs">⛶ Full</button>
<button class="ss_viewer_ctrl" id="ss_viewer_stop">✕ Stop</button>
</div>
</div>
<video id="ss_viewer_vid" autoplay playsinline></video>
<div id="ss_viewer_footer">
<div class="ss_viewer_live_dot"></div>
<span>Live · Parasidieum Screen Share</span>
<span style="margin-left:auto;" id="ss_viewer_res"></span>
</div>`;
document.body.appendChild(ssViewer);
(function () {
const bar = document.getElementById('ss_viewer_bar');
let drag = false, ox = 0, oy = 0;
bar.addEventListener('mousedown', e => {
if (e.target.closest('.ss_viewer_ctrl')) return;
drag = true;
const r = ssViewer.getBoundingClientRect();
ssViewer.style.transform = 'none';
ssViewer.style.top = r.top + 'px';
ssViewer.style.left = r.left + 'px';
ox = e.clientX - r.left;
oy = e.clientY - r.top;
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!drag) return;
ssViewer.style.left = (e.clientX - ox) + 'px';
ssViewer.style.top = (e.clientY - oy) + 'px';
});
document.addEventListener('mouseup', () => { drag = false; });
})();
const ssBtnEl = document.createElement('button');
ssBtnEl.className = 'ps_hack_btn';
ssBtnEl.id = 'ps_ss_open_btn';
ssBtnEl.textContent = 'Share Screen';
hacksBox.querySelector('.ps_body').appendChild(ssBtnEl);
const ssBox = document.createElement('div');
ssBox.id = 'ps_ss_box';
ssBox.className = 'ps_box';
ssBox.innerHTML = `
<div class="ps_body">
<div class="ss_status_row">
<div class="ss_dot" id="ss_peer_dot"></div>
<span id="ss_peer_status">Click "Share Screen" to connect</span>
</div>
<div id="ss_my_id_row"></div>
<div style="display:flex;gap:5px;align-items:center;">
<input id="ss_target_input" type="text"
placeholder="Target's username (they need script)" maxlength="32">
</div>
<button class="ss_btn" id="ss_start_btn">🖥️ Share My Screen</button>
<button class="ss_btn" id="ss_stop_btn">■ Stop Sharing</button>
<button class="ss_btn" id="ss_mic_btn">🎙 Mic: OFF</button>
<video id="ss_preview_vid" muted playsinline autoplay></video>
<div style="font-size:10px;color:rgba(255,255,255,0.22);text-align:center;line-height:1.55;">
Recipient needs Parasidieum installed.<br>
Connection is direct peer-to-peer (WebRTC).
</div>
</div>`;
ssBox.insertBefore(
makeHeader('Screen Share', () => ssBox.style.display = 'none'),
ssBox.firstChild
);
document.body.appendChild(ssBox);
makeDraggable(ssBox);
setupMinimize(ssBox);
function ssSetStatus(msg, color) {
const dot = document.getElementById('ss_peer_dot');
const txt = document.getElementById('ss_peer_status');
if (dot) dot.className = 'ss_dot' + (color ? ' ' + color : '');
if (txt) txt.textContent = msg;
}
function ssUpdateMyIdLabel() {
const el = document.getElementById('ss_my_id_row');
if (!el || !ssState.peerId) return;
const readable = ssState.peerId.replace(/^junonps_/, '');
el.textContent = `Your share ID: ${readable}`;
}
function ssInitPeer(cb) {
if (ssState.peer && !ssState.peer.destroyed && ssState.peer.open) {
if (cb) cb(); return;
}
psLoadPeerJS(() => {
const id = ssGetMyPeerId();
ssSetStatus('Connecting to relay…', 'amber');
function tryConnect(peerId) {
const peer = new Peer(peerId, {
config: {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:global.stun.twilio.com:3478' },
]
}
});
peer.on('open', openId => {
ssState.peer = peer;
ssState.peerId = openId;
ssSetStatus('Ready — share your ID', 'green');
ssUpdateMyIdLabel();
if (cb) cb();
});
peer.on('error', err => {
if (err.type === 'unavailable-id') {
tryConnect(peerId + '_' + Math.random().toString(36).slice(2, 5));
return;
}
ssSetStatus('Error: ' + err.type, 'red');
ssToast('Peer error: ' + err.type, false);
});
peer.on('call', ssHandleIncoming);
peer.on('disconnected', () => {
ssSetStatus('Reconnecting…', 'amber');
try { peer.reconnect(); } catch(e) {}
});
peer.on('close', () => ssSetStatus('Disconnected', 'red'));
}
tryConnect(id);
});
}
function ssHandleIncoming(call) {
ssState.incomingCall = call;
const callerDisplay = call.peer
.replace(/^junonps_/, '')
.replace(/_/g, ' ');
const title = document.getElementById('ss_inc_title');
if (title)
title.textContent = `"${callerDisplay}" wants to share their screen`;
ssIncoming.style.display = 'flex';
call.on('close', () => { ssIncoming.style.display = 'none'; ssCloseViewer(); });
call.on('error', () => { ssIncoming.style.display = 'none'; ssCloseViewer(); ssToast('Stream lost', false); });
}
document.getElementById('ss_inc_accept').addEventListener('click', () => {
const call = ssState.incomingCall;
if (!call) return;
ssIncoming.style.display = 'none';
call.answer();
call.on('stream', remote => ssOpenViewer(remote, call.peer));
call.on('close', () => ssCloseViewer());
call.on('error', () => { ssCloseViewer(); ssToast('Connection lost', false); });
});
document.getElementById('ss_inc_decline').addEventListener('click', () => {
if (ssState.incomingCall) { ssState.incomingCall.close(); ssState.incomingCall = null; }
ssIncoming.style.display = 'none';
ssToast('Declined screen share', false);
});
function ssOpenViewer(stream, peerId) {
ssState.viewerActive = true;
const vid = document.getElementById('ss_viewer_vid');
const who = document.getElementById('ss_viewer_who');
if (vid) vid.srcObject = stream;
if (who) who.textContent = peerId.replace(/^junonps_/, '').replace(/_/g, ' ') + ' · Live';
ssViewer.style.display = 'flex';
if (vid) {
vid.addEventListener('loadedmetadata', () => {
const res = document.getElementById('ss_viewer_res');
if (res) res.textContent = `${vid.videoWidth}×${vid.videoHeight}`;
}, { once: true });
}
ssToast('Screen share started ✓', true);
}
function ssCloseViewer() {
ssState.viewerActive = false;
const vid = document.getElementById('ss_viewer_vid');
if (vid) vid.srcObject = null;
ssViewer.style.display = 'none';
}
document.getElementById('ss_viewer_stop').addEventListener('click', () => {
if (ssState.incomingCall) { ssState.incomingCall.close(); ssState.incomingCall = null; }
ssCloseViewer();
});
let ssViewerMuted = false;
document.getElementById('ss_viewer_mute').addEventListener('click', () => {
const vid = document.getElementById('ss_viewer_vid');
const btn = document.getElementById('ss_viewer_mute');
if (!vid) return;
ssViewerMuted = !ssViewerMuted;
vid.muted = ssViewerMuted;
if (btn) btn.textContent = ssViewerMuted ? '🔇 Muted' : '🔊 Audio';
});
document.getElementById('ss_viewer_fs').addEventListener('click', () => {
const vid = document.getElementById('ss_viewer_vid');
if (!vid) return;
if (vid.requestFullscreen) vid.requestFullscreen();
else if (vid.webkitRequestFullscreen) vid.webkitRequestFullscreen();
else if (vid.mozRequestFullScreen) vid.mozRequestFullScreen();
});
document.getElementById('ss_start_btn').addEventListener('click', async () => {
const target = document.getElementById('ss_target_input').value.trim();
if (!target) { ssToast('Enter the recipient\'s username first!', false); return; }
ssSetStatus('Initialising…', 'amber');
ssInitPeer(async () => {
try {
ssSetStatus('Requesting screen…', 'amber');
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 30, cursor: 'always' },
audio: true,
});
if (ssState.micEnabled && ssState.micStream) {
const micTrack = ssState.micStream.getAudioTracks()[0];
if (micTrack) screenStream.addTrack(micTrack.clone());
}
ssState.localStream = screenStream;
ssState.isSharing = true;
const preview = document.getElementById('ss_preview_vid');
if (preview) { preview.srcObject = screenStream; preview.style.display = 'block'; }
document.getElementById('ss_start_btn').style.display = 'none';
document.getElementById('ss_stop_btn').style.display = 'block';
ssSetStatus(`Calling ${target}…`, 'amber');
ssToast(`Calling ${target}…`, true);
const targetId = ssPeerIdFor(target);
const call = ssState.peer.call(targetId, screenStream, {
metadata: { sender: ssGetMyPeerId() }
});
ssState.outgoingCall = call;
call.on('stream', () => ssSetStatus(`Live → ${target}`, 'green'));
call.on('close', () => { ssStopSharing(); ssToast('Remote ended the session', false); });
call.on('error', err => { ssStopSharing(); ssToast('Call error: ' + err.message, false); });
screenStream.getVideoTracks()[0].addEventListener('ended', () => {
ssStopSharing();
ssToast('Screen share stopped', false);
});
} catch (err) {
ssSetStatus('Ready', 'green');
if (err.name === 'NotAllowedError') ssToast('Screen capture cancelled', false);
else ssToast('Error: ' + err.message, false);
}
});
});
function ssStopSharing() {
if (ssState.localStream) {
ssState.localStream.getTracks().forEach(t => t.stop());
ssState.localStream = null;
}
if (ssState.outgoingCall) {
ssState.outgoingCall.close();
ssState.outgoingCall = null;
}
ssState.isSharing = false;
const preview = document.getElementById('ss_preview_vid');
if (preview) { preview.srcObject = null; preview.style.display = 'none'; }
document.getElementById('ss_start_btn').style.display = 'block';
document.getElementById('ss_stop_btn').style.display = 'none';
ssSetStatus('Ready — share your ID', 'green');
}
document.getElementById('ss_stop_btn').addEventListener('click', () => {
ssStopSharing();
ssToast('Screen share stopped', false);
});
document.getElementById('ss_mic_btn').addEventListener('click', async () => {
const btn = document.getElementById('ss_mic_btn');
if (!ssState.micEnabled) {
try {
const mic = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
ssState.micStream = mic;
ssState.micEnabled = true;
if (btn) { btn.textContent = '🎙 Mic: ON'; btn.classList.remove('muted'); }
ssMicBadge.classList.add('vis');
if (ssState.isSharing && ssState.localStream && ssState.outgoingCall) {
const micTrack = mic.getAudioTracks()[0];
if (micTrack) {
try {
ssState.localStream.addTrack(micTrack.clone());
const pc = ssState.outgoingCall.peerConnection;
if (pc) pc.addTrack(micTrack, ssState.localStream);
} catch(e) {}
}
}
ssToast('Mic enabled', true);
} catch(e) {
ssToast('Microphone access denied', false);
}
} else {
if (ssState.micStream) {
ssState.micStream.getTracks().forEach(t => t.stop());
ssState.micStream = null;
}
ssState.micEnabled = false;
if (btn) { btn.textContent = '🎙 Mic: OFF'; btn.classList.add('muted'); }
ssMicBadge.classList.remove('vis');
ssToast('Mic disabled', false);
}
});
ssBtnEl.addEventListener('click', () => {
hacksBox.style.display = 'none';
const t = getTheme();
ssBox.style.backgroundColor = t.hackBox;
const hdr = ssBox.querySelector('.ps_header');
if (hdr) hdr.style.backgroundColor = t.hackHeader;
ssBox.style.display = ssBox.style.display === 'flex' ? 'none' : 'flex';
if (ssBox.style.display === 'flex') ssInitPeer();
});
const _ssThemeObs = new MutationObserver(() => {
if (ssBox.style.display !== 'flex') return;
const t = getTheme();
ssBox.style.backgroundColor = t.hackBox;
const hdr = ssBox.querySelector('.ps_header');
if (hdr) hdr.style.backgroundColor = t.hackHeader;
});
_ssThemeObs.observe(document.head, { childList: true });
const JC_BAL = 'ps_jc_bal';
const JC_CLM = 'ps_jc_claimed';
const JC_OWN = 'ps_jc_owned';
const JC_EQ = 'ps_jc_equipped';
const JC_GAIN = 10;
let jcCoins = Math.max(0, parseInt(localStorage.getItem(JC_BAL) || '0'));
let jcOwned = (() => { try { return JSON.parse(localStorage.getItem(JC_OWN)||'[]'); } catch(e){return[];} })();
let jcEquipped = localStorage.getItem(JC_EQ) || null;
function jcSave() {
localStorage.setItem(JC_BAL, jcCoins);
localStorage.setItem(JC_OWN, JSON.stringify(jcOwned));
if (jcEquipped) localStorage.setItem(JC_EQ, jcEquipped);
else localStorage.removeItem(JC_EQ);
}
function jcCanClaim() {
return Date.now() - parseInt(localStorage.getItem(JC_CLM)||'0') >= 86400000;
}
function jcClaimDaily() {
if (!jcCanClaim()) return false;
jcCoins += JC_GAIN;
localStorage.setItem(JC_CLM, String(Date.now()));
jcSave(); return true;
}
function jcCountdown() {
const rem = 86400000 - (Date.now() - parseInt(localStorage.getItem(JC_CLM)||'0'));
if (rem <= 0) return 'Available now!';
const h = Math.floor(rem/3600000), m = Math.floor((rem%3600000)/60000);
return `Next in ${h>0?h+'h ':''}${m}m`;
}
function jcDraw_cherry(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.35);
for(let i=0;i<7;i++){
const a=(i/7)*Math.PI*2;
ctx.save();ctx.translate(Math.cos(a)*33,Math.sin(a)*33);ctx.rotate(a+Math.PI/2+Math.sin(t+i)*.15);
for(let p=0;p<5;p++){
ctx.save();ctx.rotate((p/5)*Math.PI*2);
const al=.7+.25*Math.sin(t*1.8+i+p);
ctx.fillStyle=`rgba(255,140,195,${al})`;
ctx.beginPath();ctx.ellipse(0,-4.5,2.2,4.5,0,0,Math.PI*2);ctx.fill();ctx.restore();
}
ctx.fillStyle='rgba(255,235,100,.95)';ctx.beginPath();ctx.arc(0,0,1.7,0,Math.PI*2);ctx.fill();
ctx.restore();
}
ctx.restore();
}
function jcDraw_stars(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);
for(let i=0;i<6;i++){
const a=(i/6)*Math.PI*2+t*.5,r=30+Math.sin(t*1.8+i)*5,sz=5.5+Math.sin(t*2.2+i*1.4)*1.5;
ctx.save();ctx.translate(Math.cos(a)*r,Math.sin(a)*r);ctx.rotate(t+i);
ctx.fillStyle='rgba(255,218,30,.12)';ctx.beginPath();ctx.arc(0,0,sz*2.4,0,Math.PI*2);ctx.fill();
ctx.fillStyle='rgba(255,218,30,.42)';ctx.beginPath();ctx.arc(0,0,sz*1.5,0,Math.PI*2);ctx.fill();
ctx.fillStyle='rgba(255,238,50,.95)';ctx.beginPath();
for(let p=0;p<5;p++){const oa=(p/5)*Math.PI*2-Math.PI/2,ia=oa+Math.PI/5;
p===0?ctx.moveTo(Math.cos(oa)*sz,Math.sin(oa)*sz):ctx.lineTo(Math.cos(oa)*sz,Math.sin(oa)*sz);
ctx.lineTo(Math.cos(ia)*sz*.42,Math.sin(ia)*sz*.42);}
ctx.closePath();ctx.fill();ctx.restore();
}
ctx.restore();
}
function jcDraw_fire(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);
for(let i=0;i<30;i++){
const ba=(i/30)*Math.PI*2,phase=((t*2.5+i*.37)%1+1)%1;
const r=24+phase*18,wb=Math.sin(t*4.5+i*2.2)*.2,life=1-phase;
const G=Math.min(255,Math.floor(40+phase*220)),B=phase>.65?Math.floor((phase-.65)*120):0;
ctx.fillStyle=`rgba(255,${G},${B},${life*.88})`;
ctx.beginPath();ctx.arc(Math.cos(ba+wb)*r,Math.sin(ba+wb)*r,2.8*life+.4,0,Math.PI*2);ctx.fill();
}
ctx.restore();
}
function jcDraw_ice(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.18);
for(let i=0;i<6;i++){
ctx.save();ctx.rotate((i/6)*Math.PI*2);
const sh=.55+.4*Math.abs(Math.sin(t*2.5+i));
ctx.fillStyle=`rgba(170,230,255,${sh})`;
ctx.beginPath();ctx.moveTo(0,-24);ctx.lineTo(-4,-34);ctx.lineTo(0,-46);ctx.lineTo(4,-34);ctx.closePath();ctx.fill();
ctx.fillStyle=`rgba(225,248,255,${sh*.62})`;
ctx.beginPath();ctx.moveTo(0,-27);ctx.lineTo(-1.5,-33);ctx.lineTo(0,-42);ctx.lineTo(1.5,-33);ctx.closePath();ctx.fill();
[[-1],[1]].forEach(([s])=>{
ctx.fillStyle=`rgba(150,215,255,${sh*.5})`;
ctx.beginPath();ctx.moveTo(s*2,-30);ctx.lineTo(s*8,-27);ctx.lineTo(s*3,-34);ctx.closePath();ctx.fill();
});
ctx.restore();
}
ctx.restore();
}
function jcDraw_galaxy(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.14);
for(let i=0;i<80;i++){
const arm=(i/80)*Math.PI*7,r=12+(i/80)*48;
if(r<24||r>47) continue;
const tw=.35+.65*Math.abs(Math.sin(t*2.2+i*.7));
const cl=['rgba(180,100,255,','rgba(100,190,255,','rgba(255,190,100,','rgba(255,255,255,'][i%4];
ctx.fillStyle=cl+tw+')';
ctx.beginPath();ctx.arc(Math.cos(arm)*r,Math.sin(arm)*r*.55,1.1+Math.sin(i)*.4,0,Math.PI*2);ctx.fill();
}
ctx.restore();
}
function jcDraw_rainbow(ctx,cx,cy,t){
const cols=['#ff4444','#ff9900','#ffee00','#22cc22','#2299ff','#8822ff','#ff22aa'];
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.28);
cols.forEach((c,i)=>{
ctx.strokeStyle=c;ctx.lineWidth=5.5;ctx.globalAlpha=.88;
ctx.beginPath();ctx.arc(0,0,34,(i/7)*Math.PI*2,((i+1)/7)*Math.PI*2);ctx.stroke();
});
ctx.globalAlpha=.32;ctx.strokeStyle='#fff';ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(0,0,34,0,Math.PI*2);ctx.stroke();
ctx.globalAlpha=1;ctx.restore();
}
function jcDraw_leaves(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.22);
for(let i=0;i<11;i++){
const a=(i/11)*Math.PI*2,r=31+(i%2)*5;
ctx.save();ctx.translate(Math.cos(a)*r,Math.sin(a)*r);ctx.rotate(a+Math.PI/2+Math.sin(t*1.2+i)*.18);
ctx.fillStyle=['rgba(55,155,55,.9)','rgba(80,185,65,.88)','rgba(100,200,75,.85)'][i%3];
ctx.beginPath();ctx.ellipse(0,0,3.5,7.5,0,0,Math.PI*2);ctx.fill();
ctx.strokeStyle='rgba(30,110,30,.5)';ctx.lineWidth=.6;
ctx.beginPath();ctx.moveTo(0,-7);ctx.lineTo(0,7);ctx.stroke();
ctx.restore();
}
ctx.restore();
}
function jcDraw_lightning(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);
for(let i=0;i<4;i++){
if(Math.sin(t*12+i*3.9)<.1) continue;
ctx.save();ctx.rotate((i/4)*Math.PI*2+t*.7);
const al=.55+.45*Math.abs(Math.sin(t*18+i));
const bolt=[[0,-24],[-3,-30],[1,-30],[-2,-38],[2,-38],[-1,-46]];
ctx.lineWidth=4;ctx.strokeStyle=`rgba(255,255,80,${al*.28})`;
ctx.beginPath();bolt.forEach(([x,y],j)=>j?ctx.lineTo(x,y):ctx.moveTo(x,y));ctx.stroke();
ctx.lineWidth=1.3;ctx.strokeStyle=`rgba(255,255,200,${al})`;
ctx.beginPath();bolt.forEach(([x,y],j)=>j?ctx.lineTo(x,y):ctx.moveTo(x,y));ctx.stroke();
ctx.restore();
}
ctx.restore();
}
function jcDraw_diamonds(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.28);
for(let i=0;i<8;i++){
const a=(i/8)*Math.PI*2,r=28+(i%2)*9,sp=.45+.55*Math.abs(Math.sin(t*2.8+i*1.7));
ctx.save();ctx.translate(Math.cos(a)*r,Math.sin(a)*r);ctx.rotate(t*1.1+i);
const sz=4.8*sp;
ctx.fillStyle=`rgba(140,210,255,${sp*.5})`;ctx.beginPath();ctx.arc(0,0,sz*1.5,0,Math.PI*2);ctx.fill();
ctx.fillStyle=`rgba(200,240,255,${sp*.88})`;
ctx.beginPath();ctx.moveTo(0,-sz*1.4);ctx.lineTo(sz,0);ctx.lineTo(0,sz*1.4);ctx.lineTo(-sz,0);ctx.closePath();ctx.fill();
ctx.fillStyle=`rgba(255,255,255,${sp*.5})`;
ctx.beginPath();ctx.moveTo(0,-sz*1.4);ctx.lineTo(sz*.5,-sz*.3);ctx.lineTo(-sz*.3,-sz*.3);ctx.closePath();ctx.fill();
if(sp>.82){ctx.strokeStyle=`rgba(255,255,255,${(sp-.82)*5.5})`;ctx.lineWidth=.7;
ctx.beginPath();ctx.moveTo(0,-sz*1.9);ctx.lineTo(0,sz*1.9);ctx.moveTo(-sz*1.9,0);ctx.lineTo(sz*1.9,0);ctx.stroke();}
ctx.restore();
}
ctx.restore();
}
function jcDraw_sunflower(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.18);
for(let i=0;i<6;i++){
ctx.save();ctx.translate(Math.cos((i/6)*Math.PI*2)*32,Math.sin((i/6)*Math.PI*2)*32);ctx.rotate((i/6)*Math.PI*2+t*.3);
for(let p=0;p<8;p++){ctx.save();ctx.rotate((p/8)*Math.PI*2);ctx.fillStyle='rgba(255,200,20,.9)';ctx.beginPath();ctx.ellipse(0,-6,2,6,0,0,Math.PI*2);ctx.fill();ctx.restore();}
ctx.fillStyle='rgba(80,40,10,.95)';ctx.beginPath();ctx.arc(0,0,3.8,0,Math.PI*2);ctx.fill();
ctx.fillStyle='rgba(160,90,30,.7)';ctx.beginPath();ctx.arc(0,0,2.2,0,Math.PI*2);ctx.fill();
ctx.restore();
}
ctx.restore();
}
function jcDraw_snow(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);
for(let i=0;i<7;i++){
const a=(i/7)*Math.PI*2+t*.28,r=30+Math.sin(t*1.3+i*1.8)*5;
ctx.save();ctx.translate(Math.cos(a)*r,Math.sin(a)*r);ctx.rotate(t*.7+i);
const al=.5+.4*Math.abs(Math.sin(t*1.5+i));
ctx.strokeStyle=`rgba(200,238,255,${al})`;ctx.lineWidth=.9;
for(let arm=0;arm<6;arm++){
ctx.save();ctx.rotate((arm/6)*Math.PI*2);
ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(0,-8);ctx.stroke();
ctx.beginPath();ctx.moveTo(-2.2,-3.5);ctx.lineTo(0,-5.5);ctx.lineTo(2.2,-3.5);ctx.stroke();
ctx.restore();
}
ctx.restore();
}
ctx.restore();
}
function jcDraw_neon(ctx,cx,cy,t){
const hex=psExpAccentHex(),[rn,gn,bn]=hexToRgb(hex),pulse=.62+.38*Math.sin(t*2.8);
ctx.save();ctx.translate(cx,cy);
for(let g=4;g>=0;g--){ctx.strokeStyle=`rgba(${rn},${gn},${bn},${(.07-g*.012)*pulse})`;ctx.lineWidth=4+g*3;ctx.beginPath();ctx.arc(0,0,32,0,Math.PI*2);ctx.stroke();}
ctx.strokeStyle=`rgba(${rn},${gn},${bn},${pulse*.95})`;ctx.lineWidth=2.2;ctx.beginPath();ctx.arc(0,0,32,0,Math.PI*2);ctx.stroke();
for(let i=0;i<6;i++){const a=(i/6)*Math.PI*2+t*1.6;ctx.fillStyle=`rgba(${rn},${gn},${bn},${pulse*.92})`;ctx.beginPath();ctx.arc(Math.cos(a)*32,Math.sin(a)*32,2.8,0,Math.PI*2);ctx.fill();}
ctx.restore();
}
function jcDraw_autumn(ctx,cx,cy,t){
const lf=[[0,30,'rgba(215,70,18,.88)'],[.85,36,'rgba(195,125,15,.9)'],[1.7,28,'rgba(175,55,12,.87)'],
[2.55,33,'rgba(225,95,8,.88)'],[3.4,31,'rgba(155,75,18,.82)'],[4.25,38,'rgba(205,108,12,.86)'],
[5.1,29,'rgba(190,60,22,.9)'],[5.95,34,'rgba(238,85,0,.87)']];
ctx.save();ctx.translate(cx,cy);
lf.forEach(([ba,r,col],i)=>{
const a=ba+t*.48+Math.sin(t*.9+i)*.14,rr=r+Math.sin(t*1.1+i)*3;
ctx.save();ctx.translate(Math.cos(a)*rr,Math.sin(a)*rr);ctx.rotate(a+t*.55+i*.5);
ctx.fillStyle=col;ctx.beginPath();ctx.moveTo(0,-7.5);ctx.bezierCurveTo(5.5,-3,5.5,3,0,7.5);ctx.bezierCurveTo(-5.5,3,-5.5,-3,0,-7.5);ctx.fill();
ctx.strokeStyle='rgba(80,30,0,.32)';ctx.lineWidth=.5;ctx.beginPath();ctx.moveTo(0,-7);ctx.lineTo(0,7);ctx.stroke();
ctx.restore();
});
ctx.restore();
}
function jcDraw_butterfly(ctx,cx,cy,t){
const cols=[['rgba(150,80,200,.88)','rgba(200,110,255,.65)'],['rgba(255,115,0,.88)','rgba(255,185,45,.65)'],
['rgba(0,155,200,.88)','rgba(75,215,255,.65)'],['rgba(200,45,95,.88)','rgba(255,95,150,.65)']];
ctx.save();ctx.translate(cx,cy);
for(let i=0;i<4;i++){
const a=(i/4)*Math.PI*2+t*.62,flap=Math.sin(t*5.5+i*2.2),ws=.5+flap*.5;
ctx.save();ctx.translate(Math.cos(a)*32,Math.sin(a)*32);ctx.rotate(a+Math.PI/2);ctx.scale(ws,1);
const[c1,c2]=cols[i];
ctx.fillStyle=c1;ctx.beginPath();ctx.ellipse(-5,-3.5,5,7,-.3,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.ellipse(5,-3.5,5,7,.3,0,Math.PI*2);ctx.fill();
ctx.fillStyle=c2;ctx.beginPath();ctx.ellipse(-3.5,3.5,3.5,4.5,.4,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.ellipse(3.5,3.5,3.5,4.5,-.4,0,Math.PI*2);ctx.fill();
ctx.scale(1/ws,1);ctx.fillStyle='rgba(30,15,55,.82)';ctx.beginPath();ctx.ellipse(0,0,1.3,5.5,0,0,Math.PI*2);ctx.fill();
ctx.restore();
}
ctx.restore();
}
function jcDraw_crown(ctx,cx,cy,t){
ctx.save();ctx.translate(cx,cy);
const bob=Math.sin(t*1.7)*2,base=-32+bob,sh=.65+.35*Math.sin(t*2.8);
ctx.fillStyle=`rgba(255,215,0,${sh*.2})`;ctx.beginPath();ctx.arc(0,base-4,19,0,Math.PI*2);ctx.fill();
ctx.fillStyle=`rgba(255,205,0,${sh})`;ctx.strokeStyle=`rgba(175,125,0,${sh*.7})`;ctx.lineWidth=.8;
ctx.beginPath();ctx.moveTo(-15,base+8);ctx.lineTo(-15,base-4);ctx.lineTo(-9,base-13);
ctx.lineTo(-4,base-4);ctx.lineTo(0,base-15);ctx.lineTo(4,base-4);ctx.lineTo(9,base-13);
ctx.lineTo(15,base-4);ctx.lineTo(15,base+8);ctx.closePath();ctx.fill();ctx.stroke();
[['rgba(255,70,70,.9)',-5],['rgba(80,140,255,.9)',0],['rgba(255,70,70,.9)',5]].forEach(([c,gx])=>{
ctx.fillStyle=c;ctx.beginPath();ctx.arc(gx,base+3,2,0,Math.PI*2);ctx.fill();
});
[[-15,base-4],[-9,base-13],[0,base-15],[9,base-13],[15,base-4]].forEach(([dx,dy])=>{
ctx.fillStyle=`rgba(255,248,180,${sh})`;ctx.beginPath();ctx.arc(dx,dy,1.5,0,Math.PI*2);ctx.fill();
});
ctx.restore();
}
function jcDraw_mushroom(ctx,cx,cy,t){
const N=5;
ctx.save();ctx.translate(cx,cy);ctx.rotate(t*.25);
for(let i=0;i<N;i++){
const a=(i/N)*Math.PI*2,r=31+(i%2)*5;
ctx.save();ctx.translate(Math.cos(a)*r,Math.sin(a)*r);ctx.rotate(a+Math.PI/2);
const al=.7+.25*Math.sin(t*2+i);
ctx.fillStyle=`rgba(220,50,50,${al})`;ctx.beginPath();ctx.arc(0,-4,6,Math.PI,0);ctx.closePath();ctx.fill();
ctx.fillStyle=`rgba(255,255,255,${al*.88})`;
[[0,-7],[3,-4],[-3,-4]].forEach(([wx,wy])=>{ctx.beginPath();ctx.arc(wx,wy,1.3,0,Math.PI*2);ctx.fill();});
ctx.fillStyle=`rgba(250,220,180,${al})`;ctx.beginPath();ctx.rect(-2.5,-4,5,7);ctx.fill();
ctx.restore();
}
ctx.restore();
}
const JC_ITEMS = [
{ id:'leaves', name:'Leaf Wreath', desc:'Fresh leaves form a wreath', price:20, draw:jcDraw_leaves },
{ id:'cherry', name:'Cherry Blossoms', desc:'Petals slowly orbit your avatar', price:25, draw:jcDraw_cherry },
{ id:'autumn', name:'Autumn Leaves', desc:'Warm leaves drift around you', price:25, draw:jcDraw_autumn },
{ id:'rainbow', name:'Rainbow Halo', desc:'A radiant spinning rainbow ring', price:30, draw:jcDraw_rainbow },
{ id:'snow', name:'Snowflakes', desc:'Gentle snowflakes orbit you', price:30, draw:jcDraw_snow },
{ id:'stars', name:'Golden Stars', desc:'Shimmering stars dance around', price:35, draw:jcDraw_stars },
{ id:'sunflower', name:'Sunflowers', desc:'A bright wreath of sunflowers', price:35, draw:jcDraw_sunflower },
{ id:'mushroom', name:'Mushrooms', desc:'Cute little mushrooms orbit you', price:40, draw:jcDraw_mushroom },
{ id:'ice', name:'Ice Crystals', desc:'Crystal spikes radiate outward', price:45, draw:jcDraw_ice },
{ id:'butterfly', name:'Butterflies', desc:'Butterflies flutter around you', price:45, draw:jcDraw_butterfly },
{ id:'lightning', name:'Lightning', desc:'Electric bolts crackle around', price:50, draw:jcDraw_lightning },
{ id:'fire', name:'Fire Ring', desc:'Flames engulf your presence', price:55, draw:jcDraw_fire },
{ id:'neon', name:'Neon Ring', desc:'A pulsing neon glow (theme color)',price:60, draw:jcDraw_neon },
{ id:'galaxy', name:'Galaxy Swirl', desc:'A spiral galaxy surrounds you', price:65, draw:jcDraw_galaxy },
{ id:'crown', name:'Royal Crown', desc:'A golden crown bobs above you', price:70, draw:jcDraw_crown },
{ id:'diamonds', name:'Diamonds', desc:'Sparkling gems orbit your avatar', price:80, draw:jcDraw_diamonds },
];
const jcCss = document.createElement('style');
jcCss.textContent = `
#ps_shop_open_btn {
position:fixed;bottom:224px;right:20px;z-index:10001;
width:36px;height:36px;border-radius:50%;
border:1.5px solid rgba(255,200,50,.45);
background:rgba(12,10,0,.8);cursor:pointer;
display:flex;align-items:center;justify-content:center;
font-size:18px;backdrop-filter:blur(8px);
box-shadow:0 2px 14px rgba(0,0,0,.55);
transition:border-color .2s,transform .15s;
}
#ps_shop_open_btn:hover{border-color:rgba(255,200,50,1);transform:scale(1.1);}
#ps_shop_panel {
position:fixed;bottom:270px;right:20px;z-index:10002;
width:312px;border-radius:14px;
background:rgba(10,10,10,.97);
border:1px solid rgba(255,200,50,.2);
box-shadow:0 8px 42px rgba(0,0,0,.82);
backdrop-filter:blur(18px);
display:none;flex-direction:column;overflow:hidden;
}
#ps_shop_panel .ps_header {
border-radius:14px 14px 0 0;
background:rgba(46,34,0,.97)!important;
border-bottom:1px solid rgba(255,200,50,.15)!important;
}
#ps_shop_panel .ps_body {
padding:0!important;gap:0!important;
overflow:hidden;min-height:0;
}
#ps_jc_bal_bar {
display:flex;align-items:center;justify-content:space-between;
padding:8px 12px 7px;flex-shrink:0;
background:rgba(255,200,50,.04);
border-bottom:1px solid rgba(255,200,50,.1);
}
.jc_bal_disp{display:flex;align-items:center;gap:5px;font-size:14px;font-weight:bold;color:#ffd700;}
.jc_bal_sub{font-size:10px;color:rgba(255,215,0,.4);font-weight:normal;margin-left:2px;}
#ps_jc_claim_btn {
padding:4px 10px;border:1px solid rgba(255,200,50,.5);
background:rgba(255,200,50,.14);color:rgba(255,210,50,.95);
border-radius:8px;font-size:10px;font-weight:bold;cursor:pointer;
transition:background .15s;white-space:nowrap;
}
#ps_jc_claim_btn:hover:not(:disabled){background:rgba(255,200,50,.3);}
#ps_jc_claim_btn:disabled{opacity:.35;cursor:default;border-color:rgba(255,255,255,.07);color:rgba(255,255,255,.22);}
#ps_shop_grid {
display:grid;grid-template-columns:1fr 1fr;gap:7px;
padding:8px;overflow-y:auto;max-height:58vh;
scrollbar-width:thin;scrollbar-color:rgba(255,200,50,.18) transparent;
}
#ps_shop_grid::-webkit-scrollbar{width:4px;}
#ps_shop_grid::-webkit-scrollbar-thumb{background:rgba(255,200,50,.18);border-radius:3px;}
.jc_card {
border-radius:10px;border:1px solid rgba(255,255,255,.07);
background:rgba(255,255,255,.034);
display:flex;flex-direction:column;align-items:center;
padding:8px 7px 7px;gap:4px;
transition:border-color .15s,background .15s;
}
.jc_card:hover{border-color:rgba(255,255,255,.18);background:rgba(255,255,255,.064);}
.jc_card.jc_owned{border-color:rgba(255,200,50,.3);}
.jc_card.jc_equipped{
border-color:rgba(255,200,50,.88);background:rgba(255,200,50,.07);
box-shadow:inset 0 0 14px rgba(255,200,50,.07),0 0 12px rgba(255,200,50,.1);
}
.jc_card.jc_equipped:hover{border-color:rgba(255,200,50,1);}
.jc_prev_canvas{display:block;border-radius:50%;}
.jc_item_name{font-size:10px;font-weight:bold;color:rgba(255,255,255,.9);text-align:center;line-height:1.3;}
.jc_item_desc{font-size:9px;color:rgba(255,255,255,.28);text-align:center;line-height:1.3;}
.jc_price{font-size:10px;font-weight:bold;color:#ffd700;display:flex;align-items:center;gap:3px;}
.jc_btn{width:100%;padding:4px 0;border:none;border-radius:6px;font-size:10px;font-weight:bold;cursor:pointer;color:#fff;transition:filter .14s;}
.jc_btn:hover:not(.jc_na){filter:brightness(1.18);}
.jc_btn.jc_buy{background:rgba(255,200,50,.88);color:#1a1000;}
.jc_btn.jc_equip{background:rgba(55,185,75,.85);}
.jc_btn.jc_uneq{background:rgba(65,65,65,.72);font-weight:normal;}
.jc_btn.jc_na{background:rgba(45,45,45,.62);color:rgba(255,255,255,.25);cursor:not-allowed;}
#ps_jc_deco_canvas{position:fixed;pointer-events:none;z-index:10000;}
#ps_jc_toast{
position:fixed;bottom:120px;right:65px;z-index:11200;
background:rgba(36,26,0,.97);color:#ffd700;
padding:7px 15px;border-radius:9px;font-size:12px;font-weight:bold;
pointer-events:none;opacity:0;transition:opacity .2s;
border:1px solid rgba(255,200,50,.35);white-space:nowrap;
display:flex;align-items:center;gap:5px;
}
#ps_jc_toast.vis{opacity:1;}
#ps_profile_btn.jc_deco_on{
border-color:rgba(255,200,50,.75)!important;
box-shadow:0 0 16px rgba(255,200,50,.22)!important;
}
`;
document.head.appendChild(jcCss);
const jcToastEl = document.createElement('div');
jcToastEl.id = 'ps_jc_toast';
document.body.appendChild(jcToastEl);
let _jcTTmr = null;
function jcToast(msg) {
jcToastEl.innerHTML = `<span>🪙</span><span>${msg}</span>`;
jcToastEl.classList.add('vis');
clearTimeout(_jcTTmr);
_jcTTmr = setTimeout(() => jcToastEl.classList.remove('vis'), 2800);
}
const jcDecoCanvas = document.createElement('canvas');
jcDecoCanvas.id = 'ps_jc_deco_canvas';
jcDecoCanvas.width = 100; jcDecoCanvas.height = 100;
document.body.appendChild(jcDecoCanvas);
const jcDecoCtx = jcDecoCanvas.getContext('2d');
function jcPosDeco() {
const btn = document.getElementById('ps_profile_btn');
if (!btn) { jcDecoCanvas.style.opacity = '0'; return; }
const r = btn.getBoundingClientRect();
jcDecoCanvas.style.opacity = '1';
jcDecoCanvas.style.left = (r.left + r.width / 2 - 50) + 'px';
jcDecoCanvas.style.top = (r.top + r.height / 2 - 50) + 'px';
const pb = document.getElementById('ps_profile_btn');
if (pb) pb.classList.toggle('jc_deco_on', !!jcEquipped);
}
const jcT0 = Date.now();
let jcPosCtr = 0;
(function jcDecoLoop() {
requestAnimationFrame(jcDecoLoop);
if (++jcPosCtr % 10 === 0) jcPosDeco();
jcDecoCtx.clearRect(0, 0, 100, 100);
if (!jcEquipped) return;
const item = JC_ITEMS.find(it => it.id === jcEquipped);
if (item) item.draw(jcDecoCtx, 50, 50, (Date.now() - jcT0) / 1000);
})();
jcPosDeco();
let jcPreviews = [];
const jcPrevT0 = Date.now();
(function jcPrevLoop() {
requestAnimationFrame(jcPrevLoop);
const t = (Date.now() - jcPrevT0) / 1000;
jcPreviews = jcPreviews.filter(p => p.canvas.isConnected);
jcPreviews.forEach(({ canvas, drawFn }) => {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 100, 100);
// Placeholder avatar
ctx.save();
ctx.beginPath(); ctx.arc(50, 50, 21, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(55,55,80,.78)'; ctx.fill();
ctx.strokeStyle = 'rgba(120,120,155,.5)'; ctx.lineWidth = 1.5; ctx.stroke();
ctx.fillStyle = 'rgba(90,90,120,.5)';
ctx.beginPath(); ctx.arc(50, 43, 6.5, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(50, 68, 13, Math.PI, 0, true); ctx.fill();
ctx.restore();
drawFn(ctx, 50, 50, t);
});
})();
function jcRender() {
const balEl = document.getElementById('ps_jc_bal_num');
const claimEl = document.getElementById('ps_jc_claim_btn');
if (balEl) balEl.textContent = jcCoins;
if (claimEl) {
const can = jcCanClaim();
claimEl.textContent = can ? `+ ${JC_GAIN} Free Coins` : jcCountdown();
claimEl.disabled = !can;
}
const grid = document.getElementById('ps_shop_grid');
if (!grid) return;
jcPreviews = [];
grid.innerHTML = '';
JC_ITEMS.forEach(item => {
const owned = jcOwned.includes(item.id);
const equipped = jcEquipped === item.id;
const afford = jcCoins >= item.price;
const card = document.createElement('div');
card.className = 'jc_card' + (equipped ? ' jc_equipped' : owned ? ' jc_owned' : '');
const cvs = document.createElement('canvas');
cvs.width = 100; cvs.height = 100;
cvs.className = 'jc_prev_canvas';
cvs.style.width = '80px'; cvs.style.height = '80px';
card.appendChild(cvs);
jcPreviews.push({ canvas: cvs, drawFn: item.draw });
const nameEl = document.createElement('div');
nameEl.className = 'jc_item_name'; nameEl.textContent = item.name;
card.appendChild(nameEl);
const descEl = document.createElement('div');
descEl.className = 'jc_item_desc'; descEl.textContent = item.desc;
card.appendChild(descEl);
if (!owned) {
const priceEl = document.createElement('div');
priceEl.className = 'jc_price';
priceEl.innerHTML = `<span>🪙</span><span>${item.price}</span>`;
card.appendChild(priceEl);
const btn = document.createElement('button');
btn.className = 'jc_btn ' + (afford ? 'jc_buy' : 'jc_na');
btn.textContent = afford ? 'Buy' : `Need ${item.price - jcCoins} more 🪙`;
if (afford) btn.addEventListener('click', () => {
if (jcCoins < item.price) return;
jcCoins -= item.price; jcOwned.push(item.id); jcSave();
jcToast(`Bought ${item.name}!`); jcRender();
});
card.appendChild(btn);
} else {
const btn = document.createElement('button');
if (equipped) {
btn.className = 'jc_btn jc_uneq'; btn.textContent = '✓ Equipped';
btn.addEventListener('click', () => {
jcEquipped = null; jcSave(); jcPosDeco(); jcRender();
});
} else {
btn.className = 'jc_btn jc_equip'; btn.textContent = 'Equip';
btn.addEventListener('click', () => {
jcEquipped = item.id; jcSave(); jcPosDeco();
jcToast(`Equipped ${item.name}!`); jcRender();
});
}
card.appendChild(btn);
}
grid.appendChild(card);
});
}
const jcShopBtn = document.createElement('button');
jcShopBtn.id = 'ps_shop_open_btn';
jcShopBtn.title = 'JuCoin Shop';
jcShopBtn.textContent = '🪙';
document.body.appendChild(jcShopBtn);
const jcPanel = document.createElement('div');
jcPanel.id = 'ps_shop_panel';
jcPanel.innerHTML = `
<div class="ps_body">
<div id="ps_jc_bal_bar">
<div class="jc_bal_disp">
<span>🪙</span>
<span id="ps_jc_bal_num">0</span>
<span class="jc_bal_sub">JuCoins</span>
</div>
<button id="ps_jc_claim_btn">+ ${JC_GAIN} Free Coins</button>
</div>
<div id="ps_shop_grid"></div>
</div>`;
jcPanel.insertBefore(
makeHeader('🪙 JuCoin Shop', () => { jcPanel.style.display = 'none'; jcPreviews = []; }),
jcPanel.firstChild
);
document.body.appendChild(jcPanel);
makeDraggable(jcPanel);
setupMinimize(jcPanel);
jcPanel.addEventListener('click', e => {
if (e.target.id !== 'ps_jc_claim_btn') return;
if (jcClaimDaily()) { jcToast(`+${JC_GAIN} JuCoins claimed!`); jcRender(); }
});
jcShopBtn.addEventListener('click', () => {
const open = jcPanel.style.display === 'flex';
jcPanel.style.display = open ? 'none' : 'flex';
if (!open) jcRender();
else jcPreviews = [];
});
setInterval(() => {
const el = document.getElementById('ps_jc_claim_btn');
if (!el) return;
const can = jcCanClaim();
el.textContent = can ? `+ ${JC_GAIN} Free Coins` : jcCountdown();
el.disabled = !can;
}, 60000);
jcPosDeco();
const ACH_TIERS = [
{ id:'common', name:'Common', color:'#c0c0c0', coins:5 },
{ id:'rare', name:'Rare', color:'#4d8eff', coins:15 },
{ id:'epic', name:'Epic', color:'#bb44ff', coins:30 },
{ id:'legendary', name:'Legendary', color:'#ffcc00', coins:60 },
{ id:'mythical', name:'Mythical', color:'#ff3388', coins:100 },
{ id:'super', name:'Super', color:'#00ffcc', coins:175 },
{ id:'celestial', name:'Celestial', color:'#fff0a0', coins:300 },
];
const ACH_LIST = [
{
id:'we_all_start', tier:'common',
name:'We all start somewhere',
desc:'Play the game for at least 10 minutes.',
icon:'🌱',
check: s => s.minsPlayed >= 10,
progress: s => ({ v: Math.min(s.minsPlayed, 10), m: 10, unit: 'min' }),
},
{
id:'quarter_hour', tier:'common',
name:'Warming Up',
desc:'Play for 15 minutes total.',
icon:'⏱️',
check: s => s.minsPlayed >= 15,
progress: s => ({ v: Math.min(s.minsPlayed, 15), m: 15, unit: 'min' }),
},
{
id:'half_hour', tier:'rare',
name:'Getting Into It',
desc:'Play for 30 minutes total.',
icon:'🕐',
check: s => s.minsPlayed >= 30,
progress: s => ({ v: Math.min(s.minsPlayed, 30), m: 30, unit: 'min' }),
},
{
id:'one_hour', tier:'rare',
name:'Time Well Spent',
desc:'Play for an hour total.',
icon:'🕐',
check: s => s.minsPlayed >= 60,
progress: s => ({ v: Math.min(s.minsPlayed, 60), m: 60, unit: 'min' }),
},
{
id:'level_5', tier:'rare',
name:'Rising Star',
desc:'Reach level 5.',
icon:'⭐',
check: s => s.level >= 5,
progress: s => ({ v: Math.min(s.level, 5), m: 5, unit: 'lv' }),
},
{
id:'level_10', tier:'epic',
name:'Veteran',
desc:'Reach level 10.',
icon:'🌟',
check: s => s.level >= 10,
progress: s => ({ v: Math.min(s.level, 10), m: 10, unit: 'lv' }),
},
{
id:'level_25', tier:'legendary',
name:'Legend',
desc:'Reach level 25.',
icon:'👑',
check: s => s.level >= 25,
progress: s => ({ v: Math.min(s.level, 25), m: 25, unit: 'lv' }),
},
{
id:'two_hours', tier:'epic',
name:'Dedicated',
desc:'Play for 2 hours total.',
icon:'🎯',
check: s => s.minsPlayed >= 120,
progress: s => ({ v: Math.min(s.minsPlayed, 120), m: 120, unit: 'min' }),
},
{
id:'five_hours', tier:'legendary',
name:'Obsessed',
desc:'Play for 5 hours total.',
icon:'🔥',
check: s => s.minsPlayed >= 300,
progress: s => ({ v: Math.min(s.minsPlayed, 300), m: 300, unit: 'min' }),
},
{
id:'ten_hours', tier:'mythical',
name:'No Life (Affectionate)',
desc:'Play for 10 hours total.',
icon:'💀',
check: s => s.minsPlayed >= 600,
progress: s => ({ v: Math.min(s.minsPlayed, 600), m: 600, unit: 'min' }),
},
{
id:'level_50', tier:'mythical',
name:'Transcendent',
desc:'Reach level 50.',
icon:'✨',
check: s => s.level >= 50,
progress: s => ({ v: Math.min(s.level, 50), m: 50, unit: 'lv' }),
},
{
id:'exp_1000', tier:'super',
name:'Centurion',
desc:'Earn 1,000 total EXP minutes.',
icon:'🏛️',
check: s => s.totalExp >= 1000,
progress: s => ({ v: Math.min(s.totalExp, 1000), m: 1000, unit: 'exp' }),
},
{
id:'level_100', tier:'celestial',
name:'Celestial Being',
desc:'Reach level 100.',
icon:'🌌',
check: s => s.level >= 100,
progress: s => ({ v: Math.min(s.level, 100), m: 100, unit: 'lv' }),
},
];
const TOWER_WORLDS = [
{
id: 'meadow', name: 'Sunlit Meadow', level: 1,
requiredAch: 0, requiredCoins: 0,
desc: 'A peaceful starting world bathed in golden sunlight. Free to all explorers.',
colors: ['#7ec850','#c8f080','#ffe87a'], symbol: '🌾',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/meadow.avif',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'ocean', name: 'Coral Ocean', level: 2,
requiredAch: 2, requiredCoins: 30,
desc: 'Dive into azure waters teeming with coral and colourful fish.',
colors: ['#0077cc','#00ccee','#ff7755'], symbol: '🪸',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/ocean%20final.webp',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'volcano', name: 'Volcanic Forge', level: 3,
requiredAch: 4, requiredCoins: 80,
desc: 'A fiery realm where lava rivers flow and obsidian towers pierce the sky.',
colors: ['#ff4400','#ff8800','#330000'], symbol: '🌋',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/volcano.jpg',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'crystal', name: 'Crystal Caverns', level: 4,
requiredAch: 6, requiredCoins: 150,
desc: 'Deep underground, enormous crystal formations glow with otherworldly light.',
colors: ['#aa44ff','#44ccff','#ff44cc'], symbol: '💎',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/crystal%20cave.jpg',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'arctic', name: 'Frozen Tundra', level: 5,
requiredAch: 8, requiredCoins: 250,
desc: 'Endless ice fields stretch to the horizon under dancing auroras.',
colors: ['#aaddff','#ffffff','#55ffcc'], symbol: '❄️',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/icy%20tundra.avif',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'jungle', name: 'Ancient Jungle', level: 6,
requiredAch: 10, requiredCoins: 400,
desc: 'A dense primordial jungle hiding temples and lost civilisations.',
colors: ['#228833','#55cc44','#ffdd44'], symbol: '🌴',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/jungle.jpg',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'desert', name: 'Shifting Sands', level: 7,
requiredAch: 12, requiredCoins: 600,
desc: 'Vast desert dunes shimmer under two suns with ancient pyramids on the horizon.',
colors: ['#ffcc44','#ff8800','#cc6600'], symbol: '🏜️',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/desert.jpg',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'storm', name: 'Tempest Peak', level: 8,
requiredAch: 14, requiredCoins: 900,
desc: 'A perpetual storm rages atop a floating mountain, crackling with lightning.',
colors: ['#8888cc','#4444aa','#ffff44'], symbol: '⛈️',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/tempest%20peak.webp',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'cosmos', name: 'Starbound Nexus', level: 9,
requiredAch: 16, requiredCoins: 1400,
desc: 'A cosmic realm beyond space where stars are born and galaxies collide.',
colors: ['#aa44ff','#4488ff','#ff88ff'], symbol: '🌌',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/space.avif',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
{
id: 'divine', name: 'Divine Citadel', level: 10,
requiredAch: 20, requiredCoins: 2500,
desc: 'The pinnacle of existence. A floating citadel of pure light and ancient power.',
colors: ['#ffffff','#ffd700','#ff88ff'], symbol: '✨',
imgUrl: 'https://raw.githubusercontent.com/Mathmaticians/Achievement-world-tower-images/main/heaven.jpg',
_img: null, _imgLoading: false, _imgLoadFailed: false,
drawBg(ctx, W, H, t) { towDrawWorldBg(ctx, W, H, t, this); }
},
];
function towDrawWorldBg(ctx, W, H, t, world) {
if (world._imgLoadFailed || !world.imgUrl) {
const [r, g, b] = hexToRgb(world.colors[0]);
const fg = ctx.createLinearGradient(0, 0, W, H);
fg.addColorStop(0, `rgba(${r},${g},${b},0.35)`);
fg.addColorStop(1, `rgba(0,0,0,0.85)`);
ctx.fillStyle = fg;
ctx.fillRect(0, 0, W, H);
ctx.save();
ctx.font = `${Math.min(W, H) * 0.45}px serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.globalAlpha = 0.18 + 0.06 * Math.sin(t * 1.2);
ctx.fillText(world.symbol, W / 2, H / 2);
ctx.restore();
return;
}
if (!world._img) {
const [r, g, b] = hexToRgb(world.colors[0]);
ctx.fillStyle = `rgba(${r},${g},${b},0.2)`;
ctx.fillRect(0, 0, W, H);
ctx.save();
ctx.globalAlpha = 0.3 + 0.2 * Math.abs(Math.sin(t * 2));
ctx.font = `${Math.min(W, H) * 0.3}px serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(world.symbol, W / 2, H / 2);
ctx.restore();
if (!world._imgLoading) {
world._imgLoading = true;
const img = new Image();
img.onload = () => { world._img = img; world._imgLoading = false; };
img.onerror = () => {
world._imgLoadFailed = true;
world._imgLoading = false;
console.warn('[Parasidieum] Tower image failed:', world.imgUrl);
};
img.src = world.imgUrl;
}
return;
}
const imgAspect = world._img.width / world._img.height;
const canvAspect = W / H;
let sx = 0, sy = 0, sw = world._img.width, sh = world._img.height;
if (imgAspect > canvAspect) {
sw = world._img.height * canvAspect;
sx = (world._img.width - sw) / 2;
} else {
sh = world._img.width / canvAspect;
sy = (world._img.height - sh) / 2;
}
ctx.drawImage(world._img, sx, sy, sw, sh, 0, 0, W, H);
const vg = ctx.createRadialGradient(W / 2, H / 2, W * 0.18, W / 2, H / 2, W * 0.75);
vg.addColorStop(0, 'rgba(0,0,0,0)');
vg.addColorStop(1, 'rgba(0,0,0,0.58)');
ctx.fillStyle = vg;
ctx.fillRect(0, 0, W, H);
const [r, g, b] = hexToRgb(world.colors[0]);
ctx.fillStyle = `rgba(${r},${g},${b},${0.04 + 0.03 * Math.sin(t * 1.1)})`;
ctx.fillRect(0, 0, W, H);
}
const ACH_DONE_KEY = 'ps_ach_done_v1';
const ACH_CLAIMED_KEY = 'ps_ach_claimed_v1';
const ACH_MINS_KEY = 'ps_ach_mins_v1';
const TOW_UNLOCKED_KEY= 'ps_tow_unlocked_v1';
function achLoadSet(key) { try { return new Set(JSON.parse(localStorage.getItem(key)||'[]')); } catch(e){ return new Set(); } }
function achSaveSet(key,s){ try { localStorage.setItem(key, JSON.stringify([...s])); } catch(e){} }
let achDone = achLoadSet(ACH_DONE_KEY);
let achClaimed = achLoadSet(ACH_CLAIMED_KEY);
let towUnlocked = achLoadSet(TOW_UNLOCKED_KEY);
let achMinsPlayed = Math.max(0, parseInt(localStorage.getItem(ACH_MINS_KEY)||'0'));
if (!towUnlocked.has('meadow')) { towUnlocked.add('meadow'); achSaveSet(TOW_UNLOCKED_KEY, towUnlocked); }
function achSaveMins(){ localStorage.setItem(ACH_MINS_KEY, achMinsPlayed); }
function achTick(){
achMinsPlayed++;
achSaveMins();
const s = { minsPlayed: achMinsPlayed, level: typeof psExpState!=='undefined'?psExpState.level:1, totalExp: typeof psExpState!=='undefined'?psExpState.totalExp:0 };
ACH_LIST.forEach(ach => {
if (achDone.has(ach.id)) return;
if (!ach.check(s)) return;
achDone.add(ach.id);
achSaveSet(ACH_DONE_KEY, achDone);
achShowUnlockFX(ach);
});
if (document.getElementById('ps_ach_overlay')) achBuildPage();
}
function achShowUnlockFX(ach){
const tier = ACH_TIERS.find(t => t.id === ach.tier);
const fl = document.createElement('div');
fl.style.cssText = `position:fixed;inset:0;z-index:20099;pointer-events:none;background:${tier.color};opacity:.12;animation:achFlashOut .75s forwards;`;
document.body.appendChild(fl);
setTimeout(()=>fl.remove(), 800);
achShowToast(ach.icon, 'Achievement Unlocked!', ach.name, `Claim your ${tier.coins} JuCoins`, tier.color);
}
function achShowToast(icon, title, body, sub, color){
const el = document.createElement('div');
el.className = 'ps_ach_toast';
el.innerHTML = `<span style="font-size:22px;flex-shrink:0">${icon}</span><div><div style="font-size:9px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:${color};margin-bottom:3px">${title}</div><div style="font-size:13px;font-weight:700;color:#fff;line-height:1.25">${body}</div><div style="font-size:10px;color:rgba(255,255,255,.35);margin-top:2px">${sub}</div></div>`;
el.style.cssText = `background:rgba(5,5,18,.98);border:1px solid ${color}44;border-radius:13px;padding:11px 16px;display:flex;align-items:center;gap:11px;box-shadow:0 4px 28px rgba(0,0,0,.85),0 0 20px ${color}22;backdrop-filter:blur(18px);animation:achToastIn .3s cubic-bezier(.2,.8,.3,1);pointer-events:none;min-width:240px;max-width:310px;`;
let box = document.getElementById('ps_ach_toast_box');
if (!box) {
box = document.createElement('div');
box.id = 'ps_ach_toast_box';
box.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:20100;display:flex;flex-direction:column;gap:7px;pointer-events:none;';
document.body.appendChild(box);
}
box.appendChild(el);
setTimeout(()=>{ el.style.animation='achToastOut .25s ease forwards'; setTimeout(()=>el.remove(),260); }, 4800);
}
function achClaim(achId, buttonEl){
const ach = ACH_LIST.find(a => a.id === achId);
if (!ach || !achDone.has(ach.id) || achClaimed.has(ach.id)) return;
const tier = ACH_TIERS.find(t => t.id === ach.tier);
achClaimed.add(ach.id);
achSaveSet(ACH_CLAIMED_KEY, achClaimed);
if (typeof jcCoins !== 'undefined') { jcCoins += tier.coins; jcSave(); }
const rect = buttonEl.closest('[data-achcard]')?.getBoundingClientRect();
if (rect) {
const cx = rect.left + rect.width/2, cy = rect.top + rect.height/2;
achSpawnParticles(cx, cy, tier.color);
achFloatLabel(cx, cy, `+${tier.coins} 🪙`, tier.color);
}
achShowToast('🪙','JuCoins Earned!',`+${tier.coins} JuCoins`, tier.name+' achievement', tier.color);
achBuildPage();
}
let _achParts = [];
let _achPCanvas = null;
function achEnsureParticleCanvas(){
if (_achPCanvas && _achPCanvas.isConnected) return;
_achPCanvas = document.createElement('canvas');
_achPCanvas.id = 'ps_ach_particle_cv';
_achPCanvas.style.cssText = 'position:fixed;inset:0;width:100vw;height:100vh;pointer-events:none;z-index:20098;';
_achPCanvas.width = window.innerWidth; _achPCanvas.height = window.innerHeight;
document.body.appendChild(_achPCanvas);
const ctx = _achPCanvas.getContext('2d');
(function loop(){
requestAnimationFrame(loop);
if (!_achParts.length){ ctx.clearRect(0,0,_achPCanvas.width,_achPCanvas.height); return; }
ctx.clearRect(0,0,_achPCanvas.width,_achPCanvas.height);
_achParts = _achParts.map(p=>({...p,x:p.x+p.vx,y:p.y+p.vy,vy:p.vy+.2,life:p.life-p.decay})).filter(p=>p.life>0);
_achParts.forEach(p=>{
ctx.globalAlpha=p.life*.9; ctx.shadowColor=p.col; ctx.shadowBlur=14;
ctx.fillStyle=p.col; ctx.beginPath(); ctx.arc(p.x,p.y,p.sz*p.life,0,Math.PI*2); ctx.fill();
});
ctx.globalAlpha=1; ctx.shadowBlur=0;
})();
}
function achSpawnParticles(x,y,color,n=40){
achEnsureParticleCanvas();
for(let i=0;i<n;i++) _achParts.push({x,y,vx:(Math.random()-.5)*18,vy:(Math.random()-.5)*16-6,life:1,decay:.013+Math.random()*.022,sz:2.5+Math.random()*5.5,col:color});
}
function achFloatLabel(x,y,text,color){
const el=document.createElement('div');
el.style.cssText=`position:fixed;left:${x-30}px;top:${y-12}px;z-index:20101;pointer-events:none;font-size:14px;font-weight:900;color:${color};text-shadow:0 0 12px ${color};white-space:nowrap;animation:achFloatXP 1.4s cubic-bezier(.2,.8,.3,1) forwards;`;
el.textContent=text;
document.body.appendChild(el);
setTimeout(()=>el.remove(),1420);
}
let towRafId = null;
let towScrollY = 0;
let towTargetScrollY = 0;
const TOW_CARD_H = 180;
const TOW_CARD_GAP = 20;
function achOpenTowerPanel(parentOverlay) {
TOWER_WORLDS.forEach(w => { w._img = null; w._imgLoading = false; w._imgLoadFailed = false; });
const old = document.getElementById('ps_tow_panel');
if (old) { old.remove(); if(towRafId){cancelAnimationFrame(towRafId);towRafId=null;} return; }
const panel = document.createElement('div');
panel.id = 'ps_tow_panel';
panel.style.cssText = `
position:fixed; inset:0; z-index:20010;
display:flex; flex-direction:column;
background:rgba(0,0,0,0.97);
font-family:"Segoe UI",Arial,sans-serif;
`;
document.body.appendChild(panel);
const hdr = document.createElement('div');
hdr.style.cssText = 'padding:14px 20px 10px;border-bottom:1px solid rgba(255,215,0,0.2);display:flex;align-items:center;justify-content:space-between;flex-shrink:0;background:rgba(0,0,0,0.8);backdrop-filter:blur(10px);';
hdr.innerHTML = `
<div>
<div style="font-size:22px;font-weight:900;letter-spacing:4px;color:#ffd700;text-shadow:0 0 30px #ffd700aa;">🗼 TOWER OF WORLDS</div>
<div style="font-size:10px;color:rgba(255,215,0,0.4);letter-spacing:2px;margin-top:3px;">${towUnlocked.size} / ${TOWER_WORLDS.length} WORLDS UNLOCKED</div>
</div>
<div style="display:flex;gap:8px;align-items:center;">
<div style="font-size:11px;color:rgba(255,255,255,0.3);">Scroll ↕ to explore</div>
<button id="ps_tow_close" style="background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);color:rgba(255,255,255,0.6);border-radius:8px;padding:7px 14px;cursor:pointer;font-size:13px;font-weight:700;">✕ Close</button>
</div>`;
panel.appendChild(hdr);
const layout = document.createElement('div');
layout.style.cssText = 'flex:1;display:flex;overflow:hidden;';
const leftBlack = document.createElement('div');
leftBlack.style.cssText = 'flex:1;background:#000;';
const rightBlack = document.createElement('div');
rightBlack.style.cssText = 'flex:1;background:#000;';
const col = document.createElement('div');
col.style.cssText = `
width:33.333%;
flex-shrink:0;
position:relative;
overflow:hidden;
background:#000;
border-left:1px solid rgba(255,215,0,0.08);
border-right:1px solid rgba(255,215,0,0.08);
`;
const inner = document.createElement('div');
inner.id = 'ps_tow_inner';
inner.style.cssText = 'position:absolute;left:0;right:0;top:0;padding:20px 18px 40px;display:flex;flex-direction:column;gap:'+TOW_CARD_GAP+'px;will-change:transform;';
const s = { minsPlayed: achMinsPlayed, level: typeof psExpState!=='undefined'?psExpState.level:1, totalExp: typeof psExpState!=='undefined'?psExpState.totalExp:0 };
const claimedCount = achClaimed.size;
TOWER_WORLDS.forEach((world, idx) => {
const unlocked = towUnlocked.has(world.id);
const card = document.createElement('div');
card.style.cssText = `
position:relative; border-radius:14px; overflow:hidden;
height:${TOW_CARD_H}px; flex-shrink:0;
border:1.5px solid ${unlocked ? world.colors[0]+'88' : 'rgba(255,255,255,0.08)'};
box-shadow:${unlocked ? `0 0 30px ${world.colors[0]}22, 0 4px 20px rgba(0,0,0,0.8)` : '0 2px 10px rgba(0,0,0,0.6)'};
cursor:${unlocked ? 'default' : (claimedCount >= world.requiredAch ? 'pointer' : 'default')};
transition:transform 0.2s, box-shadow 0.2s;
`;
if(unlocked) { card.onmouseenter=()=>{card.style.transform='scale(1.01)';card.style.boxShadow=`0 0 50px ${world.colors[0]}44, 0 8px 32px rgba(0,0,0,0.9)`;}; card.onmouseleave=()=>{card.style.transform='';card.style.boxShadow=`0 0 30px ${world.colors[0]}22, 0 4px 20px rgba(0,0,0,0.8)`;} }
const cv = document.createElement('canvas');
cv.width = 400; cv.height = TOW_CARD_H;
cv.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;';
card.appendChild(cv);
if(!unlocked){
const lockOv = document.createElement('div');
lockOv.style.cssText = 'position:absolute;inset:0;background:rgba(0,0,0,0.72);backdrop-filter:blur(3px);z-index:2;';
card.appendChild(lockOv);
}
const content = document.createElement('div');
content.style.cssText = `position:absolute;inset:0;z-index:3;padding:14px 16px;display:flex;flex-direction:column;justify-content:space-between;`;
const top = document.createElement('div');
top.style.cssText = 'display:flex;align-items:flex-start;justify-content:space-between;';
top.innerHTML = `
<div>
<div style="font-size:9px;letter-spacing:3px;font-weight:700;color:${unlocked?world.colors[0]:'rgba(255,255,255,0.25)'};margin-bottom:3px;text-transform:uppercase;">LEVEL ${world.level}</div>
<div style="font-size:18px;font-weight:900;color:${unlocked?'#fff':'rgba(255,255,255,0.3)'};text-shadow:${unlocked?`0 0 20px ${world.colors[0]}`:'none'};">${world.symbol} ${world.name}</div>
<div style="font-size:10px;color:${unlocked?'rgba(255,255,255,0.6)':'rgba(255,255,255,0.2)'};margin-top:4px;line-height:1.4;">${world.desc}</div>
</div>`;
const levelBadge = document.createElement('div');
levelBadge.style.cssText = `padding:4px 10px;border-radius:20px;font-size:10px;font-weight:700;border:1px solid ${unlocked?world.colors[0]+'88':'rgba(255,255,255,0.12)'};background:${unlocked?world.colors[0]+'22':'rgba(255,255,255,0.04)'};color:${unlocked?world.colors[0]:'rgba(255,255,255,0.25)'};white-space:nowrap;`;
levelBadge.textContent = unlocked ? '✓ Unlocked' : `Lv ${world.level}`;
top.appendChild(levelBadge);
content.appendChild(top);
const bot = document.createElement('div');
if(unlocked){
bot.style.cssText = 'display:flex;align-items:center;gap:8px;';
bot.innerHTML = `
<div style="font-size:10px;color:${world.colors[0]};font-weight:700;letter-spacing:1px;">✦ WORLD UNLOCKED</div>
${world.level===1?'<div style="font-size:9px;color:rgba(255,255,255,0.3);">Starting world — free forever</div>':''}`;
} else {
const canAfford = (typeof jcCoins!=='undefined'?jcCoins:0) >= world.requiredCoins;
const hasAch = claimedCount >= world.requiredAch;
bot.style.cssText = 'display:flex;flex-direction:column;gap:5px;';
bot.innerHTML = `
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<div style="font-size:10px;padding:3px 8px;border-radius:10px;border:1px solid ${hasAch?'rgba(100,220,80,0.5)':'rgba(255,80,80,0.4)'};background:${hasAch?'rgba(60,180,40,0.15)':'rgba(180,30,30,0.12)'};color:${hasAch?'rgba(120,240,100,0.9)':'rgba(255,100,100,0.8)'};">
${hasAch?'✓':'✗'} ${world.requiredAch} achievements claimed
</div>
<div style="font-size:10px;padding:3px 8px;border-radius:10px;border:1px solid ${canAfford?'rgba(255,200,50,0.5)':'rgba(255,80,80,0.4)'};background:${canAfford?'rgba(200,150,0,0.15)':'rgba(180,30,30,0.12)'};color:${canAfford?'#ffd700':'rgba(255,100,100,0.8)'};">
${canAfford?'✓':'✗'} 🪙 ${world.requiredCoins.toLocaleString()} JuCoins
</div>
</div>`;
if(hasAch && canAfford){
const unlockBtn = document.createElement('button');
unlockBtn.style.cssText = `padding:5px 14px;border:1px solid ${world.colors[0]}88;background:linear-gradient(90deg,${world.colors[0]}30,${world.colors[1]||world.colors[0]}30);color:${world.colors[0]};border-radius:8px;font-size:11px;font-weight:700;cursor:pointer;align-self:flex-start;transition:all 0.15s;`;
unlockBtn.textContent = `${world.symbol} Unlock World`;
unlockBtn.onmouseenter=()=>unlockBtn.style.background=`linear-gradient(90deg,${world.colors[0]}60,${world.colors[1]||world.colors[0]}60)`;
unlockBtn.onmouseleave=()=>unlockBtn.style.background=`linear-gradient(90deg,${world.colors[0]}30,${world.colors[1]||world.colors[0]}30)`;
unlockBtn.onclick = (e) => {
e.stopPropagation();
if((typeof jcCoins!=='undefined'?jcCoins:0) < world.requiredCoins) return;
jcCoins -= world.requiredCoins; jcSave();
towUnlocked.add(world.id); achSaveSet(TOW_UNLOCKED_KEY, towUnlocked);
achShowToast(world.symbol, 'World Unlocked!', world.name, `-${world.requiredCoins} JuCoins`, world.colors[0]);
achSpawnParticles(unlockBtn.getBoundingClientRect().left+40, unlockBtn.getBoundingClientRect().top, world.colors[0], 50);
const op=document.getElementById('ps_tow_panel');
if(op){op.remove();if(towRafId){cancelAnimationFrame(towRafId);towRafId=null;}}
achOpenTowerPanel(null);
};
bot.appendChild(unlockBtn);
} else {
const missing = [];
if(!hasAch) missing.push(`${world.requiredAch-claimedCount} more achievements`);
if(!canAfford) missing.push(`${(world.requiredCoins-((typeof jcCoins!=='undefined'?jcCoins:0))).toLocaleString()} more 🪙`);
const needEl = document.createElement('div');
needEl.style.cssText='font-size:9px;color:rgba(255,255,255,0.22);';
needEl.textContent = 'Still need: ' + missing.join(' & ');
bot.appendChild(needEl);
}
}
content.appendChild(bot);
card.appendChild(content);
inner.appendChild(card);
const ctx2 = cv.getContext('2d');
const cardT0 = Date.now();
(function cardLoop(){
if(!card.isConnected){return;}
requestAnimationFrame(cardLoop);
ctx2.clearRect(0,0,cv.width,cv.height);
const t=(Date.now()-cardT0)/1000;
world.drawBg(ctx2, cv.width, cv.height, t);
})();
});
col.appendChild(inner);
layout.appendChild(leftBlack);
layout.appendChild(col);
layout.appendChild(rightBlack);
panel.appendChild(layout);
const scrollTrack = document.createElement('div');
scrollTrack.style.cssText = 'position:absolute;right:0;top:0;bottom:0;width:6px;background:rgba(255,255,255,0.05);border-radius:3px;z-index:5;';
const scrollThumb = document.createElement('div');
scrollThumb.style.cssText = 'position:absolute;left:0;right:0;border-radius:3px;background:rgba(255,215,0,0.5);cursor:pointer;transition:background 0.2s;';
scrollTrack.appendChild(scrollThumb);
col.appendChild(scrollTrack);
const totalH = TOWER_WORLDS.length * (TOW_CARD_H + TOW_CARD_GAP) + 60;
let isDraggingScroll = false, dragStartY = 0, dragStartScroll = 0;
function updateScroll(newY){
const colH = col.getBoundingClientRect().height || window.innerHeight*0.85;
towScrollY = Math.max(0, Math.min(newY, Math.max(0, totalH - colH)));
inner.style.transform = `translateY(${-towScrollY}px)`;
const trackH = scrollTrack.getBoundingClientRect().height || colH;
const ratio = colH / totalH;
const thumbH = Math.max(30, trackH * ratio);
const thumbTop = towScrollY / (totalH - colH) * (trackH - thumbH);
scrollThumb.style.height = thumbH + 'px';
scrollThumb.style.top = thumbTop + 'px';
}
updateScroll(0);
scrollThumb.addEventListener('mousedown', e => {
isDraggingScroll = true; dragStartY = e.clientY; dragStartScroll = towScrollY;
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if(!isDraggingScroll) return;
const colH = col.getBoundingClientRect().height || window.innerHeight*0.85;
const trackH = scrollTrack.getBoundingClientRect().height || colH;
const ratio = totalH / trackH;
updateScroll(dragStartScroll + (e.clientY - dragStartY) * ratio);
});
document.addEventListener('mouseup', ()=>{ isDraggingScroll=false; });
col.addEventListener('wheel', e => {
e.preventDefault();
towTargetScrollY = Math.max(0, Math.min(towTargetScrollY + e.deltaY * 1.5, Math.max(0, totalH - (col.getBoundingClientRect().height||400))));
}, {passive:false});
function towScrollLoop(){
if(!document.getElementById('ps_tow_panel')){towRafId=null;return;}
towRafId=requestAnimationFrame(towScrollLoop);
const diff = towTargetScrollY - towScrollY;
if(Math.abs(diff) > 0.5) updateScroll(towScrollY + diff*0.12);
}
towRafId = requestAnimationFrame(towScrollLoop);
let isDragging = false, dragY = 0, lastScrollY = 0;
col.addEventListener('mousedown', e => {
if(e.target === scrollThumb || scrollTrack.contains(e.target)) return;
isDragging=true; dragY=e.clientY; lastScrollY=towScrollY; col.style.cursor='grabbing'; e.preventDefault();
});
document.addEventListener('mousemove', e => {
if(!isDragging) return;
towTargetScrollY = Math.max(0, lastScrollY - (e.clientY - dragY) * 1.5);
});
document.addEventListener('mouseup', ()=>{ isDragging=false; col.style.cursor=''; });
document.getElementById('ps_tow_close').addEventListener('click', ()=>{
panel.remove();
if(towRafId){cancelAnimationFrame(towRafId);towRafId=null;}
});
}
let achDragState = { active: false, startX: 0, startY: 0, scrollX: 0, scrollY: 0 };
let achAnimBg = null;
function achOpenPage(){
let overlay = document.getElementById('ps_ach_overlay');
if (overlay) { overlay.remove(); if(achAnimBg){cancelAnimationFrame(achAnimBg);achAnimBg=null;} return; }
overlay = document.createElement('div');
overlay.id = 'ps_ach_overlay';
overlay.style.cssText = 'position:fixed;inset:0;z-index:20000;background:#03030e;display:flex;flex-direction:column;font-family:"Segoe UI",Arial,sans-serif;overflow:hidden;';
document.body.appendChild(overlay);
achBuildPage();
}
function achBuildPage(){
const overlay = document.getElementById('ps_ach_overlay');
if (!overlay) return;
const s = { minsPlayed: achMinsPlayed, level: typeof psExpState!=='undefined'?psExpState.level:1, totalExp: typeof psExpState!=='undefined'?psExpState.totalExp:0 };
const byTier = Object.fromEntries(ACH_TIERS.map(t=>[t.id, ACH_LIST.filter(a=>a.tier===t.id)]));
const totalClaimed = achClaimed.size, total = ACH_LIST.length;
overlay.innerHTML = '';
const bgCv = document.createElement('canvas');
bgCv.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:0;';
bgCv.width = window.innerWidth; bgCv.height = window.innerHeight;
overlay.appendChild(bgCv);
const bCtx = bgCv.getContext('2d');
const bStars = Array.from({length:220},()=>({
x: Math.random()*bgCv.width, y: Math.random()*bgCv.height,
r: 0.3+Math.random()*1.8, a: 0.12+Math.random()*0.75,
ph: Math.random()*Math.PI*2, fr: 0.3+Math.random()*2.5,
vx: (Math.random()-0.5)*0.08, vy: (Math.random()-0.5)*0.04,
}));
const nebulae = [
{x:0.15,y:0.3,r:380,c:'rgba(80,20,160,0.12)',vx:0.00003,vy:0.00002},
{x:0.75,y:0.6,r:320,c:'rgba(20,60,160,0.10)',vx:-0.00002,vy:0.000015},
{x:0.5, y:0.85,c:'rgba(160,20,100,0.09)',r:280,vx:0.000015,vy:-0.00002},
{x:0.9, y:0.2,c:'rgba(20,140,120,0.08)',r:250,vx:-0.00003,vy:0.000025},
];
const shooters = Array.from({length:4},()=>({
t: Math.random(), speed: 0.0004+Math.random()*0.0006,
x0: Math.random(), y0: Math.random()*0.5,
angle: 0.4+Math.random()*0.5, len: 60+Math.random()*80,
}));
let bT = 0;
if(achAnimBg) cancelAnimationFrame(achAnimBg);
function bLoop(){
if(!document.getElementById('ps_ach_overlay')){ achAnimBg=null; return; }
achAnimBg = requestAnimationFrame(bLoop);
bCtx.clearRect(0,0,bgCv.width,bgCv.height);
bCtx.fillStyle='#02020c'; bCtx.fillRect(0,0,bgCv.width,bgCv.height);
nebulae.forEach(n=>{
n.x = ((n.x + n.vx + 1) % 1); n.y = ((n.y + n.vy + 1) % 1);
const ng = bCtx.createRadialGradient(n.x*bgCv.width,n.y*bgCv.height,0,n.x*bgCv.width,n.y*bgCv.height,n.r);
ng.addColorStop(0,n.c); ng.addColorStop(1,'transparent');
bCtx.fillStyle=ng; bCtx.fillRect(0,0,bgCv.width,bgCv.height);
});
ACH_TIERS.forEach((tier,ti)=>{
const angle = bT*0.3+ti*(Math.PI*2/ACH_TIERS.length);
const px = bgCv.width*(0.5+0.45*Math.cos(angle));
const py = bgCv.height*(0.5+0.35*Math.sin(angle));
const pg = bCtx.createRadialGradient(px,py,0,px,py,120);
const alpha = 0.025+0.015*Math.sin(bT*1.5+ti);
const hex = tier.color;
const ri=parseInt(hex.slice(1,3),16),gi=parseInt(hex.slice(3,5),16),bi=parseInt(hex.slice(5,7),16);
pg.addColorStop(0,`rgba(${ri},${gi},${bi},${alpha})`); pg.addColorStop(1,'transparent');
bCtx.fillStyle=pg; bCtx.fillRect(0,0,bgCv.width,bgCv.height);
});
bStars.forEach(s=>{
s.x = (s.x + s.vx + bgCv.width) % bgCv.width;
s.y = (s.y + s.vy + bgCv.height) % bgCv.height;
const tw = 0.5+0.5*Math.sin(bT*s.fr+s.ph);
bCtx.globalAlpha=s.a*tw; bCtx.fillStyle='#fff';
bCtx.beginPath(); bCtx.arc(s.x,s.y,s.r,0,Math.PI*2); bCtx.fill();
});
shooters.forEach(sh=>{
sh.t = (sh.t + sh.speed) % 1;
if(sh.t > 0.85) return;
const sx = sh.x0*bgCv.width + sh.t*bgCv.width*0.8;
const sy = sh.y0*bgCv.height + sh.t*bgCv.height*0.4;
const sa = Math.min(sh.t*8, (0.85-sh.t)*8, 1)*0.7;
bCtx.globalAlpha=sa;
bCtx.strokeStyle='rgba(200,220,255,1)'; bCtx.lineWidth=1.5;
bCtx.beginPath();
bCtx.moveTo(sx,sy);
bCtx.lineTo(sx-Math.cos(sh.angle)*sh.t*sh.len*1.5, sy-Math.sin(sh.angle)*sh.t*sh.len*0.8);
bCtx.stroke();
});
bCtx.globalAlpha=1; bT+=0.012;
}
bLoop();
const hdr = document.createElement('div');
hdr.style.cssText = 'position:relative;z-index:10;padding:16px 22px 11px;background:rgba(0,0,0,0.55);border-bottom:1px solid rgba(255,255,255,0.07);backdrop-filter:blur(14px);flex-shrink:0;';
hdr.innerHTML = `
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;">
<div>
<div style="font-size:26px;font-weight:900;letter-spacing:6px;text-transform:uppercase;background:linear-gradient(95deg,#fff 0%,#aab4ff 22%,#ee88cc 44%,#ffcc55 66%,#88ffee 88%);background-size:300%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:achGrad 5s linear infinite;">Achievements</div>
<div style="color:rgba(255,255,255,.28);font-size:10px;letter-spacing:3px;margin-top:3px;">${totalClaimed} / ${total} CLAIMED · ${typeof jcCoins!=='undefined'?jcCoins:0} 🪙 · ${achMinsPlayed} min played</div>
</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<div style="display:flex;gap:5px;flex-wrap:wrap;" id="ps_ach_legend"></div>
<button id="ps_ach_tower_btn" style="background:linear-gradient(135deg,rgba(255,215,0,0.22),rgba(255,180,0,0.12));border:1px solid rgba(255,215,0,0.55);color:#ffd700;border-radius:10px;padding:8px 16px;cursor:pointer;font-size:13px;font-weight:800;letter-spacing:1px;box-shadow:0 0 20px rgba(255,215,0,0.2);transition:all 0.18s;">🗼 Tower of Worlds</button>
<button onclick="document.getElementById('ps_ach_overlay').remove();if(window.achAnimBg){cancelAnimationFrame(window.achAnimBg);window.achAnimBg=null;}" style="background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.14);color:rgba(255,255,255,.55);border-radius:8px;padding:7px 14px;cursor:pointer;font-size:13px;font-weight:700;">✕ Close</button>
</div>
</div>
<div style="margin-top:9px;height:3px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden;">
<div style="height:100%;width:${total?Math.round((totalClaimed/total)*100):0}%;background:linear-gradient(90deg,#4d8eff,#bb44ff,#ffcc00);border-radius:2px;transition:width 1s ease;"></div>
</div>
<div style="margin-top:8px;display:flex;gap:8px;font-size:10px;color:rgba(255,255,255,0.22);align-items:center;">
<span>↔ Drag to scroll horizontally</span><span>·</span><span>↕ Drag inside columns to scroll vertically</span>
</div>`;
overlay.appendChild(hdr);
const leg = document.getElementById('ps_ach_legend');
ACH_TIERS.forEach(tier=>{
const achs=byTier[tier.id]||[];
const clm=achs.filter(a=>achClaimed.has(a.id)).length;
leg.innerHTML+=`<div style="display:flex;align-items:center;gap:5px;padding:3px 9px;border-radius:20px;border:1px solid ${tier.color}44;background:${tier.color}12;"><div style="width:6px;height:6px;border-radius:50%;background:${tier.color};box-shadow:0 0 7px ${tier.color};"></div><span style="color:${tier.color};font-size:10px;font-weight:700;">${tier.name}</span>${achs.length?`<span style="color:rgba(255,255,255,.28);font-size:9px;">${clm}/${achs.length}</span>`:''}</div>`;
});
document.getElementById('ps_ach_tower_btn').addEventListener('click', ()=>achOpenTowerPanel(overlay));
const scrollArea = document.createElement('div');
scrollArea.id = 'ps_ach_scroll_area';
scrollArea.style.cssText = 'flex:1;overflow:hidden;position:relative;z-index:10;cursor:grab;';
const colsWrap = document.createElement('div');
colsWrap.id = 'ps_ach_cols_wrap';
colsWrap.style.cssText = 'display:flex;gap:14px;padding:16px 18px 20px;align-items:flex-start;position:absolute;top:0;left:0;min-height:100%;';
scrollArea.appendChild(colsWrap);
overlay.appendChild(scrollArea);
const hScrollTrack = document.createElement('div');
hScrollTrack.style.cssText = 'position:relative;z-index:11;height:8px;background:rgba(255,255,255,0.04);margin:0 18px 10px;border-radius:4px;flex-shrink:0;';
const hScrollThumb = document.createElement('div');
hScrollThumb.style.cssText = 'position:absolute;top:0;bottom:0;border-radius:4px;background:rgba(255,255,255,0.2);cursor:pointer;min-width:30px;';
hScrollTrack.appendChild(hScrollThumb);
overlay.appendChild(hScrollTrack);
ACH_TIERS.forEach(tier=>{
const achs=byTier[tier.id]||[];
const clm=achs.filter(a=>achClaimed.has(a.id)).length;
const col=document.createElement('div');
col.style.cssText=`width:252px;flex-shrink:0;border-radius:15px;overflow:hidden;border:1px solid ${tier.color}28;background:linear-gradient(180deg,${tier.color}10,${tier.color}04 60%,transparent);box-shadow:0 0 36px ${tier.color}14,0 8px 28px rgba(0,0,0,.55);display:flex;flex-direction:column;`;
col.innerHTML=`<div style="padding:13px 15px 10px;background:linear-gradient(135deg,${tier.color}20,${tier.color}08);border-bottom:1px solid ${tier.color}22;position:relative;overflow:hidden;flex-shrink:0;">
<div style="position:absolute;top:0;left:-60%;width:40%;height:100%;background:linear-gradient(90deg,transparent,${tier.color}18,transparent);animation:achHdrShimmer 3.5s ease-in-out infinite;pointer-events:none;"></div>
<div style="position:relative;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:3px;">
<span style="font-size:14px;font-weight:900;letter-spacing:3px;text-transform:uppercase;color:${tier.color};text-shadow:0 0 22px ${tier.color},0 0 44px ${tier.color}55;">${tier.name}</span>
<div style="font-size:9px;color:rgba(255,255,255,.32);padding:2px 7px;border-radius:10px;border:1px solid rgba(255,255,255,.1);background:rgba(0,0,0,.3);">${clm}/${achs.length}</div>
</div>
<div style="font-size:9px;color:${tier.color};opacity:.6;">🪙 ${ACH_TIERS.find(t=>t.id===tier.id).coins} JuCoins per achievement</div>
</div>
</div>`;
const list=document.createElement('div');
list.style.cssText='padding:9px;display:flex;flex-direction:column;gap:7px;overflow-y:auto;max-height:calc(100vh - 220px);scrollbar-width:thin;scrollbar-color:'+tier.color+'44 transparent;';
if(!achs.length){
list.innerHTML=`<div style="padding:38px 0;text-align:center;color:rgba(255,255,255,.12);font-size:11px;letter-spacing:1px;"><div style="font-size:18px;margin-bottom:7px;opacity:.2;">🔒</div>COMING SOON</div>`;
} else {
achs.forEach(ach=>{
const isDone=achDone.has(ach.id),isClaimed=achClaimed.has(ach.id);
const prog=ach.progress(s);
const pct=Math.min(100,Math.round((prog.v/prog.m)*100));
const card=document.createElement('div');
card.setAttribute('data-achcard',ach.id);
card.style.cssText=`border-radius:11px;padding:11px;position:relative;overflow:hidden;background:${isClaimed?`linear-gradient(135deg,${tier.color}14,rgba(0,0,0,.22))`:isDone?`linear-gradient(135deg,${tier.color}22,rgba(255,255,255,.04))`:'rgba(255,255,255,.03)'};border:1px solid ${isClaimed?`${tier.color}35`:isDone?`${tier.color}65`:'rgba(255,255,255,.07)'};box-shadow:${isDone&&!isClaimed?`0 0 22px ${tier.color}30,inset 0 0 16px ${tier.color}0e`:'none'};`;
if(isDone&&!isClaimed){ const sh=document.createElement('div'); sh.style.cssText=`position:absolute;top:0;left:-70%;width:42%;height:100%;background:linear-gradient(90deg,transparent,${tier.color}24,transparent);animation:achCardShimmer 2.5s ease-in-out infinite;pointer-events:none;`; card.appendChild(sh); }
const inner=document.createElement('div');
inner.style.cssText='display:flex;gap:9px;align-items:flex-start;position:relative;';
inner.innerHTML=`<div style="font-size:24px;flex-shrink:0;line-height:1;filter:${!isDone?'grayscale(1) brightness(.3)':'drop-shadow(0 0 8px currentColor)'};">${ach.icon}</div><div style="flex:1;min-width:0;"><div style="font-size:11px;font-weight:700;line-height:1.3;margin-bottom:3px;color:${isClaimed?'rgba(255,255,255,.38)':isDone?'#fff':'rgba(255,255,255,.35)'};">${ach.name}</div><div style="font-size:10px;color:rgba(255,255,255,.24);line-height:1.45;margin-bottom:${isDone&&!isClaimed?7:5}px;">${ach.desc}</div>${!isClaimed?`<div style="margin-bottom:7px;"><div style="height:4px;border-radius:4px;background:rgba(255,255,255,.08);overflow:hidden;"><div style="height:100%;border-radius:4px;width:${pct}%;background:linear-gradient(90deg,${tier.color}75,${tier.color});box-shadow:0 0 10px ${tier.color};"></div></div><div style="display:flex;justify-content:space-between;margin-top:3px;font-size:9px;color:rgba(255,255,255,.22);"><span>${prog.v}/${prog.m} ${prog.unit}</span><span>${pct}%</span></div></div>`:''}<div class="ps_ach_action" data-achid="${ach.id}"></div></div>`;
card.appendChild(inner);
list.appendChild(card);
});
}
col.appendChild(list);
colsWrap.appendChild(col);
});
let isDragging=false, dragStartX=0, dragStartScrollLeft=0;
scrollArea.addEventListener('mousedown', e=>{
if(e.target.tagName==='BUTTON'||e.target.closest('[data-achcard]')||e.target.closest('.ps_ach_action')) return;
isDragging=true; dragStartX=e.clientX; dragStartScrollLeft=scrollArea.scrollLeft;
scrollArea.style.cursor='grabbing'; e.preventDefault();
});
document.addEventListener('mousemove', e=>{
if(!isDragging) return;
scrollArea.scrollLeft = dragStartScrollLeft - (e.clientX - dragStartX);
updateHScroll();
});
document.addEventListener('mouseup', ()=>{ isDragging=false; scrollArea.style.cursor='grab'; });
scrollArea.addEventListener('wheel', e=>{ e.preventDefault(); scrollArea.scrollLeft+=e.deltaX||e.deltaY; updateHScroll(); },{passive:false});
let hDragging=false, hDragStartX=0, hDragStartScroll=0;
function updateHScroll(){
const maxScroll = scrollArea.scrollWidth - scrollArea.clientWidth;
const ratio = scrollArea.clientWidth / scrollArea.scrollWidth;
const thumbW = Math.max(30, hScrollTrack.clientWidth * ratio);
const thumbLeft = maxScroll>0 ? (scrollArea.scrollLeft/maxScroll)*(hScrollTrack.clientWidth-thumbW) : 0;
hScrollThumb.style.width = thumbW+'px';
hScrollThumb.style.left = thumbLeft+'px';
}
hScrollThumb.addEventListener('mousedown', e=>{ hDragging=true; hDragStartX=e.clientX; hDragStartScroll=scrollArea.scrollLeft; e.preventDefault(); });
document.addEventListener('mousemove', e=>{
if(!hDragging) return;
const maxScroll = scrollArea.scrollWidth - scrollArea.clientWidth;
const ratio = scrollArea.scrollWidth / hScrollTrack.clientWidth;
scrollArea.scrollLeft = hDragStartScroll + (e.clientX - hDragStartX)*ratio;
updateHScroll();
});
document.addEventListener('mouseup', ()=>hDragging=false);
setTimeout(updateHScroll, 100);
overlay.querySelectorAll('.ps_ach_action').forEach(el=>{
const achId=el.dataset.achid;
const ach=ACH_LIST.find(a=>a.id===achId);
if(!ach) return;
const tier=ACH_TIERS.find(t=>t.id===ach.tier);
const isDone=achDone.has(achId),isClaimed=achClaimed.has(achId);
if(isClaimed){
el.innerHTML=`<div style="font-size:10px;font-weight:700;color:${tier.color};opacity:.65;display:flex;align-items:center;gap:4px;"><span>✓</span><span>Claimed · +${tier.coins} 🪙</span></div>`;
} else if(isDone){
const btn=document.createElement('button');
btn.style.cssText=`width:100%;padding:7px 0;border-radius:8px;border:1px solid ${tier.color}80;background:linear-gradient(90deg,${tier.color}28,${tier.color}55,${tier.color}28);background-size:200%;animation:achBtnPulse 2.2s ease infinite;color:${tier.color};font-size:11px;font-weight:900;cursor:pointer;letter-spacing:1px;box-shadow:0 0 18px ${tier.color}38;text-shadow:0 0 10px ${tier.color};`;
btn.textContent=`🪙 CLAIM +${tier.coins}`;
btn.onmouseenter=()=>btn.style.transform='scale(1.03)';
btn.onmouseleave=()=>btn.style.transform='';
btn.onclick=()=>achClaim(achId, btn);
el.appendChild(btn);
} else {
el.innerHTML=`<div style="font-size:9px;color:rgba(255,255,255,.16);letter-spacing:1.5px;text-transform:uppercase;">🔒 Locked</div>`;
}
});
}
const achStyle = document.createElement('style');
achStyle.textContent = `
@keyframes achGrad{0%{background-position:0% 50%}100%{background-position:300% 50%}}
@keyframes achHdrShimmer{0%{left:-60%}100%{left:160%}}
@keyframes achCardShimmer{0%,100%{opacity:.6;left:-70%}50%{opacity:1;left:0}100%{opacity:.6;left:140%}}
@keyframes achFlashOut{0%{opacity:.13}100%{opacity:0}}
@keyframes achToastIn{from{opacity:0;transform:translateX(18px)}to{opacity:1;transform:translateX(0)}}
@keyframes achToastOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(18px)}}
@keyframes achBtnPulse{0%,100%{background-position:0% 50%}50%{background-position:200% 50%}}
@keyframes achFloatXP{0%{opacity:1;transform:translateY(0) scale(1.2)}100%{opacity:0;transform:translateY(-58px) scale(.82)}}
`;
document.head.appendChild(achStyle);
const achBtn = document.createElement('button');
achBtn.id = 'ps_ach_btn';
achBtn.title = 'Achievements';
achBtn.innerHTML = `<svg viewBox="0 0 28 28" width="18" height="18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2C9.58 2 6 5.58 6 10c0 3.54 2.15 6.57 5.24 7.88L10 22h8l-1.24-4.12C19.85 16.57 22 13.54 22 10c0-4.42-3.58-8-8-8z" fill="rgba(255,200,50,0.9)" stroke="rgba(255,180,20,0.6)" stroke-width="0.8"/>
<rect x="10" y="22" width="8" height="2.5" rx="1.2" fill="rgba(255,200,50,0.8)"/>
<rect x="9" y="24.5" width="10" height="2" rx="1" fill="rgba(255,180,20,0.7)"/>
<path d="M6 8H3a1 1 0 000 2c0 2.5 1.5 4.5 3.5 5.2" stroke="rgba(255,200,50,0.6)" stroke-width="1.5" stroke-linecap="round" fill="none"/>
<path d="M22 8h3a1 1 0 010 2c0 2.5-1.5 4.5-3.5 5.2" stroke="rgba(255,200,50,0.6)" stroke-width="1.5" stroke-linecap="round" fill="none"/>
<circle cx="14" cy="10" r="3" fill="rgba(255,240,150,0.7)"/>
</svg>`;
achBtn.style.cssText = `
position:fixed;
bottom:20px;
right:170px;
z-index:10000;
color:white;
border:1px solid rgba(255,200,50,0.45);
border-radius:12px;
padding:8px 11px;
cursor:pointer;
display:flex;
align-items:center;
justify-content:center;
background:rgba(255,180,0,0.18);
backdrop-filter:blur(6px);
box-shadow:0 2px 14px rgba(255,200,50,0.15);
transition:background 0.2s,transform 0.15s,border-color 0.2s;
`;
achBtn.onmouseenter = ()=>{ achBtn.style.background='rgba(255,200,50,0.32)'; achBtn.style.borderColor='rgba(255,200,50,0.8)'; achBtn.style.transform='scale(1.08)'; };
achBtn.onmouseleave = ()=>{ achBtn.style.background='rgba(255,180,0,0.18)'; achBtn.style.borderColor='rgba(255,200,50,0.45)'; achBtn.style.transform=''; };
achBtn.onclick = achOpenPage;
document.body.appendChild(achBtn);
setInterval(achTick, 60000);
const GCHT_NTFY = 'https://ntfy.sh';
const GCHT_GLOBAL = 'parasidieum-gc-2026';
const GCHT_RATE_MS = 2500;
const GCHT_POLL_MS = 5000;
const GCHT_MAX_LEN = 200;
const GCHT_MAX_MSGS= 60;
const GCHT = {
open:false, tab:'global', active:'global',
convs:{}, seen:new Set(), lastSent:0,
polls:{}, unread:0,
rec:{ on:false, mr:null, chunks:[], timer:null, secs:0 },
};
GCHT.convs.global = {
id:'global', type:'global', name:'🌐 Global',
topic:GCHT_GLOBAL, msgs:[], unread:0,
since:Math.floor(Date.now()/1000)-600, members:[],
};
(()=>{ try {
const s=JSON.parse(localStorage.getItem('ps_gcht_v2')||'{}');
Object.values(s).forEach(c=>{
GCHT.convs[c.id]={...c,msgs:[],unread:0,since:Math.floor(Date.now()/1000)-600};
});
} catch(e){} })();
function gchtSave(){
const o={};
Object.values(GCHT.convs).forEach(c=>{
if(c.type!=='global'&&c.type!=='_inbox')
o[c.id]={id:c.id,type:c.type,name:c.name,topic:c.topic,members:c.members||[]};
});
try{localStorage.setItem('ps_gcht_v2',JSON.stringify(o));}catch(e){}
}
const gchtMe = ()=>localStorage.getItem('ps_discord_username')||currentUsername||'Player';
const gchtLv = ()=>{try{return psExpState.level;}catch(e){return 1;}};
const gchtSafe= s=>String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
const gchtTime= ts=>new Date(ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});
const gchtDur = s=>{const m=Math.floor(s/60),r=s%60;return`${m}:${String(r).padStart(2,'0')}`;};
function gchtAccent(theme){
const m={miku:'#ff82c8',hk:'#3c78dc',space:'#a050dc',gojo:'#cc44ff',doom:'#ff5522',teto:'#ff6666',kfp:'#66cc44'};
if(theme==='custom')try{return customThemeData.primaryColor||'#aaa';}catch(e){}
return m[theme]||'#8899bb';
}
function gchtDmId(a,b){return 'dm-'+[a,b].map(x=>x.toLowerCase().replace(/[^a-z0-9]/g,'_')).sort().join('-');}
function gchtDmTopic(a,b){return 'psd26dm-'+[a,b].map(x=>x.toLowerCase().replace(/[^a-z0-9]/g,'_')).sort().join('-');}
function gchtInbox(u){return 'psd26inbox-'+u.toLowerCase().replace(/[^a-z0-9]/g,'_');}
function gchtGrpId(){return 'grp'+Math.random().toString(36).slice(2,8);}
function gchtGrpTopic(id){return 'psd26grp-'+id;}
async function gchtPost(topic,payload){
try{await fetch(`${GCHT_NTFY}/${topic}`,{method:'POST',body:JSON.stringify(payload)});}catch(e){}
}
async function gchtPollTopic(topic){
const convs=Object.values(GCHT.convs).filter(c=>c.topic===topic);
if(!convs.length)return;
const since=Math.min(...convs.map(c=>c.since||0));
try{
const res=await fetch(`${GCHT_NTFY}/${topic}/json?poll=1&since=${since}`,{cache:'no-store'});
if(!res.ok)return;
const lines=(await res.text()).trim().split('\n').filter(Boolean);
lines.forEach(line=>{
try{
const n=JSON.parse(line);
if(!n?.id||GCHT.seen.has(n.id))return;
GCHT.seen.add(n.id);
const ts=n.time||Math.floor(Date.now()/1000);
convs.forEach(c=>{if(ts>=c.since)c.since=ts+1;});
let payload;try{payload=JSON.parse(n.message);}catch(e){return;}
if(!payload?.type)return;
gchtRoute(convs[0],payload,n.id);
}catch(e){}
});
}catch(e){}
}
function gchtRoute(conv,msg,ntfyId){
const isMe=msg.from===gchtMe();
if(conv.type==='_inbox'){
if(msg.type==='dm_invite'&&!isMe) {gchtHandleDmInvite(msg);return;}
if(msg.type==='group_invite'&&!isMe){gchtHandleGroupInvite(msg);return;}
return;
}
if(msg.type==='ss_join'&&!isMe){gchtHandleSsJoin(msg);return;}
if(!['text','voice','ss_invite'].includes(msg.type))return;
const full={...msg,_id:ntfyId,_conv:conv.id};
conv.msgs.push(full);
if(conv.msgs.length>GCHT_MAX_MSGS+10)conv.msgs.splice(0,10);
if(GCHT.active===conv.id&&GCHT.open){
gchtRenderMsg(full);
} else if(!isMe){
conv.unread++;GCHT.unread++;
gchtBadgeUpdate();gchtSidebarRender();
}
}
function gchtStartPoll(topic){
if(GCHT.polls[topic])return;
GCHT.polls[topic]=setInterval(()=>gchtPollTopic(topic),GCHT_POLL_MS);
gchtPollTopic(topic);
}
function gchtInitPolls(){
Object.values(GCHT.convs).forEach(c=>{if(c.topic)gchtStartPoll(c.topic);});
const inboxTopic=gchtInbox(gchtMe());
if(!GCHT.convs.__inbox){
GCHT.convs.__inbox={id:'__inbox',type:'_inbox',name:'Inbox',topic:inboxTopic,
msgs:[],unread:0,since:Math.floor(Date.now()/1000)-60,members:[]};
}
gchtStartPoll(inboxTopic);
}
window.gchtStartDm=async function(target){
document.getElementById('gcht_modal_overlay')?.remove();
if(!target||target===gchtMe()){gchtStatus('Enter a valid username',false);return;}
const id=gchtDmId(gchtMe(),target),topic=gchtDmTopic(gchtMe(),target);
if(!GCHT.convs[id]){
GCHT.convs[id]={id,type:'dm',name:`💬 ${target}`,topic,msgs:[],unread:0,
since:Math.floor(Date.now()/1000)-60,members:[gchtMe(),target]};
gchtSave();gchtStartPoll(topic);
await gchtPost(gchtInbox(target),{type:'dm_invite',from:gchtMe(),topic,convId:id});
}
GCHT.tab='dms';GCHT.active=id;
window.gchtTabSwitch('dms');
};
window.gchtCreateGroup=async function(name,members){
document.getElementById('gcht_modal_overlay')?.remove();
if(!name){gchtStatus('Enter a group name',false);return;}
const id=gchtGrpId(),topic=gchtGrpTopic(id);
const allM=[...new Set([gchtMe(),...members])];
GCHT.convs[id]={id,type:'group',name:`👥 ${name}`,topic,msgs:[],unread:0,
since:Math.floor(Date.now()/1000)-60,members:allM};
gchtSave();gchtStartPoll(topic);
for(const m of members.filter(m=>m!==gchtMe())){
await gchtPost(gchtInbox(m),{type:'group_invite',from:gchtMe(),groupName:name,groupId:id,topic,members:allM});
}
GCHT.tab='groups';GCHT.active=id;
window.gchtTabSwitch('groups');
};
function gchtHandleDmInvite(msg){
const id=gchtDmId(gchtMe(),msg.from),topic=msg.topic||gchtDmTopic(gchtMe(),msg.from);
if(GCHT.convs[id])return;
gchtSystemToast(`💬 ${msg.from} sent you a DM`,()=>{
GCHT.convs[id]={id,type:'dm',name:`💬 ${msg.from}`,topic,msgs:[],unread:0,
since:Math.floor(Date.now()/1000)-60,members:[gchtMe(),msg.from]};
gchtSave();gchtStartPoll(topic);
GCHT.tab='dms';GCHT.active=id;GCHT.open=true;
document.getElementById('gcht_box').style.display='flex';
window.gchtTabSwitch('dms');
});
}
function gchtHandleGroupInvite(msg){
const id=msg.groupId,topic=msg.topic||gchtGrpTopic(id);
if(GCHT.convs[id])return;
gchtSystemToast(`👥 ${msg.from} invited you to "${msg.groupName}"`,()=>{
GCHT.convs[id]={id,type:'group',name:`👥 ${msg.groupName}`,topic,msgs:[],unread:0,
since:Math.floor(Date.now()/1000)-60,members:msg.members||[]};
gchtSave();gchtStartPoll(topic);
GCHT.tab='groups';GCHT.active=id;GCHT.open=true;
document.getElementById('gcht_box').style.display='flex';
window.gchtTabSwitch('groups');
});
}
async function gchtSend(){
const inp=document.getElementById('gcht_input');
if(!inp)return;
const text=inp.value.trim();
if(!text||text.length>GCHT_MAX_LEN)return;
const now=Date.now();
if(now-GCHT.lastSent<GCHT_RATE_MS){
gchtStatus(`Wait ${Math.ceil((GCHT_RATE_MS-(now-GCHT.lastSent))/1000)}s`,false);return;
}
inp.value='';gchtCharUpdate();GCHT.lastSent=now;
const conv=GCHT.convs[GCHT.active];if(!conv)return;
gchtStatus('Sending…',true);
await gchtPost(conv.topic,{type:'text',from:gchtMe(),text,level:gchtLv(),theme:currentTheme,ts:now});
gchtStatus('Sent ✓',true);
setTimeout(()=>gchtPollTopic(conv.topic),400);
}
async function gchtToggleRec(){
if(GCHT.rec.on){gchtStopRec();return;}
try{
const stream=await navigator.mediaDevices.getUserMedia({audio:true,video:false});
GCHT.rec={...GCHT.rec,on:true,chunks:[],secs:0};
const mr=new MediaRecorder(stream,{mimeType:'audio/webm;codecs=opus'});
GCHT.rec.mr=mr;
mr.ondataavailable=e=>{if(e.data.size>0)GCHT.rec.chunks.push(e.data);};
mr.onstop=async()=>{
stream.getTracks().forEach(t=>t.stop());
const blob=new Blob(GCHT.rec.chunks,{type:'audio/webm'});
GCHT.rec.on=false;gchtMicBtnUpdate();
await gchtUploadVoice(blob,GCHT.rec.secs);
};
mr.start(200);
GCHT.rec.timer=setInterval(()=>{
GCHT.rec.secs++;
if(GCHT.rec.secs>=120)gchtStopRec();
gchtMicBtnUpdate();
},1000);
gchtMicBtnUpdate();
gchtStatus('Recording… tap 🎤 to stop',true);
}catch(e){
gchtStatus('Microphone access denied',false);
GCHT.rec.on=false;gchtMicBtnUpdate();
}
}
function gchtStopRec(){
clearInterval(GCHT.rec.timer);
if(GCHT.rec.mr?.state!=='inactive')GCHT.rec.mr?.stop();
gchtStatus('Processing voice…',true);
}
async function gchtUploadVoice(blob,dur){
gchtStatus('Uploading voice…',true);
try{
const form=new FormData();
form.append('file',blob,'voice.webm');
const res=await fetch('https://0x0.st',{method:'POST',body:form});
if(!res.ok)throw new Error();
const url=(await res.text()).trim();
const conv=GCHT.convs[GCHT.active];if(!conv)return;
await gchtPost(conv.topic,{type:'voice',from:gchtMe(),url,duration:dur,level:gchtLv(),theme:currentTheme,ts:Date.now()});
gchtStatus('Voice sent ✓',true);
setTimeout(()=>gchtPollTopic(conv.topic),400);
}catch(e){gchtStatus('Voice upload failed — try again',false);}
}
function gchtMicBtnUpdate(){
const btn=document.getElementById('gcht_mic_btn');
if(!btn)return;
if(GCHT.rec.on){
btn.innerHTML=`⏹ ${gchtDur(GCHT.rec.secs)}`;
btn.style.cssText='background:rgba(255,50,50,0.75);color:#fff;border:1px solid rgba(255,100,100,0.5);border-radius:8px;padding:5px 10px;cursor:pointer;font-size:11px;font-weight:bold;white-space:nowrap;animation:gcht_rec_pulse 1s ease-in-out infinite;flex-shrink:0;';
}else{
btn.innerHTML='🎤';
btn.style.cssText='background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.6);border:1px solid rgba(255,255,255,0.12);border-radius:8px;padding:5px 10px;cursor:pointer;font-size:14px;flex-shrink:0;transition:background 0.14s;';
}
}
async function gchtShareScreen(){
const conv=GCHT.convs[GCHT.active];if(!conv)return;
ssInitPeer(async()=>{
try{
const stream=await navigator.mediaDevices.getDisplayMedia({video:{frameRate:30},audio:true});
ssState.localStream=stream;ssState.isSharing=true;
await gchtPost(conv.topic,{type:'ss_invite',from:gchtMe(),peerId:ssState.peerId,level:gchtLv(),theme:currentTheme,ts:Date.now()});
gchtStatus('Screen shared — others can click Watch',true);
stream.getVideoTracks()[0].addEventListener('ended',()=>{
ssState.isSharing=false;ssState.localStream=null;
gchtStatus('Screen share ended',false);
});
}catch(e){
gchtStatus(e.name==='NotAllowedError'?'Cancelled':'Error: '+e.message,false);
}
});
}
window.gchtJoinScreen=async function(peerId,sharerName){
const conv=GCHT.convs[GCHT.active];if(!conv)return;
ssInitPeer(async()=>{
ssState._autoAcceptFrom=peerId;
await gchtPost(conv.topic,{type:'ss_join',from:gchtMe(),myPeerId:ssState.peerId,forPeerId:peerId,ts:Date.now()});
gchtStatus(`Waiting for ${sharerName} to connect…`,true);
});
};
function gchtHandleSsJoin(msg){
if(!ssState.isSharing||!ssState.localStream)return;
if(msg.forPeerId!==ssState.peerId)return;
try{
const call=ssState.peer.call(msg.myPeerId,ssState.localStream);
call.on('error',()=>{});
gchtStatus(`${msg.from} is now watching`,true);
}catch(e){}
}
const _origSsHandle=window.ssHandleIncoming||null;
(function patchSsAutoAccept(){
const checkInterval=setInterval(()=>{
if(!ssState.peer)return;
clearInterval(checkInterval);
ssState.peer.on('call',call=>{
if(ssState._autoAcceptFrom&&call.peer===ssState._autoAcceptFrom){
ssState._autoAcceptFrom=null;
document.getElementById('ps_ss_incoming') && (document.getElementById('ps_ss_incoming').style.display='none');
call.answer();
call.on('stream',remote=>ssOpenViewer(remote,call.peer));
call.on('close',()=>ssCloseViewer());
call.on('error',()=>{ssCloseViewer();gchtStatus('Stream lost',false);});
}
});
},500);
})();
function gchtRenderMsg(msg){
const container=document.getElementById('gcht_msgs');
if(!container)return;
document.getElementById('gcht_empty')?.remove();
if(msg._id&&document.getElementById('gchtm_'+msg._id))return;
const isMe=msg.from===gchtMe();
const accent=gchtAccent(msg.theme||'custom');
const wasBot=gchtIsAtBot();
const el=document.createElement('div');
el.className='gcht_msg'+(isMe?' gcht_mine':'');
if(msg._id)el.id='gchtm_'+msg._id;
let body='';
if(msg.type==='text'){
body=`<div class="gcht_body">${gchtSafe(msg.text)}</div>`;
}else if(msg.type==='voice'){
const bars=Array.from({length:18},()=>`<div class="gcht_bar" style="height:${3+Math.random()*14}px"></div>`).join('');
body=`<div class="gcht_voice_row">
<button class="gcht_play_btn" data-url="${gchtSafe(msg.url)}" onclick="gchtPlayVoice(this)">▶</button>
<div class="gcht_wave">${bars}</div>
<span class="gcht_vdur">${gchtDur(msg.duration||0)}</span>
</div>`;
}else if(msg.type==='ss_invite'){
body=isMe
?`<div class="gcht_ss_card">📺 You're sharing your screen — others can click Watch to join.</div>`
:`<div class="gcht_ss_card">📺 <strong>${gchtSafe(msg.from)}</strong> is sharing their screen <button class="gcht_watch_btn" onclick="gchtJoinScreen('${gchtSafe(msg.peerId)}','${gchtSafe(msg.from)}')">▶ Watch</button></div>`;
}
el.innerHTML=`
<div class="gcht_head">
<div class="gcht_dot" style="background:${accent};box-shadow:0 0 5px ${accent}88"></div>
<span class="gcht_name" style="color:${accent}">${gchtSafe(msg.from||'?')}</span>
<span class="gcht_lv">Lv ${msg.level||1}</span>
<span class="gcht_ts">${gchtTime(msg.ts||Date.now())}</span>
</div>${body}`;
container.appendChild(el);
const all=container.querySelectorAll('.gcht_msg');
if(all.length>GCHT_MAX_MSGS+10)for(let i=0;i<10;i++)all[i]?.remove();
if(wasBot)gchtScrollBot();
}
window.gchtPlayVoice=function(btn){
const url=btn.dataset.url;if(!url)return;
if(btn._audio){btn._audio.pause();btn._audio=null;btn.textContent='▶';return;}
const audio=new Audio(url);
btn._audio=audio;btn.textContent='⏸';
audio.play().catch(()=>{btn.textContent='▶';btn._audio=null;});
audio.onended=audio.onerror=()=>{btn.textContent='▶';btn._audio=null;};
};
function gchtFullRender(convId){
const container=document.getElementById('gcht_msgs');
if(!container)return;
container.innerHTML='<div id="gcht_empty" class="gcht_empty">No messages yet — say something!</div>';
const conv=GCHT.convs[convId];if(!conv)return;
conv.msgs.forEach(m=>gchtRenderMsg(m));
gchtScrollBot();
}
function gchtSidebarRender(){
const sidebar=document.getElementById('gcht_sidebar');
if(!sidebar)return;
if(GCHT.tab==='global'){sidebar.style.display='none';return;}
sidebar.style.display='flex';
const type=GCHT.tab==='dms'?'dm':'group';
const convs=Object.values(GCHT.convs).filter(c=>c.type===type);
let html='';
convs.forEach(c=>{
const active=GCHT.active===c.id;
html+=`<div class="gcht_conv_item${active?' gcht_conv_active':''}" onclick="gchtSelectConv('${c.id}')">
<div class="gcht_conv_name">${gchtSafe(c.name)}</div>
${c.unread>0?`<div class="gcht_conv_badge">${c.unread}</div>`:''}
</div>`;
});
html+=`<button class="gcht_new_conv_btn" onclick="${GCHT.tab==='dms'?'gchtOpenDmModal()':'gchtOpenGroupModal()'}">
+ ${GCHT.tab==='dms'?'New DM':'New Group'}
</button>`;
sidebar.innerHTML=html;
}
window.gchtSelectConv=function(id){
if(GCHT.active===id)return;
GCHT.unread=Math.max(0,GCHT.unread-(GCHT.convs[id]?.unread||0));
if(GCHT.convs[id])GCHT.convs[id].unread=0;
GCHT.active=id;
gchtBadgeUpdate();gchtSidebarRender();
const conv=GCHT.convs[id];
const hd=document.getElementById('gcht_conv_title');
if(hd&&conv)hd.textContent=conv.name;
gchtFullRender(id);
};
window.gchtTabSwitch=function(tab){
GCHT.tab=tab;
['global','dms','groups'].forEach(t=>{
document.getElementById('gcht_tab_'+t)?.classList.toggle('gcht_tab_on',t===tab);
});
if(tab==='global'){
GCHT.active='global';
const hd=document.getElementById('gcht_conv_title');
if(hd)hd.textContent='🌐 Global';
gchtSidebarRender();gchtFullRender('global');return;
}
const type=tab==='dms'?'dm':'group';
gchtSidebarRender();
const convs=Object.values(GCHT.convs).filter(c=>c.type===type);
if(convs.length&&!convs.find(c=>c.id===GCHT.active))GCHT.active=convs[0].id;
if(GCHT.active&&GCHT.convs[GCHT.active]?.type===type){
const hd=document.getElementById('gcht_conv_title');
if(hd)hd.textContent=GCHT.convs[GCHT.active]?.name||'';
gchtFullRender(GCHT.active);
}else{
const c=document.getElementById('gcht_msgs');
if(c)c.innerHTML=`<div class="gcht_empty">No ${tab==='dms'?'DMs':'groups'} yet.<br>Click the button in the sidebar!</div>`;
}
};
function gchtModal(html){
document.getElementById('gcht_modal_overlay')?.remove();
const ov=document.createElement('div');
ov.id='gcht_modal_overlay';
ov.style.cssText='position:absolute;inset:0;background:rgba(0,0,0,0.72);backdrop-filter:blur(4px);z-index:20;display:flex;align-items:center;justify-content:center;border-radius:13px;';
ov.innerHTML=`<div class="gcht_modal_box">${html}</div>`;
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove();});
document.getElementById('gcht_box').appendChild(ov);
}
window.gchtOpenDmModal=function(){
gchtModal(`
<div class="gcht_modal_title">💬 Start a DM</div>
<div class="gcht_modal_lbl">Their Parasidieum username:</div>
<input id="gcht_modal_i1" class="gcht_modal_inp" type="text" placeholder="e.g. Steve" maxlength="32">
<div class="gcht_modal_row">
<button class="gcht_modal_go" onclick="gchtStartDm(document.getElementById('gcht_modal_i1').value.trim())">Start Chat</button>
<button class="gcht_modal_cancel" onclick="document.getElementById('gcht_modal_overlay').remove()">Cancel</button>
</div>`);
setTimeout(()=>{
const inp=document.getElementById('gcht_modal_i1');
inp?.focus();
inp?.addEventListener('keydown',e=>{if(e.key==='Enter')window.gchtStartDm(inp.value.trim());});
},50);
};
window.gchtOpenGroupModal=function(){
gchtModal(`
<div class="gcht_modal_title">👥 Create Group</div>
<div class="gcht_modal_lbl">Group name:</div>
<input id="gcht_modal_gn" class="gcht_modal_inp" type="text" placeholder="My Squad" maxlength="32">
<div class="gcht_modal_lbl" style="margin-top:8px;">Members (comma-separated):</div>
<input id="gcht_modal_gm" class="gcht_modal_inp" type="text" placeholder="Alice, Bob, Charlie">
<div style="font-size:9px;color:rgba(255,255,255,0.28);margin-top:4px;line-height:1.5;">
They must have Parasidieum installed to receive the invite.
</div>
<div class="gcht_modal_row">
<button class="gcht_modal_go" onclick="(()=>{const n=document.getElementById('gcht_modal_gn').value.trim();const m=document.getElementById('gcht_modal_gm').value.split(',').map(s=>s.trim()).filter(Boolean);window.gchtCreateGroup(n,m);})()">Create</button>
<button class="gcht_modal_cancel" onclick="document.getElementById('gcht_modal_overlay').remove()">Cancel</button>
</div>`);
setTimeout(()=>document.getElementById('gcht_modal_gn')?.focus(),50);
};
function gchtSystemToast(text,onAccept){
let box=document.getElementById('gcht_sys_box');
if(!box){
box=document.createElement('div');
box.id='gcht_sys_box';
box.style.cssText='position:fixed;top:68px;right:20px;z-index:11500;display:flex;flex-direction:column;gap:8px;';
document.body.appendChild(box);
}
const el=document.createElement('div');
el.className='gcht_sys_toast';
el.innerHTML=`
<div class="gcht_sys_text">${gchtSafe(text)}</div>
<div class="gcht_sys_btns">
<button class="gcht_sys_accept">Accept</button>
<button class="gcht_sys_dismiss">Dismiss</button>
</div>`;
box.appendChild(el);
el.querySelector('.gcht_sys_accept').onclick=()=>{el.remove();onAccept?.();};
el.querySelector('.gcht_sys_dismiss').onclick=()=>el.remove();
setTimeout(()=>{if(el.isConnected)el.remove();},12000);
}
const gchtIsAtBot=()=>{const e=document.getElementById('gcht_msgs');return!e||e.scrollTop+e.clientHeight>=e.scrollHeight-32;};
const gchtScrollBot=()=>{const e=document.getElementById('gcht_msgs');if(e)e.scrollTop=e.scrollHeight;};
function gchtStatus(msg,ok){
const e=document.getElementById('gcht_status');
if(e){e.textContent=msg;e.style.color=ok?'rgba(80,220,160,0.55)':'rgba(255,120,80,0.65)';}
}
function gchtBadgeUpdate(){
const b=document.getElementById('gcht_badge');
if(b)b.textContent=GCHT.unread>9?'9+':String(GCHT.unread);
document.getElementById('gcht_btn')?.classList.toggle('gcht_has_unread',GCHT.unread>0);
}
function gchtCharUpdate(){
const inp=document.getElementById('gcht_input'),ctr=document.getElementById('gcht_char');
if(!inp||!ctr)return;
const rem=GCHT_MAX_LEN-inp.value.length;
ctr.textContent=String(rem);
ctr.className='gcht_char'+(rem<20?(rem<0?' gcht_over':' gcht_warn'):'');
}
function gchtApplyTheme(){
const t=getTheme();
const box=document.getElementById('gcht_box');
if(box)box.style.backgroundColor=t.hackBox;
const hdr=box?.querySelector('.ps_header');
if(hdr)hdr.style.backgroundColor=t.hackHeader;
}
const gchtCss=document.createElement('style');
gchtCss.textContent=`
#gcht_btn{position:fixed;bottom:20px;left:1290px;z-index:10000;border:none;border-radius:12px;padding:8px 14px;cursor:pointer;font-size:13px;font-weight:bold;color:#fff;background:rgba(50,150,220,0.85);transition:background 0.2s,transform 0.1s;display:flex;align-items:center;gap:5px;}
#gcht_btn:hover{background:rgba(50,150,220,1);}#gcht_btn:active{transform:scale(0.95);}
#gcht_badge{display:none;background:#ff4444;color:#fff;border-radius:50%;width:16px;height:16px;font-size:9px;align-items:center;justify-content:center;border:2px solid rgba(0,0,0,0.4);line-height:1;font-weight:bold;}
#gcht_btn.gcht_has_unread #gcht_badge{display:flex;}
#gcht_box{position:fixed;bottom:65px;left:1290px;width:420px;max-height:560px;z-index:10000;border-radius:13px;display:none;flex-direction:column;overflow:hidden;box-shadow:0 8px 36px rgba(0,0,0,0.55);}
#gcht_tabs{display:flex;gap:3px;padding:6px 8px 4px;background:rgba(0,0,0,0.22);flex-shrink:0;border-bottom:1px solid rgba(255,255,255,0.05);}
.gcht_tab{flex:1;padding:5px 0;border-radius:7px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:rgba(255,255,255,0.4);font-size:11px;font-weight:bold;cursor:pointer;text-align:center;transition:all 0.14s;}
.gcht_tab:hover{background:rgba(255,255,255,0.1);color:rgba(255,255,255,0.7);}
.gcht_tab_on{background:rgba(255,255,255,0.16)!important;color:#fff!important;border-color:rgba(255,255,255,0.28)!important;}
#gcht_body{display:flex;flex:1;min-height:0;overflow:hidden;}
#gcht_sidebar{width:128px;flex-shrink:0;display:none;flex-direction:column;gap:3px;padding:7px 6px;background:rgba(0,0,0,0.28);overflow-y:auto;border-right:1px solid rgba(255,255,255,0.06);scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.08) transparent;}
#gcht_sidebar::-webkit-scrollbar{width:3px;}#gcht_sidebar::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.1);border-radius:3px;}
.gcht_conv_item{padding:6px 8px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;transition:background 0.12s;border:1px solid transparent;}
.gcht_conv_item:hover{background:rgba(255,255,255,0.07);}
.gcht_conv_active{background:rgba(255,255,255,0.12)!important;border-color:rgba(255,255,255,0.15)!important;}
.gcht_conv_name{font-size:11px;color:rgba(255,255,255,0.82);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;line-height:1.35;}
.gcht_conv_badge{background:#ff4444;color:#fff;border-radius:50%;width:15px;height:15px;font-size:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:bold;}
.gcht_new_conv_btn{margin-top:auto;padding:6px 0;border-radius:8px;width:100%;border:1px dashed rgba(255,255,255,0.18);background:transparent;color:rgba(255,255,255,0.38);font-size:10px;font-weight:bold;cursor:pointer;transition:all 0.14s;text-align:center;}
.gcht_new_conv_btn:hover{background:rgba(255,255,255,0.08);color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.35);}
#gcht_msgs_wrap{display:flex;flex-direction:column;flex:1;min-width:0;min-height:0;}
#gcht_conv_bar{padding:5px 10px 4px;font-size:11px;font-weight:bold;color:rgba(255,255,255,0.6);flex-shrink:0;display:flex;align-items:center;gap:6px;background:rgba(0,0,0,0.16);border-bottom:1px solid rgba(255,255,255,0.05);}
.gcht_live_dot{width:6px;height:6px;border-radius:50%;background:#44dd44;flex-shrink:0;animation:gcht_dot 1.8s ease-in-out infinite;}
@keyframes gcht_dot{0%,100%{opacity:1;}50%{opacity:0.22;}}
#gcht_msgs{flex:1;overflow-y:auto;padding:8px 10px;display:flex;flex-direction:column;gap:5px;min-height:0;max-height:270px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.08) transparent;}
#gcht_msgs::-webkit-scrollbar{width:4px;}#gcht_msgs::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.1);border-radius:3px;}
.gcht_empty{font-size:11px;color:rgba(255,255,255,0.2);text-align:center;padding:28px 0;line-height:1.9;}
.gcht_msg{display:flex;flex-direction:column;gap:3px;padding:7px 9px;border-radius:9px;background:rgba(255,255,255,0.045);border:1px solid rgba(255,255,255,0.06);animation:gcht_in 0.18s ease;word-break:break-word;}
@keyframes gcht_in{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:none;}}
.gcht_mine{background:rgba(50,150,220,0.09);border-color:rgba(50,150,220,0.2);}
.gcht_head{display:flex;align-items:center;gap:5px;flex-wrap:wrap;}
.gcht_dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;}
.gcht_name{font-size:11px;font-weight:bold;}
.gcht_lv{font-size:9px;padding:1px 5px;border-radius:10px;background:rgba(255,255,255,0.08);color:rgba(255,255,255,0.38);}
.gcht_ts{font-size:9px;color:rgba(255,255,255,0.18);margin-left:auto;flex-shrink:0;}
.gcht_body{font-size:12px;color:rgba(255,255,255,0.85);line-height:1.45;}
.gcht_voice_row{display:flex;align-items:center;gap:7px;padding:2px 0;}
.gcht_play_btn{width:30px;height:30px;border-radius:50%;background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.2);color:#fff;cursor:pointer;font-size:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:background 0.13s;}
.gcht_play_btn:hover{background:rgba(255,255,255,0.25);}
.gcht_wave{display:flex;align-items:center;gap:1.5px;flex:1;}
.gcht_bar{width:3px;background:rgba(255,255,255,0.3);border-radius:2px;flex-shrink:0;}
.gcht_vdur{font-size:10px;color:rgba(255,255,255,0.32);flex-shrink:0;font-family:monospace;}
.gcht_ss_card{font-size:12px;color:rgba(255,255,255,0.75);line-height:1.55;padding:3px 0;display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.gcht_watch_btn{padding:3px 10px;border-radius:7px;background:rgba(50,150,220,0.22);border:1px solid rgba(50,150,220,0.5);color:rgba(80,200,255,1);font-size:11px;font-weight:bold;cursor:pointer;transition:background 0.14s;}
.gcht_watch_btn:hover{background:rgba(50,150,220,0.45);}
#gcht_input_row{display:flex;align-items:center;gap:5px;padding:7px 8px;background:rgba(0,0,0,0.22);flex-shrink:0;border-top:1px solid rgba(255,255,255,0.04);}
#gcht_mic_btn{background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.6);border:1px solid rgba(255,255,255,0.12);border-radius:8px;padding:5px 10px;cursor:pointer;font-size:14px;flex-shrink:0;transition:background 0.14s;}
#gcht_mic_btn:hover{background:rgba(255,255,255,0.15);}
#gcht_input{flex:1;background:rgba(255,255,255,0.07);border:1px solid rgba(255,255,255,0.15);border-radius:8px;padding:6px 9px;color:#fff;font-size:12px;outline:none;transition:border-color 0.14s;}
#gcht_input:focus{border-color:rgba(255,255,255,0.38);}
#gcht_input::placeholder{color:rgba(255,255,255,0.24);}
.gcht_char{font-size:9px;color:rgba(255,255,255,0.2);flex-shrink:0;min-width:20px;text-align:right;}
.gcht_warn{color:rgba(255,160,80,0.7)!important;}.gcht_over{color:rgba(255,80,80,0.85)!important;}
#gcht_ss_btn{background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.52);border:1px solid rgba(255,255,255,0.12);border-radius:8px;padding:5px 9px;cursor:pointer;font-size:13px;flex-shrink:0;transition:background 0.14s,color 0.14s;}
#gcht_ss_btn:hover{background:rgba(50,150,220,0.22);color:rgba(80,200,255,1);}
#gcht_send_btn{background:rgba(50,150,220,0.2);border:1px solid rgba(50,150,220,0.45);color:rgba(80,210,255,1);border-radius:8px;padding:5px 13px;cursor:pointer;font-size:14px;font-weight:bold;flex-shrink:0;transition:background 0.14s;}
#gcht_send_btn:hover{background:rgba(50,150,220,0.42);}
#gcht_status{padding:3px 10px;font-size:9px;color:rgba(255,255,255,0.2);background:rgba(0,0,0,0.12);text-align:center;flex-shrink:0;letter-spacing:0.03em;border-top:1px solid rgba(255,255,255,0.04);}
.gcht_modal_box{background:rgba(18,20,28,0.98);border-radius:12px;padding:16px;width:250px;border:1px solid rgba(255,255,255,0.1);box-shadow:0 8px 32px rgba(0,0,0,0.65);display:flex;flex-direction:column;gap:7px;}
.gcht_modal_title{font-size:13px;font-weight:bold;color:#fff;margin-bottom:3px;}
.gcht_modal_lbl{font-size:10px;color:rgba(255,255,255,0.38);margin-bottom:2px;}
.gcht_modal_inp{width:100%;box-sizing:border-box;background:rgba(255,255,255,0.09);border:1px solid rgba(255,255,255,0.16);border-radius:7px;padding:6px 9px;color:#fff;font-size:12px;outline:none;}
.gcht_modal_inp:focus{border-color:rgba(255,255,255,0.38);}
.gcht_modal_inp::placeholder{color:rgba(255,255,255,0.28);}
.gcht_modal_row{display:flex;gap:7px;margin-top:4px;}
.gcht_modal_go{flex:1;padding:7px;border-radius:8px;border:none;background:rgba(50,150,220,0.88);color:#fff;font-weight:bold;cursor:pointer;font-size:12px;transition:background 0.14s;}
.gcht_modal_go:hover{background:rgba(50,150,220,1);}
.gcht_modal_cancel{flex:1;padding:7px;border-radius:8px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.5);cursor:pointer;font-size:12px;}
.gcht_modal_cancel:hover{background:rgba(255,255,255,0.12);color:#fff;}
.gcht_sys_toast{background:rgba(10,12,20,0.97);border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:12px 14px;box-shadow:0 4px 24px rgba(0,0,0,0.7);backdrop-filter:blur(14px);min-width:240px;max-width:300px;animation:gcht_toast_in 0.22s cubic-bezier(.2,.8,.3,1);}
@keyframes gcht_toast_in{from{opacity:0;transform:translateX(14px);}to{opacity:1;transform:none;}}
.gcht_sys_text{font-size:12px;font-weight:bold;color:#fff;margin-bottom:8px;line-height:1.45;}
.gcht_sys_btns{display:flex;gap:7px;}
.gcht_sys_accept{flex:1;padding:6px;border:none;border-radius:8px;background:rgba(50,200,100,0.88);color:#fff;font-weight:bold;cursor:pointer;font-size:12px;}
.gcht_sys_accept:hover{background:rgba(50,200,100,1);}
.gcht_sys_dismiss{flex:1;padding:6px;border:1px solid rgba(255,255,255,0.1);border-radius:8px;background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.5);cursor:pointer;font-size:12px;}
.gcht_sys_dismiss:hover{background:rgba(255,255,255,0.12);color:#fff;}
@keyframes gcht_rec_pulse{0%,100%{box-shadow:0 0 0 0 rgba(255,60,60,0.45);}50%{box-shadow:0 0 0 7px transparent;}}
`;
document.head.appendChild(gchtCss);
const gchtBtnEl=document.createElement('button');
gchtBtnEl.id='gcht_btn';
gchtBtnEl.innerHTML='💬 Chat<span id="gcht_badge"></span>';
document.body.appendChild(gchtBtnEl);
const gchtBox=document.createElement('div');
gchtBox.id='gcht_box';
gchtBox.innerHTML=`
<div id="gcht_tabs">
<button class="gcht_tab gcht_tab_on" id="gcht_tab_global" onclick="gchtTabSwitch('global')">🌐 Global</button>
<button class="gcht_tab" id="gcht_tab_dms" onclick="gchtTabSwitch('dms')">💬 DMs</button>
<button class="gcht_tab" id="gcht_tab_groups" onclick="gchtTabSwitch('groups')">👥 Groups</button>
</div>
<div id="gcht_body">
<div id="gcht_sidebar"></div>
<div id="gcht_msgs_wrap">
<div id="gcht_conv_bar">
<div class="gcht_live_dot"></div>
<span id="gcht_conv_title">🌐 Global</span>
</div>
<div id="gcht_msgs">
<div id="gcht_empty" class="gcht_empty">No messages yet — say something!</div>
</div>
</div>
</div>
<div id="gcht_status">Connecting…</div>
<div id="gcht_input_row">
<button id="gcht_mic_btn" title="Hold to record a voice message">🎤</button>
<input id="gcht_input" type="text" placeholder="Message…" maxlength="${GCHT_MAX_LEN}">
<span class="gcht_char" id="gcht_char">${GCHT_MAX_LEN}</span>
<button id="gcht_ss_btn" title="Share your screen in this chat">📺</button>
<button id="gcht_send_btn">↑</button>
</div>`;
gchtBox.insertBefore(
makeHeader('💬 Chat',()=>{ gchtBox.style.display='none'; GCHT.open=false; }),
gchtBox.firstChild
);
document.body.appendChild(gchtBox);
makeDraggable(gchtBox);
setupMinimize(gchtBox);
gchtBtnEl.addEventListener('click',()=>{
GCHT.open=!GCHT.open;
gchtBox.style.display=GCHT.open?'flex':'none';
if(GCHT.open){
GCHT.unread=0;gchtBadgeUpdate();gchtApplyTheme();
if(GCHT.convs[GCHT.active])GCHT.convs[GCHT.active].unread=0;
gchtSidebarRender();gchtFullRender(GCHT.active);
setTimeout(()=>{gchtScrollBot();document.getElementById('gcht_input')?.focus();},50);
}
});
document.getElementById('gcht_send_btn').addEventListener('click',gchtSend);
document.getElementById('gcht_input').addEventListener('keydown',e=>{if(e.key==='Enter')gchtSend();});
document.getElementById('gcht_input').addEventListener('input',gchtCharUpdate);
document.getElementById('gcht_mic_btn').addEventListener('click',gchtToggleRec);
document.getElementById('gcht_ss_btn').addEventListener('click',gchtShareScreen);
new MutationObserver(()=>{if(GCHT.open)gchtApplyTheme();}).observe(document.head,{childList:true});
gchtInitPolls();
gchtStatus('Live · ntfy.sh',true);
// ── Para Chat Commands ──
const paraCmdHook = setInterval(() => {
if (typeof game === 'undefined' || !game.getSocketUtil || !game.getSocketUtil()) return;
clearInterval(paraCmdHook);
const su = game.getSocketUtil();
const _origEmit = su.emit.bind(su);
su.emit = function(type, data) {
if (type === 'ClientChat' && data?.message) {
const raw = data.message.trim();
if (raw.toLowerCase().startsWith('~para ') && raw.toLowerCase().endsWith('.command')) {
const cmd = raw.slice(6, -8).trim().toLowerCase();
if (paraHandleCmd(cmd)) return;
}
}
return _origEmit(type, data);
};
}, 500);
function paraHandleCmd(cmd) {
function pToast(msg) {
const el = document.createElement('div');
el.style.cssText = `position:fixed;top:16px;left:50%;transform:translateX(-50%);
z-index:20000;background:rgba(8,8,18,0.97);color:#fff;
padding:9px 20px;border-radius:11px;font-size:13px;font-weight:bold;
pointer-events:none;border:1px solid rgba(255,255,255,0.13);
box-shadow:0 4px 22px rgba(0,0,0,0.7);white-space:nowrap;`;
el.textContent = msg;
document.body.appendChild(el);
setTimeout(() => el.remove(), 2800);
}
switch(cmd) {
// ── Quick Reload ──
case 'quickreload':
document.getElementById('ps_quick_reload_btn')?.click();
pToast(`⚡ Quick Reload: ${quickReloadActive ? 'ON' : 'OFF'}`);
return true;
// ── ESP toggles ──
case 'esp':
if (junonESP) {
const on = !junonESP.settings.showPlayers;
junonESP.settings.showPlayers = on;
junonESP.settings.showMobs = on;
junonESP.saveSettings();
syncEspBoxToSettings();
pToast(`🎯 ESP: ${on ? 'ON' : 'OFF'}`);
} else pToast('🎯 ESP not ready yet');
return true;
case 'esp players':
if (junonESP) {
junonESP.settings.showPlayers = !junonESP.settings.showPlayers;
junonESP.saveSettings(); syncEspBoxToSettings();
pToast(`👤 ESP Players: ${junonESP.settings.showPlayers ? 'ON' : 'OFF'}`);
}
return true;
case 'esp mobs':
if (junonESP) {
junonESP.settings.showMobs = !junonESP.settings.showMobs;
junonESP.saveSettings(); syncEspBoxToSettings();
pToast(`👾 ESP Mobs: ${junonESP.settings.showMobs ? 'ON' : 'OFF'}`);
}
return true;
case 'esp lines':
if (junonESP) {
junonESP.settings.showLines = !junonESP.settings.showLines;
junonESP.saveSettings(); syncEspBoxToSettings();
pToast(`📏 ESP Lines: ${junonESP.settings.showLines ? 'ON' : 'OFF'}`);
}
return true;
case 'esp boxes':
if (junonESP) {
junonESP.settings.showBoxes = !junonESP.settings.showBoxes;
junonESP.saveSettings(); syncEspBoxToSettings();
pToast(`🔲 ESP Boxes: ${junonESP.settings.showBoxes ? 'ON' : 'OFF'}`);
}
return true;
case 'esp labels':
if (junonESP) {
junonESP.settings.showLabels = !junonESP.settings.showLabels;
junonESP.saveSettings(); syncEspBoxToSettings();
pToast(`🏷️ ESP Labels: ${junonESP.settings.showLabels ? 'ON' : 'OFF'}`);
}
return true;
case 'esp reset':
if (junonESP) { junonESP.resetSettings(); syncEspBoxToSettings(); }
pToast('🎯 ESP reset to defaults');
return true;
// ── OC ──
case 'oc':
document.getElementById('ps_oc_btn')?.click();
pToast(`⚙️ OC: ${ocEnabled ? 'ON' : 'OFF'}`);
return true;
// ── Omni-Build ──
case 'omnibuild':
document.getElementById('ps_omni_build_btn')?.click();
pToast(`🔨 Omni-Build: ${omniBuildEnabled ? 'ON' : 'OFF'}`);
return true;
// ── Rainbow ──
case 'rainbow':
if (rainbowInterval) { stopRainbow(); pToast('🌈 Rainbow: OFF'); }
else { startRainbow('mine'); pToast('🌈 Rainbow: ON (your suit)'); }
return true;
case 'rainbow all':
if (rainbowInterval) { stopRainbow(); pToast('🌈 Rainbow All: OFF'); }
else { startRainbow('all'); pToast('🌈 Rainbow All: ON (everyone)'); }
return true;
case 'rainbow stop':
stopRainbow();
pToast('🌈 Rainbow stopped');
return true;
// ── Visuals ──
case 'bubbles':
document.getElementById('ps_bubble_toggle_btn')?.click();
pToast(`🫧 Bubbles: ${bubblesEnabled ? 'ON' : 'OFF'}`);
return true;
case 'border':
document.getElementById('ps_border_toggle_btn')?.click();
pToast(`🔲 Border: ${borderEnabled ? 'ON' : 'OFF'}`);
return true;
case 'extra':
document.getElementById('ps_extra_effects_btn')?.click();
pToast(`✨ Extra Effects: ${extraEffectsEnabled ? 'ON' : 'OFF'}`);
return true;
case 'original':
document.getElementById('ps_original_btn')?.click();
pToast(`🎨 Original Mode: ${originalMode ? 'ON' : 'OFF'}`);
return true;
// ── Music ──
case 'music':
document.getElementById('ps_music_toggle_btn')?.click();
pToast(`🎵 Music: ${musicEnabled ? 'ON' : 'OFF'}`);
return true;
// ── Themes ──
case 'theme miku':
switchTheme('miku', MIKU_IMG, 'ps_img_miku');
pToast('🌸 Theme: Miku'); return true;
case 'theme hk':
switchTheme('hk', HK_IMG, 'ps_img_hk');
pToast('🦋 Theme: Hollow Knight'); return true;
case 'theme space':
switchTheme('space', SPACE_MIDDLE_IMG, 'ps_img_space');
pToast('🚀 Theme: Space'); return true;
case 'theme gojo':
switchTheme('gojo', GOJO_MIDDLE_IMG, 'ps_img_gojo');
pToast('💜 Theme: Gojo'); return true;
case 'theme doom':
switchTheme('doom', DOOM_MIDDLE_IMG, 'ps_img_doom');
pToast('💀 Theme: Doom'); return true;
case 'theme teto':
switchTheme('teto', TETO_MIDDLE_IMG, 'ps_img_teto');
pToast('🎵 Theme: Teto'); return true;
case 'theme kfp':
switchTheme('kfp', KFP_MIDDLE_IMG, 'ps_img_kfp');
pToast('🐼 Theme: Kung Fu Panda'); return true;
// ── Help ──
case 'help':
pToast('~para [cmd].command — quickreload | esp | esp players | esp mobs | esp lines | esp boxes | esp labels | esp reset | oc | omnibuild | rainbow | rainbow all | rainbow stop | bubbles | border | extra | original | music | theme [name] | volume [0-100]');
return true;
default:
// Volume: ~para volume 75.command
const volM = cmd.match(/^volume (\d+)$/);
if (volM) {
const v = Math.max(0, Math.min(100, parseInt(volM[1])));
audioPlayer.volume = v / 100;
const sl = document.getElementById('ps_volume_slider');
const vl = document.getElementById('ps_volume_val');
if (sl) sl.value = v;
if (vl) vl.textContent = v + '%';
pToast(`🔊 Volume: ${v}%`);
return true;
}
return false;
}
}
})();