ritual.sh/assets/js/pages/games/arcade.js
2026-01-30 14:14:08 +00:00

348 lines
8.2 KiB
JavaScript

/**
* Arcade Games List - Interactive Controls
* Handles keyboard navigation, joystick visuals, button presses, and sound effects
*/
class ArcadeGamesController {
constructor() {
// DOM elements
this.gamesGrid = document.getElementById('games-grid');
this.joystick = document.getElementById('arcade-joystick');
this.selectButton = document.getElementById('button-select');
this.backButton = document.getElementById('button-back');
this.soundToggle = document.getElementById('sound-toggle');
this.insertCoinMessage = document.querySelector('.insert-coin-message');
// State
this.currentGameIndex = 0;
this.gameCards = [];
this.soundEnabled = true;
this.sounds = {};
// Initialize
if (this.gamesGrid) {
this.init();
}
}
/**
* Initialize the arcade controller
*/
init() {
this.gameCards = Array.from(this.gamesGrid.querySelectorAll('.game-card'));
// Select first game by default
if (this.gameCards.length > 0) {
this.selectGame(0);
} else {
this.showInsertCoin();
}
// Load sound effects
this.loadSounds();
// Set up event listeners
this.setupKeyboardControls();
this.setupMouseControls();
this.setupSoundToggle();
// Hide insert coin message after a moment
setTimeout(() => this.hideInsertCoin(), 2000);
}
/**
* Load sound effect files
*/
loadSounds() {
const soundFiles = {
move: '/audio/arcade-move.mp3',
select: '/audio/arcade-select.mp3',
coin: '/audio/arcade-coin.mp3',
back: '/audio/arcade-back.mp3'
};
Object.keys(soundFiles).forEach(key => {
this.sounds[key] = new Audio(soundFiles[key]);
this.sounds[key].volume = 0.3;
// Preload
this.sounds[key].load();
});
}
/**
* Play a sound effect
*/
playSound(soundName) {
if (!this.soundEnabled || !this.sounds[soundName]) return;
// Clone and play to allow overlapping sounds
const sound = this.sounds[soundName].cloneNode();
sound.volume = this.sounds[soundName].volume;
sound.play().catch(err => console.warn('Sound play failed:', err));
}
/**
* Set up keyboard event listeners
*/
setupKeyboardControls() {
document.addEventListener('keydown', (e) => {
// Prevent default for arrow keys and space
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' ', 'Enter'].includes(e.key)) {
e.preventDefault();
}
switch (e.key) {
case 'ArrowUp':
this.moveUp();
this.updateJoystickVisual('up');
break;
case 'ArrowDown':
this.moveDown();
this.updateJoystickVisual('down');
break;
case 'ArrowLeft':
this.moveLeft();
this.updateJoystickVisual('left');
break;
case 'ArrowRight':
this.moveRight();
this.updateJoystickVisual('right');
break;
case 'Enter':
case ' ':
this.pressSelect();
this.updateButtonVisual('select', true);
break;
case 'Escape':
this.pressBack();
this.updateButtonVisual('back', true);
break;
}
});
// Reset joystick and button visuals on key up
document.addEventListener('keyup', (e) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
this.updateJoystickVisual('neutral');
}
if (['Enter', ' '].includes(e.key)) {
this.updateButtonVisual('select', false);
}
if (e.key === 'Escape') {
this.updateButtonVisual('back', false);
}
});
}
/**
* Set up mouse/touch event listeners
*/
setupMouseControls() {
// Game cards click
this.gameCards.forEach((card, index) => {
card.addEventListener('click', () => {
this.selectGame(index);
this.pressSelect();
});
card.addEventListener('mouseenter', () => {
this.selectGame(index);
});
});
// Buttons
this.selectButton.addEventListener('click', () => {
this.pressSelect();
});
this.backButton.addEventListener('click', () => {
this.pressBack();
});
}
/**
* Set up sound toggle button
*/
setupSoundToggle() {
this.soundToggle.addEventListener('click', () => {
this.soundEnabled = !this.soundEnabled;
const soundOn = this.soundToggle.querySelector('.sound-on');
const soundOff = this.soundToggle.querySelector('.sound-off');
if (this.soundEnabled) {
soundOn.style.display = 'inline-block';
soundOff.style.display = 'none';
this.playSound('coin');
} else {
soundOn.style.display = 'none';
soundOff.style.display = 'inline-block';
}
});
}
/**
* Navigate up in grid
*/
moveUp() {
// Calculate columns based on grid layout
const columns = this.calculateColumns();
const newIndex = this.currentGameIndex - columns;
if (newIndex >= 0) {
this.selectGame(newIndex);
this.playSound('move');
}
}
/**
* Navigate down in grid
*/
moveDown() {
const columns = this.calculateColumns();
const newIndex = this.currentGameIndex + columns;
if (newIndex < this.gameCards.length) {
this.selectGame(newIndex);
this.playSound('move');
}
}
/**
* Navigate left in grid
*/
moveLeft() {
if (this.currentGameIndex > 0) {
this.selectGame(this.currentGameIndex - 1);
this.playSound('move');
}
}
/**
* Navigate right in grid
*/
moveRight() {
if (this.currentGameIndex < this.gameCards.length - 1) {
this.selectGame(this.currentGameIndex + 1);
this.playSound('move');
}
}
/**
* Calculate number of columns in grid
*/
calculateColumns() {
if (this.gameCards.length === 0) return 1;
const firstCard = this.gameCards[0];
const gridWidth = this.gamesGrid.offsetWidth;
const cardWidth = firstCard.offsetWidth;
const gap = 20; // From CSS
return Math.floor((gridWidth + gap) / (cardWidth + gap)) || 1;
}
/**
* Select a game card
*/
selectGame(index) {
// Remove previous selection
this.gameCards.forEach(card => card.classList.remove('selected'));
// Add new selection
this.currentGameIndex = index;
this.gameCards[index].classList.add('selected');
// Scroll into view if needed
this.gameCards[index].scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
/**
* Press select button (navigate to game)
*/
pressSelect() {
this.playSound('select');
const selectedCard = this.gameCards[this.currentGameIndex];
const link = selectedCard.querySelector('a');
if (link) {
// Add visual feedback
selectedCard.style.transform = 'scale(0.95)';
setTimeout(() => {
selectedCard.style.transform = '';
window.location.href = link.href;
}, 150);
}
}
/**
* Press back button (go to main page or previous)
*/
pressBack() {
this.playSound('back');
// Navigate back in history or to home
if (window.history.length > 1) {
window.history.back();
} else {
window.location.href = '/';
}
}
/**
* Update joystick visual state
*/
updateJoystickVisual(direction) {
this.joystick.classList.remove('up', 'down', 'left', 'right');
if (direction !== 'neutral') {
this.joystick.classList.add(direction);
}
}
/**
* Update button visual state
*/
updateButtonVisual(button, pressed) {
const buttonElement = button === 'select' ? this.selectButton : this.backButton;
if (pressed) {
buttonElement.classList.add('pressed');
} else {
buttonElement.classList.remove('pressed');
// Slight delay to show button release
setTimeout(() => {
buttonElement.classList.remove('pressed');
}, 100);
}
}
/**
* Show insert coin message
*/
showInsertCoin() {
this.insertCoinMessage.classList.add('visible');
}
/**
* Hide insert coin message
*/
hideInsertCoin() {
this.insertCoinMessage.classList.remove('visible');
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
new ArcadeGamesController();
});
} else {
new ArcadeGamesController();
}