M4: PWA + Mobile-UX – Manifest/Service-Worker + Icons, Auf/Ab-Reorder, FAB, Touch-Targets + Safe-Area, Nav-Entdupidung, Heute-Highlight, Aktions-Trennung (Abhaken vs. Bearbeiten/Löschen), OIDC-Fixes + PWA-Tests
This commit is contained in:
+380
-52
@@ -2,102 +2,423 @@
|
||||
<html lang="de" dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>{% block title %}svc-essensplanung{% endblock %}</title>
|
||||
<link rel="manifest" href="{{ asset('manifest.webmanifest') }}">
|
||||
<meta name="theme-color" content="#3f6d4e">
|
||||
<link rel="icon" href="{{ asset('icons/icon-192.png') }}" type="image/png">
|
||||
<link rel="apple-touch-icon" href="{{ asset('icons/apple-touch-icon.png') }}">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-title" content="Essen">
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4f4f4;
|
||||
--fg: #112233;
|
||||
--card: #fff;
|
||||
--accent: #0a7bff;
|
||||
--muted: #667;
|
||||
--warn: #b45309;
|
||||
--danger: #b91c1c;
|
||||
--bg: #f4eee1;
|
||||
--bg-soft: #ece4d2;
|
||||
--card: #fffdf8;
|
||||
--fg: #2e2a22;
|
||||
--muted: #877d69;
|
||||
--border: #e6ddc9;
|
||||
--border-strong: #d8cdb4;
|
||||
--accent: #3f6d4e;
|
||||
--accent-strong: #315a3e;
|
||||
--accent-soft: #e2ecdf;
|
||||
--danger: #a8442a;
|
||||
--danger-strong: #8f3820;
|
||||
--danger-soft: #f6e6de;
|
||||
--ring: rgba(63, 109, 78, .22);
|
||||
--shadow: 0 1px 2px rgba(76, 60, 32, .05), 0 5px 18px rgba(76, 60, 32, .08);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
background: var(--bg);
|
||||
font: 16px/1.55 ui-rounded, "SF Pro Rounded", system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
color: var(--fg);
|
||||
background: linear-gradient(180deg, #f7f2e7 0%, var(--bg) 320px) fixed;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
a { color: var(--accent); }
|
||||
|
||||
/* ---------- Header ---------- */
|
||||
header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--card);
|
||||
box-shadow: 0 1px 0 #ddd;
|
||||
gap: .5rem 1rem;
|
||||
flex-wrap: wrap;
|
||||
padding: calc(.65rem + env(safe-area-inset-top, 0px)) 1.25rem .65rem;
|
||||
background: rgba(255, 253, 248, .94);
|
||||
backdrop-filter: blur(8px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
header a {
|
||||
color: var(--accent);
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
margin-left: 1rem;
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
letter-spacing: -.01em;
|
||||
}
|
||||
main {
|
||||
max-width: 40rem;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
.brand svg { color: var(--accent); flex: none; }
|
||||
nav { display: flex; align-items: center; gap: .15rem; flex-wrap: wrap; }
|
||||
nav a {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
font-weight: 550;
|
||||
font-size: .95rem;
|
||||
padding: .35rem .8rem;
|
||||
border-radius: 999px;
|
||||
transition: background .15s, color .15s;
|
||||
}
|
||||
nav a:hover { color: var(--fg); background: var(--bg-soft); }
|
||||
nav a.nav-logout { margin-left: .5rem; color: var(--muted); font-size: .88rem; }
|
||||
nav a.nav-logout:hover { color: var(--danger); background: var(--danger-soft); }
|
||||
|
||||
/* ---------- Layout ---------- */
|
||||
main { max-width: 44rem; margin: 1.75rem auto 3rem; padding: 0 1rem; }
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.55rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: -.02em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.12rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -.01em;
|
||||
margin: 1.6rem 0 .55rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
}
|
||||
.subtitle { color: var(--muted); font-size: .95rem; margin-top: .25rem; }
|
||||
.page-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
padding: 1.25rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.06);
|
||||
padding: 1.4rem;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.muted { color: var(--muted); font-size: 0.9rem; }
|
||||
.muted { color: var(--muted); font-size: .9rem; }
|
||||
.lead { font-size: 1.02rem; color: var(--fg); margin: .2rem 0 1rem; }
|
||||
.row-between { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; }
|
||||
.empty { text-align: center; color: var(--muted); margin: 1.5rem 0 .5rem; padding: 1.25rem 1rem; background: var(--bg-soft); border-radius: .8rem; }
|
||||
|
||||
/* ---------- Buttons ---------- */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: .45rem;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 0.35rem;
|
||||
padding: .55rem 1.15rem;
|
||||
border-radius: 999px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
font-size: .95rem;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 2px rgba(49, 90, 62, .28);
|
||||
transition: background .15s, box-shadow .15s, transform .06s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn:hover { background: var(--accent-strong); box-shadow: 0 2px 6px rgba(49, 90, 62, .3); }
|
||||
.btn:active { transform: translateY(1px); }
|
||||
.btn svg { width: 1.05em; height: 1.05em; flex: none; }
|
||||
.btn-link {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
box-shadow: none;
|
||||
font-weight: 550;
|
||||
}
|
||||
.btn-link:hover { color: var(--fg); background: var(--bg-soft); }
|
||||
.btn-danger {
|
||||
background: transparent;
|
||||
color: var(--danger);
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-danger:hover { background: var(--danger-soft); color: var(--danger-strong); }
|
||||
.btn:focus-visible, nav a:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--ring);
|
||||
}
|
||||
.actions { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
|
||||
.action-haupt { display: flex; }
|
||||
.action-neben {
|
||||
display: flex;
|
||||
gap: .4rem;
|
||||
align-items: center;
|
||||
padding-left: .65rem;
|
||||
margin-left: .1rem;
|
||||
border-left: 1px solid var(--border);
|
||||
}
|
||||
.action-neben .btn { font-size: .87rem; padding: .5rem .75rem; }
|
||||
.action-neben form { display: inline-flex; }
|
||||
|
||||
/* ---------- Liste ---------- */
|
||||
.list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .6rem; }
|
||||
.item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: .75rem 1rem;
|
||||
flex-wrap: wrap;
|
||||
padding: .9rem 1.05rem;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: .85rem;
|
||||
transition: box-shadow .15s, border-color .15s, background .15s;
|
||||
}
|
||||
.item:hover { box-shadow: var(--shadow); border-color: var(--border-strong); }
|
||||
.item-main { display: flex; align-items: flex-start; gap: .15rem; min-width: 0; }
|
||||
.item-name { min-width: 0; }
|
||||
.item-name strong, .item-name .name {
|
||||
font-size: 1.07rem;
|
||||
font-weight: 680;
|
||||
letter-spacing: -.01em;
|
||||
color: var(--fg);
|
||||
}
|
||||
.notiz { color: var(--muted); font-size: .9rem; margin-top: .1rem; }
|
||||
.drag-handle {
|
||||
flex: none;
|
||||
align-self: center;
|
||||
cursor: grab;
|
||||
color: #b6ab90;
|
||||
margin-right: .6rem;
|
||||
padding: .45rem .3rem;
|
||||
border-radius: .45rem;
|
||||
line-height: 0;
|
||||
user-select: none;
|
||||
transition: color .15s, background .15s;
|
||||
}
|
||||
.drag-handle:hover { color: var(--muted); background: var(--bg-soft); }
|
||||
.updown { display: none; }
|
||||
.updown button {
|
||||
flex: none;
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
margin: .1rem 0;
|
||||
padding: 0;
|
||||
border-radius: .55rem;
|
||||
line-height: 0;
|
||||
color: #b6ab90;
|
||||
background: var(--bg-soft);
|
||||
border: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:hover { opacity: 0.92; }
|
||||
.btn-danger { background: var(--danger); }
|
||||
.btn-link { background: transparent; color: var(--muted); }
|
||||
.actions { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
||||
.list { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.item { display: flex; justify-content: space-between; gap: 1rem; padding: 0.75rem; border: 1px solid #eee; border-radius: 0.4rem; align-items: center; flex-wrap: wrap; }
|
||||
.drag-handle { cursor: grab; color: var(--muted); font-weight: 600; margin-right: 0.55rem; user-select: none; }
|
||||
.item-drag { opacity: 0.4; }
|
||||
.tags { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.4rem; }
|
||||
.tag { background: #eef4ff; color: var(--accent); padding: 0.1rem 0.5rem; border-radius: 999px; font-size: 0.8rem; }
|
||||
.status { font-size: 0.8rem; color: var(--muted); }
|
||||
.status-gegessen { color: #15573b; }
|
||||
.field { margin-bottom: 1rem; }
|
||||
.field > label { display: block; font-weight: 600; margin-bottom: 0.3rem; }
|
||||
.updown button:active { color: var(--accent-strong); background: var(--accent-soft); border-color: #cfe0cd; }
|
||||
.updown button:disabled { opacity: .35; pointer-events: none; }
|
||||
.item-drag { opacity: .35; }
|
||||
.tags { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .5rem; }
|
||||
.tag {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent-strong);
|
||||
padding: .13rem .6rem;
|
||||
border-radius: 999px;
|
||||
font-size: .78rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
.tag-mo { background: #e3efe2; color: #2f5d39; }
|
||||
.tag-di { background: #ddefed; color: #2a5f56; }
|
||||
.tag-mi { background: #e1ebf6; color: #2f5578; }
|
||||
.tag-do { background: #ebe5f4; color: #54437a; }
|
||||
.tag-fr { background: #fcedda; color: #94621c; }
|
||||
.tag-sa { background: #fae6e9; color: #93404d; }
|
||||
.tag-so { background: #f9f2d4; color: #7d6414; }
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .4rem;
|
||||
margin-top: .55rem;
|
||||
padding: .16rem .65rem;
|
||||
border-radius: 999px;
|
||||
font-size: .79rem;
|
||||
font-weight: 650;
|
||||
color: var(--muted);
|
||||
background: var(--bg-soft);
|
||||
}
|
||||
.status::before {
|
||||
content: "";
|
||||
width: .45rem;
|
||||
height: .45rem;
|
||||
border-radius: 50%;
|
||||
background: #c9bfa6;
|
||||
}
|
||||
.status-gegessen { color: var(--accent-strong); background: var(--accent-soft); }
|
||||
.status-gegessen::before { background: var(--accent); }
|
||||
.item-gegessen { background: #faf6ec; }
|
||||
.item-gegessen .item-name strong, .item-gegessen .item-name .name {
|
||||
color: var(--muted);
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: #b6ab90;
|
||||
text-decoration-thickness: 1.5px;
|
||||
}
|
||||
.item .actions { margin-left: auto; }
|
||||
|
||||
/* ---------- Formular ---------- */
|
||||
.field { margin-bottom: 1.15rem; }
|
||||
.field > label { display: block; font-weight: 650; margin-bottom: .35rem; font-size: .97rem; }
|
||||
.field input[type="text"], .field textarea {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 0.35rem;
|
||||
padding: .6rem .85rem;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: .6rem;
|
||||
font: inherit;
|
||||
background: #fff;
|
||||
color: var(--fg);
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
}
|
||||
.field input[type="text"]:focus, .field textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--ring);
|
||||
}
|
||||
.field textarea { resize: vertical; min-height: 4.2rem; }
|
||||
.req { color: var(--danger); }
|
||||
.checkboxes { display: flex; flex-wrap: wrap; gap: 0.5rem 0.9rem; margin-top: 0.3rem; }
|
||||
.checkbox { display: inline-flex; align-items: center; gap: 0.3rem; font-weight: 400; cursor: pointer; }
|
||||
.flash { padding: 0.6rem 0.9rem; border-radius: 0.4rem; margin-bottom: 1rem; }
|
||||
.flash-notice { background: #e6f6ec; color: #15573b; }
|
||||
.flash-error { background: #fdecec; color: #8a1f1f; }
|
||||
.checkboxes { display: flex; flex-wrap: wrap; gap: .55rem 1rem; margin-top: .4rem; }
|
||||
.checkbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .45rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
padding: .3rem .7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
transition: border-color .15s, background .15s;
|
||||
}
|
||||
.checkbox:hover { border-color: var(--border-strong); }
|
||||
.checkbox input { accent-color: var(--accent); width: 1rem; height: 1rem; margin: 0; }
|
||||
.checkbox:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); }
|
||||
|
||||
/* ---------- Flash ---------- */
|
||||
.flash {
|
||||
padding: .7rem 1rem;
|
||||
border-radius: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 550;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.flash-notice { background: var(--accent-soft); color: var(--accent-strong); border-color: #cfe0cd; }
|
||||
.flash-error { background: var(--danger-soft); color: var(--danger-strong); border-color: #eccfc4; }
|
||||
|
||||
/* ---------- Inspiration ---------- */
|
||||
.vorschlag-meta { color: var(--muted); font-size: .85rem; margin-top: .3rem; }
|
||||
.vorschlag-meta b { color: var(--accent-strong); font-weight: 650; }
|
||||
|
||||
/* ---------- Tagesansicht ---------- */
|
||||
h2[data-count]::after {
|
||||
content: attr(data-count);
|
||||
font-size: .76rem;
|
||||
font-weight: 650;
|
||||
color: var(--muted);
|
||||
background: var(--bg-soft);
|
||||
padding: .08rem .55rem;
|
||||
border-radius: 999px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
h2[data-heute] { color: var(--accent-strong); }
|
||||
h2[data-heute]::after {
|
||||
content: attr(data-count) " · heute";
|
||||
color: var(--accent-strong);
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
h2[data-heute] + .list .item { border-color: #cfe0cd; }
|
||||
|
||||
/* ---------- FAB ---------- */
|
||||
.fab {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
right: calc(1rem + env(safe-area-inset-right, 0px));
|
||||
bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 14px rgba(49, 90, 62, .4);
|
||||
}
|
||||
.fab:active { background: var(--accent-strong); transform: translateY(1px); }
|
||||
.fab svg { width: 1.5rem; height: 1.5rem; }
|
||||
|
||||
/* ---------- Mobil ---------- */
|
||||
@media (max-width: 640px) {
|
||||
main { margin-top: 1.1rem; padding: 0 .8rem; padding-bottom: 6rem; }
|
||||
.card { padding: 1.1rem; }
|
||||
.btn { min-height: 2.75rem; }
|
||||
.fab { display: flex; }
|
||||
.drag-handle { display: none; }
|
||||
.updown { display: inline-flex; flex-direction: column; align-self: center; margin-right: .6rem; }
|
||||
.item { flex-direction: column; align-items: stretch; }
|
||||
.item .actions { margin-left: 0; flex-direction: column; align-items: stretch; }
|
||||
.item .action-haupt { width: 100%; }
|
||||
.item .action-haupt .btn { width: 100%; }
|
||||
.item .action-neben {
|
||||
width: 100%;
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
padding-top: .5rem;
|
||||
margin-top: .15rem;
|
||||
}
|
||||
.item .action-neben .btn,
|
||||
.item .action-neben form { flex: 1; }
|
||||
.item .action-neben .btn { width: auto; }
|
||||
.item .action-neben form .btn { width: 100%; }
|
||||
.page-head { flex-direction: column; align-items: stretch; }
|
||||
.page-head .actions { width: 100%; }
|
||||
.page-head .actions .btn { flex: 1; }
|
||||
}
|
||||
</style>
|
||||
{% block stylesheets %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="{{ path('home') }}" style="color: inherit; font-weight: 600;">essen.ljh31.de</a>
|
||||
<a class="brand" href="{{ path('home') }}">
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" aria-hidden="true">
|
||||
<path d="M4 12h16a8 8 0 0 1-16 0Z"/>
|
||||
<path d="M9.2 8.2c-.6-1.4.5-2-.1-3.6"/>
|
||||
<path d="M13.8 8.2c-.6-1.4.5-2-.1-3.6"/>
|
||||
</svg>
|
||||
Essensplanung
|
||||
</a>
|
||||
<nav>
|
||||
<a href="{{ path('mahlzeiten_index') }}">Mahlzeiten</a>
|
||||
<a href="{{ path('mahlzeiten_tage') }}">Tage</a>
|
||||
<a href="{{ path('mahlzeiten_inspiration') }}">Inspiration</a>
|
||||
<a href="{{ path('mahlzeiten_archiv') }}">Archiv</a>
|
||||
<a href="{{ path('logout') }}">Abmelden</a>
|
||||
<a class="nav-logout" href="{{ path('logout') }}">Abmelden</a>
|
||||
</nav>
|
||||
</header>
|
||||
{% block fab %}
|
||||
<a class="fab" href="{{ path('mahlzeiten_neu') }}" aria-label="Neue Mahlzeit">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true">
|
||||
<path d="M12 5v14M5 12h14"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% endblock %}
|
||||
<main>
|
||||
{% for label, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
@@ -108,5 +429,12 @@
|
||||
</main>
|
||||
<script src="{{ asset('js/htmx.min.js') }}"></script>
|
||||
{% block javascripts %}{% endblock %}
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('{{ asset('sw.js') }}').catch(function () {});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user