Spaces:
Running
Running
| <!DOCTYPE html> | |
| <html lang="en" class=""> <!-- Start without 'dark' class --> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Italian Brinarot - Soundboard</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Base styles */ | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| /* Smooth transition for theme changes */ | |
| } | |
| /* Light theme base */ | |
| body { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%); | |
| } | |
| /* Dark theme base */ | |
| .dark body { | |
| background: linear-gradient(135deg, #1f2937 0%, #111827 100%); /* Dark gradient */ | |
| } | |
| .character-card { | |
| transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); | |
| perspective: 1000px; | |
| } | |
| .character-card:hover { | |
| transform: translateY(-8px); | |
| /* Enhanced hover shadow for both themes */ | |
| } | |
| /* Specific light/dark styles will be applied via Tailwind classes directly */ | |
| .image-container { | |
| /* Keep the gradient or simplify */ | |
| background: linear-gradient(145deg, #ffffff, #e6e6e6); | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.08); | |
| } | |
| .dark .image-container { | |
| background: linear-gradient(145deg, #4b5563, #374151); /* Darker bg for image */ | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| } | |
| .play-btn { | |
| transition: all 0.2s ease; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| .play-btn.playing { | |
| background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); /* Red gradient for playing */ | |
| } | |
| .play-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(118, 75, 162, 0.4); | |
| } | |
| .play-btn.playing:hover { | |
| box-shadow: 0 5px 15px rgba(239, 68, 68, 0.4); /* Red shadow */ | |
| } | |
| .play-btn:active { | |
| transform: translateY(0); | |
| } | |
| .gradient-text { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| } | |
| /* Optional: Slightly adjust gradient text for dark mode if needed */ | |
| /* .dark .gradient-text { ... } */ | |
| .header-divider { | |
| height: 4px; | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| } | |
| .dark .header-divider { | |
| background: linear-gradient(90deg, #818cf8 0%, #a78bfa 100%); /* Brighter gradient for dark */ | |
| } | |
| /* Theme toggle button */ | |
| #theme-toggle { | |
| } | |
| #theme-toggle i { | |
| } | |
| .character-name { | |
| font-size: 1rem; /* Already adjusted */ | |
| } | |
| #theme-toggle { | |
| } | |
| #theme-toggle i { | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen"> <!-- Body class will be updated by JS --> | |
| <!-- Theme Toggle Button --> | |
| <button id="theme-toggle" aria-label="Toggle dark mode"> | |
| <i class="fas fa-moon"></i> <!-- Moon icon for light theme --> | |
| <i class="fas fa-sun hidden"></i> <!-- Sun icon for dark theme --> | |
| </button> | |
| <div class="container mx-auto px-4 py-12 md:py-16"> | |
| <!-- Header --> | |
| <header class="text-center mb-12 md:mb-16"> | |
| <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold gradient-text mb-3">Italian Brinarot</h1> | |
| <div class="header-divider w-32 md:w-40 mx-auto rounded-full mb-6"></div> | |
| <p class="text-lg text-gray-600 dark:text-gray-400 max-w-2xl mx-auto"> | |
| Discover the magical sounds of Italian Brinarot characters | |
| </p> | |
| </header> | |
| <!-- Character Grid --> | |
| <div id="characterGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8"> | |
| <!-- Character cards will be injected here by JS --> | |
| </div> | |
| </div> | |
| <!-- Embedded Character Data --> | |
| <script> | |
| const characterData = { | |
| // ... (Keep your characterData object exactly the same) | |
| "Bombardiro Crocodilo": { "image_path": "./assets/Bombardiro_Crocodilo.png", "audio_path": "./assets/Bombardiro_Crocodilo.wav" }, | |
| "Tralalero Tralala": { "image_path": "./assets/Tralalero_Tralala.png", "audio_path": "./assets/Tralalero_Tralala.wav" }, | |
| "Bombombini Gusini": { "image_path": "./assets/Bombombini_Gusini.png", "audio_path": "./assets/Bombombini_Gusini.wav" }, | |
| "Tung Tung Tung Tung Tung Tung Tung Tung Tung Sahur": { "image_path": "./assets/Tung_Tung_Tung_Tung_Tung_Tung_Tung_Tung_Tung_Sahur.png", "audio_path": "./assets/Tung_Tung_Tung_Tung_Tung_Tung_Tung_Tung_Tung_Sahur.wav" }, | |
| "Brr Brr Patapim": { "image_path": "./assets/Brr_Brr_Patapim.png", "audio_path": "./assets/Brr_Brr_Patapim.wav" }, | |
| "Cappuccino Assassino": { "image_path": "./assets/Cappuccino_Assassino.png", "audio_path": "./assets/Cappuccino_Assassino.wav" }, | |
| "Lirili Larila": { "image_path": "./assets/Lirili_Larila.png", "audio_path": "./assets/Lirili_Larila.wav" }, | |
| "Trulimero Trulichina": { "image_path": "./assets/Trulimero_Trulichina.png", "audio_path": "./assets/Trulimero_Trulichina.wav" }, | |
| "Boneca Ambalabu": { "image_path": "./assets/Boneca_Ambalabu.png", "audio_path": "./assets/Boneca_Ambalabu.wav" }, | |
| "Chimpanzini Bananini": { "image_path": "./assets/Chimpanzini_Bananini.png", "audio_path": "./assets/Chimpanzini_Bananini.wav" }, | |
| "Bananita Dolfinita": { "image_path": "./assets/Bananita_Dolfinita.png", "audio_path": "./assets/Bananita_Dolfinita.wav" }, | |
| "Bluberini Octopusini": { "image_path": "./assets/Bluberini_Octopusini.png", "audio_path": "./assets/Bluberini_Octopusini.wav" }, | |
| "Bobrito Bandito": { "image_path": "./assets/Bobrito_Bandito.png", "audio_path": "./assets/Bobrito_Bandito.wav" }, | |
| "Brri Brri Bicus Dicus": { "image_path": "./assets/Brri_Brri_Bicus_Dicus.png", "audio_path": "./assets/Brri_Brri_Bicus_Dicus.wav" }, | |
| "Burbaloni Luliloli": { "image_path": "./assets/Burbaloni_Luliloli.png", "audio_path": "./assets/Burbaloni_Luliloli.wav" }, | |
| "Chimpanzini Annasini": { "image_path": "./assets/Chimpanzini_Annasini.png", "audio_path": "./assets/Chimpanzini_Annasini.wav" }, | |
| "Chimpanzini Cocosini": { "image_path": "./assets/Chimpanzini_Cocosini.png", "audio_path": "./assets/Chimpanzini_Cocosini.wav" }, | |
| "Cocofanto Elefanto": { "image_path": "./assets/Cocofanto_Elefanto.png", "audio_path": "./assets/Cocofanto_Elefanto.wav" }, | |
| "Frigo Camelo Buffardello": { "image_path": "./assets/frigo_camelo_buffardello.png", "audio_path": "./assets/frigo_camelo_buffardello.wav" }, | |
| "Giraffe Celeste": { "image_path": "./assets/Giraffe_Celeste.png", "audio_path": "./assets/Giraffe_Celeste.wav" }, | |
| "Glorbo Fruttodrillo": { "image_path": "./assets/Glorbo_Fruttodrillo.png", "audio_path": "./assets/Glorbo_Fruttodrillo.wav" }, | |
| "Il Cacto Hipopotoma": { "image_path": "./assets/il_cacto_hipopotoma.png", "audio_path": "./assets/il_cacto_hipopotoma.wav" }, | |
| "Trippi Troppi Troppa Trippa": { "image_path": "./assets/Trippi_Troppi_Troppa_Trippa.png", "audio_path": "./assets/Trippi_Troppi_Troppa_Trippa.wav" } | |
| }; | |
| const characterGrid = document.getElementById('characterGrid'); | |
| const themeToggleButton = document.getElementById('theme-toggle'); | |
| const sunIcon = themeToggleButton.querySelector('.fa-sun'); | |
| const moonIcon = themeToggleButton.querySelector('.fa-moon'); | |
| const htmlElement = document.documentElement; // Target <html> for dark class | |
| let currentlyPlayingAudio = null; | |
| let currentlyPlayingButton = null; | |
| // Function to apply the theme | |
| function applyTheme(theme) { | |
| if (theme === 'dark') { | |
| htmlElement.classList.add('dark'); | |
| moonIcon.classList.add('hidden'); | |
| sunIcon.classList.remove('hidden'); | |
| localStorage.setItem('theme', 'dark'); | |
| } else { | |
| htmlElement.classList.remove('dark'); | |
| moonIcon.classList.remove('hidden'); | |
| sunIcon.classList.add('hidden'); | |
| localStorage.setItem('theme', 'light'); | |
| } | |
| } | |
| // Function to toggle the theme | |
| function toggleTheme() { | |
| const currentTheme = htmlElement.classList.contains('dark') ? 'dark' : 'light'; | |
| applyTheme(currentTheme === 'dark' ? 'light' : 'dark'); | |
| } | |
| // Function to load and display characters | |
| function loadCharacters() { | |
| characterGrid.innerHTML = ''; // Clear existing | |
| Object.entries(characterData).forEach(([name, data]) => { | |
| const card = document.createElement('div'); | |
| // Apply base and dark mode classes using Tailwind | |
| card.className = ` | |
| character-card | |
| bg-white dark:bg-gray-800 | |
| rounded-2xl overflow-hidden | |
| shadow-md hover:shadow-xl dark:hover:shadow-2xl dark:hover:shadow-indigo-500/20 | |
| border border-gray-200 dark:border-gray-700/50 | |
| flex flex-col items-center p-6 | |
| transition-all duration-300 ease-in-out | |
| `; | |
| // Image container | |
| const imageContainer = document.createElement('div'); | |
| imageContainer.className = ` | |
| image-container mb-5 w-40 h-40 lg:w-44 lg:h-44 | |
| flex items-center justify-center | |
| rounded-2xl overflow-hidden | |
| border-4 border-white/50 dark:border-gray-700/50 | |
| `; // Added slightly larger size on larger screens and border | |
| const image = document.createElement('img'); | |
| image.src = data.image_path.replace(/\\/g, '/'); | |
| image.alt = name; | |
| image.className = 'w-full h-full object-cover'; | |
| image.onerror = () => { // Basic error handling for images | |
| image.src = 'placeholder.png'; // Provide a fallback image path | |
| console.warn(`Image not found: ${data.image_path}`); | |
| }; | |
| imageContainer.appendChild(image); | |
| // Character name | |
| const nameElement = document.createElement('h3'); | |
| nameElement.className = ` | |
| character-name text-lg md:text-xl font-semibold | |
| text-gray-800 dark:text-gray-100 | |
| mb-4 text-center transition-colors duration-300 | |
| `; | |
| nameElement.textContent = name; | |
| // Play/Pause button | |
| const playButton = document.createElement('button'); | |
| playButton.className = ` | |
| play-btn text-white font-medium py-2.5 px-7 | |
| rounded-full flex items-center justify-center | |
| w-32 /* Fixed width for consistency */ | |
| `; | |
| const playIcon = document.createElement('i'); | |
| playIcon.className = 'fas fa-play mr-2'; // Start with play | |
| playButton.appendChild(playIcon); | |
| playButton.appendChild(document.createTextNode('Play')); | |
| // Audio element (keep it hidden) | |
| const audio = new Audio(data.audio_path.replace(/\\/g, '/')); | |
| audio.preload = 'metadata'; // Improve loading slightly | |
| audio.style.display = 'none'; | |
| audio.onerror = () => { | |
| console.error(`Error loading audio: ${data.audio_path}`); | |
| playButton.disabled = true; // Disable button if audio fails | |
| playButton.textContent = 'Error'; | |
| playButton.classList.add('opacity-50', 'cursor-not-allowed'); | |
| }; | |
| // --- Refined Play/Pause Logic --- | |
| playButton.addEventListener('click', () => { | |
| // If this audio is currently playing, pause it | |
| if (currentlyPlayingAudio === audio) { | |
| audio.pause(); | |
| currentlyPlayingAudio = null; | |
| currentlyPlayingButton = null; | |
| // Reset this button visually | |
| playButton.innerHTML = '<i class="fas fa-play mr-2"></i>Play'; | |
| playButton.classList.remove('playing'); | |
| } else { | |
| // If another audio is playing, stop it first | |
| if (currentlyPlayingAudio) { | |
| currentlyPlayingAudio.pause(); | |
| currentlyPlayingAudio.currentTime = 0; // Reset time | |
| // Reset the *other* button visually | |
| if (currentlyPlayingButton) { | |
| currentlyPlayingButton.innerHTML = '<i class="fas fa-play mr-2"></i>Play'; | |
| currentlyPlayingButton.classList.remove('playing'); | |
| } | |
| } | |
| // Now play this audio | |
| audio.currentTime = 0; | |
| audio.play().catch(e => console.error("Audio play failed:", e)); // Catch potential play errors | |
| currentlyPlayingAudio = audio; | |
| currentlyPlayingButton = playButton; | |
| // Update this button visually to 'Pause' | |
| playButton.innerHTML = '<i class="fas fa-pause mr-2"></i>Pause'; | |
| playButton.classList.add('playing'); | |
| } | |
| }); | |
| // When audio naturally ends, reset the button | |
| audio.addEventListener('ended', () => { | |
| if (currentlyPlayingAudio === audio) { // Ensure it's the one that just ended | |
| playButton.innerHTML = '<i class="fas fa-play mr-2"></i>Play'; | |
| playButton.classList.remove('playing'); | |
| currentlyPlayingAudio = null; | |
| currentlyPlayingButton = null; | |
| } | |
| }); | |
| // --- End Refined Play/Pause Logic --- | |
| // Assemble the card | |
| card.appendChild(imageContainer); | |
| card.appendChild(nameElement); | |
| card.appendChild(playButton); | |
| card.appendChild(audio); // Add hidden audio element | |
| // Add card to the grid | |
| characterGrid.appendChild(card); | |
| }); | |
| } | |
| // --- Initialization --- | |
| // 1. Set initial theme based on localStorage or system preference | |
| const savedTheme = localStorage.getItem('theme'); | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | |
| if (savedTheme) { | |
| applyTheme(savedTheme); | |
| } else if (prefersDark) { | |
| applyTheme('dark'); | |
| } else { | |
| applyTheme('light'); // Default to light | |
| } | |
| // 2. Add listener for the theme toggle button | |
| themeToggleButton.addEventListener('click', toggleTheme); | |
| // 3. Load characters when the page loads | |
| window.addEventListener('DOMContentLoaded', loadCharacters); | |
| </script> | |
| </body> | |
| </html> |