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.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.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();
+?>
+
+
+
+
+
+ آلبوم خاطرات حامد کوچولو 🎈 پسر ۳ ساله
+
+
+
+
+
+
+
+
+
+
+
+
+
👶
+
+
+ آلبوم خاطرات حامد کوچولو
+ 🎈
+
+
گنجینه عکسها، خندههای شیرین، اولین حرفها و لحظات ناب رشد پسر ۳ ساله ما 💖
+
+ 🎂
+ ۳ سال و ۲ ماهه (متولد تابستان ۱۴۰۲)
+
+
+
+
+
+
+
= $stats['photos'] ?>
+
📸 عکس شیرین
+
+
+
= $stats['albums'] ?>
+
📂 آلبوم موضوعی
+
+
+
= $stats['quotes'] ?>
+
💬 جمله بامزه
+
+
+
= $stats['milestones'] ?>
+
🌟 دستاورد رشد
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 🔍
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📂 دستهبندی آلبومهای خاطرات
+
+
+
+
+
+
+
+
+
+
+
+
💬 دفترچه جملات و حرفهای بامزه
+
ثبت تکهکلامها و حرفهای شیرین و خندهدار ۳ سالگی
+
+
+
+
+
+
+
+
+
+
+
+
+
🌟 خط زمانی رشد و دستاوردهای ۳ سالگی
+
مراحل مهم، مهارتهای جدید و افتخارات کوچولوی دوستداشتنی
+
+
+
+
+
+
+
+
+
+
+
+
🎵 اسلایدشو موزیکال خاطرات
+
+
+
+
+
+
![Slideshow]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![بزرگنمایی]()
+
+
+
+
+
+
+
+
+
+
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";
+}