Initial commit

This commit is contained in:
Basiclean committed 2026-05-02 10:41:00 +02:00
commit bcca176137
88 files changed
+8725

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+8
View File
@@ -0,0 +1,8 @@
export default function Datenschutz() {
return (
<main>
<h1>Datenschutz</h1>
</main>
);
}
+74
View File
@@ -0,0 +1,74 @@
.wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 80dvh;
padding: 0 var(--space-4);
overflow: hidden;
background-color: var(--color-background);
width: 100%;
}
.ghost {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: clamp(12rem, 40vw, 28rem);
font-weight: 900;
line-height: 1;
letter-spacing: -0.05em;
user-select: none;
pointer-events: none;
color: transparent;
-webkit-text-stroke: 1px rgba(23, 23, 23, 0.06);
}
.content {
position: relative;
z-index: 1;
text-align: center;
width: 100%;
}
.label {
font-size: var(--font-size-small);
font-weight: var(--font-weight-semibold);
letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase;
color: var(--color-text-soft);
margin-bottom: var(--space-4);
}
.heading {
margin-bottom: var(--space-4);
}
.description {
color: var(--color-text-muted);
margin-bottom: var(--space-7);
max-width: 38ch;
margin-inline: auto;
}
.cta {
gap: var(--btn-gap);
}
.actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--space-4);
}
@media (min-width: 768px) {
}
@media (min-width: 1024px) {
.wrapper {
padding: 0 var(--space-6);
}
}
+53
View File
@@ -0,0 +1,53 @@
"use client";
import { useEffect } from "react";
import Link from "next/link";
import styles from "./error.module.css";
import {
ArrowCounterClockwiseIcon,
ArrowLeftIcon,
} from "@phosphor-icons/react/dist/ssr";
interface ErrorProps {
error: Error & { digest?: string };
reset: () => void;
}
export default function Error({ error, reset }: ErrorProps) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<main className={styles.wrapper}>
<span className={styles.ghost} aria-hidden="true">
500
</span>
<div className={styles.content}>
<p className={styles.label}>Serverfehler</p>
<h1 className={styles.heading}>Etwas ist schiefgelaufen</h1>
<p className={styles.description}>
Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut oder
komm später zurück.
</p>
<div className={styles.actions}>
<button
type="button"
onClick={reset}
className={`btn btn--filled ${styles.cta}`}
>
<ArrowCounterClockwiseIcon className="btn-icon" size={18} />
Erneut versuchen
</button>
<Link href="/" className={`btn btn--outline ${styles.cta}`}>
<ArrowLeftIcon className="btn-icon" size={18} />
Zurück zur Startseite
</Link>
</div>
</div>
</main>
);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+58
View File
@@ -0,0 +1,58 @@
@import "../styles/tokens.css";
@import "../styles/buttons.css";
@import "../styles/forms.css";
@import "../styles/typography.css";
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
height: 100%;
font-size: 112.5%; /* 16px × 1.125 = 18px */
}
html.has-menu-open {
scrollbar-gutter: stable;
}
html,
body {
max-width: 100vw;
}
body {
min-height: 100%;
display: flex;
flex-direction: column;
color: var(--font-body-color);
background: var(--color-background);
font-family: var(--font-body);
font-size: 1rem;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: var(--line-height-body);
-webkit-tap-highlight-color: transparent;
}
main {
width: 100%;
max-width: 1280px;
margin-inline: auto;
padding: 0 var(--space-4);
}
@media (min-width: 768px) {
main {
padding: 0 var(--space-4);
}
}
@media (min-width: 1024px) {
main {
padding: 0 var(--space-6);
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 1024 1024"><metadata><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><rdf:Description><dc:creator>RealFaviconGenerator</dc:creator><dc:source>https://realfavicongenerator.net</dc:source></rdf:Description></rdf:RDF></metadata><image width="1024" height="1024" xlink:href="data:image/png;base64,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 truncated

After

Width:  |  Height:  |  Size: 30 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+8
View File
@@ -0,0 +1,8 @@
export default function Impressum() {
return (
<main>
<h1>impressum</h1>
</main>
);
}
+104
View File
@@ -0,0 +1,104 @@
import CookieConsentBanner from "@/components/CookieConsent";
import type { Metadata, Viewport } from "next";
import { Manrope, Nunito_Sans } from "next/font/google";
import "./globals.css";
import Footer from "@/components/Footer";
import Header from "@/components/Header";
const nunitoSans = Nunito_Sans({
subsets: ["latin"],
weight: ["400", "600", "700"],
variable: "--font-body",
display: "swap",
});
const manrope = Manrope({
subsets: ["latin"],
weight: ["400", "500", "600", "700"],
variable: "--font-heading",
display: "swap",
});
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
};
const siteName = "ENVU";
const title = "ENVU - Code Connect";
const description = "Wir bieten Ihnen maßgeschneiderte Lösungen.";
const siteUrl = "https://envu.de";
const ogImage = {
url: "/images/envu.png",
alt: "Envu Wallpaper",
width: 1200,
height: 628,
};
export const metadata: Metadata = {
metadataBase: new URL(siteUrl),
title: {
default: title,
template: `%s | ${siteName}`,
},
description,
keywords: "Envu, Hannover",
authors: [{ name: siteName }],
robots: "index, follow",
alternates: {
canonical: siteUrl,
},
appleWebApp: {
title: siteName,
},
openGraph: {
type: "website",
siteName,
locale: "de_DE",
title,
description,
images: [ogImage],
url: siteUrl,
},
twitter: {
card: "summary_large_image",
site: "@envu",
title,
description,
images: [{ url: ogImage.url, alt: ogImage.alt }],
},
};
const jsonLd = {
"@context": "https://schema.org",
"@type": "LocalBusiness",
name: siteName,
url: siteUrl,
description,
address: {
"@type": "PostalAddress",
addressLocality: "Hannover",
addressCountry: "DE",
},
};
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="de" className={`${nunitoSans.variable} ${manrope.variable}`}>
<body>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<Header />
{children}
<Footer />
<CookieConsentBanner />
</body>
</html>
);
}
+7
View File
@@ -0,0 +1,7 @@
export default function Loading() {
return (
<main>
<p>Laden...</p>
</main>
);
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "ENVU",
"short_name": "ENVU",
"icons": [
{
"src": "/favicon/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/favicon/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
+67
View File
@@ -0,0 +1,67 @@
.wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 80dvh;
padding: 0 var(--space-4);
overflow: hidden;
background-color: var(--color-background);
width: 100%;
}
.ghost {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: clamp(12rem, 40vw, 28rem);
font-weight: 900;
line-height: 1;
letter-spacing: -0.05em;
user-select: none;
pointer-events: none;
color: transparent;
-webkit-text-stroke: 1px rgba(23, 23, 23, 0.06);
}
.content {
position: relative;
z-index: 1;
text-align: center;
width: 100%;
}
.label {
font-size: var(--font-size-small);
font-weight: var(--font-weight-semibold);
letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase;
color: var(--color-text-soft);
margin-bottom: var(--space-4);
}
.heading {
margin-bottom: var(--space-4);
}
.description {
color: var(--color-text-muted);
margin-bottom: var(--space-7);
max-width: 38ch;
margin-inline: auto;
}
.cta {
gap: var(--btn-gap);
}
@media (min-width: 768px) {
}
@media (min-width: 1024px) {
.wrapper {
padding: 0 var(--space-6);
}
}
+32
View File
@@ -0,0 +1,32 @@
import type { Metadata } from "next";
import Link from "next/link";
import styles from "./not-found.module.css";
import { ArrowLeftIcon } from "@phosphor-icons/react/dist/ssr";
export const metadata: Metadata = {
title: "Seite nicht gefunden",
description: "Die gesuchte Seite existiert nicht.",
};
export default function NotFound() {
return (
<main className={styles.wrapper}>
<span className={styles.ghost} aria-hidden="true">
404
</span>
<div className={styles.content}>
<p className={styles.label}>Fehler 404</p>
<h1 className={styles.heading}>Seite nicht gefunden</h1>
<p className={styles.description}>
Die gesuchte Seite existiert leider nicht oder wurde verschoben.
</p>
<Link href="/" className={`btn btn--filled ${styles.cta}`}>
<ArrowLeftIcon className="btn-icon" size={18} />
Zurück zur Startseite
</Link>
</div>
</main>
);
}
View File
Whitespace-only changes.
+192
View File
@@ -0,0 +1,192 @@
import { ArrowRightIcon } from "@phosphor-icons/react/dist/ssr";
export default function Home() {
return (
<main>
<h1>Startseite</h1>
<h2>Startseite</h2>
<h3>Startseite</h3>
<h4>Startseite</h4>
<p>Startseite</p>
<br />
<button className="btn btn--filled">Filled</button>
<br />
<br />
<button className="btn btn--outline">Outline</button>
<br />
<br />
<button className="btn btn--ghost">Ghost</button>
<br />
<br />
<button className="btn btn--tertiary">Tertiary</button>
<br />
<br />
<button className="btn btn--filled" disabled>
Disabled
</button>
<br />
<br />
<button className="btn btn--filled">
<ArrowRightIcon className="btn-icon animate" size={18} />
Jetzt starten
</button>
<br />
<br />
<button className="btn btn--outline">
<ArrowRightIcon className="btn-icon animate" size={18} />
Outline
</button>
<br />
<br />
<button className="btn btn--ghost">
<ArrowRightIcon className="btn-icon animate" size={18} />
Ghost
</button>
<br />
<br />
<button className="btn btn--tertiary">
<ArrowRightIcon className="btn-icon animate" size={18} />
Tertiary
</button>
<br />
<br />
<button className="btn btn--filled" disabled>
<ArrowRightIcon className="btn-icon animate" size={18} />
Disabled
</button>
<br />
<br />
<p>
<a href="#">Webdesign</a> und Webentwicklung bilden das Fundament jeder
erfolgreichen digitalen Präsenz. Während Webdesign die visuelle
Gestaltung, Benutzerführung und das Nutzererlebnis in den Vordergrund
stellt, sorgt die Webentwicklung für die technische Umsetzung im
Hintergrund. Beide Disziplinen greifen eng ineinander – ein
ansprechendes Design entfaltet seine volle Wirkung nur durch sauberen,
performanten Code.
</p>
<p>
Moderne Websites müssen auf allen Geräten funktionieren, schnell laden
und barrierefrei zugänglich sein. Suchmaschinenoptimierung, Ladezeiten
und intuitive Navigation sind dabei keine optionalen Extras, sondern
entscheidende Faktoren für den Erfolg. Wer heute im Netz wahrgenommen
werden möchte, braucht mehr als nur eine schöne Oberfläche – er braucht
eine durchdachte digitale Strategie, die Design und Technik vereint.
</p>
<br />
<h2>Kontakt</h2>
<form>
{/* Name */}
<div className="form-group">
<label className="form-label form-label--required" htmlFor="name">
Name
</label>
<input
type="text"
id="name"
name="name"
placeholder="Max Mustermann"
/>
</div>
{/* E-Mail */}
<div className="form-group">
<label className="form-label form-label--required" htmlFor="email">
E-Mail
</label>
<input
type="email"
id="email"
name="email"
placeholder="max@beispiel.de"
/>
</div>
{/* Telefon */}
<div className="form-group">
<label className="form-label" htmlFor="telefon">
Telefon
</label>
<input
type="tel"
id="telefon"
name="telefon"
placeholder="+49 511 123456"
/>
</div>
{/* Betreff */}
<div className="form-group">
<label className="form-label" htmlFor="betreff">
Betreff
</label>
<select id="betreff" name="betreff">
<option value="">Bitte auswählen</option>
<option value="anfrage">Allgemeine Anfrage</option>
<option value="angebot">Angebotsanfrage</option>
<option value="support">Support</option>
</select>
</div>
{/* Nachricht */}
<div className="form-group">
<label
className="form-label form-label--required"
htmlFor="nachricht"
>
Nachricht
</label>
<textarea
id="nachricht"
name="nachricht"
placeholder="Ihre Nachricht..."
/>
</div>
{/* Datenschutz Checkbox */}
<div className="form-group">
<label className="form-check">
<input type="checkbox" />
<span className="form-check-label">
Ich habe die Datenschutzerklärung gelesen und stimme zu.
</span>
</label>
</div>
{/* Error-Beispiel zum Testen */}
<div className="form-group">
<label className="form-label" htmlFor="fehler">
Fehler-Beispiel
</label>
<input
type="text"
id="fehler"
className="is-error"
defaultValue="Falscher Wert"
/>
<span className="form-error-msg">
Bitte gib einen gültigen Wert ein.
</span>
</div>
{/* Disabled-Beispiel */}
<div className="form-group">
<label className="form-label" htmlFor="deaktiviert">
Deaktiviert
</label>
<input
type="text"
id="deaktiviert"
disabled
defaultValue="Nicht bearbeitbar"
/>
</div>
{/* Submit */}
<button type="submit" className="btn btn--filled">
Nachricht senden
</button>
</form>
</main>
);
}
+21
View File
@@ -0,0 +1,21 @@
import type { MetadataRoute } from "next";
const BASE_URL = "https://deinedomain.de"; // ← hier deine Domain eintragen
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: "*", // gilt für alle Crawler (Google, Bing etc.)
allow: "/", // alles erlaubt
disallow: [
"/api/", // API-Routen nicht indexieren
"/admin/", // falls vorhanden: Admin-Bereich sperren
"/datenschutz",
"/impressum",
],
},
],
sitemap: `${BASE_URL}/sitemap.xml`, // ← Sitemap verlinken
};
}
+70
View File
@@ -0,0 +1,70 @@
// app/sitemap.ts
// Erreichbar unter: https://deinedomain.de/sitemap.xml
import type { MetadataRoute } from "next";
const BASE_URL = "https://deinedomain.de"; // ← Domain anpassen
// ─── Typen ───────────────────────────────────────────────────────────────────
// interface BlogPost {
// slug: string;
// updatedAt: Date;
// }
// ─── Daten laden (nur aktivieren wenn Blog vorhanden) ────────────────────────
// async function getBlogPosts(): Promise<BlogPost[]> {
// Hier API/CMS-Call, z.B.:
// const res = await fetch(`${BASE_URL}/api/posts`);
// return res.json();
// }
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// ─── Statische Seiten ──────────────────────────────────────────────────────
const staticPages: MetadataRoute.Sitemap = [
{
url: BASE_URL,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1.0,
},
{
url: `${BASE_URL}/about`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${BASE_URL}/kontakt`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.6,
},
{
url: `${BASE_URL}/datenschutz`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.3,
},
{
url: `${BASE_URL}/impressum`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.3,
},
];
// ─── Dynamische Blog-Seiten (auskommentiert bis Blog vorhanden) ────────────
// const posts = await getBlogPosts();
// const blogPages: MetadataRoute.Sitemap = posts.map((post) => ({
// url: `${BASE_URL}/blog/${post.slug}`,
// lastModified: post.updatedAt,
// changeFrequency: "weekly",
// priority: 0.7,
// }));
// ─── Alles zusammenführen ──────────────────────────────────────────────────
return [
...staticPages,
// ...blogPages, ← aktivieren wenn Blog live ist
];
}
+5
View File
@@ -0,0 +1,5 @@
"use client";
export default function TestErrorPage() {
throw new Error("Testfehler – Error-Seite prüfen");
}
View File
Whitespace-only changes.
+8
View File
@@ -0,0 +1,8 @@
export default function UeberUns() {
return (
<main>
<h1>Über Uns</h1>
</main>
);
}