From a872ac52a98df4a4007b59ee5d8865236ea64c0b Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 19:03:04 +0200 Subject: [PATCH 01/10] chore: release v0.33.1 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 7 ++++ package-lock.json | 4 +-- package.json | 2 +- public/router.js | 77 +++++++++++----------------------------- public/styles/layout.css | 46 +++++++++++++++++++++--- 5 files changed, 73 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41583e8..4ae24a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.33.1] - 2026-04-29 + +### Changed +- Navigation: removed the dedicated Search button from the bottom bar; the bottom bar now shows three primary module links plus the More button +- Navigation: the More sheet now opens with a full-width pill-shaped search trigger at the top, replacing the grid-cell search item +- Search: the search overlay input field is now positioned at the bottom of the screen (thumb zone) instead of the top + ## [0.33.0] - 2026-04-29 ### Added diff --git a/package-lock.json b/package-lock.json index 59cdcfd..e648175 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oikos", - "version": "0.33.0", + "version": "0.33.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oikos", - "version": "0.33.0", + "version": "0.33.1", "license": "MIT", "dependencies": { "bcrypt": "^6.0.0", diff --git a/package.json b/package.json index ac8c542..1f39977 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.33.0", + "version": "0.33.1", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/router.js b/public/router.js index 2e2b9a9..b2d17ef 100644 --- a/public/router.js +++ b/public/router.js @@ -478,20 +478,6 @@ function renderAppShell(container) { const bottomItems = document.createElement('div'); bottomItems.className = 'nav-bottom__items'; navItems().slice(0, PRIMARY_NAV).forEach((item) => bottomItems.appendChild(navItemEl(item))); - const searchNavBtn = document.createElement('button'); - searchNavBtn.className = 'nav-item nav-item--search'; - searchNavBtn.id = 'search-nav-btn'; - searchNavBtn.setAttribute('aria-label', t('search.title')); - const searchNavIcon = document.createElement('i'); - searchNavIcon.dataset.lucide = 'search'; - searchNavIcon.className = 'nav-item__icon'; - searchNavIcon.setAttribute('aria-hidden', 'true'); - const searchNavLabel = document.createElement('span'); - searchNavLabel.className = 'nav-item__label'; - searchNavLabel.textContent = t('search.title'); - searchNavBtn.appendChild(searchNavIcon); - searchNavBtn.appendChild(searchNavLabel); - bottomItems.appendChild(searchNavBtn); const moreBtn = document.createElement('button'); moreBtn.className = 'nav-item nav-item--more'; moreBtn.id = 'more-btn'; @@ -520,19 +506,20 @@ function renderAppShell(container) { moreSheet.setAttribute('role', 'dialog'); moreSheet.setAttribute('aria-label', t('nav.more')); moreSheet.setAttribute('aria-hidden', 'true'); - const searchBtn = document.createElement('button'); - searchBtn.className = 'more-item'; - searchBtn.id = 'search-btn'; - const searchIcon = document.createElement('i'); - searchIcon.dataset.lucide = 'search'; - searchIcon.className = 'more-item__icon'; - searchIcon.setAttribute('aria-hidden', 'true'); - const searchLabel = document.createElement('span'); - searchLabel.className = 'more-item__label'; - searchLabel.textContent = t('search.title'); - searchBtn.appendChild(searchIcon); - searchBtn.appendChild(searchLabel); - moreSheet.appendChild(searchBtn); + const searchTrigger = document.createElement('button'); + searchTrigger.className = 'more-sheet__search-trigger'; + searchTrigger.id = 'search-btn'; + searchTrigger.setAttribute('aria-label', t('search.title')); + const searchTriggerIcon = document.createElement('i'); + searchTriggerIcon.dataset.lucide = 'search'; + searchTriggerIcon.className = 'more-sheet__search-trigger-icon'; + searchTriggerIcon.setAttribute('aria-hidden', 'true'); + const searchTriggerText = document.createElement('span'); + searchTriggerText.className = 'more-sheet__search-trigger-placeholder'; + searchTriggerText.textContent = t('search.placeholder'); + searchTrigger.appendChild(searchTriggerIcon); + searchTrigger.appendChild(searchTriggerText); + moreSheet.appendChild(searchTrigger); navItems().slice(PRIMARY_NAV).forEach((item) => moreSheet.appendChild(moreItemEl(item))); const searchOverlay = document.createElement('div'); @@ -763,9 +750,8 @@ function initMoreSheet(container) { * Initialisiert die Suchfunktion (Overlay + API-Calls). */ function initSearch(container) { - const searchBtn = container.querySelector('#search-btn'); - const searchNavBtn = container.querySelector('#search-nav-btn'); - const searchClose = container.querySelector('#search-close'); + const searchBtn = container.querySelector('#search-btn'); + const searchClose = container.querySelector('#search-close'); const overlay = container.querySelector('#search-overlay'); const input = container.querySelector('#search-input'); const results = container.querySelector('#search-results'); @@ -812,7 +798,6 @@ function initSearch(container) { } searchBtn.addEventListener('click', openSearch); - if (searchNavBtn) searchNavBtn.addEventListener('click', openSearch); searchClose.addEventListener('click', closeSearch); document.addEventListener('keydown', (e) => { @@ -1137,34 +1122,14 @@ window.addEventListener('locale-changed', () => { if (bottomItems) { const moreBtn = bottomItems.querySelector('#more-btn'); const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl); - // Such-Button neu erstellen (wird durch replaceChildren entfernt) - const newSearchBtn = document.createElement('button'); - newSearchBtn.className = 'nav-item nav-item--search'; - newSearchBtn.id = 'search-nav-btn'; - newSearchBtn.setAttribute('aria-label', t('search.title')); - const newSearchIcon = document.createElement('i'); - newSearchIcon.dataset.lucide = 'search'; - newSearchIcon.className = 'nav-item__icon'; - newSearchIcon.setAttribute('aria-hidden', 'true'); - const newSearchLbl = document.createElement('span'); - newSearchLbl.className = 'nav-item__label'; - newSearchLbl.textContent = t('search.title'); - newSearchBtn.appendChild(newSearchIcon); - newSearchBtn.appendChild(newSearchLbl); - bottomItems.replaceChildren(...newItems, newSearchBtn, moreBtn); - // Event-Listener auf neuen Such-Button - if (newSearchBtn) { - newSearchBtn.addEventListener('click', () => { - if (window._openSearch) window._openSearch(); - }); - } + bottomItems.replaceChildren(...newItems, moreBtn); } if (moreSheet) { - const searchBtn = moreSheet.querySelector('#search-btn'); - const searchLbl = searchBtn?.querySelector('.more-item__label'); - if (searchLbl) searchLbl.textContent = t('search.title'); + const searchTrig = moreSheet.querySelector('#search-btn'); + const searchTrigPlaceholder = searchTrig?.querySelector('.more-sheet__search-trigger-placeholder'); + if (searchTrigPlaceholder) searchTrigPlaceholder.textContent = t('search.placeholder'); const newMoreItems = navItems().slice(PRIMARY_NAV).map(moreItemEl); - moreSheet.replaceChildren(searchBtn, ...newMoreItems); + moreSheet.replaceChildren(searchTrig, ...newMoreItems); } document.querySelectorAll('[data-route]').forEach((el) => { diff --git a/public/styles/layout.css b/public/styles/layout.css index 79f43ae..5fa3072 100755 --- a/public/styles/layout.css +++ b/public/styles/layout.css @@ -268,6 +268,43 @@ line-height: 1.2; } +/* ── More-Sheet Suchtrigger ── */ +.more-sheet__search-trigger { + grid-column: 1 / -1; + display: flex; + align-items: center; + gap: var(--space-2); + padding: 0 var(--space-4); + height: var(--target-lg); + border-radius: var(--radius-full); + border: 1.5px solid var(--color-border); + background-color: var(--color-surface-elevated); + color: var(--color-text-tertiary); + cursor: pointer; + font-family: inherit; + font-size: var(--text-sm); + text-align: left; + width: 100%; + -webkit-tap-highlight-color: transparent; + transition: background-color var(--transition-fast), border-color var(--transition-fast); +} + +.more-sheet__search-trigger:active { + background-color: var(--color-surface-hover); + border-color: var(--color-accent); + transform: scale(0.98); +} + +.more-sheet__search-trigger-icon { + width: var(--space-4); + height: var(--space-4); + flex-shrink: 0; +} + +.more-sheet__search-trigger-placeholder { + flex: 1; +} + /* ── Such-Overlay ── */ .search-overlay { position: fixed; @@ -275,7 +312,7 @@ background-color: var(--color-surface); z-index: calc(var(--z-nav) + 3); display: flex; - flex-direction: column; + flex-direction: column-reverse; transform: translateY(100%); transition: transform 0.25s var(--ease-out); } @@ -288,8 +325,8 @@ display: flex; align-items: center; gap: var(--space-3); - padding: calc(var(--space-4) + var(--safe-area-inset-top)) var(--space-4) var(--space-3); - border-bottom: 1px solid var(--color-border-subtle); + padding: var(--space-3) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom)); + border-top: 1px solid var(--color-border-subtle); } .search-overlay__input { @@ -331,7 +368,8 @@ .search-overlay__results { flex: 1; overflow-y: auto; - padding: var(--space-4); + padding: var(--space-4) var(--space-4) var(--space-2); + padding-top: calc(var(--space-4) + var(--safe-area-inset-top)); } .search-overlay__empty { From b59c63bbb7c524813ccd0ba58c47bb0925396eb1 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 19:54:07 +0200 Subject: [PATCH 02/10] feat: add nav.kitchen, nav.search and shortcuts.goKitchen i18n keys --- public/locales/ar.json | 7 ++++++- public/locales/de.json | 7 +++++-- public/locales/el.json | 7 ++++++- public/locales/en.json | 7 +++++-- public/locales/es.json | 7 ++++++- public/locales/fr.json | 7 ++++++- public/locales/hi.json | 7 ++++++- public/locales/it.json | 7 ++++++- public/locales/ja.json | 7 ++++++- public/locales/pt.json | 7 ++++++- public/locales/ru.json | 7 ++++++- public/locales/sv.json | 7 ++++++- public/locales/tr.json | 7 ++++++- public/locales/uk.json | 7 ++++++- public/locales/zh.json | 7 ++++++- 15 files changed, 88 insertions(+), 17 deletions(-) diff --git a/public/locales/ar.json b/public/locales/ar.json index f342dae..c5da011 100644 --- a/public/locales/ar.json +++ b/public/locales/ar.json @@ -47,7 +47,9 @@ "quickActions": "الإجراءات السريعة", "recipes": "الوصفات", "more": "المزيد", - "documents": "المستندات" + "documents": "المستندات", + "kitchen": "المطبخ", + "search": "بحث" }, "dashboard": { "title": "لوحة التحكم", @@ -969,5 +971,8 @@ "dropzoneTitle": "أفلت الملف هنا أو انقر للاختيار", "dropzoneHint": "اسحب ملفًا إلى هذه المنطقة أو استخدم محدد الملفات.", "selectedFileLabel": "المحدد: {{name}}" + }, + "shortcuts": { + "goKitchen": "المطبخ" } } diff --git a/public/locales/de.json b/public/locales/de.json index bc269bf..eb1e0e9 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -47,7 +47,9 @@ "quickActions": "Schnellaktionen", "more": "Mehr", "recipes": "Rezepte", - "documents": "Dokumente" + "documents": "Dokumente", + "kitchen": "Küche", + "search": "Suche" }, "search": { "title": "Suche", @@ -945,7 +947,8 @@ "goTasks": "Aufgaben", "goCal": "Kalender", "goShop": "Einkaufsliste", - "goNotes": "Notizen" + "goNotes": "Notizen", + "goKitchen": "Küche" }, "documents": { "title": "Dokumente", diff --git a/public/locales/el.json b/public/locales/el.json index b1c39e6..2b6359b 100644 --- a/public/locales/el.json +++ b/public/locales/el.json @@ -47,7 +47,9 @@ "quickActions": "Γρήγορες ενέργειες", "recipes": "Συνταγές", "more": "Περισσότερα", - "documents": "Έγγραφα" + "documents": "Έγγραφα", + "kitchen": "Κουζίνα", + "search": "Αναζήτηση" }, "dashboard": { "title": "Επισκόπηση", @@ -969,5 +971,8 @@ "dropzoneTitle": "Αφήστε το αρχείο εδώ ή κάντε κλικ για επιλογή", "dropzoneHint": "Σύρετε ένα αρχείο σε αυτήν την περιοχή ή χρησιμοποιήστε τον επιλογέα αρχείων.", "selectedFileLabel": "Επιλέχθηκε: {{name}}" + }, + "shortcuts": { + "goKitchen": "Κουζίνα" } } diff --git a/public/locales/en.json b/public/locales/en.json index c9c5d11..efd037f 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -47,7 +47,9 @@ "quickActions": "Quick actions", "recipes": "Recipes", "more": "More", - "documents": "Documents" + "documents": "Documents", + "kitchen": "Kitchen", + "search": "Search" }, "dashboard": { "title": "Overview", @@ -915,7 +917,8 @@ "goTasks": "Tasks", "goCal": "Calendar", "goShop": "Shopping list", - "goNotes": "Notes" + "goNotes": "Notes", + "goKitchen": "Kitchen" }, "emptyHint": { "tasks": "Tap + to create your first task. Swipe a card left to delete.", diff --git a/public/locales/es.json b/public/locales/es.json index 4e6716a..0789341 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -47,7 +47,9 @@ "quickActions": "Acciones rápidas", "recipes": "Recetas", "more": "Más", - "documents": "Documentos" + "documents": "Documentos", + "kitchen": "Cocina", + "search": "Buscar" }, "dashboard": { "title": "Inicio", @@ -969,5 +971,8 @@ "dropzoneTitle": "Suelta el archivo aquí o haz clic para elegir", "dropzoneHint": "Arrastra un archivo a esta área o usa el selector de archivos.", "selectedFileLabel": "Seleccionado: {{name}}" + }, + "shortcuts": { + "goKitchen": "Cocina" } } diff --git a/public/locales/fr.json b/public/locales/fr.json index 82cf675..b9def3a 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -47,7 +47,9 @@ "quickActions": "Actions rapides", "recipes": "Recettes", "more": "Plus", - "documents": "Documents" + "documents": "Documents", + "kitchen": "Cuisine", + "search": "Recherche" }, "dashboard": { "title": "Accueil", @@ -969,5 +971,8 @@ "dropzoneTitle": "Déposez le fichier ici ou cliquez pour choisir", "dropzoneHint": "Glissez un fichier dans cette zone ou utilisez le sélecteur.", "selectedFileLabel": "Sélectionné : {{name}}" + }, + "shortcuts": { + "goKitchen": "Cuisine" } } diff --git a/public/locales/hi.json b/public/locales/hi.json index 0f2c858..9d3a22f 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -47,7 +47,9 @@ "quickActions": "त्वरित क्रियाएं", "recipes": "रेसिपी", "more": "और", - "documents": "दस्तावेज़" + "documents": "दस्तावेज़", + "kitchen": "रसोई", + "search": "खोज" }, "dashboard": { "title": "डैशबोर्ड", @@ -969,5 +971,8 @@ "dropzoneTitle": "फ़ाइल यहाँ छोड़ें या चुनने के लिए क्लिक करें", "dropzoneHint": "फ़ाइल को इस क्षेत्र में खींचें या फ़ाइल पिकर का उपयोग करें।", "selectedFileLabel": "चयनित: {{name}}" + }, + "shortcuts": { + "goKitchen": "रसोई" } } diff --git a/public/locales/it.json b/public/locales/it.json index 9e085c7..2cebc2a 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -47,7 +47,9 @@ "quickActions": "Azioni rapide", "recipes": "Ricette", "more": "Altro", - "documents": "Documenti" + "documents": "Documenti", + "kitchen": "Cucina", + "search": "Cerca" }, "dashboard": { "title": "Panoramica", @@ -969,5 +971,8 @@ "dropzoneTitle": "Rilascia il file qui o fai clic per scegliere", "dropzoneHint": "Trascina un file in quest’area oppure usa il selettore.", "selectedFileLabel": "Selezionato: {{name}}" + }, + "shortcuts": { + "goKitchen": "Cucina" } } diff --git a/public/locales/ja.json b/public/locales/ja.json index 7995b14..1ae4048 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -47,7 +47,9 @@ "quickActions": "クイックアクション", "recipes": "レシピ", "more": "もっと見る", - "documents": "書類" + "documents": "書類", + "kitchen": "キッチン", + "search": "検索" }, "dashboard": { "title": "ダッシュボード", @@ -969,5 +971,8 @@ "dropzoneTitle": "ここにファイルをドロップ、またはクリックして選択", "dropzoneHint": "この領域にファイルをドラッグするか、ファイル選択を使用します。", "selectedFileLabel": "選択済み: {{name}}" + }, + "shortcuts": { + "goKitchen": "キッチン" } } diff --git a/public/locales/pt.json b/public/locales/pt.json index e02332e..d717f8e 100644 --- a/public/locales/pt.json +++ b/public/locales/pt.json @@ -47,7 +47,9 @@ "quickActions": "Ações rápidas", "recipes": "Receitas", "more": "Mais", - "documents": "Documentos" + "documents": "Documentos", + "kitchen": "Cozinha", + "search": "Pesquisar" }, "dashboard": { "title": "Painel", @@ -970,5 +972,8 @@ "dropzoneTitle": "Solte o arquivo aqui ou clique para escolher", "dropzoneHint": "Arraste um arquivo para esta area, ou use o seletor de arquivos.", "selectedFileLabel": "Selecionado: {{name}}" + }, + "shortcuts": { + "goKitchen": "Cozinha" } } diff --git a/public/locales/ru.json b/public/locales/ru.json index 4ad7293..b3736c8 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -47,7 +47,9 @@ "quickActions": "Быстрые действия", "recipes": "Рецепты", "more": "Ещё", - "documents": "Документы" + "documents": "Документы", + "kitchen": "Кухня", + "search": "Поиск" }, "dashboard": { "title": "Обзор", @@ -969,5 +971,8 @@ "dropzoneTitle": "Перетащите файл сюда или нажмите для выбора", "dropzoneHint": "Перетащите файл в эту область или используйте выбор файла.", "selectedFileLabel": "Выбрано: {{name}}" + }, + "shortcuts": { + "goKitchen": "Кухня" } } diff --git a/public/locales/sv.json b/public/locales/sv.json index fb700de..49e989c 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -47,7 +47,9 @@ "quickActions": "Snabba åtgärder", "recipes": "Recept", "more": "Mer", - "documents": "Dokument" + "documents": "Dokument", + "kitchen": "Kök", + "search": "Sök" }, "dashboard": { "title": "Översikt", @@ -969,5 +971,8 @@ "dropzoneTitle": "Släpp filen här eller klicka för att välja", "dropzoneHint": "Dra en fil till området eller använd filväljaren.", "selectedFileLabel": "Vald: {{name}}" + }, + "shortcuts": { + "goKitchen": "Kök" } } diff --git a/public/locales/tr.json b/public/locales/tr.json index bfa2345..6533c33 100644 --- a/public/locales/tr.json +++ b/public/locales/tr.json @@ -47,7 +47,9 @@ "quickActions": "Hızlı işlemler", "recipes": "Tarifler", "more": "Daha Fazla", - "documents": "Belgeler" + "documents": "Belgeler", + "kitchen": "Mutfak", + "search": "Ara" }, "dashboard": { "title": "Genel Bakış", @@ -969,5 +971,8 @@ "dropzoneTitle": "Dosyayı buraya bırakın veya seçmek için tıklayın", "dropzoneHint": "Bir dosyayı bu alana sürükleyin veya dosya seçiciyi kullanın.", "selectedFileLabel": "Seçildi: {{name}}" + }, + "shortcuts": { + "goKitchen": "Mutfak" } } diff --git a/public/locales/uk.json b/public/locales/uk.json index f27b19e..336352f 100644 --- a/public/locales/uk.json +++ b/public/locales/uk.json @@ -47,7 +47,9 @@ "quickActions": "Швидкі дії", "recipes": "Рецепти", "more": "Більше", - "documents": "Документи" + "documents": "Документи", + "kitchen": "Кухня", + "search": "Пошук" }, "dashboard": { "title": "Огляд", @@ -977,5 +979,8 @@ "dropzoneTitle": "Перетягніть файл сюди або натисніть для вибору", "dropzoneHint": "Перетягніть файл у цю область або скористайтеся вибором файлу.", "selectedFileLabel": "Вибрано: {{name}}" + }, + "shortcuts": { + "goKitchen": "Кухня" } } diff --git a/public/locales/zh.json b/public/locales/zh.json index fdbbafb..cd35134 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -47,7 +47,9 @@ "quickActions": "快捷操作", "recipes": "食谱", "more": "更多", - "documents": "文档" + "documents": "文档", + "kitchen": "厨房", + "search": "搜索" }, "dashboard": { "title": "概览", @@ -969,5 +971,8 @@ "dropzoneTitle": "将文件拖到此处或点击选择", "dropzoneHint": "将文件拖入此区域,或使用文件选择器。", "selectedFileLabel": "已选择:{{name}}" + }, + "shortcuts": { + "goKitchen": "厨房" } } From 7e137d1c21f54479883d7bce056962d375a80ad3 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 19:55:28 +0200 Subject: [PATCH 03/10] feat: add kitchen-tabs utility, CSS, token and test --- package.json | 3 +- public/styles/kitchen-tabs.css | 86 ++++++++++++++++++++++++++++++++++ public/styles/tokens.css | 1 + public/utils/kitchen-tabs.js | 68 +++++++++++++++++++++++++++ test-kitchen-tabs.js | 63 +++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 public/styles/kitchen-tabs.css create mode 100644 public/utils/kitchen-tabs.js create mode 100644 test-kitchen-tabs.js diff --git a/package.json b/package.json index 1f39977..98fc3e3 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,13 @@ "test:ncb": "node --experimental-sqlite test-notes-contacts-budget.js", "test:ux-utils": "node test-ux-utils.js", "test:modal-utils": "node --loader ./test-browser-loader.mjs test-modal-utils.js", + "test:kitchen-tabs": "node --loader ./test-browser-loader.mjs test-kitchen-tabs.js", "test:reminders": "node --experimental-sqlite test-reminders.js", "test:api": "node test-api.js", "test:setup": "node test-setup.js", "test:ics-parser": "node test-ics-parser.js", "test:ics-sub": "node --experimental-sqlite test-ics-subscription.js", - "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js && node --experimental-sqlite test-calendar.js && node --experimental-sqlite test-notes-contacts-budget.js && npm run test:ux-utils && npm run test:modal-utils && npm run test:reminders && npm run test:api && npm run test:ics-parser && npm run test:ics-sub && npm run test:setup" + "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js && node --experimental-sqlite test-calendar.js && node --experimental-sqlite test-notes-contacts-budget.js && npm run test:ux-utils && npm run test:modal-utils && npm run test:reminders && npm run test:api && npm run test:ics-parser && npm run test:ics-sub && npm run test:setup && npm run test:kitchen-tabs" }, "dependencies": { "bcrypt": "^6.0.0", diff --git a/public/styles/kitchen-tabs.css b/public/styles/kitchen-tabs.css new file mode 100644 index 0000000..3529844 --- /dev/null +++ b/public/styles/kitchen-tabs.css @@ -0,0 +1,86 @@ +/* Modul: Kitchen Tabs Bar + * Sticky Segment-Control für Mahlzeiten/Rezepte/Einkauf + Sub-Modul Layout-Fixes + */ + +.kitchen-tabs-bar { + display: flex; + gap: var(--space-1); + padding: var(--space-2) var(--space-4); + height: var(--kitchen-tabs-height); + box-sizing: border-box; + position: sticky; + top: 0; + z-index: var(--z-sticky); + background-color: var(--color-bg); + border-bottom: 1px solid var(--color-border-subtle); + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; + flex-shrink: 0; +} + +.kitchen-tabs-bar::-webkit-scrollbar { + display: none; +} + +.kitchen-tab { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: 0 var(--space-3); + height: 36px; + border-radius: var(--radius-full); + border: none; + background: transparent; + color: var(--color-text-secondary); + font-family: inherit; + font-size: var(--text-sm); + font-weight: var(--font-weight-medium); + cursor: pointer; + white-space: nowrap; + flex-shrink: 0; + transition: background-color var(--transition-fast), color var(--transition-fast); + -webkit-tap-highlight-color: transparent; +} + +.kitchen-tab:active { + transform: scale(0.96); + transition-duration: 0.06s; +} + +.kitchen-tab--active { + background-color: color-mix(in srgb, var(--active-module-accent, var(--color-accent)) 14%, transparent); + color: var(--active-module-accent, var(--color-accent)); +} + +.kitchen-tab__icon { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.kitchen-tab__label { + line-height: 1; +} + +/* Mahlzeiten: sticky day-header unterhalb der Tab-Leiste */ +.has-kitchen-tabs .day-header { + top: var(--kitchen-tabs-height); +} + +/* Einkauf: Viewport-Höhe um Tab-Leiste reduzieren (Mobile) */ +.has-kitchen-tabs .shopping-page { + height: calc( + 100dvh + - var(--nav-bottom-height) + - var(--safe-area-inset-bottom) + - var(--kitchen-tabs-height) + ); +} + +/* Einkauf: Viewport-Höhe (Desktop) */ +@media (min-width: 1024px) { + .has-kitchen-tabs .shopping-page { + height: calc(100dvh - var(--kitchen-tabs-height)); + } +} diff --git a/public/styles/tokens.css b/public/styles/tokens.css index 4e9f0f4..74a45a2 100644 --- a/public/styles/tokens.css +++ b/public/styles/tokens.css @@ -351,6 +351,7 @@ --content-max-width: 1280px; --content-max-width-narrow: 720px; --cal-hour-height: 56px; + --kitchen-tabs-height: 56px; /* -------------------------------------------------------- * 13. Sidebar diff --git a/public/utils/kitchen-tabs.js b/public/utils/kitchen-tabs.js new file mode 100644 index 0000000..1005480 --- /dev/null +++ b/public/utils/kitchen-tabs.js @@ -0,0 +1,68 @@ +import { t } from '/i18n.js'; + +export const KITCHEN_ROUTES = ['/meals', '/recipes', '/shopping']; +export const KITCHEN_STORAGE_KEY = 'oikos-kitchen-tab'; + +const TABS = () => [ + { route: '/meals', labelKey: 'nav.meals', icon: 'utensils' }, + { route: '/recipes', labelKey: 'nav.recipes', icon: 'book-text' }, + { route: '/shopping', labelKey: 'nav.shopping', icon: 'shopping-cart' }, +]; + +export function getLastKitchenRoute() { + try { + const stored = sessionStorage.getItem(KITCHEN_STORAGE_KEY); + return KITCHEN_ROUTES.includes(stored) ? stored : '/meals'; + } catch { + return '/meals'; + } +} + +export function isKitchenRoute(path) { + return KITCHEN_ROUTES.includes(path); +} + +export function renderKitchenTabsBar(container, activeRoute) { + try { + sessionStorage.setItem(KITCHEN_STORAGE_KEY, activeRoute); + } catch { /* ignore */ } + + container.classList.add('has-kitchen-tabs'); + + const bar = document.createElement('div'); + bar.className = 'kitchen-tabs-bar'; + bar.setAttribute('role', 'tablist'); + bar.setAttribute('aria-label', t('nav.kitchen')); + + TABS().forEach(({ route, labelKey, icon }) => { + const btn = document.createElement('button'); + btn.className = 'kitchen-tab' + (route === activeRoute ? ' kitchen-tab--active' : ''); + btn.dataset.route = route; + btn.type = 'button'; + btn.setAttribute('role', 'tab'); + btn.setAttribute('aria-selected', route === activeRoute ? 'true' : 'false'); + + const i = document.createElement('i'); + i.dataset.lucide = icon; + i.className = 'kitchen-tab__icon'; + i.setAttribute('aria-hidden', 'true'); + + const span = document.createElement('span'); + span.className = 'kitchen-tab__label'; + span.textContent = t(labelKey); + + btn.appendChild(i); + btn.appendChild(span); + bar.appendChild(btn); + }); + + bar.addEventListener('click', (e) => { + const btn = e.target.closest('[data-route]'); + if (!btn || btn.dataset.route === activeRoute) return; + window.oikos?.navigate(btn.dataset.route); + }); + + container.insertAdjacentElement('afterbegin', bar); + + if (window.lucide) window.lucide.createIcons({ el: bar }); +} diff --git a/test-kitchen-tabs.js b/test-kitchen-tabs.js new file mode 100644 index 0000000..796def8 --- /dev/null +++ b/test-kitchen-tabs.js @@ -0,0 +1,63 @@ +/** + * Tests: Kitchen-Tabs Utility (pure functions) + * Läuft mit: node --loader ./test-browser-loader.mjs test-kitchen-tabs.js + */ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; + +const { KITCHEN_ROUTES, KITCHEN_STORAGE_KEY, getLastKitchenRoute, isKitchenRoute } = await (async () => { + global.window = { oikos: null }; + global.document = { + createElement: () => ({ + className: '', dataset: {}, style: {}, + setAttribute() {}, appendChild() {}, + classList: { add() {}, toggle() {} }, + insertAdjacentElement() {}, + addEventListener() {}, + }), + }; + const storage = { + _d: {}, + getItem(k) { return this._d[k] ?? null; }, + setItem(k, v) { this._d[k] = v; }, + }; + global.sessionStorage = storage; + global.t = (k) => k; + return import('./public/utils/kitchen-tabs.js'); +})(); + +test('KITCHEN_ROUTES enthält alle drei Sub-Routen', () => { + assert.deepEqual(KITCHEN_ROUTES, ['/meals', '/recipes', '/shopping']); +}); + +test('KITCHEN_STORAGE_KEY ist korrekt', () => { + assert.equal(KITCHEN_STORAGE_KEY, 'oikos-kitchen-tab'); +}); + +test('getLastKitchenRoute: Standardwert /meals wenn kein Storage-Eintrag', () => { + global.sessionStorage._d = {}; + assert.equal(getLastKitchenRoute(), '/meals'); +}); + +test('getLastKitchenRoute: gibt gespeicherte Route zurück', () => { + global.sessionStorage._d = { 'oikos-kitchen-tab': '/recipes' }; + assert.equal(getLastKitchenRoute(), '/recipes'); +}); + +test('getLastKitchenRoute: ignoriert ungültige gespeicherte Route', () => { + global.sessionStorage._d = { 'oikos-kitchen-tab': '/admin' }; + assert.equal(getLastKitchenRoute(), '/meals'); +}); + +test('isKitchenRoute: erkennt Kitchen-Routen', () => { + assert.equal(isKitchenRoute('/meals'), true); + assert.equal(isKitchenRoute('/recipes'), true); + assert.equal(isKitchenRoute('/shopping'), true); +}); + +test('isKitchenRoute: lehnt Nicht-Kitchen-Routen ab', () => { + assert.equal(isKitchenRoute('/tasks'), false); + assert.equal(isKitchenRoute('/'), false); + assert.equal(isKitchenRoute('/calendar'), false); + assert.equal(isKitchenRoute(''), false); +}); From 88778a95c95ef33db2ebc273b92de4ad70aa1628 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 19:56:41 +0200 Subject: [PATCH 04/10] feat: integrate kitchen tabs bar into meals, recipes, shopping --- public/pages/meals.js | 2 ++ public/pages/recipes.js | 2 ++ public/pages/shopping.js | 2 ++ 3 files changed, 6 insertions(+) diff --git a/public/pages/meals.js b/public/pages/meals.js index ee6af78..b2ebb6a 100644 --- a/public/pages/meals.js +++ b/public/pages/meals.js @@ -10,6 +10,7 @@ import { stagger } from '/utils/ux.js'; import { t, formatDate, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid } from '/i18n.js'; import { esc } from '/utils/html.js'; import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js'; +import { renderKitchenTabsBar } from '/utils/kitchen-tabs.js'; // -------------------------------------------------------- // Konstanten @@ -163,6 +164,7 @@ export async function render(container, { user }) { `; if (window.lucide) lucide.createIcons(); + renderKitchenTabsBar(container, '/meals'); const today = new Date().toISOString().slice(0, 10); const monday = getMondayOf(today); diff --git a/public/pages/recipes.js b/public/pages/recipes.js index f53bf96..b44763e 100644 --- a/public/pages/recipes.js +++ b/public/pages/recipes.js @@ -7,6 +7,7 @@ import { api } from '/api.js'; import { t } from '/i18n.js'; import { openModal as openSharedModal, closeModal as closeSharedModal } from '/components/modal.js'; import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js'; +import { renderKitchenTabsBar } from '/utils/kitchen-tabs.js'; let _container = null; @@ -70,6 +71,7 @@ export async function render(container) { page.append(header, list, fab); container.replaceChildren(page); + renderKitchenTabsBar(container, '/recipes'); if (window.lucide) window.lucide.createIcons(); diff --git a/public/pages/shopping.js b/public/pages/shopping.js index 195cc4f..187940b 100644 --- a/public/pages/shopping.js +++ b/public/pages/shopping.js @@ -10,6 +10,7 @@ import { t } from '/i18n.js'; import { esc } from '/utils/html.js'; import { promptModal } from '/components/modal.js'; import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js'; +import { renderKitchenTabsBar } from '/utils/kitchen-tabs.js'; // -------------------------------------------------------- // Konstanten @@ -839,6 +840,7 @@ export async function render(container, { user }) { `; + renderKitchenTabsBar(container, '/shopping'); try { await Promise.all([loadCategories(), loadLists()]); From 1ac2fbd2b57d39fc40b435ea404c78875ae47a05 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 19:59:15 +0200 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20restructure=20bottom=20nav=20?= =?UTF-8?q?=E2=80=94=20kitchen/search=20buttons,=20navItems=20cleanup,=20g?= =?UTF-8?q?-k=20shortcuts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/router.js | 136 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 105 insertions(+), 31 deletions(-) diff --git a/public/router.js b/public/router.js index b2d17ef..011a0a9 100644 --- a/public/router.js +++ b/public/router.js @@ -8,6 +8,7 @@ import { api, auth } from '/api.js'; import { initI18n, getLocale, t } from '/i18n.js'; import { esc } from '/utils/html.js'; import { init as initReminders, stop as stopReminders } from '/reminders.js'; +import { isKitchenRoute, getLastKitchenRoute } from '/utils/kitchen-tabs.js'; // -------------------------------------------------------- // Routen-Definitionen @@ -128,10 +129,10 @@ let _pendingLoginRedirect = false; // Router // -------------------------------------------------------- -const ROUTE_ORDER = ['/', '/tasks', '/calendar', '/birthdays', '/meals', '/recipes', '/shopping', - '/notes', '/contacts', '/budget', '/documents', '/settings']; +const ROUTE_ORDER = ['/', '/calendar', '/tasks', '/meals', '/recipes', '/shopping', + '/birthdays', '/notes', '/contacts', '/budget', '/documents', '/settings']; -const PRIMARY_NAV = 3; +const PRIMARY_NAV = 2; const DEFAULT_APP_NAME = 'Oikos'; const APP_NAME_STORAGE_KEY = 'oikos-app-name'; @@ -478,6 +479,42 @@ function renderAppShell(container) { const bottomItems = document.createElement('div'); bottomItems.className = 'nav-bottom__items'; navItems().slice(0, PRIMARY_NAV).forEach((item) => bottomItems.appendChild(navItemEl(item))); + + const kitchenBtn = document.createElement('button'); + kitchenBtn.className = 'nav-item nav-item--kitchen'; + kitchenBtn.id = 'kitchen-btn'; + kitchenBtn.type = 'button'; + kitchenBtn.setAttribute('aria-label', t('nav.kitchen')); + kitchenBtn.setAttribute('title', t('nav.kitchen')); + const kitchenBtnIcon = document.createElement('i'); + kitchenBtnIcon.dataset.lucide = 'utensils'; + kitchenBtnIcon.className = 'nav-item__icon'; + kitchenBtnIcon.setAttribute('aria-hidden', 'true'); + const kitchenBtnLabel = document.createElement('span'); + kitchenBtnLabel.className = 'nav-item__label'; + kitchenBtnLabel.textContent = t('nav.kitchen'); + kitchenBtn.appendChild(kitchenBtnIcon); + kitchenBtn.appendChild(kitchenBtnLabel); + kitchenBtn.addEventListener('click', () => navigate(getLastKitchenRoute())); + bottomItems.appendChild(kitchenBtn); + + const searchNavBtn = document.createElement('button'); + searchNavBtn.className = 'nav-item nav-item--search'; + searchNavBtn.id = 'search-btn'; + searchNavBtn.type = 'button'; + searchNavBtn.setAttribute('aria-label', t('nav.search')); + searchNavBtn.setAttribute('title', t('nav.search')); + const searchNavIcon = document.createElement('i'); + searchNavIcon.dataset.lucide = 'search'; + searchNavIcon.className = 'nav-item__icon'; + searchNavIcon.setAttribute('aria-hidden', 'true'); + const searchNavLabel = document.createElement('span'); + searchNavLabel.className = 'nav-item__label'; + searchNavLabel.textContent = t('nav.search'); + searchNavBtn.appendChild(searchNavIcon); + searchNavBtn.appendChild(searchNavLabel); + bottomItems.appendChild(searchNavBtn); + const moreBtn = document.createElement('button'); moreBtn.className = 'nav-item nav-item--more'; moreBtn.id = 'more-btn'; @@ -506,21 +543,11 @@ function renderAppShell(container) { moreSheet.setAttribute('role', 'dialog'); moreSheet.setAttribute('aria-label', t('nav.more')); moreSheet.setAttribute('aria-hidden', 'true'); - const searchTrigger = document.createElement('button'); - searchTrigger.className = 'more-sheet__search-trigger'; - searchTrigger.id = 'search-btn'; - searchTrigger.setAttribute('aria-label', t('search.title')); - const searchTriggerIcon = document.createElement('i'); - searchTriggerIcon.dataset.lucide = 'search'; - searchTriggerIcon.className = 'more-sheet__search-trigger-icon'; - searchTriggerIcon.setAttribute('aria-hidden', 'true'); - const searchTriggerText = document.createElement('span'); - searchTriggerText.className = 'more-sheet__search-trigger-placeholder'; - searchTriggerText.textContent = t('search.placeholder'); - searchTrigger.appendChild(searchTriggerIcon); - searchTrigger.appendChild(searchTriggerText); - moreSheet.appendChild(searchTrigger); - navItems().slice(PRIMARY_NAV).forEach((item) => moreSheet.appendChild(moreItemEl(item))); + const dragHandle = document.createElement('div'); + dragHandle.className = 'more-sheet__handle'; + dragHandle.setAttribute('aria-hidden', 'true'); + moreSheet.insertAdjacentElement('afterbegin', dragHandle); + navItems().filter((i) => !i.sidebarOnly).slice(PRIMARY_NAV).forEach((item) => moreSheet.appendChild(moreItemEl(item))); const searchOverlay = document.createElement('div'); searchOverlay.className = 'search-overlay'; @@ -588,7 +615,11 @@ const SHORTCUTS = [ { key: 'g t', description: () => t('shortcuts.goTasks'), action: () => navigate('/tasks') }, { key: 'g c', description: () => t('shortcuts.goCal'), action: () => navigate('/calendar') }, { key: 'g s', description: () => t('shortcuts.goShop'), action: () => navigate('/shopping') }, - { key: 'g n', description: () => t('shortcuts.goNotes'), action: () => navigate('/notes') }, + { key: 'g n', description: () => t('shortcuts.goNotes'), action: () => navigate('/notes') }, + { key: 'g k', description: () => t('shortcuts.goKitchen'), action: () => navigate(getLastKitchenRoute()) }, + { key: 'g k m', description: () => t('shortcuts.goKitchen'), action: () => navigate('/meals') }, + { key: 'g k r', description: () => t('shortcuts.goKitchen'), action: () => navigate('/recipes') }, + { key: 'g k s', description: () => t('shortcuts.goKitchen'), action: () => navigate('/shopping') }, ]; let _pendingKey = null; @@ -603,8 +634,32 @@ function initKeyboardShortcuts() { const key = e.key.toLowerCase(); + // 3-Tasten-Chord: g k {m|r|s} + if (_pendingKey === 'g k') { + clearTimeout(_pendingTimer); + _pendingKey = null; + const chord3 = `g k ${key}`; + const s3 = SHORTCUTS.find((s) => s.key === chord3); + if (s3) { e.preventDefault(); s3.action(); return; } + // Kein 3-Chord-Match → g k selbst ausführen + const gk = SHORTCUTS.find((s) => s.key === 'g k'); + if (gk) { e.preventDefault(); gk.action(); } + return; + } + + // 2-Tasten-Chord: g {d|t|c|s|n|k} if (_pendingKey === 'g' && key !== 'g') { clearTimeout(_pendingTimer); + if (key === 'k') { + // k ist Präfix für 3-Chord — auf dritten Tastendruck warten + _pendingKey = 'g k'; + _pendingTimer = setTimeout(() => { + _pendingKey = null; + const gk = SHORTCUTS.find((s) => s.key === 'g k'); + if (gk) gk.action(); + }, 1000); + return; + } _pendingKey = null; const combo = `g ${key}`; const shortcut = SHORTCUTS.find((s) => s.key === combo); @@ -739,6 +794,14 @@ function initMoreSheet(container) { backdrop.addEventListener('click', closeSheet); + let _touchStartY = 0; + sheet.addEventListener('touchstart', (e) => { + _touchStartY = e.touches[0].clientY; + }, { passive: true }); + sheet.addEventListener('touchend', (e) => { + if (e.changedTouches[0].clientY - _touchStartY > 60) closeSheet(); + }, { passive: true }); + sheet.querySelectorAll('[data-route]').forEach((el) => { el.addEventListener('click', () => closeSheet()); }); @@ -873,17 +936,19 @@ function renderSearchResults(container, data, onClose) { function navItems() { return [ { path: '/', label: t('nav.dashboard'), icon: 'layout-dashboard' }, - { path: '/tasks', label: t('nav.tasks'), icon: 'check-square' }, { path: '/calendar', label: t('nav.calendar'), icon: 'calendar' }, - { path: '/shopping', label: t('nav.shopping'), icon: 'shopping-cart' }, - { path: '/meals', label: t('nav.meals'), icon: 'utensils' }, - { path: '/recipes', label: t('nav.recipes'), icon: 'book-text' }, + // More-Sheet Items: + { path: '/tasks', label: t('nav.tasks'), icon: 'check-square' }, { path: '/birthdays', label: t('nav.birthdays'), icon: 'cake' }, { path: '/notes', label: t('nav.notes'), icon: 'sticky-note' }, { path: '/contacts', label: t('nav.contacts'), icon: 'book-user' }, { path: '/budget', label: t('nav.budget'), icon: 'wallet' }, { path: '/documents', label: t('nav.documents'), icon: 'folder-lock' }, { path: '/settings', label: t('nav.settings'), icon: 'settings' }, + // Sidebar only (Küche-Gruppe): + { path: '/meals', label: t('nav.meals'), icon: 'utensils', sidebarOnly: true }, + { path: '/recipes', label: t('nav.recipes'), icon: 'book-text', sidebarOnly: true }, + { path: '/shopping', label: t('nav.shopping'), icon: 'shopping-cart', sidebarOnly: true }, ]; } @@ -936,9 +1001,16 @@ function updateNav(path) { } }); + const kitchenNavBtn = document.querySelector('#kitchen-btn'); + if (kitchenNavBtn) { + const isKitchen = isKitchenRoute(path); + kitchenNavBtn.classList.toggle('nav-item--active', isKitchen); + kitchenNavBtn.toggleAttribute('aria-current', isKitchen); + } + const moreBtn = document.querySelector('#more-btn'); if (moreBtn) { - const secondaryItems = navItems().slice(PRIMARY_NAV); + const secondaryItems = navItems().filter((i) => !i.sidebarOnly).slice(PRIMARY_NAV); const activeSecondary = secondaryItems.find((n) => n.path === path); const inMoreSheet = !!activeSecondary; @@ -1120,16 +1192,18 @@ window.addEventListener('locale-changed', () => { navSidebarItems.replaceChildren(...navItems().map(navItemEl)); } if (bottomItems) { - const moreBtn = bottomItems.querySelector('#more-btn'); + const kitchenBtnEl = bottomItems.querySelector('#kitchen-btn'); + const searchBtnEl = bottomItems.querySelector('#search-btn'); + const moreBtn = bottomItems.querySelector('#more-btn'); + if (kitchenBtnEl) kitchenBtnEl.querySelector('.nav-item__label').textContent = t('nav.kitchen'); + if (searchBtnEl) searchBtnEl.querySelector('.nav-item__label').textContent = t('nav.search'); const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl); - bottomItems.replaceChildren(...newItems, moreBtn); + bottomItems.replaceChildren(...newItems, kitchenBtnEl, searchBtnEl, moreBtn); } if (moreSheet) { - const searchTrig = moreSheet.querySelector('#search-btn'); - const searchTrigPlaceholder = searchTrig?.querySelector('.more-sheet__search-trigger-placeholder'); - if (searchTrigPlaceholder) searchTrigPlaceholder.textContent = t('search.placeholder'); - const newMoreItems = navItems().slice(PRIMARY_NAV).map(moreItemEl); - moreSheet.replaceChildren(searchTrig, ...newMoreItems); + const handle = moreSheet.querySelector('.more-sheet__handle'); + const newMoreItems = navItems().filter((i) => !i.sidebarOnly).slice(PRIMARY_NAV).map(moreItemEl); + moreSheet.replaceChildren(handle, ...newMoreItems); } document.querySelectorAll('[data-route]').forEach((el) => { From 6501588370fb32a691c5dfbb41ddeb5d66b964c7 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 20:01:11 +0200 Subject: [PATCH 06/10] feat: more-sheet 2-column, drag handle, kitchen/search button styles --- public/styles/layout.css | 53 +++++++++++----------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/public/styles/layout.css b/public/styles/layout.css index 5fa3072..4e1f9a0 100755 --- a/public/styles/layout.css +++ b/public/styles/layout.css @@ -172,7 +172,9 @@ } /* ── More-Button (Button-Basis, ansonsten wie nav-item) ── */ -.nav-item--more { +.nav-item--more, +.nav-item--kitchen, +.nav-item--search { background: none; border: none; cursor: pointer; @@ -216,7 +218,7 @@ padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom)); z-index: calc(var(--z-nav) + 2); display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(2, 1fr); gap: var(--space-3); transform: translateY(100%); transition: transform 0.25s var(--ease-out); @@ -226,6 +228,16 @@ transform: translateY(0); } +/* ── More-Sheet Drag-Handle ── */ +.more-sheet__handle { + grid-column: 1 / -1; + width: 36px; + height: 4px; + border-radius: var(--radius-full); + background-color: var(--color-border); + margin: 0 auto var(--space-2); +} + /* ── More-Item ── */ .more-item { display: flex; @@ -268,43 +280,6 @@ line-height: 1.2; } -/* ── More-Sheet Suchtrigger ── */ -.more-sheet__search-trigger { - grid-column: 1 / -1; - display: flex; - align-items: center; - gap: var(--space-2); - padding: 0 var(--space-4); - height: var(--target-lg); - border-radius: var(--radius-full); - border: 1.5px solid var(--color-border); - background-color: var(--color-surface-elevated); - color: var(--color-text-tertiary); - cursor: pointer; - font-family: inherit; - font-size: var(--text-sm); - text-align: left; - width: 100%; - -webkit-tap-highlight-color: transparent; - transition: background-color var(--transition-fast), border-color var(--transition-fast); -} - -.more-sheet__search-trigger:active { - background-color: var(--color-surface-hover); - border-color: var(--color-accent); - transform: scale(0.98); -} - -.more-sheet__search-trigger-icon { - width: var(--space-4); - height: var(--space-4); - flex-shrink: 0; -} - -.more-sheet__search-trigger-placeholder { - flex: 1; -} - /* ── Such-Overlay ── */ .search-overlay { position: fixed; From bf345380a2f4cdaccb666dcd33abb1f72b5dd02b Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 20:02:04 +0200 Subject: [PATCH 07/10] feat: link kitchen-tabs.css in index.html --- public/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/public/index.html b/public/index.html index 953a94c..600026f 100644 --- a/public/index.html +++ b/public/index.html @@ -37,6 +37,7 @@ +