feat: Add Section layout component and refactor page.tsx with updated styling and new Button variants.

This commit is contained in:
1elle1
2026-01-30 16:05:22 +01:00
parent f1cb4ef2cc
commit ef983e0b79
6 changed files with 219 additions and 175 deletions

View File

@@ -3,6 +3,7 @@ import { Button } from "@/components/ui/Button";
import { SectionHeading } from "@/components/ui/SectionHeading"; import { SectionHeading } from "@/components/ui/SectionHeading";
import { Card } from "@/components/ui/Card"; import { Card } from "@/components/ui/Card";
import { FadeIn } from "@/components/ui/FadeIn"; import { FadeIn } from "@/components/ui/FadeIn";
import { Section } from "@/components/layout/Section";
import { Dumbbell, Users, Clock, Heart } from "lucide-react"; import { Dumbbell, Users, Clock, Heart } from "lucide-react";
import type { Metadata } from "next"; import type { Metadata } from "next";
@@ -67,48 +68,41 @@ export default function HomePage() {
return ( return (
<main> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="relative flex items-center min-h-screen bg-primary text-secondary"> <Section className="flex items-center min-h-[90vh] bg-primary text-secondary">
<Container className="py-32 md:py-40"> <Container className="py-20 md:py-32">
<FadeIn> <FadeIn>
<p className="text-sm uppercase tracking-[0.2em] text-secondary/60 mb-6"> <p className="text-sm uppercase tracking-[0.2em] opacity-80 mb-6">
Fitnessstudio in Reutte, Tirol Fitnessstudio in Reutte, Tirol
</p> </p>
</FadeIn> </FadeIn>
<FadeIn delay={0.1}> <FadeIn delay={0.1}>
<h1 <h1 className="font-bold max-w-4xl text-5xl md:text-6xl lg:text-[5rem] leading-[1.1] tracking-tight mb-6">
className="font-bold max-w-3xl"
style={{
fontSize: "clamp(var(--text-4xl), 5vw, var(--text-6xl))",
lineHeight: "1.1",
letterSpacing: "var(--text-5xl-letter-spacing)",
}}
>
Dein Weg zu mehr Dein Weg zu mehr
<br /> <br />
Fitness beginnt hier. Fitness beginnt hier.
</h1> </h1>
</FadeIn> </FadeIn>
<FadeIn delay={0.2}> <FadeIn delay={0.2}>
<p className="mt-6 max-w-xl text-secondary/70" style={{ fontSize: "var(--text-lg)" }}> <p className="mt-6 max-w-xl text-lg md:text-xl text-secondary/80 leading-relaxed">
Individuelle Trainingspläne, persönliche Betreuung und eine Individuelle Trainingspläne, persönliche Betreuung und eine
motivierende Community. Willkommen bei Sportbox Reutte. motivierende Community. Willkommen bei Sportbox Reutte.
</p> </p>
</FadeIn> </FadeIn>
<FadeIn delay={0.3}> <FadeIn delay={0.3}>
<div className="mt-10 flex flex-col sm:flex-row gap-4"> <div className="mt-10 flex flex-col sm:flex-row gap-4">
<Button href="/leistungen#kontakt" variant="secondary" className="border-secondary text-secondary hover:bg-secondary hover:text-primary"> <Button href="/leistungen#kontakt" variant="outline-white">
Jetzt Termin buchen Jetzt Termin buchen
</Button> </Button>
<Button href="/über-uns" variant="ghost" className="text-secondary/80 hover:text-secondary"> <Button href="/über-uns" variant="ghost-white">
Mehr über uns erfahren Mehr über uns erfahren
</Button> </Button>
</div> </div>
</FadeIn> </FadeIn>
</Container> </Container>
</section> </Section>
{/* Features Section */} {/* Features Section */}
<section className="py-[var(--spacing-section)] md:py-[var(--spacing-4xl)]"> <Section>
<Container> <Container>
<FadeIn> <FadeIn>
<SectionHeading <SectionHeading
@@ -116,52 +110,45 @@ export default function HomePage() {
subtitle="Wir bieten mehr als nur ein Fitnessstudio wir sind Ihr Partner auf dem Weg zu einem gesünderen Leben." subtitle="Wir bieten mehr als nur ein Fitnessstudio wir sind Ihr Partner auf dem Weg zu einem gesünderen Leben."
/> />
</FadeIn> </FadeIn>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{FEATURES.map((feature, index) => ( {FEATURES.map((feature, index) => (
<FadeIn key={feature.title} delay={index * 0.1}> <FadeIn key={feature.title} delay={index * 0.1}>
<Card className="text-center h-full"> {/* Simplified Card usage since it might be just a div in this specialized case or reuse Card */}
<Card className="text-center h-full p-6 bg-background border border-border shadow-sm">
<feature.icon <feature.icon
size={32} size={32}
className="mx-auto mb-4 text-primary" className="mx-auto mb-4 text-primary"
aria-hidden="true" aria-hidden="true"
/> />
<h3 className="text-base font-bold mb-2">{feature.title}</h3> <h3 className="text-lg font-bold mb-2">{feature.title}</h3>
<p className="text-sm text-muted">{feature.description}</p> <p className="text-sm text-muted leading-relaxed">{feature.description}</p>
</Card> </Card>
</FadeIn> </FadeIn>
))} ))}
</div> </div>
</Container> </Container>
</section> </Section>
{/* About Teaser Section */} {/* About Teaser Section */}
<section className="py-[var(--spacing-section)] md:py-[var(--spacing-4xl)] bg-neutral"> <Section className="bg-neutral">
<Container> <Container>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
<FadeIn> <FadeIn>
<div <div
className="aspect-[4/3] bg-accent/40 flex items-center justify-center" className="aspect-[4/3] bg-accent/40 flex items-center justify-center rounded-2xl overflow-hidden shadow-lg"
style={{ borderRadius: "var(--radius-md)" }}
> >
<p className="text-muted text-sm">PLACEHOLDER: Studio-Bild</p> <div className="text-muted text-sm font-medium">PLACEHOLDER: Studio-Bild</div>
</div> </div>
</FadeIn> </FadeIn>
<FadeIn delay={0.15}> <FadeIn delay={0.15}>
<div> <div>
<p className="text-sm uppercase tracking-[0.2em] text-muted mb-4"> <p className="text-sm uppercase tracking-[0.2em] text-muted mb-4 font-semibold">
Über uns Über uns
</p> </p>
<h2 <h2 className="text-3xl md:text-4xl font-bold mb-6 tracking-tight">
className="font-bold mb-6"
style={{
fontSize: "var(--text-3xl)",
lineHeight: "var(--text-3xl-line-height)",
letterSpacing: "var(--text-3xl-letter-spacing)",
}}
>
Mehr als nur Training eine Gemeinschaft. Mehr als nur Training eine Gemeinschaft.
</h2> </h2>
<p className="text-muted mb-6 leading-relaxed"> <p className="text-muted mb-8 leading-relaxed text-lg">
Bei Sportbox Reutte stehen Sie im Mittelpunkt. Unser erfahrenes Bei Sportbox Reutte stehen Sie im Mittelpunkt. Unser erfahrenes
Team begleitet Sie auf Ihrem individuellen Weg ob Einsteiger Team begleitet Sie auf Ihrem individuellen Weg ob Einsteiger
oder Fortgeschrittener. Gemeinsam erreichen wir Ihre Ziele. oder Fortgeschrittener. Gemeinsam erreichen wir Ihre Ziele.
@@ -173,10 +160,10 @@ export default function HomePage() {
</FadeIn> </FadeIn>
</div> </div>
</Container> </Container>
</section> </Section>
{/* Services Preview Section */} {/* Services Preview Section */}
<section className="py-[var(--spacing-section)] md:py-[var(--spacing-4xl)]"> <Section>
<Container> <Container>
<FadeIn> <FadeIn>
<SectionHeading <SectionHeading
@@ -184,49 +171,45 @@ export default function HomePage() {
subtitle="Entdecken Sie unser vielfältiges Angebot für Ihre persönlichen Fitnessziele." subtitle="Entdecken Sie unser vielfältiges Angebot für Ihre persönlichen Fitnessziele."
/> />
</FadeIn> </FadeIn>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{SERVICES_PREVIEW.map((service, index) => ( {SERVICES_PREVIEW.map((service, index) => (
<FadeIn key={service.title} delay={index * 0.1}> <FadeIn key={service.title} delay={index * 0.1}>
<Card className="h-full"> <Card className="h-full flex flex-col p-8 bg-background border border-border shadow-sm hover:shadow-md transition-shadow">
<h3 className="text-lg font-bold mb-3">{service.title}</h3> <h3 className="text-xl font-bold mb-3">{service.title}</h3>
<p className="text-sm text-muted mb-4">{service.description}</p> <p className="text-sm text-muted mb-6 flex-grow leading-relaxed">{service.description}</p>
<Button href="/leistungen" variant="ghost" className="text-sm"> <div className="mt-auto">
Mehr erfahren <Button href="/leistungen" variant="ghost" className="px-0 hover:bg-transparent">
Mehr erfahren
</Button> </Button>
</div>
</Card> </Card>
</FadeIn> </FadeIn>
))} ))}
</div> </div>
</Container> </Container>
</section> </Section>
{/* CTA Section */} {/* CTA Section */}
<section className="py-[var(--spacing-section)] md:py-[var(--spacing-4xl)] bg-primary text-secondary"> <Section className="bg-primary text-secondary text-center">
<Container className="text-center"> <Container>
<FadeIn> <FadeIn>
<h2 <h2 className="text-3xl md:text-4xl font-bold mb-6 tracking-tight">
className="font-bold mb-4"
style={{
fontSize: "var(--text-3xl)",
lineHeight: "var(--text-3xl-line-height)",
}}
>
Bereit für den ersten Schritt? Bereit für den ersten Schritt?
</h2> </h2>
<p className="text-secondary/70 mb-8 max-w-lg mx-auto"> <p className="text-secondary/80 mb-10 max-w-xl mx-auto text-lg">
Vereinbaren Sie jetzt einen unverbindlichen Termin und lernen Sie Vereinbaren Sie jetzt einen unverbindlichen Termin und lernen Sie
Sportbox Reutte kennen. Sportbox Reutte kennen.
</p> </p>
<Button <Button
href="/leistungen#kontakt" href="/leistungen#kontakt"
variant="secondary" variant="outline-white"
className="border-secondary text-secondary hover:bg-secondary hover:text-primary" className="px-8 py-4 text-base"
> >
Jetzt Termin buchen Jetzt Termin buchen
</Button> </Button>
</FadeIn> </FadeIn>
</Container> </Container>
</section> </Section>
{/* JSON-LD Structured Data */} {/* JSON-LD Structured Data */}
<script <script

View File

@@ -5,6 +5,8 @@ import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Menu, X } from "lucide-react"; import { Menu, X } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { Container } from "@/components/ui/Container";
import { Button } from "@/components/ui/Button";
const NAV_ITEMS = [ const NAV_ITEMS = [
{ href: "/", label: "Startseite" }, { href: "/", label: "Startseite" },
@@ -20,8 +22,8 @@ export function Header() {
return ( return (
<header className="fixed top-0 left-0 right-0 z-50 bg-background/95 backdrop-blur-sm border-b border-border"> <header className="fixed top-0 left-0 right-0 z-50 bg-background/95 backdrop-blur-sm border-b border-border">
<div className="mx-auto flex items-center justify-between px-[var(--spacing-container-padding)] py-4" style={{ maxWidth: "var(--spacing-container)" }}> <Container className="flex items-center justify-between py-4">
<Link href="/" className="text-xl font-bold tracking-tight" aria-label="Sportbox Reutte Zur Startseite"> <Link href="/" className="text-xl font-bold tracking-tight text-primary" aria-label="Sportbox Reutte Zur Startseite">
SPORTBOX SPORTBOX
</Link> </Link>
@@ -30,24 +32,24 @@ export function Header() {
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
className={`text-sm tracking-wide transition-colors hover:text-muted ${ className={`text-sm tracking-wide transition-colors hover:text-muted ${pathname === item.href ? "font-bold text-primary" : "font-normal text-muted-foreground/80"
pathname === item.href ? "font-bold" : "font-normal"
}`} }`}
> >
{item.label} {item.label}
</Link> </Link>
))} ))}
<Link <Button
href="/leistungen#kontakt" href="/leistungen#kontakt"
className="bg-primary text-secondary px-5 py-2.5 text-sm font-medium transition-opacity hover:opacity-80" variant="primary"
className="py-2.5 px-5 text-sm"
> >
Jetzt Termin buchen Jetzt Termin buchen
</Link> </Button>
</nav> </nav>
<button <button
type="button" type="button"
className="lg:hidden p-2 -mr-2" className="lg:hidden p-2 -mr-2 text-primary"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)} onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-expanded={mobileMenuOpen} aria-expanded={mobileMenuOpen}
aria-controls="mobile-menu" aria-controls="mobile-menu"
@@ -55,7 +57,7 @@ export function Header() {
> >
{mobileMenuOpen ? <X size={24} /> : <Menu size={24} />} {mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button> </button>
</div> </Container>
<AnimatePresence> <AnimatePresence>
{mobileMenuOpen && ( {mobileMenuOpen && (
@@ -74,20 +76,22 @@ export function Header() {
key={item.href} key={item.href}
href={item.href} href={item.href}
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
className={`py-3 text-base transition-colors hover:text-muted ${ className={`py-3 text-base transition-colors hover:text-muted ${pathname === item.href ? "font-bold text-primary" : "font-normal text-muted-foreground"
pathname === item.href ? "font-bold" : "font-normal"
}`} }`}
> >
{item.label} {item.label}
</Link> </Link>
))} ))}
<Link <div className="mt-4">
<Button
href="/leistungen#kontakt" href="/leistungen#kontakt"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
className="mt-2 bg-primary text-secondary px-5 py-3 text-center text-sm font-medium transition-opacity hover:opacity-80" variant="primary"
className="w-full justify-center"
> >
Jetzt Termin buchen Jetzt Termin buchen
</Link> </Button>
</div>
</div> </div>
</motion.nav> </motion.nav>
)} )}

View File

@@ -0,0 +1,22 @@
interface SectionProps {
children: React.ReactNode;
className?: string;
id?: string;
as?: "section" | "div";
}
export function Section({
children,
className = "",
id,
as: Component = "section",
}: SectionProps) {
return (
<Component
id={id}
className={`py-[var(--spacing-section)] relative ${className}`}
>
{children}
</Component>
);
}

View File

@@ -1,12 +1,10 @@
import Link from "next/link"; import Link from "next/link";
import { JSX } from "react";
interface ButtonProps { interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
href?: string; href?: string;
variant?: "primary" | "secondary" | "ghost"; variant?: "primary" | "secondary" | "ghost" | "outline-white" | "ghost-white";
className?: string; className?: string;
type?: "button" | "submit" | "reset";
onClick?: () => void;
} }
export function Button({ export function Button({
@@ -15,30 +13,32 @@ export function Button({
variant = "primary", variant = "primary",
className = "", className = "",
type = "button", type = "button",
onClick, ...props
}: ButtonProps) { }: ButtonProps) {
const base = const base =
"inline-flex items-center justify-center px-6 py-3 text-sm font-medium transition-all duration-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"; "inline-flex items-center justify-center px-6 py-3 text-sm font-medium transition-all duration-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:opacity-50 disabled:pointer-events-none cursor-pointer";
const variants = { const variants = {
primary: "bg-primary text-secondary hover:opacity-80", primary: "bg-primary text-secondary hover:opacity-90 border border-transparent",
secondary: secondary: "bg-transparent border border-primary text-primary hover:bg-primary hover:text-secondary",
"border border-primary text-primary hover:bg-primary hover:text-secondary", ghost: "text-primary hover:bg-neutral/50",
ghost: "text-primary underline-offset-4 hover:underline", "outline-white": "border border-secondary text-secondary hover:bg-secondary hover:text-primary",
"ghost-white": "text-secondary hover:bg-secondary/10",
}; };
const classes = `${base} ${variants[variant]} ${className}`; const selectedVariant = variants[variant] || variants.primary;
const combinedClasses = `${base} ${selectedVariant} ${className}`;
if (href) { if (href) {
return ( return (
<Link href={href} className={classes}> <Link href={href} className={combinedClasses}>
{children} {children}
</Link> </Link>
); );
} }
return ( return (
<button type={type} className={classes} onClick={onClick}> <button type={type} className={combinedClasses} {...props}>
{children} {children}
</button> </button>
); );

View File

@@ -11,8 +11,7 @@ export function Container({
}: ContainerProps) { }: ContainerProps) {
return ( return (
<Component <Component
className={`mx-auto px-[var(--spacing-container-padding)] ${className}`} className={`mx-auto w-full max-w-[var(--spacing-container)] px-[var(--spacing-container-padding)] ${className}`}
style={{ maxWidth: "var(--spacing-container)" }}
> >
{children} {children}
</Component> </Component>

View File

@@ -10,52 +10,59 @@
@theme { @theme {
/* Colors */ /* Colors */
--color-primary: #000000; --color-primary: var(--color-primary);
--color-secondary: #FFFFFF; --color-secondary: var(--color-secondary);
--color-accent: #d6d6d6; --color-accent: var(--color-accent);
--color-neutral: #F5F5F5; --color-neutral: var(--color-neutral);
--color-background: #FFFFFF; --color-background: var(--color-background);
--color-foreground: #000000; --color-foreground: var(--color-foreground);
--color-muted: #B0B0B0; --color-muted: var(--color-muted);
--color-muted-foreground: #ffffff; --color-muted-foreground: var(--color-muted-foreground);
--color-border: #E0E0E0; --color-border: var(--color-border);
--color-success: #4CAF50; --color-success: var(--color-success);
--color-warning: #FF9800; --color-warning: var(--color-warning);
--color-error: #F44336; --color-error: var(--color-error);
/* Fonts */ /* Fonts */
--font-heading: 'Inter', sans-serif; --font-heading: var(--font-heading);
--font-body: 'Inter', sans-serif; --font-body: var(--font-body);
/* Typography Scale override Tailwind defaults to match design tokens */ /* Typography Scale */
--text-xl: 1.5rem; --text-xs: var(--text-xs);
--text-xl--line-height: 2rem; --text-xs--line-height: var(--text-xs-line-height);
--text-2xl: 1.75rem; --text-sm: var(--text-sm);
--text-2xl--line-height: 2.25rem; --text-sm--line-height: var(--text-sm-line-height);
--text-3xl: 2rem; --text-base: var(--text-base);
--text-3xl--line-height: 2.5rem; --text-base--line-height: var(--text-base-line-height);
--text-4xl: 2.5rem; --text-lg: var(--text-lg);
--text-4xl--line-height: 2.75rem; --text-lg--line-height: var(--text-lg-line-height);
--text-5xl: 3rem; --text-xl: var(--text-xl);
--text-5xl--line-height: 1.1; --text-xl--line-height: var(--text-xl-line-height);
--text-6xl: 3.75rem; --text-2xl: var(--text-2xl);
--text-6xl--line-height: 1.1; --text-2xl--line-height: var(--text-2xl-line-height);
--text-3xl: var(--text-3xl);
--text-3xl--line-height: var(--text-3xl-line-height);
--text-4xl: var(--text-4xl);
--text-4xl--line-height: var(--text-4xl-line-height);
--text-5xl: var(--text-5xl);
--text-5xl--line-height: var(--text-5xl-line-height);
--text-6xl: var(--text-6xl);
--text-6xl--line-height: var(--text-6xl-line-height);
/* Spacing */
--spacing-container: var(--spacing-container);
--spacing-container-padding: var(--spacing-container-padding);
} }
/* ── Font Override (connect next/font to CSS vars) ──── */ /* ── Base Layer ────────────────────────────────────── */
/* --font-inter is injected by next/font/google on <html> */
html { @layer base {
--font-heading: var(--font-inter, 'Inter', sans-serif);
--font-body: var(--font-inter, 'Inter', sans-serif);
}
/* ── Base Resets ───────────────────────────────────── */ *,
*::before,
*, *::before, *::after { *::after {
box-sizing: border-box; box-sizing: border-box;
margin: 0; border-color: var(--color-border);
padding: 0;
} }
html { html {
@@ -73,27 +80,68 @@ body {
font-size: var(--text-base); font-size: var(--text-base);
} }
/* ── Headings ─────────────────────────────────────── */ h1,
h2,
h1, h2, h3, h4, h5, h6 { h3,
h4,
h5,
h6 {
font-family: var(--font-heading); font-family: var(--font-heading);
font-weight: var(--font-heading-weight); font-weight: var(--font-heading-weight);
color: var(--color-primary);
line-height: 1.2;
margin-bottom: 0.5em;
/* reasonable default */
} }
/* ── Links ───────────────────────────────────────── */ h1 {
font-size: var(--text-4xl);
letter-spacing: -0.025em;
}
h2 {
font-size: var(--text-3xl);
letter-spacing: -0.02em;
}
h3 {
font-size: var(--text-2xl);
letter-spacing: -0.015em;
}
h4 {
font-size: var(--text-xl);
letter-spacing: -0.01em;
}
h5 {
font-size: var(--text-lg);
}
h6 {
font-size: var(--text-base);
}
p {
margin-bottom: 1em;
max-width: 65ch;
/* readability */
}
a { a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
transition: opacity 0.2s;
} }
/* ── Images ──────────────────────────────────────── */ img,
video,
img, video, svg { svg {
display: block; display: block;
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
}
/* ── Focus ───────────────────────────────────────── */ /* ── Focus ───────────────────────────────────────── */
@@ -108,15 +156,3 @@ img, video, svg {
background-color: var(--color-primary); background-color: var(--color-primary);
color: var(--color-background); color: var(--color-background);
} }
/* ── Reduced Motion ─────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}