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
+42 -59
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">
</Button> Mehr erfahren →
</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
+24 -20
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">
href="/leistungen#kontakt" <Button
onClick={() => setMobileMenuOpen(false)} href="/leistungen#kontakt"
className="mt-2 bg-primary text-secondary px-5 py-3 text-center text-sm font-medium transition-opacity hover:opacity-80" onClick={() => setMobileMenuOpen(false)}
> variant="primary"
Jetzt Termin buchen className="w-full justify-center"
</Link> >
Jetzt Termin buchen
</Button>
</div>
</div> </div>
</motion.nav> </motion.nav>
)} )}
+22
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>
);
}
+14 -14
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>
); );
+1 -2
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>
+116 -80
View File
@@ -10,89 +10,137 @@
@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,
*::after {
box-sizing: border-box;
border-color: var(--color-border);
}
*, *::before, *::after { html {
box-sizing: border-box; scroll-behavior: smooth;
margin: 0; -webkit-font-smoothing: antialiased;
padding: 0; -moz-osx-font-smoothing: grayscale;
} }
html { body {
scroll-behavior: smooth; font-family: var(--font-body);
-webkit-font-smoothing: antialiased; font-weight: var(--font-body-weight);
-moz-osx-font-smoothing: grayscale; color: var(--color-foreground);
} background-color: var(--color-background);
line-height: var(--text-base-line-height);
font-size: var(--text-base);
}
body { h1,
font-family: var(--font-body); h2,
font-weight: var(--font-body-weight); h3,
color: var(--color-foreground); h4,
background-color: var(--color-background); h5,
line-height: var(--text-base-line-height); h6 {
font-size: var(--text-base); font-family: var(--font-heading);
} font-weight: var(--font-heading-weight);
color: var(--color-primary);
line-height: 1.2;
margin-bottom: 0.5em;
/* reasonable default */
}
/* ── Headings ─────────────────────────────────────── */ h1 {
font-size: var(--text-4xl);
letter-spacing: -0.025em;
}
h1, h2, h3, h4, h5, h6 { h2 {
font-family: var(--font-heading); font-size: var(--text-3xl);
font-weight: var(--font-heading-weight); letter-spacing: -0.02em;
} }
/* ── Links ───────────────────────────────────────── */ h3 {
font-size: var(--text-2xl);
letter-spacing: -0.015em;
}
a { h4 {
color: inherit; font-size: var(--text-xl);
text-decoration: none; letter-spacing: -0.01em;
} }
/* ── Images ──────────────────────────────────────── */ h5 {
font-size: var(--text-lg);
}
img, video, svg { h6 {
display: block; font-size: var(--text-base);
max-width: 100%; }
height: auto;
p {
margin-bottom: 1em;
max-width: 65ch;
/* readability */
}
a {
color: inherit;
text-decoration: none;
transition: opacity 0.2s;
}
img,
video,
svg {
display: block;
max-width: 100%;
height: auto;
}
} }
/* ── Focus ───────────────────────────────────────── */ /* ── Focus ───────────────────────────────────────── */
@@ -107,16 +155,4 @@ img, video, svg {
::selection { ::selection {
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;
}
}