kenso war das

This commit is contained in:
2026-02-03 23:27:25 +01:00
parent 6bf3c603d8
commit ef2faa21fd
73 changed files with 8416 additions and 257 deletions

View File

@@ -3,18 +3,18 @@
<head>
<meta charset="UTF-8" />
<!-- Favicons -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes" />
<meta name="description" content="E-Mail-Sorter - AI-powered email sorting for maximum productivity. Automatically organize your inbox." />
<meta name="description" content="MailFlow - AI-powered email sorting for maximum productivity. Automatically organize your inbox." />
<meta name="theme-color" content="#22c55e" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<title>EmailSorter - Your inbox, finally organized</title>
<title>MailFlow - Your inbox, finally organized</title>
<!-- Prevent FOUC for dark mode - Enhanced Dark Reader detection -->
<script>
(function() {

View File

@@ -1,5 +1,5 @@
{
"name": "emailsorter-client",
"name": "mailflow-client",
"private": true,
"version": "0.0.0",
"type": "module",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
client/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
client/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -1,17 +1,22 @@
{
"name": "EmailSorter",
"short_name": "EmailSorter",
"name": "MailFlow",
"short_name": "MailFlow",
"description": "AI-powered email sorting for maximum productivity",
"icons": [
{
"src": "/favicon.svg",
"src": "/favicon.png",
"sizes": "any",
"type": "image/svg+xml"
"type": "image/png"
},
{
"src": "/apple-touch-icon.svg",
"src": "/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/svg+xml"
"type": "image/png"
},
{
"src": "/logo.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#22c55e",

View File

@@ -13,7 +13,7 @@ export function ShareResults({ sortedCount, referralCode }: ShareResultsProps) {
const [copied, setCopied] = useState(false)
const { user } = useAuth()
const shareText = `I cleaned up ${sortedCount} emails with EmailSorter${referralCode ? `! Use code ${referralCode} for a bonus.` : '!'}`
const shareText = `I cleaned up ${sortedCount} emails with MailFlow${referralCode ? `! Use code ${referralCode} for a bonus.` : '!'}`
const shareUrl = referralCode
? `${window.location.origin}?ref=${referralCode}`
: window.location.origin
@@ -33,7 +33,7 @@ export function ShareResults({ sortedCount, referralCode }: ShareResultsProps) {
if (navigator.share) {
try {
await navigator.share({
title: 'EmailSorter - Clean Inbox',
title: 'MailFlow - Clean Inbox',
text: shareText,
url: shareUrl,
})

View File

@@ -53,7 +53,7 @@ export function FAQ() {
<p className="mt-10 text-center text-sm text-slate-600 dark:text-slate-400">
Still unsure?{' '}
<a
href="mailto:support@emailsorter.webklar.com"
href="mailto:support@mailflow.webklar.com"
className="text-slate-700 dark:text-slate-300 hover:underline"
>
Email us we reply fast

View File

@@ -1,107 +1,174 @@
import {
Brain,
Zap,
Shield,
Clock,
Settings,
Inbox,
Filter
import {
FolderTree,
MousePointerClick,
CalendarClock,
ScanSearch,
ShieldCheck,
Sparkles,
Filter,
} from 'lucide-react'
const features = [
const FEATURES = [
{
icon: Inbox,
icon: FolderTree,
title: "Categories, not chaos",
description: "Leads, clients, invoices, newsletters — sorted into folders. Your inbox shows what pays first.",
color: "from-violet-500 to-purple-600",
highlight: true,
desc: "Leads, clients, invoices, newsletters — sorted into folders. Your inbox shows what pays first.",
glowClass: "from-orange-500/20 to-amber-500/20",
delay: 0,
},
{
icon: Zap,
icon: MousePointerClick,
title: "One click to sort",
description: "Connect your inbox, click Sort Now. No rules to write. We read and categorize; you review.",
color: "from-amber-500 to-orange-600",
highlight: true,
desc: "Connect your inbox, click Sort Now. No rules to write. We read and categorize; you review.",
glowClass: "from-emerald-500/20 to-teal-500/20",
delay: 50,
},
{
icon: Settings,
icon: CalendarClock,
title: "Runs when you want",
description: "Sort on demand or set a schedule. Your inbox stays organized without you touching it.",
color: "from-blue-500 to-cyan-600",
highlight: true,
desc: "Sort on demand or set a schedule. Your inbox stays organized without you touching it.",
glowClass: "from-blue-500/20 to-indigo-500/20",
delay: 100,
},
{
icon: Brain,
icon: ScanSearch,
title: "Content-aware sorting",
description: "We look at sender, subject, and a short snippet to decide the category. No keyword lists.",
color: "from-green-500 to-emerald-600"
desc: "We look at sender, subject, and a short snippet to decide the category. No keyword lists.",
glowClass: "from-violet-500/20 to-purple-500/20",
delay: 150,
},
{
icon: Shield,
icon: ShieldCheck,
title: "Minimal data",
description: "We only read what we need to categorize. No storing email body or attachments. GDPR compliant.",
color: "from-pink-500 to-rose-600"
desc: "We only read what we need to categorize. No storing email body or attachments. GDPR compliant.",
glowClass: "from-green-500/20 to-emerald-500/20",
delay: 200,
},
{
icon: Clock,
icon: Sparkles,
title: "Less time on triage",
description: "Spend less time deciding what's important. Inbox shows clients and leads first.",
color: "from-indigo-500 to-blue-600"
desc: "Spend less time deciding what's important. Inbox shows clients and leads first.",
glowClass: "from-cyan-500/20 to-blue-500/20",
delay: 250,
},
]
function FeatureItem({
icon: Icon,
title,
desc,
glowClass,
delay,
}: {
icon: React.ElementType
title: string
desc: string
glowClass: string
delay: number
}) {
return (
<div
className="group relative"
style={{ animationDelay: `${delay}ms` }}
>
<div
aria-hidden="true"
className={`absolute inset-0 bg-gradient-to-br ${glowClass} rounded-xl opacity-0 group-hover:opacity-100 blur-xl transition-opacity duration-300 -z-10 scale-110`}
/>
<div
className="relative p-4 rounded-xl border border-slate-200 dark:border-slate-700 bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm h-full
transform-gpu transition-all duration-300 ease-out
hover:border-primary-400/40 dark:hover:border-primary-500/40 hover:shadow-lg hover:shadow-primary-500/5
hover:-translate-y-1 hover:scale-[1.02]
group-hover:bg-white dark:group-hover:bg-slate-800/95"
>
<div
aria-hidden="true"
className="absolute inset-0 rounded-xl overflow-hidden opacity-0 group-hover:opacity-100 transition-opacity duration-300"
>
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full transition-transform duration-700 ease-out bg-gradient-to-r from-transparent via-white/10 dark:via-white/5 to-transparent" />
</div>
<div className="relative z-10">
<div className="flex items-start gap-3 mb-3">
<div
className="p-2.5 bg-primary-500/10 rounded-xl text-primary-600 dark:text-primary-400 shrink-0
transition-all duration-300 ease-out
group-hover:bg-primary-500 group-hover:text-white
group-hover:scale-110 group-hover:rotate-3 group-hover:shadow-lg group-hover:shadow-primary-500/25"
>
<Icon className="h-6 w-6" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden />
</div>
<h4 className="text-base font-semibold text-slate-900 dark:text-slate-100 transition-colors duration-300 group-hover:text-primary-600 dark:group-hover:text-primary-400 pt-0.5">
{title}
</h4>
</div>
<p
className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed transition-all duration-300
opacity-90 group-hover:opacity-100 group-hover:text-slate-700 dark:group-hover:text-slate-300"
>
{desc}
</p>
</div>
</div>
</div>
)
}
export function Features() {
return (
<section id="features" className="py-24 bg-slate-50 dark:bg-slate-900">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section header */}
<div className="text-center mb-16">
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
What it does
</h2>
<p className="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
Sort incoming mail into categories so your inbox shows what matters first. No rules to write.
</p>
</div>
{/* Single card in engineering-card style */}
<div
data-slot="card"
className="relative bg-white/80 dark:bg-slate-800/60 text-slate-900 dark:text-slate-100 flex flex-col gap-6 rounded-2xl border border-slate-200 dark:border-slate-700 backdrop-blur-md shadow-lg shadow-black/5 dark:shadow-black/20 transform-gpu transition-all duration-300 ease-out hover:shadow-2xl hover:shadow-primary-500/10 hover:border-primary-400/30 dark:hover:border-primary-500/20 overflow-hidden
before:absolute before:inset-0 before:rounded-2xl before:p-[1.5px] before:bg-gradient-to-br before:from-primary-500/50 before:via-accent-500/30 before:to-primary-500/50 before:-z-10 before:opacity-0 before:transition-opacity before:duration-500 hover:before:opacity-100
after:absolute after:inset-0 after:rounded-2xl after:-z-20 after:bg-gradient-to-br after:from-primary-500/5 after:via-transparent after:to-accent-500/5 after:opacity-0 after:transition-opacity after:duration-500 hover:after:opacity-100
p-6 md:p-8"
>
<div className="mb-8 text-center">
<h3 className="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-2">What it does</h3>
<p className="text-slate-600 dark:text-slate-400 max-w-xl mx-auto">
Sort incoming mail into categories so your inbox shows what matters first. No rules to write.
</p>
</div>
{/* Features grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{features.map((feature, index) => (
<FeatureCard key={index} {...feature} index={index} />
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{FEATURES.map((feature, i) => (
<FeatureItem key={i} {...feature} />
))}
</div>
{/* Bottom illustration */}
<div className="mt-20 relative">
<div className="bg-white dark:bg-slate-800 rounded-3xl border border-slate-200 dark:border-slate-700 shadow-xl p-8 max-w-4xl mx-auto">
<div className="grid md:grid-cols-3 gap-8 items-center">
{/* Before */}
<div className="text-center">
<div className="w-20 h-20 mx-auto mb-4 rounded-2xl bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<Inbox className="w-10 h-10 text-red-500 dark:text-red-400" />
</div>
<h4 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Before</h4>
<p className="text-sm text-slate-500 dark:text-slate-400">Inbox chaos</p>
<div className="mt-3 text-3xl font-bold text-red-500 dark:text-red-400">847</div>
<p className="text-xs text-slate-400 dark:text-slate-500">unread emails</p>
</div>
{/* Arrow */}
<div className="hidden md:flex justify-center">
<div className="w-24 h-24 rounded-full bg-gradient-to-r from-primary-500 to-accent-500 flex items-center justify-center shadow-lg">
<Filter className="w-10 h-10 text-white" />
{/* Before → After strip (engineering-card style) */}
<div
className="mt-8 p-5 bg-gradient-to-r from-primary-500/10 via-accent-500/10 to-primary-500/10 rounded-xl border border-primary-500/20
relative overflow-hidden group/featured transition-all duration-300 hover:border-primary-500/40 hover:shadow-lg hover:shadow-primary-500/10"
>
<div
aria-hidden="true"
className="absolute inset-0 rounded-xl opacity-0 group-hover/featured:opacity-100 transition-opacity duration-500"
>
<div className="absolute inset-[-1px] rounded-xl bg-gradient-to-r from-primary-500 via-accent-500 to-primary-500 bg-[length:200%_100%] animate-gradient-x opacity-30" />
</div>
<div className="relative flex flex-col md:flex-row items-stretch md:items-center gap-6 md:gap-8">
<div className="flex-1 flex flex-col md:flex-row items-center gap-4 md:gap-6 p-4 rounded-xl bg-slate-50/50 dark:bg-slate-900/50 border border-slate-200/50 dark:border-slate-700/50">
<span className="text-xs font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Before</span>
<div className="text-center md:text-left">
<p className="font-bold text-slate-900 dark:text-slate-100 text-lg">Inbox chaos</p>
<p className="text-3xl md:text-4xl font-bold text-primary-600 dark:text-primary-400 mt-1">847</p>
<p className="text-sm text-slate-500 dark:text-slate-400">unread emails</p>
</div>
</div>
{/* After */}
<div className="text-center">
<div className="w-20 h-20 mx-auto mb-4 rounded-2xl bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
<Inbox className="w-10 h-10 text-green-500 dark:text-green-400" />
<div className="flex items-center justify-center shrink-0 text-slate-400 dark:text-slate-500 group-hover/featured:text-primary-500 transition-colors duration-300">
<Filter className="w-7 h-7" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden />
</div>
<div className="flex-1 flex flex-col md:flex-row items-center gap-4 md:gap-6 p-4 rounded-xl bg-primary-500/10 border border-primary-500/20">
<span className="text-xs font-semibold uppercase tracking-wider text-primary-600 dark:text-primary-400">After</span>
<div className="text-center md:text-left">
<p className="font-bold text-slate-900 dark:text-slate-100 text-lg">All sorted</p>
<p className="text-3xl md:text-4xl font-bold text-primary-600 dark:text-primary-400 mt-1">12</p>
<p className="text-sm text-slate-500 dark:text-slate-400">important emails</p>
</div>
<h4 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">After</h4>
<p className="text-sm text-slate-500 dark:text-slate-400">All sorted</p>
<div className="mt-3 text-3xl font-bold text-green-500 dark:text-green-400">12</div>
<p className="text-xs text-slate-400 dark:text-slate-500">important emails</p>
</div>
</div>
</div>
@@ -110,31 +177,3 @@ export function Features() {
</section>
)
}
interface FeatureCardProps {
icon: React.ElementType
title: string
description: string
color: string
index: number
highlight?: boolean
}
function FeatureCard({ icon: Icon, title, description, color, index, highlight }: FeatureCardProps) {
return (
<div
className={`group rounded-2xl p-6 border transition-all duration-300 ${
highlight
? 'bg-gradient-to-br from-white dark:from-slate-800 to-slate-50 dark:to-slate-800/50 border-primary-200 dark:border-primary-800 hover:border-primary-300 dark:hover:border-primary-700 hover:shadow-xl'
: 'bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-primary-200 dark:hover:border-primary-800 hover:shadow-lg'
}`}
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className={`w-14 h-14 rounded-xl bg-gradient-to-br ${color} flex items-center justify-center mb-5 group-hover:scale-110 transition-transform duration-300 shadow-lg`}>
<Icon className="w-7 h-7 text-white" />
</div>
<h3 className={`${highlight ? 'text-2xl' : 'text-xl'} font-semibold text-slate-900 dark:text-slate-100 mb-2`}>{title}</h3>
<p className="text-slate-600 dark:text-slate-400">{description}</p>
</div>
)
}

View File

@@ -8,12 +8,15 @@ export function Footer() {
<div className="grid md:grid-cols-4 gap-12">
{/* Brand */}
<div className="md:col-span-1">
<Link to="/" className="flex items-center gap-2 mb-4">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold text-white">
E-Mail-<span className="text-primary-400">Sorter</span>
<Link to="/" className="flex items-center mb-4 leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-20 h-20 rounded-xl object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-xl font-bold text-white ml-[5px]">
Mail<span className="text-primary-400">Flow</span>
</span>
</Link>
<p className="text-sm text-slate-400 mb-6">
@@ -79,10 +82,10 @@ export function Footer() {
<ul className="space-y-3">
<li>
<a
href="mailto:support@emailsorter.webklar.com"
href="mailto:support@mailflow.webklar.com"
className="hover:text-white transition-colors"
>
support@emailsorter.webklar.com
support@mailflow.webklar.com
</a>
</li>
<li>
@@ -125,7 +128,7 @@ export function Footer() {
<div className="mt-12 pt-8 border-t border-slate-800">
<div className="flex flex-col md:flex-row justify-between items-center gap-4 mb-4">
<p className="text-sm text-slate-500">
© {new Date().getFullYear()} EmailSorter
© {new Date().getFullYear()} MailFlow
</p>
</div>
{/* webklar.com Verweis */}

View File

@@ -1,3 +1,4 @@
import { useState, useRef, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { captureUTMParams } from '@/lib/analytics'
import { cn } from '@/lib/utils'
@@ -5,8 +6,106 @@ import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { ArrowRight, Sparkles, Check } from 'lucide-react'
const HERO_TESTIMONIALS = [
{ name: 'Sarah M.', quote: 'Inbox finally under control.', position: 'top-20 -left-10', rotate: '-20deg', side: 'left' as const },
{ name: 'Tom K.', quote: 'Newsletters sorted automatically.', position: 'top-1/2 -left-10 -translate-y-1/2', rotate: '-10deg', side: 'left' as const },
{ name: 'Lisa R.', quote: 'Leads never get lost again.', position: 'top-20 -right-10', rotate: '20deg', side: 'right' as const },
{ name: 'Jan P.', quote: 'Game changer for freelancers.', position: 'bottom-20 -left-10', rotate: '-10deg', side: 'left' as const },
{ name: 'Anna L.', quote: 'Gmail & Outlook in one place.', position: 'bottom-1/2 -right-10 -translate-y-1/2', rotate: '10deg', side: 'right' as const },
{ name: 'Max B.', quote: 'Spam stays out of my inbox.', position: 'bottom-20 -right-10', rotate: '20deg', side: 'right' as const },
]
function useScrollBow(heroRef: React.RefObject<HTMLElement | null>) {
const [progress, setProgress] = useState(0)
useEffect(() => {
const hero = heroRef?.current
// #region agent log
fetch('http://127.0.0.1:7245/ingest/e4d1df4e-a6e3-4cf2-a51c-bd8134c263cd',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'Hero.tsx:useScrollBow effect',message:'effect run',data:{hasHero:!!hero,height:hero?.getBoundingClientRect?.()?.height,rectTop:hero?.getBoundingClientRect?.()?.top},timestamp:Date.now(),sessionId:'debug-session',hypothesisId:'H4'})}).catch(()=>{});
// #endregion
if (!hero) return
const onScroll = () => {
const rect = hero.getBoundingClientRect()
const h = rect.height
if (h <= 0) return
const p = Math.max(0, Math.min(1, -rect.top / h))
setProgress((prev) => {
if (Math.abs(prev - p) > 0.05) {
// #region agent log
fetch('http://127.0.0.1:7245/ingest/e4d1df4e-a6e3-4cf2-a51c-bd8134c263cd',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'Hero.tsx:onScroll',message:'progress update',data:{p,rectTop:rect.top,h},timestamp:Date.now(),sessionId:'debug-session',hypothesisId:'H1'})}).catch(()=>{});
// #endregion
}
return p
})
}
onScroll()
window.addEventListener('scroll', onScroll, { passive: true })
return () => window.removeEventListener('scroll', onScroll)
}, [heroRef])
return progress
}
type TestimonialItem = (typeof HERO_TESTIMONIALS)[number]
function HeroEdgeCard({ name, quote, position, rotate, side, scrollProgress }: TestimonialItem & { scrollProgress: number }) {
const dropY = scrollProgress * 80
const flyOutX = scrollProgress * 600
const moveX = side === 'left' ? -flyOutX : flyOutX
const transform = `translate(${moveX}px, ${dropY}px) rotate(${rotate})`
const opacity = Math.max(0, 1 - scrollProgress * 1.2)
const visibility = opacity <= 0 ? 'hidden' : 'visible'
// When scrollProgress === 0, do NOT set opacity so CSS hero-edge-in can run (staggered fade-in).
// Once user scrolls, we drive opacity from scroll so cards bow out.
const styleOpacity = scrollProgress > 0 ? opacity : undefined
// #region agent log
if (name === HERO_TESTIMONIALS[0].name) {
fetch('http://127.0.0.1:7245/ingest/e4d1df4e-a6e3-4cf2-a51c-bd8134c263cd',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'Hero.tsx:HeroEdgeCard',message:'card style',data:{scrollProgress,transform,opacity,styleOpacity,visibility},timestamp:Date.now(),sessionId:'debug-session',runId:'post-fix',hypothesisId:'H1,H2'})}).catch(()=>{});
}
// #endregion
return (
<div
className={cn(
'hero-edge-card absolute z-20 flex items-center gap-1 rounded-md bg-white p-1 shadow-md dark:bg-slate-800 border border-slate-200 dark:border-slate-700',
position,
'hidden md:flex transition-all duration-200 ease-out'
)}
style={{ transform, ...(styleOpacity !== undefined ? { opacity: styleOpacity } : {}), visibility }}
>
<img
alt="E-Mail"
width={128}
height={128}
className="h-auto max-h-32 w-32 shrink-0 object-contain object-center block m-0"
src="/logo.png"
onLoad={(e) => {
const img = e.currentTarget
if (name === HERO_TESTIMONIALS[0].name) {
// #region agent log
fetch('http://127.0.0.1:7245/ingest/e4d1df4e-a6e3-4cf2-a51c-bd8134c263cd',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'Hero.tsx:img onLoad',message:'image size',data:{naturalWidth:img.naturalWidth,naturalHeight:img.naturalHeight,width:img.width,height:img.height},timestamp:Date.now(),sessionId:'debug-session',runId:'post-fix',hypothesisId:'H5'})}).catch(()=>{});
// #endregion
}
}}
/>
<div className="max-w-[160px]">
<h3 className="text-sm font-medium text-slate-800 dark:text-slate-200">{name}</h3>
<p className="text-xs text-slate-600 dark:text-slate-400">{quote}</p>
</div>
</div>
)
}
export function Hero() {
const navigate = useNavigate()
const heroRef = useRef<HTMLElement>(null)
const scrollProgress = useScrollBow(heroRef)
// #region agent log
useEffect(() => {
const t = setTimeout(() => {
const el = heroRef.current
fetch('http://127.0.0.1:7245/ingest/e4d1df4e-a6e3-4cf2-a51c-bd8134c263cd',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'Hero.tsx:mount',message:'after mount',data:{hasHero:!!el,height:el?.getBoundingClientRect?.()?.height,innerWidth:typeof window!=='undefined'?window.innerWidth:0},timestamp:Date.now(),sessionId:'debug-session',hypothesisId:'H3,H4'})}).catch(()=>{});
}, 100)
return () => clearTimeout(t)
}, [])
// #endregion
const handleCTAClick = () => {
// Capture UTM parameters before navigation
@@ -15,8 +114,13 @@ export function Hero() {
}
return (
<section className="relative min-h-screen flex items-center overflow-hidden">
{/* Background */}
<section ref={heroRef} className="relative min-h-screen flex items-center overflow-hidden">
{/* Edge cards stick to sides, animate on scroll (bow out) */}
{HERO_TESTIMONIALS.map((t) => (
<HeroEdgeCard key={t.name} {...t} scrollProgress={scrollProgress} />
))}
{/* Background unchanged */}
<div className="absolute inset-0 gradient-hero" />
<div className="absolute inset-0 gradient-mesh opacity-30" />

View File

@@ -5,6 +5,7 @@ import {
PartyPopper,
ArrowDown
} from 'lucide-react'
import SpotlightCard from '@/components/ui/SpotlightCard'
const steps = [
{
@@ -91,9 +92,12 @@ function StepCard({ icon: Icon, step, title, description }: StepCardProps) {
return (
<div className="relative">
{/* Card */}
<div className="bg-slate-50 dark:bg-slate-800 rounded-2xl p-6 text-center hover:bg-white dark:hover:bg-slate-700 hover:shadow-xl transition-all duration-300 border border-transparent hover:border-slate-200 dark:hover:border-slate-600">
<SpotlightCard
spotlightColor="rgba(34, 197, 94, 0.2)"
className="bg-slate-50 dark:bg-slate-800 rounded-2xl p-6 text-center hover:bg-white dark:hover:bg-slate-700 hover:shadow-xl transition-all duration-300 border border-transparent hover:border-slate-200 dark:hover:border-slate-600"
>
{/* Step number */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-gradient-to-r from-primary-500 to-primary-600 dark:from-primary-600 dark:to-primary-700 text-white text-sm font-bold px-4 py-1 rounded-full shadow-md">
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-gradient-to-r from-primary-500 to-primary-600 dark:from-primary-600 dark:to-primary-700 text-white text-sm font-bold px-4 py-1 rounded-full shadow-md z-10">
{step}
</div>
@@ -105,7 +109,7 @@ function StepCard({ icon: Icon, step, title, description }: StepCardProps) {
{/* Content */}
<h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">{title}</h3>
<p className="text-slate-600 dark:text-slate-400 text-sm">{description}</p>
</div>
</SpotlightCard>
</div>
)
}

View File

@@ -32,12 +32,15 @@ export function Navbar() {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<Link to="/" className="flex items-center gap-2">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100">
E-Mail-<span className="text-primary-600 dark:text-primary-400">Sorter</span>
<Link to="/" className="flex items-center leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-20 h-20 rounded-xl object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100 ml-[5px]">
Mail<span className="text-primary-600 dark:text-primary-400">Flow</span>
</span>
</Link>

View File

@@ -1,4 +1,5 @@
import { Code2, Users, Zap } from 'lucide-react'
import SpotlightCard from '@/components/ui/SpotlightCard'
const items = [
{
@@ -33,8 +34,9 @@ export function Testimonials() {
<div className="grid md:grid-cols-3 gap-6">
{items.map((item, index) => (
<div
<SpotlightCard
key={index}
spotlightColor="rgba(34, 197, 94, 0.25)"
className="bg-white/5 backdrop-blur-sm rounded-xl p-6 border border-white/10"
>
<div className="w-10 h-10 rounded-lg bg-primary-500/20 flex items-center justify-center mb-4">
@@ -42,7 +44,7 @@ export function Testimonials() {
</div>
<h3 className="text-base font-semibold text-white mb-1">{item.title}</h3>
<p className="text-slate-400 text-sm">{item.description}</p>
</div>
</SpotlightCard>
))}
</div>
</div>

View File

@@ -0,0 +1,44 @@
.card-spotlight {
position: relative;
overflow: hidden;
--mouse-x: 50%;
--mouse-y: 50%;
--spotlight-color: rgba(34, 197, 94, 0.2);
transition: border-color 0.3s ease;
}
.card-spotlight::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(
circle 400px at var(--mouse-x) var(--mouse-y),
var(--spotlight-color),
transparent 80%
);
opacity: 0;
transition: opacity 0.5s ease;
pointer-events: none;
z-index: 0;
}
.card-spotlight:hover::before,
.card-spotlight:focus-within::before {
opacity: 0.6;
}
.card-spotlight:hover {
border-color: rgba(34, 197, 94, 0.4);
}
.dark .card-spotlight:hover {
border-color: rgba(34, 197, 94, 0.3);
}
.card-spotlight > * {
position: relative;
z-index: 1;
}

View File

@@ -0,0 +1,36 @@
import { useRef } from 'react'
import './SpotlightCard.css'
interface SpotlightCardProps {
children: React.ReactNode
className?: string
spotlightColor?: string
}
const SpotlightCard = ({ children, className = '', spotlightColor = 'rgba(34, 197, 94, 0.2)' }: SpotlightCardProps) => {
const divRef = useRef<HTMLDivElement>(null)
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!divRef.current) return
const rect = divRef.current.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
divRef.current.style.setProperty('--mouse-x', `${x}px`)
divRef.current.style.setProperty('--mouse-y', `${y}px`)
divRef.current.style.setProperty('--spotlight-color', spotlightColor)
}
return (
<div
ref={divRef}
onMouseMove={handleMouseMove}
className={`card-spotlight ${className}`}
>
{children}
</div>
)
}
export default SpotlightCard

View File

@@ -210,6 +210,40 @@ body {
opacity: 0.8;
}
/* Hero edge cards scroll animation (stick to side, bow out on scroll) */
@keyframes hero-edge-in {
from { opacity: 0; }
to { opacity: 1; }
}
.hero-edge-card {
animation: hero-edge-in 0.6s ease-out forwards;
}
/* Bild nur so groß wie der sichtbare Inhalt, keine unsichtbare Box */
.hero-edge-card img {
display: block;
max-width: 8rem;
max-height: 8rem;
width: auto;
height: auto;
object-fit: contain;
flex-shrink: 0;
line-height: 0;
vertical-align: middle;
}
.hero-edge-card:nth-child(1) { animation-delay: 0.2s; opacity: 0; }
.hero-edge-card:nth-child(2) { animation-delay: 0.35s; opacity: 0; }
.hero-edge-card:nth-child(3) { animation-delay: 0.5s; opacity: 0; }
.hero-edge-card:nth-child(4) { animation-delay: 0.4s; opacity: 0; }
.hero-edge-card:nth-child(5) { animation-delay: 0.55s; opacity: 0; }
.hero-edge-card:nth-child(6) { animation-delay: 0.7s; opacity: 0; }
@media (max-width: 767px) {
.hero-edge-card { opacity: 0.2 !important; }
}
/* Animation classes */
@keyframes float {
0%, 100% { transform: translateY(0px); }
@@ -226,6 +260,16 @@ body {
to { opacity: 1; transform: translateY(0); }
}
/* Gradient animation for feature card strip (engineering-card style) */
@keyframes gradient-x {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.animate-gradient-x {
animation: gradient-x 3s ease infinite;
}
.animate-float {
animation: float 6s ease-in-out infinite;
}

View File

@@ -236,12 +236,15 @@ export function Dashboard() {
<header className="bg-white/90 dark:bg-slate-900/90 backdrop-blur-md border-b border-slate-200 dark:border-slate-700 sticky top-0 z-50 shadow-sm">
<div className="w-full px-3 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-14 sm:h-16">
<Link to="/" className="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
<div className="w-8 h-8 sm:w-9 sm:h-9 rounded-lg bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center shadow-lg shadow-primary-500/20">
<Mail className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
</div>
<span className="text-base sm:text-lg font-bold text-slate-900 dark:text-slate-100 whitespace-nowrap">
E-Mail-<span className="text-primary-600 dark:text-primary-400">Sorter</span>
<Link to="/" className="flex items-center flex-shrink-0 leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-20 h-20 sm:w-20 sm:h-20 rounded-lg shadow-lg object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-base sm:text-lg font-bold text-slate-900 dark:text-slate-100 whitespace-nowrap ml-[5px]">
Mail<span className="text-primary-600 dark:text-primary-400">Flow</span>
</span>
</Link>

View File

@@ -32,12 +32,15 @@ export function ForgotPassword() {
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo */}
<Link to="/" className="flex items-center justify-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100">
Email<span className="text-primary-600 dark:text-primary-400">Sorter</span>
<Link to="/" className="flex items-center justify-center mb-8 leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-24 h-24 rounded-xl object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100 ml-[5px]">
Mail<span className="text-primary-600 dark:text-primary-400">Flow</span>
</span>
</Link>

View File

@@ -43,7 +43,7 @@ export function Imprint() {
<div className="space-y-6 text-slate-700 dark:text-slate-300">
<div>
<h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">Operator</h3>
<p className="mb-2">EmailSorter is operated by:</p>
<p className="mb-2">MailFlow is operated by:</p>
<p className="mb-4">
<strong>webklar.com</strong><br />
Kenso Grimm, Justin Klein
@@ -90,12 +90,12 @@ export function Imprint() {
</a>
</p>
<p className="mt-4 text-sm text-slate-600 dark:text-slate-400">
For questions regarding EmailSorter specifically:{' '}
For questions regarding MailFlow specifically:{' '}
<a
href="mailto:support@emailsorter.com"
href="mailto:support@mailflow.com"
className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
>
support@emailsorter.com
support@mailflow.com
</a>
</p>
</div>

View File

@@ -36,12 +36,15 @@ export function Login() {
<div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 bg-slate-900">
<div className="w-full max-w-md">
{/* Logo */}
<Link to="/" className="flex items-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold text-white">
E-Mail-<span className="text-primary-400">Sorter</span>
<Link to="/" className="flex items-center mb-8 leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-24 h-24 rounded-xl object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-xl font-bold text-white ml-[5px]">
Mail<span className="text-primary-400">Flow</span>
</span>
</Link>
@@ -133,7 +136,7 @@ export function Login() {
Your inbox under control
</h2>
<p className="text-primary-100">
Thousands of users already trust EmailSorter for more productive email communication.
Thousands of users already trust MailFlow for more productive email communication.
</p>
</div>
</div>

View File

@@ -40,7 +40,7 @@ export function Privacy() {
<div className="bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg p-6 mb-8">
<h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-4">Data Protection Information</h2>
<p className="text-slate-700 dark:text-slate-300 mb-4">
EmailSorter is operated by webklar.com. The following privacy policy applies to the use of this website and our services.
MailFlow is operated by webklar.com. The following privacy policy applies to the use of this website and our services.
</p>
<h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">1. Responsible Party</h3>
@@ -64,7 +64,7 @@ export function Privacy() {
<h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">2. Data Collection and Processing</h3>
<p className="text-slate-700 dark:text-slate-300 mb-4">
When you use EmailSorter, we collect and process the following data:
When you use MailFlow, we collect and process the following data:
</p>
<ul className="list-disc list-inside text-slate-700 dark:text-slate-300 mb-4 space-y-2 ml-4">
<li>Account information (email address, name)</li>
@@ -78,7 +78,7 @@ export function Privacy() {
We process your data exclusively for the following purposes:
</p>
<ul className="list-disc list-inside text-slate-700 dark:text-slate-300 mb-4 space-y-2 ml-4">
<li>Providing and improving the EmailSorter service</li>
<li>Providing and improving the MailFlow service</li>
<li>Automated email sorting and categorization</li>
<li>Processing payments and subscriptions</li>
<li>Customer support and communication</li>

View File

@@ -86,7 +86,7 @@ export function Register() {
</Badge>
<h2 className="text-4xl font-bold text-white mb-6">
Start with EmailSorter today
Start with MailFlow today
</h2>
<ul className="space-y-4 mb-8">
@@ -117,12 +117,15 @@ export function Register() {
<div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 bg-white dark:bg-slate-900">
<div className="w-full max-w-md">
{/* Logo */}
<Link to="/" className="flex items-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100">
E-Mail-<span className="text-primary-600 dark:text-primary-400">Sorter</span>
<Link to="/" className="flex items-center mb-8 leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-24 h-24 rounded-xl object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100 ml-[5px]">
Mail<span className="text-primary-600 dark:text-primary-400">Flow</span>
</span>
</Link>

View File

@@ -86,12 +86,15 @@ export function ResetPassword() {
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo */}
<Link to="/" className="flex items-center justify-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100">
Email<span className="text-primary-600 dark:text-primary-400">Sorter</span>
<Link to="/" className="flex items-center justify-center mb-8 leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-24 h-24 rounded-xl object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100 ml-[5px]">
Mail<span className="text-primary-600 dark:text-primary-400">Flow</span>
</span>
</Link>

View File

@@ -2413,7 +2413,7 @@ export function Settings() {
<CardHeader>
<CardTitle>Referrals</CardTitle>
<CardDescription>
Share EmailSorter and earn rewards
Share MailFlow and earn rewards
</CardDescription>
</CardHeader>
<CardContent>
@@ -2500,7 +2500,7 @@ export function Settings() {
<Card>
<CardHeader>
<CardTitle>Current Subscription</CardTitle>
<CardDescription>Manage your EmailSorter subscription</CardDescription>
<CardDescription>Manage your MailFlow subscription</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-primary-50 to-accent-50 dark:from-primary-900/30 dark:to-accent-900/30 rounded-xl border border-primary-100 dark:border-primary-800">

View File

@@ -54,12 +54,15 @@ export function VerifyEmail() {
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo */}
<Link to="/" className="flex items-center justify-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100">
Email<span className="text-primary-600 dark:text-primary-400">Sorter</span>
<Link to="/" className="flex items-center justify-center mb-8 leading-none">
<img
src="/logo.png"
alt="MailFlow Logo"
className="w-24 h-24 rounded-xl object-contain pr-[5px] block"
style={{ display: 'block', margin: 0, padding: 0 }}
/>
<span className="text-xl font-bold text-slate-900 dark:text-slate-100 ml-[5px]">
Mail<span className="text-primary-600 dark:text-primary-400">Flow</span>
</span>
</Link>
@@ -98,7 +101,7 @@ export function VerifyEmail() {
</div>
<p className="text-slate-600 dark:text-slate-400">
Du kannst jetzt alle Features von EmailSorter nutzen.
Du kannst jetzt alle Features von MailFlow nutzen.
</p>
<Button onClick={() => navigate('/dashboard')} className="w-full">
@@ -144,8 +147,8 @@ export function VerifyEmail() {
{/* Help text */}
<p className="text-center text-sm text-slate-500 dark:text-slate-400 mt-6">
Probleme? Kontaktiere uns unter{' '}
<a href="mailto:support@emailsorter.de" className="text-primary-600 dark:text-primary-400 hover:underline">
support@emailsorter.de
<a href="mailto:support@mailflow.de" className="text-primary-600 dark:text-primary-400 hover:underline">
support@mailflow.de
</a>
</p>
</div>