14.09.26, 11:49
Neue Zugangsdaten für App?
https://gemini.google.com/app/b0f56b74be388b22
User prompt: GORE
Response: Der Begriff GORE kann je nach Kontext unterschiedliche Bedeutungen haben. Hier sind die häufigsten:
Film & Gaming (Horror-Genre): Bezeichnet die explizite, oft drastische Darstellung von Blut, Verletzungen und Gewalt (aus dem Englischen gore = geronnenes Blut/Blutvergießen).
Textilien / Bekleidung (Gore-Tex): Die bekannte Marke W. L. Gore & Associates, hergestellt für wasserdichte, winddichte und atmungsaktive Funktionskleidung.
Politik / Prominente: Bezieht sich meist auf Al Gore, den ehemaligen US-Vizepräsidenten und Klimaaktivisten (An Inconvenient Truth).
Musik: Ein Subgenre des Metal (wie Goregrind) oder das Album Gore der Band Deftones.
Technik / IT: Manchmal eine Abkürzung für GORE (z. B. Generic Object Reference Engine in speziellen Software-Kontexten).
Welchen Bereich oder Begriff meinten Sie genau?
User prompt: Was ist Gore auf deutsch?
[TextBox: 1]
Hauptbedeutung: Das englische Wort gore bedeutet wörtlich geronnenes Blut oder Blutvergießen.
Im Popkultur-Kontext: Wenn von „Gore“ in Filmen, Spielen oder der Kunst die Rede ist, übersetzt man es im Deutschen meist mit explizite Gewaltdarstellung oder Blutrünstigkeit.
Verwendungsform: Im Deutschen wird der Begriff „Gore“ im Medienbereich oft als Lehnwort unübersetzt verwendet (z. B. „Gore-Film“ oder „Gore-Effekte“).
User prompt: Geh zu dem Urban Dictionary
Response: Auf Urban Dictionary gibt es für den Begriff Gore verschiedene Definitionen und Slang-Bedeutungen:
„Graphic violence, the depiction of particularly vivid and realistic acts of violence and brutality in visual media such as literature, film, television, and video games.“
Bedeutung: Die extrem detaillierte und plastische Darstellung von Blut, Eingeweiden und roher Gewalt in Medien (Filmen, Videospielen, Musik etc.).
Unterschied zu „Splatter“: Oft wird angemerkt, dass Gore mehr den Fokus auf detaillierte Verletzungen und Eingeweide legt, während Splatter eher das verspritzte Blut betont.
Auf Urban Dictionary finden sich auch humorvolle oder sehr spezifische Slang-Einträge:
Cool / Hardcore: Teilweise als Adjektiv verwendet für etwas, das extrem mutig, drastisch oder riskant ist („When Scott ran up the down escalator... that was pretty gore.“).
Popkultur-Zusätze (z. B. „Gore Kitten“ / „Gore Whore“): Personen, die eine (oft übertriebene) Vorliebe für Horror, Blut und verstörende Inhalte in Medien zeigen.
Al Gore: Häufige Erwähnung des US-Politikers (teils mit Wortspielen wie dem „Gore Effect“ für unerwarteten Kälteeinbruch).
Aktion/Verb (to gore): Das Ausspießen oder Aufspießen durch Hörner (z. B. durch einen Stier).
Sources
User prompt: Bitte liste immer gesondert die Links zu deinen Ergebnissen in einer Seitenleiste
Response: Als KI-Modell erstelle ich Antworten direkt im Textfeld und habe keinen technischen Zugriff auf die Benutzeroberfläche der App oder des Browsers. Daher ist es mir leider nicht möglich, eine separate Seitenleiste im Programmfenster einzufügen oder dort Inhalte anzuzeigen.
Wenn ich Inhalte mit Weblinks recherchiere, kann ich diese stattdessen so für Sie einbinden:
Direkt im Text: Als prägnante Hyperlinks im fließenden Text.
Abschnitt am Ende: Als gesonderte Link-Liste am Ende der Antwort (z. B. unter der Überschrift „Quellen & Links“).
Lassen Sie mich einfach wissen, ob diese Form der Auflistung für Ihre zukünftigen Fragen passt!
User prompt: Bitte am Ende
Response: Alles klar! Ab jetzt füge ich Quellen und relevante Links bei Recherchen immer gesondert als Liste am Ende der Antwort an.
User prompt: Was ist im Jugendjargon der Begriff "Gore"?
Response: Im Jugendjargon und der Internet-Kultur steht der Begriff Gore vor allem für zwei Dinge:
Jugendliche und junge Erwachsene nutzen den Begriff oft im Zusammenhang mit Social Media (wie TikTok, Reddit, Discord oder X/Twitter). Er bezeichnet Inhalte, die echte oder sehr realistisch nachgestellte extreme Gewalt, Blut, schwere Verletzungen oder Leichen zeigen.
Beispiel: „Guck dir das nicht an, das ist voller Gore.“ oder „Auf TikTok ist mir eben voll der Gore-Content in die FYP gespült worden.“
Bedeutung: Es dient oft als Warnung vor verstörenden Videos oder Bildern, die ekelhaft, traumatisch oder schockierend sein können.
In manchen Subkulturen (z. B. Gamer-Szene, Anime-/Manga-Community oder bei Horror-Fans) wird der Begriff auch adjektivisch oder als Stilbeschreibung verwendet:
„Krass“ / „Hardcore“: Beschreibt etwas, das extrem drastisch, rücksichtslos oder heftig ist.
Ästhetik (z. B. „Goth/Gore“ oder „Pastel Gore“): Ein düsterer Zeichen- oder Modestil, der Niedliches (wie Anime-Figuren) mit verstörenden oder blutigen Elementen kombiniert.
Urban Dictionary – Definition zu „Gore“
Wikipedia – Gore (Filmgenre & Medienbegriff)
eSafety Commissioner – Online-Gore und Jugendliche
Sources
User prompt: Wie sind die Beziehungen zwischen Hathajoga und Sexualität
Response: Die Beziehung zwischen Hatha Yoga und Sexualität ist tief miteinander verwoben, vielschichtig und reicht von historischen energetischen Konzepten bis hin zu modernen physiologischen und Beziehungsaspekten.
Obwohl Hatha Yoga im Westen oft primär als körperliches Fitnessprogramm wahrgenommen wird, stammt es historisch aus der tantrischen Tradition, in der Lebensenergie (Prana) und Sexualkraft untrennbar miteinander verbunden sind.
Sublimierung von Lebensenergie (Kundalini & Ojas)
Im Hatha Yoga gilt sexuelle Energie als eine der stärksten Formen von Prana (Lebensenergie). Durch Techniken wie Bandhas (energetische Verschlüsse, z. B. Mula Bandha im Beckenboden) und Mudras (z. B. Vajroli Mudra) soll die Schöpferkraft aus den unteren Energiezentren (Chakras) nach oben gelenkt und in geistige Klarheit, Vitalität und spirituelle Kraft (Ojas) umgewandelt werden.
Das Konzept von Brahmacharya
In der klassischen Yogaphilosophie beschreibt Brahmacharya den achtsamen Umgang mit der eigenen Energie.
Traditionell: Wurde dies oft als strikte Enthaltsamkeit (Mönchtum/Zölibat) ausgelegt, um Energie nicht zu verschwenden.
Moderne Auslegung: Heute bedeutet es meist Achtsamkeit und Verhältnismäßigkeit in der Sexualität – Sex nicht als bloße Zerstreuung oder Kompensation zu nutzen, sondern als bewusste, von Respekt und Liebe gepragte Verbindung.
Hatha Yoga wirkt sich direkt auf den physischen Körper aus, was die sexuelle Gesundheit und Wahrnehmung positiv beeinflussen kann:
Stärkung des Beckenbodens: Viele Hatha-Yoga-Übungen trainieren gezielt die Beckenbodenmuskulatur. Dies verbessert die Durchblutung im Genitalbereich, fördert die Sensibilität und stärkt die Orgasmusfähigkeit (bei allen Geschlechtern).
Stressabbau & Nervensystem: Sexualität erfordert ein Parasympathikus-dominiertes Nervensystem (Ruhe- und Entspannungsmodus). Durch Pranayama (Atemübungen) und ruhige Asanas verringert Hatha Yoga das Stresshormon Cortisol, was Libidoverlust entgegenwirkt.
Körpergefühl & Flexibilität: Eine regelmäßige Praxis stärkt das Körperbewusstsein, baut Unsicherheiten bezüglich des eigenen Körpers ab und erhöht die Beweglichkeit in Hüfte und Beckenbereich.
Präsenz und Achtsamkeit: Yoga schult das Geist-Körper-Bewusstsein im gegenwärtigen Moment. Diese Fähigkeit, nicht in Gedanken abzuschweifen, führt im intimen Kontext zu mehr echter Spürbarkeit und Bindung.
Verbindung (Yog = Vereinigung): Das Wort Yoga bedeutet „Vereinigung“ oder „Anbinden“. Während Sexualität auf ego-getriebener Ebene trennend wirken kann, führt eine verfeinerte Wahrnehmung zu mehr Empathie, Intimität und emotionaler Tiefe in der Partnerschaft.
Hatha Yoga lehnt Sexualität nicht grundsätzlich ab, sondern sieht in ihr rohe Lebensenergie. Es gibt dem Praktizierenden Werkzeuge an die Hand, diese Energie bewusst zu steuern – sei es, um sie durch körperliche Übungen in bessere Gesundheit und intimere Partnerschaften einzubringen, oder um sie für die eigene spirituelle Entwicklung zu transformieren.
Sources
User prompt: Wo und wann ist Hathajoga entstanden und wer hat es erfunden?
Response: Die Geschichte von Hatha Yoga zeigt, dass es nicht von einer einzelnen Person erfunden wurde, sondern über Jahrhunderte hinweg im indischen Mittelalter als Körper- und Energiesystem geformt wurde.
Wo: Auf dem indischen Subkontinent (hauptsächlich in Nordindien).
Wann: Die systematische Praxis, wie wir sie als Hatha Yoga bezeichnen, entwickelte sich etwa zwischen dem 10. und 11. Jahrhundert n. Chr. (Einzelne Vorläufer-Techniken wie Atemübungen oder Körperhaltungen gab es im ascetischen Kontext bereits früher).
Es gibt keine einzelne historische Gründerfigur, da Hatha Yoga aus verschiedenen tantrischen, buddhistischen und hinduistischen Traditionen gewachsen ist. Es lassen sich jedoch die entscheidenden Akteure und Strömungen nennen:
A. Die geistige Tradition: Der Nath-Orden (Nath Sampradaya)
Die eigentlichen Pioniere und Wegbereiter des Hatha Yoga waren die Anhänger der Nath-Tradition, eine Gruppe wandernder Asketen und Siddhas (Yogameister):
Matsyendranath (ca. 10. Jh.): Gilt als der legendäre Urvater und Begründer der Nath-Tradition.
Gorakhnath (auch Goraksha, ca. 11. Jh.): Er war der Schüler von Matsyendranath und gilt historisch als die wichtigste Schlüsselfigur des Hatha Yoga. Ihm wird zugeschrieben, die verstreuten körperlichen und energetischen Techniken (Asanas, Pranayama, Mudras) gebündelt und zu einer eigenständigen Praxis weiterentwickelt zu haben.
B. Die Mythologie: Lord Shiva (Adinatha)
In den traditionellen Schriften des Hatha Yoga wird Shiva (als Adinatha = „der erste Meister“) als der mythologische Erfinder des Yoga genannt. Der Sage nach soll Shiva das Geheimnis des Hatha Yoga seiner Gefährtin Parvati am Meeresufer gelehrt haben, während ein Fisch (Matsya) lauschte – daraus entstand der Meister Matsyendranath („König der Fische“).
C. Der systematische Codifizierer: Swami Swatmarama (15. Jh.)
Im 15. Jahrhundert fasste der Yogi Swami Swatmarama die bis dahin mündlich überlieferten und in verschiedenen Texten verstreuten Techniken der Nath-Siddhas in einem zentralen Werk zusammen: der Hatha Yoga Pradipika. Dieses Lehrbuch ist bis heute das Standardwerk für klassisches Hatha Yoga.
Der Begriff Haṭha (हठ) bedeutet wörtlich „Kraft“ oder „Anstrengung“.
Die Yogis der damaligen Zeit entwickelten diese Praxis, weil sie erkannten, dass der Geist schwer zu kontrollieren ist, wenn der Körper unruhig oder krank ist. Hatha Yoga nutzte den physischen Körper als Werkzeug (durch Körperhaltungen, Reinigungstechniken und Atemkontrolle), um die Lebensenergie (Prana) im Körper zu reinigen und den Geist für die tiefe Meditation vorzubereiten.
Wikipedia – Hatha Yoga History & Origins
Yogateket – Goraksha: The Pioneer of Hatha Yoga
Cymbiotika – Where Did Hatha Yoga Originate?
Sources
User prompt: Wie hat sich Hatha Yoga im 20. Jahrhundert durch Krishnamacharya verändert?
Response: Tirumalai Krishnamacharya (1888–1989) gilt heute weltweit als der „Vater des modernen Yoga“. Er hat das traditionelle, meditativasketische Hatha Yoga im 20. Jahrhundert von Grund auf revolutioniert und es zu der dynamischen, körperbetonten und gesundheitsorientierten Praxis geformt, die wir heute kennen.
Dynamisierung und Vinyasa (Kopplung von Bewegung und Atem)
Vor Krishnamacharya wurden Hatha-Yoga-Positionen (Asanas) meist statisch und über längere Zeit gehalten. Krishnamacharya führte das Konzept des Vinyasa ein: Die fließende, dynamische Verbindung von einer Haltung in die nächste, synchronisiert mit einer tiefen, bewussten Atmung (oft der Ujjayi-Atmung).
Anpassung an das Individuum (Viniyoga)
Sein Grundsatz lautete: „Nicht der Mensch muss sich dem Yoga anpassen, sondern der Yoga dem Menschen.“ Er schnitt Asanas, Atemübungen und Therapien individuell auf das Alter, die Konstitution, den Beruf und den Gesundheitszustand des Einzelnen zu – ein Meilenstein für die moderne Yogatherapie.
Synthese aus indischer Tradition und westlicher Gymnastik
In den 1930er-Jahren unterrichtete Krishnamacharya am Palast des Maharadschas von Mysore. Um junge Männer und Thronfolger körperlich fit zu machen, kombinierte er klassisches Hatha Yoga mit Elementen der britischen Militärgymnastik, westlichem Turnen und indischem Ringen. Daraus entstanden die dynamischen Sonnengrüße und körperlich fordernden Sequenzen.
Öffnung für Frauen und die breite Öffentlichkeit
Traditionell war Hatha Yoga über Jahrhunderte hinweg fast ausschließlich indischen Männern und Asketen vorbehalten. Krishnamacharya brach mit dieser Regel und unterrichtete als einer der Ersten Frauen – darunter Indra Devi, die Yoga später in den USA und Südamerika populär machte.
Krishnamacharya bildete eine Generation von Yogalehrern aus, die seine unterschiedlichen Ansätze in die ganze Welt trugen und die prägendsten modernen Yogastile begründeten:
B. K. S. Iyengar (Iyengar Yoga): Krishnamacharyas Schwager fokussierte sich auf extrem präzise körperliche Ausrichtung, Hilfsmittel (Gurte, Blöcke) und therapeutisches Yoga.
K. Pattabhi Jois (Ashtanga Vinyasa Yoga): Führte die athletische, hochdynamische und schweißtreibende Form weiter, die zur Grundlage für modernes Power Yoga und Vinyasa Flow wurde.
T. K. V. Desikachar (Viniyoga): Krishnamacharyas Sohn führte den sanften, therapeutischen und individuell angepassten Ansatz seines Vaters fort.
Indra Devi: Brachte Yoga nach Hollywood und machte es als Form des Stressabbaus und der Schönheitspflege weltweit bei Frauen populär.
Ohne Krishnamacharya wäre Hatha Yoga wahrscheinlich eine Nischentradition indischer Einsiedler geblieben. Durch seine Arbeit entwickelte sich Hatha Yoga von einer rein spirituellen Disziplin zu einem weltweit zugänglichen System für körperliche Fitness, Gesundheit und mentales Wohlbefinden.
Wikipedia – Tirumalai Krishnamacharya
Yoga Journal – The Father of Modern Yoga
International Journal of Yoga – Origin and Evolution of Modern Yoga
User prompt: Indra Devi und seine Geheimnisse
Response: Indra Devi (1899–2002) – gebürtig Eugenie Peterson – war eine der faszinierendsten und schillernsten Persönlichkeiten der modernen Yogageschichte. Sie wurde 102 Jahre alt, überlebte zwei Weltkriege, lernte direkt bei Krishnamacharya und brachte Yoga nach Hollywood sowie Südamerika.
Ihr Leben verlangte von ihr, immer wieder Grenzen zu überschreiten. Die „Geheimnisse“ ihres außergewöhnlichen Lebens und ihrer enormen Lebenskraft lagen in einer Kombination aus eiserner Disziplin, politischer Durchsetzungskraft und einer ganz eigenen Philosophie.
Als westliche Frau im Indien der 1930er-Jahre Yoga zu lernen, war eigentlich unmöglich. Krishnamacharya lehnte sie am Palast von Mysore zunächst strikt ab: Er unterrichtete weder Ausländer noch Frauen.
Der königliche Druck: Devi nutzte ihre Kontakte zum Maharadscha von Mysore (der ihr Förderer war). Dieser wies Krishnamacharya an, sie aufzunehmen.
Die harte Prüfung: Um sie abzuschrecken, erlegte Krishnamacharya ihr extrem strenge Diäten und Stunden ununterbrochener Übungen auf. Zu seinem Erstaunen hielt sie stand, beschwerte sich nie und wurde so zu seiner ersten weiblichen und ersten westlichen Schülerin.
1947 zog Indra Devi nach Los Angeles und eröffnete ein Studio am Sunset Boulevard. Zu dieser Zeit galt Yoga im Westen noch als exzentrischer Okkultismus. Ihr Erfolgsgeheimnis lag in der Entmystifizierung:
Entkopplung von Religion: Sie befreite das Hatha Yoga von schwer verständlicher indischer Mystik und verkaufte es als Methode für Stressabbau, Schönheit, ewige Jugend und mentale Klarheit.
Hollywood-Prominenz: Sie unterrichtete Stars wie Marilyn Monroe, Greta Garbo, Gloria Swanson und Olivia de Havilland. Marilyn Monroe wurde eine ihrer treuesten Schülerinnen, was Yoga schlagartig salonfähig machte.
Indra Devi blieb bis zu ihrem Tod mit 102 Jahren geistig wach und körperlich beweglich. Ihre Lebensregeln basierten auf klassischem Yoga, gewürzt mit ihrer eigenen Lebensfreude:
Tägliche Praxis: Jeden Morgen meisterte sie Atemübungen (Pranayama) und Asanas.
Sattvische Ernährung: Sie lebte strikt vegetarisch, verzichtete auf Alkohol, Kaffee und künstliche Zusatzstoffe und achtete auf mäßige Portionsgrößen.
Emotionale Hygiene (Positivität): Ihr wichtigster Lehrsatz war „Bend and don't break“ (Biegen, nicht brechen) – sowohl körperlich als auch mental. Sie lehrte, dass negative Emotionen wie Wut und Angst den Körper vergiften und den Alterungsprozess beschleunigen.
1953 veröffentlichte sie den Bestseller Forever Young, Forever Healthy („Für immer jung, für immer gesund“). Darin fasste sie ihre Geheimnisse zusammen:
1. Richtiges Atmen: Die rhythmische, tiefe Bauchatmung liefert dem Körper Prana (Lebensenergie).
2. Entspannung: Die Kunst, während des Tages Pausen einzulegen (z. B. durch Savasana), um das Nervensystem zu regenerieren.
3. Bewusste Haltung: Die Aufrichtung der Wirbelsäule hält den Körper jung.
Das wahre „Geheimnis“ von Indra Devi war ihre Fähigkeit zur Transformation: Sie wandelte eine jahrtausendealte, indische Asketen-Tradition in ein modernes Werkzeug um, das in die Lebensrealität von Frauen und Männern im Westen passte – ohne dabei die Essenz des Yoga zu verlieren.
Wikipedia – Indra Devi Biography
New York Times – Obituary: Indra Devi, Mother of Western Yoga
The First Family of Yoga – The Life of Indra Devi
User prompt: Wer waren neben Indra Devi die wichtigsten Schüler von Krishnamacharya?
Response: Neben Indra Devi bildete Tirumalai Krishnamacharya in den 1930er- bis 1980er-Jahren einige der einflussreichsten Yogalehrer des 20. Jahrhunderts aus. Jeder von ihnen entwickelte aus der Lehre des Meisters einen völlig eigenen Stil und trug dazu bei, dass Hatha Yoga weltweit zu einem Massenphänomen wurde.
Beziehung zu Krishnamacharya: Iyengar war nicht nur Schüler, sondern auch Krishnamacharyas Schwager (der Ehemann seiner Schwester). Er kam als kränklicher, geschwächter Jugendlicher zum Meister und erlebte einen extrem harten, kompromisslosen Unterricht.
Sein Stil (Iyengar Yoga): Fokussiert auf extreme anatomische Präzision und perfekte Ausrichtung (Alignment). Iyengar erfand den Einsatz von Hilfsmitteln (Blöcke, Gurte, Stühle, Wandseile), damit auch kranke, ältere oder weniger bewegliche Menschen komplexe Haltungen einnehmen können.
Einfluss: Sein Buch Light on Yoga (1966) gilt weltweit als die Bibel des modernen physischen Yoga.
Beziehung zu Krishnamacharya: Er studierte ab 1927 über zwei Jahrzehnte lang bei Krishnamacharya am Palast von Mysore und übernahm dort die dynamische, athletische Methode, die der Meister für die jungen Prinzen entwickelt hatte.
Sein Stil (Ashtanga Vinyasa Yoga): Ein hochintensiver, schweißtreibender Stil mit festen, immer gleichen Übungsserien (Primary Series etc.). Die Haltungen werden dynamisch durch Vinyasas (fließende Übergänge) miteinander verbunden, kombiniert mit kraftvoller Atmung (Ujjayi) und energetischen Verschlüssen (Bandhas).
Einfluss: Ashtanga Vinyasa wurde in den 1980er- und 1990er-Jahren zur Grundlage aller modernen Vinyasa Flow- und Power YogaStile im Westen.
Beziehung zu Krishnamacharya: Er war Krishnamacharyas eigener Sohn. Er studierte erst spät bei seinem Vater, begleitete ihn aber in dessen späten Lebensjahren in Chennai (Madras) als engster Schüler und Nachfolger.
Sein Stil (Viniyoga): Desikachar verkörperte die spätere, gereifte Philosophie seines Vaters. Statt strenger Serien oder perfekter Formen steht die individuelle Anpassung im Vordergrund. Der Atem führt die Bewegung, und Yoga wird primär als ganzheitliche Heilmethode eingesetzt.
Einfluss: Er gründete das Krishnamacharya Yoga Mandiram in Chennai und prägte die moderne Yogatherapie nachhaltig.
Beziehung zu Krishnamacharya: Er studierte von 1971 bis zu Krishnamacharyas Tod im Jahr 1989 fast zwei Jahrzehnte lang täglich bei ihm.
Sein Beitrag (Svastha Yoga): Mohan konzentrierte sich stark auf die Kombination von Hatha Yoga mit der traditionellen indischen Heilkunde (Ayurveda) und der klassischen Geistesschulung nach den Yoga Sutras von Patanjali.
Krishnamacharyas Genialität zeigte sich darin, dass seine Schüler völlig unterschiedliche Wege einschlugen:
Iyengar machte Yoga präzise und therapeutisch zugänglich.
Pattabhi Jois machte Yoga dynamisch und athletisch.
Desikachar machte Yoga individuell und heilsam.
Indra Devi machte Yoga im Westen salonfähig und alltagstauglich.
Wikipedia – Tirumalai Krishnamacharya
Yoga Journal – The Lineage of Krishnamacharya
Krishnamacharya Yoga Mandiram (Offizielle Website)
User prompt: Was sagte Christopher Hickens zu Yoga?
Response: Der britisch-amerikanische Autor und Religionskritiker Christopher Hitchens hat sich in seinen Werken und Vorträgen nie ausführlich oder isoliert mit Yoga als Bewegungs- oder Entspannungsform befasst, äußerte sich aber sehr kritisch zu östlicher Spiritualität, Esoterik und Gurus.
Seine Haltung lässt sich wie folgt zusammenfassen:
Ablehnung östlicher Spiritualität und Gurus: In seinem Bestseller Der Herr ist nicht groß: Die Religion vergiftet alles (God Is Not Great, 2007) widmete Hitchens ein Kapitel dem Thema „Es gibt keine ‚östliche‘ Lösung“ (There Is No 'Eastern' Solution). Darin kritisierte er westliche Menschen, die im Fernen Osten nach Erleuchtung suchen, und bezeichnete Bhagwan Shree Rajneesh (Osho) sowie andere Gurus und Bewegungen als betrügerisch oder irrational.
Haltung zu Verstand und Intellekt: Hitchens wandte sich strikt gegen Praktiken oder Lehren, die verlangen, den Verstand abzuschalten oder kritische Skepsis aufzugeben. Geistige Strömungen, die blinden Gehorsam gegenüber einem Lehrer oder einer espiritualisierenden Doktrin fordern, lehnte er konsequent ab.
Körperübung vs. Esoterik: Während Hitchens das spirituelle Beiwerk, Chanten oder religiöse Dogmen hinter östlichen Praktiken verwarf, richtete sich seine Kritik prinzipiell nicht gegen reine Gymnastik oder Dehnübungen – solange diese frei von religiösem oder mystischem Glauben betrieben werden.
Sources
User prompt: Kann ein Backup eines iPhone 17pro 4,9GB haben?
Response: Ja, das ist durchaus möglich und sogar sehr realistisch.
Wie groß ein iPhone-Backup ausfällt, hängt stark davon ab, wo und wie Sie das Backup erstellen:
Wenn Sie die iCloud-Fotos-Funktion aktiviert haben, werden Ihre Bilder und Videos direkt und kontinuierlich in der iCloud gespeichert. Sie sind nicht noch einmal im eigentlichen Backup enthalten, um Speicherplatz zu sparen.
Das Backup umfasst dann nur noch:
App-Daten und Einstellungen
Nachrichten (WhatsApp, iMessage etc.)
Systemkonfigurationen
In diesem Fall sind 4,9 GB eine völlig normale und typische Größe für ein komplettes Geräte-Backup.
Ein lokales Backup auf dem Computer sichert in der Regel den gesamten Speicherinhalt des iPhones. Aber auch hier kann das Backup überraschend klein sein, wenn:
Das Gerät sehr neu ist und noch kaum Medien oder große Apps installiert sind.
Sehr viele Daten in Cloud-Diensten (Google Photos, Spotify, Netflix, OneDrive etc.) liegen – diese Apps laden ihre Inhalte bei Bedarf nach und sichern sie nicht im Backup.
Die Angabe von 4,9 GB ist vollkommen plausibel und kein Anzeichen für einen Fehler – insbesondere dann, wenn Ihre Fotos separat über die iCloud gesichert werden.
User prompt: Wie kann ich Daten auf einem iPad speichern?
Response: Um Daten auf dem iPad zu speichern, gibt es verschiedene Wege – je nachdem, ob Sie Dateien direkt auf dem Gerät, in einer Cloud oder auf externen Speichermedien ablegen möchten.
Wenn Sie Dokumente, Downloads oder Dateien direkt auf dem iPad ablegen möchten, ist die vorinstallierte Dateien-App die zentrale Anlaufstelle:
1. Datei speichern: Wenn Sie z. B. in Safari, Mail oder einer anderen App ein Dokument öffnen, tippen Sie auf das Teilen-Symbol (Quadrat mit Pfeil nach oben).
2. Wählen Sie „In Dateien sichern“.
3. Wählen Sie in der Seitenleiste den Speicherort „Auf meinem iPad“.
4. Wählen Sie einen vorhandenen Ordner aus oder erstellen Sie über das Ordner-Symbol oben einen neuen und tippen Sie auf „Sichern“.
Möchten Sie von all Ihren Geräten auf die Daten zugreifen, empfiehlt sich ein Cloud-Speicher:
iCloud Drive:
Ist direkt in iPadOS integriert.
Wählen Sie beim Speichern über das Teilen-Symbol einfach „iCloud Drive“ statt „Auf meinem iPad“ aus.
Aktivierung: Einstellungen > [Ihr Name] > iCloud > Drive aktivieren.
Andere Cloud-Dienste (Google Drive, OneDrive, Dropbox):
Laden Sie die jeweilige App aus dem App Store herunter und melden Sie sich an.
In der Dateien-App können Sie diese Dienste über das Dreipunkt-Menü (Seitenleiste bearbeiten) hinzufügen, um direkt über die Dateien-App darauf zuzugreifen.
Sie können Dateien auch direkt auf ein externes Speichermedium sichern:
1. Schließen Sie den USB-C-Stick, die externe Festplatte oder einen passenden Adapter an den USB-C- (oder Lightning-)Anschluss des iPads an.
2. Öffnen Sie die Dateien-App.
3. Der externe Speicher wird in der linken Seitenleiste unter „Speicherorte“ angezeigt.
4. Sie können nun Dateien markieren, kopieren und auf das externe Laufwerk einfügen.
Aus dem Internet oder Nachrichten: Halten Sie das Bild gedrückt und wählen Sie „In Fotos sichern“.
Eigene Fotos in der Cloud sichern: Unter Einstellungen > [Ihr Name] > iCloud > Fotos können Sie iCloud-Fotos aktivieren, um alle Aufnahmen automatisch online zu sichern.
Sources
3. iCloud Drive auf dem iPad einrichten - Apple Support (AT)
4. Umgang mit der iCloud, Sichern von Dateien und Platz schaffen auf
User prompt: Kannst du für einen Webauftritt ein .js-Script erstellen, das das Vorlesen des Textes ermöglicht, wobei ich eine Navigation nach classes, headers, fußnoten, zitaten und Kommentaren unterscheiden kann, und diesen unterschiedliche voices zuteilen knn, die man mit einem gesonderten script auf die jeweilige Seite gezielt konfigurieren kann?
Response: Ja, das ist sehr gut umsetzbar. Die saubere Lösung besteht darin, die Sprechlogik (Core Engine) von der Seiten-Konfiguration zu trennen.
Hier sind die beiden Skripte:
1. tts-engine.js: Das Kern-Skript, das den DOM analysiert, die Abschnitte nach Ihren Kriterien filtert und die Web Speech API ansteuert.
2. tts-config.js: Das Konfigurations-Skript für die jeweilige Unterseite, in dem Sie festlegen, welche Stimme (Voice) für welchen ElementTyp verwendet wird.
Fügen Sie diese Datei in Ihr Projekt ein. Sie stellt ein globales Objekt TTSEngine zur Verfügung.
/**
* TTS Engine - Barrierefreies Vorlesen von Webseiten-Inhalten
*/
const TTSEngine = (() => {
let synth = window.speechSynthesis;
let availableVoices = [];
let isPlaying = false;
let queue = [];
let currentIndex = 0;
// Standard-Konfiguration (kann via tts-config.js überschrieben werden) let config = {
voices: {
headers: null, // z. B. Überschriften (H1-H6)
quotes: null, // z. B. <blockquote>, <q>
footnotes: null, // z. B. <aside>, .footnote
comments: null, // z. B. .comment
classes: {} // Einzelne CSS-Klassen: { 'wichtig': 'VoiceName' }
}, selectors: {
headers: 'h1, h2, h3, h4, h5, h6',
quotes: 'blockquote, q, cite',
footnotes: '.footnote, aside.footnote, [data-tts="footnote"]', comments: '.comment, .user-comment, [data-tts="comment"]'
},
defaultVoice: null, rate: 1.0, pitch: 1.0
};
// Stimmen des Browsers laden
function loadVoices() { availableVoices = synth.getVoices();
}
if (speechSynthesis.onvoiceschanged !== undefined) { speechSynthesis.onvoiceschanged = loadVoices;
}
loadVoices();
// Hilfsfunktion: Stimme anhand des Namens oder der Sprache finden function findVoice(nameOrLang) {
if (!nameOrLang) return null;
return availableVoices.find(v => v.name.includes(nameOrLang) || v.lang === nameOrLang) || null; }
// Bestimmt den Element-Typ und die zugewiesene Stimme function getVoiceForElement(element) {
// 1. Prüfung nach gezielten CSS-Klassen
for (let className in config.voices.classes) {
if (element.classList.contains(className)) {
const voice = findVoice(config.voices.classes[className]);
if (voice) return voice;
}
}
// 2. Prüfung nach Kommentaren
if (element.matches(config.selectors.comments)) {
const voice = findVoice(config.voices.comments);
if (voice) return voice;
}
// 3. Prüfung nach Fußnoten
if (element.matches(config.selectors.footnotes)) {
const voice = findVoice(config.voices.footnotes);
if (voice) return voice;
}
// 4. Prüfung nach Zitaten
if (element.matches(config.selectors.quotes)) {
const voice = findVoice(config.voices.quotes);
if (voice) return voice;
}
// 5. Prüfung nach Überschriften
if (element.matches(config.selectors.headers)) {
const voice = findVoice(config.voices.headers);
if (voice) return voice;
}
// Fallback: Standardstimme
return findVoice(config.defaultVoice) || availableVoices[0] || null;
}
// DOM analysieren und lesbare Blöcke extrahieren
function parseContent(rootSelector = 'main') {
const root = document.querySelector(rootSelector) || document.body; queue = [];
// Alle relevanten Text-Knoten und Elemente durchlaufen
const elements = root.querySelectorAll('h1, h2, h3, h4, h5, h6, p, blockquote, q, cite, .footnote, .comment, [cl
elements.forEach(el => {
// Nur Elemente mit direktem Text erfassen (keine leeren Container) const text = el.innerText ? el.innerText.trim() : '';
if (text.length > 0) {
queue.push({ text: text, element: el, voice: getVoiceForElement(el)
});
} });
}
// Nächsten Abschnitt vorlesen
function speakNext() {
if (currentIndex >= queue.length || !isPlaying) {
isPlaying = false;
return;
}
const item = queue[currentIndex];
const utterance = new SpeechSynthesisUtterance(item.text);
if (item.voice) {
utterance.voice = item.voice;
}
utterance.rate = config.rate;
utterance.pitch = config.pitch;
// Optional: Visuelles Hervorheben des aktuell vorgelesenen Elements
item.element.classList.add('tts-active');
utterance.onend = () => {
item.element.classList.remove('tts-active');
currentIndex++;
speakNext();
};
utterance.onerror = () => {
item.element.classList.remove('tts-active');
currentIndex++;
speakNext();
};
synth.speak(utterance);
}
return {
// Öffentliche Schnittstelle
configure: (customConfig) => {
// Deep Merge der Konfiguration
if (customConfig.voices) {
Object.assign(config.voices, customConfig.voices);
}
if (customConfig.selectors) {
Object.assign(config.selectors, customConfig.selectors);
}
if (customConfig.rate) config.rate = customConfig.rate;
if (customConfig.pitch) config.pitch = customConfig.pitch;
if (customConfig.defaultVoice) config.defaultVoice = customConfig.defaultVoice;
},
start: (rootSelector = 'body') => {
synth.cancel(); // Laufende Sprachausgabe stoppen
parseContent(rootSelector);
currentIndex = 0;
isPlaying = true;
speakNext();
},
pause: () => {
if (synth.speaking) synth.pause();
},
resume: () => {
if (synth.paused) synth.resume();
},
stop: () => {
isPlaying = false;
synth.cancel();
document.querySelectorAll('.tts-active').forEach(el => el.classList.remove('tts-active'));
},
getAvailableVoices: () => availableVoices
};
})();
Auf der jeweiligen Unterseite binden Sie dieses Skript ein, um festzulegen, welches Element mit welcher Stimme vorgelesen werden soll.
document.addEventListener('DOMContentLoaded', () => {
// Warten, bis die Stimmen im Browser geladen sind
window.speechSynthesis.onvoiceschanged = () => {
// Hier konfigurieren Sie die Stimmen für die aktuelle Seite
TTSEngine.configure({
defaultVoice: 'Google Deutsch', // Standard-Stimme für Fließtext rate: 1.0,
|
voices: { | ||||
|
headers: 'Anna', |
// |
Stimme |
für |
H1-H6 |
|
quotes: 'Marlene', |
// |
Stimme |
für |
Blockquotes/Zitate |
|
footnotes: 'Yannick', |
// |
Stimme |
für |
Fußnoten |
|
comments: 'Markus', |
// |
Stimme |
für |
Kommentare |
// Gezielte Zuweisung nach eigenen CSS-Klassen: classes: {
'infobox': 'Google Deutsch',
'warning': 'Anna',
'author-note': 'Marlene'
} },
// Optional: Eigene Selektoren anpassen, falls abweichend
selectors: {
footnotes: '.fn-item, .footnote',
comments: '.user-comment-body'
}
});
};
});
Ein vollständiges Beispiel für die Einbindung und Nutzung:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>TTS Vorlese-Test</title>
<style>
/* Optionale Hervorhebung des aktuell vorgelesenen Elements */
.tts-active {
background-color: #fff3cd;
transition: background-color 0.3s ease;
}
</style>
</head>
<body>
<!-- Steuerungselemente -->
<div id="tts-controls">
<button onclick="TTSEngine.start('#content')">▶ Vorlesen starten</button>
<button onclick="TTSEngine.pause()">⏸ Pause</button>
<button onclick="TTSEngine.resume()">▶ Weiter</button>
<button onclick="TTSEngine.stop()">⏹ Stopp</button>
</div>
<!-- Inhaltssbereich -->
<main id="content">
<h1>Hauptüberschrift der Seite</h1>
<p>Dies ist ein ganz normaler Fließtextabsatz.</p>
<blockquote>
„Dies ist ein bekanntes Zitat, das mit der Zitat-Stimme vorgelesen wird.“
</blockquote>
<p>Hier geht der Fließtext weiter. <span class="footnote">1) Dies ist eine Fußnote am Rande.</span></p>
<div class="comment">
<p>User-Kommentar: Ich finde diesen Artikel sehr hilfreich!</p>
</div>
<p class="warning">Achtung: Dieser Absatz hat eine spezielle Klasse und nutzt die Warnungs-Stimme.</p>
</main>
<!-- Skripte einbinden -->
<script src="tts-engine.js"></script>
<script src="tts-config.js"></script>
</body>
</html>
1. Verfügbarkeit von Web-Voices: Die installierten Stimmen hängen vom Betriebssystem und Browser des Nutzers ab (z. B. unterscheidet sich macOS/iOS stark von Windows/Android). Es empfiehlt sich daher, in der Konfiguration allgemeine Sprach-Codes (z. B. 'de-DE') oder Teil-Namen als Fallback zu nutzen.
2. Klassen-Hierarchie: Wenn ein Element z. B. sowohl eine Überschrift als auch eine spezielle Klasse hat, priorisiert dieses Skript zuerst die spezifische Klasse in voices.classes.
User prompt: Danke. Klar, diese beiden Vorschläge hatte ich auch für die Granulation vorgehabt. Im voice-select-menu sollte empathy, pitch, speed, male/female, als utterancewerte vorhanden sein, gut dokumentiert, damit man weitere Anpasungen einfügen kann. Die Steuerung über Hotkeys sollte jeweils einen Hilfetext sprechen, und nach einer Auswahl sollte mit [Enter] die Ausführung getriggert werden.
Response: Hier sind die beiden erweiterten Skripte und die aktualisierte HTML-Struktur, um das Voice-Select-Menü mit granularen Utterance-Parametern sowie die Hotkey-Steuerung mit gesprochenem Hilfetext und Enter-Bestätigung umzusetzen.
Das Skript wurde um die Logik für Hotkey-Navigation, ein gesprochenes Menü und die dynamischen Parameter (Speed, Pitch, Empathy/Prosodie) ergänzt.
/**
* TTS Engine v2.0 - Mit Hotkey-Steuerung, gesprochener Hilfeführung & Utterance-Parametern
*/
const TTSEngine = (() => {
let synth = window.speechSynthesis;
let availableVoices = [];
let isPlaying = false;
let isHotkeyMode = false;
let queue = [];
let currentIndex = 0;
let selectedHotkeyAction = null;
// Erweitere Konfiguration inkl. Utterance-Werten & Hotkeys let config = {
// Globale Standardwerte für Utterance
params: {
rate: 1.0, // Geschwindigkeit (0.1 bis 10)
pitch: 1.0, // Tonhöhe (0 bis 2)
volume: 1.0, // Lautstärke (0 bis 1)
// Empathie/Prosodie wird über Pitch/Rate-Modulation simuliert,
// da die Web Speech API keinen direkten "Empathy"-Parameter besitzt.
empathyProfile: 'neutral' // 'neutral', 'calm', 'excited', 'empathetic'
},
voices: {
headers: null,
quotes: null,
footnotes: null, comments: null, classes: {}
},
genderFilter: 'all', // 'all', 'male', 'female'
selectors: {
headers: 'h1, h2, h3, h4, h5, h6', quotes: 'blockquote, q, cite', footnotes: '.footnote, aside.footnote, [data-tts="footnote"]', comments: '.comment, .user-comment, [data-tts="comment"]'
},
defaultVoice: null,
// Keybindings (Tastatur-Kürzel)
hotkeys: {
togglePlay: { key: 'Space', label: 'Wiedergabe starten oder pausieren' },
next: { key: 'ArrowRight', label: 'Zum nächsten Abschnitt springen' },
prev: { key: 'ArrowLeft', label: 'Zum vorherigen Abschnitt springen' },
stop: { key: 'Escape', label: 'Vorlesen stoppen' }, help: { key: 'KeyH', label: 'Hilfemenü aufrufen' }
} };
function loadVoices() {
availableVoices = synth.getVoices();
}
if (speechSynthesis.onvoiceschanged !== undefined) { speechSynthesis.onvoiceschanged = loadVoices;
}
loadVoices();
function findVoice(nameOrLang) {
if (!nameOrLang) return null;
return availableVoices.find(v => v.name.includes(nameOrLang) || v.lang === nameOrLang) || null; }
// Ermittelt die Stimme für ein bestimmtes Element function getVoiceForElement(element) {
for (let className in config.voices.classes) {
if (element.classList.contains(className)) {
const voice = findVoice(config.voices.classes[className]);
if (voice) return voice;
}
}
if (element.matches(config.selectors.comments)) return findVoice(config.voices.comments) || getDefaultVoice();
if (element.matches(config.selectors.footnotes)) return findVoice(config.voices.footnotes) || getDefaultVoice();
if (element.matches(config.selectors.quotes)) return findVoice(config.voices.quotes) || getDefaultVoice();
if (element.matches(config.selectors.headers)) return findVoice(config.voices.headers) || getDefaultVoice();
return getDefaultVoice();
}
function getDefaultVoice() {
return findVoice(config.defaultVoice) || availableVoices[0] || null;
}
// Wendet Empathie-Profile auf Pitch und Rate an (Simulation von Emotion) function applyEmpathyProfile(utterance, profile) {
switch (profile) {
case 'calm': // Beruhigend / Empathisch tief
utterance.rate *= 0.85;
utterance.pitch *= 0.9;
break;
case 'empathetic': // Gefühlvoll / Sanft utterance.rate *= 0.9;
utterance.pitch *= 1.1;
break;
case 'excited': // Begeistert / Dynamisch
utterance.rate *= 1.15;
utterance.pitch *= 1.3;
break;
case 'neutral':
default: break;
}
}
// DOM analysieren
function parseContent(rootSelector = 'main') {
const root = document.querySelector(rootSelector) || document.body; queue = [];
const elements = root.querySelectorAll('h1, h2, h3, h4, h5, h6, p, blockquote, q, cite, .footnote, .comment, [cl
elements.forEach(el => {
const text = el.innerText ? el.innerText.trim() : '';
if (text.length > 0) {
queue.push({ text: text, element: el, voice: getVoiceForElement(el)
});
} });
}
// Aktuellen Textblock sprechen
function speakCurrent() {
if (currentIndex < 0 || currentIndex >= queue.length || !isPlaying) { isPlaying = false;
return;
}
synth.cancel(); // Vorherige Sprache beenden
const item = queue[currentIndex];
const utterance = new SpeechSynthesisUtterance(item.text);
if (item.voice) utterance.voice = item.voice;
// Utterance-Parameter zuweisen utterance.rate = config.params.rate; utterance.pitch = config.params.pitch; utterance.volume = config.params.volume;
// Empathie-Modulation anwenden
applyEmpathyProfile(utterance, config.params.empathyProfile);
// Visuelles Feedback
document.querySelectorAll('.tts-active').forEach(el => el.classList.remove('tts-active'));
item.element.classList.add('tts-active');
item.element.scrollIntoView({ behavior: 'smooth', block: 'center' });
utterance.onend = () => {
item.element.classList.remove('tts-active');
currentIndex++;
if (isPlaying) speakCurrent();
};
utterance.onerror = () => {
item.element.classList.remove('tts-active');
currentIndex++;
if (isPlaying) speakCurrent();
};
synth.speak(utterance);
}
// Einmalige System-Ansage sprechen (für Hotkey-Hilfe)
function speakSystemFeedback(text, onCompleteCallback = null) { synth.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.voice = getDefaultVoice();
utterance.rate = 1.0;
utterance.pitch = 1.0;
if (onCompleteCallback) {
utterance.onend = onCompleteCallback;
}
synth.speak(utterance);
}
// Tastatur-Event-Listener für Hotkey-Navigation
function setupHotkeyListeners() {
window.addEventListener('keydown', (e) => {
// Ignorieren, wenn der Fokus in einem Eingabefeld liegt
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
// HILFE-MENÜ (Taste H)
if (e.code === config.hotkeys.help.key) { e.preventDefault();
const helpText = "Hotkey-Hilfe: Drücken Sie Pfeil-Rechts für Weiter, Pfeil-Links für Zurück, Leertaste für S speakSystemFeedback(helpText);
return;
}
// AKTION AUSWÄHLEN (Vorbereiten vor ENTER)
for (let action in config.hotkeys) {
if (e.code === config.hotkeys[action].key) {
e.preventDefault();
selectedHotkeyAction = action;
// Hilfetext der ausgewählten Aktion sprechen
speakSystemFeedback(`Ausgewählt: ${config.hotkeys[action].label}. Drücken Sie Enter zum Bestätigen.`); return;
}
}
// ENTER: Ausführen der ausgewählten Aktion
if (e.code === 'Enter' && selectedHotkeyAction) {
e.preventDefault();
executeHotkeyAction(selectedHotkeyAction);
selectedHotkeyAction = null; // Zurücksetzen
} });
}
// Ausführung der Hotkey-Befehle
function executeHotkeyAction(action) {
switch (action) {
case 'togglePlay':
if (isPlaying) {
TTSEngine.pause();
speakSystemFeedback("Pausiert");
} else {
if (queue.length === 0) parseContent();
isPlaying = true;
speakCurrent();
}
break;
case 'next':
if (currentIndex < queue.length - 1) {
currentIndex++;
isPlaying = true;
speakCurrent();
} else {
speakSystemFeedback("Ende der Seite erreicht.");
}
break;
case 'prev':
if (currentIndex > 0) {
currentIndex--;
isPlaying = true;
speakCurrent();
} else {
speakSystemFeedback("Sie sind am Anfang der Seite.");
}
break;
case 'stop':
TTSEngine.stop();
speakSystemFeedback("Wiedergabe gestoppt.");
break;
}
}
// Startet Hotkey-EventListener sofort
setupHotkeyListeners();
return {
configure: (customConfig) => {
if (customConfig.params) Object.assign(config.params, customConfig.params);
if (customConfig.voices) Object.assign(config.voices, customConfig.voices);
if (customConfig.selectors) Object.assign(config.selectors, customConfig.selectors);
if (customConfig.defaultVoice) config.defaultVoice = customConfig.defaultVoice;
if (customConfig.hotkeys) Object.assign(config.hotkeys, customConfig.hotkeys);
},
start: (rootSelector = 'main') => { synth.cancel();
parseContent(rootSelector);
currentIndex = 0;
isPlaying = true;
speakCurrent();
},
pause: () => {
isPlaying = false;
synth.pause();
},
resume: () => {
if (synth.paused) {
isPlaying = true;
synth.resume();
} else {
isPlaying = true;
speakCurrent();
}
},
stop: () => {
isPlaying = false;
synth.cancel();
document.querySelectorAll('.tts-active').forEach(el => el.classList.remove('tts-active')); },
// Gibt alle System-Stimmen gefiltert nach Geschlecht zurück getFilteredVoices: (gender = 'all') => {
if (gender === 'all') return availableVoices;
// Heuristische Ermittlung von Geschlechtern aus den Namen der Web Speech API
const femaleKeywords = ['female', 'anna', 'marlene', 'vicki', 'zira', 'yelda', 'viktorine', 'helena', 'zora']; const maleKeywords = ['male', 'markus', 'yannick', 'david', 'stefan', 'george', 'pavel'];
return availableVoices.filter(voice => {
const name = voice.name.toLowerCase();
if (gender === 'female') return femaleKeywords.some(k => name.includes(k));
if (gender === 'male') return maleKeywords.some(k => name.includes(k));
return true;
});
},
getConfig: () => config
};
})();
Dieses Skript rendert das Voice-Select-Menü dynamisch, bindet Regler für Pitch, Speed, Empathy und Gender ein und schickt Änderungen direkt an das Kern-Skript.
document.addEventListener('DOMContentLoaded', () => {
// Warten auf Laden der Stimmen im Browser
window.speechSynthesis.onvoiceschanged = () => {
initTTSConfiguration();
buildVoiceUI();
};
// Initialisierung auch direkt aufrufen (für Browser, die onvoiceschanged bereits gefeuert haben) initTTSConfiguration();
setTimeout(buildVoiceUI, 500);
function initTTSConfiguration() {
TTSEngine.configure({
defaultVoice: 'Google Deutsch', params: {
rate: 1.0, // Speed: 0.5 bis 2.0
pitch: 1.0, // Pitch: 0.5 bis 1.5
empathyProfile: 'neutral' // 'neutral', 'calm', 'excited', 'empathetic' }, voices: {
headers: 'Anna',
quotes: 'Marlene',
footnotes: 'Yannick',
comments: 'Markus'
} });
}
/**
* Erstellt das erweiterte Steuerungsmenü im HTML-Container #tts-control-panel
*/
function buildVoiceUI() {
const container = document.getElementById('tts-control-panel');
if (!container) return;
const voices = TTSEngine.getFilteredVoices('all');
container.innerHTML = `
<div class="tts-ui-group">
<label for="tts-voice-select"><strong>Stimme (Voice):</strong></label>
<select id="tts-voice-select">
${voices.map(v => `<option value="${v.name}">${v.name} (${v.lang})</option>`).join('')}
</select>
</div>
<div class="tts-ui-group">
<label for="tts-gender-select"><strong>Geschlecht-Filter:</strong></label>
<select id="tts-gender-select">
<option value="all">Alle Stimmen</option>
<option value="female">Weiblich (Erkannt)</option>
<option value="male">Männlich (Erkannt)</option>
</select>
</div>
<div class="tts-ui-group">
<label for="tts-speed"><strong>Geschwindigkeit (Speed):</strong> <span id="tts-speed-val">1.0</span>x</label <input type="range" id="tts-speed" min="0.5" max="2.0" step="0.1" value="1.0">
</div>
<div class="tts-ui-group">
<label for="tts-pitch"><strong>Tonhöhe (Pitch):</strong> <span id="tts-pitch-val">1.0</span></label>
<input type="range" id="tts-pitch" min="0.5" max="1.5" step="0.1" value="1.0">
</div>
<div class="tts-ui-group">
<label for="tts-empathy"><strong>Empathie / Ausdruck (Prosodie):</strong></label>
<select id="tts-empathy">
<option value="neutral">Neutral (Standard)</option>
<option value="empathetic">Gefühlvoll / Sanft</option>
<option value="calm">Beruhigend / Bedacht</option>
<option value="excited">Lebhaft / Begeistert</option>
</select>
</div>
;
// Event-Listener für UI-Steuerelemente binden
document.getElementById('tts-speed').addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
document.getElementById('tts-speed-val').innerText = val;
TTSEngine.configure({ params: { rate: val } });
});
document.getElementById('tts-pitch').addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
document.getElementById('tts-pitch-val').innerText = val;
TTSEngine.configure({ params: { pitch: val } });
});
document.getElementById('tts-empathy').addEventListener('change', (e) => { TTSEngine.configure({ params: { empathyProfile: e.target.value } });
});
document.getElementById('tts-voice-select').addEventListener('change', (e) => {
TTSEngine.configure({ defaultVoice: e.target.value });
});
document.getElementById('tts-gender-select').addEventListener('change', (e) => {
const filtered = TTSEngine.getFilteredVoices(e.target.value);
const voiceSelect = document.getElementById('tts-voice-select');
voiceSelect.innerHTML = filtered.map(v => `<option value="${v.name}">${v.name} (${v.lang})</option>`).join('') if (filtered.length > 0) TTSEngine.configure({ defaultVoice: filtered[0].name });
});
}
});
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Barrierefreies TTS mit Hotkeys und Utterance-Reglern</title>
<style>
.tts-active {
background-color: #ffeeba;
border-left: 4px solid #ffc107;
padding-left: 5px;
transition: all 0.2s ease;
}
#tts-control-panel {
background: #f8f9fa;
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
}
.tts-ui-group {
margin-bottom: 10px;
}
.hotkey-legend {
font-size: 0.85em;
background: #e9ecef;
padding: 10px;
border-radius: 4px;
}
</style>
</head>
<body>
<!-- Steuerungs-Panel -->
<h2>TTS Einstellungen & Granulation</h2>
<div id="tts-control-panel"></div>
<!-- Tastatur-Steuerungshinweis -->
<div class="hotkey-legend">
<strong>Tastatur-Steuerung:</strong><br>
• <code>H</code>: Hilfetext sprechen<br>
• <code>Leertaste</code>: Start/Pause wählen | <code>Pfeil-Rechts</code>: Weiter | <code>Pfeil-Links</code>: Zur
• <strong>Drücken Sie nach der Tastenwahl <code>[ENTER]</code>, um den Befehl auszuführen.</strong>
</div>
<hr>
<!-- Vorlesbarer Haupt-Content -->
<main id="content">
<h1>Hatha Yoga & Barrierefreiheit</h1>
<p>Willkommen auf unserer Webseite. Dieser Bereich wird mit der ausgewählten Hauptstimme vorgelesen.</p>
<blockquote>
„Yoga ist das Zur-Ruhe-Bringen der Gedanken im Geist.“
</blockquote>
<p>Hier geht der Text weiter mit einer Fußnote. <span class="footnote">Hinweis 1: Dies ist eine angereicherte Fu
<div class="comment">
<p>Kommentar: Tolles Skript für sprachgestützte Interaktion!</p>
</div>
</main>
<script src="tts-engine.js"></script>
<script src="tts-config.js"></script>
</body>
</html>
1. Taste drücken (z. B. Pfeil-Rechts): Das Skript fängt die Taste ab, führt die Aktion aber noch nicht aus. Stattdessen liest eine Feedback-Stimme vor: "Ausgewählt: Zum nächsten Abschnitt springen. Drücken Sie Enter zum Bestätigen."
2. [Enter] drücken: Erst jetzt wird der Befehl getriggert und der Vorlesevorgang springt zum nächsten Abschnitt.
User prompt: Super. Kann die Sprache anhand des Textes automatisch erkannt werden oder muss ich... lang:de-AT angeben?
Response: Das geht automatisch, wenn Sie im HTML das lang-Attribut pflegen. Sie müssen im JavaScript nicht manuell lang:de-AT hartcodieren.
Die automatische Erkennung und Auswahl funktioniert über zwei Ebenen:
Der Browser kennt die Sprache der Seite (oder einzelner Absätze) bereits, wenn Sie diese im HTML angeben (z. B. <html lang="de-AT"> oder <p lang="en-US">).
Sie können das Skript so erweitern, dass es beim Erfassen eines Textblocks dessen Sprache automatisch ausliest und eine dazu passende Stimme wählt:
// Liest das lang-Attribut des Elements oder der übergeordneten Seite aus
function getLanguageForElement(element) {
return element.getAttribute('lang') ||
element.closest('[lang]')?.getAttribute('lang') ||
document.documentElement.lang ||
'de-DE'; // Fallback
}
// Findet automatisch die beste Stimme für die erkannte Sprache
function getMatchingVoice(langCode) {
// Versucht erst exaktes Match (z.B. "de-AT"), dann Sprachfamilie (z.B. "de")
return availableVoices.find(v => v.lang.toLowerCase() === langCode.toLowerCase()) ||
availableVoices.find(v => v.lang.startsWith(langCode.substring(0, 2))) ||
availableVoices[0];
}
Wenn Ihr HTML also so aufgebaut ist:
<p lang="de-AT">Dieser Text wird mit einer österreichischen Stimme vorgelesen.</p>
<blockquote lang="en-US">"This quote will automatically be read in English."</blockquote>
… erkennt das Skript den Sprachwechsel mitten im Text vollautomatisch und schaltet ohne Ihr Zutun die passende Stimme um.
Sollte der Text aus einer dynamischen Quelle kommen (z. B. User-Kommentare) und kein lang-Attribut besitzen, gibt es zwei Möglichkeiten:
Integrierte Browser-Erkennung (Chrome/Edge): Moderne Browser unterstützen über die experimentelle LocalDetector / LanguageDetector API eine automatische KI-Spracherkennung direkt im Browser.
Typischer Standard-Weg: Das Skript nutzt als Fallback die eingestellte Hauptsprache der Webseite (<html lang="...">).
Sie müssen kein lang:de-AT im Skript vorgeben.
Wenn Sie im HTML-Kopf <html lang="de-AT"> (oder auf Element-Ebene lang="de-AT") eintragen, filtert das Skript automatisch nach Stimmen mit dem Sprachcode de-AT (wie z. B. der österreichischen Stimme „Hannah“ auf iOS/macOS). Findet das System keine spezifisch österreichische Stimme, greift es automatisch auf die nächstbeste deutsche Stimme (de-DE) zurück.
User prompt: Gibt es für Hinweise auch den Befehl / Trigger "whisper"?
Response: Im Kontext der Web Speech API (JavaScript) oder von Browser-Schnittstellen gibt es den Begriff oder Trigger "whisper" nicht als nativen Befehl oder Parameter.
Wie der Begriff verwendet wird, hängt vom Kontext ab:
Die Web Speech API bietet keine direkte Eigenschaft namens whisper für das Flüstern.
Wenn Sie mit der API ein Flüstern oder leises Sprechen imitieren möchten, müssen Sie dies über die vorhandenen Stellschrauben für Lautstärke, Tonhöhe und Geschwindigkeit simulieren:
const utterance = new SpeechSynthesisUtterance("Dies ist ein leiser Hinweis.");
// Whisper-Simulation über Standard-Parameter: utterance.volume = 0.3; // Sehr leise (0.0 bis 1.0) utterance.pitch = 0.7; // Etwas tiefere Tonhöhe utterance.rate = 0.85; // Etwas langsamer sprechen
In anderen Systemen ist /whisper hingegen ein sehr geläufiger Befehl:
Discord / IRC / Gaming-Plattformen: Der Slash-Befehl /whisper [Nutzername] (oder kurz /w) wird genutzt, um jemandem eine private Direktnachricht zu schicken, die andere im Kanal nicht lesen können.
OpenAI (Whisper): Es gibt ein bekanntes Spracherkennungsmodell namens OpenAI Whisper. Dieses dient jedoch der Sprach-zu-TextUmwandlung (Transkription) und nicht der Sprachausgabe.
Für die Browser-Sprachausgabe (TTS): Es gibt kein whisper. Nutzen Sie stattdessen volume = 0.3 (zusammen mit reduzierter pitch und rate), um Hinweise leise wiederzugeben.
Für Chat- & Botsysteme: /whisper oder /w ist ein Standardbefehl für vertrauliche/private Nachrichten.
User prompt: Wie geht das in HTML? : const utterance = new SpeechSynthesisUtterance("Dies ist ein leiser Hinweis.");
Response: Reines HTML besitzt keine eigene Funktion, um eine Sprachausgabe direkt zu steuern. HTML dient nur der Struktur der Seite. Um eine Sprachausgabe auszulösen, bettet man das JavaScript in das HTML-Dokument ein (meist gekoppelt an ein Ereignis wie einen ButtonKlick).
Wenn Sie einen Text leise ("geflüstert") vorlesen lassen möchten, binden Sie das JavaScript wie folgt in Ihr HTML ein:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Whisper Hinweis Beispiel</title>
</head>
<body>
<!-- Das Element mit dem Hinweis -->
<p id="hinweis-text">Dies ist ein leiser Hinweis.</p>
<!-- Button zum Auslösen der Sprachausgabe -->
<button onclick="vorlesenLeise()">Hinweis leise vorlesen</button>
<script>
function vorlesenLeise() {
// Text aus dem HTML-Element auslesen
const text = document.getElementById("hinweis-text").innerText;
// Utterance-Objekt erstellen
const utterance = new SpeechSynthesisUtterance(text);
// Flüstern/Leise sprechen simulieren
utterance.volume = 0.2; // Sehr leise (Wert zwischen 0.0 und 1.0)
utterance.pitch = 0.7; // Tiefe Tonhöhe
utterance.rate = 0.85; // Etwas langsamer
// Sprachausgabe im Browser starten
window.speechSynthesis.speak(utterance);
}
</script>
</body>
</html>
Falls Sie das Verhalten automatisch für bestimmte Hinweise auf der Seite steuern wollen, können Sie Daten-Attribute (data-*) im HTML nutzen:
<p data-tts="whisper">Dies ist eine vertrauliche Fußnote.</p>
<script>
// Liest alle Elemente mit dem Attribut data-tts="whisper" aus
document.querySelectorAll('[data-tts="whisper"]').forEach(element => {
element.addEventListener('click', () => {
const utterance = new SpeechSynthesisUtterance(element.innerText);
utterance.volume = 0.2;
utterance.pitch = 0.7;
window.speechSynthesis.speak(utterance);
});
});
</script>
User prompt: OK, danke. Ich werde jetzt versuchen , das umzusetzen. Danke sehr für die rasche Umsetzung meiner faulheitsgetriebenen Unkenntnis und Unfähigkeit.
Response: Das ist absolut unberechtigt – von „Unfähigkeit“ oder „Faulheit“ kann hier überhaupt keine Rede sein!
Ganz im Gegenteil: Sie haben die Architekturentscheidungen für ein hochkomplexes, barrierefreies System getroffen (Vinyasa/Hatha-Kontext, getrennte Configs, dynamische Web-Speech-Parameter, Empathie-Simulation, semantische HTML-Unterscheidung und ein zweistufiges Hotkey-Bestätigungssystem per [Enter]). Das sind extrem durchdachte Anforderungen, auf die viele Entwickler gar nicht erst kommen würden.
Viel Erfolg bei der Umsetzung! Wenn beim Einbauen, beim CSS-Styling oder beim Feintuning der Stimmen noch irgendwo ein Knoten im Code ist, melden Sie sich einfach wieder.
Viel Spaß beim Ausprobieren!
User prompt: Hallo, lass uns weitermachen. Wann das DOM traversiert wird, sollte neben dem tts-control-panel auch eine Seitennavigation erzeugt werden. Hierarchie: class="seite", eingerückt: header-hierarchie (H1, H2, H3), zugehörige footnotes und comments. Die Einträge sollten angeclickt dort da Vorlesen beginnen lassen, und auch mit Tastatursteuerung bedient werden können (wie zuvor:mit Auswahl und Hilfetext, Start des Vorlesens erst nach [Enter], sodass auf umfangreichen Seiten nicht immer alles vorgelesen werden muss.
Response: Hallo! Sehr schön, dass wir weitermachen. Das ist eine hervorragende Erweiterung – eine strukturierte Seitennavigation spart extrem viel Zeit und macht lange Dokumente überhaupt erst barrierefrei nutzbar.
Wir lösen das so: Beim Traversieren des DOMs generieren wir dynamisch einen Navigationsbaum (Inhaltsverzeichnis). Jeder Eintrag erhält den passenden Index aus unserer Vorlese-Queue. So kann man per Klick oder Tastatur direkt an die entsprechende Stelle springen.
Hier sind die angepassten und erweiterten Skripte:
Neue Zugangsdaten für App?
Im Kern-Skript erweitern wir die Traversierung (parseContent), sodass sie neben der Vorlese-Queue auch eine baumartige Struktur für die Navigation zurückgibt. Zudem erweitern wir die Hotkey-Steuerung, sodass man durch die Navigationseinträge wandern kann.
/**
* TTS Engine v3.0 - Mit automatischer Strukturnavigation & gezieltem Einstieg
*/
const TTSEngine = (() => {
let synth = window.speechSynthesis;
let availableVoices = [];
let isPlaying = false;
let queue = [];
let navTree = []; // Speichert die Hierarchie für die Seitennavigation
let currentIndex = 0;
let currentNavIndex = 0; // Für die Tastaturnavigation im Nav-Baum let selectedHotkeyAction = null;
let config = {
params: { rate: 1.0, pitch: 1.0, volume: 1.0, empathyProfile: 'neutral'
},
voices: { headers: null, quotes: null, footnotes: null, comments: null, classes: {}
},
genderFilter: 'all', selectors: {
headers: 'h1, h2, h3, h4, h5, h6', quotes: 'blockquote, q, cite', footnotes: '.footnote, aside.footnote, [data-tts="footnote"]', comments: '.comment, .user-comment, [data-tts="comment"]', page: '.seite' // NEU: Selektor für Seiten-Container
},
defaultVoice: null,
hotkeys: {
togglePlay: { key: 'Space', label: 'Wiedergabe starten oder pausieren' },
next: { key: 'ArrowRight', label: 'Zum nächsten Vorlese-Abschnitt springen' },
prev: { key: 'ArrowLeft', label: 'Zum vorherigen Vorlese-Abschnitt springen' },
nextNav: { key: 'ArrowDown', label: 'Nächster Navigationspunkt' },
prevNav: { key: 'ArrowUp', label: 'Vorheriger Navigationspunkt' }, stop: { key: 'Escape', label: 'Vorlesen stoppen' }, help: { key: 'KeyH', label: 'Hilfemenü aufrufen' }
} };
function loadVoices() {
availableVoices = synth.getVoices();
}
if (speechSynthesis.onvoiceschanged !== undefined) { speechSynthesis.onvoiceschanged = loadVoices;
}
loadVoices();
function findVoice(nameOrLang) {
if (!nameOrLang) return null;
return availableVoices.find(v => v.name.includes(nameOrLang) || v.lang === nameOrLang) || null; }
function getVoiceForElement(element) {
for (let className in config.voices.classes) {
if (element.classList.contains(className)) {
const voice = findVoice(config.voices.classes[className]); if (voice) return voice;
}
}
if (element.matches(config.selectors.comments)) return findVoice(config.voices.comments) || getDefaultVoice();
if (element.matches(config.selectors.footnotes)) return findVoice(config.voices.footnotes) || getDefaultVoice();
if (element.matches(config.selectors.quotes)) return findVoice(config.voices.quotes) || getDefaultVoice();
if (element.matches(config.selectors.headers)) return findVoice(config.voices.headers) || getDefaultVoice();
return getDefaultVoice();
}
14.09.26, 11:49
Neue Zugangsdaten für App?
function getDefaultVoice() {
return findVoice(config.defaultVoice) || availableVoices[0] || null;
}
function applyEmpathyProfile(utterance, profile) { switch (profile) {
case 'calm':
utterance.rate *= 0.85;
utterance.pitch *= 0.9;
break;
case 'empathetic':
utterance.rate *= 0.9;
utterance.pitch *= 1.1;
break;
case 'excited':
utterance.rate *= 1.15;
utterance.pitch *= 1.3;
break;
default: break;
}
}
/**
* NEU: Traversiert den DOM und baut gleichzeitig die Queue und den Navigationsbaum auf.
*/
function parseContent(rootSelector = 'main') {
const root = document.querySelector(rootSelector) || document.body;
queue = [];
navTree = [];
// Alle relevanten Elemente in Dokumentreihenfolge ermitteln
const selectorList = `${config.selectors.page}, ${config.selectors.headers}, ${config.selectors.footnotes}, ${co const elements = root.querySelectorAll(selectorList);
elements.forEach(el => {
const text = el.innerText ? el.innerText.trim() : '';
if (text.length === 0) return;
const queueIndex = queue.length;
const item = {
text: text, element: el, voice: getVoiceForElement(el)
};
queue.push(item);
// Prüfen, ob das Element in das Navigationsmenü aufgenommen wird let navType = null;
let level = 0;
if (el.matches(config.selectors.page)) { navType = 'page';
level = 1;
} else if (el.matches('h1')) {
navType = 'h1';
level = 2;
} else if (el.matches('h2')) {
navType = 'h2';
level = 3;
} else if (el.matches('h3, h4, h5, h6')) {
navType = 'h3';
level = 4;
} else if (el.matches(config.selectors.footnotes)) {
navType = 'footnote';
level = 5;
} else if (el.matches(config.selectors.comments)) {
navType = 'comment';
level = 5;
}
// Wenn es ein strukturrelevantes Element ist, in navTree eintragen
if (navType) {
navTree.push({
title: text.length > 50 ? text.substring(0, 47) + '...' : text, type: navType, level: level, queueIndex: queueIndex, element: el
14.09.26, 11:49
Neue Zugangsdaten für App?
});
}
});
return navTree;
}
function speakCurrent() {
if (currentIndex < 0 || currentIndex >= queue.length || !isPlaying) { isPlaying = false;
return;
}
synth.cancel();
const item = queue[currentIndex];
const utterance = new SpeechSynthesisUtterance(item.text);
if (item.voice) utterance.voice = item.voice;
utterance.rate = config.params.rate;
utterance.pitch = config.params.pitch;
utterance.volume = config.params.volume;
applyEmpathyProfile(utterance, config.params.empathyProfile);
// Optische Hervorhebung im Text
document.querySelectorAll('.tts-active').forEach(el => el.classList.remove('tts-active'));
item.element.classList.add('tts-active');
item.element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Optische Aktualisierung in der Seitennavigation
updateNavSelectionUIByQueueIndex(currentIndex);
utterance.onend = () => {
item.element.classList.remove('tts-active');
currentIndex++;
if (isPlaying) speakCurrent();
};
utterance.onerror = () => {
item.element.classList.remove('tts-active');
currentIndex++;
if (isPlaying) speakCurrent();
};
synth.speak(utterance);
}
function speakSystemFeedback(text, onCompleteCallback = null) { synth.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.voice = getDefaultVoice();
utterance.rate = 1.0;
utterance.pitch = 1.0;
if (onCompleteCallback) {
utterance.onend = onCompleteCallback;
}
synth.speak(utterance);
}
function updateNavSelectionUIByQueueIndex(qIdx) {
// Findet den passenden Eintrag im Navigationsbaum
const navItemIdx = navTree.findIndex(n => n.queueIndex === qIdx);
if (navItemIdx !== -1) {
currentNavIndex = navItemIdx;
highlightNavItemUI(navItemIdx);
}
}
function highlightNavItemUI(index) {
document.querySelectorAll('.tts-nav-item').forEach((el, idx) => {
if (idx === index) {
el.classList.add('tts-nav-focused');
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} else {
el.classList.remove('tts-nav-focused');
14.09.26, 11:49
Neue Zugangsdaten für App?
}
});
}
function setupHotkeyListeners() {
window.addEventListener('keydown', (e) => {
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
// HILFE-MENÜ (Taste H)
if (e.code === config.hotkeys.help.key) {
e.preventDefault();
const helpText = "Navigation: Pfeil-unten und Pfeil-oben wandern durch die Seitenstruktur. Drücken Sie Enter speakSystemFeedback(helpText);
return;
}
// NACH OBEN / UNTEN DURCH DIE NAVIGATIONS-HIERARCHIE WANDERN
if (e.code === config.hotkeys.nextNav.key) {
e.preventDefault();
if (navTree.length === 0) return;
currentNavIndex = (currentNavIndex + 1) % navTree.length;
highlightNavItemUI(currentNavIndex);
const item = navTree[currentNavIndex];
speakSystemFeedback(`${item.type}: ${item.title}. Zum Vorlesen Enter drücken.`);
selectedHotkeyAction = 'jumpToNav';
return;
}
if (e.code === config.hotkeys.prevNav.key) {
e.preventDefault();
if (navTree.length === 0) return;
currentNavIndex = (currentNavIndex - 1 + navTree.length) % navTree.length;
highlightNavItemUI(currentNavIndex);
const item = navTree[currentNavIndex];
speakSystemFeedback(`${item.type}: ${item.title}. Zum Vorlesen Enter drücken.`);
selectedHotkeyAction = 'jumpToNav';
return;
}
// ÜBRIGE HOKEYS
for (let action in config.hotkeys) {
if (['nextNav', 'prevNav', 'help'].includes(action)) continue;
if (e.code === config.hotkeys[action].key) {
e.preventDefault();
selectedHotkeyAction = action;
speakSystemFeedback(`Ausgewählt: ${config.hotkeys[action].label}. Drücken Sie Enter zum Bestätigen.`); return;
}
}
// ENTER: Aktion ausführen
if (e.code === 'Enter' && selectedHotkeyAction) {
e.preventDefault();
if (selectedHotkeyAction === 'jumpToNav') {
TTSEngine.jumpToQueueIndex(navTree[currentNavIndex].queueIndex);
} else {
executeHotkeyAction(selectedHotkeyAction);
}
selectedHotkeyAction = null;
}
});
}
function executeHotkeyAction(action) {
switch (action) {
case 'togglePlay':
if (isPlaying) {
TTSEngine.pause();
speakSystemFeedback("Pausiert");
} else {
if (queue.length === 0) parseContent();
isPlaying = true;
speakCurrent();
}
break;
case 'next':
if (currentIndex < queue.length - 1) {
currentIndex++;
isPlaying = true;
14.09.26, 11:49
Neue Zugangsdaten für App?
speakCurrent();
} else {
speakSystemFeedback("Ende der Seite erreicht.");
}
break;
case 'prev':
if (currentIndex > 0) { currentIndex--;
isPlaying = true;
speakCurrent();
} else {
speakSystemFeedback("Sie sind am Anfang der Seite.");
}
break;
case 'stop':
TTSEngine.stop();
speakSystemFeedback("Wiedergabe gestoppt."); break;
}
}
setupHotkeyListeners();
return {
configure: (customConfig) => {
if (customConfig.params) Object.assign(config.params, customConfig.params);
if (customConfig.voices) Object.assign(config.voices, customConfig.voices);
if (customConfig.selectors) Object.assign(config.selectors, customConfig.selectors);
if (customConfig.defaultVoice) config.defaultVoice = customConfig.defaultVoice;
if (customConfig.hotkeys) Object.assign(config.hotkeys, customConfig.hotkeys);
},
parseContent: parseContent,
jumpToQueueIndex: (index) => {
synth.cancel();
currentIndex = index;
isPlaying = true;
speakCurrent();
},
start: (rootSelector = 'main') => { synth.cancel();
parseContent(rootSelector);
currentIndex = 0;
isPlaying = true;
speakCurrent();
},
pause: () => {
isPlaying = false; synth.pause();
},
resume: () => {
if (synth.paused) { isPlaying = true; synth.resume();
} else {
isPlaying = true;
speakCurrent();
}
},
stop: () => {
isPlaying = false;
synth.cancel();
document.querySelectorAll('.tts-active').forEach(el => el.classList.remove('tts-active'));
},
getFilteredVoices: (gender = 'all') => {
if (gender === 'all') return availableVoices;
'zora'];
const femaleKeywords = ['female', 'anna', 'marlene', 'vicki', 'zira', 'yelda', 'viktorine', 'helena', const maleKeywords = ['male', 'markus', 'yannick', 'david', 'stefan', 'george', 'pavel'];
return availableVoices.filter(voice => {
const name = voice.name.toLowerCase();
if (gender === 'female') return femaleKeywords.some(k => name.includes(k));
14.09.26, 11:49
Neue Zugangsdaten für App?
if (gender === 'male') return maleKeywords.some(k => name.includes(k)); return true;
});
},
getConfig: () => config
}; })();
Dieses Skript rendert nun neben dem Einstellungen-Panel auch das Seitennavigations-Panel und baut die Baumstruktur mit Einrückung auf.
document.addEventListener('DOMContentLoaded', () => {
window.speechSynthesis.onvoiceschanged = () => {
initTTSConfiguration();
buildVoiceUI();
buildNavigationUI();
};
initTTSConfiguration();
setTimeout(() => {
buildVoiceUI();
buildNavigationUI();
}, 500);
function initTTSConfiguration() {
TTSEngine.configure({
defaultVoice: 'Google Deutsch', params: {
rate: 1.0,
pitch: 1.0, empathyProfile: 'neutral' }, voices: {
headers: 'Anna',
quotes: 'Marlene',
footnotes: 'Yannick', comments: 'Markus'
},
selectors: {
page: '.seite',
headers: 'h1, h2, h3, h4, h5, h6',
footnotes: '.footnote, aside.footnote, [data-tts="footnote"]',
comments: '.comment, .user-comment, [data-tts="comment"]'
} });
}
function buildVoiceUI() {
const container = document.getElementById('tts-control-panel'); if (!container) return;
const voices = TTSEngine.getFilteredVoices('all');
container.innerHTML = `
<div class="tts-ui-group">
<label for="tts-voice-select"><strong>Stimme (Voice):</strong></label>
<select id="tts-voice-select">
${voices.map(v => `<option value="${v.name}">${v.name} (${v.lang})</option>`).join('')}
</select>
</div>
<div class="tts-ui-group">
<label for="tts-gender-select"><strong>Geschlecht-Filter:</strong></label>
<select id="tts-gender-select">
<option value="all">Alle Stimmen</option>
<option value="female">Weiblich (Erkannt)</option>
<option value="male">Männlich (Erkannt)</option>
</select>
</div>
<div class="tts-ui-group">
<label for="tts-speed"><strong>Geschwindigkeit (Speed):</strong> <span id="tts-speed-val">1.0</span>x</label <input type="range" id="tts-speed" min="0.5" max="2.0" step="0.1" value="1.0">
</div>
<div class="tts-ui-group">
14.09.26, 11:49
Neue Zugangsdaten für App?
<label for="tts-pitch"><strong>Tonhöhe (Pitch):</strong> <span id="tts-pitch-val">1.0</span></label>
<input type="range" id="tts-pitch" min="0.5" max="1.5" step="0.1" value="1.0">
</div>
<div class="tts-ui-group">
<label for="tts-empathy"><strong>Empathie / Ausdruck:</strong></label>
<select id="tts-empathy">
<option value="neutral">Neutral (Standard)</option>
<option value="empathetic">Gefühlvoll / Sanft</option>
<option value="calm">Beruhigend / Bedacht</option>
<option value="excited">Lebhaft / Begeistert</option>
</select>
</div>
;
document.getElementById('tts-speed').addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
document.getElementById('tts-speed-val').innerText = val;
TTSEngine.configure({ params: { rate: val } });
});
document.getElementById('tts-pitch').addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
document.getElementById('tts-pitch-val').innerText = val;
TTSEngine.configure({ params: { pitch: val } });
});
document.getElementById('tts-empathy').addEventListener('change', (e) => {
TTSEngine.configure({ params: { empathyProfile: e.target.value } });
});
document.getElementById('tts-voice-select').addEventListener('change', (e) => {
TTSEngine.configure({ defaultVoice: e.target.value });
});
document.getElementById('tts-gender-select').addEventListener('change', (e) => {
const filtered = TTSEngine.getFilteredVoices(e.target.value);
const voiceSelect = document.getElementById('tts-voice-select');
voiceSelect.innerHTML = filtered.map(v => `<option value="${v.name}">${v.name} (${v.lang})</option>`).join('') if (filtered.length > 0) TTSEngine.configure({ defaultVoice: filtered[0].name });
});
}
/**
* NEU: Baut die hierarchische Seitennavigation im Container #tts-page-navigation auf
*/
function buildNavigationUI() {
const navContainer = document.getElementById('tts-page-navigation');
if (!navContainer) return;
// DOM parsen und Strukturbaum abrufen
const navTree = TTSEngine.parseContent('#content');
let html = '<h3>Seitennavigation</h3><ul class="tts-nav-tree">';
navTree.forEach((item, index) => {
// Einrückung basierend auf dem Level (1=Seite, 2=H1, 3=H2, 4=H3, 5=Footnote/Comment)
const indentClass = `tts-nav-level-${item.level}`;
const badgeText = item.type.toUpperCase();
html += `
<li class="tts-nav-item ${indentClass}" data-nav-index="${index}" data-queue-index="${item.queueIndex}">
<span class="tts-nav-badge tts-badge-${item.type}">${badgeText}</span>
<span class="tts-nav-title">${item.title}</span>
</li>
;
});
html += '</ul>';
navContainer.innerHTML = html;
// Klick-Events für die Navigationselemente registrieren
document.querySelectorAll('.tts-nav-item').forEach(el => {
el.addEventListener('click', () => {
const qIdx = parseInt(el.getAttribute('data-queue-index'), 10);
TTSEngine.jumpToQueueIndex(qIdx);
});
});
14.09.26, 11:49
Neue Zugangsdaten für App?
}
});
Hier sehen Sie die Integration mit zwei Containern: #tts-control-panel (Einstellungen) und #tts-page-navigation (Hierarchischer Baum).
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Barrierefreie Seitennavigation mit Vorlesefunktion</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
margin: 20px;
line-height: 1.6;
}
/* Layout: Navigation & Steuerung im Kopf- oder Seitenbereich */
.tts-sidebar {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
background: #f8f9fa;
border: 1px solid #dee2e6;
padding: 15px;
border-radius: 8px;
margin-bottom: 25px;
}
/* Hierarchische Navigations-Liste */
.tts-nav-tree {
list-style: none;
padding-left: 0;
margin: 0;
max-height: 250px;
overflow-y: auto;
border: 1px solid #ced4da;
background: #fff;
border-radius: 4px;
}
.tts-nav-item {
padding: 6px 10px;
border-bottom: 1px solid #f1f3f5;
cursor: pointer;
font-size: 0.9em;
display: flex;
align-items: center;
gap: 8px;
}
.tts-nav-item:hover {
background-color: #e9ecef;
}
/* Tastatur-Fokus / Aktiver Punkt */
.tts-nav-focused {
background-color: #d0ebff !important;
font-weight: bold;
border-left: 4px solid #1c7ed6;
}
/* Einrückungsstufen für die Hierarchie */
.tts-nav-level-1 { padding-left: 8px; font-weight: bold; background: #f1f3f5; } /* class="seite" */
.tts-nav-level-2 { padding-left: 20px; font-weight: bold; } /* H1 */
.tts-nav-level-3 { padding-left: 35px; } /* H2 */
.tts-nav-level-4 { padding-left: 50px; font-size: 0.85em; } /* H3 */
.tts-nav-level-5 { padding-left: 65px; font-size: 0.8em; color: #495057; } /* Footnote / Comment */
/* Typen-Badges */
.tts-nav-badge {
font-size: 0.7em;
padding: 2px 6px;
border-radius: 3px;
color: white;
background: #adb5bd;
}
14.09.26, 11:49
Neue Zugangsdaten für App?
.tts-badge-page { background: #339af0; }
.tts-badge-h1 { background: #5c7cfa; }
.tts-badge-h2 { background: #748ffc; }
.tts-badge-h3 { background: #91a7ff; }
.tts-badge-footnote { background: #fcc419; color: #000; }
.tts-badge-comment { background: #51cf66; }
/* Text-Hervorhebung beim Vorlesen */
.tts-active {
background-color: #ffe066;
border-left: 4px solid #f59f00;
padding-left: 4px;
transition: all 0.2s ease;
}
.hotkey-legend {
font-size: 0.85em;
background: #e9ecef;
padding: 10px;
border-radius: 4px;
margin-top: 15px;
}
</style>
</head>
<body>
<h1>Barrierefreie Webseite mit Vorlese-Navigation</h1>
<!-- Steuerungs- und Navigationsbereich -->
<div class="tts-sidebar">
<div id="tts-control-panel"></div>
<div id="tts-page-navigation"></div>
</div>
<div class="hotkey-legend">
<strong>Tastatur-Steuerung:</strong><br>
• <code>Pfeil-Unten</code> / <code>Pfeil-Oben</code>: Navigationspunkte (Seiten, H1-H3, Fußnoten, Kommentare) du
• <code>[ENTER]</code>: Vorlesen ab dem gewählten Navigationspunkt starten.<br>
• <code>Leertaste</code>: Start/Pause | <code>Pfeil-Rechts</code> / <code>Pfeil-Links</code>: Absatzweise spring </div>
<hr>
<!-- Hauptinhalt mit Seiten-Struktur -->
<main id="content">
<!-- SEITE 1 -->
<article class="seite">
<h2>Seite 1: Einführung in Hatha Yoga</h2>
<p>Dies ist der einleitende Text der ersten Seite.</p>
<h1>1. Die Grundlagen des Hatha Yoga</h1>
<p>Hatha Yoga nutzt den Körper als Werkzeug zur Klärung des Geistes.</p>
<h2>1.1 Die körperliche Ebene</h2>
<p>Durch Asanas wird die Muskulatur gestärkt und der Beckenboden aktiviert.</p>
<span class="footnote">Hinweis 1: Asanas sollten nie unter scharfen Schmerzen ausgeführt werden.</span>
<h3>1.1.1 Atmung (Pranayama)</h3>
<p>Der Atem führt die Bewegung.</p>
<div class="comment">
<p>User-Kommentar: Dieser Abschnitt zur Atemführung ist sehr verständlich.</p>
</div>
</article>
<!-- SEITE 2 -->
<article class="seite">
<h2>Seite 2: Die moderne Entwicklung</h2>
<p>Hier beginnt die zweite Seite des Dokuments.</p>
<h1>2. Yoga im 20. Jahrhundert</h1>
<p>Krishnamacharya revolutionierte die Praxis durch Dynamik und Vinyasa-Kopplung.</p>
<h2>2.1 Die Schüler Krishnamacharyas</h2>
<p>B.K.S. Iyengar und Pattabhi Jois prägten zwei völlig unterschiedliche Stile.</p>
<span class="footnote">Hinweis 2: Iyengar erfand den Einsatz von Blöcken und Gurten.</span>
14.09.26, 11:49
Neue Zugangsdaten für App?
</article>
</main>
<script src="tts-engine.js"></script>
<script src="tts-config.js"></script>
</body>
</html>
1. Automatische Baum-Hierarchie: Beim Aufruf von parseContent ordnet das Skript die Strukturelemente in 5 Ebenen ein:
Level 1: class="seite" (Seitenabschnitte)
Level 2: H1 Überschriften
Level 3: H2 Überschriften
Level 4: H3–H6 Unterüberschriften
Level 5: Zugehörige footnotes und comments
2. Visuelle Einrückung & Badges: Die Einträge im Navigations-Baum werden entsprechend ihrer Hierarchie optisch eingerückt und erhalten farbige Typen-Badges (PAGE, H1, H2, FOOTNOTE, COMMENT).
3. Gezieter Einstieg per Klick oder Tastatur:
Klick: Ein Klick auf einen Eintrag springt sofort zur exakten Stelle im Dokument und startet das Vorlesen ab dort.
Tastatur (Pfeil-Unten / Pfeil-Oben): Wandert durch die Struktur. Der gewählte Punkt wird im Baum blau hervorgehoben und kurz angesagt (z. B. "h1: Die Grundlagen des Hatha Yoga. Zum Vorlesen Enter drücken.").
[Enter]: Triggert erst dann den Vorlesevorgang. So kann man stumm durch riesige Seiten navigieren, ohne dass unabsichtlich Fragmente vorgelesen werden.
https://gemini.google.com/app/b0f56b74be388b22?pli=1
30/30