diff --git a/data/database.sqlite b/data/database.sqlite new file mode 100644 index 0000000..2e6c20e Binary files /dev/null and b/data/database.sqlite differ diff --git a/public/assets/css/style.css b/public/assets/css/style.css new file mode 100644 index 0000000..55930dd --- /dev/null +++ b/public/assets/css/style.css @@ -0,0 +1,915 @@ +@import url('https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css'); + +:root { + --primary: #3b82f6; + --primary-hover: #2563eb; + --primary-light: #eff6ff; + --accent: #f59e0b; + --accent-pink: #ec4899; + --accent-purple: #8b5cf6; + --accent-green: #10b981; + --bg-gradient: linear-gradient(135deg, #f0fdf4 0%, #eff6ff 50%, #faf5ff 100%); + --card-bg: rgba(255, 255, 255, 0.92); + --card-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.03); + --radius-xl: 24px; + --radius-lg: 16px; + --radius-md: 12px; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: 'Vazirmatn', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + -webkit-tap-highlight-color: transparent; +} + +body { + background: var(--bg-gradient); + min-height: 100vh; + color: #1e293b; + direction: rtl; + overflow-x: hidden; +} + +/* Floating background decoration */ +.floating-shapes { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 0; + overflow: hidden; +} + +.shape { + position: absolute; + font-size: 2.2rem; + opacity: 0.25; + animation: float 14s infinite ease-in-out; +} + +@keyframes float { + 0%, 100% { transform: translateY(0) rotate(0deg); } + 50% { transform: translateY(-35px) rotate(15deg); } +} + +.shape:nth-child(1) { top: 10%; left: 8%; animation-delay: 0s; } +.shape:nth-child(2) { top: 25%; right: 10%; animation-delay: 2s; } +.shape:nth-child(3) { top: 60%; left: 12%; animation-delay: 4s; } +.shape:nth-child(4) { top: 75%; right: 15%; animation-delay: 1s; } +.shape:nth-child(5) { top: 45%; left: 45%; animation-delay: 3s; } + +/* Container */ +.app-container { + position: relative; + z-index: 1; + max-width: 1280px; + margin: 0 auto; + padding: 24px 16px 80px 16px; +} + +/* Hero & Header */ +.hero-card { + background: linear-gradient(135deg, #ffffff 0%, #f0f9ff 100%); + border: 2px solid #e0f2fe; + border-radius: var(--radius-xl); + padding: 32px 28px; + box-shadow: var(--card-shadow); + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 24px; + margin-bottom: 28px; + position: relative; + overflow: hidden; +} + +.hero-card::after { + content: ''; + position: absolute; + top: -50px; + left: -50px; + width: 180px; + height: 180px; + background: radial-gradient(circle, rgba(59, 130, 246, 0.15) 0%, transparent 70%); + border-radius: 50%; +} + +.child-profile { + display: flex; + align-items: center; + gap: 20px; +} + +.avatar-badge { + width: 95px; + height: 95px; + border-radius: 50%; + background: linear-gradient(135deg, #60a5fa, #3b82f6); + display: flex; + align-items: center; + justify-content: center; + font-size: 3rem; + box-shadow: 0 10px 20px rgba(59, 130, 246, 0.3); + border: 4px solid #ffffff; + animation: pulse-avatar 3s infinite ease-in-out; +} + +@keyframes pulse-avatar { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.child-info h1 { + font-size: 1.9rem; + font-weight: 900; + color: #1e3a8a; + display: flex; + align-items: center; + gap: 10px; +} + +.child-info p { + color: #64748b; + font-size: 0.98rem; + margin-top: 4px; + max-width: 500px; +} + +.badge-age { + display: inline-flex; + align-items: center; + gap: 6px; + background: #fef3c7; + color: #b45309; + font-weight: 700; + font-size: 0.85rem; + padding: 4px 12px; + border-radius: 20px; + border: 1px solid #fde68a; + margin-top: 8px; +} + +.quick-stats { + display: flex; + gap: 14px; + flex-wrap: wrap; +} + +.stat-box { + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: var(--radius-md); + padding: 12px 18px; + min-width: 105px; + text-align: center; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); + transition: transform 0.2s; +} + +.stat-box:hover { + transform: translateY(-3px); + border-color: #93c5fd; +} + +.stat-number { + font-size: 1.5rem; + font-weight: 800; + color: #2563eb; +} + +.stat-label { + font-size: 0.8rem; + color: #64748b; + margin-top: 2px; +} + +/* Action Bar */ +.action-bar { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 24px; +} + +.nav-tabs { + display: flex; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(8px); + padding: 6px; + border-radius: 30px; + border: 1px solid #e2e8f0; + gap: 6px; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.03); +} + +.tab-btn { + border: none; + background: transparent; + padding: 9px 18px; + border-radius: 24px; + font-weight: 700; + font-size: 0.92rem; + color: #64748b; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: all 0.2s; +} + +.tab-btn.active { + background: #3b82f6; + color: #ffffff; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.35); +} + +.btn-group-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.btn { + border: none; + padding: 10px 18px; + border-radius: var(--radius-md); + font-weight: 700; + font-size: 0.92rem; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 8px; + transition: all 0.2s; +} + +.btn-primary { + background: linear-gradient(135deg, #3b82f6, #2563eb); + color: #ffffff; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); +} + +.btn-primary:hover { + background: linear-gradient(135deg, #2563eb, #1d4ed8); + transform: translateY(-2px); +} + +.btn-accent { + background: linear-gradient(135deg, #f59e0b, #d97706); + color: #ffffff; + box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); +} + +.btn-accent:hover { + transform: translateY(-2px); +} + +.btn-outline { + background: #ffffff; + color: #475569; + border: 1px solid #cbd5e1; +} + +.btn-outline:hover { + background: #f8fafc; + border-color: #94a3b8; +} + +/* Filters & Search */ +.filters-bar { + background: var(--card-bg); + padding: 16px 20px; + border-radius: var(--radius-lg); + border: 1px solid #e2e8f0; + margin-bottom: 24px; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 16px; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02); +} + +.album-filter-scroll { + display: flex; + gap: 8px; + overflow-x: auto; + padding-bottom: 4px; + max-width: 100%; +} + +.filter-chip { + padding: 6px 14px; + border-radius: 20px; + background: #f1f5f9; + border: 1px solid #e2e8f0; + color: #475569; + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + white-space: nowrap; + transition: all 0.2s; +} + +.filter-chip.active { + background: #3b82f6; + color: #ffffff; + border-color: #3b82f6; +} + +.search-box { + position: relative; + min-width: 240px; +} + +.search-box input { + width: 100%; + padding: 8px 36px 8px 14px; + border-radius: 20px; + border: 1px solid #cbd5e1; + font-size: 0.88rem; + outline: none; + background: #ffffff; +} + +.search-box input:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} + +.search-icon { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + color: #94a3b8; + font-size: 0.9rem; +} + +/* Gallery Grid */ +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 22px; +} + +.photo-card { + background: #ffffff; + border-radius: var(--radius-lg); + border: 1px solid #e2e8f0; + overflow: hidden; + box-shadow: var(--card-shadow); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + flex-direction: column; + position: relative; +} + +.photo-card:hover { + transform: translateY(-6px); + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + border-color: #bfdbfe; +} + +.photo-media-wrapper { + position: relative; + width: 100%; + padding-top: 75%; /* 4:3 Aspect Ratio */ + background: #f8fafc; + overflow: hidden; + cursor: pointer; +} + +.photo-media { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.4s ease; +} + +.photo-card:hover .photo-media { + transform: scale(1.06); +} + +.photo-badge-age { + position: absolute; + top: 10px; + right: 10px; + background: rgba(0, 0, 0, 0.65); + backdrop-filter: blur(4px); + color: #ffffff; + font-size: 0.75rem; + font-weight: 700; + padding: 4px 10px; + border-radius: 12px; +} + +.fav-btn { + position: absolute; + top: 10px; + left: 10px; + width: 34px; + height: 34px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(4px); + border: none; + font-size: 1.1rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.2s; +} + +.fav-btn:hover { + transform: scale(1.15); +} + +.fav-btn.active { + color: #ef4444; +} + +.photo-content { + padding: 16px; + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.photo-album-tag { + font-size: 0.78rem; + font-weight: 700; + color: #3b82f6; + margin-bottom: 6px; + display: flex; + align-items: center; + gap: 4px; +} + +.photo-title { + font-size: 1.05rem; + font-weight: 800; + color: #1e293b; + margin-bottom: 6px; +} + +.photo-caption { + font-size: 0.85rem; + color: #64748b; + line-height: 1.5; + margin-bottom: 12px; + flex-grow: 1; +} + +.photo-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 10px; + border-top: 1px solid #f1f5f9; + font-size: 0.8rem; + color: #94a3b8; +} + +.like-action { + display: inline-flex; + align-items: center; + gap: 5px; + background: #fef2f2; + color: #ef4444; + font-weight: 700; + padding: 4px 10px; + border-radius: 12px; + border: none; + cursor: pointer; + transition: all 0.2s; +} + +.like-action:hover { + background: #fee2e2; + transform: scale(1.05); +} + +.card-actions-menu { + display: flex; + gap: 6px; +} + +.btn-icon-sm { + background: transparent; + border: none; + color: #94a3b8; + cursor: pointer; + padding: 4px; + border-radius: 6px; + font-size: 1rem; + transition: color 0.2s; +} + +.btn-icon-sm:hover { + color: #ef4444; +} + +/* Albums Tab Cards */ +.albums-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 20px; +} + +.album-box { + background: #ffffff; + border-radius: var(--radius-lg); + border: 1px solid #e2e8f0; + padding: 22px; + box-shadow: var(--card-shadow); + transition: all 0.3s; + cursor: pointer; + display: flex; + flex-direction: column; + position: relative; +} + +.album-box:hover { + transform: translateY(-4px); + border-color: #93c5fd; + box-shadow: 0 12px 20px -5px rgba(59, 130, 246, 0.15); +} + +.album-icon-badge { + font-size: 2.8rem; + margin-bottom: 14px; +} + +.album-box h3 { + font-size: 1.15rem; + font-weight: 800; + color: #1e293b; + margin-bottom: 6px; +} + +.album-box p { + font-size: 0.85rem; + color: #64748b; + line-height: 1.4; + margin-bottom: 16px; + flex-grow: 1; +} + +.album-count-badge { + display: inline-flex; + align-items: center; + gap: 6px; + background: #eff6ff; + color: #2563eb; + font-weight: 700; + font-size: 0.82rem; + padding: 4px 12px; + border-radius: 12px; + align-self: flex-start; +} + +/* Quotes Tab */ +.quotes-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 20px; +} + +.quote-card { + background: linear-gradient(135deg, #ffffff 0%, #fefce8 100%); + border: 2px dashed #fde047; + border-radius: var(--radius-lg); + padding: 22px; + position: relative; + box-shadow: var(--card-shadow); +} + +.quote-bubble { + font-size: 1.1rem; + font-weight: 700; + color: #713f12; + line-height: 1.6; + margin-bottom: 14px; + position: relative; +} + +.quote-bubble::before { + content: '“'; + font-size: 3.5rem; + color: #facc15; + opacity: 0.4; + position: absolute; + top: -25px; + right: -10px; + font-family: Georgia, serif; +} + +.quote-meta { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.82rem; + color: #a16207; + padding-top: 10px; + border-top: 1px solid #fef08a; +} + +/* Milestones Timeline */ +.timeline-container { + position: relative; + padding: 20px 0; +} + +.timeline-container::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + right: 32px; + width: 4px; + background: linear-gradient(to bottom, #3b82f6, #8b5cf6, #ec4899); + border-radius: 4px; +} + +.timeline-item { + position: relative; + margin-bottom: 28px; + padding-right: 70px; +} + +.timeline-icon { + position: absolute; + right: 14px; + top: 0; + width: 40px; + height: 40px; + border-radius: 50%; + background: #ffffff; + border: 3px solid #3b82f6; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.timeline-card { + background: #ffffff; + border-radius: var(--radius-lg); + border: 1px solid #e2e8f0; + padding: 20px; + box-shadow: var(--card-shadow); +} + +.timeline-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.timeline-header h3 { + font-size: 1.1rem; + font-weight: 800; + color: #1e293b; +} + +.timeline-age { + background: #e0e7ff; + color: #4338ca; + font-size: 0.78rem; + font-weight: 700; + padding: 3px 10px; + border-radius: 12px; +} + +/* Lightbox Modal */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(15, 23, 42, 0.75); + backdrop-filter: blur(8px); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; +} + +.modal-overlay.open { + opacity: 1; + pointer-events: auto; +} + +.modal-card { + background: #ffffff; + border-radius: var(--radius-xl); + max-width: 600px; + width: 100%; + max-height: 90vh; + overflow-y: auto; + padding: 28px; + position: relative; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + animation: modal-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +@keyframes modal-pop { + 0% { transform: scale(0.9) translateY(20px); } + 100% { transform: scale(1) translateY(0); } +} + +.modal-close-btn { + position: absolute; + top: 18px; + left: 18px; + width: 36px; + height: 36px; + border-radius: 50%; + background: #f1f5f9; + border: none; + font-size: 1.2rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-header { + margin-bottom: 20px; +} + +.modal-header h2 { + font-size: 1.4rem; + font-weight: 800; + color: #1e293b; + display: flex; + align-items: center; + gap: 8px; +} + +.form-group { + margin-bottom: 16px; +} + +.form-group label { + display: block; + font-size: 0.88rem; + font-weight: 700; + color: #475569; + margin-bottom: 6px; +} + +.form-control { + width: 100%; + padding: 10px 14px; + border-radius: var(--radius-md); + border: 1px solid #cbd5e1; + font-size: 0.95rem; + outline: none; + background: #f8fafc; + transition: all 0.2s; +} + +.form-control:focus { + border-color: #3b82f6; + background: #ffffff; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} + +.file-drop-area { + border: 2px dashed #93c5fd; + border-radius: var(--radius-lg); + padding: 30px; + text-align: center; + background: #eff6ff; + cursor: pointer; + transition: all 0.2s; +} + +.file-drop-area:hover { + background: #dbeafe; + border-color: #3b82f6; +} + +/* Lightbox specific */ +.lightbox-modal-card { + max-width: 900px; + padding: 0; + overflow: hidden; + background: #000000; + color: #ffffff; +} + +.lightbox-content { + display: flex; + flex-direction: column; +} + +@media (min-width: 768px) { + .lightbox-content { + flex-direction: row; + height: 550px; + } +} + +.lightbox-img-area { + flex: 1.4; + background: #000000; + display: flex; + align-items: center; + justify-content: center; + position: relative; + min-height: 300px; +} + +.lightbox-img-area img { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.lightbox-sidebar { + flex: 1; + background: #ffffff; + color: #1e293b; + padding: 24px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: 60px 20px; + background: #ffffff; + border-radius: var(--radius-xl); + border: 2px dashed #cbd5e1; +} + +.empty-state-icon { + font-size: 3.5rem; + margin-bottom: 12px; +} + +.empty-state h3 { + font-size: 1.3rem; + font-weight: 800; + color: #334155; + margin-bottom: 6px; +} + +.empty-state p { + font-size: 0.9rem; + color: #64748b; + margin-bottom: 18px; +} + +/* Mobile adjustments */ +@media (max-width: 640px) { + .hero-card { + padding: 20px 16px; + } + .child-profile { + flex-direction: column; + text-align: center; + } + .child-info h1 { + justify-content: center; + } + .quick-stats { + justify-content: center; + width: 100%; + } + .action-bar { + flex-direction: column; + align-items: stretch; + } + .nav-tabs { + overflow-x: auto; + justify-content: flex-start; + } + .tab-btn { + padding: 8px 12px; + font-size: 0.82rem; + white-space: nowrap; + } +} diff --git a/public/assets/js/app.js b/public/assets/js/app.js new file mode 100644 index 0000000..6dbe8c2 --- /dev/null +++ b/public/assets/js/app.js @@ -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 = '

در حال بارگذاری عکس‌های قشنگ... 👶🎈

'; + + 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 = ` +
+
📸
+

هنوز عکسی در این بخش ثبت نشده!

+

با زدن دکمه «+ افزودن عکس جدید» اولین خاطره شیرین را اضافه کنید.

+ +
+ `; + return; + } + + grid.innerHTML = photos.map((p, index) => ` +
+
+ ${p.title} + ${p.age_tag || '۳ سالگی'} + +
+
+
+ ${p.album_icon || '🎈'} + ${p.album_title || 'آلبوم عمومی'} +
+

${escapeHtml(p.title)}

+

${escapeHtml(p.caption || '')}

+ +
+
+ `).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 = '

در حال بارگذاری آلبوم‌ها... 📂

'; + + const res = await api('albums'); + if (res.success) { + if (!res.data || res.data.length === 0) { + grid.innerHTML = '

هنوز آلبومی ساخته نشده است.

'; + return; + } + grid.innerHTML = res.data.map(a => ` +
+
${a.icon || '🎈'}
+

${escapeHtml(a.title)}

+

${escapeHtml(a.description || '')}

+
📸 ${a.photos_count || 0} عکس
+
+ `).join(''); + } +} + +// Quotes Tab +async function loadQuotes() { + const grid = document.getElementById('quotesGrid'); + if (!grid) return; + grid.innerHTML = '

در حال بارگذاری حرف‌های بامزه... 👶

'; + + const res = await api('quotes'); + if (res.success) { + if (!res.data || res.data.length === 0) { + grid.innerHTML = '

هنوز جمله‌ای ثبت نشده است.

'; + return; + } + grid.innerHTML = res.data.map(q => ` +
+
${escapeHtml(q.quote)}
+
📍 ${escapeHtml(q.context || 'بدون توضیح')}
+
+ 🏷️ ${q.age_tag || '۳ سالگی'} + 📅 ${q.said_date || ''} + +
+
+ `).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 = '

در حال بارگذاری مراحل رشد... 🌟

'; + + const res = await api('milestones'); + if (res.success) { + if (!res.data || res.data.length === 0) { + container.innerHTML = '

هنوز مرحله رشدی ثبت نشده است.

'; + return; + } + container.innerHTML = res.data.map(m => ` +
+
${m.icon || '🌟'}
+
+
+

${escapeHtml(m.title)}

+ ${m.age_tag || '۳ سالگی'} +
+

${escapeHtml(m.description || '')}

+
+ 📅 تاریخ: ${m.milestone_date || ''} + +
+
+
+ `).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, "'"); +} diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..214f694 --- /dev/null +++ b/public/index.php @@ -0,0 +1,558 @@ + true, 'data' => Photo::all($albumId, $favOnly, $search)]); + exit; + + case 'albums': + echo json_encode(['success' => true, 'data' => Album::all()]); + exit; + + case 'quotes': + echo json_encode(['success' => true, 'data' => Quote::all()]); + exit; + + case 'milestones': + echo json_encode(['success' => true, 'data' => Milestone::all()]); + exit; + + case 'stats': + echo json_encode(['success' => true, 'data' => Photo::getStats()]); + exit; + + case 'like_photo': + $id = (int)($_GET['id'] ?? 0); + $likes = Photo::incrementLike($id); + echo json_encode(['success' => true, 'likes' => $likes]); + exit; + + case 'toggle_fav': + $id = (int)($_GET['id'] ?? 0); + Photo::toggleFavorite($id); + echo json_encode(['success' => true]); + exit; + + case 'delete_photo': + $id = (int)($_GET['id'] ?? 0); + Photo::delete($id); + echo json_encode(['success' => true]); + exit; + + case 'delete_album': + $id = (int)($_GET['id'] ?? 0); + Album::delete($id); + echo json_encode(['success' => true]); + exit; + + case 'delete_quote': + $id = (int)($_GET['id'] ?? 0); + Quote::delete($id); + echo json_encode(['success' => true]); + exit; + + case 'delete_milestone': + $id = (int)($_GET['id'] ?? 0); + Milestone::delete($id); + echo json_encode(['success' => true]); + exit; + + case 'upload_photo': + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + throw new Exception('متد نامعتبر است'); + } + + if (empty($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) { + throw new Exception('لطفاً یک فایل تصویر معتبر انتخاب کنید'); + } + + $file = $_FILES['photo']; + $allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml']; + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = finfo_file($finfo, $file['tmp_name']); + finfo_close($finfo); + + if (!in_array($mime, $allowed)) { + throw new Exception('فرمت فایل پشتیبانی نمی‌شود (فقط JPG، PNG، WEBP، GIF)'); + } + + $ext = pathinfo($file['name'], PATHINFO_EXTENSION); + if (!$ext) $ext = 'jpg'; + $filename = 'photo_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $ext; + $target = __DIR__ . '/uploads/' . $filename; + + if (!move_uploaded_file($file['tmp_name'], $target)) { + throw new Exception('خطا در انتقال فایل ذخیره‌سازی'); + } + + $albumId = (int)($_POST['album_id'] ?? 1); + $title = trim($_POST['title'] ?? 'خاطره جدید'); + $caption = trim($_POST['caption'] ?? ''); + $ageTag = trim($_POST['age_tag'] ?? '۳ سالگی'); + $photoDate = $_POST['photo_date'] ?? date('Y-m-d'); + + $photoId = Photo::create($albumId, $title, 'uploads/' . $filename, $caption, $ageTag, $photoDate); + echo json_encode(['success' => true, 'photo_id' => $photoId]); + exit; + + case 'create_album': + $title = trim($_POST['title'] ?? ''); + if (!$title) throw new Exception('عنوان آلبوم الزامی است'); + $description = trim($_POST['description'] ?? ''); + $icon = trim($_POST['icon'] ?? '🎈'); + $color = trim($_POST['color'] ?? '#3b82f6'); + + $albumId = Album::create($title, $description, $icon, $color); + echo json_encode(['success' => true, 'album_id' => $albumId]); + exit; + + case 'create_quote': + $quote = trim($_POST['quote'] ?? ''); + if (!$quote) throw new Exception('متن جمله الزامی است'); + $ageTag = trim($_POST['age_tag'] ?? '۳ سالگی'); + $context = trim($_POST['context'] ?? ''); + $saidDate = $_POST['said_date'] ?? date('Y-m-d'); + + $id = Quote::create($quote, $ageTag, $context, $saidDate); + echo json_encode(['success' => true, 'quote_id' => $id]); + exit; + + case 'create_milestone': + $title = trim($_POST['title'] ?? ''); + if (!$title) throw new Exception('عنوان دستاورد الزامی است'); + $description = trim($_POST['description'] ?? ''); + $ageTag = trim($_POST['age_tag'] ?? '۳ سالگی'); + $icon = trim($_POST['icon'] ?? '🌟'); + $milestoneDate = $_POST['milestone_date'] ?? date('Y-m-d'); + + $id = Milestone::create($title, $description, $ageTag, $icon, $milestoneDate); + echo json_encode(['success' => true, 'milestone_id' => $id]); + exit; + + default: + echo json_encode(['success' => false, 'error' => 'مسیر یافت نشد']); + exit; + } + } catch (Throwable $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + exit; + } +} + +// Initial Data for HTML Render +$albums = Album::all(); +$stats = Photo::getStats(); +?> + + + + + + آلبوم خاطرات حامد کوچولو 🎈 پسر ۳ ساله + + + + + +
+
🎈
+
+
🧸
+
🚗
+
🎨
+
+ +
+ + +
+
+
👶
+
+

+ آلبوم خاطرات حامد کوچولو + 🎈 +

+

گنجینه عکس‌ها، خنده‌های شیرین، اولین حرف‌ها و لحظات ناب رشد پسر ۳ ساله ما 💖

+
+ 🎂 + ۳ سال و ۲ ماهه (متولد تابستان ۱۴۰۲) +
+
+
+ +
+
+
+
📸 عکس شیرین
+
+
+
+
📂 آلبوم موضوعی
+
+
+
+
💬 جمله بامزه
+
+
+
+
🌟 دستاورد رشد
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/public/uploads/cake_3years.svg b/public/uploads/cake_3years.svg new file mode 100644 index 0000000..e1a70e2 --- /dev/null +++ b/public/uploads/cake_3years.svg @@ -0,0 +1,14 @@ + + + + + + + + + + 🎂 + جشن تولد ۳ سالگی حامد + 🎈 🎉 🎁 🧸 🎈 + شیرین‌ترین روز زندگی ما + \ No newline at end of file diff --git a/public/uploads/finger_painting.svg b/public/uploads/finger_painting.svg new file mode 100644 index 0000000..7b84db7 --- /dev/null +++ b/public/uploads/finger_painting.svg @@ -0,0 +1,14 @@ + + + + + + + + + + 🎨 + شاهکار هنری حامد + 🌈 🖍️ ✨ 🖌️ 💖 + دنیای پر از رنگ و خلاقیت کودکانه + \ No newline at end of file diff --git a/public/uploads/lego_house.svg b/public/uploads/lego_house.svg new file mode 100644 index 0000000..f0834ef --- /dev/null +++ b/public/uploads/lego_house.svg @@ -0,0 +1,14 @@ + + + + + + + + + + 🧸 + مهندس کوچولوی خانه + 🧱 🚗 🚂 🚀 🌟 + تمرکز و شادی در دنیای اسباب‌بازی‌ها + \ No newline at end of file diff --git a/public/uploads/park_fun.svg b/public/uploads/park_fun.svg new file mode 100644 index 0000000..7ffefa1 --- /dev/null +++ b/public/uploads/park_fun.svg @@ -0,0 +1,14 @@ + + + + + + + + + + 🎠 + روز شاد در بوستان کودک + 🛝 ⚽ 🎈 🚲 🌿 + خنده‌های از ته دل و دویدن روی چمن‌ها + \ No newline at end of file diff --git a/public/uploads/sea_trip.svg b/public/uploads/sea_trip.svg new file mode 100644 index 0000000..17f2077 --- /dev/null +++ b/public/uploads/sea_trip.svg @@ -0,0 +1,14 @@ + + + + + + + + + + 🏖️ + سفر خاطره‌انگیز به شمال + 🌊 🐚 🦀 🚗 ☀️ + شن‌بازی و ساخت برج‌های شنی + \ No newline at end of file diff --git a/src/Album.php b/src/Album.php new file mode 100644 index 0000000..1a07863 --- /dev/null +++ b/src/Album.php @@ -0,0 +1,39 @@ +query(" + SELECT a.*, COUNT(p.id) as photos_count + FROM albums a + LEFT JOIN photos p ON a.id = p.album_id + GROUP BY a.id + ORDER BY a.id DESC + "); + return $stmt->fetchAll(); + } + + public static function find(int $id): ?array { + $db = Database::getConnection(); + $stmt = $db->prepare("SELECT * FROM albums WHERE id = ?"); + $stmt->execute([$id]); + return $stmt->fetch() ?: null; + } + + public static function create(string $title, ?string $description = '', ?string $icon = '🎈', ?string $color = '#3b82f6'): int { + $db = Database::getConnection(); + $stmt = $db->prepare("INSERT INTO albums (title, description, icon, color) VALUES (?, ?, ?, ?)"); + $stmt->execute([$title, $description, $icon, $color]); + return (int)$db->lastInsertId(); + } + + public static function delete(int $id): bool { + $db = Database::getConnection(); + $stmt = $db->prepare("DELETE FROM albums WHERE id = ?"); + return $stmt->execute([$id]); + } +} diff --git a/src/Database.php b/src/Database.php new file mode 100644 index 0000000..b6faec7 --- /dev/null +++ b/src/Database.php @@ -0,0 +1,118 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + self::$instance->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + + if ($isNew || filesize($dbPath) === 0) { + self::initSchema(self::$instance); + self::seedInitialData(self::$instance); + } + } + return self::$instance; + } + + private static function initSchema(PDO $db): void { + $db->exec(" + CREATE TABLE IF NOT EXISTS albums ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + cover_image TEXT, + icon TEXT DEFAULT '🎈', + color TEXT DEFAULT '#3b82f6', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS photos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + album_id INTEGER, + title TEXT NOT NULL, + caption TEXT, + file_path TEXT NOT NULL, + age_tag TEXT, + photo_date DATE, + is_favorite INTEGER DEFAULT 0, + likes_count INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE SET NULL + ); + + CREATE TABLE IF NOT EXISTS quotes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + quote TEXT NOT NULL, + age_tag TEXT, + said_date DATE, + context TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS milestones ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + milestone_date DATE, + age_tag TEXT, + icon TEXT DEFAULT '🌟', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT + ); + "); + + $db->exec(" + INSERT OR IGNORE INTO settings (key, value) VALUES + ('child_name', 'حامد کوچولو'), + ('birth_date', '2023-06-15'), + ('bio', 'آلبوم خاطرات شیرین، خنده‌ها و لحظات رشد پسر ۳ ساله دوست‌داشتنی ما') + "); + } + + private static function seedInitialData(PDO $db): void { + // Sample Albums + $db->exec(" + INSERT INTO albums (title, description, icon, color) VALUES + ('تولد ۳ سالگی 🎂', 'جشن تولد با تم دایناسور و خنده‌های پرانرژی', '🎂', '#f59e0b'), + ('پارک و بازی‌های شاد 🎠', 'دویدن، تاب‌بازی، سرسره و حباب‌بازی', '🎠', '#10b981'), + ('نقاشی‌ها و کارهای هنری 🎨', 'شاهکارهای نقاشی انگشتی و دست‌سازه‌های جالب', '🎨', '#ec4899'), + ('سفرهای هیجان‌انگیز 🚗', 'دیدن ساحل دریا، جنگل و ماجراجویی‌های خانوادگی', '🚗', '#6366f1'), + ('خاطرات و بازی‌های خانه 🧸', 'قصه قبل از خواب، خانه‌سازی با لگو و بازی‌های خودمانی', '🧸', '#8b5cf6') + "); + + // Sample Quotes + $db->exec(" + INSERT INTO quotes (quote, age_tag, said_date, context) VALUES + ('بابا خورشید خسته شد رفت لالا، ما هم بریم پیشش؟', '۳ سال و ۲ ماهگی', '2026-08-15', 'موقع غروب آفتاب کنار پنجره'), + ('مامان این دایناسوره باهام رفیق شده، واسش بشقاب بیار!', '۳ سال و ۱ ماهگی', '2026-07-20', 'وسط اتاق بازی با اسباب‌بازی‌ها'), + ('من دیگه قد درخت بزرگ شدم، سرم می‌خوره به آسمون!', '۲ سال و ۱۱ ماهگی', '2026-05-10', 'وقتی روی مبل ایستاد و دست‌هاشو بالا برد') + "); + + // Sample Milestones + $db->exec(" + INSERT INTO milestones (title, description, milestone_date, age_tag, icon) VALUES + ('اولین باری که سه چرخه سوار شد', 'بدون کمک پدال زد و کل حیاط رو دور زد و دست تکون داد', '2026-06-01', '۳ سالگی', '🚲'), + ('شمارش اعداد تا ۱۰ به فارسی و انگلیسی', 'اعداد ۱ تا ۱۰ رو با شعر و ریتم جذاب کامل یاد گرفت', '2026-07-12', '۳ سال و ۱ ماهگی', '🔢'), + ('اولین نقاشی چهره خانواده', 'مامان و بابا رو با دایره‌های بزرگ و لبخندهای پهن کشید', '2026-08-05', '۳ سال و ۲ ماهگی', '🖍️') + "); + } +} diff --git a/src/Milestone.php b/src/Milestone.php new file mode 100644 index 0000000..6dd7e3c --- /dev/null +++ b/src/Milestone.php @@ -0,0 +1,25 @@ +query("SELECT * FROM milestones ORDER BY milestone_date DESC, id DESC")->fetchAll(); + } + + public static function create(string $title, ?string $description = '', ?string $ageTag = '۳ سالگی', ?string $icon = '🌟', ?string $milestoneDate = null): int { + $db = Database::getConnection(); + $stmt = $db->prepare("INSERT INTO milestones (title, description, age_tag, icon, milestone_date) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$title, $description, $ageTag, $icon, $milestoneDate ?: date('Y-m-d')]); + return (int)$db->lastInsertId(); + } + + public static function delete(int $id): bool { + $db = Database::getConnection(); + $stmt = $db->prepare("DELETE FROM milestones WHERE id = ?"); + return $stmt->execute([$id]); + } +} diff --git a/src/Photo.php b/src/Photo.php new file mode 100644 index 0000000..c6ce306 --- /dev/null +++ b/src/Photo.php @@ -0,0 +1,93 @@ +prepare($sql); + $stmt->execute($params); + return $stmt->fetchAll(); + } + + public static function create(int $albumId, string $title, string $filePath, ?string $caption = '', ?string $ageTag = '۳ سالگی', ?string $photoDate = null): int { + $db = Database::getConnection(); + $stmt = $db->prepare(" + INSERT INTO photos (album_id, title, caption, file_path, age_tag, photo_date) + VALUES (?, ?, ?, ?, ?, ?) + "); + $stmt->execute([$albumId, $title, $caption, $filePath, $ageTag, $photoDate ?: date('Y-m-d')]); + return (int)$db->lastInsertId(); + } + + public static function toggleFavorite(int $id): bool { + $db = Database::getConnection(); + $stmt = $db->prepare("UPDATE photos SET is_favorite = (CASE WHEN is_favorite = 1 THEN 0 ELSE 1 END) WHERE id = ?"); + return $stmt->execute([$id]); + } + + public static function incrementLike(int $id): int { + $db = Database::getConnection(); + $stmt = $db->prepare("UPDATE photos SET likes_count = likes_count + 1 WHERE id = ?"); + $stmt->execute([$id]); + + $stmt2 = $db->prepare("SELECT likes_count FROM photos WHERE id = ?"); + $stmt2->execute([$id]); + return (int)$stmt2->fetchColumn(); + } + + public static function delete(int $id): bool { + $db = Database::getConnection(); + $stmt = $db->prepare("SELECT file_path FROM photos WHERE id = ?"); + $stmt->execute([$id]); + $file = $stmt->fetchColumn(); + if ($file && file_exists(__DIR__ . '/../public/' . $file)) { + @unlink(__DIR__ . '/../public/' . $file); + } + + $stmtDel = $db->prepare("DELETE FROM photos WHERE id = ?"); + return $stmtDel->execute([$id]); + } + + public static function getStats(): array { + $db = Database::getConnection(); + $photosCount = (int)$db->query("SELECT COUNT(*) FROM photos")->fetchColumn(); + $albumsCount = (int)$db->query("SELECT COUNT(*) FROM albums")->fetchColumn(); + $quotesCount = (int)$db->query("SELECT COUNT(*) FROM quotes")->fetchColumn(); + $milestonesCount = (int)$db->query("SELECT COUNT(*) FROM milestones")->fetchColumn(); + $favoritesCount = (int)$db->query("SELECT COUNT(*) FROM photos WHERE is_favorite = 1")->fetchColumn(); + + return [ + 'photos' => $photosCount, + 'albums' => $albumsCount, + 'quotes' => $quotesCount, + 'milestones' => $milestonesCount, + 'favorites' => $favoritesCount, + ]; + } +} diff --git a/src/Quote.php b/src/Quote.php new file mode 100644 index 0000000..e0d6394 --- /dev/null +++ b/src/Quote.php @@ -0,0 +1,25 @@ +query("SELECT * FROM quotes ORDER BY id DESC")->fetchAll(); + } + + public static function create(string $quote, ?string $ageTag = '۳ سالگی', ?string $context = '', ?string $saidDate = null): int { + $db = Database::getConnection(); + $stmt = $db->prepare("INSERT INTO quotes (quote, age_tag, context, said_date) VALUES (?, ?, ?, ?)"); + $stmt->execute([$quote, $ageTag, $context, $saidDate ?: date('Y-m-d')]); + return (int)$db->lastInsertId(); + } + + public static function delete(int $id): bool { + $db = Database::getConnection(); + $stmt = $db->prepare("DELETE FROM quotes WHERE id = ?"); + return $stmt->execute([$id]); + } +} diff --git a/src/seed_photos.php b/src/seed_photos.php new file mode 100644 index 0000000..74fb815 --- /dev/null +++ b/src/seed_photos.php @@ -0,0 +1,152 @@ + 'cake_3years.svg', + 'title' => 'فوت کردن شمع تولد ۳ سالگی 🎂', + 'caption' => 'وقتی همه با هم دست زدیم و با خوشحالی شمع ۳ سالگی رو فوت کرد!', + 'album_id' => 1, + 'age_tag' => '۳ سال تمام', + 'photo_date' => '2026-06-15', + 'svg' => ' + + + + + + + + + 🎂 + جشن تولد ۳ سالگی حامد + 🎈 🎉 🎁 🧸 🎈 + شیرین‌ترین روز زندگی ما + ' + ], + [ + 'file' => 'park_fun.svg', + 'title' => 'تاب‌بازی و خنده‌های بی‌پایان در پارک 🎠', + 'caption' => 'می‌گفت بابا منو بفرست تا پیش ابرا!', + 'album_id' => 2, + 'age_tag' => '۳ سال و ۱ ماهگی', + 'photo_date' => '2026-07-04', + 'svg' => ' + + + + + + + + + 🎠 + روز شاد در بوستان کودک + 🛝 ⚽ 🎈 🚲 🌿 + خنده‌های از ته دل و دویدن روی چمن‌ها + ' + ], + [ + 'file' => 'finger_painting.svg', + 'title' => 'اولین نقاشی انگشتی رنگین‌کمانی 🎨', + 'caption' => 'دست‌ها و لباس‌ها رنگی شد ولی اثر هنری شگفت‌انگیز بود!', + 'album_id' => 3, + 'age_tag' => '۳ سال و ۲ ماهگی', + 'photo_date' => '2026-08-10', + 'svg' => ' + + + + + + + + + 🎨 + شاهکار هنری حامد + 🌈 🖍️ ✨ 🖌️ 💖 + دنیای پر از رنگ و خلاقیت کودکانه + ' + ], + [ + 'file' => 'sea_trip.svg', + 'title' => 'قلعه‌سازی با ماسه در ساحل دریا 🌊', + 'caption' => 'اولین باری بود که موج‌های ریز آب به پاهای کوچولوش می‌خورد و قلقلکش می‌اومد!', + 'album_id' => 4, + 'age_tag' => '۳ سال و ۱ ماهگی', + 'photo_date' => '2026-07-28', + 'svg' => ' + + + + + + + + + 🏖️ + سفر خاطره‌انگیز به شمال + 🌊 🐚 🦀 🚗 ☀️ + شن‌بازی و ساخت برج‌های شنی + ' + ], + [ + 'file' => 'lego_house.svg', + 'title' => 'ساخت قلعه بزرگ با قطعات لگو 🏰', + 'caption' => 'نیم ساعت با دقت تمام قطعات رو چید و برای ماشینش گاراژ درست کرد.', + 'album_id' => 5, + 'age_tag' => '۳ سال و ۲ ماهگی', + 'photo_date' => '2026-08-22', + 'svg' => ' + + + + + + + + + 🧸 + مهندس کوچولوی خانه + 🧱 🚗 🚂 🚀 🌟 + تمرکز و شادی در دنیای اسباب‌بازی‌ها + ' + ] +]; + +$stmtCount = $db->query("SELECT COUNT(*) FROM photos")->fetchColumn(); +if ($stmtCount == 0) { + $stmt = $db->prepare(" + INSERT INTO photos (album_id, title, caption, file_path, age_tag, photo_date, is_favorite, likes_count) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + "); + + foreach ($photosData as $p) { + $filePath = 'uploads/' . $p['file']; + file_put_contents(__DIR__ . '/../public/' . $filePath, $p['svg']); + $stmt->execute([ + $p['album_id'], + $p['title'], + $p['caption'], + $filePath, + $p['age_tag'], + $p['photo_date'], + 1, + rand(5, 18) + ]); + } + echo "Seeded " . count($photosData) . " sample photos!\n"; +} else { + echo "Photos already present.\n"; +}