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>
213 lines
7.7 KiB
Vue
213 lines
7.7 KiB
Vue
<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: "Mo–Do 10–22 Uhr, Fr–So 10–18 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: "Mo–Fr 9–17 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>
|