Beispiel für Oberfläche einer Vietnamesisch‑Lern‑App

Als Beispiel nehmen wir eine einfache Vietnamesisch‑Lern‑App. Die Oberfläche zeigt vietnamesische Konsonanten an, unten befindet sich ein Menü.

Wir verwenden Tailwind als Web‑Vorlage.

Mehr zu Tailwind erfahren Sie in unseren anderen Kursen.

Passen Sie den Inhalt von index.html wie folgt an

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Bảng phụ âm tiếng Việt</title>
    <!-- Eingebettetes Tailwind‑CDN: Für Offline‑Betrieb nach dem Build durch lokale Dateien ersetzen -->
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#2563eb',
                        bottomNav: '#ffffff',
                        cardBg: '#f8fafc'
                    }
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .safe-bottom {
                padding-bottom: calc(4rem + env(safe-area-inset-bottom));
            }
            .card-shadow {
                box-shadow: 0 2px 8px rgba(0,0,0,0.08);
            }
        }
        body {
            /* Verhindert das Rückprall‑Scroll‑Verhalten bei Cordova */
            overscroll-behavior: none;
        }
        /* Stil für aktiven Tab in der unteren Navigation */
        .tab-active {
            color: #2563eb;
        }
    </style>
</head>
<body class="bg-gray-100 font-sans text-gray-800">

    <!-- Seiten‑Container -->
    <div id="app" class="max-w-md mx-auto bg-white min-h-screen relative safe-bottom">
        <!-- Kopfbereich mit Titel -->
        <header class="sticky top-0 bg-primary text-white px-4 py-3 shadow z-20">
            <h1 class="text-lg font-bold text-center">Phụ Âm Tiếng Việt</h1>
        </header>

        <!-- Hauptinhalt -->
        <main class="p-4">
            <!-- Seite 1: Konsonanten -->
            <div id="page-consonant" class="page">
                <h2 class="text-base font-medium mb-3 text-gray-600">Tippen Sie auf eine Karte für Details</h2>
                <div id="consonant-grid" class="grid grid-cols-3 gap-3">
                    <!-- JS rendert die Konsonanten‑Karten -->
                </div>
            </div>

            <!-- Seite 2: Favoriten -->
            <div id="page-favorite" class="page hidden mt-4">
                <h2 class="text-lg font-bold mb-4">Favoritenliste</h2>
                <div id="favorite-grid" class="grid grid-cols-3 gap-3">
                    <p class="col-span-3 text-center text-gray-400 py-10">Keine Favoriten vorhanden</p>
                </div>
            </div>

            <!-- Seite 3: Einstellungen -->
            <div id="page-setting" class="page hidden mt-4">
                <h2 class="text-lg font-bold mb-4">Einstellungen</h2>
                <div class="space-y-3">
                    <div class="bg-cardBg p-4 rounded-lg card-shadow">
                        <p>Vietnamesische Konsonanten‑Lern‑App</p>
                        <p class="text-sm text-gray-500 mt-1">Cordova + TailwindCSS</p>
                    </div>
                </div>
            </div>
        </main>

        <!-- ========== Untere Navigationsleiste ========== -->
        <nav class="fixed bottom-0 left-1/2 -translate-x-1/2 w-full max-w-md bg-bottomNav border-t border-gray-200 flex z-30" style="padding-bottom: env(safe-area-inset-bottom);">
            <button class="tab-btn flex-1 py-3 flex flex-col items-center tab-active" data-target="page-consonant">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <rect x="3" y="3" width="18" height="18" rx="2"/>
                    <path d="M3 9h18M9 21V9"/>
                </svg>
                <span class="text-xs mt-1">Konsonanten</span>
            </button>
            <button class="tab-btn flex-1 py-3 flex flex-col items-center text-gray-500" data-target="page-favorite">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
                </svg>
                <span class="text-xs mt-1">Favoriten</span>
            </button>
            <button class="tab-btn flex-1 py-3 flex flex-col items-center text-gray-500" data-target="page-setting">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <circle cx="12" cy="12" r="3"/>
                    <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
                </svg>
                <span class="text-xs mt-1">Einstellungen</span>
            </button>
        </nav>
    </div>

    <!-- ========== Modal‑Fenster für Konsonanten‑Details ========== -->
    <div id="modal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center p-4">
        <div class="bg-white rounded-xl w-full max-w-sm p-5 relative">
            <button id="close-modal" class="absolute top-3 right-3 text-gray-400 text-xl">&times;</button>
            <h3 id="modal-char" class="text-4xl font-bold text-primary text-center py-4"></h3>
            <div class="space-y-2 text-gray-700">
                <p><span class="font-semibold">Aussprache:</span> <span id="modal-pronounce"></span></p>
                <p><span class="font-semibold">Erklärung:</span> <span id="modal-desc"></span></p>
            </div>
            <button id="btn-fav" class="w-full mt-5 bg-primary text-white py-2 rounded-lg">Zu Favoriten hinzufügen</button>
        </div>
    </div>

    <!-- Cordova‑JavaScript (läuft nur auf echtem Gerät) -->
    <script src="cordova.js"></script>
    <script src="js/main.js"></script>
</body>
</html>Code-Sprache: HTML, XML (xml)

Erstellen Sie die Datei main.js unter www/js

Mit folgendem Inhalt

// Daten der vietnamesischen Konsonanten
const consonants = [
    {char: "b", pronounce: "/ɓ/", desc: "Implosiver bilabialer Konsonant"},
    {char: "c", pronounce: "/k/", desc: "Velarer Verschlusslaut"},
    {char: "ch", pronounce: "/tɕ/", desc: "Palataler Affrikat"},
    {char: "d", pronounce: "/ɗ/", desc: "Implosiver alveolarer Konsonant"},
    {char: "đ", pronounce: "/ɗ/", desc: "Präglottalisierter Alveolarlaut"},
    {char: "g", pronounce: "/ɣ/", desc: "Velarer Reibelaut"},
    {char: "gh", pronounce: "/ɣ/", desc: "Verwendet vor e, i"},
    {char: "h", pronounce: "/h/", desc: "Glottaler Reibelaut"},
    {char: "k", pronounce: "/k/", desc: "Velarer Verschlusslaut"},
    {char: "kh", pronounce: "/x/", desc: "Stimmerloser velarer Reibelaut"},
    {char: "l", pronounce: "/l/", desc: "Alveolarer Laterallaut"},
    {char: "m", pronounce: "/m/", desc: "Bilabialer Nasallaut"},
    {char: "n", pronounce: "/n/", desc: "Alveolarer Nasallaut"},
    {char: "ng", pronounce: "/ŋ/", desc: "Velarer Nasallaut"},
    {char: "ngh", pronounce: "/ŋ/", desc: "Verwendet vor e,i"},
    {char: "nh", pronounce: "/ɲ/", desc: "Palataler Nasallaut"},
    {char: "p", pronounce: "/p/", desc: "Nicht‑implosiver bilabialer Verschlusslaut"},
    {char: "ph", pronounce: "/f/", desc: "Labiodentaler Reibelaut"},
    {char: "q", pronounce: "/kw/", desc: "Steht immer mit u zusammen"},
    {char: "r", pronounce: "/r/", desc: "Alveolarer Vibrationslaut"},
    {char: "s", pronounce: "/s/", desc: "Stimmerloser alveolarer Reibelaut"},
    {char: "t", pronounce: "/t/", desc: "Alveolarer Verschlusslaut"},
    {char: "th", pronounce: "/θ/", desc: "Dentaler stimmloser Reibelaut"},
    {char: "v", pronounce: "/v/", desc: "Stimmhafter labiodentaler Reibelaut"},
    {char: "x", pronounce: "/s/", desc: "Stimmerloser alveolarer Reibelaut"}
];

// Favoritenliste speichern in localStorage
let favList = JSON.parse(localStorage.getItem("vn_consonant_fav")) || [];

// DOM‑Elemente
const gridEl = document.getElementById("consonant-grid");
const favGridEl = document.getElementById("favorite-grid");
const modal = document.getElementById("modal");
const modalChar = document.getElementById("modal-char");
const modalPro = document.getElementById("modal-pronounce");
const modalDesc = document.getElementById("modal-desc");
const btnFav = document.getElementById("btn-fav");
const closeModal = document.getElementById("close-modal");

let currentItem = null;

// Konsonanten‑Karten rendern
function renderCards(){
    gridEl.innerHTML = "";
    consonants.forEach(item=>{
        const div = document.createElement("div");
        div.className = "bg-cardBg rounded-lg py-5 text-center card-shadow active:scale-95 transition-transform";
        div.innerHTML = `<span class="text-2xl font-bold text-gray-800">${item.char}</span>`;
        div.onclick = ()=> openModal(item);
        gridEl.appendChild(div);
    })
}

// Favoriten‑Seite rendern
function renderFav(){
    favGridEl.innerHTML = "";
    if(favList.length === 0){
        favGridEl.innerHTML = `<p class="col-span-3 text-center text-gray-400 py-10">Keine Favoriten vorhanden</p>`;
        return;
    }
    favList.forEach(item=>{
        const div = document.createElement("div");
        div.className = "bg-cardBg rounded-lg py-5 text-center card-shadow active:scale-95 transition-transform";
        div.innerHTML = `<span class="text-2xl font-bold text-gray-800">${item.char}</span>`;
        div.onclick = ()=> openModal(item);
        favGridEl.appendChild(div);
    })
}

// Detail‑Popup öffnen
function openModal(item){
    currentItem = item;
    modalChar.innerText = item.char;
    modalPro.innerText = item.pronounce;
    modalDesc.innerText = item.desc;
    updateFavButton();
    modal.classList.remove("hidden");
}

// Popup schließen
closeModal.onclick = ()=>{
    modal.classList.add("hidden");
}
modal.onclick = (e)=>{
    if(e.target === modal) modal.classList.add("hidden");
}

// Favoriten‑Button aktualisieren
function updateFavButton(){
    const exist = favList.find(x=>x.char === currentItem.char);
    if(exist){
        btnFav.innerText = "Aus Favoriten entfernen";
    }else{
        btnFav.innerText = "Zu Favoriten hinzufügen";
    }
}
btnFav.onclick = ()=>{
    const idx = favList.findIndex(x=>x.char === currentItem.char);
    if(idx >=0){
        favList.splice(idx,1);
    }else{
        favList.push(currentItem);
    }
    localStorage.setItem("vn_consonant_fav", JSON.stringify(favList));
    updateFavButton();
    renderFav();
}

// ========== Tab‑Seitenwechsel ==========
const tabBtns = document.querySelectorAll(".tab-btn");
const pages = document.querySelectorAll(".page");
tabBtns.forEach(btn=>{
    btn.onclick = ()=>{
        const targetId = btn.dataset.target;
        // Aktiven Status aller Tabs entfernen
        tabBtns.forEach(b=>{
            b.classList.remove("tab-active");
            b.querySelector("svg").classList.add("text-gray-500");
        })
        btn.classList.add("tab-active");
        btn.querySelector("svg").classList.remove("text-gray-500");

        // Alle Seiten verstecken
        pages.forEach(p=>p.classList.add("hidden"));
        document.getElementById(targetId).classList.remove("hidden");
    }
})

// App‑Start
document.addEventListener("DOMContentLoaded", ()=>{
    renderCards();
    renderFav();
})

// Cordova‑Bereitschaft (auf Mobilgerät)
document.addEventListener("deviceready", ()=>{
    console.log("Cordova ist bereit");
}, false);Code-Sprache: JavaScript (javascript)

Neu kompilieren und ausführen

D:\cordovademo\hello>cordova run android

In der App sehen Sie folgenden Inhalt

Die Karten lassen sich anklicken, über das untere Menü wechseln Sie zwischen Seiten.

Als Nächstes implementieren wir die Audio‑Wiedergabe bei Klick auf eine Karte. Sehen Sie sich die nächste Seite an.

Beispiel für Oberfläche einer Vietnamesisch‑Lern‑App

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert