Ejemplo de interfaz de app para aprender vietnamita

Tomemos como ejemplo una sencilla aplicación para aprender vietnamita, su interfaz muestra las consonantes del idioma, y en la parte inferior se encuentran los menús.

Usamos Tailwind como plantilla web.

Para aprender más sobre Tailwind, puedes consultar otros tutoriales.

Modifica el contenido de index.html de la siguiente manera

<!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>Tabla de consonantes del vietnamita</title>
    <!-- CDN embebido de Tailwind, para uso sin conexión reemplázalo por recursos locales después de empaquetar -->
    <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 {
            /* Evita el rebote de desplazamiento en Cordova */
            overscroll-behavior: none;
        }
        /* Estilo de pestaña inferior activa */
        .tab-active {
            color: #2563eb;
        }
    </style>
</head>
<body class="bg-gray-100 font-sans text-gray-800">

    <!-- Contenedor de la página -->
    <div id="app" class="max-w-md mx-auto bg-white min-h-screen relative safe-bottom">
        <!-- Barra de título superior -->
        <header class="sticky top-0 bg-primary text-white px-4 py-3 shadow z-20">
            <h1 class="text-lg font-bold text-center">Consonantes del vietnamita</h1>
        </header>

        <!-- Zona de contenido principal -->
        <main class="p-4">
            <!-- Página 1: Consonantes -->
            <div id="page-consonant" class="page">
                <h2 class="text-base font-medium mb-3 text-gray-600">Pulsa la tarjeta para ver los detalles</h2>
                <div id="consonant-grid" class="grid grid-cols-3 gap-3">
                    <!-- Tarjetas de consonantes renderizadas por JS -->
                </div>
            </div>

            <!-- Página 2: Favoritos -->
            <div id="page-favorite" class="page hidden mt-4">
                <h2 class="text-lg font-bold mb-4">Lista de favoritos</h2>
                <div id="favorite-grid" class="grid grid-cols-3 gap-3">
                    <p class="col-span-3 text-center text-gray-400 py-10">No hay elementos favoritos todavía</p>
                </div>
            </div>

            <!-- Página 3: Ajustes -->
            <div id="page-setting" class="page hidden mt-4">
                <h2 class="text-lg font-bold mb-4">Ajustes</h2>
                <div class="space-y-3">
                    <div class="bg-cardBg p-4 rounded-lg card-shadow">
                        <p>Aplicación para aprender consonantes vietnamitas</p>
                        <p class="text-sm text-gray-500 mt-1">Cordova + TailwindCSS</p>
                    </div>
                </div>
            </div>
        </main>

        <!-- ========== Barra de navegación inferior ========== -->
        <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">Consonantes</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">Favoritos</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">Ajustes</span>
            </button>
        </nav>
    </div>

    <!-- ========== Modal para mostrar detalles de consonante ========== -->
    <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">Pronunciación:</span> <span id="modal-pronounce"></span></p>
                <p><span class="font-semibold">Explicación:</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">Agregar a favoritos</button>
        </div>
    </div>

    <!-- Javascript de Cordova (solo funciona en dispositivo real) -->
    <script src="cordova.js"></script>
    <script src="js/main.js"></script>
</body>
</html>Lenguaje del código: HTML, XML (xml)

Crea el archivo main.js dentro de www/js/main.js

Su contenido es el siguiente

// Datos de las consonantes del vietnamita
const consonants = [
    {char: "b", pronounce: "/ɓ/", desc: "Consonante implosiva bilabial"},
    {char: "c", pronounce: "/k/", desc: "Consonante oclusiva palatal"},
    {char: "ch", pronounce: "/tɕ/", desc: "Consonante africada palatal"},
    {char: "d", pronounce: "/ɗ/", desc: "Consonante implosiva alveolar"},
    {char: "đ", pronounce: "/ɗ/", desc: "Consonante implosiva alveolar"},
    {char: "g", pronounce: "/ɣ/", desc: "Consonante fricativa velar"},
    {char: "gh", pronounce: "/ɣ/", desc: "Se usa ante e, i"},
    {char: "h", pronounce: "/h/", desc: "Consonante fricativa glotal"},
    {char: "k", pronounce: "/k/", desc: "Consonante oclusiva velar sorda"},
    {char: "kh", pronounce: "/x/", desc: "Consonante fricativa velar sorda"},
    {char: "l", pronounce: "/l/", desc: "Consonante lateral alveolar"},
    {char: "m", pronounce: "/m/", desc: "Consonante nasal bilabial"},
    {char: "n", pronounce: "/n/", desc: "Consonante nasal alveolar"},
    {char: "ng", pronounce: "/ŋ/", desc: "Consonante nasal velar"},
    {char: "ngh", pronounce: "/ŋ/", desc: "Se usa ante e,i"},
    {char: "nh", pronounce: "/ɲ/", desc: "Consonante nasal palatal"},
    {char: "p", pronounce: "/p/", desc: "Consonante oclusiva bilabial no aspirada"},
    {char: "ph", pronounce: "/f/", desc: "Consonante fricativa labiodental"},
    {char: "q", pronounce: "/kw/", desc: "Siempre acompaña a u"},
    {char: "r", pronounce: "/r/", desc: "Consonante vibrante alveolar"},
    {char: "s", pronounce: "/s/", desc: "Consonante fricativa alveolar sorda"},
    {char: "t", pronounce: "/t/", desc: "Consonante oclusiva alveolar sorda"},
    {char: "th", pronounce: "/θ/", desc: "Consonante fricativa dental sorda"},
    {char: "v", pronounce: "/v/", desc: "Consonante fricativa labiodental sonora"},
    {char: "x", pronounce: "/s/", desc: "Consonante fricativa alveolar sorda"}
];

// Guarda la lista de favoritos en localStorage
let favList = JSON.parse(localStorage.getItem("vn_consonant_fav")) || [];

// Elementos DOM
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;

// Renderiza las tarjetas de consonantes
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);
    })
}

// Renderiza la página de favoritos
function renderFav(){
    favGridEl.innerHTML = "";
    if(favList.length === 0){
        favGridEl.innerHTML = `<p class="col-span-3 text-center text-gray-400 py-10">No hay elementos favoritos todavía</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);
    })
}

// Abre el popup con los detalles
function openModal(item){
    currentItem = item;
    modalChar.innerText = item.char;
    modalPro.innerText = item.pronounce;
    modalDesc.innerText = item.desc;
    updateFavButton();
    modal.classList.remove("hidden");
}

// Cierra el popup
closeModal.onclick = ()=>{
    modal.classList.add("hidden");
}
modal.onclick = (e)=>{
    if(e.target === modal) modal.classList.add("hidden");
}

// Botón agregar / quitar de favoritos
function updateFavButton(){
    const exist = favList.find(x=>x.char === currentItem.char);
    if(exist){
        btnFav.innerText = "Quitar de favoritos";
    }else{
        btnFav.innerText = "Agregar a favoritos";
    }
}
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();
}

// ========== Navegación por pestañas ==========
const tabBtns = document.querySelectorAll(".tab-btn");
const pages = document.querySelectorAll(".page");
tabBtns.forEach(btn=>{
    btn.onclick = ()=>{
        const targetId = btn.dataset.target;
        // Quita el estado activo de todas las pestañas
        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");

        // Oculta todas las páginas
        pages.forEach(p=>p.classList.add("hidden"));
        document.getElementById(targetId).classList.remove("hidden");
    }
})

// Inicialización
document.addEventListener("DOMContentLoaded", ()=>{
    renderCards();
    renderFav();
})

// Evento deviceready de Cordova (para ejecución en móvil)
document.addEventListener("deviceready", ()=>{
    console.log("Cordova listo");
}, false);Lenguaje del código: JavaScript (javascript)

Vuelve a compilar y ejecuta

D:\cordovademo\hello>cordova run android

Verás el siguiente contenido dentro de la aplicación

Las tarjetas son pulsables y el menú inferior permite cambiar de pestaña.

A continuación vamos a reproducir un pequeño audio al pulsar una tarjeta. Consulta la siguiente imagen

Ejemplo de interfaz de app para aprender vietnamita

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *