⬅ ギャラリーに戻る
✏️ '
eigo.html
' を修正中... (終わったら保存を押してね)
📷 素材アップロード
アップロード
🛠️ プログラム作成
[修正モード]
ファイル名:
.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ProVocab - 語源マスター版</title> <style> :root { --primary: #4a90e2; --secondary: #8e44ad; /* 語源用の色 */ --bg: #f4f7f6; --card-bg: #ffffff; --text-main: #2c3e50; } body { font-family: 'Helvetica Neue', Arial, sans-serif; background: var(--bg); margin: 0; padding-bottom: 90px; color: var(--text-main); } .container { max-width: 600px; margin: 0 auto; padding: 20px; } /* ナビゲーション */ nav { position: fixed; bottom: 0; width: 100%; background: #fff; display: flex; border-top: 1px solid #ddd; z-index: 1000; padding-bottom: env(safe-area-inset-bottom); } nav button { flex: 1; padding: 15px; border: none; background: none; cursor: pointer; font-weight: bold; color: #999; font-size: 0.8rem; display: flex; flex-direction: column; align-items: center; } nav button.active { color: var(--primary); background: #f0f7ff; } .mode-section { display: none; animation: fadeIn 0.3s; } .mode-section.active { display: block; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* 検索 */ .search-area { position: sticky; top: 0; background: var(--bg); padding: 10px 0; z-index: 100; } .search-box { display: flex; gap: 10px; } input { flex: 1; padding: 15px; border: 2px solid #ddd; border-radius: 12px; font-size: 16px; outline: none; transition: 0.2s; } input:focus { border-color: var(--primary); } /* カードデザイン */ .card { background: var(--card-bg); padding: 20px; border-radius: 16px; box-shadow: 0 4px 15px rgba(0,0,0,0.05); margin-bottom: 15px; position: relative; border-left: 5px solid transparent; } .card.registered { border-left-color: var(--primary); } .header-row { display: flex; justify-content: space-between; align-items: flex-start; } .word-info h3 { font-size: 1.6rem; margin: 0; color: var(--text-main); } .pronounce { font-size: 0.9rem; color: #888; font-weight: normal; } .emoji-big { font-size: 2.5rem; line-height: 1; } .tag { display: inline-block; background: #eee; padding: 3px 8px; border-radius: 4px; font-size: 0.75rem; margin: 5px 0; color: #555; font-weight: bold; } .meaning-box { margin: 10px 0; font-weight: bold; font-size: 1.1rem; } /* 語源スタイル */ .etymology-box { background: #f3e5f5; border-radius: 8px; padding: 10px; margin: 10px 0; border-left: 4px solid var(--secondary); } .etymology-title { color: var(--secondary); font-size: 0.75rem; font-weight: bold; display: block; margin-bottom: 3px; } .etymology-text { font-size: 0.9rem; color: #4a235a; } .example-box { font-style: italic; color: #666; background: #f9f9f9; padding: 10px; border-radius: 8px; font-size: 0.9rem; margin-top: 10px; } .btn { padding: 12px; border: none; border-radius: 8px; cursor: pointer; background: var(--primary); color: white; font-weight: bold; width: 100%; margin-top: 10px; } .btn:disabled { background: #ccc; cursor: default; } .btn-del { background: transparent; color: #e74c3c; border: 1px solid #e74c3c; width: auto; padding: 4px 10px; font-size: 0.8rem; position: absolute; top: 15px; right: 15px; } /* 記憶カード */ .flashcard-area { perspective: 1000px; height: 500px; margin-top: 10px; } .flashcard-inner { width: 100%; height: 100%; position: relative; transition: transform 0.5s; transform-style: preserve-3d; cursor: pointer; } .flashcard-inner.flipped { transform: rotateY(180deg); } .card-face { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; border-radius: 20px; background: white; box-shadow: 0 10px 30px rgba(0,0,0,0.1); padding: 25px; box-sizing: border-box; display: flex; flex-direction: column; } .card-front { justify-content: center; align-items: center; text-align: center; } .card-back { transform: rotateY(180deg); overflow-y: auto; justify-content: flex-start; } .mastery-controls { display: flex; gap: 8px; margin-top: 20px; visibility: hidden; } .m-btn { flex: 1; padding: 12px; border: none; border-radius: 30px; color: white; font-weight: bold; cursor: pointer; font-size: 0.9rem; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } /* 全リスト表示ボタン */ .show-all-btn { background: none; border: none; color: var(--primary); text-decoration: underline; cursor: pointer; width: 100%; margin-top: 10px; } </style> </head> <body> <div class="container"> <section id="search-mode" class="mode-section active"> <div class="search-area"> <h2 style="text-align:center; margin:0 0 10px 0;">🔍 辞書検索</h2> <div class="search-box"> <input type="text" id="searchInput" placeholder="単語を入力... (例: ex)" oninput="searchDict()"> </div> <button class="show-all-btn" onclick="showAllDict()">または全単語リストを表示</button> </div> <div id="searchResult"></div> </section> <section id="study-mode" class="mode-section"> <h2 style="text-align:center;">📖 My単語帳</h2> <div id="studyList"></div> </section> <section id="memorize-mode" class="mode-section"> <h2 style="text-align:center;">🧠 暗記カード</h2> <div class="flashcard-area" onclick="flipCard()"> <div class="flashcard-inner" id="cardInner"> <div class="card-face card-front" id="cardFront"> <p>単語帳に登録してスタート!</p> </div> <div class="card-face card-back" id="cardBack"></div> </div> </div> <div class="mastery-controls" id="masteryControls"> <button class="m-btn" style="background:#e74c3c;" onclick="setMastery(0, event)">❌ 忘</button> <button class="m-btn" style="background:#f39c12;" onclick="setMastery(1, event)">⚠️ 悩</button> <button class="m-btn" style="background:#2ecc71;" onclick="setMastery(2, event)">⭕ 覚</button> </div> </section> </div> <nav> <button onclick="changeMode('search-mode')" id="nav-search" class="active"><span>🔍</span>検索</button> <button onclick="changeMode('study-mode')" id="nav-study"><span>📖</span>勉強</button> <button onclick="changeMode('memorize-mode')" id="nav-memorize"><span>🧠</span>記憶</button> </nav> <script> // --- Gemini厳選:語源付き英検2級〜準1級単語データ (50語) --- const geminiDictionary = [ // A-C { w: "ambiguous", p: "/æmˈbɪɡ.ju.əs/", pos: "形", m: "曖昧な、多義の", ety: "ambi(両方) + agere(導く) → どちらにも取れる", ex: "His reply was ambiguous. (彼の返事は曖昧だった)", em: "🌫️" }, { w: "abundant", p: "/əˈbʌn.dənt/", pos: "形", m: "豊富な", ety: "ab(離れて) + unda(波) → 波があふれ出るほど", ex: "We have abundant resources. (豊富な資源がある)", em: "🌾" }, { w: "banish", p: "/ˈbæn.ɪʃ/", pos: "動", m: "追放する", ety: "ban(布告) → 命令によって追い払う", ex: "He was banished from the kingdom. (彼は王国から追放された)", em: "🚪" }, { w: "benefit", p: "/ˈben.ə.fɪt/", pos: "名", m: "利益、恩恵", ety: "bene(良い) + facere(行う) → 良いこと", ex: "The new law brought many benefits. (新法は多くの利益をもたらした)", em: "🎁" }, { w: "candidate", p: "/ˈkæn.dɪ.dət/", pos: "名", m: "候補者", ety: "candidus(白) → 白い服を着た立候補者", ex: "She is the best candidate for the job. (彼女はその仕事に最適な候補者だ)", em: "🙋" }, { w: "collapse", p: "/kəˈlæps/", pos: "動", m: "崩壊する", ety: "col(共に) + labi(滑る・落ちる) → 一緒に崩れ落ちる", ex: "The roof collapsed under the snow. (雪の重みで屋根が崩落した)", em: "🏚️" }, { w: "compensate", p: "/ˈkɑːm.pən.seɪt/", pos: "動", m: "埋め合わせる、補償する", ety: "com(共に) + pendere(吊るす・計る) → 重さを釣り合わせる", ex: "Nothing can compensate for the loss of time. (時間の喪失は何も埋め合わせられない)", em: "⚖️" }, { w: "contradict", p: "/ˌkɑːn.trəˈdɪkt/", pos: "動", m: "矛盾する、反論する", ety: "contra(反対に) + dicere(言う) → 反対のことを言う", ex: "The facts contradict his statement. (事実は彼の声明と矛盾する)", em: "🙅" }, // D-F { w: "decisive", p: "/dɪˈsaɪ.sɪv/", pos: "形", m: "決定的な、断固とした", ety: "de(離れて) + caedere(切る) → 切り離して決める", ex: "She played a decisive role. (彼女は決定的な役割を果たした)", em: "🔪" }, { w: "detect", p: "/dɪˈtekt/", pos: "動", m: "見つける、探知する", ety: "de(否定) + tegere(覆う) → 覆いを取る", ex: "The sensor detected smoke. (センサーが煙を探知した)", em: "🕵️" }, { w: "distinguish", p: "/dɪˈstɪŋ.ɡwɪʃ/", pos: "動", m: "区別する", ety: "dis(離れて) + stinguere(刺す) → 印をつけて分ける", ex: "Can you distinguish right from wrong? (善悪の区別がつきますか?)", em: "🔍" }, { w: "efficient", p: "/ɪˈfɪʃ.ənt/", pos: "形", m: "効率的な", ety: "ex(外へ) + facere(作る) → 十分に力を出し切る", ex: "He is an efficient worker. (彼は有能な働き手だ)", em: "⚡" }, { w: "encounter", p: "/ɪnˈkaʊn.t̬ɚ/", pos: "動", m: "遭遇する", ety: "in(中へ) + contra(対抗して) → 向かい合う", ex: "I encountered an old friend. (旧友にばったり会った)", em: "🤝" }, { w: "exhibit", p: "/ɪɡˈzɪb.ɪt/", pos: "動", m: "展示する、示す", ety: "ex(外へ) + habere(持つ) → 外に出して見せる", ex: "The museum exhibits ancient coins. (その博物館は古代の硬貨を展示している)", em: "🖼️" }, { w: "fluent", p: "/ˈfluː.ənt/", pos: "形", m: "流暢な", ety: "fluere(流れる) → 言葉が流れるような", ex: "She is fluent in three languages. (彼女は3ヶ国語が流暢だ)", em: "🗣️" }, // G-I { w: "generate", p: "/ˈdʒen.ə.reɪt/", pos: "動", m: "生み出す", ety: "genus(出生・種) → 新しいものを生む", ex: "This machine generates electricity. (この機械は電気を生み出す)", em: "⚙️" }, { w: "hostile", p: "/ˈhɑː.stəl/", pos: "形", m: "敵意のある", ety: "hostis(敵) → 敵のような", ex: "The crowd became hostile. (群衆は敵対的になった)", em: "😠" }, { w: "immense", p: "/ɪˈmens/", pos: "形", m: "巨大な、計り知れない", ety: "in(否定) + metiri(測る) → 測れないほど大きい", ex: "The project was an immense success. (その計画は大成功だった)", em: "🐘" }, { w: "inevitable", p: "/ɪˈnev.ə.t̬ə.bəl/", pos: "形", m: "避けられない", ety: "in(否定) + ex(外へ) + vitare(避ける) → 避けようがない", ex: "Accidents are inevitable. (事故は避けられないものだ)", em: "💥" }, { w: "inspect", p: "/ɪnˈspekt/", pos: "動", m: "検査する", ety: "in(中を) + specere(見る) → 中をじっくり見る", ex: "They inspected the elevator. (彼らはエレベーターを点検した)", em: "🔬" }, // J-M { w: "justify", p: "/ˈdʒʌs.tə.faɪ/", pos: "動", m: "正当化する", ety: "justus(正しい) + facere(する) → 正しいとする", ex: "Don't try to justify your mistakes. (間違いを正当化しようとするな)", em: "🛡️" }, { w: "launch", p: "/lɑːntʃ/", pos: "動", m: "発射する、開始する", ety: "lancea(槍) → 槍を投げるように送り出す", ex: "They launched a new rocket. (彼らは新しいロケットを発射した)", em: "🚀" }, { w: "magnificent", p: "/mæɡˈnɪf.ə.sənt/", pos: "形", m: "壮大な", ety: "magnus(大きい) + facere(作る) → 大きくなした", ex: "The view was magnificent. (景色は壮大だった)", em: "🏰" }, { w: "maintain", p: "/meɪnˈteɪn/", pos: "動", m: "維持する", ety: "manu(手) + tenere(保つ) → 手でしっかり持っておく", ex: "Maintain a constant speed. (一定の速度を維持しなさい)", em: "🔧" }, // N-P { w: "negative", p: "/ˈneɡ.ə.t̬ɪv/", pos: "形", m: "否定的な、消極的な", ety: "negare(否定する) → 否と言っている", ex: "He gave a negative answer. (彼は否定的な返事をした)", em: "➖" }, { w: "obvious", p: "/ˈɑːb.vi.əs/", pos: "形", m: "明らかな", ety: "ob(前に) + via(道) → 道にあって目立つ", ex: "The reason is obvious. (理由は明らかだ)", em: "☀️" }, { w: "opponent", p: "/əˈpoʊ.nənt/", pos: "名", m: "対戦相手、反対者", ety: "ob(対して) + ponere(置く) → 向かいに置かれた人", ex: "He defeated his opponent. (彼は対戦相手を打ち負かした)", em: "🤺" }, { w: "participate", p: "/pɑːrˈtɪs.ə.peɪt/", pos: "動", m: "参加する", ety: "pars(部分) + capere(取る) → 役割(部分)を受け持つ", ex: "Everyone participated in the game. (全員がゲームに参加した)", em: "🙋" }, { w: "persist", p: "/pɚˈsɪst/", pos: "動", m: "固執する、持続する", ety: "per(通して) + sistere(立つ) → 立ち続ける", ex: "He persisted in his opinion. (彼は自説に固執した)", em: "⚓" }, { w: "predict", p: "/prɪˈdɪkt/", pos: "動", m: "予測する", ety: "pre(前もって) + dicere(言う) → 先に言う", ex: "It is hard to predict the weather. (天気を予測するのは難しい)", em: "🔮" }, { w: "prohibit", p: "/proʊˈhɪb.ɪt/", pos: "動", m: "禁止する", ety: "pro(前に) + habere(持つ) → 前にさえぎって止める", ex: "Parking is prohibited here. (ここは駐車禁止だ)", em: "🛑" }, { w: "prospect", p: "/ˈprɑː.spekt/", pos: "名", m: "見込み、展望", ety: "pro(前方) + specere(見る) → 前の方を見ること", ex: "There is little prospect of success. (成功の見込みはほとんどない)", em: "🔭" }, // Q-S { w: "qualify", p: "/ˈkwɑː.lə.faɪ/", pos: "動", m: "資格を与える", ety: "qualis(どのような) + facere(作る) → ある性質を持たせる", ex: "He is qualified for the job. (彼はその仕事の資格がある)", em: "🎓" }, { w: "reject", p: "/rɪˈdʒekt/", pos: "動", m: "拒絶する", ety: "re(後ろへ) + jacere(投げる) → 投げ返す", ex: "She rejected his offer. (彼女は彼の申し出を断った)", em: "👋" }, { w: "reveal", p: "/rɪˈviːl/", pos: "動", m: "明らかにする", ety: "re(後ろへ・反転) + velum(ベール) → ベールをはぐ", ex: "The truth was finally revealed. (真実はついに明らかになった)", em: "🔓" }, { w: "revive", p: "/rɪˈvaɪv/", pos: "動", m: "生き返らせる", ety: "re(再び) + vivere(生きる) → 再び生きる", ex: "The rain revived the plants. (雨が植物を生き返らせた)", em: "🌱" }, { w: "sacrifice", p: "/ˈsæk.rə.faɪs/", pos: "動", m: "犠牲にする", ety: "sacer(神聖な) + facere(作る) → 神に捧げるもの", ex: "He sacrificed his holiday for work. (彼は仕事のために休日を犠牲にした)", em: "✝️" }, { w: "significant", p: "/sɪɡˈnɪf.ə.kənt/", pos: "形", m: "重要な", ety: "signum(印) + facere(作る) → 印となるほど目立つ", ex: "A significant change occurred. (重要な変化が起きた)", em: "💎" }, { w: "specialize", p: "/ˈspeʃ.ə.laɪz/", pos: "動", m: "専門にする", ety: "species(種類) → 特定の種類に絞る", ex: "She specializes in law. (彼女は法律を専門にしている)", em: "🧠" }, { w: "struggle", p: "/ˈstrʌɡ.əl/", pos: "動", m: "もがく、奮闘する", ety: "由来は不明だが「泥の中でもがく」イメージ", ex: "He struggled to get up. (彼は起き上がろうともがいた)", em: "💦" }, { w: "sufficient", p: "/səˈfɪʃ.ənt/", pos: "形", m: "十分な", ety: "sub(下に) + facere(作る) → 満たすことができる", ex: "We have sufficient food. (十分な食料がある)", em: "🥣" }, { w: "suppose", p: "/səˈpoʊz/", pos: "動", m: "仮定する、思う", ety: "sub(下に) + ponere(置く) → 土台として下に置く", ex: "I suppose you are right. (君が正しいと思うよ)", em: "🤔" }, // T-Z { w: "transfer", p: "/trænsˈfɝː/", pos: "動", m: "移動させる、乗り換える", ety: "trans(越えて) + ferre(運ぶ) → 向こうへ運ぶ", ex: "He was transferred to Tokyo. (彼は東京に転勤になった)", em: "🚛" }, { w: "transform", p: "/trænsˈfɔːrm/", pos: "動", m: "変形させる", ety: "trans(越えて) + forma(形) → 形を変える", ex: "The caterpillar transformed into a butterfly. (イモムシは蝶に変身した)", em: "🦋" }, { w: "unanimous", p: "/juːˈnæn.ə.məs/", pos: "形", m: "満場一致の", ety: "uni(一つ) + animus(心) → 心が一つになった", ex: "The decision was unanimous. (その決定は満場一致だった)", em: "☝️" }, { w: "urgent", p: "/ˈɝː.dʒənt/", pos: "形", m: "緊急の", ety: "urgere(押す・迫る) → 切迫している", ex: "I have an urgent message. (緊急のメッセージがあります)", em: "🚑" }, { w: "vague", p: "/veɪɡ/", pos: "形", m: "漠然とした", ety: "vagus(さまよう) → 意味が定まらずさまよう", ex: "I have a vague memory of it. (それについて漠然とした記憶がある)", em: "🌫️" }, { w: "valid", p: "/ˈvæl.ɪd/", pos: "形", m: "有効な、妥当な", ety: "valere(強い・価値がある) → 効力がある", ex: "Is your passport valid? (あなたのパスポートは有効ですか?)", em: "🎫" }, { w: "visible", p: "/ˈvɪz.ə.bəl/", pos: "形", m: "目に見える", ety: "vis(見る) + able(できる) → 見ることができる", ex: "Stars are visible tonight. (今夜は星が見える)", em: "👀" }, { w: "withdraw", p: "/wɪðˈdrɑː/", pos: "動", m: "撤回する、引き出す", ety: "with(離れて) + draw(引く) → 引いて離れさせる", ex: "He withdrew his hand quickly. (彼は素早く手を引っ込めた)", em: "🏧" } ]; let myWords = JSON.parse(localStorage.getItem('myWordsRoot')) || []; let currentIdx = 0; let isFlipped = false; // 初期化:全リスト表示 window.onload = function() { searchDict(); }; // --- 検索・リスト表示機能 --- function searchDict() { const input = document.getElementById('searchInput').value.trim().toLowerCase(); const resultDiv = document.getElementById('searchResult'); resultDiv.innerHTML = ""; // 入力が空の場合は何も出さない(「全リスト表示」ボタンで出す仕様へ) if (input.length === 0) { resultDiv.innerHTML = `<p style="text-align:center; color:#888;">上の入力欄に単語の一部を入れてください。<br>または「全単語リストを表示」をタップ。</p>`; return; } const matches = geminiDictionary.filter(d => d.w.toLowerCase().startsWith(input) || d.w.toLowerCase().includes(input) || d.m.includes(input) ); renderList(matches, resultDiv); } function showAllDict() { document.getElementById('searchInput').value = ""; const resultDiv = document.getElementById('searchResult'); renderList(geminiDictionary, resultDiv); } function renderList(list, container) { if (list.length === 0) { container.innerHTML = `<p style="text-align:center; color:#999;">見つかりませんでした。<br>内蔵辞書(50語)に含まれていない可能性があります。</p>`; return; } container.innerHTML = list.map(d => { const isReg = myWords.some(w => w.w === d.w); return ` <div class="card ${isReg ? 'registered' : ''}"> <div class="header-row"> <div class="emoji-big">${d.em}</div> <div class="word-info" style="flex:1; margin-left:15px;"> <h3>${d.w}</h3> <span class="pronounce">${d.p}</span> <div><span class="tag">${d.pos}</span></div> </div> </div> <div class="meaning-box">${d.m}</div> <div class="etymology-box"> <span class="etymology-title">🌱 語源・由来</span> <span class="etymology-text">${d.ety}</span> </div> <div class="example-box">${d.ex}</div> <button class="btn" onclick="toggleRegister('${d.w}')" ${isReg ? 'disabled' : ''}> ${isReg ? '✅ 単語帳に登録済み' : '単語帳に追加'} </button> </div> `; }).join(''); } // --- 登録・削除 --- function toggleRegister(wordStr) { const existing = myWords.find(w => w.w === wordStr); if (existing) return; // 既に登録済み const data = geminiDictionary.find(d => d.w === wordStr); if (data) { myWords.push({ ...data, mastery: 0 }); // mastery初期値 saveData(); // 表示更新 if(document.getElementById('searchInput').value) { searchDict(); } else { showAllDict(); } } } function saveData() { localStorage.setItem('myWordsRoot', JSON.stringify(myWords)); } // --- 勉強モード --- function renderStudyList() { const listDiv = document.getElementById('studyList'); if (myWords.length === 0) { listDiv.innerHTML = `<p style="text-align:center; margin-top:50px; color:#888;">単語帳は空です。<br>検索タブから単語を追加してください。</p>`; return; } listDiv.innerHTML = myWords.map((w, idx) => ` <div class="card"> <button class="btn-del" onclick="deleteWord(${idx})">🗑️ 削除</button> <div class="header-row"> <div class="emoji-big" style="font-size:2rem;">${w.em}</div> <div style="flex:1; margin-left:10px;"> <h3 style="font-size:1.4rem;">${w.w}</h3> <span style="font-size:0.8rem; color:#888;">記憶度: ${getMasteryLabel(w.mastery)}</span> </div> </div> <div class="meaning-box" style="font-size:1rem;">${w.m}</div> <div class="etymology-box" style="padding:5px 10px; font-size:0.85rem;"> <span style="color:var(--secondary);">🌱</span> ${w.ety} </div> <div class="example-box" style="font-size:0.8rem;">${w.ex}</div> </div> `).join(''); } function deleteWord(idx) { if(confirm("削除しますか?")) { myWords.splice(idx, 1); saveData(); renderStudyList(); } } function getMasteryLabel(level) { return ['❌', '⚠️', '⭕'][level] || '⭕'; } // --- 記憶モード --- function startMemorize() { showCard(currentIdx); } function showCard(idx) { const front = document.getElementById('cardFront'); const back = document.getElementById('cardBack'); const inner = document.getElementById('cardInner'); const controls = document.getElementById('masteryControls'); inner.classList.remove('flipped'); isFlipped = false; controls.style.visibility = 'hidden'; if (myWords.length === 0) { front.innerHTML = "<p>単語を登録してください</p>"; return; } // 範囲チェック if (idx >= myWords.length) currentIdx = 0; const w = myWords[currentIdx]; front.innerHTML = ` <div style="font-size:1rem; color:#888; margin-bottom:10px;">${w.pos}</div> <h1 style="font-size:2.8rem; margin:0; color:#333;">${w.w}</h1> <p>${w.p}</p> <p style="font-size:0.8rem; color:#ccc; margin-top:30px;">TAP TO FLIP</p> `; back.innerHTML = ` <div style="display:flex; align-items:center; gap:10px; border-bottom:1px solid #eee; padding-bottom:10px; margin-bottom:10px; width:100%;"> <span style="font-size:2rem;">${w.em}</span> <h2 style="margin:0;">${w.w}</h2> </div> <div class="meaning-box" style="color:#e74c3c;">${w.m}</div> <div class="etymology-box"> <span class="etymology-title">🌱 語源で覚える</span> ${w.ety} </div> <div class="example-box">${w.ex}</div> `; } function flipCard() { if (myWords.length === 0) return; const inner = document.getElementById('cardInner'); const controls = document.getElementById('masteryControls'); if (!isFlipped) { inner.classList.add('flipped'); isFlipped = true; // アニメーション後にボタン表示 setTimeout(() => { controls.style.visibility = 'visible'; }, 300); } } function setMastery(level, e) { e.stopPropagation(); // カード反転を防ぐ myWords[currentIdx].mastery = level; saveData(); // 次のカードへ currentIdx = (currentIdx + 1) % myWords.length; // カードを一度表に戻してから内容更新 const inner = document.getElementById('cardInner'); const controls = document.getElementById('masteryControls'); controls.style.visibility = 'hidden'; // 裏返った状態で中身を変えるとネタバレになるので、 // 一瞬で表に戻すアニメーション処理(簡易的) inner.style.transition = "none"; inner.classList.remove('flipped'); isFlipped = false; setTimeout(() => { inner.style.transition = "transform 0.5s"; showCard(currentIdx); }, 50); } // --- モード切替 --- function changeMode(modeId) { document.querySelectorAll('.mode-section').forEach(s => s.classList.remove('active')); document.querySelectorAll('nav button').forEach(b => b.classList.remove('active')); document.getElementById(modeId).classList.add('active'); document.getElementById('nav-' + modeId.split('-')[0]).classList.add('active'); if(modeId === 'study-mode') renderStudyList(); if(modeId === 'memorize-mode') startMemorize(); } </script> </body> </html>
上書き保存する
キャンセルして新規作成に戻る
📂 APP の作品
ProVocab - 縦スクロール暗記版
eigo2.html (01/15 10:37)
✏️ 修正
🌏 公開
🗑️
ProVocab - 語源マスター版
eigo.html (01/15 10:37)
✏️ 修正
🌏 公開
🗑️
進化版!わんわんタイマー
timer3.html (01/14 13:38)
✏️ 修正
🌏 公開
🗑️
わんわん応援タイマー
timer.html (01/14 12:49)
✏️ 修正
🌏 公開
🗑️
進化版!わんわんタイマー
timer2.html (01/14 12:17)
✏️ 修正
🌏 公開
🗑️