2957 lines
92 KiB
HTML
2957 lines
92 KiB
HTML
<!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();
|
||
|
||
// 優先播放預先產生的 mp3(Gemini 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, '"')}">
|
||
<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, '"');
|
||
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, '"')}">
|
||
<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>
|