From 7b53f6640687c1432f65acf4dd125764048d2100 Mon Sep 17 00:00:00 2001 From: Florian Heinz Date: Mon, 13 Apr 2026 10:52:14 +0200 Subject: [PATCH] feat: polish mobile ui and admin quick task settings --- app/__init__.py | 19 +- app/forms.py | 8 +- app/models.py | 9 +- app/routes/settings.py | 20 +- app/routes/tasks.py | 6 + app/services/app_settings.py | 38 ++- app/static/css/style.css | 402 +++++++++++++++++++++++++++--- app/templates/base.html | 10 +- app/templates/settings/index.html | 44 ++-- app/templates/tasks/calendar.html | 77 +++++- 10 files changed, 559 insertions(+), 74 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 1844a5c..bd3a6e2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + from flask import Flask from config import Config @@ -52,9 +54,16 @@ def create_app(config_class: type[Config] = Config) -> Flask: quick_task_form = QuickTaskForm(prefix="quick") quick_task_config = get_quick_task_config() quick_task_form.effort.choices = [ - (key, f"{values['label']} · {values['points']} Punkte") + (key, values["label"]) for key, values in quick_task_config.items() ] + def asset_version(filename: str) -> int: + path = Path(app.static_folder) / filename + try: + return int(path.stat().st_mtime) + except FileNotFoundError: + return 1 + return { "app_name": app.config["APP_NAME"], "nav_items": [ @@ -65,7 +74,15 @@ def create_app(config_class: type[Config] = Config) -> Flask: ("scoreboard.index", "Highscore", "trophy"), ("settings.index", "Optionen", "gear"), ], + "mobile_nav_items": [ + ("tasks.my_tasks", "Meine Aufgaben", "house"), + ("tasks.all_tasks", "Alle Aufgaben", "list"), + ("tasks.calendar_view", "Kalender", "calendar"), + ("scoreboard.index", "Highscore", "trophy"), + ("settings.index", "Optionen", "gear"), + ], "icon_svg": lambda name: load_icon_svg(name, app.static_folder), + "asset_version": asset_version, "now_local": local_now(), "quick_task_form": quick_task_form, "quick_task_config": quick_task_config, diff --git a/app/forms.py b/app/forms.py index 75f434f..2abfae0 100644 --- a/app/forms.py +++ b/app/forms.py @@ -118,15 +118,19 @@ class QuickTaskForm(FlaskForm): title = StringField("Titel", validators=[DataRequired(), Length(min=2, max=160)]) effort = SelectField( "Aufwand", - choices=[(key, label) for key, label, _ in QUICK_TASK_EFFORTS], + choices=[(key, key) for key, _, _ in QUICK_TASK_EFFORTS], validators=[DataRequired()], ) submit = SubmitField("Schnellaufgabe speichern") class QuickTaskConfigForm(FlaskForm): + fast_label = StringField("Bezeichnung", validators=[DataRequired(), Length(min=2, max=60)]) fast_points = IntegerField("Schnell", validators=[DataRequired(), NumberRange(min=1, max=500)]) + normal_label = StringField("Bezeichnung", validators=[DataRequired(), Length(min=2, max=60)]) normal_points = IntegerField("Normal", validators=[DataRequired(), NumberRange(min=1, max=500)]) + medium_label = StringField("Bezeichnung", validators=[DataRequired(), Length(min=2, max=60)]) medium_points = IntegerField("Dauert etwas", validators=[DataRequired(), NumberRange(min=1, max=500)]) + heavy_label = StringField("Bezeichnung", validators=[DataRequired(), Length(min=2, max=60)]) heavy_points = IntegerField("Aufwendig", validators=[DataRequired(), NumberRange(min=1, max=500)]) - submit = SubmitField("Punkte speichern") + submit = SubmitField("Aufwand speichern") diff --git a/app/models.py b/app/models.py index fe705ef..dc9dbc3 100644 --- a/app/models.py +++ b/app/models.py @@ -89,7 +89,14 @@ class TaskTemplate(TimestampMixin, db.Model): def recurrence_label(self) -> str: if self.recurrence_interval_unit == "none" or not self.recurrence_interval_value: return "Einmalig" - return f"Alle {self.recurrence_interval_value} {self.recurrence_interval_unit}" + units = { + "days": ("Tag", "Tage"), + "weeks": ("Woche", "Wochen"), + "months": ("Monat", "Monate"), + } + singular, plural = units.get(self.recurrence_interval_unit, (self.recurrence_interval_unit, self.recurrence_interval_unit)) + unit_label = singular if self.recurrence_interval_value == 1 else plural + return f"Alle {self.recurrence_interval_value} {unit_label}" class TaskInstance(TimestampMixin, db.Model): diff --git a/app/routes/settings.py b/app/routes/settings.py index f4b98dd..93b7430 100644 --- a/app/routes/settings.py +++ b/app/routes/settings.py @@ -10,7 +10,7 @@ from werkzeug.utils import secure_filename from ..extensions import csrf, db from ..forms import AdminUserForm, QuickTaskConfigForm, SettingsProfileForm from ..models import BadgeDefinition, MonthlyScoreSnapshot, NotificationLog, PushSubscription, TaskInstance, TaskTemplate, User -from ..services.app_settings import get_quick_task_config, set_setting_int +from ..services.app_settings import get_quick_task_config, set_setting_int, set_setting_str from ..services.badges import earned_badges_for_user from ..services.notifications import push_enabled @@ -48,6 +48,16 @@ def index(): form = SettingsProfileForm(original_email=current_user.email, obj=current_user) admin_form = AdminUserForm(prefix="admin") quick_task_config_form = QuickTaskConfigForm(prefix="quickconfig") + quick_task_config = get_quick_task_config() + if request.method == "GET": + quick_task_config_form.fast_label.data = quick_task_config["fast"]["label"] + quick_task_config_form.fast_points.data = quick_task_config["fast"]["points"] + quick_task_config_form.normal_label.data = quick_task_config["normal"]["label"] + quick_task_config_form.normal_points.data = quick_task_config["normal"]["points"] + quick_task_config_form.medium_label.data = quick_task_config["medium"]["label"] + quick_task_config_form.medium_points.data = quick_task_config["medium"]["points"] + quick_task_config_form.heavy_label.data = quick_task_config["heavy"]["label"] + quick_task_config_form.heavy_points.data = quick_task_config["heavy"]["points"] if form.validate_on_submit(): current_user.name = form.name.data.strip() current_user.email = form.email.data.lower().strip() @@ -67,7 +77,7 @@ def index(): form=form, admin_form=admin_form, quick_task_config_form=quick_task_config_form, - quick_task_config=get_quick_task_config(), + quick_task_config=quick_task_config, users=User.query.order_by(User.is_admin.desc(), User.name.asc()).all(), earned_badges=earned_badges_for_user(current_user.id), push_ready=push_enabled(), @@ -144,12 +154,16 @@ def update_quick_task_config(): flash(error, "error") return redirect(url_for("settings.index")) + set_setting_str("quick_task_label_fast", form.fast_label.data) set_setting_int("quick_task_points_fast", form.fast_points.data) + set_setting_str("quick_task_label_normal", form.normal_label.data) set_setting_int("quick_task_points_normal", form.normal_points.data) + set_setting_str("quick_task_label_medium", form.medium_label.data) set_setting_int("quick_task_points_medium", form.medium_points.data) + set_setting_str("quick_task_label_heavy", form.heavy_label.data) set_setting_int("quick_task_points_heavy", form.heavy_points.data) db.session.commit() - flash("Schnellaufgaben-Punkte wurden aktualisiert.", "success") + flash("Schnellaufgaben-Aufwand und Punkte wurden aktualisiert.", "success") return redirect(url_for("settings.index")) diff --git a/app/routes/tasks.py b/app/routes/tasks.py index 18de2cb..2eb747b 100644 --- a/app/routes/tasks.py +++ b/app/routes/tasks.py @@ -193,6 +193,11 @@ def calendar_view(): for task in tasks: tasks_by_day[task.due_date.day].append(task) + mobile_day_groups = [ + {"day": day, "tasks": grouped_tasks} + for day, grouped_tasks in sorted(tasks_by_day.items(), key=lambda item: item[0]) + ] + month_calendar = calendar.Calendar(firstweekday=0).monthdayscalendar(year, month) return render_template( "tasks/calendar.html", @@ -201,6 +206,7 @@ def calendar_view(): current_label=month_label(year, month), month_calendar=month_calendar, tasks_by_day=tasks_by_day, + mobile_day_groups=mobile_day_groups, view=view, tasks=tasks, ) diff --git a/app/services/app_settings.py b/app/services/app_settings.py index 72e5a75..f54813c 100644 --- a/app/services/app_settings.py +++ b/app/services/app_settings.py @@ -5,6 +5,10 @@ from ..models import AppSetting QUICK_TASK_DEFAULTS = { + "quick_task_label_fast": "Schnell", + "quick_task_label_normal": "Normal", + "quick_task_label_medium": "Dauert etwas", + "quick_task_label_heavy": "Aufwendig", "quick_task_points_fast": 4, "quick_task_points_normal": 8, "quick_task_points_medium": 12, @@ -12,10 +16,10 @@ QUICK_TASK_DEFAULTS = { } QUICK_TASK_EFFORTS = [ - ("fast", "Schnell", "quick_task_points_fast"), - ("normal", "Normal", "quick_task_points_normal"), - ("medium", "Dauert etwas", "quick_task_points_medium"), - ("heavy", "Aufwendig", "quick_task_points_heavy"), + ("fast", "quick_task_label_fast", "quick_task_points_fast"), + ("normal", "quick_task_label_normal", "quick_task_points_normal"), + ("medium", "quick_task_label_medium", "quick_task_points_medium"), + ("heavy", "quick_task_label_heavy", "quick_task_points_heavy"), ] @@ -46,12 +50,30 @@ def set_setting_int(key: str, value: int) -> None: setting.value = str(value) +def get_setting_str(key: str, default: str) -> str: + setting = AppSetting.query.filter_by(key=key).first() + if not setting or not setting.value.strip(): + return default + return setting.value.strip() + + +def set_setting_str(key: str, value: str) -> None: + setting = AppSetting.query.filter_by(key=key).first() + normalized = value.strip() + if not setting: + setting = AppSetting(key=key, value=normalized) + db.session.add(setting) + else: + setting.value = normalized + + def get_quick_task_config() -> dict[str, dict]: config: dict[str, dict] = {} - for effort_key, label, setting_key in QUICK_TASK_EFFORTS: + for effort_key, label_key, points_key in QUICK_TASK_EFFORTS: config[effort_key] = { - "label": label, - "setting_key": setting_key, - "points": get_setting_int(setting_key, QUICK_TASK_DEFAULTS[setting_key]), + "label_key": label_key, + "label": get_setting_str(label_key, str(QUICK_TASK_DEFAULTS[label_key])), + "points_key": points_key, + "points": get_setting_int(points_key, int(QUICK_TASK_DEFAULTS[points_key])), } return config diff --git a/app/static/css/style.css b/app/static/css/style.css index af88f5b..02ac7e3 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -48,6 +48,7 @@ } :root { + color-scheme: light; --bg: #eef3ff; --bg-accent: #d8e6ff; --surface: rgba(255, 255, 255, 0.85); @@ -69,6 +70,14 @@ --font-body: "InterLocal", system-ui, sans-serif; --font-heading: "SpaceGroteskLocal", "InterLocal", sans-serif; --safe-bottom: max(24px, env(safe-area-inset-bottom)); + --body-radial-a: rgba(181, 210, 255, 0.85); + --body-radial-b: rgba(255, 221, 196, 0.48); + --body-linear-start: #f8fbff; + --body-linear-mid: #eef3ff; + --body-linear-end: #edf2ff; + --input-bg: rgba(255, 255, 255, 0.86); + --nav-bg: rgba(255, 255, 255, 0.88); + --nav-active-bg: rgba(37, 99, 235, 0.1); } * { @@ -77,17 +86,19 @@ html { min-height: 100%; + overflow-x: hidden; } body { margin: 0; min-height: 100vh; + overflow-x: hidden; font-family: var(--font-body); color: var(--text); background: - radial-gradient(circle at top left, rgba(181, 210, 255, 0.85), transparent 32%), - radial-gradient(circle at top right, rgba(255, 221, 196, 0.48), transparent 32%), - linear-gradient(180deg, #f8fbff 0%, #eef3ff 42%, #edf2ff 100%); + radial-gradient(circle at top left, var(--body-radial-a), transparent 32%), + radial-gradient(circle at top right, var(--body-radial-b), transparent 32%), + linear-gradient(180deg, var(--body-linear-start) 0%, var(--body-linear-mid) 42%, var(--body-linear-end) 100%); } a { @@ -167,6 +178,7 @@ p { .content { display: grid; gap: 24px; + min-width: 0; } .panel, @@ -266,7 +278,7 @@ p { padding: 14px 16px; border-radius: 16px; border: 1px solid var(--border); - background: rgba(255, 255, 255, 0.94); + background: var(--surface-strong); } .flash--success { @@ -287,7 +299,7 @@ p { margin-top: 22px; padding: 18px; border-radius: var(--radius-md); - background: rgba(255, 255, 255, 0.74); + background: var(--surface-soft); border: 1px solid rgba(132, 152, 190, 0.2); } @@ -366,7 +378,7 @@ p { border-radius: var(--radius-md); color: var(--muted); text-align: center; - background: rgba(255, 255, 255, 0.58); + background: var(--surface-soft); } .task-card { @@ -396,7 +408,7 @@ p { gap: 10px; padding: 10px 14px; border-radius: 999px; - background: rgba(37, 99, 235, 0.08); + background: var(--nav-active-bg); color: var(--primary-strong); font-weight: 700; } @@ -408,7 +420,7 @@ p { width: 28px; height: 28px; border-radius: 999px; - background: rgba(255, 255, 255, 0.72); + background: var(--surface-soft); } .status-badge { @@ -537,7 +549,7 @@ p { .icon-button { width: 48px; padding: 0; - background: rgba(255, 255, 255, 0.74); + background: var(--surface-soft); border: 1px solid rgba(132, 152, 190, 0.24); } @@ -571,7 +583,7 @@ p { padding: 14px 16px; border: 1px solid rgba(132, 152, 190, 0.3); border-radius: 16px; - background: rgba(255, 255, 255, 0.86); + background: var(--input-bg); color: var(--text); } @@ -612,28 +624,176 @@ p { justify-content: space-between; align-items: center; gap: 18px; + flex-wrap: wrap; +} + +.panel--toolbar > * { + min-width: 0; } .segmented { display: inline-flex; padding: 4px; border-radius: 18px; - background: rgba(255, 255, 255, 0.74); + background: var(--surface-soft); + min-width: 0; } .segmented a { padding: 10px 16px; border-radius: 14px; color: var(--muted); + min-width: 0; } .segmented .is-active { - background: #fff; + background: var(--surface-strong); color: var(--text); } -.calendar-grid { +.calendar-toolbar-mobile { display: grid; + gap: 14px; + max-width: 100%; + overflow: hidden; +} + +.calendar-toolbar-mobile__header, +.calendar-toolbar-mobile__switch { + width: 100%; +} + +.calendar-toolbar-mobile__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + min-width: 0; +} + +.calendar-toolbar-mobile__header > div:first-child { + min-width: 0; +} + +.calendar-toolbar-mobile__arrows { + display: inline-flex; + align-items: center; + gap: 10px; + flex: 0 0 auto; +} + +.calendar-nav-button { + width: 44px; + min-width: 44px; + height: 44px; + border-radius: 14px; +} + +.calendar-nav-button span { + font-size: 1.7rem; + line-height: 1; +} + +.calendar-toolbar-mobile__switch a { + width: 100%; + min-width: 0; +} + +.calendar-toolbar-mobile__switch { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.calendar-toolbar-desktop { + display: none; +} + +.calendar-mobile-strip { + padding: 14px 16px; + max-width: 100%; + overflow: hidden; +} + +.calendar-mobile-strip__scroller { + display: flex; + gap: 10px; + overflow-x: auto; + padding-bottom: 4px; + scroll-snap-type: x proximity; + max-width: 100%; +} + +.calendar-mobile-pill { + flex: 0 0 auto; + min-width: 84px; + padding: 12px 14px; + border-radius: 18px; + border: 1px solid var(--border); + background: var(--surface-strong); + display: grid; + gap: 3px; + scroll-snap-align: start; + box-shadow: 0 10px 20px rgba(52, 79, 131, 0.08); +} + +.calendar-mobile-pill strong { + font-size: 1rem; + line-height: 1; +} + +.calendar-mobile-pill small { + color: var(--muted); + font-size: 0.74rem; + white-space: nowrap; +} + +.calendar-mobile-pill.is-active { + border-color: rgba(37, 99, 235, 0.16); + background: linear-gradient(135deg, rgba(37, 99, 235, 0.14), rgba(52, 211, 153, 0.1)); +} + +.calendar-mobile-list { + display: grid; + gap: 14px; + max-width: 100%; +} + +.calendar-mobile-day { + padding: 18px; + max-width: 100%; + overflow: hidden; +} + +.calendar-mobile-day__header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 10px; + margin-bottom: 14px; +} + +.calendar-mobile-day__header strong { + font-size: 1.12rem; +} + +.calendar-mobile-day__header span { + color: var(--muted); + font-size: 0.82rem; + white-space: nowrap; +} + +.calendar-mobile-day__tasks { + display: grid; + gap: 10px; + min-width: 0; +} + +.calendar-mobile-day .calendar-task { + background: var(--surface-strong); +} + +.calendar-grid { + display: none; gap: 12px; } @@ -666,9 +826,10 @@ p { display: grid; gap: 4px; min-width: 0; + max-width: 100%; padding: 9px 10px; border-radius: 14px; - background: rgba(255, 255, 255, 0.72); + background: var(--surface-soft); overflow: hidden; } @@ -762,7 +923,7 @@ p { } .badge-card--earned { - background: rgba(37, 99, 235, 0.03); + background: var(--nav-active-bg); } .badge-card__icon { @@ -793,7 +954,7 @@ p { gap: 10px; padding: 6px; border-radius: 22px; - background: rgba(255, 255, 255, 0.7); + background: var(--surface-soft); border: 1px solid rgba(132, 152, 190, 0.18); box-shadow: 0 12px 30px rgba(58, 82, 128, 0.1); } @@ -809,7 +970,7 @@ p { } .settings-tab.is-active { - background: #fff; + background: var(--surface-strong); color: var(--primary-strong); box-shadow: 0 10px 20px rgba(37, 99, 235, 0.08); } @@ -830,7 +991,7 @@ p { gap: 14px; padding: 18px; border-radius: var(--radius-md); - background: rgba(255, 255, 255, 0.76); + background: var(--surface-soft); border: 1px solid rgba(132, 152, 190, 0.22); } @@ -850,7 +1011,7 @@ p { align-items: flex-start; padding: 16px; border-radius: 18px; - background: rgba(255, 255, 255, 0.66); + background: var(--surface-soft); } .push-box__state.is-disabled { @@ -876,16 +1037,15 @@ p { .bottom-nav { position: fixed; - left: 50%; - transform: translateX(-50%); - width: min(calc(100vw - 52px), 560px); + left: 8px; + right: 8px; bottom: calc(10px + env(safe-area-inset-bottom)); display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 6px; - padding: 8px; - border-radius: 24px; - background: rgba(255, 255, 255, 0.88); + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 4px; + padding: 8px 8px; + border-radius: 22px; + background: var(--nav-bg); backdrop-filter: blur(16px); box-shadow: 0 24px 44px rgba(58, 82, 128, 0.2); border: 1px solid rgba(132, 152, 190, 0.22); @@ -897,19 +1057,29 @@ p { display: grid; justify-items: center; gap: 5px; - padding: 10px 4px; + min-width: 0; + padding: 10px 4px 9px; color: var(--muted); border-radius: 16px; text-align: center; - font-size: 0.69rem; + font-size: 0.66rem; font-weight: 700; - line-height: 1.1; + line-height: 1.05; +} + +.bottom-nav__item span { + display: block; + width: 100%; + min-height: 2.1em; + white-space: normal; + word-break: keep-all; + overflow-wrap: break-word; } .bottom-nav__item.is-active, .nav-link.is-active { color: var(--primary-strong); - background: rgba(37, 99, 235, 0.1); + background: var(--nav-active-bg); } .nav-icon, @@ -934,7 +1104,7 @@ p { width: min(460px, calc(100vw - 24px)); padding: 24px; border-radius: 28px; - background: #fff; + background: var(--surface-strong); box-shadow: var(--shadow); display: grid; gap: 18px; @@ -962,15 +1132,15 @@ p { .fab-quick-task { position: fixed; - right: 18px; - bottom: calc(96px + env(safe-area-inset-bottom)); + right: max(16px, env(safe-area-inset-right)); + bottom: calc(72px + var(--safe-bottom)); width: 62px; height: 62px; - border: 0; + border: 2px solid rgba(255, 255, 255, 0.55); border-radius: 999px; background: linear-gradient(135deg, #2563eb, #34d399); color: #fff; - box-shadow: 0 24px 40px rgba(37, 99, 235, 0.28); + box-shadow: 0 24px 40px rgba(37, 99, 235, 0.34); display: inline-flex; align-items: center; justify-content: center; @@ -984,7 +1154,128 @@ p { height: 24px; } +@media (max-width: 759px) { + .calendar-toolbar-mobile__header { + align-items: flex-start; + } + + .calendar-toolbar-mobile__header h2 { + font-size: 1.5rem; + } +} + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --bg: #0b1220; + --bg-accent: #15233d; + --surface: rgba(17, 24, 39, 0.82); + --surface-strong: rgba(15, 23, 38, 0.96); + --surface-soft: rgba(20, 30, 49, 0.92); + --text: #e5eefc; + --muted: #9eb0cc; + --border: rgba(121, 146, 191, 0.2); + --primary: #5ea8ff; + --primary-strong: #d7e7ff; + --secondary: rgba(37, 99, 235, 0.16); + --shadow: 0 28px 70px rgba(0, 0, 0, 0.34); + --body-radial-a: rgba(37, 99, 235, 0.28); + --body-radial-b: rgba(20, 184, 166, 0.14); + --body-linear-start: #0b1220; + --body-linear-mid: #0f172a; + --body-linear-end: #131f35; + --input-bg: rgba(16, 24, 38, 0.92); + --nav-bg: rgba(15, 23, 38, 0.9); + --nav-active-bg: rgba(59, 130, 246, 0.18); + } + + .hero-card { + background: + linear-gradient(135deg, rgba(16, 24, 38, 0.98), rgba(20, 30, 49, 0.92)), + linear-gradient(135deg, rgba(37, 99, 235, 0.1), rgba(20, 184, 166, 0.06)); + } + + .hero-stats div { + background: var(--surface-soft); + } + + .icon-button { + border-color: rgba(121, 146, 191, 0.24); + } + + .flash--success { + color: #7df1c7; + } + + .flash--error { + color: #ff94b0; + } + + .status-badge--open { + background: rgba(37, 99, 235, 0.16); + color: #8db7ff; + } + + .status-badge--soon { + background: rgba(245, 158, 11, 0.18); + color: #ffd38a; + } + + .status-badge--overdue { + background: rgba(225, 29, 72, 0.18); + color: #ff9cb2; + } + + .status-badge--completed { + background: rgba(5, 150, 105, 0.18); + color: #8df0cb; + } + + .button--ghost { + border-color: rgba(121, 146, 191, 0.24); + } + + .score-row--leader { + background: + linear-gradient(135deg, rgba(18, 29, 49, 0.98), rgba(24, 39, 62, 0.94)), + linear-gradient(135deg, rgba(52, 211, 153, 0.12), rgba(94, 168, 255, 0.08)); + border-color: rgba(94, 168, 255, 0.2); + } + + .score-row--leader .rank-badge, + .score-row--leader .earned-badge { + background: rgba(94, 168, 255, 0.16); + color: var(--primary-strong); + } + + .score-row--leader .earned-badge__icon { + background: rgba(10, 18, 32, 0.56); + } + + .score-row__points span, + .archive-row__right span { + color: var(--muted); + } + + .sidebar { + background: rgba(10, 16, 28, 0.76); + border-right-color: rgba(121, 146, 191, 0.16); + } + + .topbar-user { + border-color: rgba(121, 146, 191, 0.2); + } +} + @media (min-width: 760px) { + .calendar-toolbar-mobile { + display: none; + } + + .calendar-toolbar-desktop { + display: flex; + } + .page-shell { padding: 28px 28px 32px; } @@ -1005,8 +1296,15 @@ p { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .calendar-mobile-strip, + .calendar-mobile-list { + display: none; + } + .calendar-grid { + display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 10px; } .calendar-grid__weekdays { @@ -1026,6 +1324,34 @@ p { background: transparent; border: 0; } + + .calendar-day { + display: flex; + flex-direction: column; + min-height: 152px; + padding: 12px 10px; + } + + .calendar-day__tasks { + gap: 6px; + max-height: 100%; + overflow: auto; + padding-right: 2px; + } + + .calendar-task { + padding: 8px 9px; + border-radius: 12px; + } + + .calendar-task__title { + font-size: 0.82rem; + line-height: 1.18; + } + + .calendar-task__person { + font-size: 0.7rem; + } } @media (min-width: 1100px) { @@ -1043,7 +1369,7 @@ p { height: 100vh; padding: 28px 20px; border-right: 1px solid rgba(132, 152, 190, 0.2); - background: rgba(248, 251, 255, 0.72); + background: var(--surface-soft); backdrop-filter: blur(12px); } @@ -1070,7 +1396,7 @@ p { gap: 10px; padding: 8px 10px 8px 18px; border-radius: 999px; - background: rgba(255, 255, 255, 0.85); + background: var(--nav-bg); border: 1px solid rgba(132, 152, 190, 0.2); } diff --git a/app/templates/base.html b/app/templates/base.html index d9841bf..6e3615a 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -3,7 +3,9 @@ - + + + @@ -12,7 +14,7 @@ - + {% from "partials/macros.html" import nav_icon %} @@ -89,7 +91,7 @@ {% if current_user.is_authenticated %}