diff --git a/apps/rebreak-native/app/index.tsx b/apps/rebreak-native/app/index.tsx
index dbd7fbf..301e86d 100644
--- a/apps/rebreak-native/app/index.tsx
+++ b/apps/rebreak-native/app/index.tsx
@@ -1,32 +1,241 @@
-import { View, Text, TouchableOpacity } from 'react-native';
+import { useEffect, useRef } from 'react';
+import { Animated, Dimensions, Image, Text, TouchableOpacity, View } from 'react-native';
+import Svg, { Defs, RadialGradient, Rect, Stop } from 'react-native-svg';
import { useRouter } from 'expo-router';
-import { SafeAreaView } from 'react-native-safe-area-context';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next';
-export default function HomeScreen() {
+const { width: SW, height: SH } = Dimensions.get('window');
+
+export default function LandingScreen() {
const router = useRouter();
+ const insets = useSafeAreaInsets();
const { t } = useTranslation();
+ const glowTopOpacity = useRef(new Animated.Value(0.5)).current;
+ const glowCenterOpacity = useRef(new Animated.Value(0)).current;
+ const glowCenterScale = useRef(new Animated.Value(0.6)).current;
+
+ const nameOpacity = useRef(new Animated.Value(0)).current;
+ const nameTranslateY = useRef(new Animated.Value(12)).current;
+
+ const logoOpacity = useRef(new Animated.Value(0)).current;
+ const logoScale = useRef(new Animated.Value(0.82)).current;
+ const logoTranslateY = useRef(new Animated.Value(8)).current;
+ const logoPulse = useRef(new Animated.Value(1)).current;
+
+ const taglineOpacity = useRef(new Animated.Value(0)).current;
+ const taglineTranslateY = useRef(new Animated.Value(8)).current;
+
+ const ctaOpacity = useRef(new Animated.Value(0)).current;
+ const ctaTranslateY = useRef(new Animated.Value(10)).current;
+
+ const footerOpacity = useRef(new Animated.Value(0)).current;
+
+ useEffect(() => {
+ Animated.loop(
+ Animated.sequence([
+ Animated.timing(glowTopOpacity, { toValue: 0.9, duration: 2000, useNativeDriver: true }),
+ Animated.timing(glowTopOpacity, { toValue: 0.5, duration: 2000, useNativeDriver: true }),
+ ]),
+ ).start();
+
+ const ease = (toValue: number, duration: number) => ({ toValue, duration, useNativeDriver: true });
+
+ Animated.parallel([
+ Animated.timing(glowCenterOpacity, ease(1, 900)),
+ Animated.timing(glowCenterScale, ease(1, 900)),
+ ]).start();
+
+ setTimeout(() => {
+ Animated.parallel([
+ Animated.timing(nameOpacity, ease(1, 600)),
+ Animated.timing(nameTranslateY, ease(0, 600)),
+ ]).start();
+ }, 300);
+
+ setTimeout(() => {
+ Animated.parallel([
+ Animated.timing(logoOpacity, ease(1, 650)),
+ Animated.spring(logoScale, { toValue: 1, useNativeDriver: true, friction: 6, tension: 80 }),
+ Animated.timing(logoTranslateY, ease(0, 650)),
+ ]).start();
+ }, 700);
+
+ setTimeout(() => {
+ Animated.loop(
+ Animated.sequence([
+ Animated.timing(logoPulse, { toValue: 1.04, duration: 1300, useNativeDriver: true }),
+ Animated.timing(logoPulse, { toValue: 1, duration: 1300, useNativeDriver: true }),
+ ]),
+ ).start();
+ }, 1100);
+
+ setTimeout(() => {
+ Animated.parallel([
+ Animated.timing(taglineOpacity, ease(1, 550)),
+ Animated.timing(taglineTranslateY, ease(0, 550)),
+ ]).start();
+ }, 1300);
+
+ setTimeout(() => {
+ Animated.parallel([
+ Animated.timing(ctaOpacity, ease(1, 500)),
+ Animated.timing(ctaTranslateY, ease(0, 500)),
+ Animated.timing(footerOpacity, ease(1, 600)),
+ ]).start();
+ }, 1700);
+ }, [
+ glowTopOpacity, glowCenterOpacity, glowCenterScale,
+ nameOpacity, nameTranslateY, logoOpacity, logoScale, logoTranslateY,
+ logoPulse, taglineOpacity, taglineTranslateY, ctaOpacity, ctaTranslateY, footerOpacity,
+ ]);
+
return (
-
-
- {t('landing.appName')}
-
- {t('landing.tagline')}
-
+
+ {/* Top breathing glow */}
+
+
+
- router.push('/signin')}
- activeOpacity={0.8}
- className="bg-rebreak-500 px-8 py-4 rounded-full"
+ {/* Center indigo halo */}
+
+
+
+
+ {/* Content */}
+
+
- {t('landing.start')}
-
+ {t('appHeader.appName')}
+
-
- {t('landing.version')}
-
+
+
+
+
+
+ {t('splash.tagline')}
+
+
+
+ router.push('/signin')}
+ activeOpacity={0.85}
+ style={{
+ backgroundColor: '#6366f1',
+ borderRadius: 16,
+ paddingVertical: 16,
+ alignItems: 'center',
+ }}
+ >
+
+ {t('auth.signin')}
+
+
+
+ router.push('/signup')}
+ activeOpacity={0.85}
+ style={{
+ borderWidth: 1.5,
+ borderColor: 'rgba(255,255,255,0.25)',
+ borderRadius: 16,
+ paddingVertical: 16,
+ alignItems: 'center',
+ }}
+ >
+
+ {t('landing.start')}
+
+
+
-
+
+ {/* Footer */}
+
+ {t('splash.madeInGermany')}
+
+
);
}
diff --git a/apps/rebreak-native/components/KeyboardAwareSheet.tsx b/apps/rebreak-native/components/KeyboardAwareSheet.tsx
index c89d0a8..9e23706 100644
--- a/apps/rebreak-native/components/KeyboardAwareSheet.tsx
+++ b/apps/rebreak-native/components/KeyboardAwareSheet.tsx
@@ -1,6 +1,7 @@
import { ReactNode, useEffect, useRef, useState } from 'react';
import {
Animated,
+ Dimensions,
Easing,
Keyboard,
Modal,
@@ -121,13 +122,14 @@ export function KeyboardAwareSheet({
// Sheet-Höhe wächst/schrumpft mit Tastatur
useEffect(() => {
+ const maxHeight = Dimensions.get('window').height - insets.top - 20;
const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
const showSub = Keyboard.addListener(showEvent, (e) => {
const h = e.endCoordinates.height;
setKeyboardHeight(h);
Animated.timing(sheetHeight, {
- toValue: collapsedHeight + h,
+ toValue: Math.min(collapsedHeight + h, maxHeight),
duration: Platform.OS === 'ios' ? (e.duration ?? 250) : 220,
easing: Easing.out(Easing.cubic),
useNativeDriver: false,
@@ -146,7 +148,7 @@ export function KeyboardAwareSheet({
showSub.remove();
hideSub.remove();
};
- }, [sheetHeight, collapsedHeight]);
+ }, [sheetHeight, collapsedHeight, insets.top]);
return (
diff --git a/apps/rebreak-native/components/blocker/AddDomainSheet.tsx b/apps/rebreak-native/components/blocker/AddDomainSheet.tsx
index ca4d351..23fea79 100644
--- a/apps/rebreak-native/components/blocker/AddDomainSheet.tsx
+++ b/apps/rebreak-native/components/blocker/AddDomainSheet.tsx
@@ -3,7 +3,6 @@ import {
View,
Text,
TextInput,
- Pressable,
TouchableOpacity,
Image,
ActivityIndicator,
@@ -82,11 +81,11 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
borderBottomColor: colors.border,
}}
>
-
+
{t('common.cancel')}
-
+
{t('blocker.add_sheet_title')}
@@ -121,6 +120,10 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
setInput(v);
setError(null);
}}
+ onBlur={() => {
+ const n = normalizeDomain(input);
+ if (n !== input) setInput(n);
+ }}
placeholder={t('blocker.add_sheet_placeholder')}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
@@ -215,8 +218,9 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
{/* Confirm-Checkbox */}
{valid && (
- setConfirmPermanent((v) => !v)}
+ activeOpacity={0.7}
style={{
flexDirection: 'row',
alignItems: 'flex-start',
@@ -250,7 +254,7 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
>
{t('blocker.add_sheet_confirm_permanent')}
-
+
)}
{/* Error */}
diff --git a/apps/rebreak-native/components/mail/ConnectMailSheet.tsx b/apps/rebreak-native/components/mail/ConnectMailSheet.tsx
index 1855956..4a46be0 100644
--- a/apps/rebreak-native/components/mail/ConnectMailSheet.tsx
+++ b/apps/rebreak-native/components/mail/ConnectMailSheet.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react';
import {
ActivityIndicator,
Linking,
+ Platform,
TouchableOpacity,
ScrollView,
Text,
@@ -337,6 +338,7 @@ function FormView({
style={{ flex: 1 }}
contentContainerStyle={{ padding: 20, gap: 14 }}
keyboardShouldPersistTaps="handled"
+ automaticallyAdjustKeyboardInsets={Platform.OS === 'ios'}
showsVerticalScrollIndicator={false}
>
{/* App-Password-Guide-Hinweis */}
diff --git a/apps/rebreak-native/locales/de.json b/apps/rebreak-native/locales/de.json
index 44cceb5..979f55b 100644
--- a/apps/rebreak-native/locales/de.json
+++ b/apps/rebreak-native/locales/de.json
@@ -81,8 +81,7 @@
"landing": {
"appName": "Rebreak",
"tagline": "Du gehst nicht allein.",
- "start": "Loslegen",
- "version": "v0.1.0 — RN Migration Phase 1 Skeleton"
+ "start": "Registrieren"
},
"splash": {
"tagline": "You will never walk alone!",
diff --git a/apps/rebreak-native/locales/en.json b/apps/rebreak-native/locales/en.json
index 745502a..75f9bc5 100644
--- a/apps/rebreak-native/locales/en.json
+++ b/apps/rebreak-native/locales/en.json
@@ -81,8 +81,7 @@
"landing": {
"appName": "Rebreak",
"tagline": "You're not walking alone.",
- "start": "Get started",
- "version": "v0.1.0 — RN Migration Phase 1 Skeleton"
+ "start": "Sign up"
},
"splash": {
"tagline": "You will never walk alone!",