diff --git a/docs/Bilder-und-Ton-Nutzungsrechte.md b/docs/Bilder-und-Ton-Nutzungsrechte.md
new file mode 100644
index 0000000..2a92b16
--- /dev/null
+++ b/docs/Bilder-und-Ton-Nutzungsrechte.md
@@ -0,0 +1,127 @@
+# Bilder, Tonaufnahmen und Nutzungsrechte
+
+Stand: 4. Oktober 2026. Grundlage der Änderungen: Repository-Commit `7409072`.
+
+## Ergebnis der Recherche
+
+Die Betreiber können Nutzungsrechte erhalten; das Urheberrecht selbst bleibt
+bei den Urhebern (§§ 29 und 31 UrhG). Eine Nutzungserlaubnis und die
+Urheberbezeichnung sind verschiedene Fragen. Die Übertragung von Nutzungsrechten
+beseitigt das Recht auf Anerkennung der Urheberschaft aus § 13 UrhG nicht.
+Ein pauschaler Verzicht auf Namensnennung ist deshalb nicht Bestandteil dieses
+Entwurfs. Ein BGH-Fall zu professionellen Stockfotografen begründet keine
+allgemeine Erlaubnis, die Namen von Kindern oder anderen Beitragenden wegzulassen.
+
+Der eigenständig formulierte Entwurf in `public/index.html` begrenzt die
+Rechteeinräumung auf Wegwichtel: Prüfung, Bereitstellung der Strecken, Anzeige,
+Wiedergabe, technisch notwendige Anpassungen und die vorgesehene Offline-Nutzung.
+Das einfache Nutzungsrecht schließt den Urheber nicht von eigener Nutzung aus.
+Eine räumliche Beschränkung wäre für öffentlich erreichbare Inhalte unpraktisch;
+inhaltlich bleibt die Erlaubnis auf diesen App-Zweck begrenzt. Werbung außerhalb
+der App und eigenständiger Verkauf sind nicht umfasst.
+
+Die Datenschutzerklärung informiert getrennt über personenbezogene Aufnahmen,
+Öffentlichkeit, Speicherung, Einwilligungen und Widerruf. Sie ist kein
+Lizenzvertrag. Eine Erlaubnis des Fotografen ersetzt nicht die Rechte einer
+abgebildeten Person; bei Tonaufnahmen müssen auch Rechte an Musik, Text,
+Darbietung und Aufnahme geklärt sein. Bei minderjährigen Urhebern ist die
+Zustimmung der gesetzlichen Vertreter zu prüfen (§ 107 BGB).
+
+## Umsetzung in der App
+
+Unter Informationen gibt es die neue Nutzungsvereinbarung. Sie und ihre
+Dialogüberschrift bleiben ebenso wie Impressum und Datenschutz statisch auf
+Deutsch. Nur die Menübeschriftung und der verständliche Hinweis im Editor
+werden übersetzt. Beim Streckenformular kann die Vereinbarung vor dem
+Einsenden geöffnet werden. Das lokale Speichern räumt keine Rechte ein.
+
+Der Text ist ein **prüfbarer Entwurf**, keine Zusicherung einer rechtssicheren
+AGB-Regelung. Es wurde noch kein System implementiert, das Zustimmung, Version
+der Vereinbarung und Urheberbezeichnung je Aufnahme erfasst und serverseitig
+prüft. Der Hinweis im Editor allein ersetzt diesen Nachweis nicht. Bestehende
+Aufnahmen erhalten durch die Codeänderung keine nachträgliche Lizenz.
+Die notwendigen Arbeiten stehen in
+[TODO-MEDIENRECHTE.md](agents/TODO-MEDIENRECHTE.md).
+
+Vor verbindlicher Verwendung sind insbesondere diese Platzhalter zu ersetzen:
+
+- vollständiger Name und Anschrift des tatsächlichen Betreibers als Lizenznehmer;
+- Kontakt für Rechtefragen, Widerruf und Entfernung;
+- Rechtsgrundlagen, Empfänger, Hostinganbieter sowie Lösch- und Sicherungsfristen.
+
+Außerdem müssen die Texte und das Zustimmungsverfahren zum tatsächlichen
+Betriebsmodell der Schule passen und rechtlich geprüft werden. Die bereits
+vorhandenen Kontaktangaben im Impressum wurden nicht verändert. Die frühere
+pauschale Rechtezuordnung zur Schule wurde durch die Trennung von Urheberschaft
+und Nutzungsrechten ersetzt.
+
+## HTTP-Rechtehinweise
+
+`X-Copyright` ist ein HTTP-Antwortheader, kein HTML-Element. Die Werte stehen
+zentral in `src/services/copyright.js` und enthalten für zuverlässige
+Übertragung nur ASCII-Zeichen.
+
+- Hochgeladene Streckenbilder: Hinweis auf die Rechteeinräumung **unter Zustimmung**
+ zur Nutzungsvereinbarung und den Fortbestand der Urheberrechte. Der Wortlaut
+ beschreibt die Regel; er ist kein Beweis, dass eine konkrete Person tatsächlich
+ berechtigt war oder zugestimmt hat. Das gilt auch für alte Uploads.
+- Dateien unter `public/images/group-symbols/`: eigener Quellenhinweis auf Envato
+ mit dem vom Betreiber angegebenen Eintrag.
+- Andere statische Bilder einschließlich SVG und ICO: allgemeiner Hinweis auf
+ die jeweilige Lizenz; keine Behauptung einer Lizenz durch Nutzer-Uploads.
+- JSON-Metadaten, HTML, CSS und JavaScript erhalten keinen Bild-Rechteheader.
+
+Zusätzliche Hinweise sind getrennt: `X-Copyright-Notice` erklärt bei allen
+Bildantworten die Grenzen des technischen Hinweises; `X-Copyright-Source`
+enthält bei den Gruppensymbolen den Envato-Link. Persönliche Kontaktdaten
+werden dabei nicht öffentlich in Headern ausgegeben.
+
+Die Hinweise werden auch für HEAD, Teilabrufe und unveränderte Antworten mit
+Status 304 ausgegeben. Dieselben Node-Endpunkte werden über den geschützten
+Moderationspfad verwendet. Leitet Nginx statische Dateien unmittelbar aus,
+muss der Header auch dort konfiguriert werden; die Node-Einstellungen greifen
+nur für Antworten, die Node ausliefert.
+
+Der Header ersetzt **keine sichtbare Urheberbezeichnung**, macht ein Bild
+nicht kopiergeschützt und räumt Besuchern keine freie Weiterverwendung ein.
+Die vorhandene Offline-Speicherung bewahrt Antwortheader. Bereits gespeicherte
+App-Kopien müssen neu gespeichert werden, um die neuen Hinweise zu enthalten.
+
+## Envato-Gruppensymbole
+
+Quelle laut Betreiber:
+https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077
+
+Der Eintrag war beim Rechercheabruf nicht zugänglich. Deshalb wurde kein
+Urhebername oder individueller Lizenzstatus aus dem Eintrag behauptet.
+„Quelle: Envato“ bezeichnet die Plattform; es ist keine Behauptung, Envato sei
+selbst Urheber der Grafik. Die tatsächliche Lizenz und der Lizenznachweis für
+Wegwichtel müssen vom Betreiber geprüft und aufbewahrt werden. Auflagen des
+konkreten Downloads sind auch bei öffentlich ausgelieferten PNGs und ihrer
+Offline-Nutzung zu berücksichtigen.
+
+Die vorhandenen Grafikdateien wurden nicht verändert und gehören nicht zum
+Änderungsarchiv. Der Envato-Link steht zusätzlich bei den Bildquellen im
+Impressum.
+
+## Quellen
+
+Gesetzliche Grundlagen:
+
+- [§ 13 UrhG – Anerkennung der Urheberschaft](https://www.gesetze-im-internet.de/urhg/__13.html)
+- [§ 29 UrhG – Rechtsgeschäfte über das Urheberrecht](https://www.gesetze-im-internet.de/urhg/__29.html)
+- [§ 31 UrhG – Einräumung von Nutzungsrechten](https://www.gesetze-im-internet.de/urhg/__31.html)
+- [§ 305 BGB – Einbeziehung von AGB](https://www.gesetze-im-internet.de/bgb/__305.html)
+- [§ 107 BGB – Zustimmung bei Minderjährigen](https://www.gesetze-im-internet.de/bgb/__107.html)
+
+Rechtsinformationen und Datenschutz:
+
+- [eRecht24: Nutzungsrechte](https://www.e-recht24.de/urheberrecht/13261-nutzungsrechte.html)
+- [IT-Recht Kanzlei: Bildnachweise im Impressum](https://www.it-recht-kanzlei.de/urheber-foto-bildnachweis-impressum.html)
+- [WBS.LEGAL: BGH zum Verzicht auf Namensnennung in AGB](https://www.wbs.legal/urheberrecht/bgh-verzicht-auf-namensnennung-des-urhebers-durch-agb-ist-grundsaetzlich-moeglich-69950/)
+- [VG Bild-Kunst: Nutzungsrechte](https://www.bildkunst.de/lizenzen/bildrechte_klaeren/nutzungsrechte/)
+- [LfDI Baden-Württemberg: Veröffentlichung von Fotos](https://www.baden-wuerttemberg.datenschutz.de/faq-veroeffentlichung-von-fotos-speziell-fuer-vereine/)
+- [Envato: Lizenz-FAQ](https://help.elements.envato.com/hc/en-us/articles/360000629346-Envato-Elements-License-FAQ)
+
+Die fremden Muster wurden nicht kopiert. Der App-Entwurf ist für den hier
+beschriebenen Zweck eigenständig formuliert.
diff --git a/docs/PUBLICATION-CHECKLIST.md b/docs/PUBLICATION-CHECKLIST.md
index fb7d63a..37110cd 100644
--- a/docs/PUBLICATION-CHECKLIST.md
+++ b/docs/PUBLICATION-CHECKLIST.md
@@ -26,6 +26,10 @@ Die in der Anwendung enthaltenen Texte sind Vorlagen und ersetzen keine Prüfung
- Einwilligungen der Sorgeberechtigten für erkennbare Kinder
- Freigaben für Sprachaufnahmen und Namensnennungen
- dokumentierte Nutzungsrechte an Bildern, Texten, Tonaufnahmen und Routendaten
+- geprüfte Nutzungsvereinbarung mit identifiziertem Betreiber und belegbarer Zustimmung
+- passende Urheberbezeichnung je Bild und Tonaufnahme, sichtbar bei der Aufnahme
+- getrennte Nachweise für personenbezogene Einwilligungen und urheberrechtliche Lizenzen
+- konkreter Lizenznachweis für die Envato-Gruppensymbole
- redaktionelle Prüfung vor Veröffentlichung
- festgelegtes Verfahren für Korrektur- und Löschanfragen
@@ -41,3 +45,7 @@ Die in der Anwendung enthaltenen Texte sind Vorlagen und ersetzen keine Prüfung
## Konfiguration der Vorlagentexte
In `public/index.html` sind alle mit eckigen Klammern gekennzeichneten Platzhalter zu ersetzen. Nicht benötigte Abschnitte sind zu entfernen; zusätzliche gesetzliche oder organisatorische Pflichtangaben sind zu ergänzen.
+
+Die Formulierungen und offenen Schritte stehen in
+[Bilder-und-Ton-Nutzungsrechte.md](Bilder-und-Ton-Nutzungsrechte.md) und
+[TODO-MEDIENRECHTE.md](agents/TODO-MEDIENRECHTE.md).
diff --git a/docs/agents/AGENTS.md b/docs/agents/AGENTS.md
index 8e66ba1..aa20a75 100644
--- a/docs/agents/AGENTS.md
+++ b/docs/agents/AGENTS.md
@@ -56,7 +56,7 @@ Die App richtet sich an Grundschulkinder und Menschen ohne technische Vorkenntni
- Alle eigenen Bedienungstexte liegen zentral in der Klasse `WegwichtelTexts`
in `public/js/texts-de.js`. Keine neuen sichtbaren Texte direkt in HTML oder
Funktionscode schreiben.
-- Ausnahme: Impressum und Datenschutzerklärung bleiben ausschließlich statisch
+- Ausnahme: Impressum, Datenschutzerklärung und Nutzungsvereinbarung bleiben ausschließlich statisch
auf Deutsch in `public/index.html` und tragen `lang="de"`. Ihre Texte und
Kontaktangaben gehören nicht in Sprachdateien und erhalten keine i18n-Attribute.
Auch ihre Dialogüberschriften bleiben beim Sprachwechsel auf Deutsch.
diff --git a/docs/agents/I18N.md b/docs/agents/I18N.md
index ba8b64b..c4a7132 100644
--- a/docs/agents/I18N.md
+++ b/docs/agents/I18N.md
@@ -35,9 +35,9 @@ HTML verwendet `data-i18n`, `data-i18n-title`, `data-i18n-aria-label`,
stehen Beschriftungen bereits vor dem Laden der Skripte bereit. Nach Änderungen
die erzeugte `public/index.html` mit ins Repository übernehmen.
-Impressum und Datenschutzerklärung sind davon ausgenommen. Ihre rechtlichen
+Impressum, Datenschutzerklärung und Nutzungsvereinbarung sind davon ausgenommen. Ihre rechtlichen
Texte und Kontaktangaben stehen ausschließlich statisch auf Deutsch in
-`public/index.html`, ohne i18n-Attribute und mit `lang="de"` an beiden Artikeln.
+`public/index.html`, ohne i18n-Attribute und mit `lang="de"` an den rechtlichen Artikeln.
Änderungen an diesen Angaben direkt im HTML vornehmen. Auch die zugehörigen
Dialogüberschriften bleiben auf Deutsch. Die Menüeinträge und die übrige
Bedienoberfläche werden weiterhin übersetzt.
diff --git a/docs/agents/TODO-MEDIENRECHTE.md b/docs/agents/TODO-MEDIENRECHTE.md
new file mode 100644
index 0000000..d5eaae5
--- /dev/null
+++ b/docs/agents/TODO-MEDIENRECHTE.md
@@ -0,0 +1,52 @@
+# TODO: Einräumung und Nachweis von Medienrechten
+
+Ausgangspunkt: [Recherche und umgesetzte Hinweise](../Bilder-und-Ton-Nutzungsrechte.md).
+Die statischen Rechtstexte und X-Copyright sind eingebunden; ein belegbares
+Zustimmungs- und Namensnennungssystem ist noch nicht umgesetzt.
+
+## Vor verbindlicher Verwendung und Freigabe zu erledigen
+
+1. Den tatsächlichen Lizenznehmer, Rechtekontakt, Rechtsgrundlagen und Fristen
+ ergänzen. Den Entwurf und die Zustimmung für Kinder rechtlich prüfen lassen.
+2. Die relevante Vereinbarung vor dem Einsenden zugänglich machen und eine
+ nicht vorausgewählte, ausdrückliche Zustimmung einholen. Eine bloße
+ Datenschutzerklärung und eine zufällige Gerätekennung sind kein Rechtenachweis.
+3. Je Bild und Tonaufnahme die gewünschte Urheberbezeichnung und die Erklärung
+ über Berechtigung erfassen. Nicht automatisch den Streckenersteller mit dem
+ Fotografen, Sprecher, Musiker oder Rechteinhaber gleichsetzen.
+4. Die Urheberbezeichnung direkt bei der jeweiligen Aufnahme sichtbar anzeigen,
+ auch bei Offline-Nutzung. Ein Header oder ein allgemeines Impressum ersetzt
+ die vereinbarte Bild-/Tonzuordnung nicht. Ein Verzicht darf nur auf gesonderter,
+ belegbarer Vereinbarung beruhen, nicht auf einem leeren Namensfeld.
+5. Zustimmung auch auf dem Server prüfen: Version der Vereinbarung,
+ serverseitiger Zeitpunkt, konkretes Bild/konkrete Tonaufnahme und berechtigte
+ zuordnungsfähige Person. Nicht auf eine ausschließlich clientseitige Checkbox
+ vertrauen. Eine kleine Rechte-Tabelle mit referenzierten Medien oder
+ unveränderlichen Dateihashes evaluieren; Zugriff auf Belege beschränken.
+ Nicht pauschal zusätzliche Gerätefingerprints oder IP-Adressen sammeln.
+6. Neue und ersetzte Dateien neu erfassen; reine Beschriftungsänderungen von
+ neuer Rechteeinräumung unterscheiden. Bild-, Audio-, JSON-, API- und
+ Python-Werkzeug-Uploads sowie erneutes Einsenden berücksichtigen.
+7. Bestandsmedien separat prüfen. Fehlenden Rechtebeleg nicht rückwirkend aus
+ einem Upload oder aus dem neuen Header konstruieren. Veröffentlichung ohne
+ erforderlichen Beleg sperren oder nachträglich eine echte Erlaubnis einholen.
+8. Widerruf, begründete Beschwerden und Beendigung der Bereitstellung behandeln;
+ personenbezogene Einwilligung und Lizenz nicht gleichsetzen. Papierkorb,
+ Sicherungsfristen und verbleibende Offline-Kopien berücksichtigen.
+9. Für die Envato-Gruppensymbole Autor, konkrete Lizenz und Nachweis prüfen.
+ Die Quelle ist vom Betreiber benannt; der einzelne Eintrag war nicht abrufbar.
+ Quelle: https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077
+
+## Bereits umgesetzt und beizubehalten
+
+- Rechtstexte ausschließlich statisch auf Deutsch; Bedienhinweise in Deutsch
+ und Englisch mit semantischen i18n-Schlüsseln.
+- Unterschiedliche Header für Nutzerbilder, Envato-Gruppensymbole und andere
+ App-Bilder; keine Upload-Lizenzbehauptung für statische Bibliotheksdateien.
+- Headertexte bleiben ein Hinweis, kein Eigentums-, Zustimmungs- oder
+ Kopierschutznachweis. Bei später belegten Zustimmungen kann der individuelle
+ Header um Urheberbezeichnung und Vertragsversion ergänzt werden; persönliche
+ Kontaktdaten dürfen dabei nicht öffentlich werden.
+- Tests prüfen Bildantworten einschließlich HEAD, Range und 304 sowie die
+ Nicht-Übersetzung rechtlicher Seiten. Schemaänderungen müssen zusätzlich
+ mit der Laufzeitdatenbankprüfung abgestimmt werden.
diff --git a/public/index.html b/public/index.html
index b941bde..69de95b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -19,6 +19,7 @@
@@ -342,7 +343,13 @@
Wegwichtel verwendet keine Cookies. Es zählt keine Besuche für Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Auf deinem Gerät wird gespeichert, ob du „Meinen Fortschritt merken“ eingeschaltet hast.
7. Inhalte von Schülerinnen und Schülern
Bilder, Texte und Tonaufnahmen dürfen nur geteilt werden, wenn das erlaubt ist. Wenn Kinder auf Bildern oder in Aufnahmen erkannt werden können, müssen die Regeln der Schule beachtet werden. Auch die Eltern oder anderen Sorgeberechtigten müssen die nötige Erlaubnis geben.
-
8. Deine Rechte
+
8. Eingesandte Bilder und Tonaufnahmen
+
Wenn du „Zur Prüfung senden“ wählst, erhalten die Betreiber deine Streckenangaben, Bilder und Tonaufnahmen sowie die zugehörige Gerätekennung. Sie verwenden diese Angaben zur Prüfung und Bereitstellung deiner Strecke. Freigegebene Bilder und Tonaufnahmen können im Internet weltweit angesehen oder angehört werden. Wer eine Strecke für unterwegs speichert, kann auch ihre Bilder und Tonaufnahmen auf seinem Gerät speichern.
+
Die Nutzungsvereinbarung regelt, welche Nutzungsrechte du für diese Zwecke einräumst. Die Urheberrechte bleiben bei den jeweiligen Urhebern. Eine Nutzungserlaubnis für ein Bild oder eine Tonaufnahme ersetzt keine erforderliche Einwilligung der darin erkennbaren oder hörbaren Personen. Diese Datenschutzerklärung allein erteilt keine solche Erlaubnis.
+
Personenbezogene Aufnahmen werden nur veröffentlicht, wenn eine passende Rechtsgrundlage vorliegt. Soweit eine Einwilligung erforderlich ist, muss sie vor der Veröffentlichung eingeholt werden. Bei Kindern sind insbesondere die erforderlichen Zustimmungen ihrer Sorgeberechtigten zu beachten. Eine Einwilligung kann jederzeit für die Zukunft widerrufen werden; die Rechtmäßigkeit der bis dahin erfolgten Verarbeitung bleibt davon unberührt.
+
Bei einem Widerruf oder einer berechtigten Löschanfrage prüfen die Betreiber die weitere Nutzung und entfernen die betroffenen Aufnahmen, soweit dies rechtlich erforderlich ist, aus der öffentlichen Bereitstellung. Bereits gespeicherte Kopien auf fremden Geräten können sie nicht vollständig zurückholen. Nicht öffentliche Sicherungskopien und Nachweise dürfen nur im rechtlich erforderlichen Umfang und für die dafür festgelegte Dauer aufbewahrt werden.
+
[Vor Veröffentlichung ergänzen: Rechtsgrundlagen für Einsendung, Prüfung, Veröffentlichung und Rechtenachweise; Empfänger und Hostinganbieter; Lösch- und Sicherungsfristen; Kontakt für Widerruf und Entfernung.]
+
9. Deine Rechte
Du kannst fragen, welche Daten über dich gespeichert sind. Unter den gesetzlichen Voraussetzungen kannst du verlangen, dass sie berichtigt, gelöscht, nur noch eingeschränkt genutzt oder dir zur Weitergabe bereitgestellt werden. Du kannst der Nutzung widersprechen und eine erteilte Erlaubnis für die Zukunft zurücknehmen. Wenn du dich über den Umgang mit deinen Daten beschweren möchtest, kannst du dich an die folgende Datenschutzbehörde wenden.
Zuständige Datenschutzaufsicht: Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI) Postfach 90 04 55, 99107 Erfurt Häßlerstraße 8, 99096 Erfurt Telefon: 0361 / 57 311 29 00 Telefax: 0361 / 57 311 29 04 E-Mail: poststelle@datenschutz.thueringen.de Website: www.tlfdi.de
@@ -367,8 +374,30 @@
Je nachdem, wer Wegwichtel betreibt, können weitere Angaben nötig sein. Dazu gehören zum Beispiel die Verantwortlichen für den Betrieb der Website. Diese Angaben müssen noch geprüft werden.
Haftung für Inhalte und Verweise
Die Inhalte werden sorgfältig erstellt und regelmäßig geprüft. Falls etwas falsch, veraltet oder nicht erlaubt ist, melde dich beim Kontakt oben. Für andere verlinkte Seiten sind deren Betreiber verantwortlich.
+
Bildquelle der Gruppensymbole
+
Die Wichtel-Gruppensymbole stammen aus einer Grafik von Envato. Ihre Verwendung richtet sich nach der jeweiligen Envato-Lizenz. Envato wird hier als Quelle genannt.
Urheber- und Nutzungsrechte
-
Wenn nichts anderes angegeben ist, liegen die Rechte bei der Grundschule Herpf. Du darfst Texte, Bilder, Tonaufnahmen und Wege nur so nutzen, wie es jeweils erlaubt wurde. Auch die Rechte der beteiligten Kinder, Familien, Lehrkräfte und anderen Personen bleiben bestehen.
+
Die Urheberrechte an Bildern, Tonaufnahmen und anderen Beiträgen bleiben bei den jeweiligen Urhebern. Die Betreiber nutzen eingesandte Aufnahmen im Rahmen der vereinbarten Nutzungsrechte. Die Veröffentlichung erlaubt anderen Personen keine freie Weiterverwendung. Gesetzlich erlaubte Nutzungen und ausdrücklich erteilte Lizenzen bleiben unberührt.
+
+
+
Entwurf: Diese Vereinbarung muss vor ihrer verbindlichen Verwendung rechtlich geprüft werden. Betreiber, Kontakt und das Verfahren zur Zustimmung und Urhebernennung sind noch zu ergänzen.
+
Stand: 4. Oktober 2026
+
1. Für wen diese Vereinbarung gilt
+
Diese Vereinbarung regelt das Einsenden von Bildern und Tonaufnahmen an Wegwichtel. Nutzungsrechte werden dem Betreiber [vollständiger Name und Anschrift des tatsächlichen Betreibers ergänzen] eingeräumt. Die bloße Nutzung der App oder das Speichern eines Entwurfs auf deinem Gerät räumt keine Rechte ein.
+
2. Erlaubnis für Bilder und Tonaufnahmen
+
Mit dem Hochladen deiner Bilder und Tonaufnahmen unter Zustimmung zu dieser Nutzungsvereinbarung räumst du dem Betreiber ein einfaches, nicht ausschließliches, räumlich unbeschränktes Nutzungsrecht für den Betrieb von Wegwichtel ein. Es umfasst das Speichern und Vervielfältigen, die redaktionelle Prüfung sowie nach Freigabe das öffentliche Zugänglichmachen und Wiedergeben im Zusammenhang mit den Strecken der App. Dazu gehören die Anzeige von Bildern, das Abspielen von Tonaufnahmen sowie die von der App angebotene Speicherung ausgewählter Strecken auf Geräten für die Nutzung ohne Internet.
+
Der Betreiber darf die dafür notwendigen technischen Anpassungen vornehmen, etwa Dateiformate umwandeln, Dateien komprimieren und Bildgrößen anpassen. Inhaltliche Veränderungen oder entstellende Bearbeitungen sind dadurch nicht erlaubt. Beauftragte Hosting- und technische Dienstleister dürfen die Aufnahmen nur soweit verwenden, wie dies zur Bereitstellung von Wegwichtel erforderlich ist. Besucher erhalten nur die für die Nutzung innerhalb der App und ihrer vorgesehenen Offline-Funktion erforderliche Erlaubnis.
+
Die Rechteeinräumung gilt für die Dauer der vereinbarten Bereitstellung deiner Aufnahmen in Wegwichtel. Nach einer vereinbarten Beendigung stellt der Betreiber die weitere öffentliche Nutzung ein. Gesetzlich erforderliche nicht öffentliche Nachweise dürfen im erforderlichen Umfang aufbewahrt werden. Bereits rechtmäßig auf Geräten gespeicherte Kopien können nicht vollständig zurückgerufen werden. Gesetzliche Rückrufrechte und Ansprüche bleiben unberührt.
+
Eine Vergütung für die hier beschriebene freiwillige Bereitstellung ist nicht vereinbart. Unabdingbare gesetzliche Vergütungsansprüche bleiben unberührt. Nutzungen für Werbung außerhalb von Wegwichtel, den Verkauf der Aufnahmen oder eigenständige Veröffentlichungen auf anderen Plattformen sind nicht umfasst und benötigen eine gesonderte Vereinbarung.
+
3. Urheberschaft und Namensnennung
+
Das Urheberrecht wird nicht übertragen. Das Recht auf Anerkennung der Urheberschaft und auf die vereinbarte Urheberbezeichnung bleibt bestehen. Für jede Aufnahme ist vor der Veröffentlichung anzugeben, welcher Name oder welches Pseudonym als Urheberbezeichnung verwendet werden soll. Diese Bezeichnung ist bei der Aufnahme sichtbar zu nennen. Ein Verzicht auf Namensnennung wird nicht durch das Hochladen unterstellt. Eine abweichende Vereinbarung ist gesondert mit dem berechtigten Urheber zu treffen.
+
4. Du musst die nötigen Rechte haben
+
Du darfst nur Aufnahmen senden, die du selbst erstellt hast oder für die du alle erforderlichen Erlaubnisse besitzt, um diese Nutzungsrechte einzuräumen. Bei Tonaufnahmen sind auch die Rechte an Musik, gesprochenen Texten, Darbietungen und der Aufnahme selbst zu beachten. Die Erlaubnis zum privaten Anhören oder zum eigenen Gebrauch reicht für eine Veröffentlichung in Wegwichtel nicht aus.
+
Auch die Rechte erkennbarer oder hörbarer Personen müssen beachtet werden. Eine Nutzungserlaubnis des Fotografen ersetzt nicht die erforderliche Einwilligung der abgebildeten Person. Kinder brauchen für die Rechteeinräumung die erforderliche Zustimmung ihrer gesetzlichen Vertreter. Sorgeberechtigte dürfen Rechte anderer Urheber nicht ohne deren Erlaubnis einräumen.
+
5. Prüfung, Entfernung und Beschwerden
+
Die Betreiber sind nicht verpflichtet, eingesandte Aufnahmen zu veröffentlichen. Wenn Rechte unklar sind oder eine berechtigte Beanstandung vorliegt, dürfen sie die Veröffentlichung aussetzen oder die Aufnahme entfernen. Fragen zu Urheberschaft, Nutzungsrechten, Namensnennung oder Entfernung richtest du an [Kontakt für Rechtefragen ergänzen]. Datenschutzrechtliche Einwilligungen und Widerrufe werden getrennt von den urheberrechtlichen Nutzungsrechten geprüft.
+
6. Hinweis bei ausgelieferten Bildern
+
Der technische Rechtehinweis bei Bildern informiert über diese Nutzungsregeln. Er ersetzt weder die Urhebernennung bei der Aufnahme noch einen Nachweis über die tatsächlich erteilte Nutzungserlaubnis. Er verhindert auch nicht das Kopieren einer Aufnahme.
Was merkt sich Wegwichtel?
@@ -464,6 +493,8 @@
Deine neue Strecke bleibt zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ kannst du sie zur Freigabe einreichen.
+
Wenn du Bilder oder Tonaufnahmen sendest, erlaubst du Wegwichtel, sie nach der Nutzungsvereinbarung zu verwenden. Sende nur Aufnahmen, für die du diese Erlaubnis geben darfst. Kinder brauchen die Zustimmung ihrer Sorgeberechtigten.
+
diff --git a/public/js/editor-ui.js b/public/js/editor-ui.js
index f447ee1..a606fb1 100644
--- a/public/js/editor-ui.js
+++ b/public/js/editor-ui.js
@@ -44,6 +44,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
routeName: 'editor-route-name',
routeSave: 'editor-route-save',
routeUpload: 'editor-route-upload',
+ rightsOpen: 'editor-rights-open',
status: 'editor-status'
});
@@ -1448,6 +1449,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
activate(byId(ids.newRoute), newRoute);
activate(byId(ids.routeBack), () => guarded(loadRoutes).then(showListView));
activate(byId(ids.routeUpload), () => guarded(uploadLocalRoute));
+ activate(byId(ids.rightsOpen), () => ns.LegalUi?.open('terms'));
activate(byId(ids.addPoi), newPoi);
activate(byId(ids.poiAudioAdd), () => guarded(() => chooseMedia('audio', ids.poiAudio)));
activate(byId(ids.poiImagesAdd), () => guarded(() => chooseMedia('image', ids.poiImages)));
diff --git a/public/js/legal-ui.js b/public/js/legal-ui.js
index 017e785..eb715f0 100644
--- a/public/js/legal-ui.js
+++ b/public/js/legal-ui.js
@@ -7,11 +7,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
const pageTitles = Object.freeze({
privacy: 'Datenschutzerklärung',
imprint: 'Impressum',
+ terms: 'Nutzungsvereinbarung',
get technology() { return ns.I18n.t("info.storage"); }
});
function menuEntries() { return [
['privacy', ns.I18n.t("info.privacy")],
['imprint', ns.I18n.t("info.imprint")],
+ ['terms', ns.I18n.t("info.terms")],
['technology', ns.I18n.t("info.storage")]
]; }
const selectors = Object.freeze({
@@ -439,7 +441,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
function updateDialogTitle(pageName) {
if (!elements?.title) return;
- const isLegalPage = pageName === 'privacy' || pageName === 'imprint';
+ const isLegalPage = pageName !== 'technology';
if (isLegalPage) delete elements.title.dataset.i18n;
else elements.title.dataset.i18n = 'info.storage';
setAttribute(elements.title, 'lang', isLegalPage ? 'de' : ns.I18n.language);
diff --git a/public/js/texts-de.js b/public/js/texts-de.js
index cddcc8e..f492586 100644
--- a/public/js/texts-de.js
+++ b/public/js/texts-de.js
@@ -131,6 +131,8 @@ globalThis.Wegwichtel ??= {};
"no-stations": "Noch keine Stationen angelegt.",
"loading-routes": "Strecken werden geladen …",
"submitted": "Erneut zur Prüfung senden",
+ "rights-hint": "Wenn du Bilder oder Tonaufnahmen sendest, erlaubst du Wegwichtel, sie nach der Nutzungsvereinbarung zu verwenden. Sende nur Aufnahmen, für die du diese Erlaubnis geben darfst. Kinder brauchen die Zustimmung ihrer Sorgeberechtigten.",
+ "rights-open": "Vereinbarung für Bilder und Ton lesen",
"submit": "Zur Prüfung senden",
"submitted-hint": "Du kannst Änderungen erneut zur Prüfung senden, solange die Strecke noch nicht freigegeben wurde.",
"draft-hint": "Dieser Entwurf bleibt auf deinem Gerät. Mit „Zur Prüfung senden“ reichst du ihn zur Freigabe ein.",
@@ -205,6 +207,7 @@ globalThis.Wegwichtel ??= {};
"pumpkin": "Kürbis"
},
"info": {
+ "terms": "Nutzungsvereinbarung",
"privacy": "Datenschutzerklärung",
"imprint": "Impressum",
"storage": "Speichern und Berechtigungen",
diff --git a/public/js/texts-en.js b/public/js/texts-en.js
index 0e0305d..3e102b7 100644
--- a/public/js/texts-en.js
+++ b/public/js/texts-en.js
@@ -131,6 +131,8 @@ globalThis.Wegwichtel ??= {};
"no-stations": "There are no stops yet.",
"loading-routes": "Loading trails …",
"submitted": "Send changes for review",
+ "rights-hint": "By sending pictures or recordings, you allow Wegwichtel to use them under its terms of use. Only send recordings you are allowed to share this way. Children need their parent or guardian’s permission.",
+ "rights-open": "Read the agreement for pictures and recordings",
"submit": "Send for review",
"submitted-hint": "You can send changes for review until the route has been approved.",
"draft-hint": "This draft stays on your device. Choose “Send for review” to submit it for approval.",
@@ -205,6 +207,7 @@ globalThis.Wegwichtel ??= {};
"pumpkin": "Pumpkin"
},
"info": {
+ "terms": "Terms of use",
"privacy": "Privacy notice",
"imprint": "Legal notice",
"storage": "Saving and permissions",
diff --git a/server.js b/server.js
index c7103ac..e785644 100644
--- a/server.js
+++ b/server.js
@@ -2,6 +2,7 @@ import express from 'express';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { offlineManifest } from './src/services/offline-manifest.js';
+import { setAppImageCopyright } from './src/services/copyright.js';
import { config, ensureRuntimeDirectories } from './src/config.js';
import { openDatabase } from './src/database.js';
import { createApiRouter } from './src/routes/api.js';
@@ -30,6 +31,7 @@ app.use(express.static(publicDirectory, {
lastModified: true,
maxAge: 0,
setHeaders(res, filePath) {
+ setAppImageCopyright(res, filePath);
const extension = path.extname(filePath).toLowerCase();
const authoredAsset = ['.html', '.css', '.js'].includes(extension);
res.setHeader(
diff --git a/src/routes/api.js b/src/routes/api.js
index c4a02a4..732c56e 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -1,6 +1,7 @@
import { Router } from 'express';
import { inspectUpload, upload } from '../middleware/upload.js';
import { config } from '../config.js';
+import { UPLOAD_COPYRIGHT_HEADERS } from '../services/copyright.js';
import {
listRoutes, getRoute, getRouteGpxFile, createRoute, updateRoute, appendRoute,
listPois, getPoi, createPoi, updatePoi, deletePoi, softDeleteRoute, restoreRoute
@@ -17,12 +18,13 @@ const numberOrUndefined = value => {
return Number.isFinite(parsed) ? parsed : undefined;
};
-function sendFileResource(res, file, contentType = null) {
+function sendFileResource(res, file, contentType = null, headers = {}) {
if (contentType) res.type(contentType);
res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.filename)}`);
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate');
return res.sendFile(file.absolutePath, {
+ headers,
acceptRanges: true,
cacheControl: true,
immutable: false,
@@ -132,7 +134,8 @@ export function createApiRouter(db) {
res.json(getPicture(db, routeId, poiId, pictureId));
return;
}
- sendFileResource(res, getPictureFile(db, routeId, poiId, pictureId));
+ const file = getPictureFile(db, routeId, poiId, pictureId);
+ sendFileResource(res, file, null, UPLOAD_COPYRIGHT_HEADERS);
});
api.post('/routes/:routeId/pois/:poiId/pictures', requireWritableRoute('routeId'), upload.single('picture'), inspectUpload('picture', 'picture', { allowJson: true }), (req, res) => {
diff --git a/src/services/copyright.js b/src/services/copyright.js
new file mode 100644
index 0000000..6f87da5
--- /dev/null
+++ b/src/services/copyright.js
@@ -0,0 +1,33 @@
+import path from 'node:path';
+
+// These are policy notices, not evidence of a particular uploader's rights.
+// ASCII keeps values safe across HTTP servers and reverse proxies.
+export const UPLOAD_COPYRIGHT_NOTICE = 'Urheberrechte verbleiben bei den Urhebern. Nutzungsrechte an Upload-Bildern werden den Wegwichtel-Betreibern mit dem Hochladen unter Zustimmung zur Nutzungsvereinbarung eingeraeumt. Keine freie Weiterverwendung.';
+export const APP_IMAGE_COPYRIGHT_NOTICE = 'Wegwichtel-App-Grafik: Urheberrechte verbleiben bei den jeweiligen Urhebern. Nutzung gemaess der jeweiligen Lizenz.';
+export const ENVATO_SYMBOL_SOURCE = 'https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077';
+export const GROUP_SYMBOL_COPYRIGHT_NOTICE = 'Quelle: Envato. Urheberrechte verbleiben bei den jeweiligen Urhebern. Nutzung gemaess Envato-Lizenz.';
+export const COPYRIGHT_HEADER_LIMITS = 'Dieser technische Hinweis ersetzt weder die sichtbare Urheberbezeichnung noch einen Nachweis ueber tatsaechlich eingeraeumte Nutzungsrechte.';
+
+export const UPLOAD_COPYRIGHT_HEADERS = Object.freeze({
+ 'X-Copyright': UPLOAD_COPYRIGHT_NOTICE,
+ 'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS
+});
+const APP_IMAGE_COPYRIGHT_HEADERS = Object.freeze({
+ 'X-Copyright': APP_IMAGE_COPYRIGHT_NOTICE,
+ 'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS
+});
+const GROUP_SYMBOL_COPYRIGHT_HEADERS = Object.freeze({
+ 'X-Copyright': GROUP_SYMBOL_COPYRIGHT_NOTICE,
+ 'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS,
+ 'X-Copyright-Source': ENVATO_SYMBOL_SOURCE
+});
+
+const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.ico', '.avif']);
+
+export function setAppImageCopyright(res, filePath) {
+ if (IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase())) {
+ const isGroupSymbol = path.basename(path.dirname(filePath)) === 'group-symbols';
+ const headers = isGroupSymbol ? GROUP_SYMBOL_COPYRIGHT_HEADERS : APP_IMAGE_COPYRIGHT_HEADERS;
+ for (const [name, value] of Object.entries(headers)) res.setHeader(name, value);
+ }
+}
diff --git a/test/copyright.test.js b/test/copyright.test.js
new file mode 100644
index 0000000..cd39812
--- /dev/null
+++ b/test/copyright.test.js
@@ -0,0 +1,28 @@
+import assert from 'node:assert/strict';
+import path from 'node:path';
+import test from 'node:test';
+import { APP_IMAGE_COPYRIGHT_NOTICE, GROUP_SYMBOL_COPYRIGHT_NOTICE, UPLOAD_COPYRIGHT_NOTICE, COPYRIGHT_HEADER_LIMITS, ENVATO_SYMBOL_SOURCE, setAppImageCopyright } from '../src/services/copyright.js';
+
+test('image notices distinguish Envato symbols from other assets and skip non-image files', () => {
+ const headers = new Map();
+ const res = { setHeader: (name, value) => headers.set(name, value) };
+ for (const extension of ['png', 'jpg', 'jpeg', 'webp', 'gif', 'svg', 'ico', 'avif', 'PNG']) {
+ headers.clear();
+ setAppImageCopyright(res, path.join('public', 'assets', `graphic.${extension}`));
+ assert.equal(headers.get('X-Copyright'), APP_IMAGE_COPYRIGHT_NOTICE);
+ assert.equal(headers.get('X-Copyright-Notice'), COPYRIGHT_HEADER_LIMITS);
+ assert.equal(headers.has('X-Copyright-Source'), false);
+ }
+ setAppImageCopyright(res, path.join('public', 'images', 'group-symbols', '🐦.png'));
+ assert.equal(headers.get('X-Copyright'), GROUP_SYMBOL_COPYRIGHT_NOTICE);
+ assert.equal(headers.get('X-Copyright-Notice'), COPYRIGHT_HEADER_LIMITS);
+ assert.equal(headers.get('X-Copyright-Source'), ENVATO_SYMBOL_SOURCE);
+ for (const extension of ['js', 'css', 'html', 'txt', 'mp3']) {
+ headers.clear();
+ setAppImageCopyright(res, path.join('public', 'assets', `file.${extension}`));
+ assert.equal(headers.has('X-Copyright'), false);
+ }
+ for (const value of [APP_IMAGE_COPYRIGHT_NOTICE, GROUP_SYMBOL_COPYRIGHT_NOTICE, UPLOAD_COPYRIGHT_NOTICE, COPYRIGHT_HEADER_LIMITS, ENVATO_SYMBOL_SOURCE]) {
+ assert.match(value, /^[\x20-\x7e]+$/, 'Header values must not contain Unicode or line breaks');
+ }
+});
diff --git a/test/editor-ui.test.js b/test/editor-ui.test.js
index 885bb5e..f02d83f 100644
--- a/test/editor-ui.test.js
+++ b/test/editor-ui.test.js
@@ -225,6 +225,7 @@ function buildEditorContext() {
'editor-route-list-view',
'editor-route-name',
'editor-route-upload',
+ 'editor-rights-open',
'editor-status'
];
const elements = new Map(ids.map(id => [id, new FakeElement(id)]));
@@ -365,6 +366,7 @@ function buildEditorContext() {
Wegwichtel: {
App: { currentPosition: () => ({ lat: 50.123456789, lon: 10.987654321, accuracy: 4.321 }) },
Config: { apiBase: '/api/' },
+ LegalUi: { open: name => { controls.legalPage = name; } },
RouteRecorder: {
beginEditing(id) { controls.recording = id; },
endEditing() { controls.recording = null; },
@@ -396,6 +398,9 @@ test('public editor workflow saves new route and GPX Station locally without POS
assert.equal(elements.get('editor-button').attributes.get('aria-expanded'), 'true');
assert.ok(calls.some(call => call.method === 'GET' && call.url === '/api/routes?activatedOnly=true'));
+ elements.get('editor-rights-open').dispatchEvent(pointerEvent());
+ assert.equal(controls.legalPage, 'terms');
+
await context.Wegwichtel.EditorUi.openNewLocalRoute();
assert.equal(elements.get('editor-route-gpx-group').hidden, true);
elements.get('editor-route-name').value = 'Neue lokale Strecke';
diff --git a/test/i18n.test.js b/test/i18n.test.js
index 6bf1a7b..7af64f4 100644
--- a/test/i18n.test.js
+++ b/test/i18n.test.js
@@ -13,7 +13,7 @@ test('German legal pages and contacts remain static when the app uses English',
const i18n = await loadI18n(context);
const html = await fs.readFile(path.join(root, 'public/index.html'), 'utf8');
const englishHtml = localizeHtml(html, key => i18n.t(key));
- for (const name of ['privacy', 'imprint']) {
+ for (const name of ['privacy', 'imprint', 'terms']) {
const pattern = new RegExp(`]+data-info-page="${name}"[^>]*>[\\s\\S]*?`);
const page = pattern.exec(html)?.[0];
assert.ok(page, name);
@@ -22,7 +22,7 @@ test('German legal pages and contacts remain static when the app uses English',
assert.equal(pattern.exec(englishHtml)?.[0], page);
}
for (const catalog of context.Wegwichtel.TextCatalogs.values()) {
- for (const key of ['privacy', 'imprint', 'contacts']) {
+ for (const key of ['privacy', 'imprint', 'terms', 'contacts']) {
assert.equal(Object.hasOwn(catalog, key), false, key);
}
}
diff --git a/test/legal-ui.test.js b/test/legal-ui.test.js
index 122e7c4..ba46023 100644
--- a/test/legal-ui.test.js
+++ b/test/legal-ui.test.js
@@ -31,7 +31,7 @@ test('start screen exposes cookie status, optional local progress and deliberate
assert.match(storage, /cookiesEnabled: \(\) => false/);
});
-test('information button opens scrollable overlay pages for privacy, imprint and technology', async () => {
+test('information button opens scrollable overlay pages for privacy, imprint, terms and technology', async () => {
const html = await read('public/index.html');
const css = await read('public/css/app.css');
const ui = await read('public/js/legal-ui.js');
@@ -41,6 +41,7 @@ test('information button opens scrollable overlay pages for privacy, imprint and
assert.match(html, /