Initial commit
This commit is contained in:
77
components/Footer.tsx
Normal file
77
components/Footer.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import { Page } from '../types';
|
||||
|
||||
interface FooterProps {
|
||||
setPage: (page: Page) => void;
|
||||
}
|
||||
|
||||
const Footer: React.FC<FooterProps> = ({ setPage }) => {
|
||||
return (
|
||||
<footer className="bg-slate-50 dark:bg-black pt-24 pb-12 border-t border-slate-200 dark:border-white/5 relative z-20">
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-12 mb-20">
|
||||
<div className="col-span-1 md:col-span-1">
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="flex items-center justify-center">
|
||||
<img alt="Riboneo Footer Logo" className="h-10 w-auto object-contain dark:hidden block" src="/logo-black.png" />
|
||||
<img alt="Riboneo Footer Logo" className="h-10 w-auto object-contain hidden dark:block" src="/logo-white.png" />
|
||||
</div>
|
||||
<div className="flex flex-col -space-y-1">
|
||||
<span className="font-display text-xl tracking-[0.2em] uppercase font-medium text-slate-900 dark:text-white">Riboneo</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-slate-500 dark:text-slate-400 text-sm leading-loose">
|
||||
Heilung ist ein Weg, kein Ziel. Begleite uns auf der Reise zur Revolution des Bewusstseins.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold mb-6 text-slate-900 dark:text-white">Programm</h4>
|
||||
<ul className="space-y-4 text-slate-500 dark:text-slate-400 text-sm">
|
||||
<li><button onClick={() => setPage(Page.BOOK)} className="hover:text-primary transition-colors">Das Buch</button></li>
|
||||
<li><button onClick={() => setPage(Page.RETREAT)} className="hover:text-primary transition-colors">Retreats 2026</button></li>
|
||||
<li><button className="hover:text-primary transition-colors">Mentoring</button></li>
|
||||
<li><button className="hover:text-primary transition-colors">Online Kurs</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold mb-6 text-slate-900 dark:text-white">Unternehmen</h4>
|
||||
<ul className="space-y-4 text-slate-500 dark:text-slate-400 text-sm">
|
||||
<li><button className="hover:text-primary transition-colors">Über Roberto</button></li>
|
||||
<li><button onClick={() => setPage(Page.BLOG)} className="hover:text-primary transition-colors">Blog</button></li>
|
||||
<li><button onClick={() => setPage(Page.CONTACT)} className="hover:text-primary transition-colors">Kontakt</button></li>
|
||||
<li><button className="hover:text-primary transition-colors">Presse</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold mb-6 text-slate-900 dark:text-white">Newsletter</h4>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 mb-6 italic">Erhalte wöchentliche Impulse für dein Bewusstsein.</p>
|
||||
<form className="flex gap-2">
|
||||
<input className="flex-1 bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-full px-4 text-sm focus:ring-primary focus:border-primary" placeholder="E-Mail" type="email" />
|
||||
<button className="bg-primary text-white p-2 shape-squircle hover:bg-opacity-90 transition-all">
|
||||
<span className="material-symbols-outlined">send</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-slate-200 dark:border-white/10 text-xs text-slate-400 gap-4">
|
||||
<p>© 2026 Roberto de Marco. Riboneo® ist eine eingetragene Marke.</p>
|
||||
<div className="flex gap-6">
|
||||
<button className="hover:text-primary">Impressum</button>
|
||||
<button className="hover:text-primary">Datenschutz</button>
|
||||
<button className="hover:text-primary">AGB</button>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<button className="w-8 h-8 rounded-full bg-slate-200 dark:bg-white/5 flex items-center justify-center hover:bg-primary hover:text-white transition-all">
|
||||
<span className="material-symbols-outlined text-sm">share</span>
|
||||
</button>
|
||||
<button className="w-8 h-8 rounded-full bg-slate-200 dark:bg-white/5 flex items-center justify-center hover:bg-primary hover:text-white transition-all">
|
||||
<span className="material-symbols-outlined text-sm">camera_alt</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
178
components/Navbar.tsx
Normal file
178
components/Navbar.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Page } from '../types';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { supabase } from '../supabaseClient';
|
||||
|
||||
interface NavbarProps {
|
||||
currentPage: Page;
|
||||
setPage: (page: Page) => void;
|
||||
session: Session | null;
|
||||
}
|
||||
|
||||
const Navbar: React.FC<NavbarProps> = ({ currentPage, setPage, session }) => {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
const navLinks = [
|
||||
{ label: 'Home', value: Page.HOME },
|
||||
{ label: 'Das Buch', value: Page.BOOK },
|
||||
{ label: 'Retreat', value: Page.RETREAT },
|
||||
{ label: 'Blog', value: Page.BLOG },
|
||||
{ label: 'Kontakt', value: Page.CONTACT },
|
||||
];
|
||||
|
||||
return (
|
||||
<nav className="fixed w-full z-50 top-0 glass-effect border-b border-slate-200/50 dark:border-white/10 transition-all duration-300">
|
||||
<div className="max-w-7xl mx-auto px-6 h-24 flex items-center justify-between">
|
||||
<div
|
||||
className="flex items-center gap-4 cursor-pointer"
|
||||
onClick={() => setPage(Page.HOME)}
|
||||
>
|
||||
<img
|
||||
alt="Riboneo Logo"
|
||||
className="h-10 w-auto object-contain transition-all dark:hidden block"
|
||||
src="/logo-black.png"
|
||||
/>
|
||||
<img
|
||||
alt="Riboneo Logo"
|
||||
className="h-10 w-auto object-contain transition-all hidden dark:block"
|
||||
src="/logo-white.png"
|
||||
/>
|
||||
<div className="flex flex-col -space-y-1">
|
||||
<span className="font-display text-2xl tracking-[0.2em] uppercase font-medium text-slate-900 dark:text-white">Riboneo</span>
|
||||
<span className="text-[10px] tracking-[0.4em] uppercase font-light text-slate-500 dark:text-slate-400">By Roberto de Marco</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:flex items-center gap-6">
|
||||
<div className="flex gap-6 mr-4">
|
||||
{navLinks.map((link) => (
|
||||
<button
|
||||
key={link.value}
|
||||
onClick={() => setPage(link.value)}
|
||||
className={`text-xs tracking-widest uppercase font-medium hover:text-primary transition-colors ${currentPage === link.value ? 'text-primary font-bold' : 'text-slate-600 dark:text-slate-300'
|
||||
}`}
|
||||
>
|
||||
{link.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 pl-6 border-l border-slate-200 dark:border-white/10">
|
||||
{!session ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setPage(Page.LOGIN)}
|
||||
className="text-xs tracking-widest uppercase font-bold text-slate-900 dark:text-white hover:text-primary transition-colors"
|
||||
>
|
||||
Einloggen
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage(Page.REGISTER)}
|
||||
className="px-5 py-2 bg-primary text-white text-xs tracking-widest uppercase font-bold rounded-full hover:bg-opacity-90 transition-all shadow-lg shadow-primary/20"
|
||||
>
|
||||
Anmelden
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center gap-4">
|
||||
<span
|
||||
onClick={() => setPage(Page.DASHBOARD)}
|
||||
className="text-xs tracking-widest uppercase font-bold text-slate-900 dark:text-white hover:text-primary cursor-pointer transition-colors"
|
||||
>
|
||||
Hallo, {session.user.user_metadata.first_name || 'User'}
|
||||
</span>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await supabase.auth.signOut();
|
||||
setPage(Page.HOME);
|
||||
}}
|
||||
className="px-5 py-2 bg-secondary/20 text-primary text-xs tracking-widest uppercase font-bold rounded-full hover:bg-secondary/30 transition-all"
|
||||
>
|
||||
Ausloggen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className="p-2 ml-2 rounded-full hover:bg-slate-100 dark:hover:bg-white/10 transition-all"
|
||||
onClick={() => document.documentElement.classList.toggle('dark')}
|
||||
>
|
||||
<span className="material-symbols-outlined block dark:hidden text-slate-600">dark_mode</span>
|
||||
<span className="material-symbols-outlined hidden dark:block text-yellow-400">light_mode</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="md:hidden p-2"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
>
|
||||
<span className="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden absolute top-24 left-0 w-full bg-white dark:bg-background-dark border-b border-slate-200 dark:border-slate-800 p-6 flex flex-col space-y-4 shadow-xl">
|
||||
{navLinks.map((link) => (
|
||||
<button
|
||||
key={link.value}
|
||||
onClick={() => {
|
||||
setPage(link.value);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
className="text-sm tracking-widest uppercase font-medium hover:text-primary text-left py-2 text-slate-900 dark:text-white"
|
||||
>
|
||||
{link.label}
|
||||
</button>
|
||||
))}
|
||||
{!session ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPage(Page.LOGIN);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
className="text-sm tracking-widest uppercase font-medium hover:text-primary text-left py-2 text-slate-900 dark:text-white"
|
||||
>
|
||||
Einloggen
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPage(Page.REGISTER);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
className="text-sm tracking-widest uppercase font-bold text-primary text-left py-2"
|
||||
>
|
||||
Konto erstellen
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPage(Page.DASHBOARD);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
className="text-sm tracking-widest uppercase font-medium hover:text-primary text-left py-2 text-slate-900 dark:text-white"
|
||||
>
|
||||
Hallo, {session.user.user_metadata.first_name || 'User'} (Dashboard)
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await supabase.auth.signOut();
|
||||
setPage(Page.HOME);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
className="text-sm tracking-widest uppercase font-bold text-primary text-left py-2"
|
||||
>
|
||||
Ausloggen
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
113
components/TestimonialCarousel.tsx
Normal file
113
components/TestimonialCarousel.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import useEmblaCarousel from 'embla-carousel-react';
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
text: "Dieses Buch war der Wendepunkt in meiner chronischen Erschöpfung. Roberto schreibt mit einer Klarheit, die einen direkt im Herzen trifft.",
|
||||
author: "Stefan M.",
|
||||
role: "Unternehmer",
|
||||
stars: 5,
|
||||
},
|
||||
{
|
||||
text: "Man spürt auf jeder Seite, dass hier jemand schreibt, der den Weg selbst gegangen ist. Grounded Luxury trifft es perfekt.",
|
||||
author: "Julia K.",
|
||||
role: "Ärztin",
|
||||
stars: 5,
|
||||
},
|
||||
{
|
||||
text: "Das Retreat war lebensverändernd. Die Kombination aus Natur, Theorie und Praxis ist in dieser Form einzigartig.",
|
||||
author: "Markus L.",
|
||||
role: "Creative Director",
|
||||
stars: 5,
|
||||
},
|
||||
{
|
||||
text: "Endlich ein Ansatz, der Wissenschaft und Spiritualität nicht als Gegensätze sieht, sondern vereint. Danke für diese Arbeit.",
|
||||
author: "Sarah B.",
|
||||
role: "Psychologin",
|
||||
stars: 5,
|
||||
},
|
||||
{
|
||||
text: "Ich habe schon viele Methoden ausprobiert, aber erst Riboneo hat mir geholfen, wirklich zur Ruhe zu kommen und meine Energie zurückzugewinnen.",
|
||||
author: "Thomas W.",
|
||||
role: "Architekt",
|
||||
stars: 5,
|
||||
},
|
||||
];
|
||||
|
||||
const TestimonialCarousel: React.FC = () => {
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: 'start' });
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const scrollTo = useCallback((index: number) => {
|
||||
if (emblaApi) emblaApi.scrollTo(index);
|
||||
}, [emblaApi]);
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
if (!emblaApi) return;
|
||||
setSelectedIndex(emblaApi.selectedScrollSnap());
|
||||
}, [emblaApi]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return;
|
||||
onSelect();
|
||||
emblaApi.on('select', onSelect);
|
||||
|
||||
// Auto-play functionality
|
||||
const intervalId = setInterval(() => {
|
||||
if (emblaApi.canScrollNext()) {
|
||||
emblaApi.scrollNext();
|
||||
} else {
|
||||
emblaApi.scrollTo(0);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
emblaApi.off('select', onSelect);
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [emblaApi, onSelect]);
|
||||
|
||||
return (
|
||||
<div className="relative max-w-7xl mx-auto px-6">
|
||||
<div className="overflow-hidden" ref={emblaRef}>
|
||||
<div className="flex -ml-4">
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<div className="flex-[0_0_100%] md:flex-[0_0_50%] lg:flex-[0_0_33.333%] min-w-0 pl-4" key={index}>
|
||||
<div className="h-full p-10 bg-secondary/20 dark:bg-white/5 shape-squircle flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex gap-1 text-yellow-500 mb-6">
|
||||
{[...Array(testimonial.stars)].map((_, i) => (
|
||||
<span key={i} className="material-symbols-outlined fill-1">star</span>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-lg italic mb-8 leading-relaxed text-slate-700 dark:text-slate-300">"{testimonial.text}"</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-slate-300 flex-shrink-0"></div>
|
||||
<div>
|
||||
<p className="font-bold text-slate-900 dark:text-white">{testimonial.author}</p>
|
||||
<p className="text-sm opacity-60 text-slate-600 dark:text-slate-400">{testimonial.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-3 mt-10">
|
||||
{testimonials.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`w-3 h-3 rounded-full transition-all duration-300 ${index === selectedIndex ? 'bg-primary w-8' : 'bg-slate-300 dark:bg-white/20 hover:bg-primary/50'
|
||||
}`}
|
||||
onClick={() => scrollTo(index)}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestimonialCarousel;
|
||||
Reference in New Issue
Block a user