Compare commits
2 Commits
ae8c699640
...
0a188ad4ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
0a188ad4ab
|
|||
|
def176db4d
|
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.45.1",
|
"version": "1.46.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ const translations: Translations = {
|
|||||||
kbd_next: { en: 'next', de: 'weiter' },
|
kbd_next: { en: 'next', de: 'weiter' },
|
||||||
kbd_skip: { en: 'skip', de: 'auslassen' },
|
kbd_skip: { en: 'skip', de: 'auslassen' },
|
||||||
kbd_wheel: { en: '\u00b10.1', de: '\u00b10,1' },
|
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: {
|
no_body_parts_selected: {
|
||||||
en: 'Enter at least one value before saving.',
|
en: 'Enter at least one value before saving.',
|
||||||
de: 'Bitte mindestens einen Wert eingeben.'
|
de: 'Bitte mindestens einen Wert eingeben.'
|
||||||
@@ -311,6 +312,7 @@ const translations: Translations = {
|
|||||||
general: { en: 'General', de: 'Allgemein' },
|
general: { en: 'General', de: 'Allgemein' },
|
||||||
body_fat_pct: { en: 'Body Fat (%)', de: 'Körperfett (%)' },
|
body_fat_pct: { en: 'Body Fat (%)', de: 'Körperfett (%)' },
|
||||||
history: { en: 'History', de: 'Verlauf' },
|
history: { en: 'History', de: 'Verlauf' },
|
||||||
|
past_measurements: { en: 'Past measurements', de: 'Frühere Messungen' },
|
||||||
|
|
||||||
// SetTable
|
// SetTable
|
||||||
set_header: { en: 'SET', de: 'SATZ' },
|
set_header: { en: 'SET', de: 'SATZ' },
|
||||||
|
|||||||
@@ -68,6 +68,9 @@
|
|||||||
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/food`) &&
|
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/food`) &&
|
||||||
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/meals`)
|
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/meals`)
|
||||||
);
|
);
|
||||||
|
const isMeasureIndex = $derived(
|
||||||
|
/^\/fitness\/(measure|messen)\/?$/.test($page.url.pathname)
|
||||||
|
);
|
||||||
/** @param {number} secs */
|
/** @param {number} secs */
|
||||||
function formatElapsed(secs) {
|
function formatElapsed(secs) {
|
||||||
const m = Math.floor(secs / 60);
|
const m = Math.floor(secs / 60);
|
||||||
@@ -100,7 +103,7 @@
|
|||||||
<UserHeader {user} />
|
<UserHeader {user} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<div class="fitness-content" style:--fitness-max-width={isNutritionPage ? '1400px' : null}>
|
<div class="fitness-content" style:--fitness-max-width={isNutritionPage || isMeasureIndex ? '1400px' : null}>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
|||||||
import { toast } from '$lib/js/toast.svelte';
|
import { toast } from '$lib/js/toast.svelte';
|
||||||
import DatePicker from '$lib/components/DatePicker.svelte';
|
import DatePicker from '$lib/components/DatePicker.svelte';
|
||||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||||
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
import { bodyPartAccent } from '$lib/js/fitnessBodyParts';
|
import { bodyPartAccent } from '$lib/js/fitnessBodyParts';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -139,14 +140,18 @@
|
|||||||
return v ? `${v} cm` : '—';
|
return v ? `${v} cm` : '—';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let showShortcuts = $state(false);
|
||||||
|
|
||||||
/** @param {KeyboardEvent} e */
|
/** @param {KeyboardEvent} e */
|
||||||
function onkey(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 (done) {
|
||||||
if (e.key === 'ArrowLeft') { e.preventDefault(); idx = total - 1; direction = -1; }
|
if (e.key === 'ArrowLeft') { e.preventDefault(); idx = total - 1; direction = -1; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tag = /** @type {HTMLElement|null} */ (e.target)?.tagName;
|
|
||||||
const inInput = tag === 'INPUT';
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); next(); }
|
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); next(); }
|
||||||
else if (e.key === 'ArrowRight' && !inInput) { e.preventDefault(); next(); }
|
else if (e.key === 'ArrowRight' && !inInput) { e.preventDefault(); next(); }
|
||||||
else if (e.key === 'ArrowLeft' && !inInput) { e.preventDefault(); back(); }
|
else if (e.key === 'ArrowLeft' && !inInput) { e.preventDefault(); back(); }
|
||||||
@@ -427,10 +432,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<label class="same-toggle">
|
<div class="same-toggle">
|
||||||
<input type="checkbox" bind:checked={pv.same} />
|
<Toggle bind:checked={pv.same} label={t('same_both_sides', lang)} />
|
||||||
<span>{t('same_both_sides', lang)}</span>
|
</div>
|
||||||
</label>
|
|
||||||
{:else}
|
{:else}
|
||||||
<div class="stepper" onwheel={(e) => onWheel(e, step.key, null)}>
|
<div class="stepper" onwheel={(e) => onWheel(e, step.key, null)}>
|
||||||
<button type="button" class="step-btn" onclick={() => bump(step.key, null, -0.5)} aria-label="-0.5">
|
<button type="button" class="step-btn" onclick={() => bump(step.key, null, -0.5)} aria-label="-0.5">
|
||||||
@@ -477,7 +481,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<aside class="panel" aria-label={t('running_totals', lang)}>
|
<aside class="panel">
|
||||||
{#if !done}
|
{#if !done}
|
||||||
{#key step.key}
|
{#key step.key}
|
||||||
<div class="panel-section chart-section" in:fade={{ duration: 180 }}>
|
<div class="panel-section chart-section" in:fade={{ duration: 180 }}>
|
||||||
@@ -549,32 +553,29 @@
|
|||||||
{/key}
|
{/key}
|
||||||
{/if}
|
{/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>
|
</aside>
|
||||||
|
|
||||||
<footer class="bottombar">
|
<footer class="bottombar">
|
||||||
{#if !done}
|
{#if !done}
|
||||||
<button type="button" class="ghost" onclick={skip}>{t('skip', lang)}</button>
|
<button type="button" class="ghost" onclick={skip}>{t('skip', lang)}</button>
|
||||||
<div class="kbd-legend" aria-hidden="true">
|
{#if showShortcuts}
|
||||||
<span><kbd>←</kbd><kbd>→</kbd> {t('kbd_nav', lang)}</span>
|
<div class="kbd-legend" aria-hidden="true">
|
||||||
<span><kbd>↵</kbd> {t('kbd_next', lang)}</span>
|
<span><kbd>←</kbd><kbd>→</kbd> {t('kbd_nav', lang)}</span>
|
||||||
<span><kbd>S</kbd> {t('kbd_skip', lang)}</span>
|
<span><kbd>↵</kbd> {t('kbd_next', lang)}</span>
|
||||||
<span><kbd>scroll</kbd> {t('kbd_wheel', lang)}</span>
|
<span><kbd>S</kbd> {t('kbd_skip', lang)}</span>
|
||||||
</div>
|
<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">
|
<div class="nav-pair">
|
||||||
<button type="button" class="nav-btn" onclick={back} disabled={idx === 0} aria-label={t('back', lang)}>
|
<button type="button" class="nav-btn" onclick={back} disabled={idx === 0} aria-label={t('back', lang)}>
|
||||||
<ArrowLeft size={16} />
|
<ArrowLeft size={16} />
|
||||||
@@ -617,7 +618,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 1rem 1.25rem;
|
padding: 1rem 2rem;
|
||||||
}
|
}
|
||||||
.progress {
|
.progress {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -859,30 +860,18 @@
|
|||||||
|
|
||||||
.same-toggle {
|
.same-toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
|
||||||
gap: 0.45rem;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.same-toggle input {
|
|
||||||
accent-color: var(--color-primary);
|
|
||||||
width: 0.95rem;
|
|
||||||
height: 0.95rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel { display: none; }
|
.panel { display: none; }
|
||||||
|
|
||||||
.bottombar {
|
.bottombar {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 1rem 1.25rem 1.5rem;
|
padding: 1rem 0.5rem 1.5rem;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
max-width: 520px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-inline: auto;
|
|
||||||
}
|
}
|
||||||
.ghost {
|
.ghost {
|
||||||
border: none;
|
border: none;
|
||||||
@@ -924,6 +913,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.kbd-legend { display: none; }
|
.kbd-legend { display: none; }
|
||||||
|
.kbd-hint { display: none; }
|
||||||
.bottom-spacer { flex: 1; }
|
.bottom-spacer { flex: 1; }
|
||||||
|
|
||||||
.summary {
|
.summary {
|
||||||
@@ -1005,7 +995,7 @@
|
|||||||
.topbar { grid-area: topbar; padding: calc(1.25rem + var(--fitness-header-offset)) 2rem 0; }
|
.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; }
|
.stage { grid-area: stage; padding: 1.5rem 2rem 2rem; align-items: center; }
|
||||||
.panel { grid-area: panel; }
|
.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; }
|
.topbar .progress { visibility: hidden; }
|
||||||
|
|
||||||
@@ -1290,20 +1280,25 @@
|
|||||||
.stepper.compact { flex: 1 1 180px; min-width: 180px; }
|
.stepper.compact { flex: 1 1 180px; min-width: 180px; }
|
||||||
|
|
||||||
.kbd-legend {
|
.kbd-legend {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
color: var(--color-text-tertiary);
|
color: var(--color-text-tertiary);
|
||||||
flex: 1;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.kbd-legend span {
|
.kbd-legend span {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.3rem;
|
gap: 0.3rem;
|
||||||
}
|
}
|
||||||
.kbd-legend kbd {
|
.kbd-legend kbd,
|
||||||
|
.kbd-hint kbd {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
@@ -1318,6 +1313,23 @@
|
|||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 700;
|
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) {
|
@media (min-width: 1400px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user