feat: initial release of 3-year-old child photo album website with PHP 8, SQLite, audio slideshow, and memories timeline
This commit is contained in:
@@ -0,0 +1,619 @@
|
||||
// Hamed Site - Baby & Toddler Photo Album App
|
||||
|
||||
let currentTab = 'gallery';
|
||||
let activeAlbumId = null;
|
||||
let currentSearch = '';
|
||||
let currentFavoriteOnly = false;
|
||||
let allPhotos = [];
|
||||
let activeLightboxIndex = 0;
|
||||
let isAudioPlaying = false;
|
||||
let audioCtx = null;
|
||||
let musicInterval = null;
|
||||
|
||||
// Initialize on DOM load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadPhotos();
|
||||
loadStats();
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
function setupEventListeners() {
|
||||
// Search input
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (searchInput) {
|
||||
let debounceTimeout;
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
clearTimeout(debounceTimeout);
|
||||
debounceTimeout = setTimeout(() => {
|
||||
currentSearch = e.target.value.trim();
|
||||
loadPhotos();
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
// Keyboard navigation for lightbox
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const lightbox = document.getElementById('lightboxModal');
|
||||
if (lightbox && lightbox.classList.contains('open')) {
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
|
||||
prevLightbox();
|
||||
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
|
||||
nextLightbox();
|
||||
} else if (e.key === 'Escape') {
|
||||
closeModal('lightboxModal');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// File input drag & drop
|
||||
const dropArea = document.getElementById('fileDropArea');
|
||||
const fileInput = document.getElementById('photoFileInput');
|
||||
if (dropArea && fileInput) {
|
||||
dropArea.addEventListener('click', () => fileInput.click());
|
||||
fileInput.addEventListener('change', () => handleFileSelect(fileInput.files[0]));
|
||||
|
||||
['dragenter', 'dragover'].forEach(name => {
|
||||
dropArea.addEventListener(name, (e) => {
|
||||
e.preventDefault();
|
||||
dropArea.style.borderColor = '#2563eb';
|
||||
dropArea.style.background = '#dbeafe';
|
||||
});
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(name => {
|
||||
dropArea.addEventListener(name, (e) => {
|
||||
e.preventDefault();
|
||||
dropArea.style.borderColor = '#93c5fd';
|
||||
dropArea.style.background = '#eff6ff';
|
||||
});
|
||||
});
|
||||
|
||||
dropArea.addEventListener('drop', (e) => {
|
||||
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
|
||||
fileInput.files = e.dataTransfer.files;
|
||||
handleFileSelect(e.dataTransfer.files[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect(file) {
|
||||
if (!file) return;
|
||||
const previewContainer = document.getElementById('photoPreviewContainer');
|
||||
const previewImg = document.getElementById('photoPreviewImg');
|
||||
const dropText = document.getElementById('dropText');
|
||||
|
||||
if (file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
previewImg.src = e.target.result;
|
||||
previewContainer.style.display = 'block';
|
||||
dropText.style.display = 'none';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Switch Tab
|
||||
function switchTab(tabName) {
|
||||
currentTab = tabName;
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.tab === tabName);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.tab-pane').forEach(pane => {
|
||||
pane.style.display = pane.id === `tab-${tabName}` ? 'block' : 'none';
|
||||
});
|
||||
|
||||
if (tabName === 'gallery') {
|
||||
loadPhotos();
|
||||
} else if (tabName === 'albums') {
|
||||
loadAlbums();
|
||||
} else if (tabName === 'quotes') {
|
||||
loadQuotes();
|
||||
} else if (tabName === 'milestones') {
|
||||
loadMilestones();
|
||||
} else if (tabName === 'slideshow') {
|
||||
startSlideshow();
|
||||
}
|
||||
}
|
||||
|
||||
// API Helper
|
||||
async function api(action, data = null, isFormData = false) {
|
||||
try {
|
||||
let options = { method: data ? 'POST' : 'GET' };
|
||||
if (data) {
|
||||
if (isFormData) {
|
||||
options.body = data;
|
||||
} else {
|
||||
options.headers = { 'Content-Type': 'application/json' };
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
const res = await fetch(`index.php?api=${action}`, options);
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error('API Error:', err);
|
||||
showToast('خطا در برقراری ارتباط با سرور', 'error');
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Load Photos
|
||||
async function loadPhotos() {
|
||||
const grid = document.getElementById('photosGrid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 40px;"><p>در حال بارگذاری عکسهای قشنگ... 👶🎈</p></div>';
|
||||
|
||||
let url = `photos&search=${encodeURIComponent(currentSearch)}`;
|
||||
if (activeAlbumId) url += `&album_id=${activeAlbumId}`;
|
||||
if (currentFavoriteOnly) url += `&favorite_only=1`;
|
||||
|
||||
const res = await api(url);
|
||||
if (res.success) {
|
||||
allPhotos = res.data;
|
||||
renderPhotos(res.data);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPhotos(photos) {
|
||||
const grid = document.getElementById('photosGrid');
|
||||
if (!photos || photos.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="empty-state" style="grid-column: 1/-1;">
|
||||
<div class="empty-state-icon">📸</div>
|
||||
<h3>هنوز عکسی در این بخش ثبت نشده!</h3>
|
||||
<p>با زدن دکمه «+ افزودن عکس جدید» اولین خاطره شیرین را اضافه کنید.</p>
|
||||
<button class="btn btn-primary" onclick="openModal('uploadPhotoModal')">+ افزودن اولین عکس</button>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = photos.map((p, index) => `
|
||||
<div class="photo-card" data-id="${p.id}">
|
||||
<div class="photo-media-wrapper" onclick="openLightbox(${index})">
|
||||
<img src="${p.file_path}" alt="${p.title}" class="photo-media" loading="lazy">
|
||||
<span class="photo-badge-age">${p.age_tag || '۳ سالگی'}</span>
|
||||
<button class="fav-btn ${p.is_favorite ? 'active' : ''}" onclick="event.stopPropagation(); toggleFavorite(${p.id})">
|
||||
${p.is_favorite ? '⭐' : '☆'}
|
||||
</button>
|
||||
</div>
|
||||
<div class="photo-content">
|
||||
<div class="photo-album-tag">
|
||||
<span>${p.album_icon || '🎈'}</span>
|
||||
<span>${p.album_title || 'آلبوم عمومی'}</span>
|
||||
</div>
|
||||
<h3 class="photo-title">${escapeHtml(p.title)}</h3>
|
||||
<p class="photo-caption">${escapeHtml(p.caption || '')}</p>
|
||||
<div class="photo-footer">
|
||||
<button class="like-action" onclick="likePhoto(${p.id})">
|
||||
<span>❤️</span>
|
||||
<span id="likes-count-${p.id}">${p.likes_count || 0}</span>
|
||||
</button>
|
||||
<div class="card-actions-menu">
|
||||
<span style="font-size: 0.75rem;">📅 ${p.photo_date || ''}</span>
|
||||
<button class="btn-icon-sm" title="حذف عکس" onclick="deletePhoto(${p.id})">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Filter Album
|
||||
function filterByAlbum(albumId, btnElement) {
|
||||
activeAlbumId = albumId;
|
||||
document.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
|
||||
if (btnElement) btnElement.classList.add('active');
|
||||
loadPhotos();
|
||||
}
|
||||
|
||||
function toggleFavoriteFilter() {
|
||||
currentFavoriteOnly = !currentFavoriteOnly;
|
||||
const btn = document.getElementById('favFilterBtn');
|
||||
if (btn) {
|
||||
btn.classList.toggle('active', currentFavoriteOnly);
|
||||
btn.innerText = currentFavoriteOnly ? '⭐ فقط برگزیدهها' : '⭐ نمایش همه';
|
||||
}
|
||||
loadPhotos();
|
||||
}
|
||||
|
||||
// Like Photo
|
||||
async function likePhoto(id) {
|
||||
const res = await api(`like_photo&id=${id}`, {});
|
||||
if (res.success) {
|
||||
const countSpan = document.getElementById(`likes-count-${id}`);
|
||||
if (countSpan) countSpan.innerText = res.likes;
|
||||
showConfetti();
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle Favorite
|
||||
async function toggleFavorite(id) {
|
||||
const res = await api(`toggle_fav&id=${id}`, {});
|
||||
if (res.success) {
|
||||
loadPhotos();
|
||||
loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Photo
|
||||
async function deletePhoto(id) {
|
||||
if (!confirm('آیا از حذف این عکس خاطرهانگیز مطمئن هستید؟')) return;
|
||||
const res = await api(`delete_photo&id=${id}`, {});
|
||||
if (res.success) {
|
||||
showToast('عکس با موفقیت حذف شد', 'success');
|
||||
loadPhotos();
|
||||
loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Lightbox
|
||||
function openLightbox(index) {
|
||||
if (!allPhotos || allPhotos.length === 0) return;
|
||||
activeLightboxIndex = index;
|
||||
updateLightboxContent();
|
||||
openModal('lightboxModal');
|
||||
}
|
||||
|
||||
function updateLightboxContent() {
|
||||
const photo = allPhotos[activeLightboxIndex];
|
||||
if (!photo) return;
|
||||
|
||||
document.getElementById('lightboxImg').src = photo.file_path;
|
||||
document.getElementById('lightboxTitle').innerText = photo.title;
|
||||
document.getElementById('lightboxCaption').innerText = photo.caption || 'بدون توضیح';
|
||||
document.getElementById('lightboxAlbum').innerText = `${photo.album_icon || '🎈'} ${photo.album_title || 'عمومی'}`;
|
||||
document.getElementById('lightboxAge').innerText = photo.age_tag || '۳ سالگی';
|
||||
document.getElementById('lightboxDate').innerText = photo.photo_date || '';
|
||||
document.getElementById('lightboxLikes').innerText = photo.likes_count || 0;
|
||||
document.getElementById('lightboxDownloadBtn').href = photo.file_path;
|
||||
}
|
||||
|
||||
function nextLightbox() {
|
||||
if (activeLightboxIndex < allPhotos.length - 1) {
|
||||
activeLightboxIndex++;
|
||||
} else {
|
||||
activeLightboxIndex = 0;
|
||||
}
|
||||
updateLightboxContent();
|
||||
}
|
||||
|
||||
function prevLightbox() {
|
||||
if (activeLightboxIndex > 0) {
|
||||
activeLightboxIndex--;
|
||||
} else {
|
||||
activeLightboxIndex = allPhotos.length - 1;
|
||||
}
|
||||
updateLightboxContent();
|
||||
}
|
||||
|
||||
// Albums Tab
|
||||
async function loadAlbums() {
|
||||
const grid = document.getElementById('albumsGrid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 40px;"><p>در حال بارگذاری آلبومها... 📂</p></div>';
|
||||
|
||||
const res = await api('albums');
|
||||
if (res.success) {
|
||||
if (!res.data || res.data.length === 0) {
|
||||
grid.innerHTML = '<p style="grid-column: 1/-1; text-align: center;">هنوز آلبومی ساخته نشده است.</p>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = res.data.map(a => `
|
||||
<div class="album-box" onclick="filterByAlbum(${a.id}); switchTab('gallery');">
|
||||
<div class="album-icon-badge">${a.icon || '🎈'}</div>
|
||||
<h3>${escapeHtml(a.title)}</h3>
|
||||
<p>${escapeHtml(a.description || '')}</p>
|
||||
<div class="album-count-badge">📸 ${a.photos_count || 0} عکس</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
// Quotes Tab
|
||||
async function loadQuotes() {
|
||||
const grid = document.getElementById('quotesGrid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 40px;"><p>در حال بارگذاری حرفهای بامزه... 👶</p></div>';
|
||||
|
||||
const res = await api('quotes');
|
||||
if (res.success) {
|
||||
if (!res.data || res.data.length === 0) {
|
||||
grid.innerHTML = '<p style="grid-column: 1/-1; text-align: center;">هنوز جملهای ثبت نشده است.</p>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = res.data.map(q => `
|
||||
<div class="quote-card">
|
||||
<div class="quote-bubble">${escapeHtml(q.quote)}</div>
|
||||
<div style="font-size: 0.85rem; color: #78350f; margin-bottom: 8px;">📍 ${escapeHtml(q.context || 'بدون توضیح')}</div>
|
||||
<div class="quote-meta">
|
||||
<span>🏷️ ${q.age_tag || '۳ سالگی'}</span>
|
||||
<span>📅 ${q.said_date || ''}</span>
|
||||
<button class="btn-icon-sm" onclick="deleteQuote(${q.id})">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteQuote(id) {
|
||||
if (!confirm('حذف این جمله بامزه؟')) return;
|
||||
const res = await api(`delete_quote&id=${id}`, {});
|
||||
if (res.success) {
|
||||
loadQuotes();
|
||||
loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Milestones Tab
|
||||
async function loadMilestones() {
|
||||
const container = document.getElementById('milestonesList');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p style="text-align: center; padding: 30px;">در حال بارگذاری مراحل رشد... 🌟</p>';
|
||||
|
||||
const res = await api('milestones');
|
||||
if (res.success) {
|
||||
if (!res.data || res.data.length === 0) {
|
||||
container.innerHTML = '<p style="text-align: center;">هنوز مرحله رشدی ثبت نشده است.</p>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = res.data.map(m => `
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon">${m.icon || '🌟'}</div>
|
||||
<div class="timeline-card">
|
||||
<div class="timeline-header">
|
||||
<h3>${escapeHtml(m.title)}</h3>
|
||||
<span class="timeline-age">${m.age_tag || '۳ سالگی'}</span>
|
||||
</div>
|
||||
<p style="color: #475569; font-size: 0.9rem; line-height: 1.5; margin-bottom: 8px;">${escapeHtml(m.description || '')}</p>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; font-size: 0.78rem; color: #94a3b8;">
|
||||
<span>📅 تاریخ: ${m.milestone_date || ''}</span>
|
||||
<button class="btn-icon-sm" onclick="deleteMilestone(${m.id})">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMilestone(id) {
|
||||
if (!confirm('حذف این دستاورد؟')) return;
|
||||
const res = await api(`delete_milestone&id=${id}`, {});
|
||||
if (res.success) {
|
||||
loadMilestones();
|
||||
loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Stats
|
||||
async function loadStats() {
|
||||
const res = await api('stats');
|
||||
if (res.success) {
|
||||
document.getElementById('statPhotosCount').innerText = res.data.photos;
|
||||
document.getElementById('statAlbumsCount').innerText = res.data.albums;
|
||||
document.getElementById('statQuotesCount').innerText = res.data.quotes;
|
||||
document.getElementById('statMilestonesCount').innerText = res.data.milestones;
|
||||
}
|
||||
}
|
||||
|
||||
// Forms Submission
|
||||
async function submitPhotoUpload(e) {
|
||||
e.preventDefault();
|
||||
const form = document.getElementById('uploadPhotoForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerText = 'در حال ذخیرهسازی... ⏳';
|
||||
|
||||
const res = await api('upload_photo', formData, true);
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerText = 'ذخیره در آلبوم 🎈';
|
||||
|
||||
if (res.success) {
|
||||
showToast('عکس با موفقیت به آلبوم اضافه شد!', 'success');
|
||||
closeModal('uploadPhotoModal');
|
||||
form.reset();
|
||||
document.getElementById('photoPreviewContainer').style.display = 'none';
|
||||
document.getElementById('dropText').style.display = 'block';
|
||||
loadPhotos();
|
||||
loadStats();
|
||||
showConfetti();
|
||||
} else {
|
||||
showToast(res.error || 'خطا در آپلود عکس', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function submitNewAlbum(e) {
|
||||
e.preventDefault();
|
||||
const form = document.getElementById('newAlbumForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
const res = await api('create_album', formData, true);
|
||||
if (res.success) {
|
||||
showToast('آلبوم جدید با موفقیت ساخته شد!', 'success');
|
||||
closeModal('newAlbumModal');
|
||||
form.reset();
|
||||
loadAlbums();
|
||||
loadStats();
|
||||
// Update album dropdown in upload modal
|
||||
updateAlbumSelectOptions();
|
||||
} else {
|
||||
showToast(res.error || 'خطا در ایجاد آلبوم', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function submitNewQuote(e) {
|
||||
e.preventDefault();
|
||||
const form = document.getElementById('newQuoteForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
const res = await api('create_quote', formData, true);
|
||||
if (res.success) {
|
||||
showToast('جمله بامزه ذخیره شد!', 'success');
|
||||
closeModal('newQuoteModal');
|
||||
form.reset();
|
||||
loadQuotes();
|
||||
loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
async function submitNewMilestone(e) {
|
||||
e.preventDefault();
|
||||
const form = document.getElementById('newMilestoneForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
const res = await api('create_milestone', formData, true);
|
||||
if (res.success) {
|
||||
showToast('دستاورد رشد ثبت شد!', 'success');
|
||||
closeModal('newMilestoneModal');
|
||||
form.reset();
|
||||
loadMilestones();
|
||||
loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
// Modal Helpers
|
||||
function openModal(id) {
|
||||
const modal = document.getElementById(id);
|
||||
if (modal) modal.classList.add('open');
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
const modal = document.getElementById(id);
|
||||
if (modal) modal.classList.remove('open');
|
||||
}
|
||||
|
||||
// Soothing Nursery Synthesizer for Slideshow
|
||||
function toggleLullaby() {
|
||||
if (isAudioPlaying) {
|
||||
stopLullaby();
|
||||
} else {
|
||||
playLullaby();
|
||||
}
|
||||
}
|
||||
|
||||
function playLullaby() {
|
||||
try {
|
||||
if (!audioCtx) {
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
isAudioPlaying = true;
|
||||
document.getElementById('musicToggleBtn').innerHTML = '🔊 توقف موزیک لایت';
|
||||
|
||||
const notes = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 523.25]; // C, D, E, F, G, A, C5
|
||||
let noteIdx = 0;
|
||||
|
||||
musicInterval = setInterval(() => {
|
||||
if (!isAudioPlaying) return;
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
|
||||
const freq = notes[Math.floor(Math.random() * notes.length)];
|
||||
osc.type = 'sine';
|
||||
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
||||
|
||||
gain.gain.setValueAtTime(0.08, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1.2);
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + 1.2);
|
||||
}, 1200);
|
||||
|
||||
} catch (e) {
|
||||
console.error('Audio init error', e);
|
||||
}
|
||||
}
|
||||
|
||||
function stopLullaby() {
|
||||
isAudioPlaying = false;
|
||||
if (musicInterval) clearInterval(musicInterval);
|
||||
const btn = document.getElementById('musicToggleBtn');
|
||||
if (btn) btn.innerHTML = '🎵 پخش موزیک آرامشبخش';
|
||||
}
|
||||
|
||||
let slideshowTimer = null;
|
||||
let currentSlideshowIndex = 0;
|
||||
|
||||
function startSlideshow() {
|
||||
if (!allPhotos || allPhotos.length === 0) return;
|
||||
currentSlideshowIndex = 0;
|
||||
renderSlideshowSlide();
|
||||
|
||||
if (slideshowTimer) clearInterval(slideshowTimer);
|
||||
slideshowTimer = setInterval(() => {
|
||||
currentSlideshowIndex = (currentSlideshowIndex + 1) % allPhotos.length;
|
||||
renderSlideshowSlide();
|
||||
}, 4500);
|
||||
}
|
||||
|
||||
function renderSlideshowSlide() {
|
||||
const photo = allPhotos[currentSlideshowIndex];
|
||||
if (!photo) return;
|
||||
const slideImg = document.getElementById('slideshowCurrentImg');
|
||||
const slideTitle = document.getElementById('slideshowCurrentTitle');
|
||||
const slideDesc = document.getElementById('slideshowCurrentDesc');
|
||||
|
||||
if (slideImg) slideImg.src = photo.file_path;
|
||||
if (slideTitle) slideTitle.innerText = photo.title;
|
||||
if (slideDesc) slideDesc.innerText = photo.caption || `${photo.album_title || ''} • ${photo.age_tag || '۳ سالگی'}`;
|
||||
}
|
||||
|
||||
// Confetti Animation
|
||||
function showConfetti() {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const conf = document.createElement('div');
|
||||
conf.innerText = ['🎈', '⭐', '🎉', '💖', '👶', '✨'][Math.floor(Math.random() * 6)];
|
||||
conf.style.position = 'fixed';
|
||||
conf.style.left = Math.random() * 90 + 5 + 'vw';
|
||||
conf.style.top = '100vh';
|
||||
conf.style.fontSize = Math.random() * 20 + 20 + 'px';
|
||||
conf.style.zIndex = 9999;
|
||||
conf.style.pointerEvents = 'none';
|
||||
conf.style.transition = 'all 2.5s ease-out';
|
||||
document.body.appendChild(conf);
|
||||
|
||||
setTimeout(() => {
|
||||
conf.style.top = Math.random() * 20 + 'vh';
|
||||
conf.style.opacity = '0';
|
||||
conf.style.transform = `scale(${Math.random() + 0.5}) rotate(${Math.random() * 360}deg)`;
|
||||
}, 50);
|
||||
|
||||
setTimeout(() => conf.remove(), 2600);
|
||||
}
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.style.position = 'fixed';
|
||||
toast.style.bottom = '24px';
|
||||
toast.style.right = '24px';
|
||||
toast.style.background = type === 'error' ? '#ef4444' : '#10b981';
|
||||
toast.style.color = '#ffffff';
|
||||
toast.style.padding = '12px 22px';
|
||||
toast.style.borderRadius = '12px';
|
||||
toast.style.fontWeight = '700';
|
||||
toast.style.fontSize = '0.92rem';
|
||||
toast.style.boxShadow = '0 10px 25px rgba(0,0,0,0.2)';
|
||||
toast.style.zIndex = 10000;
|
||||
toast.style.direction = 'rtl';
|
||||
toast.innerText = msg;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.transition = 'opacity 0.5s';
|
||||
toast.style.opacity = '0';
|
||||
setTimeout(() => toast.remove(), 500);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||
}
|
||||
Reference in New Issue
Block a user