Files
EventSnap/frontend/src/lib/components/BottomNav.svelte
MechaCat02 f243bfe89a feat(ui): elegant wedding white/silver/gold redesign
Reskin the whole app via design tokens in tailwind-theme.css (remapped
color ramps) plus a define-once component layer in lib/styles/components.css
(.btn, .card, .input, .chip, .badge, .sheet, …). Buttons are muted/outlined
gold rather than flat fills. Self-host Inter + Fraunces (woff2) under the
existing font-src 'self' CSP. Restyle the shared components and the account,
admin, host, join, recover and upload screens against the new tokens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:28:42 +02:00

137 lines
5.0 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { uploadSheetOpen, uploadBadgeCount } from '$lib/ui-store';
import { exportStatus, initExportStatus } from '$lib/export-status-store';
function isActive(path: string): boolean {
return $page.url.pathname.startsWith(path);
}
onMount(() => {
// Hydrate export status once when the bottom nav first mounts (i.e. the user
// is authenticated). Subsequent SSE events update the snapshot live so the
// Export tab can fade in mid-session.
initExportStatus();
});
let showExport = $derived($exportStatus.released);
let zipReady = $derived($exportStatus.zip?.status === 'done');
</script>
<!-- Bottom navigation bar — fixed, full-width, safe-area aware -->
<nav
class="fixed bottom-0 left-0 right-0 z-40 border-t border-gray-200 bg-white/90 backdrop-blur-md dark:border-gray-800 dark:bg-gray-900/90"
style="padding-bottom: env(safe-area-inset-bottom)"
data-testid="bottom-nav"
>
<div class="mx-auto flex h-14 max-w-2xl items-end justify-around px-4 pb-1">
<!-- Feed tab -->
<a
href="/feed"
class="flex flex-col items-center gap-0.5 px-4 py-1 text-xs font-medium transition-colors
{isActive('/feed')
? 'text-blue-600 dark:text-blue-400'
: 'text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300'}"
aria-label="Galerie"
>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
/>
</svg>
<span>Galerie</span>
</a>
<!-- Upload FAB (center, elevated) -->
<div class="relative -translate-y-3">
<button
onclick={() => ($uploadSheetOpen = true)}
class="relative flex h-14 w-14 items-center justify-center rounded-full border-2 border-primary-500 bg-primary-100 text-primary-700 shadow-lg transition active:scale-95 hover:bg-primary-200 dark:border-primary-500 dark:bg-primary-950/60 dark:text-primary-300"
aria-label="Hochladen"
>
<!-- Camera + plus icon -->
<svg
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="1.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z"
/>
</svg>
<!-- Badge -->
{#if $uploadBadgeCount > 0}
<span
class="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white"
>
{$uploadBadgeCount > 9 ? '9+' : $uploadBadgeCount}
</span>
{/if}
</button>
</div>
<!-- Export tab — appears only when the host has released the export. -->
{#if showExport}
<a
href="/export"
data-testid="bottom-nav-export"
class="relative flex flex-col items-center gap-0.5 px-4 py-1 text-xs font-medium transition-colors
{isActive('/export')
? 'text-blue-600 dark:text-blue-400'
: 'text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300'}"
aria-label="Export"
>
<svg
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="1.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
/>
</svg>
<span>Export</span>
{#if zipReady}
<span class="absolute right-2 top-0 h-2 w-2 rounded-full bg-blue-500" aria-hidden="true"
></span>
{/if}
</a>
{/if}
<!-- Account tab -->
<a
href="/account"
class="flex flex-col items-center gap-0.5 px-4 py-1 text-xs font-medium transition-colors
{isActive('/account')
? 'text-blue-600 dark:text-blue-400'
: 'text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300'}"
aria-label="Konto"
>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"
/>
</svg>
<span>Konto</span>
</a>
</div>
</nav>