diff --git a/西班牙语日记-实现计划.md b/西班牙语日记-实现计划.md
new file mode 100644
index 0000000..0c3bad0
--- /dev/null
+++ b/西班牙语日记-实现计划.md
@@ -0,0 +1,821 @@
+# Spanish Diary (Phase 1) Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Build a pure-frontend Spanish learning diary app where users write daily entries in Spanish, see English/Chinese translations auto-displayed, hear pronunciation via Web Speech API, and manage entries via IndexedDB.
+
+**Architecture:** Single-page application with modular JavaScript files. Three-panel layout (entry list | Spanish editor | translation display). Each JS module encapsulates one concern (database, translation, speech). The HTML file loads all scripts and provides the DOM structure; CSS handles the three-column responsive layout.
+
+**Tech Stack:** HTML5, CSS3, Vanilla JavaScript (ES6+), IndexedDB (native API), Web Speech API.
+
+---
+
+## File Structure
+
+```
+spanish-diary/
+ index.html — Main HTML page with DOM skeleton
+ style.css — All styles, three-column layout, responsive
+ app.js — Application logic: state management, event wiring between panels
+ db.js — IndexedDB wrapper: open CRUD operations
+ translate.js — Translation module: mock dictionary lookup
+ speech.js — Speech module: Web Speech API wrapper
+```
+
+Each file has one clear responsibility. `app.js` is the coordinator that connects the other modules.
+
+---
+
+### Task 1: Create project directory and HTML skeleton
+
+**Files:**
+- Create: `spanish-diary/index.html`
+
+**Details:** The HTML must define the three-panel structure with IDs for JS targeting. No external dependencies. Load all JS files at the bottom.
+
+- [ ] **Step 1: Write the HTML file**
+
+```html
+
+
+
+
+
+ Mi Diario — 西班牙语日记
+
+
+
+
+
+
+
+
+
+ 输入西语后点击保存,右侧自动显示翻译
+
+
+
+
+
+
🇬🇧 English
+
-
+
🔊 朗读
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Verify: Open the file in browser — three empty panels should display (no styling yet).
+
+- [ ] **Step 2: Verify in browser**
+
+Open `file:///path/to/spanish-diary/index.html`. Should show unstyled three columns with text elements.
+
+---
+
+### Task 2: Write the CSS for the three-column layout
+
+**Files:**
+- Create: `spanish-diary/style.css`
+
+**Details:** Responsive three-column layout. Left panel 240px, center flexible, right panel 280px. Dark mode support via `prefers-color-scheme`. Clean, professional look with muted brand colors.
+
+- [ ] **Step 1: Write the CSS file**
+
+```css
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+:root {
+ --bg: #f8f9fa;
+ --panel-bg: #ffffff;
+ --border: #dee2e6;
+ --text: #212529;
+ --text-muted: #6c757d;
+ --accent: #0d6efd;
+ --accent-hover: #0b5ed7;
+ --danger: #dc3545;
+ --success: #198754;
+ --highlight: #fff3cd;
+ --shadow: 0 1px 3px rgba(0,0,0,0.08);
+ --radius: 8px;
+ --font-mono: 'Consolas', 'Courier New', monospace;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --bg: #1a1d23;
+ --panel-bg: #212529;
+ --border: #373b3e;
+ --text: #e9ecef;
+ --text-muted: #9ca3af;
+ --accent: #3b82f6;
+ --accent-hover: #60a5fa;
+ --danger: #ef4444;
+ --success: #22c55e;
+ --highlight: #3d3520;
+ }
+}
+
+html, body { height: 100%; }
+body {
+ font-family: system-ui, -apple-system, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ display: flex;
+ justify-content: center;
+ align-items: stretch;
+ padding: 1rem;
+}
+
+#app {
+ display: grid;
+ grid-template-columns: 240px 1fr 280px;
+ gap: 1rem;
+ width: 100%;
+ max-width: 1200px;
+ min-height: 0;
+}
+
+#app > * {
+ background: var(--panel-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid var(--border);
+ gap: 0.5rem;
+}
+
+.panel-header h2 { font-size: 0.95rem; font-weight: 600; }
+
+/* Left panel — entry list */
+#panel-list { overflow-y: auto; }
+#entry-list {
+ list-style: none;
+ flex: 1;
+ overflow-y: auto;
+}
+#entry-list li {
+ padding: 0.75rem 1rem;
+ cursor: pointer;
+ border-bottom: 1px solid var(--border);
+ transition: background 0.1s;
+}
+#entry-list li:hover { background: var(--highlight); }
+#entry-list li.active { background: var(--highlight); border-left: 3px solid var(--accent); }
+#entry-list .entry-date { font-size: 0.75rem; color: var(--text-muted); }
+#entry-list .entry-preview { font-size: 0.85rem; margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.entry-actions { display: none; gap: 0.3rem; margin-top: 0.3rem; }
+#entry-list li:hover .entry-actions { display: flex; }
+.entry-actions button {
+ background: none; border: 1px solid var(--border); border-radius: 4px;
+ padding: 0.15rem 0.4rem; font-size: 0.7rem; cursor: pointer; color: var(--text-muted);
+}
+.entry-actions .btn-delete { color: var(--danger); border-color: var(--danger); }
+
+#btn-new {
+ background: var(--accent); color: #fff; border: none;
+ width: 28px; height: 28px; border-radius: 6px;
+ font-size: 1.2rem; cursor: pointer; line-height: 1;
+}
+#btn-new:hover { background: var(--accent-hover); }
+
+/* Center panel — editor */
+#panel-editor { display: flex; flex-direction: column; }
+#entry-date {
+ border: 1px solid var(--border); border-radius: 4px;
+ padding: 0.3rem 0.5rem; font-size: 0.85rem;
+ background: var(--panel-bg); color: var(--text);
+}
+#btn-save {
+ background: var(--accent); color: #fff; border: none;
+ padding: 0.35rem 0.75rem; border-radius: 6px;
+ font-size: 0.85rem; cursor: pointer; white-space: nowrap;
+}
+#btn-save:hover { background: var(--accent-hover); }
+#entry-text {
+ flex: 1; resize: none; border: none; padding: 1rem;
+ font-size: 1.05rem; line-height: 1.7;
+ font-family: var(--font-mono);
+ background: transparent; color: var(--text);
+ outline: none;
+}
+#entry-hint {
+ padding: 0.5rem 1rem; font-size: 0.75rem; color: var(--text-muted);
+ border-top: 1px solid var(--border);
+}
+
+/* Right panel — translation */
+#panel-translate { overflow-y: auto; }
+.translate-section {
+ padding: 1rem;
+ border-bottom: 1px solid var(--border);
+}
+.translate-label { font-size: 0.8rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-muted); }
+.translate-text {
+ font-size: 0.95rem; line-height: 1.6;
+ min-height: 3rem; padding: 0.5rem;
+ background: var(--bg); border-radius: 4px;
+}
+.btn-speech {
+ background: none; border: 1px solid var(--border); border-radius: 4px;
+ padding: 0.25rem 0.6rem; font-size: 0.75rem; cursor: pointer;
+ margin-top: 0.5rem; color: var(--text-muted);
+}
+.btn-speech:hover { border-color: var(--accent); color: var(--accent); }
+
+/* Modal overlay for delete confirmation */
+#modal-overlay {
+ display: none;
+ position: fixed; inset: 0;
+ background: rgba(0,0,0,0.4);
+ align-items: center; justify-content: center;
+ z-index: 100;
+}
+#modal-overlay.show { display: flex; }
+#modal-box {
+ background: var(--panel-bg); border-radius: var(--radius);
+ padding: 1.5rem; max-width: 360px; width: 90%;
+ box-shadow: 0 8px 24px rgba(0,0,0,0.15);
+}
+#modal-box p { margin-bottom: 1rem; font-size: 0.9rem; }
+#modal-box .modal-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
+#modal-box button {
+ padding: 0.4rem 1rem; border-radius: 6px; border: 1px solid var(--border);
+ cursor: pointer; font-size: 0.85rem;
+}
+#modal-box .btn-cancel { background: var(--panel-bg); color: var(--text); }
+#modal-box .btn-confirm { background: var(--danger); color: #fff; border-color: var(--danger); }
+
+@media (max-width: 768px) {
+ body { padding: 0.5rem; }
+ #app { grid-template-columns: 1fr; }
+ #panel-list { max-height: 30vh; }
+ #panel-editor { min-height: 40vh; }
+ #panel-translate { max-height: 30vh; }
+}
+```
+
+Verify: Open `index.html` — three-column layout with styled panels.
+
+---
+
+### Task 3: Implement IndexedDB module (db.js)
+
+**Files:**
+- Create: `spanish-diary/db.js`
+
+**Details:** Wrapper around IndexedDB. Database name `SpanishDiary`, object store `entries` with key path `id`. Provides `openDb()`, `getAllEntries()`, `getEntry(id)`, `addEntry(entry)`, `updateEntry(entry)`, `deleteEntry(id)`. Uses a `dbPromise` pattern (store the DB reference after open).
+
+- [ ] **Step 1: Write db.js**
+
+```js
+const DB_NAME = 'SpanishDiary';
+const DB_VERSION = 1;
+const STORE_NAME = 'entries';
+
+let db = null;
+
+function openDb() {
+ return new Promise((resolve, reject) => {
+ if (db) { resolve(db); return; }
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
+ req.onupgradeneeded = (e) => {
+ const d = e.target.result;
+ if (!d.objectStoreNames.contains(STORE_NAME)) {
+ const store = d.createObjectStore(STORE_NAME, { keyPath: 'id' });
+ store.createIndex('date', 'date', { unique: false });
+ }
+ };
+ req.onsuccess = (e) => { db = e.target.result; resolve(db); };
+ req.onerror = (e) => reject(e.target.error);
+ });
+}
+
+function getAllEntries() {
+ return openDb().then(db => {
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(STORE_NAME, 'readonly');
+ const store = tx.objectStore(STORE_NAME);
+ const index = store.index('date');
+ const req = index.openCursor(null, 'prev');
+ const results = [];
+ req.onsuccess = (e) => {
+ const cursor = e.target.result;
+ if (cursor) { results.push(cursor.value); cursor.continue(); }
+ else resolve(results);
+ };
+ req.onerror = (e) => reject(e.target.error);
+ });
+ });
+}
+
+function getEntry(id) {
+ return openDb().then(db => {
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(STORE_NAME, 'readonly');
+ const store = tx.objectStore(STORE_NAME);
+ const req = store.get(id);
+ req.onsuccess = () => resolve(req.result || null);
+ req.onerror = (e) => reject(e.target.error);
+ });
+ });
+}
+
+function addEntry(entry) {
+ return openDb().then(db => {
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(STORE_NAME, 'readwrite');
+ const store = tx.objectStore(STORE_NAME);
+ const req = store.add(entry);
+ req.onsuccess = () => resolve(entry);
+ req.onerror = (e) => reject(e.target.error);
+ });
+ });
+}
+
+function updateEntry(entry) {
+ return openDb().then(db => {
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(STORE_NAME, 'readwrite');
+ const store = tx.objectStore(STORE_NAME);
+ const req = store.put(entry);
+ req.onsuccess = () => resolve(entry);
+ req.onerror = (e) => reject(e.target.error);
+ });
+ });
+}
+
+function deleteEntry(id) {
+ return openDb().then(db => {
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(STORE_NAME, 'readwrite');
+ const store = tx.objectStore(STORE_NAME);
+ const req = store.delete(id);
+ req.onsuccess = () => resolve();
+ req.onerror = (e) => reject(e.target.error);
+ });
+ });
+}
+```
+
+Verify: Load `index.html` and check console for `db` object (will test with app.js later).
+
+---
+
+### Task 4: Implement translation module (translate.js)
+
+**Files:**
+- Create: `spanish-diary/translate.js`
+
+**Details:** Lookup-based mock translation for common Spanish diary phrases. Function `translateText(text)` returns `{ english, chinese }`. Falls back to `text` + `[待翻译]` for unknown phrases. Covers ~20+ common daily expressions.
+
+- [ ] **Step 1: Write translate.js**
+
+```js
+const MOCK_TRANSLATIONS = {
+ 'hola': { en: 'Hello', zh: '你好' },
+ 'buenos días': { en: 'Good morning', zh: '早上好' },
+ 'buenas tardes': { en: 'Good afternoon', zh: '下午好' },
+ 'buenas noches': { en: 'Good night', zh: '晚安' },
+ 'hoy': { en: 'today', zh: '今天' },
+ 'ayer': { en: 'yesterday', zh: '昨天' },
+ 'mañana': { en: 'tomorrow', zh: '明天' },
+ 'fui': { en: 'I went', zh: '我去了' },
+ 'fui a': { en: 'I went to', zh: '我去了' },
+ 'estoy': { en: 'I am', zh: '我' },
+ 'estoy muy': { en: 'I am very', zh: '我非常' },
+ 'tengo': { en: 'I have', zh: '我有' },
+ 'quiero': { en: 'I want', zh: '我想要' },
+ 'me gusta': { en: 'I like', zh: '我喜欢' },
+ 'me gustó': { en: 'I liked', zh: '我喜欢了' },
+ 'aprendí': { en: 'I learned', zh: '我学了' },
+ 'estudié': { en: 'I studied', zh: '我学习了' },
+ 'comí': { en: 'I ate', zh: '我吃了' },
+ 'bebí': { en: 'I drank', zh: '我喝了' },
+ 'leí': { en: 'I read', zh: '我读了' },
+ 'escribí': { en: 'I wrote', zh: '我写了' },
+ 'trabajé': { en: 'I worked', zh: '我工作了' },
+ 'descansé': { en: 'I rested', zh: '我休息了' },
+ 'caminé': { en: 'I walked', zh: '我散步了' },
+ 'corrí': { en: 'I ran', zh: '我跑步了' },
+ 'compré': { en: 'I bought', zh: '我买了' },
+ 'vi': { en: 'I saw', zh: '我看了' },
+ 'con': { en: 'with', zh: '和' },
+ 'mi': { en: 'my', zh: '我的' },
+ 'amigo': { en: 'friend', zh: '朋友' },
+ 'amiga': { en: 'friend (female)', zh: '女性朋友' },
+ 'familia': { en: 'family', zh: '家人' },
+ 'casa': { en: 'home', zh: '家' },
+ 'trabajo': { en: 'work', zh: '工作' },
+ 'escuela': { en: 'school', zh: '学校' },
+ 'parque': { en: 'park', zh: '公园' },
+ 'restaurante': { en: 'restaurant', zh: '餐厅' },
+ 'café': { en: 'coffee / café', zh: '咖啡 / 咖啡馆' },
+ 'libro': { en: 'book', zh: '书' },
+ 'película': { en: 'movie', zh: '电影' },
+ 'música': { en: 'music', zh: '音乐' },
+ 'día': { en: 'day', zh: '天' },
+ 'noche': { en: 'night', zh: '夜晚' },
+ 'comida': { en: 'food', zh: '食物' },
+ 'agua': { en: 'water', zh: '水' },
+ 'tiempo': { en: 'time / weather', zh: '时间 / 天气' },
+ 'feliz': { en: 'happy', zh: '开心的' },
+ 'cansado': { en: 'tired', zh: '累的' },
+ 'enfermo': { en: 'sick', zh: '生病的' },
+ 'bien': { en: 'well / good', zh: '好' },
+ 'mal': { en: 'bad', zh: '不好' },
+ 'muy': { en: 'very', zh: '非常' },
+ 'un poco': { en: 'a little', zh: '一点' },
+ 'mucho': { en: 'a lot', zh: '很多' },
+ 'y': { en: 'and', zh: '和' },
+ 'pero': { en: 'but', zh: '但是' },
+ 'porque': { en: 'because', zh: '因为' },
+ 'también': { en: 'also', zh: '也' },
+ 'no': { en: 'no / not', zh: '不 / 没有' },
+ 'sí': { en: 'yes', zh: '是的' },
+ 'gracias': { en: 'thank you', zh: '谢谢' },
+ 'por favor': { en: 'please', zh: '请' },
+ 'lo siento': { en: 'sorry', zh: '对不起' },
+};
+
+function translateText(text) {
+ if (!text || !text.trim()) return { english: '', chinese: '' };
+ const lower = text.toLowerCase().trim();
+
+ let enParts = [];
+ let zhParts = [];
+ let unknownParts = [];
+ const words = lower.split(/\s+/);
+
+ for (const word of words) {
+ const cleaned = word.replace(/[^a-záéíóúüñ]/g, '');
+ if (!cleaned) continue;
+ const entry = MOCK_TRANSLATIONS[cleaned];
+ if (entry) {
+ enParts.push(entry.en);
+ zhParts.push(entry.zh);
+ } else {
+ enParts.push(cleaned);
+ zhParts.push(cleaned + '[?]');
+ unknownParts.push(cleaned);
+ }
+ }
+
+ return {
+ english: enParts.length > 0 ? enParts.join(' ') : text,
+ chinese: zhParts.length > 0 ? zhParts.join('') : text,
+ unknown: unknownParts.length > 0 ? unknownParts : []
+ };
+}
+```
+
+Verify: Open browser console, call `translateText('hoy fui al parque')`, check result.
+
+---
+
+### Task 5: Implement speech module (speech.js)
+
+**Files:**
+- Create: `spanish-diary/speech.js`
+
+**Details:** Wrapper around `window.speechSynthesis`. `speakText(text, lang)` function. Language mapping: `es` → Spanish, `en` → English UK/US, `zh` → Chinese.
+
+- [ ] **Step 1: Write speech.js**
+
+```js
+function speakText(text, lang) {
+ if (!text || !window.speechSynthesis) return;
+ window.speechSynthesis.cancel();
+ const utterance = new SpeechSynthesisUtterance(text);
+
+ const langMap = {
+ 'es': 'es-ES',
+ 'en': 'en-US',
+ 'zh': 'zh-CN'
+ };
+ utterance.lang = langMap[lang] || lang;
+ utterance.rate = 0.9;
+ utterance.pitch = 1;
+
+ window.speechSynthesis.speak(utterance);
+}
+```
+
+Verify: Open console, call `speakText('Hola', 'es')` — should pronounce Spanish.
+
+---
+
+### Task 6: Implement application logic (app.js)
+
+**Files:**
+- Create: `spanish-diary/app.js`
+
+**Details:** Coordinates everything. State: `entries` (all entries), `currentEntry` (selected/null). On load: open DB, load entries into list. Feature logic: new entry, select entry, save entry, delete entry, render translation, handle speech buttons.
+
+- [ ] **Step 1: Write app.js**
+
+```js
+let state = {
+ entries: [],
+ currentId: null
+};
+
+function uuid() {
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
+}
+
+function todayStr() {
+ const d = new Date();
+ return d.getFullYear() + '-' +
+ String(d.getMonth() + 1).padStart(2, '0') + '-' +
+ String(d.getDate()).padStart(2, '0');
+}
+
+// ----- Render functions -----
+
+function renderList() {
+ const ul = document.getElementById('entry-list');
+ ul.innerHTML = state.entries.map(e => {
+ const preview = e.spanish.slice(0, 30) + (e.spanish.length > 30 ? '...' : '');
+ const active = e.id === state.currentId ? 'active' : '';
+ return `
+ ${e.date}
+ ${escHtml(preview)}
+
+ 🗑 删除
+
+ `;
+ }).join('');
+}
+
+function escHtml(s) {
+ const div = document.createElement('div');
+ div.textContent = s;
+ return div.innerHTML;
+}
+
+function renderTranslate(entry) {
+ document.getElementById('translate-en').textContent = entry ? entry.english : '-';
+ document.getElementById('translate-cn').textContent = entry ? entry.chinese : '-';
+}
+
+function setEditor(entry) {
+ document.getElementById('entry-date').value = entry ? entry.date : todayStr();
+ document.getElementById('entry-text').value = entry ? entry.spanish : '';
+ const hint = document.getElementById('entry-hint');
+ hint.textContent = entry && entry.spanish
+ ? '已保存。修改后点击保存更新。'
+ : '输入西语后点击保存,右侧自动显示翻译';
+}
+
+function clearEditor() {
+ state.currentId = null;
+ document.querySelectorAll('#entry-list li').forEach(el => el.classList.remove('active'));
+ setEditor(null);
+ renderTranslate(null);
+}
+
+// ----- Event handlers -----
+
+async function selectEntry(id) {
+ state.currentId = id;
+ const entry = await dbGetEntry(id);
+ if (entry) {
+ setEditor(entry);
+ renderTranslate(entry);
+ renderList();
+ }
+}
+
+async function saveCurrentEntry() {
+ const date = document.getElementById('entry-date').value;
+ const spanish = document.getElementById('entry-text').value.trim();
+ if (!spanish) { alert('请输入西语内容'); return; }
+
+ const translation = translateText(spanish);
+
+ if (state.currentId) {
+ const existing = await dbGetEntry(state.currentId);
+ existing.date = date;
+ existing.spanish = spanish;
+ existing.english = translation.english;
+ existing.chinese = translation.chinese;
+ existing.updatedAt = new Date().toISOString();
+ await dbUpdateEntry(existing);
+ } else {
+ const entry = {
+ id: uuid(),
+ date: date,
+ spanish: spanish,
+ english: translation.english,
+ chinese: translation.chinese,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString()
+ };
+ await dbAddEntry(entry);
+ state.currentId = entry.id;
+ }
+
+ state.entries = await dbGetAllEntries();
+ renderList();
+ renderTranslate({ english: translation.english, chinese: translation.chinese });
+ setEditor(state.entries.find(e => e.id === state.currentId));
+ document.getElementById('btn-save').textContent = '💾 已保存 ✓';
+ setTimeout(() => { document.getElementById('btn-save').textContent = '💾 保存'; }, 1500);
+}
+
+async function deleteEntry(id) {
+ showDeleteModal(id);
+}
+
+async function confirmDelete(id) {
+ await dbDeleteEntry(id);
+ if (state.currentId === id) clearEditor();
+ state.entries = await dbGetAllEntries();
+ renderList();
+ hideDeleteModal();
+}
+
+function newEntry() {
+ clearEditor();
+ document.getElementById('entry-date').value = todayStr();
+ document.getElementById('entry-text').focus();
+}
+
+// ----- Modal -----
+
+let pendingDeleteId = null;
+
+function showDeleteModal(id) {
+ pendingDeleteId = id;
+ document.getElementById('modal-overlay').classList.add('show');
+}
+
+function hideDeleteModal() {
+ pendingDeleteId = null;
+ document.getElementById('modal-overlay').classList.remove('show');
+}
+
+// ----- Speech handler -----
+
+function handleSpeech(lang) {
+ const text = lang === 'en'
+ ? document.getElementById('translate-en').textContent
+ : document.getElementById('translate-cn').textContent;
+ if (text && text !== '-') speakText(text, lang);
+}
+
+// ----- Initialization -----
+
+async function init() {
+ state.entries = await dbGetAllEntries();
+ renderList();
+ setEditor(null);
+ renderTranslate(null);
+
+ // Event delegation for list clicks
+ document.getElementById('entry-list').addEventListener('click', (e) => {
+ const li = e.target.closest('li[data-id]');
+ if (!li) return;
+ if (e.target.closest('.btn-delete')) {
+ deleteEntry(li.dataset.id);
+ return;
+ }
+ selectEntry(li.dataset.id);
+ });
+
+ document.getElementById('btn-new').addEventListener('click', newEntry);
+ document.getElementById('btn-save').addEventListener('click', saveCurrentEntry);
+
+ document.querySelectorAll('.btn-speech').forEach(btn => {
+ btn.addEventListener('click', () => handleSpeech(btn.dataset.lang));
+ });
+
+ document.getElementById('modal-box').querySelector('.btn-cancel').addEventListener('click', hideDeleteModal);
+ document.getElementById('modal-box').querySelector('.btn-confirm').addEventListener('click', () => {
+ if (pendingDeleteId) confirmDelete(pendingDeleteId);
+ });
+
+ // Keyboard shortcut
+ document.addEventListener('keydown', (e) => {
+ if (e.ctrlKey && e.key === 'Enter') saveCurrentEntry();
+ });
+}
+
+document.addEventListener('DOMContentLoaded', init);
+```
+
+Also add the delete confirmation modal HTML to `index.html` before the `