Implement Phase 2: Complete Audio Management UI

Major audio management interface implementation:

HTML Structure (index.html):
- Added complete audio-management-screen after image management
- Audio import buttons for background/ambient/effects categories
- Audio gallery with tabbed interface (background, ambient, effects)
- Audio preview section with HTML5 audio player
- Gallery controls: select all, deselect all, delete, preview
- Manage Audio button added to main start screen
- Professional layout matching image management design

CSS Styling (styles.css):
- Complete audio management styling system
- Audio gallery grid layout with 280px cards
- Audio item cards with icons, titles, controls
- Tabbed interface styling with active states
- Audio preview section with integrated player
- Category-specific icons ( background,  ambient,  effects)
- Hover effects, selection states, and responsive design
- Consistent with image management visual patterns

JavaScript Functionality (game.js):
- showAudioManagement() - main screen initialization
- setupAudioManagementEventListeners() - comprehensive event handling
- loadAudioGallery() - populate all three category galleries
- Audio tab switching (background/ambient/effects)
- Audio selection/deselection with click handlers
- Audio preview system with HTML5 player integration
- Delete functionality for selected audio files
- Enable/disable audio file toggles
- Audio storage info modal with desktop/web mode details
- Complete event listener management with duplicate prevention
- Audio discovery initialization in constructor

Features:
- Three-category audio organization (background, ambient, effects)
- Click-to-select audio items with visual feedback
- Preview selected audio files with integrated player
- Bulk operations: select all, deselect all, delete selected
- Enable/disable individual audio files
- Desktop file management integration
- Automatic audio directory scanning
- Storage information with category breakdowns
- Professional gallery layout with metadata display

Ready for Phase 3: Integration with MusicManager for playlist functionality
This commit is contained in:
fritzsenpai 2025-09-25 21:27:21 -05:00
parent c67d4dca27
commit 9c8876b89f
3 changed files with 715 additions and 0 deletions

447
game.js
View File

@ -27,6 +27,8 @@ class TaskChallengeGame {
this.timerInterval = null;
this.imageDiscoveryComplete = false;
this.imageManagementListenersAttached = false;
this.audioDiscoveryComplete = false;
this.audioManagementListenersAttached = false;
this.musicManager = new MusicManager(this.dataManager);
this.initializeEventListeners();
this.setupKeyboardShortcuts();
@ -34,6 +36,7 @@ class TaskChallengeGame {
this.discoverImages().then(() => {
this.showScreen('start-screen');
});
this.discoverAudio();
// Check for auto-resume after initialization
this.checkAutoResume();
@ -297,6 +300,29 @@ class TaskChallengeGame {
return `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjBmMGYwIiBzdHJva2U9IiNjY2MiIHN0cm9rZS13aWR0aD0iMiIvPjx0ZXh0IHg9IjUwJSIgeT0iNDAlIiBmb250LXNpemU9IjE2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSIgZmlsbD0iIzY2NiI+JHtsYWJlbH08L3RleHQ+PHRleHQgeD0iNTAlIiB5PSI2MCUiIGZvbnQtc2l6ZT0iMTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIiBmaWxsPSIjOTk5Ij5BZGQgaW1hZ2VzIHRvIGZvbGRlcjwvdGV4dD48L3N2Zz4=`;
}
// Audio Discovery Functions
async discoverAudio() {
try {
console.log('Discovering audio files...');
// Initialize audio discovery - scan directories if desktop mode
if (this.fileManager) {
await this.fileManager.scanDirectoryForAudio('background');
await this.fileManager.scanDirectoryForAudio('ambient');
await this.fileManager.scanDirectoryForAudio('effects');
console.log('Desktop audio discovery completed');
} else {
console.log('Web mode - audio discovery skipped');
}
} catch (error) {
console.log('Audio discovery failed:', error);
}
this.audioDiscoveryComplete = true;
console.log('Audio discovery completed');
}
initializeEventListeners() {
// Screen navigation
document.getElementById('start-btn').addEventListener('click', () => this.startGame());
@ -346,6 +372,9 @@ class TaskChallengeGame {
// Image management - only the main button, others will be attached when screen is shown
document.getElementById('manage-images-btn').addEventListener('click', () => this.showImageManagement());
// Audio management - only the main button, others will be attached when screen is shown
document.getElementById('manage-audio-btn').addEventListener('click', () => this.showAudioManagement());
// Load saved theme
this.loadSavedTheme();
}
@ -1765,6 +1794,424 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
return `${baseMessage}: ${specificMessage}`;
}
// Audio Management Functions
showAudioManagement() {
// Reset listener flag to allow fresh attachment
this.audioManagementListenersAttached = false;
this.showScreen('audio-management-screen');
this.setupAudioManagementEventListeners();
// Wait for audio discovery to complete before loading gallery
if (!this.audioDiscoveryComplete) {
const galleries = document.querySelectorAll('.audio-gallery');
galleries.forEach(gallery => {
gallery.innerHTML = '<div class="loading">Discovering audio files...</div>';
});
// Wait and try again
setTimeout(() => {
if (this.audioDiscoveryComplete) {
this.loadAudioGallery();
} else {
galleries.forEach(gallery => {
gallery.innerHTML = '<div class="loading">Still discovering audio... Please wait</div>';
});
setTimeout(() => this.loadAudioGallery(), 1000);
}
}, 500);
} else {
this.loadAudioGallery();
}
}
switchAudioTab(tabType) {
// Update tab buttons
const backgroundTab = document.getElementById('background-audio-tab');
const ambientTab = document.getElementById('ambient-audio-tab');
const effectsTab = document.getElementById('effects-audio-tab');
const backgroundGallery = document.getElementById('background-audio-gallery');
const ambientGallery = document.getElementById('ambient-audio-gallery');
const effectsGallery = document.getElementById('effects-audio-gallery');
// Remove active class from all tabs and galleries
[backgroundTab, ambientTab, effectsTab].forEach(tab => tab && tab.classList.remove('active'));
[backgroundGallery, ambientGallery, effectsGallery].forEach(gallery => gallery && gallery.classList.remove('active'));
// Add active class to selected tab and gallery
if (tabType === 'background') {
backgroundTab && backgroundTab.classList.add('active');
backgroundGallery && backgroundGallery.classList.add('active');
} else if (tabType === 'ambient') {
ambientTab && ambientTab.classList.add('active');
ambientGallery && ambientGallery.classList.add('active');
} else if (tabType === 'effects') {
effectsTab && effectsTab.classList.add('active');
effectsGallery && effectsGallery.classList.add('active');
}
// Update gallery controls to work with current tab
this.updateAudioGalleryControls(tabType);
}
setupAudioManagementEventListeners() {
// Check if we already have listeners attached to prevent duplicates
if (this.audioManagementListenersAttached) {
return;
}
// Back button
const backBtn = document.getElementById('back-to-start-from-audio-btn');
if (backBtn) {
backBtn.onclick = () => this.showScreen('start-screen');
}
// Desktop import buttons
const importBackgroundBtn = document.getElementById('import-background-audio-btn');
if (importBackgroundBtn) {
importBackgroundBtn.onclick = async () => {
if (this.fileManager) {
await this.fileManager.selectAndImportAudio('background');
this.loadAudioGallery(); // Refresh the gallery to show new audio
} else {
this.showNotification('Desktop file manager not available', 'warning');
}
};
}
const importAmbientBtn = document.getElementById('import-ambient-audio-btn');
if (importAmbientBtn) {
importAmbientBtn.onclick = async () => {
if (this.fileManager) {
await this.fileManager.selectAndImportAudio('ambient');
this.loadAudioGallery(); // Refresh the gallery to show new audio
} else {
this.showNotification('Desktop file manager not available', 'warning');
}
};
}
const importEffectsBtn = document.getElementById('import-effects-audio-btn');
if (importEffectsBtn) {
importEffectsBtn.onclick = async () => {
if (this.fileManager) {
await this.fileManager.selectAndImportAudio('effects');
this.loadAudioGallery(); // Refresh the gallery to show new audio
} else {
this.showNotification('Desktop file manager not available', 'warning');
}
};
}
// Audio storage info button
const audioStorageInfoBtn = document.getElementById('audio-storage-info-btn');
if (audioStorageInfoBtn) {
audioStorageInfoBtn.onclick = () => this.showAudioStorageInfo();
}
// Tab buttons
const backgroundAudioTab = document.getElementById('background-audio-tab');
if (backgroundAudioTab) {
backgroundAudioTab.onclick = () => this.switchAudioTab('background');
}
const ambientAudioTab = document.getElementById('ambient-audio-tab');
if (ambientAudioTab) {
ambientAudioTab.onclick = () => this.switchAudioTab('ambient');
}
const effectsAudioTab = document.getElementById('effects-audio-tab');
if (effectsAudioTab) {
effectsAudioTab.onclick = () => this.switchAudioTab('effects');
}
// Gallery control buttons - assign onclick directly to avoid expensive DOM operations
const selectAllAudioBtn = document.getElementById('select-all-audio-btn');
if (selectAllAudioBtn) {
selectAllAudioBtn.onclick = () => this.selectAllAudio();
}
const deselectAllAudioBtn = document.getElementById('deselect-all-audio-btn');
if (deselectAllAudioBtn) {
deselectAllAudioBtn.onclick = () => this.deselectAllAudio();
}
const deleteSelectedAudioBtn = document.getElementById('delete-selected-audio-btn');
if (deleteSelectedAudioBtn) {
deleteSelectedAudioBtn.onclick = () => this.deleteSelectedAudio();
}
const previewSelectedAudioBtn = document.getElementById('preview-selected-audio-btn');
if (previewSelectedAudioBtn) {
previewSelectedAudioBtn.onclick = () => this.previewSelectedAudio();
}
// Close preview button
const closePreviewBtn = document.getElementById('close-preview-btn');
if (closePreviewBtn) {
closePreviewBtn.onclick = () => this.closeAudioPreview();
}
// Mark listeners as attached
this.audioManagementListenersAttached = true;
}
loadAudioGallery() {
const backgroundGallery = document.getElementById('background-audio-gallery');
const ambientGallery = document.getElementById('ambient-audio-gallery');
const effectsGallery = document.getElementById('effects-audio-gallery');
if (!backgroundGallery || !ambientGallery || !effectsGallery) {
console.error('Audio gallery elements not found');
return;
}
// Get custom audio from storage
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
// Load each category
this.loadAudioCategory('background', backgroundGallery, customAudio.background);
this.loadAudioCategory('ambient', ambientGallery, customAudio.ambient);
this.loadAudioCategory('effects', effectsGallery, customAudio.effects);
// Update audio count
this.updateAudioCount();
// Setup initial gallery controls for the active tab
this.updateAudioGalleryControls('background');
}
loadAudioCategory(category, gallery, audioFiles) {
if (!audioFiles || audioFiles.length === 0) {
gallery.innerHTML = `<div class="no-audio">No ${category} audio files found. Use the import button to add some!</div>`;
return;
}
const audioItems = audioFiles.map(audio => {
return `
<div class="audio-item" data-category="${category}" data-filename="${audio.filename}" onclick="game.toggleAudioSelection(this)">
<div class="audio-icon" data-category="${category}"></div>
<div class="audio-title">${audio.title}</div>
<div class="audio-filename">${audio.filename}</div>
<div class="audio-controls">
<button class="audio-preview-btn" onclick="event.stopPropagation(); game.previewAudio('${audio.path}', '${audio.title}')">🎧 Preview</button>
<label class="audio-status" onclick="event.stopPropagation()">
<input type="checkbox" class="audio-checkbox" ${audio.enabled !== false ? 'checked' : ''}
onchange="game.toggleAudioEnabled('${category}', '${audio.filename}', this.checked)">
<span class="${audio.enabled !== false ? 'audio-enabled' : 'audio-disabled'}">
${audio.enabled !== false ? 'Enabled' : 'Disabled'}
</span>
</label>
</div>
</div>
`;
}).join('');
gallery.innerHTML = audioItems;
}
updateAudioGalleryControls(activeCategory = 'background') {
// This will be called when the active tab changes to update controls
// for the current category
console.log(`Audio gallery controls updated for ${activeCategory} category`);
}
updateAudioCount() {
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
const backgroundCount = customAudio.background ? customAudio.background.length : 0;
const ambientCount = customAudio.ambient ? customAudio.ambient.length : 0;
const effectsCount = customAudio.effects ? customAudio.effects.length : 0;
const total = backgroundCount + ambientCount + effectsCount;
const audioCountElement = document.querySelector('.audio-count');
if (audioCountElement) {
audioCountElement.textContent = `${total} total audio files (${backgroundCount} background, ${ambientCount} ambient, ${effectsCount} effects)`;
}
}
selectAllAudio() {
const activeGallery = document.querySelector('.audio-gallery.active');
if (activeGallery) {
const audioItems = activeGallery.querySelectorAll('.audio-item');
audioItems.forEach(item => item.classList.add('selected'));
}
}
deselectAllAudio() {
const activeGallery = document.querySelector('.audio-gallery.active');
if (activeGallery) {
const audioItems = activeGallery.querySelectorAll('.audio-item');
audioItems.forEach(item => item.classList.remove('selected'));
}
}
deleteSelectedAudio() {
const activeGallery = document.querySelector('.audio-gallery.active');
if (!activeGallery) return;
const selectedItems = activeGallery.querySelectorAll('.audio-item.selected');
if (selectedItems.length === 0) {
this.showNotification('No audio files selected', 'warning');
return;
}
if (!confirm(`Are you sure you want to delete ${selectedItems.length} selected audio file(s)?`)) {
return;
}
let deletedCount = 0;
const isDesktop = window.electronAPI !== undefined;
selectedItems.forEach(async (item) => {
const category = item.dataset.category;
const filename = item.dataset.filename;
if (isDesktop && this.fileManager) {
// Desktop mode - delete actual file
const success = await this.fileManager.deleteAudio(category, filename);
if (success) {
deletedCount++;
} else {
console.error(`Failed to delete audio file: ${filename}`);
}
} else {
// Web mode - remove from storage only
this.removeAudioFromStorage(category, filename);
deletedCount++;
}
});
if (deletedCount > 0) {
const modeText = isDesktop ? 'file(s) deleted from disk' : 'reference(s) removed from storage';
this.showNotification(`${deletedCount} audio ${modeText}`, 'success');
this.loadAudioGallery(); // Refresh the gallery
}
}
removeAudioFromStorage(category, filename) {
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
if (customAudio[category]) {
customAudio[category] = customAudio[category].filter(audio => audio.filename !== filename);
this.dataManager.set('customAudio', customAudio);
}
}
previewSelectedAudio() {
const activeGallery = document.querySelector('.audio-gallery.active');
if (!activeGallery) return;
const selectedItems = activeGallery.querySelectorAll('.audio-item.selected');
if (selectedItems.length === 0) {
this.showNotification('No audio file selected for preview', 'warning');
return;
}
if (selectedItems.length > 1) {
this.showNotification('Please select only one audio file for preview', 'warning');
return;
}
const selectedItem = selectedItems[0];
const category = selectedItem.dataset.category;
const filename = selectedItem.dataset.filename;
// Find the audio file data
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
const audioFile = customAudio[category].find(audio => audio.filename === filename);
if (audioFile) {
this.previewAudio(audioFile.path, audioFile.title);
}
}
previewAudio(audioPath, title) {
const previewSection = document.getElementById('audio-preview-section');
const audioPlayer = document.getElementById('audio-preview-player');
const previewFileName = document.getElementById('preview-file-name');
if (previewSection && audioPlayer && previewFileName) {
previewSection.style.display = 'block';
audioPlayer.src = audioPath;
previewFileName.textContent = title || 'Unknown';
// Scroll to preview section
previewSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
closeAudioPreview() {
const previewSection = document.getElementById('audio-preview-section');
const audioPlayer = document.getElementById('audio-preview-player');
if (previewSection && audioPlayer) {
previewSection.style.display = 'none';
audioPlayer.pause();
audioPlayer.src = '';
}
}
toggleAudioEnabled(category, filename, enabled) {
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
if (customAudio[category]) {
const audioFile = customAudio[category].find(audio => audio.filename === filename);
if (audioFile) {
audioFile.enabled = enabled;
this.dataManager.set('customAudio', customAudio);
// Update the visual status
this.loadAudioGallery();
this.showNotification(`Audio ${enabled ? 'enabled' : 'disabled'}`, 'success');
}
}
}
toggleAudioSelection(audioItem) {
audioItem.classList.toggle('selected');
}
showAudioStorageInfo() {
try {
const isDesktop = window.electronAPI !== undefined;
const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
const backgroundCount = customAudio.background ? customAudio.background.length : 0;
const ambientCount = customAudio.ambient ? customAudio.ambient.length : 0;
const effectsCount = customAudio.effects ? customAudio.effects.length : 0;
const totalCustomAudio = backgroundCount + ambientCount + effectsCount;
let message = `📊 Audio Storage Information\n\n`;
if (isDesktop) {
message += `🖥️ Desktop Mode - Unlimited Storage\n`;
message += `Files stored in native file system\n\n`;
message += `📁 Audio Categories:\n`;
message += `🎵 Background Music: ${backgroundCount} files\n`;
message += `🌿 Ambient Sounds: ${ambientCount} files\n`;
message += `🔊 Sound Effects: ${effectsCount} files\n`;
message += `📊 Total Custom Audio: ${totalCustomAudio} files\n\n`;
message += `💾 Storage: Uses native file system\n`;
message += `📂 Location: audio/ folder in app directory\n`;
message += `🔄 Auto-scanned on startup`;
} else {
message += `🌐 Web Mode - Browser Storage\n`;
message += `Limited by browser storage quotas\n\n`;
message += `📁 Audio References:\n`;
message += `🎵 Background Music: ${backgroundCount} files\n`;
message += `🌿 Ambient Sounds: ${ambientCount} files\n`;
message += `🔊 Sound Effects: ${effectsCount} files\n`;
message += `📊 Total Custom Audio: ${totalCustomAudio} files\n\n`;
message += `💾 Storage: Browser localStorage\n`;
message += `⚠️ Subject to browser storage limits`;
}
alert(message);
} catch (error) {
console.error('Error showing audio storage info:', error);
alert('Error retrieving audio storage information.');
}
}
showNotification(message, type = 'info') {
// Create notification element if it doesn't exist
let notification = document.getElementById('notification');

View File

@ -48,6 +48,7 @@
<button id="start-btn" class="btn btn-primary">Start Game</button>
<button id="manage-tasks-btn" class="btn btn-secondary">Manage Tasks</button>
<button id="manage-images-btn" class="btn btn-secondary">Manage Images</button>
<button id="manage-audio-btn" class="btn btn-secondary">🎵 Manage Audio</button>
</div>
<!-- Options Menu -->
@ -195,6 +196,79 @@
</div>
</div>
<!-- Audio Management Screen -->
<div id="audio-management-screen" class="screen">
<h2>Manage Your Audio</h2>
<!-- Upload Section -->
<div class="upload-section">
<h3>🎵 Import Audio Files</h3>
<div class="upload-controls">
<button id="import-background-audio-btn" class="btn btn-primary">🎶 Import Background Music</button>
<button id="import-ambient-audio-btn" class="btn btn-success">🌿 Import Ambient Sounds</button>
<button id="import-effects-audio-btn" class="btn btn-warning">🔊 Import Sound Effects</button>
<input type="file" id="audio-upload-input" accept="audio/*" multiple style="display: none;">
<span class="upload-info desktop-feature">Desktop: Native file dialogs • MP3, WAV, OGG, M4A, AAC, FLAC</span>
<span class="upload-info web-feature" style="display: none;">Web: Limited browser upload</span>
</div>
<div class="directory-controls">
<button id="audio-storage-info-btn" class="btn btn-outline">📊 Audio Storage Info</button>
<span class="scan-info">Audio files automatically scanned on startup</span>
</div>
</div>
<!-- Audio Gallery Section -->
<div class="gallery-section">
<div class="gallery-header">
<h3>Current Audio Files</h3>
<div class="gallery-controls">
<button id="select-all-audio-btn" class="btn btn-small">Select All</button>
<button id="deselect-all-audio-btn" class="btn btn-small">Deselect All</button>
<button id="delete-selected-audio-btn" class="btn btn-danger btn-small">Delete Selected</button>
<button id="preview-selected-audio-btn" class="btn btn-info btn-small">🎧 Preview</button>
<span class="audio-count">Loading audio files...</span>
</div>
</div>
<!-- Audio Tabs -->
<div class="audio-tabs">
<button id="background-audio-tab" class="tab-btn active">Background Music</button>
<button id="ambient-audio-tab" class="tab-btn">Ambient Sounds</button>
<button id="effects-audio-tab" class="tab-btn">Sound Effects</button>
</div>
<div id="background-audio-gallery" class="audio-gallery active">
<!-- Background audio files will be populated here -->
</div>
<div id="ambient-audio-gallery" class="audio-gallery">
<!-- Ambient audio files will be populated here -->
</div>
<div id="effects-audio-gallery" class="audio-gallery">
<!-- Effects audio files will be populated here -->
</div>
</div>
<!-- Audio Preview Section -->
<div class="audio-preview-section" id="audio-preview-section" style="display: none;">
<h4>🎧 Audio Preview</h4>
<div class="preview-controls">
<audio id="audio-preview-player" controls style="width: 100%; max-width: 500px;">
Your browser does not support the audio element.
</audio>
<div class="preview-info">
<span id="preview-file-name">No file selected</span>
<button id="close-preview-btn" class="btn btn-small btn-outline">Close Preview</button>
</div>
</div>
</div>
<div class="management-buttons">
<button id="back-to-start-from-audio-btn" class="btn btn-secondary">Back to Start</button>
</div>
</div>
<!-- Game Screen -->
<div id="game-screen" class="screen">
<div class="task-container">

View File

@ -1454,4 +1454,198 @@ body.theme-monochrome {
background: #f8f9fa;
border-radius: 8px;
border: 2px dashed #dee2e6;
}
/* Audio Management Styles */
.audio-tabs {
display: flex;
margin-bottom: 15px;
border-bottom: 2px solid #dee2e6;
}
.audio-tabs .tab-btn {
flex: 1;
padding: 12px 16px;
border: none;
background: #f8f9fa;
color: #6c757d;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
font-weight: 600;
}
.audio-tabs .tab-btn:hover {
background: #e9ecef;
color: #495057;
}
.audio-tabs .tab-btn.active {
background: white;
color: #007bff;
border-bottom-color: #007bff;
}
.audio-count {
font-size: 0.9em;
color: #6c757d;
font-weight: 500;
}
.audio-gallery {
display: none; /* Hide galleries by default */
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 15px;
max-height: 400px;
overflow-y: auto;
padding: 10px;
border: 1px solid #dee2e6;
border-radius: 8px;
}
.audio-gallery.active {
display: grid; /* Show active gallery as grid */
}
.audio-item {
position: relative;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
padding: 15px;
}
.audio-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.audio-item.selected {
border: 3px solid #007bff;
transform: scale(0.98);
background: #f8f9ff;
}
.audio-icon {
font-size: 2.5em;
text-align: center;
margin-bottom: 10px;
color: #007bff;
}
.audio-title {
font-size: 0.9em;
color: #333;
margin-bottom: 8px;
font-weight: 600;
word-break: break-word;
text-align: center;
}
.audio-filename {
font-size: 0.7em;
color: #6c757d;
margin-bottom: 10px;
text-align: center;
word-break: break-word;
}
.audio-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
.audio-preview-btn {
background: #28a745;
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8em;
transition: background 0.3s ease;
}
.audio-preview-btn:hover {
background: #218838;
}
.audio-checkbox {
margin: 0;
}
.audio-status {
font-size: 0.7em;
padding: 2px 6px;
border-radius: 4px;
font-weight: bold;
}
.audio-enabled {
background: #d4edda;
color: #155724;
}
.audio-disabled {
background: #f8d7da;
color: #721c24;
}
.audio-preview-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.audio-preview-section h4 {
margin-bottom: 15px;
color: #333;
}
.preview-controls {
display: flex;
flex-direction: column;
gap: 10px;
}
.preview-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
.preview-info span {
font-size: 0.9em;
color: #6c757d;
font-weight: 500;
}
.no-audio {
text-align: center;
padding: 40px;
color: #6c757d;
font-style: italic;
background: #f8f9fa;
border-radius: 8px;
border: 2px dashed #dee2e6;
}
/* Audio category icons */
.audio-item[data-category="background"] .audio-icon::before {
content: "🎵";
}
.audio-item[data-category="ambient"] .audio-icon::before {
content: "🌿";
}
.audio-item[data-category="effects"] .audio-icon::before {
content: "🔊";
}