<!doctype html><html lang=”zh-TW” class=”h-full”> <head> <meta charset=”UTF-8″> <meta name=”viewport” content=”width=device-width, initial-scale=1.0″> <title>國文詞彙記憶</title> <script src=”https://cdn.tailwindcss.com/3.4.17″></script> <script src=”https://cdn.jsdelivr.net/npm/lucide@0.263.0/dist/umd/lucide.min.js”></script> <script src=”/_sdk/element_sdk.js”></script> <script src=”/_sdk/data_sdk.js”></script> <link rel=”preconnect” href=”https://fonts.googleapis.com”> <link rel=”preconnect” href=”https://fonts.gstatic.com” crossorigin> <link href=”https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap” rel=”stylesheet”> <style> * { font-family: ‘Noto Sans TC’, sans-serif; } .macaron-pink { background-color: #FFB5BA; } .macaron-mint { background-color: #98D4BB; } .macaron-lavender { background-color: #C5A3FF; } .macaron-yellow { background-color: #FFE5A0; } .macaron-blue { background-color: #A0D2FF; } .card-hover { transition: all 0.3s ease; } .card-hover:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0,0,0,0.1); } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .fade-in { animation: fadeIn 0.4s ease forwards; } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } .shake { animation: shake 0.3s ease; } .tag-badge { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.8rem; border-radius: 9999px; font-size: 0.875rem; font-weight: 500; background-color: #E0E7FF; color: #6B7280; cursor: pointer; transition: all 0.2s ease; } .tag-badge.selected { background-color: #FFB5BA; color: white; } </style> <style>body { box-sizing: border-box; }</style> <script src=”/_sdk/telemetry_sdk.js”></script></head> <body class=”h-full”> <div id=”app” class=”h-full w-full overflow-auto” style=”background-color: #FFF5F5;”> <!– 主頁 –> <div id=”home-page” class=”min-h-full p-6 flex flex-col items-center justify-center relative”> <div class=”absolute top-6 right-6 z-40″> <button onclick=”showManagePinsModal()” class=”macaron-yellow text-white px-4 py-2 rounded-lg text-sm font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″ style=”color: #5C4033;”> <i data-lucide=”settings” class=”w-4 h-4″></i> 管理PIN碼 </button> </div> <div class=”text-center fade-in”> <div class=”w-24 h-24 mx-auto mb-6 rounded-full macaron-lavender flex items-center justify-center shadow-lg”> <i data-lucide=”book-open” class=”w-12 h-12 text-white”></i> </div> <h1 id=”main-title” class=”text-4xl font-bold mb-4″ style=”color: #6B5B7A;”>國文詞彙記憶</h1> <p class=”text-lg mb-8″ style=”color: #8B7B9B;”>輕鬆記憶,快樂學習</p><button onclick=”showPinModal()” class=”macaron-pink text-white px-8 py-4 rounded-full text-xl font-medium shadow-lg card-hover mx-auto”>進入複習詞彙解釋</button> </div> </div><!– 詞彙頁面 –> <div id=”vocab-page” class=”min-h-full p-6 hidden”> <div class=”max-w-4xl mx-auto”> <!– 頂部導航 –> <div class=”flex items-center justify-between mb-6″> <button onclick=”showHomePage()” class=”flex items-center gap-2 text-gray-600 hover:text-gray-800 transition”> <i data-lucide=”arrow-left” class=”w-5 h-5″></i> 返回主頁 </button> <h2 class=”text-2xl font-bold” style=”color: #6B5B7A;”>詞彙列表</h2> <div class=”w-20″></div> </div><!– 新增詞彙區塊 –> <div class=”bg-white rounded-2xl p-6 shadow-md mb-6 fade-in”> <h3 class=”text-lg font-medium mb-4 flex items-center gap-2″ style=”color: #6B5B7A;”><i data-lucide=”plus-circle” class=”w-5 h-5″></i> <span id=”add-section-title”>新增詞彙</span></h3> <div class=”grid md:grid-cols-2 gap-4 mb-4″> <div> <label class=”block text-sm font-medium text-gray-600 mb-2″>語詞</label> <input type=”text” id=”word-input” placeholder=”輸入語詞” class=”w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition”> </div> <div> <label class=”block text-sm font-medium text-gray-600 mb-2″>詞語解釋</label> <input type=”text” id=”definition-input” placeholder=”輸入解釋” class=”w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition”> </div> </div> <div class=”mb-4″> <label class=”block text-sm font-medium text-gray-600 mb-2″>標籤</label> <div id=”selected-tags-container” class=”flex flex-wrap gap-2 mb-3 min-h-10 p-2 rounded-xl border-2 border-gray-200 bg-gray-50″> </div> <div class=”mb-3″><label class=”block text-xs font-medium text-gray-500 mb-2″>點擊下方標籤快速新增</label> <div id=”tag-suggestions” class=”flex flex-wrap gap-2 mb-3″> <!– 現有標籤建議會顯示在這裡 –> </div> </div> <div class=”flex gap-2″> <input type=”text” id=”new-tag-input” placeholder=”輸入新標籤,按 Enter 確認” class=”flex-1 px-4 py-2 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition text-sm”> <button id=”add-new-tag-btn” class=”macaron-yellow text-white px-5 py-2 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″ style=”color: #5C4033;”><i data-lucide=”plus” class=”w-4 h-4″></i> 新增</button> </div> </div><button id=”add-btn” onclick=”addVocab()” class=”macaron-mint text-white px-6 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″> <i data-lucide=”plus” class=”w-5 h-5″></i> <span id=”add-btn-text”>新增詞彙</span> </button> </div><!– 操作按鈕區 –> <div class=”flex flex-wrap gap-3 mb-6″> <button onclick=”toggleSelectAll()” id=”select-all-btn” class=”macaron-blue text-white px-5 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″> <i data-lucide=”check-square” class=”w-5 h-5″></i> 全選 </button> <button onclick=”showFilterTagsModal()” class=”macaron-lavender text-white px-5 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″> <i data-lucide=”filter” class=”w-5 h-5″></i> 選取標籤 </button> <button onclick=”showTagVisibilityModal()” class=”macaron-yellow text-white px-5 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″ style=”color: #5C4033;”> <i data-lucide=”eye” class=”w-5 h-5″></i> 顯示標籤 </button> <button onclick=”showBulkDeleteModal()” class=”macaron-pink text-white px-5 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″> <i data-lucide=”trash-2″ class=”w-5 h-5″></i> 刪除選取 </button> <button onclick=”startQuiz()” id=”quiz-btn” class=”macaron-pink text-white px-5 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″> <i data-lucide=”play” class=”w-5 h-5″></i> 開始測驗 </button> <span id=”selected-count” class=”flex items-center text-gray-500 text-sm ml-2″>已選擇 0 個詞彙</span> </div><!– 詞彙列表 –> <div id=”vocab-list” class=”space-y-3″> <!– 詞彙卡片會動態添加 –> </div> <div id=”empty-state” class=”text-center py-12 hidden”> <div class=”w-20 h-20 mx-auto mb-4 rounded-full macaron-yellow flex items-center justify-center”> <i data-lucide=”inbox” class=”w-10 h-10 text-white”></i> </div> <p class=”text-gray-500″>還沒有詞彙,快來新增吧!</p> </div> </div> </div><!– 測驗頁面 –> <div id=”quiz-page” class=”min-h-full p-6 hidden”> <div class=”max-w-2xl mx-auto”> <div class=”flex items-center justify-between mb-6″> <button onclick=”exitQuiz()” class=”flex items-center gap-2 text-gray-600 hover:text-gray-800 transition”> <i data-lucide=”x” class=”w-5 h-5″></i> 結束測驗 </button> <div id=”quiz-progress” class=”text-gray-600 font-medium”></div> </div> <div id=”quiz-card” class=”bg-white rounded-3xl p-8 shadow-lg text-center fade-in”> <div class=”mb-6″> <span class=”macaron-lavender text-white px-4 py-1 rounded-full text-sm”>題目</span> </div> <h3 id=”quiz-word” class=”text-4xl font-bold mb-8″ style=”color: #6B5B7A;”></h3><!– 選擇題模式 –> <div id=”choice-mode” class=”space-y-3″> <div id=”quiz-options” class=”space-y-3″> <!– 選項會動態添加 –> </div> <div id=”quiz-result-choice” class=”mt-6 hidden”> <p id=”result-text” class=”text-xl font-medium mb-4″></p> <p id=”correct-answer” class=”text-gray-600 mb-6″></p> </div> </div><!– 自由作答模式 –> <div id=”free-mode” class=”hidden”> <div class=”mb-6″> <label class=”block text-sm font-medium text-gray-600 mb-3″>請輸入解釋:</label> <textarea id=”free-answer” placeholder=”輸入你的答案…” class=”w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition” rows=”4″></textarea> </div><button id=”submit-free-btn” class=”macaron-yellow px-8 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition” style=”color: #5C4033;”>提交答案</button> <div id=”quiz-result-free” class=”mt-6 hidden”> <p id=”result-text-free” class=”text-xl font-medium mb-4″></p> <div class=”bg-gray-50 rounded-xl p-4 mb-6 text-left”> <p class=”text-sm text-gray-600 mb-2″><strong>你的答案:</strong></p> <p id=”user-answer” class=”text-gray-800″></p> <p class=”text-sm text-gray-600 mt-4 mb-2″><strong>正確答案:</strong></p> <p id=”correct-answer-free” class=”text-gray-800″></p> </div> </div> </div> </div><!– 測驗結果 –> <div id=”quiz-final” class=”bg-white rounded-3xl p-8 shadow-lg text-center hidden”> <div class=”w-24 h-24 mx-auto mb-6 rounded-full macaron-yellow flex items-center justify-center”> <i data-lucide=”trophy” class=”w-12 h-12 text-white”></i> </div> <h3 class=”text-3xl font-bold mb-4″ style=”color: #6B5B7A;”>測驗完成!</h3> <p id=”final-score” class=”text-xl text-gray-600 mb-6″></p><button onclick=”showVocabPage()” class=”macaron-pink text-white px-8 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition”> 返回詞彙列表 </button> </div> </div> </div> </div><!– PIN碼選擇模態 –> <div id=”pin-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4″> <h3 class=”text-xl font-bold mb-4 text-center” style=”color: #6B5B7A;”>選擇PIN碼</h3> <div id=”pin-list” class=”space-y-3 max-h-64 overflow-y-auto mb-6″> </div> <div class=”flex gap-3 justify-center”> <button onclick=”closePinModal()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> </div> </div> </div><!– 管理PIN碼模態 –> <div id=”manage-pins-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4″> <h3 class=”text-xl font-bold mb-4″ style=”color: #6B5B7A;”>管理PIN碼</h3> <div id=”existing-pins-list” class=”space-y-2 max-h-48 overflow-y-auto mb-6″> </div> <div class=”mb-4″><label class=”block text-sm font-medium text-gray-600 mb-2″>新增PIN碼</label> <div class=”flex gap-2″><input type=”text” id=”new-pin-input” maxlength=”4″ placeholder=”輸入4位數字” class=”flex-1 px-4 py-2 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition”> <button onclick=”addNewPin()” class=”macaron-yellow text-white px-4 py-2 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″ style=”color: #5C4033;”><i data-lucide=”plus” class=”w-4 h-4″></i></button> </div> </div> <div class=”flex gap-3 justify-center”> <button onclick=”closeManagePinsModal()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 完成 </button> </div> </div> </div><!– 刪除確認 –> <div id=”delete-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4 text-center”> <div class=”w-16 h-16 mx-auto mb-4 rounded-full macaron-pink flex items-center justify-center”> <i data-lucide=”trash-2″ class=”w-8 h-8 text-white”></i> </div> <h3 class=”text-xl font-bold mb-2″ style=”color: #6B5B7A;”>確定刪除?</h3> <p class=”text-gray-500 mb-6″>此操作無法復原</p> <div class=”flex gap-3 justify-center”> <button onclick=”cancelDelete()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> <button onclick=”confirmDelete()” class=”macaron-pink text-white px-6 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition”> 刪除 </button> </div> </div> </div><!– 編輯詞彙模態 –> <div id=”edit-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4″> <h3 class=”text-xl font-bold mb-4″ style=”color: #6B5B7A;”>編輯詞彙</h3> <div class=”space-y-4″> <div><label class=”block text-sm font-medium text-gray-600 mb-2″>語詞</label> <input type=”text” id=”edit-word-input” placeholder=”輸入語詞” class=”w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition”> </div> <div><label class=”block text-sm font-medium text-gray-600 mb-2″>詞語解釋</label> <input type=”text” id=”edit-definition-input” placeholder=”輸入解釋” class=”w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition”> </div> <div><label class=”block text-sm font-medium text-gray-600 mb-2″>標籤</label> <div id=”edit-selected-tags-container” class=”flex flex-wrap gap-2 mb-3 min-h-10 p-2 rounded-xl border-2 border-gray-200 bg-gray-50″> </div> <div class=”mb-3″><label class=”block text-xs font-medium text-gray-500 mb-2″>點擊下方標籤快速新增</label> <div id=”edit-tag-suggestions” class=”flex flex-wrap gap-2 mb-3″> </div> </div> <div class=”flex gap-2″><input type=”text” id=”edit-new-tag-input” placeholder=”輸入新標籤,按 Enter 確認” class=”flex-1 px-4 py-2 rounded-xl border-2 border-gray-200 focus:border-pink-300 focus:outline-none transition text-sm”> <button id=”add-new-edit-tag-btn” class=”macaron-yellow text-white px-4 py-2 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-2″ style=”color: #5C4033;” onclick=”addNewEditTag()”><i data-lucide=”plus” class=”w-4 h-4″></i></button> </div> </div> </div> <div class=”flex gap-3 justify-center mt-6″><button onclick=”cancelEdit()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> <button onclick=”confirmEdit()” class=”macaron-mint text-white px-6 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition”> 保存 </button> </div> </div> </div><!– 選取標籤模態 –> <div id=”filter-tags-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4″> <h3 class=”text-xl font-bold mb-4″ style=”color: #6B5B7A;”>選取標籤</h3> <p class=”text-gray-600 text-sm mb-4″>選擇一個或多個標籤,選取具有這些標籤的詞彙</p> <div id=”filter-tag-list” class=”flex flex-wrap gap-2 mb-6 max-h-48 overflow-y-auto”> </div> <div class=”flex gap-3 justify-center”><button onclick=”confirmFilterTags()” class=”macaron-mint text-white px-6 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition”> 確認 </button> <button onclick=”closeFilterTagsModal()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> </div> </div> </div><!– 顯示標籤模態 –> <div id=”tag-visibility-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4″> <h3 class=”text-xl font-bold mb-4″ style=”color: #6B5B7A;”>顯示標籤</h3> <p class=”text-gray-600 text-sm mb-4″>選擇要顯示的標籤</p> <div id=”visibility-tag-list” class=”flex flex-wrap gap-2 mb-6 max-h-48 overflow-y-auto”> </div> <div class=”flex gap-3 justify-center”><button onclick=”confirmTagVisibility()” class=”macaron-mint text-white px-6 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition”> 完成 </button> <button onclick=”closeTagVisibilityModal()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> </div> </div> </div><!– 刪除標籤確認 –> <div id=”delete-tag-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4 text-center”> <div class=”w-16 h-16 mx-auto mb-4 rounded-full macaron-pink flex items-center justify-center”> <i data-lucide=”trash-2″ class=”w-8 h-8 text-white”></i> </div> <h3 class=”text-xl font-bold mb-2″ style=”color: #6B5B7A;”>確定刪除標籤?</h3> <p id=”delete-tag-info” class=”text-gray-500 mb-6″>此操作無法復原</p> <div class=”flex gap-3 justify-center”> <button onclick=”cancelDeleteTag()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> <button onclick=”confirmDeleteTag()” class=”macaron-pink text-white px-6 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition”> 刪除 </button> </div> </div> </div><!– 測驗模式選擇 –> <div id=”quiz-mode-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4 text-center”> <h3 class=”text-xl font-bold mb-4″ style=”color: #6B5B7A;”>選擇測驗模式</h3> <p class=”text-gray-600 text-sm mb-6″>你要選擇哪種方式進行測驗?</p> <div class=”space-y-3″><button onclick=”startQuizWithMode(‘choice’)” class=”w-full macaron-pink text-white px-6 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-3 justify-center”> <i data-lucide=”radio” class=”w-5 h-5″></i> 選擇題 </button> <button onclick=”startQuizWithMode(‘free’)” class=”w-full macaron-lavender text-white px-6 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition flex items-center gap-3 justify-center”> <i data-lucide=”edit-3″ class=”w-5 h-5″></i> 自由作答 </button> </div><button onclick=”closeQuizModeModal()” class=”mt-4 w-full px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> </div> </div><!– 批量刪除確認 –> <div id=”bulk-delete-modal” class=”fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50″> <div class=”bg-white rounded-2xl p-6 max-w-sm mx-4 text-center”> <div class=”w-16 h-16 mx-auto mb-4 rounded-full macaron-pink flex items-center justify-center”> <i data-lucide=”trash-2″ class=”w-8 h-8 text-white”></i> </div> <h3 class=”text-xl font-bold mb-2″ style=”color: #6B5B7A;”>確定刪除選取的詞彙?</h3> <p id=”bulk-delete-count” class=”text-gray-500 mb-6″>即將刪除 0 個詞彙</p> <div class=”flex gap-3 justify-center”> <button onclick=”cancelBulkDelete()” class=”px-6 py-2.5 rounded-xl border-2 border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition”> 取消 </button> <button onclick=”confirmBulkDelete()” class=”macaron-pink text-white px-6 py-2.5 rounded-xl font-medium shadow-md hover:shadow-lg transition”> 確認刪除 </button> </div> </div> </div> <script> let vocabList = []; let selectedIds = new Set(); let deleteTargetId = null; let editTargetId = null; let editSelectedTags = new Set(); let quizQuestions = []; let currentQuizIndex = 0; let correctCount = 0; let answered = false; let selectedTags = new Set(); let allTags = new Set(); let quizModeSelected = null; let visibleTags = new Set(); let pendingDeleteTag = null; let filterSelectedTags = new Set(); let registeredPins = [‘0725’]; let currentPin = null; const pin0725Data = [ { word: ‘昏聵’, definition: ‘愚蠢糊塗。’, tags: ‘L3’, created_at: ‘2026-03-15T11:07:12.607Z’, pin_code: ‘0725’ }, { word: ‘聵’, definition: ‘糊塗不明事理。’, tags: ‘L3’, created_at: ‘2026-03-15T11:07:34.129Z’, pin_code: ‘0725’ }, { word: ‘老神在在’, definition: ‘形容很有把握,一切盡在掌握中。’, tags: ‘L3’, created_at: ‘2026-03-15T11:08:07.489Z’, pin_code: ‘0725’ }, { word: ‘出糗’, definition: ‘出醜。’, tags: ‘L3’, created_at: ‘2026-03-15T11:08:26.268Z’, pin_code: ‘0725’ }, { word: ‘叱吒風雲’, definition: ‘形容威風氣概,足以左右世局。’, tags: ‘L3’, created_at: ‘2026-03-15T11:08:42.379Z’, pin_code: ‘0725’ }, { word: ‘叱吒’, definition: ‘大聲斥喝。’, tags: ‘L3’, created_at: ‘2026-03-15T11:08:55.751Z’, pin_code: ‘0725’ }, { word: ‘縱橫捭闔’, definition: ‘比喻政治或外交上的各種手段,運用極為靈活。’, tags: ‘L3’, created_at: ‘2026-03-15T11:09:21.640Z’, pin_code: ‘0725’ }, { word: ‘縱橫’, definition: ‘指分化和拉攏。’, tags: ‘L3’, created_at: ‘2026-03-15T11:09:33.903Z’, pin_code: ‘0725’ }, { word: ‘三寸不爛之舌’, definition: ‘形容能言善道,擅長辭令的口才。’, tags: ‘L3’, created_at: ‘2026-03-15T11:09:44.583Z’, pin_code: ‘0725’ }, { word: ‘誣枉’, definition: ‘以不實言語加以陷害、冤枉。’, tags: ‘L3’, created_at: ‘2026-03-15T11:09:53.950Z’, pin_code: ‘0725’ }, { word: ‘無恙’, definition: ‘指安好。’, tags: ‘L3’, created_at: ‘2026-03-15T11:10:08.358Z’, pin_code: ‘0725’ }, { word: ‘恙’, definition: ‘疾病。’, tags: ‘L3’, created_at: ‘2026-03-15T11:10:15.745Z’, pin_code: ‘0725’ }, { word: ‘煽動’, definition: ‘慫恿、鼓動。’, tags: ‘L3’, created_at: ‘2026-03-15T11:10:24.115Z’, pin_code: ‘0725’ }, { word: ‘巧舌如簧’, definition: ‘形容人言辭巧妙動聽。’, tags: ‘L3’, created_at: ‘2026-03-15T11:10:48.347Z’, pin_code: ‘0725’ }, { word: ‘騁快’, definition: ‘放縱。’, tags: ‘L3’, created_at: ‘2026-03-15T11:11:00.657Z’, pin_code: ‘0725’ }, { word: ‘騁’, definition: ‘奔馳。’, tags: ‘L3’, created_at: ‘2026-03-15T11:11:18.625Z’, pin_code: ‘0725’ }, { word: ‘樊籠’, definition: ‘關鳥獸的籠子。’, tags: ‘L3’, created_at: ‘2026-03-15T11:11:26.402Z’, pin_code: ‘0725’ }, { word: ‘猛獸出柙’, definition: ‘兇猛的野獸跑出柵欄。’, tags: ‘L3’, created_at: ‘2026-03-15T11:11:34.209Z’, pin_code: ‘0725’ }, { word: ‘柙’, definition: ‘關獸畜的籠子。’, tags: ‘L3’, created_at: ‘2026-03-15T11:11:49.960Z’, pin_code: ‘0725’ }, { word: ‘一言喪邦’, definition: ‘一句話可使國家淪亡。’, tags: ‘L3’, created_at: ‘2026-03-15T11:11:59.394Z’, pin_code: ‘0725’ }, { word: ‘水溶溶’, definition: ‘濕潤的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:17:28.403Z’, pin_code: ‘0725’ }, { word: ‘窈窕’, definition: ‘幽靜美好的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:20:36.279Z’, pin_code: ‘0725’ }, { word: ‘爛縵(漫)’, definition: ‘光彩分呈的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:21:48.969Z’, pin_code: ‘0725’ }, { word: ‘掛單’, definition: ‘原指僧侶投宿寺院暫駐住,此指借宿。’, tags: ‘L4’, created_at: ‘2026-04-15T12:23:18.542Z’, pin_code: ‘0725’ }, { word: ‘恣’, definition: ‘隨、任憑。’, tags: ‘L4’, created_at: ‘2026-04-15T12:23:43.698Z’, pin_code: ‘0725’ }, { word: ‘風流’, definition: ‘此指風雅灑脫。男子多情。’, tags: ‘L4’, created_at: ‘2026-04-15T12:24:18.017Z’, pin_code: ‘0725’ }, { word: ‘荒誕’, definition: ‘荒唐不合情理。’, tags: ‘L4’, created_at: ‘2026-04-15T12:24:36.873Z’, pin_code: ‘0725’ }, { word: ‘溫存’, definition: ‘溫柔。’, tags: ‘L4’, created_at: ‘2026-04-15T12:24:46.893Z’, pin_code: ‘0725’ }, { word: ‘栩栩’, definition: ‘活生生的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:25:02.613Z’, pin_code: ‘0725’ }, { word: ‘偌’, definition: ‘如此,那麼。’, tags: ‘L4’, created_at: ‘2026-04-15T12:25:33.288Z’, pin_code: ‘0725’ }, { word: ‘冉冉’, definition: ‘緩慢移動的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:25:53.125Z’, pin_code: ‘0725’ }, { word: ‘剎那’, definition: ‘極短的時間。’, tags: ‘L4’, created_at: ‘2026-04-15T12:26:16.991Z’, pin_code: ‘0725’ }, { word: ‘迷眩’, definition: ‘迷亂昏眩。’, tags: ‘L4’, created_at: ‘2026-04-15T12:26:45.723Z’, pin_code: ‘0725’ }, { word: ‘漠楞楞’, definition: ‘模糊不清的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:27:45.303Z’, pin_code: ‘0725’ }, { word: ‘山巖’, definition: ‘山峰。’, tags: ‘L4’, created_at: ‘2026-04-15T12:28:12.131Z’, pin_code: ‘0725’ }, { word: ‘沃腴’, definition: ‘土地肥美。’, tags: ‘L4’, created_at: ‘2026-04-15T12:28:48.552Z’, pin_code: ‘0725’ }, { word: ‘腴’, definition: ‘豐美。’, tags: ‘L4’, created_at: ‘2026-04-15T12:29:42.694Z’, pin_code: ‘0725’ }, { word: ‘土阜’, definition: ‘土丘。’, tags: ‘L4’, created_at: ‘2026-04-15T12:30:03.814Z’, pin_code: ‘0725’ }, { word: ‘阜’, definition: ‘土山。’, tags: ‘L4’, created_at: ‘2026-04-15T12:30:16.969Z’, pin_code: ‘0725’ }, { word: ‘娉婷’, definition: ‘輕巧美好的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:30:36.597Z’, pin_code: ‘0725’ }, { word: ‘嫵媚’, definition: ‘姿態美好的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:30:54.508Z’, pin_code: ‘0725’ }, { word: ‘參差’, definition: ‘不整齊的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:31:07.197Z’, pin_code: ‘0725’ }, { word: ‘翳’, definition: ‘隱沒。’, tags: ‘L4’, created_at: ‘2026-04-15T12:31:39.687Z’, pin_code: ‘0725’ }, { word: ‘糝’, definition: ‘撒落。’, tags: ‘L4’, created_at: ‘2026-04-15T12:32:10.857Z’, pin_code: ‘0725’ }, { word: ‘怯怜怜(憐)’, definition: ‘膽小可憐的樣子。’, tags: ‘L4’, created_at: ‘2026-04-15T12:36:26.932Z’, pin_code: ‘0725’ }, { word: ‘唧唧’, definition: ‘形容織布機織布時發出的聲音。’, tags: ‘L2’, created_at: ‘2026-04-15T12:37:53.850Z’, pin_code: ‘0725’ }, { word: ‘軍帖’, definition: ‘徵兵的文書。’, tags: ‘L2’, created_at: ‘2026-04-15T12:38:15.187Z’, pin_code: ‘0725’ }, { word: ‘帖’, definition: ‘文書。’, tags: ‘L2’, created_at: ‘2026-04-15T12:38:26.221Z’, pin_code: ‘0725’ }, { word: ‘可汗’, definition: ‘古代西域和北方各國對君王的稱呼。’, tags: ‘L2’, created_at: ‘2026-04-15T12:38:55.140Z’, pin_code: ‘0725’ }, { word: ‘爺’, definition: ‘父親。’, tags: ‘L2’, created_at: ‘2026-04-15T12:39:04.605Z’, pin_code: ‘0725’ }, { word: ‘市’, definition: ‘買。’, tags: ‘L2’, created_at: ‘2026-04-15T12:39:15.443Z’, pin_code: ‘0725’ }, { word: ‘孃’, definition: ‘通娘。’, tags: ‘L2’, created_at: ‘2026-04-15T12:39:51.586Z’, pin_code: ‘0725’ }, { word: ‘濺濺’, definition: ‘形容流水聲。’, tags: ‘L2’, created_at: ‘2026-04-15T12:40:42.953Z’, pin_code: ‘0725’ }, { word: ‘戎機’, definition: ‘上級所指示的軍事行動。’, tags: ‘L2’, created_at: ‘2026-04-15T12:41:14.566Z’, pin_code: ‘0725’ }, { word: ‘關山度若飛’, definition: ‘此指飛快地越過重重的關口和山嶺。’, tags: ‘L2’, created_at: ‘2026-04-15T12:42:05.904Z’, pin_code: ‘0725’ }, { word: ‘朔氣’, definition: ‘北方的寒氣。’, tags: ‘L2’, created_at: ‘2026-04-15T12:42:27.534Z’, pin_code: ‘0725’ }, { word: ‘策勳’, definition: ‘記功。’, tags: ‘L2’, created_at: ‘2026-04-15T12:42:55.239Z’, pin_code: ‘0725’ }, { word: ‘強’, definition: ‘指比….多。’, tags: ‘L2’, created_at: ‘2026-04-15T12:43:38.351Z’, pin_code: ‘0725’ }, { word: ‘明駝’, definition: ‘精壯的駱駝。’, tags: ‘L2’, created_at: ‘2026-04-15T12:43:54.372Z’, pin_code: ‘0725’ }, { word: ‘扶將’, definition: ‘扶持。’, tags: ‘L2’, created_at: ‘2026-04-15T12:44:15.914Z’, pin_code: ‘0725’ }, { word: ‘霍霍’, definition: ‘形容磨刀聲。’, tags: ‘L2’, created_at: ‘2026-04-15T12:44:35.485Z’, pin_code: ‘0725’ }, { word: ‘火伴’, definition: ‘指同事,也作伙伴、夥伴。’, tags: ‘L2’, created_at: ‘2026-04-15T12:45:15.304Z’, pin_code: ‘0725’ }, { word: ‘撲朔’, definition: ‘跳躍的樣子。’, tags: ‘L2’, created_at: ‘2026-04-15T12:46:00.115Z’, pin_code: ‘0725’ }, { word: ‘迷離’, definition: ‘模糊難以分辨的樣子。’, tags: ‘L2’, created_at: ‘2026-04-15T12:46:19.354Z’, pin_code: ‘0725’ }, { word: ‘兩兔傍地走’, definition: ‘指雌雄兩兔一起貼近地面奔跑。’, tags: ‘L2’, created_at: ‘2026-04-15T12:46:53.981Z’, pin_code: ‘0725’ }, { word: ‘傍’, definition: ‘靠近。’, tags: ‘L2’, created_at: ‘2026-04-15T12:47:13.961Z’, pin_code: ‘0725’ }, { word: ‘走’, definition: ‘跑。’, tags: ‘L2’, created_at: ‘2026-04-15T12:47:22.091Z’, pin_code: ‘0725’ } ]; const defaultConfig = { site_title: ‘國文詞彙記憶’, add_button_text: ‘新增詞彙’, background_color: ‘#FFF5F5’, card_color: ‘#FFFFFF’, text_color: ‘#6B5B7A’, primary_color: ‘#FFB5BA’, secondary_color: ‘#98D4BB’, font_family: ‘Noto Sans TC’, font_size: 16 }; window.elementSdk.init({ defaultConfig, onConfigChange: async (config) => { const c = { …defaultConfig, …config }; document.getElementById(‘app’).style.backgroundColor = c.background_color; document.getElementById(‘main-title’).textContent = c.site_title; document.getElementById(‘main-title’).style.color = c.text_color; document.getElementById(‘add-btn-text’).textContent = c.add_button_text; document.getElementById(‘add-section-title’).textContent = c.add_button_text; document.querySelectorAll(‘.macaron-pink’).forEach(el => { el.style.backgroundColor = c.primary_color; }); document.querySelectorAll(‘.macaron-mint’).forEach(el => { el.style.backgroundColor = c.secondary_color; }); document.body.style.fontFamily = `${c.font_family}, sans-serif`; }, mapToCapabilities: (config) => { const c = { …defaultConfig, …config }; return { recolorables: [ { get: () => c.background_color, set: (v) => window.elementSdk.setConfig({ background_color: v }) }, { get: () => c.card_color, set: (v) => window.elementSdk.setConfig({ card_color: v }) }, { get: () => c.text_color, set: (v) => window.elementSdk.setConfig({ text_color: v }) }, { get: () => c.primary_color, set: (v) => window.elementSdk.setConfig({ primary_color: v }) }, { get: () => c.secondary_color, set: (v) => window.elementSdk.setConfig({ secondary_color: v }) } ], borderables: [], fontEditable: { get: () => c.font_family, set: (v) => window.elementSdk.setConfig({ font_family: v }) }, fontSizeable: { get: () => c.font_size, set: (v) => window.elementSdk.setConfig({ font_size: v }) } }; }, mapToEditPanelValues: (config) => { const c = { …defaultConfig, …config }; return new Map([ [‘site_title’, c.site_title], [‘add_button_text’, c.add_button_text] ]); } }); const dataHandler = { onDataChanged(data) { vocabList = data.filter(v => !v.pin_code || v.pin_code === currentPin); updateAllTags(); renderVocabList(); renderTagSuggestions(); } }; window.dataSdk.init(dataHandler); // 初始化時清空所有數據 async function clearAllVocab() { for (const item of vocabList) { try { await window.dataSdk.delete(item); } catch (e) { console.error(‘Error deleting item:’, e); } } } // 在頁面加載時清空所有詞彙 setTimeout(async () => { if (vocabList.length > 0) { await clearAllVocab(); } }, 1000); function updateAllTags() { allTags.clear(); vocabList.forEach(vocab => { if (vocab.tags) { vocab.tags.split(‘,’).forEach(tag => { const trimmedTag = tag.trim(); if (trimmedTag) allTags.add(trimmedTag); }); } }); } function renderTagSuggestions() { const container = document.getElementById(‘tag-suggestions’); container.innerHTML = ”; allTags.forEach(tag => { const tagEl = document.createElement(‘button’); const isSelected = selectedTags.has(tag); tagEl.className = `tag-badge ${isSelected ? ‘selected’ : ”}`; tagEl.style.display = ‘inline-flex’; tagEl.style.alignItems = ‘center’; tagEl.style.gap = ‘0.5rem’; tagEl.onclick = (e) => { e.preventDefault(); toggleTag(tag); }; tagEl.innerHTML = `<span>${escapeHtml(tag)}</span>`; container.appendChild(tagEl); }); renderSelectedTagsDisplay(); lucide.createIcons(); } function renderSelectedTagsDisplay() { const container = document.getElementById(‘selected-tags-container’); container.innerHTML = ”; if (selectedTags.size === 0) { container.innerHTML = ‘<span style=”color: #9CA3AF; font-size: 0.875rem;”>未選擇標籤</span>’; } else { selectedTags.forEach(tag => { const tagEl = document.createElement(‘div’); tagEl.className = ‘tag-badge selected’; tagEl.innerHTML = `<span>${escapeHtml(tag)}</span><button onclick=”event.stopPropagation(); removeTag(‘${escapeHtml(tag)}’)” class=”hover:opacity-70″><i data-lucide=”x” class=”w-3 h-3″></i></button>`; container.appendChild(tagEl); }); } lucide.createIcons(); renderVocabList(); } function toggleTag(tag) { if (selectedTags.has(tag)) { selectedTags.delete(tag); } else { selectedTags.add(tag); } renderTagSuggestions(); } function removeTag(tag) { selectedTags.delete(tag); renderTagSuggestions(); } function showPinModal() { const pinList = document.getElementById(‘pin-list’); pinList.innerHTML = ”; registeredPins.forEach(pin => { const btn = document.createElement(‘button’); btn.className = ‘w-full macaron-pink text-white px-6 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition’; btn.textContent = `PIN: ${pin}`; btn.onclick = () => selectPin(pin); pinList.appendChild(btn); }); document.getElementById(‘pin-modal’).classList.remove(‘hidden’); document.getElementById(‘pin-modal’).classList.add(‘flex’); lucide.createIcons(); } function selectPin(pin) { currentPin = pin; closePinModal(); showVocabPage(); } function closePinModal() { document.getElementById(‘pin-modal’).classList.add(‘hidden’); document.getElementById(‘pin-modal’).classList.remove(‘flex’); } function showManagePinsModal() { renderExistingPinsList(); document.getElementById(‘manage-pins-modal’).classList.remove(‘hidden’); document.getElementById(‘manage-pins-modal’).classList.add(‘flex’); document.getElementById(‘new-pin-input’).value = ”; lucide.createIcons(); } function closeManagePinsModal() { document.getElementById(‘manage-pins-modal’).classList.add(‘hidden’); document.getElementById(‘manage-pins-modal’).classList.remove(‘flex’); } function renderExistingPinsList() { const container = document.getElementById(‘existing-pins-list’); container.innerHTML = ”; registeredPins.forEach(pin => { const item = document.createElement(‘div’); item.className = ‘flex items-center justify-between gap-3 p-3 bg-gray-50 rounded-lg’; item.innerHTML = ` <span class=”font-medium” style=”color: #6B5B7A;”>PIN: ${pin}</span> <button onclick=”deletePin(‘${pin}’)” class=”text-red-400 hover:text-red-600 transition”> <i data-lucide=”trash-2″ class=”w-4 h-4″></i> </button> `; container.appendChild(item); }); lucide.createIcons(); } function addNewPin() { const input = document.getElementById(‘new-pin-input’); const newPin = input.value.trim(); if (!newPin || newPin.length !== 4 || !/^\d+$/.test(newPin)) { input.classList.add(‘shake’); setTimeout(() => input.classList.remove(‘shake’), 300); return; } if (registeredPins.includes(newPin)) { alert(‘此PIN碼已存在’); return; } registeredPins.push(newPin); input.value = ”; renderExistingPinsList(); input.focus(); } function deletePin(pin) { if (pin === ‘0725’) { alert(‘不能刪除默認PIN碼’); return; } registeredPins = registeredPins.filter(p => p !== pin); renderExistingPinsList(); } function addNewTag() { const input = document.getElementById(‘new-tag-input’); const tag = input.value.trim(); if (!tag) return; selectedTags.add(tag); allTags.add(tag); visibleTags.add(tag); input.value = ”; renderTagSuggestions(); input.focus(); } document.addEventListener(‘DOMContentLoaded’, () => { document.getElementById(‘edit-new-tag-input’).addEventListener(‘keypress’, (e) => { if (e.key === ‘Enter’) { e.preventDefault(); addNewEditTag(); } }); document.getElementById(‘new-tag-input’).addEventListener(‘keypress’, (e) => { if (e.key === ‘Enter’) { e.preventDefault(); addNewTag(); } }); document.getElementById(‘add-new-tag-btn’).addEventListener(‘click’, (e) => { e.preventDefault(); addNewTag(); }); }); function showVocabPage() { document.getElementById(‘home-page’).classList.add(‘hidden’); document.getElementById(‘vocab-page’).classList.remove(‘hidden’); document.getElementById(‘quiz-page’).classList.add(‘hidden’); selectedTags.clear(); document.getElementById(‘word-input’).value = ”; document.getElementById(‘definition-input’).value = ”; if (visibleTags.size === 0) { allTags.forEach(tag => visibleTags.add(tag)); } renderTagSuggestions(); renderVocabList(); } function showHomePage() { document.getElementById(‘home-page’).classList.remove(‘hidden’); document.getElementById(‘vocab-page’).classList.add(‘hidden’); document.getElementById(‘quiz-page’).classList.add(‘hidden’); } function showQuizPage() { document.getElementById(‘home-page’).classList.add(‘hidden’); document.getElementById(‘vocab-page’).classList.add(‘hidden’); document.getElementById(‘quiz-page’).classList.remove(‘hidden’); } async function addVocab() { const wordInput = document.getElementById(‘word-input’); const defInput = document.getElementById(‘definition-input’); const word = wordInput.value.trim(); const definition = defInput.value.trim(); if (!word || !definition) { wordInput.classList.add(‘shake’); defInput.classList.add(‘shake’); setTimeout(() => { wordInput.classList.remove(‘shake’); defInput.classList.remove(‘shake’); }, 300); return; } if (vocabList.length >= 999) { alert(‘已達到最大詞彙數量限制(999個)’); return; } const addBtn = document.getElementById(‘add-btn’); addBtn.disabled = true; addBtn.innerHTML = ‘<i data-lucide=”loader” class=”w-5 h-5 animate-spin”></i> 新增中…’; const tagsString = Array.from(selectedTags).join(‘, ‘); selectedTags.forEach(tag => visibleTags.add(tag)); const result = await window.dataSdk.create({ word, definition, tags: tagsString, created_at: new Date().toISOString(), pin_code: currentPin }); addBtn.disabled = false; addBtn.innerHTML = ‘<i data-lucide=”plus” class=”w-5 h-5″></i> <span id=”add-btn-text”>新增詞彙</span>’; lucide.createIcons(); if (result.isOk) { wordInput.value = ”; defInput.value = ”; selectedTags.clear(); renderSelectedTagsDisplay(); renderTagSuggestions(); } } function renderVocabList() { const container = document.getElementById(‘vocab-list’); const emptyState = document.getElementById(’empty-state’); if (vocabList.length === 0) { container.innerHTML = ”; emptyState.classList.remove(‘hidden’); updateSelectedCount(); return; } emptyState.classList.add(‘hidden’); const existingCards = new Map(); container.querySelectorAll(‘[data-vocab-id]’).forEach(el => { existingCards.set(el.dataset.vocabId, el); }); const currentIds = new Set(vocabList.map(v => v.__backendId)); existingCards.forEach((el, id) => { if (!currentIds.has(id)) { el.remove(); } }); const filteredVocab = vocabList.filter(vocab => { const hasNoTags = !vocab.tags || vocab.tags.trim() === ”; // 檢查無標籤選項 if (visibleTags.has(‘__NO_TAG__’) && hasNoTags) { return true; } // 檢查有標籤的詞彙 if (vocab.tags) { const tags = vocab.tags.split(‘,’).map(t => t.trim()).filter(t => t); const visibleTagsArray = Array.from(visibleTags).filter(t => t !== ‘__NO_TAG__’); return tags.some(tag => visibleTagsArray.includes(tag)) || visibleTagsArray.length === 0; } // 如果詞彙沒有標籤且沒有選擇無標籤,則隱藏 return visibleTags.size === 0 || visibleTags.has(‘__NO_TAG__’); }); filteredVocab.forEach((vocab, index) => { const isSelected = selectedIds.has(vocab.__backendId); if (existingCards.has(vocab.__backendId)) { const card = existingCards.get(vocab.__backendId); updateCardContent(card, vocab, isSelected); card.style.display = ”; } else { const card = createVocabCard(vocab, isSelected); card.style.animationDelay = `${index * 0.05}s`; container.appendChild(card); } }); vocabList.forEach(vocab => { if (!filteredVocab.find(v => v.__backendId === vocab.__backendId)) { const existingCard = existingCards.get(vocab.__backendId); if (existingCard) { existingCard.style.display = ‘none’; } } }); lucide.createIcons(); updateSelectedCount(); } function createVocabCard(vocab, isSelected) { const card = document.createElement(‘div’); card.dataset.vocabId = vocab.__backendId; card.className = `bg-white rounded-xl p-4 shadow-md card-hover fade-in ${isSelected ? ‘ring-2 ring-pink-300’ : ”}`; const tagsHtml = getVisibleTags(vocab); card.innerHTML = ` <div class=”flex items-center gap-4″> <button onclick=”toggleSelect(‘${vocab.__backendId}’)” class=”w-6 h-6 rounded-lg border-2 flex items-center justify-center transition flex-shrink-0 ${isSelected ? ‘bg-pink-400 border-pink-400’ : ‘border-gray-300 hover:border-pink-300’}”> ${isSelected ? ‘<i data-lucide=”check” class=”w-4 h-4 text-white”></i>’ : ”} </button> <div class=”flex-1″> <div class=”flex items-start justify-between gap-4 mb-2″> <h4 class=”text-lg font-bold” style=”color: #6B5B7A;”>${escapeHtml(vocab.word)}</h4> ${tagsHtml ? `<div class=”flex flex-wrap gap-1 justify-end flex-shrink-0″>${tagsHtml}</div>` : ”} </div> <p class=”text-gray-600″>${escapeHtml(vocab.definition)}</p> </div> <button onclick=”showEditModal(‘${vocab.__backendId}’)” class=”p-2 text-gray-400 hover:text-blue-400 transition rounded-lg hover:bg-blue-50 flex-shrink-0″ title=”編輯”> <i data-lucide=”pencil” class=”w-5 h-5″></i> </button> <button onclick=”showDeleteModal(‘${vocab.__backendId}’)” class=”p-2 text-gray-400 hover:text-red-400 transition rounded-lg hover:bg-red-50 flex-shrink-0″ title=”刪除”> <i data-lucide=”trash-2″ class=”w-5 h-5″></i> </button> </div> `; return card; } function getVisibleTags(vocab) { if (!vocab.tags) { return ”; } const tags = vocab.tags.split(‘,’).map(t => t.trim()).filter(t => t); const visibleTagsArray = tags.filter(tag => visibleTags.has(tag)); if (visibleTagsArray.length === 0) return ”; return visibleTagsArray.map(tag => ` <div style=”display: inline-flex; align-items: center; gap: 0.3rem; background-color: #E0E7FF; color: #8B7B9B; font-size: 0.875rem; padding: 0.3rem 0.6rem; border-radius: 9999px;”> #${escapeHtml(tag)} </div> `).join(‘ ‘); } function updateCardContent(card, vocab, isSelected) { card.className = `bg-white rounded-xl p-4 shadow-md card-hover fade-in ${isSelected ? ‘ring-2 ring-pink-300’ : ”}`; const tagsHtml = getVisibleTags(vocab); const selectBtn = card.querySelector(‘button:first-child’); selectBtn.className = `w-6 h-6 rounded-lg border-2 flex items-center justify-center transition flex-shrink-0 ${isSelected ? ‘bg-pink-400 border-pink-400’ : ‘border-gray-300 hover:border-pink-300’}`; selectBtn.innerHTML = isSelected ? ‘<i data-lucide=”check” class=”w-4 h-4 text-white”></i>’ : ”; const wordEl = card.querySelector(‘h4’); const defEl = card.querySelector(‘p’); wordEl.textContent = vocab.word; defEl.textContent = vocab.definition; const tagsContainer = card.querySelector(‘div[style*=”flex-wrap”]’); if (tagsContainer) { tagsContainer.innerHTML = tagsHtml; } } function escapeHtml(text) { const div = document.createElement(‘div’); div.textContent = text; return div.innerHTML; } function toggleSelect(id) { if (selectedIds.has(id)) { selectedIds.delete(id); } else { selectedIds.add(id); } renderVocabList(); } function toggleSelectAll() { const allSelected = vocabList.length > 0 && vocabList.every(v => selectedIds.has(v.__backendId)); if (allSelected) { selectedIds.clear(); } else { vocabList.forEach(v => selectedIds.add(v.__backendId)); } renderVocabList(); updateSelectAllButton(); } function updateSelectAllButton() { const btn = document.getElementById(‘select-all-btn’); const allSelected = vocabList.length > 0 && vocabList.every(v => selectedIds.has(v.__backendId)); btn.innerHTML = allSelected ? ‘<i data-lucide=”square” class=”w-5 h-5″></i> 取消全選’ : ‘<i data-lucide=”check-square” class=”w-5 h-5″></i> 全選’; lucide.createIcons(); } function updateSelectedCount() { document.getElementById(‘selected-count’).textContent = `已選擇 ${selectedIds.size} 個詞彙`; updateSelectAllButton(); } function showDeleteModal(id) { deleteTargetId = id; document.getElementById(‘delete-modal’).classList.remove(‘hidden’); document.getElementById(‘delete-modal’).classList.add(‘flex’); } function cancelDelete() { deleteTargetId = null; document.getElementById(‘delete-modal’).classList.add(‘hidden’); document.getElementById(‘delete-modal’).classList.remove(‘flex’); } async function confirmDelete() { if (!deleteTargetId) return; const vocab = vocabList.find(v => v.__backendId === deleteTargetId); if (vocab) { await window.dataSdk.delete(vocab); selectedIds.delete(deleteTargetId); } cancelDelete(); } function showEditModal(id) { editTargetId = id; const vocab = vocabList.find(v => v.__backendId === id); if (!vocab) return; document.getElementById(‘edit-word-input’).value = vocab.word; document.getElementById(‘edit-definition-input’).value = vocab.definition; editSelectedTags.clear(); if (vocab.tags) { vocab.tags.split(‘,’).forEach(tag => { editSelectedTags.add(tag.trim()); }); } renderEditSelectedTags(); renderEditTagSuggestions(); document.getElementById(‘edit-modal’).classList.remove(‘hidden’); document.getElementById(‘edit-modal’).classList.add(‘flex’); } function cancelEdit() { editTargetId = null; editSelectedTags.clear(); document.getElementById(‘edit-modal’).classList.add(‘hidden’); document.getElementById(‘edit-modal’).classList.remove(‘flex’); } async function confirmEdit() { if (!editTargetId) return; const word = document.getElementById(‘edit-word-input’).value.trim(); const definition = document.getElementById(‘edit-definition-input’).value.trim(); if (!word || !definition) { document.getElementById(‘edit-word-input’).classList.add(‘shake’); document.getElementById(‘edit-definition-input’).classList.add(‘shake’); setTimeout(() => { document.getElementById(‘edit-word-input’).classList.remove(‘shake’); document.getElementById(‘edit-definition-input’).classList.remove(‘shake’); }, 300); return; } const vocab = vocabList.find(v => v.__backendId === editTargetId); if (!vocab) return; const tagsString = Array.from(editSelectedTags).join(‘, ‘); const updatedVocab = { …vocab, word, definition, tags: tagsString }; const result = await window.dataSdk.update(updatedVocab); if (result.isOk) { cancelEdit(); } } function renderEditSelectedTags() { const container = document.getElementById(‘edit-selected-tags-container’); container.innerHTML = ”; if (editSelectedTags.size === 0) { container.innerHTML = ‘<span style=”color: #9CA3AF; font-size: 0.875rem;”>未選擇標籤</span>’; } else { editSelectedTags.forEach(tag => { const tagEl = document.createElement(‘div’); tagEl.className = ‘tag-badge selected’; tagEl.innerHTML = `<span>${escapeHtml(tag)}</span><button onclick=”event.stopPropagation(); removeEditTag(‘${escapeHtml(tag)}’)” class=”hover:opacity-70″><i data-lucide=”x” class=”w-3 h-3″></i></button>`; container.appendChild(tagEl); }); } lucide.createIcons(); } function removeEditTag(tag) { editSelectedTags.delete(tag); renderEditSelectedTags(); renderEditTagSuggestions(); } function addNewEditTag() { const input = document.getElementById(‘edit-new-tag-input’); const tag = input.value.trim(); if (!tag) return; editSelectedTags.add(tag); allTags.add(tag); input.value = ”; renderEditSelectedTags(); renderEditTagSuggestions(); input.focus(); } function renderEditTagSuggestions() { const container = document.getElementById(‘edit-tag-suggestions’); container.innerHTML = ”; allTags.forEach(tag => { const tagEl = document.createElement(‘button’); tagEl.className = `tag-badge ${editSelectedTags.has(tag) ? ‘selected’ : ”}`; tagEl.onclick = (e) => { e.preventDefault(); toggleEditTag(tag); }; tagEl.innerHTML = `<span>${escapeHtml(tag)}</span>`; container.appendChild(tagEl); }); } function toggleEditTag(tag) { if (editSelectedTags.has(tag)) { editSelectedTags.delete(tag); } else { editSelectedTags.add(tag); } renderEditSelectedTags(); renderEditTagSuggestions(); } function showFilterTagsModal() { const container = document.getElementById(‘filter-tag-list’); container.innerHTML = ”; // 無標籤按鈕 const noTagBtn = document.createElement(‘button’); const noTagSelected = filterSelectedTags.has(‘__NO_TAG__’); noTagBtn.className = `tag-badge ${noTagSelected ? ‘selected’ : ”}`; noTagBtn.style.display = ‘inline-flex’; noTagBtn.style.alignItems = ‘center’; noTagBtn.style.gap = ‘0.5rem’; noTagBtn.style.padding = ‘0.4rem 0.8rem’; noTagBtn.style.borderRadius = ‘9999px’; noTagBtn.style.fontSize = ‘0.875rem’; noTagBtn.style.fontWeight = ‘500’; noTagBtn.style.backgroundColor = noTagSelected ? ‘#FFB5BA’ : ‘#E0E7FF’; noTagBtn.style.color = noTagSelected ? ‘white’ : ‘#6B7280’; noTagBtn.style.cursor = ‘pointer’; noTagBtn.style.transition = ‘all 0.2s ease’; noTagBtn.style.border = ‘none’; noTagBtn.style.marginRight = ‘0.5rem’; noTagBtn.style.marginBottom = ‘0.5rem’; noTagBtn.onclick = (e) => { e.preventDefault(); toggleFilterTag(‘__NO_TAG__’); updateFilterTagDisplay(container, ‘__NO_TAG__’); }; noTagBtn.innerHTML = `<span>無標籤</span>`; container.appendChild(noTagBtn); if (allTags.size === 0) { // 如果沒有標籤,只顯示無標籤選項 } else { allTags.forEach(tag => { const isSelected = filterSelectedTags.has(tag); const tagEl = document.createElement(‘button’); tagEl.className = `tag-badge ${isSelected ? ‘selected’ : ”}`; tagEl.style.display = ‘inline-flex’; tagEl.style.alignItems = ‘center’; tagEl.style.gap = ‘0.5rem’; tagEl.style.padding = ‘0.4rem 0.8rem’; tagEl.style.borderRadius = ‘9999px’; tagEl.style.fontSize = ‘0.875rem’; tagEl.style.fontWeight = ‘500’; tagEl.style.backgroundColor = isSelected ? ‘#FFB5BA’ : ‘#E0E7FF’; tagEl.style.color = isSelected ? ‘white’ : ‘#6B7280’; tagEl.style.cursor = ‘pointer’; tagEl.style.transition = ‘all 0.2s ease’; tagEl.style.border = ‘none’; tagEl.style.marginRight = ‘0.5rem’; tagEl.style.marginBottom = ‘0.5rem’; tagEl.onclick = (e) => { e.preventDefault(); toggleFilterTag(tag); updateFilterTagDisplay(container, tag); }; tagEl.innerHTML = `<span>${escapeHtml(tag)}</span>`; container.appendChild(tagEl); }); } document.getElementById(‘filter-tags-modal’).classList.remove(‘hidden’); document.getElementById(‘filter-tags-modal’).classList.add(‘flex’); lucide.createIcons(); } function updateFilterTagDisplay(container, changedTag) { const buttons = container.querySelectorAll(‘button’); buttons.forEach(btn => { const tagText = btn.querySelector(‘span’).textContent; if (tagText === changedTag) { const isNowSelected = filterSelectedTags.has(changedTag); btn.style.backgroundColor = isNowSelected ? ‘#FFB5BA’ : ‘#E0E7FF’; btn.style.color = isNowSelected ? ‘white’ : ‘#6B7280’; if (isNowSelected) { btn.classList.add(‘selected’); } else { btn.classList.remove(‘selected’); } } }); } function closeFilterTagsModal() { filterSelectedTags.clear(); document.getElementById(‘filter-tags-modal’).classList.add(‘hidden’); document.getElementById(‘filter-tags-modal’).classList.remove(‘flex’); } function toggleFilterTag(tag) { if (filterSelectedTags.has(tag)) { filterSelectedTags.delete(tag); } else { filterSelectedTags.add(tag); } } function confirmFilterTags() { if (filterSelectedTags.size === 0) { closeFilterTagsModal(); return; } selectedIds.clear(); vocabList.forEach(vocab => { const hasNoTags = !vocab.tags || vocab.tags.trim() === ”; if (filterSelectedTags.has(‘__NO_TAG__’) && hasNoTags) { selectedIds.add(vocab.__backendId); } else if (vocab.tags) { const tags = vocab.tags.split(‘,’).map(t => t.trim()); const filterTagsArray = Array.from(filterSelectedTags).filter(t => t !== ‘__NO_TAG__’); if (tags.some(tag => filterTagsArray.includes(tag))) { selectedIds.add(vocab.__backendId); } } }); renderVocabList(); closeFilterTagsModal(); } function showTagVisibilityModal() { const container = document.getElementById(‘visibility-tag-list’); container.innerHTML = ”; // 無標籤按鈕 const noTagBtn = document.createElement(‘button’); const noTagVisible = visibleTags.has(‘__NO_TAG__’); noTagBtn.style.display = ‘inline-flex’; noTagBtn.style.alignItems = ‘center’; noTagBtn.style.gap = ‘0.5rem’; noTagBtn.style.padding = ‘0.4rem 0.8rem’; noTagBtn.style.borderRadius = ‘9999px’; noTagBtn.style.fontSize = ‘0.875rem’; noTagBtn.style.fontWeight = ‘500’; noTagBtn.style.backgroundColor = noTagVisible ? ‘#FFB5BA’ : ‘#E0E7FF’; noTagBtn.style.color = noTagVisible ? ‘white’ : ‘#6B7280’; noTagBtn.style.cursor = ‘pointer’; noTagBtn.style.transition = ‘all 0.2s ease’; noTagBtn.style.border = ‘none’; noTagBtn.style.marginRight = ‘0.5rem’; noTagBtn.style.marginBottom = ‘0.5rem’; noTagBtn.innerHTML = `<span>無標籤</span>`; noTagBtn.addEventListener(‘click’, (e) => { e.preventDefault(); toggleTagVisibility(‘__NO_TAG__’); showTagVisibilityModal(); }); container.appendChild(noTagBtn); const allTagsArray = Array.from(allTags); if (allTagsArray.length > 0) { allTagsArray.forEach(tag => { const isVisible = visibleTags.has(tag); const tagEl = document.createElement(‘button’); tagEl.style.display = ‘inline-flex’; tagEl.style.alignItems = ‘center’; tagEl.style.gap = ‘0.5rem’; tagEl.style.padding = ‘0.4rem 0.8rem’; tagEl.style.borderRadius = ‘9999px’; tagEl.style.fontSize = ‘0.875rem’; tagEl.style.fontWeight = ‘500’; tagEl.style.backgroundColor = isVisible ? ‘#FFB5BA’ : ‘#E0E7FF’; tagEl.style.color = isVisible ? ‘white’ : ‘#6B7280’; tagEl.style.cursor = ‘pointer’; tagEl.style.transition = ‘all 0.2s ease’; tagEl.style.border = ‘none’; tagEl.style.marginRight = ‘0.5rem’; tagEl.style.marginBottom = ‘0.5rem’; tagEl.innerHTML = `<span>${escapeHtml(tag)}</span>`; tagEl.addEventListener(‘click’, (e) => { e.preventDefault(); toggleTagVisibility(tag); showTagVisibilityModal(); }); container.appendChild(tagEl); }); } document.getElementById(‘tag-visibility-modal’).classList.remove(‘hidden’); document.getElementById(‘tag-visibility-modal’).classList.add(‘flex’); lucide.createIcons(); } function toggleTagVisibility(tag) { if (visibleTags.has(tag)) { visibleTags.delete(tag); } else { visibleTags.add(tag); } renderVocabList(); } function closeTagVisibilityModal() { document.getElementById(‘tag-visibility-modal’).classList.add(‘hidden’); document.getElementById(‘tag-visibility-modal’).classList.remove(‘flex’); renderVocabList(); } function confirmTagVisibility() { closeTagVisibilityModal(); } function cancelDeleteTag() { pendingDeleteTag = null; document.getElementById(‘delete-tag-modal’).classList.add(‘hidden’); document.getElementById(‘delete-tag-modal’).classList.remove(‘flex’); } async function confirmDeleteTag() { if (!pendingDeleteTag) return; const tagToDelete = pendingDeleteTag; for (const vocab of vocabList) { if (vocab.tags) { const tags = vocab.tags.split(‘,’).map(t => t.trim()).filter(t => t); if (tags.includes(tagToDelete)) { const updatedTags = tags.filter(t => t !== tagToDelete); const updatedVocab = { …vocab, tags: updatedTags.join(‘, ‘) }; await window.dataSdk.update(updatedVocab); } } } allTags.delete(tagToDelete); visibleTags.delete(tagToDelete); selectedTags.delete(tagToDelete); cancelDeleteTag(); renderTagSuggestions(); renderVocabList(); } function showBulkDeleteModal() { if (selectedIds.size === 0) { const deleteBtn = document.querySelector(‘button[onclick=”showBulkDeleteModal()”]’); deleteBtn.classList.add(‘shake’); setTimeout(() => deleteBtn.classList.remove(‘shake’), 300); return; } document.getElementById(‘bulk-delete-count’).textContent = `即將刪除 ${selectedIds.size} 個詞彙`; document.getElementById(‘bulk-delete-modal’).classList.remove(‘hidden’); document.getElementById(‘bulk-delete-modal’).classList.add(‘flex’); } function cancelBulkDelete() { document.getElementById(‘bulk-delete-modal’).classList.add(‘hidden’); document.getElementById(‘bulk-delete-modal’).classList.remove(‘flex’); } async function confirmBulkDelete() { const idsToDelete = Array.from(selectedIds); for (const id of idsToDelete) { const vocab = vocabList.find(v => v.__backendId === id); if (vocab) { await window.dataSdk.delete(vocab); } } selectedIds.clear(); cancelBulkDelete(); } function startQuiz() { if (selectedIds.size < 2) { const quizBtn = document.getElementById(‘quiz-btn’); quizBtn.classList.add(‘shake’); setTimeout(() => quizBtn.classList.remove(‘shake’), 300); return; } document.getElementById(‘quiz-mode-modal’).classList.remove(‘hidden’); document.getElementById(‘quiz-mode-modal’).classList.add(‘flex’); lucide.createIcons(); } function closeQuizModeModal() { document.getElementById(‘quiz-mode-modal’).classList.add(‘hidden’); document.getElementById(‘quiz-mode-modal’).classList.remove(‘flex’); } function startQuizWithMode(mode) { quizModeSelected = mode; const selectedVocabs = vocabList.filter(v => selectedIds.has(v.__backendId)); quizQuestions = shuffleArray([…selectedVocabs]); currentQuizIndex = 0; correctCount = 0; closeQuizModeModal(); showQuizPage(); showQuestion(); } function showQuestion() { const quizCard = document.getElementById(‘quiz-card’); const quizFinal = document.getElementById(‘quiz-final’); if (currentQuizIndex >= quizQuestions.length) { quizCard.classList.add(‘hidden’); quizFinal.classList.remove(‘hidden’); document.getElementById(‘final-score’).textContent = `答對 ${correctCount} 題,共 ${quizQuestions.length} 題(正確率:${Math.round(correctCount / quizQuestions.length * 100)}%)`; return; } quizCard.classList.remove(‘hidden’); quizFinal.classList.add(‘hidden’); answered = false; const current = quizQuestions[currentQuizIndex]; document.getElementById(‘quiz-progress’).textContent = `第 ${currentQuizIndex + 1} / ${quizQuestions.length} 題`; document.getElementById(‘quiz-word’).textContent = current.word; if (quizModeSelected === ‘choice’) { showChoiceQuestion(current); } else { showFreeQuestion(); } } function showChoiceQuestion(current) { const wrongOptions = vocabList .filter(v => v.__backendId !== current.__backendId) .sort(() => Math.random() – 0.5) .slice(0, 3); const options = shuffleArray([current, …wrongOptions]); const optionsContainer = document.getElementById(‘quiz-options’); optionsContainer.innerHTML = options.map(opt => ` <button onclick=”selectAnswer(‘${opt.__backendId}’, ‘${current.__backendId}’)” data-option-id=”${opt.__backendId}” class=”w-full p-4 rounded-xl border-2 border-gray-200 text-left hover:border-pink-300 transition font-medium”> ${escapeHtml(opt.definition)} </button> `).join(”); document.getElementById(‘quiz-result-choice’).classList.add(‘hidden’); document.getElementById(‘choice-mode’).style.display = ‘block’; document.getElementById(‘free-mode’).style.display = ‘none’; } function showFreeQuestion() { document.getElementById(‘free-answer’).value = ”; document.getElementById(‘quiz-result-free’).classList.add(‘hidden’); resetFreeAnswerButton(); document.getElementById(‘choice-mode’).style.display = ‘none’; document.getElementById(‘free-mode’).style.display = ‘block’; document.getElementById(‘free-answer’).focus(); } function selectAnswer(selectedId, correctId) { if (answered) return; answered = true; const isCorrect = selectedId === correctId; if (isCorrect) correctCount++; const options = document.querySelectorAll(‘#quiz-options button’); options.forEach(btn => { const optId = btn.dataset.optionId; if (optId === correctId) { btn.classList.remove(‘border-gray-200’); btn.classList.add(‘border-green-400’, ‘bg-green-50’); } else if (optId === selectedId && !isCorrect) { btn.classList.remove(‘border-gray-200’); btn.classList.add(‘border-red-400’, ‘bg-red-50’); } btn.disabled = true; }); const resultDiv = document.getElementById(‘quiz-result-choice’); const resultText = document.getElementById(‘result-text’); const correctAnswer = document.getElementById(‘correct-answer’); resultText.textContent = isCorrect ? ‘ 答對了!’ : ‘ 答錯了’; resultText.style.color = isCorrect ? ‘#10B981’ : ‘#EF4444’; const correctVocab = quizQuestions[currentQuizIndex]; correctAnswer.textContent = `正確答案:${correctVocab.definition}`; correctAnswer.style.marginBottom = ‘1.5rem’; const nextBtn = document.createElement(‘button’); nextBtn.className = ‘macaron-mint text-white px-8 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition’; nextBtn.textContent = ‘下一題’; nextBtn.onclick = () => nextQuestion(); resultDiv.innerHTML = ”; resultDiv.appendChild(resultText); resultDiv.appendChild(correctAnswer); resultDiv.appendChild(nextBtn); resultDiv.classList.remove(‘hidden’); lucide.createIcons(); } function submitFreeAnswer() { if (answered) return; answered = true; const userAnswer = document.getElementById(‘free-answer’).value.trim(); const correctVocab = quizQuestions[currentQuizIndex]; const submitBtn = document.getElementById(‘submit-free-btn’); const userLower = userAnswer.toLowerCase(); const correctLower = correctVocab.definition.toLowerCase(); const similarity = calculateSimilarity(userLower, correctLower); const isCorrect = similarity > 0.5; if (isCorrect) correctCount++; const resultDiv = document.getElementById(‘quiz-result-free’); const resultText = document.getElementById(‘result-text-free’); resultText.textContent = isCorrect ? ‘ 答對了!’ : ‘ 答錯了’; resultText.style.color = isCorrect ? ‘#10B981’ : ‘#EF4444’; document.getElementById(‘user-answer’).textContent = userAnswer || ‘(未作答)’; document.getElementById(‘correct-answer-free’).textContent = correctVocab.definition; submitBtn.className = ‘macaron-mint text-white px-8 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition’; submitBtn.innerHTML = ‘<i data-lucide=”arrow-right” class=”w-5 h-5 mr-2″></i>下一題’; submitBtn.onclick = () => nextQuestion(); resultDiv.classList.remove(‘hidden’); lucide.createIcons(); } function resetFreeAnswerButton() { const submitBtn = document.getElementById(‘submit-free-btn’); submitBtn.className = ‘macaron-yellow px-8 py-3 rounded-xl font-medium shadow-md hover:shadow-lg transition’; submitBtn.style.color = ‘#5C4033’; submitBtn.innerHTML = ‘<i data-lucide=”check” class=”w-5 h-5 mr-2″></i>提交答案’; submitBtn.onclick = () => submitFreeAnswer(); } function calculateSimilarity(str1, str2) { if (str1 === str2) return 1; const longer = str1.length > str2.length ? str1 : str2; const shorter = str1.length > str2.length ? str2 : str1; if (longer.length === 0) return 1; const editDistance = getEditDistance(shorter, longer); return (longer.length – editDistance) / longer.length; } function getEditDistance(s1, s2) { const costs = []; for (let i = 0; i <= s1.length; i++) { let lastValue = i; for (let j = 0; j <= s2.length; j++) { if (i === 0) { costs[j] = j; } else if (j > 0) { let newValue = costs[j – 1]; if (s1.charAt(i – 1) !== s2.charAt(j – 1)) { newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1; } costs[j – 1] = lastValue; lastValue = newValue; } } if (i > 0) costs[s2.length] = lastValue; } return costs[s2.length]; } function nextQuestion() { currentQuizIndex++; showQuestion(); } function exitQuiz() { showVocabPage(); } function shuffleArray(array) { const arr = […array]; for (let i = arr.length – 1; i > 0; i–) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } lucide.createIcons(); </script> <script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement(‘script’);d.innerHTML=”window.__CF$cv$params={r:’a075e0a83b59dd67′,t:’MTc4MDczMjc2NQ==’};var a=document.createElement(‘script’);a.src=’/cdn-cgi/challenge-platform/scripts/jsd/main.js’;document.getElementsByTagName(‘head’)[0].appendChild(a);”;b.getElementsByTagName(‘head’)[0].appendChild(d)}}if(document.body){var a=document.createElement(‘iframe’);a.height=1;a.width=1;a.style.position=’absolute’;a.style.top=0;a.style.left=0;a.style.border=’none’;a.style.visibility=’hidden’;document.body.appendChild(a);if(‘loading’!==document.readyState)c();else if(window.addEventListener)document.addEventListener(‘DOMContentLoaded’,c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);’loading’!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>
