syurein
commited on
Commit
·
016f062
1
Parent(s):
04a9f31
First
Browse files- static/script.js +272 -89
- static/style.css +167 -32
- templates/learning.html +23 -16
static/script.js
CHANGED
@@ -6,6 +6,8 @@
|
|
6 |
let learningData = null; // 学習データ (learning.html用)
|
7 |
let currentItemIndex = 0; // 現在表示中のアイテムインデックス (learning.html用)
|
8 |
let currentMode = 'quiz'; // 現在のモード 'quiz' or 'summary' (learning.html用)
|
|
|
|
|
9 |
|
10 |
// --- 共通関数 ---
|
11 |
|
@@ -23,28 +25,43 @@ function navigateTo(url) {
|
|
23 |
*/
|
24 |
function openMenu() {
|
25 |
console.log("Menu button clicked. Implement menu display logic here.");
|
26 |
-
// 例: サイドバーメニューを表示する、モーダルを表示するなど
|
27 |
alert("メニュー機能は未実装です。\nフッターのナビゲーションを使用してください。");
|
28 |
}
|
29 |
|
30 |
/**
|
31 |
* ローディングスピナーを表示/非表示します。
|
32 |
* @param {boolean} show - trueで表示、falseで非表示
|
|
|
33 |
*/
|
34 |
-
function toggleLoading(show) {
|
35 |
-
const
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
generateButton.disabled =
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
}
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
}
|
49 |
}
|
50 |
|
@@ -96,23 +113,40 @@ async function handleGenerateSubmit() {
|
|
96 |
|
97 |
if (!youtubeUrl) {
|
98 |
displayErrorMessage('YouTubeリンクを入力してください。');
|
99 |
-
return false;
|
100 |
}
|
101 |
|
102 |
-
// 簡単なURL
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
return false;
|
106 |
}
|
107 |
|
108 |
|
109 |
-
toggleLoading(true); // ローディング開始
|
110 |
|
111 |
try {
|
112 |
const response = await fetch('/api/generate', {
|
113 |
method: 'POST',
|
114 |
headers: {
|
115 |
'Content-Type': 'application/json',
|
|
|
116 |
},
|
117 |
body: JSON.stringify({ url: youtubeUrl }),
|
118 |
});
|
@@ -121,19 +155,29 @@ async function handleGenerateSubmit() {
|
|
121 |
|
122 |
if (response.ok && result.success && result.data && result.data.id) {
|
123 |
// 成功したら学習画面へ遷移
|
124 |
-
alert('生成に成功しました!学習画面に移動します。');
|
125 |
goToLearning(result.data.id);
|
126 |
} else {
|
127 |
// 失敗したらエラーメッセージ表示
|
128 |
console.error('Generation failed:', result);
|
129 |
-
|
|
|
|
|
130 |
}
|
131 |
|
132 |
} catch (error) {
|
133 |
console.error('Error during generation request:', error);
|
134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
} finally {
|
136 |
-
toggleLoading(false); // ローディング終了
|
137 |
}
|
138 |
|
139 |
return false; // prevent default form submission
|
@@ -149,9 +193,9 @@ async function initializeLearningScreen() {
|
|
149 |
console.log('Initializing Learning Screen...');
|
150 |
const params = new URLSearchParams(window.location.search);
|
151 |
const contentId = params.get('id');
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
|
156 |
if (!contentId) {
|
157 |
displayLearningError('コンテンツIDが指定されていません。');
|
@@ -159,17 +203,20 @@ async function initializeLearningScreen() {
|
|
159 |
}
|
160 |
console.log('Content ID:', contentId);
|
161 |
|
162 |
-
//
|
163 |
-
if (loadingIndicator) loadingIndicator.textContent = '読み込み中...';
|
164 |
-
if (cardElement) cardElement.style.opacity = '0.5'; // 少し薄くする
|
165 |
-
if (paginationElement) paginationElement.style.display = 'none';
|
166 |
-
|
167 |
|
168 |
try {
|
169 |
const response = await fetch(`/api/learning/${contentId}`);
|
170 |
if (!response.ok) {
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
}
|
174 |
|
175 |
const result = await response.json();
|
@@ -177,8 +224,8 @@ async function initializeLearningScreen() {
|
|
177 |
|
178 |
if (result.success && result.data) {
|
179 |
learningData = result.data; // グローバル変数に格納
|
180 |
-
if (!learningData.items || learningData.items.length === 0) {
|
181 |
-
throw new Error('
|
182 |
}
|
183 |
// タイトルを設定
|
184 |
const titleElement = document.getElementById('learning-title');
|
@@ -189,7 +236,6 @@ async function initializeLearningScreen() {
|
|
189 |
// 最初のアイテムを表示
|
190 |
currentItemIndex = 0;
|
191 |
displayCurrentItem();
|
192 |
-
if (paginationElement) paginationElement.style.display = 'flex'; // ページネーション表示
|
193 |
|
194 |
} else {
|
195 |
throw new Error(result.message || '学習データの読み込みに失敗しました。');
|
@@ -199,9 +245,7 @@ async function initializeLearningScreen() {
|
|
199 |
console.error('Error initializing learning screen:', error);
|
200 |
displayLearningError(`読み込みエラー: ${error.message}`);
|
201 |
} finally {
|
202 |
-
//
|
203 |
-
if (cardElement) cardElement.style.opacity = '1';
|
204 |
-
// mode-indicatorはdisplayCurrentItemで更新される
|
205 |
}
|
206 |
}
|
207 |
|
@@ -209,6 +253,10 @@ async function initializeLearningScreen() {
|
|
209 |
* 現在の学習アイテムをカードに表示
|
210 |
*/
|
211 |
function displayCurrentItem() {
|
|
|
|
|
|
|
|
|
212 |
if (!learningData || !learningData.items || currentItemIndex < 0 || currentItemIndex >= learningData.items.length) {
|
213 |
console.error('Invalid learning data or index');
|
214 |
displayLearningError('学習データを表示できません。');
|
@@ -216,6 +264,7 @@ function displayCurrentItem() {
|
|
216 |
}
|
217 |
|
218 |
const item = learningData.items[currentItemIndex];
|
|
|
219 |
const cardTextElement = document.getElementById('card-text');
|
220 |
const answerTextElement = document.getElementById('answer-text');
|
221 |
const tapToShowElement = document.getElementById('tap-to-show');
|
@@ -227,52 +276,100 @@ function displayCurrentItem() {
|
|
227 |
answerTextElement.style.display = 'none';
|
228 |
tapToShowElement.style.display = 'none';
|
229 |
optionsArea.innerHTML = '';
|
230 |
-
optionsArea.style.display = 'none';
|
|
|
231 |
|
232 |
-
if (item.type === 'question') {
|
233 |
currentMode = 'quiz';
|
234 |
modeIndicator.textContent = 'クイズモード';
|
235 |
-
cardTextElement.textContent = item.
|
236 |
answerTextElement.textContent = `答え: ${item.answer}`; // 答えを事前に設定(非表示)
|
237 |
tapToShowElement.style.display = 'block'; // タップして表示を表示
|
238 |
|
239 |
-
//
|
240 |
-
if (item.options && Array.isArray(item.options)) {
|
241 |
-
optionsArea.style.display = 'block';
|
242 |
-
|
|
|
|
|
|
|
|
|
243 |
const button = document.createElement('button');
|
244 |
button.classList.add('option-button');
|
245 |
button.textContent = option;
|
246 |
-
// ★★★
|
247 |
-
button.onclick =
|
248 |
optionsArea.appendChild(button);
|
249 |
});
|
|
|
|
|
|
|
|
|
250 |
}
|
|
|
|
|
|
|
251 |
|
252 |
-
} else if (item.type === 'summary') {
|
253 |
currentMode = 'summary';
|
254 |
modeIndicator.textContent = '要約モード';
|
255 |
-
|
|
|
|
|
256 |
|
257 |
-
//
|
|
|
|
|
258 |
} else {
|
259 |
-
console.warn('Unknown item type:', item.type);
|
260 |
-
|
261 |
-
|
|
|
|
|
262 |
}
|
263 |
|
264 |
updatePagination();
|
265 |
}
|
266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
267 |
/**
|
268 |
-
*
|
|
|
269 |
*/
|
270 |
-
function revealAnswer() {
|
271 |
// クイズモードの場合のみ動作
|
272 |
if (currentMode === 'quiz') {
|
273 |
const answerTextElement = document.getElementById('answer-text');
|
274 |
const tapToShowElement = document.getElementById('tap-to-show');
|
275 |
const optionsArea = document.getElementById('options-area');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
|
277 |
if (answerTextElement) {
|
278 |
answerTextElement.style.display = 'block'; // 答えを表示
|
@@ -280,20 +377,26 @@ function revealAnswer() {
|
|
280 |
if (tapToShowElement) {
|
281 |
tapToShowElement.style.display = 'none'; // 「タップして表示」を隠す
|
282 |
}
|
|
|
|
|
|
|
|
|
283 |
|
284 |
// 選択肢ボタンを正解・不正解で色付け&無効化
|
285 |
if (optionsArea && learningData && learningData.items[currentItemIndex]) {
|
286 |
const correctAnswer = learningData.items[currentItemIndex].answer;
|
287 |
const buttons = optionsArea.getElementsByTagName('button');
|
288 |
for (let btn of buttons) {
|
289 |
-
btn.disabled = true; //
|
|
|
|
|
290 |
if (btn.textContent === correctAnswer) {
|
291 |
-
btn.classList.add('correct'); //
|
|
|
|
|
292 |
} else {
|
293 |
-
btn.classList.add('disabled'); //
|
294 |
}
|
295 |
-
// クリックイベントを削除(任意)
|
296 |
-
btn.onclick = null;
|
297 |
}
|
298 |
}
|
299 |
}
|
@@ -330,9 +433,21 @@ function updatePagination() {
|
|
330 |
|
331 |
if (pageInfo && prevButton && nextButton && learningData && learningData.items) {
|
332 |
const totalItems = learningData.items.length;
|
333 |
-
|
334 |
-
|
335 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
336 |
}
|
337 |
}
|
338 |
|
@@ -348,13 +463,44 @@ function displayLearningError(message) {
|
|
348 |
const tapToShow = document.getElementById('tap-to-show');
|
349 |
|
350 |
if (titleElement) titleElement.textContent = 'エラー';
|
351 |
-
if (
|
|
|
|
|
|
|
|
|
352 |
if (paginationElement) paginationElement.style.display = 'none';
|
|
|
353 |
if (optionsArea) optionsArea.style.display = 'none';
|
354 |
-
if (modeIndicator) modeIndicator.style.display = 'none';
|
355 |
if (tapToShow) tapToShow.style.display = 'none';
|
356 |
}
|
357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
358 |
|
359 |
// --- settings.html 用の処理 ---
|
360 |
|
@@ -380,9 +526,11 @@ function handleToggleChange(checkbox, type) {
|
|
380 |
function handleLogout() {
|
381 |
console.log("Logout clicked");
|
382 |
// TODO: 実際のログアウト処理(API呼び出し、セッションクリアなど)
|
383 |
-
alert("
|
384 |
// 必要であればログイン画面などに遷移
|
385 |
// navigateTo('/login');
|
|
|
|
|
386 |
}
|
387 |
|
388 |
/**
|
@@ -393,10 +541,8 @@ function applyDarkModePreference() {
|
|
393 |
const darkModeEnabled = localStorage.getItem('darkModeEnabled') === 'true';
|
394 |
document.body.classList.toggle('dark-mode', darkModeEnabled);
|
395 |
// 設定画面のトグルスイッチの状態も合わせる
|
396 |
-
|
397 |
-
|
398 |
-
toggle.checked = darkModeEnabled;
|
399 |
-
}
|
400 |
} catch (e) {
|
401 |
console.warn('Could not load dark mode preference from localStorage.');
|
402 |
}
|
@@ -404,30 +550,54 @@ function applyDarkModePreference() {
|
|
404 |
|
405 |
|
406 |
// --- ページの初期化処理 ---
|
407 |
-
|
408 |
-
|
|
|
|
|
409 |
|
410 |
-
|
411 |
|
412 |
-
if (pathname === '/' || pathname === '/input') {
|
413 |
-
// input.html の初期化(特に不要かもしれないが、フォームイベントリスナーなど)
|
414 |
const form = document.getElementById('generate-form');
|
415 |
if (form) {
|
416 |
form.addEventListener('submit', (event) => {
|
417 |
-
event.preventDefault();
|
418 |
handleGenerateSubmit();
|
419 |
});
|
420 |
}
|
421 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
422 |
initializeLearningScreen();
|
423 |
-
} else if (pathname === '/history') {
|
424 |
// history.html の初期化(動的にリストを生成する場合など)
|
425 |
-
|
426 |
-
|
427 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
428 |
}
|
429 |
|
430 |
-
//
|
431 |
updateFooterNavActiveState(pathname);
|
432 |
});
|
433 |
|
@@ -435,7 +605,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
435 |
/**
|
436 |
* フッターナビゲーションのアクティブ状態を更新
|
437 |
*/
|
438 |
-
function updateFooterNavActiveState(
|
439 |
const footerNav = document.querySelector('.footer-nav');
|
440 |
if (!footerNav) return;
|
441 |
|
@@ -444,19 +614,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
444 |
button.classList.remove('active'); // Reset all
|
445 |
const onclickAttr = button.getAttribute('onclick');
|
446 |
if (onclickAttr) {
|
447 |
-
|
|
|
448 |
button.classList.add('active');
|
449 |
-
} else if (
|
450 |
button.classList.add('active');
|
451 |
-
} else if (
|
452 |
button.classList.add('active');
|
453 |
}
|
|
|
|
|
|
|
|
|
454 |
}
|
455 |
});
|
456 |
}
|
457 |
|
458 |
|
459 |
// デバッグ用に一部関数をグローバルスコープに公開(開発中のみ推奨)
|
|
|
460 |
window.debug = {
|
461 |
navigateTo,
|
462 |
goToInput,
|
@@ -466,9 +642,16 @@ window.debug = {
|
|
466 |
openMenu,
|
467 |
handleGenerateSubmit,
|
468 |
initializeLearningScreen,
|
|
|
469 |
revealAnswer,
|
470 |
goToNext,
|
471 |
goToPrev,
|
472 |
handleToggleChange,
|
473 |
-
handleLogout
|
474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
let learningData = null; // 学習データ (learning.html用)
|
7 |
let currentItemIndex = 0; // 現在表示中のアイテムインデックス (learning.html用)
|
8 |
let currentMode = 'quiz'; // 現在のモード 'quiz' or 'summary' (learning.html用)
|
9 |
+
let correctEffectTimeout; // 正解エフェクト非表示用のタイマーID (learning.html用)
|
10 |
+
let correctEffect = null; // 正解エフェクト要素 (learning.html用)
|
11 |
|
12 |
// --- 共通関数 ---
|
13 |
|
|
|
25 |
*/
|
26 |
function openMenu() {
|
27 |
console.log("Menu button clicked. Implement menu display logic here.");
|
|
|
28 |
alert("メニュー機能は未実装です。\nフッターのナビゲーションを使用してください。");
|
29 |
}
|
30 |
|
31 |
/**
|
32 |
* ローディングスピナーを表示/非表示します。
|
33 |
* @param {boolean} show - trueで表示、falseで非表示
|
34 |
+
* @param {string} buttonId - 操作対象のボタンID (input.html用)
|
35 |
*/
|
36 |
+
function toggleLoading(show, buttonId = 'generate-button') {
|
37 |
+
const generateButton = document.getElementById(buttonId);
|
38 |
+
if (!generateButton) return;
|
39 |
+
|
40 |
+
const spinner = generateButton.querySelector('.loading-spinner');
|
41 |
+
const buttonText = generateButton.querySelector('.button-text');
|
42 |
+
|
43 |
+
if (show) {
|
44 |
+
generateButton.disabled = true;
|
45 |
+
if (spinner) spinner.style.display = 'inline-block';
|
46 |
+
if (buttonText) buttonText.textContent = '生成中...';
|
47 |
+
} else {
|
48 |
+
generateButton.disabled = false;
|
49 |
+
if (spinner) spinner.style.display = 'none';
|
50 |
+
if (buttonText) buttonText.textContent = '生成する'; // 元のテキストに戻す
|
51 |
}
|
52 |
+
|
53 |
+
// learning.html 用のローディング表示/非表示
|
54 |
+
const loadingIndicator = document.getElementById('mode-indicator');
|
55 |
+
const cardElement = document.getElementById('learning-card');
|
56 |
+
const paginationElement = document.querySelector('.pagination');
|
57 |
+
const optionsArea = document.getElementById('options-area');
|
58 |
+
|
59 |
+
if (pathname === '/learning') { // learning.htmlの場合のみ
|
60 |
+
if (loadingIndicator) loadingIndicator.textContent = show ? '読み込み中...' : ''; // モード表示はdisplayCurrentItemで行う
|
61 |
+
if (loadingIndicator) loadingIndicator.classList.toggle('loading', show);
|
62 |
+
if (cardElement) cardElement.style.opacity = show ? '0.5' : '1';
|
63 |
+
if (paginationElement) paginationElement.style.display = show ? 'none' : 'flex';
|
64 |
+
if (optionsArea) optionsArea.style.display = show ? 'none' : 'block'; // 初期はblockだがdisplayCurrentItemで調整
|
65 |
}
|
66 |
}
|
67 |
|
|
|
113 |
|
114 |
if (!youtubeUrl) {
|
115 |
displayErrorMessage('YouTubeリンクを入力してください。');
|
116 |
+
return false;
|
117 |
}
|
118 |
|
119 |
+
// 簡単なURL形式チェック
|
120 |
+
try {
|
121 |
+
const urlObj = new URL(youtubeUrl);
|
122 |
+
if (!['www.youtube.com', 'youtube.com', 'youtu.be'].includes(urlObj.hostname)) {
|
123 |
+
throw new Error('Invalid hostname');
|
124 |
+
}
|
125 |
+
// youtu.be/XXX または youtube.com/watch?v=XXX の形式を期待
|
126 |
+
if (urlObj.hostname === 'youtu.be' && !urlObj.pathname.substring(1)) {
|
127 |
+
throw new Error('Missing video ID for youtu.be');
|
128 |
+
}
|
129 |
+
if (urlObj.hostname.includes('youtube.com') && urlObj.pathname === '/watch' && !urlObj.searchParams.get('v')) {
|
130 |
+
throw new Error('Missing video ID for youtube.com/watch');
|
131 |
+
}
|
132 |
+
if (urlObj.hostname.includes('youtube.com') && urlObj.pathname.startsWith('/shorts/') && !urlObj.pathname.substring(8)) {
|
133 |
+
throw new Error('Missing video ID for youtube.com/shorts');
|
134 |
+
}
|
135 |
+
|
136 |
+
} catch (e) {
|
137 |
+
displayErrorMessage('有効なYouTube動画リンクを入力してください。(例: https://www.youtube.com/watch?v=...)');
|
138 |
return false;
|
139 |
}
|
140 |
|
141 |
|
142 |
+
toggleLoading(true, 'generate-button'); // ローディング開始
|
143 |
|
144 |
try {
|
145 |
const response = await fetch('/api/generate', {
|
146 |
method: 'POST',
|
147 |
headers: {
|
148 |
'Content-Type': 'application/json',
|
149 |
+
'Accept': 'application/json', // サーバーがJSONを返すことを期待
|
150 |
},
|
151 |
body: JSON.stringify({ url: youtubeUrl }),
|
152 |
});
|
|
|
155 |
|
156 |
if (response.ok && result.success && result.data && result.data.id) {
|
157 |
// 成功したら学習画面へ遷移
|
158 |
+
// alert('生成に成功しました!学習画面に移動します。'); // すぐ遷移するので不要かも
|
159 |
goToLearning(result.data.id);
|
160 |
} else {
|
161 |
// 失敗したらエラーメッセージ表示
|
162 |
console.error('Generation failed:', result);
|
163 |
+
// サーバーからのメッセージがあれば表示、なければ汎用メッセージ
|
164 |
+
const serverMessage = result.message || (result.error ? result.error.message : null);
|
165 |
+
displayErrorMessage(serverMessage || '生成中に不明なエラーが発生しました。');
|
166 |
}
|
167 |
|
168 |
} catch (error) {
|
169 |
console.error('Error during generation request:', error);
|
170 |
+
// ネットワークエラーか、JSONパースエラーか
|
171 |
+
if (error instanceof SyntaxError) {
|
172 |
+
displayErrorMessage('サーバーからの応答形式が不正です。');
|
173 |
+
} else if (error instanceof TypeError) {
|
174 |
+
displayErrorMessage('通信エラーが発生しました。ネットワーク接続を確認してください。');
|
175 |
+
}
|
176 |
+
else {
|
177 |
+
displayErrorMessage(`通信エラーが発生しました: ${error.message}`);
|
178 |
+
}
|
179 |
} finally {
|
180 |
+
toggleLoading(false, 'generate-button'); // ローディング終了
|
181 |
}
|
182 |
|
183 |
return false; // prevent default form submission
|
|
|
193 |
console.log('Initializing Learning Screen...');
|
194 |
const params = new URLSearchParams(window.location.search);
|
195 |
const contentId = params.get('id');
|
196 |
+
|
197 |
+
// ★★★ 正解エフェクト要素を取得 ★★★
|
198 |
+
correctEffect = document.getElementById('correct-effect');
|
199 |
|
200 |
if (!contentId) {
|
201 |
displayLearningError('コンテンツIDが指定されていません。');
|
|
|
203 |
}
|
204 |
console.log('Content ID:', contentId);
|
205 |
|
206 |
+
toggleLoading(true); // ローディング表示開始
|
|
|
|
|
|
|
|
|
207 |
|
208 |
try {
|
209 |
const response = await fetch(`/api/learning/${contentId}`);
|
210 |
if (!response.ok) {
|
211 |
+
// エラーレスポンスがJSON形式でない場合も考慮
|
212 |
+
let errorMessage = `サーバーからのデータ取得に失敗しました (${response.status})`;
|
213 |
+
try {
|
214 |
+
const errorData = await response.json();
|
215 |
+
errorMessage = errorData.message || errorMessage;
|
216 |
+
} catch (e) {
|
217 |
+
// JSONパース失敗時はそのままのエラーメッセージを使う
|
218 |
+
}
|
219 |
+
throw new Error(errorMessage);
|
220 |
}
|
221 |
|
222 |
const result = await response.json();
|
|
|
224 |
|
225 |
if (result.success && result.data) {
|
226 |
learningData = result.data; // グローバル変数に格納
|
227 |
+
if (!learningData.items || !Array.isArray(learningData.items) || learningData.items.length === 0) {
|
228 |
+
throw new Error('学習データが見つからないか、形式が正しくありません。');
|
229 |
}
|
230 |
// タイトルを設定
|
231 |
const titleElement = document.getElementById('learning-title');
|
|
|
236 |
// 最初のアイテムを表示
|
237 |
currentItemIndex = 0;
|
238 |
displayCurrentItem();
|
|
|
239 |
|
240 |
} else {
|
241 |
throw new Error(result.message || '学習データの読み込みに失敗しました。');
|
|
|
245 |
console.error('Error initializing learning screen:', error);
|
246 |
displayLearningError(`読み込みエラー: ${error.message}`);
|
247 |
} finally {
|
248 |
+
toggleLoading(false); // ローディング表示終了
|
|
|
|
|
249 |
}
|
250 |
}
|
251 |
|
|
|
253 |
* 現在の学習アイテムをカードに表示
|
254 |
*/
|
255 |
function displayCurrentItem() {
|
256 |
+
// ★★★ 最初にエフェクトを隠し、タイマーもクリア ★★★
|
257 |
+
hideCorrectEffect();
|
258 |
+
clearTimeout(correctEffectTimeout);
|
259 |
+
|
260 |
if (!learningData || !learningData.items || currentItemIndex < 0 || currentItemIndex >= learningData.items.length) {
|
261 |
console.error('Invalid learning data or index');
|
262 |
displayLearningError('学習データを表示できません。');
|
|
|
264 |
}
|
265 |
|
266 |
const item = learningData.items[currentItemIndex];
|
267 |
+
const cardElement = document.getElementById('learning-card');
|
268 |
const cardTextElement = document.getElementById('card-text');
|
269 |
const answerTextElement = document.getElementById('answer-text');
|
270 |
const tapToShowElement = document.getElementById('tap-to-show');
|
|
|
276 |
answerTextElement.style.display = 'none';
|
277 |
tapToShowElement.style.display = 'none';
|
278 |
optionsArea.innerHTML = '';
|
279 |
+
optionsArea.style.display = 'none'; // デフォルトは非表示
|
280 |
+
modeIndicator.classList.remove('loading'); // ローディングクラス除去
|
281 |
|
282 |
+
if (item.type === 'question' && item.question && item.answer) { // question と answer が存在するかチェック
|
283 |
currentMode = 'quiz';
|
284 |
modeIndicator.textContent = 'クイズモード';
|
285 |
+
cardTextElement.textContent = item.question; // question プロパティを使用
|
286 |
answerTextElement.textContent = `答え: ${item.answer}`; // 答えを事前に設定(非表示)
|
287 |
tapToShowElement.style.display = 'block'; // タップして表示を表示
|
288 |
|
289 |
+
// 選択肢ボタンを生成
|
290 |
+
if (item.options && Array.isArray(item.options) && item.options.length > 0) {
|
291 |
+
optionsArea.style.display = 'block'; // 選択肢があれば表示
|
292 |
+
// 選択肢をシャッフルする場合(任意)
|
293 |
+
// const shuffledOptions = [...item.options].sort(() => Math.random() - 0.5);
|
294 |
+
const optionsToDisplay = item.options; // シャッフルしない場合
|
295 |
+
|
296 |
+
optionsToDisplay.forEach(option => {
|
297 |
const button = document.createElement('button');
|
298 |
button.classList.add('option-button');
|
299 |
button.textContent = option;
|
300 |
+
// ★★★ 選択肢クリック時の動作を変更 ★★★
|
301 |
+
button.onclick = () => handleOptionClick(option); // クリックされた選択肢を渡す
|
302 |
optionsArea.appendChild(button);
|
303 |
});
|
304 |
+
} else {
|
305 |
+
console.warn(`Item ${currentItemIndex} is a question but has no options.`);
|
306 |
+
// 選択肢がないクイズの場合の表示(例:自由記述?)
|
307 |
+
tapToShowElement.style.display = 'block'; // 答え表示はできるようにしておく
|
308 |
}
|
309 |
+
// カード自体のクリックでも解答表示できるようにする
|
310 |
+
cardElement.onclick = revealAnswer;
|
311 |
+
tapToShowElement.onclick = revealAnswer;
|
312 |
|
313 |
+
} else if (item.type === 'summary' && item.text) { // summary と text が存在するかチェック
|
314 |
currentMode = 'summary';
|
315 |
modeIndicator.textContent = '要約モード';
|
316 |
+
// 改行を<br>に変換し、安全にHTMLとして設定(XSS対策のためtextContent推奨だが、改行表示のためにinnerHTMLを使用)
|
317 |
+
// サニタイズが必要な場合はライブラリ(DOMPurifyなど)を使うこと
|
318 |
+
cardTextElement.innerHTML = item.text.replace(/\n/g, '<br>');
|
319 |
|
320 |
+
// 要約モードでは答えも選択肢も不要、カードクリックも不要
|
321 |
+
cardElement.onclick = null;
|
322 |
+
tapToShowElement.style.display = 'none';
|
323 |
} else {
|
324 |
+
console.warn('Unknown or invalid item type:', item.type, item);
|
325 |
+
modeIndicator.textContent = '不明なデータ';
|
326 |
+
cardTextElement.textContent = `[データ形式エラー] ${item.text || item.question || '内容なし'}`;
|
327 |
+
cardElement.onclick = null; // クリック動作無効
|
328 |
+
tapToShowElement.style.display = 'none';
|
329 |
}
|
330 |
|
331 |
updatePagination();
|
332 |
}
|
333 |
|
334 |
+
// ★★★ 選択肢クリック時の処理を追加 ★★★
|
335 |
+
function handleOptionClick(selectedOption) {
|
336 |
+
if (currentMode !== 'quiz') return; // クイズモード以外では何もしない
|
337 |
+
|
338 |
+
const currentItem = learningData.items[currentItemIndex];
|
339 |
+
const correctAnswer = currentItem.answer;
|
340 |
+
const isCorrect = selectedOption === correctAnswer;
|
341 |
+
|
342 |
+
// --- 正解だった場合の処理 ---
|
343 |
+
if (isCorrect) {
|
344 |
+
console.log("正解!");
|
345 |
+
showCorrectEffect(); // ★★★ 正解エフェクトを表示 ★★★
|
346 |
+
} else {
|
347 |
+
console.log("不正解...");
|
348 |
+
// 不正解時のエフェクトなどが必要ならここに追加
|
349 |
+
}
|
350 |
+
|
351 |
+
// 選択肢ボタンの状態更新と解答表示
|
352 |
+
revealAnswer(selectedOption); // 選んだ選択肢を渡してスタイルを設定
|
353 |
+
}
|
354 |
+
|
355 |
+
|
356 |
/**
|
357 |
+
* クイズの解答を表示する(選択肢クリックまたはカードタップ時)
|
358 |
+
* @param {string|null} selectedOption - ユーザーが選択した選択肢 (選択肢クリック時のみ渡される)
|
359 |
*/
|
360 |
+
function revealAnswer(selectedOption = null) {
|
361 |
// クイズモードの場合のみ動作
|
362 |
if (currentMode === 'quiz') {
|
363 |
const answerTextElement = document.getElementById('answer-text');
|
364 |
const tapToShowElement = document.getElementById('tap-to-show');
|
365 |
const optionsArea = document.getElementById('options-area');
|
366 |
+
const cardElement = document.getElementById('learning-card');
|
367 |
+
|
368 |
+
// 既に表示済みなら何もしない(二重実行防止)
|
369 |
+
if (answerTextElement && answerTextElement.style.display === 'block') {
|
370 |
+
return;
|
371 |
+
}
|
372 |
+
|
373 |
|
374 |
if (answerTextElement) {
|
375 |
answerTextElement.style.display = 'block'; // 答えを表示
|
|
|
377 |
if (tapToShowElement) {
|
378 |
tapToShowElement.style.display = 'none'; // 「タップして表示」を隠す
|
379 |
}
|
380 |
+
// カード自体のクリックイベントを解除(解答表示後は不要)
|
381 |
+
if (cardElement) {
|
382 |
+
cardElement.onclick = null;
|
383 |
+
}
|
384 |
|
385 |
// 選択肢ボタンを正解・不正解で色付け&無効化
|
386 |
if (optionsArea && learningData && learningData.items[currentItemIndex]) {
|
387 |
const correctAnswer = learningData.items[currentItemIndex].answer;
|
388 |
const buttons = optionsArea.getElementsByTagName('button');
|
389 |
for (let btn of buttons) {
|
390 |
+
btn.disabled = true; // すべてのボタンを無効化
|
391 |
+
btn.onclick = null; // クリックイベントも削除
|
392 |
+
|
393 |
if (btn.textContent === correctAnswer) {
|
394 |
+
btn.classList.add('correct'); // 正解ボタン
|
395 |
+
} else if (btn.textContent === selectedOption) {
|
396 |
+
btn.classList.add('incorrect'); // 選択された不正解ボタン
|
397 |
} else {
|
398 |
+
btn.classList.add('other-disabled'); // 選ばれなかった他の選択肢
|
399 |
}
|
|
|
|
|
400 |
}
|
401 |
}
|
402 |
}
|
|
|
433 |
|
434 |
if (pageInfo && prevButton && nextButton && learningData && learningData.items) {
|
435 |
const totalItems = learningData.items.length;
|
436 |
+
// totalItemsが0の場合のエラーを防ぐ
|
437 |
+
if (totalItems > 0) {
|
438 |
+
pageInfo.textContent = `${currentItemIndex + 1} / ${totalItems}`;
|
439 |
+
prevButton.disabled = currentItemIndex === 0;
|
440 |
+
nextButton.disabled = currentItemIndex === totalItems - 1;
|
441 |
+
} else {
|
442 |
+
pageInfo.textContent = `0 / 0`;
|
443 |
+
prevButton.disabled = true;
|
444 |
+
nextButton.disabled = true;
|
445 |
+
}
|
446 |
+
} else {
|
447 |
+
// 要素がないかデータがない場合
|
448 |
+
if(pageInfo) pageInfo.textContent = '- / -';
|
449 |
+
if(prevButton) prevButton.disabled = true;
|
450 |
+
if(nextButton) nextButton.disabled = true;
|
451 |
}
|
452 |
}
|
453 |
|
|
|
463 |
const tapToShow = document.getElementById('tap-to-show');
|
464 |
|
465 |
if (titleElement) titleElement.textContent = 'エラー';
|
466 |
+
if (modeIndicator) modeIndicator.textContent = 'エラー';
|
467 |
+
if (cardElement) {
|
468 |
+
cardElement.innerHTML = `<p class="main-text" style="color: red; text-align: center; padding: 20px;">${message}</p>`;
|
469 |
+
cardElement.onclick = null; // クリック無効
|
470 |
+
}
|
471 |
if (paginationElement) paginationElement.style.display = 'none';
|
472 |
+
if (optionsArea) optionsArea.innerHTML = ''; // オプションクリア
|
473 |
if (optionsArea) optionsArea.style.display = 'none';
|
|
|
474 |
if (tapToShow) tapToShow.style.display = 'none';
|
475 |
}
|
476 |
|
477 |
+
// --- ★★★ 正解エフェクト表示関数を追加 ★★★ ---
|
478 |
+
function showCorrectEffect() {
|
479 |
+
if (correctEffect) {
|
480 |
+
// 既存のタイムアウトがあればクリア
|
481 |
+
clearTimeout(correctEffectTimeout);
|
482 |
+
|
483 |
+
correctEffect.classList.add('show'); // 表示用クラスを追加
|
484 |
+
|
485 |
+
// 1秒後 (1000ms) に非表示処理を開始
|
486 |
+
correctEffectTimeout = setTimeout(() => {
|
487 |
+
hideCorrectEffect();
|
488 |
+
}, 1000); // 表示時間はここで調整 (ミリ秒)
|
489 |
+
}
|
490 |
+
}
|
491 |
+
|
492 |
+
// --- ★★★ 正解エフェクト非表示関数を追加 ★★★ ---
|
493 |
+
function hideCorrectEffect() {
|
494 |
+
if (correctEffect && correctEffect.classList.contains('show')) {
|
495 |
+
correctEffect.classList.remove('show');
|
496 |
+
// アニメーション完了後に display: none に戻したい場合は transitionend イベントを使う
|
497 |
+
// correctEffect.addEventListener('transitionend', () => {
|
498 |
+
// correctEffect.style.display = 'none';
|
499 |
+
// }, { once: true }); // 一度だけ実行
|
500 |
+
// ただし、クラスの付け外しだけで opacity と transform を制御する方がシンプル
|
501 |
+
}
|
502 |
+
}
|
503 |
+
|
504 |
|
505 |
// --- settings.html 用の処理 ---
|
506 |
|
|
|
526 |
function handleLogout() {
|
527 |
console.log("Logout clicked");
|
528 |
// TODO: 実際のログアウト処理(API呼び出し、セッションクリアなど)
|
529 |
+
alert("ログアウトしました。(機能は未実装)");
|
530 |
// 必要であればログイン画面などに遷移
|
531 |
// navigateTo('/login');
|
532 |
+
// 例として入力画面に戻る
|
533 |
+
goToInput();
|
534 |
}
|
535 |
|
536 |
/**
|
|
|
541 |
const darkModeEnabled = localStorage.getItem('darkModeEnabled') === 'true';
|
542 |
document.body.classList.toggle('dark-mode', darkModeEnabled);
|
543 |
// 設定画面のトグルスイッチの状態も合わせる
|
544 |
+
// DOMContentLoaded より前に実行される場合があるので、
|
545 |
+
// settings.htmlの初期化時にスイッチの状態を更新する方が確実
|
|
|
|
|
546 |
} catch (e) {
|
547 |
console.warn('Could not load dark mode preference from localStorage.');
|
548 |
}
|
|
|
550 |
|
551 |
|
552 |
// --- ページの初期化処理 ---
|
553 |
+
const pathname = window.location.pathname; // グローバルスコープで取得
|
554 |
+
|
555 |
+
// ダークモード設定を最初に適用 (FOUC対策)
|
556 |
+
applyDarkModePreference();
|
557 |
|
558 |
+
document.addEventListener('DOMContentLoaded', () => {
|
559 |
|
560 |
+
if (pathname === '/' || pathname === '/input' || pathname === '/input.html') { // input.html のパスパターンを考慮
|
|
|
561 |
const form = document.getElementById('generate-form');
|
562 |
if (form) {
|
563 |
form.addEventListener('submit', (event) => {
|
564 |
+
event.preventDefault();
|
565 |
handleGenerateSubmit();
|
566 |
});
|
567 |
}
|
568 |
+
// input.html 専用の初期化があればここに追加
|
569 |
+
// 例: URLパラメータから初期値を設定するなど
|
570 |
+
const urlParams = new URLSearchParams(window.location.search);
|
571 |
+
const initialUrl = urlParams.get('url');
|
572 |
+
if (initialUrl) {
|
573 |
+
const urlInput = document.getElementById('youtube-url');
|
574 |
+
if (urlInput) {
|
575 |
+
urlInput.value = initialUrl;
|
576 |
+
}
|
577 |
+
}
|
578 |
+
|
579 |
+
} else if (pathname === '/learning' || pathname === '/learning.html') {
|
580 |
initializeLearningScreen();
|
581 |
+
} else if (pathname === '/history' || pathname === '/history.html') {
|
582 |
// history.html の初期化(動的にリストを生成する場合など)
|
583 |
+
console.log("History page loaded.");
|
584 |
+
// loadHistoryData(); // 仮の関数呼び出し
|
585 |
+
} else if (pathname === '/settings' || pathname === '/settings.html') {
|
586 |
+
// settings.html の初期化
|
587 |
+
console.log("Settings page loaded.");
|
588 |
+
// ダークモードトグルの状態をlocalStorageに合わせて更新
|
589 |
+
try {
|
590 |
+
const darkModeEnabled = localStorage.getItem('darkModeEnabled') === 'true';
|
591 |
+
const toggle = document.querySelector('input[onchange*="handleToggleChange"][onchange*="dark"]');
|
592 |
+
if (toggle) {
|
593 |
+
toggle.checked = darkModeEnabled;
|
594 |
+
}
|
595 |
+
} catch (e) {
|
596 |
+
console.warn('Could not set dark mode toggle state.');
|
597 |
+
}
|
598 |
}
|
599 |
|
600 |
+
// フッターナビゲーションのアクティブ状態設定
|
601 |
updateFooterNavActiveState(pathname);
|
602 |
});
|
603 |
|
|
|
605 |
/**
|
606 |
* フッターナビゲーションのアクティブ状態を更新
|
607 |
*/
|
608 |
+
function updateFooterNavActiveState(currentPath) {
|
609 |
const footerNav = document.querySelector('.footer-nav');
|
610 |
if (!footerNav) return;
|
611 |
|
|
|
614 |
button.classList.remove('active'); // Reset all
|
615 |
const onclickAttr = button.getAttribute('onclick');
|
616 |
if (onclickAttr) {
|
617 |
+
// パスの末尾が一致するか、またはルートパスの場合を確認
|
618 |
+
if ((currentPath === '/' || currentPath.endsWith('/input') || currentPath.endsWith('/input.html')) && onclickAttr.includes('goToInput')) {
|
619 |
button.classList.add('active');
|
620 |
+
} else if ((currentPath.endsWith('/history') || currentPath.endsWith('/history.html')) && onclickAttr.includes('goToHistory')) {
|
621 |
button.classList.add('active');
|
622 |
+
} else if ((currentPath.endsWith('/settings') || currentPath.endsWith('/settings.html')) && onclickAttr.includes('goToSettings')) {
|
623 |
button.classList.add('active');
|
624 |
}
|
625 |
+
// learning ページの場合はどのフッターもアクティブにしない (任意)
|
626 |
+
else if ((currentPath.endsWith('/learning') || currentPath.endsWith('/learning.html'))) {
|
627 |
+
// 何もしない(アクティブにならない)
|
628 |
+
}
|
629 |
}
|
630 |
});
|
631 |
}
|
632 |
|
633 |
|
634 |
// デバッグ用に一部関数をグローバルスコープに公開(開発中のみ推奨)
|
635 |
+
// 本番環境では削除またはコメントアウトすること
|
636 |
window.debug = {
|
637 |
navigateTo,
|
638 |
goToInput,
|
|
|
642 |
openMenu,
|
643 |
handleGenerateSubmit,
|
644 |
initializeLearningScreen,
|
645 |
+
handleOptionClick, // handleOptionClick を公開
|
646 |
revealAnswer,
|
647 |
goToNext,
|
648 |
goToPrev,
|
649 |
handleToggleChange,
|
650 |
+
handleLogout,
|
651 |
+
showCorrectEffect, // デバッグ用にエフェクト表示関数も公開
|
652 |
+
hideCorrectEffect, // デバッグ用にエフェクト非表示関数も公開
|
653 |
+
learningData, // 現在の学習データ確認用
|
654 |
+
currentItemIndex // 現在のインデックス確認用
|
655 |
+
};
|
656 |
+
|
657 |
+
// --- END OF FILE script.js ---
|
static/style.css
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
/* 基本スタイル */
|
2 |
body {
|
3 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
@@ -21,6 +23,7 @@ body {
|
|
21 |
display: flex;
|
22 |
flex-direction: column; /* 子要素を縦に並べる */
|
23 |
overflow: hidden; /* はみ出しを隠す */
|
|
|
24 |
}
|
25 |
|
26 |
.header {
|
@@ -39,23 +42,36 @@ body {
|
|
39 |
font-size: 24px;
|
40 |
cursor: pointer;
|
41 |
padding: 5px;
|
|
|
|
|
|
|
|
|
42 |
}
|
43 |
|
|
|
44 |
.header .action-btn {
|
45 |
-
|
|
|
|
|
46 |
height: 30px;
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
50 |
/* 必要ならアイコンや文字を配置 */
|
51 |
}
|
52 |
|
|
|
53 |
.header .title {
|
54 |
font-size: 18px;
|
55 |
font-weight: bold;
|
56 |
margin: 0;
|
57 |
text-align: center;
|
58 |
flex-grow: 1; /* 中央に配置するために */
|
|
|
|
|
|
|
|
|
59 |
}
|
60 |
|
61 |
main {
|
@@ -104,6 +120,7 @@ li.list-item:last-child {
|
|
104 |
align-items: center;
|
105 |
flex-grow: 1; /* テキスト部分が幅を取るように */
|
106 |
margin-right: 10px; /* 矢印とのスペース */
|
|
|
107 |
}
|
108 |
|
109 |
.list-item-icon {
|
@@ -112,8 +129,14 @@ li.list-item:last-child {
|
|
112 |
color: #007bff; /* アイコンの色 */
|
113 |
width: 24px; /* 幅を固定して揃える */
|
114 |
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
115 |
}
|
116 |
|
|
|
117 |
.list-item-text h3 {
|
118 |
font-size: 16px;
|
119 |
margin: 0 0 4px 0;
|
@@ -127,6 +150,9 @@ li.list-item:last-child {
|
|
127 |
font-size: 13px;
|
128 |
color: #777;
|
129 |
margin: 0;
|
|
|
|
|
|
|
130 |
}
|
131 |
|
132 |
.list-arrow {
|
@@ -148,18 +174,26 @@ li.list-item:last-child {
|
|
148 |
display: flex;
|
149 |
flex-direction: column;
|
150 |
justify-content: center;
|
|
|
151 |
cursor: pointer; /* タップ可能を示す */
|
|
|
|
|
|
|
|
|
152 |
}
|
|
|
153 |
.card p.main-text {
|
154 |
margin: 0;
|
155 |
line-height: 1.7; /* 行間調整 */
|
156 |
font-size: 16px;
|
|
|
157 |
}
|
158 |
.card p.answer-text {
|
159 |
margin-top: 15px;
|
160 |
font-size: 18px;
|
161 |
font-weight: bold;
|
162 |
color: #007bff;
|
|
|
163 |
}
|
164 |
.tap-to-show {
|
165 |
text-align: center;
|
@@ -167,6 +201,7 @@ li.list-item:last-child {
|
|
167 |
font-size: 14px;
|
168 |
margin-top: 10px;
|
169 |
cursor: pointer; /* タップ可能を示す */
|
|
|
170 |
}
|
171 |
|
172 |
/* Pagination */
|
@@ -184,7 +219,13 @@ li.list-item:last-child {
|
|
184 |
color: #007bff;
|
185 |
cursor: pointer;
|
186 |
padding: 5px 15px; /* タップしやすく */
|
|
|
|
|
187 |
}
|
|
|
|
|
|
|
|
|
188 |
.pagination button:disabled {
|
189 |
color: #ccc;
|
190 |
cursor: default;
|
@@ -213,6 +254,7 @@ li.list-item:last-child {
|
|
213 |
width: 50px;
|
214 |
height: 24px;
|
215 |
cursor: pointer;
|
|
|
216 |
}
|
217 |
.toggle-switch input {
|
218 |
opacity: 0;
|
@@ -294,27 +336,25 @@ input:checked + .slider:before {
|
|
294 |
align-items: center;
|
295 |
justify-content: center;
|
296 |
margin: 0 auto 25px auto;
|
297 |
-
transition: background-color 0.2s;
|
298 |
}
|
299 |
.input-area .generate-button:hover {
|
300 |
background-color: #e03024;
|
301 |
}
|
302 |
.input-area .generate-button:disabled {
|
303 |
-
background-color: #
|
|
|
304 |
cursor: not-allowed;
|
305 |
}
|
|
|
|
|
|
|
306 |
.input-area .generate-button .icon { /* 再生ボタン風アイコン */
|
307 |
margin-left: 8px;
|
308 |
font-size: 14px;
|
309 |
}
|
310 |
.input-area .loading-spinner {
|
311 |
-
|
312 |
-
border-radius: 50%;
|
313 |
-
border-top-color: #fff;
|
314 |
-
width: 16px;
|
315 |
-
height: 16px;
|
316 |
-
animation: spin 1s linear infinite;
|
317 |
-
margin-right: 8px; /* テキストとのスペース */
|
318 |
}
|
319 |
@keyframes spin {
|
320 |
to { transform: rotate(360deg); }
|
@@ -347,6 +387,8 @@ input:checked + .slider:before {
|
|
347 |
margin-top: -15px; /* ボタンとの間を詰める */
|
348 |
margin-bottom: 15px;
|
349 |
min-height: 1em; /* エラーなくても高さを確保 */
|
|
|
|
|
350 |
}
|
351 |
/* static/style.css に追加 */
|
352 |
|
@@ -360,9 +402,9 @@ input:checked + .slider:before {
|
|
360 |
border-top: 1px solid #e0e0e0;
|
361 |
display: flex;
|
362 |
justify-content: space-around;
|
363 |
-
padding: 4px 0 8px 0; /*
|
364 |
box-shadow: 0 -1px 4px rgba(0,0,0,0.08);
|
365 |
-
z-index:
|
366 |
}
|
367 |
|
368 |
.footer-nav button {
|
@@ -378,6 +420,9 @@ input:checked + .slider:before {
|
|
378 |
flex-grow: 1; /* ボタンが均等に幅を取るように */
|
379 |
transition: color 0.2s ease;
|
380 |
}
|
|
|
|
|
|
|
381 |
|
382 |
.footer-nav .nav-icon {
|
383 |
font-size: 22px;
|
@@ -391,21 +436,26 @@ input:checked + .slider:before {
|
|
391 |
/* --- main要素の底上げ --- */
|
392 |
/* フッターにコンテンツが隠れないように */
|
393 |
main {
|
394 |
-
padding-bottom:
|
395 |
}
|
396 |
|
397 |
-
/* --- ローディングスピナー (
|
398 |
.loading-spinner {
|
399 |
-
border: 3px solid rgba(0, 0, 0, 0.1);
|
400 |
-
border-left-color: #
|
401 |
border-radius: 50%;
|
402 |
width: 16px;
|
403 |
height: 16px;
|
404 |
animation: spin 1s linear infinite;
|
405 |
display: inline-block; /* ボタン内で表示 */
|
406 |
-
margin-
|
407 |
vertical-align: middle;
|
408 |
}
|
|
|
|
|
|
|
|
|
|
|
409 |
|
410 |
@keyframes spin {
|
411 |
0% { transform: rotate(0deg); }
|
@@ -413,53 +463,138 @@ main {
|
|
413 |
}
|
414 |
|
415 |
/* --- learning.html のローディング(簡易版)--- */
|
416 |
-
/* 必要であれば .loading-
|
417 |
-
|
|
|
|
|
418 |
|
419 |
/* --- learning.html の解答ボタン スタイル --- */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
420 |
.option-button.correct {
|
421 |
background-color: #d4edda; /* 緑系 */
|
422 |
color: #155724;
|
423 |
border-color: #c3e6cb;
|
|
|
424 |
}
|
425 |
|
426 |
-
.option-button.incorrect { /*
|
427 |
background-color: #f8d7da; /* 赤系 */
|
428 |
color: #721c24;
|
429 |
border-color: #f5c6cb;
|
|
|
430 |
}
|
431 |
|
432 |
-
.option-button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
433 |
background-color: #e9ecef;
|
434 |
color: #6c757d;
|
435 |
border-color: #ced4da;
|
436 |
-
opacity: 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
437 |
}
|
438 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
439 |
|
440 |
/* --- ダークモード用スタイル (一部) --- */
|
441 |
body.dark-mode {
|
442 |
background-color: #121212;
|
443 |
color: #e0e0e0;
|
444 |
}
|
445 |
-
body.dark-mode .screen { background-color: #1e1e1e; }
|
446 |
body.dark-mode .header { background-color: #1f1f1f; border-bottom-color: #333; }
|
447 |
-
body.dark-mode .header .title, body.dark-mode .header .menu-btn { color: #e0e0e0; }
|
448 |
body.dark-mode .footer-nav { background-color: #1f1f1f; border-top-color: #333; }
|
449 |
body.dark-mode .footer-nav button { color: #888; }
|
450 |
body.dark-mode .footer-nav button.active { color: #58a6ff; } /* ダークモードでのアクティブ色 */
|
451 |
body.dark-mode .card { background-color: #2c2c2c; border-color: #444; color: #e0e0e0; }
|
452 |
-
body.dark-mode .
|
453 |
-
body.dark-mode .list-item-
|
|
|
|
|
|
|
454 |
body.dark-mode .list-arrow { color: #aaa; }
|
455 |
body.dark-mode .settings-item span { color: #e0e0e0; }
|
456 |
body.dark-mode .section-title { color: #aaa; }
|
457 |
body.dark-mode input[type="text"] { background-color: #333; border-color: #555; color: #e0e0e0; }
|
458 |
-
body.dark-mode .generate-button { background-color: #
|
|
|
|
|
|
|
459 |
body.dark-mode .option-button { background-color: #444; color: #e0e0e0; border-color: #666; }
|
|
|
|
|
460 |
body.dark-mode .option-button.correct { background-color: #2a6831; color: #e0e0e0; border-color: #41984b; }
|
461 |
-
body.dark-mode .option-button.
|
|
|
|
|
|
|
462 |
body.dark-mode .pagination button:disabled { color: #666; }
|
|
|
|
|
463 |
body.dark-mode .toggle-switch .slider { background-color: #555; }
|
464 |
body.dark-mode .toggle-switch input:checked + .slider { background-color: #58a6ff; }
|
465 |
-
/*
|
|
|
|
|
|
|
|
1 |
+
/* --- START OF FILE style.css --- */
|
2 |
+
|
3 |
/* 基本スタイル */
|
4 |
body {
|
5 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
|
23 |
display: flex;
|
24 |
flex-direction: column; /* 子要素を縦に並べる */
|
25 |
overflow: hidden; /* はみ出しを隠す */
|
26 |
+
position: relative; /* Correct Effectの基準になるかも */
|
27 |
}
|
28 |
|
29 |
.header {
|
|
|
42 |
font-size: 24px;
|
43 |
cursor: pointer;
|
44 |
padding: 5px;
|
45 |
+
color: #007bff; /* ボタンの色も少し設定 */
|
46 |
+
}
|
47 |
+
.header .menu-btn:active, .header .action-btn:active {
|
48 |
+
opacity: 0.7;
|
49 |
}
|
50 |
|
51 |
+
|
52 |
.header .action-btn {
|
53 |
+
/* 右上のボタンのデザイン例 (不要なら削除) */
|
54 |
+
/* visibility: hidden; */ /* 表示しない場合はこれ */
|
55 |
+
width: 30px; /* サイズ調整 */
|
56 |
height: 30px;
|
57 |
+
color: #007bff; /* アイコンの色 */
|
58 |
+
/* background-color: #007bff; */ /* 背景色なし */
|
59 |
+
/* border-radius: 50%; */
|
60 |
+
/* padding: 0; */
|
61 |
/* 必要ならアイコンや文字を配置 */
|
62 |
}
|
63 |
|
64 |
+
|
65 |
.header .title {
|
66 |
font-size: 18px;
|
67 |
font-weight: bold;
|
68 |
margin: 0;
|
69 |
text-align: center;
|
70 |
flex-grow: 1; /* 中央に配置するために */
|
71 |
+
padding: 0 5px; /* 左右ボタンとの間隔 */
|
72 |
+
white-space: nowrap;
|
73 |
+
overflow: hidden;
|
74 |
+
text-overflow: ellipsis;
|
75 |
}
|
76 |
|
77 |
main {
|
|
|
120 |
align-items: center;
|
121 |
flex-grow: 1; /* テキスト部分が幅を取るように */
|
122 |
margin-right: 10px; /* 矢印とのスペース */
|
123 |
+
overflow: hidden; /* はみ出し防止 */
|
124 |
}
|
125 |
|
126 |
.list-item-icon {
|
|
|
129 |
color: #007bff; /* アイコンの色 */
|
130 |
width: 24px; /* 幅を固定して揃える */
|
131 |
text-align: center;
|
132 |
+
flex-shrink: 0;
|
133 |
+
}
|
134 |
+
|
135 |
+
.list-item-text {
|
136 |
+
overflow: hidden; /* はみ出し防止 */
|
137 |
}
|
138 |
|
139 |
+
|
140 |
.list-item-text h3 {
|
141 |
font-size: 16px;
|
142 |
margin: 0 0 4px 0;
|
|
|
150 |
font-size: 13px;
|
151 |
color: #777;
|
152 |
margin: 0;
|
153 |
+
white-space: nowrap;
|
154 |
+
overflow: hidden;
|
155 |
+
text-overflow: ellipsis;
|
156 |
}
|
157 |
|
158 |
.list-arrow {
|
|
|
174 |
display: flex;
|
175 |
flex-direction: column;
|
176 |
justify-content: center;
|
177 |
+
align-items: center; /* 中央揃え */
|
178 |
cursor: pointer; /* タップ可能を示す */
|
179 |
+
transition: background-color 0.2s; /* 背景色変更アニメーション */
|
180 |
+
}
|
181 |
+
.card:active {
|
182 |
+
background-color: #f0f0f0; /* タップ時のフィードバック */
|
183 |
}
|
184 |
+
|
185 |
.card p.main-text {
|
186 |
margin: 0;
|
187 |
line-height: 1.7; /* 行間調整 */
|
188 |
font-size: 16px;
|
189 |
+
max-width: 100%; /* はみ出し防止 */
|
190 |
}
|
191 |
.card p.answer-text {
|
192 |
margin-top: 15px;
|
193 |
font-size: 18px;
|
194 |
font-weight: bold;
|
195 |
color: #007bff;
|
196 |
+
max-width: 100%; /* はみ出し防止 */
|
197 |
}
|
198 |
.tap-to-show {
|
199 |
text-align: center;
|
|
|
201 |
font-size: 14px;
|
202 |
margin-top: 10px;
|
203 |
cursor: pointer; /* タップ可能を示す */
|
204 |
+
padding: 5px; /* タップしやすく */
|
205 |
}
|
206 |
|
207 |
/* Pagination */
|
|
|
219 |
color: #007bff;
|
220 |
cursor: pointer;
|
221 |
padding: 5px 15px; /* タップしやすく */
|
222 |
+
border-radius: 8px; /* 視覚的フィードバック用 */
|
223 |
+
transition: background-color 0.2s;
|
224 |
}
|
225 |
+
.pagination button:active:not(:disabled) {
|
226 |
+
background-color: rgba(0, 123, 255, 0.1); /* タップフィードバック */
|
227 |
+
}
|
228 |
+
|
229 |
.pagination button:disabled {
|
230 |
color: #ccc;
|
231 |
cursor: default;
|
|
|
254 |
width: 50px;
|
255 |
height: 24px;
|
256 |
cursor: pointer;
|
257 |
+
flex-shrink: 0; /* 縮まないように */
|
258 |
}
|
259 |
.toggle-switch input {
|
260 |
opacity: 0;
|
|
|
336 |
align-items: center;
|
337 |
justify-content: center;
|
338 |
margin: 0 auto 25px auto;
|
339 |
+
transition: background-color 0.2s, opacity 0.2s;
|
340 |
}
|
341 |
.input-area .generate-button:hover {
|
342 |
background-color: #e03024;
|
343 |
}
|
344 |
.input-area .generate-button:disabled {
|
345 |
+
background-color: #ff3b30; /* 色は変えず */
|
346 |
+
opacity: 0.6; /* 薄くする */
|
347 |
cursor: not-allowed;
|
348 |
}
|
349 |
+
.input-area .generate-button .button-text {
|
350 |
+
/* display: inline-block; */ /* 縦中央揃えのためFlexで十分 */
|
351 |
+
}
|
352 |
.input-area .generate-button .icon { /* 再生ボタン風アイコン */
|
353 |
margin-left: 8px;
|
354 |
font-size: 14px;
|
355 |
}
|
356 |
.input-area .loading-spinner {
|
357 |
+
/* Spinnerは下の共通スタイルで定義 */
|
|
|
|
|
|
|
|
|
|
|
|
|
358 |
}
|
359 |
@keyframes spin {
|
360 |
to { transform: rotate(360deg); }
|
|
|
387 |
margin-top: -15px; /* ボタンとの間を詰める */
|
388 |
margin-bottom: 15px;
|
389 |
min-height: 1em; /* エラーなくても高さを確保 */
|
390 |
+
text-align: center; /* 中央揃え */
|
391 |
+
padding: 0 15px; /* 左右に余白 */
|
392 |
}
|
393 |
/* static/style.css に追加 */
|
394 |
|
|
|
402 |
border-top: 1px solid #e0e0e0;
|
403 |
display: flex;
|
404 |
justify-content: space-around;
|
405 |
+
padding: 4px 0 calc(env(safe-area-inset-bottom, 0px) + 8px) 0; /* iPhone下部対応 + 少し余白 */
|
406 |
box-shadow: 0 -1px 4px rgba(0,0,0,0.08);
|
407 |
+
z-index: 100; /* Correct Effectより下 */
|
408 |
}
|
409 |
|
410 |
.footer-nav button {
|
|
|
420 |
flex-grow: 1; /* ボタンが均等に幅を取るように */
|
421 |
transition: color 0.2s ease;
|
422 |
}
|
423 |
+
.footer-nav button:active {
|
424 |
+
opacity: 0.7;
|
425 |
+
}
|
426 |
|
427 |
.footer-nav .nav-icon {
|
428 |
font-size: 22px;
|
|
|
436 |
/* --- main要素の底上げ --- */
|
437 |
/* フッターにコンテンツが隠れないように */
|
438 |
main {
|
439 |
+
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 80px); /* フッターの高さ + iPhone下部 + 余白 */
|
440 |
}
|
441 |
|
442 |
+
/* --- ローディングスピナー (共通) --- */
|
443 |
.loading-spinner {
|
444 |
+
border: 3px solid rgba(0, 0, 0, 0.1); /* generate button以外でのデフォルト */
|
445 |
+
border-left-color: #007bff; /* デフォルトの色 */
|
446 |
border-radius: 50%;
|
447 |
width: 16px;
|
448 |
height: 16px;
|
449 |
animation: spin 1s linear infinite;
|
450 |
display: inline-block; /* ボタン内で表示 */
|
451 |
+
margin-right: 8px; /* ボタンテキストとの間隔 */
|
452 |
vertical-align: middle;
|
453 |
}
|
454 |
+
/* input.html の generate-button 内のスピナー */
|
455 |
+
.generate-button .loading-spinner {
|
456 |
+
border-color: rgba(255, 255, 255, 0.3);
|
457 |
+
border-left-color: #fff;
|
458 |
+
}
|
459 |
|
460 |
@keyframes spin {
|
461 |
0% { transform: rotate(0deg); }
|
|
|
463 |
}
|
464 |
|
465 |
/* --- learning.html のローディング(簡易版)--- */
|
466 |
+
/* 必要であれば .loading-indicator-large などを定義 */
|
467 |
+
#mode-indicator.loading {
|
468 |
+
color: #888;
|
469 |
+
}
|
470 |
|
471 |
/* --- learning.html の解答ボタン スタイル --- */
|
472 |
+
.option-button {
|
473 |
+
display: block;
|
474 |
+
width: calc(100% - 20px); /* 左右の余白を考慮 */
|
475 |
+
margin: 10px auto; /* 中央寄せ */
|
476 |
+
padding: 15px;
|
477 |
+
font-size: 16px;
|
478 |
+
border: 1px solid #ccc;
|
479 |
+
border-radius: 8px;
|
480 |
+
background-color: #fff;
|
481 |
+
color: #333;
|
482 |
+
cursor: pointer;
|
483 |
+
transition: background-color 0.2s, border-color 0.2s, color 0.2s, opacity 0.2s;
|
484 |
+
text-align: left; /* テキスト左寄せ */
|
485 |
+
}
|
486 |
+
.option-button:hover:not(:disabled) {
|
487 |
+
background-color: #f0f0f0;
|
488 |
+
}
|
489 |
+
.option-button:active:not(:disabled) {
|
490 |
+
background-color: #e0e0e0;
|
491 |
+
}
|
492 |
+
|
493 |
+
|
494 |
.option-button.correct {
|
495 |
background-color: #d4edda; /* 緑系 */
|
496 |
color: #155724;
|
497 |
border-color: #c3e6cb;
|
498 |
+
font-weight: bold; /* 正解を強調 */
|
499 |
}
|
500 |
|
501 |
+
.option-button.incorrect { /* 不正解を選んだ場合のスタイル */
|
502 |
background-color: #f8d7da; /* 赤系 */
|
503 |
color: #721c24;
|
504 |
border-color: #f5c6cb;
|
505 |
+
opacity: 0.8; /* 少し薄く */
|
506 |
}
|
507 |
|
508 |
+
.option-button:disabled { /* 正解・不正解表示後の状態 */
|
509 |
+
cursor: default;
|
510 |
+
opacity: 0.7; /* すべての無効ボタンを少し薄く */
|
511 |
+
}
|
512 |
+
.option-button.correct:disabled {
|
513 |
+
opacity: 1; /* 正解ボタンは薄くしない */
|
514 |
+
}
|
515 |
+
.option-button.incorrect:disabled {
|
516 |
+
opacity: 0.7; /* 不正解ボタンは薄くする */
|
517 |
+
}
|
518 |
+
/* 選ばれなかった他の選択肢(不正解)のスタイル */
|
519 |
+
.option-button.other-disabled:disabled {
|
520 |
background-color: #e9ecef;
|
521 |
color: #6c757d;
|
522 |
border-color: #ced4da;
|
523 |
+
opacity: 0.6;
|
524 |
+
}
|
525 |
+
|
526 |
+
|
527 |
+
/* --- ★★★ 正解エフェクトのスタイルを追加 ★★★ --- */
|
528 |
+
.correct-effect {
|
529 |
+
position: fixed; /* 画面に固定 */
|
530 |
+
top: 45%; /* 少し上に調整 (フッターを考慮) */
|
531 |
+
left: 50%;
|
532 |
+
transform: translate(-50%, -50%) scale(0.8); /* 中央揃え & 少し小さめから開始 */
|
533 |
+
font-size: 15rem; /* 〇の大きさ */
|
534 |
+
color: rgba(0, 190, 0, 0.8); /* 〇の色 (例: 少し透明な明るい緑) */
|
535 |
+
font-weight: bold;
|
536 |
+
display: none; /* 初期状態では非表示 */
|
537 |
+
align-items: center;
|
538 |
+
justify-content: center;
|
539 |
+
z-index: 1001; /* フッターより手前に表示 */
|
540 |
+
opacity: 0; /* 初期状態では透明 */
|
541 |
+
transition: opacity 0.3s ease-out, transform 0.3s ease-out; /* フェードインと拡大のアニメーション */
|
542 |
+
pointer-events: none; /* エフェクトがクリック操作を妨げないように */
|
543 |
+
width: 20rem; /* 幅と高さを指定 */
|
544 |
+
height: 20rem;
|
545 |
+
/* background-color: rgba(255, 255, 255, 0.1); */ /* 必要なら背景 */
|
546 |
+
/* border-radius: 50%; */ /* 円形背景にしたい場合 */
|
547 |
+
line-height: 1; /* 〇の縦位置調整 */
|
548 |
}
|
549 |
|
550 |
+
/* エフェクト表示時のスタイル */
|
551 |
+
.correct-effect.show {
|
552 |
+
display: flex; /* 表示状態にする (flexで中央揃え) */
|
553 |
+
opacity: 1; /* 不透明にする */
|
554 |
+
transform: translate(-50%, -50%) scale(1); /* 元のサイズに戻す */
|
555 |
+
}
|
556 |
+
/* --- ★★★ ここまで追加 ★★★ --- */
|
557 |
+
|
558 |
|
559 |
/* --- ダークモード用スタイル (一部) --- */
|
560 |
body.dark-mode {
|
561 |
background-color: #121212;
|
562 |
color: #e0e0e0;
|
563 |
}
|
564 |
+
body.dark-mode .screen { background-color: #1e1e1e; border-color: #333; }
|
565 |
body.dark-mode .header { background-color: #1f1f1f; border-bottom-color: #333; }
|
566 |
+
body.dark-mode .header .title, body.dark-mode .header .menu-btn, body.dark-mode .header .action-btn { color: #e0e0e0; }
|
567 |
body.dark-mode .footer-nav { background-color: #1f1f1f; border-top-color: #333; }
|
568 |
body.dark-mode .footer-nav button { color: #888; }
|
569 |
body.dark-mode .footer-nav button.active { color: #58a6ff; } /* ダークモードでのアクティブ色 */
|
570 |
body.dark-mode .card { background-color: #2c2c2c; border-color: #444; color: #e0e0e0; }
|
571 |
+
body.dark-mode .card:active { background-color: #3a3a3a; }
|
572 |
+
body.dark-mode .list-item-button:hover { background-color: #3a3a3a; }
|
573 |
+
body.dark-mode li.list-item { border-bottom-color: #444; }
|
574 |
+
body.dark-mode .list-item-text h3 { color: #e0e0e0; }
|
575 |
+
body.dark-mode .list-item-text p { color: #aaa; }
|
576 |
body.dark-mode .list-arrow { color: #aaa; }
|
577 |
body.dark-mode .settings-item span { color: #e0e0e0; }
|
578 |
body.dark-mode .section-title { color: #aaa; }
|
579 |
body.dark-mode input[type="text"] { background-color: #333; border-color: #555; color: #e0e0e0; }
|
580 |
+
body.dark-mode .generate-button { background-color: #e53e3e; color: white; } /* 少し調整した赤 */
|
581 |
+
body.dark-mode .generate-button:hover { background-color: #c53030; }
|
582 |
+
body.dark-mode .generate-button:disabled { background-color: #e53e3e; opacity: 0.6; }
|
583 |
+
body.dark-mode .error-message { color: #ff7f7f; }
|
584 |
body.dark-mode .option-button { background-color: #444; color: #e0e0e0; border-color: #666; }
|
585 |
+
body.dark-mode .option-button:hover:not(:disabled) { background-color: #555; }
|
586 |
+
body.dark-mode .option-button:active:not(:disabled) { background-color: #666; }
|
587 |
body.dark-mode .option-button.correct { background-color: #2a6831; color: #e0e0e0; border-color: #41984b; }
|
588 |
+
body.dark-mode .option-button.incorrect { background-color: #8b3a3e; color: #ffdddd; border-color: #a85055; opacity: 0.8; }
|
589 |
+
body.dark-mode .option-button.other-disabled:disabled { background-color: #333; color: #888; border-color: #555; opacity: 0.6; }
|
590 |
+
body.dark-mode .pagination button { color: #58a6ff; }
|
591 |
+
body.dark-mode .pagination button:active:not(:disabled) { background-color: rgba(88, 166, 255, 0.15); }
|
592 |
body.dark-mode .pagination button:disabled { color: #666; }
|
593 |
+
body.dark-mode .tap-to-show { color: #aaa; }
|
594 |
+
body.dark-mode .mode-indicator { color: #bbb; }
|
595 |
body.dark-mode .toggle-switch .slider { background-color: #555; }
|
596 |
body.dark-mode .toggle-switch input:checked + .slider { background-color: #58a6ff; }
|
597 |
+
/* ダークモードの正解エフェクトの色 */
|
598 |
+
body.dark-mode .correct-effect { color: rgba(50, 220, 50, 0.85); }
|
599 |
+
|
600 |
+
/* --- END OF FILE style.css --- */
|
templates/learning.html
CHANGED
@@ -11,6 +11,7 @@
|
|
11 |
<header class="header">
|
12 |
<button class="menu-btn" aria-label="メニュー" onclick="openMenu()">☰</button>
|
13 |
<h1 class="title" id="learning-title">学習セット: 面白い動画の分析</h1>
|
|
|
14 |
<button class="action-btn" aria-label="アクション"></button>
|
15 |
</header>
|
16 |
<main>
|
@@ -23,37 +24,43 @@
|
|
23 |
|
24 |
<p class="tap-to-show" id="tap-to-show" onclick="revealAnswer()">タップして解答を表示</p>
|
25 |
|
|
|
26 |
<div id="options-area" style="margin-top: 15px;">
|
27 |
-
<!--
|
|
|
|
|
|
|
|
|
28 |
</div>
|
29 |
|
30 |
<div class="pagination">
|
31 |
<button id="prev-button" aria-label="前へ" onclick="goToPrev()"><</button>
|
32 |
-
<span id="page-info"
|
33 |
<button id="next-button" aria-label="次へ" onclick="goToNext()">></button>
|
34 |
</div>
|
35 |
</main>
|
36 |
</div>
|
37 |
|
38 |
-
<!-- ここから追加 -->
|
39 |
<div id="correct-effect" class="correct-effect">〇</div>
|
40 |
-
<!-- ここまで追加 -->
|
41 |
|
42 |
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
43 |
|
|
|
44 |
<footer class="footer-nav">
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
</footer>
|
58 |
</body>
|
59 |
</html>
|
|
|
11 |
<header class="header">
|
12 |
<button class="menu-btn" aria-label="メニュー" onclick="openMenu()">☰</button>
|
13 |
<h1 class="title" id="learning-title">学習セット: 面白い動画の分析</h1>
|
14 |
+
<!-- 右上のボタンは不要なら削除 -->
|
15 |
<button class="action-btn" aria-label="アクション"></button>
|
16 |
</header>
|
17 |
<main>
|
|
|
24 |
|
25 |
<p class="tap-to-show" id="tap-to-show" onclick="revealAnswer()">タップして解答を表示</p>
|
26 |
|
27 |
+
<!-- クイズの選択肢表示エリア (JSで動的に生成) -->
|
28 |
<div id="options-area" style="margin-top: 15px;">
|
29 |
+
<!-- 例:
|
30 |
+
<button class="option-button">A: 選択肢A</button>
|
31 |
+
<button class="option-button">B: 選択肢B</button>
|
32 |
+
...
|
33 |
+
-->
|
34 |
</div>
|
35 |
|
36 |
<div class="pagination">
|
37 |
<button id="prev-button" aria-label="前へ" onclick="goToPrev()"><</button>
|
38 |
+
<span id="page-info">? / ?</span> <!-- 初期表示 -->
|
39 |
<button id="next-button" aria-label="次へ" onclick="goToNext()">></button>
|
40 |
</div>
|
41 |
</main>
|
42 |
</div>
|
43 |
|
44 |
+
<!-- ★★★ ここから追加 ★★★ -->
|
45 |
<div id="correct-effect" class="correct-effect">〇</div>
|
46 |
+
<!-- ★★★ ここまで追加 ★★★ -->
|
47 |
|
48 |
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
49 |
|
50 |
+
<!-- 例: フッターナビゲーション -->
|
51 |
<footer class="footer-nav">
|
52 |
+
<button onclick="goToInput()" aria-label="入力">
|
53 |
+
<span class="nav-icon">➕</span> <!-- アイコンは好みで変更 -->
|
54 |
+
<span class="nav-text">入力</span>
|
55 |
+
</button>
|
56 |
+
<button onclick="goToHistory()" aria-label="履歴">
|
57 |
+
<span class="nav-icon">🕒</span>
|
58 |
+
<span class="nav-text">履歴</span>
|
59 |
+
</button>
|
60 |
+
<button onclick="goToSettings()" aria-label="設定">
|
61 |
+
<span class="nav-icon">⚙️</span>
|
62 |
+
<span class="nav-text">設定</span>
|
63 |
+
</button>
|
64 |
</footer>
|
65 |
</body>
|
66 |
</html>
|