diff --git a/assets/css/app.css b/assets/css/app.css
index ec760a2..fd8036a 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -826,6 +826,12 @@ button {
line-height: 1.35;
}
+.sport-choice__card small {
+ color: var(--muted);
+ font-size: 0.82rem;
+ line-height: 1.3;
+}
+
.sport-choice input:checked + .sport-choice__card {
border-color: rgba(139, 228, 255, 0.44);
background: linear-gradient(180deg, rgba(96, 184, 255, 0.18), rgba(255, 255, 255, 0.08));
@@ -1002,6 +1008,12 @@ input[type="range"] {
flex-wrap: wrap;
}
+.section-actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 0.95rem;
+}
+
.primary-button,
.ghost-button,
.button-link {
diff --git a/assets/js/app.js b/assets/js/app.js
index 110f3d2..6410f0b 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -763,11 +763,16 @@
const syncPreview = row => {
const labelInput = row.querySelector('input[data-name-template$="[label]"]');
const iconSelect = row.querySelector('select[data-name-template$="[icon]"]');
+ const locationSelect = row.querySelector('select[data-name-template$="[location]"]');
const previewText = row.querySelector(".sport-pill span:last-child");
const previewImage = row.querySelector(".sport-pill img");
if (previewText) {
- previewText.textContent = (labelInput && labelInput.value.trim()) || "Neue Sportart";
+ const label = (labelInput && labelInput.value.trim()) || "Neue Sportart";
+ const location = locationSelect && locationSelect.value
+ ? locationSelect.options[locationSelect.selectedIndex]?.textContent || ""
+ : "";
+ previewText.textContent = location ? `${label} · ${location}` : label;
}
if (previewImage && iconSelect) {
@@ -808,6 +813,7 @@
const idInput = row.querySelector('input[data-name-template$="[id]"]');
const labelInput = row.querySelector('input[data-name-template$="[label]"]');
const iconSelect = row.querySelector('select[data-name-template$="[icon]"]');
+ const locationSelect = row.querySelector('select[data-name-template$="[location]"]');
const groupInput = row.querySelector('input[data-name-template$="[recovery_group]"]');
const bonusInput = row.querySelector('input[data-name-template$="[bonus_points]"]');
const consecutiveInput = row.querySelector('input[data-name-template$="[allow_consecutive]"]');
@@ -821,6 +827,9 @@
if (iconSelect) {
iconSelect.value = values.icon || "run";
}
+ if (locationSelect) {
+ locationSelect.value = values.location || "";
+ }
if (groupInput) {
groupInput.value = values.recovery_group || "";
}
@@ -845,6 +854,7 @@
id: button.dataset.id || "",
label: button.dataset.label || "",
icon: button.dataset.icon || "run",
+ location: button.dataset.location || "",
recovery_group: button.dataset.recoveryGroup || "",
bonus_points: button.dataset.bonusPoints || "2",
allow_consecutive: button.dataset.allowConsecutive === "1",
diff --git a/src/App.php b/src/App.php
index 24fe773..aad933b 100644
--- a/src/App.php
+++ b/src/App.php
@@ -330,6 +330,7 @@ final class App
'authUser' => $user,
'settings' => $settings,
'sportTypePresets' => $sportTypePresets,
+ 'sportLocationOptions' => sport_location_options(),
'users' => $user['is_admin'] ? $this->users->all() : [],
'maxScore' => $this->scoring->evaluate([
'mood' => 10,
@@ -481,7 +482,12 @@ final class App
'sport' => $entry['sport_minutes'],
'walk' => $entry['walk_minutes'],
'sport_labels' => array_values(array_filter(array_map(
- static fn (array $type): string => (string) ($type['label'] ?? ''),
+ static function (array $type): string {
+ $label = (string) ($type['label'] ?? '');
+ $location = sport_location_label((string) ($type['location'] ?? ''));
+
+ return $location !== '' ? $label . ' · ' . $location : $label;
+ },
$entry['sport_type_meta'] ?? []
))),
'sport_icons' => array_values(array_filter(array_map(
diff --git a/src/Support/Defaults.php b/src/Support/Defaults.php
index 25bf8e0..e95b462 100644
--- a/src/Support/Defaults.php
+++ b/src/Support/Defaults.php
@@ -21,6 +21,7 @@ final class Defaults
'id' => 'running',
'label' => 'Joggen',
'icon' => 'run',
+ 'location' => '',
'recovery_group' => 'joggen',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -29,6 +30,7 @@ final class Defaults
'id' => 'cycling',
'label' => 'Radfahren',
'icon' => 'bike',
+ 'location' => '',
'recovery_group' => 'radfahren',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -37,6 +39,7 @@ final class Defaults
'id' => 'strength',
'label' => 'Krafttraining',
'icon' => 'strength',
+ 'location' => '',
'recovery_group' => 'kraftsport',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -45,6 +48,7 @@ final class Defaults
'id' => 'hiking',
'label' => 'Wandern',
'icon' => 'hike',
+ 'location' => '',
'recovery_group' => 'wandern',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -53,6 +57,7 @@ final class Defaults
'id' => 'swimming',
'label' => 'Schwimmen',
'icon' => 'swim',
+ 'location' => '',
'recovery_group' => 'schwimmen',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -61,6 +66,7 @@ final class Defaults
'id' => 'yoga',
'label' => 'Yoga',
'icon' => 'yoga',
+ 'location' => '',
'recovery_group' => 'yoga',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -69,6 +75,7 @@ final class Defaults
'id' => 'hiit-workout',
'label' => 'HIIT / Workout',
'icon' => 'hiit',
+ 'location' => '',
'recovery_group' => 'hiit',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -77,6 +84,7 @@ final class Defaults
'id' => 'rowing',
'label' => 'Rudern',
'icon' => 'row',
+ 'location' => '',
'recovery_group' => 'rudern',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -85,6 +93,7 @@ final class Defaults
'id' => 'dance',
'label' => 'Tanzen',
'icon' => 'dance',
+ 'location' => '',
'recovery_group' => 'tanzen',
'bonus_points' => 2,
'allow_consecutive' => false,
@@ -93,6 +102,7 @@ final class Defaults
'id' => 'core',
'label' => 'Core',
'icon' => 'core',
+ 'location' => '',
'recovery_group' => 'core',
'bonus_points' => 2,
'allow_consecutive' => true,
diff --git a/src/helpers.php b/src/helpers.php
index 2a1e91a..69044cb 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -240,6 +240,23 @@ function sport_icon_options(): array
];
}
+function sport_location_options(): array
+{
+ return [
+ '' => 'ohne Angabe',
+ 'home' => 'Zuhause',
+ 'away' => 'Auswärts',
+ ];
+}
+
+function sport_location_label(?string $value): string
+{
+ $options = sport_location_options();
+ $value = is_string($value) ? trim($value) : '';
+
+ return $options[$value] ?? '';
+}
+
function normalize_sport_type_id(string $value): string
{
$value = trim(strtr($value, [
@@ -300,6 +317,11 @@ function normalized_sport_types(array $settings): array
$icon = 'run';
}
+ $location = trim((string) ($type['location'] ?? ''));
+ if (!array_key_exists($location, sport_location_options())) {
+ $location = '';
+ }
+
$group = trim((string) ($type['recovery_group'] ?? ''));
if ($group === '') {
$group = $id;
@@ -309,6 +331,7 @@ function normalized_sport_types(array $settings): array
'id' => $id,
'label' => $label,
'icon' => $icon,
+ 'location' => $location,
'recovery_group' => normalize_sport_type_id($group) ?: $id,
'bonus_points' => max(0, min(20, (int) ($type['bonus_points'] ?? 0))),
'allow_consecutive' => !empty($type['allow_consecutive']),
diff --git a/templates/pages/archive.php b/templates/pages/archive.php
index f73e62c..d973503 100644
--- a/templates/pages/archive.php
+++ b/templates/pages/archive.php
@@ -22,7 +22,7 @@
- = e($sportType['label']) ?>
+ = e($sportType['label']) ?>= !empty($sportType['location']) ? ' · ' . e(sport_location_label((string) $sportType['location'])) : '' ?>
@@ -65,7 +65,7 @@
- = e($sportType['label']) ?>
+ = e($sportType['label']) ?>= !empty($sportType['location']) ? ' · ' . e(sport_location_label((string) $sportType['location'])) : '' ?>
diff --git a/templates/pages/options.php b/templates/pages/options.php
index 9c92271..d82d901 100644
--- a/templates/pages/options.php
+++ b/templates/pages/options.php
@@ -82,6 +82,7 @@
data-id="= e($preset['id']) ?>"
data-label="= e($preset['label']) ?>"
data-icon="= e($preset['icon']) ?>"
+ data-location="= e($preset['location'] ?? '') ?>"
data-recovery-group="= e($preset['recovery_group']) ?>"
data-bonus-points="= e((string) $preset['bonus_points']) ?>"
data-allow-consecutive="= !empty($preset['allow_consecutive']) ? '1' : '0' ?>"
@@ -114,10 +115,21 @@
+
+
+
+
Die Erholungsgruppe brauchst du nur, wenn mehrere Varianten dieselbe Belastung teilen sollen, zum Beispiel Krafttraining Zuhause und Krafttraining Auswärts. Wenn du nichts Besonderes einträgst, reicht die Sportart selbst.
+Die Erholungsgruppe ist nur dann nützlich, wenn mehrere Varianten sportlich gleich belastend sind und denselben Bonusrhythmus teilen sollen.
+