Relaunch Phase 1: Gestaltungs-Grundlage, Kopfleiste, Footer, neue Startseiten-Struktur
Token für Hell und Dunkel, eine Schriftfamilie (Instrument Sans selbst gehostet, ?schrift=a für die Systemschrift), gemeinsames Layout für alle Seiten, Abschnitte Über uns und Kontakt, Platzhalter für Hero, Kundenportal, Leistungen und Preise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
11fd118aa7
commit
822481f2e4
19 files changed
+1150
-51
No files matched your search
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2022 The Instrument Sans Project Authors (https://github.com/Instrument/instrument-sans) InstrumentSans-Italic[wdth,wght].ttf: Copyright 2022 The Instrument Sans Project Authors (https://github.com/Instrument/instrument-sans)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
|||||||
|
/** Platzhalter: wird im Relaunch-Workflow gebaut (Spezifikation Abschnitt 7). */
|
||||||
|
const HeroKarte = () => (
|
||||||
|
<section className="wk-section">
|
||||||
|
<div className="wk-container">
|
||||||
|
<h2 className="wk-h2">HeroKarte</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default HeroKarte;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
/** Spezifikation 7.7: ein Satz, ein Button, E-Mail und Telefon. */
|
||||||
|
const KontaktBuehne = () => (
|
||||||
|
<section id="kontakt" className="wk-section wk-section--alt wk-kontakt" aria-labelledby="kontakt-titel">
|
||||||
|
<div className="wk-container wk-kontakt__inhalt">
|
||||||
|
<h2 id="kontakt-titel" className="wk-h2 wk-reveal">
|
||||||
|
Erzählen Sie uns, was Sie vorhaben.
|
||||||
|
</h2>
|
||||||
|
<p className="wk-lead wk-reveal">
|
||||||
|
Zwei Sätze genügen. Wir melden uns spätestens am nächsten Werktag. Im ersten Gespräch klären wir in
|
||||||
|
15 Minuten, ob und wie wir helfen können – kostenlos und unverbindlich.
|
||||||
|
</p>
|
||||||
|
<div className="wk-kontakt__aktion wk-reveal">
|
||||||
|
<Link to="/kontakt" className="wk-btn wk-btn--primary">
|
||||||
|
Projekt besprechen
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<p className="wk-kontakt__wege">
|
||||||
|
<a href="mailto:support@webklar.com" className="wk-link">
|
||||||
|
support@webklar.com
|
||||||
|
</a>
|
||||||
|
<span aria-hidden>·</span>
|
||||||
|
<a href="tel:+4917623726355" className="wk-link wk-num">
|
||||||
|
0176 23726355
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default KontaktBuehne;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** Platzhalter: wird im Relaunch-Workflow gebaut (Spezifikation Abschnitt 7). */
|
||||||
|
const Kundenportal = () => (
|
||||||
|
<section id="kundenportal" className="wk-section">
|
||||||
|
<div className="wk-container">
|
||||||
|
<h2 className="wk-h2">Kundenportal</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Kundenportal;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** Platzhalter: wird im Relaunch-Workflow gebaut (Spezifikation Abschnitt 7). */
|
||||||
|
const Leistungen = () => (
|
||||||
|
<section id="leistungen" className="wk-section">
|
||||||
|
<div className="wk-container">
|
||||||
|
<h2 className="wk-h2">Leistungen</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Leistungen;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** Platzhalter: wird im Relaunch-Workflow gebaut (Spezifikation Abschnitt 7). */
|
||||||
|
const Preise = () => (
|
||||||
|
<section id="preise" className="wk-section">
|
||||||
|
<div className="wk-container">
|
||||||
|
<h2 className="wk-h2">Preise</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Preise;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
/** Spezifikation 7.6: wer dahinter steckt. Keine Fotos, bis echte Teamfotos vorliegen. */
|
||||||
|
const UeberUns = () => (
|
||||||
|
<section id="ueber-uns" className="wk-section" aria-labelledby="ueber-uns-titel">
|
||||||
|
<div className="wk-container wk-ueber-uns">
|
||||||
|
<h2 id="ueber-uns-titel" className="wk-h2 wk-reveal">
|
||||||
|
Wer hinter WEBklar steckt.
|
||||||
|
</h2>
|
||||||
|
<div className="wk-reveal">
|
||||||
|
<p className="wk-lead">
|
||||||
|
Wir sind ein kleines Team aus Winnweiler in der Pfalz. Sie sprechen bei uns direkt mit den Leuten, die Ihre
|
||||||
|
Website bauen.
|
||||||
|
</p>
|
||||||
|
<p className="wk-ueber-uns__link">
|
||||||
|
<Link to="/ueber-uns" className="wk-link">
|
||||||
|
Mehr über uns
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default UeberUns;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/* Kleine Abschnitte der Startseite: Über uns und Kontakt */
|
||||||
|
|
||||||
|
.wk-ueber-uns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
|
||||||
|
gap: 2rem 4rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
.wk-ueber-uns__link {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 899.98px) {
|
||||||
|
.wk-ueber-uns {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-kontakt__inhalt {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.wk-kontakt .wk-h2 {
|
||||||
|
max-width: 16ch;
|
||||||
|
}
|
||||||
|
.wk-kontakt .wk-lead {
|
||||||
|
max-width: 38rem;
|
||||||
|
}
|
||||||
|
.wk-kontakt__aktion {
|
||||||
|
margin-top: 2.25rem;
|
||||||
|
}
|
||||||
|
.wk-kontakt__wege {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem 0.9rem;
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
}
|
||||||
|
.wk-kontakt__wege a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import Logo from "@/components/Logo";
|
||||||
|
import "./site.css";
|
||||||
|
|
||||||
|
type FooterLink = { label: string; to?: string; href?: string; pflicht?: boolean };
|
||||||
|
|
||||||
|
/** Spezifikation 7.8: Seiten und Rechtliches; Kündigen und Widerrufen bleiben hervorgehoben. */
|
||||||
|
const SEITEN: FooterLink[] = [
|
||||||
|
{ label: "Startseite", to: "/" },
|
||||||
|
{ label: "Kundenportal", to: "/#kundenportal" },
|
||||||
|
{ label: "Was wir bauen", to: "/#leistungen" },
|
||||||
|
{ label: "Preise", to: "/#preise" },
|
||||||
|
{ label: "Über uns", to: "/ueber-uns" },
|
||||||
|
{ label: "Kontakt", to: "/kontakt" },
|
||||||
|
{ label: "Wartungs-Abo", to: "/wartungs-abo" },
|
||||||
|
{ label: "Hosting-Abo", to: "/hosting-abo" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const RECHTLICHES: FooterLink[] = [
|
||||||
|
{ label: "AGB", to: "/agb" },
|
||||||
|
{ label: "Impressum", to: "/impressum" },
|
||||||
|
{ label: "Datenschutz", href: "https://project.webklar.com/datenschutz.html" },
|
||||||
|
{ label: "Verträge hier kündigen", href: "https://project.webklar.com/kuendigen.html", pflicht: true },
|
||||||
|
{ label: "Vertrag widerrufen", href: "https://project.webklar.com/widerrufen.html", pflicht: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const Eintrag = ({ link }: { link: FooterLink }) => (
|
||||||
|
<li>
|
||||||
|
{link.to ? (
|
||||||
|
<Link to={link.to}>{link.label}</Link>
|
||||||
|
) : (
|
||||||
|
<a href={link.href} className={link.pflicht ? "wk-footer__pflicht" : undefined}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SiteFooter = () => (
|
||||||
|
<footer className="wk-footer">
|
||||||
|
<div className="wk-container">
|
||||||
|
<div className="wk-footer__raster">
|
||||||
|
<div className="wk-footer__info">
|
||||||
|
<Link to="/" className="wk-footer__marke">
|
||||||
|
<Logo width={24} height={24} />
|
||||||
|
<span>WEBklar</span>
|
||||||
|
</Link>
|
||||||
|
<p>
|
||||||
|
WEBklar GbR
|
||||||
|
<br />
|
||||||
|
Am Schwimmbad 10
|
||||||
|
<br />
|
||||||
|
67722 Winnweiler
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Seiten">
|
||||||
|
<h2>Seiten</h2>
|
||||||
|
<ul>
|
||||||
|
{SEITEN.map((link) => (
|
||||||
|
<Eintrag key={link.label} link={link} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<nav aria-label="Rechtliches">
|
||||||
|
<h2>Rechtliches</h2>
|
||||||
|
<ul>
|
||||||
|
{RECHTLICHES.map((link) => (
|
||||||
|
<Eintrag key={link.label} link={link} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<p className="wk-footer__unten">© {new Date().getFullYear()} WEBklar GbR</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SiteFooter;
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { useEffect, useId, useRef, useState } from "react";
|
||||||
|
import { Link, useLocation } from "react-router-dom";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import Logo from "@/components/Logo";
|
||||||
|
import { projekte } from "@/data/projekte";
|
||||||
|
import "./site.css";
|
||||||
|
|
||||||
|
/** Navigation der Kopfleiste (Spezifikation 6.1). „Projekte“ erscheint erst mit freigegebenen Referenzen. */
|
||||||
|
export const NAVIGATION = [
|
||||||
|
{ label: "Kundenportal", to: "/#kundenportal" },
|
||||||
|
{ label: "Was wir bauen", to: "/#leistungen" },
|
||||||
|
...(projekte.length > 0 ? [{ label: "Projekte", to: "/#projekte" }] : []),
|
||||||
|
{ label: "Preise", to: "/#preise" },
|
||||||
|
{ label: "Über uns", to: "/#ueber-uns" },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Hell/Dunkel umschalten. Vor dem ersten Rendern ist das Thema unbekannt, daher kein Symbolwechsel beim Laden. */
|
||||||
|
const ThemaSchalter = ({ mitText = false }: { mitText?: boolean }) => {
|
||||||
|
const { resolvedTheme, setTheme } = useTheme();
|
||||||
|
const [bereit, setBereit] = useState(false);
|
||||||
|
useEffect(() => setBereit(true), []);
|
||||||
|
const dunkel = bereit && resolvedTheme === "dark";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={mitText ? "wk-menu-theme" : "wk-icon-btn"}
|
||||||
|
onClick={() => setTheme(dunkel ? "light" : "dark")}
|
||||||
|
aria-label={dunkel ? "Hellen Modus einschalten" : "Dunklen Modus einschalten"}
|
||||||
|
>
|
||||||
|
{dunkel ? <Sun size={18} aria-hidden /> : <Moon size={18} aria-hidden />}
|
||||||
|
{mitText && <span>{dunkel ? "Heller Modus" : "Dunkler Modus"}</span>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Ein zweiter Klick auf denselben Anker ändert die URL nicht; dann selbst hinscrollen. */
|
||||||
|
const zumAnker = (to: string) => {
|
||||||
|
const [pfad, id] = to.split("#");
|
||||||
|
if (!id || window.location.pathname !== (pfad || "/") || window.location.hash !== `#${id}`) return;
|
||||||
|
document.getElementById(id)?.scrollIntoView();
|
||||||
|
};
|
||||||
|
|
||||||
|
const SiteHeader = () => {
|
||||||
|
const [offen, setOffen] = useState(false);
|
||||||
|
const [gescrollt, setGescrollt] = useState(false);
|
||||||
|
const { pathname, hash } = useLocation();
|
||||||
|
const panelId = useId();
|
||||||
|
const knopfRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
// Trennlinie erst nach dem ersten Scrollen
|
||||||
|
useEffect(() => {
|
||||||
|
const pruefe = () => setGescrollt(window.scrollY > 4);
|
||||||
|
pruefe();
|
||||||
|
window.addEventListener("scroll", pruefe, { passive: true });
|
||||||
|
return () => window.removeEventListener("scroll", pruefe);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Menü schließt bei jedem Seiten- oder Ankerwechsel
|
||||||
|
useEffect(() => setOffen(false), [pathname, hash]);
|
||||||
|
|
||||||
|
// Offenes Menü: Seite sperren, Escape schließt und gibt den Fokus an den Knopf zurück
|
||||||
|
useEffect(() => {
|
||||||
|
if (!offen) return;
|
||||||
|
const vorher = document.body.style.overflow;
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
const taste = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
setOffen(false);
|
||||||
|
knopfRef.current?.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("keydown", taste);
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = vorher;
|
||||||
|
window.removeEventListener("keydown", taste);
|
||||||
|
};
|
||||||
|
}, [offen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className={`wk-header${gescrollt || offen ? " wk-header--linie" : ""}`}>
|
||||||
|
<div className="wk-container wk-header__zeile">
|
||||||
|
<Link to="/" className="wk-header__marke" aria-label="WEBklar – zur Startseite">
|
||||||
|
<Logo width={28} height={28} />
|
||||||
|
<span>WEBklar</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<nav className="wk-header__nav" aria-label="Hauptnavigation">
|
||||||
|
<ul>
|
||||||
|
{NAVIGATION.map((eintrag) => (
|
||||||
|
<li key={eintrag.to}>
|
||||||
|
<Link to={eintrag.to} onClick={() => zumAnker(eintrag.to)}>
|
||||||
|
{eintrag.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="wk-header__aktionen">
|
||||||
|
<ThemaSchalter />
|
||||||
|
<Link to="/kontakt" className="wk-btn wk-btn--primary wk-btn--small">
|
||||||
|
Projekt besprechen
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
ref={knopfRef}
|
||||||
|
type="button"
|
||||||
|
className="wk-header__menuknopf"
|
||||||
|
aria-expanded={offen}
|
||||||
|
aria-controls={panelId}
|
||||||
|
aria-label={offen ? "Menü schließen" : "Menü öffnen"}
|
||||||
|
onClick={() => setOffen((o) => !o)}
|
||||||
|
>
|
||||||
|
<span className="wk-burger" data-offen={offen} aria-hidden>
|
||||||
|
<span />
|
||||||
|
<span />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Handy-Menü: Abdunkelung schließt per Tipp daneben */}
|
||||||
|
<div className="wk-menu-hintergrund" data-offen={offen} onClick={() => setOffen(false)} aria-hidden />
|
||||||
|
<div id={panelId} className="wk-menu" data-offen={offen} hidden={!offen}>
|
||||||
|
<nav aria-label="Menü">
|
||||||
|
<ul>
|
||||||
|
{NAVIGATION.map((eintrag) => (
|
||||||
|
<li key={eintrag.to}>
|
||||||
|
<Link
|
||||||
|
to={eintrag.to}
|
||||||
|
onClick={() => {
|
||||||
|
setOffen(false);
|
||||||
|
zumAnker(eintrag.to);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{eintrag.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div className="wk-menu__fuss">
|
||||||
|
<Link to="/kontakt" className="wk-btn wk-btn--primary" onClick={() => setOffen(false)}>
|
||||||
|
Projekt besprechen
|
||||||
|
</Link>
|
||||||
|
<ThemaSchalter mitText />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiteHeader;
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
/* Kopfleiste, Handy-Menü und Footer (Spezifikation 6.1 und 7.8) */
|
||||||
|
|
||||||
|
.wk-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 50;
|
||||||
|
background: color-mix(in srgb, var(--bg) 94%, transparent);
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
transition: border-color var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
.wk-header--linie {
|
||||||
|
border-bottom-color: var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-header__zeile {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
height: var(--header-h);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-header__marke {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
letter-spacing: 0;
|
||||||
|
color: var(--fg);
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
.wk-header__marke svg {
|
||||||
|
border-radius: 7px;
|
||||||
|
box-shadow: inset 0 0 0 1px var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-header__nav {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.wk-header__nav ul {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
.wk-header__nav a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 0 0.85rem;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
transition: color var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.wk-header__nav a:hover {
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-header__aktionen {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-icon-btn {
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
transition: color var(--dur-state) var(--ease), background-color var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.wk-icon-btn:hover {
|
||||||
|
color: var(--fg);
|
||||||
|
background: var(--bg-alt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-header__menuknopf {
|
||||||
|
display: none;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: -10px;
|
||||||
|
place-items: center;
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-burger {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 20px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
.wk-burger span {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 1.5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: currentColor;
|
||||||
|
transition: transform var(--dur-state) var(--ease), top var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
.wk-burger span:first-child {
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
.wk-burger span:last-child {
|
||||||
|
top: 9px;
|
||||||
|
}
|
||||||
|
.wk-burger[data-offen="true"] span:first-child {
|
||||||
|
top: 5.5px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.wk-burger[data-offen="true"] span:last-child {
|
||||||
|
top: 5.5px;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-menu,
|
||||||
|
.wk-menu-hintergrund {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 899.98px) {
|
||||||
|
.wk-header__nav,
|
||||||
|
.wk-header__aktionen {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.wk-header__menuknopf {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-menu-hintergrund {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
inset: var(--header-h) 0 0 0;
|
||||||
|
background: rgb(0 0 0 / 0.32);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
.wk-menu-hintergrund[data-offen="true"] {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-menu {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: var(--header-h);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
max-height: calc(100dvh - var(--header-h));
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding: 0.5rem var(--gutter) calc(1.5rem + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
.wk-menu[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.wk-menu nav a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 56px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--fg);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
.wk-menu__fuss {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.wk-menu__fuss .wk-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.wk-menu-theme {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-height: 48px;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--fg);
|
||||||
|
box-shadow: inset 0 0 0 1px var(--line);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.wk-menu[data-offen="true"] {
|
||||||
|
animation: wk-menu-auf var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wk-menu-auf {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.wk-footer {
|
||||||
|
background: var(--bg-alt);
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
color: var(--fg-muted);
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
line-height: 1.5;
|
||||||
|
padding-block: 56px calc(40px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
.wk-footer__raster {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.4fr 1fr 1fr;
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
.wk-footer__marke {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--fg);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.wk-footer__marke svg {
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: inset 0 0 0 1px var(--line);
|
||||||
|
}
|
||||||
|
.wk-footer h2 {
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0;
|
||||||
|
color: var(--fg);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.wk-footer li a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 32px;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
transition: color var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
.wk-footer li a.wk-footer__pflicht {
|
||||||
|
color: var(--fg);
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
}
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.wk-footer li a:hover {
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wk-footer__unten {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 1.25rem;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 719.98px) {
|
||||||
|
.wk-footer__raster {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
.wk-footer__info {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
.wk-footer li a {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Referenzen für den Abschnitt „Projekte“ (Spezifikation 7.4).
|
||||||
|
* Nur Projekte, die online sind und deren Kunden zugestimmt haben (AGB § 13).
|
||||||
|
* Solange die Liste leer ist, gibt es weder Abschnitt noch Menüpunkt.
|
||||||
|
*/
|
||||||
|
export type Projekt = {
|
||||||
|
name: string;
|
||||||
|
beschreibung: string;
|
||||||
|
url: string;
|
||||||
|
/** eigener Screenshot unter public/, kein Stockfoto */
|
||||||
|
bild: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const projekte: Projekt[] = [];
|
||||||
@@ -2,5 +2,18 @@ import { createRoot } from "react-dom/client";
|
|||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import "./fonts.css";
|
import "./fonts.css";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import "./styles/relaunch.css";
|
||||||
|
|
||||||
|
// Entwurf: Schriftvariante vergleichen, ?schrift=a (Systemschrift) oder ?schrift=b (Instrument Sans, Standard).
|
||||||
|
// Die Wahl gilt für den ganzen Besuch. Phase 2 entfernt den Umschalter.
|
||||||
|
try {
|
||||||
|
const wahl = new URLSearchParams(window.location.search).get("schrift") ?? sessionStorage.getItem("schrift");
|
||||||
|
if (wahl === "a" || wahl === "b") {
|
||||||
|
sessionStorage.setItem("schrift", wahl);
|
||||||
|
document.documentElement.dataset.schrift = wahl;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ohne sessionStorage bleibt die Standardschrift
|
||||||
|
}
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(<App />);
|
createRoot(document.getElementById("root")!).render(<App />);
|
||||||
+18
-24
@@ -1,34 +1,28 @@
|
|||||||
import Header from "@/components/Header";
|
import HeroKarte from "@/components/home/HeroKarte";
|
||||||
import Hero from "@/components/Hero";
|
import Kundenportal from "@/components/home/Kundenportal";
|
||||||
import Partners from "@/components/Partners";
|
import Leistungen from "@/components/home/Leistungen";
|
||||||
import ProblemSection from "@/components/ProblemSection";
|
import Preise from "@/components/home/Preise";
|
||||||
import SolutionSection from "@/components/SolutionSection";
|
import UeberUns from "@/components/home/UeberUns";
|
||||||
import Values from "@/components/Values";
|
import KontaktBuehne from "@/components/home/KontaktBuehne";
|
||||||
import Services from "@/components/Services";
|
import "@/components/home/home.css";
|
||||||
import Process from "@/components/Process";
|
|
||||||
import Contact from "@/components/Contact";
|
|
||||||
import Footer from "@/components/Footer";
|
|
||||||
import { usePageMeta } from "@/hooks/use-page-meta";
|
import { usePageMeta } from "@/hooks/use-page-meta";
|
||||||
|
|
||||||
|
/** Startseite des Relaunchs (Spezifikation 6.2). Kopfleiste und Footer kommen aus dem gemeinsamen Layout. */
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
usePageMeta(
|
usePageMeta(
|
||||||
"WEBklar – Webagentur für KMU",
|
"WEBklar – Websites, Shops und Apps für kleine Betriebe",
|
||||||
"WEBklar digitalisiert KMU: Website, Prozesse und Systeme aus einer Hand. Kostenlose Potenzialanalyse – Antwort spätestens am nächsten Werktag."
|
"WEBklar baut Websites, Online-Shops und Web-Apps. Im Kundenportal sehen Sie jeden Schritt, mit Live-Vorschau Ihrer Website.",
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<>
|
||||||
<Header />
|
<HeroKarte />
|
||||||
<Hero />
|
<Kundenportal />
|
||||||
<Partners />
|
<Leistungen />
|
||||||
<ProblemSection />
|
<Preise />
|
||||||
<SolutionSection />
|
<UeberUns />
|
||||||
<Values />
|
<KontaktBuehne />
|
||||||
<Services />
|
</>
|
||||||
<Process />
|
|
||||||
<Contact />
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+15
-8
@@ -1,5 +1,6 @@
|
|||||||
import { Routes, Route, Outlet } from "react-router-dom";
|
import { Routes, Route, Outlet } from "react-router-dom";
|
||||||
import Footer from "@/components/Footer";
|
import SiteHeader from "@/components/site/SiteHeader";
|
||||||
|
import SiteFooter from "@/components/site/SiteFooter";
|
||||||
import Index from "./pages/Index";
|
import Index from "./pages/Index";
|
||||||
import ContactPage from "./pages/Contact";
|
import ContactPage from "./pages/Contact";
|
||||||
import AGBPage from "./pages/AGB";
|
import AGBPage from "./pages/AGB";
|
||||||
@@ -10,21 +11,27 @@ import HostingAboPage from "./pages/HostingAbo";
|
|||||||
import DatenschutzPage from "./pages/Datenschutz";
|
import DatenschutzPage from "./pages/Datenschutz";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
|
|
||||||
/** Gemeinsamer Rahmen aller Unterseiten: Seiteninhalt, darunter der Footer mit Impressum, Datenschutz und Kündigen. */
|
/** Gemeinsamer Rahmen aller Seiten: Kopfleiste, Inhalt, Footer mit Impressum, Datenschutz und Kündigen. */
|
||||||
const SubpageLayout = () => (
|
const SiteLayout = () => (
|
||||||
<>
|
<>
|
||||||
<Outlet />
|
<a href="#inhalt" className="wk-skiplink">
|
||||||
<Footer />
|
Zum Inhalt springen
|
||||||
|
</a>
|
||||||
|
<SiteHeader />
|
||||||
|
<main id="inhalt">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
<SiteFooter />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Alle Seiten der Website; App.tsx rendert sie im BrowserRouter, die Tests im MemoryRouter. */
|
/** Alle Seiten der Website; App.tsx rendert sie im BrowserRouter, die Tests im MemoryRouter. */
|
||||||
export const AppRoutes = () => (
|
export const AppRoutes = () => (
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Die Startseite bringt ihren Footer selbst mit; /datenschutz leitet nur weiter. */}
|
{/* /datenschutz leitet nur weiter */}
|
||||||
<Route path="/" element={<Index />} />
|
|
||||||
<Route path="/datenschutz" element={<DatenschutzPage />} />
|
<Route path="/datenschutz" element={<DatenschutzPage />} />
|
||||||
<Route element={<SubpageLayout />}>
|
<Route element={<SiteLayout />}>
|
||||||
|
<Route path="/" element={<Index />} />
|
||||||
<Route path="/kontakt" element={<ContactPage />} />
|
<Route path="/kontakt" element={<ContactPage />} />
|
||||||
<Route path="/agb" element={<AGBPage />} />
|
<Route path="/agb" element={<AGBPage />} />
|
||||||
<Route path="/impressum" element={<ImpressumPage />} />
|
<Route path="/impressum" element={<ImpressumPage />} />
|
||||||
|
|||||||
@@ -0,0 +1,335 @@
|
|||||||
|
/*
|
||||||
|
* Relaunch 2026: Gestaltungs-Grundlage (Spezifikation Abschnitt 8).
|
||||||
|
* Eine Schriftfamilie, zwei Gewichte, neutrale Flächen und eine Akzentfarbe (Rot wie der Kartenpunkt).
|
||||||
|
* Wird nach index.css geladen und überschreibt dort die alten Token, damit auch die
|
||||||
|
* Rechtsseiten (shadcn-Klassen wie bg-background, text-muted-foreground) den neuen Look bekommen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Schrift B: Instrument Sans, selbst gehostet (SIL Open Font License, public/fonts/instrument-sans/LICENSE) */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Instrument Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/fonts/instrument-sans/instrument-sans-latin-ext-wght-normal.woff2) format("woff2");
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Instrument Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/fonts/instrument-sans/instrument-sans-latin-wght-normal.woff2) format("woff2");
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Schrift: B (Instrument Sans) ist Standard im Entwurf, ?schrift=a schaltet auf die Systemschrift */
|
||||||
|
--font-system: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
--font-ui: "Instrument Sans", var(--font-system);
|
||||||
|
|
||||||
|
--fs-hero: clamp(2.5rem, 1.569rem + 3.81vw, 5rem);
|
||||||
|
--fs-h2: clamp(2rem, 1.444rem + 2.29vw, 3.5rem);
|
||||||
|
--fs-h3: clamp(1.25rem, 1.1rem + 0.5vw, 1.5rem);
|
||||||
|
--fs-lead: clamp(1.1875rem, 1.05rem + 0.6vw, 1.625rem);
|
||||||
|
--fs-body: 1.0625rem;
|
||||||
|
--fs-small: 0.875rem;
|
||||||
|
--measure-text: 46rem;
|
||||||
|
--container: 1100px;
|
||||||
|
--gutter: clamp(16px, 4vw, 32px);
|
||||||
|
--section-space: clamp(72px, 9vw, 160px);
|
||||||
|
--header-h: 60px;
|
||||||
|
|
||||||
|
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
--dur-state: 200ms;
|
||||||
|
--dur-enter: 600ms;
|
||||||
|
|
||||||
|
/* Farben hell */
|
||||||
|
--bg: #ffffff;
|
||||||
|
--bg-alt: #f5f5f7;
|
||||||
|
--fg: #1d1d1f;
|
||||||
|
--fg-muted: #6e6e73;
|
||||||
|
--line: #d2d2d7;
|
||||||
|
--line-strong: #86868b;
|
||||||
|
--accent: #e5251c;
|
||||||
|
--accent-text: #c81e16;
|
||||||
|
--focus: #c81e16;
|
||||||
|
|
||||||
|
/* shadcn-Token auf die neuen Farben gelegt (HSL ohne hsl()) */
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 240 3% 12%;
|
||||||
|
--card: 240 11% 96%;
|
||||||
|
--card-foreground: 240 3% 12%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 240 3% 12%;
|
||||||
|
--primary: 240 3% 12%;
|
||||||
|
--primary-foreground: 0 0% 100%;
|
||||||
|
--secondary: 240 11% 96%;
|
||||||
|
--secondary-foreground: 240 3% 12%;
|
||||||
|
--muted: 240 11% 96%;
|
||||||
|
--muted-foreground: 240 2% 44%;
|
||||||
|
--accent-hsl: 3 80% 43%;
|
||||||
|
--border: 240 6% 84%;
|
||||||
|
--input: 240 6% 84%;
|
||||||
|
--ring: 3 80% 43%;
|
||||||
|
--radius: 0.75rem;
|
||||||
|
--font-sans: var(--font-ui);
|
||||||
|
--font-display: var(--font-ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--bg: #0b0b0c;
|
||||||
|
--bg-alt: #161618;
|
||||||
|
--fg: #f5f5f7;
|
||||||
|
--fg-muted: #a1a1a6;
|
||||||
|
--line: #2c2c2e;
|
||||||
|
--line-strong: #636366;
|
||||||
|
--accent: #e5251c;
|
||||||
|
--accent-text: #ff5a4f;
|
||||||
|
--focus: #ff5a4f;
|
||||||
|
|
||||||
|
--background: 240 4% 4%;
|
||||||
|
--foreground: 240 11% 96%;
|
||||||
|
--card: 240 4% 9%;
|
||||||
|
--card-foreground: 240 11% 96%;
|
||||||
|
--popover: 240 4% 9%;
|
||||||
|
--popover-foreground: 240 11% 96%;
|
||||||
|
--primary: 240 11% 96%;
|
||||||
|
--primary-foreground: 240 3% 12%;
|
||||||
|
--secondary: 240 4% 9%;
|
||||||
|
--secondary-foreground: 240 11% 96%;
|
||||||
|
--muted: 240 4% 9%;
|
||||||
|
--muted-foreground: 240 2% 64%;
|
||||||
|
--accent-hsl: 4 100% 65%;
|
||||||
|
--border: 240 2% 18%;
|
||||||
|
--input: 240 2% 18%;
|
||||||
|
--ring: 4 100% 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-schrift="a"] {
|
||||||
|
--font-ui: var(--font-system);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--fs-body);
|
||||||
|
line-height: 1.47;
|
||||||
|
letter-spacing: normal;
|
||||||
|
font-feature-settings: "kern", "calt";
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
text-transform: none;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-wrap: pretty;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--focus);
|
||||||
|
outline-offset: 3px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section[id] {
|
||||||
|
scroll-margin-top: calc(var(--header-h) + 24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Bausteine ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.wk-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: calc(var(--container) + 2 * var(--gutter));
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: var(--gutter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-section {
|
||||||
|
padding-block: var(--section-space);
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
.wk-section--alt {
|
||||||
|
background: var(--bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-eyebrow {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
letter-spacing: 0.005em;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-h1 {
|
||||||
|
font-size: var(--fs-hero);
|
||||||
|
line-height: 1.05;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-h2 {
|
||||||
|
font-size: var(--fs-h2);
|
||||||
|
line-height: 1.07;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
max-width: 20ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-h3 {
|
||||||
|
font-size: var(--fs-h3);
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-lead {
|
||||||
|
font-size: var(--fs-lead);
|
||||||
|
line-height: 1.38;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
max-width: var(--measure-text);
|
||||||
|
text-wrap: pretty;
|
||||||
|
}
|
||||||
|
.wk-h2 + .wk-lead,
|
||||||
|
.wk-h1 + .wk-lead {
|
||||||
|
margin-top: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-muted {
|
||||||
|
color: var(--fg-muted);
|
||||||
|
}
|
||||||
|
.wk-small {
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
line-height: 1.43;
|
||||||
|
}
|
||||||
|
.wk-num {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition:
|
||||||
|
background-color var(--dur-state) var(--ease),
|
||||||
|
color var(--dur-state) var(--ease),
|
||||||
|
box-shadow var(--dur-state) var(--ease),
|
||||||
|
opacity var(--dur-state) var(--ease);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.wk-btn--primary {
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
.wk-btn--secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--fg);
|
||||||
|
box-shadow: inset 0 0 0 1px var(--line-strong);
|
||||||
|
}
|
||||||
|
.wk-btn--small {
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.wk-btn--primary:hover {
|
||||||
|
opacity: 0.86;
|
||||||
|
}
|
||||||
|
.wk-btn--secondary:hover {
|
||||||
|
box-shadow: inset 0 0 0 1px var(--fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-link {
|
||||||
|
color: var(--fg);
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 4px;
|
||||||
|
text-decoration-color: var(--line-strong);
|
||||||
|
transition: text-decoration-color var(--dur-state) var(--ease);
|
||||||
|
}
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.wk-link:hover {
|
||||||
|
text-decoration-color: var(--fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-visually-hidden {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sanftes Erscheinen beim Scrollen: Grundzustand sichtbar, nur mit Unterstützung und ohne reduzierte Bewegung */
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
@supports (animation-timeline: view()) {
|
||||||
|
.wk-reveal {
|
||||||
|
animation: wk-reveal linear both;
|
||||||
|
animation-timeline: view();
|
||||||
|
animation-range: entry 0% cover 22%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes wk-reveal {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(28px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wk-skiplink {
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
top: -100px;
|
||||||
|
z-index: 100;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--bg);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.wk-skiplink:focus {
|
||||||
|
top: 0.75rem;
|
||||||
|
}
|
||||||
+3
-19
@@ -14,25 +14,9 @@ export default {
|
|||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
// Relaunch: eine Schriftfamilie für alles (src/styles/relaunch.css, --font-ui)
|
||||||
'Inter',
|
sans: ['var(--font-ui)'],
|
||||||
'ui-sans-serif',
|
display: ['var(--font-ui)'],
|
||||||
'system-ui',
|
|
||||||
'-apple-system',
|
|
||||||
'BlinkMacSystemFont',
|
|
||||||
'Segoe UI',
|
|
||||||
'Roboto',
|
|
||||||
'Helvetica Neue',
|
|
||||||
'Arial',
|
|
||||||
'Noto Sans',
|
|
||||||
'sans-serif'
|
|
||||||
],
|
|
||||||
display: [
|
|
||||||
'Space Grotesk',
|
|
||||||
'Inter',
|
|
||||||
'system-ui',
|
|
||||||
'sans-serif'
|
|
||||||
],
|
|
||||||
serif: [
|
serif: [
|
||||||
'Lora',
|
'Lora',
|
||||||
'ui-serif',
|
'ui-serif',
|
||||||
|
|||||||
Reference in new issue
Block a user