토익 영단어 퀴즈

TOEIC 영어 단어 타자 연습 (뜻만 보고 입력) 📘 TOEIC 영어 단어 타자 연습 (뜻만 보고 입력) 뜻 맞춘 개수: 0 / 300 처음부터 다시하기

토익 영어문장 타자연습

import React, { useEffect, useMemo, useRef, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; // --- Utility: Local Storage Hook --- function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { return initialValue; } }); useEffect(() => { try { window.localStorage.setItem(key, JSON.stringify(storedValue)); } catch {} }, [key, storedValue]); return [storedValue, setStoredValue]; } // --- TOEIC-style Sentence Generator (Creates 500+) --- function generateToeicSentences(count = 500) { const templates = [ "Please submit the {doc} by {day}.", "The {dept} meeting has been {status} to {time}.", "Our company will {verb} a new {item} next {day}.", "Employees are required to {action} before {deadline}.", "The maintenance team will check the {place} this {day}.", "We appreciate your {noun} during the {event}.", "The {role} is responsible for {gerund} the {doc}.", "Please be advised that the {item} is {status}.", "Customer feedback should be {verb_past} to the {dept}.", "The shipment is expected to {verb} on {day}.", "Please make sure to {action} the {item}.", "Due to {event}, the {dept} office will be {status}.", "Kindly {action} your {doc} for review.", "The {role} will provide an update by {time}.", "There will be a {event} in the main {place} at {time}.", "We regret to inform you that the {item} has been {status}.", "All staff must {action} their {doc} by {deadline}.", "The {dept} is conducting a survey on {topic}.", "Please {action} to the new {policy}.", "The {role} will be {verb_ger} the project next {day}.", ]; const pools = { doc: ["expense report", "timesheet", "proposal", "application", "invoice", "contract", "presentation", "travel request"], day: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "weekend"], dept: ["marketing", "sales", "human resources", "finance", "IT", "logistics", "customer service"], status: ["rescheduled", "postponed", "moved", "canceled", "confirmed", "under review"], time: ["9 a.m.", "noon", "3 p.m.", "5 p.m.", "end of day"], verb: ["launch", "announce", "introduce", "deliver", "ship", "release"], item: ["policy", "product", "service", "newsletter", "training program", "software update"], action: ["complete", "sign", "attach", "review", "confirm", "submit", "reset"], deadline: ["Friday", "tomorrow", "this afternoon", "next week", "the end of the month"], place: ["conference room", "cafeteria", "warehouse", "lobby", "parking lot", "server room"], noun: ["patience", "cooperation", "understanding", "participation", "support"], event: ["system upgrade", "power outage", "renovation", "holiday", "annual audit", "weather conditions"], role: ["manager", "supervisor", "director", "team leader", "coordinator", "assistant"], gerund: ["preparing", "reviewing", "finalizing", "submitting", "approving"], verb_past: ["forwarded", "submitted", "reported", "sent", "delivered"], topic: ["workplace safety", "remote work", "customer satisfaction", "training needs", "product quality"], policy: ["policy", "guideline", "schedule", "seating arrangement", "security procedure"], verb_ger: ["overseeing", "leading", "managing", "coordinating", "facilitating"], }; function fill(template) { return template.replace(/\{(.*?)\}/g, (_, key) => { const arr = pools[key] || [key]; return arr[Math.floor(Math.random() * arr.length)]; }); } const set = new Set(); let guard = 0; while (set.size < count && guard < count * 20) { guard++; const t = templates[Math.floor(Math.random() * templates.length)]; set.add(fill(t)); } // Add a few fixed authentic TOEIC-like sentences for variety and length control const fixed = [ "Please be reminded that the deadline has been extended to next Friday.", "If you have any questions regarding the itinerary, feel free to contact our travel desk.", "Participants are encouraged to arrive at least ten minutes before the seminar begins.", "Due to unforeseen circumstances, the elevator will be out of service until further notice.", "Please note that all orders placed after 4 p.m. will be processed the following business day.", "To enhance security, employees must display their ID badges at all times.", ]; return [...fixed, ...Array.from(set)].slice(0, count); } // --- Helper: caret positioning for input --- function setCaretToEnd(el) { const value = el.value; el.value = ""; el.value = value; } export default function App() { const [sentences, setSentences] = useLocalStorage("toeic.sentences", []); const [countSetting, setCountSetting] = useLocalStorage("toeic.count", 500); const [shuffle, setShuffle] = useLocalStorage("toeic.shuffle", true); const [currentIndex, setCurrentIndex] = useLocalStorage("toeic.index", 0); const [input, setInput] = useState(""); const [startedAt, setStartedAt] = useState(null); const [finishedCount, setFinishedCount] = useLocalStorage("toeic.finished", 0); const [keystrokes, setKeystrokes] = useLocalStorage("toeic.keys", 0); const [errors, setErrors] = useLocalStorage("toeic.errors", 0); const [goalWPM, setGoalWPM] = useLocalStorage("toeic.goalWPM", 50); const [sessionId, setSessionId] = useState(() => Date.now()); const inputRef = useRef(null); // Initialize sentences on first load or when user regenerates useEffect(() => { if (!sentences || sentences.length === 0) { const gen = generateToeicSentences(countSetting); const arr = shuffle ? shuffleArray(gen) : gen; setSentences(arr); setCurrentIndex(0); setFinishedCount(0); setKeystrokes(0); setErrors(0); } }, []); // eslint-disable-line function shuffleArray(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; } const current = sentences[currentIndex] || "Click '새로 만들기' to generate sentences."; // Stats const elapsedMinutes = useMemo(() => { if (!startedAt) return 0; const diffMs = Date.now() - startedAt; return diffMs / 60000; }, [startedAt, sessionId]); const grossWPM = useMemo(() => { if (!startedAt || elapsedMinutes <= 0) return 0; return Math.round(((keystrokes / 5) / elapsedMinutes) * 10) / 10; }, [keystrokes, elapsedMinutes, startedAt]); const accuracy = useMemo(() => { const total = keystrokes || 1; const acc = ((total - errors) / total) * 100; return Math.max(0, Math.min(100, Math.round(acc * 10) / 10)); }, [keystrokes, errors]); useEffect(() => { if (inputRef.current) { setCaretToEnd(inputRef.current); inputRef.current.focus(); } }, [currentIndex]); function handleChange(e) { const v = e.target.value; const expected = current.slice(0, v.length); const lastChar = v.slice(-1); setInput(v); // Keystroke + error detection (per char) if (startedAt === null) setStartedAt(Date.now()); setKeystrokes((k) => k + 1); if (v !== expected) { setErrors((er) => er + 1); } // Completed sentence if (v === current) { setFinishedCount((n) => n + 1); setTimeout(() => nextSentence(), 80); } } function nextSentence() { setInput(""); setCurrentIndex((i) => Math.min(i + 1, sentences.length)); setSessionId(Date.now()); } function prevSentence() { setInput(""); setCurrentIndex((i) => Math.max(0, i - 1)); } function regenerate() { const gen = generateToeicSentences(countSetting); const arr = shuffle ? shuffleArray(gen) : gen; setSentences(arr); setCurrentIndex(0); setInput(""); setFinishedCount(0); setKeystrokes(0); setErrors(0); setStartedAt(null); setSessionId(Date.now()); } function importList(text) { const lines = text .split(/\r?\n/) .map((s) => s.trim()) .filter(Boolean); if (lines.length > 0) { setSentences(lines); setCurrentIndex(0); setInput(""); setFinishedCount(0); setKeystrokes(0); setErrors(0); setStartedAt(null); setSessionId(Date.now()); } } const progress = sentences.length ? Math.min(100, Math.round(((currentIndex) / sentences.length) * 100)) : 0; function coloredChars() { const out = []; const tgt = current; for (let i = 0; i < tgt.length; i++) { const ch = tgt[i]; const typed = input[i]; let cls = ""; if (typed == null) cls = "text-gray-400"; else if (typed === ch) cls = "text-green-600"; else cls = "text-red-600 bg-red-50"; out.push( {ch === " " ? "\u00A0" : ch} ); } return out; } return (

TOEIC 타자 연습 · 500 문장

진행률 {progress}% WPM {grossWPM} 정확도 {accuracy}%
{/* Controls */}
설정
모든 설정과 기록은 브라우저에만 저장됩니다.
문장 가져오기 / 내보내기