diff --git a/nouri/__init__.py b/nouri/__init__.py index 152a6ee..1a855f6 100644 --- a/nouri/__init__.py +++ b/nouri/__init__.py @@ -36,6 +36,14 @@ from .main import main_bp WEEKDAY_NAMES = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"] WEEKDAY_SHORT_NAMES = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"] DEFAULT_RELEASE_URL = "https://git.hnz.io/hnzio/nouri-App/releases" +DAYPART_ICON_CLASSES = { + "breakfast": "icon-daypart-breakfast", + "morning-snack": "icon-daypart-morning-snack", + "lunch": "icon-daypart-lunch", + "afternoon-snack": "icon-daypart-afternoon-snack", + "dinner": "icon-daypart-dinner", + "late-snack": "icon-daypart-late-snack", +} def load_secret_key(data_dir: Path) -> str: @@ -149,6 +157,7 @@ def create_app() -> Flask: "push_available": bool(app.config["VAPID_PUBLIC_KEY"] and app.config["VAPID_PRIVATE_KEY"]), "weekday_name": lambda value: WEEKDAY_NAMES[value.weekday()], "weekday_short_name": lambda value: WEEKDAY_SHORT_NAMES[value.weekday()], + "daypart_icon_class": lambda slug: DAYPART_ICON_CLASSES.get(slug, "icon-calendar"), "is_admin": lambda: bool(getattr(g, "user", None)) and g.user["role"] == "admin", "asset_url": asset_url, "image_url": lambda filename, variant="md": image_url( diff --git a/nouri/static/css/styles.css b/nouri/static/css/styles.css index fa97237..b1b4676 100644 --- a/nouri/static/css/styles.css +++ b/nouri/static/css/styles.css @@ -101,7 +101,7 @@ button, background: var(--accent); color: white; cursor: pointer; - transition: transform 160ms ease, background 160ms ease, border-color 160ms ease; + transition: transform 160ms ease, background 160ms ease, border-color 160ms ease, box-shadow 160ms ease; } button:focus-visible, @@ -124,19 +124,64 @@ button:hover, .button.secondary, button.secondary, .ghost-button { - background: transparent; + background: linear-gradient( + 180deg, + color-mix(in srgb, var(--surface-soft) 72%, #fff 28%), + color-mix(in srgb, var(--surface-strong) 82%, #fff 18%) + ); color: var(--text); - border-color: var(--line); + border-color: color-mix(in srgb, var(--accent) 34%, var(--line) 66%); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 20px rgba(225, 181, 138, 0.12); } .button.secondary:hover, button.secondary:hover, .ghost-button:hover { + background: linear-gradient( + 180deg, + color-mix(in srgb, var(--accent-soft) 58%, #fff 42%), + color-mix(in srgb, var(--surface-soft) 88%, #fff 12%) + ); + border-color: color-mix(in srgb, var(--accent) 46%, var(--line) 54%); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.84), + 0 12px 28px rgba(212, 155, 104, 0.16); +} + +[data-theme="dark"] button:not(.secondary):not(.ghost-button), +[data-theme="dark"] .button:not(.secondary):not(.ghost-button) { + background: #d7935f; + color: #201a17; + border-color: rgba(243, 177, 125, 0.28); +} + +[data-theme="dark"] button:not(.secondary):not(.ghost-button):hover, +[data-theme="dark"] .button:not(.secondary):not(.ghost-button):hover { + background: #e0a270; + color: #181311; +} + +[data-theme="dark"] .button.secondary, +[data-theme="dark"] button.secondary, +[data-theme="dark"] .ghost-button { + background: transparent; + color: var(--text); + border-color: var(--line); + box-shadow: none; +} + +[data-theme="dark"] .button.secondary:hover, +[data-theme="dark"] button.secondary:hover, +[data-theme="dark"] .ghost-button:hover { background: var(--accent-soft); + border-color: rgba(243, 177, 125, 0.2); + box-shadow: none; } .page-shell { - width: min(1320px, calc(100% - 2rem)); + width: min(1680px, calc(100% - 2rem)); margin: 1rem auto 2rem; } @@ -233,6 +278,7 @@ h3, } .site-nav a { + flex: 0 0 auto; padding: 0.55rem 0.85rem; border-radius: 999px; color: var(--muted); @@ -265,6 +311,8 @@ h3, column-gap: 1.5rem; row-gap: 0.9rem; align-items: center; + padding-left: 1rem; + padding-right: 1rem; } .desktop-header-main { @@ -279,6 +327,7 @@ h3, grid-column: 2; grid-row: 1; display: flex; + gap: 0.3rem; flex-wrap: nowrap; justify-content: flex-start; align-items: center; @@ -309,6 +358,10 @@ h3, .desktop-actions > * { white-space: nowrap; } + + .desktop-nav a { + padding: 0.5rem 0.74rem; + } } .user-chip, @@ -396,6 +449,14 @@ h3, linear-gradient(180deg, color-mix(in srgb, var(--surface) 86%, #fff 14%), color-mix(in srgb, var(--surface) 80%, #ffe5d2 20%)); } +[data-theme="dark"] .hero { + background: + linear-gradient(135deg, rgba(255, 255, 255, 0.06), transparent 42%), + linear-gradient(180deg, rgba(64, 55, 52, 0.98), rgba(49, 42, 39, 0.99)); + border-color: rgba(243, 177, 125, 0.14); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.24); +} + .hero h1, .page-intro h1, .panel h2 { @@ -614,6 +675,11 @@ h3 { background: var(--sky-soft); } +[data-theme="dark"] .status-idea { + background: rgba(126, 143, 160, 0.24); + color: #ece8e4; +} + .status-soft { background: var(--lilac-soft); } @@ -702,6 +768,10 @@ h3 { gap: 1rem; } +.dashboard-spaced-panel > .panel-head + * { + margin-top: 0.45rem; +} + .template-library-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -1078,6 +1148,14 @@ legend { background: color-mix(in srgb, var(--surface-strong) 84%, #fff 16%); } +[data-theme="dark"] .template-card, +[data-theme="dark"] .template-list-card, +[data-theme="dark"] .suggestion-card { + background: linear-gradient(180deg, rgba(66, 57, 54, 0.98), rgba(54, 47, 44, 0.99)); + border-color: rgba(243, 177, 125, 0.12); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02); +} + .selected-quick-action { background: linear-gradient(180deg, color-mix(in srgb, var(--accent-soft) 82%, #fff 18%), color-mix(in srgb, var(--surface-strong) 82%, #fff 18%)); border-color: color-mix(in srgb, var(--accent) 36%, var(--line) 64%); @@ -1128,6 +1206,12 @@ legend { border: 1px solid var(--line); } +[data-theme="dark"] .hint-chip { + background: linear-gradient(180deg, rgba(77, 68, 64, 0.96), rgba(63, 56, 53, 0.98)); + border-color: rgba(243, 177, 125, 0.12); + color: #f0e8e2; +} + .suggestion-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); @@ -1367,6 +1451,20 @@ legend { margin-bottom: 0.5rem; } +.week-slot-title { + display: inline-flex; + align-items: center; + gap: 0.5rem; + min-width: 0; +} + +.week-slot-title .ui-icon { + width: 1rem; + height: 1rem; + color: var(--accent-strong); + flex: 0 0 auto; +} + .week-slot-head-meta { display: inline-flex; align-items: center; @@ -1607,6 +1705,11 @@ legend { min-width: 5rem; } +.theme-toggle, +.mobile-extra-button[data-theme-toggle] { + gap: 0.55rem; +} + .ui-icon { width: 1rem; height: 1rem; @@ -1651,6 +1754,36 @@ legend { mask-image: url("../icons/fa/calendar-days.svg"); } +.icon-daypart-breakfast { + -webkit-mask-image: url("../icons/dayparts/breakfast.svg"); + mask-image: url("../icons/dayparts/breakfast.svg"); +} + +.icon-daypart-morning-snack { + -webkit-mask-image: url("../icons/dayparts/morning-snack.svg"); + mask-image: url("../icons/dayparts/morning-snack.svg"); +} + +.icon-daypart-lunch { + -webkit-mask-image: url("../icons/dayparts/lunch.svg"); + mask-image: url("../icons/dayparts/lunch.svg"); +} + +.icon-daypart-afternoon-snack { + -webkit-mask-image: url("../icons/dayparts/afternoon-snack.svg"); + mask-image: url("../icons/dayparts/afternoon-snack.svg"); +} + +.icon-daypart-dinner { + -webkit-mask-image: url("../icons/dayparts/dinner.svg"); + mask-image: url("../icons/dayparts/dinner.svg"); +} + +.icon-daypart-late-snack { + -webkit-mask-image: url("../icons/dayparts/late-snack.svg"); + mask-image: url("../icons/dayparts/late-snack.svg"); +} + .icon-archive { -webkit-mask-image: url("../icons/fa/archive.svg"); mask-image: url("../icons/fa/archive.svg"); @@ -1696,6 +1829,16 @@ legend { mask-image: url("../icons/fa/mobile-screen-button.svg"); } +.icon-sun-theme { + -webkit-mask-image: url("../icons/fa/theme-sun.svg"); + mask-image: url("../icons/fa/theme-sun.svg"); +} + +.icon-moon-theme { + -webkit-mask-image: url("../icons/fa/theme-moon.svg"); + mask-image: url("../icons/fa/theme-moon.svg"); +} + .icon-apple-whole { -webkit-mask-image: url("../icons/fa/apple-whole.svg"); mask-image: url("../icons/fa/apple-whole.svg"); @@ -2005,6 +2148,10 @@ legend { .mobile-nav-button { cursor: pointer; font: inherit; + background: transparent; + color: var(--muted); + border: 0; + box-shadow: none; } .mobile-bottom-nav a.active, @@ -2012,6 +2159,52 @@ legend { .mobile-nav-button.is-open { background: var(--accent-soft); color: var(--text); + box-shadow: none; + } + + [data-theme="dark"] .mobile-nav-button.is-open { + background: var(--accent-soft); + color: var(--text); + border: 0; + box-shadow: none; + } + + [data-theme="dark"] .mobile-nav-stack .mobile-nav-button { + background: transparent; + color: var(--muted); + border: 0; + box-shadow: none; + } + + [data-theme="dark"] .mobile-nav-stack .mobile-nav-button.is-open { + background: var(--accent-soft); + color: var(--text); + border: 0; + box-shadow: none; + } + + [data-theme="dark"] .mobile-nav-extension .mobile-extra-button, + [data-theme="dark"] .mobile-nav-extension .mobile-extra-form .mobile-extra-button { + background: transparent; + color: var(--muted); + border: 0; + box-shadow: none; + } + + [data-theme="dark"] .mobile-nav-extension .mobile-extra-button[data-theme-toggle] { + background: transparent; + color: var(--muted); + border: 0; + box-shadow: none; + } + + [data-theme="dark"] .mobile-nav-extension .mobile-extra-button:hover, + [data-theme="dark"] .mobile-nav-extension .mobile-extra-button:focus-visible, + [data-theme="dark"] .mobile-nav-extension .mobile-extra-form .mobile-extra-button:hover, + [data-theme="dark"] .mobile-nav-extension .mobile-extra-form .mobile-extra-button:focus-visible { + background: var(--accent-soft); + color: var(--text); + box-shadow: none; } .mobile-profile-link { diff --git a/nouri/static/icons/dayparts/afternoon-snack.svg b/nouri/static/icons/dayparts/afternoon-snack.svg new file mode 100644 index 0000000..b39951a --- /dev/null +++ b/nouri/static/icons/dayparts/afternoon-snack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nouri/static/icons/dayparts/breakfast.svg b/nouri/static/icons/dayparts/breakfast.svg new file mode 100644 index 0000000..d474915 --- /dev/null +++ b/nouri/static/icons/dayparts/breakfast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nouri/static/icons/dayparts/dinner.svg b/nouri/static/icons/dayparts/dinner.svg new file mode 100644 index 0000000..f976428 --- /dev/null +++ b/nouri/static/icons/dayparts/dinner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nouri/static/icons/dayparts/late-snack.svg b/nouri/static/icons/dayparts/late-snack.svg new file mode 100644 index 0000000..e9a55f6 --- /dev/null +++ b/nouri/static/icons/dayparts/late-snack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nouri/static/icons/dayparts/lunch.svg b/nouri/static/icons/dayparts/lunch.svg new file mode 100644 index 0000000..d020dbd --- /dev/null +++ b/nouri/static/icons/dayparts/lunch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nouri/static/icons/dayparts/morning-snack.svg b/nouri/static/icons/dayparts/morning-snack.svg new file mode 100644 index 0000000..97cd1f9 --- /dev/null +++ b/nouri/static/icons/dayparts/morning-snack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nouri/static/icons/fa/theme-moon.svg b/nouri/static/icons/fa/theme-moon.svg new file mode 100644 index 0000000..474b0bf --- /dev/null +++ b/nouri/static/icons/fa/theme-moon.svg @@ -0,0 +1 @@ + diff --git a/nouri/static/icons/fa/theme-sun.svg b/nouri/static/icons/fa/theme-sun.svg new file mode 100644 index 0000000..c2029dd --- /dev/null +++ b/nouri/static/icons/fa/theme-sun.svg @@ -0,0 +1 @@ + diff --git a/nouri/static/js/theme.js b/nouri/static/js/theme.js index a13d9e4..71c23f5 100644 --- a/nouri/static/js/theme.js +++ b/nouri/static/js/theme.js @@ -12,7 +12,22 @@ root.dataset.theme = finalTheme; toggles().forEach((button) => { - button.textContent = finalTheme === "dark" ? "Hell" : "Dunkel"; + const nextModeLabel = finalTheme === "dark" ? "Hell" : "Dunkel"; + const label = button.querySelector("[data-theme-label]"); + const icon = button.querySelector("[data-theme-icon]"); + + if (label) { + label.textContent = nextModeLabel; + } else { + button.textContent = nextModeLabel; + } + + if (icon) { + icon.classList.toggle("icon-sun-theme", finalTheme === "dark"); + icon.classList.toggle("icon-moon-theme", finalTheme !== "dark"); + } + + button.setAttribute("aria-label", `${nextModeLabel} aktivieren`); }); }; diff --git a/nouri/templates/base.html b/nouri/templates/base.html index 2cae425..217cc7d 100644 --- a/nouri/templates/base.html +++ b/nouri/templates/base.html @@ -53,7 +53,10 @@ {% if g.user %}