Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Ink Poetry Canvas</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .paper { | |
| background-color: #f8f4e8; | |
| background-image: linear-gradient(#e5e5e5 1px, transparent 1px); | |
| background-size: 100% 32px; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
| border-radius: 8px; | |
| } | |
| .control-panel { | |
| border-radius: 8px; | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | |
| } | |
| .handwriting-canvas { | |
| touch-action: none; | |
| background-color: transparent; | |
| } | |
| .btn-primary { | |
| background-color: #2563eb; | |
| transition: all 0.2s ease; | |
| } | |
| .btn-primary:hover { | |
| background-color: #1d4ed8; | |
| } | |
| .btn-primary:disabled { | |
| background-color: #93c5fd; | |
| cursor: not-allowed; | |
| } | |
| .ink-sample { | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| border: 2px solid transparent; | |
| transition: transform 0.2s; | |
| } | |
| .ink-sample:hover { | |
| transform: scale(1.1); | |
| } | |
| .ink-sample.active { | |
| border-color: #333; | |
| transform: scale(1.1); | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| .loading-dots { | |
| display: inline-block; | |
| margin-left: 4px; | |
| } | |
| .loading-dots span { | |
| display: inline-block; | |
| width: 4px; | |
| height: 4px; | |
| border-radius: 50%; | |
| background-color: currentColor; | |
| margin: 0 1px; | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .loading-dots span:nth-child(1) { | |
| animation-delay: -0.32s; | |
| } | |
| .loading-dots span:nth-child(2) { | |
| animation-delay: -0.16s; | |
| } | |
| @keyframes bounce { | |
| 0%, 80%, 100% { | |
| transform: translateY(0); | |
| } | |
| 40% { | |
| transform: translateY(-6px); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen flex flex-col items-center py-8 px-4"> | |
| <div class="max-w-3xl w-full space-y-6"> | |
| <div class="text-center"> | |
| <h1 class="text-3xl font-light text-gray-800 mb-1">Ink Poetry Canvas</h1> | |
| <p class="text-gray-600">Experience the art of handwritten poetry</p> | |
| </div> | |
| <div class="control-panel bg-white p-5"> | |
| <div class="mb-4"> | |
| <label for="poetry-input" class="block text-sm font-medium text-gray-700 mb-2">Your Poetry</label> | |
| <textarea | |
| id="poetry-input" | |
| rows="6" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" | |
| placeholder="She walks in beauty, like the night | |
| Of cloudless climes and starry skies; | |
| And all that's best of dark and bright | |
| Meet in her aspect and her eyes..."></textarea> | |
| </div> | |
| <div class="flex flex-wrap items-center justify-between gap-4"> | |
| <div class="flex items-center gap-3"> | |
| <button id="generate-btn" class="btn-primary text-white px-5 py-2 rounded-md flex items-center"> | |
| <i class="fas fa-pen-nib mr-2"></i> | |
| <span>Generate</span> | |
| <span class="loading-dots hidden"><span></span><span></span><span></span></span> | |
| </button> | |
| <div class="flex items-center gap-2"> | |
| <button id="clear-btn" class="border border-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-50"> | |
| <i class="fas fa-eraser"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <span class="text-sm text-gray-600">Ink Color:</span> | |
| <div class="flex items-center gap-1"> | |
| <div class="ink-sample active" style="background-color: #2563eb;" data-color="#2563eb"></div> | |
| <div class="ink-sample" style="background-color: #1e293b;" data-color="#1e293b"></div> | |
| <div class="ink-sample" style="background-color: #7c2d12;" data-color="#7c2d12"></div> | |
| <div class="ink-sample" style="background-color: #6b21a8;" data-color="#6b21a8"></div> | |
| </div> | |
| </div> | |
| <button id="download-btn" class="border border-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-50 hidden"> | |
| <i class="fas fa-download mr-2"></i> | |
| <span>Save as Image</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="relative"> | |
| <div class="paper p-8 relative overflow-hidden"> | |
| <canvas id="handwriting-canvas" class="handwriting-canvas w-full"></canvas> | |
| <div id="placeholder" class="absolute inset-0 flex items-center justify-center text-gray-400 pointer-events-none"> | |
| <div class="text-center"> | |
| <i class="fas fa-pen-fancy text-4xl mb-4 opacity-30"></i> | |
| <p>Your handwritten masterpiece will appear here</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const poetryInput = document.getElementById('poetry-input'); | |
| const generateBtn = document.getElementById('generate-btn'); | |
| const downloadBtn = document.getElementById('download-btn'); | |
| const clearBtn = document.getElementById('clear-btn'); | |
| const canvas = document.getElementById('handwriting-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const placeholder = document.getElementById('placeholder'); | |
| const loadingDots = document.querySelector('.loading-dots'); | |
| const inkSamples = document.querySelectorAll('.ink-sample'); | |
| let currentInkColor = '#2563eb'; // Default blue ink | |
| let isDrawing = false; | |
| let lastX = 0; | |
| let lastY = 0; | |
| let currentLine = []; | |
| let allLines = []; | |
| // Set up canvas | |
| function setupCanvas() { | |
| const container = canvas.parentElement; | |
| const width = container.clientWidth; | |
| const height = 600; // Fixed height for better experience | |
| canvas.width = width; | |
| canvas.height = height; | |
| // Paper texture | |
| ctx.fillStyle = '#f8f4e8'; | |
| ctx.fillRect(0, 0, width, height); | |
| } | |
| // Handle ink color selection | |
| inkSamples.forEach(sample => { | |
| sample.addEventListener('click', function() { | |
| inkSamples.forEach(s => s.classList.remove('active')); | |
| this.classList.add('active'); | |
| currentInkColor = this.dataset.color; | |
| }); | |
| }); | |
| // Simulate handwriting with canvas | |
| function drawPoetry() { | |
| const text = poetryInput.value.trim(); | |
| if (!text) return; | |
| // Show loading state | |
| generateBtn.disabled = true; | |
| loadingDots.classList.remove('hidden'); | |
| // Hide placeholder | |
| placeholder.classList.add('hidden'); | |
| // Clear previous drawing | |
| clearCanvas(); | |
| // Split text into lines | |
| const lines = text.split('\n'); | |
| const lineHeight = 36; | |
| let x = 50; | |
| let y = 60; | |
| // Generate handwriting line by line | |
| let lineIndex = 0; | |
| function drawNextLine() { | |
| if (lineIndex >= lines.length) { | |
| // Drawing complete | |
| generateBtn.disabled = false; | |
| loadingDots.classList.add('hidden'); | |
| downloadBtn.classList.remove('hidden'); | |
| return; | |
| } | |
| const line = lines[lineIndex]; | |
| if (line.trim() === '') { | |
| lineIndex++; | |
| y += lineHeight; | |
| setTimeout(drawNextLine, 100); | |
| return; | |
| } | |
| // For each line, draw each character with slight variations | |
| const words = line.split(' '); | |
| let wordIndex = 0; | |
| function drawNextWord() { | |
| if (wordIndex >= words.length) { | |
| lineIndex++; | |
| y += lineHeight; | |
| setTimeout(drawNextLine, 200); | |
| return; | |
| } | |
| const word = words[wordIndex]; | |
| if (word === '') { | |
| wordIndex++; | |
| x += 15; // Space between words | |
| setTimeout(drawNextWord, 0); | |
| return; | |
| } | |
| // Break words longer than 10 characters to simulate realistic line breaking | |
| const chunks = []; | |
| const chunkSize = 5 + Math.floor(Math.random() * 5); | |
| for (let i = 0; i < word.length; i += chunkSize) { | |
| chunks.push(word.substring(i, i + chunkSize)); | |
| } | |
| let charIndex = 0; | |
| function drawNextChunk(chunk) { | |
| if (charIndex >= chunk.length) { | |
| wordIndex++; | |
| x += 15; // Space between words | |
| setTimeout(drawNextWord, 0); | |
| return; | |
| } | |
| const char = chunk[charIndex]; | |
| // Random variations for natural feel | |
| const sizeVariation = 0.8 + Math.random() * 0.4; | |
| const baseFontSize = 28; | |
| const fontSize = baseFontSize * sizeVariation; | |
| const angle = (Math.random() - 0.5) * 15; | |
| const lean = 0; // Math.random() * 30 - 15; | |
| const inkOpacity = 0.8 + Math.random() * 0.2; | |
| // Draw the character with variations | |
| ctx.save(); | |
| ctx.translate(x, y); | |
| ctx.rotate(angle * Math.PI / 180); | |
| ctx.font = `${fontSize}px 'Sacramento', cursive, sans-serif`; | |
| ctx.fillStyle = currentInkColor; | |
| // Simulate ink flow - darker at start of stroke | |
| if (Math.random() > 0.7) { | |
| const gradient = ctx.createLinearGradient(0, -fontSize/2, 0, fontSize/2); | |
| gradient.addColorStop(0, currentInkColor); | |
| gradient.addColorStop(1, lightenColor(currentInkColor, 40)); | |
| ctx.fillStyle = gradient; | |
| } | |
| // Add ink blot at random points | |
| if (Math.random() > 0.9) { | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 1 + Math.random() * 2, 0, Math.PI * 2); | |
| ctx.fillStyle = currentInkColor; | |
| ctx.fill(); | |
| } | |
| ctx.fillText(char, 0, 0); | |
| ctx.restore(); | |
| // Some characters might connect (cursive effect) | |
| const charWidth = ctx.measureText(char).width * sizeVariation; | |
| x += charWidth - (charWidth * 0.3 * Math.random()); | |
| charIndex++; | |
| // Random delay between characters (20-100ms) | |
| setTimeout(() => drawNextChunk(chunk), 20 + Math.random() * 80); | |
| } | |
| // Start drawing first chunk | |
| drawNextChunk(chunks.shift()); | |
| // If there are remaining chunks, move to next line | |
| if (chunks.length > 0) { | |
| x = 50; | |
| y += lineHeight * (0.8 + Math.random() * 0.4); | |
| } | |
| } | |
| // Start drawing first word | |
| drawNextWord(); | |
| } | |
| // Start drawing | |
| drawNextLine(); | |
| } | |
| // Helper function to lighten/darken colors | |
| function lightenColor(color, percent) { | |
| const num = parseInt(color.replace("#", ""), 16); | |
| const amt = Math.round(2.55 * percent); | |
| const R = (num >> 16) + amt; | |
| const G = (num >> 8 & 0x00FF) + amt; | |
| const B = (num & 0x0000FF) + amt; | |
| return `#${( | |
| 0x1000000 + | |
| (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + | |
| (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + | |
| (B < 255 ? B < 1 ? 0 : B : 255) | |
| ).toString(16).slice(1)}`; | |
| } | |
| // Clear canvas | |
| function clearCanvas() { | |
| ctx.fillStyle = '#f8f4e8'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| allLines = []; | |
| placeholder.classList.remove('hidden'); | |
| downloadBtn.classList.add('hidden'); | |
| } | |
| // Download canvas as image | |
| function downloadCanvas() { | |
| const link = document.createElement('a'); | |
| link.download = 'handwritten-poetry.png'; | |
| link.href = canvas.toDataURL('image/png'); | |
| link.click(); | |
| } | |
| // Event listeners | |
| generateBtn.addEventListener('click', drawPoetry); | |
| downloadBtn.addEventListener('click', downloadCanvas); | |
| clearBtn.addEventListener('click', clearCanvas); | |
| // Initialize | |
| setupCanvas(); | |
| window.addEventListener('resize', setupCanvas); | |
| // Load sample poem (for demo purposes) | |
| poetryInput.value = `She walks in beauty, like the night | |
| Of cloudless climes and starry skies; | |
| And all that's best of dark and bright | |
| Meet in her aspect and her eyes; | |
| Thus mellowed to that tender light | |
| Which heaven to gaudy day denies.`; | |
| }); | |
| </script> | |
| <!-- Load cursive-like font --> | |
| <link href="https://fonts.googleapis.com/css2?family=Sacramento&display=swap" rel="stylesheet"> | |
| <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=salmanarshad/poetry" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
| </html> |