Compare commits
2 Commits
main
...
testing-op
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d1d9d6f33 | ||
|
|
11bbd1cf3f |
41
.gitignore
vendored
@@ -31,3 +31,44 @@ npm-debug.log*
|
||||
|
||||
# Generated files
|
||||
generated/
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
18
eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
10
next.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
trailingSlash: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
6612
package-lock.json
generated
Normal file
30
package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "telenetsystems",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.33.0",
|
||||
"lucide-react": "^0.563.0",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
7
postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
BIN
public/images/gallery/firma1.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/gallery/firmenkunden.jpg
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
public/images/gallery/internet.jpg
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
public/images/gallery/lwl1.jpg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/images/gallery/privatkunden.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
public/images/gallery/serverraum.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/images/gallery/telefon.jpg
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
public/images/gallery/telefonie1.jpg
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
public/images/gallery/tv-business.jpg
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
public/images/gallery/tv-paytv.jpg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/images/gallery/tv-privat.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/images/hero/internet-header.jpg
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
public/images/hero/tv-header.jpg
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
public/images/logo/logo-systems.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
public/images/logo/logo-weiss.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/images/team/david-mueller.jpg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
public/images/team/franz.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
public/images/team/furkan-demirel.jpg
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
public/images/team/juergen-graessle.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/images/team/julia-besler.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
public/images/team/lorena.jpg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/images/team/lukas-schennach.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
public/images/team/mario-kien.jpg
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
public/images/team/martin-mueller.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
public/images/team/team-telenet.jpg
Normal file
|
After Width: | Height: | Size: 274 KiB |
BIN
public/images/team/timo.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
public/images/team/wolfgang-schwaiger.jpg
Normal file
|
After Width: | Height: | Size: 127 KiB |
3
public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://www.telenetsystems.at/sitemap.xml
|
||||
43
public/sitemap.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/internet/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/fernsehen/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/telefonie/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/leistungen/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/ueber-uns/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/impressum/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.telenetsystems.at/datenschutz/</loc>
|
||||
<lastmod>2026-02-06</lastmod>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
99
src/app/datenschutz/page.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { Metadata } from "next";
|
||||
import Container from "@/components/ui/Container";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Datenschutz",
|
||||
description: "Datenschutzerklärung der TeleNetSystems gemäß DSGVO. Informationen zur Verarbeitung Ihrer personenbezogenen Daten.",
|
||||
};
|
||||
|
||||
export default function DatenschutzPage() {
|
||||
return (
|
||||
<section className="py-20 lg:py-28">
|
||||
<Container>
|
||||
<article className="prose prose-neutral mx-auto max-w-3xl">
|
||||
<h1 className="text-display font-bold tracking-tight text-neutral-900">Datenschutzerklärung</h1>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-10">1. Verantwortlicher</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
TeleNetSystems<br />
|
||||
Untermarkt 16<br />
|
||||
6600 Reutte, Tirol, Österreich<br />
|
||||
E-Mail: info@telenetsystems.at<br />
|
||||
Telefon: +43 5672 21400
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">2. Erhebung und Verarbeitung personenbezogener Daten</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Wenn Sie unsere Website besuchen, werden automatisch Informationen allgemeiner Natur
|
||||
erfasst. Diese Informationen (Server-Logfiles) beinhalten etwa die Art des Webbrowsers,
|
||||
das verwendete Betriebssystem, den Domainnamen Ihres Internet-Service-Providers und
|
||||
ähnliches. Hierbei handelt es sich ausschließlich um Informationen, welche keine
|
||||
Rückschlüsse auf Ihre Person zulassen.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">3. Kontaktformular</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben
|
||||
aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten
|
||||
zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns
|
||||
gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.
|
||||
</p>
|
||||
<p className="text-neutral-600 leading-relaxed mt-2">
|
||||
Die Verarbeitung der in das Kontaktformular eingegebenen Daten erfolgt somit
|
||||
ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO).
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">4. Cookies</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Diese Website verwendet keine Tracking-Cookies. Es werden ausschließlich technisch
|
||||
notwendige Cookies eingesetzt, die für den Betrieb der Website erforderlich sind.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">5. Ihre Rechte</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Sie haben jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten
|
||||
personenbezogenen Daten, deren Herkunft und Empfänger und den Zweck der
|
||||
Datenverarbeitung sowie ein Recht auf Berichtigung, Sperrung oder Löschung
|
||||
dieser Daten.
|
||||
</p>
|
||||
<p className="text-neutral-600 leading-relaxed mt-2">
|
||||
Ihnen stehen im Wesentlichen folgende Rechte zu:
|
||||
</p>
|
||||
<ul className="text-neutral-600 list-disc pl-6 space-y-1 mt-2">
|
||||
<li>Recht auf Auskunft (Art. 15 DSGVO)</li>
|
||||
<li>Recht auf Berichtigung (Art. 16 DSGVO)</li>
|
||||
<li>Recht auf Löschung (Art. 17 DSGVO)</li>
|
||||
<li>Recht auf Einschränkung der Verarbeitung (Art. 18 DSGVO)</li>
|
||||
<li>Recht auf Datenübertragbarkeit (Art. 20 DSGVO)</li>
|
||||
<li>Recht auf Widerspruch (Art. 21 DSGVO)</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">6. Beschwerderecht bei der Aufsichtsbehörde</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Wenn Sie der Ansicht sind, dass die Verarbeitung Ihrer personenbezogenen Daten
|
||||
gegen die DSGVO verstößt, haben Sie das Recht, sich bei der Österreichischen
|
||||
Datenschutzbehörde zu beschweren.
|
||||
</p>
|
||||
<p className="text-neutral-600 leading-relaxed mt-2">
|
||||
Österreichische Datenschutzbehörde<br />
|
||||
Barichgasse 40-42<br />
|
||||
1030 Wien<br />
|
||||
Telefon: +43 1 52 152-0<br />
|
||||
E-Mail: dsb@dsb.gv.at
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">7. Änderung dieser Datenschutzerklärung</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Wir behalten uns vor, diese Datenschutzerklärung gelegentlich anzupassen, damit
|
||||
sie stets den aktuellen rechtlichen Anforderungen entspricht oder um Änderungen
|
||||
unserer Leistungen in der Datenschutzerklärung umzusetzen.
|
||||
</p>
|
||||
|
||||
<p className="text-neutral-500 text-sm mt-10">
|
||||
Stand: Februar 2026
|
||||
</p>
|
||||
</article>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
BIN
src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
114
src/app/fernsehen/page.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Building2 } from "lucide-react";
|
||||
import PageHero from "@/components/sections/PageHero";
|
||||
import TariffTable from "@/components/sections/TariffTable";
|
||||
import ContactSection from "@/components/sections/ContactSection";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import Button from "@/components/ui/Button";
|
||||
import { TV_PRIVAT_PAKETE } from "@/lib/constants";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Fernsehen",
|
||||
description:
|
||||
"TV-Pakete für Privat- und Geschäftskunden in Tirol. Von der Grundversorgung bis zum individuellen Business-TV. Empfang über Kabel, kein Schüssel nötig.",
|
||||
};
|
||||
|
||||
export default function FernsehenPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="Fernsehen mit TeleNetSystems"
|
||||
description="Kein Herumfummeln an der Satellitenschüssel. Unser TV kommt über Kabel — zuverlässig, in HD und mit einer Senderauswahl, die passt."
|
||||
backgroundImage="/images/hero/tv-header.jpg"
|
||||
/>
|
||||
|
||||
{/* TV Privat */}
|
||||
<section className="py-20 lg:py-24" aria-labelledby="tv-privat-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="TV für Privatkunden"
|
||||
subtitle="Drei Pakete, klare Inhalte. Wählen Sie, was zu Ihrem Fernsehabend passt."
|
||||
/>
|
||||
<TariffTable plans={TV_PRIVAT_PAKETE} />
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Pay TV */}
|
||||
<section className="bg-neutral-50 py-20 lg:py-24" aria-labelledby="paytv-heading">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
|
||||
<div className="overflow-hidden rounded-card">
|
||||
<img
|
||||
src="/images/gallery/tv-paytv.jpg"
|
||||
alt="Pay-TV Zusatzpakete"
|
||||
className="w-full object-cover"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 id="paytv-heading" className="text-title font-bold text-neutral-900">
|
||||
Pay TV — mehr Auswahl, wenn Sie möchten
|
||||
</h2>
|
||||
<p className="mt-4 text-neutral-600 leading-relaxed">
|
||||
Sport, Filme, Dokumentationen oder internationale Sender: Mit unseren
|
||||
Pay-TV-Zusatzpaketen erweitern Sie Ihr Programm gezielt. Kein Abo-Dschungel,
|
||||
sondern klare Zusatzoptionen zu Ihrem bestehenden TV-Paket.
|
||||
</p>
|
||||
<p className="mt-4 text-neutral-600 leading-relaxed">
|
||||
Welche Zusatzpakete für Sie sinnvoll sind, hängt von Ihren Interessen ab.
|
||||
Wir beraten Sie gerne — unverbindlich und ehrlich.
|
||||
</p>
|
||||
<Button href="/#kontakt" variant="outline" size="md" className="mt-8">
|
||||
Zu Pay TV beraten lassen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* TV Business */}
|
||||
<section className="py-20 lg:py-24" aria-labelledby="tv-business-heading">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-amber-100">
|
||||
<Building2 className="h-5 w-5 text-amber-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h2 id="tv-business-heading" className="text-title font-bold text-neutral-900">
|
||||
TV für Unternehmen
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Hotels, Pensionen, Wartezimmer, Gastronomiebetriebe — überall dort, wo Fernsehen
|
||||
zum Angebot gehört, brauchen Sie eine Lösung, die einfach funktioniert.
|
||||
</p>
|
||||
<p className="mt-4 text-neutral-600 leading-relaxed">
|
||||
Wir richten Ihr Business-TV so ein, dass es zu Ihrem Betrieb passt. Senderauswahl,
|
||||
Anzahl der Anschlüsse, zentrale Steuerung — alles wird individuell abgestimmt.
|
||||
</p>
|
||||
<Button href="/#kontakt" variant="primary" size="md" className="mt-8">
|
||||
Business-TV anfragen
|
||||
</Button>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-card">
|
||||
<img
|
||||
src="/images/gallery/tv-business.jpg"
|
||||
alt="TV-Lösungen für Hotels und Geschäftskunden"
|
||||
className="w-full object-cover"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
80
src/app/globals.css
Normal file
@@ -0,0 +1,80 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme inline {
|
||||
--color-primary-50: #fef7ed;
|
||||
--color-primary-100: #fdebd4;
|
||||
--color-primary-200: #fbd4a8;
|
||||
--color-primary-300: #f8b871;
|
||||
--color-primary-400: #f5a038;
|
||||
--color-primary-500: #F39212;
|
||||
--color-primary-600: #da7c0a;
|
||||
--color-primary-700: #b5620b;
|
||||
--color-primary-800: #914d10;
|
||||
--color-primary-900: #764010;
|
||||
--color-primary-950: #402006;
|
||||
|
||||
--color-secondary-50: #fef3ed;
|
||||
--color-secondary-100: #fde3d4;
|
||||
--color-secondary-200: #fac4a8;
|
||||
--color-secondary-300: #f69d71;
|
||||
--color-secondary-400: #f17838;
|
||||
--color-secondary-500: #EB5C23;
|
||||
--color-secondary-600: #d14416;
|
||||
--color-secondary-700: #ae3313;
|
||||
--color-secondary-800: #8b2a17;
|
||||
--color-secondary-900: #712616;
|
||||
--color-secondary-950: #3d100a;
|
||||
|
||||
--color-neutral-50: #fafafa;
|
||||
--color-neutral-100: #f5f5f5;
|
||||
--color-neutral-200: #e5e5e5;
|
||||
--color-neutral-300: #d4d4d4;
|
||||
--color-neutral-400: #a3a3a3;
|
||||
--color-neutral-500: #737373;
|
||||
--color-neutral-600: #525252;
|
||||
--color-neutral-700: #404040;
|
||||
--color-neutral-800: #262626;
|
||||
--color-neutral-900: #171717;
|
||||
--color-neutral-950: #0a0a0a;
|
||||
|
||||
--color-accent: #F39212;
|
||||
--color-accent-dark: #da7c0a;
|
||||
|
||||
--font-sans: var(--font-inter), system-ui, sans-serif;
|
||||
|
||||
--text-hero: 3.5rem;
|
||||
--text-hero--line-height: 1.1;
|
||||
--text-hero--letter-spacing: -0.02em;
|
||||
|
||||
--text-display: 2.5rem;
|
||||
--text-display--line-height: 1.15;
|
||||
--text-display--letter-spacing: -0.015em;
|
||||
|
||||
--text-title: 1.75rem;
|
||||
--text-title--line-height: 1.25;
|
||||
|
||||
--text-body-lg: 1.125rem;
|
||||
--text-body-lg--line-height: 1.7;
|
||||
|
||||
--spacing-section: 5rem;
|
||||
--spacing-section-lg: 7rem;
|
||||
|
||||
--radius-card: 0.75rem;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-white text-neutral-800 antialiased;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
@apply outline-2 outline-offset-2 outline-primary-500;
|
||||
}
|
||||
75
src/app/impressum/page.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { Metadata } from "next";
|
||||
import Container from "@/components/ui/Container";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Impressum",
|
||||
description: "Impressum und rechtliche Informationen der TeleNetSystems in Reutte, Tirol.",
|
||||
};
|
||||
|
||||
export default function ImpressumPage() {
|
||||
return (
|
||||
<section className="py-20 lg:py-28">
|
||||
<Container>
|
||||
<article className="prose prose-neutral mx-auto max-w-3xl">
|
||||
<h1 className="text-display font-bold tracking-tight text-neutral-900">Impressum</h1>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-10">Angaben gemäß § 5 ECG</h2>
|
||||
<p>
|
||||
TeleNetSystems<br />
|
||||
Untermarkt 16<br />
|
||||
6600 Reutte<br />
|
||||
Tirol, Österreich
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">Kontakt</h2>
|
||||
<p>
|
||||
Telefon: +43 5672 21400<br />
|
||||
E-Mail: info@telenetsystems.at
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">Unternehmensgegenstand</h2>
|
||||
<p>
|
||||
IT-Dienstleistungen und Telekommunikation
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">Aufsichtsbehörde</h2>
|
||||
<p>
|
||||
Bezirkshauptmannschaft Reutte
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">Berufsbezeichnung und berufsrechtliche Regelungen</h2>
|
||||
<p>
|
||||
Berufsbezeichnung: IT-Dienstleister<br />
|
||||
Verleihungsstaat: Österreich
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">Haftungsausschluss</h2>
|
||||
|
||||
<h3 className="text-lg font-semibold text-neutral-900 mt-6">Haftung für Inhalte</h3>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit,
|
||||
Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen.
|
||||
Als Diensteanbieter sind wir gemäß § 7 Abs. 1 ECG für eigene Inhalte auf diesen
|
||||
Seiten nach den allgemeinen Gesetzen verantwortlich.
|
||||
</p>
|
||||
|
||||
<h3 className="text-lg font-semibold text-neutral-900 mt-6">Haftung für Links</h3>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir
|
||||
keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine
|
||||
Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige
|
||||
Anbieter oder Betreiber der Seiten verantwortlich.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-neutral-900 mt-8">Urheberrecht</h2>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten
|
||||
unterliegen dem österreichischen Urheberrecht. Die Vervielfältigung, Bearbeitung,
|
||||
Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes
|
||||
bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.
|
||||
</p>
|
||||
</article>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
93
src/app/internet/page.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Building2 } from "lucide-react";
|
||||
import PageHero from "@/components/sections/PageHero";
|
||||
import TariffTable from "@/components/sections/TariffTable";
|
||||
import ContactSection from "@/components/sections/ContactSection";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import Button from "@/components/ui/Button";
|
||||
import { INTERNET_TARIFE } from "@/lib/constants";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Internet",
|
||||
description:
|
||||
"Schnelles Internet in Reutte und Tirol. Kabel- und Glasfaser-Tarife für Privat- und Geschäftskunden. Faire Preise, stabile Verbindung.",
|
||||
};
|
||||
|
||||
export default function InternetPage() {
|
||||
const kabelTarife = INTERNET_TARIFE.filter((t) => t.category === "kabel");
|
||||
const glasfaserTarife = INTERNET_TARIFE.filter((t) => t.category === "glasfaser");
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="Internet für Reutte und Umgebung"
|
||||
description="Ob Streaming, Homeoffice oder einfach sorgenfreies Surfen — wir bringen Sie ins Netz. Stabil, schnell und ohne Überraschungen auf der Rechnung."
|
||||
backgroundImage="/images/hero/internet-header.jpg"
|
||||
/>
|
||||
|
||||
{/* Kabel-Internet */}
|
||||
<section className="py-20 lg:py-24" aria-labelledby="kabel-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Kabel-Internet"
|
||||
subtitle="Unsere bewährten Tarife über das Kabelnetz. Verfügbar im Großteil des Außerferns."
|
||||
/>
|
||||
<TariffTable plans={kabelTarife} />
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Glasfaser */}
|
||||
<section className="bg-neutral-50 py-20 lg:py-24" aria-labelledby="glasfaser-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Glasfaser-Internet"
|
||||
subtitle="Wo Glasfaser verfügbar ist, geht mehr. Deutlich mehr. Ideal für anspruchsvolle Nutzer und Mehrpersonenhaushalte."
|
||||
/>
|
||||
<TariffTable plans={glasfaserTarife} />
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Business Internet */}
|
||||
<section className="py-20 lg:py-24" aria-labelledby="business-internet-heading">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-amber-100">
|
||||
<Building2 className="h-5 w-5 text-amber-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h2 id="business-internet-heading" className="text-title font-bold text-neutral-900">
|
||||
Internet für Ihr Unternehmen
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-neutral-600 leading-relaxed">
|
||||
Standardtarife passen nicht immer. Wenn Ihr Betrieb symmetrische Bandbreiten braucht,
|
||||
feste IP-Adressen benötigt oder mehrere Standorte vernetzen will — sprechen Sie mit uns.
|
||||
</p>
|
||||
<p className="mt-4 text-neutral-600 leading-relaxed">
|
||||
Wir schauen uns Ihre Situation an und machen Ihnen ein Angebot, das zu Ihrem
|
||||
Unternehmen passt. Keine Pakete von der Stange, sondern genau das, was Sie brauchen.
|
||||
</p>
|
||||
<Button href="/#kontakt" variant="primary" size="md" className="mt-8">
|
||||
Individuelles Angebot anfragen
|
||||
</Button>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-card">
|
||||
<img
|
||||
src="/images/gallery/serverraum.jpg"
|
||||
alt="Serverraum für Business-Internet-Lösungen"
|
||||
className="w-full object-cover"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
108
src/app/layout.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-inter",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://www.telenetsystems.at"),
|
||||
title: {
|
||||
default: "TeleNetSystems | Internet, TV & Telefonie in Reutte, Tirol",
|
||||
template: "%s | TeleNetSystems",
|
||||
},
|
||||
description:
|
||||
"Ihr regionaler Partner für Internet, Fernsehen und Telefonie in Reutte und Tirol. Persönliche Beratung, stabile Technik, faire Preise.",
|
||||
keywords: [
|
||||
"Internet Reutte",
|
||||
"TV Anbieter Tirol",
|
||||
"Telefonie Reutte",
|
||||
"TeleNetSystems",
|
||||
"Internet Tirol",
|
||||
"Glasfaser Reutte",
|
||||
],
|
||||
authors: [{ name: "TeleNetSystems" }],
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "de_AT",
|
||||
siteName: "TeleNetSystems",
|
||||
title: "TeleNetSystems | Internet, TV & Telefonie in Reutte, Tirol",
|
||||
description:
|
||||
"Ihr regionaler Partner für Internet, Fernsehen und Telefonie in Reutte und Tirol.",
|
||||
images: [
|
||||
{
|
||||
url: "/images/logo/logo-systems.png",
|
||||
width: 500,
|
||||
height: 200,
|
||||
alt: "TeleNetSystems Logo",
|
||||
},
|
||||
],
|
||||
},
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
},
|
||||
};
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "LocalBusiness",
|
||||
name: "TeleNetSystems",
|
||||
description:
|
||||
"Regionaler IT- und Telekommunikationsanbieter in Reutte, Tirol. Internet, Fernsehen und Telefonie für Privat- und Geschäftskunden.",
|
||||
address: {
|
||||
"@type": "PostalAddress",
|
||||
streetAddress: "Untermarkt 16",
|
||||
addressLocality: "Reutte",
|
||||
addressRegion: "Tirol",
|
||||
postalCode: "6600",
|
||||
addressCountry: "AT",
|
||||
},
|
||||
telephone: "+43 5672 21400",
|
||||
email: "info@telenetsystems.at",
|
||||
url: "https://www.telenetsystems.at",
|
||||
image: "/images/logo/logo-systems.png",
|
||||
areaServed: {
|
||||
"@type": "GeoCircle",
|
||||
geoMidpoint: {
|
||||
"@type": "GeoCoordinates",
|
||||
latitude: 47.4833,
|
||||
longitude: 10.7167,
|
||||
},
|
||||
geoRadius: "50000",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="de" className={inter.variable}>
|
||||
<head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
</head>
|
||||
<body className="antialiased">
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:rounded-lg focus:bg-primary-600 focus:px-4 focus:py-2 focus:text-white"
|
||||
>
|
||||
Zum Inhalt springen
|
||||
</a>
|
||||
<Header />
|
||||
<main id="main-content" className="pt-16 lg:pt-20">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
147
src/app/leistungen/page.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Wifi, Tv, Phone, Server, Shield, Wrench } from "lucide-react";
|
||||
import PageHero from "@/components/sections/PageHero";
|
||||
import ContactSection from "@/components/sections/ContactSection";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Leistungen",
|
||||
description:
|
||||
"Alle Dienstleistungen von TeleNetSystems im Überblick. Internet, TV, Telefonie und IT-Lösungen für Privat- und Geschäftskunden in Reutte und Tirol.",
|
||||
};
|
||||
|
||||
const services = [
|
||||
{
|
||||
icon: Wifi,
|
||||
title: "Internet",
|
||||
description:
|
||||
"Kabel und Glasfaser für Privathaushalte und Unternehmen. Von 30 Mbit/s bis zu 500 Mbit/s — je nach Verfügbarkeit und Bedarf.",
|
||||
href: "/internet",
|
||||
image: "/images/gallery/internet.jpg",
|
||||
imageAlt: "Internet-Dienste von TeleNetSystems",
|
||||
},
|
||||
{
|
||||
icon: Tv,
|
||||
title: "Fernsehen",
|
||||
description:
|
||||
"TV-Pakete über Kabel, von der Grundversorgung bis zum umfangreichen Premium-Paket. Für Privatkunden und Unternehmen.",
|
||||
href: "/fernsehen",
|
||||
image: "/images/gallery/tv-privat.jpg",
|
||||
imageAlt: "TV-Dienste von TeleNetSystems",
|
||||
},
|
||||
{
|
||||
icon: Phone,
|
||||
title: "Telefonie",
|
||||
description:
|
||||
"Festnetz-Tarife, die zum Telefonierverhalten passen. Minutengenau oder als Flatrate, auch in Kombination mit Internet.",
|
||||
href: "/telefonie",
|
||||
image: "/images/gallery/telefonie1.jpg",
|
||||
imageAlt: "Telefonie-Dienste von TeleNetSystems",
|
||||
},
|
||||
];
|
||||
|
||||
const additionalServices = [
|
||||
{
|
||||
icon: Server,
|
||||
title: "IT-Infrastruktur",
|
||||
description: "Netzwerke, Server und die Technik dahinter. Wir planen, installieren und betreuen Ihre IT-Umgebung.",
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: "Netzwerksicherheit",
|
||||
description: "Firewalls, sichere Zugänge und Datenschutz. Damit Ihre Daten dort bleiben, wo sie hingehören.",
|
||||
},
|
||||
{
|
||||
icon: Wrench,
|
||||
title: "Technischer Support",
|
||||
description: "Wenn etwas nicht funktioniert, sind wir da. Vor Ort in Reutte, nicht am anderen Ende einer Hotline.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function LeistungenPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="Unsere Leistungen im Überblick"
|
||||
description="Von Internet über TV bis zur kompletten IT-Betreuung. Alles aus einer Hand, alles aus Reutte."
|
||||
backgroundImage="/images/gallery/firma1.jpg"
|
||||
/>
|
||||
|
||||
{/* Main Services */}
|
||||
<section className="py-20 lg:py-24" aria-labelledby="main-services-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Unsere Kernbereiche"
|
||||
subtitle="Drei Dienste, auf die Sie sich verlassen können."
|
||||
/>
|
||||
<div className="space-y-16" id="main-services-heading">
|
||||
{services.map((service, index) => {
|
||||
const Icon = service.icon;
|
||||
const isReversed = index % 2 !== 0;
|
||||
return (
|
||||
<div
|
||||
key={service.title}
|
||||
className={`grid grid-cols-1 items-center gap-12 lg:grid-cols-2 ${isReversed ? "lg:direction-rtl" : ""}`}
|
||||
>
|
||||
<div className={isReversed ? "lg:order-2" : ""}>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary-100">
|
||||
<Icon className="h-5 w-5 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="text-title font-bold text-neutral-900">{service.title}</h3>
|
||||
</div>
|
||||
<p className="text-neutral-600 leading-relaxed">{service.description}</p>
|
||||
<Button href={service.href} variant="outline" size="sm" className="mt-6">
|
||||
Zu den {service.title}-Tarifen
|
||||
</Button>
|
||||
</div>
|
||||
<div className={`overflow-hidden rounded-card ${isReversed ? "lg:order-1" : ""}`}>
|
||||
<img
|
||||
src={service.image}
|
||||
alt={service.imageAlt}
|
||||
className="w-full object-cover"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Additional Services */}
|
||||
<section className="bg-neutral-50 py-20 lg:py-24" aria-labelledby="additional-services-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Darüber hinaus"
|
||||
subtitle="Neben Internet, TV und Telefonie unterstützen wir Sie auch bei der IT-Infrastruktur."
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-3" id="additional-services-heading">
|
||||
{additionalServices.map((service) => {
|
||||
const Icon = service.icon;
|
||||
return (
|
||||
<div
|
||||
key={service.title}
|
||||
className="rounded-card bg-white p-8 shadow-sm border border-neutral-200"
|
||||
>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-100">
|
||||
<Icon className="h-6 w-6 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="mt-4 text-lg font-semibold text-neutral-900">{service.title}</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-neutral-600">{service.description}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
21
src/app/not-found.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import Container from "@/components/ui/Container";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<section className="flex min-h-[60vh] items-center py-20">
|
||||
<Container className="text-center">
|
||||
<p className="text-6xl font-bold text-primary-500">404</p>
|
||||
<h1 className="mt-4 text-2xl font-bold text-neutral-900">
|
||||
Diese Seite gibt es leider nicht
|
||||
</h1>
|
||||
<p className="mt-4 text-neutral-600">
|
||||
Die angeforderte Seite konnte nicht gefunden werden. Vielleicht hilft Ihnen die Startseite weiter.
|
||||
</p>
|
||||
<Button href="/" variant="primary" size="md" className="mt-8">
|
||||
Zur Startseite
|
||||
</Button>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
19
src/app/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import HeroSection from "@/components/sections/HeroSection";
|
||||
import ServiceOverview from "@/components/sections/ServiceOverview";
|
||||
import CustomerSegments from "@/components/sections/CustomerSegments";
|
||||
import ValueProposition from "@/components/sections/ValueProposition";
|
||||
import TrustSection from "@/components/sections/TrustSection";
|
||||
import ContactSection from "@/components/sections/ContactSection";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<HeroSection />
|
||||
<ServiceOverview />
|
||||
<CustomerSegments />
|
||||
<ValueProposition />
|
||||
<TrustSection />
|
||||
<ContactSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
63
src/app/telefonie/page.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { Metadata } from "next";
|
||||
import PageHero from "@/components/sections/PageHero";
|
||||
import TariffTable from "@/components/sections/TariffTable";
|
||||
import ContactSection from "@/components/sections/ContactSection";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import { TELEFONIE_TARIFE } from "@/lib/constants";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Telefonie",
|
||||
description:
|
||||
"Günstige Telefonie-Tarife in Reutte und Tirol. Festnetz-Flatrates und minutengenaue Abrechnung. Klar, einfach, regional.",
|
||||
};
|
||||
|
||||
export default function TelefoniePage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="Telefonie — klar und günstig"
|
||||
description="Festnetz, das funktioniert. Ohne Vertragsfallen, ohne versteckte Kosten. Genau das, was Sie zum Telefonieren brauchen."
|
||||
/>
|
||||
|
||||
{/* Telefonie Tarife */}
|
||||
<section className="py-20 lg:py-24" aria-labelledby="telefonie-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Unsere Telefonie-Tarife"
|
||||
subtitle="Von der minutengenauen Abrechnung bis zur Flatrate. Wählen Sie den Tarif, der zu Ihrem Telefonierverhalten passt."
|
||||
/>
|
||||
<TariffTable plans={TELEFONIE_TARIFE} />
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Additional Info */}
|
||||
<section className="bg-neutral-50 py-20 lg:py-24" aria-labelledby="telefonie-info-heading">
|
||||
<Container>
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<h2 id="telefonie-info-heading" className="text-title font-bold text-neutral-900 text-center">
|
||||
Gut zu wissen
|
||||
</h2>
|
||||
<div className="mt-8 space-y-6 text-neutral-600 leading-relaxed">
|
||||
<p>
|
||||
Unsere Telefonie-Tarife lassen sich mit jedem Internet-Paket kombinieren. So nutzen
|
||||
Sie beides über einen Anschluss und haben nur einen Ansprechpartner.
|
||||
</p>
|
||||
<p>
|
||||
Rufnummernmitnahme? Kein Problem. Wir kümmern uns darum, dass Ihre bestehende
|
||||
Nummer erhalten bleibt. Der Wechsel läuft im Hintergrund — Sie müssen sich um
|
||||
nichts kümmern.
|
||||
</p>
|
||||
<p>
|
||||
Für Geschäftskunden bieten wir auch Telefonanlagen und SIP-Trunking an.
|
||||
Sprechen Sie uns einfach an.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
117
src/app/ueber-uns/page.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Heart, Target, Users, Zap } from "lucide-react";
|
||||
import PageHero from "@/components/sections/PageHero";
|
||||
import TeamGrid from "@/components/sections/TeamGrid";
|
||||
import ContactSection from "@/components/sections/ContactSection";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Über uns",
|
||||
description:
|
||||
"Lernen Sie das Team hinter TeleNetSystems kennen. Regional verwurzelt in Reutte, Tirol. Persönlicher Service seit über 20 Jahren.",
|
||||
};
|
||||
|
||||
const companyValues = [
|
||||
{
|
||||
icon: Heart,
|
||||
title: "Ehrliche Beratung",
|
||||
description: "Wir empfehlen, was sinnvoll ist — nicht, was die höchste Marge bringt.",
|
||||
},
|
||||
{
|
||||
icon: Target,
|
||||
title: "Klare Kommunikation",
|
||||
description: "Was wir versprechen, halten wir. Ohne Sternchen im Kleingedruckten.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Regionale Verantwortung",
|
||||
description: "Wir leben und arbeiten hier. Was wir tun, hat direkten Einfluss auf unsere Nachbarn.",
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
title: "Schnelles Handeln",
|
||||
description: "Wenn es brennt, sind wir da. Nicht morgen, sondern heute.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function UeberUnsPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="Wir sind TeleNetSystems"
|
||||
description="Ein Team aus Reutte, das Technik versteht und Menschen ernst nimmt."
|
||||
backgroundImage="/images/team/team-telenet.jpg"
|
||||
/>
|
||||
|
||||
{/* Company Story */}
|
||||
<section className="py-20 lg:py-24" aria-labelledby="story-heading">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
|
||||
<div>
|
||||
<h2 id="story-heading" className="text-title font-bold text-neutral-900">
|
||||
Aus der Region, für die Region
|
||||
</h2>
|
||||
<div className="mt-6 space-y-4 text-neutral-600 leading-relaxed">
|
||||
<p>
|
||||
TeleNetSystems gibt es nicht erst seit gestern. Seit über zwei Jahrzehnten
|
||||
versorgen wir Reutte und das Außerfern mit Internet, Fernsehen und Telefonie.
|
||||
Angefangen hat alles mit ein paar Kabelanschlüssen. Heute betreuen wir
|
||||
über tausend Kunden — privat und gewerblich.
|
||||
</p>
|
||||
<p>
|
||||
Was sich nicht geändert hat: Wir sitzen hier. In Reutte, nicht in Wien oder
|
||||
München. Wenn Sie anrufen, heben wir ab. Wenn etwas nicht funktioniert,
|
||||
kommen wir vorbei. Das klingt selbstverständlich, ist es aber bei den
|
||||
Großen der Branche längst nicht mehr.
|
||||
</p>
|
||||
<p>
|
||||
Uns geht es nicht darum, die meisten Kunden zu haben. Es geht darum,
|
||||
die Kunden, die wir haben, gut zu betreuen. Daran messen wir uns selbst.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-card">
|
||||
<img
|
||||
src="/images/gallery/firma1.jpg"
|
||||
alt="TeleNetSystems Firmengebäude in Reutte"
|
||||
className="w-full object-cover"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Values */}
|
||||
<section className="bg-neutral-50 py-20 lg:py-24" aria-labelledby="values-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Wofür wir stehen"
|
||||
subtitle="Vier Grundsätze, an denen wir uns im Alltag messen."
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4" id="values-heading">
|
||||
{companyValues.map((value) => {
|
||||
const Icon = value.icon;
|
||||
return (
|
||||
<div key={value.title} className="rounded-card bg-white p-6 shadow-sm border border-neutral-200">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-100">
|
||||
<Icon className="h-6 w-6 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="mt-4 text-base font-semibold text-neutral-900">{value.title}</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-neutral-600">{value.description}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<TeamGrid />
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
118
src/components/layout/Footer.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import Link from "next/link";
|
||||
import { Phone, Mail, MapPin } from "lucide-react";
|
||||
import Container from "@/components/ui/Container";
|
||||
import { COMPANY_NAME, CONTACT, FOOTER_LINKS } from "@/lib/constants";
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-neutral-900 text-neutral-300" role="contentinfo">
|
||||
<Container className="py-16">
|
||||
<div className="grid grid-cols-1 gap-12 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{/* Company Info */}
|
||||
<div className="sm:col-span-2 lg:col-span-1">
|
||||
<Link href="/" aria-label="TeleNetSystems – Zur Startseite">
|
||||
<img
|
||||
src="/images/logo/logo-weiss.png"
|
||||
alt="TeleNetSystems Logo"
|
||||
className="h-10 w-auto"
|
||||
width={160}
|
||||
height={40}
|
||||
/>
|
||||
</Link>
|
||||
<p className="mt-4 text-sm leading-relaxed text-neutral-400">
|
||||
Ihr regionaler Partner für Internet, Fernsehen und Telefonie in Reutte und ganz Tirol.
|
||||
</p>
|
||||
<div className="mt-6 space-y-3">
|
||||
<a
|
||||
href={`tel:${CONTACT.phone.replace(/\s/g, "")}`}
|
||||
className="flex items-center gap-2 text-sm text-neutral-400 transition-colors hover:text-white"
|
||||
>
|
||||
<Phone className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
|
||||
{CONTACT.phone}
|
||||
</a>
|
||||
<a
|
||||
href={`mailto:${CONTACT.email}`}
|
||||
className="flex items-center gap-2 text-sm text-neutral-400 transition-colors hover:text-white"
|
||||
>
|
||||
<Mail className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
|
||||
{CONTACT.email}
|
||||
</a>
|
||||
<div className="flex items-start gap-2 text-sm text-neutral-400">
|
||||
<MapPin className="h-4 w-4 flex-shrink-0 mt-0.5" aria-hidden="true" />
|
||||
<address className="not-italic">
|
||||
{CONTACT.address.slice(1).join(", ")}
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Services */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white">
|
||||
Dienste
|
||||
</h3>
|
||||
<ul className="mt-4 space-y-3">
|
||||
{FOOTER_LINKS.services.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm text-neutral-400 transition-colors hover:text-white"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Company */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white">
|
||||
Unternehmen
|
||||
</h3>
|
||||
<ul className="mt-4 space-y-3">
|
||||
{FOOTER_LINKS.company.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm text-neutral-400 transition-colors hover:text-white"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white">
|
||||
Rechtliches
|
||||
</h3>
|
||||
<ul className="mt-4 space-y-3">
|
||||
{FOOTER_LINKS.legal.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm text-neutral-400 transition-colors hover:text-white"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Bar */}
|
||||
<div className="mt-12 border-t border-neutral-800 pt-8 text-center">
|
||||
<p className="text-sm text-neutral-500">
|
||||
© {currentYear} {COMPANY_NAME}. Alle Rechte vorbehalten.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
79
src/components/layout/Header.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
import { Menu } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Navigation from "./Navigation";
|
||||
import MobileMenu from "./MobileMenu";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
export default function Header() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
};
|
||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const closeMobileMenu = useCallback(() => {
|
||||
setIsMobileMenuOpen(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
"fixed top-0 left-0 right-0 z-30 transition-all duration-300",
|
||||
isScrolled
|
||||
? "bg-white/95 shadow-sm backdrop-blur-sm"
|
||||
: "bg-white"
|
||||
)}
|
||||
role="banner"
|
||||
>
|
||||
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4 sm:px-6 lg:h-20 lg:px-8">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex-shrink-0"
|
||||
aria-label="TeleNetSystems – Zur Startseite"
|
||||
>
|
||||
<img
|
||||
src="/images/logo/logo-systems.png"
|
||||
alt="TeleNetSystems Logo"
|
||||
className="h-9 w-auto lg:h-11"
|
||||
width={180}
|
||||
height={44}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<Navigation />
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
href="/#kontakt"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="hidden lg:inline-flex"
|
||||
>
|
||||
Kontakt
|
||||
</Button>
|
||||
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(true)}
|
||||
className="rounded-lg p-2 text-neutral-700 hover:bg-neutral-100 lg:hidden"
|
||||
aria-label="Menü öffnen"
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
aria-controls="mobile-menu"
|
||||
>
|
||||
<Menu className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MobileMenu isOpen={isMobileMenuOpen} onClose={closeMobileMenu} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
121
src/components/layout/MobileMenu.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { NAV_LINKS, CONTACT } from "@/lib/constants";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
interface MobileMenuProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function MobileMenu({ isOpen, onClose }: MobileMenuProps) {
|
||||
const pathname = usePathname();
|
||||
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
closeButtonRef.current?.focus();
|
||||
} else {
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
if (isOpen) {
|
||||
window.addEventListener("keydown", handleEscape);
|
||||
}
|
||||
return () => window.removeEventListener("keydown", handleEscape);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
onClose();
|
||||
}, [pathname, onClose]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-40 bg-black/50"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ x: "100%" }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: "100%" }}
|
||||
transition={{ type: "tween", duration: 0.3 }}
|
||||
className="fixed inset-y-0 right-0 z-50 w-full max-w-sm bg-white shadow-xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Menü"
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-center justify-between border-b border-neutral-200 p-4">
|
||||
<span className="text-lg font-semibold text-neutral-900">Menü</span>
|
||||
<button
|
||||
ref={closeButtonRef}
|
||||
onClick={onClose}
|
||||
className="rounded-lg p-2 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700"
|
||||
aria-label="Menü schließen"
|
||||
>
|
||||
<X className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<nav aria-label="Mobile Navigation" className="flex-1 overflow-y-auto p-4">
|
||||
<ul className="space-y-1">
|
||||
{NAV_LINKS.map((link) => {
|
||||
const isActive = pathname.startsWith(link.href);
|
||||
return (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className={cn(
|
||||
"block rounded-lg px-4 py-3 text-base font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-primary-50 text-primary-700"
|
||||
: "text-neutral-700 hover:bg-neutral-100"
|
||||
)}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="border-t border-neutral-200 p-4 space-y-3">
|
||||
<Button href="/#kontakt" variant="primary" size="md" className="w-full">
|
||||
Kontakt aufnehmen
|
||||
</Button>
|
||||
<a
|
||||
href={`tel:${CONTACT.phone.replace(/\s/g, "")}`}
|
||||
className="block text-center text-sm text-neutral-600"
|
||||
>
|
||||
{CONTACT.phone}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
36
src/components/layout/Navigation.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { NAV_LINKS } from "@/lib/constants";
|
||||
|
||||
export default function Navigation() {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<nav aria-label="Hauptnavigation" className="hidden lg:block">
|
||||
<ul className="flex items-center gap-1">
|
||||
{NAV_LINKS.map((link) => {
|
||||
const isActive = pathname.startsWith(link.href);
|
||||
return (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className={cn(
|
||||
"rounded-lg px-4 py-2 text-sm font-medium transition-colors duration-200",
|
||||
isActive
|
||||
? "bg-primary-50 text-primary-700"
|
||||
: "text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900"
|
||||
)}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
137
src/components/sections/ContactSection.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import { Phone, Mail, MapPin, Clock } from "lucide-react";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import Button from "@/components/ui/Button";
|
||||
import { CONTACT } from "@/lib/constants";
|
||||
|
||||
export default function ContactSection() {
|
||||
return (
|
||||
<section id="kontakt" className="bg-neutral-50 py-20 lg:py-24" aria-labelledby="contact-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Sprechen Sie mit uns"
|
||||
subtitle="Rufen Sie an, schreiben Sie uns — oder kommen Sie einfach in Reutte vorbei."
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-12 lg:grid-cols-2" id="contact-heading">
|
||||
{/* Contact Info */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-neutral-900">Direkt erreichen</h3>
|
||||
<p className="mt-2 text-neutral-600 leading-relaxed">
|
||||
Kein Callcenter, keine automatische Weiterleitung. Bei uns landen Sie direkt bei den richtigen Leuten.
|
||||
</p>
|
||||
<div className="mt-8 space-y-6">
|
||||
<a
|
||||
href={`tel:${CONTACT.phone.replace(/\s/g, "")}`}
|
||||
className="flex items-center gap-4 group"
|
||||
>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-100 transition-colors group-hover:bg-primary-200">
|
||||
<Phone className="h-5 w-5 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-neutral-500">Telefon</p>
|
||||
<p className="font-semibold text-neutral-900">{CONTACT.phone}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href={`mailto:${CONTACT.email}`}
|
||||
className="flex items-center gap-4 group"
|
||||
>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-100 transition-colors group-hover:bg-primary-200">
|
||||
<Mail className="h-5 w-5 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-neutral-500">E-Mail</p>
|
||||
<p className="font-semibold text-neutral-900">{CONTACT.email}</p>
|
||||
</div>
|
||||
</a>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-100">
|
||||
<MapPin className="h-5 w-5 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-neutral-500">Adresse</p>
|
||||
<address className="not-italic font-semibold text-neutral-900">
|
||||
{CONTACT.address[1]}, {CONTACT.address[2]}
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-100">
|
||||
<Clock className="h-5 w-5 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-neutral-500">Öffnungszeiten</p>
|
||||
<p className="font-semibold text-neutral-900">Mo–Fr: 8:00–17:00 Uhr</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Form */}
|
||||
<div className="rounded-card bg-white p-8 shadow-sm border border-neutral-200">
|
||||
<h3 className="text-lg font-semibold text-neutral-900">Nachricht senden</h3>
|
||||
<p className="mt-1 text-sm text-neutral-500">Wir melden uns zeitnah bei Ihnen.</p>
|
||||
<form className="mt-6 space-y-4" action={`mailto:${CONTACT.email}`} method="POST" encType="text/plain">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-neutral-700">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
className="mt-1 block w-full rounded-lg border border-neutral-300 px-4 py-3 text-neutral-900 placeholder:text-neutral-400 focus:border-primary-500 focus:ring-1 focus:ring-primary-500"
|
||||
placeholder="Ihr Name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-neutral-700">
|
||||
E-Mail
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
className="mt-1 block w-full rounded-lg border border-neutral-300 px-4 py-3 text-neutral-900 placeholder:text-neutral-400 focus:border-primary-500 focus:ring-1 focus:ring-primary-500"
|
||||
placeholder="ihre@email.at"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="phone-field" className="block text-sm font-medium text-neutral-700">
|
||||
Telefon <span className="text-neutral-400">(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone-field"
|
||||
name="phone"
|
||||
className="mt-1 block w-full rounded-lg border border-neutral-300 px-4 py-3 text-neutral-900 placeholder:text-neutral-400 focus:border-primary-500 focus:ring-1 focus:ring-primary-500"
|
||||
placeholder="+43 ..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-neutral-700">
|
||||
Nachricht
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
rows={4}
|
||||
required
|
||||
className="mt-1 block w-full rounded-lg border border-neutral-300 px-4 py-3 text-neutral-900 placeholder:text-neutral-400 focus:border-primary-500 focus:ring-1 focus:ring-primary-500 resize-none"
|
||||
placeholder="Wie können wir Ihnen helfen?"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="primary" size="md" className="w-full">
|
||||
Nachricht absenden
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
104
src/components/sections/CustomerSegments.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { User, Building2 } from "lucide-react";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
export default function CustomerSegments() {
|
||||
return (
|
||||
<section className="bg-neutral-50 py-20 lg:py-24" aria-labelledby="segments-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Für wen wir da sind"
|
||||
subtitle="Ob Privathaushalt oder Unternehmen — wir kennen die Anforderungen und finden die passende Lösung."
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2" id="segments-heading">
|
||||
{/* Privatkunden */}
|
||||
<div className="overflow-hidden rounded-card bg-white shadow-sm border border-neutral-200">
|
||||
<div className="aspect-video overflow-hidden">
|
||||
<img
|
||||
src="/images/gallery/privatkunden.jpg"
|
||||
alt="Privatkunden von TeleNetSystems"
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={338}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100">
|
||||
<User className="h-5 w-5 text-blue-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-neutral-900">Privatkunden</h3>
|
||||
</div>
|
||||
<ul className="mt-5 space-y-2 text-neutral-600">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
Stabile Internet-Verbindung für den Alltag
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
TV-Pakete, die zu Ihrem Sehverhalten passen
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
Telefonie ohne Kleingedrucktes
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
Persönlicher Ansprechpartner in Reutte
|
||||
</li>
|
||||
</ul>
|
||||
<Button href="/internet" variant="outline" size="sm" className="mt-6">
|
||||
Privatangebote ansehen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Geschäftskunden */}
|
||||
<div className="overflow-hidden rounded-card bg-white shadow-sm border border-neutral-200">
|
||||
<div className="aspect-video overflow-hidden">
|
||||
<img
|
||||
src="/images/gallery/firmenkunden.jpg"
|
||||
alt="Geschäftskunden von TeleNetSystems"
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={338}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-amber-100">
|
||||
<Building2 className="h-5 w-5 text-amber-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-neutral-900">Geschäftskunden</h3>
|
||||
</div>
|
||||
<ul className="mt-5 space-y-2 text-neutral-600">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
Individuelle Internet- und IT-Lösungen
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
TV für Hotels, Gastro und Büros
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
Schneller technischer Support vor Ort
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary-500" aria-hidden="true" />
|
||||
Beratung, die Ihre Abläufe versteht
|
||||
</li>
|
||||
</ul>
|
||||
<Button href="/#kontakt" variant="outline" size="sm" className="mt-6">
|
||||
Beratung anfragen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
68
src/components/sections/HeroSection.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
export default function HeroSection() {
|
||||
return (
|
||||
<section
|
||||
className="relative flex min-h-[90vh] items-center overflow-hidden bg-neutral-900"
|
||||
aria-label="Willkommen bei TeleNetSystems"
|
||||
>
|
||||
{/* Background Image */}
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src="/images/gallery/firma1.jpg"
|
||||
alt=""
|
||||
className="h-full w-full object-cover opacity-70"
|
||||
width={1920}
|
||||
height={1080}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-neutral-900/80 via-neutral-900/50 to-transparent" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-neutral-900/40 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative mx-auto max-w-6xl px-4 py-24 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className="max-w-2xl"
|
||||
>
|
||||
<p className="mb-4 inline-block rounded-full bg-primary-500/20 px-4 py-1.5 text-sm font-semibold uppercase tracking-widest !text-white">
|
||||
Aus Reutte. Für die Region.
|
||||
</p>
|
||||
<h1 className="text-4xl font-bold leading-tight tracking-tight !text-white sm:text-5xl lg:text-hero">
|
||||
Technik, die verbindet.{" "}
|
||||
<br className="hidden sm:block" />
|
||||
<span className="text-primary-400">Service, der da ist.</span>
|
||||
</h1>
|
||||
<p className="mt-6 max-w-xl text-lg leading-relaxed !text-white/80 sm:text-xl">
|
||||
Internet, Fernsehen und Telefonie direkt aus Reutte — mit persönlicher Betreuung, die
|
||||
Sie nicht bei einer Hotline suchen müssen.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-col gap-4 sm:flex-row">
|
||||
<Button href="/#kontakt" variant="primary" size="lg">
|
||||
Jetzt beraten lassen
|
||||
</Button>
|
||||
<Button href="/leistungen" variant="outline" size="lg" className="border-white/30 text-white hover:bg-white/10 hover:text-white">
|
||||
Unsere Leistungen
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<motion.div
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
animate={{ y: [0, 8, 0] }}
|
||||
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
|
||||
>
|
||||
<ChevronDown className="h-6 w-6 text-white/50" aria-hidden="true" />
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
45
src/components/sections/PageHero.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import Container from "@/components/ui/Container";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface PageHeroProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
backgroundImage?: string;
|
||||
}
|
||||
|
||||
export default function PageHero({ title, description, backgroundImage }: PageHeroProps) {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"relative flex items-end overflow-hidden py-20 sm:py-28 lg:py-32",
|
||||
backgroundImage ? "bg-neutral-900" : "bg-gradient-to-br from-neutral-900 to-primary-950"
|
||||
)}
|
||||
>
|
||||
{backgroundImage && (
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src={backgroundImage}
|
||||
alt=""
|
||||
className="h-full w-full object-cover opacity-70"
|
||||
width={1920}
|
||||
height={600}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-neutral-900/70 via-neutral-900/30 to-transparent" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-neutral-900/60 to-transparent" />
|
||||
</div>
|
||||
)}
|
||||
<Container className="relative">
|
||||
<div className="mb-4 h-1 w-16 rounded bg-primary-500" />
|
||||
<h1 className="text-3xl font-bold tracking-tight !text-white drop-shadow-lg sm:text-4xl lg:text-display">
|
||||
{title}
|
||||
</h1>
|
||||
{description && (
|
||||
<p className="mt-4 max-w-2xl text-lg !text-white/80 leading-relaxed drop-shadow-md">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
40
src/components/sections/ServiceOverview.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Wifi, Tv, Phone } from "lucide-react";
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import Card from "@/components/ui/Card";
|
||||
import { SERVICES } from "@/lib/constants";
|
||||
|
||||
const icons = [Wifi, Tv, Phone];
|
||||
|
||||
export default function ServiceOverview() {
|
||||
return (
|
||||
<section className="py-20 lg:py-24" aria-labelledby="services-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Was wir für Sie tun"
|
||||
subtitle="Drei Dienste, ein Ansprechpartner. Alles aus einer Hand, direkt vor Ort in Reutte."
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-3" id="services-heading">
|
||||
{SERVICES.map((service, index) => {
|
||||
const Icon = icons[index];
|
||||
return (
|
||||
<Card
|
||||
key={service.title}
|
||||
title={service.title}
|
||||
description={service.description}
|
||||
imageSrc={service.image}
|
||||
imageAlt={service.imageAlt}
|
||||
href={service.href}
|
||||
>
|
||||
<div className="mt-4 flex items-center gap-2 text-primary-600 font-medium text-sm">
|
||||
<Icon className="h-4 w-4" aria-hidden="true" />
|
||||
<span>Mehr erfahren</span>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
54
src/components/sections/TariffTable.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Check } from "lucide-react";
|
||||
import Badge from "@/components/ui/Badge";
|
||||
import Button from "@/components/ui/Button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { TariffPlan } from "@/types";
|
||||
|
||||
interface TariffTableProps {
|
||||
plans: TariffPlan[];
|
||||
contactHref?: string;
|
||||
}
|
||||
|
||||
export default function TariffTable({ plans, contactHref = "/#kontakt" }: TariffTableProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{plans.map((plan) => (
|
||||
<div
|
||||
key={plan.name}
|
||||
className={cn(
|
||||
"relative flex flex-col rounded-card border bg-white p-6 shadow-sm transition-shadow hover:shadow-md",
|
||||
plan.recommended
|
||||
? "border-primary-500 ring-1 ring-primary-500"
|
||||
: "border-neutral-200"
|
||||
)}
|
||||
>
|
||||
{plan.recommended && (
|
||||
<div className="absolute -top-3 left-6">
|
||||
<Badge variant="popular">Beliebt</Badge>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold text-neutral-900">{plan.name}</h3>
|
||||
<p className="mt-2 text-2xl font-bold text-neutral-900">{plan.price}</p>
|
||||
</div>
|
||||
<ul className="flex-1 space-y-3">
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-2 text-sm text-neutral-600">
|
||||
<Check className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary-600" aria-hidden="true" />
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button
|
||||
href={contactHref}
|
||||
variant={plan.recommended ? "primary" : "outline"}
|
||||
size="sm"
|
||||
className="mt-6 w-full"
|
||||
>
|
||||
Jetzt anfragen
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
38
src/components/sections/TeamGrid.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import Container from "@/components/ui/Container";
|
||||
import SectionHeading from "@/components/ui/SectionHeading";
|
||||
import { TEAM_MEMBERS } from "@/lib/constants";
|
||||
|
||||
export default function TeamGrid() {
|
||||
return (
|
||||
<section className="py-20 lg:py-24" aria-labelledby="team-heading">
|
||||
<Container>
|
||||
<SectionHeading
|
||||
title="Unser Team"
|
||||
subtitle="Die Menschen hinter TeleNetSystems. Persönlich erreichbar, fachlich versiert."
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-6 sm:grid-cols-3 lg:grid-cols-4" id="team-heading">
|
||||
{TEAM_MEMBERS.map((member) => (
|
||||
<div key={member.name} className="text-center">
|
||||
<div className="mx-auto aspect-square w-full max-w-[200px] overflow-hidden rounded-2xl bg-neutral-100">
|
||||
<img
|
||||
src={member.image}
|
||||
alt={`${member.name}${member.role ? `, ${member.role}` : ""}`}
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="mt-4 text-sm font-semibold text-neutral-900">
|
||||
{member.name}
|
||||
</h3>
|
||||
{member.role && (
|
||||
<p className="mt-1 text-xs text-neutral-500">{member.role}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
32
src/components/sections/TrustSection.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import Container from "@/components/ui/Container";
|
||||
|
||||
const stats = [
|
||||
{ value: "20+", label: "Jahre Erfahrung" },
|
||||
{ value: "1.000+", label: "Kunden in der Region" },
|
||||
{ value: "Reutte", label: "Unser Standort" },
|
||||
{ value: "24/7", label: "Netzüberwachung" },
|
||||
];
|
||||
|
||||
export default function TrustSection() {
|
||||
return (
|
||||
<section className="bg-primary-950 py-16 lg:py-20" aria-labelledby="trust-heading">
|
||||
<Container>
|
||||
<h2 id="trust-heading" className="sr-only">
|
||||
TeleNetSystems in Zahlen
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-8 lg:grid-cols-4">
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.label} className="text-center">
|
||||
<p className="text-3xl font-bold text-primary-400 sm:text-4xl">
|
||||
{stat.value}
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-neutral-300">
|
||||
{stat.label}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
55
src/components/sections/ValueProposition.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Shield, MapPin, Headphones, Award } from "lucide-react";
|
||||
import Container from "@/components/ui/Container";
|
||||
|
||||
const values = [
|
||||
{
|
||||
icon: Shield,
|
||||
title: "Zuverlässig",
|
||||
description: "Stabile Netze, die halten, was sie versprechen. Wir setzen auf Technik, der Sie vertrauen können.",
|
||||
},
|
||||
{
|
||||
icon: MapPin,
|
||||
title: "Regional",
|
||||
description: "Kein Callcenter, sondern echte Menschen in Reutte. Bei Fragen sind wir vor Ort.",
|
||||
},
|
||||
{
|
||||
icon: Headphones,
|
||||
title: "Persönlich",
|
||||
description: "Sie erreichen uns direkt — ohne Warteschleifen und ohne Weiterleitung.",
|
||||
},
|
||||
{
|
||||
icon: Award,
|
||||
title: "Kompetent",
|
||||
description: "Jahrelange Erfahrung im Außerfern. Wir kennen die Region und ihre Anforderungen.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function ValueProposition() {
|
||||
return (
|
||||
<section className="py-20 lg:py-24" aria-labelledby="values-heading">
|
||||
<Container>
|
||||
<h2 id="values-heading" className="sr-only">
|
||||
Unsere Werte
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{values.map((value) => {
|
||||
const Icon = value.icon;
|
||||
return (
|
||||
<div key={value.title} className="text-center">
|
||||
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-2xl bg-primary-100">
|
||||
<Icon className="h-7 w-7 text-primary-700" aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="mt-4 text-lg font-semibold text-neutral-900">
|
||||
{value.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-neutral-600">
|
||||
{value.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
26
src/components/ui/Badge.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
variant?: "default" | "popular" | "privat" | "business";
|
||||
}
|
||||
|
||||
const variants = {
|
||||
default: "bg-neutral-100 text-neutral-700",
|
||||
popular: "bg-primary-100 text-primary-800",
|
||||
privat: "bg-blue-100 text-blue-800",
|
||||
business: "bg-amber-100 text-amber-800",
|
||||
};
|
||||
|
||||
export default function Badge({ children, variant = "default" }: BadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-wide",
|
||||
variants[variant]
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
56
src/components/ui/Button.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode;
|
||||
variant?: "primary" | "secondary" | "outline" | "ghost";
|
||||
size?: "sm" | "md" | "lg";
|
||||
href?: string;
|
||||
className?: string;
|
||||
type?: "button" | "submit";
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const variants = {
|
||||
primary: "bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500",
|
||||
secondary: "bg-neutral-800 text-white hover:bg-neutral-900",
|
||||
outline: "border-2 border-primary-600 text-primary-700 hover:bg-primary-50",
|
||||
ghost: "text-neutral-700 hover:bg-neutral-100",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: "px-4 py-2 text-sm",
|
||||
md: "px-6 py-3 text-base",
|
||||
lg: "px-8 py-4 text-lg",
|
||||
};
|
||||
|
||||
export default function Button({
|
||||
children,
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
href,
|
||||
className,
|
||||
type = "button",
|
||||
onClick,
|
||||
}: ButtonProps) {
|
||||
const classes = cn(
|
||||
"inline-flex items-center justify-center gap-2 rounded-lg font-medium transition-colors duration-200",
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
className
|
||||
);
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link href={href} className={classes}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button type={type} className={classes} onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
59
src/components/ui/Card.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface CardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc?: string;
|
||||
imageAlt?: string;
|
||||
href?: string;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Card({
|
||||
title,
|
||||
description,
|
||||
imageSrc,
|
||||
imageAlt,
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
}: CardProps) {
|
||||
const content = (
|
||||
<>
|
||||
{imageSrc && (
|
||||
<div className="aspect-video overflow-hidden rounded-t-card">
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={imageAlt || title}
|
||||
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
width={600}
|
||||
height={338}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold text-neutral-900">{title}</h3>
|
||||
<p className="mt-2 text-neutral-600 leading-relaxed">{description}</p>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const cardClasses = cn(
|
||||
"group overflow-hidden rounded-card bg-white shadow-sm border border-neutral-200 transition-shadow duration-300 hover:shadow-md",
|
||||
className
|
||||
);
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link href={href} className={cn(cardClasses, "block")}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={cardClasses}>{content}</div>;
|
||||
}
|
||||
15
src/components/ui/Container.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ContainerProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
as?: React.ElementType;
|
||||
}
|
||||
|
||||
export default function Container({ children, className, as: Component = "div" }: ContainerProps) {
|
||||
return (
|
||||
<Component className={cn("mx-auto max-w-6xl px-4 sm:px-6 lg:px-8", className)}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
43
src/components/ui/SectionHeading.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SectionHeadingProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
align?: "left" | "center";
|
||||
as?: "h1" | "h2" | "h3";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function SectionHeading({
|
||||
title,
|
||||
subtitle,
|
||||
align = "center",
|
||||
as: Tag = "h2",
|
||||
className,
|
||||
}: SectionHeadingProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mb-12",
|
||||
align === "center" && "text-center",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Tag className="text-display font-bold tracking-tight text-neutral-900">
|
||||
{title}
|
||||
</Tag>
|
||||
{subtitle && (
|
||||
<p className="mt-4 max-w-2xl text-body-lg text-neutral-600 leading-relaxed mx-auto">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"mt-4 h-1 w-12 rounded-full bg-primary-500",
|
||||
align === "center" && "mx-auto"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
150
src/lib/constants.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import type { NavLink, TeamMember, TariffPlan, ContactInfo, ServiceCard } from "@/types";
|
||||
|
||||
export const COMPANY_NAME = "TeleNetSystems";
|
||||
export const COMPANY_TAGLINE = "Internet, TV & Telefonie in Reutte";
|
||||
|
||||
export const CONTACT: ContactInfo = {
|
||||
phone: "+43 5672 21400",
|
||||
email: "info@telenetsystems.at",
|
||||
address: [
|
||||
"TeleNetSystems",
|
||||
"Untermarkt 16",
|
||||
"6600 Reutte",
|
||||
"Tirol, Österreich",
|
||||
],
|
||||
};
|
||||
|
||||
export const NAV_LINKS: NavLink[] = [
|
||||
{ href: "/fernsehen", label: "Fernsehen" },
|
||||
{ href: "/internet", label: "Internet" },
|
||||
{ href: "/telefonie", label: "Telefonie" },
|
||||
{ href: "/leistungen", label: "Leistungen" },
|
||||
{ href: "/ueber-uns", label: "Über uns" },
|
||||
];
|
||||
|
||||
export const FOOTER_LINKS = {
|
||||
services: [
|
||||
{ href: "/fernsehen", label: "Fernsehen" },
|
||||
{ href: "/internet", label: "Internet" },
|
||||
{ href: "/telefonie", label: "Telefonie" },
|
||||
],
|
||||
company: [
|
||||
{ href: "/leistungen", label: "Leistungen" },
|
||||
{ href: "/ueber-uns", label: "Über uns" },
|
||||
],
|
||||
legal: [
|
||||
{ href: "/impressum", label: "Impressum" },
|
||||
{ href: "/datenschutz", label: "Datenschutz" },
|
||||
],
|
||||
};
|
||||
|
||||
export const TEAM_MEMBERS: TeamMember[] = [
|
||||
{ name: "Wolfgang Schwaiger", role: "Geschäftsführer", image: "/images/team/wolfgang-schwaiger.jpg" },
|
||||
{ name: "Martin Müller", role: "Technik", image: "/images/team/martin-mueller.jpg" },
|
||||
{ name: "David Müller", role: "Technik", image: "/images/team/david-mueller.jpg" },
|
||||
{ name: "Jürgen Gräßle", role: "Kundenbetreuung", image: "/images/team/juergen-graessle.jpg" },
|
||||
{ name: "Julia Besler", role: "Verwaltung", image: "/images/team/julia-besler.jpg" },
|
||||
{ name: "Furkan Demirel", role: "Technik", image: "/images/team/furkan-demirel.jpg" },
|
||||
{ name: "Lukas Schennach", role: "Technik", image: "/images/team/lukas-schennach.jpg" },
|
||||
{ name: "Mario Kien", role: "Technik", image: "/images/team/mario-kien.jpg" },
|
||||
{ name: "Franz", role: "Technik", image: "/images/team/franz.jpg" },
|
||||
{ name: "Lorena", role: "Verwaltung", image: "/images/team/lorena.jpg" },
|
||||
{ name: "Timo", role: "Technik", image: "/images/team/timo.jpg" },
|
||||
];
|
||||
|
||||
export const SERVICES: ServiceCard[] = [
|
||||
{
|
||||
title: "Internet",
|
||||
description: "Schnelles und stabiles Internet für Reutte und Umgebung. Kabel- und Glasfaser-Tarife, die zu Ihrem Bedarf passen.",
|
||||
href: "/internet",
|
||||
image: "/images/gallery/internet.jpg",
|
||||
imageAlt: "Glasfaserkabel für schnelles Internet in Tirol",
|
||||
},
|
||||
{
|
||||
title: "Fernsehen",
|
||||
description: "TV-Pakete für jeden Geschmack. Von der Grundversorgung bis zum individuellen Business-TV.",
|
||||
href: "/fernsehen",
|
||||
image: "/images/gallery/tv-privat.jpg",
|
||||
imageAlt: "Fernsehempfang über TeleNetSystems",
|
||||
},
|
||||
{
|
||||
title: "Telefonie",
|
||||
description: "Klar und günstig telefonieren. Einfache Tarife ohne versteckte Kosten.",
|
||||
href: "/telefonie",
|
||||
image: "/images/gallery/telefon.jpg",
|
||||
imageAlt: "Telefonie-Dienste von TeleNetSystems",
|
||||
},
|
||||
];
|
||||
|
||||
export const INTERNET_TARIFE: TariffPlan[] = [
|
||||
{
|
||||
name: "Internet 30",
|
||||
price: "ab € 29,90/Monat",
|
||||
category: "kabel",
|
||||
features: ["Download bis 30 Mbit/s", "Upload bis 5 Mbit/s", "Keine Drosselung", "Inkl. WLAN-Router"],
|
||||
},
|
||||
{
|
||||
name: "Internet 60",
|
||||
price: "ab € 39,90/Monat",
|
||||
category: "kabel",
|
||||
recommended: true,
|
||||
features: ["Download bis 60 Mbit/s", "Upload bis 10 Mbit/s", "Keine Drosselung", "Inkl. WLAN-Router"],
|
||||
},
|
||||
{
|
||||
name: "Internet 100",
|
||||
price: "ab € 49,90/Monat",
|
||||
category: "kabel",
|
||||
features: ["Download bis 100 Mbit/s", "Upload bis 20 Mbit/s", "Keine Drosselung", "Inkl. WLAN-Router"],
|
||||
},
|
||||
{
|
||||
name: "Glasfaser 200",
|
||||
price: "ab € 59,90/Monat",
|
||||
category: "glasfaser",
|
||||
features: ["Download bis 200 Mbit/s", "Upload bis 50 Mbit/s", "Keine Drosselung", "Inkl. WLAN-Router"],
|
||||
},
|
||||
{
|
||||
name: "Glasfaser 500",
|
||||
price: "ab € 79,90/Monat",
|
||||
category: "glasfaser",
|
||||
recommended: true,
|
||||
features: ["Download bis 500 Mbit/s", "Upload bis 100 Mbit/s", "Keine Drosselung", "Inkl. WLAN-Router"],
|
||||
},
|
||||
];
|
||||
|
||||
export const TV_PRIVAT_PAKETE: TariffPlan[] = [
|
||||
{
|
||||
name: "TV Basis",
|
||||
price: "ab € 9,90/Monat",
|
||||
features: ["Über 40 Sender", "ORF, ZDF, ARD und mehr", "HD-Qualität", "Digitaler Empfang"],
|
||||
},
|
||||
{
|
||||
name: "TV Komfort",
|
||||
price: "ab € 19,90/Monat",
|
||||
recommended: true,
|
||||
features: ["Über 80 Sender", "Alle Basis-Sender", "Zusätzliche Spartensender", "HD und Full-HD"],
|
||||
},
|
||||
{
|
||||
name: "TV Premium",
|
||||
price: "ab € 29,90/Monat",
|
||||
features: ["Über 120 Sender", "Alle Komfort-Sender", "Internationale Sender", "HD, Full-HD und 4K"],
|
||||
},
|
||||
];
|
||||
|
||||
export const TELEFONIE_TARIFE: TariffPlan[] = [
|
||||
{
|
||||
name: "Telefon Basis",
|
||||
price: "ab € 4,90/Monat",
|
||||
features: ["Festnetzanschluss", "Minutengenaue Abrechnung", "Österreichweit"],
|
||||
},
|
||||
{
|
||||
name: "Telefon Flat",
|
||||
price: "ab € 9,90/Monat",
|
||||
recommended: true,
|
||||
features: ["Festnetzanschluss", "Flatrate Österreich Festnetz", "Günstige Mobilfunktarife"],
|
||||
},
|
||||
{
|
||||
name: "Telefon Flat Plus",
|
||||
price: "ab € 14,90/Monat",
|
||||
features: ["Festnetzanschluss", "Flatrate Österreich & Deutschland", "Günstige Mobilfunktarife", "Inkl. Voicemail"],
|
||||
},
|
||||
];
|
||||
5
src/lib/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return clsx(inputs);
|
||||
}
|
||||
39
src/types/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export interface NavLink {
|
||||
href: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface TeamMember {
|
||||
name: string;
|
||||
role?: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export interface TariffPlan {
|
||||
name: string;
|
||||
price: string;
|
||||
features: string[];
|
||||
recommended?: boolean;
|
||||
category?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ServiceCard {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
image: string;
|
||||
imageAlt: string;
|
||||
}
|
||||
|
||||
export interface ValueItem {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface ContactInfo {
|
||||
phone: string;
|
||||
email: string;
|
||||
address: string[];
|
||||
}
|
||||
42
tsconfig.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||