Files
42_104/caddy/www/english_units.html
2026-06-23 19:33:50 +08:00

2957 lines
92 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wonder World — Unit 1 & 2</title>
<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=JetBrains+Mono:wght@400;500&family=Caveat:wght@400;500;600;700&family=Shadows+Into+Light&display=swap" rel="stylesheet">
<script>window.__audioManifest = {};</script>
<script src="audio/manifest.js" onerror="void 0"></script>
<style>
/* LINE Seed TW — 主要字體 */
@font-face {
font-family: 'LINE Seed TW';
src: url('fonts/LINESeedTW_OTF_Th.woff2') format('woff2');
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'LINE Seed TW';
src: url('fonts/LINESeedTW_OTF_Rg.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'LINE Seed TW';
src: url('fonts/LINESeedTW_OTF_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'LINE Seed TW';
src: url('fonts/LINESeedTW_OTF_Eb.woff2') format('woff2');
font-weight: 800;
font-style: normal;
font-display: swap;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
/* Claude / Anthropic 色票 */
--parchment: #f5f4ed;
--ivory: #faf9f5;
--white: #ffffff;
--sand: #e8e6dc;
--sand-deep: #dedcd0;
--near-black: #141413;
--dark-surface: #30302e;
--charcoal: #4d4c48;
--olive: #5e5d59;
--stone: #87867f;
--silver: #b0aea5;
--terracotta: #c96442;
--coral: #d97757;
--terracotta-ink: #a8502f;
--border-cream: #f0eee6;
--border-warm: #e8e6dc;
--ring-warm: #d1cfc5;
--ring-deep: #c2c0b6;
--focus-blue: #3898ec;
--error: #b53333;
--serif: 'LINE Seed TW', 'PingFang TC', 'Noto Sans TC', -apple-system, BlinkMacSystemFont, sans-serif;
--sans: 'LINE Seed TW', 'PingFang TC', 'Noto Sans TC', -apple-system, BlinkMacSystemFont, sans-serif;
--mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
--zh-serif: 'LINE Seed TW', 'PingFang TC', 'Noto Sans TC', sans-serif;
--hand: 'Shadows Into Light', 'Caveat', 'LINE Seed TW', sans-serif;
--hand-en: 'Caveat', 'LINE Seed TW', cursive;
}
html { scroll-behavior: smooth; }
body {
font-family: var(--sans);
color: var(--near-black);
background: var(--parchment);
min-height: 100vh;
font-feature-settings: 'ss01', 'ss02';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.5;
position: relative;
}
/* 細微紙面雜訊紋理 */
body::before {
content: '';
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.35'/></svg>");
opacity: 0.04;
pointer-events: none;
z-index: 0;
mix-blend-mode: multiply;
}
::selection { background: var(--terracotta); color: var(--ivory); }
/* ===== 人味:手寫註記與塗鴉 ===== */
.hand {
font-family: var(--hand);
font-weight: 400;
}
.hand-en {
font-family: var(--hand-en);
font-weight: 500;
}
.margin-note {
font-family: var(--hand);
color: var(--terracotta);
font-size: 18px;
line-height: 1.2;
display: inline-block;
}
.sticky-note {
position: absolute;
font-family: var(--hand);
color: var(--coral);
font-size: 19px;
line-height: 1.25;
pointer-events: none;
z-index: 3;
}
/* 手繪箭頭/曲線裝飾 */
.doodle {
position: absolute;
pointer-events: none;
opacity: 0.85;
z-index: 2;
}
/* 底線用手繪波浪筆觸 */
.underline {
background: none;
padding: 0 1px 4px;
font-weight: 700;
color: var(--terracotta-ink);
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 12' preserveAspectRatio='none'><path d='M2 8 Q 15 2, 28 7 T 55 7 T 82 7 T 98 6' stroke='%23c96442' stroke-width='2.2' fill='none' stroke-linecap='round' opacity='0.85'/></svg>");
background-repeat: no-repeat;
background-size: 100% 10px;
background-position: 0 100%;
}
/* ===== 頁首:像一本書的扉頁 ===== */
.masthead {
position: relative;
background: var(--near-black);
color: var(--ivory);
padding: 72px 24px 88px;
overflow: hidden;
border-bottom: 1px solid var(--dark-surface);
}
.masthead-inner {
max-width: 960px;
margin: 0 auto;
position: relative;
z-index: 2;
text-align: center;
}
.overline {
font-family: var(--sans);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--coral);
margin-bottom: 22px;
display: inline-flex;
align-items: center;
gap: 10px;
}
.overline::before, .overline::after {
content: '';
width: 24px;
height: 1px;
background: var(--coral);
opacity: 0.5;
}
.masthead h1 {
font-family: var(--serif);
font-weight: 800;
font-size: clamp(44px, 8vw, 88px);
line-height: 1.04;
letter-spacing: -0.01em;
color: var(--ivory);
font-optical-sizing: auto;
position: relative;
display: inline-block;
}
.masthead h1 em {
color: var(--coral);
font-weight: 800;
font-style: normal;
position: relative;
display: inline-block;
isolation: isolate;
}
.masthead h1 em::before {
content: '';
position: absolute;
inset: -14% -10% -8% -10%;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 100' preserveAspectRatio='none'><path d='M18 50 Q 12 18, 95 12 Q 180 6, 186 48 Q 192 90, 100 92 Q 8 94, 18 50 Z' stroke='%23faf9f5' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round' opacity='0.55'/></svg>");
background-repeat: no-repeat;
background-size: 100% 100%;
transform: rotate(-2deg);
pointer-events: none;
z-index: -1;
}
/* 手寫題辭 */
.handwritten-tag {
font-family: var(--hand);
color: var(--coral);
font-size: 22px;
transform: rotate(-3deg);
display: inline-block;
margin-bottom: 8px;
opacity: 0.92;
letter-spacing: 0.02em;
}
.handwritten-tag::before {
content: '— ';
opacity: 0.6;
}
.masthead .dek {
margin-top: 20px;
font-family: var(--serif);
font-weight: 400;
font-size: clamp(16px, 2vw, 19px);
line-height: 1.6;
color: var(--silver);
max-width: 560px;
margin-left: auto;
margin-right: auto;
}
.masthead .meta-row {
margin-top: 34px;
display: flex;
justify-content: center;
gap: 32px;
flex-wrap: wrap;
font-size: 13px;
color: var(--silver);
letter-spacing: 0.02em;
}
.masthead .meta-row span {
display: inline-flex;
align-items: center;
gap: 8px;
}
.masthead .meta-row .dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--terracotta);
display: inline-block;
}
/* 裝飾 SVG */
.ornament {
position: absolute;
opacity: 0.2;
z-index: 1;
pointer-events: none;
}
.ornament.left { top: 38%; left: -40px; transform: rotate(-12deg); }
.ornament.right { bottom: 10%; right: -30px; transform: rotate(18deg); }
/* ===== Unit 切換:期刊式頁籤 ===== */
.tabs-wrap {
background: var(--parchment);
border-bottom: 1px solid var(--border-warm);
position: sticky;
top: 0;
z-index: 50;
backdrop-filter: saturate(1.2);
}
.tabs {
max-width: 960px;
margin: 0 auto;
padding: 0 24px;
display: flex;
gap: 0;
align-items: stretch;
overflow-x: auto;
scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
background: transparent;
border: none;
padding: 22px 4px;
margin-right: 22px;
flex-shrink: 0;
font-family: var(--serif);
font-weight: 700;
font-size: 17px;
color: var(--stone);
cursor: pointer;
position: relative;
transition: color 0.25s ease;
letter-spacing: -0.005em;
}
.tab .tab-num {
font-family: var(--mono);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.15em;
color: var(--stone);
display: block;
margin-bottom: 4px;
transition: color 0.25s ease;
}
.tab:hover { color: var(--near-black); }
.tab:hover .tab-num { color: var(--terracotta); }
.tab.active { color: var(--near-black); }
.tab.active .tab-num { color: var(--terracotta); }
.tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background: var(--terracotta);
}
/* ===== 主體 ===== */
main {
max-width: 960px;
margin: 0 auto;
padding: 56px 24px 120px;
position: relative;
z-index: 1;
}
.unit {
display: none;
animation: fadeUp 0.5s cubic-bezier(.2,.7,.2,1) both;
}
.unit.active { display: block; }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
/* 章節 */
.section {
margin-bottom: 64px;
animation: fadeUp 0.6s cubic-bezier(.2,.7,.2,1) both;
}
.unit.active .section:nth-child(1) { animation-delay: 0.02s; }
.unit.active .section:nth-child(2) { animation-delay: 0.08s; }
.unit.active .section:nth-child(3) { animation-delay: 0.14s; }
.unit.active .section:nth-child(4) { animation-delay: 0.2s; }
.unit.active .section:nth-child(5) { animation-delay: 0.26s; }
.unit.active .section:nth-child(6) { animation-delay: 0.32s; }
.unit.active .section:nth-child(7) { animation-delay: 0.38s; }
.section-header {
display: flex;
align-items: baseline;
gap: 18px;
margin-bottom: 28px;
padding-bottom: 18px;
border-bottom: 1px solid var(--border-warm);
}
.section-icon {
font-family: var(--mono);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.18em;
color: var(--terracotta);
text-transform: uppercase;
padding: 4px 10px;
border: 1px solid var(--terracotta);
border-radius: 999px;
background: transparent;
line-height: 1;
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
width: auto;
height: auto;
justify-content: flex-start;
box-shadow: none;
}
.section-title {
font-family: var(--serif);
font-weight: 700;
font-size: clamp(28px, 4.2vw, 40px);
line-height: 1.15;
color: var(--near-black);
letter-spacing: -0.02em;
flex: 1;
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 14px;
}
.section-title .zh {
font-family: var(--zh-serif);
font-size: 16px;
font-weight: 400;
color: var(--olive);
margin-left: 0;
letter-spacing: 0.02em;
}
/* 每個章節旁邊的手寫小註 */
.section-note {
font-family: var(--hand);
color: var(--terracotta);
font-size: 19px;
line-height: 1.35;
margin-left: auto;
transform: rotate(-1.5deg);
padding-left: 18px;
max-width: 220px;
text-align: right;
flex-shrink: 0;
opacity: 0.88;
}
.section-note::before {
content: '✎ ';
opacity: 0.6;
margin-right: 2px;
}
@media (max-width: 760px) {
.section-note {
margin-left: 0;
text-align: left;
padding-left: 0;
max-width: 100%;
font-size: 17px;
}
}
/* ===== Story 對話列表 ===== */
#story-1, #story-2 {
background: var(--ivory);
border: 1px solid var(--border-cream);
border-radius: 16px;
overflow: hidden;
}
.story-item {
display: flex;
gap: 18px;
padding: 20px 24px;
cursor: pointer;
align-items: flex-start;
border-top: 1px solid var(--border-cream);
transition: background 0.2s ease;
}
.story-item:first-child { border-top: none; }
.story-item:hover {
background: var(--parchment);
}
.story-item.playing {
background: var(--parchment);
box-shadow: inset 3px 0 0 var(--terracotta);
}
.num {
flex-shrink: 0;
width: 28px;
font-family: var(--mono);
font-size: 12px;
font-weight: 500;
color: var(--terracotta);
letter-spacing: 0.02em;
padding-top: 4px;
background: transparent;
border: none;
border-radius: 0;
height: auto;
display: block;
text-align: left;
}
.num::before { content: '·'; margin-right: 4px; color: var(--ring-deep); }
.story-content { flex: 1; min-width: 0; }
.en {
font-family: var(--serif);
font-size: 20px;
font-weight: 700;
line-height: 1.35;
margin-bottom: 4px;
color: var(--near-black);
letter-spacing: -0.01em;
}
.zh-trans {
font-family: var(--zh-serif);
font-size: 14.5px;
color: var(--olive);
line-height: 1.65;
font-weight: 400;
}
.speak-btn {
flex-shrink: 0;
width: 36px;
height: 36px;
background: var(--sand);
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: var(--charcoal);
transition: all 0.2s ease;
box-shadow: 0 0 0 1px var(--ring-warm);
margin-top: 2px;
}
.speak-btn:hover {
background: var(--terracotta);
color: var(--ivory);
box-shadow: 0 0 0 1px var(--terracotta);
transform: translateY(-1px);
}
.speak-btn:active {
transform: translateY(0);
}
/* ===== Vocabulary 卡片網格 ===== */
.vocab-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 14px;
}
.vocab-card {
background: var(--ivory);
border: 1px solid var(--border-cream);
border-radius: 14px;
padding: 24px 22px;
cursor: pointer;
transition: all 0.25s ease;
position: relative;
overflow: hidden;
}
.vocab-card::after {
content: '';
position: absolute;
top: 18px;
right: 18px;
width: 6px;
height: 6px;
background: var(--terracotta);
border-radius: 50%;
opacity: 0.35;
transition: opacity 0.25s ease, transform 0.25s ease;
}
.vocab-card:hover {
border-color: var(--ring-deep);
box-shadow: 0 0 0 1px var(--ring-warm), rgba(0,0,0,0.04) 0 4px 16px;
transform: translateY(-2px);
}
.vocab-card:hover::after {
opacity: 1;
transform: scale(1.4);
}
.vocab-en {
font-family: var(--serif);
font-weight: 700;
font-size: 22px;
margin-bottom: 6px;
color: var(--near-black);
letter-spacing: -0.01em;
line-height: 1.2;
}
.vocab-zh {
font-family: var(--zh-serif);
font-size: 14px;
color: var(--stone);
font-weight: 400;
}
/* ===== Sentences / Daily ===== */
.sentence-list {
background: var(--ivory);
border: 1px solid var(--border-cream);
border-radius: 16px;
overflow: hidden;
}
.sentence-item {
display: flex;
gap: 18px;
padding: 22px 26px;
background: transparent;
border: none;
border-top: 1px solid var(--border-cream);
border-radius: 0;
cursor: pointer;
transition: background 0.2s ease;
align-items: flex-start;
box-shadow: none;
}
.sentence-item:first-child { border-top: none; }
.sentence-item:hover {
background: var(--parchment);
}
.sentence-item.playing {
background: var(--parchment);
box-shadow: inset 3px 0 0 var(--terracotta);
}
.sentence-content { flex: 1; }
.underline {
background: linear-gradient(to top, rgba(201,100,66,0.18) 42%, transparent 42%);
padding: 0 2px;
font-weight: 700;
color: var(--terracotta-ink);
}
.chunk-sep {
display: inline-block;
margin: 0 6px;
color: var(--terracotta);
opacity: 0.42;
font-weight: 400;
font-family: var(--serif);
transform: translateY(-1px);
}
/* ===== Phonics ===== */
.phonics-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
}
.phonics-group {
background: var(--ivory);
border: 1px solid var(--border-cream);
border-radius: 16px;
padding: 26px 24px;
}
.phonics-tag {
display: inline-flex;
align-items: baseline;
gap: 8px;
background: transparent;
color: var(--near-black);
padding: 0 0 14px 0;
font-family: var(--serif);
font-weight: 800;
font-size: 36px;
letter-spacing: -0.03em;
border: none;
border-bottom: 1px solid var(--border-warm);
margin-bottom: 14px;
box-shadow: none;
width: 100%;
border-radius: 0;
}
.phonics-tag::before {
content: 'sound';
font-family: var(--mono);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.2em;
color: var(--stone);
text-transform: uppercase;
margin-right: auto;
order: 2;
padding-bottom: 4px;
}
.phonics-tag {
flex-direction: row-reverse;
justify-content: flex-end;
}
.phonics-word {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 4px;
cursor: pointer;
border-radius: 8px;
transition: all 0.15s ease;
margin-bottom: 2px;
}
.phonics-word:hover {
background: var(--parchment);
padding-left: 10px;
}
.phonics-en {
font-family: var(--serif);
font-weight: 700;
font-size: 19px;
color: var(--near-black);
}
.phonics-en .star {
color: var(--terracotta);
margin-right: 6px;
font-size: 10px;
vertical-align: middle;
}
.phonics-zh {
font-family: var(--zh-serif);
font-size: 14px;
color: var(--olive);
}
/* ===== 發音小提示 ===== */
.tips-wrapper {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 14px;
}
.tip-card {
background: var(--parchment);
border: 1px dashed var(--ring-deep);
border-radius: 14px;
padding: 20px 22px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
}
.tip-card:hover {
background: var(--ivory);
border-style: solid;
transform: translateY(-1px);
}
.tip-card.playing {
background: var(--ivory);
border-style: solid;
border-color: var(--terracotta);
}
.tip-head {
display: flex;
align-items: baseline;
gap: 10px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.tip-en {
font-family: var(--serif);
font-weight: 700;
font-size: 21px;
color: var(--near-black);
letter-spacing: -0.01em;
}
.tip-phonetic {
font-family: var(--zh-sans);
font-size: 15px;
color: var(--terracotta-ink);
font-weight: 600;
}
.tip-note {
font-family: var(--hand);
font-size: 17px;
color: var(--charcoal);
line-height: 1.55;
}
/* ===== 手寫便利貼風的提示 ===== */
.hint {
text-align: center;
margin: -44px auto 56px;
font-family: var(--hand);
color: var(--charcoal);
font-size: 21px;
line-height: 1.4;
transform: rotate(-1.2deg);
display: block;
width: fit-content;
padding: 6px 14px;
}
.hint::before {
content: '↳';
color: var(--terracotta);
font-family: var(--hand);
font-size: 22px;
margin-right: 6px;
display: inline-block;
transform: rotate(12deg);
}
.hint-badge {
display: none;
}
/* ===== 測驗區 ===== */
.section.quiz {
background: var(--near-black);
margin: 0 -24px 72px;
padding: 72px 48px;
border-radius: 0;
border: none;
color: var(--ivory);
}
.section.quiz .section-header {
border-bottom-color: var(--dark-surface);
}
.section.quiz .section-icon {
color: var(--coral);
border-color: var(--coral);
}
.section.quiz .section-title {
color: var(--ivory);
}
.section.quiz .section-title .zh {
color: var(--silver);
}
.section.quiz .quiz-home > p {
font-family: var(--serif);
color: var(--silver) !important;
font-size: 17px !important;
margin-bottom: 24px !important;
line-height: 1.6;
}
.quiz-modes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
margin-top: 20px;
}
.mode-btn {
background: var(--dark-surface);
border: 1px solid var(--dark-surface);
border-radius: 14px;
padding: 26px 22px;
cursor: pointer;
transition: all 0.25s ease;
box-shadow: none;
font-family: inherit;
text-align: left;
color: var(--ivory);
position: relative;
overflow: hidden;
}
.mode-btn::after {
content: '→';
position: absolute;
top: 22px;
right: 22px;
font-size: 18px;
color: var(--silver);
transition: all 0.25s ease;
font-family: var(--serif);
}
.mode-btn:hover {
background: #3a3a37;
border-color: var(--coral);
transform: translateY(-2px);
}
.mode-btn:hover::after {
color: var(--coral);
transform: translate(3px, -1px);
}
.mode-btn:active {
transform: translateY(0);
}
.mode-btn:nth-child(1), .mode-btn:nth-child(2),
.mode-btn:nth-child(3), .mode-btn:nth-child(4) {
background: var(--dark-surface);
}
.mode-emoji {
font-family: var(--mono);
font-size: 11px;
font-weight: 500;
color: var(--coral);
letter-spacing: 0.18em;
text-transform: uppercase;
display: block;
margin-bottom: 14px;
}
.mode-btn:nth-child(1) .mode-emoji::before { content: '01 / '; color: var(--stone); }
.mode-btn:nth-child(2) .mode-emoji::before { content: '02 / '; color: var(--stone); }
.mode-btn:nth-child(3) .mode-emoji::before { content: '03 / '; color: var(--stone); }
.mode-btn:nth-child(4) .mode-emoji::before { content: '04 / '; color: var(--stone); }
.mode-name {
font-family: var(--zh-serif);
font-weight: 700;
font-size: 20px;
display: block;
color: var(--ivory);
line-height: 1.3;
margin-bottom: 4px;
letter-spacing: 0.01em;
}
.mode-desc {
font-family: var(--serif);
font-size: 14px;
color: var(--silver);
margin-top: 2px;
display: block;
font-weight: 400;
}
/* 測驗進行中 */
.quiz-playing { display: none; }
.quiz-playing.active { display: block; }
.quiz-header-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
padding: 16px 22px;
background: var(--dark-surface);
border: 1px solid #3a3a37;
border-radius: 12px;
box-shadow: none;
}
.quiz-progress {
font-family: var(--mono);
font-weight: 500;
font-size: 13px;
color: var(--silver);
letter-spacing: 0.05em;
}
.quiz-progress .q-current, .quiz-progress .q-total {
color: var(--ivory);
font-size: 16px;
}
.quiz-score {
display: flex;
gap: 10px;
font-weight: 500;
}
.score-chip {
padding: 5px 12px;
border-radius: 999px;
border: none;
font-size: 13px;
font-family: var(--mono);
background: transparent;
letter-spacing: 0.02em;
}
.score-chip.correct {
background: rgba(95, 200, 143, 0.12);
color: #8cd6aa;
}
.score-chip.wrong {
background: rgba(181, 51, 51, 0.15);
color: #d67878;
}
.quit-btn {
background: transparent;
border: 1px solid var(--silver);
border-radius: 50%;
width: 30px;
height: 30px;
font-size: 12px;
cursor: pointer;
color: var(--silver);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.quit-btn:hover {
background: var(--silver);
color: var(--near-black);
}
/* 題目卡片 */
.question-box {
background: var(--ivory);
color: var(--near-black);
border: 1px solid var(--border-cream);
border-radius: 20px;
padding: 56px 32px 48px;
text-align: center;
margin-bottom: 22px;
box-shadow: none;
position: relative;
}
.question-box::before {
content: 'Question';
position: absolute;
top: 22px;
left: 28px;
font-family: var(--mono);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.22em;
color: var(--terracotta);
text-transform: uppercase;
background: transparent;
width: auto;
height: auto;
border: none;
border-radius: 0;
display: block;
}
.question-box::after {
content: '';
position: absolute;
top: 22px;
left: 100px;
width: 40px;
height: 1px;
background: var(--terracotta);
opacity: 0.5;
}
.question-label {
font-family: var(--serif);
color: var(--olive);
font-size: 15px;
font-weight: 400;
margin-bottom: 20px;
}
.question-main {
font-family: var(--serif);
font-weight: 800;
font-size: clamp(36px, 7vw, 60px);
line-height: 1.05;
letter-spacing: -0.025em;
margin-bottom: 18px;
color: var(--near-black);
}
.question-main.zh {
font-family: var(--zh-serif);
font-size: clamp(30px, 6vw, 48px);
letter-spacing: 0;
}
.question-listen {
background: var(--sand);
border: none;
border-radius: 50%;
width: 56px;
height: 56px;
cursor: pointer;
font-size: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 16px;
transition: all 0.2s ease;
box-shadow: 0 0 0 1px var(--ring-warm);
color: var(--charcoal);
}
.question-listen:hover {
background: var(--terracotta);
color: var(--ivory);
box-shadow: 0 0 0 1px var(--terracotta);
transform: translateY(-1px);
}
.question-listen:active {
transform: translateY(0);
}
.choices {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.choice {
background: var(--ivory);
border: 1px solid var(--border-cream);
border-radius: 12px;
padding: 20px 18px;
cursor: pointer;
font-family: var(--serif);
font-size: 19px;
font-weight: 700;
transition: all 0.2s ease;
box-shadow: none;
text-align: center;
color: var(--near-black);
letter-spacing: -0.005em;
}
.choice:hover:not(.revealed) {
background: var(--parchment);
border-color: var(--ring-deep);
transform: translateY(-1px);
box-shadow: 0 0 0 1px var(--ring-warm);
}
.choice:active:not(.revealed) {
transform: translateY(0);
}
.choice.revealed.correct {
background: rgba(95, 200, 143, 0.15);
border-color: #5fc88f;
color: #2d6b45;
animation: pop 0.35s ease;
}
.choice.revealed.wrong {
background: rgba(181, 51, 51, 0.1);
border-color: var(--error);
color: var(--error);
animation: shake 0.4s ease;
}
.choice.revealed.correct-answer {
background: rgba(95, 200, 143, 0.15);
border-color: #5fc88f;
color: #2d6b45;
}
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.04); }
100% { transform: scale(1); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* 拼字題 */
.spelling-input {
width: 100%;
padding: 22px 20px;
border: 1px solid var(--border-warm);
border-radius: 14px;
font-family: var(--serif);
font-size: 28px;
font-weight: 700;
text-align: center;
background: var(--ivory);
box-shadow: none;
outline: none;
color: var(--near-black);
letter-spacing: -0.01em;
transition: all 0.2s ease;
}
.spelling-input:focus {
border-color: var(--focus-blue);
box-shadow: 0 0 0 3px rgba(56, 152, 236, 0.15);
}
.spelling-input.correct {
background: rgba(95, 200, 143, 0.12);
border-color: #5fc88f;
}
.spelling-input.wrong {
background: rgba(181, 51, 51, 0.08);
border-color: var(--error);
animation: shake 0.4s ease;
}
.spelling-hint {
text-align: center;
margin-top: 14px;
font-size: 13px;
color: var(--silver);
font-family: var(--sans);
font-weight: 400;
letter-spacing: 0.02em;
}
.spelling-hint strong {
font-family: var(--mono);
font-weight: 500;
color: var(--coral);
}
.submit-btn {
display: inline-block;
margin: 20px auto 0;
background: var(--terracotta);
color: var(--ivory);
border: none;
border-radius: 10px;
padding: 13px 28px;
font-family: var(--sans);
font-weight: 500;
font-size: 15px;
cursor: pointer;
box-shadow: 0 0 0 1px var(--terracotta);
transition: all 0.2s ease;
letter-spacing: 0.01em;
}
.submit-btn:hover {
background: var(--terracotta-ink);
box-shadow: 0 0 0 1px var(--terracotta-ink), rgba(0,0,0,0.08) 0 4px 12px;
transform: translateY(-1px);
}
.submit-btn:active {
transform: translateY(0);
}
.submit-btn.spell-submit {
display: block;
margin: 20px auto 0;
}
/* 結果頁 */
.result-screen {
display: none;
text-align: center;
padding: 32px 20px;
}
.result-screen.active { display: block; }
.result-emoji {
font-size: 56px;
margin-bottom: 14px;
display: inline-block;
animation: fadeIn 0.6s ease;
filter: saturate(0.9);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.result-title {
font-family: var(--zh-serif);
font-weight: 700;
font-size: 32px;
margin-bottom: 6px;
color: var(--ivory);
letter-spacing: -0.01em;
}
.result-score {
font-family: var(--serif);
font-size: 88px;
font-weight: 800;
color: var(--coral);
margin: 8px 0 4px;
line-height: 1;
text-shadow: none;
letter-spacing: -0.04em;
}
.result-detail {
color: var(--silver);
margin-bottom: 24px;
font-family: var(--serif);
font-size: 15px;
}
.result-buttons {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-top: 28px;
}
.result-buttons .submit-btn.back-btn {
background: var(--dark-surface);
color: var(--ivory);
box-shadow: 0 0 0 1px #3a3a37;
}
.result-buttons .submit-btn.back-btn:hover {
background: #3a3a37;
box-shadow: 0 0 0 1px var(--silver);
}
.wrong-list {
text-align: left;
background: var(--dark-surface);
border: 1px solid #3a3a37;
border-radius: 14px;
padding: 22px 24px;
margin-top: 24px;
max-width: 480px;
margin-left: auto;
margin-right: auto;
}
.wrong-list-title {
font-family: var(--mono);
font-weight: 500;
font-size: 11px;
letter-spacing: 0.2em;
text-transform: uppercase;
margin-bottom: 14px;
color: var(--coral);
padding-bottom: 10px;
border-bottom: 1px solid #3a3a37;
}
.wrong-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #3a3a37;
font-size: 15px;
align-items: baseline;
color: var(--silver);
font-family: var(--zh-serif);
}
.wrong-item:last-child { border: none; }
.wrong-item .w-en {
font-family: var(--serif);
font-weight: 700;
color: var(--coral);
font-size: 18px;
letter-spacing: -0.01em;
}
/* 頁尾 */
footer {
text-align: center;
padding: 72px 24px 96px;
color: var(--olive);
font-size: 15px;
position: relative;
z-index: 1;
border-top: 1px solid var(--border-warm);
background: var(--parchment);
margin-top: 0;
}
footer .farewell {
font-family: var(--hand);
font-size: 28px;
color: var(--charcoal);
line-height: 1.3;
transform: rotate(-1deg);
display: inline-block;
margin-bottom: 18px;
}
footer .farewell .heart {
color: var(--terracotta);
display: inline-block;
font-style: normal;
animation: pulse 2s ease-in-out infinite;
margin: 0 4px;
}
footer .signature {
display: inline-flex;
align-items: center;
gap: 14px;
margin-top: 6px;
font-family: var(--hand);
color: var(--terracotta);
font-size: 22px;
transform: rotate(-2deg);
}
footer .signature::before {
content: '';
width: 70px;
height: 12px;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 20'><path d='M2 10 Q 15 2, 30 12 T 55 8 T 80 14 T 98 8' stroke='%23c96442' stroke-width='2' fill='none' stroke-linecap='round'/></svg>");
background-repeat: no-repeat;
background-size: 100% 100%;
opacity: 0.7;
}
footer .mono-line {
display: block;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.18em;
color: var(--stone);
margin-top: 28px;
text-transform: uppercase;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
/* ===== 數字卡Get Ready ===== */
.numbers-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
}
.number-card {
background: var(--ivory);
border: 1px solid var(--border-cream);
border-radius: 16px;
padding: 26px 20px 22px;
cursor: pointer;
transition: all 0.25s ease;
text-align: center;
position: relative;
overflow: hidden;
}
.number-card:hover {
border-color: var(--ring-deep);
box-shadow: 0 0 0 1px var(--ring-warm), rgba(0,0,0,0.04) 0 4px 16px;
transform: translateY(-2px);
}
.number-digit {
font-family: var(--serif);
font-size: 56px;
font-weight: 800;
color: var(--terracotta);
line-height: 1;
letter-spacing: -0.03em;
margin-bottom: 6px;
}
.number-en {
font-family: var(--serif);
font-size: 18px;
font-weight: 700;
color: var(--near-black);
margin-top: 6px;
}
/* ===== 響應式 ===== */
@media (max-width: 760px) {
.number-digit { font-size: 44px; }
.masthead { padding: 56px 20px 64px; }
main { padding: 40px 20px 80px; }
.phonics-wrapper { grid-template-columns: 1fr; }
.vocab-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
.tips-wrapper { grid-template-columns: 1fr; }
.section { margin-bottom: 48px; }
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.section.quiz {
margin-left: -20px;
margin-right: -20px;
padding: 48px 24px;
}
.choices { grid-template-columns: 1fr; }
.story-item, .sentence-item { padding: 18px 20px; gap: 14px; }
.en { font-size: 18px; }
.vocab-card { padding: 20px 18px; }
.vocab-en { font-size: 20px; }
.question-box { padding: 48px 22px 36px; }
.tabs { padding: 0 20px; overflow-x: auto; }
.tab { margin-right: 24px; padding: 18px 4px; }
}
@media (max-width: 440px) {
.vocab-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<!-- 頁首 -->
<header class="masthead">
<!-- 手繪感裝飾 -->
<svg class="ornament left" width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 60 Q 40 20, 60 60 T 100 60" stroke="#c96442" stroke-width="2" fill="none" stroke-linecap="round"/>
<circle cx="60" cy="60" r="3" fill="#c96442"/>
<path d="M15 85 Q 30 75, 45 85" stroke="#c96442" stroke-width="1.5" fill="none" stroke-linecap="round"/>
</svg>
<svg class="ornament right" width="140" height="140" viewBox="0 0 140 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="70" cy="70" r="40" stroke="#d97757" stroke-width="1.5" fill="none"/>
<path d="M40 70 Q 70 40, 100 70 Q 70 100, 40 70" stroke="#d97757" stroke-width="1.5" fill="none"/>
<circle cx="70" cy="70" r="4" fill="#d97757"/>
</svg>
<div class="masthead-inner">
<div class="handwritten-tag">寫給我家的小讀者</div>
<h1>Wonder <em>World</em></h1>
<p class="dek">這不是課本,是我陪你讀的筆記。<br>慢慢念沒關係,念錯也沒關係,一起把字詞變成聲音就好。</p>
<div class="meta-row">
<span><span class="dot"></span>Unit 1 · 家裡</span>
<span><span class="dot"></span>Unit 2 · 大家在做什麼</span>
<span><span class="dot"></span>故事 · 單字 · 發音</span>
</div>
</div>
</header>
<!-- Unit 切換 -->
<div class="tabs-wrap">
<div class="tabs">
<button class="tab active" data-unit="1">
<span class="tab-num">UNIT 01</span>
家 · At Home
</button>
<button class="tab" data-unit="2">
<span class="tab-num">UNIT 02</span>
動作中 · In Motion
</button>
<button class="tab" data-unit="3">
<span class="tab-num">UNIT 03</span>
午餐 · Lunch
</button>
<button class="tab" data-unit="4">
<span class="tab-num">UNIT 04</span>
動物園 · At the Zoo
</button>
<button class="tab" data-unit="festivals">
<span class="tab-num">CULTURE</span>
端午節 · Festivals
</button>
<button class="tab" data-unit="numbers">
<span class="tab-num">GET READY</span>
數字 · Numbers
</button>
</div>
</div>
<main>
<!-- UNIT 1 -->
<div class="unit active" id="unit-1">
<section class="section quiz" data-unit="1">
<div class="section-header">
<div class="section-icon">§ Quiz</div>
<div class="section-title">Word Quiz<span class="zh">· 單字測驗</span></div>
</div>
<div class="quiz-home">
<p>選一種方式練習 Unit 1 的單字,慢慢來,答錯也沒關係。</p>
<div class="quiz-modes">
<button class="mode-btn" data-mode="en-to-zh">
<span class="mode-emoji">Read</span>
<span class="mode-name">英文選中文</span>
<span class="mode-desc">English → Chinese</span>
</button>
<button class="mode-btn" data-mode="zh-to-en">
<span class="mode-emoji">Recall</span>
<span class="mode-name">中文選英文</span>
<span class="mode-desc">Chinese → English</span>
</button>
<button class="mode-btn" data-mode="listen">
<span class="mode-emoji">Listen</span>
<span class="mode-name">聽音選字</span>
<span class="mode-desc">By ear</span>
</button>
<button class="mode-btn" data-mode="spell">
<span class="mode-emoji">Write</span>
<span class="mode-name">拼字挑戰</span>
<span class="mode-desc">Spelling</span>
</button>
</div>
</div>
<div class="quiz-playing">
<div class="quiz-header-bar">
<div class="quiz-progress"><span class="q-current">1</span> <span style="opacity:0.4;">/</span> <span class="q-total">5</span></div>
<div class="quiz-score">
<span class="score-chip correct"><span class="s-correct">0</span></span>
<span class="score-chip wrong"><span class="s-wrong">0</span></span>
</div>
<button class="quit-btn" title="結束"></button>
</div>
<div class="question-area"></div>
</div>
<div class="result-screen">
<div class="result-emoji"></div>
<div class="result-title"></div>
<div class="result-score"></div>
<div class="result-detail"></div>
<div class="wrong-list-wrapper"></div>
<div class="result-buttons">
<button class="submit-btn retry-btn">再來一次</button>
<button class="submit-btn back-btn">← 回選單</button>
</div>
</div>
</section>
<div class="hint">點下去就會念給你聽喔~</div>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 01</div>
<div class="section-title">Story<span class="zh">· 故事</span></div>
<div class="section-note">十四句話,<br>全都發生在 Amy 家裡</div>
</div>
<div id="story-1"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 02</div>
<div class="section-title">Pronunciation Tips<span class="zh">· 怎麼唸</span></div>
<div class="section-note">這幾個字容易卡住,<br>點下去聽念法</div>
</div>
<div class="tips-wrapper" id="tips-1"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 03</div>
<div class="section-title">Vocabulary<span class="zh">· 單字</span></div>
<div class="section-note">五個房間的名字,<br>記起來就能帶路了</div>
</div>
<div class="vocab-grid" id="vocab-1"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 04</div>
<div class="section-title">Sentences<span class="zh">· 句型</span></div>
<div class="section-note">「誰在哪裡?」<br>的問答模式</div>
</div>
<div class="sentence-list" id="sentences-1"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 05</div>
<div class="section-title">Daily English<span class="zh">· 生活會話</span></div>
<div class="section-note">兩句很常用的<br>小話,收下</div>
</div>
<div class="sentence-list" id="daily-1"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 06</div>
<div class="section-title">Phonics<span class="zh">· 自然發音</span></div>
<div class="section-note">兩種 th ——<br>舌頭位置不一樣喔</div>
</div>
<div class="phonics-wrapper" id="phonics-1"></div>
</section>
</div>
<!-- UNIT 2 -->
<div class="unit" id="unit-2">
<section class="section quiz" data-unit="2">
<div class="section-header">
<div class="section-icon">§ Quiz</div>
<div class="section-title">Word Quiz<span class="zh">· 單字測驗</span></div>
</div>
<div class="quiz-home">
<p>選一種方式練習 Unit 2 的單字,慢慢來,答錯也沒關係。</p>
<div class="quiz-modes">
<button class="mode-btn" data-mode="en-to-zh">
<span class="mode-emoji">Read</span>
<span class="mode-name">英文選中文</span>
<span class="mode-desc">English → Chinese</span>
</button>
<button class="mode-btn" data-mode="zh-to-en">
<span class="mode-emoji">Recall</span>
<span class="mode-name">中文選英文</span>
<span class="mode-desc">Chinese → English</span>
</button>
<button class="mode-btn" data-mode="listen">
<span class="mode-emoji">Listen</span>
<span class="mode-name">聽音選字</span>
<span class="mode-desc">By ear</span>
</button>
<button class="mode-btn" data-mode="spell">
<span class="mode-emoji">Write</span>
<span class="mode-name">拼字挑戰</span>
<span class="mode-desc">Spelling</span>
</button>
</div>
</div>
<div class="quiz-playing">
<div class="quiz-header-bar">
<div class="quiz-progress"><span class="q-current">1</span> <span style="opacity:0.4;">/</span> <span class="q-total">6</span></div>
<div class="quiz-score">
<span class="score-chip correct"><span class="s-correct">0</span></span>
<span class="score-chip wrong"><span class="s-wrong">0</span></span>
</div>
<button class="quit-btn" title="結束"></button>
</div>
<div class="question-area"></div>
</div>
<div class="result-screen">
<div class="result-emoji"></div>
<div class="result-title"></div>
<div class="result-score"></div>
<div class="result-detail"></div>
<div class="wrong-list-wrapper"></div>
<div class="result-buttons">
<button class="submit-btn retry-btn">再來一次</button>
<button class="submit-btn back-btn">← 回選單</button>
</div>
</div>
</section>
<div class="hint">點下去就會念給你聽喔~</div>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 01</div>
<div class="section-title">Story<span class="zh">· 故事</span></div>
<div class="section-note">視訊的時候,<br>奶奶在跑步 🏃</div>
</div>
<div id="story-2"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 02</div>
<div class="section-title">Pronunciation Tips<span class="zh">· 怎麼唸</span></div>
<div class="section-note">-ing 動作詞,<br>先把尾巴的鼻音學好</div>
</div>
<div class="tips-wrapper" id="tips-2"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 03</div>
<div class="section-title">Vocabulary<span class="zh">· 單字</span></div>
<div class="section-note">六個 -ing 動作,<br>念起來有節奏</div>
</div>
<div class="vocab-grid" id="vocab-2"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 04</div>
<div class="section-title">Sentences<span class="zh">· 句型</span></div>
<div class="section-note">「在做什麼?」<br>的萬用問句</div>
</div>
<div class="sentence-list" id="sentences-2"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 05</div>
<div class="section-title">Daily English<span class="zh">· 生活會話</span></div>
<div class="section-note">兩句簡單的,<br>脫口就能講</div>
</div>
<div class="sentence-list" id="daily-2"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 06</div>
<div class="section-title">Phonics<span class="zh">· 自然發音</span></div>
<div class="section-note">ng 和 nk ——<br>嘴巴合起來念</div>
</div>
<div class="phonics-wrapper" id="phonics-2"></div>
</section>
</div>
<!-- UNIT 3 -->
<div class="unit" id="unit-3">
<section class="section quiz" data-unit="3">
<div class="section-header">
<div class="section-icon">§ Quiz</div>
<div class="section-title">Word Quiz<span class="zh">· 單字測驗</span></div>
</div>
<div class="quiz-home">
<p>選一種方式練習 Unit 3 的單字,慢慢來,答錯也沒關係。</p>
<div class="quiz-modes">
<button class="mode-btn" data-mode="en-to-zh">
<span class="mode-emoji">Read</span>
<span class="mode-name">英文選中文</span>
<span class="mode-desc">English → Chinese</span>
</button>
<button class="mode-btn" data-mode="zh-to-en">
<span class="mode-emoji">Recall</span>
<span class="mode-name">中文選英文</span>
<span class="mode-desc">Chinese → English</span>
</button>
<button class="mode-btn" data-mode="listen">
<span class="mode-emoji">Listen</span>
<span class="mode-name">聽音選字</span>
<span class="mode-desc">By ear</span>
</button>
<button class="mode-btn" data-mode="spell">
<span class="mode-emoji">Write</span>
<span class="mode-name">拼字挑戰</span>
<span class="mode-desc">Spelling</span>
</button>
</div>
</div>
<div class="quiz-playing">
<div class="quiz-header-bar">
<div class="quiz-progress"><span class="q-current">1</span> <span style="opacity:0.4;">/</span> <span class="q-total">6</span></div>
<div class="quiz-score">
<span class="score-chip correct"><span class="s-correct">0</span></span>
<span class="score-chip wrong"><span class="s-wrong">0</span></span>
</div>
<button class="quit-btn" title="結束"></button>
</div>
<div class="question-area"></div>
</div>
<div class="result-screen">
<div class="result-emoji"></div>
<div class="result-title"></div>
<div class="result-score"></div>
<div class="result-detail"></div>
<div class="wrong-list-wrapper"></div>
<div class="result-buttons">
<button class="submit-btn retry-btn">再來一次</button>
<button class="submit-btn back-btn">← 回選單</button>
</div>
</div>
</section>
<div class="hint">錢包別忘了帶~</div>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 01</div>
<div class="section-title">Story<span class="zh">· 故事</span></div>
<div class="section-note">和 Boka 一起去<br>吃午餐 🍔</div>
</div>
<div id="story-3"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 02</div>
<div class="section-title">Pronunciation Tips<span class="zh">· 怎麼唸</span></div>
<div class="section-note">點餐前,<br>先把食物名字念清楚</div>
</div>
<div class="tips-wrapper" id="tips-3"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 03</div>
<div class="section-title">Vocabulary<span class="zh">· 單字</span></div>
<div class="section-note">六樣常吃的<br>食物和飲料</div>
</div>
<div class="vocab-grid" id="vocab-3"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 04</div>
<div class="section-title">Sentences<span class="zh">· 句型</span></div>
<div class="section-note">「你想吃什麼?」<br>點餐句型</div>
</div>
<div class="sentence-list" id="sentences-3"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 05</div>
<div class="section-title">Daily English<span class="zh">· 生活會話</span></div>
<div class="section-note">讚美和反問,<br>超級實用</div>
</div>
<div class="sentence-list" id="daily-3"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 06</div>
<div class="section-title">Phonics<span class="zh">· 自然發音</span></div>
<div class="section-note">wh 和 ph<br>都念 /f/ 或 /w/ 喔</div>
</div>
<div class="phonics-wrapper" id="phonics-3"></div>
</section>
</div>
<!-- UNIT 4 -->
<div class="unit" id="unit-4">
<section class="section quiz" data-unit="4">
<div class="section-header">
<div class="section-icon">§ Quiz</div>
<div class="section-title">Word Quiz<span class="zh">· 單字測驗</span></div>
</div>
<div class="quiz-home">
<p>選一種方式練習 Unit 4 的單字,慢慢來,答錯也沒關係。</p>
<div class="quiz-modes">
<button class="mode-btn" data-mode="en-to-zh">
<span class="mode-emoji">Read</span>
<span class="mode-name">英文選中文</span>
<span class="mode-desc">English → Chinese</span>
</button>
<button class="mode-btn" data-mode="zh-to-en">
<span class="mode-emoji">Recall</span>
<span class="mode-name">中文選英文</span>
<span class="mode-desc">Chinese → English</span>
</button>
<button class="mode-btn" data-mode="listen">
<span class="mode-emoji">Listen</span>
<span class="mode-name">聽音選字</span>
<span class="mode-desc">By ear</span>
</button>
<button class="mode-btn" data-mode="spell">
<span class="mode-emoji">Write</span>
<span class="mode-name">拼字挑戰</span>
<span class="mode-desc">Spelling</span>
</button>
</div>
</div>
<div class="quiz-playing">
<div class="quiz-header-bar">
<div class="quiz-progress"><span class="q-current">1</span> <span style="opacity:0.4;">/</span> <span class="q-total">5</span></div>
<div class="quiz-score">
<span class="score-chip correct"><span class="s-correct">0</span></span>
<span class="score-chip wrong"><span class="s-wrong">0</span></span>
</div>
<button class="quit-btn" title="結束"></button>
</div>
<div class="question-area"></div>
</div>
<div class="result-screen">
<div class="result-emoji"></div>
<div class="result-title"></div>
<div class="result-score"></div>
<div class="result-detail"></div>
<div class="wrong-list-wrapper"></div>
<div class="result-buttons">
<button class="submit-btn retry-btn">再來一次</button>
<button class="submit-btn back-btn">← 回選單</button>
</div>
</div>
</section>
<div class="hint">看到老虎就快跑~</div>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 01</div>
<div class="section-title">Story<span class="zh">· 故事</span></div>
<div class="section-note">去動物園的<br>一天,驚險</div>
</div>
<div id="story-4"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 02</div>
<div class="section-title">Pronunciation Tips<span class="zh">· 怎麼唸</span></div>
<div class="section-note">動物名字,<br>有些字藏了小陷阱</div>
</div>
<div class="tips-wrapper" id="tips-4"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 03</div>
<div class="section-title">Vocabulary<span class="zh">· 單字</span></div>
<div class="section-note">五種動物,<br>都是複數 -s</div>
</div>
<div class="vocab-grid" id="vocab-4"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 04</div>
<div class="section-title">Sentences<span class="zh">· 句型</span></div>
<div class="section-note">「你喜歡 _ 嗎?」<br>肯定和否定</div>
</div>
<div class="sentence-list" id="sentences-4"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 05</div>
<div class="section-title">Daily English<span class="zh">· 生活會話</span></div>
<div class="section-note">開心玩,<br>遇到危險叫救命</div>
</div>
<div class="sentence-list" id="daily-4"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 06</div>
<div class="section-title">Phonics<span class="zh">· 自然發音</span></div>
<div class="section-note">sh 和 ch<br>舌頭位置不同</div>
</div>
<div class="phonics-wrapper" id="phonics-4"></div>
</section>
</div>
<!-- CULTURE & FESTIVALS -->
<div class="unit" id="unit-festivals">
<section class="section quiz" data-unit="festivals">
<div class="section-header">
<div class="section-icon">§ Quiz</div>
<div class="section-title">Word Quiz<span class="zh">· 單字測驗</span></div>
</div>
<div class="quiz-home">
<p>選一種方式練習端午節的單字,慢慢來,答錯也沒關係。</p>
<div class="quiz-modes">
<button class="mode-btn" data-mode="en-to-zh">
<span class="mode-emoji">Read</span>
<span class="mode-name">英文選中文</span>
<span class="mode-desc">English → Chinese</span>
</button>
<button class="mode-btn" data-mode="zh-to-en">
<span class="mode-emoji">Recall</span>
<span class="mode-name">中文選英文</span>
<span class="mode-desc">Chinese → English</span>
</button>
<button class="mode-btn" data-mode="listen">
<span class="mode-emoji">Listen</span>
<span class="mode-name">聽音選字</span>
<span class="mode-desc">By ear</span>
</button>
<button class="mode-btn" data-mode="spell">
<span class="mode-emoji">Write</span>
<span class="mode-name">拼字挑戰</span>
<span class="mode-desc">Spelling</span>
</button>
</div>
</div>
<div class="quiz-playing">
<div class="quiz-header-bar">
<div class="quiz-progress"><span class="q-current">1</span> <span style="opacity:0.4;">/</span> <span class="q-total">4</span></div>
<div class="quiz-score">
<span class="score-chip correct"><span class="s-correct">0</span></span>
<span class="score-chip wrong"><span class="s-wrong">0</span></span>
</div>
<button class="quit-btn" title="結束"></button>
</div>
<div class="question-area"></div>
</div>
<div class="result-screen">
<div class="result-emoji"></div>
<div class="result-title"></div>
<div class="result-score"></div>
<div class="result-detail"></div>
<div class="wrong-list-wrapper"></div>
<div class="result-buttons">
<button class="submit-btn retry-btn">再來一次</button>
<button class="submit-btn back-btn">← 回選單</button>
</div>
</div>
</section>
<div class="hint">端午節那天可以立蛋喔~</div>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 01</div>
<div class="section-title">Story<span class="zh">· 故事</span></div>
<div class="section-note">端午節的<br>小日常</div>
</div>
<div id="story-festivals"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 02</div>
<div class="section-title">Pronunciation Tips<span class="zh">· 怎麼唸</span></div>
<div class="section-note">端午節特別的<br>幾個字,跟著念</div>
</div>
<div class="tips-wrapper" id="tips-festivals"></div>
</section>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 03</div>
<div class="section-title">Vocabulary<span class="zh">· 單字</span></div>
<div class="section-note">四個端午節<br>專屬的詞</div>
</div>
<div class="vocab-grid" id="vocab-festivals"></div>
</section>
</div>
<!-- GET READY NUMBERS -->
<div class="unit" id="unit-numbers">
<section class="section quiz" data-unit="numbers">
<div class="section-header">
<div class="section-icon">§ Quiz</div>
<div class="section-title">Word Quiz<span class="zh">· 單字測驗</span></div>
</div>
<div class="quiz-home">
<p>選一種方式練習數字的英文念法,慢慢來。</p>
<div class="quiz-modes">
<button class="mode-btn" data-mode="en-to-zh">
<span class="mode-emoji">Read</span>
<span class="mode-name">英文選數字</span>
<span class="mode-desc">English → Number</span>
</button>
<button class="mode-btn" data-mode="zh-to-en">
<span class="mode-emoji">Recall</span>
<span class="mode-name">數字選英文</span>
<span class="mode-desc">Number → English</span>
</button>
<button class="mode-btn" data-mode="listen">
<span class="mode-emoji">Listen</span>
<span class="mode-name">聽音選字</span>
<span class="mode-desc">By ear</span>
</button>
<button class="mode-btn" data-mode="spell">
<span class="mode-emoji">Write</span>
<span class="mode-name">拼字挑戰</span>
<span class="mode-desc">Spelling</span>
</button>
</div>
</div>
<div class="quiz-playing">
<div class="quiz-header-bar">
<div class="quiz-progress"><span class="q-current">1</span> <span style="opacity:0.4;">/</span> <span class="q-total">5</span></div>
<div class="quiz-score">
<span class="score-chip correct"><span class="s-correct">0</span></span>
<span class="score-chip wrong"><span class="s-wrong">0</span></span>
</div>
<button class="quit-btn" title="結束"></button>
</div>
<div class="question-area"></div>
</div>
<div class="result-screen">
<div class="result-emoji"></div>
<div class="result-title"></div>
<div class="result-score"></div>
<div class="result-detail"></div>
<div class="wrong-list-wrapper"></div>
<div class="result-buttons">
<button class="submit-btn retry-btn">再來一次</button>
<button class="submit-btn back-btn">← 回選單</button>
</div>
</div>
</section>
<div class="hint">一百以內,都能念了~</div>
<section class="section">
<div class="section-header">
<div class="section-icon">§ 01</div>
<div class="section-title">Numbers<span class="zh">· 六十到一百</span></div>
<div class="section-note">五個大數字,<br>點一下聽念法</div>
</div>
<div class="numbers-grid" id="numbers-list"></div>
</section>
</div>
</main>
<footer>
<div class="farewell">
慢慢讀就好,別急 <span class="heart"></span><br>
念錯幾個字,也是練習的一部分
</div>
<span class="mono-line">An English Reader · 2026 · 手工整理</span>
</footer>
<script>
// ============== 資料 ==============
const data = {
unit1: {
story: [
{ en: "Amy!", zh: "Amy" },
{ en: "Amy, / where are you?", zh: "Amy你在哪裡" },
{ en: "I'm here. / I'm / in the bedroom.", zh: "我在這裡。我在臥室裡。" },
{ en: "This is for you, / Jello.", zh: "Jello,這是給你的。" },
{ en: "Wow! / Thank you. / It's a dollhouse!", zh: "哇!謝謝。這是娃娃屋!" },
{ en: "Cool!", zh: "酷!" },
{ en: "That's / my father.", zh: "那是我爸爸。" },
{ en: "He's / in the kitchen.", zh: "他在廚房裡。" },
{ en: "Where is / my mother?", zh: "我媽媽在哪裡?" },
{ en: "She's / in the dining room.", zh: "她在飯廳裡。" },
{ en: "Look! / My sister / is / in the living room.", zh: "看!我姐姐在客廳裡。" },
{ en: "Where is / the bathroom?", zh: "洗手間在哪裡?" },
{ en: "Over there.", zh: "在那裡。" },
{ en: "Uh, / I need / to use / the bathroom.", zh: "呃,我要用洗手間。" }
],
vocab: [
{ en: "bathroom", zh: "洗手間" },
{ en: "bedroom", zh: "臥室" },
{ en: "kitchen", zh: "廚房" },
{ en: "dining room", zh: "飯廳" },
{ en: "living room", zh: "客廳" }
],
tips: [
{ en: "where", phonetic: "威兒 (wehr)", note: "w 嘴巴嘟起來一點點,像要親親的樣子。" },
{ en: "there", phonetic: "賊兒 (thehr)", note: "th 舌頭伸到兩排牙齒中間,輕輕吐氣。" },
{ en: "bedroom", phonetic: "貝-嚕姆", note: "兩個字黏在一起念BED 唸得比 room 用力。" },
{ en: "bathroom", phonetic: "霸-嚕姆", note: "th 也要咬一下舌頭喔!" },
{ en: "dollhouse", phonetic: "豆-好斯", note: "house 的 h 小小地吹氣,不要念成『後斯』。" },
{ en: "kitchen", phonetic: "欺-勤", note: "嘴巴放鬆,不是注音『ㄎㄧ』那麼用力。" }
],
sentences: [
{ en: 'Where / are you?', zh: "你在哪裡?" },
{ en: "I'm / in the <u>bedroom</u>.", zh: "我在<u>臥室</u>裡。" },
{ en: "Where's / Kevin?", zh: "Kevin 在哪裡?" },
{ en: "He's / in the <u>dining room</u>.", zh: "他在<u>飯廳</u>裡。" },
{ en: "Where's / Mom?", zh: "媽媽在哪裡?" },
{ en: "She's / in the <u>living room</u>.", zh: "她在<u>客廳</u>裡。" }
],
daily: [
{ en: "This is / for you.", zh: "這是給你的。" },
{ en: "Cool!", zh: "酷!" }
],
phonics: [
{
tag: "th",
words: [
{ en: "three", zh: "三" },
{ en: "teeth", zh: "牙齒" },
{ en: "mouth", zh: "嘴巴" }
]
},
{
tag: "th",
words: [
{ en: "this", zh: "這個" },
{ en: "father", zh: "爸爸" },
{ en: "mother", zh: "媽媽" }
]
}
]
},
unit2: {
story: [
{ en: "Hi, Mom. / I miss you / so much.", zh: "嗨,媽媽。我好想你。" },
{ en: "Me too.", zh: "我也是。" },
{ en: "Hi, / Mrs. Blue.", zh: "嗨Blue 太太。" },
{ en: "Hi, Amy. / What are you / doing?", zh: "嗨Amy。你在做什麼?" },
{ en: "I'm / eating / an apple.", zh: "我在吃蘋果。" },
{ en: "Is that / Grandma?", zh: "那是奶奶嗎?" },
{ en: "Yes. / She's / running.", zh: "是的。她在跑步。" },
{ en: "Hi, Boka. / Look! / I can run / very fast.", zh: "嗨Boka。看!我可以跑得很快。" },
{ en: "Wow! / That's great!", zh: "哇!太棒了!" },
{ en: "What's / Grandpa / doing?", zh: "爺爺在做什麼?" },
{ en: "He's / writing.", zh: "他在寫字。" },
{ en: "Yay! / I'm done.", zh: "太棒了!我寫好了。" },
{ en: "Dinner / is ready.", zh: "晚餐做好了。" },
{ en: "It's / dinner time. / Bye, Mom!", zh: "晚餐時間到了。媽媽再見!" },
{ en: "Dinner! / Oh, no!", zh: "晚餐!糟糕!" }
],
vocab: [
{ en: "cooking", zh: "(正在)下廚" },
{ en: "eating", zh: "(正在)吃東西" },
{ en: "reading", zh: "(正在)閱讀" },
{ en: "sleeping", zh: "(正在)睡覺" },
{ en: "writing", zh: "(正在)寫字" },
{ en: "running", zh: "(正在)跑步" }
],
tips: [
{ en: "writing", phonetic: "來-聽", note: "w 不發音i 要唸成『ㄞ』,不是『一』。" },
{ en: "reading", phonetic: "瑞-丁", note: "r 舌頭往後縮,不要碰到上面。" },
{ en: "running", phonetic: "讓-寧", note: "r 一樣要捲一下,不是注音『ㄖㄨ』。" },
{ en: "eating", phonetic: "衣-聽", note: "ea 拉長一點點,嘴巴微笑。" },
{ en: "sleeping", phonetic: "斯立-平", note: "sl 兩個音連在一起,不要拆開唸。" },
{ en: "doing", phonetic: "do-ing", note: "do 嘴巴嘟起來,像在吹蠟燭。" }
],
sentences: [
{ en: "What are you / doing?", zh: "你在做什麼?" },
{ en: "I'm / <u>reading</u>.", zh: "我在<u>閱讀</u>。" },
{ en: "What's he / doing?", zh: "他在做什麼?" },
{ en: "He's / <u>sleeping</u>.", zh: "他在<u>睡覺</u>。" },
{ en: "What's she / doing?", zh: "她在做什麼?" },
{ en: "She's / <u>eating</u>.", zh: "她在<u>吃東西</u>。" }
],
daily: [
{ en: "I'm done.", zh: "我完成了。" },
{ en: "Me too.", zh: "我也是。" }
],
phonics: [
{
tag: "ng",
words: [
{ en: "sing", zh: "唱歌" },
{ en: "king", zh: "國王" },
{ en: "song", zh: "歌曲" }
]
},
{
tag: "nk",
words: [
{ en: "sink", zh: "水槽" },
{ en: "pink", zh: "粉紅色" },
{ en: "rink", zh: "溜冰場" }
]
}
]
},
unit3: {
story: [
{ en: "All set / and ready / to go!", zh: "一切都準備好了,可以出發了!" },
{ en: "Dad, / I'm hungry. / Let's / have lunch.", zh: "爸爸,我餓了。我們去吃午餐吧。" },
{ en: "Good idea!", zh: "好主意!" },
{ en: "Here we are.", zh: "我們到了。" },
{ en: "Thank you, / Mr. Lee.", zh: "謝謝你,李先生。" },
{ en: "Hurry up!", zh: "快一點!" },
{ en: "What do you want / for lunch?", zh: "你午餐想吃什麼?" },
{ en: "Mmm, / let me see. / I want / a hamburger.", zh: "嗯,讓我想一想。我要一個漢堡。" },
{ en: "What / do you want?", zh: "你想要什麼?" },
{ en: "I want / a hot dog.", zh: "我要一份熱狗。" },
{ en: "I want / a hamburger / and some juice. / How about you, / Boka?", zh: "我要一個漢堡和一些果汁。Boka你呢?" },
{ en: "Mmm, / I want / two hot dogs, / three hamburgers, / and some ice cream.", zh: "嗯,我要兩份熱狗、三個漢堡和一些冰淇淋。" },
{ en: "Uh-oh! / Where's / my wallet?", zh: "糟了!我的錢包在哪裡?" }
],
vocab: [
{ en: "hamburger", zh: "漢堡" },
{ en: "hot dog", zh: "熱狗" },
{ en: "juice", zh: "果汁" },
{ en: "milk", zh: "牛奶" },
{ en: "water", zh: "水" },
{ en: "ice cream", zh: "冰淇淋" }
],
tips: [
{ en: "hamburger", phonetic: "漢-伯-哥", note: "三個音節HAM 最用力,像在叫『漢』堡。" },
{ en: "water", phonetic: "娃-特兒", note: "t 輕輕帶過就好,不要用力。" },
{ en: "juice", phonetic: "啾-斯", note: "嘴巴要嘟起來,像小鳥叫『啾』。" },
{ en: "milk", phonetic: "妹-k", note: "結尾的 k 小小地吐氣,不要念成『克』。" },
{ en: "ice cream", phonetic: "愛斯 克林", note: "兩個字分開念cream 的 cr 要連在一起。" },
{ en: "hungry", phonetic: "橫-葛瑞", note: "hun 像在問『橫?』,尾巴的 gry 要上揚。" }
],
sentences: [
{ en: "What do you want / for lunch?", zh: "你午餐想吃什麼?" },
{ en: "I want / a <u>hot dog</u>.", zh: "我要一份<u>熱狗</u>。" },
{ en: "I want / two <u>hamburgers</u>.", zh: "我要兩個<u>漢堡</u>。" },
{ en: "I want / some <u>juice</u>.", zh: "我要一些<u>果汁</u>。" }
],
daily: [
{ en: "Good idea!", zh: "好主意!" },
{ en: "How about / you?", zh: "你呢?" }
],
phonics: [
{
tag: "wh",
words: [
{ en: "white", zh: "白色" },
{ en: "wheel", zh: "輪子" },
{ en: "whale", zh: "鯨魚" }
]
},
{
tag: "ph",
words: [
{ en: "photo", zh: "照片" },
{ en: "phone", zh: "電話" },
{ en: "dolphin", zh: "海豚" }
]
}
]
},
unit4: {
story: [
{ en: "Hello, / monkeys!", zh: "嗨,猴子們!" },
{ en: "I like / monkeys. / They are / cute!", zh: "我喜歡猴子。牠們真可愛!" },
{ en: "Have fun!", zh: "祝你們玩得愉快!" },
{ en: "Do you like / zebras?", zh: "你喜歡斑馬嗎?" },
{ en: "Yes, / I do.", zh: "是的,我喜歡。" },
{ en: "Me too.", zh: "我也是。" },
{ en: "I like / bears. / Do you like / bears?", zh: "我喜歡熊。你喜歡熊嗎?" },
{ en: "No, / I don't. / They are / big.", zh: "不,我不喜歡。牠們很大隻。" },
{ en: "Wow! / What's that?", zh: "哇!那是什麼?" },
{ en: "It's / a tiger.", zh: "牠是一隻老虎。" },
{ en: "I don't like / bears.", zh: "我不喜歡熊。" },
{ en: "I don't like / tigers.", zh: "我不喜歡老虎。" },
{ en: "A bear! / Help!", zh: "有熊呀!救命呀!" },
{ en: "A tiger! / Run!", zh: "有老虎呀!快跑呀!" }
],
vocab: [
{ en: "bears", zh: "熊(複數)" },
{ en: "lions", zh: "獅子(複數)" },
{ en: "monkeys", zh: "猴子(複數)" },
{ en: "tigers", zh: "老虎(複數)" },
{ en: "zebras", zh: "斑馬(複數)" }
],
tips: [
{ en: "bears", phonetic: "杯兒-斯", note: "ear 唸成『杯兒』,不是『耳朵』的耳。" },
{ en: "lions", phonetic: "賴-恩斯", note: "lion 拆成 lai-ən 兩個音。" },
{ en: "tigers", phonetic: "泰-哥斯", note: "ti 念『泰』,不是注音『ㄊㄧ』。" },
{ en: "zebras", phonetic: "基-布拉斯", note: "z 先用牙齒擋一下空氣,嗡嗡地震動。" },
{ en: "monkeys", phonetic: "忙-kis", note: "mon 嘴巴張開,不是『蒙』。" },
{ en: "like", phonetic: "賴-k", note: "i 念『ㄞ』,結尾 k 輕輕吐一點氣。" }
],
sentences: [
{ en: "Do you like / <u>bears</u>?", zh: "你喜歡<u>熊</u>嗎?" },
{ en: "Yes, / I do.", zh: "是的,我喜歡。" },
{ en: "No, / I don't. / I like / <u>tigers</u>.", zh: "不,我不喜歡。我喜歡<u>老虎</u>。" }
],
daily: [
{ en: "Have fun!", zh: "祝你們玩得愉快!" },
{ en: "Help!", zh: "救命呀!" }
],
phonics: [
{
tag: "sh",
words: [
{ en: "shirt", zh: "襯衫" },
{ en: "fish", zh: "魚" },
{ en: "wash", zh: "洗" }
]
},
{
tag: "ch",
words: [
{ en: "chips", zh: "薯片" },
{ en: "cheese", zh: "起司" },
{ en: "lunch", zh: "午餐" }
]
}
]
},
unitfestivals: {
story: [
{ en: "What are / those?", zh: "那些是什麼?" },
{ en: "They are / sachets.", zh: "它們是香包。" },
{ en: "We wear / sachets / on the Dragon Boat Festival.", zh: "我們在端午節配戴香包。" },
{ en: "It's / a dragon boat race.", zh: "是划龍舟比賽。" },
{ en: "Go! Go! Go!", zh: "加油!加油!加油!" },
{ en: "It's / twelve o'clock.", zh: "現在是十二點。" },
{ en: "Time / to stand / an egg.", zh: "立蛋的時間到了。" },
{ en: "Nice!", zh: "真棒!" },
{ en: "What are / these?", zh: "這些是什麼?" },
{ en: "They are / rice dumplings.", zh: "它們是粽子。" },
{ en: "Wait!", zh: "等一下!" }
],
vocab: [
{ en: "sachet", zh: "香包" },
{ en: "dragon boat race", zh: "划龍舟比賽" },
{ en: "rice dumpling", zh: "粽子" },
{ en: "stand an egg", zh: "立蛋" }
],
tips: [
{ en: "sachet", phonetic: "沙-sei", note: "這是從法文來的字ch 唸『ㄕ』不是『ㄑ』。" },
{ en: "dragon", phonetic: "拽-根", note: "dr 兩個音連在一起,像小小嚇一跳的『拽』。" },
{ en: "dumpling", phonetic: "當-普林", note: "mp 中間沒有停頓,連起來念。" },
{ en: "egg", phonetic: "ㄟ-g", note: "結尾 g 小小地吐氣,不要大聲『哥』。" }
]
},
unitnumbers: {
vocab: [
{ en: "sixty", zh: "60", digit: "60" },
{ en: "seventy", zh: "70", digit: "70" },
{ en: "eighty", zh: "80", digit: "80" },
{ en: "ninety", zh: "90", digit: "90" },
{ en: "one hundred", zh: "100", digit: "100" }
]
}
};
// ============== 發音 ==============
let voices = [];
let bestVoice = null;
function pickBestVoice() {
voices = window.speechSynthesis.getVoices();
if (!voices.length) return;
const priorities = [
v => v.name.includes('Samantha'),
v => v.name.includes('Ava'),
v => v.name.includes('Karen'),
v => v.name.includes('Moira'),
v => v.name.includes('Google US English'),
v => v.name.includes('Google UK English Female'),
v => v.name.includes('Microsoft Aria'),
v => v.name.includes('Microsoft Jenny'),
v => v.name.includes('Natural') && v.lang.startsWith('en'),
v => v.name.includes('Neural') && v.lang.startsWith('en'),
v => v.lang === 'en-US' && v.localService,
v => v.lang === 'en-US',
v => v.lang.startsWith('en')
];
for (const test of priorities) {
const found = voices.find(test);
if (found) { bestVoice = found; return; }
}
}
if (window.speechSynthesis) {
pickBestVoice();
window.speechSynthesis.onvoiceschanged = pickBestVoice;
}
let currentAudio = null;
function speak(text, btn) {
const clean = text.replace(/<[^>]+>/g, '').trim();
// 優先播放預先產生的 mp3Gemini TTS
const file = window.__audioManifest && window.__audioManifest[clean];
if (file) {
if (window.speechSynthesis) window.speechSynthesis.cancel();
if (currentAudio) { try { currentAudio.pause(); } catch (e) {} }
const audio = new Audio('audio/' + file);
currentAudio = audio;
if (btn) btn.classList.add('playing');
const cleanup = () => { if (btn) btn.classList.remove('playing'); };
audio.addEventListener('ended', cleanup);
audio.addEventListener('error', () => { cleanup(); fallbackSpeak(text, btn); });
audio.play().catch(() => { cleanup(); fallbackSpeak(text, btn); });
return;
}
fallbackSpeak(text, btn);
}
function fallbackSpeak(text, btn) {
if (!window.speechSynthesis) return;
window.speechSynthesis.cancel();
const clean = text.replace(/<[^>]+>/g, '').trim();
const utter = new SpeechSynthesisUtterance(clean);
if (bestVoice) {
utter.voice = bestVoice;
utter.lang = bestVoice.lang;
} else {
utter.lang = 'en-US';
}
const wordCount = clean.split(/\s+/).length;
const isQuestion = clean.includes('?');
const isExclaim = clean.includes('!');
if (wordCount === 1) {
utter.rate = 0.75; utter.pitch = 1.0;
} else if (isQuestion) {
utter.rate = 0.95; utter.pitch = 1.1;
} else if (isExclaim) {
utter.rate = 1.0; utter.pitch = 1.15;
} else {
utter.rate = 0.95; utter.pitch = 1.0;
}
utter.volume = 1;
if (btn) {
btn.classList.add('playing');
utter.onend = () => btn.classList.remove('playing');
}
window.speechSynthesis.speak(utter);
}
// ============== 渲染 ==============
const stripChunks = s => s.replace(/ \/ /g, ' ').replace(/\s+/g, ' ').trim();
const chunkHTML = s => s.replace(/ \/ /g, '<span class="chunk-sep">/</span>');
function renderStory(list, container) {
container.innerHTML = list.map((item, i) => `
<div class="story-item" data-text="${stripChunks(item.en).replace(/"/g, '&quot;')}">
<div class="num">${String(i + 1).padStart(2, '0')}</div>
<div class="story-content">
<div class="en">${chunkHTML(item.en)}</div>
<div class="zh-trans">${item.zh}</div>
</div>
<button class="speak-btn" aria-label="播放">♪</button>
</div>
`).join('');
}
function renderVocab(list, container) {
container.innerHTML = list.map(item => `
<div class="vocab-card" data-text="${item.en}">
<div class="vocab-en">${item.en}</div>
<div class="vocab-zh">${item.zh}</div>
</div>
`).join('');
}
function renderSentences(list, container) {
container.innerHTML = list.map(item => {
const enDisplay = chunkHTML(item.en).replace(/<u>/g, '<span class="underline">').replace(/<\/u>/g, '</span>');
const zhDisplay = item.zh.replace(/<u>/g, '<span class="underline">').replace(/<\/u>/g, '</span>');
const dataText = stripChunks(item.en).replace(/<[^>]+>/g, '').replace(/"/g, '&quot;');
return `
<div class="sentence-item" data-text="${dataText}">
<div class="sentence-content">
<div class="en">${enDisplay}</div>
<div class="zh-trans">${zhDisplay}</div>
</div>
<button class="speak-btn" aria-label="播放">♪</button>
</div>
`;
}).join('');
}
function renderTips(list, container) {
if (!container) return;
container.innerHTML = list.map(item => `
<div class="tip-card" data-text="${item.en.replace(/"/g, '&quot;')}">
<div class="tip-head">
<span class="tip-en">${item.en}</span>
<span class="tip-phonetic">${item.phonetic}</span>
</div>
<div class="tip-note">${item.note}</div>
</div>
`).join('');
}
function renderPhonics(groups, container) {
container.innerHTML = groups.map(g => `
<div class="phonics-group">
<div class="phonics-tag">${g.tag}</div>
${g.words.map(w => `
<div class="phonics-word" data-text="${w.en}">
<span class="phonics-en"><span class="star">●</span>${w.en}</span>
<span class="phonics-zh">${w.zh}</span>
</div>
`).join('')}
</div>
`).join('');
}
renderStory(data.unit1.story, document.getElementById('story-1'));
renderTips(data.unit1.tips, document.getElementById('tips-1'));
renderVocab(data.unit1.vocab, document.getElementById('vocab-1'));
renderSentences(data.unit1.sentences, document.getElementById('sentences-1'));
renderSentences(data.unit1.daily, document.getElementById('daily-1'));
renderPhonics(data.unit1.phonics, document.getElementById('phonics-1'));
renderStory(data.unit2.story, document.getElementById('story-2'));
renderTips(data.unit2.tips, document.getElementById('tips-2'));
renderVocab(data.unit2.vocab, document.getElementById('vocab-2'));
renderSentences(data.unit2.sentences, document.getElementById('sentences-2'));
renderSentences(data.unit2.daily, document.getElementById('daily-2'));
renderPhonics(data.unit2.phonics, document.getElementById('phonics-2'));
renderStory(data.unit3.story, document.getElementById('story-3'));
renderTips(data.unit3.tips, document.getElementById('tips-3'));
renderVocab(data.unit3.vocab, document.getElementById('vocab-3'));
renderSentences(data.unit3.sentences, document.getElementById('sentences-3'));
renderSentences(data.unit3.daily, document.getElementById('daily-3'));
renderPhonics(data.unit3.phonics, document.getElementById('phonics-3'));
renderStory(data.unit4.story, document.getElementById('story-4'));
renderTips(data.unit4.tips, document.getElementById('tips-4'));
renderVocab(data.unit4.vocab, document.getElementById('vocab-4'));
renderSentences(data.unit4.sentences, document.getElementById('sentences-4'));
renderSentences(data.unit4.daily, document.getElementById('daily-4'));
renderPhonics(data.unit4.phonics, document.getElementById('phonics-4'));
renderStory(data.unitfestivals.story, document.getElementById('story-festivals'));
renderTips(data.unitfestivals.tips, document.getElementById('tips-festivals'));
renderVocab(data.unitfestivals.vocab, document.getElementById('vocab-festivals'));
// 數字卡
function renderNumbers(list, container) {
container.innerHTML = list.map(item => `
<div class="number-card" data-text="${item.en}">
<div class="number-digit">${item.digit}</div>
<div class="number-en">${item.en}</div>
</div>
`).join('');
}
renderNumbers(data.unitnumbers.vocab, document.getElementById('numbers-list'));
// ============== 點擊發音 ==============
document.addEventListener('click', (e) => {
if (e.target.closest('.quiz-section, .quiz, .question-area, .mode-btn, .choice, .quit-btn, .submit-btn, .spelling-input')) return;
const target = e.target.closest('[data-text]');
if (target) {
const text = target.dataset.text;
speak(text, target);
target.classList.add('playing');
setTimeout(() => target.classList.remove('playing'), 1500);
}
});
// ============== Unit 切換 ==============
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.unit').forEach(u => u.classList.remove('active'));
tab.classList.add('active');
document.getElementById('unit-' + tab.dataset.unit).classList.add('active');
window.scrollTo({ top: 0, behavior: 'smooth' });
});
});
// ============== 測驗 ==============
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
class Quiz {
constructor(section) {
this.section = section;
this.unit = section.dataset.unit;
this.vocab = data['unit' + this.unit].vocab;
this.home = section.querySelector('.quiz-home');
this.playing = section.querySelector('.quiz-playing');
this.result = section.querySelector('.result-screen');
this.qArea = section.querySelector('.question-area');
this.bind();
}
bind() {
this.section.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => this.start(btn.dataset.mode));
});
this.section.querySelector('.quit-btn').addEventListener('click', () => this.goHome());
this.section.querySelector('.retry-btn').addEventListener('click', () => this.start(this.mode));
this.section.querySelector('.back-btn').addEventListener('click', () => this.goHome());
}
goHome() {
this.home.style.display = 'block';
this.playing.classList.remove('active');
this.result.classList.remove('active');
}
start(mode) {
this.mode = mode;
this.questions = shuffle(this.vocab);
this.idx = 0;
this.correct = 0;
this.wrong = 0;
this.wrongList = [];
this.home.style.display = 'none';
this.result.classList.remove('active');
this.playing.classList.add('active');
this.section.querySelector('.q-total').textContent = this.questions.length;
this.section.querySelector('.s-correct').textContent = 0;
this.section.querySelector('.s-wrong').textContent = 0;
this.showQuestion();
}
showQuestion() {
if (this.idx >= this.questions.length) { this.finish(); return; }
this.section.querySelector('.q-current').textContent = this.idx + 1;
const q = this.questions[this.idx];
if (this.mode === 'en-to-zh') this.renderChoice(q, 'en', 'zh');
else if (this.mode === 'zh-to-en') this.renderChoice(q, 'zh', 'en');
else if (this.mode === 'listen') this.renderListen(q);
else if (this.mode === 'spell') this.renderSpell(q);
}
renderChoice(q, showField, answerField) {
const others = this.vocab.filter(v => v.en !== q.en);
const wrongs = shuffle(others).slice(0, 3);
const choices = shuffle([q, ...wrongs]);
const label = showField === 'en' ? '這個英文的意思是?' : '這個中文的英文是?';
this.qArea.innerHTML = `
<div class="question-box">
<div class="question-label">${label}</div>
<div class="question-main ${showField === 'zh' ? 'zh' : ''}">${q[showField]}</div>
${showField === 'en' ? '<button class="question-listen" title="聽發音">♪</button>' : ''}
</div>
<div class="choices">
${choices.map(c => `<button class="choice" data-answer="${c[answerField]}">${c[answerField]}</button>`).join('')}
</div>
`;
if (showField === 'en') {
setTimeout(() => speak(q.en), 300);
this.qArea.querySelector('.question-listen').addEventListener('click', () => speak(q.en));
}
this.qArea.querySelectorAll('.choice').forEach(btn => {
btn.addEventListener('click', () => this.answerChoice(btn, q[answerField], q));
});
}
renderListen(q) {
const others = this.vocab.filter(v => v.en !== q.en);
const wrongs = shuffle(others).slice(0, 3);
const choices = shuffle([q, ...wrongs]);
this.qArea.innerHTML = `
<div class="question-box">
<div class="question-label">聽聽看,是哪個字?</div>
<button class="question-listen" title="聽發音" style="font-size: 28px; width: 76px; height: 76px;">♪</button>
</div>
<div class="choices">
${choices.map(c => `<button class="choice" data-answer="${c.en}">${c.en}</button>`).join('')}
</div>
`;
setTimeout(() => speak(q.en), 400);
this.qArea.querySelector('.question-listen').addEventListener('click', () => speak(q.en));
this.qArea.querySelectorAll('.choice').forEach(btn => {
btn.addEventListener('click', () => this.answerChoice(btn, q.en, q));
});
}
renderSpell(q) {
const firstChar = q.en[0];
this.qArea.innerHTML = `
<div class="question-box">
<div class="question-label">把英文拼出來</div>
<div class="question-main zh">${q.zh}</div>
<button class="question-listen" title="聽發音">♪</button>
</div>
<input type="text" class="spelling-input" placeholder="${firstChar} _ _ _" autocomplete="off" autocapitalize="off" spellcheck="false">
<div class="spelling-hint">開頭字母 <strong>${firstChar}</strong> · 共 ${q.en.replace(/\s/g, '').length} 個字母${q.en.includes(' ') ? ' (兩個字)' : ''}</div>
<button class="submit-btn spell-submit">確認 →</button>
`;
const input = this.qArea.querySelector('.spelling-input');
const submit = this.qArea.querySelector('.spell-submit');
const listenBtn = this.qArea.querySelector('.question-listen');
setTimeout(() => { speak(q.en); input.focus(); }, 300);
listenBtn.addEventListener('click', () => speak(q.en));
const check = () => {
const val = input.value.trim().toLowerCase();
const ans = q.en.toLowerCase();
if (val === ans) {
input.classList.add('correct');
input.disabled = true;
submit.disabled = true;
this.correct++;
this.section.querySelector('.s-correct').textContent = this.correct;
speak(q.en);
setTimeout(() => this.nextQuestion(), 1200);
} else {
input.classList.add('wrong');
this.wrong++;
this.wrongList.push({ ...q, userAnswer: input.value.trim() || '(空白)' });
this.section.querySelector('.s-wrong').textContent = this.wrong;
input.disabled = true;
submit.disabled = true;
const hint = this.qArea.querySelector('.spelling-hint');
hint.innerHTML = `正確答案 <strong style="color: var(--coral); font-size: 18px;">${q.en}</strong>`;
setTimeout(() => this.nextQuestion(), 2000);
}
};
submit.addEventListener('click', check);
input.addEventListener('keydown', (e) => { if (e.key === 'Enter') check(); });
}
answerChoice(btn, correctAnswer, q) {
const allChoices = this.qArea.querySelectorAll('.choice');
allChoices.forEach(c => {
c.classList.add('revealed');
if (c.dataset.answer === correctAnswer) c.classList.add('correct-answer');
c.disabled = true;
});
if (btn.dataset.answer === correctAnswer) {
btn.classList.add('correct');
this.correct++;
this.section.querySelector('.s-correct').textContent = this.correct;
} else {
btn.classList.add('wrong');
this.wrong++;
this.wrongList.push(q);
this.section.querySelector('.s-wrong').textContent = this.wrong;
}
if (this.mode !== 'listen') speak(q.en);
setTimeout(() => this.nextQuestion(), 1400);
}
nextQuestion() { this.idx++; this.showQuestion(); }
finish() {
this.playing.classList.remove('active');
this.result.classList.add('active');
const total = this.questions.length;
const score = Math.round((this.correct / total) * 100);
const percent = this.correct / total;
let emoji, title;
if (percent === 1) { emoji = '✦'; title = '滿分,太厲害了'; }
else if (percent >= 0.8) { emoji = '✶'; title = '很棒'; }
else if (percent >= 0.6) { emoji = '✧'; title = '不錯喔'; }
else { emoji = '✺'; title = '再練習一下'; }
this.result.querySelector('.result-emoji').textContent = emoji;
this.result.querySelector('.result-title').textContent = title;
this.result.querySelector('.result-score').textContent = score;
this.result.querySelector('.result-detail').textContent =
`答對 ${this.correct} · 答錯 ${this.wrong} · 共 ${total}`;
const wrongWrap = this.result.querySelector('.wrong-list-wrapper');
if (this.wrongList.length > 0) {
wrongWrap.innerHTML = `
<div class="wrong-list">
<div class="wrong-list-title">要再複習的單字</div>
${this.wrongList.map(w => `
<div class="wrong-item">
<span class="w-en">${w.en}</span>
<span>${w.zh}</span>
</div>
`).join('')}
</div>
`;
} else {
wrongWrap.innerHTML = '';
}
}
}
document.querySelectorAll('.quiz[data-unit]').forEach(s => new Quiz(s));
</script>
</body>
</html>