fix(mobile): stack header, declutter stepper, and reflow chrome
Some checks failed
Deploy to GitHub Pages / build (push) Has been cancelled
Deploy to GitHub Pages / deploy (push) Has been cancelled

The header now stacks on mobile: title row up top, stepper on its own
row below, and the theme toggle moves into the footer's right edge so
it isn't competing for space with the title or stepper. Stepper labels
drop the leading "1." prefix (saves ~8ch across five steps) and the
mobile container has overflow-x-auto so very narrow screens scroll
instead of breaking layout.

Clear-cache button on the upload card collapses to icon-only on mobile
so the centered "Load Source Image" title doesn't sit underneath it;
the confirm state still reads "Sure?" so the destructive prompt is
visible at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Samuel Prevost 2026-04-26 17:57:25 +02:00
parent 9032af426e
commit e56ee9611d
3 changed files with 76 additions and 34 deletions

View File

@ -25,28 +25,45 @@ const store = useAppStore()
>Fork me on Gitea</a
>
<!-- Header lays out as a single h-14 row on desktop and stacks
into title-row + stepper-row on mobile so the title doesn't
collide with the stepper at narrow widths. The theme toggle
stays in the desktop header but moves into the footer on
mobile (see below). -->
<header
class="sticky top-0 z-50 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
>
<div
class="mx-auto grid h-14 max-w-7xl grid-cols-3 items-center px-4"
>
<div><!-- spacer for ribbon --></div>
<div class="flex items-center justify-center gap-2">
<SkwikLogo :size="28" />
<h1
class="font-mono text-lg font-semibold tracking-tight"
>
Skwik
</h1>
<span
class="hidden text-[10px] font-medium uppercase tracking-widest text-muted-foreground sm:inline"
>Perspective Correction</span
>
<div class="mx-auto max-w-7xl px-4">
<div
class="flex items-center justify-between gap-3 py-2 sm:grid sm:h-14 sm:grid-cols-3 sm:py-0"
>
<div class="hidden sm:block">
<!-- spacer for the fork-me ribbon -->
</div>
<div class="flex items-center gap-2 sm:justify-center">
<SkwikLogo :size="28" />
<h1
class="font-mono text-lg font-semibold tracking-tight"
>
Skwik
</h1>
<span
class="text-[10px] font-medium uppercase tracking-widest text-muted-foreground"
>Perspective Correction</span
>
</div>
<div class="hidden items-center justify-end gap-4 sm:flex">
<StepIndicator />
<ThemeToggle />
</div>
</div>
<div class="flex items-center justify-end gap-4">
<!-- Mobile-only stepper row. overflow-x-auto keeps the
page width sane if the labels still don't fit on
very narrow screens. -->
<div
class="-mx-4 flex justify-center overflow-x-auto px-4 pb-2 sm:hidden"
>
<StepIndicator />
<ThemeToggle />
</div>
</div>
</header>
@ -62,17 +79,28 @@ const store = useAppStore()
<MeasureViewer v-else-if="store.currentStep === 5" />
</main>
<!-- Footer is fixed to the bottom. On mobile we tuck the theme
toggle into the right edge so it's reachable without
eating into the header chrome; the toggle is absolutely
positioned so it doesn't push the centered byline around. -->
<footer
class="fixed inset-x-0 bottom-0 z-40 border-t border-border/50 bg-background/95 py-3 text-center text-xs text-muted-foreground backdrop-blur supports-[backdrop-filter]:bg-background/60"
>
Made by
<a
href="https://github.com/usr-ein"
target="_blank"
rel="noopener"
class="underline underline-offset-2 transition-colors hover:text-foreground"
>Samuel Prevost</a
<span>
Made by
<a
href="https://github.com/usr-ein"
target="_blank"
rel="noopener"
class="underline underline-offset-2 transition-colors hover:text-foreground"
>Samuel Prevost</a
>
</span>
<div
class="absolute bottom-1/2 right-2 translate-y-1/2 sm:hidden"
>
<ThemeToggle />
</div>
</footer>
</div>
</template>

View File

@ -296,16 +296,25 @@ function onFileSelect(e: Event) {
primary drop target. Two-step confirm: first click
arms it; second click within
CLEAR_CONFIRM_TIMEOUT_MS commits. -->
<!-- Mobile: icon-only when idle, icon + "Sure?" while
confirming so the destructive prompt is unmistakable.
Desktop: full label always (the centered card title
has room beside it). -->
<Button
v-if="cacheCount > 0"
variant="ghost"
size="sm"
class="absolute right-2 top-2 h-7 gap-1.5 text-xs"
class="absolute right-2 top-2 h-7 gap-1.5 px-2 text-xs"
:class="
confirmingClear
? 'text-destructive hover:text-destructive'
: 'text-muted-foreground/60 hover:text-destructive'
"
:aria-label="
confirmingClear
? 'Confirm clear cache'
: `Clear cache (${String(cacheCount)})`
"
@click="handleClearCacheClick"
>
<svg
@ -325,11 +334,16 @@ function onFileSelect(e: Event) {
/>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
{{
confirmingClear
? "Are you sure?"
: `Clear cache (${String(cacheCount)})`
}}
<span
v-if="confirmingClear"
class="whitespace-nowrap"
>
<span class="hidden sm:inline">Are you sure?</span>
<span class="sm:hidden">Sure?</span>
</span>
<span v-else class="hidden whitespace-nowrap sm:inline">
Clear cache ({{ cacheCount }})
</span>
</Button>
<CardHeader class="text-center">
<CardTitle class="text-lg">Load Source Image</CardTitle>

View File

@ -29,10 +29,10 @@ function handleClick(num: AppStep) {
<template v-for="(step, i) in steps" :key="step.num">
<button
v-if="isReachable(step.num)"
class="inline-flex items-center rounded-md border border-border px-2 py-0.5 font-mono text-xs font-medium transition-colors hover:bg-accent hover:text-accent-foreground"
class="inline-flex shrink-0 items-center rounded-md border border-border px-2 py-0.5 font-mono text-xs font-medium transition-colors hover:bg-accent hover:text-accent-foreground"
@click="handleClick(step.num)"
>
{{ step.num }}.{{ step.label }}
{{ step.label }}
</button>
<Badge
v-else
@ -41,13 +41,13 @@ function handleClick(num: AppStep) {
? 'default'
: 'outline'
"
class="cursor-default select-none font-mono text-xs"
class="shrink-0 cursor-default select-none font-mono text-xs"
:class="{
'opacity-40':
step.num > store.maxStepReached,
}"
>
{{ step.num }}.{{ step.label }}
{{ step.label }}
</Badge>
<span
v-if="i < steps.length - 1"