Updated imprint and legal documents
Sonarqube Scanner / Build and analyze (push) Canceled after 0s

This commit is contained in:
2026-08-29 14:57:24 +02:00
parent 41e858c0a2
commit fd16c66142
5 changed files with 273 additions and 220 deletions
+83 -63
View File
@@ -19,7 +19,6 @@ body {
color: var(--text);
overscroll-behavior-y: contain;
}
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
@@ -40,7 +39,6 @@ button,
[hidden] {
display: none !important;
}
#app-shell,
.app-page {
min-height: 100dvh;
@@ -49,7 +47,6 @@ button,
.app-page {
background: var(--page-background);
}
#app-shell.has-navigation .app-page {
padding-bottom: calc(var(--footer-height) + env(safe-area-inset-bottom));
}
@@ -67,7 +64,6 @@ button,
color: #fff;
box-shadow: 0 .15rem .5rem rgba(0, 0, 0, .2);
}
.app-page > header.has-back-button {
grid-template-columns: var(--icon-button-size) minmax(0, 1fr) var(--icon-button-size);
gap: .5rem;
@@ -88,7 +84,6 @@ button,
text-overflow: ellipsis;
white-space: nowrap;
}
.app-page > header .back-button.ui-button {
display: grid;
place-items: center;
@@ -108,7 +103,6 @@ button,
height: 1.25rem;
pointer-events: none;
}
.content {
width: min(48rem, 100%);
box-sizing: border-box;
@@ -123,7 +117,6 @@ button,
border-radius: .25rem;
background: var(--notice);
}
#routes-page .content > section + section {
margin-top: 2rem;
padding-top: 1.25rem;
@@ -147,7 +140,6 @@ button,
grid-template-columns: minmax(0, 1fr) auto;
gap: .35rem;
}
#route-search {
width: 100%;
min-height: 3rem;
@@ -170,7 +162,6 @@ button,
min-height: 3rem;
margin: 0;
}
#all-routes-summary {
margin: .75rem 0;
color: var(--muted);
@@ -189,7 +180,6 @@ button,
padding: 0;
list-style: none;
}
.route-list button {
display: grid;
width: 100%;
@@ -214,7 +204,6 @@ button,
.route-list button > small {
color: var(--muted);
}
.route-list button > small {
margin-top: .25rem;
font-weight: 600;
@@ -230,12 +219,10 @@ button,
cursor: progress;
opacity: .65;
}
#poi-list button {
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
}
#poi-list button > strong,
#poi-list button > span:not(.poi-proximity) {
grid-column: 1;
@@ -260,14 +247,12 @@ button,
transition: transform 180ms linear;
will-change: transform;
}
.poi-proximity small {
color: var(--text);
font-size: .8rem;
font-weight: 700;
white-space: nowrap;
}
#poi-list button[aria-current="step"] {
border-color: var(--primary);
background: #e7f1e9;
@@ -288,7 +273,6 @@ button,
background: var(--surface);
color: var(--muted);
}
#route-page dl {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -318,7 +302,6 @@ button,
min-width: 0;
border: 0;
}
#route-page fieldset {
display: grid;
padding: 0;
@@ -340,7 +323,6 @@ button,
cursor: pointer;
touch-action: manipulation;
}
#route-page fieldset button.ui-button:focus-visible {
outline: .2rem solid var(--focus);
outline-offset: .15rem;
@@ -357,7 +339,6 @@ button,
height: 2rem;
pointer-events: none;
}
#route-page fieldset > legend,
#slideshow > fieldset > legend {
position: absolute;
@@ -384,7 +365,6 @@ button,
background: #9b2c2c;
border-color: #701f1f;
}
#slideshow {
overflow: hidden;
border-radius: .75rem;
@@ -405,7 +385,6 @@ button,
object-fit: contain;
background: #111;
}
#slideshow > fieldset {
display: grid;
grid-template-columns: var(--icon-button-size) minmax(0, 1fr) var(--icon-button-size);
@@ -417,7 +396,6 @@ button,
margin: 0;
padding: .65rem;
}
#slideshow > fieldset > button.ui-button {
display: grid;
place-items: center;
@@ -434,7 +412,6 @@ button,
height: 1.2rem;
pointer-events: none;
}
#slideshow > fieldset > div {
display: flex;
align-self: stretch;
@@ -454,7 +431,6 @@ button,
overflow-wrap: anywhere;
line-height: 1.3;
}
#slide-position {
display: block;
margin-top: .15rem;
@@ -473,7 +449,6 @@ button,
#poi-audio[hidden] {
display: none;
}
#navigation-footer {
position: fixed;
right: 0;
@@ -493,7 +468,6 @@ button,
box-shadow: 0 -.25rem .8rem rgba(0, 0, 0, .2);
backdrop-filter: blur(.4rem);
}
#navigation-arrow {
width: 3.5rem;
height: 3.5rem;
@@ -516,7 +490,6 @@ button,
text-overflow: ellipsis;
white-space: nowrap;
}
#navigation-distance {
font-size: 1.5rem;
line-height: 1.1;
@@ -535,7 +508,6 @@ button,
background: var(--primary-dark);
color: #fff;
}
#pause-route:not(:disabled):hover,
#pause-route:not(:disabled):focus {
background: #704a00;
@@ -566,27 +538,37 @@ button,
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: .01ms !important;
transition-duration: .01ms !important;
}
}
.info-button {
.info-control {
position: fixed;
top: calc(env(safe-area-inset-top) + .45rem);
right: max(.65rem, env(safe-area-inset-right));
z-index: 10001;
display: grid;
justify-items: end;
gap: .45rem;
pointer-events: none;
}
.info-control > * {
pointer-events: auto;
}
.info-button {
display: grid;
place-items: center;
width: var(--icon-button-size);
height: var(--icon-button-size);
padding: 0;
border: 2px solid rgba(255, 255, 255, .9);
border: 2px solid rgba(255, 255, 255, .95);
border-radius: 50%;
background: var(--primary-dark);
color: #fff;
@@ -596,6 +578,7 @@ button,
line-height: 1;
cursor: pointer;
touch-action: manipulation;
transition: background-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}
.info-button:focus-visible {
@@ -603,11 +586,60 @@ button,
outline-offset: .15rem;
}
.info-button[aria-expanded="true"] {
background: var(--primary);
box-shadow: 0 .22rem .7rem rgba(0, 0, 0, .34);
transform: scale(1.03);
}
.info-dropdown {
position: absolute;
top: calc(var(--icon-button-size) + .45rem);
right: 0;
display: grid;
width: min(18rem, calc(100vw - 1.25rem - env(safe-area-inset-left) - env(safe-area-inset-right)));
box-sizing: border-box;
gap: .35rem;
padding: .45rem;
border: 1px solid rgba(49, 93, 58, .24);
border-radius: .8rem;
background: rgba(255, 255, 255, .98);
box-shadow: 0 .45rem 1.4rem rgba(0, 0, 0, .24);
opacity: 0;
transform: translateY(-.35rem) scale(.98);
transform-origin: top right;
transition: opacity 180ms ease, transform 180ms ease;
pointer-events: none;
}
.info-dropdown.is-open {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.info-dropdown button {
min-height: 3rem;
padding: .75rem .85rem;
border: 1px solid #aebcaf;
border-radius: .6rem;
background: var(--surface);
color: var(--text);
font-weight: 700;
text-align: left;
cursor: pointer;
touch-action: manipulation;
}
.info-dropdown button:focus-visible {
outline: .2rem solid var(--focus);
outline-offset: .12rem;
}
.app-page > header:not(.has-back-button) h1 {
padding-right: calc(var(--icon-button-size) + 1rem);
padding-left: calc(var(--icon-button-size) + 1rem);
}
.switch-row {
position: relative;
display: grid;
@@ -635,7 +667,6 @@ button,
color: var(--muted);
line-height: 1.35;
}
.switch-row > input {
position: absolute;
width: 1px;
@@ -654,7 +685,6 @@ button,
background: #d9ded9;
transition: background-color 160ms ease, border-color 160ms ease;
}
.switch-track::after {
position: absolute;
top: .15rem;
@@ -676,7 +706,6 @@ button,
.switch-row > input:checked + .switch-track::after {
transform: translateX(1.45rem);
}
.switch-row > input:focus-visible + .switch-track {
outline: .2rem solid var(--focus);
outline-offset: .15rem;
@@ -690,7 +719,6 @@ button,
.compact-switch {
margin: 1rem 0;
}
.info-dialog {
width: min(48rem, calc(100% - 1.5rem));
max-width: 48rem;
@@ -710,7 +738,6 @@ button,
background: rgba(12, 20, 14, .72);
backdrop-filter: blur(.15rem);
}
.info-dialog-shell {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
@@ -719,24 +746,22 @@ button,
.info-dialog-header {
display: grid;
grid-template-columns: var(--icon-button-size) minmax(0, 1fr) var(--icon-button-size);
grid-template-columns: minmax(0, 1fr) var(--icon-button-size);
align-items: center;
gap: .4rem;
padding: max(.4rem, env(safe-area-inset-top)) .5rem .4rem;
background: var(--primary);
color: #fff;
}
.info-dialog-header h2 {
min-width: 0;
margin: 0;
overflow: hidden;
font-size: 1.15rem;
text-align: center;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
}
.info-dialog-header button {
display: grid;
place-items: center;
@@ -752,7 +777,6 @@ button,
cursor: pointer;
touch-action: manipulation;
}
.info-dialog-content {
min-height: 0;
padding: 1rem max(1rem, env(safe-area-inset-right)) calc(1rem + env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
@@ -763,25 +787,6 @@ button,
-webkit-overflow-scrolling: touch;
}
.info-menu {
display: grid;
gap: .75rem;
}
.info-menu button {
min-height: 3.5rem;
padding: .85rem 1rem;
border: 1px solid #aebcaf;
border-radius: .65rem;
background: var(--surface);
color: var(--text);
box-shadow: 0 .12rem .35rem rgba(0, 0, 0, .08);
font-weight: 700;
text-align: left;
cursor: pointer;
touch-action: manipulation;
}
.info-page {
line-height: 1.55;
overflow-wrap: anywhere;
@@ -795,7 +800,6 @@ button,
.info-page h3:first-of-type {
margin-top: .5rem;
}
.info-page code {
padding: .08rem .25rem;
border-radius: .2rem;
@@ -810,6 +814,22 @@ button,
background: var(--notice);
}
@keyframes info-dialog-in {
from {
opacity: 0;
transform: translateY(.45rem) scale(.985);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.info-dialog[open] {
animation: info-dialog-in 160ms ease-out;
}
@media (max-width: 34rem) {
.info-dialog {
width: 100%;
+60 -70
View File
@@ -10,10 +10,16 @@
<link rel="stylesheet" href="css/app.css?v=0.12.14">
</head>
<body>
<button id="info-button" class="info-button" type="button" aria-label="Informationen, Datenschutz und Impressum öffnen" title="Informationen">
<span aria-hidden="true">i</span>
</button>
<div class="info-control">
<button id="info-button" class="info-button" type="button" aria-label="Informationsmenü öffnen" title="Informationen" aria-haspopup="true" aria-expanded="false" aria-controls="info-dropdown">
<span aria-hidden="true">i</span>
</button>
<nav id="info-dropdown" class="info-dropdown" aria-label="Rechtliche und technische Informationen" hidden>
<button type="button" data-info-target="privacy">Datenschutzerklärung</button>
<button type="button" data-info-target="imprint">Impressum</button>
<button type="button" data-info-target="technology">Cookies und verwendete JavaScripts</button>
</nav>
</div>
<div id="bootstrap-screen" aria-live="polite" aria-busy="true">
<section>
<span aria-hidden="true">🧭</span>
@@ -22,7 +28,6 @@
<div class="privacy-summary">
<p><strong>Cookies:</strong> Diese Anwendung setzt derzeit keine Cookies und verwendet weder Werbung noch Tracking.</p>
<label class="switch-row switch-disabled">
<span>
<strong>Optionale Cookies</strong>
@@ -31,7 +36,6 @@
<input class="cookie-toggle" type="checkbox" role="switch" disabled>
<span class="switch-track" aria-hidden="true"></span>
</label>
<label class="switch-row">
<span>
<strong>Streckenfortschritt lokal speichern</strong>
@@ -41,11 +45,9 @@
<span class="switch-track" aria-hidden="true"></span>
</label>
</div>
<p id="storage-cleanup-status" class="storage-cleanup-status" hidden></p>
<progress id="bootstrap-progress" max="6" value="0"></progress>
<ol id="bootstrap-steps">
<li data-step="jquery">jQuery laden</li>
<li data-step="jquery-ui">jQuery UI laden</li>
@@ -54,14 +56,12 @@
<li data-step="routes">Routen laden</li>
<li data-step="location">Standort ermitteln</li>
</ol>
<p id="bootstrap-ready" class="bootstrap-ready" hidden>Alles ist bereit. Tippe oder klicke auf diese Startseite, um die Anwendung zu öffnen.</p>
<p id="bootstrap-error" hidden></p>
<button id="bootstrap-retry" type="button" hidden>Erneut versuchen</button>
<button id="bootstrap-enter" type="button" hidden>Anwendung öffnen</button>
</section>
</div>
<main id="app-shell" hidden>
<section id="routes-page" class="app-page">
<header>
@@ -77,7 +77,6 @@
<li><p>Routen werden geladen …</p></li>
</ul>
</section>
<section>
<h2>Alle Routen</h2>
<label for="route-search">Routen durchsuchen</label>
@@ -91,7 +90,6 @@
placeholder="Name, Schule oder Beschreibung">
<button id="clear-route-search" type="button">Suche leeren</button>
</div>
<p id="all-routes-summary" aria-live="polite">Routen werden geladen …</p>
<ul id="all-route-list" class="route-list" aria-live="polite">
<li><p>Routen werden geladen …</p></li>
@@ -99,7 +97,6 @@
</section>
</div>
</section>
<section id="route-page" class="app-page" hidden>
<header class="has-back-button">
<button class="back-button" type="button" data-page-target="#routes-page" aria-label="Zurück zur Routenauswahl" title="Zurück zur Routenauswahl">
@@ -110,13 +107,11 @@
<div class="content">
<p id="route-description"></p>
<dl>
<div><dt>Länge</dt><dd id="route-distance">–</dd></div>
<div><dt>Höhenmeter</dt><dd id="route-elevation">–</dd></div>
<div><dt>POIs</dt><dd id="route-poi-count">–</dd></div>
</dl>
<fieldset>
<legend>Routensteuerung</legend>
<button id="start-route" type="button" aria-label="Route starten" title="Route starten">
@@ -130,7 +125,6 @@
</button>
</fieldset>
<p id="tracking-status" class="notice" hidden></p>
<h2>Stationen</h2>
<ul id="poi-list" class="route-list"></ul>
</div>
@@ -143,13 +137,11 @@
</button>
<h1 id="poi-title">Station</h1>
</header>
<div class="content">
<div id="slideshow" aria-live="polite">
<figure>
<img id="slide-image" alt="">
</figure>
<fieldset>
<legend>Bildnavigation</legend>
<button id="slide-prev" type="button" aria-label="Vorheriges Bild" title="Vorheriges Bild">
@@ -164,12 +156,10 @@
</button>
</fieldset>
</div>
<p id="poi-description"></p>
<audio id="poi-audio" controls preload="metadata" aria-label="Audiowiedergabe zur Station" hidden>Audio kann auf diesem Gerät nicht wiedergegeben werden.</audio>
</div>
</section>
<footer id="navigation-footer" hidden aria-live="polite">
<svg id="navigation-arrow" viewBox="0 0 64 64" aria-hidden="true" focusable="false">
<path d="M32 4 53 54 32 44 11 54Z"/>
@@ -180,45 +170,42 @@
</p>
</footer>
</main>
<dialog id="info-dialog" class="info-dialog" aria-labelledby="info-dialog-title">
<div class="info-dialog-shell">
<header class="info-dialog-header">
<button id="info-back" type="button" aria-label="Zurück zum Informationsmenü" title="Zurück" hidden>‹</button>
<h2 id="info-dialog-title">Informationen</h2>
<button id="info-close" type="button" aria-label="Informationsfenster schließen" title="Schließen">×</button>
</header>
<div class="info-dialog-content">
<nav id="info-menu" class="info-menu" aria-label="Rechtliche und technische Informationen">
<button type="button" data-info-target="privacy">Datenschutzerklärung</button>
<button type="button" data-info-target="imprint">Impressum</button>
<button type="button" data-info-target="technology">Cookies und verwendete JavaScripts</button>
</nav>
<article class="info-page" data-info-page="privacy" hidden>
<p class="legal-warning"><strong>Vor der öffentlichen Freigabe:</strong> Die gekennzeichneten Angaben müssen durch die verantwortliche Schule, Gemeinde, Initiative oder sonstige Betreiberin ergänzt und rechtlich geprüft werden.</p>
<h3>1. Verantwortliche Stelle</h3>
<p class="legal-warning"><strong>Hinweis:</strong> Diese Datenschutzhinweise sind mit den öffentlich angegebenen Kontaktdaten der Staatlichen Grundschule Herpf vorbefüllt. Hostinganbieter, konkrete Serverprotokollierung und Speicherdauer müssen für die tatsächlich betriebene Wegwichtel-Instanz noch technisch geprüft und ergänzt werden.</p>
<h3>1. Verantwortliche Stelle und Datenschutzkontakt</h3>
<p>
<strong>[Name der verantwortlichen Organisation ergänzen]</strong><br>
[Straße und Hausnummer ergänzen]<br>
[Postleitzahl und Ort ergänzen]<br>
E-Mail: [E-Mail-Adresse ergänzen]<br>
Telefon: [Telefonnummer ergänzen]
<strong>Staatliche Grundschule Herpf</strong><br>
Zum Eichig 3<br>
98617 Meiningen / OT Herpf<br>
Telefon: (036943) 63722<br>
E-Mail: info@grundschule-herpf.de
</p>
<p>
Datenschutzangaben der Schule:<br>
<strong>Staatliches Schulamt Südthüringen</strong><br>
Datenschutzbeauftragter<br>
Hölderlinstraße 1<br>
98527 Suhl<br>
Telefon: 03681 / 7341 25<br>
Fax: 03681 / 7341 09<br>
E-Mail: datenschutz.suedthueriungen@schulamt.thueringen.de
</p>
<p>Der Datenschutzbeauftragte kann außerdem über die Postadresse der Schule mit dem Zusatz „der Datenschutzbeauftragte“ erreicht werden.</p>
<h3>2. Bereitstellung der Website</h3>
<p>Beim Aufruf der Website können durch den Webserver technisch erforderliche Verbindungsdaten verarbeitet werden. Dazu können IP-Adresse, Zeitpunkt, aufgerufene Adresse, übertragene Datenmenge, Browsertyp und Betriebssystem gehören. Welche Protokolldaten tatsächlich gespeichert werden und wie lange dies geschieht, muss für den eingesetzten Hostingdienst ergänzt werden.</p>
<p>Hostinganbieter: <strong>[Anbieter, Anschrift und Speicherdauer ergänzen]</strong></p>
<p>Beim Aufruf der Website können durch den Webserver technisch erforderliche Verbindungsdaten verarbeitet werden. Dazu können IP-Adresse, Datum und Uhrzeit der Anfrage, Zeitzonendifferenz zur Greenwich Mean Time, aufgerufene Seite, Zugriffsstatus oder HTTP-Statuscode, übertragene Datenmenge, Referrer, Browser, Betriebssystem, Oberfläche sowie Sprache und Version der Browsersoftware gehören. Diese Verarbeitung dient der technischen Bereitstellung, Stabilität und Sicherheit der Website.</p>
<p>Hostinganbieter, konkrete Protokolldaten und Speicherdauer dieser Wegwichtel-Instanz: <strong>[Anbieter, Anschrift, Art der Serverlogs und Speicherdauer ergänzen]</strong></p>
<h3>3. Standortdaten</h3>
<p>Die Standortfreigabe ist freiwillig. Sie wird benötigt, um nahe Routen vorzuschlagen und während einer gestarteten Route Entfernungen und Richtungen zu berechnen. Die Anwendung verarbeitet die laufende Position im Browser. Sie legt daraus keinen serverseitigen Standortverlauf an.</p>
<h3>4. Lokaler Streckenfortschritt</h3>
<p>Nach ausdrücklicher Aktivierung speichert die Anwendung ein JSON-Objekt im Local Storage dieses Browsers. Es enthält die aktuell ausgewählte Strecke, besuchte und angesehene Stationen, abgespielte Audioeinträge, den Routenstatus sowie Zeitstempel. Diese Angaben bleiben auf dem verwendeten Gerät und werden nicht mit dem Server synchronisiert.</p>
<p>Gespeicherte Streckendaten werden beim nächsten Öffnen entfernt, sobald ihre letzte Aktualisierung mindestens 24 Stunden zurückliegt. Beim Ausschalten der Funktion werden die Streckendaten sofort gelöscht.</p>
<label class="switch-row compact-switch">
<span>
<strong>Streckenfortschritt lokal speichern</strong>
@@ -227,52 +214,60 @@
<input class="progress-storage-toggle" type="checkbox" role="switch">
<span class="switch-track" aria-hidden="true"></span>
</label>
<h3>5. Cookies und Tracking</h3>
<p>Die Anwendung setzt derzeit keine Cookies. Sie verwendet keine Reichweitenmessung, keine personalisierte Werbung und keine Trackingdienste. Eine technisch notwendige Local-Storage-Einstellung merkt sich lediglich, ob die freiwillige Speicherung des Streckenfortschritts aktiviert wurde.</p>
<h3>6. Inhalte von Schülerinnen und Schülern</h3>
<p>Fotos, Texte und Sprachaufnahmen dürfen nur veröffentlicht werden, wenn die erforderlichen Rechte und Einwilligungen vorliegen. Besonders bei erkennbaren Kindern sind die Vorgaben der Schule und die Einwilligungen der Sorgeberechtigten zu beachten.</p>
<h3>7. Rechte betroffener Personen</h3>
<p>Betroffene Personen können je nach rechtlicher Voraussetzung Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit und Widerspruch verlangen. Erteilte Einwilligungen können für die Zukunft widerrufen werden.</p>
<p>Datenschutzkontakt: <strong>[Kontakt ergänzen]</strong><br>Zuständige Datenschutzaufsicht: <strong>[Behörde und Kontakt ergänzen]</strong></p>
<p>Betroffene Personen können je nach rechtlicher Voraussetzung Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit und Widerspruch verlangen. Erteilte Einwilligungen können für die Zukunft widerrufen werden. Außerdem besteht das Recht, sich bei einer Datenschutzaufsichtsbehörde über die Verarbeitung personenbezogener Daten zu beschweren.</p>
<p>
Zuständige Datenschutzaufsicht:<br>
<strong>Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI)</strong><br>
Postfach 90 04 55, 99107 Erfurt<br>
Häßlerstraße 8, 99096 Erfurt<br>
Telefon: 0361 / 57 311 29 00<br>
Telefax: 0361 / 57 311 29 04<br>
E-Mail: poststelle@datenschutz.thueringen.de<br>
Website: www.tlfdi.de
</p>
</article>
<article class="info-page" data-info-page="imprint" hidden>
<p class="legal-warning"><strong>Platzhalter:</strong> Dieses Impressum ist vor der Veröffentlichung mit den Angaben der tatsächlichen Betreiberin zu vervollständigen.</p>
<p class="legal-warning"><strong>Hinweis:</strong> Dieses Impressum ist mit den öffentlich angegebenen Angaben der Staatlichen Grundschule Herpf vorbefüllt. Vor Veröffentlichung der Wegwichtel-Instanz sollte geprüft werden, ob für dieses konkrete Angebot zusätzliche oder abweichende Pflichtangaben erforderlich sind.</p>
<h3>Anbieter und verantwortlich für dieses Angebot</h3>
<p>
<strong>[Vollständiger Name der Schule, Gemeinde, Initiative oder Person]</strong><br>
[Rechtsform oder Vertretungsberechtigung ergänzen]<br>
[Straße und Hausnummer]<br>
[Postleitzahl und Ort]
<strong>Staatliche Grundschule Herpf</strong><br>
Zum Eichig 3<br>
98617 Meiningen / OT Herpf
</p>
<h3>Kontakt</h3>
<p>
Telefon: [Telefonnummer ergänzen]<br>
E-Mail: [E-Mail-Adresse ergänzen]
Telefon: (036943) 63722<br>
Telefax: (036943) 63843<br>
E-Mail: info@grundschule-herpf.de
</p>
<h3>Vertretungsberechtigte Person</h3>
<p>Frau Wichert, Schulleiterin</p>
<h3>Schulträger</h3>
<p>
<strong>Landkreis Schmalkalden-Meiningen</strong><br>
Landratsamt<br>
Obertshäuser Platz 1<br>
98617 Meiningen
</p>
<h3>Inhaltlich verantwortlich</h3>
<p>[Name und ladungsfähige Anschrift ergänzen]</p>
<p>Frau Wichert</p>
<h3>Weitere Pflichtangaben</h3>
<p>Je nach Betreiberin können zusätzliche Angaben erforderlich sein, zum Beispiel zur Aufsichtsbehörde, Registernummer, Umsatzsteuer-ID oder zuständigen Körperschaft. Nicht benötigte Angaben sind zu entfernen.</p>
<p>Je nach konkretem Betrieb der Wegwichtel-Instanz können zusätzliche Angaben erforderlich sein, zum Beispiel zur technischen Betreiberin, zum Hosting oder zu ergänzenden Zuständigkeiten. Nicht benötigte Angaben sind zu entfernen.</p>
<h3>Haftung für Inhalte und Verweise</h3>
<p>Die Inhalte werden sorgfältig erstellt und regelmäßig geprüft. Für Hinweise auf fehlerhafte, veraltete oder rechtsverletzende Inhalte steht der oben genannte Kontakt zur Verfügung. Für Inhalte externer Seiten sind deren Betreiber verantwortlich.</p>
<h3>Urheber- und Nutzungsrechte</h3>
<p>Texte, Bilder, Tonaufnahmen und Routendaten dürfen nur im Rahmen der jeweils eingeräumten Nutzungsrechte verwendet werden. Die Rechte der beteiligten Kinder, Familien, Lehrkräfte und weiteren Urheberinnen und Urheber bleiben unberührt.</p>
<p>Soweit nicht anders angegeben, liegen die Rechte bei der Grundschule Herpf. Texte, Bilder, Tonaufnahmen und Routendaten dürfen nur im Rahmen der jeweils eingeräumten Nutzungsrechte verwendet werden. Die Rechte der beteiligten Kinder, Familien, Lehrkräfte und weiteren Urheberinnen und Urheber bleiben unberührt.</p>
</article>
<article class="info-page" data-info-page="technology" hidden>
<h3>Cookies</h3>
<p>Wegwichtel setzt derzeit keine Cookies. Die Anwendung besitzt keine Werbe-, Analyse- oder Trackingfunktionen. Der Cookie-Schalter bleibt deshalb ausgeschaltet.</p>
<label class="switch-row switch-disabled compact-switch">
<span>
<strong>Optionale Cookies</strong>
@@ -281,10 +276,8 @@
<input class="cookie-toggle" type="checkbox" role="switch" disabled>
<span class="switch-track" aria-hidden="true"></span>
</label>
<h3>Local Storage</h3>
<p>Der Browser kann auf Wunsch Streckenfortschritte lokal speichern. Verwendet werden die Schlüssel <code>wegwichtel.privacy.v1</code> für die gewählte Einstellung und <code>wegwichtel.route-progress.v1</code> für die freiwilligen Streckendaten. Die Streckendaten werden nach 24 Stunden bereinigt oder beim Ausschalten sofort entfernt.</p>
<label class="switch-row compact-switch">
<span>
<strong>Streckenfortschritt lokal speichern</strong>
@@ -293,7 +286,6 @@
<input class="progress-storage-toggle" type="checkbox" role="switch">
<span class="switch-track" aria-hidden="true"></span>
</label>
<h3>Lokal eingebundene JavaScripts</h3>
<ul>
<li><strong>jQuery 4.0.0:</strong> DOM-Zugriffe, Ereignisbehandlung und API-Anfragen.</li>
@@ -301,7 +293,6 @@
<li><strong>Wegwichtel-Module:</strong> Routenanzeige, Navigation, Standort, Geräteausrichtung, Bilder, Audio, Vibration, Datenschutz und lokaler Fortschritt.</li>
</ul>
<p>Alle Bibliotheken werden vom eigenen Server ausgeliefert. Die Anwendung lädt keine JavaScripts, Schriftarten oder Analysepixel von externen Anbietern.</p>
<h3>Browser-Schnittstellen</h3>
<ul>
<li>Geolocation API für freiwillige Standortdaten,</li>
@@ -310,7 +301,6 @@
<li>HTML-Audio und HTML-Bilder für lokal bereitgestellte Medien,</li>
<li>History API für die Navigation zwischen den Ansichten.</li>
</ul>
<h3>Technische Grenzen</h3>
<p>Browser und Betriebssystem können Standort, Kompass, Audio, Vibration oder Local Storage sperren. Die Anwendung bleibt grundsätzlich nutzbar, einzelne Komfortfunktionen können dann jedoch fehlen.</p>
</article>
+92 -22
View File
@@ -1,10 +1,10 @@
(function (ns) {
'use strict';
const DROPDOWN_ANIMATION_MS = 180;
const dialog = document.getElementById('info-dialog');
const title = document.getElementById('info-dialog-title');
const menu = document.getElementById('info-menu');
const backButton = document.getElementById('info-back');
const dropdown = document.getElementById('info-dropdown');
const closeButton = document.getElementById('info-close');
const infoButton = document.getElementById('info-button');
const pageTitles = Object.freeze({
@@ -12,6 +12,8 @@
imprint: 'Impressum',
technology: 'Cookies und Technik'
});
let dropdownOpen = false;
let dropdownCloseTimer = null;
function isActivation(event) {
if (event.type === 'keydown') return event.key === 'Enter' || event.key === ' ';
@@ -21,6 +23,7 @@
}
function activate(element, handler) {
if (!element) return;
element.addEventListener('pointerup', event => {
if (!isActivation(event)) return;
event.preventDefault();
@@ -35,6 +38,64 @@
});
}
function setClass(element, className, enabled) {
if (!element || !element.classList) return;
element.classList.toggle(className, enabled);
}
function runNextFrame(callback) {
if (typeof globalThis.requestAnimationFrame === 'function') {
globalThis.requestAnimationFrame(callback);
return;
}
callback();
}
function schedule(callback, timeout) {
if (typeof globalThis.setTimeout === 'function') return globalThis.setTimeout(callback, timeout);
callback();
return null;
}
function cancelSchedule(timer) {
if (timer === null || typeof globalThis.clearTimeout !== 'function') return;
globalThis.clearTimeout(timer);
}
function setDropdownOpen(open) {
if (!dropdown || !infoButton) return;
dropdownOpen = open;
infoButton.setAttribute('aria-expanded', open ? 'true' : 'false');
cancelSchedule(dropdownCloseTimer);
dropdownCloseTimer = null;
if (open) {
dropdown.hidden = false;
runNextFrame(() => setClass(dropdown, 'is-open', true));
return;
}
setClass(dropdown, 'is-open', false);
dropdownCloseTimer = schedule(() => {
if (dropdownOpen) return;
dropdown.hidden = true;
}, DROPDOWN_ANIMATION_MS);
}
function toggleDropdown() {
setDropdownOpen(!dropdownOpen);
}
function elementContains(element, target) {
if (!element || !target) return false;
if (element === target) return true;
return typeof element.contains === 'function' && element.contains(target);
}
function isInsideInfoControl(target) {
return elementContains(infoButton, target) || elementContains(dropdown, target);
}
function syncStorageToggles() {
const enabled = ns.PrivacyStorage.isProgressStorageEnabled();
document.querySelectorAll('.progress-storage-toggle').forEach(toggle => {
@@ -45,22 +106,11 @@
});
}
function showMenu() {
title.textContent = 'Informationen';
menu.hidden = false;
backButton.hidden = true;
document.querySelectorAll('[data-info-page]').forEach(page => {
page.hidden = true;
});
}
function showPage(name) {
const page = document.querySelector(`[data-info-page="${name}"]`);
if (!page) return;
title.textContent = pageTitles[name] ?? 'Informationen';
menu.hidden = true;
backButton.hidden = false;
document.querySelectorAll('[data-info-page]').forEach(item => {
item.hidden = item !== page;
});
@@ -68,18 +118,22 @@
if (content) content.scrollTop = 0;
}
function openDialog() {
if (dialog.open) return;
showMenu();
function openDialog(name = 'privacy') {
const pageName = Object.prototype.hasOwnProperty.call(pageTitles, name) ? name : 'privacy';
setDropdownOpen(false);
showPage(pageName);
syncStorageToggles();
if (typeof dialog.showModal === 'function') dialog.showModal();
else dialog.setAttribute('open', '');
if (!dialog.open) {
if (typeof dialog.showModal === 'function') dialog.showModal();
else dialog.setAttribute('open', '');
}
closeButton.focus();
}
function closeDialog() {
if (typeof dialog.close === 'function') dialog.close();
else dialog.removeAttribute('open');
setDropdownOpen(false);
infoButton.focus();
}
@@ -95,11 +149,21 @@
});
}
activate(infoButton, openDialog);
activate(infoButton, toggleDropdown);
activate(closeButton, closeDialog);
activate(backButton, showMenu);
document.querySelectorAll('[data-info-target]').forEach(button => {
activate(button, () => showPage(button.dataset.infoTarget));
activate(button, () => openDialog(button.dataset.infoTarget));
});
document.addEventListener('pointerup', event => {
if (!dropdownOpen || isInsideInfoControl(event.target)) return;
setDropdownOpen(false);
});
document.addEventListener('keydown', event => {
if (!dropdownOpen || event.key !== 'Escape' || dialog.open) return;
event.preventDefault();
setDropdownOpen(false);
infoButton.focus();
});
dialog.addEventListener('pointerup', event => {
@@ -116,5 +180,11 @@
bindStorageToggles();
syncStorageToggles();
ns.LegalUi = Object.freeze({ open: openDialog, close: closeDialog, syncStorageToggles });
ns.LegalUi = Object.freeze({
open: openDialog,
close: closeDialog,
toggleMenu: toggleDropdown,
closeMenu: () => setDropdownOpen(false),
syncStorageToggles
});
}(globalThis.Wegwichtel ??= {}));