
教室アプリ15:フラッシュカード

言葉を入れる:白い箱に、練習したい言葉をスペースで区切って入れます。
速さを決める:スライダーを動かして、切り替わる速さを調節します。
ボタンを選ぶ:順番通りか、バラバラに出すかを選びます。
「開始」を押す:3秒の合図(音と数字)のあと、大きく縦書きで表示されます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>教室アプリ:フラッシュカード</title>
<style>
:root {
--bg-color: #e8f5e9 ;
--main-color: #1b5e20 ;
--accent-color: #d84315 ;
--count-color: #ffc107 ;
}
body {
font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "BIZ UDPGothic", "Meiryo", sans-serif;
background-color: var(--bg-color);
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
.app-container {
width: 100vw;
height: 100vh;
background: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
/* 入力画面 */
#input -screen { width: 90%; max-width: 800px; }
textarea {
width: 100%;
height: 120px;
font-size: 1.5rem;
padding: 15px;
border: 4px solid #ccc ;
border-radius: 15px;
margin-bottom: 20px;
}
.settings-grid {
display: flex;
flex-direction: column;
gap: 30px;
margin-bottom: 40px;
}
.mode-settings {
display: flex;
justify-content: center;
gap: 15px;
}
.btn-option {
padding: 15px 30px;
border: 3px solid #ddd ;
border-radius: 15px;
background: #f8f9fa ;
cursor: pointer;
font-size: 1.2rem;
font-weight: bold;
}
.btn-option.active {
background: var(--main-color);
color: white;
border-color: var(--main-color);
}
.speed-slider-container {
width: 80%;
margin: 0 auto;
}
.slider-labels {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-weight: bold;
color: var(--main-color);
font-size: 1.2rem;
}
input[type="range"] {
width: 100%;
cursor: pointer;
height: 25px;
}
/* カウントダウン */
#countdown -overlay {
display: none;
font-size: 25rem;
color: var(--count-color);
font-weight: 900;
z-index: 10;
}
/* フラッシュ表示 */
#flash -screen {
display: none;
width: 100%;
height: 100%;
background: #fff ;
justify-content: center;
align-items: center;
}
#card -display {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
writing-mode: vertical-rl;
-webkit-writing-mode: vertical-rl;
font-weight: 900;
line-height: 1.1;
white-space: nowrap;
}
.big-btn {
font-size: 2.5rem;
padding: 20px 80px;
background: var(--accent-color);
color: white;
border: none;
border-radius: 100px;
cursor: pointer;
box-shadow: 0 8px 0 #870000 ;
}
.big-btn:active { transform: translateY(5px); box-shadow: none; }
#back -btn {
display: none;
position: fixed;
bottom: 40px;
right: 40px;
z-index: 100;
font-size: 1.8rem;
padding: 15px 40px;
}
ruby rt { font-size: 0.3em; writing-mode: vertical-rl; }
</style>
</head>
<body>
<div class="app-container">
<div id="input-screen">
<h1>フラッシュカード</h1>
<textarea id="word-input" placeholder="たけやぶ やけた なのはな"></textarea>
<div class="settings-grid">
<div class="mode-settings">
<button class="btn-option active" id="mode-normal" onclick="setMode('normal')">じゅんばん</button>
<button class="btn-option" id="mode-random" onclick="setMode('random')">ばらばら</button>
</div>
<div class="speed-slider-container">
<div class="slider-labels">
<span>ゆっくり</span>
<span id="speed-display">1.0秒</span>
<span>はやい</span>
</div>
<input type="range" id="speed-range" min="500" max="3000" step="100" value="1000">
<p style="margin-top:5px; color:#666; font-size:0.9rem;">右に動かすとはやくなります(最速0.5秒)</p>
</div>
</div>
<button class="big-btn" onclick="startCountdown()">開始!</button>
</div>
<div id="countdown-overlay"></div>
<div id="flash-screen">
<div id="card-display"></div>
<button id="back-btn" class="big-btn" onclick="resetApp()">もどる</button>
</div>
</div>
<script>
let words = [];
let isRandom = false;
let timer;
let audioCtx = null;
const slider = document.getElementById('speed-range');
const speedDisplay = document.getElementById('speed-display');
// スライダーの反転計算(右に行くほどはやい=待ち時間が短い)
function getActualMs() {
const val = parseInt(slider.value);
const min = parseInt(slider.min);
const max = parseInt(slider.max);
return (max + min) - val;
}
slider.addEventListener('input', () => {
const ms = getActualMs();
speedDisplay.innerText = (ms / 1000).toFixed(1) + "秒";
});
window.onload = () => {
const saved = localStorage.getItem('v_flash_words_05s');
if (saved) document.getElementById('word-input').value = saved;
speedDisplay.innerText = (getActualMs() / 1000).toFixed(1) + "秒";
};
function playBeep(freq, duration) {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = "sine";
osc.frequency.value = freq;
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
function setMode(mode) {
isRandom = (mode === 'random');
document.getElementById('mode-normal').classList.toggle('active', !isRandom);
document.getElementById('mode-random').classList.toggle('active', isRandom);
}
function startCountdown() {
const text = document.getElementById('word-input').value.trim();
let rawWords = text.split(/\s+/).filter(w => w !== "");
if (rawWords.length === 0) return alert("言葉を入力してね");
localStorage.setItem('v_flash_words_05s', text);
words = isRandom ? [...rawWords].sort(() => Math.random() - 0.5) : [...rawWords];
document.getElementById('input-screen').style.display = 'none';
const overlay = document.getElementById('countdown-overlay');
overlay.style.display = 'block';
let count = 3;
overlay.innerText = count;
playBeep(880, 0.1);
const countTimer = setInterval(() => {
count--;
if (count > 0) {
overlay.innerText = count;
playBeep(880, 0.1);
} else {
clearInterval(countTimer);
playBeep(1760, 0.3);
overlay.style.display = 'none';
startFlash();
}
}, 1000);
}
function startFlash() {
document.getElementById('flash-screen').style.display = 'flex';
document.getElementById('back-btn').style.display = 'none';
runFlash(0, getActualMs());
}
function runFlash(index, interval) {
const display = document.getElementById('card-display');
if (index < words.length) {
const word = words[index];
display.innerText = word;
const length = word.length;
if (length > 2) {
const newSize = 85 / length;
display.style.fontSize = newSize + "vh";
} else {
display.style.fontSize = "40vh";
}
timer = setTimeout(() => runFlash(index + 1, interval), interval);
} else {
finish();
}
}
function finish() {
document.getElementById('back-btn').style.display = 'block';
}
function resetApp() {
document.getElementById('input-screen').style.display = 'block';
document.getElementById('flash-screen').style.display = 'none';
document.getElementById('card-display').innerText = "";
}
</script>
</body>
</html>