feat: initial release of 3-year-old child photo album website with PHP 8, SQLite, audio slideshow, and memories timeline

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