// ==UserScript== // @name profile viewer // @version 1.0 // @author Zhiro // @description view player profiles without going to a different page (LMB to open the window, RMB to open the profile in a new tab, if you want to see all the stats). My discord: zhiro999. // @match https://case-clicker.com/* // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (async () => { 'use strict'; const getBuildId = () => { const nextData = document.getElementById('__NEXT_DATA__'); if (nextData) { try { const data = JSON.parse(nextData.textContent); return data.buildId; } catch(e) {} } return null; }; const getUserFromAvatar = (avatarEl) => { let node = avatarEl; for (let i = 0; i < 10; i++) { const reactKey = Object.keys(node).find(k => k.startsWith('__reactFiber$')); if (reactKey) { let fiber = node[reactKey]; let depth = 0; while (fiber && depth < 20) { const props = fiber.memoizedProps || fiber.props; const msg = props?.message || props?.msg || props?.chatMessage; if (msg?.user?._id) { return { id: msg.user._id, name: msg.user.name, image: msg.user.image }; } fiber = fiber.return; depth++; } } node = node.parentElement; if (!node) break; } return null; }; const formatNumber = (n) => { if (!n && n !== 0) return '0'; if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B'; if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M'; if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K'; return n.toLocaleString(); }; const formatMoney = (n) => { if (!n && n !== 0) return '$0'; return '$' + formatNumber(n); }; const formatDate = (isoString) => { if (!isoString) return 'Unknown'; const date = new Date(isoString); if (isNaN(date.getTime())) return 'Unknown'; const month = date.getMonth() + 1; const day = date.getDate(); const year = date.getFullYear(); return month + '/' + day + '/' + year; }; const escapeHtml = (str) => { if (!str) return ''; return str.replace(/[&<>]/g, (m) => { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }); }; const fetchProfile = async (userId) => { const buildId = getBuildId(); if (!buildId) return null; const url = '/_next/data/' + buildId + '/en/profile/' + userId + '.json?id=' + userId; try { const res = await fetch(url, { credentials: 'include' }); if (!res.ok) return null; const data = await res.json(); const user = JSON.parse(data.pageProps.user); const userstat = JSON.parse(data.pageProps.userstat); const rank = data.pageProps.rank ? JSON.parse(data.pageProps.rank) : null; return { name: user.name, image: user.image, memberSince: formatDate(userstat.createdAt), role: user.role, rankName: rank ? rank.name : 'Unranked', premierRating: userstat.premierRating || 0, clicks: userstat.clicks || 0, clickedCases: userstat.clickedCases || 0, moneyEarned: userstat.moneyEarned || 0, moneySpent: userstat.moneySpent || 0, casebattles: userstat.casebattles || 0, casebattlesWon: userstat.casebattlesWon || 0, tokensWon: userstat.tokensWon || 0, tokensLost: userstat.tokensLost || 0 }; } catch(e) { console.error(e); return null; } }; const showModal = async (userId, userImage, userName) => { const overlay = document.createElement('div'); overlay.className = 'profile-modal-overlay'; const modal = document.createElement('div'); modal.className = 'profile-modal'; modal.innerHTML = '
Loading profile...
'; overlay.appendChild(modal); document.body.appendChild(overlay); overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); }; const profile = await fetchProfile(userId); if (!profile) { modal.innerHTML = '
Failed to load profile
'; return; } const profit = profile.moneyEarned - profile.moneySpent; const battleWinrate = profile.casebattles > 0 ? ((profile.casebattlesWon / profile.casebattles) * 100).toFixed(1) : 0; const tokenProfit = profile.tokensWon - profile.tokensLost; let rankDisplay = ''; if (profile.premierRating > 0) { rankDisplay = 'Premier Rating: ' + profile.premierRating.toLocaleString(); } else { rankDisplay = profile.rankName; } const avatarUrl = userImage || profile.image; modal.innerHTML = `
${escapeHtml(profile.name)}
${escapeHtml(rankDisplay)}
ECONOMY
Earned ${formatMoney(profile.moneyEarned)}
Spent ${formatMoney(profile.moneySpent)}
Net Profit ${profit >= 0 ? '' : '-'}${formatMoney(Math.abs(profit))}
ACTIVITY
Clicks ${formatNumber(profile.clicks)}
Cases ${formatNumber(profile.clickedCases)}
Case Battles ${formatNumber(profile.casebattles)} (Winrate ${battleWinrate}%)
TOKENS
Won ${formatNumber(profile.tokensWon)}
Lost ${formatNumber(profile.tokensLost)}
Net ${tokenProfit >= 0 ? '' : '-'}${formatNumber(Math.abs(tokenProfit))}
INFO
Member since ${profile.memberSince}
`; const img = modal.querySelector('.avatar-image'); const fallback = modal.querySelector('.avatar-fallback'); img.style.width = '80px'; img.style.height = '80px'; img.style.borderRadius = '50%'; img.style.objectFit = 'cover'; img.style.border = '2px solid rgba(233,69,96,0.5)'; if (img.complete && img.naturalWidth === 0) { img.style.display = 'none'; fallback.style.display = 'flex'; } else { img.onerror = () => { img.style.display = 'none'; fallback.style.display = 'flex'; }; } modal.querySelector('.profile-close').onclick = () => { overlay.remove(); }; }; const attachAvatarListeners = () => { const avatars = document.querySelectorAll('img[alt="avatar"], img[class*="Avatar"]'); for (const avatar of avatars) { if (avatar.dataset.profileLinked) continue; avatar.dataset.profileLinked = 'true'; avatar.style.cursor = 'pointer'; avatar.title = 'Left click - quick view, Right click - full profile'; avatar.onclick = async (e) => { e.stopPropagation(); const user = getUserFromAvatar(avatar); if (user && user.id) { await showModal(user.id, user.image, user.name); } }; avatar.oncontextmenu = (e) => { e.preventDefault(); e.stopPropagation(); const user = getUserFromAvatar(avatar); if (user && user.id) { window.open('/profile/' + user.id, '_blank'); } return false; }; } }; const init = () => { attachAvatarListeners(); }; const observer = new MutationObserver(() => { attachAvatarListeners(); }); observer.observe(document.body, { childList: true, subtree: true }); GM_addStyle(` @import url('https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400&display=swap'); .profile-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); backdrop-filter: blur(12px); z-index: 100000; display: flex; align-items: center; justify-content: center; } .profile-modal { animation: modalFloat 0.35s cubic-bezier(0.34, 1.2, 0.64, 1); } @keyframes modalFloat { from { opacity: 0; transform: scale(0.9) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } } .profile-modal-inner { position: relative; background: linear-gradient(135deg, rgba(18, 18, 30, 0.98), rgba(28, 28, 45, 0.98)); border-radius: 40px; width: 400px; max-width: 85vw; border: 1px solid rgba(255,255,255,0.08); box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5), 0 0 0 1px rgba(233,69,96,0.15); font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; overflow: hidden; } .profile-modal-inner::-webkit-scrollbar { display: none; } .profile-close { position: absolute; top: 16px; right: 16px; background: rgba(255,255,255,0.06); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.1); color: rgba(255,255,255,0.7); font-size: 20px; cursor: pointer; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: all 0.25s ease; z-index: 10; font-weight: 300; } .profile-close:hover { background: #e94560; color: white; transform: rotate(90deg); border-color: #e94560; } .profile-header-glow { position: absolute; top: 0; left: 0; right: 0; height: 160px; background: radial-gradient(ellipse at 50% 0%, rgba(233,69,96,0.25), rgba(121,40,202,0.1), transparent); pointer-events: none; } .profile-avatar { display: flex; justify-content: center; padding-top: 32px; margin-bottom: 8px; } .avatar-image { width: 88px; height: 88px; border-radius: 50%; object-fit: cover; border: 3px solid rgba(233,69,96,0.5); box-shadow: 0 8px 20px rgba(0,0,0,0.3); } .profile-username { text-align: center; font-size: 22px; font-weight: 700; color: white; margin-bottom: 6px; padding: 0 24px; letter-spacing: -0.2px; } .profile-rank-badge { text-align: center; font-size: 12px; font-weight: 600; color: #ffd700; background: rgba(255,215,0,0.08); display: block; width: fit-content; max-width: 90%; padding: 4px 16px; border-radius: 50px; margin: 0 auto 24px auto; letter-spacing: 0.3px; border: 1px solid rgba(255,215,0,0.15); white-space: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; } .profile-rank-badge::-webkit-scrollbar { display: none; } .profile-stats { padding: 0 20px 28px 20px; } .stat-card { background: rgba(255,255,255,0.03); border-radius: 20px; padding: 16px; margin-bottom: 12px; border: 1px solid rgba(255,255,255,0.04); } .stat-card-title { font-size: 10px; font-weight: 700; color: #e94560; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 1.5px; opacity: 0.7; } .stat-grid { display: flex; flex-direction: column; gap: 10px; } .stat-item { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; padding: 6px 0; } .stat-item.full-width { width: 100%; } .stat-item.highlight { padding-top: 10px; margin-top: 4px; border-top: 1px solid rgba(255,255,255,0.06); } .stat-label { color: #8899b0; font-size: 12px; font-weight: 500; } .stat-value { color: #f0f0f5; font-weight: 700; font-size: 13px; text-align: right; } .stat-value.positive { color: #4ade80; } .stat-value.negative { color: #f87171; } .winrate { color: #ffd700; font-size: 11px; margin-left: 6px; font-weight: 600; } .profile-loader { text-align: center; padding: 60px 40px; } .loader-spinner { width: 40px; height: 40px; border: 2px solid rgba(233,69,96,0.2); border-top: 2px solid #e94560; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto 16px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loader-text { color: #8899b0; font-size: 13px; font-weight: 500; } .error-message { text-align: center; padding: 60px 40px; color: #f87171; font-size: 13px; font-weight: 500; } `); init(); })();