chahinebrini 5d6c322129 wip: KeyboardAwareSheet migrations + Snake/Tetris UI + iron.png + useMe live-update
Sheets via neuer KeyboardAwareSheet-Composable (in Modal pattern, auto-grow
mit Tastatur, paddingBottom-Lift): EditMail, AddDomain, CreateRoom, ConnectMail.
GameOverScreen behält Spring-Slide-In, nutzt RN Keyboard.addListener für Lift.

- KeyboardAwareSheet.tsx — universal modal with sheet-grow + keyboard-padding
- react-native-keyboard-controller installiert + KeyboardProvider in Root
- Snake: time + ScoreProgressBar + useSnakeSounds (haptic, audio TODO)
- Tetris: title weg, Buttons zentriert, kein Pressable mit style-fn
- DPad-Buttons 60→48, more bg, no scale
- useMe: pub-sub listener pattern für app-weite avatar/nickname-Updates
- dm.tsx: resolveAvatar wrap (iron.png-Warning)
- Mail-error-humanizer + locales

Recovery-Doc-Update in docs/internal/RECOVERY_LOG_2026-05-10.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:59:25 +02:00

96 lines
3.9 KiB
Vue

<template>
<div data-layout="default" class="flex flex-col overflow-hidden bg-default text-highlighted"
:style="{ height: vpHeight + 'px' }">
<!-- Header sticky, innerhalb des Flex-Flows -->
<header class="shrink-0 h-16 border-b border-default bg-default/95 backdrop-blur-md">
<div class="max-w-6xl mx-auto px-4 sm:px-6 h-full flex items-center justify-between">
<NuxtLink to="/" class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
<UIcon name="i-heroicons-shield-check" class="text-white" />
</div>
<span class="font-bold text-lg text-highlighted">ReBreak</span>
</NuxtLink>
<nav class="hidden sm:flex items-center gap-6 text-sm text-muted">
<NuxtLink to="/pricing" class="hover:text-highlighted transition-colors">{{ $t("nav.pricing") }}</NuxtLink>
<NuxtLink to="/resources" class="hover:text-highlighted transition-colors">{{ $t("nav.resources") }}</NuxtLink>
</nav>
<!-- App-Download CTA statt Login (Marketing hat keine Auth) -->
<div class="flex items-center gap-3">
<a href="https://apps.apple.com/app/rebreak" target="_blank" rel="noopener">
<UButton size="sm" color="primary">{{ $t("nav.download_app") }}</UButton>
</a>
</div>
</div>
</header>
<!-- Scrollbarer Inhalt -->
<div class="flex-1 overflow-y-auto">
<slot />
<!-- DSGVO-Pflicht: Datenschutz + Impressum public erreichbar -->
<footer class="border-t border-default mt-12 pb-24 md:pb-6 pt-6 px-4">
<div
class="max-w-6xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-3 text-xs text-muted">
<p>© {{ new Date().getFullYear() }} Rebreak</p>
<nav class="flex flex-wrap items-center gap-x-5 gap-y-2">
<NuxtLink to="/datenschutz" class="hover:text-primary-400 transition-colors">Datenschutz</NuxtLink>
<NuxtLink to="/impressum" class="hover:text-primary-400 transition-colors">Impressum</NuxtLink>
<NuxtLink to="/nutzungsbedingungen" class="hover:text-primary-400 transition-colors">
Nutzungsbedingungen
</NuxtLink>
</nav>
</div>
</footer>
</div>
<!-- Floating Pill Tab-Bar (Mobile) -->
<div class="fixed bottom-3 left-4 right-4 z-50 pointer-events-none">
<nav
class="pointer-events-auto max-w-lg mx-auto bg-white/80 dark:bg-elevated backdrop-blur-md rounded-4xl shadow-[0_4px_24px_rgba(0,0,0,0.12)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.3)] ring-1 ring-black/10 dark:ring-white/10 px-2 py-1.5">
<div class="flex items-center">
<NuxtLink v-for="tab in tabs" :key="tab.to" :to="tab.to"
class="relative flex-1 flex flex-col items-center gap-0.5 px-1 py-1.5 rounded-3xl transition-colors"
:class="isActive(tab.to) ? 'text-primary-400' : 'text-muted'">
<UIcon :name="isActive(tab.to) ? tab.iconActive : tab.icon" class="size-6 shrink-0" />
<span class="text-[10px] leading-none whitespace-nowrap font-semibold">{{ tab.label }}</span>
</NuxtLink>
</div>
</nav>
</div>
</div>
</template>
<script setup lang="ts">
const { height: vpHeight } = useViewportHeight();
const route = useRoute();
const { t } = useI18n();
const tabs = computed(() => [
{
to: "/",
icon: "i-heroicons-home",
iconActive: "i-heroicons-home-solid",
label: t('pricing.footer_home'),
},
{
to: "/pricing",
icon: "i-heroicons-credit-card",
iconActive: "i-heroicons-credit-card-solid",
label: t('pricing.footer_pricing'),
},
{
to: "/resources",
icon: "i-heroicons-book-open",
iconActive: "i-heroicons-book-open-solid",
label: t('pricing.footer_resources'),
},
]);
function isActive(to: string) {
if (to === "/") return route.path === "/";
return route.path.startsWith(to);
}
</script>