diff --git a/nginx.conf b/nginx.conf index 137b9b7..734a92b 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,5 +1,16 @@ +# Access log without the client address. +# +# The default format writes $remote_addr and X-Forwarded-For, so a static page +# that needs no personal data at all was storing the IP of everyone who looked +# at it. This format keeps what is useful for operating the site — when, what, +# which status, how big, from where, with what — and drops the one field that +# identifies a person. Data minimisation is cheaper than a retention policy. +log_format noip '[$time_local] "$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + server { listen 80; + access_log /var/log/nginx/access.log noip; server_name _; root /usr/share/nginx/html; index index.html; diff --git a/src/components/Legal.astro b/src/components/Legal.astro new file mode 100644 index 0000000..f21356d --- /dev/null +++ b/src/components/Legal.astro @@ -0,0 +1,77 @@ +--- +import '@fontsource/space-grotesk/400.css'; +import '@fontsource/space-grotesk/500.css'; +import '@fontsource/space-grotesk/700.css'; +import '@fontsource/space-mono/400.css'; +import '@fontsource/space-mono/700.css'; +import '../styles/tokens.css'; +import '../styles/site.css'; +import type { LegalPage } from '../lib/legal'; + +interface Props { + page: LegalPage; + lang: 'en' | 'de'; + canonical: string; + otherHref: string; + otherLabel: string; + backHref: string; + backLabel: string; + siblingHref: string; + siblingLabel: string; +} +const { page, lang, canonical, otherHref, otherLabel, backHref, backLabel, siblingHref, siblingLabel } = Astro.props; +const home = lang === 'de' ? '/de/' : '/'; +--- + + + + + + + + {page.title} · Klarbild + + + + +
+
+ klarbild + +
+
+ +
+ {page.updated} +

{page.title}

+ {page.intro &&

{page.intro}

} + + {page.blocks.map((b) => ( +
+ {b.h &&

{b.h}

} + {b.rows && ( +
+ {b.rows.map(([k, v]) => (<>
{k}
{v}
))} +
+ )} + {b.p &&

{b.p}

} + {b.html &&

} +

+ ))} + +

{siblingLabel}

+
+ + + + diff --git a/src/components/Page.astro b/src/components/Page.astro index 54743b8..5f25604 100644 --- a/src/components/Page.astro +++ b/src/components/Page.astro @@ -380,6 +380,8 @@ docker compose logs app {c.install.comment2} klarbild GitHub AGPL-3.0 + {c.footer.imprint} + {c.footer.privacy} {c.otherLabel === 'DE' ? 'Deutsch' : 'English'} {c.footer.fine} Heidrich Digital. diff --git a/src/lib/copy.ts b/src/lib/copy.ts index 3a780c1..95e6acf 100644 --- a/src/lib/copy.ts +++ b/src/lib/copy.ts @@ -85,7 +85,7 @@ export interface Copy { }; install: { kicker: string; h2: string; p1: string; p2: string; cta1: string; cta2: string; comment1: string; comment2: string; stack: string }; privacy: { kicker: string; h2: string; lead: string; body: string }; - footer: { fine: string }; + footer: { fine: string; imprint: string; privacy: string; imprintHref: string; privacyHref: string }; } const TASKS_EN = [ @@ -276,7 +276,11 @@ export const EN: Copy = { lead: 'The print studio never makes an outbound request at all. With the image module on, only what you hand the model leaves the building, and the key is yours.', body: 'Share links are built to be careful rather than convenient: they expire after thirty days by default, can be revoked, can carry a passphrase, and are noindex. Access hangs on the link rather than on the picture, so guessing an image id returns a 404. The view counter records when and with what, never who — the address is hashed with a server secret and the link id, one row per visitor per hour, deleted after ninety days.', }, - footer: { fine: 'A demo page. The layout engine is real; the pictures are outlines. Built by' }, + footer: { + fine: 'A demo page. The layout engine is real; the pictures are outlines. Built by', + imprint: 'Imprint', privacy: 'Privacy', + imprintHref: '/imprint/', privacyHref: '/privacy/', + }, }; export const DE: Copy = { @@ -388,7 +392,11 @@ export const DE: Copy = { lead: 'Das Druckstudio macht überhaupt keine Anfrage nach außen. Mit eingeschaltetem Bildmodul verlässt nur das Haus, was du dem Modell gibst — und der Schlüssel gehört dir.', body: 'Teilen-Links sind auf Vorsicht gebaut, nicht auf Bequemlichkeit: Sie laufen standardmäßig nach dreißig Tagen ab, lassen sich widerrufen, können ein Zugangswort tragen und sind noindex. Der Zugriff hängt am Link, nicht am Bild — eine geratene Bildkennung ergibt 404. Der Zähler hält fest, wann und womit, nie wer: Die Adresse wird mit einem Server-Geheimnis und der Link-Kennung gehasht, eine Zeile je Besucher und Stunde, gelöscht nach neunzig Tagen.', }, - footer: { fine: 'Eine Demoseite. Der Layout-Kern ist echt, die Bilder sind Umrisse. Gebaut von' }, + footer: { + fine: 'Eine Demoseite. Der Layout-Kern ist echt, die Bilder sind Umrisse. Gebaut von', + imprint: 'Impressum', privacy: 'Datenschutz', + imprintHref: '/de/impressum/', privacyHref: '/de/datenschutz/', + }, }; export const COPY: Record = { en: EN, de: DE }; diff --git a/src/lib/legal.ts b/src/lib/legal.ts new file mode 100644 index 0000000..e49296f --- /dev/null +++ b/src/lib/legal.ts @@ -0,0 +1,141 @@ +/** + * Imprint and privacy notice, both languages. + * + * The facts here are checked, not assumed: the hosting details and the + * processing agreement come from the Art. 28 contract with Hetzner, the server + * location from the provider's own API (Nuremberg, DE), and the "no third-party + * requests, no cookies, no storage" statement from a measurement of the live + * page. The German version is the authoritative one; the English one is a + * courtesy translation. + * + * This is a plain notice written from verified facts, not legal advice. + */ + +export interface LegalBlock { h?: string; p?: string; html?: string; rows?: [string, string][] } +export interface LegalPage { title: string; intro?: string; updated: string; blocks: LegalBlock[] } + +const OWNER = { + firm: 'Till Heidrich – Content Creation', + street: 'Rotdornweg 17', + city: '21255 Tostedt', + country: 'Deutschland', + phone: '0157 73953819', + mail: 'mail@tillheidrich.de', +}; + +/* ------------------------------------------------------------------ imprint */ + +export const IMPRINT_DE: LegalPage = { + title: 'Impressum', + updated: 'Stand: 15. September 2026', + blocks: [ + { h: 'Angaben gemäß § 5 DDG', rows: [ + ['Anbieter', OWNER.firm], + ['Anschrift', `${OWNER.street}, ${OWNER.city}, ${OWNER.country}`], + ['Telefon', OWNER.phone], + ['E-Mail', OWNER.mail], + ] }, + { h: 'Umsatzsteuer', + p: 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet. Eine Umsatzsteuer-Identifikationsnummer nach § 27a UStG besteht daher nicht.' }, + { h: 'Redaktionell verantwortlich (§ 18 Abs. 2 MStV)', + p: `Till Heidrich, ${OWNER.street}, ${OWNER.city}` }, + { h: 'Was diese Seite ist', + p: 'Eine Produkt- und Demoseite für Klarbild, eine quelloffene Software unter der GNU AGPL-3.0. Sie verkauft nichts, sammelt keine Anfragen und schließt keine Verträge. Der Quellcode liegt öffentlich auf GitHub.' }, + { h: 'EU-Streitschlichtung', + html: 'Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung bereit: ec.europa.eu/consumers/odr. Wir sind nicht bereit und nicht verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen (§ 36 VSBG).' }, + { h: 'Haftung für Inhalte', + p: 'Als Diensteanbieter sind wir für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich (§ 7 Abs. 1 DDG). Nach §§ 8 bis 10 DDG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich; bei Bekanntwerden entsprechender Rechtsverletzungen entfernen wir diese Inhalte umgehend.' }, + { h: 'Haftung für Links', + p: 'Diese Seite verlinkt auf externe Seiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Für diese fremden Inhalte kann keine Gewähr übernommen werden; verantwortlich ist stets der jeweilige Anbieter oder Betreiber. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße geprüft, rechtswidrige Inhalte waren nicht erkennbar. Eine dauerhafte inhaltliche Kontrolle ohne konkrete Anhaltspunkte einer Rechtsverletzung ist nicht zumutbar; bei Bekanntwerden von Rechtsverletzungen entfernen wir solche Links umgehend.' }, + { h: 'Urheberrecht', + p: 'Die Software Klarbild steht unter der GNU Affero General Public License, Version 3 — sie darf unter deren Bedingungen genutzt, verändert und weitergegeben werden. Die Texte, Abbildungen und das Gestaltungsraster dieser Seite unterliegen dem deutschen Urheberrecht. Beiträge Dritter sind als solche gekennzeichnet.' }, + ], +}; + +export const IMPRINT_EN: LegalPage = { + title: 'Imprint', + updated: 'Last updated: 15 September 2026', + intro: 'German law requires this notice. The German version is the authoritative one; this is a courtesy translation.', + blocks: [ + { h: 'Information pursuant to § 5 DDG', rows: [ + ['Provider', OWNER.firm], + ['Address', `${OWNER.street}, ${OWNER.city}, Germany`], + ['Phone', OWNER.phone], + ['Email', OWNER.mail], + ] }, + { h: 'VAT', + p: 'Under § 19 of the German VAT Act no VAT is charged, and there is therefore no VAT identification number under § 27a.' }, + { h: 'Responsible for editorial content (§ 18 (2) MStV)', + p: `Till Heidrich, ${OWNER.street}, ${OWNER.city}, Germany` }, + { h: 'What this page is', + p: 'A product and demo page for Klarbild, open-source software under the GNU AGPL-3.0. It sells nothing, collects no enquiries and concludes no contracts. The source is public on GitHub.' }, + { h: 'Online dispute resolution', + html: 'The European Commission provides a platform for online dispute resolution: ec.europa.eu/consumers/odr. We are neither willing nor obliged to take part in dispute resolution proceedings before a consumer arbitration board.' }, + { h: 'Liability for content and links', + p: 'We are responsible for our own content under the general laws. We are not obliged to monitor third-party information we transmit or store, nor to investigate circumstances indicating unlawful activity. This page links to external sites whose content we do not control; responsibility rests with their respective operators. We remove content or links promptly once we become aware of an actual infringement.' }, + { h: 'Copyright', + p: 'The Klarbild software is licensed under the GNU Affero General Public License, version 3, and may be used, modified and redistributed under its terms. The text, images and layout of this page are protected by German copyright law.' }, + ], +}; + +/* ------------------------------------------------------------------ privacy */ + +export const PRIVACY_DE: LegalPage = { + title: 'Datenschutzerklärung', + updated: 'Stand: 15. September 2026', + intro: 'Diese Seite ist eine statische Produktseite. Sie setzt keine Cookies, bindet nichts von Dritten ein, misst nichts und speichert nichts in Ihrem Browser. Was unten steht, ist nachgemessen, nicht angenommen.', + blocks: [ + { h: 'Verantwortlicher', rows: [ + ['Name', OWNER.firm], + ['Anschrift', `${OWNER.street}, ${OWNER.city}, ${OWNER.country}`], + ['Telefon', OWNER.phone], + ['E-Mail', OWNER.mail], + ] }, + { h: 'Was diese Seite nicht tut', + html: 'Kein Cookie — auch kein „technisch notwendiges". Kein Analysewerkzeug, keine Reichweitenmessung, kein Pixel, kein Einwilligungsbanner, weil es nichts gibt, worin einzuwilligen wäre. Keine Schriftart, kein Skript und kein Bild von einer fremden Domain: die Schriften sind mitgeliefert, alles andere liegt auf demselben Server. Nachgeprüft am 15.09.2026 mit einem Browser, der jede ausgehende Anfrage mitschreibt — sie gingen ausnahmslos an klarbild-demo.heidrich-digital.de. Kein Kontaktformular, keine Anmeldung, kein Newsletter.' }, + { h: 'Der Bogen- und der Vorlagen-Baukasten', + p: 'Beide rechnen vollständig in Ihrem Browser. Es gibt hinter dieser Seite kein Programm, das Bilder entgegennimmt. Das PDF, das der Bogen-Baukasten anbietet, entsteht ebenfalls in Ihrem Browser und wird nirgendwohin übertragen. Ihre Eingaben — Papier, Maß, Anzahl — verlassen das Gerät nicht und werden auch dort nicht gespeichert.' }, + { h: 'Hosting und Auftragsverarbeitung', + html: 'Die Seite läuft auf einem Server der Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen. Standort des Servers ist Nürnberg, Deutschland. Mit Hetzner besteht ein Vertrag über die Auftragsverarbeitung nach Art. 28 DSGVO. Die vertraglich vereinbarte Verarbeitung findet ausschließlich in der EU beziehungsweise im EWR statt; eine Verlagerung in ein Drittland bedürfte unserer vorherigen Zustimmung. Hetzner hat einen Datenschutzbeauftragten bestellt (data-protection@hetzner.com) und ist verpflichtet, uns Datenschutzverletzungen unverzüglich zu melden. Die Rechenzentren sind nach ISO/IEC 27001 zertifiziert und verfügen über ein BSI-C5-Typ-2-Testat.' }, + { h: 'Zugriffsdaten', + html: 'Damit eine Seite ausgeliefert werden kann, muss Ihr Gerät eine Verbindung zum Server aufbauen; dabei ist Ihre IP-Adresse technisch unvermeidbar im Spiel. Der Webserver dieser Seite protokolliert sie nicht: das Zugriffsprotokoll enthält Zeitpunkt, angefragte Datei, Statuscode, übertragene Größe, Verweisseite und Browserkennung — die Adressfelder sind aus dem Protokollformat entfernt. Der vorgelagerte Reverse-Proxy verarbeitet Verbindungsdaten, soweit das für die Auslieferung und die Abwehr von Angriffen erforderlich ist. Diese Daten werden nicht ausgewertet, nicht mit anderen Daten zusammengeführt und nicht zur Identifizierung von Personen verwendet. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; das berechtigte Interesse liegt im sicheren Betrieb der Seite.' }, + { h: 'Links auf andere Seiten', + p: 'Diese Seite verlinkt auf GitHub (GitHub, Inc., ein Unternehmen von Microsoft) und auf heidrich-digital.de. Solange Sie nicht klicken, entsteht keine Verbindung dorthin — es sind gewöhnliche Links, keine eingebetteten Inhalte. Sobald Sie klicken, gilt die Datenschutzerklärung der jeweiligen Seite. Bei GitHub ist dabei eine Übermittlung in die USA möglich.' }, + { h: 'Ihre Rechte', + p: 'Sie haben das Recht auf Auskunft (Art. 15 DSGVO), Berichtigung (Art. 16), Löschung (Art. 17), Einschränkung der Verarbeitung (Art. 18), Datenübertragbarkeit (Art. 20) und Widerspruch gegen eine Verarbeitung auf Grundlage berechtigter Interessen (Art. 21). Wenden Sie sich dafür an die oben genannte Adresse. In der Praxis liegt hier allerdings nichts, wozu wir Auskunft geben könnten — es werden keine personenbezogenen Daten gespeichert, die einer Person zuzuordnen wären.' }, + { h: 'Beschwerderecht', + p: 'Sie können sich bei einer Datenschutz-Aufsichtsbehörde beschweren. Für uns zuständig ist die Landesbeauftragte für den Datenschutz Niedersachsen, Prinzenstraße 5, 30159 Hannover.' }, + { h: 'Änderungen', + p: 'Ändert sich etwas an der Seite, ändert sich diese Erklärung mit. Das Datum oben sagt, wann sie zuletzt angefasst wurde.' }, + ], +}; + +export const PRIVACY_EN: LegalPage = { + title: 'Privacy', + updated: 'Last updated: 15 September 2026', + intro: 'This is a static product page. It sets no cookies, embeds nothing from anyone else, measures nothing and stores nothing in your browser. What follows was measured, not assumed. The German version is the authoritative one.', + blocks: [ + { h: 'Controller', rows: [ + ['Name', OWNER.firm], + ['Address', `${OWNER.street}, ${OWNER.city}, Germany`], + ['Phone', OWNER.phone], + ['Email', OWNER.mail], + ] }, + { h: 'What this page does not do', + html: 'No cookie — not even a “strictly necessary” one. No analytics, no pixel, no consent banner, because there is nothing to consent to. No font, script or image from another domain: the fonts ship with the page and everything else sits on the same server. Checked on 15 September 2026 with a browser that records every outgoing request — all of them went to klarbild-demo.heidrich-digital.de. No contact form, no sign-up, no newsletter.' }, + { h: 'The sheet and preset builders', + p: 'Both run entirely in your browser. There is no program behind this page that accepts images. The PDF the sheet builder offers is assembled in your browser and transmitted nowhere. Your inputs — paper, size, count — never leave the device, and are not stored on it either.' }, + { h: 'Hosting and data processing agreement', + html: 'The page runs on a server operated by Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany. The server is located in Nuremberg, Germany. A data processing agreement under Art. 28 GDPR is in place with Hetzner. The processing covered by that contract takes place exclusively within the EU/EEA; moving it to a third country would require our prior consent. Hetzner has appointed a data protection officer (data-protection@hetzner.com) and is obliged to report data breaches to us without undue delay. The data centres are certified to ISO/IEC 27001 and hold a BSI C5 Type 2 attestation.' }, + { h: 'Access data', + html: 'Delivering a page requires your device to open a connection, which technically involves your IP address. This site\'s web server does not log it: the access log records the time, the file requested, the status code, the size, the referrer and the browser string — the address fields have been removed from the log format. The reverse proxy in front of it processes connection data as far as delivery and attack mitigation require. That data is not analysed, not combined with anything else and not used to identify anyone. The legal basis is Art. 6(1)(f) GDPR; the legitimate interest is operating the site securely.' }, + { h: 'Links to other sites', + p: 'This page links to GitHub (GitHub, Inc., a Microsoft company) and to heidrich-digital.de. Until you click, no connection is made — these are ordinary links, not embedded content. Once you click, the privacy notice of that site applies; with GitHub this may involve a transfer to the United States.' }, + { h: 'Your rights', + p: 'You have the right of access (Art. 15 GDPR), rectification (Art. 16), erasure (Art. 17), restriction (Art. 18), portability (Art. 20) and to object to processing based on legitimate interests (Art. 21). Write to the address above. In practice there is nothing here to give access to — no personal data attributable to a person is stored.' }, + { h: 'Right to complain', + p: 'You may lodge a complaint with a supervisory authority. Ours is the Data Protection Commissioner of Lower Saxony, Prinzenstraße 5, 30159 Hannover, Germany.' }, + { h: 'Changes', + p: 'If the page changes, this notice changes with it. The date above says when it was last touched.' }, + ], +}; diff --git a/src/pages/de/datenschutz.astro b/src/pages/de/datenschutz.astro new file mode 100644 index 0000000..522d3a7 --- /dev/null +++ b/src/pages/de/datenschutz.astro @@ -0,0 +1,9 @@ +--- +import Legal from '../../components/Legal.astro'; +import { PRIVACY_DE } from '../../lib/legal'; +--- + diff --git a/src/pages/de/impressum.astro b/src/pages/de/impressum.astro new file mode 100644 index 0000000..7420976 --- /dev/null +++ b/src/pages/de/impressum.astro @@ -0,0 +1,9 @@ +--- +import Legal from '../../components/Legal.astro'; +import { IMPRINT_DE } from '../../lib/legal'; +--- + diff --git a/src/pages/imprint.astro b/src/pages/imprint.astro new file mode 100644 index 0000000..3a3af39 --- /dev/null +++ b/src/pages/imprint.astro @@ -0,0 +1,9 @@ +--- +import Legal from '../components/Legal.astro'; +import { IMPRINT_EN } from '../lib/legal'; +--- + diff --git a/src/pages/privacy.astro b/src/pages/privacy.astro new file mode 100644 index 0000000..b6ecda2 --- /dev/null +++ b/src/pages/privacy.astro @@ -0,0 +1,9 @@ +--- +import Legal from '../components/Legal.astro'; +import { PRIVACY_EN } from '../lib/legal'; +--- + diff --git a/src/styles/site.css b/src/styles/site.css index c8f7bef..8e9e843 100644 --- a/src/styles/site.css +++ b/src/styles/site.css @@ -245,3 +245,24 @@ footer .fine { font-size: 0.8rem; color: var(--ink-soft); margin-left: auto; } .mod-after { display: grid; gap: 22px; margin-top: 26px; align-items: start; } @media (min-width: 880px) { .mod-after { grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr); gap: 40px; } } .stackline { font-size: .84rem; color: var(--ink-soft); margin-top: 14px; } + +/* ---------------------------------------------------------------- legal pages */ + +.legal { position: relative; z-index: 2; max-width: 74ch; + padding: clamp(40px, 7vw, 76px) 0 clamp(48px, 8vw, 96px); } +.legal h1 { font-size: clamp(2rem, 5vw, 3rem); margin-bottom: 8px; } +.legal .lead { margin: 18px 0 0; } +.lblock { margin-top: clamp(28px, 4vw, 40px); } +.lblock h2 { font-size: clamp(1.1rem, 2vw, 1.3rem); margin-bottom: 10px; } +.lblock p { color: var(--ink-soft); line-height: 1.7; margin: 0 0 .9em; } +.lblock p:last-child { margin-bottom: 0; } +.lblock code { background: var(--card); border: 1px solid var(--line); + border-radius: 3px; padding: 1px 5px; font-size: .86em; } +.lrows { display: grid; gap: 1px; margin: 0 0 14px; background: var(--line); + border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; } +@media (min-width: 520px) { .lrows { grid-template-columns: 12rem minmax(0, 1fr); } } +.lrows dt { background: var(--card); padding: 9px 12px; font-family: var(--font-mono); + font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft); + display: flex; align-items: center; } +.lrows dd { background: var(--card); padding: 9px 12px; margin: 0; font-size: .95rem; } +.lsibling { margin-top: clamp(32px, 5vw, 48px); padding-top: 20px; border-top: 1px solid var(--line); }