diff --git a/assets/js/pages/games/arcade.js b/assets/js/pages/games/arcade.js new file mode 100644 index 0000000..5ac7012 --- /dev/null +++ b/assets/js/pages/games/arcade.js @@ -0,0 +1,348 @@ +/** + * 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(); +} diff --git a/assets/sass/pages/games/arcade.scss b/assets/sass/pages/games/arcade.scss new file mode 100644 index 0000000..980c969 --- /dev/null +++ b/assets/sass/pages/games/arcade.scss @@ -0,0 +1,938 @@ +// ============================================ +// ARCADE GAMES LIST PAGE +// Full arcade cabinet with CRT screen, joystick, and buttons +// ============================================ + +.games-arcade-page { + min-height: 100vh; + padding: 2rem; + background: #1a1a1a; + position: relative; + + @include media-down(lg) { + padding: 0; + } + + .desk { + z-index: 1; + position: fixed; + height: 35%; + } + + .arcade-neon { + pointer-events: none; + .neon-sign { + right: 0; + left: auto; + } + } + + .arcade-lamp { + position: absolute; + bottom: 30%; + left: 10%; + z-index: 2; + pointer-events: none; + + .lava-lamp-container { + width: 180px; + height: 400px; + + .lamp-text-shadow, + .lamp-text { + display: none; + } + } + } +} + +// ============================================ +// ARCADE CONTAINER +// ============================================ + +.arcade-container { + max-width: 1000px; + margin: 0 auto; + perspective: 1500px; + z-index: 5; + position: relative; + + @include media-down(lg) { + perspective: none; + } + + // ============================================ + // ARCADE CABINET BODY + // ============================================ + + .arcade-cabinet { + background: + repeating-linear-gradient(90deg, #3d2817 0px, #4a3420 2px, #3d2817 4px), + linear-gradient(180deg, #4a3420 0%, #2d1f12 100%); + border: 4px solid #2a2a2a; + border-top: none; + border-bottom: none; + position: relative; + padding-top: 30px; + + @include media-down(lg) { + border-left: none; + border-right: none; + } + } + + // ============================================ + // MARQUEE (Top of Cabinet) + // ============================================ + + .arcade-marquee { + position: relative; + background: linear-gradient(180deg, #1a1a1a 0%, #0a0a0a 100%); + border: 4px solid #2a2a2a; + border-bottom: none; + border-radius: 20px 20px 0 0; + padding: 20px; + text-align: center; + box-shadow: + inset 0 2px 4px rgba(255, 255, 255, 0.1), + inset 0 -2px 4px rgba(0, 0, 0, 0.5); + + @include media-down(lg) { + border-radius: 0; + padding: 15px 10px; + border-left: none; + border-right: none; + } + + .marquee-content { + color: #0f0; + text-shadow: + 0 0 10px rgba(0, 255, 0, 0.8), + 0 0 20px rgba(0, 255, 0, 0.5); + font-family: "Pixelify Sans", monospace; + + @include media-down(lg) { + font-size: 0.5rem; + } + } + + .arcade-title { + margin: 0; + animation: textGlow 2s ease-in-out infinite alternate; + } + } + + .marquee-light { + position: absolute; + top: 50%; + width: 20px; + height: 20px; + background: #ff0; + border-radius: 50%; + box-shadow: + 0 0 15px #ff0, + 0 0 30px #ff0; + animation: marquee-blink 1.5s ease-in-out infinite alternate; + + &.marquee-light-left { + left: 20px; + } + + &.marquee-light-right { + right: 20px; + animation-delay: 0.75s; + } + + @include media-down(lg) { + width: 12px; + height: 12px; + + &.marquee-light-left { + left: 10px; + } + &.marquee-light-right { + right: 10px; + } + } + } + + @keyframes marquee-blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.3; + } + } + + // ============================================ + // CRT SCREEN AREA + // ============================================ + + .arcade-screen-bezel { + background: linear-gradient(145deg, #e8e0c8, #c4b89a); + padding: 30px; + margin: 30px; + margin-top: 0px; + border-radius: 12px; + box-shadow: + inset 0 4px 8px rgba(0, 0, 0, 0.4), + inset 0 -2px 4px rgba(255, 255, 255, 0.3); + position: relative; + + @include media-down(lg) { + margin: 15px; + padding: 20px; + } + + &::before { + content: "RITUAL ARCADE"; + position: absolute; + bottom: 10px; + left: 50%; + transform: translateX(-50%); + color: #666; + font-size: 10px; + font-weight: bold; + letter-spacing: 2px; + } + } + + .arcade-screen { + width: 100%; + min-height: 500px; + background: #000; + border-radius: 8px; + position: relative; + overflow: hidden; + box-shadow: + inset 0 0 80px rgba(0, 255, 100, 0.1), + inset 0 0 40px rgba(0, 255, 100, 0.05), + inset 3px 3px 8px rgba(255, 255, 255, 0.1), + inset -3px -3px 8px rgba(0, 0, 0, 0.5); + + @include media-down(lg) { + min-height: 400px; + border-radius: 0; + } + + &.crt { + position: relative; + } + + &::before { + content: ""; + position: absolute; + top: -5%; + left: -5%; + right: -5%; + bottom: -5%; + background: + radial-gradient( + ellipse at 30% 30%, + rgba(255, 255, 255, 0.15) 0%, + transparent 40% + ), + radial-gradient( + ellipse at center, + transparent 0%, + rgba(0, 0, 0, 0.3) 100% + ); + pointer-events: none; + z-index: 3; + border-radius: 8px; + } + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: repeating-linear-gradient( + 0deg, + rgba(0, 0, 0, 0.15) 0px, + rgba(0, 0, 0, 0.15) 1px, + transparent 1px, + transparent 2px + ); + pointer-events: none; + z-index: 2; + animation: scanline 8s linear infinite; + } + + .screen-content { + position: relative; + width: 100%; + height: 100%; + padding: 30px; + color: #0f0; + z-index: 1; + font-family: monospace; + text-shadow: 0 0 10px rgba(0, 255, 0, 0.8); + + @include media-down(lg) { + padding: 20px; + } + + // ============================================ + // GAMES GRID + // ============================================ + + .games-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; + margin-bottom: 30px; + + @include media-down(lg) { + grid-template-columns: 1fr; + gap: 15px; + max-height: 400px; + overflow-y: auto; + @include scrollbar-custom(#0f0, 6px); + } + } + + .game-card { + background: rgba(0, 255, 0, 0.05); + border: 2px solid rgba(0, 255, 0, 0.3); + border-radius: 8px; + padding: 15px; + transition: all 0.3s ease; + cursor: pointer; + position: relative; + + @include enhance-3d-transform; + + &:hover, + &.selected { + background: rgba(0, 255, 0, 0.1); + border-color: rgba(0, 255, 0, 0.8); + box-shadow: + 0 0 20px rgba(0, 255, 0, 0.4), + inset 0 0 10px rgba(0, 255, 0, 0.1); + transform: translateY(-2px); + } + + &.selected { + animation: card-pulse 1s ease-in-out infinite alternate; + } + + // ============================================ + // GAME SUMMARY CARD + // ============================================ + + .game-summary { + display: flex; + gap: 15px; + } + + .game-thumbnail { + flex-shrink: 0; + width: 80px; + height: 80px; + border-radius: 6px; + overflow: hidden; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 255, 0, 0.3); + + img { + width: 100%; + height: 100%; + object-fit: cover; + filter: contrast(1.1) brightness(0.9); + } + } + + .game-thumbnail-placeholder { + display: flex; + align-items: center; + justify-content: center; + + .placeholder-icon { + font-size: 2rem; + opacity: 0.5; + } + } + + .game-info { + flex: 1; + min-width: 0; + } + + .game-title { + font-size: 1.2rem; + margin: 0 0 8px 0; + + a { + color: greenyellow; + text-decoration: none; + text-shadow: 0 0 5px rgba(173, 255, 47, 0.5); + + &:hover { + text-shadow: 0 0 10px rgba(173, 255, 47, 0.8); + } + } + } + + .game-description { + font-size: 0.85rem; + margin: 0 0 8px 0; + line-height: 1.4; + opacity: 0.9; + } + + .game-meta { + font-size: 0.75rem; + + .game-genre { + display: inline-block; + padding: 3px 8px; + background: rgba(0, 255, 0, 0.2); + border: 1px solid rgba(0, 255, 0, 0.5); + border-radius: 3px; + } + } + } + + @keyframes card-pulse { + 0% { + box-shadow: 0 0 20px rgba(0, 255, 0, 0.4); + } + 100% { + box-shadow: 0 0 30px rgba(0, 255, 0, 0.6); + } + } + } + } +} + +// ============================================ +// INSERT COIN MESSAGE +// ============================================ + +.insert-coin-message { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 2rem; + font-family: "Pixelify Sans", monospace; + color: #ff0; + text-shadow: + 0 0 10px rgba(255, 255, 0, 0.8), + 0 0 20px rgba(255, 255, 0, 0.5); + z-index: 10; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; + + &.visible { + opacity: 1; + } + + .blinking { + animation: blink 1.5s ease-in-out infinite; + } +} + +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.2; + } +} + +// ============================================ +// CONTROL PANEL +// ============================================ + +.control-panel { + position: relative; + + // 3D perspective transform + transform-style: preserve-3d; + perspective: 1250px; + + width: 113%; + transform: translateX(-50%); + margin-left: 50.25%; + margin-top: -50px; + + @include media-down(lg) { + padding: 30px 20px; + transform: none; + } +} + +.control-panel-surface { + background: + linear-gradient(135deg, #4a4a4a 25%, transparent 25%), + linear-gradient(225deg, #4a4a4a 25%, transparent 25%), + linear-gradient(45deg, #3a3a3a 25%, transparent 25%), + linear-gradient(315deg, #3a3a3a 25%, #2a2a2a 25%); + background-size: 20px 20px; + background-position: + 0 0, + 0 10px, + 10px -10px, + -10px 0px; + height: 225px; + padding: 40px; + position: relative; + z-index: 2; // Above the joystick + + // 3D perspective transform - subtle tilt toward viewer + transform: rotateX(40deg); + transform-origin: bottom center; + transform-style: preserve-3d; + + // Enhanced 3D beveled edges + box-shadow: + // Stronger outer shadow for lifted appearance + 0 12px 24px rgba(0, 0, 0, 0.6), + 0 8px 16px rgba(0, 0, 0, 0.4), + 0 4px 8px rgba(0, 0, 0, 0.3), + // Top highlight (raised effect) + inset 0 3px 0 rgba(0, 0, 0, 0.2), + inset 0 6px 12px rgba(0, 0, 0, 0.08), + // Bottom shadow (depth) + inset 0 -3px 0 rgba(0, 0, 0, 0.7), + inset 0 -6px 12px rgba(0, 0, 0, 0.5), + // Left highlight + inset 3px 0 0 rgba(255, 255, 255, 0.12), + // Right shadow + inset -3px 0 0 rgba(0, 0, 0, 0.6); + + // Subtle gradient overlay for 3D effect + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 12px; + background: + radial-gradient( + ellipse at top left, + rgba(255, 255, 255, 0.1) 0%, + transparent 50% + ), + radial-gradient( + ellipse at bottom right, + rgba(0, 0, 0, 0.4) 0%, + transparent 50% + ); + pointer-events: none; + z-index: 0; + } + + display: flex; + justify-content: space-around; + align-items: center; + gap: 40px; + + // Ensure controls are above the overlay + > * { + position: relative; + z-index: 1; + } + + @include media-down(lg) { + flex-direction: column; + gap: 30px; + padding: 30px 20px; + transform: none; + } +} + +.control-label { + text-align: center; + margin-top: 10px; + font-family: "Pixelify Sans", monospace; + font-size: 0.75rem; + color: #666; + letter-spacing: 1px; +} + +// Buttons overlay - positioned absolutely to avoid perspective transform +.buttons-overlay { + position: absolute; + left: 50%; + top: 60%; + transform: translate(-50%, -50%) scaleY(0.85); + z-index: 10; + pointer-events: none; + + @include media-down(lg) { + position: static; + transform: none; + pointer-events: auto; + } +} + +// Sound toggle overlay - positioned absolutely to avoid perspective transform +.sound-toggle-overlay { + position: absolute; + right: 15%; + top: 50%; + transform: translate(0, -50%) scaleY(0.85); + z-index: 10; + pointer-events: none; + text-align: center; + + @include media-down(lg) { + position: static; + transform: none; + pointer-events: auto; + } +} + +// ============================================ +// JOYSTICK +// ============================================ + +.joystick-container { + text-align: center; + + // Position absolutely to place on control panel without perspective transform + position: absolute; + left: 12.5%; + top: 35%; + transform: translateY(-50%); + z-index: 0; // Behind the control panel surface + pointer-events: none; // Allow clicks to pass through to buttons below + + @include media-down(lg) { + position: static; + transform: none; + pointer-events: auto; + } +} + +.joystick { + position: relative; + width: 100px; + height: 120px; + user-select: none; + + @include enhance-3d-transform; +} + +.joystick-base { + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 80px; + height: 30px; + background: radial-gradient(ellipse at center, #1a1a1a 0%, #0a0a0a 100%); + border-radius: 50%; + box-shadow: + inset 0 3px 6px rgba(0, 0, 0, 0.8), + 0 2px 4px rgba(0, 0, 0, 0.5); + + &::before, + &::after { + content: ""; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 8px; + height: 8px; + background: #2a2a2a; + border-radius: 50%; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.8); + } + + &::before { + left: 10px; + } + &::after { + right: 10px; + } +} + +.joystick-stick { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + width: 20px; + height: 70px; + background: linear-gradient(90deg, #3a3a3a 0%, #2a2a2a 50%, #1a1a1a 100%); + border-radius: 10px; + box-shadow: + -2px 0 4px rgba(0, 0, 0, 0.5), + 2px 0 4px rgba(255, 255, 255, 0.1); + transition: transform 0.1s ease; + transform-origin: bottom center; + + @include enhance-3d-transform; +} + +.joystick-ball { + position: absolute; + top: -15px; + left: 50%; + transform: translateX(-50%); + width: 40px; + height: 40px; + background: radial-gradient(circle at 30% 30%, #ff3333 0%, #cc0000 100%); + border-radius: 50%; + box-shadow: + 0 4px 8px rgba(0, 0, 0, 0.6), + inset -2px -2px 4px rgba(0, 0, 0, 0.4), + inset 2px 2px 4px rgba(255, 100, 100, 0.4); +} + +.joystick.up .joystick-stick { + transform: translateX(-50%) rotate(-15deg); +} + +.joystick.down .joystick-stick { + transform: translateX(-50%) rotate(15deg); +} + +.joystick.left .joystick-stick { + transform: translateX(-50%) rotate(-10deg) translateX(-5px); +} + +.joystick.right .joystick-stick { + transform: translateX(-50%) rotate(10deg) translateX(5px); +} + +// ============================================ +// ARCADE BUTTONS +// ============================================ + +.buttons-container { + display: flex; + gap: 30px; + min-height: auto; + pointer-events: auto; + + @include media-down(lg) { + gap: 20px; + } +} + +.arcade-button-wrap { + text-align: center; + pointer-events: auto; +} + +.arcade-button { + position: relative; + width: 70px; + height: 70px; + border: none; + background: transparent; + cursor: pointer; + padding: 0; + pointer-events: auto; + z-index: 10; + + @include enhance-3d-transform; + + &:focus { + outline: none; + } + + &::before { + content: ""; + position: absolute; + top: 60%; + left: 50%; + transform: translate(-50%, -50%); + width: 80px; + height: 80px; + background: radial-gradient( + circle at center, + #1a1a1a 0%, + rgba(0, 0, 0, 0.8) + ); + border-radius: 50%; + box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.6); + } +} + +.button-top { + display: block; + width: 70px; + height: 70px; + border-radius: 50%; + background: radial-gradient(circle at 30% 30%, #ffff00 0%, #cccc00 100%); + box-shadow: + 0 6px 0 #999900, + 0 8px 12px rgba(0, 0, 0, 0.6), + inset -2px -2px 4px rgba(0, 0, 0, 0.3), + inset 2px 2px 4px rgba(255, 255, 150, 0.5); + transition: all 0.1s ease; + position: relative; + top: 0; +} + +.arcade-button-select .button-top { + background: radial-gradient(circle at 30% 30%, #ff3333 0%, #cc0000 100%); + box-shadow: + 0 6px 0 #990000, + 0 8px 12px rgba(0, 0, 0, 0.6), + inset -2px -2px 4px rgba(0, 0, 0, 0.3), + inset 2px 2px 4px rgba(255, 100, 100, 0.5); +} + +.arcade-button-back .button-top { + background: radial-gradient(circle at 30% 30%, #3333ff 0%, #0000cc 100%); + box-shadow: + 0 6px 0 #000099, + 0 8px 12px rgba(0, 0, 0, 0.6), + inset -2px -2px 4px rgba(0, 0, 0, 0.3), + inset 2px 2px 4px rgba(100, 100, 255, 0.5); +} + +.arcade-button:active .button-top, +.arcade-button.pressed .button-top { + top: 4px; + box-shadow: + 0 2px 0 #999900, + 0 4px 8px rgba(0, 0, 0, 0.6), + inset -2px -2px 4px rgba(0, 0, 0, 0.3), + inset 2px 2px 4px rgba(255, 255, 150, 0.5); +} + +.arcade-button-select:active .button-top, +.arcade-button-select.pressed .button-top { + box-shadow: + 0 2px 0 #990000, + 0 4px 8px rgba(0, 0, 0, 0.6), + inset -2px -2px 4px rgba(0, 0, 0, 0.3), + inset 2px 2px 4px rgba(255, 100, 100, 0.5); +} + +.arcade-button-back:active .button-top, +.arcade-button-back.pressed .button-top { + box-shadow: + 0 2px 0 #000099, + 0 4px 8px rgba(0, 0, 0, 0.6), + inset -2px -2px 4px rgba(0, 0, 0, 0.3), + inset 2px 2px 4px rgba(100, 100, 255, 0.5); +} + +// ============================================ +// SOUND TOGGLE +// ============================================ + +.sound-toggle-container { + text-align: center; +} + +.sound-toggle { + width: 50px; + height: 50px; + border: 2px solid #3a3a3a; + border-radius: 8px; + background: #1a1a1a; + color: #0f0; + font-size: 1.5rem; + cursor: pointer; + transition: all 0.2s ease; + pointer-events: auto; + z-index: 10; + position: relative; + box-shadow: + inset 0 2px 4px rgba(0, 0, 0, 0.6), + 0 2px 4px rgba(0, 0, 0, 0.3); + + &:hover { + background: #2a2a2a; + box-shadow: + inset 0 2px 4px rgba(0, 0, 0, 0.6), + 0 2px 4px rgba(0, 0, 0, 0.3), + 0 0 10px rgba(0, 255, 0, 0.3); + } + + &:active { + transform: translateY(2px); + } + + &:focus { + outline: none; + box-shadow: + inset 0 2px 4px rgba(0, 0, 0, 0.6), + 0 0 0 2px rgba(0, 255, 0, 0.5); + } +} + +.sound-icon { + display: inline-block; +} + +// ============================================ +// CABINET BASE +// ============================================ + +.cabinet-base { + width: 112%; + margin-left: -6%; + height: 80px; + background: linear-gradient(180deg, #2d1f12 0%, #1d1208 100%); + border: 4px solid #2a2a2a; + position: relative; + display: flex; + align-items: center; + justify-content: center; + box-shadow: + 0 30px 60px rgba(0, 0, 0, 0.9), + inset 0 2px 4px rgba(255, 255, 255, 0.1), + inset 0 -2px 4px rgba(0, 0, 0, 0.5); + + @include media-down(lg) { + height: 60px; + } +} + +.coin-slot { + width: 60px; + height: 6px; + background: #000; + border-radius: 3px; + box-shadow: + inset 0 2px 4px rgba(0, 0, 0, 0.9), + 0 1px 0 rgba(255, 255, 255, 0.1); + position: relative; + + &::before { + content: "INSERT COIN"; + position: absolute; + bottom: 15px; + left: 50%; + transform: translateX(-50%); + font-size: 0.65rem; + color: #666; + font-family: "Pixelify Sans", monospace; + letter-spacing: 1px; + white-space: nowrap; + } +} + +// ============================================ +// ACCESSIBILITY - REDUCED MOTION +// ============================================ + +@media (prefers-reduced-motion: reduce) { + .arcade-container *, + .arcade-container *::before, + .arcade-container *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/assets/sass/style.scss b/assets/sass/style.scss index 2fed977..53da605 100644 --- a/assets/sass/style.scss +++ b/assets/sass/style.scss @@ -35,6 +35,7 @@ @import "pages/log"; @import "pages/games/whittler"; +@import "pages/games/arcade"; @import url(https://fonts.bunny.net/css?family=abel:400|barlow-condensed:400,500|caveat:400|lato:300,300i,400,400i|neonderthaw:400|pixelify-sans:400); diff --git a/content/games/_index.md b/content/games/_index.md new file mode 100644 index 0000000..b368234 --- /dev/null +++ b/content/games/_index.md @@ -0,0 +1,6 @@ +--- +title: "Games" +draft: false +--- + +Welcome to my games section. diff --git a/content/games/test.md b/content/games/test.md new file mode 100644 index 0000000..7494fc1 --- /dev/null +++ b/content/games/test.md @@ -0,0 +1,10 @@ +--- +title: "Test" +type: games +logo: logo.avif +thumbnail: "logo.avif" +description: "A clicker game where you whittle wood to earn ¥1,000,000" +genre: "Clicker" +--- + +Whittler the game :) diff --git a/content/games/whittler/index.md b/content/games/whittler/index.md index c35e1e6..b0f7c38 100644 --- a/content/games/whittler/index.md +++ b/content/games/whittler/index.md @@ -3,6 +3,9 @@ title: "Whittler" type: games layout: whittler logo: logo.avif +thumbnail: "logo.avif" +description: "A clicker game where you whittle wood to earn ¥1,000,000" +genre: "Clicker" --- Whittler the game :) diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 25001c2..ed6f1ca 100755 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -64,5 +64,6 @@ {{ $buttons := resources.Get "js/buttons.js" | resources.Minify | resources.Fingerprint }} {{ end }} + diff --git a/layouts/games/list.html b/layouts/games/list.html new file mode 100644 index 0000000..a47a0a8 --- /dev/null +++ b/layouts/games/list.html @@ -0,0 +1,137 @@ +{{ define "main" }} + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
I WANT TO
BELIEVE
+
+
+
{{ partial "elements/neon-sign.html" . }}
+
{{ partial "elements/lavalamp.html" . }}
+
+ +
+
+
+
+       ██████   █████  ███    ███ ███████ ███████
+██       ██   ██ ████  ████ ██      ██
+      ██   ███ ███████ ██ ████ ██ █████   ███████
+  ██    ██ ██   ██ ██  ██  ██ ██           ██
+  ██████  ██   ██ ██      ██ ███████ ███████
+        
+
+
+
+ + +
+ +
+
+
+ +
+ {{ range $index, $element := .Paginator.Pages }} +
+ {{ .Render "summary" }} +
+ {{ end }} +
+ + + {{ if eq (len .Paginator.Pages) 0 }} +
+ INSERT COIN +
+ {{ else }} +
+ INSERT COIN +
+ {{ end }} +
+
+
+ + +
+ +
+
+
+
+
+
+
+
MOVE
+
+ + +
+
+
+ +
SELECT
+
+
+ +
BACK
+
+
+
+ + +
+ +
SOUND
+
+ +
+ +
+
+ + +
+
+
+
+ + + {{ partial "pagination.html" .Paginator }} +
+
+{{ end }} diff --git a/layouts/games/summary.html b/layouts/games/summary.html new file mode 100644 index 0000000..c449a96 --- /dev/null +++ b/layouts/games/summary.html @@ -0,0 +1,27 @@ +
+ {{ if .Params.thumbnail }} +
+ {{ .Title }} +
+ {{ else }} +
+
🎮
+
+ {{ end }} + +
+

+ {{ .Title }} +

+ + {{ if .Params.description }} +

{{ .Params.description }}

+ {{ end }} + + {{ if .Params.genre }} +
+ {{ .Params.genre }} +
+ {{ end }} +
+