diff --git a/docker/index.html b/docker/index.html index 1444e3f..90af93a 100644 --- a/docker/index.html +++ b/docker/index.html @@ -1920,6 +1920,97 @@ body { grid-template-columns: repeat(2, 1fr); } } + +/* ===== Animation System ===== */ + +/* -- Keyframes -- */ +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } +@keyframes slideUp { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } } +@keyframes slideDown { from { transform: translateY(0); opacity: 1; } to { transform: translateY(100%); opacity: 0; } } +@keyframes slideInRight { from { transform: translateX(120%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } +@keyframes slideOutRight{ from { transform: translateX(0); opacity: 1; } to { transform: translateX(120%); opacity: 0; } } +@keyframes scaleIn { from { transform: scale(0.85); opacity: 0; } to { transform: scale(1); opacity: 1; } } +@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.12); } } +@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } + +/* -- Button press -- */ +button, .btn, .meal-card-main, .dagboek-item, .food-item, +.standaard-portie-btn, .portie-stepper-plus, .portie-stepper-minus { + transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease; +} +button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-item:active, +.portie-stepper-plus:active, .portie-stepper-minus:active { + transform: scale(0.96); +} + +/* -- Modal / Bottom Sheet -- */ +.modal-overlay { + animation: fadeIn 0.2s ease both; +} +.modal-overlay.closing { + animation: fadeOut 0.2s ease both; +} +.modal-sheet { + animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.modal-overlay.closing .modal-sheet { + animation: slideDown 0.25s ease-in both; +} + +/* -- Tab content transition -- */ +.tab-content { + animation: fadeIn 0.25s ease both; +} +.tab-content.switching { + animation: fadeIn 0.2s ease both; +} + +/* -- Staggered list fade-in -- */ +.search-result-item, .food-item, .meal-card, .dagboek-item, .maaltijd-item { + animation: fadeIn 0.3s ease both; + animation-delay: calc(var(--i, 0) * 50ms); +} + +/* -- Toast notification -- */ +.toast { + animation: slideInRight 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.toast.hiding { + animation: slideOutRight 0.3s ease-in both; +} + +/* -- Stepper -- */ +.portie-stepper.active { + transition: background 0.2s ease, border-color 0.2s ease; +} +.portie-stepper-plus.tapped, .portie-stepper-minus.tapped { + animation: pulse 0.3s ease; +} + +/* -- Chip / Pill select -- */ +.chip, .categorie-chip, .tab-chip { + transition: transform 0.15s ease, background 0.2s ease, color 0.2s ease; +} +.chip:active, .categorie-chip:active, .tab-chip:active { + transform: scale(0.93); +} + +/* -- Diary entry add animation -- */ +.eetmoment-entry { + animation: fadeIn 0.3s ease both, slideUp 0.3s ease both; +} + +/* -- Page transitions between main views -- */ +.page-slide-right { animation: slideInRight 0.3s ease both; } +.page-fade-in { animation: fadeIn 0.3s ease both; } + +/* -- Loading skeleton shimmer -- */ +.skeleton { + background: linear-gradient(90deg, var(--border) 25%, #e8e8e8 50%, var(--border) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} @@ -2608,10 +2699,10 @@ body { emptyEl.classList.remove('hidden'); } else { emptyEl.classList.add('hidden'); - listEl.innerHTML = items.map(item => { + listEl.innerHTML = items.map((item, idx) => { const kh = parseKh(item.kh); return ` -
  • +
  • ${escapeHtml(displayNaam(item.naam))}
    ${escapeHtml(item.cat)}
    @@ -3443,9 +3534,18 @@ body { function switchTab(tab) { activeTab = tab; - document.getElementById('tabZoeken').classList.toggle('hidden', tab !== 'zoeken'); - document.getElementById('tabDagboek').classList.toggle('hidden', tab !== 'dagboek'); - document.getElementById('tabMaaltijden').classList.toggle('hidden', tab !== 'maaltijden'); + const panels = ['tabZoeken','tabDagboek','tabMaaltijden']; + panels.forEach(id => { + const el = document.getElementById(id); + if (id === 'tab' + tab.charAt(0).toUpperCase() + tab.slice(1)) { + el.classList.add('switching'); + el.classList.remove('hidden'); + } else { + el.classList.add('hidden'); + el.classList.remove('switching'); + } + }); + document.getElementById('searchContainer').classList.toggle('hidden', tab !== 'zoeken'); document.getElementById('categoryFilter').classList.toggle('hidden', tab !== 'zoeken'); @@ -4025,8 +4125,14 @@ body { // ===== Modal Helpers ===== function closeModal(modalId) { - document.getElementById(modalId).classList.add('hidden'); - document.body.style.overflow = ''; + const el = document.getElementById(modalId); + if (!el) return; + el.classList.add('closing'); + setTimeout(() => { + el.classList.add('hidden'); + el.classList.remove('closing'); + document.body.style.overflow = ''; + }, 250); } function showToast(message) { @@ -4035,7 +4141,10 @@ body { toast.className = 'toast'; toast.textContent = message; container.appendChild(toast); - setTimeout(() => toast.remove(), 2000); + setTimeout(() => { + toast.classList.add('hiding'); + setTimeout(() => toast.remove(), 300); + }, 2000); } function showNameInputModal(title, defaultValue, onSave) { diff --git a/index.html b/index.html index 1444e3f..90af93a 100644 --- a/index.html +++ b/index.html @@ -1920,6 +1920,97 @@ body { grid-template-columns: repeat(2, 1fr); } } + +/* ===== Animation System ===== */ + +/* -- Keyframes -- */ +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } +@keyframes slideUp { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } } +@keyframes slideDown { from { transform: translateY(0); opacity: 1; } to { transform: translateY(100%); opacity: 0; } } +@keyframes slideInRight { from { transform: translateX(120%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } +@keyframes slideOutRight{ from { transform: translateX(0); opacity: 1; } to { transform: translateX(120%); opacity: 0; } } +@keyframes scaleIn { from { transform: scale(0.85); opacity: 0; } to { transform: scale(1); opacity: 1; } } +@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.12); } } +@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } + +/* -- Button press -- */ +button, .btn, .meal-card-main, .dagboek-item, .food-item, +.standaard-portie-btn, .portie-stepper-plus, .portie-stepper-minus { + transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease; +} +button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-item:active, +.portie-stepper-plus:active, .portie-stepper-minus:active { + transform: scale(0.96); +} + +/* -- Modal / Bottom Sheet -- */ +.modal-overlay { + animation: fadeIn 0.2s ease both; +} +.modal-overlay.closing { + animation: fadeOut 0.2s ease both; +} +.modal-sheet { + animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.modal-overlay.closing .modal-sheet { + animation: slideDown 0.25s ease-in both; +} + +/* -- Tab content transition -- */ +.tab-content { + animation: fadeIn 0.25s ease both; +} +.tab-content.switching { + animation: fadeIn 0.2s ease both; +} + +/* -- Staggered list fade-in -- */ +.search-result-item, .food-item, .meal-card, .dagboek-item, .maaltijd-item { + animation: fadeIn 0.3s ease both; + animation-delay: calc(var(--i, 0) * 50ms); +} + +/* -- Toast notification -- */ +.toast { + animation: slideInRight 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.toast.hiding { + animation: slideOutRight 0.3s ease-in both; +} + +/* -- Stepper -- */ +.portie-stepper.active { + transition: background 0.2s ease, border-color 0.2s ease; +} +.portie-stepper-plus.tapped, .portie-stepper-minus.tapped { + animation: pulse 0.3s ease; +} + +/* -- Chip / Pill select -- */ +.chip, .categorie-chip, .tab-chip { + transition: transform 0.15s ease, background 0.2s ease, color 0.2s ease; +} +.chip:active, .categorie-chip:active, .tab-chip:active { + transform: scale(0.93); +} + +/* -- Diary entry add animation -- */ +.eetmoment-entry { + animation: fadeIn 0.3s ease both, slideUp 0.3s ease both; +} + +/* -- Page transitions between main views -- */ +.page-slide-right { animation: slideInRight 0.3s ease both; } +.page-fade-in { animation: fadeIn 0.3s ease both; } + +/* -- Loading skeleton shimmer -- */ +.skeleton { + background: linear-gradient(90deg, var(--border) 25%, #e8e8e8 50%, var(--border) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} @@ -2608,10 +2699,10 @@ body { emptyEl.classList.remove('hidden'); } else { emptyEl.classList.add('hidden'); - listEl.innerHTML = items.map(item => { + listEl.innerHTML = items.map((item, idx) => { const kh = parseKh(item.kh); return ` -
  • +
  • ${escapeHtml(displayNaam(item.naam))}
    ${escapeHtml(item.cat)}
    @@ -3443,9 +3534,18 @@ body { function switchTab(tab) { activeTab = tab; - document.getElementById('tabZoeken').classList.toggle('hidden', tab !== 'zoeken'); - document.getElementById('tabDagboek').classList.toggle('hidden', tab !== 'dagboek'); - document.getElementById('tabMaaltijden').classList.toggle('hidden', tab !== 'maaltijden'); + const panels = ['tabZoeken','tabDagboek','tabMaaltijden']; + panels.forEach(id => { + const el = document.getElementById(id); + if (id === 'tab' + tab.charAt(0).toUpperCase() + tab.slice(1)) { + el.classList.add('switching'); + el.classList.remove('hidden'); + } else { + el.classList.add('hidden'); + el.classList.remove('switching'); + } + }); + document.getElementById('searchContainer').classList.toggle('hidden', tab !== 'zoeken'); document.getElementById('categoryFilter').classList.toggle('hidden', tab !== 'zoeken'); @@ -4025,8 +4125,14 @@ body { // ===== Modal Helpers ===== function closeModal(modalId) { - document.getElementById(modalId).classList.add('hidden'); - document.body.style.overflow = ''; + const el = document.getElementById(modalId); + if (!el) return; + el.classList.add('closing'); + setTimeout(() => { + el.classList.add('hidden'); + el.classList.remove('closing'); + document.body.style.overflow = ''; + }, 250); } function showToast(message) { @@ -4035,7 +4141,10 @@ body { toast.className = 'toast'; toast.textContent = message; container.appendChild(toast); - setTimeout(() => toast.remove(), 2000); + setTimeout(() => { + toast.classList.add('hiding'); + setTimeout(() => toast.remove(), 300); + }, 2000); } function showNameInputModal(title, defaultValue, onSave) {