mirror of
https://github.com/Since-3/since3-website.git
synced 2026-10-12 20:51:17 +00:00
Merge pull request #2 from Since-3/feature/added-legal-stuff
added legeal stuff
This commit is contained in:
commit
d51a3999f7
23 changed files with 987 additions and 21 deletions
442
app/(legal)/datenschutz/page.tsx
Normal file
442
app/(legal)/datenschutz/page.tsx
Normal file
|
|
@ -0,0 +1,442 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Datenschutzerklärung",
|
||||||
|
}
|
||||||
|
|
||||||
|
const Datenschutz = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Datenschutzerklärung</h1>
|
||||||
|
|
||||||
|
<h2>1. Datenschutz auf einen Blick</h2>
|
||||||
|
|
||||||
|
<h3>Allgemeine Hinweise</h3>
|
||||||
|
<p>
|
||||||
|
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren
|
||||||
|
personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene
|
||||||
|
Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können.
|
||||||
|
Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem
|
||||||
|
Text aufgeführten Datenschutzerklärung.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Datenerfassung auf dieser Website</h3>
|
||||||
|
|
||||||
|
<h4>Wer ist verantwortlich für die Datenerfassung auf dieser Website?</h4>
|
||||||
|
<p>
|
||||||
|
Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen
|
||||||
|
Kontaktdaten können Sie dem Abschnitt „Hinweis zur verantwortlichen Stelle“ in dieser
|
||||||
|
Datenschutzerklärung entnehmen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Wie erfassen wir Ihre Daten?</h4>
|
||||||
|
<p>
|
||||||
|
Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei
|
||||||
|
kann es sich z. B. um Daten handeln, die Sie in ein Kontaktformular eingeben.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website
|
||||||
|
durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z. B.
|
||||||
|
Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser
|
||||||
|
Daten erfolgt automatisch, sobald Sie diese Website betreten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Wofür nutzen wir Ihre Daten?</h4>
|
||||||
|
<p>
|
||||||
|
Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu
|
||||||
|
gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet
|
||||||
|
werden. Sofern über die Website Verträge geschlossen oder angebahnt werden können,
|
||||||
|
werden die übermittelten Daten auch für Vertragsangebote, Bestellungen oder sonstige
|
||||||
|
Auftragsanfragen verarbeitet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Welche Rechte haben Sie bezüglich Ihrer Daten?</h4>
|
||||||
|
<p>
|
||||||
|
Sie haben jederzeit das Recht, unentgeltlich Auskunft über Herkunft, Empfänger und
|
||||||
|
Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein
|
||||||
|
Recht, die Berichtigung oder Löschung dieser Daten zu verlangen. Wenn Sie eine
|
||||||
|
Einwilligung zur Datenverarbeitung erteilt haben, können Sie diese Einwilligung
|
||||||
|
jederzeit für die Zukunft widerrufen. Außerdem haben Sie das Recht, unter bestimmten
|
||||||
|
Umständen die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu
|
||||||
|
verlangen. Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen
|
||||||
|
Aufsichtsbehörde zu.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit an
|
||||||
|
uns wenden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Analyse-Tools und Tools von Drittanbietern</h3>
|
||||||
|
<p>
|
||||||
|
Beim Besuch dieser Website kann Ihr Surf-Verhalten statistisch ausgewertet werden.
|
||||||
|
Das geschieht vor allem mit sogenannten Analyseprogrammen.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Detaillierte Informationen zu diesen Analyseprogrammen finden Sie in der folgenden
|
||||||
|
Datenschutzerklärung.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>2. Hosting</h2>
|
||||||
|
<p>Wir hosten die Inhalte unserer Website bei folgendem Anbieter:</p>
|
||||||
|
|
||||||
|
<h3>Externes Hosting</h3>
|
||||||
|
<p>
|
||||||
|
Diese Website wird extern gehostet. Die personenbezogenen Daten, die auf dieser
|
||||||
|
Website erfasst werden, werden auf den Servern des Hosters / der Hoster gespeichert.
|
||||||
|
Hierbei kann es sich v. a. um IP-Adressen, Kontaktanfragen, Meta- und
|
||||||
|
Kommunikationsdaten, Vertragsdaten, Kontaktdaten, Namen, Websitezugriffe und
|
||||||
|
sonstige Daten, die über eine Website generiert werden, handeln.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Das externe Hosting erfolgt zum Zwecke der Vertragserfüllung gegenüber unseren
|
||||||
|
potenziellen und bestehenden Kunden (Art. 6 Abs. 1 lit. b DSGVO) und im Interesse
|
||||||
|
einer sicheren, schnellen und effizienten Bereitstellung unseres Online-Angebots
|
||||||
|
durch einen professionellen Anbieter (Art. 6 Abs. 1 lit. f DSGVO). Sofern eine
|
||||||
|
entsprechende Einwilligung abgefragt wurde, erfolgt die Verarbeitung ausschließlich
|
||||||
|
auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1 TDDDG, soweit die
|
||||||
|
Einwilligung die Speicherung von Cookies oder den Zugriff auf Informationen im
|
||||||
|
Endgerät des Nutzers (z. B. Device-Fingerprinting) im Sinne des TDDDG umfasst. Die
|
||||||
|
Einwilligung ist jederzeit widerrufbar.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Unser(e) Hoster wird bzw. werden Ihre Daten nur insoweit verarbeiten, wie dies zur
|
||||||
|
Erfüllung seiner Leistungspflichten erforderlich ist und unsere Weisungen in Bezug
|
||||||
|
auf diese Daten befolgen.
|
||||||
|
</p>
|
||||||
|
<p>Wir setzen folgende(n) Hoster ein:</p>
|
||||||
|
<address>
|
||||||
|
Vercel Inc.
|
||||||
|
<br />
|
||||||
|
440 Barranca Ave #4133
|
||||||
|
<br />
|
||||||
|
Covina, CA 91723
|
||||||
|
<br />
|
||||||
|
USA
|
||||||
|
</address>
|
||||||
|
|
||||||
|
<h4>Auftragsverarbeitung</h4>
|
||||||
|
<p>
|
||||||
|
Wir haben einen Vertrag über Auftragsverarbeitung (AVV) zur Nutzung des oben
|
||||||
|
genannten Dienstes geschlossen. Hierbei handelt es sich um einen
|
||||||
|
datenschutzrechtlich vorgeschriebenen Vertrag, der gewährleistet, dass dieser die
|
||||||
|
personenbezogenen Daten unserer Websitebesucher nur nach unseren Weisungen und unter
|
||||||
|
Einhaltung der DSGVO verarbeitet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>3. Allgemeine Hinweise und Pflichtinformationen</h2>
|
||||||
|
|
||||||
|
<h3>Datenschutz</h3>
|
||||||
|
<p>
|
||||||
|
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst.
|
||||||
|
Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den
|
||||||
|
gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben.
|
||||||
|
Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden
|
||||||
|
können. Die vorliegende Datenschutzerklärung erläutert, welche Daten wir erheben und
|
||||||
|
wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck das geschieht.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der
|
||||||
|
Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz
|
||||||
|
der Daten vor dem Zugriff durch Dritte ist nicht möglich.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Hinweis zur verantwortlichen Stelle</h3>
|
||||||
|
<p>Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:</p>
|
||||||
|
<address>
|
||||||
|
Ben Dalhoff, Rajpreet Singh
|
||||||
|
<br />
|
||||||
|
Sandweg 91
|
||||||
|
<br />
|
||||||
|
48565 Steinfurt
|
||||||
|
</address>
|
||||||
|
<p>
|
||||||
|
Telefon: +49 176 47601638
|
||||||
|
<br />
|
||||||
|
E-Mail: <a href="mailto:info@since3.de">info@since3.de</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Verantwortliche Stelle ist die natürliche oder juristische Person, die allein oder
|
||||||
|
gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von
|
||||||
|
personenbezogenen Daten (z. B. Namen, E-Mail-Adressen o. Ä.) entscheidet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Speicherdauer</h3>
|
||||||
|
<p>
|
||||||
|
Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer genannt
|
||||||
|
wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck für die
|
||||||
|
Datenverarbeitung entfällt. Wenn Sie ein berechtigtes Löschersuchen geltend machen
|
||||||
|
oder eine Einwilligung zur Datenverarbeitung widerrufen, werden Ihre Daten gelöscht,
|
||||||
|
sofern wir keine anderen rechtlich zulässigen Gründe für die Speicherung Ihrer
|
||||||
|
personenbezogenen Daten haben (z. B. steuer- oder handelsrechtliche
|
||||||
|
Aufbewahrungsfristen); im letztgenannten Fall erfolgt die Löschung nach Fortfall
|
||||||
|
dieser Gründe.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung auf dieser Website</h3>
|
||||||
|
<p>
|
||||||
|
Sofern Sie in die Datenverarbeitung eingewilligt haben, verarbeiten wir Ihre
|
||||||
|
personenbezogenen Daten auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO bzw. Art. 9 Abs.
|
||||||
|
2 lit. a DSGVO, sofern besondere Datenkategorien nach Art. 9 Abs. 1 DSGVO verarbeitet
|
||||||
|
werden. Im Falle einer ausdrücklichen Einwilligung in die Übertragung
|
||||||
|
personenbezogener Daten in Drittstaaten erfolgt die Datenverarbeitung außerdem auf
|
||||||
|
Grundlage von Art. 49 Abs. 1 lit. a DSGVO. Sofern Sie in die Speicherung von Cookies
|
||||||
|
oder in den Zugriff auf Informationen in Ihr Endgerät (z. B. via
|
||||||
|
Device-Fingerprinting) eingewilligt haben, erfolgt die Datenverarbeitung zusätzlich
|
||||||
|
auf Grundlage von § 25 Abs. 1 TDDDG. Die Einwilligung ist jederzeit widerrufbar. Sind
|
||||||
|
Ihre Daten zur Vertragserfüllung oder zur Durchführung vorvertraglicher Maßnahmen
|
||||||
|
erforderlich, verarbeiten wir Ihre Daten auf Grundlage des Art. 6 Abs. 1 lit. b
|
||||||
|
DSGVO. Des Weiteren verarbeiten wir Ihre Daten, sofern diese zur Erfüllung einer
|
||||||
|
rechtlichen Verpflichtung erforderlich sind, auf Grundlage von Art. 6 Abs. 1 lit. c
|
||||||
|
DSGVO. Die Datenverarbeitung kann ferner auf Grundlage unseres berechtigten
|
||||||
|
Interesses nach Art. 6 Abs. 1 lit. f DSGVO erfolgen. Über die jeweils im Einzelfall
|
||||||
|
einschlägigen Rechtsgrundlagen wird in den folgenden Absätzen dieser
|
||||||
|
Datenschutzerklärung informiert.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Empfänger von personenbezogenen Daten</h3>
|
||||||
|
<p>
|
||||||
|
Im Rahmen unserer Geschäftstätigkeit arbeiten wir mit verschiedenen externen Stellen
|
||||||
|
zusammen. Dabei ist teilweise auch eine Übermittlung von personenbezogenen Daten an
|
||||||
|
diese externen Stellen erforderlich. Wir geben personenbezogene Daten nur dann an
|
||||||
|
externe Stellen weiter, wenn dies im Rahmen einer Vertragserfüllung erforderlich ist,
|
||||||
|
wenn wir gesetzlich hierzu verpflichtet sind (z. B. Weitergabe von Daten an
|
||||||
|
Steuerbehörden), wenn wir ein berechtigtes Interesse nach Art. 6 Abs. 1 lit. f DSGVO
|
||||||
|
an der Weitergabe haben oder wenn eine sonstige Rechtsgrundlage die Datenweitergabe
|
||||||
|
erlaubt. Beim Einsatz von Auftragsverarbeitern geben wir personenbezogene Daten
|
||||||
|
unserer Kunden nur auf Grundlage eines gültigen Vertrags über Auftragsverarbeitung
|
||||||
|
weiter. Im Falle einer gemeinsamen Verarbeitung wird ein Vertrag über gemeinsame
|
||||||
|
Verarbeitung geschlossen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3>
|
||||||
|
<p>
|
||||||
|
Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung
|
||||||
|
möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Die
|
||||||
|
Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf
|
||||||
|
unberührt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
Widerspruchsrecht gegen die Datenerhebung in besonderen Fällen sowie gegen
|
||||||
|
Direktwerbung (Art. 21 DSGVO)
|
||||||
|
</h3>
|
||||||
|
<p className="legal-caps">
|
||||||
|
WENN DIE DATENVERARBEITUNG AUF GRUNDLAGE VON ART. 6 ABS. 1 LIT. E ODER F DSGVO
|
||||||
|
ERFOLGT, HABEN SIE JEDERZEIT DAS RECHT, AUS GRÜNDEN, DIE SICH AUS IHRER BESONDEREN
|
||||||
|
SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN WIDERSPRUCH
|
||||||
|
EINZULEGEN; DIES GILT AUCH FÜR EIN AUF DIESE BESTIMMUNGEN GESTÜTZTES PROFILING. DIE
|
||||||
|
JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT, ENTNEHMEN SIE DIESER
|
||||||
|
DATENSCHUTZERKLÄRUNG. WENN SIE WIDERSPRUCH EINLEGEN, WERDEN WIR IHRE BETROFFENEN
|
||||||
|
PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES SEI DENN, WIR KÖNNEN ZWINGENDE
|
||||||
|
SCHUTZWÜRDIGE GRÜNDE FÜR DIE VERARBEITUNG NACHWEISEN, DIE IHRE INTERESSEN, RECHTE
|
||||||
|
UND FREIHEITEN ÜBERWIEGEN ODER DIE VERARBEITUNG DIENT DER GELTENDMACHUNG, AUSÜBUNG
|
||||||
|
ODER VERTEIDIGUNG VON RECHTSANSPRÜCHEN (WIDERSPRUCH NACH ART. 21 ABS. 1 DSGVO).
|
||||||
|
</p>
|
||||||
|
<p className="legal-caps">
|
||||||
|
WERDEN IHRE PERSONENBEZOGENEN DATEN VERARBEITET, UM DIREKTWERBUNG ZU BETREIBEN, SO
|
||||||
|
HABEN SIE DAS RECHT, JEDERZEIT WIDERSPRUCH GEGEN DIE VERARBEITUNG SIE BETREFFENDER
|
||||||
|
PERSONENBEZOGENER DATEN ZUM ZWECKE DERARTIGER WERBUNG EINZULEGEN; DIES GILT AUCH FÜR
|
||||||
|
DAS PROFILING, SOWEIT ES MIT SOLCHER DIREKTWERBUNG IN VERBINDUNG STEHT. WENN SIE
|
||||||
|
WIDERSPRECHEN, WERDEN IHRE PERSONENBEZOGENEN DATEN ANSCHLIESSEND NICHT MEHR ZUM
|
||||||
|
ZWECKE DER DIREKTWERBUNG VERWENDET (WIDERSPRUCH NACH ART. 21 ABS. 2 DSGVO).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Beschwerderecht bei der zuständigen Aufsichtsbehörde</h3>
|
||||||
|
<p>
|
||||||
|
Im Falle von Verstößen gegen die DSGVO steht den Betroffenen ein Beschwerderecht bei
|
||||||
|
einer Aufsichtsbehörde, insbesondere in dem Mitgliedstaat ihres gewöhnlichen
|
||||||
|
Aufenthalts, ihres Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes zu. Das
|
||||||
|
Beschwerderecht besteht unbeschadet anderweitiger verwaltungsrechtlicher oder
|
||||||
|
gerichtlicher Rechtsbehelfe.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Recht auf Datenübertragbarkeit</h3>
|
||||||
|
<p>
|
||||||
|
Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in
|
||||||
|
Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in
|
||||||
|
einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die
|
||||||
|
direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt
|
||||||
|
dies nur, soweit es technisch machbar ist.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Auskunft, Berichtigung und Löschung</h3>
|
||||||
|
<p>
|
||||||
|
Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf
|
||||||
|
unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren
|
||||||
|
Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf
|
||||||
|
Berichtigung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema
|
||||||
|
personenbezogene Daten können Sie sich jederzeit an uns wenden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Recht auf Einschränkung der Verarbeitung</h3>
|
||||||
|
<p>
|
||||||
|
Sie haben das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen
|
||||||
|
Daten zu verlangen. Hierzu können Sie sich jederzeit an uns wenden. Das Recht auf
|
||||||
|
Einschränkung der Verarbeitung besteht in folgenden Fällen:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Wenn Sie die Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten
|
||||||
|
bestreiten, benötigen wir in der Regel Zeit, um dies zu überprüfen. Für die Dauer
|
||||||
|
der Prüfung haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer
|
||||||
|
personenbezogenen Daten zu verlangen.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Wenn die Verarbeitung Ihrer personenbezogenen Daten unrechtmäßig
|
||||||
|
geschah/geschieht, können Sie statt der Löschung die Einschränkung der
|
||||||
|
Datenverarbeitung verlangen.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Wenn wir Ihre personenbezogenen Daten nicht mehr benötigen, Sie sie jedoch zur
|
||||||
|
Ausübung, Verteidigung oder Geltendmachung von Rechtsansprüchen benötigen, haben
|
||||||
|
Sie das Recht, statt der Löschung die Einschränkung der Verarbeitung Ihrer
|
||||||
|
personenbezogenen Daten zu verlangen.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Wenn Sie einen Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss eine
|
||||||
|
Abwägung zwischen Ihren und unseren Interessen vorgenommen werden. Solange noch
|
||||||
|
nicht feststeht, wessen Interessen überwiegen, haben Sie das Recht, die
|
||||||
|
Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschränkt haben, dürfen
|
||||||
|
diese Daten – von ihrer Speicherung abgesehen – nur mit Ihrer Einwilligung oder zur
|
||||||
|
Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der
|
||||||
|
Rechte einer anderen natürlichen oder juristischen Person oder aus Gründen eines
|
||||||
|
wichtigen öffentlichen Interesses der Europäischen Union oder eines Mitgliedstaats
|
||||||
|
verarbeitet werden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>SSL- bzw. TLS-Verschlüsselung</h3>
|
||||||
|
<p>
|
||||||
|
Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung
|
||||||
|
vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an uns
|
||||||
|
als Seitenbetreiber senden, eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte
|
||||||
|
Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von „http://“ auf
|
||||||
|
„https://“ wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Wenn die SSL- bzw. TLS-Verschlüsselung aktiviert ist, können die Daten, die Sie an
|
||||||
|
uns übermitteln, nicht von Dritten mitgelesen werden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>4. Datenerfassung auf dieser Website</h2>
|
||||||
|
|
||||||
|
<h3>Cookies</h3>
|
||||||
|
<p>
|
||||||
|
Unsere Internetseiten verwenden so genannte „Cookies“. Cookies sind kleine
|
||||||
|
Datenpakete und richten auf Ihrem Endgerät keinen Schaden an. Sie werden entweder
|
||||||
|
vorübergehend für die Dauer einer Sitzung (Session-Cookies) oder dauerhaft
|
||||||
|
(permanente Cookies) auf Ihrem Endgerät gespeichert. Session-Cookies werden nach
|
||||||
|
Ende Ihres Besuchs automatisch gelöscht. Permanente Cookies bleiben auf Ihrem
|
||||||
|
Endgerät gespeichert, bis Sie diese selbst löschen oder eine automatische Löschung
|
||||||
|
durch Ihren Webbrowser erfolgt.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cookies können von uns (First-Party-Cookies) oder von Drittunternehmen stammen (sog.
|
||||||
|
Third-Party-Cookies). Third-Party-Cookies ermöglichen die Einbindung bestimmter
|
||||||
|
Dienstleistungen von Drittunternehmen innerhalb von Webseiten (z. B. Cookies zur
|
||||||
|
Abwicklung von Zahlungsdienstleistungen).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cookies haben verschiedene Funktionen. Zahlreiche Cookies sind technisch notwendig,
|
||||||
|
da bestimmte Webseitenfunktionen ohne diese nicht funktionieren würden (z. B. die
|
||||||
|
Warenkorbfunktion oder die Anzeige von Videos). Andere Cookies können zur Auswertung
|
||||||
|
des Nutzerverhaltens oder zu Werbezwecken verwendet werden.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cookies, die zur Durchführung des elektronischen Kommunikationsvorgangs, zur
|
||||||
|
Bereitstellung bestimmter, von Ihnen erwünschter Funktionen (z. B. für die
|
||||||
|
Warenkorbfunktion) oder zur Optimierung der Website (z. B. Cookies zur Messung des
|
||||||
|
Webpublikums) erforderlich sind (notwendige Cookies), werden auf Grundlage von Art. 6
|
||||||
|
Abs. 1 lit. f DSGVO gespeichert, sofern keine andere Rechtsgrundlage angegeben wird.
|
||||||
|
Der Websitebetreiber hat ein berechtigtes Interesse an der Speicherung von
|
||||||
|
notwendigen Cookies zur technisch fehlerfreien und optimierten Bereitstellung seiner
|
||||||
|
Dienste. Sofern eine Einwilligung zur Speicherung von Cookies und vergleichbaren
|
||||||
|
Wiedererkennungstechnologien abgefragt wurde, erfolgt die Verarbeitung
|
||||||
|
ausschließlich auf Grundlage dieser Einwilligung (Art. 6 Abs. 1 lit. a DSGVO und § 25
|
||||||
|
Abs. 1 TDDDG); die Einwilligung ist jederzeit widerrufbar.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies
|
||||||
|
informiert werden und Cookies nur im Einzelfall erlauben, die Annahme von Cookies für
|
||||||
|
bestimmte Fälle oder generell ausschließen sowie das automatische Löschen der Cookies
|
||||||
|
beim Schließen des Browsers aktivieren. Bei der Deaktivierung von Cookies kann die
|
||||||
|
Funktionalität dieser Website eingeschränkt sein.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Sofern weitere Cookies und Dienste auf dieser Website eingesetzt werden, können Sie
|
||||||
|
dies dieser Datenschutzerklärung entnehmen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Vercel Analytics</h3>
|
||||||
|
<p>
|
||||||
|
Wir nutzen auf unserer Website die Analysedienste Vercel Analytics der Vercel Inc. (440 Barranca Ave #4133, Covina, CA 91723, USA).
|
||||||
|
Dabei werden technische Informationen (z. B. aufgerufene Seite, Browsertyp, Ladezeiten) an Server von Vercel übermittelt.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Die Einbindung und Verarbeitung erfolgt ausschließlich auf Grundlage Ihrer ausdrücklichen Einwilligung gemäß
|
||||||
|
Art. 6 Abs. 1 lit. a DSGVO sowie § 25 Abs. 1 TDDDG. Die Einwilligung wird über unseren Cookie-Banner abgefragt
|
||||||
|
und ist jederzeit mit Wirkung für die Zukunft über die Cookie-Einstellungen auf unserer Website widerrufbar.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Kontaktformular</h3>
|
||||||
|
<p>
|
||||||
|
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>
|
||||||
|
Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO,
|
||||||
|
sofern Ihre Anfrage mit der Erfüllung eines Vertrags zusammenhängt oder zur
|
||||||
|
Durchführung vorvertraglicher Maßnahmen erforderlich ist. In allen übrigen Fällen
|
||||||
|
beruht die Verarbeitung auf unserem berechtigten Interesse an der effektiven
|
||||||
|
Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) oder auf
|
||||||
|
Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) sofern diese abgefragt wurde; die
|
||||||
|
Einwilligung ist jederzeit widerrufbar.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Die von Ihnen im Kontaktformular eingegebenen Daten verbleiben bei uns, bis Sie uns
|
||||||
|
zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck
|
||||||
|
für die Datenspeicherung entfällt (z. B. nach abgeschlossener Bearbeitung Ihrer
|
||||||
|
Anfrage). Zwingende gesetzliche Bestimmungen – insbesondere Aufbewahrungsfristen –
|
||||||
|
bleiben unberührt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Anfrage per E-Mail, Telefon oder Telefax</h3>
|
||||||
|
<p>
|
||||||
|
Wenn Sie uns per E-Mail, Telefon oder Telefax kontaktieren, wird Ihre Anfrage
|
||||||
|
inklusive aller daraus hervorgehenden personenbezogenen Daten (Name, Anfrage) zum
|
||||||
|
Zwecke der Bearbeitung Ihres Anliegens bei uns gespeichert und verarbeitet. Diese
|
||||||
|
Daten geben wir nicht ohne Ihre Einwilligung weiter.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO,
|
||||||
|
sofern Ihre Anfrage mit der Erfüllung eines Vertrags zusammenhängt oder zur
|
||||||
|
Durchführung vorvertraglicher Maßnahmen erforderlich ist. In allen übrigen Fällen
|
||||||
|
beruht die Verarbeitung auf unserem berechtigten Interesse an der effektiven
|
||||||
|
Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) oder auf
|
||||||
|
Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) sofern diese abgefragt wurde; die
|
||||||
|
Einwilligung ist jederzeit widerrufbar.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Die von Ihnen an uns per Kontaktanfragen übersandten Daten verbleiben bei uns, bis
|
||||||
|
Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder
|
||||||
|
der Zweck für die Datenspeicherung entfällt (z. B. nach abgeschlossener Bearbeitung
|
||||||
|
Ihres Anliegens). Zwingende gesetzliche Bestimmungen – insbesondere gesetzliche
|
||||||
|
Aufbewahrungsfristen – bleiben unberührt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="legal-source">
|
||||||
|
Quelle: <a href="https://www.e-recht24.de">https://www.e-recht24.de</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Datenschutz
|
||||||
46
app/(legal)/impressum/page.tsx
Normal file
46
app/(legal)/impressum/page.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Impressum",
|
||||||
|
}
|
||||||
|
|
||||||
|
const Impressum = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Impressum</h1>
|
||||||
|
|
||||||
|
<address>
|
||||||
|
Since3 GbR
|
||||||
|
<br />
|
||||||
|
Sandweg 91
|
||||||
|
<br />
|
||||||
|
48565 Steinfurt
|
||||||
|
</address>
|
||||||
|
|
||||||
|
<h3>Vertreten durch:</h3>
|
||||||
|
<p>Ben Dalhoff, Rajpreet Singh</p>
|
||||||
|
|
||||||
|
<h2>Kontakt</h2>
|
||||||
|
<p>
|
||||||
|
Telefon: <a href="tel:+4917647601638">+49 176 47601638</a>
|
||||||
|
<br />
|
||||||
|
E-Mail: <a href="mailto:info@since3.de">info@since3.de</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Redaktionell verantwortlich</h2>
|
||||||
|
<p>Ben Dalhoff, Rajpreet Singh</p>
|
||||||
|
|
||||||
|
<h2>Verbraucherstreitbeilegung/Universalschlichtungsstelle</h2>
|
||||||
|
<p>
|
||||||
|
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer
|
||||||
|
Verbraucherschlichtungsstelle teilzunehmen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="legal-source">
|
||||||
|
Quelle: <a href="https://www.e-recht24.de">https://www.e-recht24.de</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Impressum
|
||||||
22
app/(legal)/layout.tsx
Normal file
22
app/(legal)/layout.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
|
||||||
|
// Shared frame for Impressum and Datenschutz: light page, one readable column
|
||||||
|
export default function LegalLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<section data-theme="light" className="min-h-svh pt-32 pb-section md:pt-40">
|
||||||
|
<div className="container-site">
|
||||||
|
<div className="mx-auto max-w-176">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex items-center gap-2 font-mono text-label text-fg-muted transition-colors hover:text-fg"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="size-4" aria-hidden />
|
||||||
|
Zur Startseite
|
||||||
|
</Link>
|
||||||
|
<article className="legal-prose mt-10">{children}</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
154
app/globals.css
154
app/globals.css
|
|
@ -49,6 +49,18 @@
|
||||||
|
|
||||||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
--ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1);
|
--ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Single stacking scale for every fixed/overlay layer on the page.
|
||||||
|
* Highest wins, so the custom cursor stays above all real UI, and the
|
||||||
|
* cookie banner (a third-party widget) is pinned below it explicitly —
|
||||||
|
* see the vanilla-cookieconsent override further down.
|
||||||
|
*/
|
||||||
|
--z-header: 80;
|
||||||
|
--z-mobile-menu: 85;
|
||||||
|
--z-cookie-banner: 90;
|
||||||
|
--z-cursor: 95;
|
||||||
|
--z-preloader: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
|
|
@ -244,6 +256,148 @@ html.lenis body {
|
||||||
paint-order: stroke fill;
|
paint-order: stroke fill;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Cookie Consent Banner ─────────────────────────────── */
|
||||||
|
:root.cc--elegant-black {
|
||||||
|
color-scheme: dark;
|
||||||
|
|
||||||
|
--cc-bg: #000;
|
||||||
|
--cc-primary-color: rgb(239, 244, 246);
|
||||||
|
--cc-secondary-color: #b1bdc3;
|
||||||
|
|
||||||
|
--cc-btn-primary-bg: #ffffff;
|
||||||
|
--cc-btn-primary-color: #000;
|
||||||
|
--cc-btn-primary-hover-bg: #ccd4d8;
|
||||||
|
--cc-btn-primary-hover-color: #000;
|
||||||
|
|
||||||
|
--cc-btn-secondary-bg: rgba(255, 255, 255, 0.039);
|
||||||
|
--cc-btn-secondary-color: var(--cc-primary-color);
|
||||||
|
--cc-btn-secondary-border-color: #252729;
|
||||||
|
--cc-btn-secondary-hover-bg: #252729;
|
||||||
|
--cc-btn-secondary-hover-color: #fff;
|
||||||
|
--cc-btn-secondary-hover-border-color: #252729;
|
||||||
|
|
||||||
|
--cc-cookie-category-block-bg: #101111;
|
||||||
|
--cc-cookie-category-block-border: #1d1e1f;
|
||||||
|
--cc-cookie-category-block-hover-bg: #151516;
|
||||||
|
--cc-cookie-category-block-hover-border: #1d1e1f;
|
||||||
|
--cc-cookie-category-expanded-block-hover-bg: #1d1e1f;
|
||||||
|
--cc-cookie-category-expanded-block-bg: #101111;
|
||||||
|
--cc-toggle-readonly-bg: #2f3132;
|
||||||
|
--cc-overlay-bg: rgba(0, 0, 0, 0.9)!important;
|
||||||
|
|
||||||
|
--cc-toggle-on-knob-bg: var(--cc-bg);
|
||||||
|
--cc-toggle-readonly-knob-bg: var( --cc-cookie-category-block-bg);
|
||||||
|
|
||||||
|
--cc-separator-border-color: #252729;
|
||||||
|
|
||||||
|
--cc-footer-border-color: #212529;
|
||||||
|
--cc-footer-bg: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc--elegant-black #cc-main .cm,
|
||||||
|
.cc--elegant-black #cc-main .pm {
|
||||||
|
border: 1px solid var(--cc-separator-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Cookie consent (vanilla-cookieconsent) ───────────────────────────── */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The library defaults --cc-z-index to 2147483647 (max signed 32-bit int),
|
||||||
|
* which sits above literally everything, including the custom cursor.
|
||||||
|
* Pin it into our own stacking scale instead.
|
||||||
|
*
|
||||||
|
* Its stylesheet is imported by <CookieBanner> as a separate CSS module, so
|
||||||
|
* load order relative to this file isn't guaranteed. Setting the variable on
|
||||||
|
* #cc-main itself (the library's own root selector) beats a plain :root
|
||||||
|
* declaration on specificity regardless of which file loads last.
|
||||||
|
*/
|
||||||
|
#cc-main {
|
||||||
|
--cc-z-index: var(--z-cookie-banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Legal pages (Impressum, Datenschutz) ─────────────────────────────── */
|
||||||
|
|
||||||
|
/* Plain, readable long-form text. No effects, just hierarchy and rhythm. */
|
||||||
|
@layer components {
|
||||||
|
|
||||||
|
.legal-prose {
|
||||||
|
hyphens: auto;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
.legal-prose h1 {
|
||||||
|
font-size: clamp(2rem, 8vw, 4.5rem);
|
||||||
|
font-variation-settings: "wght" 800, "wdth" 75;
|
||||||
|
line-height: 0.9;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.legal-prose h2 {
|
||||||
|
margin-top: 3.5rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
font-size: var(--text-title);
|
||||||
|
font-variation-settings: "wght" 600, "wdth" 90;
|
||||||
|
line-height: 1.15;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
.legal-prose h1 + h2,
|
||||||
|
.legal-prose h1 + * {
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
.legal-prose h3 {
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
font-size: 1.1875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
.legal-prose h4 {
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.legal-prose :is(h2, h3) + :is(h3, h4) {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
|
.legal-prose :is(p, ul, address) {
|
||||||
|
margin-top: 0.875rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: color-mix(in oklch, var(--fg) 78%, var(--bg));
|
||||||
|
}
|
||||||
|
.legal-prose address {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
.legal-prose ul {
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
.legal-prose li + li {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.legal-prose a {
|
||||||
|
color: var(--fg);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
}
|
||||||
|
.legal-prose a:hover {
|
||||||
|
text-decoration-thickness: 2px;
|
||||||
|
}
|
||||||
|
/* The legally required all-caps paragraph (Art. 21 DSGVO), slightly calmer */
|
||||||
|
.legal-prose .legal-caps {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
.legal-prose .legal-source {
|
||||||
|
margin-top: 4rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--text-label);
|
||||||
|
color: var(--fg-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Grain over everything ────────────────────────────────────────────── */
|
/* ── Grain over everything ────────────────────────────────────────────── */
|
||||||
|
|
||||||
body::after {
|
body::after {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { SoundEffects } from "@/modules/shared/ui/components/sound-effects";
|
||||||
|
|
||||||
import { mono, sans } from "./fonts";
|
import { mono, sans } from "./fonts";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import CookieBanner from "@/modules/shared/ui/components/cookie-consent";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL(site.url),
|
metadataBase: new URL(site.url),
|
||||||
|
|
@ -48,7 +49,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||||
<div id="top" />
|
<div id="top" />
|
||||||
<a
|
<a
|
||||||
href="#inhalt"
|
href="#inhalt"
|
||||||
className="sr-only z-100 rounded-full bg-white px-5 py-3 text-black focus:not-sr-only focus:fixed focus:top-4 focus:left-4"
|
className="sr-only z-[var(--z-preloader)] rounded-full bg-white px-5 py-3 text-black focus:not-sr-only focus:fixed focus:top-4 focus:left-4"
|
||||||
>
|
>
|
||||||
Zum Inhalt springen
|
Zum Inhalt springen
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -62,6 +63,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||||
<main id="inhalt" tabIndex={-1} className="outline-none">
|
<main id="inhalt" tabIndex={-1} className="outline-none">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
<CookieBanner/>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
54
app/not-found.tsx
Normal file
54
app/not-found.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
|
import { RequestedPath } from "@/modules/shared/ui/components/requested-path";
|
||||||
|
import { Magnetic } from "@/modules/shared/ui/components/magnetic";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Seite nicht gefunden",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Renders inside the root layout, so header, footer and the shared theme
|
||||||
|
// still apply — this is the same dark, oversized-type language as the hero.
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
aria-labelledby="not-found-title"
|
||||||
|
data-theme="dark"
|
||||||
|
className="flex min-h-svh flex-col justify-center py-32"
|
||||||
|
>
|
||||||
|
<div className="container-site flex flex-col items-start">
|
||||||
|
<p className="font-mono text-label text-fg-muted">(Fehler 404)</p>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
id="not-found-title"
|
||||||
|
aria-label="Seite nicht gefunden"
|
||||||
|
className="mt-6 text-mega leading-none type-poster text-outline"
|
||||||
|
>
|
||||||
|
404
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="type-body mt-6 max-w-[46ch] text-lead text-fg-muted">
|
||||||
|
Diese Seite gibt es nicht, oder sie ist umgezogen. Der Link war
|
||||||
|
vielleicht veraltet oder es hat sich jemand vertippt.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<RequestedPath />
|
||||||
|
|
||||||
|
<Magnetic strength={0.3} className="mt-10">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="group inline-flex h-14 items-center gap-2 rounded-full bg-white px-7 font-medium text-black transition-[scale] duration-500 ease-out-expo hover:scale-105"
|
||||||
|
>
|
||||||
|
Zur Startseite
|
||||||
|
<ArrowRight
|
||||||
|
aria-hidden
|
||||||
|
className="size-4 transition-transform duration-500 ease-out-expo group-hover:translate-x-1"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</Magnetic>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
app/robots.ts
Normal file
14
app/robots.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
|
import { site } from "@/modules/shared/config/site";
|
||||||
|
|
||||||
|
// The whole site is public marketing content, so crawlers get full access.
|
||||||
|
export default function robots(): MetadataRoute.Robots {
|
||||||
|
return {
|
||||||
|
rules: {
|
||||||
|
userAgent: "*",
|
||||||
|
allow: "/",
|
||||||
|
},
|
||||||
|
sitemap: `${site.url}/sitemap.xml`,
|
||||||
|
};
|
||||||
|
}
|
||||||
30
app/sitemap.ts
Normal file
30
app/sitemap.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
|
import { site } from "@/modules/shared/config/site";
|
||||||
|
|
||||||
|
// One entry per actual route. The in-page anchors (/#projekte etc.) are
|
||||||
|
// sections of "/", not separate pages, so they don't get their own entry.
|
||||||
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
const lastModified = new Date();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: site.url,
|
||||||
|
lastModified,
|
||||||
|
changeFrequency: "weekly",
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${site.url}/impressum`,
|
||||||
|
lastModified,
|
||||||
|
changeFrequency: "yearly",
|
||||||
|
priority: 0.3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${site.url}/datenschutz`,
|
||||||
|
lastModified,
|
||||||
|
changeFrequency: "yearly",
|
||||||
|
priority: 0.3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
13
lib/sound.ts
13
lib/sound.ts
|
|
@ -33,7 +33,8 @@ export function setEnabled(enabled: boolean) {
|
||||||
type Engine = { ctx: AudioContext; master: GainNode; noise: AudioBuffer }
|
type Engine = { ctx: AudioContext; master: GainNode; noise: AudioBuffer }
|
||||||
let engine: Engine | null = null
|
let engine: Engine | null = null
|
||||||
|
|
||||||
// Created lazily on first use, which is always inside a user gesture
|
// Created lazily on first use. Hover is not a user activation, so `play`
|
||||||
|
// checks activation before calling this — see there.
|
||||||
function getEngine(): Engine {
|
function getEngine(): Engine {
|
||||||
if (!engine) {
|
if (!engine) {
|
||||||
const ctx = new AudioContext()
|
const ctx = new AudioContext()
|
||||||
|
|
@ -156,6 +157,16 @@ const VOICES: Record<SoundVoice, (engine: Engine, now: number) => void> = {
|
||||||
export function play(voice: SoundVoice, { force = false } = {}) {
|
export function play(voice: SoundVoice, { force = false } = {}) {
|
||||||
if (typeof window === "undefined") return
|
if (typeof window === "undefined") return
|
||||||
if (!force && !isEnabled()) return
|
if (!force && !isEnabled()) return
|
||||||
|
|
||||||
|
// Before the first user activation, a new AudioContext stays "suspended"
|
||||||
|
// and resume() is a no-op. currentTime is then frozen at 0, so every
|
||||||
|
// sound scheduled while hovering (not an activation) would queue up at
|
||||||
|
// t=0 and all fire at once, as one loud burst, once a click resumes it.
|
||||||
|
const activated = navigator.userActivation?.hasBeenActive ?? true
|
||||||
|
if (!engine && !activated) return
|
||||||
|
if (engine && engine.ctx.state !== "running" && !activated) return
|
||||||
|
|
||||||
const current = getEngine()
|
const current = getEngine()
|
||||||
|
if (current.ctx.state !== "running") return
|
||||||
VOICES[voice](current, current.ctx.currentTime)
|
VOICES[voice](current, current.ctx.currentTime)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@ ${el.value}` : text
|
||||||
{...fieldProps("privacy")}
|
{...fieldProps("privacy")}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
required
|
required
|
||||||
|
defaultChecked={state.values?.privacy}
|
||||||
className="mt-0.5 size-5 shrink-0 accent-current"
|
className="mt-0.5 size-5 shrink-0 accent-current"
|
||||||
/>
|
/>
|
||||||
<label htmlFor={fieldId("privacy")} className="text-sm text-fg-muted">
|
<label htmlFor={fieldId("privacy")} className="text-sm text-fg-muted">
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,13 @@ export const contactSchema = z.object({
|
||||||
email: z
|
email: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.pipe(z.email("Bitte gib eine gültige E-Mail-Adresse an.")),
|
// Zod's default email pattern rejects valid Unicode addresses (e.g. müller@example.de)
|
||||||
|
.pipe(
|
||||||
|
z.email({
|
||||||
|
pattern: z.regexes.unicodeEmail,
|
||||||
|
error: "Bitte gib eine gültige E-Mail-Adresse an.",
|
||||||
|
})
|
||||||
|
),
|
||||||
company: z.string().trim().max(120, "Maximal 120 Zeichen.").optional(),
|
company: z.string().trim().max(120, "Maximal 120 Zeichen.").optional(),
|
||||||
message: z
|
message: z
|
||||||
.string()
|
.string()
|
||||||
|
|
@ -28,8 +34,12 @@ export type ContactFormState = {
|
||||||
status: "idle" | "success" | "error"
|
status: "idle" | "success" | "error"
|
||||||
message?: string
|
message?: string
|
||||||
fieldErrors?: Partial<Record<ContactField, string[]>>
|
fieldErrors?: Partial<Record<ContactField, string[]>>
|
||||||
// Echoed back so the form keeps its values after a failed submit
|
// Echoed back so the form keeps its values after a failed submit.
|
||||||
values?: Partial<Record<ContactField, string>>
|
// `privacy` is separate from ContactField's string values: the checkbox
|
||||||
|
// needs a boolean, not the raw "on" the form field submits.
|
||||||
|
values?: Partial<Record<Exclude<ContactField, "privacy">, string>> & {
|
||||||
|
privacy?: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialContactState: ContactFormState = { status: "idle" }
|
export const initialContactState: ContactFormState = { status: "idle" }
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export async function submitContact(
|
||||||
email: raw.email,
|
email: raw.email,
|
||||||
company: raw.company,
|
company: raw.company,
|
||||||
message: raw.message,
|
message: raw.message,
|
||||||
|
privacy: raw.privacy === "on",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,14 @@ export function ContrastTool() {
|
||||||
const [bg, setBg] = useState("#ffffff")
|
const [bg, setBg] = useState("#ffffff")
|
||||||
const [lines, setLines] = useState<OutputLine[] | null>(null)
|
const [lines, setLines] = useState<OutputLine[] | null>(null)
|
||||||
|
|
||||||
const preview = parseHex(fg) && parseHex(bg) ? { color: fg, background: bg } : undefined
|
// parseHex accepts values without "#" (e.g. "1a1a1a"), but CSS doesn't —
|
||||||
|
// build the preview from the parsed RGB channels instead of the raw input
|
||||||
|
const fgRgb = parseHex(fg)
|
||||||
|
const bgRgb = parseHex(bg)
|
||||||
|
const preview =
|
||||||
|
fgRgb && bgRgb
|
||||||
|
? { color: `rgb(${fgRgb.join(" ")})`, background: `rgb(${bgRgb.join(" ")})` }
|
||||||
|
: undefined
|
||||||
|
|
||||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,15 @@ export const site = {
|
||||||
city: "Steinfurt",
|
city: "Steinfurt",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
// Absolute ("/#...") so the links also work from the legal pages.
|
||||||
|
// Lenis still scrolls smoothly when they point to the current page.
|
||||||
export const mainNav = [
|
export const mainNav = [
|
||||||
{ label: "Projekte", href: "#projekte" },
|
{ label: "Projekte", href: "/#projekte" },
|
||||||
{ label: "Leistungen", href: "#leistungen" },
|
{ label: "Leistungen", href: "/#leistungen" },
|
||||||
{ label: "Tools", href: "#tools" },
|
{ label: "Tools", href: "/#tools" },
|
||||||
{ label: "Prozess", href: "#prozess" },
|
{ label: "Prozess", href: "/#prozess" },
|
||||||
{ label: "Preise", href: "#preise" },
|
{ label: "Preise", href: "/#preise" },
|
||||||
{ label: "Über uns", href: "#ueber-uns" },
|
{ label: "Über uns", href: "/#ueber-uns" },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
// Regional terms for local SEO.
|
// Regional terms for local SEO.
|
||||||
|
|
@ -36,5 +38,4 @@ export const regions = [
|
||||||
export const legalNav = [
|
export const legalNav = [
|
||||||
{ label: "Impressum", href: "/impressum" },
|
{ label: "Impressum", href: "/impressum" },
|
||||||
{ label: "Datenschutz", href: "/datenschutz" },
|
{ label: "Datenschutz", href: "/datenschutz" },
|
||||||
{ label: "Barrierefreiheit", href: "/barrierefreiheit" },
|
|
||||||
] as const
|
] as const
|
||||||
|
|
|
||||||
70
modules/shared/ui/components/cookie-consent.tsx
Normal file
70
modules/shared/ui/components/cookie-consent.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
"use client";
|
||||||
|
import { Analytics } from "@vercel/analytics/next";
|
||||||
|
import type { BeforeSendEvent } from "@vercel/analytics";
|
||||||
|
import * as CookieConsent from 'vanilla-cookieconsent';
|
||||||
|
import 'vanilla-cookieconsent/dist/cookieconsent.css';
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
// Re-checked on every single event, so it stays correct for the rest of the
|
||||||
|
// session regardless of the <Analytics> component's own mount state.
|
||||||
|
// Module-level (not created inside the component) so its reference is
|
||||||
|
// stable across renders and Analytics doesn't re-register it needlessly.
|
||||||
|
function gateByConsent(event: BeforeSendEvent) {
|
||||||
|
return CookieConsent.acceptedCategory('analytics') ? event : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CookieBanner() {
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.classList.add('cc--elegant-black');
|
||||||
|
|
||||||
|
CookieConsent.run({
|
||||||
|
guiOptions: {
|
||||||
|
consentModal: { layout: 'box', position: 'bottom right' },
|
||||||
|
preferencesModal: {layout: 'box'},
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
analytics: {}
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
default: 'de',
|
||||||
|
translations: {
|
||||||
|
de: {
|
||||||
|
consentModal: {
|
||||||
|
title: 'Wir nutzen Cookies',
|
||||||
|
description:
|
||||||
|
'Diese Webseite nutzt optionale Cookies für Statistik und Marketing. Mehr dazu in unserer <a href="/datenschutz" target="_blank" rel="noopener">Datenschutzerklärung</a>.',
|
||||||
|
acceptAllBtn: 'Alle akzeptieren',
|
||||||
|
acceptNecessaryBtn: 'Nur notwendige',
|
||||||
|
showPreferencesBtn: 'Einstellungen verwalten',
|
||||||
|
},
|
||||||
|
preferencesModal: {
|
||||||
|
title: 'Cookie-Einstellungen',
|
||||||
|
acceptAllBtn: 'Alle akzeptieren',
|
||||||
|
acceptNecessaryBtn: 'Nur notwendige',
|
||||||
|
savePreferencesBtn: 'Auswahl speichern',
|
||||||
|
sections: [
|
||||||
|
{ title: "Statistik", linkedCategory: 'analytics' }
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/*
|
||||||
|
* Always mounted, regardless of consent: @vercel/analytics injects
|
||||||
|
* its script into <head> on mount and never removes it on unmount
|
||||||
|
* (v2.0.1 has no cleanup), so a script that already loaded would
|
||||||
|
* keep tracking after consent is withdrawn if we unmounted this.
|
||||||
|
* Gating happens per-event in beforeSend instead, which is
|
||||||
|
* re-checked live on every event and therefore stays effective
|
||||||
|
* for the rest of the session, including after withdrawal.
|
||||||
|
*/}
|
||||||
|
<Analytics beforeSend={gateByConsent} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
modules/shared/ui/components/cookie-prefrence-button.tsx
Normal file
16
modules/shared/ui/components/cookie-prefrence-button.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as CookieConsent from "vanilla-cookieconsent";
|
||||||
|
|
||||||
|
|
||||||
|
export default function CookiePrefencesButton({ className = ""}: { className?: string}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => CookieConsent.showPreferences()}
|
||||||
|
className={`${className} bg-transparent border-0 p-0 cursor-pointer text-left `}
|
||||||
|
>
|
||||||
|
Cookie-Einstellungen
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -92,7 +92,7 @@ export function CustomCursor() {
|
||||||
ref={ref}
|
ref={ref}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none fixed top-0 left-0 z-95 will-change-transform",
|
"pointer-events-none fixed top-0 left-0 z-[var(--z-cursor)] will-change-transform",
|
||||||
mode !== "label" && "mix-blend-difference"
|
mode !== "label" && "mix-blend-difference"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export function Preloader() {
|
||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="preloader fixed inset-0 z-100 flex-col justify-between bg-black p-gutter text-white"
|
className="preloader fixed inset-0 z-[var(--z-preloader)] flex-col justify-between bg-black p-gutter text-white"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between font-mono text-label text-ink-400">
|
<div className="flex items-start justify-between font-mono text-label text-ink-400">
|
||||||
<span>Digitalagentur</span>
|
<span>Digitalagentur</span>
|
||||||
|
|
|
||||||
21
modules/shared/ui/components/requested-path.tsx
Normal file
21
modules/shared/ui/components/requested-path.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
|
/** Echoes the URL that didn't resolve, terminal-log style. */
|
||||||
|
export function RequestedPath() {
|
||||||
|
const pathname = usePathname()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p className="mt-8 max-w-[60ch] rounded-2xl border border-white/15 bg-white/5 px-6 py-4 text-left font-mono text-label text-white">
|
||||||
|
<span className="text-ink-400">$ curl </span>
|
||||||
|
{pathname}
|
||||||
|
<br />
|
||||||
|
<span className="text-signal-error">404 Not Found</span>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="ml-1 inline-block h-4 w-2 translate-y-0.5 animate-pulse bg-white"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import { legalNav, mainNav, regions, site } from "@/modules/shared/config/site"
|
||||||
|
|
||||||
import { LiveClock } from "./live-clock"
|
import { LiveClock } from "./live-clock"
|
||||||
import { Magnetic } from "./magnetic"
|
import { Magnetic } from "./magnetic"
|
||||||
|
import CookiePrefencesButton from "./cookie-prefrence-button"
|
||||||
|
|
||||||
export function SiteFooter() {
|
export function SiteFooter() {
|
||||||
const year = new Date().getFullYear()
|
const year = new Date().getFullYear()
|
||||||
|
|
@ -72,6 +73,9 @@ export function SiteFooter() {
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
<li>
|
||||||
|
<CookiePrefencesButton className="hover:text-fg hover:underline text-label"/>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export function SiteHeader() {
|
||||||
const [hidden, setHidden] = useState(false)
|
const [hidden, setHidden] = useState(false)
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
const firstLinkRef = useRef<HTMLAnchorElement>(null)
|
const firstLinkRef = useRef<HTMLAnchorElement>(null)
|
||||||
|
const toggleRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let lastY = window.scrollY
|
let lastY = window.scrollY
|
||||||
|
|
@ -36,7 +37,11 @@ export function SiteHeader() {
|
||||||
if (!menuOpen) return
|
if (!menuOpen) return
|
||||||
firstLinkRef.current?.focus()
|
firstLinkRef.current?.focus()
|
||||||
const onKey = (event: KeyboardEvent) => {
|
const onKey = (event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape") setMenuOpen(false)
|
// Focus would otherwise fall through to <body> once #mobile-menu goes inert
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
setMenuOpen(false)
|
||||||
|
toggleRef.current?.focus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
document.documentElement.style.overflow = "hidden"
|
document.documentElement.style.overflow = "hidden"
|
||||||
window.addEventListener("keydown", onKey)
|
window.addEventListener("keydown", onKey)
|
||||||
|
|
@ -52,13 +57,14 @@ export function SiteHeader() {
|
||||||
<>
|
<>
|
||||||
<header
|
<header
|
||||||
data-hidden={hidden && !menuOpen}
|
data-hidden={hidden && !menuOpen}
|
||||||
className="site-header fixed inset-x-0 top-0 z-85"
|
className="site-header fixed inset-x-0 top-0 z-[var(--z-header)]"
|
||||||
>
|
>
|
||||||
<nav
|
<nav
|
||||||
aria-label="Hauptnavigation"
|
aria-label="Hauptnavigation"
|
||||||
className="container-site grid h-20 grid-cols-[1fr_auto] items-center gap-6 md:h-24 lg:grid-cols-[1fr_auto_1fr]"
|
className="container-site grid h-20 grid-cols-[1fr_auto] items-center gap-6 md:h-24 lg:grid-cols-[1fr_auto_1fr]"
|
||||||
>
|
>
|
||||||
<a href="#top" aria-label="since3 – nach oben" className="w-fit">
|
{/* eslint-disable-next-line @next/next/no-html-link-for-pages -- Plain anchor on purpose: Lenis smooth-scrolls same-page hash links, next/link would scroll as well */}
|
||||||
|
<a href="/#top" aria-label="since3 – zur Startseite" className="w-fit">
|
||||||
<Image
|
<Image
|
||||||
src="/pictures/since3-logo-weiss.svg"
|
src="/pictures/since3-logo-weiss.svg"
|
||||||
alt=""
|
alt=""
|
||||||
|
|
@ -82,13 +88,15 @@ export function SiteHeader() {
|
||||||
<div className="flex items-center justify-end gap-2 md:gap-4">
|
<div className="flex items-center justify-end gap-2 md:gap-4">
|
||||||
<LiveClock className="hidden font-mono text-label tabular-nums opacity-70 xl:block" />
|
<LiveClock className="hidden font-mono text-label tabular-nums opacity-70 xl:block" />
|
||||||
<SoundToggle />
|
<SoundToggle />
|
||||||
|
{/* eslint-disable-next-line @next/next/no-html-link-for-pages -- Plain anchor on purpose: Lenis smooth-scrolls same-page hash links, next/link would scroll as well */}
|
||||||
<a
|
<a
|
||||||
href="#kontakt"
|
href="/#kontakt"
|
||||||
className="hidden h-10 items-center rounded-full border border-white px-5 text-sm font-medium transition-colors duration-300 hover:bg-white hover:text-black sm:inline-flex"
|
className="hidden h-10 items-center rounded-full border border-white px-5 text-sm font-medium transition-colors duration-300 hover:bg-white hover:text-black sm:inline-flex"
|
||||||
>
|
>
|
||||||
Projekt starten
|
Projekt starten
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
|
ref={toggleRef}
|
||||||
type="button"
|
type="button"
|
||||||
data-sound="off"
|
data-sound="off"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -125,13 +133,13 @@ export function SiteHeader() {
|
||||||
data-theme="dark"
|
data-theme="dark"
|
||||||
inert={!menuOpen}
|
inert={!menuOpen}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-80 flex flex-col justify-end pb-10 transition-[clip-path] duration-700 ease-in-out-quart lg:hidden",
|
"fixed inset-0 z-[var(--z-mobile-menu)] flex flex-col justify-end pb-10 transition-[clip-path] duration-700 ease-in-out-quart lg:hidden",
|
||||||
menuOpen ? "[clip-path:inset(0_0_0_0)]" : "[clip-path:inset(0_0_100%_0)]"
|
menuOpen ? "[clip-path:inset(0_0_0_0)]" : "[clip-path:inset(0_0_100%_0)]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<nav aria-label="Mobile Navigation" className="container-site">
|
<nav aria-label="Mobile Navigation" className="container-site">
|
||||||
<ul className="flex flex-col">
|
<ul className="flex flex-col">
|
||||||
{[...mainNav, { label: "Kontakt", href: "#kontakt" }].map((item, index) => (
|
{[...mainNav, { label: "Kontakt", href: "/#kontakt" }].map((item, index) => (
|
||||||
<li key={item.href} className="overflow-clip border-b border-line">
|
<li key={item.href} className="overflow-clip border-b border-line">
|
||||||
<a
|
<a
|
||||||
ref={index === 0 ? firstLinkRef : undefined}
|
ref={index === 0 ? firstLinkRef : undefined}
|
||||||
|
|
|
||||||
50
package-lock.json
generated
50
package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.8.0",
|
"@base-ui/react": "^1.8.0",
|
||||||
|
"@vercel/analytics": "^2.0.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"cn": "^0.4.0",
|
"cn": "^0.4.0",
|
||||||
"lenis": "^1.3.26",
|
"lenis": "^1.3.26",
|
||||||
|
|
@ -19,6 +20,7 @@
|
||||||
"react-dom": "19.2.8",
|
"react-dom": "19.2.8",
|
||||||
"shadcn": "^4.21.0",
|
"shadcn": "^4.21.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"vanilla-cookieconsent": "^3.1.0",
|
||||||
"zod": "^4.6.5"
|
"zod": "^4.6.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -2945,6 +2947,48 @@
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@vercel/analytics": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@remix-run/react": "^2",
|
||||||
|
"@sveltejs/kit": "^1 || ^2",
|
||||||
|
"next": ">= 13",
|
||||||
|
"nuxt": ">= 3",
|
||||||
|
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"svelte": ">= 4",
|
||||||
|
"vue": "^3",
|
||||||
|
"vue-router": "^4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@remix-run/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@sveltejs/kit": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"next": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nuxt": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"svelte": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue-router": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
|
|
@ -9586,6 +9630,12 @@
|
||||||
"node": "^20.17.0 || >=22.9.0"
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vanilla-cookieconsent": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vanilla-cookieconsent/-/vanilla-cookieconsent-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-/McNRtm/3IXzb9dhqMIcbquoU45SzbN2VB+To4jxEPqMmp7uVniP6BhGLjU8MC7ZCDsNQVOp27fhQTM/ruIXAA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.8.0",
|
"@base-ui/react": "^1.8.0",
|
||||||
|
"@vercel/analytics": "^2.0.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"cn": "^0.4.0",
|
"cn": "^0.4.0",
|
||||||
"lenis": "^1.3.26",
|
"lenis": "^1.3.26",
|
||||||
|
|
@ -20,6 +21,7 @@
|
||||||
"react-dom": "19.2.8",
|
"react-dom": "19.2.8",
|
||||||
"shadcn": "^4.21.0",
|
"shadcn": "^4.21.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"vanilla-cookieconsent": "^3.1.0",
|
||||||
"zod": "^4.6.5"
|
"zod": "^4.6.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue