persian-calligraphy / index.html
amirpoorazima's picture
Add 3 files
33877bd verified
<!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 -->
<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>
<!-- Main App -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Tools Panel -->
<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>
<!-- Pen Selection -->
<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>
<!-- Ink Color -->
<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>
<!-- Pen Size -->
<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>
<!-- Actions -->
<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>
<!-- Canvas Area -->
<div class="lg:col-span-2">
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
<!-- Canvas Header -->
<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>
<!-- Canvas -->
<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>
<!-- Canvas Footer -->
<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>
<!-- Quick Phrases -->
<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>
<!-- History Section -->
<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">
<!-- History items will be added here dynamically -->
<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 -->
<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() {
// Canvas setup
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;
// Set canvas size
function resizeCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
// Redraw if there's history
if (drawingHistory.length > 0) {
redrawCanvas();
}
}
// Initial resize
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Drawing functions
function startDrawing(e) {
isDrawing = true;
[lastX, lastY] = getPosition(e);
// Create ink drip effect
createInkDrip(lastX, lastY);
// Start new path
ctx.beginPath();
ctx.moveTo(lastX, lastY);
}
function draw(e) {
if (!isDrawing) return;
const [x, y] = getPosition(e);
// Apply different styles based on pen type
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;
// Draw line
ctx.lineTo(x, y);
ctx.stroke();
// For smoother curves
ctx.beginPath();
ctx.moveTo(x, y);
lastX = x;
lastY = y;
}
function stopDrawing() {
isDrawing = false;
// Save drawing state to history
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];
}
// Ink drip effect
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);
// Remove after animation
setTimeout(() => {
drip.remove();
}, 1500);
}
// Save drawing state to history
function saveDrawingState() {
// If we're not at the end of history, remove future states
if (historyIndex < drawingHistory.length - 1) {
drawingHistory = drawingHistory.slice(0, historyIndex + 1);
}
// Save current canvas state
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
drawingHistory.push(imageData);
historyIndex++;
// Update history display
updateHistoryDisplay();
}
// Redraw canvas from history
function redrawCanvas() {
if (historyIndex >= 0 && historyIndex < drawingHistory.length) {
ctx.putImageData(drawingHistory[historyIndex], 0, 0);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
// Undo action
function undo() {
if (historyIndex > 0) {
historyIndex--;
redrawCanvas();
}
}
// Redo action
function redo() {
if (historyIndex < drawingHistory.length - 1) {
historyIndex++;
redrawCanvas();
}
}
// Clear canvas
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
saveDrawingState();
}
// Save canvas as image
function saveCanvas() {
const link = document.createElement('a');
link.download = 'خوشنویسی-فارسی.png';
link.href = canvas.toDataURL('image/png');
link.click();
// Add to history display
addToHistoryGrid(canvas.toDataURL('image/png'));
}
// Update history display
function updateHistoryDisplay() {
const historyGrid = document.getElementById('historyGrid');
// Clear existing items except the first one (empty state)
while (historyGrid.children.length > 1) {
historyGrid.removeChild(historyGrid.lastChild);
}
// Remove empty state if there are items
if (drawingHistory.length > 0 && historyGrid.children[0].textContent.includes("هنوز اثری ایجاد نکرده‌اید")) {
historyGrid.removeChild(historyGrid.children[0]);
}
// Add current state to history display
addToHistoryGrid(canvas.toDataURL('image/png'));
}
// Add to history grid
function addToHistoryGrid(imageUrl) {
const historyGrid = document.getElementById('historyGrid');
// Create history item
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>
`;
// Add click event to load this drawing
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);
// Limit history to 10 items
if (historyGrid.children.length > 10) {
historyGrid.removeChild(historyGrid.lastChild);
}
}
// Event listeners for canvas
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
// Touch support
canvas.addEventListener('touchstart', function(e) {
e.preventDefault();
startDrawing(e);
});
canvas.addEventListener('touchmove', function(e) {
e.preventDefault();
draw(e);
});
canvas.addEventListener('touchend', stopDrawing);
// Pen selection
document.querySelectorAll('.pen-option').forEach(option => {
option.addEventListener('click', function() {
// Remove active class from all options
document.querySelectorAll('.pen-option').forEach(opt => {
opt.classList.remove('border-blue-300');
opt.classList.add('border-transparent');
});
// Add active class to selected option
this.classList.add('border-blue-300');
this.classList.remove('border-transparent');
// Update current pen
currentPen = this.dataset.pen;
document.getElementById('currentPen').textContent =
currentPen === 'nastaliq' ? 'نستعلیق' :
currentPen === 'shekasteh' ? 'شکسته' : 'ثلث';
});
});
// Set first pen as active by default
document.querySelector('.pen-option').click();
// Ink color selection
document.querySelectorAll('.ink-color').forEach(color => {
color.addEventListener('click', function() {
currentColor = this.dataset.color;
document.getElementById('currentColor').style.backgroundColor = currentColor;
});
});
// Set black as default color
document.querySelector('.ink-color').click();
// Pen size
document.getElementById('penSize').addEventListener('input', function() {
currentSize = parseInt(this.value);
});
// Quick phrases
document.querySelectorAll('.quick-phrase').forEach(phrase => {
phrase.addEventListener('click', function() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the phrase
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);
// Save to history
saveDrawingState();
});
});
// Action buttons
document.getElementById('clearCanvas').addEventListener('click', clearCanvas);
document.getElementById('saveCanvas').addEventListener('click', saveCanvas);
document.getElementById('undoAction').addEventListener('click', undo);
document.getElementById('redoAction').addEventListener('click', redo);
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
// Ctrl+Z for undo
if (e.ctrlKey && e.key === 'z') {
e.preventDefault();
undo();
}
// Ctrl+Y for redo
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>