|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Dungeon Master Soundscapes</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/howler.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=MedievalSharp&display=swap" rel="stylesheet"> |
|
|
|
<script> |
|
tailwind.config = { |
|
theme: { |
|
extend: { |
|
colors: { |
|
primary: '#D4AF37', |
|
secondary: '#5C2D91', |
|
dark: '#1A0B23', |
|
light: '#F2EBD3' |
|
}, |
|
fontFamily: { |
|
medieval: ['Cinzel', 'MedievalSharp', 'serif'], |
|
body: ['Roboto', 'sans-serif'] |
|
}, |
|
backgroundImage: { |
|
'paper-texture': "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIj4KICA8cmVjdCB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCIgZmlsbD0iI0YyRUJEMyIvPgogIDxwYXRoIGQ9Ik0gMCwwIEwgMCw1MDAgTCA1MDAsNTAwIEwgNTAwLDAgeiIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz4KICA8cGF0aCBkPSJNIDI1LDI1IGMgMTAsMTAgMTUsMjAgMTUsMzAgIiBzdHJva2U9IiNGOEYyREIiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMC4yIi8+Cjwvc3ZnPg==')" |
|
} |
|
} |
|
} |
|
} |
|
</script> |
|
|
|
<style type="text/css"> |
|
body { |
|
font-family: 'Roboto', sans-serif; |
|
background: #1A0B23; |
|
color: #F2EBD3; |
|
overflow-x: hidden; |
|
touch-action: manipulation; |
|
} |
|
|
|
.medieval-font { |
|
font-family: 'Cinzel', 'MedievalSharp', serif; |
|
letter-spacing: 0.5px; |
|
} |
|
|
|
.scrollbar-hide::-webkit-scrollbar { |
|
display: none; |
|
} |
|
|
|
.scrollbar-hide { |
|
-ms-overflow-style: none; |
|
scrollbar-width: none; |
|
} |
|
|
|
.gradient-border { |
|
border: 2px solid transparent; |
|
background-clip: padding-box; |
|
position: relative; |
|
background: #1A0B23; |
|
} |
|
|
|
.gradient-border::before { |
|
content: ''; |
|
position: absolute; |
|
top: -2px; |
|
left: -2px; |
|
right: -2px; |
|
bottom: -2px; |
|
background: linear-gradient(135deg, #D4AF37, #5C2D91); |
|
z-index: -1; |
|
border-radius: inherit; |
|
} |
|
|
|
.sound-item:hover, .pad:hover { |
|
transform: scale(1.02); |
|
box-shadow: 0 4px 15px rgba(212, 175, 55, 0.3); |
|
transition: all 0.2s ease; |
|
} |
|
|
|
.playing { |
|
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.6); |
|
animation: pulse 1.5s infinite; |
|
} |
|
|
|
@keyframes pulse { |
|
0% { box-shadow: 0 0 0 0 rgba(212, 175, 55, 0.6); } |
|
70% { box-shadow: 0 0 0 10px rgba(212, 175, 55, 0); } |
|
100% { box-shadow: 0 0 0 0 rgba(212, 175, 55, 0); } |
|
} |
|
|
|
.volume-slider { |
|
-webkit-appearance: none; |
|
appearance: none; |
|
height: 6px; |
|
border-radius: 3px; |
|
background: #5C2D91; |
|
outline: none; |
|
} |
|
|
|
.volume-slider::-webkit-slider-thumb { |
|
-webkit-appearance: none; |
|
appearance: none; |
|
width: 18px; |
|
height: 18px; |
|
border-radius: 50%; |
|
background: #D4AF37; |
|
cursor: pointer; |
|
} |
|
|
|
.sound-wave { |
|
display: flex; |
|
align-items: flex-end; |
|
justify-content: space-between; |
|
height: 40px; |
|
width: 100%; |
|
} |
|
|
|
.sound-bar { |
|
width: 3px; |
|
background-color: #D4AF37; |
|
border-radius: 2px; |
|
margin: 0 1px; |
|
} |
|
|
|
.page-enter { |
|
animation: fadeIn 0.3s forwards; |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
@media (max-width: 640px) { |
|
.bottom-nav { |
|
box-shadow: 0 -3px 10px rgba(0,0,0,0.2); |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body class="min-h-screen bg-gradient-to-b from-dark to-secondary"> |
|
<div id="app" class="flex flex-col min-h-screen max-w-full overflow-hidden"> |
|
|
|
<header class="gradient-border px-4 py-3 flex items-center justify-between"> |
|
<button id="menuBtn" class="text-primary text-2xl"> |
|
<i class="fas fa-bars"></i> |
|
</button> |
|
<h1 class="medieval-font text-2xl text-primary text-center font-bold"> |
|
Dungeon Master Soundscapes |
|
</h1> |
|
<button class="text-primary text-2xl"> |
|
<i class="fas fa-user"></i> |
|
</button> |
|
</header> |
|
|
|
|
|
<main class="flex-grow overflow-hidden relative" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cGF0aCBkPSJNMCAwTDEwMCAwTDEwMCAxMDBMMCAxMDBaTTAgMEw1MCA1MFo='); background-size: 20px 20px; background-color: #1A0B23; background-blend-mode: overlay; opacity: 0.15;"> |
|
<div id="page-content" class="absolute inset-0 overflow-auto scrollbar-hide p-4"> |
|
|
|
</div> |
|
</main> |
|
|
|
|
|
<nav id="bottomNav" class="bottom-nav bg-secondary flex justify-around py-2 border-t border-primary"> |
|
<button data-page="dashboard" class="nav-item flex flex-col items-center text-primary hover:text-white"> |
|
<i class="fas fa-home text-xl"></i> |
|
<span class="text-xs mt-1">Dashboard</span> |
|
</button> |
|
<button data-page="sounds" class="nav-item flex flex-col items-center text-primary hover:text-white"> |
|
<i class="fas fa-music text-xl"></i> |
|
<span class="text-xs mt-1">Sounds</span> |
|
</button> |
|
<button data-page="pad" class="nav-item flex flex-col items-center text-primary hover:text-white"> |
|
<i class="fas fa-th-large text-xl"></i> |
|
<span class="text-xs mt-1">Sound Pad</span> |
|
</button> |
|
<button data-page="playlists" class="nav-item flex flex-col items-center text-primary hover:text-white"> |
|
<i class="fas fa-list text-xl"></i> |
|
<span class="text-xs mt-1">Playlists</span> |
|
</button> |
|
<button data-page="projects" class="nav-item flex flex-col items-center text-primary hover:text-white"> |
|
<i class="fas fa-folder text-xl"></i> |
|
<span class="text-xs mt-1">Projects</span> |
|
</button> |
|
</nav> |
|
</div> |
|
|
|
<script> |
|
// Main app state |
|
const state = { |
|
currentPage: 'dashboard', |
|
sounds: [], |
|
playlists: [], |
|
projects: [], |
|
currentProject: null, |
|
playingSounds: {}, |
|
volume: 0.7 |
|
}; |
|
|
|
// DOM Elements |
|
const elements = { |
|
pageContent: document.getElementById('page-content'), |
|
bottomNav: document.getElementById('bottomNav'), |
|
menuBtn: document.getElementById('menuBtn') |
|
}; |
|
|
|
// Initialize the app |
|
function initApp() { |
|
// Load state from localStorage |
|
loadState(); |
|
|
|
// Setup navigation |
|
setupNavigation(); |
|
|
|
// Render initial page |
|
renderPage(state.currentPage); |
|
|
|
// Load sample data if empty |
|
setupSampleData(); |
|
} |
|
|
|
// Setup bottom navigation |
|
function setupNavigation() { |
|
// Handle bottom nav clicks |
|
elements.bottomNav.querySelectorAll('.nav-item').forEach(item => { |
|
item.addEventListener('click', () => { |
|
const page = item.dataset.page; |
|
navigateTo(page); |
|
}); |
|
}); |
|
|
|
// Handle menu button |
|
elements.menuBtn.addEventListener('click', showMainMenu); |
|
} |
|
|
|
// Navigate to a page |
|
function navigateTo(pageName) { |
|
state.currentPage = pageName; |
|
renderPage(pageName); |
|
|
|
// Update active nav item |
|
elements.bottomNav.querySelectorAll('.nav-item').forEach(item => { |
|
const isActive = item.dataset.page === pageName; |
|
item.classList.toggle('text-white', isActive); |
|
item.classList.toggle('text-primary', !isActive); |
|
}); |
|
} |
|
|
|
// Render the current page |
|
function renderPage(pageName) { |
|
elements.pageContent.innerHTML = ''; |
|
elements.pageContent.classList.add('page-enter'); |
|
|
|
switch(pageName) { |
|
case 'dashboard': |
|
renderDashboard(); |
|
break; |
|
case 'sounds': |
|
renderSoundLibrary(); |
|
break; |
|
case 'pad': |
|
renderSoundPad(); |
|
break; |
|
case 'playlists': |
|
renderPlaylists(); |
|
break; |
|
case 'projects': |
|
renderProjects(); |
|
break; |
|
default: |
|
renderDashboard(); |
|
} |
|
} |
|
|
|
// Sample Pages Rendering Functions |
|
|
|
function renderDashboard() { |
|
const content = ` |
|
<div class="p-4"> |
|
<h2 class="medieval-font text-xl text-primary mb-4 font-bold">Current Project</h2> |
|
${renderProjectCard()} |
|
|
|
<div class="mt-8"> |
|
<h2 class="medieval-font text-xl text-primary mb-4 font-bold">Quick Actions</h2> |
|
<div class="grid grid-cols-2 gap-4"> |
|
<button class="bg-secondary py-4 rounded-lg flex flex-col items-center justify-center gradient-border"> |
|
<i class="fas fa-plus-circle text-primary text-3xl mb-2"></i> |
|
<span class="text-primary">New Sound</span> |
|
</button> |
|
<button class="bg-secondary py-4 rounded-lg flex flex-col items-center justify-center gradient-border"> |
|
<i class="fas fa-headphones text-primary text-3xl mb-2"></i> |
|
<span class="text-primary">Ambience</span> |
|
</button> |
|
<button class="bg-secondary py-4 rounded-lg flex flex-col items-center justify-center gradient-border"> |
|
<i class="fas fa-cloud-download-alt text-primary text-3xl mb-2"></i> |
|
<span class="text-primary">Import</span> |
|
</button> |
|
<button class="bg-secondary py-4 rounded-lg flex flex-col items-center justify-center gradient-border"> |
|
<i class="fas fa-dragon text-primary text-3xl mb-2"></i> |
|
<span class="text-primary">Monsters</span> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="mt-8"> |
|
<h2 class="medieval-font text-xl text-primary mb-4 font-bold">Recent Sounds</h2> |
|
<div class="grid grid-cols-3 gap-3"> |
|
${state.sounds.slice(0, 6).map(sound => ` |
|
<div class="sound-item bg-secondary rounded-lg p-3 cursor-pointer gradient-border"> |
|
<div class="text-primary text-lg mb-2"><i class="fas ${sound.icon}"></i></div> |
|
<p class="text-white truncate text-xs">${sound.name}</p> |
|
</div> |
|
`).join('')} |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
elements.pageContent.innerHTML = content; |
|
} |
|
|
|
function renderProjectCard() { |
|
if (!state.currentProject) { |
|
return ` |
|
<div class="bg-secondary rounded-lg p-4 mb-4 gradient-border"> |
|
<p class="text-light mb-4">No project currently selected</p> |
|
<button class="bg-primary hover:bg-opacity-90 text-dark py-2 px-4 rounded-md w-full medieval-font"> |
|
Create New Project |
|
</button> |
|
</div> |
|
`; |
|
} |
|
|
|
const project = state.currentProject; |
|
return ` |
|
<div class="bg-secondary rounded-lg p-4 mb-4 gradient-border"> |
|
<div class="flex justify-between items-center mb-3"> |
|
<h3 class="text-lg text-primary medieval-font">${project.name}</h3> |
|
<span class="text-xs text-light">${project.lastEdited}</span> |
|
</div> |
|
<p class="text-light text-sm mb-4">${project.description}</p> |
|
<div class="grid grid-cols-3 gap-2 text-center text-xs text-light"> |
|
<div class="bg-dark py-1 rounded"> |
|
<div class="text-primary font-bold">${project.playlists.length}</div> |
|
<div>Playlists</div> |
|
</div> |
|
<div class="bg-dark py-1 rounded"> |
|
<div class="text-primary font-bold">${countProjectSounds(project)}</div> |
|
<div>Sounds</div> |
|
</div> |
|
<div class="bg-dark py-1 rounded"> |
|
<div class="text-primary font-bold">5</div> |
|
<div>Pads</div> |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
} |
|
|
|
function renderSoundLibrary() { |
|
const content = ` |
|
<div class="p-4"> |
|
<div class="flex mb-4"> |
|
<input type="text" placeholder="Search sounds..." class="flex-grow bg-dark text-light p-2 rounded-l-lg border border-secondary focus:outline-none"> |
|
<button class="bg-primary text-dark p-2 rounded-r-lg"> |
|
<i class="fas fa-search"></i> |
|
</button> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<div class="flex justify-between items-center mb-3"> |
|
<h2 class="medieval-font text-lg text-primary font-bold">All Sounds</h2> |
|
<button class="text-primary text-sm"> |
|
<i class="fas fa-sort"></i> Sort |
|
</button> |
|
</div> |
|
|
|
<div class="grid grid-cols-2 gap-3"> |
|
${state.sounds.slice(0, 12).map(sound => renderSoundItem(sound)).join('')} |
|
</div> |
|
</div> |
|
|
|
<div class="mt-6"> |
|
<div class="flex justify-between items-center mb-3"> |
|
<h2 class="medieval-font text-lg text-primary font-bold">Atmosphere</h2> |
|
</div> |
|
|
|
<div class="grid grid-cols-3 gap-3"> |
|
${state.sounds.slice(12, 18).map(sound => renderSoundItem(sound)).join('')} |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
elements.pageContent.innerHTML = content; |
|
} |
|
|
|
function renderSoundItem(sound) { |
|
const isPlaying = state.playingSounds[sound.id]; |
|
const classes = isPlaying ? 'playing' : ''; |
|
return ` |
|
<div class="sound-item ${classes} bg-secondary rounded-lg p-3 cursor-pointer gradient-border" data-id="${sound.id}"> |
|
<div class="flex justify-between items-start"> |
|
<div class="text-primary text-lg"><i class="fas ${sound.icon}"></i></div> |
|
<button class="text-light text-xs"> |
|
<i class="fas ${isPlaying ? 'fa-stop' : 'fa-play'}"></i> |
|
</button> |
|
</div> |
|
<div class="mt-2"> |
|
<p class="text-white truncate text-sm">${sound.name}</p> |
|
<div class="text-light text-xs flex mt-1"> |
|
<span class="bg-dark px-1 rounded mr-1">${sound.category}</span> |
|
<span class="bg-dark px-1 rounded">${sound.duration}</span> |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
} |
|
|
|
function renderSoundPad() { |
|
const content = ` |
|
<div class="p-4"> |
|
<div class="bg-secondary rounded-lg p-3 mb-4 gradient-border"> |
|
<h2 class="medieval-font text-xl text-primary font-bold text-center">Sound Pad</h2> |
|
<p class="text-center text-light text-sm">Tap any pad to play sounds simultaneously</p> |
|
</div> |
|
|
|
<div class="grid grid-cols-4 gap-3"> |
|
${Array.from({length: 16}, (_, i) => renderPad(i + 1)).join('')} |
|
</div> |
|
|
|
<div class="mt-8"> |
|
<div class="flex items-center"> |
|
<span class="text-light mr-2"><i class="fas fa-volume-up text-primary"></i></span> |
|
<input type="range" min="0" max="1" step="0.01" value="${state.volume}" class="volume-slider flex-grow"> |
|
<span class="text-light ml-2"><i class="fas fa-wave-square text-primary"></i></span> |
|
</div> |
|
</div> |
|
|
|
<div class="flex justify-around mt-8"> |
|
<button class="bg-primary hover:bg-opacity-90 text-dark py-2 px-6 rounded-lg medieval-font font-bold"> |
|
Stop All |
|
</button> |
|
<button class="bg-secondary border border-primary text-primary py-2 px-6 rounded-lg medieval-font font-bold"> |
|
Save |
|
</button> |
|
</div> |
|
</div> |
|
`; |
|
|
|
elements.pageContent.innerHTML = content; |
|
} |
|
|
|
function renderPad(index) { |
|
const sound = state.sounds.length > index ? state.sounds[index] : null; |
|
const padContent = sound ? ` |
|
<div class="text-center"> |
|
<div class="text-primary text-lg mb-1"><i class="fas ${sound.icon}"></i></div> |
|
<div class="text-white text-xs truncate">${sound.name}</div> |
|
</div> |
|
` : ` |
|
<div class="text-center"> |
|
<div class="text-primary text-lg mb-1"><i class="fas fa-plus"></i></div> |
|
<div class="text-light text-xs">Add Sound</div> |
|
</div> |
|
`; |
|
|
|
return ` |
|
<div class="pad aspect-square bg-secondary rounded-lg flex items-center justify-center gradient-border cursor-pointer"> |
|
${padContent} |
|
</div> |
|
`; |
|
} |
|
|
|
function renderPlaylists() { |
|
const content = ` |
|
<div class="p-4"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="medieval-font text-lg text-primary font-bold">Playlists</h2> |
|
<button class="text-primary"> |
|
<i class="fas fa-plus-circle"></i> New |
|
</button> |
|
</div> |
|
|
|
<div class="grid grid-cols-2 gap-4"> |
|
${state.playlists.map(playlist => renderPlaylistCard(playlist)).join('')} |
|
</div> |
|
</div> |
|
`; |
|
|
|
elements.pageContent.innerHTML = content; |
|
} |
|
|
|
function renderPlaylistCard(playlist) { |
|
return ` |
|
<div class="bg-secondary rounded-lg p-3 gradient-border"> |
|
<div class="flex justify-between items-start mb-2"> |
|
<h3 class="text-primary font-bold truncate">${playlist.name}</h3> |
|
<button class="text-light text-xs"> |
|
<i class="fas fa-play"></i> |
|
</button> |
|
</div> |
|
<div class="text-light text-xs mb-3">${playlist.sounds.length} sounds</div> |
|
<div class="text-light text-xs"> |
|
<div class="flex overflow-hidden w-full h-6 items-center mb-1"> |
|
${playlist.sounds.slice(0, 3).map(sound => ` |
|
<div class="rounded-full border border-primary h-4 w-4 flex items-center justify-center mr-1 flex-shrink-0"> |
|
<i class="fas ${sound.icon} text-xs text-primary"></i> |
|
</div> |
|
`).join('')} |
|
</div> |
|
<div class="text-xs text-light opacity-80">${playlist.tags.join(', ')}</div> |
|
</div> |
|
</div> |
|
`; |
|
} |
|
|
|
function renderProjects() { |
|
const content = ` |
|
<div class="p-4"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="medieval-font text-lg text-primary font-bold">Projects</h2> |
|
<button class="text-primary"> |
|
<i class="fas fa-plus-circle"></i> New |
|
</button> |
|
</div> |
|
|
|
${state.projects.map(project => renderProjectItem(project)).join('')} |
|
</div> |
|
`; |
|
|
|
elements.pageContent.innerHTML = content; |
|
} |
|
|
|
function renderProjectItem(project) { |
|
const isActive = project.id === (state.currentProject?.id || null); |
|
const classes = isActive ? 'ring-2 ring-primary' : ''; |
|
return ` |
|
<div class="project-item bg-secondary rounded-lg p-3 mb-3 gradient-border ${classes}"> |
|
<div class="flex justify-between items-center mb-2"> |
|
<h3 class="text-primary font-bold">${project.name}</h3> |
|
<div> |
|
<button class="text-light p-1"> |
|
<i class="fas fa-pen"></i> |
|
</button> |
|
<button class="text-light p-1"> |
|
<i class="fas fa-ellipsis-v"></i> |
|
</button> |
|
</div> |
|
</div> |
|
<div class="text-light text-sm mb-3">${project.description}</div> |
|
<div class="flex justify-between text-xs text-light"> |
|
<div> |
|
<i class="fas fa-play-circle mr-1 text-primary"></i> |
|
${project.playlists.length} playlists |
|
</div> |
|
<div>Last edited: ${project.lastEdited}</div> |
|
</div> |
|
</div> |
|
`; |
|
} |
|
|
|
function showMainMenu() { |
|
const menuContent = ` |
|
<div class="fixed inset-0 bg-black bg-opacity-70 flex z-50"> |
|
<div class="bg-secondary w-4/5 max-w-sm h-full p-4 overflow-auto gradient-border"> |
|
<div class="flex justify-between items-center mb-8"> |
|
<h2 class="medieval-font text-xl text-primary">Menu</h2> |
|
<button id="closeMenuBtn" class="text-primary text-2xl"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<h3 class="medieval-font text-primary text-lg mb-2 font-bold">App Settings</h3> |
|
<div class="pl-4 text-light"> |
|
<div class="flex items-center py-2"> |
|
<i class="fas fa-volume-up mr-3 text-primary"></i> |
|
<div class="flex-grow">Volume</div> |
|
<div class="w-1/3"> |
|
<input type="range" min="0" max="1" step="0.01" value="0.7" class="volume-slider w-full"> |
|
</div> |
|
</div> |
|
<div class="flex items-center py-2"> |
|
<i class="fas fa-music mr-3 text-primary"></i> |
|
<div class="flex-grow">Auto-play</div> |
|
<label class="relative inline-block w-10 h-6"> |
|
<input type="checkbox" class="opacity-0 w-0 h-0 peer"> |
|
<span class="absolute cursor-pointer top-0 left-0 right-0 bottom-0 bg-dark rounded-full transition peer-checked:bg-primary"></span> |
|
<span class="absolute h-4 w-4 bg-white rounded-full left-1 top-1 transition peer-checked:translate-x-4"></span> |
|
</label> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<h3 class="medieval-font text-primary text-lg mb-2 font-bold">Import/Export</h3> |
|
<div class="grid grid-cols-2 gap-2"> |
|
<button class="py-2 text-primary border border-primary rounded-md text-center"> |
|
<i class="fas fa-download mr-1"></i> Import |
|
</button> |
|
<button class="py-2 bg-primary text-dark rounded-md text-center"> |
|
<i class="fas fa-upload mr-1"></i> Export |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<h3 class="medieval-font text-primary text-lg mb-2 font-bold">Pre-made Packs</h3> |
|
<div class="flex overflow-x-auto pb-2 -mx-2 px-2"> |
|
<div class="flex-shrink-0 w-32 mr-3"> |
|
<div class="bg-dark aspect-video rounded flex items-center justify-center"> |
|
<i class="fas fa-dragon text-2xl text-primary"></i> |
|
</div> |
|
<div class="text-light text-xs text-center mt-1">Monsters</div> |
|
</div> |
|
<div class="flex-shrink-0 w-32 mr-3"> |
|
<div class="bg-dark aspect-video rounded flex items-center justify-center"> |
|
<i class="fas fa-city text-2xl text-primary"></i> |
|
</div> |
|
<div class="text-light text-xs text-center mt-1">Cities</div> |
|
</div> |
|
<div class="flex-shrink-0 w-32 mr-3"> |
|
<div class="bg-dark aspect-video rounded flex items-center justify-center"> |
|
<i class="fas fa-water text-2xl text-primary"></i> |
|
</div> |
|
<div class="text-light text-xs text-center mt-1">Nature</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
const menuContainer = document.createElement('div'); |
|
menuContainer.innerHTML = menuContent; |
|
document.body.appendChild(menuContainer); |
|
|
|
menuContainer.querySelector('#closeMenuBtn').addEventListener('click', () => { |
|
menuContainer.remove(); |
|
}); |
|
} |
|
|
|
// Helper Functions |
|
|
|
function loadState() { |
|
// Load from localStorage or localForage |
|
const savedState = localStorage.getItem('dmsoundscapes-state'); |
|
if (savedState) { |
|
Object.assign(state, JSON.parse(savedState)); |
|
} |
|
} |
|
|
|
function saveState() { |
|
localStorage.setItem('dmsoundscapes-state', JSON.stringify(state)); |
|
} |
|
|
|
function setupSampleData() { |
|
if (state.sounds.length === 0) { |
|
state.sounds = [ |
|
{ id: 1, name: "Medieval Tavern", icon: "fa-beer", category: "Ambience", duration: "4:20" }, |
|
{ id: 2, name: "Castle Hall", icon: "fa-landmark", category: "Ambience", duration: "3:45" }, |
|
{ id: 3, name: "Forest Night", icon: "fa-tree", category: "Ambience", duration: "5:15" }, |
|
{ id: 4, name: "Sword Clash", icon: "fa-swords", category: "Combat", duration: "0:07" }, |
|
{ id: 5, name: "Fireball", icon: "fa-fire", category: "Spells", duration: "0:12" }, |
|
{ id: 6, name: "Orc Roar", icon: "fa-dragon", category: "Monsters", duration: "0:08" }, |
|
{ id: 7, name: "Rainstorm", icon: "fa-cloud-rain", category: "Weather", duration: "6:00" }, |
|
{ id: 8, name: "Horse Galloping", icon: "fa-horse", category: "Travel", duration: "0:45" }, |
|
{ id: 9, name: "Crowd Cheer", icon: "fa-users", category: "Ambience", duration: "0:15" }, |
|
{ id: 10, name: "Creaky Door", icon: "fa-door-open", category: "Effects", duration: "0:05" }, |
|
{ id: 11, name: "Dragon Roar", icon: "fa-dragon", category: "Monsters", duration: "0:18" }, |
|
{ id: 12, name: "Magic Portal", icon: "fa-portal-enter", category: "Spells", duration: "0:20" } |
|
]; |
|
} |
|
|
|
if (state.playlists.length === 0) { |
|
state.playlists = [ |
|
{ |
|
id: 1, |
|
name: "Castle Siege", |
|
sounds: [state.sounds[2], state.sounds[3], state.sounds[5], state.sounds[10]], |
|
tags: ["combat", "dramatic"], |
|
lastEdited: "Yesterday" |
|
}, |
|
{ |
|
id: 2, |
|
name: "Peaceful Village", |
|
sounds: [state.sounds[0], state.sounds[1], state.sounds[8], state.sounds[11]], |
|
tags: ["ambience", "calm"], |
|
lastEdited: "2 days ago" |
|
} |
|
]; |
|
} |
|
|
|
if (state.projects.length === 0) { |
|
state.projects = [ |
|
{ |
|
id: 1, |
|
name: "Lost Mines Campaign", |
|
description: "Main campaign sound setup", |
|
playlists: [state.playlists[0], state.playlists[1]], |
|
lastEdited: "Today" |
|
}, |
|
{ |
|
id: 2, |
|
name: "Icewind Dale", |
|
description: "Winter campaign sounds", |
|
playlists: [state.playlists[1]], |
|
lastEdited: "1 week ago" |
|
} |
|
]; |
|
state.currentProject = state.projects[0]; |
|
} |
|
|
|
saveState(); |
|
} |
|
|
|
function countProjectSounds(project) { |
|
return project.playlists.reduce((acc, playlist) => acc + playlist.sounds.length, 0); |
|
} |
|
|
|
// Initialize app when DOM is loaded |
|
document.addEventListener('DOMContentLoaded', initApp); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ZoroaStrella/rawdio" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |