Vamos usar um aplicativo simples de aprendizado de vietnamita como exemplo. A interface exibe as consoantes do idioma, e na parte inferior ficam os menus.
Utilizamos o Tailwind como modelo de página web.
Para saber mais sobre o Tailwind, consulte outros materiais de estudo.
Altere o conteúdo do arquivo index.html conforme abaixo
<!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>Tabela de Consoantes Vietnamitas</title>
<!-- CDN incorporado do Tailwind. Para funcionar offline, substitua por arquivos locais após empacotar -->
<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 o efeito de rebote de rolagem no Cordova */
overscroll-behavior: none;
}
/* Estilo da aba inferior ativa */
.tab-active {
color: #2563eb;
}
</style>
</head>
<body class="bg-gray-100 font-sans text-gray-800">
<!-- Container da página -->
<div id="app" class="max-w-md mx-auto bg-white min-h-screen relative safe-bottom">
<!-- Cabeçalho superior com título -->
<header class="sticky top-0 bg-primary text-white px-4 py-3 shadow z-20">
<h1 class="text-lg font-bold text-center">Consoantes do Vietnamita</h1>
</header>
<!-- Área principal de conteúdo -->
<main class="p-4">
<!-- Página 1: Consoantes -->
<div id="page-consonant" class="page">
<h2 class="text-base font-medium mb-3 text-gray-600">Clique no cartão para ver detalhes</h2>
<div id="consonant-grid" class="grid grid-cols-3 gap-3">
<!-- Cartões de consoantes renderizados via JavaScript -->
</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">Nenhum item adicionado aos favoritos</p>
</div>
</div>
<!-- Página 3: Configurações -->
<div id="page-setting" class="page hidden mt-4">
<h2 class="text-lg font-bold mb-4">Configurações</h2>
<div class="space-y-3">
<div class="bg-cardBg p-4 rounded-lg card-shadow">
<p>Aplicativo de estudo de consoantes vietnamitas</p>
<p class="text-sm text-gray-500 mt-1">Cordova + TailwindCSS</p>
</div>
</div>
</div>
</main>
<!-- ========== Barra de navegação 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">Consoantes</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">Configurações</span>
</button>
</nav>
</div>
<!-- ========== Modal que exibe detalhes da consoante ========== -->
<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">×</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">Pronúncia:</span> <span id="modal-pronounce"></span></p>
<p><span class="font-semibold">Explicação:</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">Adicionar aos favoritos</button>
</div>
</div>
<!-- Arquivo js do Cordova (funciona apenas em dispositivo físico) -->
<script src="cordova.js"></script>
<script src="js/main.js"></script>
</body>
</html>Code language: HTML, XML (xml)
Crie o arquivo main.js no caminho www/js/main.js
O conteúdo segue abaixo
// Dados das consoantes do vietnamita
const consonants = [
{char: "b", pronounce: "/ɓ/", desc: "Consoante implosiva bilabial"},
{char: "c", pronounce: "/k/", desc: "Consoante oclusiva palatal"},
{char: "ch", pronounce: "/tɕ/", desc: "Consoante africada palatal"},
{char: "d", pronounce: "/ɗ/", desc: "Consoante implosiva alveolar"},
{char: "đ", pronounce: "/ɗ/", desc: "Consoante implosiva alveolar"},
{char: "g", pronounce: "/ɣ/", desc: "Consoante fricativa velar"},
{char: "gh", pronounce: "/ɣ/", desc: "Usado antes de e, i"},
{char: "h", pronounce: "/h/", desc: "Consoante fricativa glotal"},
{char: "k", pronounce: "/k/", desc: "Consoante oclusiva velar surda"},
{char: "kh", pronounce: "/x/", desc: "Consoante fricativa velar surda"},
{char: "l", pronounce: "/l/", desc: "Consoante lateral alveolar"},
{char: "m", pronounce: "/m/", desc: "Consoante nasal bilabial"},
{char: "n", pronounce: "/n/", desc: "Consoante nasal alveolar"},
{char: "ng", pronounce: "/ŋ/", desc: "Consoante nasal velar"},
{char: "ngh", pronounce: "/ŋ/", desc: "Usado antes de e,i"},
{char: "nh", pronounce: "/ɲ/", desc: "Consoante nasal palatal"},
{char: "p", pronounce: "/p/", desc: "Consoante oclusiva bilabial não aspirada"},
{char: "ph", pronounce: "/f/", desc: "Consoante fricativa labiodental"},
{char: "q", pronounce: "/kw/", desc: "Sempre acompanhado da letra u"},
{char: "r", pronounce: "/r/", desc: "Consoante vibrante alveolar"},
{char: "s", pronounce: "/s/", desc: "Consoante fricativa alveolar surda"},
{char: "t", pronounce: "/t/", desc: "Consoante oclusiva alveolar surda"},
{char: "th", pronounce: "/θ/", desc: "Consoante fricativa dental surda"},
{char: "v", pronounce: "/v/", desc: "Consoante fricativa labiodental sonora"},
{char: "x", pronounce: "/s/", desc: "Consoante fricativa alveolar surda"}
];
// Salva lista de favoritos no 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 os cartões das consoantes
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 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">Nenhum item adicionado aos favoritos</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 popup com detalhes
function openModal(item){
currentItem = item;
modalChar.innerText = item.char;
modalPro.innerText = item.pronounce;
modalDesc.innerText = item.desc;
updateFavButton();
modal.classList.remove("hidden");
}
// Fecha popup
closeModal.onclick = ()=>{
modal.classList.add("hidden");
}
modal.onclick = (e)=>{
if(e.target === modal) modal.classList.add("hidden");
}
// Botão adicionar / remover dos favoritos
function updateFavButton(){
const exist = favList.find(x=>x.char === currentItem.char);
if(exist){
btnFav.innerText = "Remover dos favoritos";
}else{
btnFav.innerText = "Adicionar aos 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();
}
// ========== Troca de abas ==========
const tabBtns = document.querySelectorAll(".tab-btn");
const pages = document.querySelectorAll(".page");
tabBtns.forEach(btn=>{
btn.onclick = ()=>{
const targetId = btn.dataset.target;
// Remove estado ativo de todas as abas
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 as páginas
pages.forEach(p=>p.classList.add("hidden"));
document.getElementById(targetId).classList.remove("hidden");
}
})
// Executa na inicialização da página
document.addEventListener("DOMContentLoaded", ()=>{
renderCards();
renderFav();
})
// Evento deviceready do Cordova (para dispositivos móveis)
document.addEventListener("deviceready", ()=>{
console.log("Cordova pronto");
}, false);Code language: JavaScript (javascript)
Recompile e execute o projeto
D:\cordovademo\hello>cordova run android
Você verá o seguinte conteúdo dentro do aplicativo

Os cartões são clicáveis e o menu inferior faz a troca de telas.
Agora vamos implementar a reprodução de um pequeno áudio ao clicar em um cartão. Veja a próxima imagem
Exemplo de interface de aplicativo para aprender vietnamita