fix(fitness): measure page polish — stable steppers, narrow history, body-parts chrome tweaks
CI / update (push) Successful in 4m5s

- Lock +/- button positions by normalizing stepped weight/body-fat
  values to .toFixed(1) so trailing zeros stay; placeholders also
  normalized. Input width no longer jitters through a step sequence.
- Cap .history-section width on mobile/tablet to match .main-col
  (480px / 760px) so "Past measurements" aligns with the metric cards.
- Body-parts page:
  - Remove the "Running totals" list from the right panel.
  - Hide the keyboard-shortcut legend by default; show on `?` (toggle)
    or Escape (dismiss), with a small `?` pill hint in its place.
    Added kbd_hint i18n string.
  - Push skip + back/next toward the edges of the bottombar; pull
    progress dots + close button inward symmetrically.
  - Center the keyboard legend / hint on the screen width rather than
    between the skip and nav buttons (position: absolute + translate).
This commit is contained in:
2026-04-23 11:29:33 +02:00
parent def176db4d
commit 0a188ad4ab
4 changed files with 67 additions and 37 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "1.46.0",
"version": "1.46.1",
"private": true,
"type": "module",
"scripts": {
+1
View File
@@ -297,6 +297,7 @@ const translations: Translations = {
kbd_next: { en: 'next', de: 'weiter' },
kbd_skip: { en: 'skip', de: 'auslassen' },
kbd_wheel: { en: '\u00b10.1', de: '\u00b10,1' },
kbd_hint: { en: 'Press ? for shortcuts', de: '? dr\u00fccken f\u00fcr Tastenk\u00fcrzel' },
no_body_parts_selected: {
en: 'Enter at least one value before saving.',
de: 'Bitte mindestens einen Wert eingeben.'
@@ -186,12 +186,12 @@
function stepWeight(delta) {
const cur = Number(formWeight) || lastWeight || 0;
formWeight = String(Math.round((cur + delta) * 10) / 10);
formWeight = (Math.round((cur + delta) * 10) / 10).toFixed(1);
}
function stepBodyFat(delta) {
const cur = Number(formBodyFat) || lastBodyFat || 0;
formBodyFat = String(Math.round((cur + delta) * 10) / 10);
formBodyFat = (Math.round((cur + delta) * 10) / 10).toFixed(1);
}
/**
@@ -393,7 +393,7 @@
type="number"
step="0.1"
bind:value={formWeight}
placeholder={lastWeight != null ? String(lastWeight) : '0.0'}
placeholder={lastWeight != null ? Number(lastWeight).toFixed(1) : '0.0'}
class="metric-input"
inputmode="decimal"
onkeydown={(e) => onMetricKey(e, stepWeight)}
@@ -424,7 +424,7 @@
type="number"
step="0.1"
bind:value={formBodyFat}
placeholder={lastBodyFat != null ? String(lastBodyFat) : '0.0'}
placeholder={lastBodyFat != null ? Number(lastBodyFat).toFixed(1) : '0.0'}
class="metric-input"
inputmode="decimal"
onkeydown={(e) => onMetricKey(e, stepBodyFat)}
@@ -588,6 +588,11 @@
margin-inline: auto;
width: 100%;
}
.history-section {
max-width: 480px;
margin-inline: auto;
width: 100%;
}
h2 {
margin: 0 0 0.5rem;
font-size: 1.1rem;
@@ -1314,7 +1319,8 @@
/* Weight + body fat side-by-side once there's room (tablet and up) */
@media (min-width: 560px) {
.main-col,
.add-form {
.add-form,
.history-section {
max-width: 760px;
}
.metric-grid {
@@ -140,14 +140,18 @@
return v ? `${v} cm` : '—';
}
let showShortcuts = $state(false);
/** @param {KeyboardEvent} e */
function onkey(e) {
const tag = /** @type {HTMLElement|null} */ (e.target)?.tagName;
const inInput = tag === 'INPUT';
if (e.key === '?' && !inInput) { e.preventDefault(); showShortcuts = !showShortcuts; return; }
if (e.key === 'Escape' && showShortcuts) { e.preventDefault(); showShortcuts = false; return; }
if (done) {
if (e.key === 'ArrowLeft') { e.preventDefault(); idx = total - 1; direction = -1; }
return;
}
const tag = /** @type {HTMLElement|null} */ (e.target)?.tagName;
const inInput = tag === 'INPUT';
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); next(); }
else if (e.key === 'ArrowRight' && !inInput) { e.preventDefault(); next(); }
else if (e.key === 'ArrowLeft' && !inInput) { e.preventDefault(); back(); }
@@ -477,7 +481,7 @@
{/if}
</main>
<aside class="panel" aria-label={t('running_totals', lang)}>
<aside class="panel">
{#if !done}
{#key step.key}
<div class="panel-section chart-section" in:fade={{ duration: 180 }}>
@@ -549,32 +553,29 @@
{/key}
{/if}
<div class="panel-section totals-section">
<div class="panel-head">
<h3 class="panel-title">{t('running_totals', lang)}</h3>
</div>
<ul class="totals">
{#each steps as s, i (s.key)}
<li class:dim={!isFilled(s)} class:focused={i === idx && !done}>
<button type="button" class="totals-item" onclick={() => jumpTo(i)}>
<span class="totals-label">{stepLabel(s)}</span>
<span class="totals-val">{formatValue(s)}</span>
</button>
</li>
{/each}
</ul>
</div>
</aside>
<footer class="bottombar">
{#if !done}
<button type="button" class="ghost" onclick={skip}>{t('skip', lang)}</button>
<div class="kbd-legend" aria-hidden="true">
<span><kbd></kbd><kbd></kbd> {t('kbd_nav', lang)}</span>
<span><kbd></kbd> {t('kbd_next', lang)}</span>
<span><kbd>S</kbd> {t('kbd_skip', lang)}</span>
<span><kbd>scroll</kbd> {t('kbd_wheel', lang)}</span>
</div>
{#if showShortcuts}
<div class="kbd-legend" aria-hidden="true">
<span><kbd></kbd><kbd></kbd> {t('kbd_nav', lang)}</span>
<span><kbd></kbd> {t('kbd_next', lang)}</span>
<span><kbd>S</kbd> {t('kbd_skip', lang)}</span>
<span><kbd>scroll</kbd> {t('kbd_wheel', lang)}</span>
</div>
{:else}
<button
type="button"
class="kbd-hint"
onclick={() => showShortcuts = true}
aria-label={t('kbd_hint', lang)}
title={t('kbd_hint', lang)}
>
<kbd>?</kbd>
</button>
{/if}
<div class="nav-pair">
<button type="button" class="nav-btn" onclick={back} disabled={idx === 0} aria-label={t('back', lang)}>
<ArrowLeft size={16} />
@@ -617,7 +618,7 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
padding: 1rem 2rem;
}
.progress {
display: flex;
@@ -864,14 +865,13 @@
.panel { display: none; }
.bottombar {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem 1.5rem;
padding: 1rem 0.5rem 1.5rem;
gap: 0.75rem;
max-width: 520px;
width: 100%;
margin-inline: auto;
}
.ghost {
border: none;
@@ -913,6 +913,7 @@
}
.kbd-legend { display: none; }
.kbd-hint { display: none; }
.bottom-spacer { flex: 1; }
.summary {
@@ -994,7 +995,7 @@
.topbar { grid-area: topbar; padding: calc(1.25rem + var(--fitness-header-offset)) 2rem 0; }
.stage { grid-area: stage; padding: 1.5rem 2rem 2rem; align-items: center; }
.panel { grid-area: panel; }
.bottombar { grid-area: bottom; max-width: none; margin-inline: 0; padding: 1rem 2rem 1.5rem; }
.bottombar { grid-area: bottom; max-width: none; margin-inline: 0; padding: 1rem 0.5rem 1.5rem; }
.topbar .progress { visibility: hidden; }
@@ -1279,20 +1280,25 @@
.stepper.compact { flex: 1 1 180px; min-width: 180px; }
.kbd-legend {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
gap: 1rem;
font-size: 0.68rem;
color: var(--color-text-tertiary);
flex: 1;
justify-content: center;
align-items: center;
pointer-events: none;
}
.kbd-legend span {
display: inline-flex;
align-items: center;
gap: 0.3rem;
}
.kbd-legend kbd {
.kbd-legend kbd,
.kbd-hint kbd {
font-family: inherit;
display: inline-grid;
place-items: center;
@@ -1307,6 +1313,23 @@
font-size: 0.65rem;
font-weight: 700;
}
.kbd-hint {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: inline-flex;
align-items: center;
justify-content: center;
background: none;
border: none;
padding: 0.25rem 0.5rem;
cursor: pointer;
color: var(--color-text-tertiary);
opacity: 0.6;
transition: opacity 150ms;
}
.kbd-hint:hover { opacity: 1; }
}
@media (min-width: 1400px) {