Replaces blue color gradients and variables with purple alternatives across CSS and HTML files to align with the desired application theme. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: f9f1a4eb-2832-4cc3-92fc-6f966658f686 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/K1dCpNm Replit-Helium-Checkpoint-Created: true
1115 lines
35 KiB
HTML
1115 lines
35 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dashboard - AeThex Bot</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--background: hsl(270, 50%, 4%);
|
|
--foreground: hsl(270, 20%, 98%);
|
|
--card: hsl(270, 50%, 6%);
|
|
--card-hover: hsl(270, 50%, 8%);
|
|
--primary: hsl(270, 100%, 60%);
|
|
--secondary: hsl(270, 30%, 15%);
|
|
--muted: hsl(270, 30%, 15%);
|
|
--muted-foreground: hsl(270, 15%, 65%);
|
|
--border: hsl(270, 30%, 20%);
|
|
--aethex-400: hsl(270, 100%, 75%);
|
|
--aethex-500: hsl(270, 100%, 65%);
|
|
--neon-purple: hsl(280, 100%, 70%);
|
|
--neon-violet: hsl(270, 100%, 70%);
|
|
--neon-magenta: hsl(300, 100%, 70%);
|
|
--success: hsl(142, 76%, 36%);
|
|
--warning: hsl(38, 92%, 50%);
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: var(--background);
|
|
color: var(--foreground);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.wallpaper {
|
|
position: fixed;
|
|
inset: 0;
|
|
background-image: linear-gradient(135deg, rgba(147, 51, 234, 0.08) 0%, rgba(168, 85, 247, 0.05) 100%);
|
|
pointer-events: none;
|
|
z-index: -1;
|
|
}
|
|
|
|
.app {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 260px;
|
|
background: rgba(10, 10, 20, 0.95);
|
|
border-right: 1px solid var(--border);
|
|
padding: 1.5rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: fixed;
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 2rem;
|
|
text-decoration: none;
|
|
color: var(--foreground);
|
|
}
|
|
|
|
.logo-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.logo-text {
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
background: linear-gradient(to right, var(--aethex-400), var(--neon-purple));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.guild-selector {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.guild-selector select {
|
|
width: 100%;
|
|
padding: 0.75rem;
|
|
background: var(--secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
color: var(--foreground);
|
|
font-size: 0.875rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.nav-section {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.nav-section-title {
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
color: var(--muted-foreground);
|
|
margin-bottom: 0.75rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 8px;
|
|
color: var(--muted-foreground);
|
|
text-decoration: none;
|
|
transition: all 0.2s;
|
|
cursor: pointer;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.nav-item:hover, .nav-item.active {
|
|
background: var(--secondary);
|
|
color: var(--foreground);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(59, 130, 246, 0.1));
|
|
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
}
|
|
|
|
.nav-icon { width: 20px; text-align: center; }
|
|
|
|
.user-card {
|
|
margin-top: auto;
|
|
padding: 1rem;
|
|
background: var(--secondary);
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
background: var(--aethex-500);
|
|
}
|
|
|
|
.user-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.user-name {
|
|
font-weight: 600;
|
|
font-size: 0.875rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.user-level {
|
|
font-size: 0.75rem;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.main {
|
|
flex: 1;
|
|
margin-left: 260px;
|
|
padding: 2rem;
|
|
max-width: calc(100vw - 260px);
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 1.75rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.page-subtitle {
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: linear-gradient(135deg, rgba(139, 92, 246, 0.1), rgba(59, 130, 246, 0.05));
|
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
border-color: var(--aethex-500);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.875rem;
|
|
color: var(--muted-foreground);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: var(--aethex-400);
|
|
}
|
|
|
|
.stat-sub {
|
|
font-size: 0.75rem;
|
|
color: var(--muted-foreground);
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.card {
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.card-header {
|
|
padding: 1.25rem 1.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.card-body { padding: 1.5rem; }
|
|
|
|
.progress-bar {
|
|
height: 8px;
|
|
background: var(--secondary);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--aethex-500), var(--neon-purple));
|
|
border-radius: 4px;
|
|
transition: width 0.5s ease;
|
|
}
|
|
|
|
.achievement-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.achievement-item {
|
|
display: flex;
|
|
gap: 1rem;
|
|
padding: 1rem;
|
|
background: var(--secondary);
|
|
border-radius: 10px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.achievement-item:hover {
|
|
background: var(--card-hover);
|
|
}
|
|
|
|
.achievement-item.locked {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.achievement-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.achievement-info h4 {
|
|
font-weight: 600;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.achievement-info p {
|
|
font-size: 0.875rem;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.quest-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 1rem;
|
|
background: var(--secondary);
|
|
border-radius: 10px;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.quest-info { flex: 1; }
|
|
|
|
.quest-name {
|
|
font-weight: 600;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.quest-desc {
|
|
font-size: 0.875rem;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.quest-progress {
|
|
text-align: right;
|
|
min-width: 100px;
|
|
}
|
|
|
|
.quest-progress-text {
|
|
font-weight: 600;
|
|
color: var(--aethex-400);
|
|
}
|
|
|
|
.shop-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.shop-item {
|
|
background: var(--secondary);
|
|
border-radius: 12px;
|
|
padding: 1.25rem;
|
|
text-align: center;
|
|
transition: all 0.3s;
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.shop-item:hover {
|
|
border-color: var(--aethex-500);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.shop-icon {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.shop-name {
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.shop-price {
|
|
color: var(--aethex-400);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
transition: all 0.2s;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--secondary);
|
|
color: var(--foreground);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.leaderboard-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 8px;
|
|
margin-bottom: 0.5rem;
|
|
background: var(--secondary);
|
|
}
|
|
|
|
.leaderboard-item.top-3 {
|
|
background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(59, 130, 246, 0.1));
|
|
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
}
|
|
|
|
.leaderboard-rank {
|
|
width: 32px;
|
|
height: 32px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
border-radius: 8px;
|
|
background: var(--muted);
|
|
}
|
|
|
|
.leaderboard-item.top-3 .leaderboard-rank {
|
|
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
|
|
}
|
|
|
|
.leaderboard-avatar {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
background: var(--aethex-500);
|
|
}
|
|
|
|
.leaderboard-name { flex: 1; font-weight: 500; }
|
|
.leaderboard-xp { color: var(--aethex-400); font-weight: 600; }
|
|
|
|
.tabs {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.tab {
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
color: var(--muted-foreground);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.tab:hover { color: var(--foreground); }
|
|
|
|
.tab.active {
|
|
background: var(--aethex-500);
|
|
color: white;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 3rem;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.empty-state-icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 1rem;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.login-prompt {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 100vh;
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.login-prompt h1 {
|
|
font-size: 2rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.login-prompt p {
|
|
color: var(--muted-foreground);
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.hidden { display: none !important; }
|
|
|
|
.loading-spinner {
|
|
display: inline-block;
|
|
width: 24px;
|
|
height: 24px;
|
|
border: 3px solid var(--secondary);
|
|
border-top-color: var(--aethex-500);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
.text-gradient {
|
|
background: linear-gradient(to right, var(--aethex-400), var(--neon-purple));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.sidebar {
|
|
transform: translateX(-100%);
|
|
z-index: 100;
|
|
transition: transform 0.3s;
|
|
}
|
|
.sidebar.open { transform: translateX(0); }
|
|
.main { margin-left: 0; max-width: 100%; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wallpaper"></div>
|
|
|
|
<div id="loginPrompt" class="login-prompt">
|
|
<h1>Welcome to <span class="text-gradient">AeThex</span></h1>
|
|
<p>Login with Discord to view your profile, achievements, and more.</p>
|
|
<a href="/auth/discord" class="btn btn-primary">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
|
Login with Discord
|
|
</a>
|
|
</div>
|
|
|
|
<div id="app" class="app hidden">
|
|
<aside class="sidebar">
|
|
<a href="/" class="logo">
|
|
<div class="logo-icon">A</div>
|
|
<span class="logo-text">AeThex</span>
|
|
</a>
|
|
|
|
<div class="guild-selector">
|
|
<select id="guildSelect">
|
|
<option value="">Select a server...</option>
|
|
</select>
|
|
</div>
|
|
|
|
<nav>
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">Overview</div>
|
|
<div class="nav-item active" data-page="profile">
|
|
<span class="nav-icon">👤</span> Profile
|
|
</div>
|
|
<div class="nav-item" data-page="leaderboard">
|
|
<span class="nav-icon">🏆</span> Leaderboard
|
|
</div>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">Rewards</div>
|
|
<div class="nav-item" data-page="achievements">
|
|
<span class="nav-icon">🎖️</span> Achievements
|
|
</div>
|
|
<div class="nav-item" data-page="quests">
|
|
<span class="nav-icon">🎯</span> Quests
|
|
</div>
|
|
<div class="nav-item" data-page="shop">
|
|
<span class="nav-icon">🛒</span> Shop
|
|
</div>
|
|
<div class="nav-item" data-page="inventory">
|
|
<span class="nav-icon">🎒</span> Inventory
|
|
</div>
|
|
</div>
|
|
|
|
<div class="nav-section" id="adminSection" style="display:none">
|
|
<div class="nav-section-title">Admin</div>
|
|
<div class="nav-item" data-page="admin-xp">
|
|
<span class="nav-icon">⚙️</span> XP Settings
|
|
</div>
|
|
<div class="nav-item" data-page="admin-quests">
|
|
<span class="nav-icon">📝</span> Manage Quests
|
|
</div>
|
|
<div class="nav-item" data-page="admin-achievements">
|
|
<span class="nav-icon">🏅</span> Manage Achievements
|
|
</div>
|
|
<div class="nav-item" data-page="admin-shop">
|
|
<span class="nav-icon">🏪</span> Manage Shop
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="user-card" id="userCard">
|
|
<img class="user-avatar" id="userAvatar" src="" alt="">
|
|
<div class="user-info">
|
|
<div class="user-name" id="userName">Loading...</div>
|
|
<div class="user-level" id="userLevel">Level 0</div>
|
|
</div>
|
|
<a href="/auth/logout" class="btn btn-secondary" style="padding:0.5rem">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></svg>
|
|
</a>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="main">
|
|
<div id="page-profile" class="page">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Your <span class="text-gradient">Profile</span></h1>
|
|
<p class="page-subtitle">Track your progress and stats</p>
|
|
</div>
|
|
|
|
<div class="stats-grid" id="statsGrid">
|
|
<div class="stat-card">
|
|
<div class="stat-label">Total XP</div>
|
|
<div class="stat-value" id="statXp">0</div>
|
|
<div class="stat-sub" id="statXpProgress">0 XP to next level</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Level</div>
|
|
<div class="stat-value" id="statLevel">0</div>
|
|
<div class="stat-sub" id="statPrestige">Prestige 0</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Messages</div>
|
|
<div class="stat-value" id="statMessages">0</div>
|
|
<div class="stat-sub">All time</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Voice Time</div>
|
|
<div class="stat-value" id="statVoice">0h</div>
|
|
<div class="stat-sub">All time</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Daily Streak</div>
|
|
<div class="stat-value" id="statStreak">0</div>
|
|
<div class="stat-sub">days</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Achievements</div>
|
|
<div class="stat-value" id="statAchievements">0</div>
|
|
<div class="stat-sub">earned</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Level Progress</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:0.5rem">
|
|
<span>Level <span id="currentLevel">0</span></span>
|
|
<span>Level <span id="nextLevel">1</span></span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="levelProgress" style="width:0%"></div>
|
|
</div>
|
|
<p style="margin-top:0.75rem;color:var(--muted-foreground);font-size:0.875rem">
|
|
<span id="xpCurrent">0</span> / <span id="xpRequired">100</span> XP
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-leaderboard" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Leaderboard</h1>
|
|
<p class="page-subtitle">See who's on top</p>
|
|
</div>
|
|
|
|
<div class="tabs">
|
|
<div class="tab active" data-lb-type="all">All Time</div>
|
|
<div class="tab" data-lb-type="monthly">Monthly</div>
|
|
<div class="tab" data-lb-type="weekly">Weekly</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body" id="leaderboardList">
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">🏆</div>
|
|
<p>Select a server to view the leaderboard</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-achievements" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Achievements</h1>
|
|
<p class="page-subtitle">Collect badges and show off your accomplishments</p>
|
|
</div>
|
|
|
|
<div class="achievement-grid" id="achievementGrid">
|
|
<div class="empty-state" style="grid-column:1/-1">
|
|
<div class="empty-state-icon">🎖️</div>
|
|
<p>Select a server to view achievements</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-quests" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Quests</h1>
|
|
<p class="page-subtitle">Complete challenges to earn rewards</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body" id="questList">
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">🎯</div>
|
|
<p>Select a server to view quests</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-shop" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Shop</h1>
|
|
<p class="page-subtitle">Spend your XP on rewards</p>
|
|
</div>
|
|
|
|
<div class="shop-grid" id="shopGrid">
|
|
<div class="empty-state" style="grid-column:1/-1">
|
|
<div class="empty-state-icon">🛒</div>
|
|
<p>Select a server to view shop items</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-inventory" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Inventory</h1>
|
|
<p class="page-subtitle">Your purchased items and perks</p>
|
|
</div>
|
|
|
|
<div class="shop-grid" id="inventoryGrid">
|
|
<div class="empty-state" style="grid-column:1/-1">
|
|
<div class="empty-state-icon">🎒</div>
|
|
<p>Your inventory is empty</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-admin-xp" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">XP Settings</h1>
|
|
<p class="page-subtitle">Configure XP earning for your server</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<p style="color:var(--muted-foreground)">Use <code>/xp-settings</code> in Discord to configure XP settings. Web-based configuration coming soon!</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-admin-quests" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Manage Quests</h1>
|
|
<p class="page-subtitle">Create and edit server quests</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<p style="color:var(--muted-foreground)">Use <code>/quests-manage</code> in Discord to manage quests. Web-based management coming soon!</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-admin-achievements" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Manage Achievements</h1>
|
|
<p class="page-subtitle">Create custom achievements</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<p style="color:var(--muted-foreground)">Use <code>/achievements-manage</code> in Discord to manage achievements. Web-based management coming soon!</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-admin-shop" class="page hidden">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Manage Shop</h1>
|
|
<p class="page-subtitle">Configure shop items and prices</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<p style="color:var(--muted-foreground)">Use <code>/shop-manage</code> in Discord to manage shop items. Web-based management coming soon!</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
let currentUser = null;
|
|
let currentGuild = null;
|
|
let currentPage = 'profile';
|
|
|
|
async function init() {
|
|
try {
|
|
const res = await fetch('/api/me');
|
|
if (!res.ok) {
|
|
document.getElementById('loginPrompt').classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
currentUser = await res.json();
|
|
document.getElementById('loginPrompt').classList.add('hidden');
|
|
document.getElementById('app').classList.remove('hidden');
|
|
|
|
document.getElementById('userAvatar').src = currentUser.avatarUrl;
|
|
document.getElementById('userName').textContent = currentUser.globalName || currentUser.username;
|
|
|
|
const select = document.getElementById('guildSelect');
|
|
currentUser.guilds.forEach(g => {
|
|
const opt = document.createElement('option');
|
|
opt.value = g.id;
|
|
opt.textContent = g.name;
|
|
opt.dataset.isAdmin = g.isAdmin;
|
|
select.appendChild(opt);
|
|
});
|
|
|
|
select.addEventListener('change', () => {
|
|
currentGuild = select.value;
|
|
const isAdmin = select.selectedOptions[0]?.dataset.isAdmin === 'true';
|
|
document.getElementById('adminSection').style.display = isAdmin ? 'block' : 'none';
|
|
loadPageData();
|
|
});
|
|
|
|
if (currentUser.guilds.length > 0) {
|
|
select.value = currentUser.guilds[0].id;
|
|
select.dispatchEvent(new Event('change'));
|
|
}
|
|
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
|
|
item.classList.add('active');
|
|
currentPage = item.dataset.page;
|
|
showPage(currentPage);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('[data-lb-type]').forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
document.querySelectorAll('[data-lb-type]').forEach(t => t.classList.remove('active'));
|
|
tab.classList.add('active');
|
|
loadLeaderboard(tab.dataset.lbType);
|
|
});
|
|
});
|
|
|
|
await loadProfile();
|
|
|
|
} catch (e) {
|
|
console.error('Init error:', e);
|
|
document.getElementById('loginPrompt').classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
function showPage(page) {
|
|
document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
|
|
document.getElementById('page-' + page)?.classList.remove('hidden');
|
|
loadPageData();
|
|
}
|
|
|
|
async function loadPageData() {
|
|
if (!currentGuild) return;
|
|
|
|
switch (currentPage) {
|
|
case 'profile': await loadProfile(); break;
|
|
case 'leaderboard': await loadLeaderboard('all'); break;
|
|
case 'achievements': await loadAchievements(); break;
|
|
case 'quests': await loadQuests(); break;
|
|
case 'shop': await loadShop(); break;
|
|
case 'inventory': await loadInventory(); break;
|
|
}
|
|
}
|
|
|
|
async function loadProfile() {
|
|
if (!currentUser) return;
|
|
|
|
try {
|
|
const [profileRes, statsRes] = await Promise.all([
|
|
fetch('/api/profile/' + currentUser.id),
|
|
currentGuild ? fetch('/api/stats/' + currentUser.id + '/' + currentGuild) : null
|
|
]);
|
|
|
|
const profileData = await profileRes.json();
|
|
const statsData = statsRes ? await statsRes.json() : { stats: null };
|
|
|
|
if (profileData.linked && profileData.profile) {
|
|
const p = profileData.profile;
|
|
const xp = p.xp || 0;
|
|
const level = Math.floor(Math.sqrt(xp / 100));
|
|
const nextLevel = level + 1;
|
|
const currentLevelXp = level * level * 100;
|
|
const nextLevelXp = nextLevel * nextLevel * 100;
|
|
const progress = ((xp - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100;
|
|
|
|
document.getElementById('statXp').textContent = xp.toLocaleString();
|
|
document.getElementById('statLevel').textContent = level;
|
|
document.getElementById('statPrestige').textContent = 'Prestige ' + (p.prestigeLevel || 0);
|
|
document.getElementById('statStreak').textContent = p.dailyStreak || 0;
|
|
document.getElementById('userLevel').textContent = 'Level ' + level;
|
|
|
|
document.getElementById('currentLevel').textContent = level;
|
|
document.getElementById('nextLevel').textContent = nextLevel;
|
|
document.getElementById('xpCurrent').textContent = (xp - currentLevelXp).toLocaleString();
|
|
document.getElementById('xpRequired').textContent = (nextLevelXp - currentLevelXp).toLocaleString();
|
|
document.getElementById('levelProgress').style.width = progress + '%';
|
|
}
|
|
|
|
if (statsData.stats) {
|
|
const s = statsData.stats;
|
|
document.getElementById('statMessages').textContent = (s.messages_sent || 0).toLocaleString();
|
|
const voiceMinutes = s.voice_minutes || 0;
|
|
document.getElementById('statVoice').textContent = Math.floor(voiceMinutes / 60) + 'h';
|
|
}
|
|
|
|
} catch (e) {
|
|
console.error('Profile load error:', e);
|
|
}
|
|
}
|
|
|
|
async function loadLeaderboard(type) {
|
|
if (!currentGuild) return;
|
|
|
|
const container = document.getElementById('leaderboardList');
|
|
container.innerHTML = '<div class="loading-spinner"></div>';
|
|
|
|
try {
|
|
const res = await fetch('/api/leaderboard/' + currentGuild + '?type=' + type + '&limit=25');
|
|
const data = await res.json();
|
|
|
|
if (!data.leaderboard || data.leaderboard.length === 0) {
|
|
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🏆</div><p>No leaderboard data yet</p></div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = data.leaderboard.map((entry, i) => `
|
|
<div class="leaderboard-item ${i < 3 ? 'top-3' : ''}">
|
|
<div class="leaderboard-rank">${i + 1}</div>
|
|
<img class="leaderboard-avatar" src="${entry.user_profiles?.avatar_url || ''}" alt="">
|
|
<div class="leaderboard-name">${entry.user_profiles?.username || 'Unknown'}</div>
|
|
<div class="leaderboard-xp">${(entry.xp || 0).toLocaleString()} XP</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
} catch (e) {
|
|
container.innerHTML = '<div class="empty-state"><p>Failed to load leaderboard</p></div>';
|
|
}
|
|
}
|
|
|
|
async function loadAchievements() {
|
|
if (!currentGuild) return;
|
|
|
|
const container = document.getElementById('achievementGrid');
|
|
container.innerHTML = '<div class="loading-spinner"></div>';
|
|
|
|
try {
|
|
const res = await fetch('/api/achievements/' + currentGuild);
|
|
const data = await res.json();
|
|
|
|
if (!data.achievements || data.achievements.length === 0) {
|
|
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">🎖️</div><p>No achievements configured for this server</p></div>';
|
|
return;
|
|
}
|
|
|
|
const earnedIds = new Set(data.userAchievements?.map(a => a.achievement_id) || []);
|
|
document.getElementById('statAchievements').textContent = earnedIds.size;
|
|
|
|
container.innerHTML = data.achievements.map(a => `
|
|
<div class="achievement-item ${earnedIds.has(a.id) ? '' : 'locked'}">
|
|
<div class="achievement-icon">${a.icon || '🏅'}</div>
|
|
<div class="achievement-info">
|
|
<h4>${a.name}</h4>
|
|
<p>${a.description || ''}</p>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
} catch (e) {
|
|
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><p>Failed to load achievements</p></div>';
|
|
}
|
|
}
|
|
|
|
async function loadQuests() {
|
|
if (!currentGuild) return;
|
|
|
|
const container = document.getElementById('questList');
|
|
container.innerHTML = '<div class="loading-spinner"></div>';
|
|
|
|
try {
|
|
const res = await fetch('/api/quests/' + currentGuild);
|
|
const data = await res.json();
|
|
|
|
if (!data.quests || data.quests.length === 0) {
|
|
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🎯</div><p>No active quests</p></div>';
|
|
return;
|
|
}
|
|
|
|
const userQuestMap = {};
|
|
(data.userQuests || []).forEach(uq => userQuestMap[uq.quest_id] = uq);
|
|
|
|
container.innerHTML = data.quests.map(q => {
|
|
const uq = userQuestMap[q.id];
|
|
const progress = uq?.progress || 0;
|
|
const pct = Math.min(100, (progress / q.target_value) * 100);
|
|
|
|
return `
|
|
<div class="quest-item">
|
|
<div class="quest-info">
|
|
<div class="quest-name">${q.name}</div>
|
|
<div class="quest-desc">${q.description || ''}</div>
|
|
<div class="progress-bar" style="margin-top:0.5rem">
|
|
<div class="progress-fill" style="width:${pct}%"></div>
|
|
</div>
|
|
</div>
|
|
<div class="quest-progress">
|
|
<div class="quest-progress-text">${progress}/${q.target_value}</div>
|
|
<div style="font-size:0.75rem;color:var(--muted-foreground)">${q.xp_reward} XP</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
} catch (e) {
|
|
container.innerHTML = '<div class="empty-state"><p>Failed to load quests</p></div>';
|
|
}
|
|
}
|
|
|
|
async function loadShop() {
|
|
if (!currentGuild) return;
|
|
|
|
const container = document.getElementById('shopGrid');
|
|
container.innerHTML = '<div class="loading-spinner"></div>';
|
|
|
|
try {
|
|
const res = await fetch('/api/shop/' + currentGuild);
|
|
const data = await res.json();
|
|
|
|
if (!data.items || data.items.length === 0) {
|
|
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">🛒</div><p>No shop items available</p></div>';
|
|
return;
|
|
}
|
|
|
|
const icons = { title: '📛', badge: '🏅', color: '🎨', role: '👑', xp_boost: '⚡', custom: '✨' };
|
|
|
|
container.innerHTML = data.items.map(item => `
|
|
<div class="shop-item">
|
|
<div class="shop-icon">${icons[item.item_type] || '🎁'}</div>
|
|
<div class="shop-name">${item.name}</div>
|
|
<div class="shop-price">${item.price.toLocaleString()} XP</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
} catch (e) {
|
|
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><p>Failed to load shop</p></div>';
|
|
}
|
|
}
|
|
|
|
async function loadInventory() {
|
|
if (!currentGuild) return;
|
|
|
|
const container = document.getElementById('inventoryGrid');
|
|
container.innerHTML = '<div class="loading-spinner"></div>';
|
|
|
|
try {
|
|
const res = await fetch('/api/inventory/' + currentGuild);
|
|
const data = await res.json();
|
|
|
|
if (!data.inventory || data.inventory.length === 0) {
|
|
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">🎒</div><p>Your inventory is empty</p></div>';
|
|
return;
|
|
}
|
|
|
|
const icons = { title: '📛', badge: '🏅', color: '🎨', role: '👑', xp_boost: '⚡', custom: '✨' };
|
|
|
|
container.innerHTML = data.inventory.map(item => `
|
|
<div class="shop-item ${item.equipped ? 'equipped' : ''}">
|
|
<div class="shop-icon">${icons[item.shop_items?.item_type] || '🎁'}</div>
|
|
<div class="shop-name">${item.shop_items?.name || 'Unknown Item'}</div>
|
|
<div style="font-size:0.75rem;color:var(--muted-foreground)">${item.equipped ? 'Equipped' : ''}</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
} catch (e) {
|
|
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><p>Failed to load inventory</p></div>';
|
|
}
|
|
}
|
|
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|