feat: Implement initial website structure, core pages, and layout components, including a redesigned homepage.
This commit is contained in:
@@ -1,20 +1,28 @@
|
||||
import type { ElementType, ReactNode } from "react";
|
||||
|
||||
type ContainerVariant = "default" | "narrow" | "wide";
|
||||
|
||||
interface ContainerProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
as?: ElementType;
|
||||
variant?: ContainerVariant;
|
||||
}
|
||||
|
||||
export function Container({
|
||||
children,
|
||||
className = "",
|
||||
as: Tag = "div",
|
||||
variant = "default",
|
||||
}: ContainerProps) {
|
||||
const variantClasses: Record<ContainerVariant, string> = {
|
||||
default: "container",
|
||||
narrow: "container container--narrow",
|
||||
wide: "container container--wide",
|
||||
};
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={`mx-auto w-full max-w-[var(--container-max-width)] px-[var(--container-padding-x)] ${className}`.trim()}
|
||||
>
|
||||
<Tag className={`${variantClasses[variant]} ${className}`.trim()}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import Link from "next/link";
|
||||
import { Phone, Mail, MapPin } from "lucide-react";
|
||||
|
||||
const footerLinks = {
|
||||
services: [
|
||||
{ name: "Fernsehen", href: "/fernsehen" },
|
||||
{ name: "Internet", href: "/internet" },
|
||||
{ name: "Telefonie", href: "/telefonie" },
|
||||
{ name: "Leistungen", href: "/leistungen" },
|
||||
],
|
||||
company: [
|
||||
{ name: "Über uns", href: "/ueber-uns" },
|
||||
{ name: "Kontakt", href: "/kontakt" },
|
||||
{ name: "Impressum", href: "/impressum" },
|
||||
{ name: "Datenschutz", href: "/datenschutz" },
|
||||
],
|
||||
};
|
||||
|
||||
export function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-[var(--color-foreground)] text-[var(--color-background)]">
|
||||
<div className="container">
|
||||
{/* Main Footer Content */}
|
||||
<div className="py-[var(--spacing-3xl)] grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-[var(--spacing-2xl)]">
|
||||
{/* Company Info */}
|
||||
<div className="stack-md">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-xl font-semibold text-[var(--color-background)] hover:no-underline"
|
||||
>
|
||||
<span className="text-[var(--color-primary)]">Tele</span>NetSystems
|
||||
</Link>
|
||||
<p className="text-sm opacity-80">
|
||||
Ihr regionaler Telekom- und IT-Partner in Tirol.
|
||||
Zuverlässige Technik, ehrliche Beratung und persönlicher Service seit 1976.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Services Links */}
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-[var(--spacing-md)] text-[var(--color-background)]">
|
||||
Leistungen
|
||||
</h3>
|
||||
<ul className="stack-sm">
|
||||
{footerLinks.services.map((link) => (
|
||||
<li key={link.name}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm opacity-80 hover:opacity-100 hover:text-[var(--color-primary)] hover:no-underline transition-colors"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Company Links */}
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-[var(--spacing-md)] text-[var(--color-background)]">
|
||||
Unternehmen
|
||||
</h3>
|
||||
<ul className="stack-sm">
|
||||
{footerLinks.company.map((link) => (
|
||||
<li key={link.name}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm opacity-80 hover:opacity-100 hover:text-[var(--color-primary)] hover:no-underline transition-colors"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-[var(--spacing-md)] text-[var(--color-background)]">
|
||||
Kontakt
|
||||
</h3>
|
||||
<ul className="stack-md">
|
||||
<li className="flex items-start gap-[var(--spacing-sm)]">
|
||||
<MapPin size={18} className="shrink-0 mt-0.5 text-[var(--color-primary)]" />
|
||||
<span className="text-sm opacity-80">
|
||||
PLACEHOLDER: Adresse<br />
|
||||
6600 Reutte, Tirol
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="tel:+43567612345"
|
||||
className="flex items-center gap-[var(--spacing-sm)] text-sm opacity-80 hover:opacity-100 hover:text-[var(--color-primary)] hover:no-underline transition-colors"
|
||||
>
|
||||
<Phone size={18} className="shrink-0 text-[var(--color-primary)]" />
|
||||
PLACEHOLDER: Telefon
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="mailto:info@tnr.at"
|
||||
className="flex items-center gap-[var(--spacing-sm)] text-sm opacity-80 hover:opacity-100 hover:text-[var(--color-primary)] hover:no-underline transition-colors"
|
||||
>
|
||||
<Mail size={18} className="shrink-0 text-[var(--color-primary)]" />
|
||||
PLACEHOLDER: E-Mail
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Bar */}
|
||||
<div className="py-[var(--spacing-lg)] border-t border-[var(--color-background)]/10">
|
||||
<p className="text-sm opacity-60 text-center">
|
||||
© {currentYear} Telenet Systems GmbH. Alle Rechte vorbehalten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Menu, X, Phone } from "lucide-react";
|
||||
|
||||
const navigation = [
|
||||
{ name: "Fernsehen", href: "/fernsehen" },
|
||||
{ name: "Internet", href: "/internet" },
|
||||
{ name: "Telefonie", href: "/telefonie" },
|
||||
{ name: "Leistungen", href: "/leistungen" },
|
||||
{ name: "Über uns", href: "/ueber-uns" },
|
||||
];
|
||||
|
||||
export function Header() {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 bg-[var(--color-background)] border-b border-[var(--color-border)]">
|
||||
<div className="container">
|
||||
<nav className="flex items-center justify-between h-16 md:h-20">
|
||||
{/* Logo */}
|
||||
<Link
|
||||
href="/"
|
||||
className="text-xl font-semibold text-[var(--color-foreground)] hover:no-underline"
|
||||
>
|
||||
<span className="text-[var(--color-primary)]">Tele</span>NetSystems
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden lg:flex items-center gap-[var(--spacing-lg)]">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="text-[var(--color-foreground)] hover:text-[var(--color-primary)] hover:no-underline transition-colors"
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Desktop CTAs */}
|
||||
<div className="hidden lg:flex items-center gap-[var(--spacing-md)]">
|
||||
<a
|
||||
href="tel:+43567612345"
|
||||
className="flex items-center gap-[var(--spacing-xs)] text-[var(--color-muted-foreground)] hover:text-[var(--color-foreground)] hover:no-underline transition-colors"
|
||||
>
|
||||
<Phone size={18} />
|
||||
<span className="text-sm">PLACEHOLDER: Telefon</span>
|
||||
</a>
|
||||
<Link href="/kontakt" className="btn btn-primary">
|
||||
Jetzt beraten lassen
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
type="button"
|
||||
className="lg:hidden p-2 text-[var(--color-foreground)]"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
aria-label={mobileMenuOpen ? "Menü schließen" : "Menü öffnen"}
|
||||
>
|
||||
{mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="lg:hidden border-t border-[var(--color-border)] pb-[var(--spacing-lg)]">
|
||||
<div className="stack-sm pt-[var(--spacing-md)]">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="block py-[var(--spacing-sm)] text-[var(--color-foreground)] hover:text-[var(--color-primary)] hover:no-underline"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
<div className="pt-[var(--spacing-md)] border-t border-[var(--color-border)] mt-[var(--spacing-md)]">
|
||||
<Link
|
||||
href="/kontakt"
|
||||
className="btn btn-primary w-full"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
Jetzt beraten lassen
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,32 @@
|
||||
import type { ElementType, ReactNode } from "react";
|
||||
|
||||
type SectionVariant = "default" | "hero" | "sm" | "lg" | "flush";
|
||||
|
||||
interface SectionProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
as?: ElementType;
|
||||
variant?: SectionVariant;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export function Section({
|
||||
children,
|
||||
className = "",
|
||||
as: Tag = "section",
|
||||
variant = "default",
|
||||
id,
|
||||
}: SectionProps) {
|
||||
const variantClasses: Record<SectionVariant, string> = {
|
||||
default: "section",
|
||||
hero: "section section--hero",
|
||||
sm: "section section--sm",
|
||||
lg: "section section--lg",
|
||||
flush: "section section--flush",
|
||||
};
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={`py-[var(--section-spacing-y)] ${className}`.trim()}
|
||||
>
|
||||
<Tag id={id} className={`${variantClasses[variant]} ${className}`.trim()}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user