From 09d7303eb2a0564b09fd65a92000fa0e350d627f Mon Sep 17 00:00:00 2001 From: Daniel Michelberger Date: Mon, 7 Sep 2026 22:51:46 +0200 Subject: [PATCH] feat: Zeitnahme fuer den Daumen an der Strecke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Leiste der laufenden Zeiten steht jetzt offen: Wer an der Strecke steht, will sie sehen und nicht erst danach fragen. Und die ganze Zeile ist der Knopf, nicht ein Icon an ihrem Rand — ein 16-px-Ziel neben einer fingerbreiten Zeile ist eine Einladung zum Danebentippen. Der Name der Stage verlinkt dafuer nicht mehr auf ihre Seite: ein Link im Knopf waere weder gueltiges HTML noch eindeutig zu treffen. Im Zeit-Dialog steht der Fahrer als Kachel statt in einer Auswahlliste — die Startnummer gross wie am Trikot (font-startnumber: Impact, mit Haettenschweiler und Arial Narrow Bold als Ersatz), der Name darunter. Eine Liste verlangt aufklappen, zielen und treffen; drei Schritte fuer etwas, das ein Zeitnehmer zwanzigmal am Tag macht. Starten und Stoppen sind Flaechen von 96 px Hoehe, Symbol ueber Wort, statt einer Zeile im Fussbereich. DNF steht daneben, gleich hoch, damit es sich treffen laesst, aber schmaler, damit es nicht versehentlich getroffen wird. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr --- frontend/src/app.css | 15 +++ .../lib/components/RunningTimesToast.svelte | 72 +++++------ frontend/src/lib/components/TimeDialog.svelte | 113 +++++++++++++----- 3 files changed, 137 insertions(+), 63 deletions(-) diff --git a/frontend/src/app.css b/frontend/src/app.css index dca4688..a87ca12 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -145,3 +145,18 @@ cursor: default; } } + +@layer components { + /* + * Startnummern wie am Trikot: schmal, fett, ohne Zierrat. Impact ist auf + * Windows und macOS da, Haettenschweiler und Arial Narrow Bold springen + * ein; am Ende steht eine beliebige serifenlose Schrift, dann sieht die + * Nummer eben normal aus statt falsch. + */ + .font-startnumber { + font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', 'Franklin Gothic Bold', sans-serif; + font-weight: 700; + font-stretch: condensed; + letter-spacing: 0.02em; + } +} diff --git a/frontend/src/lib/components/RunningTimesToast.svelte b/frontend/src/lib/components/RunningTimesToast.svelte index 40c0ff5..8dfad7e 100644 --- a/frontend/src/lib/components/RunningTimesToast.svelte +++ b/frontend/src/lib/components/RunningTimesToast.svelte @@ -5,11 +5,12 @@ * zwei Startern die Trails oder die Fahrerliste an, und die laufende Uhr * bleibt trotzdem im Blick — samt Stopp ohne Umweg über die Stage-Seite. * - * Bei mehreren Zeiten zeigt die Leiste eingeklappt nur die Zahl und die - * längste Zeit; ausgeklappt alle. Bei genau einer entfällt das Klappen. + * Aufgeklappt ist der Normalfall: Wer an der Strecke steht, will die + * laufenden Zeiten sehen und nicht erst danach fragen. Einklappen geht, + * sobald es mehr als eine ist. Bei genau einer entfällt das Klappen. * - * Gestoppt wird auch von hier aus nicht direkt: Der Knopf öffnet den - * Zeit-Dialog mit Fahrer und Stage vorbelegt. + * Gestoppt wird auch von hier aus nicht direkt: Ein Klick auf die Zeile + * öffnet den Zeit-Dialog mit Fahrer und Stage vorbelegt. */ import { getTimeContext } from '$lib/stores/times.svelte' import { riderName, getRiderContext } from '$lib/stores/riders.svelte' @@ -29,7 +30,7 @@ const running = $derived(times.allRunning(clock.now)) const single = $derived(running.length === 1) - let expanded = $state(false) + let expanded = $state(true) // Vorbelegung für den Zeit-Dialog let timeDialog = $state(false) @@ -48,7 +49,8 @@ } // Eine einzelne Zeit steht immer offen da; bei mehreren entscheidet der - // Nutzer. Fällt die Anzahl wieder auf eine, ist der Zustand egal. + // Nutzer — voreingestellt ist offen. Fällt die Anzahl wieder auf eine, + // ist der Zustand egal. const open = $derived(single || expanded) /** Die Startnummer gilt je Event, deshalb der Umweg über die Stage. */ @@ -61,12 +63,6 @@ return participants.riderLabel(eventId, riderId) } - function stagePath(stageId: string | undefined) { - const stage = stageId ? stages.getById(stageId) : undefined - if (!stage?.event) return null - return `/dashboard/events/${stage.event}/stages/${stage.id}` - } - function stageLabel(stageId: string | undefined) { const stage = stageId ? stages.getById(stageId) : undefined return stage?.description || 'Stage' @@ -113,32 +109,38 @@ {#if open} diff --git a/frontend/src/lib/components/TimeDialog.svelte b/frontend/src/lib/components/TimeDialog.svelte index 0ade8b7..c3bcf2b 100644 --- a/frontend/src/lib/components/TimeDialog.svelte +++ b/frontend/src/lib/components/TimeDialog.svelte @@ -5,6 +5,12 @@ * beim Starten und Stoppen nie ein Zweifel bleibt, wen und was man * gerade erwischt. Start und Stopp passieren ausschließlich hier: weder * Tabellen noch die Toast-Leiste lösen sie direkt aus. + * + * Bedient wird das an der Strecke, im Stehen, mit einer Hand am Lenker + * und einem Daumen am Telefon. Danach ist alles hier bemessen: Der Fahrer + * wird aus Kacheln gewählt statt aus einer Auswahlliste — die Startnummer + * gross wie am Trikot, der Name darunter —, und Starten und Stoppen sind + * flächige Knöpfe und keine Zeile im Fussbereich. */ import { getTimeContext, type TimeStatus } from '$lib/stores/times.svelte' import { riderName, getRiderContext } from '$lib/stores/riders.svelte' @@ -15,7 +21,7 @@ import { Label } from '@/components/ui/label' import * as Dialog from '@/components/ui/dialog' import * as Select from '@/components/ui/select' - import { AlertTriangle, Play, Square } from 'lucide-svelte' + import { AlertTriangle, Flag, Play, Square } from 'lucide-svelte' let { open = $bindable(false), @@ -65,9 +71,6 @@ ) const eventStages = $derived(eventId ? stages.getByEvent(eventId) : []) - const riderLabel = $derived( - selectedRider ? participants.riderLabel(eventId, selectedRider) : 'Fahrer wählen…', - ) const stageLabel = $derived( selectedStage ? stages.getById(selectedStage)?.description ?? 'Stage wählen…' : 'Stage wählen…', ) @@ -134,19 +137,45 @@
- - - {riderLabel} - - {#each eventRiders as r (r.id)} - - {participants.riderLabel(eventId, r.id)} - - {/each} - - + + {#if eventRiders.length === 0}

Für dieses Event gibt es noch keine Fahrer.

+ {:else} + +
+ {#each eventRiders as r (r.id)} + {@const number = participants.numberOf(eventId, r.id)} + {@const chosen = selectedRider === r.id} + + {/each} +
{/if}
@@ -202,22 +231,50 @@
{:else} - - - {#if active} - - - {:else} - - {/if} + + {:else} + + {/if} + + + {/if}