diff --git a/public/pages/budget.js b/public/pages/budget.js
index 81765a6..eefd4c4 100644
--- a/public/pages/budget.js
+++ b/public/pages/budget.js
@@ -98,6 +98,9 @@ export async function render(container, { user }) {
+
`;
@@ -131,7 +134,9 @@ function wireNav() {
renderBody();
updateLabel();
});
- _container.querySelector('#budget-add').addEventListener('click', () => openModal({ mode: 'create' }));
+ const addHandler = () => openModal({ mode: 'create' });
+ _container.querySelector('#budget-add').addEventListener('click', addHandler);
+ _container.querySelector('#fab-new-budget').addEventListener('click', addHandler);
updateLabel();
}
diff --git a/public/pages/calendar.js b/public/pages/calendar.js
index e8eceba..b84377e 100644
--- a/public/pages/calendar.js
+++ b/public/pages/calendar.js
@@ -159,6 +159,9 @@ export async function render(container, { user }) {
`;
@@ -167,6 +170,8 @@ export async function render(container, { user }) {
renderToolbar();
renderView();
+
+ container.querySelector('#fab-new-event')?.addEventListener('click', () => openEventModal({ mode: 'create' }));
}
// --------------------------------------------------------
diff --git a/public/pages/contacts.js b/public/pages/contacts.js
index 733bfe8..14d740a 100644
--- a/public/pages/contacts.js
+++ b/public/pages/contacts.js
@@ -61,6 +61,9 @@ export async function render(container, { user }) {
`).join('')}
+
`;
@@ -92,9 +95,9 @@ export async function render(container, { user }) {
});
// Neu
- _container.querySelector('#contacts-add-btn').addEventListener('click', () =>
- openModal({ mode: 'create' })
- );
+ const addHandler = () => openModal({ mode: 'create' });
+ _container.querySelector('#contacts-add-btn').addEventListener('click', addHandler);
+ _container.querySelector('#fab-new-contact').addEventListener('click', addHandler);
}
// --------------------------------------------------------
diff --git a/public/pages/notes.js b/public/pages/notes.js
index 09ca433..89599bc 100644
--- a/public/pages/notes.js
+++ b/public/pages/notes.js
@@ -53,6 +53,9 @@ export async function render(container, { user }) {
+
`;
@@ -68,7 +71,9 @@ export async function render(container, { user }) {
}
renderGrid();
- _container.querySelector('#notes-add-btn').addEventListener('click', () => openModal({ mode: 'create' }));
+ const addHandler = () => openModal({ mode: 'create' });
+ _container.querySelector('#notes-add-btn').addEventListener('click', addHandler);
+ _container.querySelector('#fab-new-note').addEventListener('click', addHandler);
}
// --------------------------------------------------------
diff --git a/public/pages/tasks.js b/public/pages/tasks.js
index 9d9e87c..479f135 100644
--- a/public/pages/tasks.js
+++ b/public/pages/tasks.js
@@ -864,10 +864,12 @@ function wireGroupToggle(container) {
}
function wireNewTaskBtn(container) {
- container.querySelector('#btn-new-task')?.addEventListener('click', () => {
+ const handler = () => {
openModal(renderModal({ users: state.users }));
wireModalEvents(container);
- });
+ };
+ container.querySelector('#btn-new-task')?.addEventListener('click', handler);
+ container.querySelector('#fab-new-task')?.addEventListener('click', handler);
}
function wireModalEvents(container) {
@@ -974,6 +976,9 @@ export async function render(container, { user }) {
`).join('')}
+
`;
diff --git a/public/styles/dashboard.css b/public/styles/dashboard.css
index 0484b85..de424fa 100644
--- a/public/styles/dashboard.css
+++ b/public/styles/dashboard.css
@@ -621,7 +621,7 @@
* -------------------------------------------------------- */
.fab-container {
position: fixed;
- bottom: calc(var(--nav-height-mobile) + var(--safe-area-inset-bottom) + var(--space-4));
+ bottom: calc(var(--nav-height-mobile) + 24px + var(--safe-area-inset-bottom));
right: var(--space-4);
z-index: calc(var(--z-nav) - 1);
display: flex;
diff --git a/public/styles/layout.css b/public/styles/layout.css
index 1341a7e..def2b7e 100644
--- a/public/styles/layout.css
+++ b/public/styles/layout.css
@@ -177,6 +177,58 @@
line-height: 1;
}
+/* --------------------------------------------------------
+ * Page FAB — Schwebender Erstellen-Button (nur Mobile/Tablet)
+ *
+ * Einheitlicher FAB auf Unterseiten. Auf Desktop versteckt,
+ * da der Toolbar-Button dort gut erreichbar ist.
+ * -------------------------------------------------------- */
+.page-fab {
+ position: fixed;
+ bottom: calc(var(--nav-height-mobile) + 24px + var(--safe-area-inset-bottom));
+ right: var(--space-4);
+ width: 52px;
+ height: 52px;
+ border-radius: var(--radius-full);
+ background-color: var(--color-accent);
+ color: #ffffff;
+ box-shadow: var(--shadow-lg);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ border: none;
+ z-index: calc(var(--z-nav) - 1);
+ transition: transform var(--transition-base), background-color var(--transition-fast);
+ -webkit-tap-highlight-color: transparent;
+}
+
+.page-fab:hover {
+ background-color: var(--color-accent-hover);
+}
+
+.page-fab:active {
+ transform: scale(0.92);
+}
+
+/* Auf Desktop: FAB verstecken, Toolbar-Button reicht */
+@media (min-width: 1024px) {
+ .page-fab {
+ display: none;
+ }
+}
+
+/* Auf Mobile: Toolbar-"Neu"-Buttons verstecken, FAB übernimmt */
+@media (max-width: 1023px) {
+ #btn-new-task,
+ #notes-add-btn,
+ #contacts-add-btn,
+ #budget-add,
+ #cal-add {
+ display: none !important;
+ }
+}
+
/* ================================================================
* Sidebar Navigation — Desktop (≥ 1024px)
*
diff --git a/public/sw.js b/public/sw.js
index 3ad0181..fd5eecb 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -12,9 +12,9 @@
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
*/
-const SHELL_CACHE = 'oikos-shell-v12';
-const PAGES_CACHE = 'oikos-pages-v12';
-const ASSETS_CACHE = 'oikos-assets-v12';
+const SHELL_CACHE = 'oikos-shell-v13';
+const PAGES_CACHE = 'oikos-pages-v13';
+const ASSETS_CACHE = 'oikos-assets-v13';
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
// App-Shell: sofort benötigt für ersten Render