Make sleep phase bars proportional

This commit is contained in:
2026-05-19 16:43:33 +02:00
parent 3b2c36c849
commit 0fb8adbb14
3 changed files with 57 additions and 4 deletions
+26 -1
View File
@@ -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 {
+26
View File
@@ -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();
+5 -3
View File
@@ -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
<?php foreach (['deep' => ['Tief', 'deep'], 'rem' => ['REM', 'rem'], 'core' => ['Kern', 'core']] as $phase => [$label, $class]): ?>
<?php $phaseHours = max(0.0, (float) ($sleepPhases[$phase] ?? 0)); ?>
<?php if ($phaseHours <= 0) { continue; } ?>
<span class="sleep-phase-bar__segment sleep-phase-bar__segment--<?= e($class) ?>" style="flex-grow: <?= e((string) max(0.1, $phaseHours)) ?>" title="<?= e($label) ?>: <?= e(format_points($phaseHours)) ?> h">
<?php $phasePercent = $sleepBarTotal > 0 ? max(0.5, min(100, ($phaseHours / $sleepBarTotal) * 100)) : 0; ?>
<span class="sleep-phase-bar__segment sleep-phase-bar__segment--<?= e($class) ?>" style="--sleep-segment-width: <?= e((string) $phasePercent) ?>%" title="<?= e($label) ?>: <?= e(format_points($phaseHours)) ?> h" data-tooltip="<?= e($label) ?>: <?= e(format_points($phaseHours)) ?> h">
<strong><?= e($label) ?></strong> <?= e(format_points($phaseHours)) ?> h
</span>
<?php endforeach; ?>
<?php if ($sleepPhaseRemainder > 0): ?>
<span class="sleep-phase-bar__segment sleep-phase-bar__segment--rest" style="flex-grow: <?= e((string) max(0.1, $sleepPhaseRemainder)) ?>" title="Nicht phasenzugeordnet: <?= e(format_points($sleepPhaseRemainder)) ?> h"></span>
<?php $remainderPercent = $sleepBarTotal > 0 ? max(0.5, min(100, ($sleepPhaseRemainder / $sleepBarTotal) * 100)) : 0; ?>
<span class="sleep-phase-bar__segment sleep-phase-bar__segment--rest" style="--sleep-segment-width: <?= e((string) $remainderPercent) ?>%" title="Bis Ziel-/Skalenende: <?= e(format_points($sleepPhaseRemainder)) ?> h" data-tooltip="Bis Ziel-/Skalenende: <?= e(format_points($sleepPhaseRemainder)) ?> h"></span>
<?php endif; ?>
<span class="sleep-phase-bar__target"><span><?= e(format_points($optimalSleepHours)) ?> h</span></span>
</div>