chahinebrini e0cb0517fc feat(marketing): cross-device pricing-hero + Header-Nav statt Tabbar (live prod)
- Pricing: Device-Hero-Reihe (iPhone/Android/Mac/Windows) + Tagline 'ein Abo,
  alle Geräte' — heroicons (offline gebündelt)
- Layout: Floating-Tabbar raus -> Header-Nav (Desktop) / Hamburger (mobil)
- Locales: cross_device_tagline + Geräte-Matrix-Texte

Deployed: rebreak.org (marketing-prod) via deploy-marketing.sh

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 22:56:14 +02:00

309 lines
15 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="min-h-screen bg-default font-sans pb-16 md:pb-0">
<!-- Header -->
<div v-motion :initial="{ opacity: 0, y: -30 }" :visible="{ opacity: 1, y: 0, transition: { duration: 600 } }"
class="pt-10 pb-12 text-center px-4">
<div
class="inline-flex items-center gap-2 bg-amber-950/60 border border-amber-700/40 rounded-full px-4 py-1.5 text-sm text-amber-300 mb-4 animate-pulse">
<UIcon name="i-heroicons-fire" class="text-amber-400" />
{{ $t('pricing.founding_banner') }}
</div>
<div
class="inline-flex items-center gap-2 bg-primary-950/60 border border-primary-800/40 rounded-full px-4 py-1.5 text-sm text-primary-300 mb-6">
<UIcon name="i-heroicons-sparkles" class="text-primary-400" />
{{ $t('pricing.title') }}
</div>
<h1 class="text-4xl md:text-5xl font-extrabold text-highlighted mb-4">
{{ $t('pricing.subtitle_start') }}<br />
<span
class="text-transparent bg-clip-text bg-linear-to-r from-primary-400 via-primary-300 to-green-400">
{{ $t('pricing.subtitle_end') }}
</span>
</h1>
<!-- Geräteübergreifender Schutz Device-Hero -->
<div class="mt-8 flex flex-col items-center gap-4">
<p class="text-sm text-muted max-w-md">{{ $t('pricing.cross_device_tagline') }}</p>
<div class="flex items-center justify-center gap-4 sm:gap-6">
<div v-for="d in crossDevices" :key="d.label" class="flex flex-col items-center gap-2">
<div
class="w-14 h-14 rounded-2xl bg-elevated border border-default flex items-center justify-center">
<UIcon :name="d.icon" class="text-primary-300 text-2xl" />
</div>
<span class="text-xs text-muted font-medium">{{ d.label }}</span>
</div>
</div>
</div>
<!-- Billing Cycle Picker -->
<div class="flex items-center justify-center gap-2 mt-8">
<button v-for="opt in billingOptions" :key="opt.value" @click="billing = opt.value"
class="relative px-4 py-2 rounded-full text-sm font-semibold transition-all"
:class="billing === opt.value ? 'bg-primary-700 text-white' : 'bg-muted text-muted hover:text-highlighted'">
{{ opt.label }}
<span v-if="opt.badge"
class="absolute -top-2 -right-2 bg-green-500 text-white text-[9px] font-bold px-1.5 py-0.5 rounded-full">{{
opt.badge }}</span>
</button>
</div>
</div>
<!-- Pricing Plans -->
<div v-motion :initial="{ opacity: 0, y: 40 }"
:visible="{ opacity: 1, y: 0, transition: { duration: 700, delay: 100 } }"
class="px-4 pb-16 max-w-5xl mx-auto">
<UPricingPlans :plans="plans" />
</div>
<!-- Feature Comparison -->
<div v-motion :initial="{ opacity: 0, y: 50 }" :visible="{ opacity: 1, y: 0, transition: { duration: 700 } }"
class="px-4 pb-12 max-w-3xl mx-auto">
<div class="bg-elevated border border-purple-800/30 rounded-2xl p-6 md:p-8">
<div class="flex items-start gap-4">
<div class="w-10 h-10 rounded-xl bg-purple-800/30 flex items-center justify-center shrink-0 mt-0.5">
<UIcon name="i-heroicons-shield-check" class="text-purple-400 text-xl" />
</div>
<div>
<h3 class="font-bold text-highlighted text-lg mb-2">{{ $t('pricing.pro_meaning_title') }}</h3>
<p class="text-muted text-sm leading-relaxed">
{{ $t('pricing.pro_meaning_desc') }}
</p>
</div>
</div>
</div>
</div>
<!-- Feature Comparison Table -->
<div v-motion :initial="{ opacity: 0, y: 50 }" :visible="{ opacity: 1, y: 0, transition: { duration: 700 } }"
class="px-4 pb-12 max-w-4xl mx-auto">
<h2 class="text-2xl font-extrabold text-highlighted text-center mb-2">
{{ $t('pricing.comparison_title') }}
</h2>
<p class="text-muted text-center text-sm mb-10">
{{ $t('pricing.comparison_subtitle') }}
</p>
<div class="bg-elevated border border-default rounded-2xl overflow-hidden">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-default">
<th class="text-left p-4 text-muted font-semibold">{{ $t('pricing.feature') }}</th>
<th class="p-4 text-center font-semibold text-xs text-primary-300">Pro</th>
<th class="p-4 text-center text-purple-400 font-semibold text-xs">Legend</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in comparisonRows" :key="row.label"
:class="i % 2 === 0 ? 'bg-white/2' : ''">
<td class="p-4 text-default font-medium">{{ row.label }}</td>
<td class="p-4 text-center">
<UIcon v-if="row.pro === true" name="i-heroicons-check-circle"
class="text-primary-400 text-lg" />
<span v-else-if="typeof row.pro === 'string'"
class="text-primary-300 text-xs font-semibold">{{ row.pro }}</span>
<UIcon v-else name="i-heroicons-minus" class="text-dimmed" />
</td>
<td class="p-4 text-center">
<UIcon v-if="row.legend === true" name="i-heroicons-check-circle"
class="text-purple-400 text-lg" />
<span v-else-if="typeof row.legend === 'string'"
class="text-purple-300 text-xs font-semibold">{{ row.legend }}</span>
<UIcon v-else name="i-heroicons-minus" class="text-dimmed" />
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Quotes -->
<div v-motion :initial="{ opacity: 0, y: 40 }" :visible="{ opacity: 1, y: 0, transition: { duration: 700 } }"
class="px-4 pb-24 max-w-4xl mx-auto">
<h2 class="text-2xl font-extrabold text-highlighted text-center mb-2">
{{ $t('pricing.quotes_title') }}
</h2>
<p class="text-muted text-center text-sm mb-10">{{ $t('pricing.quotes_subtitle') }}</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<UPageCard v-for="(q, i) in quotes" :key="q.author" v-motion :initial="{ opacity: 0, y: 30 }"
:visible="{ opacity: 1, y: 0, transition: { duration: 500, delay: i * 120 } }">
<div class="flex items-center gap-3 mb-4">
<UAvatar :src="q.image" :text="q.initials" size="md" class="ring-2 ring-white/10 shrink-0" />
<div>
<div class="text-highlighted text-sm font-semibold leading-tight">{{ q.author }}</div>
<div class="text-muted text-xs">{{ q.role }}</div>
</div>
</div>
<p class="text-default text-sm leading-relaxed italic">&ldquo; {{ q.text }} &rdquo;</p>
</UPageCard>
</div>
</div>
<!-- FAQ -->
<div v-motion :initial="{ opacity: 0, y: 40 }" :visible="{ opacity: 1, y: 0, transition: { duration: 700 } }"
class="px-4 pb-24 max-w-2xl mx-auto">
<h2 class="text-2xl font-extrabold text-highlighted text-center mb-8">{{ $t('pricing.faq_title') }}</h2>
<UAccordion :items="faqItems" />
</div>
<!-- CTA -->
<div v-motion :initial="{ opacity: 0, scale: 0.95 }"
:visible="{ opacity: 1, scale: 1, transition: { duration: 600 } }"
class="px-4 pb-32 max-w-xl mx-auto text-center">
<h2 class="text-3xl font-extrabold text-highlighted mb-3">{{ $t('pricing.cta_title') }}</h2>
<p class="text-muted mb-6">{{ $t('pricing.cta_desc') }}</p>
<a href="https://apps.apple.com/app/rebreak" target="_blank" rel="noopener">
<UButton size="xl" class="font-bold px-10 rounded-full">
{{ $t('pricing.cta_button') }}
<template #trailing>
<UIcon name="i-heroicons-arrow-right" />
</template>
</UButton>
</a>
</div>
</div>
</template>
<script setup lang="ts">
import type { PricingPlanProps } from "@nuxt/ui";
definePageMeta({ layout: "default" });
const { t } = useI18n();
const billing = ref<'monthly' | 'yearly'>('monthly');
const billingOptions = computed(() => [
{ value: 'monthly', label: t('pricing.billing_monthly'), badge: null },
{ value: 'yearly', label: t('pricing.billing_yearly'), badge: t('pricing.billing_save_pct') },
] as const);
// Geräteübergreifend: ein Abo, alle Plattformen (heroicons sind offline gebündelt).
const crossDevices = [
{ icon: 'i-heroicons-device-phone-mobile', label: 'iPhone' },
{ icon: 'i-heroicons-device-phone-mobile', label: 'Android' },
{ icon: 'i-heroicons-computer-desktop', label: 'Mac' },
{ icon: 'i-heroicons-computer-desktop', label: 'Windows' },
];
const proMonthly = 3.99;
const legendMonthly = 7.99;
const proPrice = computed(() => {
if (billing.value === 'yearly') return (29 / 12).toFixed(2);
return proMonthly.toFixed(2);
});
const legendPrice = computed(() => {
if (billing.value === 'yearly') return (59 / 12).toFixed(2);
return legendMonthly.toFixed(2);
});
const billingCycleLabel = computed(() => {
if (billing.value === 'yearly') return t('pricing.billing_per_year');
return t('pricing.billing_per_month');
});
// Marketing: alle Plan-Buttons zeigen auf App-Store
const appStoreUrl = "https://apps.apple.com/app/rebreak";
const plans = computed<PricingPlanProps[]>(() => [
{
title: t('pricing.plan_pro_title'),
description: t('pricing.plan_pro_desc'),
price: `${proPrice.value}`,
billingCycle: billingCycleLabel.value,
scale: true,
badge: t('pricing.plan_recommended'),
features: [
t('pricing.feat_pro_devices'),
t('pricing.feat_blocklist'),
t('pricing.feat_pro_domains'),
t('pricing.feat_pro_mail'),
t('pricing.feat_coach_pro'),
t('pricing.feat_streak'),
t('pricing.feat_urge_stats'),
t('pricing.feat_community_post'),
],
button: {
label: t('pricing.plan_pro_btn'),
to: appStoreUrl,
target: "_blank",
},
},
{
title: t('pricing.plan_legend_title'),
description: t('pricing.plan_legend_desc'),
price: `${legendPrice.value}`,
billingCycle: billingCycleLabel.value,
features: [
t('pricing.feat_all_pro'),
t('pricing.feat_legend_devices'),
t('pricing.feat_legend_domains'),
t('pricing.feat_legend_mail'),
t('pricing.feat_legend_binder'),
t('pricing.feat_legend_add'),
t('pricing.feat_legend_validate'),
t('pricing.feat_legend_groups'),
t('pricing.feat_coach_legend'),
],
button: {
label: t('pricing.plan_legend_btn'),
to: appStoreUrl,
target: "_blank",
color: "neutral" as const,
variant: "subtle" as const,
},
},
]);
const comparisonRows = computed(() => [
{ label: t('pricing.comp_devices'), pro: t('pricing.comp_pro_devices'), legend: t('pricing.comp_legend_devices') },
{ label: t('pricing.comp_domains'), pro: t('pricing.comp_pro_domains'), legend: t('pricing.comp_legend_domains') },
{ label: t('pricing.comp_mail'), pro: t('pricing.comp_pro_mail_val'), legend: t('pricing.comp_legend_mail_val') },
{ label: t('pricing.comp_coach'), pro: t('pricing.comp_pro_coach_val'), legend: t('pricing.comp_legend_coach_val') },
{ label: t('pricing.comp_blocklist'), pro: true, legend: true },
{ label: t('pricing.comp_streak'), pro: true, legend: true },
{ label: t('pricing.comp_urge'), pro: true, legend: true },
{ label: t('pricing.comp_sos'), pro: true, legend: true },
{ label: t('pricing.comp_community'), pro: true, legend: true },
{ label: t('pricing.comp_post'), pro: true, legend: true },
{ label: t('pricing.comp_buddy'), pro: true, legend: true },
{ label: t('pricing.comp_urge_stats'), pro: true, legend: true },
{ label: t('pricing.comp_binder'), pro: false, legend: true },
{ label: t('pricing.comp_add_domain'), pro: false, legend: true },
{ label: t('pricing.comp_validate'), pro: false, legend: true },
{ label: t('pricing.comp_groups'), pro: false, legend: true },
]);
const quotes = [
{
text: "Zwischen Reiz und Reaktion liegt ein Raum. In diesem Raum liegt unsere Macht, unsere Reaktion zu wählen.",
author: "Viktor Frankl",
role: "Psychiater & Logotherapeut",
image: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Viktor_Frankl2.jpg/200px-Viktor_Frankl2.jpg",
initials: "VF",
},
{
text: "Bis du das Unbewusste bewusst machst, wird es dein Leben lenken und du wirst es Schicksal nennen.",
author: "Carl Gustav Jung",
role: "Psychiater & Begründer der analytischen Psychologie",
image: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/CGJung.jpg/200px-CGJung.jpg",
initials: "CJ",
},
{
text: "Sucht ist keine Charakterschwäche. Sie ist eine Erkrankung des Gehirns und sie ist behandelbar.",
author: "Nora Volkow",
role: "Neurowissenschaftlerin, Direktorin des NIDA",
image: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9b/Nora_Volkow2.jpg/200px-Nora_Volkow2.jpg",
initials: "NV",
},
];
const faqItems = computed(() => [
{ label: t('pricing.faq1_q'), content: t('pricing.faq1_a') },
{ label: t('pricing.faq2_q'), content: t('pricing.faq2_a') },
{ label: t('pricing.faq3_q'), content: t('pricing.faq3_a') },
{ label: t('pricing.faq4_q'), content: t('pricing.faq4_a') },
{ label: t('pricing.faq5_q'), content: t('pricing.faq5_a') },
{ label: t('pricing.faq6_q'), content: t('pricing.faq6_a') },
]);
</script>