App als Download hinzugefügt

This commit is contained in:
2026-05-05 16:13:05 +02:00
parent 6d763372ae
commit bb4d4ae86b
6 changed files with 435 additions and 22 deletions

View File

@@ -12,4 +12,14 @@ public class PlaylistController {
public String index() { public String index() {
return "index"; return "index";
} }
@GetMapping("/impressum")
public String impressum() {
return "impressum";
}
@GetMapping("/datenschutz")
public String datenschutz() {
return "datenschutz";
}
} }

View File

@@ -1203,3 +1203,139 @@ body { padding-top: 4rem; }
width: 100%; width: 100%;
max-width: 260px; max-width: 260px;
} }
/* ── Footer links ── */
.footer-links {
display: flex;
gap: .6rem;
align-items: center;
font-size: .82rem;
color: var(--text-muted);
margin-top: .4rem;
}
.footer-links a {
color: var(--text-muted);
transition: color .15s;
}
.footer-links a:hover { color: var(--teal); }
/* ── Cookie Banner ── */
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 800;
background: var(--surface-2);
border-top: 1px solid var(--border);
padding: 1rem 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.25rem;
flex-wrap: wrap;
box-shadow: 0 -4px 24px rgba(0,0,0,.4);
}
.cookie-text {
font-size: .88rem;
color: var(--text-muted);
flex: 1;
min-width: 200px;
}
.cookie-text a {
color: var(--teal);
text-decoration: underline;
text-underline-offset: 2px;
}
.cookie-btn { white-space: nowrap; flex-shrink: 0; }
/* ── Legal pages ── */
.legal-header {
padding: 1rem 0;
background: var(--bg-2);
border-bottom: 1px solid var(--border);
}
.legal-header .section-inner {
display: flex;
align-items: center;
gap: 1.25rem;
}
.legal-back {
display: inline-flex;
align-items: center;
gap: .35rem;
color: var(--text-muted);
font-size: .9rem;
transition: color .15s;
}
.legal-back:hover { color: var(--teal); }
.legal-logo {
height: 32px;
width: auto;
margin-left: auto;
opacity: .75;
}
.legal-main {
padding: 4rem 0 6rem;
min-height: calc(100vh - 200px);
}
.legal-inner h1 {
font-size: clamp(1.8rem, 4vw, 2.6rem);
font-weight: 900;
letter-spacing: -.5px;
margin-bottom: 2.5rem;
background: var(--grad);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.legal-section {
margin-bottom: 2.25rem;
padding-bottom: 2.25rem;
border-bottom: 1px solid var(--border);
}
.legal-section:last-child {
border-bottom: none;
padding-bottom: 0;
}
.legal-section h2 {
font-size: 1.05rem;
font-weight: 700;
color: var(--teal);
margin-bottom: .75rem;
text-transform: uppercase;
letter-spacing: .05em;
font-size: .82rem;
}
.legal-section p {
color: var(--text-muted);
line-height: 1.8;
margin-bottom: .75rem;
}
.legal-section p:last-child { margin-bottom: 0; }
.legal-section ul {
list-style: none;
margin: .5rem 0 .75rem;
display: flex;
flex-direction: column;
gap: .5rem;
}
.legal-section ul li {
color: var(--text-muted);
padding-left: 1.1rem;
position: relative;
line-height: 1.7;
}
.legal-section ul li::before {
content: '';
position: absolute;
left: 0;
color: var(--teal);
}
.legal-section a {
color: var(--teal);
text-decoration: underline;
text-underline-offset: 2px;
transition: color .15s;
}
.legal-section a:hover { color: var(--teal-h); }

View File

@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Datenschutzerklärung LibreDeck</title>
<link rel="icon" type="image/png" th:href="@{/images/favicon.png}">
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header class="legal-header">
<div class="section-inner">
<a th:href="@{/}" class="legal-back">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true">
<polyline points="15 18 9 12 15 6"/>
</svg>
Zurück
</a>
<img th:src="@{/images/logo.png}" alt="LibreDeck" class="legal-logo">
</div>
</header>
<main class="legal-main">
<div class="section-inner legal-inner">
<h1>Datenschutzerklärung</h1>
<section class="legal-section">
<h2>1. Verantwortlicher</h2>
<p>
Mario Störmer<br>
Langheide 14<br>
24354 Rieseby<br>
E-Mail: <a href="mailto:mario.stoerner@langhei.de">mario.stoerner@langhei.de</a>
</p>
</section>
<section class="legal-section">
<h2>2. Grundsätze der Datenverarbeitung</h2>
<p>
LibreDeck verarbeitet keine personenbezogenen Daten dauerhaft. Es werden keine
Nutzerkonten angelegt, keine Registrierung verlangt und keine Daten persistent
auf dem Server gespeichert. Alle Verarbeitungsschritte finden ausschließlich
im Arbeitsspeicher während einer aktiven Sitzung statt.
</p>
</section>
<section class="legal-section">
<h2>3. Zugriffsdaten / Server-Logs</h2>
<p>
Beim Aufruf der Website werden vom Webserver automatisch allgemeine Zugriffsdaten
erfasst (sog. Server-Logs): IP-Adresse, Zeitpunkt des Zugriffs, aufgerufene URL,
HTTP-Statuscode, übertragene Datenmenge sowie Referrer und Browser-Kennung.
Diese Daten werden ausschließlich zur Sicherstellung des technischen Betriebs
verwendet und nach spätestens 7 Tagen gelöscht. Rechtsgrundlage ist
Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse am sicheren Betrieb).
</p>
</section>
<section class="legal-section">
<h2>4. Cookies und Sitzungsdaten</h2>
<p>
LibreDeck setzt ausschließlich technisch notwendige Session-Cookies ein
(JSESSIONID). Diese dienen dazu, während einer Browsersitzung den
Authentifizierungsstatus gegenüber den verbundenen Streaming-Diensten
aufrechtzuerhalten. Das Cookie wird beim Schließen des Browsers automatisch
gelöscht. Es findet keine Auswertung für Werbe- oder Trackingzwecke statt.
</p>
<p>
Rechtsgrundlage: Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung, da das Cookie
für den Betrieb des Dienstes technisch erforderlich ist).
</p>
</section>
<section class="legal-section">
<h2>5. Verbindung mit Streaming-Diensten</h2>
<p>
Wenn Sie sich über LibreDeck mit einem Streaming-Dienst (Deezer, Spotify, Tidal,
Apple Music, YouTube) verbinden, werden Sie zur Anmeldeseite des jeweiligen
Anbieters weitergeleitet. LibreDeck erhält anschließend ein zeitlich begrenztes
Zugriffstoken, das nur für die Dauer Ihrer Sitzung gespeichert wird. Die
eigentliche Authentifizierung und die damit verbundene Datenverarbeitung erfolgt
beim jeweiligen Drittanbieter; dessen Datenschutzbestimmungen gelten:
</p>
<ul>
<li>Spotify: <a href="https://www.spotify.com/de/legal/privacy-policy/" target="_blank" rel="noopener">Datenschutzrichtlinie Spotify</a></li>
<li>Deezer: <a href="https://www.deezer.com/legal/personal-datas" target="_blank" rel="noopener">Datenschutzrichtlinie Deezer</a></li>
<li>Tidal: <a href="https://tidal.com/privacy" target="_blank" rel="noopener">Datenschutzrichtlinie Tidal</a></li>
<li>Apple Music: <a href="https://www.apple.com/de/legal/privacy/" target="_blank" rel="noopener">Datenschutzrichtlinie Apple</a></li>
<li>YouTube: <a href="https://policies.google.com/privacy" target="_blank" rel="noopener">Datenschutzrichtlinie Google</a></li>
</ul>
</section>
<section class="legal-section">
<h2>6. MusicBrainz</h2>
<p>
Zur Ermittlung von Erscheinungsjahren werden Künstlername und Titel einzelner
Tracks an die öffentliche API von MusicBrainz (MetaBrainz Foundation, USA)
übermittelt. Es handelt sich dabei um keine personenbezogenen Daten.
Weitere Informationen: <a href="https://metabrainz.org/privacy" target="_blank" rel="noopener">MetaBrainz Datenschutzrichtlinie</a>.
</p>
</section>
<section class="legal-section">
<h2>7. Ihre Rechte</h2>
<p>Nach der DSGVO stehen Ihnen folgende Rechte zu:</p>
<ul>
<li><strong>Auskunft</strong> (Art. 15 DSGVO): Sie können Auskunft über die zu Ihrer Person gespeicherten Daten verlangen.</li>
<li><strong>Berichtigung</strong> (Art. 16 DSGVO): Sie können die Berichtigung unrichtiger Daten verlangen.</li>
<li><strong>Löschung</strong> (Art. 17 DSGVO): Sie können die Löschung Ihrer Daten verlangen, soweit keine gesetzliche Aufbewahrungspflicht entgegensteht.</li>
<li><strong>Einschränkung</strong> (Art. 18 DSGVO): Sie können die Einschränkung der Verarbeitung Ihrer Daten verlangen.</li>
<li><strong>Widerspruch</strong> (Art. 21 DSGVO): Sie können der Verarbeitung Ihrer Daten widersprechen, soweit diese auf Art. 6 Abs. 1 lit. f DSGVO beruht.</li>
<li><strong>Beschwerderecht</strong> (Art. 77 DSGVO): Sie haben das Recht, sich bei einer Datenschutzaufsichtsbehörde zu beschweren.</li>
</ul>
<p>Anfragen richten Sie bitte an: <a href="mailto:mario.stoerner@langhei.de">mario.stoerner@langhei.de</a></p>
</section>
<section class="legal-section">
<h2>8. Änderungen</h2>
<p>
Wir behalten uns vor, diese Datenschutzerklärung anzupassen, um sie stets den
aktuellen rechtlichen Anforderungen oder Änderungen am Dienst anzupassen.
</p>
</section>
</div>
</main>
<footer class="site-footer">
<div class="section-inner footer-inner">
<img th:src="@{/images/logo.png}" alt="LibreDeck" class="footer-logo-img">
<p>Keine Datenspeicherung · Kein Account erforderlich</p>
<div class="footer-links">
<a th:href="@{/impressum}">Impressum</a>
<span aria-hidden="true">·</span>
<a th:href="@{/datenschutz}">Datenschutz</a>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Impressum LibreDeck</title>
<link rel="icon" type="image/png" th:href="@{/images/favicon.png}">
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header class="legal-header">
<div class="section-inner">
<a th:href="@{/}" class="legal-back">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true">
<polyline points="15 18 9 12 15 6"/>
</svg>
Zurück
</a>
<img th:src="@{/images/logo.png}" alt="LibreDeck" class="legal-logo">
</div>
</header>
<main class="legal-main">
<div class="section-inner legal-inner">
<h1>Impressum</h1>
<section class="legal-section">
<h2>Angaben gemäß § 5 TMG</h2>
<p>
Mario Störmer<br>
Langheide 14<br>
24354 Rieseby
</p>
</section>
<section class="legal-section">
<h2>Kontakt</h2>
<p>E-Mail: <a href="mailto:mario.stoerner@langhei.de">mario.stoerner@langhei.de</a></p>
</section>
<section class="legal-section">
<h2>EU-Streitbeilegung</h2>
<p>
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener">
https://ec.europa.eu/consumers/odr/
</a>.<br>
Unsere E-Mail-Adresse finden Sie oben im Impressum.
</p>
<p>
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle teilzunehmen.
</p>
</section>
<section class="legal-section">
<h2>Haftung für Inhalte</h2>
<p>
Als Diensteanbieter sind wir gemäß § 7 Abs. 1 TMG für eigene Inhalte auf diesen Seiten
nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG 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.
</p>
</section>
<section class="legal-section">
<h2>Urheberrecht</h2>
<p>
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen
dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art
der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen
Zustimmung des jeweiligen Autors bzw. Erstellers.
</p>
</section>
</div>
</main>
<footer class="site-footer">
<div class="section-inner footer-inner">
<img th:src="@{/images/logo.png}" alt="LibreDeck" class="footer-logo-img">
<p>Keine Datenspeicherung · Kein Account erforderlich</p>
<div class="footer-links">
<a th:href="@{/impressum}">Impressum</a>
<span aria-hidden="true">·</span>
<a th:href="@{/datenschutz}">Datenschutz</a>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -216,6 +216,28 @@
</div> </div>
</section> </section>
<!-- ── Android Download ── -->
<section class="download-section" id="androidDownload" style="display:none">
<div class="section-inner download-inner">
<div class="download-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12" y2="18.01"/>
</svg>
</div>
<div class="download-text">
<h2>Android-App</h2>
<p>Scanne deine Karten direkt mit der App öffnet den richtigen Streaming-Dienst per Tap.</p>
<a href="/downloads/libredeck.apk" class="btn btn-lg download-btn" download>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true" style="flex-shrink:0">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
</svg>
APK herunterladen
</a>
<p class="download-hint">Android · Sideload · Einstellungen → Apps → Unbekannte Quellen erlauben</p>
</div>
</div>
</section>
<!-- ── Card detail ── --> <!-- ── Card detail ── -->
<section class="detail-section"> <section class="detail-section">
<div class="section-inner detail-inner"> <div class="section-inner detail-inner">
@@ -458,36 +480,40 @@
</div> </div>
</section> </section>
<!-- ── Android Download ── -->
<section class="download-section">
<div class="section-inner download-inner">
<div class="download-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12" y2="18.01"/>
</svg>
</div>
<div class="download-text">
<h2>Android-App</h2>
<p>Scanne deine Karten direkt mit der App öffnet den richtigen Streaming-Dienst per Tap.</p>
<a href="/downloads/libredeck.apk" class="btn btn-lg download-btn" download>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true" style="flex-shrink:0">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
</svg>
APK herunterladen
</a>
<p class="download-hint">Android · Sideload · Einstellungen → Apps → Unbekannte Quellen erlauben</p>
</div>
</div>
</section>
<!-- ── Footer ── --> <!-- ── Footer ── -->
<footer class="site-footer"> <footer class="site-footer">
<div class="section-inner footer-inner"> <div class="section-inner footer-inner">
<img th:src="@{/images/logo.png}" alt="LibreDeck" class="footer-logo-img"> <img th:src="@{/images/logo.png}" alt="LibreDeck" class="footer-logo-img">
<p>Keine Datenspeicherung · Kein Account erforderlich</p> <p>Keine Datenspeicherung · Kein Account erforderlich</p>
<div class="footer-links">
<a th:href="@{/impressum}">Impressum</a>
<span aria-hidden="true">·</span>
<a th:href="@{/datenschutz}">Datenschutz</a>
</div>
</div> </div>
</footer> </footer>
<!-- ── Cookie Banner ── -->
<div id="cookieBanner" class="cookie-banner" role="dialog" aria-label="Cookie-Hinweis" style="display:none">
<p class="cookie-text">
Diese Website verwendet ausschließlich technisch notwendige Cookies zur Aufrechterhaltung
der Sitzung. Es findet kein Tracking statt.
<a href="/datenschutz">Datenschutzerklärung</a>
</p>
<button class="btn btn-sm cookie-btn" onclick="acceptCookies()">Verstanden</button>
</div>
<script>
(function () {
if (!localStorage.getItem('cookieConsent')) {
document.getElementById('cookieBanner').style.display = 'flex';
}
})();
function acceptCookies() {
localStorage.setItem('cookieConsent', '1');
document.getElementById('cookieBanner').style.display = 'none';
}
</script>
<!-- ── Bulk-check warning dialog ── --> <!-- ── Bulk-check warning dialog ── -->
<div id="bulkCheckDialog" style="display:none" role="dialog" aria-modal="true" aria-labelledby="bulkCheckTitle"> <div id="bulkCheckDialog" style="display:none" role="dialog" aria-modal="true" aria-labelledby="bulkCheckTitle">
<div class="dialog-backdrop" onclick="document.getElementById('bulkCheckDialog').style.display='none'"></div> <div class="dialog-backdrop" onclick="document.getElementById('bulkCheckDialog').style.display='none'"></div>
@@ -537,6 +563,11 @@
</div> </div>
<script> <script>
// ── Android download banner ──
if (/Android/i.test(navigator.userAgent)) {
document.getElementById('androidDownload').style.display = '';
}
// ── Unified service state ── // ── Unified service state ──
const SERVICES = ['Spotify', 'Tidal', 'Deezer', 'Apple Music', 'YouTube']; const SERVICES = ['Spotify', 'Tidal', 'Deezer', 'Apple Music', 'YouTube'];
const GEN_IDS = ['panelSpotify', 'panelTidal', 'panelDeezer', 'panelApple', 'panelYoutube']; const GEN_IDS = ['panelSpotify', 'panelTidal', 'panelDeezer', 'panelApple', 'panelYoutube'];