Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>3x3 Asset Matrix</title> | |
| <script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.4.0/model-viewer.min.js"></script> | |
| <style> | |
| :root { | |
| --bg: #050505; | |
| --accent: #7b2ff7; | |
| --card: #111; | |
| --text: #fff; | |
| } | |
| body, html { | |
| margin: 0; | |
| padding: 0; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: sans-serif; | |
| overflow-x: hidden; | |
| } | |
| #top-bar { | |
| padding: 15px; | |
| display: flex; | |
| gap: 10px; | |
| background: rgba(0,0,0,0.8); | |
| position: sticky; | |
| top: 0; | |
| z-index: 1000; | |
| border-bottom: 1px solid #222; | |
| } | |
| /* 3x3 GRID SYSTEM */ | |
| #canvas { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 10px; | |
| padding: 10px; | |
| width: 100vw; | |
| box-sizing: border-box; | |
| min-height: calc(100vh - 70px); | |
| } | |
| .module-wrapper { | |
| aspect-ratio: 1 / 1; | |
| perspective: 1000px; | |
| position: relative; | |
| } | |
| .module-box { | |
| width: 100%; | |
| height: 100%; | |
| position: relative; | |
| transform-style: preserve-3d; | |
| transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1), | |
| width 0.5s, height 0.5s, top 0.5s, left 0.5s; | |
| cursor: pointer; | |
| } | |
| /* STATES */ | |
| .module-wrapper.expanded { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100vw; height: 100vh; | |
| z-index: 500; | |
| background: var(--bg); | |
| } | |
| .module-box.flipped { | |
| transform: rotateY(180deg); | |
| } | |
| /* FACES */ | |
| .face { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| backface-visibility: hidden; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| border: 1px solid #333; | |
| background: #000; | |
| } | |
| .face-front { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .face-back { | |
| transform: rotateY(180deg); | |
| background: #111; | |
| padding: 20px; | |
| box-sizing: border-box; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| /* PREVIEW CONTENT */ | |
| .preview-area { | |
| flex-grow: 1; | |
| width: 100%; | |
| height: 100%; | |
| background: #fff; | |
| pointer-events: none; /* Allows click to pass to box when in grid */ | |
| } | |
| .expanded .preview-area { | |
| pointer-events: auto; /* Allow interaction when full screen */ | |
| } | |
| iframe, model-viewer, img { | |
| width: 100%; | |
| height: 100%; | |
| border: none; | |
| display: block; | |
| } | |
| /* UI ELEMENTS */ | |
| textarea { | |
| flex-grow: 1; | |
| background: #000; | |
| color: #00ff41; | |
| border: 1px solid var(--accent); | |
| font-family: monospace; | |
| padding: 10px; | |
| resize: none; | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 5px; | |
| padding: 10px; | |
| background: #1a1a1a; | |
| } | |
| button { | |
| background: var(--accent); | |
| border: none; | |
| color: white; | |
| padding: 8px; | |
| border-radius: 4px; | |
| font-size: 0.7rem; | |
| cursor: pointer; | |
| } | |
| .lib-panel { | |
| padding: 20px; | |
| border-top: 1px solid #333; | |
| } | |
| .lib-grid { display: flex; flex-wrap: wrap; gap: 10px; } | |
| @media (max-width: 768px) { | |
| #canvas { grid-template-columns: repeat(2, 1fr); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="top-bar"> | |
| <button onclick="createModule()">+ NEW NODE</button> | |
| <span style="font-size: 0.7rem; color: #666; align-self: center;"> | |
| TAP: Expand | TAP AGAIN: Flip Code | DBL-TAP: Close | |
| </span> | |
| </div> | |
| <div id="canvas"></div> | |
| <div class="lib-panel"> | |
| <div style="color:#888; margin-bottom:10px">LIBRARY</div> | |
| <div id="library-grid" class="lib-grid"></div> | |
| </div> | |
| <script> | |
| let library = JSON.parse(localStorage.getItem('asset_library_v3')) || []; | |
| let activeSessions = JSON.parse(localStorage.getItem('asset_sessions_v3')) || []; | |
| function init() { | |
| renderLibrary(); | |
| if (activeSessions.length > 0) { | |
| activeSessions.forEach(s => createModule(s.content, s.type)); | |
| } else { | |
| for(let i=0; i<3; i++) createModule(); | |
| } | |
| } | |
| function createModule(content = '', type = 'html') { | |
| const canvas = document.getElementById('canvas'); | |
| const id = 'mod_' + Math.random().toString(36).substr(2, 9); | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = 'module-wrapper'; | |
| wrapper.id = 'wrap_' + id; | |
| wrapper.innerHTML = ` | |
| <div class="module-box" id="${id}" data-type="${type}"> | |
| <div class="face face-front"> | |
| <div class="preview-area" id="prev_${id}"></div> | |
| </div> | |
| <div class="face face-back"> | |
| <div style="font-size:0.7rem; color:var(--accent)">SOURCE CODE</div> | |
| <input type="file" id="file_${id}" style="font-size:0.7rem" onchange="handleFile(this, '${id}')"> | |
| <textarea id="text_${id}" oninput="updateLive('${id}')" placeholder="Paste HTML...">${content}</textarea> | |
| <div class="controls"> | |
| <button onclick="saveToLibrary('${id}')">SAVE TO LIB</button> | |
| <button style="background:#ff4444" onclick="removeModule('${id}')">DELETE</button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| canvas.appendChild(wrapper); | |
| const box = document.getElementById(id); | |
| let lastTap = 0; | |
| // TRIP-WIRE LOGIC (TAP / FLIP / CLOSE) | |
| box.addEventListener('click', (e) => { | |
| const now = Date.now(); | |
| const DOUBLE_TAP_DELAY = 300; | |
| if (now - lastTap < DOUBLE_TAP_DELAY) { | |
| // DOUBLE TAP: Close/Minimize | |
| wrapper.classList.remove('expanded'); | |
| box.classList.remove('flipped'); | |
| return; | |
| } | |
| lastTap = now; | |
| if (!wrapper.classList.contains('expanded')) { | |
| // FIRST TAP: Expand to full screen | |
| wrapper.classList.add('expanded'); | |
| } else { | |
| // SECOND TAP (while expanded): Flip to code | |
| box.classList.toggle('flipped'); | |
| } | |
| }); | |
| updateLive(id); | |
| } | |
| function updateLive(id) { | |
| const box = document.getElementById(id); | |
| const content = document.getElementById(`text_${id}`).value; | |
| const prev = document.getElementById(`prev_${id}`); | |
| const type = box.dataset.type; | |
| prev.innerHTML = ''; | |
| if (type === '3d' && content) { | |
| const mv = document.createElement('model-viewer'); | |
| mv.src = content; mv.setAttribute('auto-rotate', ''); mv.setAttribute('camera-controls', ''); | |
| prev.appendChild(mv); | |
| } else if (type === 'img' && content) { | |
| const img = document.createElement('img'); | |
| img.src = content; img.style.objectFit = 'contain'; | |
| prev.appendChild(img); | |
| } else { | |
| const iframe = document.createElement('iframe'); | |
| const blob = new Blob([content || ' '], { type: 'text/html' }); | |
| iframe.src = URL.createObjectURL(blob); | |
| prev.appendChild(iframe); | |
| } | |
| updateSessionStore(); | |
| } | |
| function handleFile(input, id) { | |
| const file = input.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| const ext = file.name.split('.').pop().toLowerCase(); | |
| reader.onload = (e) => { | |
| const box = document.getElementById(id); | |
| box.dataset.type = ['glb','gltf'].includes(ext) ? '3d' : (['png','jpg','jpeg','webp'].includes(ext) ? 'img' : 'html'); | |
| document.getElementById(`text_${id}`).value = e.target.result; | |
| updateLive(id); | |
| }; | |
| if (['glb','gltf','png','jpg','jpeg','webp'].includes(ext)) reader.readAsDataURL(file); | |
| else reader.readAsText(file); | |
| } | |
| function updateSessionStore() { | |
| const sessions = Array.from(document.querySelectorAll('.module-box')).map(box => ({ | |
| content: document.getElementById(`text_${box.id}`).value, | |
| type: box.dataset.type | |
| })); | |
| localStorage.setItem('asset_sessions_v3', JSON.stringify(sessions)); | |
| } | |
| function saveToLibrary(id) { | |
| const content = document.getElementById(`text_${id}`).value; | |
| const type = document.getElementById(id).dataset.type; | |
| const name = prompt("Name this asset:"); | |
| if (name) { | |
| library.push({ name, content, type }); | |
| localStorage.setItem('asset_library_v3', JSON.stringify(library)); | |
| renderLibrary(); | |
| } | |
| } | |
| function renderLibrary() { | |
| const grid = document.getElementById('library-grid'); | |
| grid.innerHTML = ''; | |
| library.forEach(item => { | |
| const btn = document.createElement('button'); | |
| btn.style.background = '#222'; | |
| btn.innerText = item.name; | |
| btn.onclick = () => createModule(item.content, item.type); | |
| grid.appendChild(btn); | |
| }); | |
| } | |
| function removeModule(id) { | |
| document.getElementById('wrap_' + id).remove(); | |
| updateSessionStore(); | |
| } | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |