From 0fb8adbb14a4fa45f4479be6e92cb9e31101eae4 Mon Sep 17 00:00:00 2001 From: Florian Heinz Date: Tue, 19 May 2026 16:43:33 +0200 Subject: [PATCH] Make sleep phase bars proportional --- assets/css/app.css | 27 ++++++++++++++++++++++++++- assets/js/app.js | 26 ++++++++++++++++++++++++++ templates/pages/dashboard.php | 8 +++++--- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/assets/css/app.css b/assets/css/app.css index 992b235..f0bf3c4 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -926,13 +926,38 @@ body.page-dashboard .content { justify-content: center; gap: 0.25rem; min-width: 0; - flex-basis: 0; + flex: 0 0 var(--sleep-segment-width, auto); padding: 0.35rem 0.65rem; color: rgba(255, 255, 255, 0.94); font-size: 0.82rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + position: relative; +} + +.sleep-phase-bar__segment::after { + content: attr(data-tooltip); + position: fixed; + left: 50%; + bottom: calc(1.2rem + env(safe-area-inset-bottom)); + z-index: 1400; + max-width: min(18rem, calc(100vw - 2rem)); + padding: 0.55rem 0.75rem; + border-radius: 999px; + background: rgba(6, 16, 28, 0.92); + color: #fff; + box-shadow: 0 12px 34px rgba(0, 0, 0, 0.32); + opacity: 0; + pointer-events: none; + transform: translateX(-50%) translateY(0.35rem); + transition: opacity 140ms ease, transform 140ms ease; +} + +.sleep-phase-bar__segment:hover::after, +.sleep-phase-bar__segment.is-tooltip-visible::after { + opacity: 1; + transform: translateX(-50%) translateY(0); } .sleep-phase-bar__segment--deep { diff --git a/assets/js/app.js b/assets/js/app.js index 59a65d4..a7eec20 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1703,6 +1703,31 @@ }); } + function initSleepPhaseTooltips() { + document.querySelectorAll(".sleep-phase-bar__segment[data-tooltip]").forEach(segment => { + segment.addEventListener("click", event => { + event.preventDefault(); + event.stopPropagation(); + document.querySelectorAll(".sleep-phase-bar__segment.is-tooltip-visible").forEach(active => { + if (active !== segment) { + active.classList.remove("is-tooltip-visible"); + } + }); + segment.classList.toggle("is-tooltip-visible"); + }); + }); + + document.addEventListener("click", event => { + if (event.target.closest(".sleep-phase-bar__segment[data-tooltip]")) { + return; + } + + document.querySelectorAll(".sleep-phase-bar__segment.is-tooltip-visible").forEach(segment => { + segment.classList.remove("is-tooltip-visible"); + }); + }); + } + function csrfToken() { return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || ""; } @@ -2036,6 +2061,7 @@ initOptionsPanels(); initHealthImportStatus(); initMediaLightbox(); + initSleepPhaseTooltips(); initSportTypeManager(); initPwaShell(); initPullToRefresh(); diff --git a/templates/pages/dashboard.php b/templates/pages/dashboard.php index 77e64b9..4b954b2 100644 --- a/templates/pages/dashboard.php +++ b/templates/pages/dashboard.php @@ -115,7 +115,7 @@ $optimalSleepHours = max(1.0, min(16.0, (float) ($settings['sleep']['optimal_hou } } $sleepPhaseTotal = max(0.0, array_sum($sleepPhases)); - $sleepBarTotal = $eventType === 'sleep' ? max((float) ($item['value'] ?? 0), $sleepPhaseTotal, $optimalSleepHours) : 0.0; + $sleepBarTotal = $eventType === 'sleep' ? max((float) ($item['value'] ?? 0), $sleepPhaseTotal, $optimalSleepHours / 0.75) : 0.0; $sleepPhaseRemainder = max(0.0, $sleepBarTotal - $sleepPhaseTotal); $sleepOptimalPercent = $sleepBarTotal > 0 ? max(0, min(100, ($optimalSleepHours / $sleepBarTotal) * 100)) : 0; ?> @@ -191,12 +191,14 @@ $optimalSleepHours = max(1.0, min(16.0, (float) ($settings['sleep']['optimal_hou ['Tief', 'deep'], 'rem' => ['REM', 'rem'], 'core' => ['Kern', 'core']] as $phase => [$label, $class]): ?> - + 0 ? max(0.5, min(100, ($phaseHours / $sleepBarTotal) * 100)) : 0; ?> + h 0): ?> - + 0 ? max(0.5, min(100, ($sleepPhaseRemainder / $sleepBarTotal) * 100)) : 0; ?> + h