forked from mico/idle_moloch
Merges main
This commit is contained in:
commit
d83e5323d2
114
app/src/components/Leaderboard.tsx
Normal file
114
app/src/components/Leaderboard.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import styles from '../styles/Leaderboard.module.css'
|
||||||
|
import { TopEarnersResponse, TopRaidersResponse } from '../types/leaderboard'
|
||||||
|
import { formatUnits } from 'viem'
|
||||||
|
|
||||||
|
const SUBGRAPH_URL = 'https://api.studio.thegraph.com/query/75782/slay-the-moloch-base-sepolia/version/latest'
|
||||||
|
|
||||||
|
const Leaderboard = () => {
|
||||||
|
const [topEarners, setTopEarners] = useState<TopEarnersResponse>()
|
||||||
|
const [topRaiders, setTopRaiders] = useState<TopRaidersResponse>()
|
||||||
|
const [activeTab, setActiveTab] = useState<'earners' | 'raiders'>('earners')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchLeaderboards = async () => {
|
||||||
|
try {
|
||||||
|
// Fetch top earners
|
||||||
|
const earnersResponse = await fetch(SUBGRAPH_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: `{
|
||||||
|
armies(first: 10, orderBy: profitPerSecond, orderDirection: desc) {
|
||||||
|
player {
|
||||||
|
id
|
||||||
|
totalMinted
|
||||||
|
currentBalance
|
||||||
|
numberOfRaids
|
||||||
|
}
|
||||||
|
profitPerSecond
|
||||||
|
molochDenierLevel
|
||||||
|
apprenticeLevel
|
||||||
|
anointedLevel
|
||||||
|
championLevel
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const earnersData = await earnersResponse.json()
|
||||||
|
setTopEarners({ armies: earnersData.data.armies })
|
||||||
|
|
||||||
|
// Fetch top raiders
|
||||||
|
const raidersResponse = await fetch(SUBGRAPH_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: `{
|
||||||
|
players(first: 10, orderBy: numberOfRaids, orderDirection: desc) {
|
||||||
|
id
|
||||||
|
numberOfRaids
|
||||||
|
totalMinted
|
||||||
|
currentBalance
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const raidersData = await raidersResponse.json()
|
||||||
|
setTopRaiders({ players: raidersData.data.players })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching leaderboard:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchLeaderboards()
|
||||||
|
const interval = setInterval(fetchLeaderboards, 30000) // Refresh every 30 seconds
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.leaderboard}>
|
||||||
|
<h2 className={styles.title}>Leaderboard</h2>
|
||||||
|
|
||||||
|
<div className={styles.tabs}>
|
||||||
|
<button
|
||||||
|
className={`${styles.tab} ${activeTab === 'earners' ? styles.active : ''}`}
|
||||||
|
onClick={() => setActiveTab('earners')}
|
||||||
|
>
|
||||||
|
Top Earners
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${styles.tab} ${activeTab === 'raiders' ? styles.active : ''}`}
|
||||||
|
onClick={() => setActiveTab('raiders')}
|
||||||
|
>
|
||||||
|
Top Raiders
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{activeTab === 'earners' && (
|
||||||
|
<div className={styles.list}>
|
||||||
|
{topEarners?.armies.map((army, index) => (
|
||||||
|
<div key={army.player.id} className={styles.item}>
|
||||||
|
<span className={styles.rank}>#{index + 1}</span>
|
||||||
|
<span className={styles.address}>{army.player.id.slice(0, 6)}...{army.player.id.slice(-4)}</span>
|
||||||
|
<span className={styles.stat}>{formatUnits(BigInt(army.profitPerSecond), 4)} GELD/s</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'raiders' && (
|
||||||
|
<div className={styles.list}>
|
||||||
|
{topRaiders?.players.map((player, index) => (
|
||||||
|
<div key={player.id} className={styles.item}>
|
||||||
|
<span className={styles.rank}>#{index + 1}</span>
|
||||||
|
<span className={styles.address}>{player.id.slice(0, 6)}...{player.id.slice(-4)}</span>
|
||||||
|
<span className={styles.stat}>{player.numberOfRaids} raids</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Leaderboard
|
||||||
@ -1,51 +1,219 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import styles from "../styles/Army.module.css"
|
import { usePlayer } from "../providers/PlayerProvider";
|
||||||
|
import styles from "../styles/Army.module.css";
|
||||||
|
|
||||||
const tavernerQuotes = [
|
// Quote categories based on total minted GELD
|
||||||
|
const PROGRESSION_TIERS = {
|
||||||
|
BEGINNER: BigInt(1000000),
|
||||||
|
NOVICE: BigInt(10000000),
|
||||||
|
INTERMEDIATE: BigInt(100000000),
|
||||||
|
EXPERIENCED: BigInt(1000000000),
|
||||||
|
EXPERT: BigInt(10000000000),
|
||||||
|
MASTER: BigInt(100000000000),
|
||||||
|
EPIC: BigInt(1000000000000),
|
||||||
|
LEGENDARY: BigInt(10000000000000),
|
||||||
|
GUILD_LEADER: BigInt(100000000000000),
|
||||||
|
DIVINE: BigInt(1000000000000000),
|
||||||
|
MAXIMUM: BigInt(10000000000000000),
|
||||||
|
};
|
||||||
|
|
||||||
|
const quotes = {
|
||||||
|
BEGINNER: [
|
||||||
|
"Well, well... another fresh face looking to be a hero. At least you're holding your weapon the right way up.",
|
||||||
|
"Ha! Your armor's so shiny I can see my reflection. Give it a week, rookie.",
|
||||||
|
"Another raider? The rats in the cellar are trembling... with laughter.",
|
||||||
|
"Ah, the optimism of the inexperienced. Refreshing, like a cool drink before a hard fall.",
|
||||||
|
"Don't worry, we keep the bandages cheap for newcomers like yourself.",
|
||||||
|
"I've seen tougher looking raiders, but hey, everyone starts somewhere!",
|
||||||
|
"You, a raider? Well, beggars can't be choosers, I suppose.",
|
||||||
|
"Try not to dent your armor on the doorframe on your way out.",
|
||||||
|
"I remember when I was green as you. Actually, I don't.",
|
||||||
|
"The practice dummy out back is taking bets against you. Just thought you should know.",
|
||||||
|
],
|
||||||
|
NOVICE: [
|
||||||
|
"Back again? You're lasting longer than most of the fresh blood.",
|
||||||
|
"You're developing some calluses finally. Good, you'll need them.",
|
||||||
|
"Well, you haven't disappeared yet. That's... something.",
|
||||||
|
"Starting to walk with a bit of confidence, eh? Don't let it go to your head.",
|
||||||
|
"Your reputation precedes you... mind you, it's not saying much.",
|
||||||
|
"The local wolves don't laugh quite as hard when they see you coming now.",
|
||||||
|
"You're making progress! The practice dummy finally lost some coins.",
|
||||||
|
"I see your training is coming along. You only singed your eyebrows twice this week.",
|
||||||
|
"Getting better with that weapon. You actually hit what you're aiming at... occasionally.",
|
||||||
|
"The raiders have stopped taking bets on when you'll quit. Progress!",
|
||||||
|
],
|
||||||
|
INTERMEDIATE: [
|
||||||
|
"Now there's a familiar face that's earned their drink!",
|
||||||
|
"The usual? You've been around long enough to have a usual!",
|
||||||
|
"Looking more battle-worn these days. It suits you.",
|
||||||
|
"Your reputation's growing. Heard some kids playing 'mighty raider' in the street.",
|
||||||
|
"The local raiders actually know your name now. Well, most of it.",
|
||||||
|
"That's quite a collection of scars you're building. Each one's a lesson, eh?",
|
||||||
|
"You carry yourself like a real adventurer these days.",
|
||||||
|
"Those monsters in the forest? They're starting to learn your name.",
|
||||||
|
"Your deeds are becoming tavern tales. Small tales, but tales nonetheless.",
|
||||||
|
"That gleam in your eye... you're starting to believe in yourself, aren't you?",
|
||||||
|
],
|
||||||
|
EXPERIENCED: [
|
||||||
|
"Honor to serve you, friend. Your exploits bring customers to my humble establishment.",
|
||||||
|
"The bards are starting to write songs about you. Decent ones, too!",
|
||||||
|
"Ah, the hero of the hour! Your usual table awaits.",
|
||||||
|
"When they speak of a great raider these days, your name comes up!",
|
||||||
|
"Your presence honors us. What tales will you bring today?",
|
||||||
|
"The young ones whisper your name in awe. Remember when that was you?",
|
||||||
|
"Your legend grows with each passing moon. As does my profit from telling your tales!",
|
||||||
|
"The guild speak of your deeds with respect.",
|
||||||
|
"You've come so far from that nervous newcomer who first walked through my door.",
|
||||||
|
"They say you've met Moloch face to face. Now that's a tale worth hearing!",
|
||||||
|
],
|
||||||
|
EXPERT: [
|
||||||
|
"My most esteemed patron! The usual bottle of our finest?",
|
||||||
|
"Blessed are we to host a hero of your caliber!",
|
||||||
|
"Your very presence brings honor to this humble establishment.",
|
||||||
|
"The stuff of legends walks among us, friends!",
|
||||||
|
"They say you've slain dragons now. Dragons! In my tavern!",
|
||||||
|
"Your name echoes through the kingdoms. Yet you still honor us with your presence.",
|
||||||
|
"Ah, the mighty raider returns! Shall I dust off the good chalice?",
|
||||||
|
"The bards fight over who gets to sing your tales first.",
|
||||||
|
"Your achievements bring glory to all who follow your footsteps.",
|
||||||
|
"Even the ancient raiders speak of your deeds with reverence.",
|
||||||
|
],
|
||||||
|
MASTER: [
|
||||||
|
"You humble us with your magnificent presence!",
|
||||||
|
"Champions and heroes seek you out for guidance now. How far you've come!",
|
||||||
|
"They say your name in whispers in the dark kingdoms of Moloch. In fear.",
|
||||||
|
"Your legend spreads even to distant shores. Travelers speak of your deeds in awe.",
|
||||||
|
"The very mountains seem to bow in your presence, great one.",
|
||||||
|
"Your power radiates like the sun. You've mastered arts few can comprehend.",
|
||||||
|
"To think, I've watched you grow from newcomer to living legend!",
|
||||||
|
"They say you've went berserk naked in a raid. The gods must surely know your name.",
|
||||||
|
"Your mastery of the raider's arts is unmatched in all the realms.",
|
||||||
|
"The ancient prophecies speak of one such as you...",
|
||||||
|
],
|
||||||
|
EPIC: [
|
||||||
|
"They say you've mastered every weapon in the realm!",
|
||||||
|
"Is it true you've tamed the ancient dragons?",
|
||||||
|
"The Moloch speaks your name in terror!",
|
||||||
|
"Your magic reshapes the very world!",
|
||||||
|
"None have delved deeper into the ancient dungeons!",
|
||||||
|
],
|
||||||
|
LEGENDARY: [
|
||||||
|
"The greatest raider of our age graces us! All hail!",
|
||||||
|
"Your presence blesses this humble tavern, O mighty one!",
|
||||||
|
"Let it be known - a living legend walks among us!",
|
||||||
|
"The realm's greatest champion returns to honor us!",
|
||||||
|
"Your deeds will be told for a thousand years!",
|
||||||
|
"Gods walk among us this day! Welcome, mighty hero!",
|
||||||
|
"Champion of champions! Slayer of the Moloch!",
|
||||||
|
"Your very footsteps shake the foundations of reality!",
|
||||||
|
"None have achieved what you have, great one!",
|
||||||
|
"The stars themselves dim in your presence!",
|
||||||
|
],
|
||||||
|
GUILD_LEADER: [
|
||||||
|
"The guild masters seek your counsel now, don't they?",
|
||||||
|
"Your followers grow more numerous by the day!",
|
||||||
|
"Even kings bow to your wisdom in matters of war!",
|
||||||
|
"The greatest raider academy bears your name!",
|
||||||
|
"Your teachings will guide generations to come!",
|
||||||
|
"The realm was forever changed when you sealed the Dark Portal of Moloch!",
|
||||||
|
"Peace reigns because you guard our lands!",
|
||||||
|
"The ancient prophecies spoke true of your coming!",
|
||||||
|
"Even the gods watch your journey with interest!",
|
||||||
|
"The very fabric of magic bears your mark!",
|
||||||
|
],
|
||||||
|
DIVINE: [
|
||||||
|
"Even the Moloch fears your coming!",
|
||||||
|
"The Cohort VI champions pale before your glory!",
|
||||||
|
"The Moon shines brighter in your presence!",
|
||||||
|
"The Golden Lady herself seeks your blessing!",
|
||||||
|
"The elements themselves bend to your will!",
|
||||||
|
"The Sword of Ages chosen YOU as its wielder!",
|
||||||
|
"You've walked the Path of a Thousand Stars!",
|
||||||
|
"The Ancient Trials bow before your mastery!",
|
||||||
|
"Bearer of the Eternal Flame!",
|
||||||
|
"Even Sayonara uses your deeds as images!",
|
||||||
|
],
|
||||||
|
MAXIMUM: [
|
||||||
|
"Hamsterverse itself bends in your presence, great one!",
|
||||||
|
"Your power transcends mortal understanding!",
|
||||||
|
"Moloch trembles at your footsteps!",
|
||||||
|
"Your legend will outlive the gods themselves!",
|
||||||
|
"In all my centuries, none have achieved what you have, mighty raider!",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const EARLY_GAME_QUOTES = [
|
||||||
"There is always Moloch to be slain here...",
|
"There is always Moloch to be slain here...",
|
||||||
"We prioritize Shipping at All Costs!",
|
"We prioritize Shipping at All Costs!",
|
||||||
"Get out there RAIDER, Moloch won't Slay Himself!",
|
"Get out there RAIDER, Moloch won't Slay Himself!",
|
||||||
];
|
];
|
||||||
|
|
||||||
function PixelatedQuote() {
|
function PixelatedQuote() {
|
||||||
|
const { player } = usePlayer();
|
||||||
const [isShown, setIsShown] = useState(true);
|
const [isShown, setIsShown] = useState(true);
|
||||||
const [currentQuote, setCurrentQuote] = useState(
|
const [currentQuote, setCurrentQuote] = useState("Welcome to the Dark Forest!");
|
||||||
"Welcome to the Dark Forest!"
|
const intervalIdRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
);
|
const hasShownWelcome = useRef(false);
|
||||||
const intervalIdRef = useRef<NodeJS.Timeout | null>(null); // Define the type for Node environment compatibility
|
|
||||||
|
// Determine which tier of quotes to use based on total minted
|
||||||
|
const getQuoteTier = (totalMinted: bigint) => {
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.MAXIMUM) return "MAXIMUM";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.DIVINE) return "DIVINE";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.GUILD_LEADER) return "GUILD_LEADER";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.LEGENDARY) return "LEGENDARY";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.EPIC) return "EPIC";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.MASTER) return "MASTER";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.EXPERT) return "EXPERT";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.EXPERIENCED) return "EXPERIENCED";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.INTERMEDIATE) return "INTERMEDIATE";
|
||||||
|
if (totalMinted >= PROGRESSION_TIERS.NOVICE) return "NOVICE";
|
||||||
|
return "BEGINNER";
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (intervalIdRef.current !== null) {
|
if (intervalIdRef.current !== null) {
|
||||||
clearInterval(intervalIdRef.current);
|
clearInterval(intervalIdRef.current);
|
||||||
} // Clear interval if it exists
|
}
|
||||||
|
|
||||||
// Set up an interval to show the toast every 10 seconds
|
|
||||||
intervalIdRef.current = setInterval(() => {
|
intervalIdRef.current = setInterval(() => {
|
||||||
setCurrentQuote(
|
const totalMinted = player?.total_minted ?? BigInt(0);
|
||||||
tavernerQuotes[Math.floor(Math.random() * tavernerQuotes.length)]
|
|
||||||
);
|
// Show welcome message only once at the start
|
||||||
|
if (!hasShownWelcome.current) {
|
||||||
|
setCurrentQuote("Welcome to the Dark Forest!");
|
||||||
|
hasShownWelcome.current = true;
|
||||||
|
} else if (totalMinted < PROGRESSION_TIERS.BEGINNER) {
|
||||||
|
// Show early game quotes until player reaches beginner level
|
||||||
|
setCurrentQuote(
|
||||||
|
EARLY_GAME_QUOTES[Math.floor(Math.random() * EARLY_GAME_QUOTES.length)]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const tier = getQuoteTier(totalMinted);
|
||||||
|
const tierQuotes = quotes[tier];
|
||||||
|
setCurrentQuote(
|
||||||
|
tierQuotes[Math.floor(Math.random() * tierQuotes.length)]
|
||||||
|
);
|
||||||
|
}
|
||||||
setIsShown(true);
|
setIsShown(true);
|
||||||
|
|
||||||
// Hide the toast after 4 seconds
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsShown(false);
|
setIsShown(false);
|
||||||
}, 4000);
|
}, 4000);
|
||||||
}, 6000);
|
}, 6000);
|
||||||
|
|
||||||
// Clean up the interval on component unmount
|
|
||||||
return () => {
|
return () => {
|
||||||
if (intervalIdRef.current !== null) {
|
if (intervalIdRef.current !== null) {
|
||||||
clearInterval(intervalIdRef.current); // Clear interval using correct reference
|
clearInterval(intervalIdRef.current);
|
||||||
intervalIdRef.current = null;
|
intervalIdRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [player?.total_minted]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className={`pixel-borders pixel-borders--2 pixelFont ${styles.pixelQuote}`}
|
className={`pixel-borders pixel-borders--2 pixelFont ${styles.pixelQuote}`}
|
||||||
style={{ opacity: isShown ? 1 : 0 /* Control visibility with opacity */ }}
|
style={{ opacity: isShown ? 1 : 0 }}
|
||||||
>
|
>
|
||||||
{currentQuote}
|
{currentQuote}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import React, { useCallback } from "react"
|
import React, { useCallback, useState } from "react"
|
||||||
import styles from '../styles/Background.module.css';
|
import styles from '../styles/Background.module.css';
|
||||||
import Tower from "./Tower";
|
import Tower from "./Tower";
|
||||||
import Army from "./Army";
|
import Army from "./Army";
|
||||||
import MarchingBand from "./MarchingBand";
|
import MarchingBand from "./MarchingBand";
|
||||||
import MusicPlayer from "./MusicPlayer";
|
import MusicPlayer from "./MusicPlayer";
|
||||||
|
import Leaderboard from "./Leaderboard";
|
||||||
import { usePlayer } from "../providers/PlayerProvider";
|
import { usePlayer } from "../providers/PlayerProvider";
|
||||||
import Boss from "./Boss";
|
import Boss from "./Boss";
|
||||||
import BossInfo from "./BossInfo";
|
import BossInfo from "./BossInfo";
|
||||||
@ -20,6 +21,8 @@ const bossToMountainsClass = {
|
|||||||
|
|
||||||
const Scene = () => {
|
const Scene = () => {
|
||||||
const { isRegistered, boss } = usePlayer();
|
const { isRegistered, boss } = usePlayer();
|
||||||
|
const [isLeaderboardOpen, setIsLeaderboardOpen] = useState(false);
|
||||||
|
|
||||||
const handleMusicReady = useCallback((unmute: () => void) => {
|
const handleMusicReady = useCallback((unmute: () => void) => {
|
||||||
if (isRegistered) {
|
if (isRegistered) {
|
||||||
unmute();
|
unmute();
|
||||||
@ -40,6 +43,26 @@ const Scene = () => {
|
|||||||
<div className={`${styles.bonfire} ${styles.background_asset}`} />
|
<div className={`${styles.bonfire} ${styles.background_asset}`} />
|
||||||
<Army />
|
<Army />
|
||||||
<MusicPlayer onReady={handleMusicReady} />
|
<MusicPlayer onReady={handleMusicReady} />
|
||||||
|
<button
|
||||||
|
onClick={() => setIsLeaderboardOpen(true)}
|
||||||
|
className={styles.leaderboardButton}
|
||||||
|
title="Leaderboard"
|
||||||
|
>
|
||||||
|
📜
|
||||||
|
</button>
|
||||||
|
{isLeaderboardOpen && (
|
||||||
|
<div className={styles.leaderboardOverlay}>
|
||||||
|
<div className={styles.leaderboardContent}>
|
||||||
|
<button
|
||||||
|
className={styles.closeButton}
|
||||||
|
onClick={() => setIsLeaderboardOpen(false)}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
<Leaderboard />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -364,3 +364,69 @@
|
|||||||
transform: scale(1.02, 1.03) skew(0deg, -1deg);
|
transform: scale(1.02, 1.03) skew(0deg, -1deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaderboardButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 80px;
|
||||||
|
right: 30px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1000;
|
||||||
|
transition: all 0.2s cubic-bezier(0.265, 1.4, 0.68, 1.65);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboardButton:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboardButton:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboardOverlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboardContent {
|
||||||
|
background: var(--bg-color);
|
||||||
|
border-width: 8px;
|
||||||
|
border-image: url("/background/frame.png") 22 fill / auto space;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton:hover {
|
||||||
|
color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|||||||
70
app/src/styles/Leaderboard.module.css
Normal file
70
app/src/styles/Leaderboard.module.css
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
.leaderboard {
|
||||||
|
padding: 1rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
flex: 1;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #666;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
background: var(--hover-color);
|
||||||
|
border-color: var(--hover-color);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank {
|
||||||
|
min-width: 30px;
|
||||||
|
color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
flex: 1;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
color: #0f0;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
25
app/src/types/leaderboard.ts
Normal file
25
app/src/types/leaderboard.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export interface Player {
|
||||||
|
id: string
|
||||||
|
totalMinted: string
|
||||||
|
currentBalance: string
|
||||||
|
numberOfRaids: string
|
||||||
|
army?: Army
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Army {
|
||||||
|
player: Player
|
||||||
|
profitPerSecond: string
|
||||||
|
projectedDailyEarnings: string
|
||||||
|
molochDenierLevel: string
|
||||||
|
apprenticeLevel: string
|
||||||
|
anointedLevel: string
|
||||||
|
championLevel: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopEarnersResponse {
|
||||||
|
armies: Army[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopRaidersResponse {
|
||||||
|
players: Player[]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user