fix(ux): prevent iOS auto-zoom on inputs + lazy-load page CSS
Increase font-size to 16px on mobile for shopping quick-add inputs, notes search, and contacts search. Desktop breakpoint restores compact sizes. Move 9 page-specific stylesheets from index.html to on-demand loading in router.js, reducing initial CSS payload.
This commit is contained in:
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.7.3] - 2026-04-04
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
- Increase font-size to 16px (`--text-md`) on mobile for `quick-add__input`, `quick-add__qty`, `quick-add__cat` (shopping), `notes-toolbar__search-input`, and `contacts-toolbar__search-input` - prevents iOS auto-zoom on input focus (WCAG touch-friendly inputs)
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- Lazy-load page-specific stylesheets on route change instead of loading all 10 upfront in `index.html` - reduces initial CSS payload; only tokens, reset, pwa, layout, and login styles are render-blocking
|
||||||
|
|
||||||
## [0.7.2] - 2026-04-04
|
## [0.7.2] - 2026-04-04
|
||||||
|
|
||||||
### Accessibility
|
### Accessibility
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "oikos",
|
"name": "oikos",
|
||||||
"version": "0.7.2",
|
"version": "0.7.3",
|
||||||
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
|
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
|
||||||
"main": "server/index.js",
|
"main": "server/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+1
-10
@@ -31,21 +31,12 @@
|
|||||||
<link rel="modulepreload" href="/router.js" />
|
<link rel="modulepreload" href="/router.js" />
|
||||||
<link rel="modulepreload" href="/rrule-ui.js" />
|
<link rel="modulepreload" href="/rrule-ui.js" />
|
||||||
|
|
||||||
<!-- Styles -->
|
<!-- Styles (Basis - seitenspezifische CSS wird vom Router on-demand geladen) -->
|
||||||
<link rel="stylesheet" href="/styles/tokens.css" />
|
<link rel="stylesheet" href="/styles/tokens.css" />
|
||||||
<link rel="stylesheet" href="/styles/reset.css" />
|
<link rel="stylesheet" href="/styles/reset.css" />
|
||||||
<link rel="stylesheet" href="/styles/pwa.css" />
|
<link rel="stylesheet" href="/styles/pwa.css" />
|
||||||
<link rel="stylesheet" href="/styles/layout.css" />
|
<link rel="stylesheet" href="/styles/layout.css" />
|
||||||
<link rel="stylesheet" href="/styles/login.css" />
|
<link rel="stylesheet" href="/styles/login.css" />
|
||||||
<link rel="stylesheet" href="/styles/dashboard.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/tasks.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/shopping.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/meals.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/calendar.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/notes.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/contacts.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/budget.css" />
|
|
||||||
<link rel="stylesheet" href="/styles/settings.css" />
|
|
||||||
|
|
||||||
<!-- Theme: Vor CSS-Rendering anwenden (Flash-Prevention) -->
|
<!-- Theme: Vor CSS-Rendering anwenden (Flash-Prevention) -->
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+29
-1
@@ -64,6 +64,31 @@ function updateThemeColorForRoute(route) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// Dynamisches Stylesheet-Loading pro Seitenmodul
|
||||||
|
// --------------------------------------------------------
|
||||||
|
let activePageStyle = null;
|
||||||
|
|
||||||
|
function loadPageStyle(moduleName) {
|
||||||
|
if (!moduleName) return Promise.resolve();
|
||||||
|
const href = `/styles/${moduleName}.css`;
|
||||||
|
if (activePageStyle?.getAttribute('href') === href) return Promise.resolve();
|
||||||
|
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = href;
|
||||||
|
|
||||||
|
const loaded = new Promise((resolve) => {
|
||||||
|
link.onload = resolve;
|
||||||
|
link.onerror = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (activePageStyle) activePageStyle.remove();
|
||||||
|
document.head.appendChild(link);
|
||||||
|
activePageStyle = link;
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
// Modul-Cache: verhindert redundante dynamic imports bei Navigation
|
// Modul-Cache: verhindert redundante dynamic imports bei Navigation
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
@@ -170,7 +195,10 @@ async function renderPage(route, previousPath = null) {
|
|||||||
if (loading) loading.hidden = true;
|
if (loading) loading.hidden = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const module = await importPage(route.page);
|
const [module] = await Promise.all([
|
||||||
|
importPage(route.page),
|
||||||
|
loadPageStyle(route.module),
|
||||||
|
]);
|
||||||
|
|
||||||
if (typeof module.render !== 'function') {
|
if (typeof module.render !== 'function') {
|
||||||
throw new Error(`Seite ${route.page} exportiert keine render()-Funktion.`);
|
throw new Error(`Seite ${route.page} exportiert keine render()-Funktion.`);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
border: 1.5px solid var(--color-border);
|
border: 1.5px solid var(--color-border);
|
||||||
background-color: var(--color-surface-2);
|
background-color: var(--color-surface-2);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-md);
|
||||||
transition: border-color var(--transition-fast);
|
transition: border-color var(--transition-fast);
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
@@ -72,6 +72,10 @@
|
|||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.contacts-toolbar__search-input { font-size: var(--text-sm); }
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------
|
/* --------------------------------------------------------
|
||||||
* Kategorie-Filter (horizontal scroll)
|
* Kategorie-Filter (horizontal scroll)
|
||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-md);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +77,10 @@
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.notes-toolbar__search-input { font-size: var(--text-sm); }
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------
|
/* --------------------------------------------------------
|
||||||
* Masonry-Grid
|
* Masonry-Grid
|
||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
|
|||||||
@@ -165,7 +165,7 @@
|
|||||||
border: 1.5px solid var(--color-border);
|
border: 1.5px solid var(--color-border);
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-size: var(--text-base);
|
font-size: var(--text-md);
|
||||||
transition: border-color var(--transition-fast);
|
transition: border-color var(--transition-fast);
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-md);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
border: 1.5px solid var(--color-border);
|
border: 1.5px solid var(--color-border);
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-md);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
@@ -216,6 +216,12 @@
|
|||||||
border-color: var(--color-accent);
|
border-color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.quick-add__input { font-size: var(--text-base); }
|
||||||
|
.quick-add__qty { font-size: var(--text-sm); }
|
||||||
|
.quick-add__cat { font-size: var(--text-sm); }
|
||||||
|
}
|
||||||
|
|
||||||
.quick-add__btn {
|
.quick-add__btn {
|
||||||
width: 44px;
|
width: 44px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
|
|||||||
Reference in New Issue
Block a user