Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Interactive Comic Editor</title> | |
| <link id="pageStyle" rel="stylesheet" href="page.css"> | |
| <link rel="stylesheet" href="bubble.css"> | |
| <style> | |
| /* Additional styles for editing */ | |
| .bubble { | |
| cursor: move; | |
| transition: box-shadow 0.2s; | |
| user-select: none; | |
| } | |
| .bubble:hover { | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); | |
| } | |
| .bubble.editing { | |
| cursor: text; | |
| } | |
| .bubble textarea { | |
| width: 100%; | |
| height: 100%; | |
| border: none; | |
| background: transparent; | |
| font-family: inherit; | |
| font-size: inherit; | |
| font-weight: inherit; | |
| text-align: center; | |
| resize: none; | |
| outline: 2px solid #4CAF50; | |
| padding: 5px; | |
| box-sizing: border-box; | |
| } | |
| .edit-controls { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| background: rgba(0,0,0,0.85); | |
| color: white; | |
| padding: 15px 20px; | |
| border-radius: 10px; | |
| font-size: 14px; | |
| z-index: 1000; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); | |
| } | |
| .edit-controls h4 { | |
| margin: 0 0 10px 0; | |
| color: #4CAF50; | |
| } | |
| .edit-controls p { | |
| margin: 5px 0; | |
| opacity: 0.9; | |
| } | |
| .edit-controls button { | |
| margin-top: 10px; | |
| padding: 8px 16px; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-weight: bold; | |
| transition: all 0.2s; | |
| } | |
| .save-btn { | |
| background: #4CAF50; | |
| color: white; | |
| margin-right: 10px; | |
| } | |
| .save-btn:hover { | |
| background: #45a049; | |
| } | |
| .reset-btn { | |
| background: #f44336; | |
| color: white; | |
| } | |
| .reset-btn:hover { | |
| background: #da190b; | |
| } | |
| </style> | |
| <script src="page.js"></script> | |
| <script src="page_place.js"></script> | |
| </head> | |
| <body> | |
| <div class="button"> | |
| <button onclick="prevPage()"><img src="assets/backward.png" alt=""></button> | |
| </div> | |
| <div class="wrapper"> | |
| <div class="grid-container"> | |
| <div class="grid-item" id="_1"></div> | |
| <div class="grid-item" id="_2"></div> | |
| <div class="grid-item" id="_3"></div> | |
| <div class="grid-item" id="_4"></div> | |
| <div class="grid-item" id="_5"></div> | |
| <div class="grid-item" id="_6"></div> | |
| <div class="grid-item" id="_7"></div> | |
| <div class="grid-item" id="_8"></div> | |
| <div class="grid-item" id="_9"></div> | |
| <div class="grid-item" id="_10"></div> | |
| <div class="grid-item" id="_11"></div> | |
| <div class="grid-item" id="_12"></div> | |
| </div> | |
| </div> | |
| <div class="button"> | |
| <button onclick="nextPage()"><img src="assets/forward.png" alt=""></button> | |
| </div> | |
| <!-- Edit Controls --> | |
| <div class="edit-controls"> | |
| <h4>✏️ Comic Editor</h4> | |
| <p>• <strong>Drag</strong> speech bubbles to reposition</p> | |
| <p>• <strong>Double-click</strong> to edit text</p> | |
| <p>• <strong>Enter</strong> to save text</p> | |
| <button class="save-btn" onclick="saveComic()">💾 Save Changes</button> | |
| <button class="reset-btn" onclick="resetComic()">↩️ Reset</button> | |
| </div> | |
| <script> | |
| // Interactive editing functionality | |
| let currentEditBubble = null; | |
| let draggedBubble = null; | |
| let offset = {x: 0, y: 0}; | |
| // Wait for page to load | |
| window.addEventListener('load', () => { | |
| setTimeout(initializeEditor, 500); | |
| }); | |
| function initializeEditor() { | |
| // Find all speech bubbles | |
| document.querySelectorAll('.bubble').forEach(bubble => { | |
| // Make bubbles interactive | |
| bubble.addEventListener('dblclick', (e) => { | |
| e.stopPropagation(); | |
| editBubbleText(bubble); | |
| }); | |
| bubble.addEventListener('mousedown', startDrag); | |
| }); | |
| // Global mouse events | |
| document.addEventListener('mousemove', drag); | |
| document.addEventListener('mouseup', stopDrag); | |
| // Load saved state if exists | |
| loadSavedState(); | |
| } | |
| function editBubbleText(bubble) { | |
| if (currentEditBubble) return; | |
| currentEditBubble = bubble; | |
| bubble.classList.add('editing'); | |
| const text = bubble.innerText; | |
| const textarea = document.createElement('textarea'); | |
| textarea.value = text; | |
| bubble.innerHTML = ''; | |
| bubble.appendChild(textarea); | |
| textarea.focus(); | |
| textarea.select(); | |
| textarea.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| saveBubbleText(bubble, textarea.value); | |
| } | |
| if (e.key === 'Escape') { | |
| saveBubbleText(bubble, text); | |
| } | |
| }); | |
| textarea.addEventListener('blur', () => { | |
| setTimeout(() => { | |
| if (currentEditBubble === bubble) { | |
| saveBubbleText(bubble, textarea.value); | |
| } | |
| }, 100); | |
| }); | |
| } | |
| function saveBubbleText(bubble, text) { | |
| bubble.innerText = text; | |
| bubble.classList.remove('editing'); | |
| currentEditBubble = null; | |
| saveState(); | |
| } | |
| function startDrag(e) { | |
| if (e.target.tagName === 'TEXTAREA') return; | |
| const bubble = e.target.closest('.bubble'); | |
| if (!bubble || currentEditBubble) return; | |
| draggedBubble = bubble; | |
| const rect = bubble.getBoundingClientRect(); | |
| offset.x = e.clientX - rect.left; | |
| offset.y = e.clientY - rect.top; | |
| bubble.style.opacity = '0.9'; | |
| bubble.style.zIndex = '100'; | |
| e.preventDefault(); | |
| } | |
| function drag(e) { | |
| if (!draggedBubble) return; | |
| const parent = draggedBubble.parentElement; | |
| const parentRect = parent.getBoundingClientRect(); | |
| let x = e.clientX - parentRect.left - offset.x; | |
| let y = e.clientY - parentRect.top - offset.y; | |
| // Keep within bounds | |
| x = Math.max(0, Math.min(x, parentRect.width - draggedBubble.offsetWidth)); | |
| y = Math.max(0, Math.min(y, parentRect.height - draggedBubble.offsetHeight)); | |
| draggedBubble.style.left = x + 'px'; | |
| draggedBubble.style.top = y + 'px'; | |
| } | |
| function stopDrag() { | |
| if (draggedBubble) { | |
| draggedBubble.style.opacity = ''; | |
| draggedBubble.style.zIndex = ''; | |
| saveState(); | |
| draggedBubble = null; | |
| } | |
| } | |
| function saveState() { | |
| const bubbles = []; | |
| document.querySelectorAll('.bubble').forEach((bubble, index) => { | |
| bubbles.push({ | |
| index: index, | |
| text: bubble.innerText, | |
| left: bubble.style.left, | |
| top: bubble.style.top | |
| }); | |
| }); | |
| localStorage.setItem('comicBubbles', JSON.stringify(bubbles)); | |
| } | |
| function loadSavedState() { | |
| const saved = localStorage.getItem('comicBubbles'); | |
| if (!saved) return; | |
| try { | |
| const bubbles = JSON.parse(saved); | |
| const elements = document.querySelectorAll('.bubble'); | |
| bubbles.forEach((data, index) => { | |
| if (elements[index]) { | |
| elements[index].innerText = data.text; | |
| if (data.left) elements[index].style.left = data.left; | |
| if (data.top) elements[index].style.top = data.top; | |
| } | |
| }); | |
| } catch (e) { | |
| console.error('Failed to load saved state:', e); | |
| } | |
| } | |
| function saveComic() { | |
| saveState(); | |
| alert('✅ Comic changes saved!\n\nYour edits are saved locally. To export the comic, use your browser\'s print function or take a screenshot.'); | |
| } | |
| function resetComic() { | |
| if (confirm('Reset all changes to original?')) { | |
| localStorage.removeItem('comicBubbles'); | |
| location.reload(); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |