Homepage fertig: Horizontales Scrollen, größere Sections, How-it-works überarbeitet
This commit is contained in:
11
COMMIT_MSG_HOMEPAGE.txt
Normal file
11
COMMIT_MSG_HOMEPAGE.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Homepage fertig: Horizontales Scrollen, größere Sections, How-it-works überarbeitet
|
||||||
|
|
||||||
|
Homepage abgeschlossen
|
||||||
|
|
||||||
|
- Horizontales Scrollen: Features, How it works, Pricing und FAQ in einem
|
||||||
|
horizontalen Scroll-Container mit Snap, Tastatur- und Touch-Navigation
|
||||||
|
- Zentrierung: Sections und Inhalte zentriert, overflow-x auf Body verhindert
|
||||||
|
- Größen: Moderate Schrift- und Abstände in Features, Pricing, FAQ
|
||||||
|
- How it works: Vergrößert (Titel 4xl–6xl, große Step-Cards, größere Icons und CTA)
|
||||||
|
- Navbar: Links springen in die passende Horizontal-Section
|
||||||
|
- Neue Komponente: HorizontalScrollNav (Dots), Hover-Pfeile, Scrollbar ausgeblendet
|
||||||
60
client/src/components/landing/HorizontalScrollNav.tsx
Normal file
60
client/src/components/landing/HorizontalScrollNav.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
interface Section {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HorizontalScrollNavProps {
|
||||||
|
sections: Section[]
|
||||||
|
activeIndex: number
|
||||||
|
onNavigate: (index: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HorizontalScrollNav({
|
||||||
|
sections,
|
||||||
|
activeIndex,
|
||||||
|
onNavigate
|
||||||
|
}: HorizontalScrollNavProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Desktop - Vertical Dots rechts */}
|
||||||
|
<div className="hidden md:flex fixed right-8 top-1/2 -translate-y-1/2 z-50 flex-col gap-4">
|
||||||
|
{sections.map((section, index) => (
|
||||||
|
<button
|
||||||
|
key={section.id}
|
||||||
|
onClick={() => onNavigate(index)}
|
||||||
|
className={`
|
||||||
|
transition-all duration-300 rounded-full
|
||||||
|
${index === activeIndex
|
||||||
|
? 'w-1.5 h-16 bg-primary-500 dark:bg-primary-400'
|
||||||
|
: 'w-1 h-12 bg-slate-600 dark:bg-slate-500 hover:bg-slate-400 dark:hover:bg-slate-400'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
title={section.name}
|
||||||
|
aria-label={`Go to ${section.name}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile - Horizontal Dots unten */}
|
||||||
|
<div className="md:hidden fixed bottom-8 left-1/2 -translate-x-1/2 z-50 flex gap-3">
|
||||||
|
{sections.map((section, index) => (
|
||||||
|
<button
|
||||||
|
key={section.id}
|
||||||
|
onClick={() => onNavigate(index)}
|
||||||
|
className={`
|
||||||
|
transition-all duration-300 rounded-full
|
||||||
|
${index === activeIndex
|
||||||
|
? 'w-8 h-2 bg-primary-500 dark:bg-primary-400'
|
||||||
|
: 'w-6 h-2 bg-slate-600 dark:bg-slate-500'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
title={section.name}
|
||||||
|
aria-label={`Go to ${section.name}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -14,6 +14,24 @@ export function Navbar() {
|
|||||||
const scrollToSection = useCallback((sectionId: string) => {
|
const scrollToSection = useCallback((sectionId: string) => {
|
||||||
setIsMenuOpen(false)
|
setIsMenuOpen(false)
|
||||||
|
|
||||||
|
// Horizontal scroll sections
|
||||||
|
const horizontalSections = ['features', 'how-it-works', 'pricing', 'faq']
|
||||||
|
|
||||||
|
if (horizontalSections.includes(sectionId)) {
|
||||||
|
// Dispatch custom event for horizontal scroll
|
||||||
|
const event = new CustomEvent('scrollToHorizontalSection', {
|
||||||
|
detail: { sectionId }
|
||||||
|
})
|
||||||
|
window.dispatchEvent(event)
|
||||||
|
|
||||||
|
// If not on home page, navigate first
|
||||||
|
if (location.pathname !== '/') {
|
||||||
|
navigate('/')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical scroll for other sections
|
||||||
// If not on home page, navigate first
|
// If not on home page, navigate first
|
||||||
if (location.pathname !== '/') {
|
if (location.pathname !== '/') {
|
||||||
navigate('/')
|
navigate('/')
|
||||||
|
|||||||
@@ -84,6 +84,7 @@
|
|||||||
/* Base styles */
|
/* Base styles */
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
overflow-x: hidden; /* Prevent horizontal scroll on body */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -93,6 +94,8 @@ body {
|
|||||||
/* Improve touch scrolling on mobile */
|
/* Improve touch scrolling on mobile */
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
overflow-scrolling: touch;
|
overflow-scrolling: touch;
|
||||||
|
/* Prevent horizontal scroll */
|
||||||
|
overflow-x: hidden;
|
||||||
/* Base colors - Tailwind will handle dark mode */
|
/* Base colors - Tailwind will handle dark mode */
|
||||||
background-color: var(--color-slate-50);
|
background-color: var(--color-slate-50);
|
||||||
color: var(--color-slate-900);
|
color: var(--color-slate-900);
|
||||||
@@ -324,3 +327,38 @@ body {
|
|||||||
html:not(.dark-mode-initialized) body {
|
html:not(.dark-mode-initialized) body {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Horizontal Scroll Container Styles */
|
||||||
|
/* Hide scrollbar but keep functionality */
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Snap scroll behavior */
|
||||||
|
.snap-x {
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snap-center {
|
||||||
|
scroll-snap-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snap-mandatory {
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth scrolling for horizontal containers */
|
||||||
|
.scroll-smooth {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure sections take full viewport width */
|
||||||
|
.w-screen {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,25 +1,187 @@
|
|||||||
|
import { useRef, useState, useEffect, useCallback } from 'react'
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
import { Navbar } from '@/components/landing/Navbar'
|
import { Navbar } from '@/components/landing/Navbar'
|
||||||
import { Hero } from '@/components/landing/Hero'
|
import { Hero } from '@/components/landing/Hero'
|
||||||
import { Features } from '@/components/landing/Features'
|
import { Features } from '@/components/landing/Features'
|
||||||
import { HowItWorks } from '@/components/landing/HowItWorks'
|
import { HowItWorks } from '@/components/landing/HowItWorks'
|
||||||
import { Testimonials } from '@/components/landing/Testimonials'
|
import { Testimonials } from '@/components/landing/Testimonials'
|
||||||
import { TrustSection } from '@/components/landing/TrustSection'
|
|
||||||
import { Pricing } from '@/components/landing/Pricing'
|
import { Pricing } from '@/components/landing/Pricing'
|
||||||
import { FAQ } from '@/components/landing/FAQ'
|
import { FAQ } from '@/components/landing/FAQ'
|
||||||
import { Footer } from '@/components/landing/Footer'
|
import { Footer } from '@/components/landing/Footer'
|
||||||
|
import { HorizontalScrollNav } from '@/components/landing/HorizontalScrollNav'
|
||||||
|
|
||||||
|
const sections = [
|
||||||
|
{ id: 'features', name: 'Features' },
|
||||||
|
{ id: 'how-it-works', name: 'How It Works' },
|
||||||
|
{ id: 'pricing', name: 'Pricing' },
|
||||||
|
{ id: 'faq', name: 'FAQ' }
|
||||||
|
]
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [activeIndex, setActiveIndex] = useState(0)
|
||||||
|
|
||||||
|
// Scroll to specific section
|
||||||
|
const scrollToSection = useCallback((index: number) => {
|
||||||
|
const container = scrollContainerRef.current
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const sectionWidth = container.clientWidth
|
||||||
|
container.scrollTo({
|
||||||
|
left: index * sectionWidth,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Track active section based on scroll position
|
||||||
|
useEffect(() => {
|
||||||
|
const container = scrollContainerRef.current
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const scrollLeft = container.scrollLeft
|
||||||
|
const sectionWidth = container.clientWidth
|
||||||
|
const index = Math.round(scrollLeft / sectionWidth)
|
||||||
|
setActiveIndex(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
return () => container.removeEventListener('scroll', handleScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
// Only handle arrow keys when horizontal scroll container is in view
|
||||||
|
const container = scrollContainerRef.current
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const rect = container.getBoundingClientRect()
|
||||||
|
const isVisible = rect.top < window.innerHeight && rect.bottom > 0
|
||||||
|
|
||||||
|
if (!isVisible) return
|
||||||
|
|
||||||
|
if (e.key === 'ArrowRight' && activeIndex < 3) {
|
||||||
|
e.preventDefault()
|
||||||
|
scrollToSection(activeIndex + 1)
|
||||||
|
} else if (e.key === 'ArrowLeft' && activeIndex > 0) {
|
||||||
|
e.preventDefault()
|
||||||
|
scrollToSection(activeIndex - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown)
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||||
|
}, [activeIndex, scrollToSection])
|
||||||
|
|
||||||
|
// Listen for custom events from Navbar
|
||||||
|
useEffect(() => {
|
||||||
|
const handleHorizontalScroll = (e: Event) => {
|
||||||
|
const customEvent = e as CustomEvent<{ sectionId: string }>
|
||||||
|
const sectionId = customEvent.detail.sectionId
|
||||||
|
const sectionIndex = sections.findIndex(s => s.id === sectionId)
|
||||||
|
if (sectionIndex !== -1) {
|
||||||
|
// Small delay to ensure container is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToSection(sectionIndex)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scrollToHorizontalSection', handleHorizontalScroll as EventListener)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scrollToHorizontalSection', handleHorizontalScroll as EventListener)
|
||||||
|
}
|
||||||
|
}, [scrollToSection])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="min-h-screen">
|
||||||
|
{/* NAVBAR - fixed top */}
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Hero />
|
|
||||||
<Features />
|
{/* HERO SECTION - normal vertical scroll */}
|
||||||
<HowItWorks />
|
<section className="hero-section min-h-screen">
|
||||||
<Testimonials />
|
<Hero />
|
||||||
<TrustSection />
|
</section>
|
||||||
<Pricing />
|
|
||||||
<FAQ />
|
{/* HORIZONTAL SCROLL CONTAINER */}
|
||||||
<Footer />
|
<div className="relative group">
|
||||||
|
{/* Horizontal Scroll Area */}
|
||||||
|
<div
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
className="flex overflow-x-auto overflow-y-hidden snap-x snap-mandatory scroll-smooth scrollbar-hide"
|
||||||
|
style={{
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
msOverflowStyle: 'none',
|
||||||
|
WebkitOverflowScrolling: 'touch'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Section 1: Features */}
|
||||||
|
<div className="w-screen min-h-screen snap-center flex-shrink-0 flex items-center justify-center">
|
||||||
|
<div className="w-full">
|
||||||
|
<Features />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section 2: How It Works */}
|
||||||
|
<div className="w-screen min-h-screen snap-center flex-shrink-0 flex items-center justify-center">
|
||||||
|
<div className="w-full">
|
||||||
|
<HowItWorks />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section 3: Pricing */}
|
||||||
|
<div className="w-screen min-h-screen snap-center flex-shrink-0 flex items-center justify-center">
|
||||||
|
<div className="w-full">
|
||||||
|
<Pricing />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section 4: FAQ */}
|
||||||
|
<div className="w-screen min-h-screen snap-center flex-shrink-0 flex items-center justify-center">
|
||||||
|
<div className="w-full">
|
||||||
|
<FAQ />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Left Arrow - appears on hover */}
|
||||||
|
<button
|
||||||
|
onClick={() => scrollToSection(Math.max(0, activeIndex - 1))}
|
||||||
|
className="fixed left-8 top-1/2 -translate-y-1/2 z-50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-slate-900/90 dark:bg-slate-800/90 hover:bg-slate-800 dark:hover:bg-slate-700 p-4 rounded-lg shadow-xl backdrop-blur-sm"
|
||||||
|
disabled={activeIndex === 0}
|
||||||
|
aria-label="Previous section"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-6 h-6 text-white" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Right Arrow - appears on hover */}
|
||||||
|
<button
|
||||||
|
onClick={() => scrollToSection(Math.min(3, activeIndex + 1))}
|
||||||
|
className="fixed right-8 top-1/2 -translate-y-1/2 z-50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-slate-900/90 dark:bg-slate-800/90 hover:bg-slate-800 dark:hover:bg-slate-700 p-4 rounded-lg shadow-xl backdrop-blur-sm"
|
||||||
|
disabled={activeIndex === 3}
|
||||||
|
aria-label="Next section"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-6 h-6 text-white" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Scroll Indicators (Dots) */}
|
||||||
|
<HorizontalScrollNav
|
||||||
|
sections={sections}
|
||||||
|
activeIndex={activeIndex}
|
||||||
|
onNavigate={scrollToSection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TESTIMONIALS - normal vertical scroll */}
|
||||||
|
<section className="testimonials-section py-20">
|
||||||
|
<Testimonials />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FOOTER - normal vertical scroll */}
|
||||||
|
<footer className="footer-section">
|
||||||
|
<Footer />
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user