From 74f0cac5e72aa4574223ee879cecd02995583403 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Nov 2025 16:46:56 +0000 Subject: [PATCH] Refactor to modern JavaScript and add collapsible custom coin section Modern JavaScript improvements: - Replace inline event handlers with addEventListener pattern - Use arrow functions consistently throughout the code - Add DOM elements caching for better performance - Use template literals for string interpolation - Use optional chaining (?.) for safer null checks - Use ternary operators for cleaner conditional logic - Destructure objects where appropriate - Add clear separation of concerns with dedicated functions UI improvements: - Add toggle button to show/hide custom coin options - Custom coin section now hidden by default with "collapsed" class - Smooth transition animations when toggling section visibility - Add aria-expanded attribute for better accessibility - Responsive design updates for toggle button on mobile The refactored code is more maintainable, follows modern best practices, and provides a better user experience with the collapsible custom options. --- index.html | 16 ++-- script.js | 213 +++++++++++++++++++++++++++++++---------------------- styles.css | 39 +++++++++- 3 files changed, 172 insertions(+), 96 deletions(-) diff --git a/index.html b/index.html index 03359b2..e20988f 100644 --- a/index.html +++ b/index.html @@ -24,18 +24,22 @@

Keyforge Coin Flip

-
+
Keyforge Coin
- +
-
+ + +
diff --git a/script.js b/script.js index 823de65..e6c1938 100644 --- a/script.js +++ b/script.js @@ -6,79 +6,129 @@ const DEFAULT_BACK_IMAGE = 'back-key.png'; let currentFrontImage = DEFAULT_FRONT_IMAGE; let currentBackImage = DEFAULT_BACK_IMAGE; -// Load custom images from localStorage on page load -window.addEventListener('DOMContentLoaded', () => { +// DOM elements cache +const elements = { + coin: null, + coinImage: null, + flipButton: null, + toggleButton: null, + customSection: null, + frontInput: null, + backInput: null, + frontPreview: null, + backPreview: null, + resetButton: null +}; + +// Initialize app when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + initializeElements(); loadCustomImages(); + attachEventListeners(); }); -function loadCustomImages() { +// Cache DOM elements for better performance +const initializeElements = () => { + elements.coin = document.getElementById('coin'); + elements.coinImage = document.getElementById('coinImage'); + elements.flipButton = document.querySelector('.flip-button'); + elements.toggleButton = document.getElementById('toggleCustom'); + elements.customSection = document.getElementById('customSection'); + elements.frontInput = document.getElementById('frontImageInput'); + elements.backInput = document.getElementById('backImageInput'); + elements.frontPreview = document.getElementById('frontPreview'); + elements.backPreview = document.getElementById('backPreview'); + elements.resetButton = document.querySelector('.reset-button'); +}; + +// Attach all event listeners +const attachEventListeners = () => { + elements.coin?.addEventListener('click', flipCoin); + elements.flipButton?.addEventListener('click', flipCoin); + elements.toggleButton?.addEventListener('click', toggleCustomSection); + elements.frontInput?.addEventListener('change', handleFrontImageUpload); + elements.backInput?.addEventListener('change', handleBackImageUpload); + elements.resetButton?.addEventListener('click', resetToDefault); +}; + +// Toggle custom section visibility +const toggleCustomSection = () => { + const { customSection, toggleButton } = elements; + if (!customSection || !toggleButton) return; + + customSection.classList.toggle('collapsed'); + const isCollapsed = customSection.classList.contains('collapsed'); + toggleButton.textContent = isCollapsed ? 'Show Custom Coin Options' : 'Hide Custom Coin Options'; + toggleButton.setAttribute('aria-expanded', !isCollapsed); +}; + +// Load custom images from localStorage +const loadCustomImages = () => { const savedFrontImage = localStorage.getItem('customFrontImage'); const savedBackImage = localStorage.getItem('customBackImage'); if (savedFrontImage) { currentFrontImage = savedFrontImage; - updatePreview('frontPreview', savedFrontImage); + updatePreview(elements.frontPreview, savedFrontImage); } if (savedBackImage) { currentBackImage = savedBackImage; - updatePreview('backPreview', savedBackImage); + updatePreview(elements.backPreview, savedBackImage); } // Update the coin image to show the current front image - const coinImage = document.getElementById('coinImage'); - if (coinImage) { - coinImage.src = currentFrontImage; + if (elements.coinImage) { + elements.coinImage.src = currentFrontImage; } -} - -function handleFrontImageUpload(event) { - const file = event.target.files[0]; - if (file && file.type.startsWith('image/')) { - const reader = new FileReader(); - reader.onload = function(e) { - const imageData = e.target.result; - currentFrontImage = imageData; - localStorage.setItem('customFrontImage', imageData); - - // Update preview - updatePreview('frontPreview', imageData); - - // Update coin image - const coinImage = document.getElementById('coinImage'); - if (coinImage) { - coinImage.src = currentFrontImage; - } - }; - reader.readAsDataURL(file); - } -} - -function handleBackImageUpload(event) { - const file = event.target.files[0]; - if (file && file.type.startsWith('image/')) { - const reader = new FileReader(); - reader.onload = function(e) { - const imageData = e.target.result; - currentBackImage = imageData; - localStorage.setItem('customBackImage', imageData); - - // Update preview - updatePreview('backPreview', imageData); - }; - reader.readAsDataURL(file); - } -} +}; -function updatePreview(previewId, imageSrc) { - const preview = document.getElementById(previewId); - if (preview) { - preview.src = imageSrc; - preview.style.display = 'block'; - } -} +// Handle front image upload +const handleFrontImageUpload = (event) => { + const file = event.target.files?.[0]; + if (!file?.type.startsWith('image/')) return; + + const reader = new FileReader(); + reader.onload = (e) => { + const imageData = e.target.result; + currentFrontImage = imageData; + localStorage.setItem('customFrontImage', imageData); -function resetToDefault() { + updatePreview(elements.frontPreview, imageData); + + if (elements.coinImage) { + elements.coinImage.src = currentFrontImage; + } + }; + reader.readAsDataURL(file); +}; + +// Handle back image upload +const handleBackImageUpload = (event) => { + const file = event.target.files?.[0]; + if (!file?.type.startsWith('image/')) return; + + const reader = new FileReader(); + reader.onload = (e) => { + const imageData = e.target.result; + currentBackImage = imageData; + localStorage.setItem('customBackImage', imageData); + + updatePreview(elements.backPreview, imageData); + }; + reader.readAsDataURL(file); +}; + +// Update preview image +const updatePreview = (previewElement, imageSrc) => { + if (!previewElement) return; + + previewElement.src = imageSrc; + previewElement.style.display = 'block'; +}; + +// Reset to default images +const resetToDefault = () => { // Clear localStorage localStorage.removeItem('customFrontImage'); localStorage.removeItem('customBackImage'); @@ -88,33 +138,29 @@ function resetToDefault() { currentBackImage = DEFAULT_BACK_IMAGE; // Clear file inputs - const frontInput = document.getElementById('frontImageInput'); - const backInput = document.getElementById('backImageInput'); - if (frontInput) frontInput.value = ''; - if (backInput) backInput.value = ''; + if (elements.frontInput) elements.frontInput.value = ''; + if (elements.backInput) elements.backInput.value = ''; // Clear previews - const frontPreview = document.getElementById('frontPreview'); - const backPreview = document.getElementById('backPreview'); - if (frontPreview) { - frontPreview.src = ''; - frontPreview.style.display = 'none'; + if (elements.frontPreview) { + elements.frontPreview.src = ''; + elements.frontPreview.style.display = 'none'; } - if (backPreview) { - backPreview.src = ''; - backPreview.style.display = 'none'; + if (elements.backPreview) { + elements.backPreview.src = ''; + elements.backPreview.style.display = 'none'; } // Update coin image - const coinImage = document.getElementById('coinImage'); - if (coinImage) { - coinImage.src = currentFrontImage; + if (elements.coinImage) { + elements.coinImage.src = currentFrontImage; } -} +}; -function flipCoin() { - const coin = document.getElementById('coin'); - const coinImage = coin.querySelector('.coin-image'); +// Flip coin animation +const flipCoin = () => { + const { coin, coinImage } = elements; + if (!coin || !coinImage) return; // Remove any existing animation coin.classList.remove('flipping'); @@ -123,7 +169,7 @@ function flipCoin() { const flipDuration = Math.random() * 0.5 + 0.5; // Set the animation duration - coin.style.animationDuration = flipDuration + 's'; + coin.style.animationDuration = `${flipDuration}s`; // Start flipping animation setTimeout(() => { @@ -133,11 +179,7 @@ function flipCoin() { // Switch images during flip let isShowingFront = true; const flipInterval = setInterval(() => { - if (isShowingFront) { - coinImage.src = currentBackImage; - } else { - coinImage.src = currentFrontImage; - } + coinImage.src = isShowingFront ? currentBackImage : currentFrontImage; isShowingFront = !isShowingFront; }, 150); // Switch every 150ms for visual effect @@ -148,15 +190,10 @@ function flipCoin() { // Random final result (50/50 chance) const finalResult = Math.random() < 0.5; - if (finalResult) { - coinImage.src = currentFrontImage; - console.log('Result: Front'); - } else { - coinImage.src = currentBackImage; - console.log('Result: Back'); - } + coinImage.src = finalResult ? currentFrontImage : currentBackImage; + console.log(`Result: ${finalResult ? 'Front' : 'Back'}`); // Remove animation class coin.classList.remove('flipping'); }, flipDuration * 1000); -} +}; diff --git a/styles.css b/styles.css index 3c4ef05..7850774 100644 --- a/styles.css +++ b/styles.css @@ -121,9 +121,29 @@ main { 100% { transform: rotateY(0deg); } } +/* Toggle Custom Button */ +.toggle-custom-button { + background: linear-gradient(45deg, #4a90e2, #67b8f7); + color: white; + border: none; + padding: 12px 24px; + font-size: 1rem; + font-weight: bold; + border-radius: 25px; + cursor: pointer; + transition: all 0.3s ease; + margin: 20px auto; + display: block; +} + +.toggle-custom-button:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(74, 144, 226, 0.4); +} + /* Custom Images Section */ .custom-images-section { - margin-top: 50px; + margin-top: 20px; padding: 30px; background-color: #16213e; border-radius: 15px; @@ -131,6 +151,17 @@ main { max-width: 600px; margin-left: auto; margin-right: auto; + max-height: 1000px; + opacity: 1; + overflow: hidden; + transition: all 0.5s ease-in-out; +} + +.custom-images-section.collapsed { + max-height: 0; + opacity: 0; + padding: 0 30px; + margin-top: 0; } .custom-images-section h2 { @@ -235,9 +266,13 @@ main { font-size: 1.5rem; } + .toggle-custom-button { + font-size: 0.9rem; + padding: 10px 20px; + } + .custom-images-section { padding: 20px; - margin-top: 30px; } .custom-images-section h2 {