From efe1cd941c74e5a5f5196300fc44264c5b16b92c Mon Sep 17 00:00:00 2001 From: ulfrxdev Date: Mon, 30 Mar 2026 21:41:07 +0200 Subject: [PATCH] Add dark mode --- index.html | 141 ++++++++++++++++++++- js/app.js | 40 ++++-- js/views/Shopping.js | 290 ------------------------------------------- 3 files changed, 167 insertions(+), 304 deletions(-) delete mode 100644 js/views/Shopping.js diff --git a/index.html b/index.html index 1961772..11c9109 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + @@ -22,6 +22,8 @@ input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 4px; cursor: pointer; background: #e5e7eb; border-radius: 2px; } + .dark input[type=range]::-webkit-slider-thumb { background: #e8e8e8; } + .dark input[type=range]::-webkit-slider-runnable-track { background: #333333; } /* Ingredient Active States */ .ingredient-active .check-box, @@ -30,14 +32,151 @@ .ingredient-active .rd-check-icon { display: block; } .ingredient-active .ingredient-text, .ingredient-active .rd-ing-text { text-decoration: line-through; color: #9ca3af; } + .dark .ingredient-active .check-box, + .dark .ingredient-active .rd-check-box { background-color: #f0f0f0; border-color: #f0f0f0; } /* Utilities */ .no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border-width: 0; } + + /* ===== Dark Mode Overrides ===== */ + .dark { color-scheme: dark; } + + /* --- Gray backgrounds --- */ + .dark .bg-white { background-color: #1a1a1a !important; } + .dark .bg-gray-50 { background-color: #0d0d0d !important; } + .dark .bg-gray-100 { background-color: #242424 !important; } + .dark .bg-gray-200 { background-color: #2e2e2e !important; } + .dark .bg-gray-900 { background-color: #f0f0f0 !important; } + .dark .bg-gray-900.text-white, + .dark .bg-gray-900 .text-white { color: #0d0d0d !important; } + .dark .border-gray-900 { border-color: #f0f0f0 !important; } + + /* Semi-transparent backgrounds */ + .dark .bg-white\/90 { background-color: rgba(26, 26, 26, 0.92) !important; } + .dark .bg-white\/80 { background-color: rgba(26, 26, 26, 0.85) !important; } + .dark .bg-gray-50\/80 { background-color: rgba(20, 20, 20, 0.85) !important; } + .dark .bg-gray-50\/90 { background-color: rgba(20, 20, 20, 0.92) !important; } + + /* --- Gray text --- */ + .dark .text-gray-900 { color: #f5f5f5 !important; } + .dark .text-gray-800 { color: #e8e8e8 !important; } + .dark .text-gray-700 { color: #d4d4d4 !important; } + .dark .text-gray-600 { color: #a0a0a0 !important; } + .dark .text-gray-500 { color: #888888 !important; } + .dark .text-gray-400 { color: #666666 !important; } + .dark .text-gray-300 { color: #444444 !important; } + .dark .text-black { color: #ffffff !important; } + + /* --- Gray borders --- */ + .dark .border-gray-200 { border-color: #2a2a2a !important; } + .dark .border-gray-100 { border-color: #1f1f1f !important; } + .dark .border-gray-300 { border-color: #333333 !important; } + + /* --- Dividers --- */ + .dark .divide-gray-50 > :not([hidden]) ~ :not([hidden]) { border-color: #1a1a1a !important; } + .dark .divide-gray-100 > :not([hidden]) ~ :not([hidden]) { border-color: #1f1f1f !important; } + .dark .divide-gray-200 > :not([hidden]) ~ :not([hidden]) { border-color: #2a2a2a !important; } + .dark .divide-red-50 > :not([hidden]) ~ :not([hidden]) { border-color: #2a1a1a !important; } + + /* --- Ring utilities --- */ + .dark .ring-gray-200 { --tw-ring-color: #2a2a2a !important; } + .dark .ring-gray-900 { --tw-ring-color: #f0f0f0 !important; } + + /* --- Hover states --- */ + .dark .hover\:bg-gray-50:hover { background-color: #1f1f1f !important; } + .dark .hover\:bg-gray-100:hover { background-color: #2e2e2e !important; } + .dark .hover\:bg-white:hover { background-color: #242424 !important; } + .dark .hover\:text-gray-700:hover { color: #e8e8e8 !important; } + .dark .hover\:text-gray-900:hover { color: #f5f5f5 !important; } + .dark .hover\:text-black:hover { color: #ffffff !important; } + .dark .hover\:bg-black:hover { background-color: #e0e0e0 !important; color: #0d0d0d !important; } + .dark .hover\:bg-red-50:hover { background-color: #2a1a1a !important; } + .dark .hover\:border-gray-300:hover { border-color: #444444 !important; } + .dark .hover\:border-gray-400:hover { border-color: #555555 !important; } + .dark .hover\:border-gray-900:hover { border-color: #f0f0f0 !important; } + .dark .hover\:border-red-200:hover { border-color: #5c2020 !important; } + + /* --- Focus --- */ + .dark .focus\:border-gray-400:focus { border-color: #555555 !important; } + .dark .focus\:border-gray-300:focus { border-color: #444444 !important; } + + /* --- Shadows --- */ + .dark .shadow-lg { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -4px rgba(0,0,0,0.4) !important; } + .dark .shadow-sm { box-shadow: 0 1px 3px 0 rgba(0,0,0,0.4) !important; } + .dark .border-dashed { border-color: #333333 !important; } + + /* --- Inputs --- */ + .dark input, .dark select, .dark textarea { + background-color: #1a1a1a !important; + color: #e8e8e8 !important; + border-color: #2a2a2a !important; + } + .dark input::placeholder, .dark textarea::placeholder { + color: #666666 !important; + } + + /* --- Toast --- */ + .dark #app-toast .rounded-xl, + .dark #planner-toast .rounded-xl { + background-color: #f0f0f0 !important; + color: #0d0d0d !important; + } + + /* --- Amber / Summary card --- */ + .dark #planner-summary-card { + background: #1e1a10 !important; + border-color: #4a3800 !important; + } + .dark .bg-amber-50 { background-color: #1e1a10 !important; } + .dark .bg-amber-50\/30 { background-color: rgba(60, 45, 0, 0.25) !important; } + .dark .bg-amber-50\/50 { background-color: rgba(80, 60, 0, 0.2) !important; } + .dark .bg-amber-100\/50 { background-color: rgba(80, 60, 0, 0.2) !important; } + .dark .hover\:bg-amber-100\/50:hover { background-color: rgba(80, 60, 0, 0.35) !important; } + .dark .border-amber-200 { border-color: #4a3800 !important; } + .dark .border-amber-200\/80 { border-color: #4a3800 !important; } + .dark .border-amber-200\/60 { border-color: #3d2e00 !important; } + .dark .border-amber-100 { border-color: #332600 !important; } + .dark .text-amber-900\/70 { color: #fbbf24 !important; } + .dark .text-amber-900\/80 { color: #fbbf24 !important; } + .dark .text-amber-900 { color: #fcd34d !important; } + .dark .text-amber-600 { color: #f59e0b !important; } + .dark .text-amber-500 { color: #f59e0b !important; } + .dark .divide-amber-100\/80 > :not([hidden]) ~ :not([hidden]) { border-color: #332600 !important; } + + /* --- Emerald accents --- */ + .dark .bg-emerald-50 { background-color: #0a1f15 !important; } + .dark .bg-emerald-100 { background-color: #064e3b !important; } + .dark .bg-emerald-500 { background-color: #10b981 !important; } + .dark .bg-emerald-600 { background-color: #059669 !important; } + .dark .hover\:bg-emerald-700:hover { background-color: #047857 !important; } + .dark .hover\:bg-emerald-100\/80:hover { background-color: rgba(6, 78, 59, 0.5) !important; } + .dark .border-emerald-200\/80 { border-color: #065f46 !important; } + .dark .text-emerald-800 { color: #6ee7b7 !important; } + .dark .text-emerald-600 { color: #34d399 !important; } + .dark .text-emerald-600\/80 { color: rgba(52, 211, 153, 0.85) !important; } + .dark .text-emerald-500 { color: #34d399 !important; } + + /* --- Red accents --- */ + .dark .bg-red-50 { background-color: #1f0a0a !important; } + .dark .bg-red-100 { background-color: #450a0a !important; } + .dark .bg-red-500 { background-color: #ef4444 !important; } + .dark .border-red-200\/80 { border-color: #5c2020 !important; } + .dark .border-red-100\/80 { border-color: #3b1010 !important; } + .dark .text-red-800 { color: #fca5a5 !important; } + .dark .text-red-600 { color: #f87171 !important; } + .dark .text-red-600\/80 { color: rgba(248, 113, 113, 0.85) !important; } + .dark .text-red-500 { color: #f87171 !important; } + .dark .text-red-400 { color: #fb7185 !important; } + .dark .hover\:text-red-600:hover { color: #f87171 !important; } + .dark .bg-amber-500 { background-color: #f59e0b !important; } +
diff --git a/js/app.js b/js/app.js index 71e2cca..f7f23f6 100644 --- a/js/app.js +++ b/js/app.js @@ -3,7 +3,6 @@ import { getFilterHTML, setupFilter } from './views/Filter.js?v=2'; import { getRecipeDetailHTML, setupRecipeDetail } from './views/RecipeDetailV2.js?v=2'; import { getMealPlannerHTML, setupMealPlanner } from './views/MealPlanner.js?v=2'; import { getPantryHTML, refreshPantry, setupPantry } from './views/Pantry.js?v=2'; -import { getShoppingHTML, refreshShopping, setupShopping } from './views/Shopping.js?v=2'; function getAppToastHTML() { return ` @@ -14,6 +13,7 @@ function getAppToastHTML() { } function getBottomNavHTML() { + const isDark = document.documentElement.classList.contains('dark'); return ` `; } +function setupThemeToggle() { + const btn = document.getElementById('nav-theme-toggle'); + if (!btn) return; + + btn.addEventListener('click', () => { + const html = document.documentElement; + const isDark = html.classList.toggle('dark'); + localStorage.setItem('theme', isDark ? 'dark' : 'light'); + + const icon = btn.querySelector('i'); + const label = btn.querySelector('span'); + if (icon) icon.className = isDark ? 'fas fa-sun text-base' : 'fas fa-moon text-base'; + if (label) label.textContent = isDark ? 'Jasny' : 'Ciemny'; + + const meta = document.querySelector('meta[name="theme-color"]'); + if (meta) meta.setAttribute('content', isDark ? '#0d0d0d' : '#ffffff'); + }); +} + function setupTabs() { const main = document.getElementById('main-view'); const planner = document.getElementById('planner-view'); const pantry = document.getElementById('pantry-view'); - const shopping = document.getElementById('shopping-view'); const nav = document.getElementById('app-bottom-nav'); - if (!main || !planner || !pantry || !shopping || !nav) return; + if (!main || !planner || !pantry || !nav) return; const activeTab = 'nav-tab flex flex-col items-center gap-0.5 text-black flex-1 min-w-0 max-w-[5.5rem]'; const idleTab = 'nav-tab flex flex-col items-center gap-0.5 text-gray-500 hover:text-gray-700 flex-1 min-w-0 max-w-[5.5rem]'; @@ -51,15 +69,13 @@ function setupTabs() { main.classList.toggle('hidden', tab !== 'recipes'); planner.classList.toggle('hidden', tab !== 'planner'); pantry.classList.toggle('hidden', tab !== 'pantry'); - shopping.classList.toggle('hidden', tab !== 'shopping'); if (tab === 'pantry') refreshPantry(); - if (tab === 'shopping') refreshShopping(); nav.querySelectorAll('.nav-tab[data-tab]').forEach((btn) => { const id = btn.getAttribute('data-tab'); if (btn.hasAttribute('disabled')) return; - if (id === 'recipes' || id === 'planner' || id === 'pantry' || id === 'shopping') { + if (id === 'recipes' || id === 'planner' || id === 'pantry') { btn.className = id === tab ? activeTab : idleTab; } }); @@ -69,14 +85,13 @@ function setupTabs() { const btn = e.target.closest('.nav-tab[data-tab]'); if (!btn || btn.hasAttribute('disabled')) return; const tab = btn.getAttribute('data-tab'); - if (tab === 'recipes' || tab === 'planner' || tab === 'pantry' || tab === 'shopping') apply(tab); + if (tab === 'recipes' || tab === 'planner' || tab === 'pantry') apply(tab); }); apply('recipes'); window.refreshStockViews = () => { refreshPantry(); - refreshShopping(); }; } @@ -87,7 +102,6 @@ document.addEventListener('DOMContentLoaded', () => { ${getRecipeListHTML()} ${getMealPlannerHTML()} ${getPantryHTML()} - ${getShoppingHTML()} ${getBottomNavHTML()} ${getRecipeDetailHTML()} ${getFilterHTML()} @@ -95,10 +109,10 @@ document.addEventListener('DOMContentLoaded', () => { `; setupTabs(); + setupThemeToggle(); setupRecipeList(); setupMealPlanner(); setupPantry(); - setupShopping(); setupFilter(); setupRecipeDetail(); }); diff --git a/js/views/Shopping.js b/js/views/Shopping.js deleted file mode 100644 index 17e1ed4..0000000 --- a/js/views/Shopping.js +++ /dev/null @@ -1,290 +0,0 @@ -import { - addFreeformLine, - addFreeformList, - applyCheckedKitchenListToPantry, - categoryLabel, - clearCheckedInList, - deleteList, - getActiveList, - getListSummaries, - KITCHEN_LIST_ID, - removeItemFromList, - setActiveListId, - toggleItemInList, - updateKitchenItemAmount, -} from '../services/pantryShopping.js'; -import { showAppToast } from '../ui/toast.js'; - -function escapeHtml(s) { - return String(s) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); -} - -export function getShoppingHTML() { - return ` - - `; -} - -function syncListSelect() { - const sel = document.getElementById('shopping-list-select'); - if (!sel) return; - const { lists, activeListId } = getListSummaries(); - sel.innerHTML = lists.map((l) => { - const suffix = l.openCount ? ` (${l.openCount})` : ''; - const label = `${l.name}${suffix}`; - return ``; - }).join(''); - sel.value = activeListId; -} - -function syncChromeForList() { - const list = getActiveList(); - const isKitchen = list.type === 'kitchen'; - const delBtn = document.getElementById('shopping-delete-list'); - const ffAdd = document.getElementById('shopping-freeform-add'); - const kitchenActions = document.getElementById('shopping-kitchen-actions'); - - if (ffAdd) ffAdd.classList.toggle('hidden', isKitchen); - if (delBtn) delBtn.classList.toggle('hidden', isKitchen); - - if (kitchenActions) { - const hasChecked = isKitchen && list.items.some((i) => i.checked); - kitchenActions.classList.toggle('hidden', !hasChecked); - } -} - -function renderKitchenItems() { - const root = document.getElementById('shopping-list-root'); - if (!root) return; - - const list = getActiveList(); - if (list.type !== 'kitchen') return; - - const items = list.items; - if (items.length === 0) { - root.innerHTML = '

Brak pozycji.

'; - return; - } - - const groups = {}; - for (const it of items) { - const c = it.category || 'inne'; - if (!groups[c]) groups[c] = []; - groups[c].push(it); - } - - root.innerHTML = Object.keys(groups) - .sort((a, b) => categoryLabel(a).localeCompare(categoryLabel(b))) - .map((cat) => ` -
-

${escapeHtml(categoryLabel(cat))}

- -
- `) - .join(''); - - bindItemButtons(list.id); -} - -function renderFreeformItems() { - const root = document.getElementById('shopping-list-root'); - if (!root) return; - - const list = getActiveList(); - if (list.type !== 'freeform') return; - - const items = list.items; - if (items.length === 0) { - root.innerHTML = '

Dodaj dowolny tekst powyżej — bez powiązania z katalogiem składników.

'; - return; - } - - root.innerHTML = ` -
- -
`; - - bindItemButtons(list.id); -} - -function bindItemButtons(listId) { - const root = document.getElementById('shopping-list-root'); - if (!root) return; - - root.querySelectorAll('[data-shop-toggle]').forEach((btn) => { - btn.addEventListener('click', () => { - const id = btn.getAttribute('data-shop-toggle'); - if (id) toggleItemInList(listId, id); - refreshShopping(); - }); - }); - root.querySelectorAll('[data-shop-remove]').forEach((btn) => { - btn.addEventListener('click', () => { - const id = btn.getAttribute('data-shop-remove'); - if (id) removeItemFromList(listId, id); - refreshShopping(); - }); - }); - root.querySelectorAll('[data-shop-edit-amount]').forEach((btn) => { - btn.addEventListener('click', (e) => { - e.stopPropagation(); - const itemId = btn.getAttribute('data-shop-edit-amount'); - const current = btn.getAttribute('data-current-amount'); - const unit = btn.getAttribute('data-unit'); - const newVal = window.prompt(`Nowa ilość (${unit}):`, current); - if (newVal === null) return; - const num = parseFloat(newVal.replace(',', '.')); - if (!Number.isFinite(num) || num < 0) { - showAppToast('Nieprawidłowa wartość.'); - return; - } - updateKitchenItemAmount(listId, itemId, num); - refreshShopping(); - }); - }); -} - -export function refreshShopping() { - syncListSelect(); - syncChromeForList(); - const list = getActiveList(); - if (list.type === 'kitchen') renderKitchenItems(); - else renderFreeformItems(); -} - -export function setupShopping() { - const sel = document.getElementById('shopping-list-select'); - sel?.addEventListener('change', () => { - const v = sel.value; - if (v) setActiveListId(v); - refreshShopping(); - }); - - document.getElementById('shopping-new-list')?.addEventListener('click', () => { - const name = window.prompt('Nazwa nowej listy (dowolne zakupy):', 'Nowa lista'); - if (name === null) return; - addFreeformList(name); - showAppToast('Utworzono listę.'); - refreshShopping(); - }); - - document.getElementById('shopping-delete-list')?.addEventListener('click', () => { - const list = getActiveList(); - if (list.id === KITCHEN_LIST_ID) return; - if (!window.confirm(`Usunąć listę „${list.name}”?`)) return; - deleteList(list.id); - showAppToast('Lista usunięta.'); - refreshShopping(); - }); - - const submitFreeform = () => { - const list = getActiveList(); - if (list.type !== 'freeform') return; - const input = document.getElementById('shopping-freeform-input'); - const note = document.getElementById('shopping-freeform-note'); - const text = input?.value || ''; - addFreeformLine(list.id, text, note?.value || ''); - if (input) input.value = ''; - if (note) note.value = ''; - refreshShopping(); - }; - - document.getElementById('shopping-freeform-submit')?.addEventListener('click', submitFreeform); - document.getElementById('shopping-freeform-input')?.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - submitFreeform(); - } - }); - - document.getElementById('shopping-to-pantry')?.addEventListener('click', () => { - const list = getActiveList(); - if (list.type !== 'kitchen') return; - const checked = list.items.filter((i) => i.checked); - if (checked.length === 0) return; - const preview = checked.map((i) => ` • ${i.name}: ${i.amount} ${i.unit}`).join('\n'); - if (!window.confirm(`Przenieść do spiżarni?\n\n${preview}`)) return; - applyCheckedKitchenListToPantry(); - showAppToast(`Przeniesiono ${checked.length} pozycji do spiżarni.`); - window.refreshPantry?.(); - refreshShopping(); - }); - - document.getElementById('shopping-clear-checked')?.addEventListener('click', () => { - const list = getActiveList(); - const checkedCount = list.items.filter((i) => i.checked).length; - if (checkedCount === 0) return; - clearCheckedInList(list.id); - showAppToast(`Usunięto ${checkedCount} kupionych pozycji.`); - refreshShopping(); - }); - - refreshShopping(); - window.refreshShopping = refreshShopping; -}