| <!DOCTYPE html> |
| <html lang="en" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>هنر خوشنویسی فارسی | Persian Calligraphy</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> |
| @import url('https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu&display=swap'); |
| |
| .persian-font { |
| font-family: 'Noto Nastaliq Urdu', serif; |
| } |
| |
| .canvas-container { |
| background-image: url('data:image/svg+xml;utf8,<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M0 0 L100 0 L100 100 L0 100 Z" fill="none" stroke="%23e5e7eb" stroke-width="1"/></svg>'); |
| background-size: 20px 20px; |
| } |
| |
| .ink-flow { |
| position: absolute; |
| width: 60px; |
| height: 60px; |
| background: url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M30,10 Q50,0 70,10 Q90,30 70,50 Q50,70 30,50 Q10,30 30,10 Z" fill="%233b82f6"/></svg>') no-repeat; |
| background-size: contain; |
| opacity: 0; |
| animation: drip 1.5s ease-out; |
| } |
| |
| @keyframes drip { |
| 0% { transform: translateY(-20px) scale(0.8); opacity: 0; } |
| 20% { opacity: 0.8; } |
| 100% { transform: translateY(40px) scale(1.2); opacity: 0; } |
| } |
| |
| .pen-tip { |
| position: absolute; |
| width: 20px; |
| height: 40px; |
| background: linear-gradient(to bottom, #1e3a8a, #3b82f6); |
| border-radius: 50% 50% 0 0; |
| transform: rotate(45deg); |
| bottom: -15px; |
| left: 50%; |
| transform-origin: bottom center; |
| } |
| |
| .pen-body { |
| position: relative; |
| width: 15px; |
| height: 120px; |
| background: linear-gradient(to bottom, #111827, #1f2937); |
| border-radius: 5px; |
| } |
| |
| .history-item { |
| transition: all 0.3s ease; |
| } |
| |
| .history-item:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 text-gray-800"> |
| <div class="container mx-auto px-4 py-8"> |
| |
| <header class="flex flex-col items-center mb-8"> |
| <h1 class="text-4xl font-bold text-blue-800 mb-2 persian-font">هنر خوشنویسی فارسی</h1> |
| <p class="text-lg text-gray-600">تجربهای اصیل از هنر نوشتار فارسی</p> |
| <div class="w-full h-1 bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 mt-4 rounded-full"></div> |
| </header> |
| |
| |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> |
| |
| <div class="bg-white rounded-xl shadow-lg p-6 lg:order-3"> |
| <h2 class="text-xl font-semibold mb-4 border-b pb-2">ابزارها</h2> |
| |
| |
| <div class="mb-6"> |
| <h3 class="text-lg font-medium mb-3">نوع قلم</h3> |
| <div class="grid grid-cols-3 gap-3"> |
| <button class="pen-option bg-gray-100 p-3 rounded-lg flex flex-col items-center transition-all hover:bg-blue-100 border-2 border-transparent hover:border-blue-300" data-pen="nastaliq"> |
| <div class="pen-body mx-auto mb-2"> |
| <div class="pen-tip"></div> |
| </div> |
| <span class="text-sm">نستعلیق</span> |
| </button> |
| <button class="pen-option bg-gray-100 p-3 rounded-lg flex flex-col items-center transition-all hover:bg-blue-100" data-pen="shekasteh"> |
| <div class="pen-body mx-auto mb-2" style="width: 12px; height: 100px;"> |
| <div class="pen-tip" style="width: 15px; height: 30px;"></div> |
| </div> |
| <span class="text-sm">شکسته</span> |
| </button> |
| <button class="pen-option bg-gray-100 p-3 rounded-lg flex flex-col items-center transition-all hover:bg-blue-100" data-pen="thuluth"> |
| <div class="pen-body mx-auto mb-2" style="width: 18px; height: 140px;"> |
| <div class="pen-tip" style="width: 25px; height: 50px;"></div> |
| </div> |
| <span class="text-sm">ثلث</span> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="mb-6"> |
| <h3 class="text-lg font-medium mb-3">رنگ مرکب</h3> |
| <div class="flex flex-wrap gap-2"> |
| <button class="w-8 h-8 rounded-full bg-black border-2 border-gray-300 ink-color" data-color="#000000"></button> |
| <button class="w-8 h-8 rounded-full bg-blue-800 border-2 border-gray-300 ink-color" data-color="#1e40af"></button> |
| <button class="w-8 h-8 rounded-full bg-red-700 border-2 border-gray-300 ink-color" data-color="#b91c1c"></button> |
| <button class="w-8 h-8 rounded-full bg-green-700 border-2 border-gray-300 ink-color" data-color="#15803d"></button> |
| <button class="w-8 h-8 rounded-full bg-yellow-600 border-2 border-gray-300 ink-color" data-color="#ca8a04"></button> |
| <button class="w-8 h-8 rounded-full bg-purple-800 border-2 border-gray-300 ink-color" data-color="#6b21a8"></button> |
| <button class="w-8 h-8 rounded-full bg-brown-600 border-2 border-gray-300 ink-color" style="background-color: #78350f;" data-color="#78350f"></button> |
| <button class="w-8 h-8 rounded-full bg-gray-600 border-2 border-gray-300 ink-color" data-color="#4b5563"></button> |
| </div> |
| </div> |
| |
| |
| <div class="mb-6"> |
| <h3 class="text-lg font-medium mb-3">ضخامت قلم</h3> |
| <input type="range" min="1" max="10" value="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" id="penSize"> |
| <div class="flex justify-between text-sm text-gray-600 mt-1"> |
| <span>نازک</span> |
| <span>ضخیم</span> |
| </div> |
| </div> |
| |
| |
| <div class="grid grid-cols-2 gap-3"> |
| <button id="clearCanvas" class="bg-red-100 text-red-700 py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition hover:bg-red-200"> |
| <i class="fas fa-trash-alt"></i> |
| پاک کردن |
| </button> |
| <button id="saveCanvas" class="bg-blue-100 text-blue-700 py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition hover:bg-blue-200"> |
| <i class="fas fa-save"></i> |
| ذخیره |
| </button> |
| <button id="undoAction" class="bg-gray-100 text-gray-700 py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition hover:bg-gray-200"> |
| <i class="fas fa-undo-alt"></i> |
| بازگشت |
| </button> |
| <button id="redoAction" class="bg-gray-100 text-gray-700 py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition hover:bg-gray-200"> |
| <i class="fas fa-redo-alt"></i> |
| تکرار |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="lg:col-span-2"> |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden"> |
| |
| <div class="bg-gradient-to-r from-blue-600 to-blue-800 p-4 text-white flex justify-between items-center"> |
| <h2 class="text-xl font-semibold">صفحه خوشنویسی</h2> |
| <div class="flex items-center gap-2"> |
| <span id="currentPen" class="bg-white bg-opacity-20 px-3 py-1 rounded-full text-sm">نستعلیق</span> |
| <span id="currentColor" class="w-5 h-5 rounded-full bg-black border-2 border-white"></span> |
| </div> |
| </div> |
| |
| |
| <div class="p-4"> |
| <div class="canvas-container bg-white border-2 border-gray-200 rounded-lg overflow-hidden relative" style="height: 500px;"> |
| <canvas id="calligraphyCanvas" class="w-full h-full cursor-crosshair"></canvas> |
| </div> |
| </div> |
| |
| |
| <div class="bg-gray-100 p-3 flex justify-between items-center text-sm text-gray-600"> |
| <div> |
| <span>راهنما: برای شروع خوشنویسی، قلم را روی صفحه بکشید</span> |
| </div> |
| <div class="flex items-center gap-2"> |
| <i class="fas fa-info-circle"></i> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="mt-6 bg-white rounded-xl shadow-lg p-6"> |
| <h2 class="text-xl font-semibold mb-4 border-b pb-2">جملههای آماده</h2> |
| <div class="grid grid-cols-2 md:grid-cols-3 gap-3"> |
| <button class="quick-phrase bg-gray-100 hover:bg-blue-100 transition py-2 px-3 rounded-lg text-right persian-font" data-phrase="دل نوشته">دل نوشته</button> |
| <button class="quick-phrase bg-gray-100 hover:bg-blue-100 transition py-2 px-3 rounded-lg text-right persian-font" data-phrase="عشق">عشق</button> |
| <button class="quick-phrase bg-gray-100 hover:bg-blue-100 transition py-2 px-3 rounded-lg text-right persian-font" data-phrase="ایران">ایران</button> |
| <button class="quick-phrase bg-gray-100 hover:bg-blue-100 transition py-2 px-3 rounded-lg text-right persian-font" data-phrase="سلام">سلام</button> |
| <button class="quick-phrase bg-gray-100 hover:bg-blue-100 transition py-2 px-3 rounded-lg text-right persian-font" data-phrase="بهار">بهار</button> |
| <button class="quick-phrase bg-gray-100 hover:bg-blue-100 transition py-2 px-3 rounded-lg text-right persian-font" data-phrase="هنر">هنر</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="mt-12 bg-white rounded-xl shadow-lg p-6"> |
| <h2 class="text-xl font-semibold mb-4 border-b pb-2">آثار شما</h2> |
| <div id="historyGrid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4"> |
| |
| <div class="history-item bg-gray-100 rounded-lg p-3 flex flex-col items-center cursor-pointer"> |
| <div class="text-gray-500 text-sm mb-2">هنوز اثری ایجاد نکردهاید</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <footer class="mt-12 pt-8 border-t border-gray-200 text-center text-gray-600"> |
| <p class="persian-font mb-2">هنر خوشنویسی فارسی - گنجینهای از فرهنگ و تمدن ایران</p> |
| <p class="text-sm">© 2023 تمامی حقوق محفوظ است</p> |
| </footer> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const canvas = document.getElementById('calligraphyCanvas'); |
| const ctx = canvas.getContext('2d'); |
| let isDrawing = false; |
| let lastX = 0; |
| let lastY = 0; |
| let currentPen = 'nastaliq'; |
| let currentColor = '#000000'; |
| let currentSize = 3; |
| let drawingHistory = []; |
| let historyIndex = -1; |
| |
| |
| function resizeCanvas() { |
| const container = canvas.parentElement; |
| canvas.width = container.clientWidth; |
| canvas.height = container.clientHeight; |
| |
| |
| if (drawingHistory.length > 0) { |
| redrawCanvas(); |
| } |
| } |
| |
| |
| resizeCanvas(); |
| window.addEventListener('resize', resizeCanvas); |
| |
| |
| function startDrawing(e) { |
| isDrawing = true; |
| [lastX, lastY] = getPosition(e); |
| |
| |
| createInkDrip(lastX, lastY); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(lastX, lastY); |
| } |
| |
| function draw(e) { |
| if (!isDrawing) return; |
| |
| const [x, y] = getPosition(e); |
| |
| |
| switch(currentPen) { |
| case 'nastaliq': |
| ctx.lineWidth = currentSize * 2; |
| ctx.lineCap = 'round'; |
| ctx.lineJoin = 'round'; |
| break; |
| case 'shekasteh': |
| ctx.lineWidth = currentSize; |
| ctx.lineCap = 'square'; |
| ctx.lineJoin = 'miter'; |
| break; |
| case 'thuluth': |
| ctx.lineWidth = currentSize * 3; |
| ctx.lineCap = 'round'; |
| ctx.lineJoin = 'round'; |
| break; |
| } |
| |
| ctx.strokeStyle = currentColor; |
| |
| |
| ctx.lineTo(x, y); |
| ctx.stroke(); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(x, y); |
| |
| lastX = x; |
| lastY = y; |
| } |
| |
| function stopDrawing() { |
| isDrawing = false; |
| |
| |
| saveDrawingState(); |
| } |
| |
| function getPosition(e) { |
| const rect = canvas.getBoundingClientRect(); |
| let x, y; |
| |
| if (e.type.includes('touch')) { |
| x = e.touches[0].clientX - rect.left; |
| y = e.touches[0].clientY - rect.top; |
| } else { |
| x = e.clientX - rect.left; |
| y = e.clientY - rect.top; |
| } |
| |
| return [x, y]; |
| } |
| |
| |
| function createInkDrip(x, y) { |
| const drip = document.createElement('div'); |
| drip.className = 'ink-flow'; |
| drip.style.left = `${x - 30}px`; |
| drip.style.top = `${y - 30}px`; |
| drip.style.backgroundImage = drip.style.backgroundImage.replace('%233b82f6', currentColor.substring(1)); |
| |
| canvas.parentElement.appendChild(drip); |
| |
| |
| setTimeout(() => { |
| drip.remove(); |
| }, 1500); |
| } |
| |
| |
| function saveDrawingState() { |
| |
| if (historyIndex < drawingHistory.length - 1) { |
| drawingHistory = drawingHistory.slice(0, historyIndex + 1); |
| } |
| |
| |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| drawingHistory.push(imageData); |
| historyIndex++; |
| |
| |
| updateHistoryDisplay(); |
| } |
| |
| |
| function redrawCanvas() { |
| if (historyIndex >= 0 && historyIndex < drawingHistory.length) { |
| ctx.putImageData(drawingHistory[historyIndex], 0, 0); |
| } else { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| } |
| } |
| |
| |
| function undo() { |
| if (historyIndex > 0) { |
| historyIndex--; |
| redrawCanvas(); |
| } |
| } |
| |
| |
| function redo() { |
| if (historyIndex < drawingHistory.length - 1) { |
| historyIndex++; |
| redrawCanvas(); |
| } |
| } |
| |
| |
| function clearCanvas() { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| saveDrawingState(); |
| } |
| |
| |
| function saveCanvas() { |
| const link = document.createElement('a'); |
| link.download = 'خوشنویسی-فارسی.png'; |
| link.href = canvas.toDataURL('image/png'); |
| link.click(); |
| |
| |
| addToHistoryGrid(canvas.toDataURL('image/png')); |
| } |
| |
| |
| function updateHistoryDisplay() { |
| const historyGrid = document.getElementById('historyGrid'); |
| |
| |
| while (historyGrid.children.length > 1) { |
| historyGrid.removeChild(historyGrid.lastChild); |
| } |
| |
| |
| if (drawingHistory.length > 0 && historyGrid.children[0].textContent.includes("هنوز اثری ایجاد نکردهاید")) { |
| historyGrid.removeChild(historyGrid.children[0]); |
| } |
| |
| |
| addToHistoryGrid(canvas.toDataURL('image/png')); |
| } |
| |
| |
| function addToHistoryGrid(imageUrl) { |
| const historyGrid = document.getElementById('historyGrid'); |
| |
| |
| const historyItem = document.createElement('div'); |
| historyItem.className = 'history-item bg-gray-100 rounded-lg overflow-hidden cursor-pointer'; |
| historyItem.innerHTML = ` |
| <img src="${imageUrl}" alt="اثر خوشنویسی" class="w-full h-32 object-contain bg-white"> |
| <div class="p-2 text-center text-sm text-gray-600">${new Date().toLocaleString('fa-IR')}</div> |
| `; |
| |
| |
| historyItem.addEventListener('click', function() { |
| const img = new Image(); |
| img.onload = function() { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| ctx.drawImage(img, 0, 0, canvas.width, canvas.height); |
| saveDrawingState(); |
| }; |
| img.src = imageUrl; |
| }); |
| |
| historyGrid.insertBefore(historyItem, historyGrid.firstChild); |
| |
| |
| if (historyGrid.children.length > 10) { |
| historyGrid.removeChild(historyGrid.lastChild); |
| } |
| } |
| |
| |
| canvas.addEventListener('mousedown', startDrawing); |
| canvas.addEventListener('mousemove', draw); |
| canvas.addEventListener('mouseup', stopDrawing); |
| canvas.addEventListener('mouseout', stopDrawing); |
| |
| |
| canvas.addEventListener('touchstart', function(e) { |
| e.preventDefault(); |
| startDrawing(e); |
| }); |
| |
| canvas.addEventListener('touchmove', function(e) { |
| e.preventDefault(); |
| draw(e); |
| }); |
| |
| canvas.addEventListener('touchend', stopDrawing); |
| |
| |
| document.querySelectorAll('.pen-option').forEach(option => { |
| option.addEventListener('click', function() { |
| |
| document.querySelectorAll('.pen-option').forEach(opt => { |
| opt.classList.remove('border-blue-300'); |
| opt.classList.add('border-transparent'); |
| }); |
| |
| |
| this.classList.add('border-blue-300'); |
| this.classList.remove('border-transparent'); |
| |
| |
| currentPen = this.dataset.pen; |
| document.getElementById('currentPen').textContent = |
| currentPen === 'nastaliq' ? 'نستعلیق' : |
| currentPen === 'shekasteh' ? 'شکسته' : 'ثلث'; |
| }); |
| }); |
| |
| |
| document.querySelector('.pen-option').click(); |
| |
| |
| document.querySelectorAll('.ink-color').forEach(color => { |
| color.addEventListener('click', function() { |
| currentColor = this.dataset.color; |
| document.getElementById('currentColor').style.backgroundColor = currentColor; |
| }); |
| }); |
| |
| |
| document.querySelector('.ink-color').click(); |
| |
| |
| document.getElementById('penSize').addEventListener('input', function() { |
| currentSize = parseInt(this.value); |
| }); |
| |
| |
| document.querySelectorAll('.quick-phrase').forEach(phrase => { |
| phrase.addEventListener('click', function() { |
| |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| ctx.font = `bold ${canvas.width / 8}px 'Noto Nastaliq Urdu'`; |
| ctx.fillStyle = currentColor; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillText(this.dataset.phrase, canvas.width / 2, canvas.height / 2); |
| |
| |
| saveDrawingState(); |
| }); |
| }); |
| |
| |
| document.getElementById('clearCanvas').addEventListener('click', clearCanvas); |
| document.getElementById('saveCanvas').addEventListener('click', saveCanvas); |
| document.getElementById('undoAction').addEventListener('click', undo); |
| document.getElementById('redoAction').addEventListener('click', redo); |
| |
| |
| document.addEventListener('keydown', function(e) { |
| |
| if (e.ctrlKey && e.key === 'z') { |
| e.preventDefault(); |
| undo(); |
| } |
| |
| |
| if (e.ctrlKey && e.key === 'y') { |
| e.preventDefault(); |
| redo(); |
| } |
| }); |
| }); |
| </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=amirpoorazima/persian-calligraphy" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |