Design updates and fresh export prep
This commit is contained in:
parent
021f5fc304
commit
96936a302a
18 changed files with 292 additions and 1740 deletions
158
assets/js/pages/games/whittler.js
Normal file
158
assets/js/pages/games/whittler.js
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* Whittler Game High Scores
|
||||
* Fetches and displays high scores from the ritual.sh high score API
|
||||
*/
|
||||
|
||||
class WhittlerHighScores {
|
||||
constructor() {
|
||||
this.apiUrl = "https://api.ritual.sh/highscore";
|
||||
this.gameId = "whittling-clicker";
|
||||
this.container = document.getElementById("whittler-highscores");
|
||||
this.maxScores = 10;
|
||||
|
||||
if (this.container) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.loadHighScores();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch high scores from the API
|
||||
*/
|
||||
async loadHighScores() {
|
||||
try {
|
||||
this.showLoading();
|
||||
|
||||
const response = await fetch(
|
||||
`${this.apiUrl}/leaderboard?game_id=${this.gameId}&limit=${this.maxScores}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.scores) {
|
||||
this.renderScores(data.scores);
|
||||
} else {
|
||||
throw new Error(data.error || "Failed to load high scores");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading high scores:", error);
|
||||
this.showError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render high scores to the DOM
|
||||
*/
|
||||
renderScores(scores) {
|
||||
if (!scores || scores.length === 0) {
|
||||
this.container.innerHTML = `
|
||||
<div class="no-scores">
|
||||
<p>No high scores yet. Be the first!</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const scoresHTML = `
|
||||
<ol class="highscore-list">
|
||||
${scores
|
||||
.map(
|
||||
(score, index) => `
|
||||
<li class="highscore-entry ${index === 0 ? "top-score" : ""}">
|
||||
<span class="rank">#${index + 1}</span>
|
||||
<span class="player-name">${this.escapeHtml(this.truncateName(score.playerName || "Anonymous"))}</span>
|
||||
<span class="completion-time">${this.formatTime(score.formattedTime)}</span>
|
||||
</li>
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</ol>
|
||||
`;
|
||||
|
||||
this.container.innerHTML = scoresHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show loading state
|
||||
*/
|
||||
showLoading() {
|
||||
this.container.innerHTML = `
|
||||
<div class="loading-scores">
|
||||
<p>Loading high scores...</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error state
|
||||
*/
|
||||
showError() {
|
||||
this.container.innerHTML = `
|
||||
<div class="error-scores">
|
||||
<p>Failed to load high scores.</p>
|
||||
<button onclick="window.whittlerHighScores.loadHighScores()">Retry</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format completion time (seconds) to readable format
|
||||
* Expects completionTime in seconds or HH:MM:SS format
|
||||
*/
|
||||
formatTime(time) {
|
||||
if (typeof time === "string" && time.includes(":")) {
|
||||
// Already formatted
|
||||
return time;
|
||||
}
|
||||
|
||||
const seconds = parseInt(time);
|
||||
if (isNaN(seconds)) return "Unknown";
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m ${secs}s`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}m ${secs}s`;
|
||||
} else {
|
||||
return `${secs}s`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate player name to max length
|
||||
*/
|
||||
truncateName(name, maxLength = 15) {
|
||||
if (name.length <= maxLength) {
|
||||
return name;
|
||||
}
|
||||
return name.substring(0, maxLength) + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML to prevent XSS
|
||||
*/
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize high scores when DOM is ready
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.whittlerHighScores = new WhittlerHighScores();
|
||||
});
|
||||
} else {
|
||||
window.whittlerHighScores = new WhittlerHighScores();
|
||||
}
|
||||
|
|
@ -157,5 +157,80 @@ body.games.whittler {
|
|||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.game-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
|
||||
@include media-up(md) {
|
||||
flex-direction: row;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
border-image: url("/images/games/whittler/panelInset_brown.png") 10 10 /
|
||||
10px 10px / 1px round round;
|
||||
filter: drop-shadow(5px 5px 5px rgba(0, 0, 0, 0.6));
|
||||
margin-bottom: 2rem;
|
||||
padding: 5px;
|
||||
|
||||
a {
|
||||
color: #563c23;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 1em;
|
||||
background-color: #97714a;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
.game-highscores {
|
||||
ol {
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
li.highscore-entry {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 1rem;
|
||||
color: #563c23;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px dotted #563c23;
|
||||
padding: 0.5em;
|
||||
&.top-score {
|
||||
color: #f6e6c4;
|
||||
border: 1px dashed #563c23;
|
||||
border-radius: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.game-details {
|
||||
p {
|
||||
text-align: left;
|
||||
color: #563c23;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
li {
|
||||
text-align: left;
|
||||
color: #563c23;
|
||||
margin-bottom: 0.5rem;
|
||||
list-style: "🪵 ";
|
||||
list-style-position: outside;
|
||||
margin-left: 1.25rem;
|
||||
strong {
|
||||
color: #f6e6c4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue