diff --git a/README.md b/README.md index 3c67eb9..6997eeb 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ # Mood -Dateibasierter Stimmungstracker fuer LAMP/Cloudron ohne Datenbank. +Dateibasierter Stimmungstracker für LAMP/Cloudron ohne Datenbank. ## Features -- Geschuetzter Login mit Session, `password_hash`, CSRF-Schutz und Security-Headern +- Geschützter Login mit Session, `password_hash`, CSRF-Schutz und Security-Headern - Vier Bereiche: Dashboard, Tracking, Optionen, Archiv - Speicherung aller Tage als Markdown in `storage/users//days/YYYY-MM-DD.txt` -- Pro Nutzer eigene Einstellungen fuer die Bewertungslogik -- Admin kann weitere Accounts direkt in der Weboberflaeche anlegen +- Pro Nutzer eigene Einstellungen für die Bewertungslogik +- Admin kann weitere Accounts direkt in der Weboberfläche anlegen - Moderner, responsiver Liquid-Glass-Look mit lokalen Assets und ohne externe CDNs ## Struktur - `index.php`: Front-Controller und Routing-Einstieg -- `src/`: PHP-Logik fuer Auth, Storage, Scoring und Rendering +- `src/`: PHP-Logik für Auth, Storage, Scoring und Rendering - `templates/`: Seiten-Templates - `assets/`: CSS und JavaScript -- `storage/`: geschuetzter Dateispeicher, per `.htaccess` nicht direkt abrufbar +- `storage/`: geschützter Dateispeicher, per `.htaccess` nicht direkt abrufbar ## Deployment auf Cloudron / LAMP @@ -30,5 +30,5 @@ Dateibasierter Stimmungstracker fuer LAMP/Cloudron ohne Datenbank. ## Hinweise - Die Inhalte liegen absichtlich nicht in einer Datenbank, sondern in menschenlesbaren TXT-Dateien. -- Mehrere Accounts sind moeglich und verursachen hier wenig Overhead, weil jeder Nutzer nur einen eigenen Unterordner mit Tagen und Einstellungen bekommt. -- Wenn du spaeter Reverse Proxy oder HTTPS ueber Cloudron nutzt, bleiben die Daten weiterhin nur ueber die App erreichbar. +- Mehrere Accounts sind möglich und verursachen hier wenig Overhead, weil jeder Nutzer nur einen eigenen Unterordner mit Tagen und Einstellungen bekommt. +- Wenn du später Reverse Proxy oder HTTPS über Cloudron nutzt, bleiben die Daten weiterhin nur über die App erreichbar. diff --git a/assets/css/app.css b/assets/css/app.css index b421f75..f808696 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -19,6 +19,9 @@ --radius-sm: 14px; --panel-blur: 28px; --font-ui: "SF Pro Display", "Avenir Next", "Segoe UI Variable", "Helvetica Neue", system-ui, sans-serif; + --track-accent: rgba(139, 228, 255, 0.34); + --track-surface: rgba(255, 255, 255, 0.08); + --track-glow: rgba(139, 228, 255, 0.18); } *, @@ -110,7 +113,7 @@ button { .sidebar { display: flex; flex-direction: column; - justify-content: space-between; + justify-content: flex-start; padding: 1.35rem; border-radius: var(--radius-xl); min-height: calc(100vh - 2.5rem); @@ -153,6 +156,24 @@ button { letter-spacing: 0.01em; } +.meta-pill--button { + cursor: pointer; +} + +.topbar-date-form { + display: inline-flex; +} + +.topbar-date-input { + width: auto; + min-height: 2.2rem; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 999px; + padding: 0.45rem 0.9rem; + background: rgba(255, 255, 255, 0.08); + color: var(--text); +} + .chart-chip--warm { background: rgba(255, 173, 124, 0.12); } @@ -209,6 +230,9 @@ button { } .main-nav a { + display: flex; + align-items: center; + gap: 0.8rem; padding: 0.9rem 1rem; border-radius: 18px; color: var(--muted); @@ -225,6 +249,14 @@ button { .sidebar-footer { display: grid; gap: 0.85rem; + margin-top: auto; + padding-top: 1.2rem; +} + +.nav-icon { + width: 1.1rem; + height: 1.1rem; + opacity: 0.9; } .user-chip { @@ -497,6 +529,13 @@ button { gap: 1rem; } +.section-head__actions { + display: flex; + gap: 0.7rem; + align-items: center; + flex-wrap: wrap; +} + .field-grid--single { grid-template-columns: 1fr; } @@ -560,8 +599,9 @@ input[type="range"] { .range-card { padding: 1rem; border-radius: var(--radius-md); - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--track-surface); + border: 1px solid var(--track-accent); + transition: border-color 220ms ease, background 220ms ease, transform 220ms ease, box-shadow 220ms ease; } .range-card output { @@ -570,6 +610,66 @@ input[type="range"] { font-weight: 700; } +.preview-card { + position: relative; + overflow: hidden; + border-color: var(--track-accent); + box-shadow: 0 24px 70px rgba(4, 18, 31, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.preview-card::before { + content: ""; + position: absolute; + inset: auto -12% -25% auto; + width: 14rem; + height: 14rem; + border-radius: 50%; + background: radial-gradient(circle, var(--track-glow), transparent 70%); + pointer-events: none; + transition: background 220ms ease, transform 220ms ease; +} + +.preview-status { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 0.8rem; +} + +.preview-status__icon { + width: 4.5rem; + height: 4.5rem; + border-radius: 20px; + display: grid; + place-items: center; + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.18); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12); +} + +.preview-status__icon img { + width: 2.3rem; + height: 2.3rem; +} + +.preview-status__label { + margin: 0; + font-size: 1.55rem; + font-weight: 750; +} + +.preview-scoreline { + margin: 0 0 1rem; + color: var(--muted); + font-size: 0.95rem; +} + +.preview-scoreline span { + color: var(--text); + font-size: 1.2rem; + font-weight: 700; +} + .component-list, .detail-grid { display: grid; @@ -684,6 +784,17 @@ input[type="range"] { text-align: right; } +.archive-item__actions { + display: flex; + gap: 0.55rem; + flex-wrap: wrap; +} + +.archive-action { + min-height: 2.4rem; + padding-inline: 0.85rem; +} + .note-box { padding: 1rem; border-radius: 18px; @@ -740,6 +851,68 @@ input[type="range"] { width: fit-content; } +.page-track[data-track-mood="storm"] { + --track-accent: rgba(255, 143, 143, 0.38); + --track-surface: rgba(255, 143, 143, 0.08); + --track-glow: rgba(255, 126, 126, 0.22); +} + +.page-track[data-track-mood="heavy"] { + --track-accent: rgba(255, 191, 141, 0.34); + --track-surface: rgba(255, 191, 141, 0.08); + --track-glow: rgba(255, 191, 141, 0.18); +} + +.page-track[data-track-mood="steady"] { + --track-accent: rgba(139, 228, 255, 0.34); + --track-surface: rgba(255, 255, 255, 0.08); + --track-glow: rgba(139, 228, 255, 0.18); +} + +.page-track[data-track-mood="bright"] { + --track-accent: rgba(143, 243, 198, 0.34); + --track-surface: rgba(143, 243, 198, 0.08); + --track-glow: rgba(143, 243, 198, 0.2); +} + +.page-track[data-track-mood="radiant"] { + --track-accent: rgba(255, 233, 140, 0.35); + --track-surface: rgba(255, 233, 140, 0.1); + --track-glow: rgba(255, 233, 140, 0.22); +} + +.page-track[data-track-mood] .aurora-one, +.page-track[data-track-mood] .aurora-two, +.page-track[data-track-mood] .range-card, +.page-track[data-track-mood] .preview-card, +.page-track[data-track-mood] .preview-card::before { + transition: background 220ms ease, opacity 220ms ease, transform 220ms ease, border-color 220ms ease, box-shadow 220ms ease; +} + +.page-track[data-track-mood="storm"] .aurora-one, +.page-track[data-track-mood="storm"] .aurora-two { + opacity: 0.5; + background: radial-gradient(circle, rgba(255, 128, 128, 0.32), transparent 70%); +} + +.page-track[data-track-mood="heavy"] .aurora-one, +.page-track[data-track-mood="heavy"] .aurora-two { + opacity: 0.55; + background: radial-gradient(circle, rgba(255, 192, 128, 0.3), transparent 70%); +} + +.page-track[data-track-mood="bright"] .aurora-one, +.page-track[data-track-mood="bright"] .aurora-two { + opacity: 0.72; + background: radial-gradient(circle, rgba(127, 243, 187, 0.3), transparent 70%); +} + +.page-track[data-track-mood="radiant"] .aurora-one, +.page-track[data-track-mood="radiant"] .aurora-two { + opacity: 0.78; + background: radial-gradient(circle, rgba(255, 228, 122, 0.32), transparent 70%); +} + @media (max-width: 1100px) { .shell { grid-template-columns: 1fr; @@ -778,6 +951,12 @@ input[type="range"] { overflow-x: auto; padding-bottom: 0.4rem; } + + .archive-item, + .preview-status { + flex-direction: column; + align-items: flex-start; + } } @media (max-width: 640px) { diff --git a/assets/icons/archive.svg b/assets/icons/archive.svg new file mode 100644 index 0000000..81231fe --- /dev/null +++ b/assets/icons/archive.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/dashboard.svg b/assets/icons/dashboard.svg new file mode 100644 index 0000000..2e5b2b0 --- /dev/null +++ b/assets/icons/dashboard.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/mood-bright.svg b/assets/icons/mood-bright.svg new file mode 100644 index 0000000..4e50ed7 --- /dev/null +++ b/assets/icons/mood-bright.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/mood-heavy.svg b/assets/icons/mood-heavy.svg new file mode 100644 index 0000000..2776cd5 --- /dev/null +++ b/assets/icons/mood-heavy.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/mood-radiant.svg b/assets/icons/mood-radiant.svg new file mode 100644 index 0000000..7a4a06d --- /dev/null +++ b/assets/icons/mood-radiant.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/mood-steady.svg b/assets/icons/mood-steady.svg new file mode 100644 index 0000000..843347b --- /dev/null +++ b/assets/icons/mood-steady.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/mood-storm.svg b/assets/icons/mood-storm.svg new file mode 100644 index 0000000..cc1fb19 --- /dev/null +++ b/assets/icons/mood-storm.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/options.svg b/assets/icons/options.svg new file mode 100644 index 0000000..c03db60 --- /dev/null +++ b/assets/icons/options.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/track.svg b/assets/icons/track.svg new file mode 100644 index 0000000..8c0fb08 --- /dev/null +++ b/assets/icons/track.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/js/app.js b/assets/js/app.js index a26204c..1364b7d 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -34,6 +34,10 @@ return value || fallback; } + function moodIconPath(sentiment) { + return `/assets/icons/mood-${sentiment}.svg`; + } + function updateRangeOutputs() { document.querySelectorAll("[data-output-for]").forEach(output => { const input = document.querySelector(`[name="${output.dataset.outputFor}"]`); @@ -50,6 +54,17 @@ }); } + function initHeaderDatePicker() { + document.querySelectorAll(".topbar-date-input").forEach(input => { + input.addEventListener("change", () => { + const form = input.closest("form"); + if (form) { + form.submit(); + } + }); + }); + } + function sleepDurationPoints(hours, points) { if (hours < 4) { return Number(points.lt4 || 0); @@ -123,6 +138,35 @@ return currentIndex > capIndex ? cap : current; } + function sentimentForLabel(label, ratings) { + const order = ratings.map(item => item.label); + const index = order.indexOf(label); + + if (index === -1 || order.length <= 1) { + return "steady"; + } + + const ratio = index / Math.max(order.length - 1, 1); + + if (ratio <= 0.1) { + return "storm"; + } + + if (ratio <= 0.35) { + return "heavy"; + } + + if (ratio <= 0.65) { + return "steady"; + } + + if (ratio <= 0.9) { + return "bright"; + } + + return "radiant"; + } + function evaluateEntry(entry, settings) { const ratings = sortedRatings(settings.ratings || []); const scoring = settings.scoring || {}; @@ -151,7 +195,7 @@ } } - return { total, label, components }; + return { total, label, components, sentiment: sentimentForLabel(label, ratings) }; } function initTrackPreview() { @@ -169,6 +213,7 @@ const totalNode = card.querySelector("[data-preview-total]"); const labelNode = card.querySelector("[data-preview-label]"); + const iconNode = card.querySelector("[data-preview-icon]"); const componentsNode = card.querySelector("[data-preview-components]"); const componentLabels = { mood: "Stimmung", @@ -196,6 +241,9 @@ const result = evaluateEntry(collect(), payload.settings); totalNode.textContent = formatNumber(result.total); labelNode.textContent = result.label; + iconNode.src = moodIconPath(result.sentiment); + card.dataset.sentiment = result.sentiment; + document.body.dataset.trackMood = result.sentiment; componentsNode.innerHTML = Object.entries(result.components).map(([key, value]) => { return `
${componentLabels[key] || key}
${formatNumber(Number(value))}
`; }).join(""); @@ -419,6 +467,7 @@ } updateRangeOutputs(); + initHeaderDatePicker(); initTrackPreview(); initDashboardCharts(); })(); diff --git a/src/App.php b/src/App.php index b5e16c7..c140c8b 100644 --- a/src/App.php +++ b/src/App.php @@ -118,7 +118,7 @@ final class App } if ($password !== $passwordConfirm) { - flash('error', 'Die Passwoerter stimmen nicht ueberein.'); + flash('error', 'Die Passwörter stimmen nicht überein.'); redirect('/setup'); } @@ -153,12 +153,12 @@ final class App if (!$this->auth->attempt($username, $password)) { $this->throttle->hit($throttleKey); - flash('error', 'Login fehlgeschlagen. Bitte pruefe Benutzername und Passwort.'); + flash('error', 'Login fehlgeschlagen. Bitte prüfe Benutzername und Passwort.'); redirect('/login'); } $this->throttle->clear($throttleKey); - flash('success', 'Willkommen zurueck.'); + flash('success', 'Willkommen zurück.'); redirect('/'); } @@ -219,6 +219,8 @@ final class App 'entry' => $entry, 'evaluation' => $evaluation, 'settings' => $settings, + 'trackMood' => $evaluation['sentiment'], + 'topbarDate' => $entry['date'], 'trackPayload' => encode_payload([ 'settings' => $settings, 'entry' => $entry, @@ -246,7 +248,7 @@ final class App ]); if (!$this->isValidDate($entry['date'])) { - flash('error', 'Bitte waehle ein gueltiges Datum.'); + flash('error', 'Bitte wähle ein gültiges Datum.'); redirect('/track'); } @@ -344,7 +346,7 @@ final class App } if ($new !== $confirm) { - flash('error', 'Die neuen Passwoerter stimmen nicht ueberein.'); + flash('error', 'Die neuen Passwörter stimmen nicht überein.'); redirect('/options'); } @@ -359,7 +361,7 @@ final class App $isAdmin = isset($_POST['is_admin']) && $_POST['is_admin'] === '1'; if (!$this->isValidUsername($username)) { - flash('error', 'Bitte nutze fuer neue Accounts einen sauberen Benutzernamen.'); + flash('error', 'Bitte nutze für neue Accounts einen sauberen Benutzernamen.'); redirect('/options'); } @@ -538,7 +540,7 @@ final class App { if (!verify_csrf($_POST['_token'] ?? null)) { http_response_code(419); - exit('Ungueltiges Formular-Token.'); + exit('Ungültiges Formular-Token.'); } } diff --git a/src/Domain/EntryRepository.php b/src/Domain/EntryRepository.php index 3f56293..786c05d 100644 --- a/src/Domain/EntryRepository.php +++ b/src/Domain/EntryRepository.php @@ -68,7 +68,7 @@ final class EntryRepository 'energy' => (int) ($this->extract('/^- Energie:\s*(.+)$/m', $content) ?? 5), 'stress' => (int) ($this->extract('/^- Stress:\s*(.+)$/m', $content) ?? 5), 'sleep_hours' => (float) ($this->extract('/^- Schlafdauer:\s*(.+)$/m', $content) ?? 0), - 'sleep_feeling' => (int) ($this->extract('/^- Schlafgefuehl:\s*(.+)$/m', $content) ?? 3), + 'sleep_feeling' => (int) ($this->extract('/^- Schlaf(?:gefühl|gefuehl):\s*(.+)$/mu', $content) ?? 3), 'sport_minutes' => (int) ($this->extract('/^- Sport:\s*(.+)$/m', $content) ?? 0), 'walk_minutes' => (int) ($this->extract('/^- Spaziergang:\s*(.+)$/m', $content) ?? 0), 'note' => $this->extractNote($content), @@ -112,7 +112,7 @@ final class EntryRepository '- Energie: ' . $entry['energy'], '- Stress: ' . $entry['stress'], '- Schlafdauer: ' . $entry['sleep_hours'], - '- Schlafgefuehl: ' . $entry['sleep_feeling'], + '- Schlafgefühl: ' . $entry['sleep_feeling'], '- Sport: ' . $entry['sport_minutes'], '- Spaziergang: ' . $entry['walk_minutes'], '', @@ -125,7 +125,7 @@ final class EntryRepository '- Energie: ' . format_points((float) $evaluation['components']['energy']), '- Stress: ' . format_points((float) $evaluation['components']['stress']), '- Schlafdauer: ' . format_points((float) $evaluation['components']['sleep_hours']), - '- Schlafgefuehl: ' . format_points((float) $evaluation['components']['sleep_feeling']), + '- Schlafgefühl: ' . format_points((float) $evaluation['components']['sleep_feeling']), '- Sport: ' . format_points((float) $evaluation['components']['sport_minutes']), '- Spaziergang: ' . format_points((float) $evaluation['components']['walk_minutes']), '- Notiz: ' . format_points((float) $evaluation['components']['note']), diff --git a/src/Domain/ScoringService.php b/src/Domain/ScoringService.php index 5b126b4..3bf8673 100644 --- a/src/Domain/ScoringService.php +++ b/src/Domain/ScoringService.php @@ -72,6 +72,7 @@ final class ScoringService 'max_total' => $maxTotal, 'label' => $label, 'guardrail' => $guardrail, + 'sentiment' => $this->sentimentForLabel($label, $ratings), 'percentage' => $maxTotal > 0 ? round(($total / $maxTotal) * 100, 1) : 0, ]; } @@ -168,5 +169,24 @@ final class ScoringService return $currentIndex > $capIndex ? $cap : $current; } -} + private function sentimentForLabel(string $label, array $ratings): string + { + $order = array_values(array_map(static fn (array $rating): string => (string) $rating['label'], $ratings)); + $index = array_search($label, $order, true); + + if ($index === false || count($order) <= 1) { + return 'steady'; + } + + $ratio = $index / max(count($order) - 1, 1); + + return match (true) { + $ratio <= 0.1 => 'storm', + $ratio <= 0.35 => 'heavy', + $ratio <= 0.65 => 'steady', + $ratio <= 0.9 => 'bright', + default => 'radiant', + }; + } +} diff --git a/src/Domain/UserRepository.php b/src/Domain/UserRepository.php index 8ec2461..523ab1b 100644 --- a/src/Domain/UserRepository.php +++ b/src/Domain/UserRepository.php @@ -56,7 +56,7 @@ final class UserRepository $normalized = normalize_username($username); if ($normalized === '' || $this->find($normalized) !== null) { - throw new RuntimeException('Benutzername existiert bereits oder ist ungueltig.'); + throw new RuntimeException('Benutzername existiert bereits oder ist ungültig.'); } $users = $this->all(); @@ -100,4 +100,3 @@ final class UserRepository ); } } - diff --git a/src/helpers.php b/src/helpers.php index 791b179..569388a 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -122,3 +122,56 @@ function encode_payload(array $payload): string return base64_encode((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } +function shift_date(string $date, int $days): string +{ + $current = DateTimeImmutable::createFromFormat('Y-m-d', $date); + + if ($current === false) { + return today(); + } + + return $current->modify(($days >= 0 ? '+' : '') . $days . ' day')->format('Y-m-d'); +} + +function format_display_date(string $date, bool $withWeekday = true): string +{ + $current = DateTimeImmutable::createFromFormat('Y-m-d', $date); + + if ($current === false) { + return $date; + } + + $weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; + $months = [ + 1 => 'Januar', + 2 => 'Februar', + 3 => 'März', + 4 => 'April', + 5 => 'Mai', + 6 => 'Juni', + 7 => 'Juli', + 8 => 'August', + 9 => 'September', + 10 => 'Oktober', + 11 => 'November', + 12 => 'Dezember', + ]; + + $label = $current->format('j.') . ' ' . $months[(int) $current->format('n')] . ' ' . $current->format('Y'); + + if (!$withWeekday) { + return $label; + } + + return $weekdays[(int) $current->format('w')] . ', ' . $label; +} + +function icon_path(string $name): string +{ + return '/assets/icons/' . $name . '.svg'; +} + +function mood_icon_path(string $sentiment): string +{ + return icon_path('mood-' . $sentiment); +} diff --git a/templates/layout.php b/templates/layout.php index 393fd4a..a0682fc 100644 --- a/templates/layout.php +++ b/templates/layout.php @@ -5,9 +5,9 @@ declare(strict_types=1); $brandSubtitle = match ($page) { 'dashboard' => 'Statistiken und Verlauf', 'track' => 'Tag erfassen und bewerten', - 'archive' => 'Rueckblick auf vergangene Tage', + 'archive' => 'Rückblick auf vergangene Tage', 'options' => 'Logik, Sicherheit und Accounts', - 'login' => 'Geschuetzter Zugang', + 'login' => 'Geschützter Zugang', 'setup' => 'Erstkonfiguration', default => 'Stimmungstracker', }; @@ -21,7 +21,7 @@ $brandSubtitle = match ($page) { - +>
@@ -30,16 +30,28 @@ $brandSubtitle = match ($page) {
M
-

mood.hnz.io

+

mood.heinz.media

Mood

- + + Vorheriger Tag +
+ +
+ Nächster Tag + + +
@@ -78,4 +98,3 @@ $brandSubtitle = match ($page) {
- diff --git a/templates/pages/archive.php b/templates/pages/archive.php index dff2d90..139ac6b 100644 --- a/templates/pages/archive.php +++ b/templates/pages/archive.php @@ -5,24 +5,28 @@

Archiv

Alle gespeicherten Tage

- Eintraege + Einträge -

Noch keine Eintraege vorhanden. Auf der Tracking-Seite kannst du den ersten Tag anlegen.

+

Noch keine Einträge vorhanden. Auf der Tracking-Seite kannst du den ersten Tag anlegen.

- +
@@ -31,16 +35,17 @@ - diff --git a/templates/pages/dashboard.php b/templates/pages/dashboard.php index cb3db2e..1304882 100644 --- a/templates/pages/dashboard.php +++ b/templates/pages/dashboard.php @@ -1,8 +1,8 @@

Stimmung im Blick

-

Dein Dashboard verbindet Verlauf, Score und Bewegungsdaten in einer schnellen Uebersicht.

-

Die Bewertung basiert auf deiner konfigurierbaren Logik. Sobald du die Regeln in den Optionen aenderst, spiegeln Archiv und Statistiken die neue Gewichtung wider.

+

Dein Dashboard verbindet Verlauf, Score und Bewegungsdaten in einer schnellen Übersicht.

+

Die Bewertung basiert auf deiner konfigurierbaren Logik. Sobald du die Regeln in den Optionen änderst, spiegeln Archiv und Statistiken die neue Gewichtung wider.

@@ -12,7 +12,7 @@

-
-

Noch kein Eintrag fuer heute

+

Noch kein Eintrag für heute

@@ -57,7 +57,7 @@

Trend

Tagesstimmung

- letzte 30 Eintraege + letzte 30 Einträge
@@ -68,7 +68,7 @@

Belastung

Stressverlauf

- letzte 30 Eintraege + letzte 30 Einträge
@@ -76,7 +76,7 @@
-

Aktivitaet

+

Aktivität

Sport und Spaziergang

Minuten pro Tag @@ -84,4 +84,3 @@
- diff --git a/templates/pages/login.php b/templates/pages/login.php index a5ecb28..ab9c620 100644 --- a/templates/pages/login.php +++ b/templates/pages/login.php @@ -1,8 +1,8 @@
-

Geschuetzt und dateibasiert

+

Geschützt und dateibasiert

Einloggen

-

Die Eintraege liegen als Markdown-TXT-Dateien im geschuetzten Speicher und sind nur nach Login sichtbar.

+

Die Einträge liegen als Markdown-TXT-Dateien im geschützten Speicher und sind nur nach Login sichtbar.

@@ -20,4 +20,3 @@
- diff --git a/templates/pages/not-found.php b/templates/pages/not-found.php index 114c1f8..d0db1d7 100644 --- a/templates/pages/not-found.php +++ b/templates/pages/not-found.php @@ -6,4 +6,3 @@ Zur Startseite - diff --git a/templates/pages/options.php b/templates/pages/options.php index 62493a0..4ffe92d 100644 --- a/templates/pages/options.php +++ b/templates/pages/options.php @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@
-

Sport-Baender

+

Sport-Bänder

$band): ?>
@@ -48,7 +48,7 @@
-

Spaziergang-Baender

+

Spaziergang-Bänder

$band): ?>
@@ -98,7 +98,7 @@ - diff --git a/templates/pages/setup.php b/templates/pages/setup.php index a3f6cfe..7e044f6 100644 --- a/templates/pages/setup.php +++ b/templates/pages/setup.php @@ -2,7 +2,7 @@

Erste Einrichtung

Mood initialisieren

-

Lege den ersten Admin-Account an. Danach ist die Anwendung sofort geschuetzt und weitere Accounts koennen spaeter in den Optionen erstellt werden.

+

Lege den ersten Admin-Account an. Danach ist die Anwendung sofort geschützt und weitere Accounts können später in den Optionen erstellt werden.

@@ -21,8 +21,7 @@ - +
- diff --git a/templates/pages/track.php b/templates/pages/track.php index b8af43e..6c74805 100644 --- a/templates/pages/track.php +++ b/templates/pages/track.php @@ -3,20 +3,17 @@

Tag erfassen

-

Eintrag fuer

+

+
+ - Im Archiv ansehen
- -
- -
+
- Heute laden + Werte ändern, speichern und bei Bedarf vergangene Tage bequem nachtragen.