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

213 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="max-w-4xl mx-auto px-4 pt-8 pb-24 space-y-20">
<!-- BLOCKLIST -->
<section>
<div class="flex items-start gap-5 mb-6">
<div>
<h2 class="text-2xl font-black text-highlighted leading-tight">
{{ $t('resources.blocklist_title') }}
</h2>
<p class="text-muted text-sm mt-1">
{{ $t('resources.blocklist_desc', { count: domainCount.toLocaleString('de-DE') }) }}
</p>
</div>
</div>
<UPageCard>
<div class="flex items-center justify-between mb-4">
<span class="text-xs text-muted uppercase tracking-wider font-medium">{{ $t('resources.chart_label') }}</span>
<span class="text-sm font-bold text-primary-500">{{ domainCount.toLocaleString("de-DE") }}</span>
</div>
<ChartsBlocklistGrowth :data="chartData" :height="160" />
</UPageCard>
</section>
<!-- SOFORT-HILFE -->
<section>
<div class="flex items-start gap-5 mb-6">
<div>
<h2 class="text-2xl font-black text-highlighted leading-tight">
{{ $t('resources.hotlines_title') }}
</h2>
<p class="text-muted text-sm mt-1">
{{ $t('resources.hotlines_desc') }}
</p>
</div>
</div>
<div class="grid sm:grid-cols-3 gap-4">
<a v-for="h in hotlines" :key="h.country" :href="h.url" target="_blank" rel="noopener noreferrer"
class="block bg-elevated border border-default rounded-2xl p-5 hover:border-primary-500/40 transition-colors">
<div class="text-xs text-primary-500 font-bold mb-1">{{ h.country }}</div>
<div class="font-semibold text-highlighted text-sm mb-2">
{{ h.name }}
</div>
<div class="text-lg font-mono font-bold text-green-500">{{ h.phone }}</div>
<div class="text-xs text-muted mt-1">{{ h.hours }}</div>
</a>
</div>
</section>
<!-- SELBSTHILFE TIPPS -->
<section>
<div class="flex items-start gap-5 mb-6">
<div>
<h2 class="text-2xl font-black text-highlighted leading-tight">
{{ $t('resources.tips_title') }}
</h2>
<p class="text-muted text-sm mt-1">
{{ $t('resources.tips_desc') }}
</p>
</div>
</div>
<div class="grid sm:grid-cols-2 gap-4">
<div v-for="tip in selfHelpTips" :key="tip.title"
class="bg-elevated border border-default rounded-2xl p-5 flex gap-4">
<img :src="tip.icon" class="w-10 h-10 shrink-0 opacity-80 mt-0.5" alt="" />
<div>
<h3 class="font-bold text-highlighted text-sm mb-1.5">
{{ tip.title }}
</h3>
<p class="text-sm text-muted leading-relaxed">{{ tip.text }}</p>
</div>
</div>
</div>
</section>
<!-- WARUM GEFÄHRLICH -->
<section>
<div class="flex items-start gap-5 mb-6">
<div>
<h2 class="text-2xl font-black text-highlighted leading-tight">
{{ $t('resources.not_weak_title') }}
</h2>
<p class="text-muted text-sm mt-1">
{{ $t('resources.not_weak_desc') }}
</p>
</div>
</div>
<div class="space-y-3">
<div v-for="fact in facts" :key="fact.title"
class="flex gap-4 bg-elevated border border-default rounded-2xl p-4 items-start">
<img :src="fact.icon" class="w-9 h-9 shrink-0 opacity-80 mt-0.5" alt="" />
<div>
<div class="font-semibold text-highlighted text-sm">
{{ fact.title }}
</div>
<div class="text-sm text-muted mt-0.5">{{ fact.text }}</div>
</div>
</div>
</div>
</section>
<!-- FINAL CTA -->
<section class="text-center py-6">
<img src="/astronaut.svg" class="w-20 h-20 mx-auto mb-5 opacity-80" alt="" />
<h2 class="text-3xl font-black text-highlighted mb-2">
{{ $t('resources.cta_title') }}
</h2>
<a href="https://apps.apple.com/app/rebreak" target="_blank" rel="noopener">
<UButton size="xl" class="px-10">
<UIcon name="i-heroicons-bolt" />
{{ $t('resources.cta_button') }}
</UButton>
</a>
</section>
</div>
</template>
<script setup lang="ts">
definePageMeta({ layout: "default" });
const { t } = useI18n();
const { public: { apiBase } } = useRuntimeConfig();
const domainCount = ref(0);
const chartData = ref<{ label: string; count: number }[]>([]);
onMounted(async () => {
// Blocklist-Count vom Backend (public API, kein Auth nötig)
$fetch<{ count: number }>(`${apiBase}/api/blocklist/count`).then((r) => {
if (r?.count) domainCount.value = r.count;
}).catch(() => { });
$fetch<{
current: number;
history: { label: string; count: number }[];
}>(`${apiBase}/api/blocklist/stats`).then((stats) => {
if (stats?.current) domainCount.value = stats.current;
if (stats?.history) chartData.value = stats.history;
}).catch(() => { });
});
const hotlines = computed(() => [
{
country: t('resources.hotline_de'),
name: "BZgA check-dein-spiel.de",
phone: "0800 1372700",
hours: "MoDo 1022 Uhr, FrSo 1018 Uhr",
url: "https://www.check-dein-spiel.de",
},
{
country: t('resources.hotline_at'),
name: "Spielsuchthilfe",
phone: "0800 040 080",
hours: "24h erreichbar",
url: "https://www.spielsuchthilfe.at",
},
{
country: t('resources.hotline_ch'),
name: "Addiction Suisse",
phone: "0800 040 080",
hours: "MoFr 917 Uhr",
url: "https://www.addictionsuisse.ch",
},
]);
const selfHelpTips = computed(() => [
{
icon: "/snowflake.svg",
title: t('resources.tip_breathing'),
text: t('resources.tip_breathing_desc'),
},
{
icon: "/diary.svg",
title: t('resources.tip_15min'),
text: t('resources.tip_15min_desc'),
},
{
icon: "/walk.svg",
title: t('resources.tip_move'),
text: t('resources.tip_move_desc'),
},
{
icon: "/alert.svg",
title: t('resources.tip_triggers'),
text: t('resources.tip_triggers_desc'),
},
]);
const facts = computed(() => [
{
icon: "/brain.svg",
title: t('resources.fact1_title'),
text: t('resources.fact1_text'),
},
{
icon: "/phone-call.svg",
title: t('resources.fact2_title'),
text: t('resources.fact2_text'),
},
{
icon: "/kidneys.svg",
title: t('resources.fact3_title'),
text: t('resources.fact3_text'),
},
{
icon: "/graph.svg",
title: t('resources.fact4_title'),
text: t('resources.fact4_text'),
},
]);
</script>