DER DIE DAS 1207 — preview (2 blocks)
const sectionBgWhite = { background: 'radial-gradient(ellipse 70% 45% at 50% 0%, rgba(255, 200, 0, 0.12) 0%, transparent 50%), radial-gradient(ellipse 55% 40% at 100% 100%, rgba(253, 246, 227, 0.9) 0%, transparent 50%), radial-gradient(ellipse 58% 42% at 0% 35%, rgba(28, 176, 246, 0.07) 0%, transparent 55%)', }; const sectionBgCream = { background: 'radial-gradient(ellipse 75% 50% at 50% 0%, rgba(255, 200, 0, 0.2) 0%, transparent 52%), radial-gradient(ellipse 60% 48% at 12% 85%, rgba(88, 204, 2, 0.14) 0%, transparent 45%), radial-gradient(ellipse 55% 40% at 95% 25%, rgba(28, 176, 246, 0.1) 0%, transparent 48%)', }; const sectionBgDark = { background: 'radial-gradient(ellipse 80% 55% at 50% -18%, rgba(88, 204, 2, 0.16) 0%, transparent 48%), radial-gradient(ellipse 65% 45% at 100% 105%, rgba(28, 176, 246, 0.12) 0%, transparent 45%), radial-gradient(ellipse 50% 38% at 0% 45%, rgba(255, 200, 0, 0.08) 0%, transparent 42%)', }; const PROMO_PCT = 50; const FULL_PRICE = 39; const PROMO_PRICE = 20; // --- Components --- const ScrollProgress = () => { if (!mnemoMotionOk) return null; const { scrollYProgress } = useScroll(); const scaleX = useSpring(scrollYProgress, { stiffness: 120, damping: 28, mass: 0.4 }); const reduce = useReducedMotion(); return ( <motion.div className="fixed top-0 left-0 right-0 h-1.5 z-[100] origin-left pointer-events-none" style={{ scaleX: reduce ? scrollYProgress : scaleX, transformOrigin: '0% 50%', background: 'linear-gradient(90deg, #58cc02, #1cb0f6, #ffc800)' }} /> ); }; const HeroVideo = React.memo(function HeroVideo() { return ( <div id="mnemo-hero-video" className="mnemo-hero-video"> <div className="video-wrap"> <iframe className="video-frame" src="https://kinescope.io/embed/n8fCvUPHSiRPnepDfzk4F8" title="Der Die Das guide video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen /> </div> </div> ); }); const useDailyCountdown = () => { const [parts, setParts] = useState({ h: '00', m: '00', s: '00' }); useEffect(() => { const tick = () => { const now = new Date(); const end = new Date(now); end.setDate(end.getDate() + 1); end.setHours(0, 0, 0, 0); const diff = Math.max(0, end.getTime() - now.getTime()); const h = Math.floor(diff / (1000 * 60 * 60)); const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const s = Math.floor((diff % (1000 * 60)) / 1000); setParts({ h: h.toString().padStart(2, '0'), m: m.toString().padStart(2, '0'), s: s.toString().padStart(2, '0'), }); }; tick(); const timer = setInterval(tick, 1000); return () => clearInterval(timer); }, []); return parts; }; const HeroPromoTeaser = () => { const reduce = useReducedMotion(); const parts = useDailyCountdown(); return ( <motion.div variants={{ hidden: { opacity: 0, y: 16 }, show: { opacity: 1, y: 0 } }} className="flex flex-col items-center justify-center gap-5 sm:gap-6 w-full" > <div className="hero-promo-teaser w-[58%] sm:w-[70%] max-w-sm rounded-2xl px-4 py-4 sm:py-5 mx-auto flex flex-col items-center"> <span className="text-2xl sm:text-3xl font-black text-duo-gold-dark leading-none"> 50% only today </span> <div className="mt-3 w-full grid grid-cols-3 gap-2 sm:gap-3"> {[ { v: parts.h, l: 'H' }, { v: parts.m, l: 'M' }, { v: parts.s, l: 'S' }, ].map((cell) => ( <div key={cell.l} className="flex flex-col items-center gap-1"> <div className="text-xs sm:text-sm font-black uppercase tracking-widest text-duo-muted">{cell.l}</div> <div className="w-full rounded-xl bg-duo-blue-dark px-2 py-2.5 shadow-[0_4px_0_0_rgba(11,79,153,0.45)]"> <div className="font-display text-lg sm:text-xl font-black tracking-wider text-white text-center">{cell.v}</div> </div> </div> ))} </div> </div> <motion.a href="#pricing" whileHover={reduce ? {} : { scale: 1.03, y: -4 }} whileTap={{ scale: 0.98 }} className="btn-3d btn-blue btn-shine w-[58%] sm:w-[70%] max-w-sm rounded-2xl px-4 py-3.5 sm:py-4 flex items-center justify-center text-center mx-auto shadow-[0_10px_0_0_#0e9bd8]" > <span className="text-lg sm:text-2xl lg:text-3xl font-black leading-tight tracking-tight text-white"> Get the course </span> </motion.a> </motion.div> ); }; const Hero = () => { const reduce = useReducedMotion(); return ( <section className="pt-12 pb-8 overflow-hidden bg-white relative hero-mesh"> {/* Decorative Elements */} <motion.div animate={reduce ? {} : { y: [0, -10, 0], rotate: [0, 5, 0] }} transition={{ duration: 4, repeat: Infinity, ease: "easeInOut" }} className="absolute top-20 left-[10%] opacity-25 hidden lg:block" > <Star className="w-14 h-14 text-duo-gold fill-duo-gold drop-shadow-sm" /> </motion.div> <motion.div animate={reduce ? {} : { y: [0, 10, 0], rotate: [0, -5, 0] }} transition={{ duration: 5, repeat: Infinity, ease: "easeInOut" }} className="absolute bottom-20 right-[10%] opacity-25 hidden lg:block" > <Zap className="w-14 h-14 text-duo-blue fill-duo-blue drop-shadow-sm" /> </motion.div> <div className="max-w-7xl mx-auto px-4 sm:px-6 relative z-10"> <motion.div initial="hidden" whileInView="show" viewport={{ once: true, margin: '-40px' }} variants={{ hidden: {}, show: { transition: { staggerChildren: reduce ? 0 : 0.12, delayChildren: reduce ? 0 : 0.05 } }, }} className="text-center max-w-4xl mx-auto" > <motion.h1 variants={{ hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 } }} transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }} className="hero-headline mb-8 max-w-3xl mx-auto text-duo-dark text-center w-full text-3xl font-black sm:text-4xl md:text-5xl" > How to stop memorizing german articles and get{' '} <span className="text-duo-blue">der</span> <span className="text-duo-muted"> · </span> <span className="text-duo-red">die</span> <span className="text-duo-muted"> · </span> <span className="text-duo-green">das</span> {' '}right{' '} without mistakes </motion.h1> <motion.div variants={{ hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 } }} transition={{ duration: 0.5 }} className="mb-8 flex justify-center" > <a href="#mnemo-hero-video" className="hero-banner" > First, watch my guide </a> </motion.div> <HeroVideo /> <motion.div variants={{ hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 } }} transition={{ duration: 0.5 }} className="flex flex-col items-center gap-3 mb-10 mt-4 max-w-4xl mx-auto tracking-tight text-center" > <span className="hero-banner"> Now you can master articles </span> <ChevronDown className="h-8 w-8 sm:h-10 sm:w-10 text-orange-500 opacity-90" strokeWidth={2.75} /> <motion.p className="text-2xl sm:text-3xl lg:text-4xl text-duo-dark font-black leading-tight" animate={reduce ? {} : { y: [0, -3, 0] }} transition={{ duration: 2.4, repeat: Infinity, ease: 'easeInOut' }} > In <span className="text-4xl sm:text-5xl lg:text-6xl text-duo-green-dark align-middle">2 weeks</span> you learn all the rules for determining articles — and stop guessing{' '} <span className="text-duo-blue">der</span>,{' '} <span className="text-duo-red">die</span>, or{' '} <span className="text-duo-green">das</span> </motion.p> </motion.div> <HeroPromoTeaser /> </motion.div> </div> </section> ); };
const SolutionSection = () => { const solutions = [ { title: 'Suffixes — up to 40%', percent: null, body: (<>Examples: -ung, -heit, -keit, -chen, -lein<br />These cover up to 40% of words where gender can be predicted from the word form.</>), ring: 'border-duo-blue/20', box: 'bg-duo-blue/10', numColor: 'text-duo-blue', tilt: -1, glow: 'radial-gradient(circle, rgba(28,176,246,0.22) 0%, transparent 70%)' }, { title: 'Semantic groups', percent: 'up to 35%', body: 'Days of the week, seasons, car brands, metals, and other categories — together they cover about 35% of words.', ring: 'border-duo-gold/20', box: 'bg-duo-gold/10', numColor: 'text-duo-gold-dark', tilt: 0, glow: 'radial-gradient(circle, rgba(255,200,0,0.24) 0%, transparent 70%)' }, { title: 'Associations', percent: '25%', body: 'The remaining ~25% is learned through associations and mnemonics for exceptions.', ring: 'border-duo-green/20', box: 'bg-duo-green/10', numColor: 'text-duo-green-dark', tilt: 1, glow: 'radial-gradient(circle, rgba(88,204,2,0.2) 0%, transparent 70%)' }, ]; const SolutionCard = ({ c, delay }) => ( <motion.div initial={{ opacity: 0, y: 28 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay, type: 'spring', stiffness: 180, damping: 20 }} whileHover={{ y: -8, rotate: c.tilt * 0.8, transition: { type: 'spring', stiffness: 280, damping: 16 } }} className="card-3d p-7 sm:p-9 lg:p-11 text-center bg-white relative overflow-hidden group w-full max-w-md mx-auto sm:max-w-xl lg:max-w-3xl" > <div className="absolute -right-8 -top-8 w-36 h-36 rounded-full opacity-0 group-hover:opacity-100 pointer-events-none motion-safe:transition-opacity motion-safe:duration-500 scale-100 group-hover:scale-125" style={{ background: c.glow }} /> <h4 className="mb-5 flex items-center justify-center gap-3 text-2xl sm:text-3xl font-black"> <span>{c.title}</span> {c.percent && ( <span className={`inline-flex items-center justify-center rounded-xl border-2 px-3 py-1 text-xl font-black leading-none ${c.ring} ${c.box} ${c.numColor}`}> {c.percent} </span> )} </h4> <p className="text-duo-muted font-bold text-base sm:text-lg">{c.body}</p> </motion.div> ); return ( <section id="artikel-system" className="scroll-mt-28 py-12 text-duo-dark relative overflow-hidden bg-white"> <div className="absolute inset-0 pointer-events-none" aria-hidden style={sectionBgWhite} /> <div className="relative z-10 mx-auto max-w-6xl px-4 sm:px-6"> <div className="mb-8 text-center sm:mb-10"> <h2 className="mb-4 text-3xl font-black sm:text-4xl md:text-5xl">Here's what will solve your article problem</h2> <p className="mx-auto max-w-3xl text-base font-bold leading-relaxed text-duo-muted sm:text-lg md:text-xl"> A clear system </p> </div> <div className="relative z-10 mx-auto flex max-w-4xl flex-col items-center gap-1 py-1 sm:gap-2 lg:max-w-6xl"> <SolutionCard c={solutions[0]} delay={0} /> <div className="flex justify-center py-0.5" aria-hidden> <ChevronDown className="h-8 w-8 text-duo-blue opacity-70" strokeWidth={2.5} /> </div> <SolutionCard c={solutions[1]} delay={0.12} /> <div className="flex justify-center py-0.5" aria-hidden> <ChevronDown className="h-8 w-8 text-duo-gold-dark opacity-70" strokeWidth={2.5} /> </div> <SolutionCard c={solutions[2]} delay={0.24} /> </div> </div> </section> ); }; const MasterySpeedometer = ({ progress }) => { const needleRot = useTransform(progress, [0, 1], [-90, 90]); const arcColor = useTransform(progress, [0, 1], ['#ff4b4b', '#58cc02']); const [pctLabel, setPctLabel] = useState(0); const syncPct = (v) => setPctLabel(Math.round(Math.min(1, Math.max(0, v)) * 100)); useLayoutEffect(() => { syncPct(progress.get()); }, [progress]); useMotionValueEvent(progress, 'change', syncPct); return ( <div className="flex flex-col items-center gap-0.5" role="img" aria-label={`Course progress: ${pctLabel} percent`}> <svg viewBox="0 0 220 128" className="w-[230px] sm:w-[290px] h-[130px] sm:h-[152px]" aria-hidden> <path d="M 28 100 A 82 82 0 0 1 192 100" fill="none" stroke="#e5e5e5" strokeWidth="16" strokeLinecap="round" /> <motion.path d="M 28 100 A 82 82 0 0 1 192 100" fill="none" strokeWidth="16" strokeLinecap="round" style={{ pathLength: progress, stroke: arcColor }} /> <motion.g style={{ rotate: needleRot, transformOrigin: '110px 100px' }}> <line x1="110" y1="100" x2="110" y2="30" stroke="#3c3c3c" strokeWidth="5" strokeLinecap="round" /> <circle cx="110" cy="100" r="8" fill="#3c3c3c" /> </motion.g> <text x="20" y="122" fill="#777777" className="font-display" style={{ fontSize: 14, fontWeight: 900 }}>Before</text> <text x="200" y="122" fill="#777777" className="font-display" textAnchor="end" style={{ fontSize: 14, fontWeight: 900 }}>After</text> </svg> <motion.div className="font-display text-4xl sm:text-5xl font-black tabular-nums -mt-1" style={{ color: arcColor }}> {pctLabel}<span className="text-2xl sm:text-3xl align-top">%</span> </motion.div> <p className="text-xs sm:text-sm font-black text-duo-muted uppercase tracking-[0.18em]">result</p> </div> ); }; const MasteryPathSection = () => { const reduce = useReducedMotion(); const gaugeProgress = useMotionValue(0); useEffect(() => { if (!mnemoMotionOk) return; if (reduce) { gaugeProgress.set(1); return; } const controls = animate(gaugeProgress, 1, { duration: 3.2, ease: [0.4, 0, 0.2, 1], }); return () => controls.stop(); }, [gaugeProgress, reduce]); const steps = [ { range: "PHASE 1", title: "Modules 0–4", content: "Learn what articles are and why they matter.", desc: "Study the first reliable rules — suffixes and endings with no exceptions. You'll already confidently determine gender for many German words.", color: "bg-duo-blue" }, { range: "PHASE 2", title: "Modules 5–9", content: "Master the main suffixes for all three genders and semantic groups.", desc: "Memory techniques are added — stories and spatial images that help you retain the material without mechanical memorization.", color: "bg-duo-gold" }, { range: "PHASE 3", title: "Modules 10–14", content: "Cover semantic groups, exceptions, and tricky cases.", desc: "Work with complex words, words with dual gender, and unfamiliar nouns. The final module brings everything together into one decision-making system.", color: "bg-duo-green" } ]; return ( <section className="py-16 bg-white relative overflow-hidden"> <div className="absolute inset-0 pointer-events-none" aria-hidden style={sectionBgWhite} /> <div className="max-w-7xl mx-auto px-4 sm:px-6 relative z-10"> <div className="text-center mb-10 sm:mb-12"> <h2 className="text-4xl sm:text-6xl font-black mb-6">What you'll get from the course</h2> </div> <div className="flex flex-col items-center gap-8 lg:gap-10"> <div className="order-2 flex-1 min-w-0 w-full"> <div className="space-y-8 lg:space-y-10 max-w-5xl mx-auto"> {steps.map((step, i) => ( <motion.div key={i} initial={{ opacity: 0, x: i % 2 === 0 ? -28 : 28 }} whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true }} transition={{ type: 'spring', stiffness: 120, damping: 20 }} whileHover={{ scale: 1.01, boxShadow: '0 12px 0 0 #e5e5e5', transition: { type: 'spring', stiffness: 400, damping: 22 } }} className="card-3d bg-white p-8 sm:p-10 lg:p-12 flex flex-col gap-8 lg:gap-9" > <div className="text-center lg:text-left"> <span className={`inline-block px-4 py-1 rounded-full text-white text-xs font-black mb-4 ${step.color}`}> {step.range} </span> <h3 className="text-3xl sm:text-4xl font-black mb-3">{step.title}</h3> <p className="text-duo-muted font-bold text-base sm:text-lg uppercase tracking-widest">{step.content}</p> </div> <div className="border-t-2 border-duo-border pt-8"> <p className="text-lg sm:text-xl font-bold leading-relaxed text-duo-dark italic"> "{step.desc}" </p> </div> </motion.div> ))} </div> </div> {mnemoMotionOk ? ( <div className="order-1 shrink-0 w-full flex justify-center"> <div className="w-full max-w-[340px] rounded-2xl border-2 border-duo-border bg-white p-1.5 shadow-[0_6px_0_0_#e5e5e5]"> <div className="rounded-[14px] border-2 border-duo-border bg-white px-4 py-5 sm:px-5 sm:py-6"> <MasterySpeedometer progress={gaugeProgress} /> </div> </div> </div> ) : null} </div> <motion.div initial={{ opacity: 0, y: 24 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true, margin: '-60px' }} transition={{ type: 'spring', stiffness: 120, damping: 22 }} className="hidden mt-16 relative overflow-hidden rounded-[28px] border-4 border-duo-green p-1 sm:p-1.5 shadow-[0_12px_0_0_#46a302,0_24px_48px_-12px_rgba(0,0,0,0.18)] bg-gradient-to-br from-white via-duo-cream to-[#e8fbd4]" > <div className="absolute inset-0 pointer-events-none bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(88,204,2,0.35),transparent_55%),radial-gradient(ellipse_60%_40%_at_100%_100%,rgba(28,176,246,0.12),transparent_45%)]" aria-hidden /> <div className="relative rounded-[22px] bg-white/90 backdrop-blur-sm px-5 py-6 sm:px-10 sm:py-10 border-2 border-white/80"> <div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-6"> <div> <span className="inline-flex items-center gap-2 rounded-full bg-duo-green px-4 py-1.5 text-[11px] font-black uppercase tracking-widest text-white shadow-[0_3px_0_0_#46a302] mb-3"> <Sparkles className="w-3.5 h-3.5" strokeWidth={2.5} /> Video inside the course </span> <h3 className="text-3xl sm:text-4xl font-black text-duo-dark leading-tight tracking-tight"> What the course looks like <span className="text-duo-green-dark">inside</span> </h3> </div> </div> </div> </motion.div> </div> </section> ); }; const PricingSection = () => { const ORDER_HREF = '#order:Der Die Das Mastery Course=39 :::extid=edloahpma0fuvu5'; const openOrderLink = (href) => { // Tilda payment: real <a href="#order:..."> + hashchange fallback. const triggerOrder = () => { const link = document.createElement('a'); link.href = href; link.setAttribute('data-tilda-order', '1'); link.style.display = 'none'; document.body.appendChild(link); link.click(); setTimeout(() => { try { document.body.removeChild(link); } catch (e) {} }, 100); }; try { if (window.history && window.history.pushState) { const base = window.location.pathname + window.location.search; window.history.pushState(null, '', base + href); } else { window.location.hash = href; } window.dispatchEvent(new HashChangeEvent('hashchange')); } catch (e) { try { window.location.hash = href; } catch (e2) {} } setTimeout(triggerOrder, 0); setTimeout(triggerOrder, 80); }; const includedCheck = ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" className="w-5 h-5 text-duo-green-dark" aria-hidden> <path d="M20 6 9 17l-5-5" /> </svg> ); const included = [ { title: 'The full rule system in 15 video lessons' }, { title: 'Interactive exercises to reinforce the rules' }, { title: 'Access to materials and exercises for 6 months after the course' }, { title: 'Reference guide to all suffixes and article rules' }, ]; return ( <section id="pricing" className="scroll-mt-28 py-16 bg-duo-cream relative overflow-hidden"> <div className="absolute inset-0 pointer-events-none" aria-hidden style={sectionBgCream} /> <div className="max-w-6xl mx-auto px-4 sm:px-6 relative z-10"> <div className="text-center mb-8 sm:mb-10"> <h2 className="text-4xl sm:text-6xl font-black text-duo-gold-dark leading-tight tracking-tight"> Solve your article problem with <br /> <span className="text-duo-blue">Der</span>{' '} <span className="text-duo-red">Die</span>{' '} <span className="text-duo-green">Das</span> {' '}— once and for all </h2> <div className="mt-4 mx-auto w-full max-w-lg"> </div> </div> <div className="mt-4 rounded-[28px] border-4 border-duo-gold bg-white shadow-[0_10px_0_0_#e6a800] overflow-hidden ring-1 ring-duo-gold/35"> <div className="grid grid-cols-1 divide-y-2 divide-duo-border"> <div className="pricing-col p-7 sm:p-9 flex flex-col justify-center items-center text-center bg-white"> <div className="pricing-price-box rounded-2xl border-2 border-duo-border bg-duo-cream/50 p-6 shadow-[0_4px_0_0_#e5e5e5]"> <div className="flex flex-wrap items-baseline justify-center gap-3"> <span className="text-4xl lg:text-5xl font-black text-duo-border line-through tabular-nums">€{FULL_PRICE}</span> <span className="text-5xl sm:text-6xl font-black text-duo-gold-dark leading-none tabular-nums">€{PROMO_PRICE}</span> </div> <p className="pricing-deadline mt-2 text-base sm:text-lg font-black text-duo-red"> {PROMO_PCT}% off · only today </p> </div> <motion.a href={ORDER_HREF} onClick={() => openOrderLink(ORDER_HREF)} whileHover={{ scale: 1.02, y: -3 }} whileTap={{ scale: 0.98 }} className="pricing-signup btn-3d btn-blue btn-shine w-full sm:w-auto sm:min-w-[420px] mx-auto py-5 rounded-2xl text-lg sm:text-xl lg:text-2xl flex items-center justify-center gap-2 shadow-[0_10px_0_0_#0e9bd8] no-underline" > Get the course </motion.a> <p className="pricing-secure mt-4 flex items-center justify-center gap-2 text-sm lg:text-base font-bold text-duo-muted"> <Lock className="w-3.5 h-3.5" /> Secure payment </p> </div> <div className="p-7 sm:p-9 lg:p-10 bg-white"> <h3 className="text-xl sm:text-2xl font-black text-duo-dark mb-6 pb-4 border-b-2 border-duo-border">What's included</h3> <ul className="flex flex-col gap-0 divide-y divide-duo-border"> {included.map((item, i) => ( <li key={i} className="flex gap-4 items-center py-4 first:pt-0"> <div className="w-11 h-11 rounded-xl flex items-center justify-center shrink-0 border-2 border-duo-green/30 bg-duo-green/10 shadow-[0_2px_0_0_#46a302]"> {includedCheck} </div> <h4 className="text-duo-dark font-black text-base sm:text-lg leading-snug">{item.title}</h4> </li> ))} </ul> </div> </div> </div> </div> </section> ); }; const ReviewsCarousel = () => { const reduce = useReducedMotion(); const reviews = [ { name: 'Olena', meta: 'Kyiv · B1 learner', text: 'Honestly, this course is amazing. The presentation is super clear — finally someone explains articles without drowning you in grammar. The exercises really help lock everything in. After week one I was already spotting suffixes automatically.', color: 'bg-duo-blue/15 text-duo-blue-dark border-duo-blue/25', }, { name: 'Alex', meta: 'Berlin · living in Germany', text: 'I used to guess der/die/das every single time. Super frustrating. The course breaks it into real rules, not random lists. After finishing, speaking got so much easier — I actually feel confident now.', color: 'bg-duo-green/15 text-duo-green-dark border-duo-green/25', }, { name: 'Jessica', meta: 'London · course graduate', text: 'Loved the format: short video, clear rule, then practice right away. The exercises are the best part — they force you to think, not just watch. Now I know all the article rules and exceptions actually make sense.', color: 'bg-duo-red/10 text-duo-red border-duo-red/20', }, { name: 'Max', meta: 'Vienna · 2 weeks in', text: 'Best money I spent on German this year. Presentation is on point, not boring at all. Suffixes, semantic groups, everything clicks. Wish I had this before my B1 exam — would have saved me months of guessing.', color: 'bg-duo-gold/20 text-duo-gold-dark border-duo-gold/35', }, ]; const [index, setIndex] = useState(0); const review = reviews[index]; useEffect(() => { if (reduce) return; const timer = setInterval(() => { setIndex((i) => (i + 1) % reviews.length); }, 6500); return () => clearInterval(timer); }, [reduce, reviews.length]); const go = (dir) => { setIndex((i) => (i + dir + reviews.length) % reviews.length); }; const carouselArrow = (direction) => ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="w-5 h-5" aria-hidden> {direction === 'left' ? <path d="M15 18l-6-6 6-6" /> : <path d="M9 18l6-6-6-6" />} </svg> ); return ( <section id="reviews" className="py-16 bg-duo-cream relative overflow-hidden"> <div className="absolute inset-0 pointer-events-none" aria-hidden style={sectionBgCream} /> <div className="max-w-4xl mx-auto px-4 sm:px-6 relative z-10"> <div className="text-center mb-8 sm:mb-10"> <span className="inline-flex items-center gap-2 rounded-full bg-white border-2 border-duo-border px-4 py-1.5 text-[11px] font-black uppercase tracking-widest text-duo-muted shadow-[0_3px_0_0_#e5e5e5] mb-4"> <Star className="w-3.5 h-3.5 text-duo-gold fill-duo-gold" /> Student reviews </span> <h2 className="text-3xl sm:text-5xl font-black text-duo-dark leading-tight tracking-tight"> What learners say about the course </h2> </div> <div className="relative"> <motion.div key={index} initial={reduce ? false : { opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.35, ease: [0.22, 1, 0.36, 1] }} className="rounded-[28px] border-4 border-white bg-white p-7 sm:p-9 shadow-[0_10px_0_0_#e5e5e5] ring-1 ring-duo-border/60" > <span className="block text-5xl sm:text-6xl text-duo-gold/70 font-black leading-none mb-4" aria-hidden="true">“</span> <p className="text-lg sm:text-xl font-bold leading-relaxed text-duo-dark mb-6"> “{review.text}” </p> <div className="flex flex-wrap items-center justify-between gap-4 pt-5 border-t-2 border-duo-border"> <div className="flex items-center gap-3"> <div className={`w-12 h-12 rounded-full border-2 flex items-center justify-center font-black text-lg shrink-0 ${review.color}`}> {review.name.charAt(0)} </div> <div className="text-left"> <p className="font-black text-duo-dark text-lg leading-tight">{review.name}</p> <p className="text-sm font-bold text-duo-muted">{review.meta}</p> </div> </div> <div className="flex items-center gap-0.5" aria-label="5 out of 5 stars"> {[...Array(5)].map((_, i) => ( <Star key={i} className="w-5 h-5 text-duo-gold fill-duo-gold" /> ))} </div> </div> </motion.div> <div className="mt-6 flex items-center justify-center gap-4"> <button type="button" onClick={() => go(-1)} aria-label="Previous review" className="w-11 h-11 rounded-xl border-2 border-duo-border bg-white flex items-center justify-center text-duo-dark shadow-[0_4px_0_0_#e5e5e5] hover:bg-duo-cream transition-colors" > {carouselArrow('left')} </button> <div className="flex items-center gap-2"> {reviews.map((_, i) => ( <button key={i} type="button" onClick={() => setIndex(i)} aria-label={`Review ${i + 1}`} className={`h-2.5 rounded-full transition-all ${i === index ? 'w-8 bg-duo-green' : 'w-2.5 bg-duo-border hover:bg-duo-muted'}`} /> ))} </div> <button type="button" onClick={() => go(1)} aria-label="Next review" className="w-11 h-11 rounded-xl border-2 border-duo-border bg-white flex items-center justify-center text-duo-dark shadow-[0_4px_0_0_#e5e5e5] hover:bg-duo-cream transition-colors" > {carouselArrow('right')} </button> </div> </div> </div> </section> ); }; const FaqSection = () => { const [openIndex, setOpenIndex] = useState(null); const items = [ { q: 'Is this course right for me?', a: 'Yes — if der, die, and das slow you down when you speak or write German. The course fits learners at A1–B2 who want a clear system instead of guessing. You follow short video lessons, practice with exercises, and build confidence step by step — even if articles have felt random until now.', }, { q: "What if I can't keep up?", a: 'Modules open gradually, so you are not overwhelmed with everything at once. You also get access to all materials and exercises for 6 months after the course — enough time to revisit lessons, repeat exercises, and learn at your own pace.', }, ]; return ( <section id="faq" className="scroll-mt-28 py-16 bg-duo-cream relative overflow-hidden"> <div className="absolute inset-0 pointer-events-none" aria-hidden style={sectionBgCream} /> <div className="max-w-3xl mx-auto px-4 sm:px-6 relative z-10"> <div className="text-center mb-8 sm:mb-10"> <h2 className="text-3xl sm:text-5xl font-black text-duo-dark leading-tight tracking-tight"> FAQ </h2> </div> <div className="flex flex-col gap-3"> {items.map((item, i) => { const open = openIndex === i; return ( <div key={item.q} className="rounded-2xl border-2 border-duo-border bg-white shadow-[0_4px_0_0_#e5e5e5] overflow-hidden" > <button type="button" onClick={() => setOpenIndex(open ? null : i)} aria-expanded={open} className="w-full flex items-center justify-between gap-4 p-5 sm:p-6 text-left bg-white border-0 cursor-pointer" > <span className="font-black text-base sm:text-lg text-duo-dark leading-snug">{item.q}</span> <ChevronDown className={`w-5 h-5 shrink-0 text-duo-muted transition-transform duration-200 ${open ? 'rotate-180' : ''}`} strokeWidth={2.5} /> </button> <div className={`overflow-hidden transition-all duration-300 ease-in-out ${open ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'}`} > <p className="px-5 sm:px-6 pb-5 sm:pb-6 pt-0 text-base sm:text-lg font-bold leading-relaxed text-duo-muted border-t-2 border-duo-border/80"> {item.a} </p> </div> </div> ); })} </div> </div> </section> ); }; const ContactsSection = () => { const contactIcons = { email: ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round" aria-hidden> <rect width="20" height="16" x="2" y="4" rx="2" /> <path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /> </svg> ), instagram: ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round" aria-hidden> <rect width="20" height="20" x="2" y="2" rx="5" ry="5" /> <path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" /> <line x1="17.5" x2="17.51" y1="6.5" y2="6.5" /> </svg> ), whatsapp: ( <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden> <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.435 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413z" /> </svg> ), }; const contacts = [ { label: 'Email', href: 'mailto:mnemo.german.team@gmail.com', ariaLabel: 'Send email', icon: contactIcons.email, color: 'bg-duo-blue/10 text-duo-blue-dark border-duo-blue/25', }, { label: 'Instagram', href: 'https://www.instagram.com/mnemo.deutsch/', ariaLabel: 'Open Instagram', icon: contactIcons.instagram, color: 'bg-duo-red/10 text-duo-red border-duo-red/20', }, { label: 'WhatsApp', href: 'https://wa.me/380938906935', ariaLabel: 'Open WhatsApp', icon: contactIcons.whatsapp, color: 'bg-[#25D366]/15 text-[#128C7E] border-[#25D366]/35', }, ]; return ( <section id="contact" className="py-14 bg-white relative overflow-hidden"> <div className="absolute inset-0 pointer-events-none" aria-hidden style={sectionBgWhite} /> <div className="max-w-4xl mx-auto px-4 sm:px-6 relative z-10"> <div className="text-center mb-8"> <h2 className="text-3xl sm:text-4xl font-black text-duo-dark leading-tight tracking-tight"> Contact us </h2> <p className="mt-3 text-base sm:text-lg font-bold text-duo-muted"> Questions about the course? Reach out anytime. </p> </div> <div className="grid grid-cols-1 sm:grid-cols-3 gap-4"> {contacts.map((item) => ( <motion.a key={item.label} href={item.href} aria-label={item.ariaLabel} target={item.href.startsWith('http') ? '_blank' : undefined} rel={item.href.startsWith('http') ? 'noopener noreferrer' : undefined} whileHover={{ y: -4, scale: 1.01 }} whileTap={{ scale: 0.98 }} className="contact-card rounded-2xl border-2 border-duo-border bg-white p-5 text-center shadow-[0_6px_0_0_#e5e5e5] no-underline transition-colors hover:bg-duo-cream/40" > <div className={`contact-card-icon mx-auto mb-3 w-12 h-12 rounded-xl border-2 flex items-center justify-center ${item.color}`}> {item.icon} </div> <p className="contact-card-label text-xs font-black uppercase tracking-widest text-duo-muted">{item.label}</p> </motion.a> ))} </div> </div> </section> ); }; const Footer = () => { return ( <footer className="bg-duo-dark py-12 text-center border-t-4 border-white/5 relative overflow-hidden"> <div className="absolute inset-0 pointer-events-none" aria-hidden style={{ background: 'radial-gradient(ellipse 100% 85% at 50% 0%, rgba(88, 204, 2, 0.12) 0%, transparent 55%), radial-gradient(ellipse 50% 40% at 100% 100%, rgba(28, 176, 246, 0.08) 0%, transparent 45%)', }} /> <div className="max-w-6xl mx-auto px-4 sm:px-6 relative z-10"> <div className="text-white/20 font-black text-sm mb-6 space-x-4"> <a href="#" className="hover:text-white/40">Privacy Policy</a> <span>·</span> <a href="#" className="hover:text-white/40">Terms of Use</a> <span>·</span> <a href="#" className="hover:text-white/40">Contact</a> </div> <p className="text-white/10 text-xs font-black uppercase tracking-widest"> © 2026 DER, DIE, DAS — Mastery Course. All rights reserved. </p> </div> </footer> ); }; function App() { return ( <div className="mnemo-dd min-h-screen w-full max-w-full mx-auto overflow-x-hidden selection:bg-duo-green selection:text-white pb-[calc(5.75rem+env(safe-area-inset-bottom,0px))] sm:pb-0"> <div className="noise-bg" aria-hidden="true" /> <ScrollProgress /> <Hero /> <SolutionSection /> <ReviewsCarousel /> <MasteryPathSection /> <PricingSection /> <FaqSection /> <ContactsSection /> </div> ); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);
Your Name
Your Email
I agree to the Privacy Policy (Datenschutzerklärung) and Terms & Conditions (AGB) and consent to receive marketing emails.
Payment method
Stripe (Visa, Mastercard)
PayPal
Checkout
Made on
Tilda