first commit

This commit is contained in:
2026-04-11 18:57:00 +02:00
commit 58bcc8f0f3
29 changed files with 3290 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
<section class="page-grid">
<article class="glass-panel archive-list">
<div class="section-head">
<div>
<p class="eyebrow">Archiv</p>
<h3>Alle gespeicherten Tage</h3>
</div>
<span class="chart-chip"><?= e((string) count($entries)) ?> Eintraege</span>
</div>
<?php if ($entries === []): ?>
<p class="empty-state">Noch keine Eintraege vorhanden. Auf der Tracking-Seite kannst du den ersten Tag anlegen.</p>
<?php else: ?>
<div class="archive-items">
<?php foreach ($entries as $entry): ?>
<a class="archive-item <?= $selectedEntry !== null && $selectedEntry['date'] === $entry['date'] ? 'active' : '' ?>" href="/archive?date=<?= e(rawurlencode($entry['date'])) ?>">
<div>
<strong><?= e($entry['date']) ?></strong>
<span><?= e($entry['evaluation']['label']) ?></span>
</div>
<div class="archive-item__meta">
<span><?= e(format_points((float) $entry['evaluation']['total'])) ?></span>
<span>Stimmung <?= e((string) $entry['mood']) ?>/10</span>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</article>
<aside class="stack-column">
<?php if ($selectedEntry !== null): ?>
<article class="glass-panel detail-card">
<p class="eyebrow">Ausgewaehlt</p>
<h3><?= e($selectedEntry['date']) ?></h3>
<p class="hero-label"><?= e($selectedEntry['evaluation']['label']) ?> · <?= e(format_points((float) $selectedEntry['evaluation']['total'])) ?> Punkte</p>
<dl class="detail-grid">
<div><dt>Stimmung</dt><dd><?= e((string) $selectedEntry['mood']) ?>/10</dd></div>
<div><dt>Energie</dt><dd><?= e((string) $selectedEntry['energy']) ?>/10</dd></div>
<div><dt>Stress</dt><dd><?= e((string) $selectedEntry['stress']) ?>/10</dd></div>
<div><dt>Schlaf</dt><dd><?= e((string) $selectedEntry['sleep_hours']) ?> h</dd></div>
<div><dt>Schlafgefuehl</dt><dd><?= e((string) $selectedEntry['sleep_feeling']) ?>/5</dd></div>
<div><dt>Sport</dt><dd><?= e((string) $selectedEntry['sport_minutes']) ?> min</dd></div>
<div><dt>Spaziergang</dt><dd><?= e((string) $selectedEntry['walk_minutes']) ?> min</dd></div>
</dl>
<div class="note-box">
<h4>Notiz</h4>
<p><?= nl2br(e($selectedEntry['note'] !== '' ? $selectedEntry['note'] : 'Keine Notiz hinterlegt.')) ?></p>
</div>
</article>
<?php else: ?>
<article class="glass-panel detail-card">
<p class="eyebrow">Details</p>
<h3>Archivansicht</h3>
<p>Waehle links einen Tag aus, um alle Werte und die Tagebuchnotiz anzuzeigen.</p>
</article>
<?php endif; ?>
</aside>
</section>
+87
View File
@@ -0,0 +1,87 @@
<section class="hero-grid">
<article class="hero-card hero-card--wide glass-panel">
<p class="eyebrow">Stimmung im Blick</p>
<h3>Dein Dashboard verbindet Verlauf, Score und Bewegungsdaten in einer schnellen Uebersicht.</h3>
<p class="hero-copy">Die Bewertung basiert auf deiner konfigurierbaren Logik. Sobald du die Regeln in den Optionen aenderst, spiegeln Archiv und Statistiken die neue Gewichtung wider.</p>
</article>
<article class="hero-card glass-panel">
<p class="eyebrow">Heute</p>
<?php if ($summary['today'] !== null): ?>
<div class="hero-score"><?= e(format_points((float) $summary['today']['evaluation']['total'])) ?></div>
<p class="hero-label"><?= e($summary['today']['evaluation']['label']) ?></p>
<?php else: ?>
<div class="hero-score">-</div>
<p class="hero-label">Noch kein Eintrag fuer heute</p>
<?php endif; ?>
</article>
</section>
<section class="stats-grid">
<article class="metric-card glass-panel">
<span>Getrackte Tage</span>
<strong><?= e((string) $summary['tracked_days']) ?></strong>
</article>
<article class="metric-card glass-panel">
<span>Ø Score</span>
<strong><?= e(format_points((float) $summary['average_score'])) ?></strong>
</article>
<article class="metric-card glass-panel">
<span>Ø Stimmung</span>
<strong><?= e(format_points((float) $summary['average_mood'])) ?>/10</strong>
</article>
<article class="metric-card glass-panel">
<span>Ø Stress</span>
<strong><?= e(format_points((float) $summary['average_stress'])) ?>/10</strong>
</article>
<article class="metric-card glass-panel">
<span>Serie</span>
<strong><?= e((string) $summary['streak']) ?> Tage</strong>
</article>
</section>
<section class="dashboard-grid">
<article class="glass-panel chart-card chart-card--calendar">
<div class="section-head">
<div>
<p class="eyebrow">Kalender</p>
<h3>Gesamtstimmung pro Tag</h3>
</div>
</div>
<div id="calendar-heatmap" class="calendar-heatmap" data-payload="<?= e($chartPayload) ?>"></div>
</article>
<article class="glass-panel chart-card">
<div class="section-head">
<div>
<p class="eyebrow">Trend</p>
<h3>Tagesstimmung</h3>
</div>
<span class="chart-chip">letzte 30 Eintraege</span>
</div>
<div class="line-chart" data-chart-type="line" data-series="mood" data-payload="<?= e($chartPayload) ?>"></div>
</article>
<article class="glass-panel chart-card">
<div class="section-head">
<div>
<p class="eyebrow">Belastung</p>
<h3>Stressverlauf</h3>
</div>
<span class="chart-chip chart-chip--warm">letzte 30 Eintraege</span>
</div>
<div class="line-chart" data-chart-type="line" data-series="stress" data-payload="<?= e($chartPayload) ?>"></div>
</article>
<article class="glass-panel chart-card chart-card--wide">
<div class="section-head">
<div>
<p class="eyebrow">Aktivitaet</p>
<h3>Sport und Spaziergang</h3>
</div>
<span class="chart-chip chart-chip--cool">Minuten pro Tag</span>
</div>
<div class="bar-chart" data-chart-type="bars" data-series="sport" data-payload="<?= e($chartPayload) ?>"></div>
</article>
</section>
+23
View File
@@ -0,0 +1,23 @@
<section class="auth-shell">
<div class="auth-card glass-panel">
<p class="eyebrow">Geschuetzt und dateibasiert</p>
<h1>Einloggen</h1>
<p class="auth-copy">Die Eintraege liegen als Markdown-TXT-Dateien im geschuetzten Speicher und sind nur nach Login sichtbar.</p>
<form method="post" action="/login" class="stack-form">
<?= csrf_field() ?>
<label>
<span>Benutzername</span>
<input type="text" name="username" autocomplete="username" required>
</label>
<label>
<span>Passwort</span>
<input type="password" name="password" autocomplete="current-password" required>
</label>
<button class="primary-button" type="submit">Anmelden</button>
</form>
</div>
</section>
+9
View File
@@ -0,0 +1,9 @@
<section class="auth-shell">
<div class="auth-card glass-panel">
<p class="eyebrow">404</p>
<h1>Diese Seite gibt es nicht</h1>
<p class="auth-copy">Der angeforderte Bereich wurde nicht gefunden.</p>
<a class="primary-button button-link" href="/">Zur Startseite</a>
</div>
</section>
+139
View File
@@ -0,0 +1,139 @@
<section class="page-grid">
<article class="glass-panel form-panel form-panel--wide">
<div class="section-head">
<div>
<p class="eyebrow">Bewertungslogik</p>
<h3>Score und Schutzregeln anpassen</h3>
</div>
<span class="chart-chip">Maximal <?= e(format_points((float) $maxScore)) ?> Punkte</span>
</div>
<form method="post" action="/options" class="stack-form stack-form--spacious">
<?= csrf_field() ?>
<input type="hidden" name="form_name" value="settings">
<div class="settings-section">
<h4>Multiplikatoren</h4>
<div class="field-grid field-grid--four">
<label><span>Stimmung</span><input type="number" name="settings[scoring][mood_multiplier]" value="<?= e((string) $settings['scoring']['mood_multiplier']) ?>" min="0" max="10"></label>
<label><span>Energie</span><input type="number" name="settings[scoring][energy_multiplier]" value="<?= e((string) $settings['scoring']['energy_multiplier']) ?>" min="0" max="10"></label>
<label><span>Stress</span><input type="number" name="settings[scoring][stress_multiplier]" value="<?= e((string) $settings['scoring']['stress_multiplier']) ?>" min="0" max="10"></label>
<label><span>Schlafgefuehl</span><input type="number" name="settings[scoring][sleep_feeling_multiplier]" value="<?= e((string) $settings['scoring']['sleep_feeling_multiplier']) ?>" min="0" max="10"></label>
</div>
</div>
<div class="settings-section">
<h4>Schlafdauerpunkte</h4>
<div class="field-grid field-grid--four">
<?php foreach ($settings['scoring']['sleep_duration_points'] as $key => $value): ?>
<label>
<span><?= e($key) ?></span>
<input type="number" name="settings[scoring][sleep_duration_points][<?= e($key) ?>]" value="<?= e((string) $value) ?>" min="0" max="20">
</label>
<?php endforeach; ?>
</div>
</div>
<div class="settings-section">
<h4>Sport-Baender</h4>
<div class="band-grid">
<?php foreach ($settings['scoring']['sport_bands'] as $index => $band): ?>
<div class="band-card">
<label><span>Min</span><input type="number" name="settings[scoring][sport_bands][<?= e((string) $index) ?>][min]" value="<?= e((string) $band['min']) ?>"></label>
<label><span>Max</span><input type="number" name="settings[scoring][sport_bands][<?= e((string) $index) ?>][max]" value="<?= e((string) $band['max']) ?>"></label>
<label><span>Punkte</span><input type="number" name="settings[scoring][sport_bands][<?= e((string) $index) ?>][points]" value="<?= e((string) $band['points']) ?>"></label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="settings-section">
<h4>Spaziergang-Baender</h4>
<div class="band-grid">
<?php foreach ($settings['scoring']['walk_bands'] as $index => $band): ?>
<div class="band-card">
<label><span>Min</span><input type="number" name="settings[scoring][walk_bands][<?= e((string) $index) ?>][min]" value="<?= e((string) $band['min']) ?>"></label>
<label><span>Max</span><input type="number" name="settings[scoring][walk_bands][<?= e((string) $index) ?>][max]" value="<?= e((string) $band['max']) ?>"></label>
<label><span>Punkte</span><input type="number" name="settings[scoring][walk_bands][<?= e((string) $index) ?>][points]" value="<?= e((string) $band['points']) ?>"></label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="settings-section">
<h4>Bewertungsskala</h4>
<div class="band-grid">
<?php foreach ($settings['ratings'] as $index => $rating): ?>
<div class="band-card">
<label><span>Label</span><input type="text" name="settings[ratings][<?= e((string) $index) ?>][label]" value="<?= e($rating['label']) ?>"></label>
<label><span>Min</span><input type="number" name="settings[ratings][<?= e((string) $index) ?>][min]" value="<?= e((string) $rating['min']) ?>"></label>
<label><span>Max</span><input type="number" name="settings[ratings][<?= e((string) $index) ?>][max]" value="<?= e((string) $rating['max']) ?>"></label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="settings-section">
<h4>Schutzregeln</h4>
<div class="band-grid">
<?php foreach ($settings['guardrails'] as $index => $guardrail): ?>
<div class="band-card">
<label><span>Stimmung max</span><input type="number" name="settings[guardrails][<?= e((string) $index) ?>][mood_max]" value="<?= e((string) $guardrail['mood_max']) ?>" min="1" max="10"></label>
<label><span>Energie max</span><input type="number" name="settings[guardrails][<?= e((string) $index) ?>][energy_max]" value="<?= e((string) ($guardrail['energy_max'] ?? '')) ?>" min="1" max="10"></label>
<label><span>Maximales Label</span><input type="text" name="settings[guardrails][<?= e((string) $index) ?>][cap_label]" value="<?= e($guardrail['cap_label']) ?>"></label>
</div>
<?php endforeach; ?>
</div>
</div>
<label>
<span>Tagebuchpunkte bei nicht-leerer Notiz</span>
<input type="number" name="settings[scoring][journal_points]" value="<?= e((string) $settings['scoring']['journal_points']) ?>" min="0" max="20">
</label>
<button class="primary-button" type="submit">Bewertung speichern</button>
</form>
</article>
<aside class="stack-column">
<article class="glass-panel detail-card">
<p class="eyebrow">Sicherheit</p>
<h3>Passwort aendern</h3>
<form method="post" action="/options" class="stack-form">
<?= csrf_field() ?>
<input type="hidden" name="form_name" value="password">
<label><span>Aktuelles Passwort</span><input type="password" name="current_password" required></label>
<label><span>Neues Passwort</span><input type="password" name="new_password" minlength="10" required></label>
<label><span>Neues Passwort wiederholen</span><input type="password" name="new_password_confirm" minlength="10" required></label>
<button class="primary-button" type="submit">Passwort aktualisieren</button>
</form>
</article>
<?php if (!empty($authUser['is_admin'])): ?>
<article class="glass-panel detail-card">
<p class="eyebrow">Mehrere Accounts</p>
<h3>Neuen Nutzer anlegen</h3>
<form method="post" action="/options" class="stack-form">
<?= csrf_field() ?>
<input type="hidden" name="form_name" value="create_user">
<label><span>Benutzername</span><input type="text" name="username" required></label>
<label><span>Startpasswort</span><input type="password" name="password" minlength="10" required></label>
<label class="checkbox-row"><input type="checkbox" name="is_admin" value="1"><span>Als Admin anlegen</span></label>
<button class="primary-button" type="submit">Account erstellen</button>
</form>
<?php if ($users !== []): ?>
<div class="user-list">
<?php foreach ($users as $account): ?>
<div class="user-row">
<strong><?= e($account['username']) ?></strong>
<span><?= !empty($account['is_admin']) ? 'Admin' : 'Nutzer' ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</article>
<?php endif; ?>
</aside>
</section>
+28
View File
@@ -0,0 +1,28 @@
<section class="auth-shell">
<div class="auth-card glass-panel">
<p class="eyebrow">Erste Einrichtung</p>
<h1>Mood initialisieren</h1>
<p class="auth-copy">Lege den ersten Admin-Account an. Danach ist die Anwendung sofort geschuetzt und weitere Accounts koennen spaeter in den Optionen erstellt werden.</p>
<form method="post" action="/setup" class="stack-form">
<?= csrf_field() ?>
<label>
<span>Admin-Benutzername</span>
<input type="text" name="username" autocomplete="username" required>
</label>
<label>
<span>Passwort</span>
<input type="password" name="password" autocomplete="new-password" minlength="10" required>
</label>
<label>
<span>Passwort wiederholen</span>
<input type="password" name="password_confirm" autocomplete="new-password" minlength="10" required>
</label>
<button class="primary-button" type="submit">Setup abschliessen</button>
</form>
</div>
</section>
+105
View File
@@ -0,0 +1,105 @@
<section class="page-grid">
<article class="glass-panel form-panel form-panel--wide">
<div class="section-head">
<div>
<p class="eyebrow">Tag erfassen</p>
<h3>Eintrag fuer <?= e($entry['date']) ?></h3>
</div>
<a class="ghost-link" href="/archive?date=<?= e(rawurlencode($entry['date'])) ?>">Im Archiv ansehen</a>
</div>
<form method="post" action="/track" class="tracker-form" id="tracker-form">
<?= csrf_field() ?>
<div class="field-grid field-grid--single">
<label>
<span>Datum</span>
<input type="date" name="date" value="<?= e($entry['date']) ?>" required>
</label>
</div>
<div class="field-grid field-grid--three">
<label class="range-card">
<span>Stimmung</span>
<output data-output-for="mood"><?= e((string) $entry['mood']) ?></output>
<input type="range" min="1" max="10" step="1" name="mood" value="<?= e((string) $entry['mood']) ?>">
</label>
<label class="range-card">
<span>Energie</span>
<output data-output-for="energy"><?= e((string) $entry['energy']) ?></output>
<input type="range" min="1" max="10" step="1" name="energy" value="<?= e((string) $entry['energy']) ?>">
</label>
<label class="range-card">
<span>Stress</span>
<output data-output-for="stress"><?= e((string) $entry['stress']) ?></output>
<input type="range" min="1" max="10" step="1" name="stress" value="<?= e((string) $entry['stress']) ?>">
</label>
</div>
<div class="field-grid field-grid--two">
<label>
<span>Schlafdauer in Stunden</span>
<input type="number" min="0" max="24" step="0.25" name="sleep_hours" value="<?= e((string) $entry['sleep_hours']) ?>" required>
</label>
<label>
<span>Schlafgefuehl</span>
<select name="sleep_feeling">
<?php foreach ($settings['labels']['sleep_feeling'] as $value => $label): ?>
<option value="<?= e((string) $value) ?>" <?= (int) $entry['sleep_feeling'] === (int) $value ? 'selected' : '' ?>>
<?= e((string) $value) ?> · <?= e($label) ?>
</option>
<?php endforeach; ?>
</select>
</label>
</div>
<div class="field-grid field-grid--two">
<label>
<span>Sport in Minuten</span>
<input type="number" min="0" max="1440" step="1" name="sport_minutes" value="<?= e((string) $entry['sport_minutes']) ?>" required>
</label>
<label>
<span>Spaziergang in Minuten</span>
<input type="number" min="0" max="1440" step="1" name="walk_minutes" value="<?= e((string) $entry['walk_minutes']) ?>" required>
</label>
</div>
<label>
<span>Tagebuchnotiz</span>
<textarea name="note" rows="8" placeholder="Was war heute wichtig, schwer oder schoen?"><?= e($entry['note']) ?></textarea>
</label>
<div class="form-actions">
<a class="ghost-link" href="/track?date=<?= e(today()) ?>">Heute laden</a>
<button class="primary-button" type="submit">Tag speichern</button>
</div>
</form>
</article>
<aside class="stack-column">
<article class="glass-panel preview-card" id="live-score-card" data-payload="<?= e($trackPayload) ?>">
<p class="eyebrow">Live-Bewertung</p>
<div class="hero-score" data-preview-total><?= e(format_points((float) $evaluation['total'])) ?></div>
<p class="hero-label" data-preview-label><?= e($evaluation['label']) ?></p>
<p class="helper-text">Die Einschaetzung passt sich beim Aendern der Werte sofort an.</p>
<dl class="component-list" data-preview-components>
<?php foreach ($evaluation['components'] as $name => $value): ?>
<div>
<dt><?= e($name) ?></dt>
<dd><?= e(format_points((float) $value)) ?></dd>
</div>
<?php endforeach; ?>
</dl>
</article>
<article class="glass-panel info-card">
<p class="eyebrow">Dateiformat</p>
<h3>Markdown in `YYYY-MM-DD.txt`</h3>
<p>Jeder Tag wird als menschenlesbare Datei gespeichert. Das macht Backups, Portabilitaet und manuelle Kontrolle einfach.</p>
</article>
</aside>
</section>