Adaptiv KI
  1. Aktuelle Seite:  
  2. Startseite
  3. Uncategorised

Uncategorised

TEST

Details
Geschrieben von: admin
Kategorie: Uncategorised
Veröffentlicht: 03. Dezember 2025
Zugriffe: 12
 
Pokéball

Pokémon

Ruf-Galerie

Finde und höre jedes Pokémon.

Lade Pokémon...

Download bestätigen

Möchtest du den Ruf von Glurak (#006) wirklich als MP3-Datei herunterladen?

TEST

Details
Geschrieben von: admin
Kategorie: Uncategorised
Veröffentlicht: 03. Dezember 2025
Zugriffe: 5
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title id="pageTitle">Pokémon-Ruf-Galerie</title>
   
    <!-- Lade Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
   
    <!-- Lade Lucide Icons -->
    <script type="module">
        // Import der Lucide Icons. 'Download' wurde für Kompatibilität verwendet.
        import { createIcons, Zap, Volume2, Search, Loader, Globe, Download, BellRing } from 'https://unpkg.com/lucide@latest/dist/umd/lucide.js';
        createIcons({ icons: { Zap, Volume2, Search, Loader, Globe, Download, BellRing } });
    </script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=Press+Start+2P&display=swap');
        body {
            font-family: 'Inter', sans-serif;
            background-color: #f7f7f7;
        }
        /* Eigene Schriftart für das Logo */
        .logo-font {
            font-family: 'Press Start 2P', cursive;
            text-shadow:
                -2px -2px 0 #3b82f6,
                2px -2px 0 #3b82f6,
                -2px 2px 0 #3b82f6,
                2px 2px 0 #3b82f6,
                4px 4px 0 #a5b4fc;
            -webkit-text-stroke: 1px #3b82f6;
        }
        /* Custom scrollbar für besseren Look */
        ::-webkit-scrollbar { width: 8px; }
        ::-webkit-scrollbar-thumb { background: #ef4444; border-radius: 4px; }
        ::-webkit-scrollbar-track { background: #fca5a5; }
       
        .pokemon-card {
            transition: transform 0.2s, box-shadow 0.2s;
            cursor: pointer;
            position: relative;
            user-select: none;
            -webkit-user-select: none;
            touch-action: manipulation;
        }
        .pokemon-card:hover {
            transform: translateY(-4px);
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
        }
       
        /* Visuelles Feedback für den Langen Druck */
        .pokemon-card.long-press-active {
            box-shadow: 0 0 0 5px rgba(255, 193, 7, 0.5); /* Gelber Rahmen als Halte-Indikator */
        }
       
        /* Animation für den Sound-Indikator */
        .cry-indicator i[data-lucide="volume-2"]:hover {
            animation: pulse-ring 1.5s infinite;
        }
        @keyframes pulse-ring {
            0% { transform: scale(0.9); opacity: 1; }
            70% { transform: scale(1.1); opacity: 0.5; }
            100% { transform: scale(0.9); opacity: 1; }
        }
       
        /* Icon-Platzierung für visuelles Feedback */
        .action-icon {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%) scale(0);
            opacity: 0;
            transition: all 0.2s ease-out;
            pointer-events: none;
            z-index: 10;
            background: rgba(255, 255, 255, 0.8);
            border-radius: 50%;
            padding: 8px;
        }
        .action-icon.show-play {
            transform: translate(-50%, -50%) scale(1);
            opacity: 1;
            color: #ef4444; /* Rot für Play */
            animation: none;
        }
    </style>
</head>
<body class="p-4 sm:p-8">

    <div class="max-w-6xl mx-auto">
       
        <!-- Header und Banner im Pokémon-Stil -->
        <header class="mb-8 pb-4 border-b-4 border-red-500 relative overflow-hidden rounded-xl bg-gradient-to-r from-red-600 to-red-400 shadow-xl p-6 sm:p-8 text-white">
           
            <!-- Hintergrundmuster -->
            <div class="absolute inset-0 opacity-10 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')]"></div>

            <!-- Sprachauswahl -->
            <div class="absolute top-4 right-4 z-20 flex items-center bg-white/20 backdrop-blur-sm rounded-full pr-3">
                <i data-lucide="globe" class="w-5 h-5 ml-3 mr-2"></i>
                <select id="languageSelector" onchange="changeLanguage(this.value)" class="bg-transparent text-white text-sm font-semibold focus:outline-none cursor-pointer">
                    <option value="de" class="text-gray-800">Deutsch</option>
                    <option value="en" class="text-gray-800">English</option>
                    <option value="es" class="text-gray-800">Español</option>
                    <option value="fr" class="text-gray-800">Français</option>
                    <option value="it" class="text-gray-800">Italiano</option>
                    <option value="ja" class="text-gray-800">日本語</option>
                </select>
            </div>


            <div class="relative z-10 flex flex-col sm:flex-row items-center justify-center sm:justify-start pt-6 sm:pt-0">
               
                <!-- Animierter Pokéball -->
                <img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/poke-ball.png" alt="Pokéball" class="w-16 h-16 sm:w-20 sm:h-20 mr-0 sm:mr-4 mb-4 sm:mb-0 animate-bounce-slow">
               
                <div>
                    <!-- Logo Text mit Spezialschnitt (Pixel-Font) -->
                    <h1 class="text-4xl sm:text-5xl font-extrabold logo-font text-white mb-2 text-center sm:text-left">
                        Pokémon
                    </h1>
                    <p id="headerSubtitle" class="text-lg sm:text-xl font-semibold text-white text-center sm:text-left">
                        Ruf-Galerie
                    </p>
                    <p id="headerDescription" class="text-sm opacity-90 mt-2 text-center sm:text-left">
                        Finde und höre jedes Pokémon.
                    </p>
                </div>
            </div>
           
        </header>

        <!-- Suchleiste (Sticky) -->
        <div class="mb-6 sticky top-0 bg-[#f7f7f7] py-3 z-10 -mx-4 px-4 sm:mx-0 sm:px-0 shadow-md rounded-xl">
            <div class="relative">
                <i data-lucide="search" class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400"></i>
                <input
                    type="text"
                    id="searchInput"
                    placeholder="Pokémon-Namen oder ID suchen (z.B. Glurak, 6)..."
                    class="w-full p-3 pl-10 border border-gray-300 rounded-xl focus:ring-red-500 focus:border-red-500 transition shadow-inner"
                    oninput="filterAndFetchPokemons()"
                />
            </div>
        </div>

        <!-- Lade- und Fehlerstatus -->
        <div id="statusMessage" class="text-center p-8 text-lg font-semibold text-gray-700">
             <i data-lucide="loader" class="w-6 h-6 animate-spin text-red-500 mx-auto mb-2"></i>
             Lade Pokémon...
        </div>

        <!-- Pokémon-Karten Container -->
        <main id="pokemonList" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4"></main>
       
        <!-- Mehr-laden Button -->
        <div class="text-center mt-8">
            <button
                id="loadMoreButton"
                onclick="loadMorePokemons()"
                class="bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-full shadow-lg transition duration-200 transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-4 focus:ring-red-300"
                style="display: none;"
            >
                 <i data-lucide="loader" class="w-4 h-4 mr-2 inline-block"></i>
                 Nächste 200 Pokémon laden
            </button>
        </div>
       
    </div>
   
    <!-- DOWNLOAD BESTÄTIGUNGS-MODAL -->
    <div id="downloadModal" class="fixed inset-0 bg-gray-900 bg-opacity-75 hidden flex items-center justify-center z-50 p-4" onclick="closeModal()">
        <div
            id="modalContent"
            class="bg-white rounded-xl shadow-2xl w-full max-w-sm p-6 transform transition-all duration-300"
            onclick="event.stopPropagation()"
        >
            <div class="text-center mb-4">
                <!-- Icon für den Download-Button im Modal -->
                <i data-lucide="download" class="w-10 h-10 text-blue-500 mx-auto mb-3"></i>
                <h3 id="modalTitle" class="text-xl font-bold text-gray-800">Download bestätigen</h3>
            </div>
           
            <p id="modalMessage" class="text-gray-600 mb-6 text-center">Möchtest du den Ruf von Glurak (#006) wirklich als MP3-Datei herunterladen?</p>
           
            <div class="flex justify-end space-x-3">
                <button
                    onclick="closeModal()"
                    id="modalCancelButton"
                    class="px-4 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300 transition"
                >
                    Abbrechen
                </button>
                <button
                    id="modalConfirmButton"
                    class="px-4 py-2 bg-red-500 text-white font-semibold rounded-lg hover:bg-red-600 transition"
                >
                    Bestätigen und herunterladen
                </button>
            </div>
        </div>
    </div>

    <script>
        // --- TRANSLATIONS (Übersetzungen) ---
        const translations = {
            de: {
                title: 'Pokémon-Ruf-Galerie',
                subtitle: 'Ruf-Galerie',
                description: 'Finde und höre jedes Pokémon.',
                searchPlaceholder: 'Pokémon-Namen oder ID suchen (z.B. Glurak, 6)...',
                loading: 'Lade Pokémon...',
                loadingBlock: 'Lade Pokémon-Block...',
                loadMore: (count) => `Nächste ${count} Pokémon laden`,
                notFound: (term) => `Pokémon "${term}" nicht gefunden.`,
                fetchError: 'Fehler beim Laden der Pokémon. Bitte versuchen Sie es später erneut.',
                // Modal
                modalTitle: 'Download bestätigen',
                modalMessage: (name, id) => `Möchtest du den Ruf von ${name} (#${String(id).padStart(3, '0')}) wirklich als MP3-Datei herunterladen?`,
                modalCancel: 'Abbrechen',
                modalConfirm: 'Bestätigen und herunterladen',
            },
            en: {
                title: 'Pokémon Cry Gallery',
                subtitle: 'Cry Gallery',
                description: 'Find and listen to every Pokémon.',
                searchPlaceholder: 'Search Pokémon name or ID (e.g. Charizard, 6)...',
                loading: 'Loading Pokémon...',
                loadingBlock: 'Loading Pokémon block...',
                loadMore: (count) => `Load next ${count} Pokémon`,
                notFound: (term) => `Pokémon "${term}" not found.`,
                fetchError: 'Error loading Pokémon. Please try again later.',
                // Modal
                modalTitle: 'Confirm Download',
                modalMessage: (name, id) => `Do you really want to download the cry for ${name} (#${String(id).padStart(3, '0')}) as an MP3 file?`,
                modalCancel: 'Cancel',
                modalConfirm: 'Confirm and Download',
            },
            es: {
                title: 'Galería de Gritos Pokémon',
                subtitle: 'Galería de Gritos',
                description: 'Encuentra y escucha a todos los Pokémon.',
                searchPlaceholder: 'Buscar nombre o ID de Pokémon (ej. Charizard, 6)...',
                loading: 'Cargando Pokémon...',
                loadingBlock: 'Cargando bloque de Pokémon...',
                loadMore: (count) => `Cargar ${count} Pokémon más`,
                notFound: (term) => `Pokémon "${term}" no encontrado.`,
                fetchError: 'Error al cargar Pokémon. Por favor, inténtelo de nuevo más tarde.',
                // Modal
                modalTitle: 'Confirmar Descarga',
                modalMessage: (name, id) => `¿Realmente quieres descargar el grito de ${name} (#${String(id).padStart(3, '0')}) como archivo MP3?`,
                modalCancel: 'Cancelar',
                modalConfirm: 'Confirmar y Descargar',
            },
             fr: {
                title: 'Galerie des Cris Pokémon',
                subtitle: 'Galerie des Cris',
                description: 'Trouvez et écoutez tous les Pokémon.',
                searchPlaceholder: 'Chercher nom ou ID de Pokémon (ex. Dracaufeu, 6)...',
                loading: 'Chargement des Pokémon...',
                loadingBlock: 'Chargement du bloc Pokémon...',
                loadMore: (count) => `Charger les ${count} prochains Pokémon`,
                notFound: (term) => `Pokémon "${term}" non trouvé.`,
                fetchError: 'Erreur lors du chargement des Pokémon. Veuillez réessayer plus tard.',
                // Modal
                modalTitle: 'Confirmer le Téléchargement',
                modalMessage: (name, id) => `Voulez-vous vraiment télécharger le cri de ${name} (#${String(id).padStart(3, '0')}) au format MP3 ?`,
                modalCancel: 'Annuler',
                modalConfirm: 'Confirmer et Télécharger',
            },
            it: {
                title: 'Galleria dei Versi Pokémon',
                subtitle: 'Galleria dei Versi',
                description: 'Trova e ascolta ogni Pokémon.',
                searchPlaceholder: 'Cerca nome o ID Pokémon (es. Charizard, 6)...',
                loading: 'Caricamento Pokémon...',
                loadingBlock: 'Caricamento blocco Pokémon...',
                loadMore: (count) => `Carica i prossimi ${count} Pokémon`,
                notFound: (term) => `Pokémon "${term}" non trovato.`,
                fetchError: 'Errore durante il caricamento dei Pokémon. Riprova più tardi.',
                // Modal
                modalTitle: 'Conferma Download',
                modalMessage: (name, id) => `Vuoi davvero scaricare il verso di ${name} (#${String(id).padStart(3, '0')}) come file MP3?`,
                modalCancel: 'Annulla',
                modalConfirm: 'Conferma e Scarica',
            },
            ja: {
                title: 'ポケモン鳴き声ギャラリー',
                subtitle: '鳴き声ギャラリー',
                description: 'すべてのポケモンを見つけて聞く。',
                searchPlaceholder: 'ポケモンの名前またはIDを検索 (例: リザードン, 6)...',
                loading: 'ポケモンをロード中...',
                loadingBlock: 'ポケモンブロックをロード中...',
                loadMore: (count) => `次の${count}匹のポケモンをロード`,
                notFound: (term) => `ポケモン"${term}"は見つかりませんでした。`,
                fetchError: 'ポケモンのロード中にエラーが発生しました。後でもう一度お試しください。',
                // Modal
                modalTitle: 'ダウンロード確認',
                modalMessage: (name, id) => `${name}(#${String(id).padStart(3, '0')})の鳴き声をMP3ファイルとしてダウンロードしますか?`,
                modalCancel: 'キャンセル',
                modalConfirm: '確認してダウンロード',
            }
        };

        // --- GLOBALE KONSTANTEN UND ZUSTAND ---
        const POKEMON_LIST_ELEMENT = document.getElementById('pokemonList');
        const STATUS_MESSAGE = document.getElementById('statusMessage');
        const LOAD_MORE_BUTTON = document.getElementById('loadMoreButton');
        const CRY_BASE_URL = 'https://play.pokemonshowdown.com/audio/cries/';
        const POKEAPI_BASE_URL = 'https://pokeapi.co/api/v2/pokemon/';
        const POKEAPI_SPECIES_URL = 'https://pokeapi.co/api/v2/pokemon-species/';
        const DOWNLOAD_MODAL = document.getElementById('downloadModal');

        // Mappt die Sprachcodes zu den IDs der PokeAPI (für lokalisierte Namen)
        const POKEAPI_LANG_MAP = {
            'de': 'de', 'en': 'en', 'es': 'es', 'fr': 'fr', 'it': 'it', 'ja': 'ja'
        };
       
        // Konfiguration für die Ladestrategie
        const INITIAL_LIMIT = 251;
        const LOAD_INCREMENT = 200;
       
        let currentLanguage = 'de';
        let allPokemonData = new Map();
        let currentOffset = 0;
        let isLoading = false;
        let pokemonToDownload = null; // Speichert das Pokémon-Objekt für den Modal-Download


        // --- INTERAKTIONS-LOGIK (KURZ-/LANGER DRUCK) ---
        let pressTimer = null;
        const LONG_PRESS_DURATION = 500; // 0.5 Sekunden
        let isLongPress = false;

        /**
         * Startet den Timer für den langen Druck.
         */
        window.startPress = (englishName, id, event) => {
            if (event.type === 'mousedown') { event.preventDefault(); }
            if (event.type === 'touchstart') { event.stopPropagation(); }

            clearTimeout(pressTimer);
            isLongPress = false;

            // Füge visuelles Feedback hinzu
            const card = event.currentTarget;
            card.classList.add('long-press-active');
           
            // Starte den Download-Timer
            pressTimer = setTimeout(() => {
                isLongPress = true;
                card.classList.remove('long-press-active'); // Entferne Halte-Feedback
                showConfirmationModal(id);
            }, LONG_PRESS_DURATION);
        };

        /**
         * Beendet den Druck und führt bei kurzem Druck die Play-Aktion aus.
         */
        window.endPress = (englishName, id, event) => {
            const card = event.currentTarget;
           
            clearTimeout(pressTimer);
            card.classList.remove('long-press-active');

            // Wenn es kein langer Druck war, ist es ein kurzer Klick/Tipp
            if (!isLongPress) {
                playCry(englishName, card);
            }
           
            // Reset state
            isLongPress = false;
        };
       
        /**
         * Bricht den langen Druck ab (z.B. bei Scrollen/Bewegen des Fingers).
         */
        window.cancelPress = (event) => {
            clearTimeout(pressTimer);
            isLongPress = true; // Setze auf true, um versehentliches Abspielen zu verhindern
            const card = event.currentTarget;
            card.classList.remove('long-press-active');
        }

        // --- MODAL LOGIK ---
       
        /**
         * Zeigt das Bestätigungs-Modal an.
         */
        const showConfirmationModal = (pokemonId) => {
            const pokemon = allPokemonData.get(parseInt(pokemonId));
            if (!pokemon) return;

            pokemonToDownload = pokemon;
            const t = translations[currentLanguage];
            const localizedName = pokemon.localizedNames[currentLanguage];
           
            // Inhalt lokalisieren
            document.getElementById('modalTitle').textContent = t.modalTitle;
            document.getElementById('modalMessage').innerHTML = t.modalMessage(localizedName, pokemon.id);
            document.getElementById('modalCancelButton').textContent = t.modalCancel;
            document.getElementById('modalConfirmButton').textContent = t.modalConfirm;
           
            // Download-Funktion an den Button binden
            document.getElementById('modalConfirmButton').onclick = () => {
                triggerDownload(pokemonToDownload);
                closeModal();
            };

            DOWNLOAD_MODAL.classList.remove('hidden');
            if (typeof createIcons !== 'undefined') { createIcons(); } // Icons im Modal aktualisieren
        };

        /**
         * Schließt das Bestätigungs-Modal.
         */
        window.closeModal = () => {
            DOWNLOAD_MODAL.classList.add('hidden');
            pokemonToDownload = null;
        };


        /**
         * Löst den Download der MP3-Datei aus.
         */
        const triggerDownload = (pokemon) => {
            const paddedId = String(pokemon.id).padStart(3, '0');
            const downloadFileName = `${paddedId}_${pokemon.englishName}.mp3`;
            const downloadUrl = `${CRY_BASE_URL}${sanitizeName(pokemon.englishName)}.mp3`;
           
            // Verwende einen temporären Anker-Tag für den Download
            const a = document.createElement('a');
            a.href = downloadUrl;
            a.download = downloadFileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
        };


        // --- SOUND LOGIK ---
       
        /**
         * Konvertiert den ENGLISCHEN Namen für die Sound-URL.
         */
        const sanitizeName = (name) => {
            return name.toLowerCase().replace(/[^a-z0-9-]/g, '');
        };
       
        /**
         * Spielt den Ruf-Sound des Pokémons ab und zeigt Feedback.
         */
        window.playCry = (englishName, card) => {
            const sanitizedName = sanitizeName(englishName);
            const audio = new Audio(`${CRY_BASE_URL}${sanitizedName}.mp3`);
            audio.play().catch(e => console.error("Fehler beim Abspielen des Rufs:", e));
           
            // Visuelles Feedback für Play
            showActionIcon(card, 'play');
        };

        /**
         * Zeigt ein temporäres Icon über der Karte (Play).
         */
        const showActionIcon = (card, type) => {
            const playIcon = card.querySelector('.action-icon-play');
           
            // Setze alle zurück
            playIcon.classList.remove('show-play');

            // Zeige das korrekte Icon
            if (type === 'play') {
                playIcon.classList.add('show-play');
            }

            // Verstecke das Icon nach einer kurzen Zeit
            setTimeout(() => {
                playIcon.classList.remove('show-play');
            }, 800);
        };

        // --- LOKALISIERUNGS- UND UI-LOGIK ---

        /**
         * Ruft den lokalisierten Namen aus den Species-Daten ab.
         */
        const getLocalizedName = (speciesData, langCode) => {
            if (!speciesData || !speciesData.names) return speciesData?.name || 'Unknown';
           
            const apiLangCode = POKEAPI_LANG_MAP[langCode];
           
            const nameEntry = speciesData.names.find(
                name => name.language.name === apiLangCode
            );

            if (nameEntry) {
                return nameEntry.name;
            } else {
                const englishNameEntry = speciesData.names.find(name => name.language.name === 'en');
                return englishNameEntry ? englishNameEntry.name : speciesData.name;
            }
        };

        /**
         * Aktualisiert die Anzeige aller Pokémon-Namen, wenn die Sprache gewechselt wird.
         */
        const updateAllPokemonNames = () => {
             allPokemonData.forEach(pokemon => {
                const card = document.getElementById(`card-${pokemon.id}`);
                if (card) {
                    const displayLocalizedName = pokemon.localizedNames[currentLanguage] || pokemon.englishName;
                    const paddedId = String(pokemon.id).padStart(3, '0');
                   
                    const nameElement = card.querySelector('h3');
                    if (nameElement) {
                        nameElement.innerHTML = `${displayLocalizedName}
                            <span class="text-gray-500 text-sm font-normal">#${paddedId}</span>`;
                    }
                }
             });
             // Aktualisiere das Modal, falls es sichtbar ist
             if (!DOWNLOAD_MODAL.classList.contains('hidden') && pokemonToDownload) {
                 const t = translations[currentLanguage];
                 const localizedName = pokemonToDownload.localizedNames[currentLanguage];
                 document.getElementById('modalTitle').textContent = t.modalTitle;
                 document.getElementById('modalMessage').innerHTML = t.modalMessage(localizedName, pokemonToDownload.id);
                 document.getElementById('modalCancelButton').textContent = t.modalCancel;
                 document.getElementById('modalConfirmButton').textContent = t.modalConfirm;
             }
        };

        /**
         * Ändert die Sprache der gesamten Benutzeroberfläche.
         */
        window.changeLanguage = (langCode) => {
            if (!translations[langCode]) return;
            currentLanguage = langCode;
            const t = translations[langCode];

            // 1. UI-Texte aktualisieren
            document.getElementById('pageTitle').textContent = t.title;
            document.getElementById('headerSubtitle').textContent = t.subtitle;
            document.getElementById('headerDescription').textContent = t.description;
            document.getElementById('searchInput').setAttribute('placeholder', t.searchPlaceholder);
            LOAD_MORE_BUTTON.innerHTML = `<i data-lucide="loader" class="w-4 h-4 mr-2 inline-block"></i> ${t.loadMore(LOAD_INCREMENT)}`;
           
            // 2. Namen der Pokémon und Modal-Texte aktualisieren
            updateAllPokemonNames();

            // 3. Statusmeldung aktualisieren
            if (isLoading) {
                 showStatus(t.loadingBlock, true);
            } else if (POKEMON_LIST_ELEMENT.children.length === 0 && document.getElementById('searchInput').value.trim() === '') {
                 showStatus(t.loading, true);
            } else if (POKEMON_LIST_ELEMENT.children.length === 0 && document.getElementById('searchInput').value.trim() !== '') {
                 showStatus(t.notFound(document.getElementById('searchInput').value.trim()));
            } else {
                 STATUS_MESSAGE.style.display = 'none';
            }
        };

        /**
         * Erstellt das HTML-Element für eine Pokémon-Karte.
         */
        const createPokemonCard = (pokemon) => {
            const paddedId = String(pokemon.id).padStart(3, '0');
            const displayLocalizedName = pokemon.localizedNames[currentLanguage] || pokemon.englishName;
           
            return `
                <div
                    id="card-${pokemon.id}"
                    class="pokemon-card bg-white rounded-xl shadow-lg p-4 text-center border-4 border-transparent"
                    data-name="${pokemon.englishName.toLowerCase()}"
                    data-id="${pokemon.id}"
                   
                    onmousedown="startPress('${pokemon.englishName}', '${pokemon.id}', event)"
                    onmouseup="endPress('${pokemon.englishName}', '${pokemon.id}', event)"
                    ontouchstart="startPress('${pokemon.englishName}', '${pokemon.id}', event)"
                    ontouchend="endPress('${pokemon.englishName}', '${pokemon.id}', event)"
                    ontouchmove="cancelPress(event)"
                >
                    <div class="cry-button flex justify-center items-center mb-2 relative">
                        <!-- Icon für visuelles Feedback beim Abspielen (overlay) -->
                        <i data-lucide="volume-2" class="action-icon action-icon-play w-16 h-16 text-red-500"></i>
                       
                        <img
                            src="/${pokemon.sprites.front_default}"
                            alt="${pokemon.englishName}"
                            onerror="this.onerror=null;this.src='https://placehold.co/100x100/CCCCCC/333333?text=${paddedId}'"
                            class="w-24 h-24 object-contain transition duration-300"
                        >
                    </div>
                   
                    <h3 class="text-lg font-extrabold text-gray-800">${displayLocalizedName}
                        <span class="text-gray-500 text-sm font-normal">#${paddedId}</span>
                    </h3>
                </div>
            `;
        };

        /**
         * Blendet die Statusmeldung ein/aus.
         */
        const showStatus = (message, showLoader = false) => {
            STATUS_MESSAGE.innerHTML = showLoader
                ? `<i data-lucide="loader" class="w-6 h-6 animate-spin text-red-500 mx-auto mb-2"></i> ${message}`
                : message;
            STATUS_MESSAGE.style.display = 'block';
             if (typeof createIcons !== 'undefined') { createIcons(); }
        };

        /**
         * Ruft ein einzelnes Pokémon (Daten und Species) ab und gibt das kombinierte Objekt zurück.
         */
        const fetchSinglePokemon = async (idOrName) => {
             try {
                // 1. Normale Pokémon-Daten abrufen
                const pokemonResponse = await fetch(`${POKEAPI_BASE_URL}${idOrName.toLowerCase()}`);
                if (!pokemonResponse.ok) return null;
                const pokemonData = await pokemonResponse.json();
               
                // 2. Species-Daten abrufen (für lokalisierte Namen)
                const speciesResponse = await fetch(`${POKEAPI_SPECIES_URL}${pokemonData.id}`);
                if (!speciesResponse.ok) return null;
                const speciesData = await speciesResponse.json();

                // 3. Lokalisierte Namen extrahieren
                const localizedNames = {};
                for (const langCode in POKEAPI_LANG_MAP) {
                    localizedNames[langCode] = getLocalizedName(speciesData, langCode);
                }
               
                // 4. Kombiniertes Objekt zurückgeben
                return {
                    ...pokemonData,
                    englishName: pokemonData.name, // Speichere den Originalnamen für Cry-Wiedergabe
                    localizedNames: localizedNames
                };

            } catch (error) {
                console.error("Fehler beim Abrufen des Pokémons:", error);
                return null;
            }
        };

        /**
         * Filtert die geladenen Pokémon oder sucht ein einzelnes Pokémon von der API.
         */
        window.filterAndFetchPokemons = async () => {
            const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim();
            const t = translations[currentLanguage];
           
            // 1. Wenn die Suche leer ist, zeige alle geladenen Karten und den Button an
            if (searchTerm === '') {
                POKEMON_LIST_ELEMENT.innerHTML = '';
                allPokemonData.forEach(pokemon => {
                    POKEMON_LIST_ELEMENT.insertAdjacentHTML('beforeend', createPokemonCard(pokemon));
                });
                updateAllPokemonNames();
                STATUS_MESSAGE.style.display = 'none';
                LOAD_MORE_BUTTON.style.display = 'block';
                return;
            }
           
            // 2. Suche lokal nach ID (einfachste und schnellste Methode)
            const foundById = allPokemonData.get(parseInt(searchTerm));

            if (foundById) {
                 // Nur das gefundene Pokémon anzeigen
                POKEMON_LIST_ELEMENT.innerHTML = createPokemonCard(foundById);
                updateAllPokemonNames();
                STATUS_MESSAGE.style.display = 'none';
                LOAD_MORE_BUTTON.style.display = 'none';
                return;
            }

            // 3. Wenn lokal nichts gefunden, versuche API-Fetch (funktioniert am besten mit EN Namen oder ID)
            if (searchTerm.length >= 2 && !isLoading) {
                showStatus(`${t.loadingBlock.replace('Block', 'Pokémon')} "${searchTerm}"...`, true);
               
                const singlePokemon = await fetchSinglePokemon(searchTerm);

                if (singlePokemon) {
                    POKEMON_LIST_ELEMENT.innerHTML = createPokemonCard(singlePokemon);
                    updateAllPokemonNames();
                    STATUS_MESSAGE.style.display = 'none';
                } else {
                    showStatus(t.notFound(searchTerm));
                    POKEMON_LIST_ELEMENT.innerHTML = ''; // Leere die Liste bei Nicht-Fund
                }
            } else {
                showStatus(t.notFound(searchTerm));
                POKEMON_LIST_ELEMENT.innerHTML = '';
            }
            LOAD_MORE_BUTTON.style.display = 'none';
        };


        /**
         * Lädt eine Reihe von Pokémon von der API.
         */
        const fetchPokemonBatch = async (offset, count) => {
            if (isLoading) return;
            isLoading = true;
            const t = translations[currentLanguage];
           
            showStatus(t.loadingBlock, true);
            LOAD_MORE_BUTTON.disabled = true;

            try {
                // Schritt 1: Hole die Liste der Pokémon-Namen/URLs
                const listResponse = await fetch(`${POKEAPI_BASE_URL}?offset=${offset}&limit=${count}`);
                if (!listResponse.ok) throw new Error(t.fetchError);
               
                const listData = await listResponse.json();
               
                // Setze den "Mehr laden" Text auf die korrekte nächste Zahl
                LOAD_MORE_BUTTON.innerHTML = `<i data-lucide="loader" class="w-4 h-4 mr-2 inline-block"></i> ${t.loadMore(LOAD_INCREMENT)}`;

                // Schritt 2: Hole die Details (inkl. Species für Namen) für jedes Pokémon
                const detailPromises = listData.results.map(p => fetchSinglePokemon(p.name));
                const detailedPokemons = await Promise.all(detailPromises);

                // Schritt 3: Aktualisiere den globalen Cache und die UI
                let newCardsHtml = '';
                detailedPokemons.forEach(p => {
                    if (p && !allPokemonData.has(p.id)) {
                        allPokemonData.set(p.id, p);
                        newCardsHtml += createPokemonCard(p);
                    }
                });
               
                POKEMON_LIST_ELEMENT.insertAdjacentHTML('beforeend', newCardsHtml);
                updateAllPokemonNames();
               
                currentOffset += detailedPokemons.length;

                // Verstecke die Statusmeldung und zeige den Button an, wenn die Liste nicht leer ist
                if (POKEMON_LIST_ELEMENT.children.length > 0) {
                     STATUS_MESSAGE.style.display = 'none';
                     LOAD_MORE_BUTTON.style.display = 'block';
                }

            } catch (error) {
                console.error("Gesamtfehler beim Laden der Pokémon:", error);
                showStatus(t.fetchError);
                LOAD_MORE_BUTTON.style.display = 'none';
            } finally {
                isLoading = false;
                LOAD_MORE_BUTTON.disabled = false;
                 // Re-initialisiere Lucide Icons nach dem Einfügen neuer Karten
                if (typeof createIcons !== 'undefined') { createIcons(); }
            }
        };

        /**
         * Lädt die nächste Staffel von Pokémon (für den "Mehr laden"-Button).
         */
        window.loadMorePokemons = () => {
             fetchPokemonBatch(currentOffset, LOAD_INCREMENT);
        };

        /**
         * Initialisiert die Anwendung.
         */
        window.onload = () => {
            // Setze die Standard-Sprache und lade den ersten Block
            const defaultLang = document.getElementById('languageSelector').value;
            changeLanguage(defaultLang);
            fetchPokemonBatch(currentOffset, INITIAL_LIMIT);
        };
       
    </script>
</body>
</html>

TEST

Details
Geschrieben von: admin
Kategorie: Uncategorised
Veröffentlicht: 03. Dezember 2025
Zugriffe: 3
 
Pokéball

Pokémon

Ruf-Galerie

Finde und höre jedes Pokémon.

Lade Pokémon...

Download bestätigen

Möchtest du den Ruf von Glurak (#006) wirklich als MP3-Datei herunterladen?

Main Menu

  • Home
  • Pokeruf

Login Form

  • Passwort vergessen?
  • Benutzername vergessen?