Spaces:
No application file
No application file
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Fantasy Rally: Modular Workspace</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.1/font/bootstrap-icons.min.css" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --accent: #3b82f6; | |
| --bg-dark: #0f172a; | |
| --card-bg: #1e293b; | |
| } | |
| body { background-color: var(--bg-dark); color: white; overflow: hidden; height: 100vh; } | |
| .block-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
| gap: 1.5rem; | |
| padding: 2rem; | |
| height: calc(100vh - 80px); | |
| overflow-y: auto; | |
| } | |
| .workspace-block { | |
| background: var(--card-bg); | |
| border: 2px solid #334155; | |
| border-radius: 12px; | |
| position: relative; | |
| transition: all 0.2s ease; | |
| min-height: 400px; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .block-header { | |
| padding: 0.75rem; | |
| background: rgba(0,0,0,0.2); | |
| border-bottom: 1px solid #334155; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .content-area { flex-grow: 1; position: relative; overflow: hidden; border-radius: 0 0 12px 12px; } | |
| /* Media Handling */ | |
| .preview-media { width: 100%; height: 100%; object-fit: cover; } | |
| iframe { width: 100%; height: 100%; border: none; background: white; } | |
| /* Floating Controls */ | |
| #toolbar { | |
| height: 60px; | |
| background: #1e293b; | |
| border-bottom: 2px solid #3b82f6; | |
| display: flex; | |
| align-items: center; | |
| padding: 0 2rem; | |
| gap: 1rem; | |
| } | |
| .hidden-file { display: none; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="toolbar"> | |
| <h1 class="text-xl font-bold mr-4">Fantasy Rally: Workspace</h1> | |
| <button onclick="addNewBlock()" class="bg-blue-600 hover:bg-blue-500 px-4 py-2 rounded-lg text-sm font-bold transition"> | |
| <i class="bi bi-plus-lg mr-2"></i>New Block | |
| </button> | |
| <button onclick="exportSQL()" class="bg-green-600 hover:bg-green-500 px-4 py-2 rounded-lg text-sm font-bold transition ml-auto"> | |
| <i class="bi bi-database-down mr-2"></i>Export SQL | |
| </button> | |
| </div> | |
| <div id="grid" class="block-grid"> | |
| </div> | |
| <div id="background-processes" class="hidden"></div> | |
| <script> | |
| let blocks = []; | |
| const grid = document.getElementById('grid'); | |
| function addNewBlock() { | |
| const id = Date.now(); | |
| const blockObj = { | |
| id: id, | |
| type: 'empty', | |
| title: 'Unsaved Reward', | |
| content: null, | |
| fileName: 'none' | |
| }; | |
| blocks.push(blockObj); | |
| renderBlocks(); | |
| } | |
| function handleFileUpload(event, id) { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| const block = blocks.find(b => b.id === id); | |
| block.fileName = file.name; | |
| const extension = file.name.split('.').pop().toLowerCase(); | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension)) { | |
| block.type = 'image'; | |
| block.content = e.target.result; | |
| } else if (['mp4', 'webm'].includes(extension)) { | |
| block.type = 'video'; | |
| block.content = e.target.result; | |
| } else if (['html', 'js'].includes(extension)) { | |
| block.type = 'code'; | |
| block.content = e.target.result; | |
| } else { | |
| block.type = 'other'; | |
| block.content = e.target.result; | |
| } | |
| renderBlocks(); | |
| }; | |
| if (block.type === 'code') { | |
| reader.readAsText(file); | |
| } else { | |
| reader.readAsDataURL(file); | |
| } | |
| } | |
| function renderBlocks() { | |
| grid.innerHTML = ''; | |
| blocks.forEach((block, index) => { | |
| const card = document.createElement('div'); | |
| card.className = 'workspace-block'; | |
| let contentHtml = ''; | |
| if (block.type === 'empty') { | |
| contentHtml = ` | |
| <div class="flex flex-col items-center justify-center h-full p-8 text-center"> | |
| <i class="bi bi-cloud-upload text-4xl text-slate-500 mb-4"></i> | |
| <p class="text-slate-400 mb-4">Upload Asset (IMG, MP4, HTML)</p> | |
| <input type="file" id="file-${block.id}" class="hidden-file" onchange="handleFileUpload(event, ${block.id})"> | |
| <button onclick="document.getElementById('file-${block.id}').click()" class="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded">Browse Files</button> | |
| </div> | |
| `; | |
| } else if (block.type === 'image') { | |
| contentHtml = `<img src="${block.content}" class="preview-media">`; | |
| } else if (block.type === 'video') { | |
| contentHtml = `<video src="${block.content}" class="preview-media" controls autoplay loop muted></video>`; | |
| } else if (block.type === 'code') { | |
| contentHtml = `<iframe srcdoc="${block.content.replace(/"/g, '"')}" sandbox="allow-scripts"></iframe>`; | |
| } | |
| card.innerHTML = ` | |
| <div class="block-header"> | |
| <input class="bg-transparent border-none font-bold text-sm focus:ring-0 w-2/3" value="${block.title}" onchange="blocks[${index}].title = this.value"> | |
| <div class="flex gap-2"> | |
| <button onclick="deleteBlock(${block.id})" class="text-slate-400 hover:text-red-400"><i class="bi bi-trash"></i></button> | |
| </div> | |
| </div> | |
| <div class="content-area"> | |
| ${contentHtml} | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| } | |
| function deleteBlock(id) { | |
| blocks = blocks.filter(b => b.id !== id); | |
| renderBlocks(); | |
| } | |
| function exportSQL() { | |
| if (blocks.length === 0) return alert("No blocks to export."); | |
| let sql = "INSERT INTO fantasy_rally_assets (id, title, file_type, data) VALUES \n"; | |
| const values = blocks.map(b => { | |
| const safeTitle = b.title.replace(/'/g, "''"); | |
| // Truncate data for preview in SQL string | |
| const dataPreview = b.content ? b.content.substring(0, 50) + "..." : "NULL"; | |
| return `(${b.id}, '${safeTitle}', '${b.type}', '${dataPreview}')`; | |
| }).join(",\n"); | |
| const blob = new Blob([sql + values + ";"], { type: 'text/sql' }); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = "workspace_export.sql"; | |
| a.click(); | |
| } | |
| // Initialize with one empty block | |
| window.onload = addNewBlock; | |
| </script> | |
| </body> | |
| </html> | |