mirror of
https://github.com/Since-3/since3-website.git
synced 2026-10-12 19:51:16 +00:00
added legeal stuff
This commit is contained in:
parent
f059cfb81c
commit
02af696d4f
23 changed files with 979 additions and 21 deletions
444
app/(legal)/datenschutz/page.tsx
Normal file
444
app/(legal)/datenschutz/page.tsx
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
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).
|
||||
Diese Dienste dienen der Messung der Performance unserer Website (z. B. Ladezeiten) sowie der statistischen Auswertung
|
||||
der Nutzung unseres Online-Angebots. 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-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 {
|
||||
|
|
@ -244,6 +256,148 @@ html.lenis body {
|
|||
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 ────────────────────────────────────────────── */
|
||||
|
||||
body::after {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { SoundEffects } from "@/modules/shared/ui/components/sound-effects";
|
|||
|
||||
import { mono, sans } from "./fonts";
|
||||
import "./globals.css";
|
||||
import CookieBanner from "@/modules/shared/ui/components/cookie-consent";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(site.url),
|
||||
|
|
@ -48,7 +49,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
|
|||
<div id="top" />
|
||||
<a
|
||||
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
|
||||
</a>
|
||||
|
|
@ -62,6 +63,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
|
|||
<main id="inhalt" tabIndex={-1} className="outline-none">
|
||||
{children}
|
||||
</main>
|
||||
<CookieBanner/>
|
||||
<SiteFooter />
|
||||
</body>
|
||||
</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 }
|
||||
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 {
|
||||
if (!engine) {
|
||||
const ctx = new AudioContext()
|
||||
|
|
@ -156,6 +157,16 @@ const VOICES: Record<SoundVoice, (engine: Engine, now: number) => void> = {
|
|||
export function play(voice: SoundVoice, { force = false } = {}) {
|
||||
if (typeof window === "undefined") 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()
|
||||
if (current.ctx.state !== "running") return
|
||||
VOICES[voice](current, current.ctx.currentTime)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ ${el.value}` : text
|
|||
{...fieldProps("privacy")}
|
||||
type="checkbox"
|
||||
required
|
||||
defaultChecked={state.values?.privacy}
|
||||
className="mt-0.5 size-5 shrink-0 accent-current"
|
||||
/>
|
||||
<label htmlFor={fieldId("privacy")} className="text-sm text-fg-muted">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ export const contactSchema = z.object({
|
|||
email: z
|
||||
.string()
|
||||
.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(),
|
||||
message: z
|
||||
.string()
|
||||
|
|
@ -28,8 +34,12 @@ export type ContactFormState = {
|
|||
status: "idle" | "success" | "error"
|
||||
message?: string
|
||||
fieldErrors?: Partial<Record<ContactField, string[]>>
|
||||
// Echoed back so the form keeps its values after a failed submit
|
||||
values?: Partial<Record<ContactField, string>>
|
||||
// Echoed back so the form keeps its values after a failed submit.
|
||||
// `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" }
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export async function submitContact(
|
|||
email: raw.email,
|
||||
company: raw.company,
|
||||
message: raw.message,
|
||||
privacy: raw.privacy === "on",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,14 @@ export function ContrastTool() {
|
|||
const [bg, setBg] = useState("#ffffff")
|
||||
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>) => {
|
||||
event.preventDefault()
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ export const site = {
|
|||
city: "Steinfurt",
|
||||
} 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 = [
|
||||
{ label: "Projekte", href: "#projekte" },
|
||||
{ label: "Leistungen", href: "#leistungen" },
|
||||
{ label: "Tools", href: "#tools" },
|
||||
{ label: "Prozess", href: "#prozess" },
|
||||
{ label: "Preise", href: "#preise" },
|
||||
{ label: "Über uns", href: "#ueber-uns" },
|
||||
{ label: "Projekte", href: "/#projekte" },
|
||||
{ label: "Leistungen", href: "/#leistungen" },
|
||||
{ label: "Tools", href: "/#tools" },
|
||||
{ label: "Prozess", href: "/#prozess" },
|
||||
{ label: "Preise", href: "/#preise" },
|
||||
{ label: "Über uns", href: "/#ueber-uns" },
|
||||
] as const
|
||||
|
||||
// Regional terms for local SEO.
|
||||
|
|
@ -36,5 +38,4 @@ export const regions = [
|
|||
export const legalNav = [
|
||||
{ label: "Impressum", href: "/impressum" },
|
||||
{ label: "Datenschutz", href: "/datenschutz" },
|
||||
{ label: "Barrierefreiheit", href: "/barrierefreiheit" },
|
||||
] as const
|
||||
|
|
|
|||
60
modules/shared/ui/components/cookie-consent.tsx
Normal file
60
modules/shared/ui/components/cookie-consent.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"use client";
|
||||
import { Analytics } from "@vercel/analytics/next";
|
||||
import * as CookieConsent from 'vanilla-cookieconsent';
|
||||
import 'vanilla-cookieconsent/dist/cookieconsent.css';
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function CookieBanner() {
|
||||
|
||||
const [analyticsAccepted, setAnalyticsAccepted] = useState(false);
|
||||
|
||||
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',
|
||||
showPreferencesBtn: 'Einstellungen verwalten',
|
||||
},
|
||||
preferencesModal: {
|
||||
title: 'Cookie-Einstellungen',
|
||||
acceptAllBtn: 'Alle akzeptieren',
|
||||
savePreferencesBtn: 'Auswahl speichern',
|
||||
sections: [
|
||||
{ title: "Statistik", linkedCategory: 'analytics' }
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
onConsent: ({ cookie }) => {
|
||||
setAnalyticsAccepted(cookie.categories.includes('analytics'));
|
||||
},
|
||||
onChange: ({ cookie }) => {
|
||||
setAnalyticsAccepted(cookie.categories.includes('analytics'))
|
||||
}
|
||||
})
|
||||
}, []);
|
||||
|
||||
if (!analyticsAccepted) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Analytics/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
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}
|
||||
aria-hidden="true"
|
||||
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"
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export function Preloader() {
|
|||
<div
|
||||
ref={rootRef}
|
||||
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">
|
||||
<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 { Magnetic } from "./magnetic"
|
||||
import CookiePrefencesButton from "./cookie-prefrence-button"
|
||||
|
||||
export function SiteFooter() {
|
||||
const year = new Date().getFullYear()
|
||||
|
|
@ -72,6 +73,9 @@ export function SiteFooter() {
|
|||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<CookiePrefencesButton className="hover:text-fg hover:underline text-label"/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export function SiteHeader() {
|
|||
const [hidden, setHidden] = useState(false)
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const firstLinkRef = useRef<HTMLAnchorElement>(null)
|
||||
const toggleRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let lastY = window.scrollY
|
||||
|
|
@ -36,7 +37,11 @@ export function SiteHeader() {
|
|||
if (!menuOpen) return
|
||||
firstLinkRef.current?.focus()
|
||||
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"
|
||||
window.addEventListener("keydown", onKey)
|
||||
|
|
@ -52,13 +57,14 @@ export function SiteHeader() {
|
|||
<>
|
||||
<header
|
||||
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
|
||||
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]"
|
||||
>
|
||||
<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
|
||||
src="/pictures/since3-logo-weiss.svg"
|
||||
alt=""
|
||||
|
|
@ -82,13 +88,15 @@ export function SiteHeader() {
|
|||
<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" />
|
||||
<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
|
||||
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"
|
||||
>
|
||||
Projekt starten
|
||||
</a>
|
||||
<button
|
||||
ref={toggleRef}
|
||||
type="button"
|
||||
data-sound="off"
|
||||
onClick={() => {
|
||||
|
|
@ -125,13 +133,13 @@ export function SiteHeader() {
|
|||
data-theme="dark"
|
||||
inert={!menuOpen}
|
||||
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)]"
|
||||
)}
|
||||
>
|
||||
<nav aria-label="Mobile Navigation" className="container-site">
|
||||
<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">
|
||||
<a
|
||||
ref={index === 0 ? firstLinkRef : undefined}
|
||||
|
|
|
|||
50
package-lock.json
generated
50
package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.8.0",
|
||||
"@vercel/analytics": "^2.0.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"cn": "^0.4.0",
|
||||
"lenis": "^1.3.26",
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
"react-dom": "19.2.8",
|
||||
"shadcn": "^4.21.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vanilla-cookieconsent": "^3.1.0",
|
||||
"zod": "^4.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -2945,6 +2947,48 @@
|
|||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
|
|
@ -9586,6 +9630,12 @@
|
|||
"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": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.8.0",
|
||||
"@vercel/analytics": "^2.0.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"cn": "^0.4.0",
|
||||
"lenis": "^1.3.26",
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
"react-dom": "19.2.8",
|
||||
"shadcn": "^4.21.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vanilla-cookieconsent": "^3.1.0",
|
||||
"zod": "^4.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue