diff --git a/README.md b/README.md index 5fb123c..82f4732 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Datumsrechner Live: [https://date.elpatron.me](https://date.elpatron.me) - Datum plus/minus X Wochen/Monate - Kalenderwoche zu Datum - Start-/Enddatum einer Kalenderwoche eines Jahres +- **Sprachausgabe** für alle Ergebnisse (barrierefrei) - Statistik-Dashboard mit Passwortschutz unter `/stats` ## Bundesland-Feiertage @@ -408,6 +409,7 @@ Es werden keine IP-Adressen oder sonstigen persönlichen Daten gespeichert, ledi - *Fokus-Indikatoren:* Deutliche visuelle Hervorhebung des Fokus für alle Bedienelemente. - *Farbkontraste:* Hohe Kontraste für Texte, Buttons und Ergebnisboxen, geprüft nach WCAG-Richtlinien. - *Status- und Fehlermeldungen:* Ergebnisse und Fehler werden mit `aria-live` für Screenreader zugänglich gemacht. +- **Sprachausgabe:** Alle Ergebnisse können über 🔊-Buttons vorgelesen werden (Web Speech API, deutsche Sprache). - *Mobile Optimierung:* Zusätzliche Meta-Tags für bessere Bedienbarkeit auf mobilen Geräten und Unterstützung von Screenreadern. - *SEO:* Das Thema Barrierefreiheit ist in den Meta-Tags für Suchmaschinen sichtbar. diff --git a/templates/index.html b/templates/index.html index 7c0a4a9..f1c4dfd 100644 --- a/templates/index.html +++ b/templates/index.html @@ -296,8 +296,44 @@ button:focus, .accordion-header:focus { color: #1e293b; border-radius: 6px; padding: 0.7em 1em; + padding-right: 4em; box-shadow: 0 1px 2px rgba(30,41,59,0.04); border: 2px solid #2563eb; + position: relative; +} +.read-aloud-btn { + position: absolute; + top: 0.5em; + right: 0.5em; + background: rgba(37, 99, 235, 0.1); + color: var(--primary); + border: 1px solid var(--border); + border-radius: 4px; + padding: 0.3em 0.6em; + font-size: 0.8em; + cursor: pointer; + transition: all 0.2s; + min-width: 44px; + min-height: 44px; + display: flex; + align-items: center; + justify-content: center; + z-index: 5; +} +.read-aloud-btn:hover { + background: rgba(37, 99, 235, 0.2); + border-color: var(--primary); +} +.read-aloud-btn:focus { + outline: 3px solid #facc15; + outline-offset: 2px; + box-shadow: 0 0 0 4px #1e293b; + background: rgba(37, 99, 235, 0.2); + border-color: var(--primary); +} +.read-aloud-btn.playing { + background: var(--primary); + color: white; } .accordion { border-radius: 12px; @@ -525,6 +561,75 @@ footer br + a { // Fokus zurück auf den Hilfe-Button setzen document.querySelector('.help-button').focus(); } + + // Sprachausgabe-Funktionalität + let currentSpeech = null; + + function readAloud(text, button) { + // Stoppe vorherige Wiedergabe + if (currentSpeech) { + currentSpeech.cancel(); + } + + // Entferne "playing" Klasse von allen Buttons + document.querySelectorAll('.read-aloud-btn').forEach(btn => { + btn.classList.remove('playing'); + btn.textContent = '🔊'; + }); + + // Erstelle neue Sprachausgabe + currentSpeech = new SpeechSynthesisUtterance(text); + currentSpeech.lang = 'de-DE'; + currentSpeech.rate = 0.9; + currentSpeech.pitch = 1; + + // Button-Status aktualisieren + button.classList.add('playing'); + button.textContent = '⏹️'; + + // Event-Handler für Ende der Wiedergabe + currentSpeech.onend = function() { + button.classList.remove('playing'); + button.textContent = '🔊'; + currentSpeech = null; + }; + + currentSpeech.onerror = function() { + button.classList.remove('playing'); + button.textContent = '🔊'; + currentSpeech = null; + }; + + // Wiedergabe starten + speechSynthesis.speak(currentSpeech); + } + + function readAloudFromElement(button) { + // Finde das Ergebnis-Element (das div mit class="result") + const resultElement = button.closest('.result'); + if (!resultElement) return; + + // Entferne den Button-Text aus dem zu lesenden Text + const buttonText = button.textContent; + let textToRead = resultElement.textContent.replace(buttonText, '').trim(); + + // Bereinige den Text (entferne HTML-Tags und überschüssige Leerzeichen) + textToRead = textToRead.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim(); + + readAloud(textToRead, button); + } + + function stopReading() { + if (currentSpeech) { + currentSpeech.cancel(); + currentSpeech = null; + } + document.querySelectorAll('.read-aloud-btn').forEach(btn => { + btn.classList.remove('playing'); + btn.textContent = '🔊'; + }); + } + document.addEventListener('DOMContentLoaded', function() { // Sofortige Aktivierung der ersten Accordion-Sektion um Layout-Shifts zu vermeiden const activeIdx = parseInt("{{ active_idx|default(0) }}"); @@ -563,6 +668,7 @@ footer br + a { document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { hideHelp(); + stopReading(); } }); @@ -646,6 +752,7 @@ footer br + a { {% if tage is not none %}
+ {% if request.form.get('werktage') %} Anzahl der Werktage zwischen {{ format_date(request.form.get('start1', '')) }} und {{ format_date(request.form.get('end1', '')) }}{% if request.form.get('bundesland') %} (Feiertage {{ request.form.get('bundesland') }}){% endif %}: {{ tage }} {% else %} @@ -686,7 +793,10 @@ footer br + a { {% if wochentag is not none %} -
Wochentag von {{ format_date(request.form.get('datum3', '')) }}: {{ wochentag }}
+
+ + Wochentag von {{ format_date(request.form.get('datum3', '')) }}: {{ wochentag }} +
{% endif %}
@@ -709,7 +819,10 @@ footer br + a { {% if kw_berechnen is not none %} -
Kalenderwoche von {{ format_date(request.form.get('datum6', '')) }}: {{ kw_berechnen }}
+
+ + Kalenderwoche von {{ format_date(request.form.get('datum6', '')) }}: {{ kw_berechnen }} +
{% endif %} @@ -732,7 +845,10 @@ footer br + a { {% if kw_datum is not none %} -
Start-/Enddatum der KW {{ request.form.get('kw7', '') }} im Jahr {{ request.form.get('jahr7', '') }}: {{ kw_datum }}
+
+ + Start-/Enddatum der KW {{ request.form.get('kw7', '') }} im Jahr {{ request.form.get('jahr7', '') }}: {{ kw_datum }} +
{% endif %} @@ -775,7 +891,10 @@ footer br + a { {% if plusminus_result is not none %} -
{{ plusminus_result }}
+
+ + {{ plusminus_result }} +
{% endif %}