seaprep / index.html
AptlyDigital's picture
Update index.html
595abcd verified
/*Huggingface 1/11/26*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spelling Trainer | AI-Powered Learning</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;600;700&family=Exo+2:wght@300;400;600;800&display=swap" rel="stylesheet">
<style>
/* === FUTURISTIC THEME === */
:root {
--primary: #00f3ff;
--secondary: #9d00ff;
--accent: #ff00ff;
--success: #00ff9d;
--warning: #ffd600;
--danger: #ff006e;
--bg: #0a0a1a;
--card: #101025;
--surface: #1a1a3a;
--text: #ffffff;
--muted: #8a8aff;
--glow: 0 0 40px rgba(0, 243, 255, 0.5);
--pulse: 0 0 80px rgba(157, 0, 255, 0.3);
--transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
overflow-x: hidden;
}
body {
font-family: 'Exo 2', sans-serif;
background-color: var(--bg);
color: var(--text);
min-height: 100vh;
overflow-x: hidden;
position: relative;
letter-spacing: 0.5px;
}
/* === ADVANCED BACKGROUND === */
.neural-network {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -3;
background:
radial-gradient(circle at 20% 30%, rgba(0, 243, 255, 0.08) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(157, 0, 255, 0.06) 0%, transparent 50%),
radial-gradient(circle at 40% 50%, rgba(255, 0, 255, 0.04) 0%, transparent 50%),
linear-gradient(135deg, #0a0a1a 0%, #0d0d23 25%, #101025 50%, #0d0d23 75%, #0a0a1a 100%);
background-size: 400% 400%;
animation: neuralFlow 20s ease infinite;
}
@keyframes neuralFlow {
0%, 100% { background-position: 0% 0%; }
33% { background-position: 100% 50%; }
66% { background-position: 50% 100%; }
}
.synapse-layer {
position: fixed;
top: 0;
left: 0;
width: 200%;
height: 200%;
z-index: -2;
background-image:
repeating-linear-gradient(90deg, transparent, transparent 98px, rgba(0, 243, 255, 0.02) 98px, rgba(0, 243, 255, 0.02) 100px),
repeating-linear-gradient(0deg, transparent, transparent 98px, rgba(157, 0, 255, 0.02) 98px, rgba(157, 0, 255, 0.02) 100px);
animation: synapseMove 40s linear infinite;
transform-origin: center;
}
@keyframes synapseMove {
0% { transform: translate(0, 0) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
.neuron-grid {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background-image: radial-gradient(circle at var(--x, 50%) var(--y, 50%), rgba(255, 0, 255, 0.1) 0%, transparent 10%);
animation: neuronPulse 3s ease-in-out infinite;
}
@keyframes neuronPulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.8; }
}
.data-stream {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background: linear-gradient(90deg, transparent 30%, rgba(0, 243, 255, 0.05) 50%, transparent 70%);
animation: dataStream 15s linear infinite;
filter: blur(1px);
}
@keyframes dataStream {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* === CELEBRATION EFFECTS === */
.celebration-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
overflow: hidden;
}
.confetti {
position: absolute;
width: 12px;
height: 12px;
opacity: 0;
animation: confettiFall 3s ease-out forwards;
}
@keyframes confettiFall {
0% {
transform: translateY(-100px) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(360deg);
opacity: 0;
}
}
/* === MAIN CONTAINER === */
.app-container {
max-width: 1800px;
margin: 0 auto;
padding: 30px;
display: grid;
grid-template-columns: 1fr;
gap: 30px;
min-height: 100vh;
position: relative;
z-index: 1;
}
/* === HEADER === */
.neural-header {
text-align: center;
padding: 40px 30px;
margin-bottom: 30px;
background: rgba(16, 16, 37, 0.7);
backdrop-filter: blur(20px);
border-radius: 30px;
border: 1px solid rgba(0, 243, 255, 0.2);
position: relative;
overflow: hidden;
box-shadow: var(--glow);
}
.neural-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent, rgba(0, 243, 255, 0.1), transparent);
transform: translateX(-100%);
animation: headerShine 8s infinite;
}
@keyframes headerShine {
0% { transform: translateX(-100%); }
50%, 100% { transform: translateX(100%); }
}
.header-title {
font-family: 'Orbitron', sans-serif;
font-size: 4.5rem;
font-weight: 900;
background: linear-gradient(135deg, var(--primary), var(--secondary), var(--accent));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 20px;
letter-spacing: 4px;
text-shadow: 0 0 50px rgba(0, 243, 255, 0.5);
animation: titleGlitch 10s infinite;
}
@keyframes titleGlitch {
0%, 100% { transform: translate(0); }
98% { transform: translate(0); }
99% { transform: translate(-2px, 1px); }
}
.header-subtitle {
font-family: 'Rajdhani', sans-serif;
font-size: 1.4rem;
color: var(--muted);
max-width: 800px;
margin: 0 auto;
font-weight: 300;
letter-spacing: 2px;
}
/* === MAIN GRID === */
.neural-grid {
display: grid;
grid-template-columns: 350px 1fr;
gap: 30px;
min-height: 80vh;
}
@media (max-width: 1400px) {
.neural-grid {
grid-template-columns: 1fr;
}
}
/* === SIDEBAR === */
.neural-sidebar {
background: rgba(16, 16, 37, 0.8);
backdrop-filter: blur(30px);
border-radius: 25px;
padding: 30px;
border: 1px solid rgba(0, 243, 255, 0.2);
box-shadow: var(--glow);
height: fit-content;
position: sticky;
top: 30px;
display: flex;
flex-direction: column;
gap: 35px;
}
@media (max-width: 1400px) {
.neural-sidebar {
position: static;
}
}
.sidebar-section {
padding-bottom: 30px;
border-bottom: 1px solid rgba(0, 243, 255, 0.1);
}
.sidebar-section:last-child {
border-bottom: none;
padding-bottom: 0;
}
.section-title {
font-family: 'Orbitron', sans-serif;
font-size: 1.1rem;
color: var(--primary);
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 15px;
text-transform: uppercase;
letter-spacing: 2px;
}
.section-title i {
font-size: 1.3rem;
color: var(--accent);
filter: drop-shadow(0 0 8px currentColor);
}
/* === FORM ELEMENTS === */
.input-group {
margin-bottom: 25px;
}
.label {
display: block;
margin-bottom: 12px;
color: var(--text);
font-family: 'Rajdhani', sans-serif;
font-weight: 600;
font-size: 0.95rem;
letter-spacing: 1px;
}
.select, .input {
width: 100%;
padding: 16px 20px;
background: rgba(26, 26, 58, 0.8);
border: 1px solid rgba(0, 243, 255, 0.3);
border-radius: 15px;
color: var(--text);
font-family: 'Exo 2', sans-serif;
font-size: 1rem;
transition: var(--transition);
outline: none;
}
.select:focus, .input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(0, 243, 255, 0.2);
}
.select option {
background: var(--card);
color: var(--text);
padding: 10px;
}
/* === BUTTONS === */
.btn {
padding: 18px 25px;
border-radius: 15px;
border: none;
font-family: 'Orbitron', sans-serif;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
position: relative;
overflow: hidden;
letter-spacing: 1px;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: 0.6s;
}
.btn:hover::before {
left: 100%;
}
.btn.primary {
background: linear-gradient(135deg, var(--primary), #0088cc);
color: #000;
border: 2px solid rgba(0, 243, 255, 0.5);
box-shadow: 0 10px 40px rgba(0, 243, 255, 0.3);
}
.btn.primary:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 20px 60px rgba(0, 243, 255, 0.5);
}
.btn.secondary {
background: linear-gradient(135deg, var(--secondary), #7d00cc);
color: white;
border: 2px solid rgba(157, 0, 255, 0.5);
box-shadow: 0 10px 40px rgba(157, 0, 255, 0.3);
}
.btn.secondary:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 20px 60px rgba(157, 0, 255, 0.5);
}
.btn.success {
background: linear-gradient(135deg, var(--success), #00cc7a);
color: #000;
border: 2px solid rgba(0, 255, 157, 0.5);
box-shadow: 0 10px 40px rgba(0, 255, 157, 0.3);
}
.btn.success:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 20px 60px rgba(0, 255, 157, 0.5);
}
.btn.warning {
background: linear-gradient(135deg, var(--warning), #ccaa00);
color: #000;
border: 2px solid rgba(255, 214, 0, 0.5);
box-shadow: 0 10px 40px rgba(255, 214, 0, 0.3);
}
.btn.warning:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 20px 60px rgba(255, 214, 0, 0.5);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}
/* === GOOGLE TTS CONFIG === */
.tts-config {
background: rgba(0, 243, 255, 0.05);
border-radius: 20px;
padding: 25px;
border: 1px solid rgba(0, 243, 255, 0.3);
position: relative;
overflow: hidden;
}
.tts-config::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
}
/* === MAIN CONTENT === */
.neural-content {
background: rgba(16, 16, 37, 0.8);
backdrop-filter: blur(30px);
border-radius: 30px;
border: 1px solid rgba(0, 243, 255, 0.2);
box-shadow: var(--glow);
overflow: hidden;
min-height: 600px;
}
/* === TAB SYSTEM === */
.tabs {
display: flex;
background: rgba(10, 10, 26, 0.9);
border-bottom: 1px solid rgba(0, 243, 255, 0.2);
position: relative;
overflow-x: auto;
}
.tab {
flex: 1;
min-width: 200px;
padding: 25px 30px;
background: transparent;
border: none;
color: var(--muted);
font-family: 'Orbitron', sans-serif;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: var(--transition);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
letter-spacing: 1.5px;
}
.tab::before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary), var(--accent));
transition: var(--transition);
}
.tab:hover {
color: var(--primary);
background: rgba(0, 243, 255, 0.05);
}
.tab.active {
color: var(--primary);
background: rgba(0, 243, 255, 0.1);
}
.tab.active::before {
width: 80%;
}
.panel {
padding: 40px;
display: none;
animation: panelFade 0.6s ease;
}
@keyframes panelFade {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.panel.active {
display: block;
}
/* === GAME HEADER === */
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
padding-bottom: 30px;
border-bottom: 1px solid rgba(0, 243, 255, 0.2);
flex-wrap: wrap;
gap: 30px;
}
.progress-container {
display: flex;
align-items: center;
gap: 25px;
flex: 1;
min-width: 400px;
}
.progress {
flex: 1;
height: 16px;
background: rgba(0, 243, 255, 0.1);
border-radius: 8px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--secondary));
border-radius: 8px;
transition: width 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
overflow: hidden;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: progressShimmer 2s infinite;
}
@keyframes progressShimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
flex: 2;
min-width: 500px;
}
@media (max-width: 1200px) {
.stats {
min-width: 100%;
}
}
.stat-card {
background: rgba(26, 26, 58, 0.6);
border-radius: 20px;
padding: 25px;
border: 1px solid rgba(0, 243, 255, 0.2);
transition: var(--transition);
position: relative;
overflow: hidden;
text-align: center;
}
.stat-card:hover {
transform: translateY(-5px);
border-color: var(--primary);
box-shadow: var(--glow);
}
.stat-value {
font-family: 'Orbitron', sans-serif;
font-size: 2.8rem;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
text-shadow: 0 0 20px rgba(0, 243, 255, 0.5);
}
.stat-label {
font-family: 'Rajdhani', sans-serif;
font-size: 1rem;
color: var(--muted);
font-weight: 600;
letter-spacing: 1.5px;
text-transform: uppercase;
}
/* === WORD DISPLAY === */
.word-display-container {
background: rgba(26, 26, 58, 0.4);
border-radius: 25px;
padding: 60px 40px;
margin: 50px 0;
border: 2px solid rgba(0, 243, 255, 0.2);
transition: var(--transition);
position: relative;
overflow: hidden;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
.word-display-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 25px;
padding: 3px;
background: linear-gradient(135deg, var(--primary), transparent, var(--accent));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
opacity: 0.5;
}
.word-display {
font-family: 'Orbitron', sans-serif;
font-size: 4.5rem;
font-weight: 900;
letter-spacing: 25px;
color: var(--text);
text-align: center;
text-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
line-height: 1.3;
word-break: break-word;
padding: 0 30px;
filter: drop-shadow(0 0 10px rgba(0, 243, 255, 0.3));
}
@media (max-width: 900px) {
.word-display {
font-size: 3rem;
letter-spacing: 15px;
padding: 0 20px;
}
}
@media (max-width: 600px) {
.word-display {
font-size: 2.2rem;
letter-spacing: 10px;
padding: 0 15px;
}
}
.word-context {
font-family: 'Exo 2', sans-serif;
font-size: 0rem;
color: var(--muted);
font-style: italic;
text-align: center;
max-width: 900px;
margin: 30px auto 0;
padding: 0 30px;
font-weight: 300;
line-height: 1.6;
}
/* === AUDIO CONTROLS === */
.audio-controls-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
margin: 50px 0;
}
/* === INPUT AREA === */
.input-container {
margin: 60px 0;
position: relative;
}
.input-label {
font-family: 'Orbitron', sans-serif;
font-size: 1.2rem;
color: var(--primary);
margin-bottom: 25px;
display: block;
text-align: center;
letter-spacing: 2px;
}
.answer-input {
width: 100%;
padding: 30px 40px;
font-size: 2rem;
font-family: 'Rajdhani', sans-serif;
background: rgba(26, 26, 58, 0.6);
border: 2px solid rgba(0, 243, 255, 0.3);
border-radius: 20px;
color: var(--text);
text-align: center;
transition: var(--transition);
outline: none;
letter-spacing: 3px;
}
@media (max-width: 900px) {
.answer-input {
font-size: 1.6rem;
padding: 25px 30px;
}
}
.answer-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 5px rgba(0, 243, 255, 0.2);
}
.answer-input::placeholder {
color: rgba(138, 138, 255, 0.5);
font-size: 1.6rem;
letter-spacing: 2px;
}
.hint-text {
text-align: center;
color: var(--muted);
margin-top: 20px;
font-size: 1rem;
opacity: 0.8;
font-family: 'Rajdhani', sans-serif;
}
/* === ACTION BUTTONS === */
.action-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
margin-top: 60px;
}
/* === FEEDBACK === */
.feedback-container {
margin-top: 40px;
padding: 30px;
border-radius: 20px;
text-align: center;
font-size: 1.5rem;
font-weight: 600;
display: none;
animation: feedbackPop 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
position: relative;
overflow: hidden;
font-family: 'Orbitron', sans-serif;
}
@keyframes feedbackPop {
0% { transform: scale(0.5); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.feedback-container.correct {
background: rgba(0, 255, 157, 0.15);
border: 2px solid rgba(0, 255, 157, 0.4);
color: var(--success);
display: block;
}
.feedback-container.incorrect {
background: rgba(255, 214, 0, 0.15);
border: 2px solid rgba(255, 214, 0, 0.4);
color: var(--warning);
display: block;
}
/* === TOAST === */
.toast-container {
position: fixed;
bottom: 40px;
right: 40px;
z-index: 10000;
display: flex;
flex-direction: column;
gap: 20px;
max-width: 450px;
}
@media (max-width: 900px) {
.toast-container {
bottom: 20px;
right: 20px;
left: 20px;
max-width: none;
}
}
.toast {
background: rgba(16, 16, 37, 0.95);
border-radius: 20px;
padding: 25px 30px;
border-left: 5px solid var(--primary);
box-shadow: var(--glow);
display: flex;
align-items: center;
gap: 20px;
transform: translateX(100%);
opacity: 0;
animation: toastSlide 0.4s forwards;
backdrop-filter: blur(20px);
border: 1px solid rgba(0, 243, 255, 0.2);
}
@keyframes toastSlide {
to {
transform: translateX(0);
opacity: 1;
}
}
.toast-icon {
font-size: 1.8rem;
filter: drop-shadow(0 0 8px currentColor);
}
.toast-content {
flex: 1;
}
.toast-title {
font-family: 'Orbitron', sans-serif;
font-weight: 600;
margin-bottom: 8px;
color: var(--text);
}
.toast-message {
font-family: 'Exo 2', sans-serif;
font-size: 0.95rem;
color: var(--muted);
line-height: 1.5;
}
.toast-close {
background: transparent;
border: none;
color: var(--muted);
cursor: pointer;
font-size: 1.5rem;
transition: var(--transition);
}
.toast-close:hover {
color: var(--danger);
}
/* === MODAL === */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(10, 10, 26, 0.95);
backdrop-filter: blur(30px);
display: flex;
align-items: center;
justify-content: center;
z-index: 20000;
opacity: 0;
visibility: hidden;
transition: var(--transition);
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: rgba(16, 16, 37, 0.95);
border-radius: 30px;
padding: 50px;
max-width: 700px;
width: 90%;
border: 1px solid rgba(0, 243, 255, 0.3);
box-shadow: var(--glow);
transform: translateY(50px) scale(0.95);
transition: var(--transition);
position: relative;
}
.modal.active .modal-content {
transform: translateY(0) scale(1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
}
.modal-title {
font-family: 'Orbitron', sans-serif;
font-size: 2.5rem;
color: var(--primary);
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.modal-close {
background: transparent;
border: none;
color: var(--muted);
font-size: 2rem;
cursor: pointer;
transition: var(--transition);
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.modal-close:hover {
color: var(--danger);
background: rgba(255, 0, 110, 0.1);
}
/* === DASHBOARD === */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
}
.dashboard-card {
background: rgba(26, 26, 58, 0.6);
border-radius: 25px;
padding: 30px;
border: 1px solid rgba(0, 243, 255, 0.2);
transition: var(--transition);
position: relative;
overflow: hidden;
}
.dashboard-card:hover {
transform: translateY(-8px);
border-color: var(--primary);
box-shadow: var(--glow);
}
.dashboard-card h3 {
font-family: 'Orbitron', sans-serif;
color: var(--primary);
margin-bottom: 25px;
font-size: 1.5rem;
display: flex;
align-items: center;
gap: 15px;
}
.dashboard-card h3 i {
font-size: 1.6rem;
color: var(--accent);
filter: drop-shadow(0 0 10px currentColor);
}
/* === BADGES === */
.badge-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 20px;
margin-top: 20px;
}
.badge {
background: rgba(16, 16, 37, 0.8);
border-radius: 20px;
padding: 25px;
text-align: center;
border: 2px solid transparent;
transition: var(--transition);
position: relative;
overflow: hidden;
cursor: pointer;
}
.badge::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 20px;
padding: 3px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
opacity: 0;
transition: var(--transition);
}
.badge:hover::before {
opacity: 1;
}
.badge.earned {
border-color: var(--accent);
box-shadow: 0 10px 30px rgba(255, 0, 255, 0.2);
}
.badge.earned::before {
opacity: 1;
}
.badge.locked {
opacity: 0.5;
filter: grayscale(0.8);
}
.badge-icon {
font-size: 3.5rem;
margin-bottom: 15px;
display: block;
filter: drop-shadow(0 0 10px currentColor);
}
.badge-name {
font-family: 'Orbitron', sans-serif;
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 8px;
color: var(--text);
}
.badge-desc {
font-family: 'Exo 2', sans-serif;
font-size: 0.85rem;
color: var(--muted);
line-height: 1.4;
}
/* === HISTORY === */
.history-list {
max-height: 500px;
overflow-y: auto;
padding-right: 15px;
}
.history-list::-webkit-scrollbar {
width: 8px;
}
.history-list::-webkit-scrollbar-track {
background: rgba(0, 243, 255, 0.05);
border-radius: 4px;
}
.history-list::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 4px;
}
.history-item {
padding: 25px;
margin-bottom: 15px;
background: rgba(26, 26, 58, 0.4);
border-radius: 20px;
border-left: 5px solid var(--success);
transition: var(--transition);
cursor: pointer;
}
.history-item:hover {
transform: translateX(8px);
background: rgba(26, 26, 58, 0.6);
}
.history-item.hint {
border-left-color: var(--secondary);
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.history-word {
font-family: 'Orbitron', sans-serif;
font-weight: 600;
font-size: 1.3rem;
color: var(--text);
}
.history-stats {
font-size: 1rem;
color: var(--muted);
display: flex;
align-items: center;
gap: 15px;
font-family: 'Rajdhani', sans-serif;
}
.history-details {
font-size: 0.9rem;
color: var(--muted);
display: flex;
gap: 20px;
font-family: 'Exo 2', sans-serif;
}
/* === LOADING === */
.loader {
display: inline-block;
width: 30px;
height: 30px;
border: 4px solid rgba(0, 243, 255, 0.3);
border-radius: 50%;
border-top-color: var(--primary);
animation: neuralSpin 1s linear infinite;
filter: drop-shadow(0 0 8px var(--primary));
}
@keyframes neuralSpin {
to { transform: rotate(360deg); }
}
/* === RESPONSIVE === */
@media (max-width: 1200px) {
.app-container {
padding: 20px;
}
.header-title {
font-size: 3.5rem;
}
.header-subtitle {
font-size: 1.2rem;
}
.panel {
padding: 30px;
}
}
@media (max-width: 900px) {
.header-title {
font-size: 2.8rem;
letter-spacing: 3px;
}
.header-subtitle {
font-size: 1.1rem;
}
.tab {
min-width: 160px;
padding: 20px;
font-size: 0.9rem;
}
.stats {
grid-template-columns: repeat(2, 1fr);
}
.progress-container {
min-width: 100%;
}
.audio-controls-grid {
grid-template-columns: 1fr;
}
.action-grid {
grid-template-columns: 1fr;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
.badge-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 600px) {
.header-title {
font-size: 2.2rem;
}
.header-subtitle {
font-size: 1rem;
}
.tab {
min-width: 140px;
padding: 15px 10px;
font-size: 0.8rem;
}
.stats {
grid-template-columns: 1fr;
gap: 15px;
}
.stat-value {
font-size: 2.2rem;
}
.word-display {
font-size: 2rem;
letter-spacing: 8px;
}
.word-context {
font-size: 1.2rem;
}
.answer-input {
font-size: 1.4rem;
padding: 20px;
}
.btn {
padding: 15px 20px;
font-size: 0.9rem;
}
.modal-content {
padding: 30px 20px;
}
.modal-title {
font-size: 2rem;
}
.badge-grid {
grid-template-columns: 1fr;
}
}
/* === UTILITY === */
.text-center { text-align: center; }
.mt-1 { margin-top: 10px; }
.mt-2 { margin-top: 20px; }
.mt-3 { margin-top: 30px; }
.mt-4 { margin-top: 40px; }
.mb-1 { margin-bottom: 10px; }
.mb-2 { margin-bottom: 20px; }
.mb-3 { margin-bottom: 30px; }
.mb-4 { margin-bottom: 40px; }
.p-1 { padding: 10px; }
.p-2 { padding: 20px; }
.p-3 { padding: 30px; }
.p-4 { padding: 40px; }
.hidden { display: none; }
.fade-in { animation: fadeIn 0.6s ease; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
</style>
</head>
<body>
<!-- BACKGROUND EFFECTS -->
<div class="neural-network"></div>
<div class="synapse-layer"></div>
<div class="neuron-grid" id="neuronGrid"></div>
<div class="data-stream"></div>
<!-- CELEBRATION EFFECTS -->
<div class="celebration-container" id="celebrationContainer"></div>
<!-- TOAST NOTIFICATIONS -->
<div class="toast-container" id="toastContainer"></div>
<!-- SESSION COMPLETE MODAL -->
<div class="modal" id="sessionCompleteModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">🎉 SESSION COMPLETE!</h2>
<button class="modal-close" id="modalCloseBtn">&times;</button>
</div>
<div id="modalContent"></div>
</div>
</div>
<!-- MAIN APP -->
<div class="app-container">
<!-- HEADER -->
<header class="neural-header">
<h1 class="header-title">SPELLING TRAINER</h1>
<p class="header-subtitle">AI-Powered Spelling Practice • Google TTS • Smart Learning</p>
</header>
<!-- MAIN GRID -->
<div class="neural-grid">
<!-- SIDEBAR -->
<aside class="neural-sidebar">
<!-- USER PROFILE -->
<div class="sidebar-section">
<h3 class="section-title"><i class="fas fa-user-astronaut"></i> USER PROFILE</h3>
<div class="input-group">
<label class="label">Name</label>
<input type="text" id="userName" class="input" placeholder="Enter your name" value="Spelling Learner">
</div>
<div class="input-group">
<label class="label">Level</label>
<select id="userLevel" class="select">
<option value="initiate">🧠 Beginner</option>
<option value="adept">⚡ Intermediate</option>
<option value="master">👑 Advanced</option>
<option value="architect">🏛️ Expert</option>
</select>
</div>
<button id="saveProfileBtn" class="btn secondary mt-2">
<i class="fas fa-save"></i> Save Profile
</button>
</div>
<!-- TRAINING SETTINGS -->
<div class="sidebar-section">
<h3 class="section-title"><i class="fas fa-cogs"></i> TRAINING SETTINGS</h3>
<div class="input-group">
<label class="label">Training Mode</label>
<select id="trainingMode" class="select">
<option value="learning">📚 Learn in Order</option>
<option value="practice">🎯 Random Practice</option>
<option value="sentence">💬 Use in Sentences</option>
<option value="time">⏱️ Timed Challenge</option>
<option value="trini">🌴 Advanced Mode</option>
</select>
</div>
<div class="input-group">
<label class="label">Difficulty Level</label>
<select id="difficultyLevel" class="select">
<option value="grade1">Grade 1: Basic</option>
<option value="grade2">Grade 2: Easy</option>
<option value="grade3">Grade 3: Intermediate</option>
<option value="grade4">Grade 4: Advanced</option>
<option value="grade5">Grade 5: Expert</option>
<option value="grade6">Grade 6: Master</option>
<option value="advanced">Grade 7: Challenge</option>
</select>
</div>
<div class="input-group">
<label class="label">Session Length: <span id="sessionLengthValue">15</span> words</label>
<input type="range" id="sessionLength" min="1" max="6" step="1" value="2" style="width: 100%;">
<div style="color: var(--muted); font-size: 0.9rem; text-align: center; margin-top: 10px;">
<span>10</span>
<span style="margin-left: 20px;">20</span>
<span style="margin-left: 20px;">30</span>
<span style="margin-left: 20px;">40</span>
<span style="margin-left: 20px;">50</span>
<span style="margin-left: 20px;">All</span>
</div>
</div>
</div>
<!-- GOOGLE TTS CONFIG -->
<div class="tts-config">
<h3 class="section-title"><i class="fab fa-google"></i> GOOGLE TTS</h3>
<p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 20px; line-height: 1.5;">
Premium voice synthesis. Get free API key from
<a href="https://console.cloud.google.com" target="_blank" style="color: var(--primary);">Google Cloud Console</a>
(1M characters free/month).
</p>
<div class="input-group">
<label class="label">API Key</label>
<input type="password" id="googleApiKey" class="input" placeholder="Enter Google Cloud API key">
</div>
<div class="input-group">
<label class="label">Voice</label>
<select id="googleVoice" class="select">
<!-- US English Neural2 Voices -->
<optgroup label="🇺🇸 US English - Neural2 (Premium)">
<option value="en-US-Neural2-A">American Female - Natural Tone</option>
<option value="en-US-Neural2-B">American Male - Warm Tone</option>
<option value="en-US-Neural2-C">American Female - Clear Articulation</option>
<option value="en-US-Neural2-D">American Male - Confident Tone</option>
<option value="en-US-Neural2-E">American Female - Friendly Tone</option>
<option value="en-US-Neural2-F">American Female - Professional Tone</option>
<option value="en-US-Neural2-G">American Male - Authoritative Tone</option>
<option value="en-US-Neural2-H">American Female - Calm Tone</option>
<option value="en-US-Neural2-I">American Male - Energetic Tone</option>
<option value="en-US-Neural2-J">American Female - Expressive Tone</option>
</optgroup>
<!-- US English Wavenet Voices -->
<optgroup label="🇺🇸 US English - Wavenet">
<option value="en-US-Wavenet-A">American Female - Standard Voice</option>
<option value="en-US-Wavenet-B">American Male - Standard Voice</option>
<option value="en-US-Wavenet-C">American Female - Soft Tone</option>
<option value="en-US-Wavenet-D">American Male - Deep Tone</option>
<option value="en-US-Wavenet-E">American Female - Warm Tone</option>
<option value="en-US-Wavenet-F">American Female - Clear Voice</option>
<option value="en-US-Wavenet-G">American Male - Strong Tone</option>
<option value="en-US-Wavenet-H">American Female - Gentle Tone</option>
<option value="en-US-Wavenet-I">American Male - Friendly Tone</option>
<option value="en-US-Wavenet-J">American Female - Professional Voice</option>
</optgroup>
<!-- US English Standard Voices -->
<optgroup label="🇺🇸 US English - Standard">
<option value="en-US-Standard-A">American Female - Basic Voice</option>
<option value="en-US-Standard-B">American Male - Basic Voice</option>
<option value="en-US-Standard-C">American Female - Standard Tone</option>
<option value="en-US-Standard-D">American Male - Standard Tone</option>
<option value="en-US-Standard-E">American Female - Classic Voice</option>
<option value="en-US-Standard-F">American Female - Traditional Tone</option>
<option value="en-US-Standard-G">American Male - Classic Tone</option>
<option value="en-US-Standard-H">American Female - Clear Pronunciation</option>
<option value="en-US-Standard-I">American Male - Direct Tone</option>
<option value="en-US-Standard-J">American Female - Simple Voice</option>
</optgroup>
<!-- UK English Neural2 Voices -->
<optgroup label="🇬🇧 UK English - Neural2 (Premium)">
<option value="en-GB-Neural2-A">British Female - Received Pronunciation</option>
<option value="en-GB-Neural2-B">British Male - Received Pronunciation</option>
<option value="en-GB-Neural2-C">British Female - Modern Tone</option>
<option value="en-GB-Neural2-D">British Male - Traditional Tone</option>
</optgroup>
<!-- UK English Wavenet Voices -->
<optgroup label="🇬🇧 UK English - Wavenet">
<option value="en-GB-Wavenet-A">British Female - Standard Accent</option>
<option value="en-GB-Wavenet-B">British Male - Standard Accent</option>
<option value="en-GB-Wavenet-C">British Female - Formal Tone</option>
<option value="en-GB-Wavenet-D">British Male - Formal Tone</option>
</optgroup>
<!-- UK English Standard Voices -->
<optgroup label="🇬🇧 UK English - Standard">
<option value="en-GB-Standard-A">British Female - Basic Accent</option>
<option value="en-GB-Standard-B">British Male - Basic Accent</option>
<option value="en-GB-Standard-C">British Female - Classic Tone</option>
<option value="en-GB-Standard-D">British Male - Classic Tone</option>
</optgroup>
<!-- Australian English Neural2 Voices -->
<optgroup label="🇦🇺 Australian English - Neural2">
<option value="en-AU-Neural2-A">Australian Female - General Accent</option>
<option value="en-AU-Neural2-B">Australian Male - General Accent</option>
<option value="en-AU-Neural2-C">Australian Female - Friendly Tone</option>
<option value="en-AU-Neural2-D">Australian Male - Casual Tone</option>
</optgroup>
<!-- Australian English Wavenet Voices -->
<optgroup label="🇦🇺 Australian English - Wavenet">
<option value="en-AU-Wavenet-A">Australian Female - Standard Voice</option>
<option value="en-AU-Wavenet-B">Australian Male - Standard Voice</option>
<option value="en-AU-Wavenet-C">Australian Female - Clear Tone</option>
<option value="en-AU-Wavenet-D">Australian Male - Warm Tone</option>
</optgroup>
<!-- Australian English Standard Voices -->
<optgroup label="🇦🇺 Australian English - Standard">
<option value="en-AU-Standard-A">Australian Female - Basic Voice</option>
<option value="en-AU-Standard-B">Australian Male - Basic Voice</option>
<option value="en-AU-Standard-C">Australian Female - Natural Tone</option>
<option value="en-AU-Standard-D">Australian Male - Natural Tone</option>
</optgroup>
<!-- Indian English Neural2 Voices -->
<optgroup label="🇮🇳 Indian English - Neural2">
<option value="en-IN-Neural2-A">Indian Female - Standard Accent</option>
<option value="en-IN-Neural2-B">Indian Male - Standard Accent</option>
<option value="en-IN-Neural2-C">Indian Female - Clear Pronunciation</option>
</optgroup>
<!-- Indian English Wavenet Voices -->
<optgroup label="🇮🇳 Indian English - Wavenet">
<option value="en-IN-Wavenet-A">Indian Female - Professional Tone</option>
<option value="en-IN-Wavenet-B">Indian Male - Professional Tone</option>
<option value="en-IN-Wavenet-C">Indian Female - Formal Tone</option>
</optgroup>
<!-- Indian English Standard Voices -->
<optgroup label="🇮🇳 Indian English - Standard">
<option value="en-IN-Standard-A">Indian Female - Basic Tone</option>
<option value="en-IN-Standard-B">Indian Male - Basic Tone</option>
<option value="en-IN-Standard-C">Indian Female - Standard Pronunciation</option>
</optgroup>
<!-- Canadian English Neural2 Voices -->
<optgroup label="🇨🇦 Canadian English - Neural2">
<option value="en-CA-Neural2-A">Canadian Female - Neutral Accent</option>
<option value="en-CA-Neural2-B">Canadian Male - Neutral Accent</option>
<option value="en-CA-Neural2-C">Canadian Female - Clear Tone</option>
<option value="en-CA-Neural2-D">Canadian Male - Warm Tone</option>
</optgroup>
<!-- Canadian English Wavenet Voices -->
<optgroup label="🇨🇦 Canadian English - Wavenet">
<option value="en-CA-Wavenet-A">Canadian Female - Standard Voice</option>
<option value="en-CA-Wavenet-B">Canadian Male - Standard Voice</option>
<option value="en-CA-Wavenet-C">Canadian Female - Friendly Tone</option>
<option value="en-CA-Wavenet-D">Canadian Male - Professional Tone</option>
</optgroup>
<!-- Canadian English Standard Voices -->
<optgroup label="🇨🇦 Canadian English - Standard">
<option value="en-CA-Standard-A">Canadian Female - Basic Voice</option>
<option value="en-CA-Standard-B">Canadian Male - Basic Voice</option>
<option value="en-CA-Standard-C">Canadian Female - Natural Tone</option>
<option value="en-CA-Standard-D">Canadian Male - Natural Tone</option>
</optgroup>
</select>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 25px;">
<button id="testGoogleBtn" class="btn secondary">
<i class="fas fa-play"></i> Test Voice
</button>
<button id="clearGoogleBtn" class="btn secondary">
<i class="fas fa-times"></i> Clear Key
</button>
</div>
</div>
<!-- QUICK STATS -->
<div class="sidebar-section">
<h3 class="section-title"><i class="fas fa-chart-network"></i> QUICK STATS</h3>
<div id="quickStats">
<!-- Dynamic stats -->
</div>
</div>
<!-- SESSION CONTROLS -->
<div class="sidebar-section">
<h3 class="section-title"><i class="fas fa-brain"></i> SESSION CONTROLS</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
<button id="saveSessionBtn" class="btn secondary">
<i class="fas fa-save"></i> Save Progress
</button>
<button id="newSessionBtn" class="btn secondary">
<i class="fas fa-sync"></i> New Session
</button>
</div>
<button id="exportDataBtn" class="btn secondary mt-2" style="width: 100%;">
<i class="fas fa-download"></i> Export Data
</button>
</div>
</aside>
<!-- MAIN CONTENT -->
<main class="neural-content">
<!-- TAB NAVIGATION -->
<div class="tabs">
<button class="tab active" data-tab="game">
<i class="fas fa-brain-circuit"></i> Practice
</button>
<button class="tab" data-tab="analytics">
<i class="fas fa-chart-network"></i> Stats & Achievements
</button>
<button class="tab" data-tab="voices">
<i class="fas fa-wave-sine"></i> Voice Studio
</button>
<button class="tab" data-tab="settings">
<i class="fas fa-sliders-h"></i> Settings
</button>
</div>
<!-- GAME PANEL -->
<div class="panel active" id="game-panel">
<!-- GAME HEADER -->
<div class="game-header">
<div class="progress-container">
<div style="flex: 1;">
<div class="progress">
<div class="progress-fill" id="progressFill" style="width: 20%;"></div>
</div>
<div style="text-align: center; margin-top: 15px; color: var(--muted); font-family: 'Rajdhani';" id="progressText">
Word 3/15
</div>
</div>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="scoreValue">0</div>
<div class="stat-label">SCORE</div>
</div>
<div class="stat-card">
<div class="stat-value" id="streakValue">0</div>
<div class="stat-label">STREAK</div>
</div>
<div class="stat-card">
<div class="stat-value" id="accuracyValue">0%</div>
<div class="stat-label">ACCURACY</div>
</div>
<div class="stat-card">
<div class="stat-value" id="timeValue">0s</div>
<div class="stat-label">TIME</div>
</div>
</div>
</div>
<!-- WORD DISPLAY (HIDDEN CONTENT) -->
<div class="word-display-container" id="wordDisplay">
<div style="text-align: center;">
<div class="word-display" id="currentWord">_ _ _ _ _ _ _ _</div>
<div class="word-context" id="wordContext">
Loading...
</div>
</div>
</div>
<!-- AUDIO CONTROLS -->
<div class="audio-controls-grid">
<button id="hearWordBtn" class="btn primary">
<i class="fas fa-volume-up"></i> Hear Word (1)
</button>
<button id="contextBtn" class="btn secondary">
<i class="fas fa-headphones"></i> Hear Context (2)
</button>
</div>
<!-- INPUT AREA -->
<div class="input-container">
<label class="input-label">ENTER THE SPELLING:</label>
<input type="text" id="answerInput" class="answer-input"
placeholder="Type the word... [ENTER] to submit"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
<div class="hint-text" id="hintText">
Hint: Press [ENTER] to submit or [TAB] for a hint
</div>
</div>
<!-- ACTION BUTTONS -->
<div class="action-grid">
<button id="checkAnswerBtn" class="btn success">
<i class="fas fa-check-circle"></i> Check Answer (ENTER)
</button>
<button id="hintBtn" class="btn warning">
<i class="fas fa-lightbulb"></i> Get Hint (H)
</button>
<button id="nextWordBtn" class="btn secondary" disabled>
<i class="fas fa-arrow-right"></i> Next Word (N)
</button>
<button id="revealBtn" class="btn secondary">
<i class="fas fa-eye"></i> Reveal Answer (R)
</button>
</div>
<!-- FEEDBACK -->
<div class="feedback-container" id="feedbackContainer"></div>
</div>
<!-- ANALYTICS PANEL -->
<div class="panel" id="analytics-panel">
<div class="dashboard-grid">
<!-- PERFORMANCE OVERVIEW -->
<div class="dashboard-card">
<h3><i class="fas fa-chart-network"></i> Performance Overview</h3>
<div id="performanceChart">
<div style="height: 250px; display: flex; align-items: flex-end; gap: 15px; margin-top: 30px;">
<div style="flex: 1; display: flex; flex-direction: column; align-items: center;">
<div style="width: 85%; background: linear-gradient(to top, var(--success), #00ff9d); border-radius: 10px 10px 0 0; height: 85%;"></div>
<div style="margin-top: 15px; color: var(--text); font-weight: 600; font-family: 'Rajdhani';">Correct</div>
<div style="color: var(--muted); font-size: 0.9rem;">72%</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column; align-items: center;">
<div style="width: 85%; background: linear-gradient(to top, var(--primary), #00f3ff); border-radius: 10px 10px 0 0; height: 60%;"></div>
<div style="margin-top: 15px; color: var(--text); font-weight: 600; font-family: 'Rajdhani';">With Hint</div>
<div style="color: var(--muted); font-size: 0.9rem;">18%</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column; align-items: center;">
<div style="width: 85%; background: linear-gradient(to top, var(--warning), #ffd600); border-radius: 10px 10px 0 0; height: 25%;"></div>
<div style="margin-top: 15px; color: var(--text); font-weight: 600; font-family: 'Rajdhani';">Incorrect</div>
<div style="color: var(--muted); font-size: 0.9rem;">10%</div>
</div>
</div>
</div>
</div>
<!-- BADGES COLLECTION -->
<div class="dashboard-card">
<h3><i class="fas fa-trophy"></i> Achievements</h3>
<div class="badge-grid" id="badgesGrid">
<!-- Dynamic badges -->
</div>
</div>
<!-- WORD HISTORY -->
<div class="dashboard-card">
<h3><i class="fas fa-history"></i> Recent Words</h3>
<div class="history-list" id="wordHistory">
<!-- Dynamic history -->
</div>
</div>
<!-- ACHIEVEMENT STATS -->
<div class="dashboard-card">
<h3><i class="fas fa-star"></i> Milestones</h3>
<div id="achievementStats">
<!-- Dynamic stats -->
</div>
</div>
</div>
</div>
<!-- VOICE STUDIO PANEL -->
<div class="panel" id="voices-panel">
<div class="dashboard-grid">
<!-- VOICE TESTING -->
<div class="dashboard-card">
<h3><i class="fas fa-flask"></i> Voice Testing</h3>
<div style="margin-bottom: 30px;">
<label class="label">Text to Speak</label>
<textarea id="testText" class="input" style="height: 150px; resize: vertical;">Welcome to Spelling Trainer. This system uses Google Text-to-Speech with premium voices for better learning.</textarea>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin-bottom: 30px;">
<button id="testTtsBtn" class="btn primary">
<i class="fas fa-play"></i> Speak
</button>
<button id="stopTtsBtn" class="btn secondary">
<i class="fas fa-stop"></i> Stop
</button>
<button id="downloadAudioBtn" class="btn secondary">
<i class="fas fa-download"></i> Download Audio
</button>
</div>
<div class="input-group">
<label class="label">Voice Settings</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
<div>
<div style="color: var(--muted); font-size: 0.9rem; margin-bottom: 8px;">Speed</div>
<input type="range" id="speechRate" min="0.5" max="2" step="0.1" value="1" style="width: 100%;">
<div style="text-align: center; color: var(--primary); margin-top: 8px; font-family: 'Orbitron';" id="rateValue">1.0x</div>
</div>
<div>
<div style="color: var(--muted); font-size: 0.9rem; margin-bottom: 8px;">Pitch</div>
<input type="range" id="speechPitch" min="0.5" max="2" step="0.1" value="1" style="width: 100%;">
<div style="text-align: center; color: var(--primary); margin-top: 8px; font-family: 'Orbitron';" id="pitchValue">1.0</div>
</div>
</div>
</div>
</div>
<!-- VOICE GALLERY -->
<div class="dashboard-card">
<h3><i class="fas fa-users"></i> Voice Gallery</h3>
<div id="voiceGallery" style="margin-top: 30px;">
<!-- Dynamic voice gallery -->
</div>
</div>
<!-- VOICE COMPARISON -->
<div class="dashboard-card">
<h3><i class="fas fa-balance-scale"></i> Voice Comparison</h3>
<div id="voiceComparison">
<!-- Dynamic comparison -->
</div>
</div>
</div>
</div>
<!-- SETTINGS PANEL -->
<div class="panel" id="settings-panel">
<div class="dashboard-grid">
<!-- GAME SETTINGS -->
<div class="dashboard-card">
<h3><i class="fas fa-gamepad"></i> Game Settings</h3>
<div class="input-group">
<label class="label" style="display: flex; align-items: center; gap: 15px;">
<input type="checkbox" id="autoPlayCheck" checked style="transform: scale(1.2);">
Play word automatically
</label>
</div>
<div class="input-group">
<label class="label" style="display: flex; align-items: center; gap: 15px;">
<input type="checkbox" id="soundEffectsCheck" checked style="transform: scale(1.2);">
Enable sound effects
</label>
</div>
<div class="input-group">
<label class="label" style="display: flex; align-items: center; gap: 15px;">
<input type="checkbox" id="animationsCheck" checked style="transform: scale(1.2);">
Enable animations
</label>
</div>
<div class="input-group">
<label class="label" style="display: flex; align-items: center; gap: 15px;">
<input type="checkbox" id="hintsEnabledCheck" checked style="transform: scale(1.2);">
Enable hints
</label>
</div>
<div class="input-group">
<label class="label">Difficulty Adjustment</label>
<select id="difficultyAdjustment" class="select">
<option value="auto">Auto-adjust to performance</option>
<option value="fixed">Fixed difficulty</option>
<option value="progressive">Progressive increase</option>
</select>
</div>
</div>
<!-- AUDIO SETTINGS -->
<div class="dashboard-card">
<h3><i class="fas fa-wave-sine"></i> Audio Settings</h3>
<div class="input-group">
<label class="label">TTS Engine</label>
<select id="ttsEngine" class="select">
<option value="google">🧬 Google TTS (Premium)</option>
<option value="edge">⚡ Edge TTS (Fallback)</option>
<option value="web-speech">🔄 Web Speech API (Basic)</option>
</select>
</div>
<div class="input-group">
<label class="label">Voice Gender</label>
<select id="voiceGender" class="select">
<option value="female">♀ Female</option>
<option value="male">♂ Male</option>
<option value="neutral">⚪ Neutral</option>
</select>
</div>
<div class="input-group">
<label class="label">Volume</label>
<input type="range" id="masterVolume" min="0" max="1" step="0.1" value="0.8" style="width: 100%;">
<div style="text-align: center; color: var(--primary); margin-top: 8px; font-family: 'Orbitron';" id="volumeValue">80%</div>
</div>
</div>
<!-- DATA MANAGEMENT -->
<div class="dashboard-card">
<h3><i class="fas fa-database"></i> Data Management</h3>
<div style="display: grid; grid-template-columns: 1fr; gap: 20px; margin-top: 30px;">
<button id="exportProgressBtn" class="btn secondary">
<i class="fas fa-file-export"></i> Export Progress
</button>
<button id="importProgressBtn" class="btn secondary">
<i class="fas fa-file-import"></i> Import Data
</button>
<button id="clearDataBtn" class="btn secondary">
<i class="fas fa-trash"></i> Clear Data
</button>
<button id="resetSettingsBtn" class="btn secondary">
<i class="fas fa-undo"></i> Reset Settings
</button>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- MASTER ENGINE -->
<script>
// ========== STATE ARCHITECTURE ==========
const AppState = {
// Core Training State
currentWordIndex: 0,
totalWords: 15,
score: 0,
streak: 0,
attempts: 0,
hintsUsed: 0,
currentWord: "",
currentContext: "",
maskedWord: "",
revealedLetters: 0,
isCorrect: false,
isFirstTry: true,
startTime: null,
sessionTime: 0,
// TTS Engine Configuration
ttsEngine: 'google',
googleApiKey: localStorage.getItem('googleApiKey') || '',
googleVoice: localStorage.getItem('googleVoice') || 'en-US-Neural2-A',
edgeVoice: 'en-US-AriaNeural',
speechRate: 1.0,
speechPitch: 1.0,
speechVolume: 0.8,
isSpeaking: false,
// User Profile
userName: localStorage.getItem('userName') || 'Spelling Learner',
userLevel: localStorage.getItem('userLevel') || 'initiate',
userXP: parseInt(localStorage.getItem('userXP')) || 0,
// Word Database
wordLists: {
grade1: [], grade2: [], grade3: [], grade4: [], grade5: [], grade6: [],
sentence1: [], sentence2: [], sentence3: [], time: [], trini: []
},
currentWordList: [],
// Performance Metrics
performance: {
totalSessions: parseInt(localStorage.getItem('totalSessions')) || 0,
totalWordsCompleted: parseInt(localStorage.getItem('totalWordsCompleted')) || 0,
perfectFirstTries: parseInt(localStorage.getItem('perfectFirstTries')) || 0,
hintCorrect: parseInt(localStorage.getItem('hintCorrect')) || 0,
incorrect: parseInt(localStorage.getItem('incorrect')) || 0,
totalTime: parseInt(localStorage.getItem('totalTime')) || 0,
longestStreak: parseInt(localStorage.getItem('longestStreak')) || 0,
currentStreak: 0
},
// Achievements
badges: [
{ id: 1, name: "Perfect 10", description: "10 words correct in a row", earned: false, icon: "🔟", rarity: "common" },
{ id: 2, name: "Word Explorer", description: "50 words mastered", earned: false, icon: "🧭", rarity: "common" },
{ id: 3, name: "Speed Learner", description: "<30s average per word", earned: false, icon: "⚡", rarity: "uncommon" },
{ id: 4, name: "Accuracy Master", description: "90%+ accuracy", earned: false, icon: "🎯", rarity: "rare" },
{ id: 5, name: "Perfect Session", description: "100% session accuracy", earned: false, icon: "💎", rarity: "epic" },
{ id: 6, name: "Word Master", description: "100 words mastered", earned: false, icon: "🏛️", rarity: "legendary" },
{ id: 7, name: "Long Streak", description: "20+ perfect streak", earned: false, icon: "🔥", rarity: "rare" },
{ id: 8, name: "No Help", description: "Session without hints", earned: false, icon: "💪", rarity: "uncommon" },
{ id: 9, name: "Very Fast", description: "<15s average per word", earned: false, icon: "⏱️", rarity: "epic" },
{ id: 10, name: "7-Day Streak", description: "7-day learning streak", earned: false, icon: "📅", rarity: "rare" }
],
// History
history: JSON.parse(localStorage.getItem('wordHistory')) || [],
sessionHistory: [],
// Sound Effects
sounds: {
perfect: null,
ding: null,
tryAgain: null
}
};
// ========== INITIALIZATION ==========
document.addEventListener('DOMContentLoaded', async function() {
// Initialize visualization
initVisualization();
// Load sound effects
loadSoundEffects();
// Load word lists
await loadWordLists();
// Initialize training session
initTrainingSession();
// Initialize UI
initUI();
// Initialize event handlers
initEventHandlers();
// Load first word
loadWord();
// Update interface
updateInterface();
updateQuickStats();
renderBadges();
renderHistory();
// Display welcome
showToast('System Ready', 'Google TTS ready. Training session started.', 'success');
// Start time tracking
AppState.startTime = Date.now();
startTimeTracker();
});
// ========== SOUND EFFECTS ==========
function loadSoundEffects() {
try {
AppState.sounds.perfect = new Audio('sounds/perfect.wav');
AppState.sounds.ding = new Audio('sounds/ding.wav');
AppState.sounds.tryAgain = new Audio('sounds/try_again.wav');
// Preload sounds
AppState.sounds.perfect.load();
AppState.sounds.ding.load();
AppState.sounds.tryAgain.load();
console.log('Sound effects loaded successfully');
} catch (error) {
console.warn('Could not load sound effects:', error);
}
}
function playSound(soundName) {
try {
if (!AppState.sounds[soundName]) return;
const sound = AppState.sounds[soundName];
sound.volume = AppState.speechVolume;
sound.currentTime = 0;
sound.play().catch(e => console.warn('Could not play sound:', e));
} catch (error) {
console.warn('Error playing sound:', error);
}
}
// ========== CELEBRATION EFFECTS ==========
function createCelebration() {
const container = document.getElementById('celebrationContainer');
container.innerHTML = '';
// Create confetti for all correct answers
for (let i = 0; i < 25; i++) {
createConfetti(i);
}
}
function createConfetti(index) {
const container = document.getElementById('celebrationContainer');
const confetti = document.createElement('div');
confetti.className = 'confetti';
const colors = [
'var(--primary)',
'var(--secondary)',
'var(--accent)',
'var(--success)',
'var(--warning)'
];
confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
confetti.style.left = `${Math.random() * 100}%`;
confetti.style.animationDelay = `${Math.random() * 1}s`;
confetti.style.width = `${Math.random() * 8 + 4}px`;
confetti.style.height = confetti.style.width;
container.appendChild(confetti);
setTimeout(() => {
if (confetti.parentNode) {
confetti.remove();
}
}, 3000);
}
// ========== VISUALIZATION ENGINE ==========
function initVisualization() {
const neuronGrid = document.getElementById('neuronGrid');
const neuronCount = 50;
for (let i = 0; i < neuronCount; i++) {
const neuron = document.createElement('div');
neuron.style.position = 'absolute';
neuron.style.width = Math.random() * 8 + 2 + 'px';
neuron.style.height = neuron.style.width;
neuron.style.background = `radial-gradient(circle,
rgba(${Math.random() > 0.5 ? '0,243,255' : '157,0,255'}, ${Math.random() * 0.5 + 0.3}) 0%,
transparent 70%)`;
neuron.style.borderRadius = '50%';
neuron.style.left = Math.random() * 100 + '%';
neuron.style.top = Math.random() * 100 + '%';
neuron.style.filter = 'blur(1px)';
neuron.style.animation = `neuronPulse ${Math.random() * 4 + 2}s ease-in-out infinite`;
neuron.style.animationDelay = Math.random() * 5 + 's';
neuronGrid.appendChild(neuron);
}
// Mouse interaction
document.addEventListener('mousemove', (e) => {
const x = (e.clientX / window.innerWidth) * 100;
const y = (e.clientY / window.innerHeight) * 100;
neuronGrid.style.setProperty('--x', `${x}%`);
neuronGrid.style.setProperty('--y', `${y}%`);
});
}
// ========== WORD DATABASE LOADER ==========
async function loadWordLists() {
// Define file names for each difficulty level
const fileMap = {
grade1: 'grade1.txt',
grade2: 'grade2.txt',
grade3: 'grade3.txt',
grade4: 'grade4.txt',
grade5: 'grade5.txt',
grade6: 'grade6.txt',
sentence1: 'sentence1.txt',
sentence2: 'sentence2.txt',
sentence3: 'sentence3.txt',
time: 'time.txt',
trini: 'trini.txt'
};
// Load each file from the "lists" folder
for (const [grade, fileName] of Object.entries(fileMap)) {
try {
// Fetch the text file from the lists folder
const response = await fetch(`lists/${fileName}`);
if (!response.ok) {
throw new Error(`Failed to load ${fileName}: ${response.status}`);
}
const text = await response.text();
AppState.wordLists[grade] = parseWordFile(text);
console.log(`Loaded ${AppState.wordLists[grade].length} words from ${fileName}`);
} catch (error) {
console.error(`Error loading ${fileName}:`, error);
// Fallback to empty array if file can't be loaded
AppState.wordLists[grade] = [];
showToast('File Error', `Could not load ${fileName}. Using empty word list.`, 'warning');
}
}
// Set initial word list
AppState.currentWordList = AppState.wordLists.grade1;
}
// Helper function to parse the text file format
function parseWordFile(text) {
const lines = text.trim().split('\n');
const wordList = [];
for (const line of lines) {
// Skip empty lines
if (!line.trim()) continue;
// Split by '|' character
const parts = line.split('|');
if (parts.length >= 2) {
// Format: word|context
wordList.push({
word: parts[0].trim(),
context: parts[1].trim()
});
} else if (parts.length === 1) {
// Fallback: if no context provided, just use the word
wordList.push({
word: parts[0].trim(),
context: ""
});
}
}
return wordList;
}
// ========== TRAINING SESSION ==========
function initTrainingSession() {
const mode = document.getElementById('trainingMode').value;
const difficulty = document.getElementById('difficultyLevel').value;
// Select appropriate word list
let wordListKey = difficulty;
if (mode === 'sentence') wordListKey = 'sentence1';
else if (mode === 'time') wordListKey = 'time';
else if (mode === 'trini') wordListKey = 'trini';
AppState.currentWordList = AppState.wordLists[wordListKey] || AppState.wordLists.grade1;
// Set session duration based on slider
const sliderValue = parseInt(document.getElementById('sessionLength').value);
let sessionLength;
// Map slider values: 1=10, 2=20, 3=30, 4=40, 5=50, 6=All
if (sliderValue === 6) {
// Use all words in the list
sessionLength = AppState.currentWordList.length;
} else {
// 10, 20, 30, 40, or 50 words
sessionLength = sliderValue * 10;
}
AppState.totalWords = Math.min(sessionLength, AppState.currentWordList.length);
// Reset state
AppState.currentWordIndex = 0;
AppState.score = 0;
AppState.streak = 0;
AppState.sessionTime = 0;
AppState.performance.currentStreak = 0;
AppState.sessionHistory = [];
// Randomize if in practice mode
if (mode === 'practice') {
shuffleArray(AppState.currentWordList);
}
// Update interface
updateInterface();
}
// ========== WORD PROCESSOR ==========
function loadWord() {
if (AppState.currentWordIndex >= AppState.currentWordList.length) {
showCompletionModal();
return;
}
const wordData = AppState.currentWordList[AppState.currentWordIndex];
AppState.currentWord = wordData.word.toUpperCase();
AppState.currentContext = wordData.context;
AppState.maskedWord = maskWord(AppState.currentWord, 0);
AppState.revealedLetters = 0;
AppState.isCorrect = false;
AppState.isFirstTry = true;
AppState.attempts = 0;
AppState.hintsUsed = 0;
// Update display (ALWAYS show masked word)
document.getElementById('currentWord').textContent = AppState.maskedWord;
document.getElementById('wordContext').textContent = AppState.currentContext || "Context not available.";
document.getElementById('wordDisplay').className = 'word-display-container';
document.getElementById('feedbackContainer').className = 'feedback-container';
document.getElementById('answerInput').value = '';
document.getElementById('nextWordBtn').disabled = true;
// Auto-play if enabled
if (document.getElementById('autoPlayCheck')?.checked) {
setTimeout(() => speakWord(), 600);
}
// Update progress
updateProgress();
}
// ========== GOOGLE TTS ENGINE ==========
async function speakWord() {
try {
if (AppState.ttsEngine === 'google' && AppState.googleApiKey) {
await speakWithGoogleTTS(AppState.currentWord);
} else {
await speakWithEdgeTTS(AppState.currentWord);
}
} catch (error) {
console.error('Primary TTS failed, using fallback:', error);
await speakWithEdgeTTS(AppState.currentWord);
}
}
async function speakWithGoogleTTS(text) {
if (!AppState.googleApiKey) {
throw new Error('Google TTS not configured');
}
try {
const response = await fetch(
`https://texttospeech.googleapis.com/v1/text:synthesize?key=${AppState.googleApiKey}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
input: { text: text },
voice: {
languageCode: AppState.googleVoice.substring(0, 5), // Extract language code (en-US, en-GB, etc.)
name: AppState.googleVoice
},
audioConfig: {
audioEncoding: 'MP3',
speakingRate: AppState.speechRate,
pitch: AppState.speechPitch
}
})
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Google TTS API error: ${JSON.stringify(errorData)}`);
}
const data = await response.json();
const audio = new Audio('data:audio/mp3;base64,' + data.audioContent);
audio.volume = AppState.speechVolume;
AppState.isSpeaking = true;
audio.play();
return new Promise((resolve) => {
audio.onended = () => {
AppState.isSpeaking = false;
resolve();
};
audio.onerror = () => {
AppState.isSpeaking = false;
resolve();
};
});
} catch (error) {
console.error('Google TTS failed:', error);
throw error;
}
}
// ========== EDGE TTS ENGINE ==========
async function speakWithEdgeTTS(text) {
return new Promise((resolve) => {
try {
const encodedText = encodeURIComponent(text);
const voice = AppState.edgeVoice;
const rate = Math.round(AppState.speechRate * 100);
const url = `https://edge.microsoft.com/tts/v1/speak?text=${encodedText}&voice=${voice}&rate=${rate}%`;
const audio = new Audio(url);
audio.volume = AppState.speechVolume;
AppState.isSpeaking = true;
audio.play();
audio.onended = () => {
AppState.isSpeaking = false;
resolve();
};
audio.onerror = () => {
AppState.isSpeaking = false;
resolve();
};
} catch (error) {
console.error('Edge TTS failed:', error);
AppState.isSpeaking = false;
resolve();
}
});
}
// ========== VALIDATION ==========
function validateAnswer() {
const input = document.getElementById('answerInput');
const userAnswer = input.value.trim().toUpperCase();
if (!userAnswer) {
showToast('No Input', 'Please enter a word.', 'warning');
return;
}
AppState.attempts++;
if (userAnswer === AppState.currentWord) {
processCorrectAnswer();
} else {
processIncorrectAnswer();
}
}
function processCorrectAnswer() {
AppState.isCorrect = true;
AppState.isFirstTry = AppState.attempts === 1 && AppState.hintsUsed === 0;
// REVEAL THE WORD FOR REINFORCEMENT
document.getElementById('currentWord').textContent = AppState.currentWord.split('').join(' ');
// Play sound effect
if (AppState.isFirstTry) {
playSound('perfect');
} else {
playSound('ding');
}
// Create visual celebration
if (document.getElementById('animationsCheck')?.checked) {
createCelebration();
}
// Calculate points
let points = 0;
if (AppState.isFirstTry) {
points = 10;
AppState.streak++;
AppState.performance.currentStreak++;
showFeedback('🧠 Perfect! +10 points', 'correct');
} else {
points = 5;
AppState.streak = 0;
showFeedback('✅ Correct! +5 points', 'correct');
}
// Update score
AppState.score += points;
// Update performance
AppState.performance.totalWordsCompleted++;
if (AppState.isFirstTry) {
AppState.performance.perfectFirstTries++;
} else {
AppState.performance.hintCorrect++;
}
// Update longest streak
if (AppState.performance.currentStreak > AppState.performance.longestStreak) {
AppState.performance.longestStreak = AppState.performance.currentStreak;
}
// Add to history
const historyEntry = {
word: AppState.currentWord,
status: AppState.isFirstTry ? 'perfect' : 'hint',
attempts: AppState.attempts,
hints: AppState.hintsUsed,
time: Math.floor((Date.now() - AppState.startTime) / 1000),
timestamp: Date.now(),
points: points
};
AppState.history.unshift(historyEntry);
AppState.sessionHistory.push(historyEntry);
// Save to storage
localStorage.setItem('wordHistory', JSON.stringify(AppState.history.slice(0, 100)));
// Enable next word
document.getElementById('nextWordBtn').disabled = false;
// SHOW THE WORD - keep it revealed
document.getElementById('wordDisplay').classList.add('correct');
// Update interface
updateInterface();
updateQuickStats();
renderHistory();
// Check achievements
checkAchievements();
// Auto-proceed
setTimeout(() => {
if (AppState.currentWordIndex < AppState.totalWords - 1) {
document.getElementById('nextWordBtn').focus();
}
}, 2000);
}
function processIncorrectAnswer() {
AppState.isFirstTry = false;
AppState.streak = 0;
AppState.performance.currentStreak = 0;
// Play try again sound
playSound('tryAgain');
showFeedback('❌ Incorrect. Try again.', 'incorrect');
document.getElementById('wordDisplay').classList.add('incorrect');
// Update performance
AppState.performance.incorrect++;
// Clear input for retry
setTimeout(() => {
document.getElementById('answerInput').value = '';
document.getElementById('answerInput').focus();
}, 1000);
updateInterface();
}
// ========== HINT FUNCTIONS ==========
function provideHint() {
if (AppState.revealedLetters >= AppState.currentWord.length) return;
AppState.revealedLetters++;
AppState.hintsUsed++;
// Update masked display
AppState.maskedWord = maskWord(AppState.currentWord, AppState.revealedLetters);
document.getElementById('currentWord').textContent = AppState.maskedWord;
// Update interface
updateInterface();
// Announce hint
const revealedLetter = AppState.currentWord[AppState.revealedLetters - 1];
speakWithGoogleTTS(`Hint: Letter ${AppState.revealedLetters} is ${revealedLetter}`);
}
function revealAnswer() {
AppState.revealedLetters = AppState.currentWord.length;
AppState.maskedWord = maskWord(AppState.currentWord, AppState.revealedLetters);
document.getElementById('currentWord').textContent = AppState.maskedWord;
document.getElementById('hintBtn').disabled = true;
speakWithGoogleTTS(`Answer revealed: ${AppState.currentWord}`);
showToast('Answer Revealed', 'Answer shown. Try to remember it.', 'warning');
}
// ========== INTERFACE UPDATER ==========
function updateInterface() {
// Update progress
const progressPercent = (AppState.currentWordIndex / AppState.totalWords) * 100;
document.getElementById('progressFill').style.width = `${progressPercent}%`;
document.getElementById('progressText').textContent =
`Word ${AppState.currentWordIndex + 1}/${AppState.totalWords}`;
// Update statistics
document.getElementById('scoreValue').textContent = AppState.score;
document.getElementById('streakValue').textContent = AppState.streak;
// Calculate accuracy
const totalAttempts = AppState.performance.perfectFirstTries +
AppState.performance.hintCorrect +
AppState.performance.incorrect;
const accuracy = totalAttempts > 0 ?
Math.round(((AppState.performance.perfectFirstTries + AppState.performance.hintCorrect) / totalAttempts) * 100) : 0;
document.getElementById('accuracyValue').textContent = `${accuracy}%`;
// Update time
document.getElementById('timeValue').textContent = `${Math.floor(AppState.sessionTime)}s`;
// Update masked display if not correct
if (!AppState.isCorrect) {
document.getElementById('currentWord').textContent = AppState.maskedWord;
}
// Update controls
document.getElementById('contextBtn').disabled = !AppState.currentContext;
document.getElementById('hintBtn').disabled = AppState.isCorrect ||
AppState.revealedLetters >= AppState.currentWord.length;
}
// ========== UTILITIES ==========
function maskWord(word, revealed) {
return word.split('').map((char, index) => {
if (char === ' ') return ' ';
return index < revealed ? char : '_';
}).join(' ');
}
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// ========== UI INITIALIZATION ==========
function initUI() {
// Set user configuration
document.getElementById('userName').value = AppState.userName;
document.getElementById('userLevel').value = AppState.userLevel;
document.getElementById('googleApiKey').value = AppState.googleApiKey;
document.getElementById('googleVoice').value = AppState.googleVoice;
document.getElementById('ttsEngine').value = AppState.ttsEngine;
// Initialize sliders
document.getElementById('speechRate').value = AppState.speechRate;
document.getElementById('speechPitch').value = AppState.speechPitch;
document.getElementById('masterVolume').value = AppState.speechVolume;
// Update slider displays
document.getElementById('rateValue').textContent = `${AppState.speechRate.toFixed(1)}x`;
document.getElementById('pitchValue').textContent = AppState.speechPitch.toFixed(1);
document.getElementById('volumeValue').textContent = `${Math.round(AppState.speechVolume * 100)}%`;
document.getElementById('sessionLengthValue').textContent = AppState.totalWords;
}
// ========== EVENT HANDLERS ==========
function initEventHandlers() {
// Tab navigation
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
const panelId = this.getAttribute('data-tab');
activatePanel(panelId);
});
});
// Game controls
document.getElementById('hearWordBtn').addEventListener('click', () => speakWord());
document.getElementById('contextBtn').addEventListener('click', () => {
if (AppState.currentContext) {
speakWithGoogleTTS(AppState.currentContext);
}
});
// Validation
document.getElementById('checkAnswerBtn').addEventListener('click', validateAnswer);
document.getElementById('hintBtn').addEventListener('click', provideHint);
document.getElementById('nextWordBtn').addEventListener('click', () => {
AppState.currentWordIndex++;
loadWord();
});
document.getElementById('revealBtn').addEventListener('click', revealAnswer);
// Input handling
document.getElementById('answerInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
validateAnswer();
}
});
// Session length slider
document.getElementById('sessionLength').addEventListener('input', function() {
const sliderValue = parseInt(this.value);
let displayValue;
// Map slider values: 1=10, 2=20, 3=30, 4=40, 5=50, 6=All
if (sliderValue === 6) {
displayValue = "All";
} else {
displayValue = (sliderValue * 10).toString();
}
document.getElementById('sessionLengthValue').textContent = displayValue;
});
document.getElementById('sessionLength').addEventListener('change', function() {
initTrainingSession();
loadWord();
});
// Google TTS configuration
document.getElementById('testGoogleBtn').addEventListener('click', async () => {
const apiKey = document.getElementById('googleApiKey').value;
const voice = document.getElementById('googleVoice').value;
if (!apiKey) {
showToast('API Key Required',
'Enter Google Cloud API key for premium voice synthesis.', 'warning');
return;
}
AppState.googleApiKey = apiKey;
AppState.googleVoice = voice;
localStorage.setItem('googleApiKey', apiKey);
localStorage.setItem('googleVoice', voice);
try {
await speakWithGoogleTTS("Google TTS ready. System online.");
showToast('Google TTS Connected', 'Premium voice synthesis activated.', 'success');
} catch (error) {
showToast('TTS Failed',
'Check API key. Using fallback TTS.', 'error');
}
});
// Clear configuration
document.getElementById('clearGoogleBtn').addEventListener('click', () => {
document.getElementById('googleApiKey').value = '';
AppState.googleApiKey = '';
localStorage.removeItem('googleApiKey');
showToast('Settings Cleared', 'Google TTS settings cleared.', 'success');
});
// Training settings
document.getElementById('trainingMode').addEventListener('change', () => {
initTrainingSession();
loadWord();
});
document.getElementById('difficultyLevel').addEventListener('change', () => {
initTrainingSession();
loadWord();
});
// Profile
document.getElementById('saveProfileBtn').addEventListener('click', () => {
AppState.userName = document.getElementById('userName').value;
AppState.userLevel = document.getElementById('userLevel').value;
localStorage.setItem('userName', AppState.userName);
localStorage.setItem('userLevel', AppState.userLevel);
showToast('Profile Saved', `Name: ${AppState.userName}`, 'success');
});
// Slider controls
document.getElementById('speechRate').addEventListener('input', function() {
AppState.speechRate = parseFloat(this.value);
document.getElementById('rateValue').textContent = `${this.value}x`;
});
document.getElementById('speechPitch').addEventListener('input', function() {
AppState.speechPitch = parseFloat(this.value);
document.getElementById('pitchValue').textContent = this.value;
});
document.getElementById('masterVolume').addEventListener('input', function() {
AppState.speechVolume = parseFloat(this.value);
document.getElementById('volumeValue').textContent = `${Math.round(this.value * 100)}%`;
});
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
if (e.key !== 'Enter' && e.key !== 'Tab') return;
}
const shortcuts = ['1', '2', 'h', 'H', 'n', 'N', 'r', 'R', 'Enter'];
if (shortcuts.includes(e.key)) {
e.preventDefault();
}
// Control mapping
switch(e.key) {
case '1': document.getElementById('hearWordBtn').click(); break;
case '2': if (!document.getElementById('contextBtn').disabled)
document.getElementById('contextBtn').click(); break;
case 'h':
case 'H': if (!document.getElementById('hintBtn').disabled)
document.getElementById('hintBtn').click(); break;
case 'n':
case 'N': if (!document.getElementById('nextWordBtn').disabled)
document.getElementById('nextWordBtn').click(); break;
case 'r':
case 'R': document.getElementById('revealBtn').click(); break;
case 'Enter':
if (!AppState.isCorrect) {
document.getElementById('checkAnswerBtn').click();
} else if (!document.getElementById('nextWordBtn').disabled) {
document.getElementById('nextWordBtn').click();
}
break;
case 'Tab':
e.preventDefault();
if (!AppState.isCorrect) {
document.getElementById('hintBtn').click();
}
break;
}
});
}
// ========== PANEL ACTIVATION ==========
function activatePanel(panelId) {
// Update tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
if (tab.getAttribute('data-tab') === panelId) {
tab.classList.add('active');
}
});
// Update panels
document.querySelectorAll('.panel').forEach(panel => {
panel.classList.remove('active');
if (panel.id === `${panelId}-panel`) {
panel.classList.add('active');
}
});
// Panel-specific initialization
if (panelId === 'analytics') {
updateQuickStats();
renderHistory();
renderBadges();
}
}
// ========== TOAST SYSTEM ==========
function showToast(title, message, type = 'info') {
const toastContainer = document.getElementById('toastContainer');
const toastId = 'toast-' + Date.now();
const iconMap = {
success: '✅',
warning: '⚠️',
error: '❌',
info: 'ℹ️'
};
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.id = toastId;
toast.innerHTML = `
<div class="toast-icon">${iconMap[type] || iconMap.info}</div>
<div class="toast-content">
<div class="toast-title">${title}</div>
<div class="toast-message">${message}</div>
</div>
<button class="toast-close" onclick="document.getElementById('${toastId}').remove()">&times;</button>
`;
toastContainer.appendChild(toast);
// Auto-remove
setTimeout(() => {
const toastElement = document.getElementById(toastId);
if (toastElement) {
toastElement.style.opacity = '0';
toastElement.style.transform = 'translateX(100%)';
setTimeout(() => toastElement.remove(), 300);
}
}, 5000);
}
// ========== FEEDBACK SYSTEM ==========
function showFeedback(message, type) {
const feedback = document.getElementById('feedbackContainer');
feedback.textContent = message;
feedback.className = `feedback-container ${type}`;
setTimeout(() => {
feedback.className = 'feedback-container';
}, 3000);
}
// ========== ACHIEVEMENT SYSTEM ==========
function checkAchievements() {
const achievements = AppState.badges.filter(badge =>
!badge.earned && validateAchievement(badge.id));
if (achievements.length > 0) {
achievements.forEach(badge => {
badge.earned = true;
showToast('Achievement Unlocked',
`${badge.icon} ${badge.name}: ${badge.description}`, 'success');
});
localStorage.setItem('badges', JSON.stringify(AppState.badges));
}
}
function validateAchievement(badgeId) {
switch(badgeId) {
case 1: return AppState.performance.currentStreak >= 10;
case 2: return AppState.performance.totalWordsCompleted >= 50;
case 3: return AppState.performance.totalWordsCompleted > 0 &&
(AppState.performance.totalTime / AppState.performance.totalWordsCompleted) < 30;
case 4: return AppState.performance.totalWordsCompleted > 0 &&
(AppState.performance.perfectFirstTries / AppState.performance.totalWordsCompleted) >= 0.9;
case 5: return AppState.sessionHistory.length > 0 &&
AppState.sessionHistory.every(item => item.status === 'perfect');
case 6: return AppState.performance.totalWordsCompleted >= 100;
case 7: return AppState.performance.currentStreak >= 20;
case 8: return AppState.sessionHistory.length > 0 &&
AppState.sessionHistory.every(item => item.hints === 0);
case 9: return AppState.performance.totalWordsCompleted > 0 &&
(AppState.performance.totalTime / AppState.performance.totalWordsCompleted) < 15;
default: return false;
}
}
// ========== COMPLETION MODAL ==========
function showCompletionModal() {
const modal = document.getElementById('sessionCompleteModal');
const content = document.getElementById('modalContent');
// Calculate session metrics
const perfectCount = AppState.sessionHistory.filter(item => item.status === 'perfect').length;
const accuracy = AppState.sessionHistory.length > 0 ?
Math.round((perfectCount / AppState.sessionHistory.length) * 100) : 0;
content.innerHTML = `
<div style="text-align: center; padding: 20px 0;">
<div style="font-size: 5rem; margin-bottom: 30px; animation: neuralPulse 2s infinite;">🧠</div>
<h3 style="color: var(--primary); margin-bottom: 20px; font-size: 2.5rem; font-family: 'Orbitron';">SESSION COMPLETE!</h3>
<p style="margin-bottom: 40px; color: var(--muted); font-size: 1.3rem; max-width: 600px; margin: 0 auto 40px;">
You have completed ${AppState.totalWords} words.
</p>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 40px;">
<div class="stat-card" style="background: rgba(0, 255, 157, 0.1);">
<div class="stat-value" style="color: var(--success);">${AppState.score}</div>
<div class="stat-label">SCORE</div>
</div>
<div class="stat-card" style="background: rgba(0, 243, 255, 0.1);">
<div class="stat-value" style="color: var(--primary);">${accuracy}%</div>
<div class="stat-label">ACCURACY</div>
</div>
<div class="stat-card" style="background: rgba(255, 0, 255, 0.1);">
<div class="stat-value" style="color: var(--accent);">${perfectCount}</div>
<div class="stat-label">PERFECT WORDS</div>
</div>
<div class="stat-card" style="background: rgba(255, 214, 0, 0.1);">
<div class="stat-value" style="color: var(--warning);">${Math.floor(AppState.sessionTime / 60)}m</div>
<div class="stat-label">TIME SPENT</div>
</div>
</div>
<div style="display: flex; gap: 20px; justify-content: center; flex-wrap: wrap;">
<button id="newSessionModalBtn" class="btn primary" style="min-width: 200px;">
<i class="fas fa-sync"></i> New Session
</button>
<button id="reviewSessionBtn" class="btn secondary" style="min-width: 200px;">
<i class="fas fa-chart-network"></i> View Stats
</button>
<button id="shareResultsBtn" class="btn secondary" style="min-width: 200px;">
<i class="fas fa-share"></i> Export Data
</button>
</div>
</div>
`;
modal.classList.add('active');
// Modal event handlers
document.getElementById('newSessionModalBtn').addEventListener('click', () => {
completeSession();
modal.classList.remove('active');
});
document.getElementById('reviewSessionBtn').addEventListener('click', () => {
activatePanel('analytics');
modal.classList.remove('active');
});
document.getElementById('shareResultsBtn').addEventListener('click', exportData);
}
function completeSession() {
// Update performance
AppState.performance.totalSessions++;
AppState.performance.totalTime += AppState.sessionTime;
// Save data
localStorage.setItem('totalSessions', AppState.performance.totalSessions);
localStorage.setItem('totalWordsCompleted', AppState.performance.totalWordsCompleted);
localStorage.setItem('perfectFirstTries', AppState.performance.perfectFirstTries);
localStorage.setItem('hintCorrect', AppState.performance.hintCorrect);
localStorage.setItem('incorrect', AppState.performance.incorrect);
localStorage.setItem('totalTime', AppState.performance.totalTime);
localStorage.setItem('longestStreak', AppState.performance.longestStreak);
// Start new session
initTrainingSession();
loadWord();
// Reset time tracking
AppState.startTime = Date.now();
AppState.sessionTime = 0;
showToast('New Session Started', 'New training session started.', 'success');
}
// ========== DATA EXPORT ==========
function exportData() {
const data = {
profile: {
name: AppState.userName,
level: AppState.userLevel,
xp: AppState.userXP
},
performance: AppState.performance,
achievements: AppState.badges.filter(b => b.earned),
history: AppState.history,
timestamp: Date.now()
};
const dataStr = JSON.stringify(data, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const fileName = `spelling-data-${new Date().toISOString().split('T')[0]}.json`;
const downloadLink = document.createElement('a');
downloadLink.setAttribute('href', dataUri);
downloadLink.setAttribute('download', fileName);
downloadLink.click();
showToast('Data Exported', 'Your progress data has been downloaded.', 'success');
}
// ========== STATISTICS RENDERER ==========
function updateQuickStats() {
const container = document.getElementById('quickStats');
if (!container) return;
const accuracy = AppState.performance.totalWordsCompleted > 0 ?
Math.round((AppState.performance.perfectFirstTries / AppState.performance.totalWordsCompleted) * 100) : 0;
container.innerHTML = `
<div style="display: flex; justify-content: space-between; margin-bottom: 15px; align-items: center;">
<span style="color: var(--muted); font-size: 0.9rem;">Level:</span>
<span style="color: var(--primary); font-weight: 600; font-size: 1rem; font-family: 'Orbitron';">${AppState.userLevel.toUpperCase()}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 15px; align-items: center;">
<span style="color: var(--muted); font-size: 0.9rem;">XP:</span>
<span style="color: var(--primary); font-weight: 600; font-size: 1rem; font-family: 'Orbitron';">${AppState.userXP}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 15px; align-items: center;">
<span style="color: var(--muted); font-size: 0.9rem;">Words Completed:</span>
<span style="color: var(--primary); font-weight: 600; font-size: 1rem; font-family: 'Orbitron';">${AppState.performance.totalWordsCompleted}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 15px; align-items: center;">
<span style="color: var(--muted); font-size: 0.9rem;">Accuracy:</span>
<span style="color: var(--primary); font-weight: 600; font-size: 1rem; font-family: 'Orbitron';">${accuracy}%</span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="color: var(--muted); font-size: 0.9rem;">Best Streak:</span>
<span style="color: var(--primary); font-weight: 600; font-size: 1rem; font-family: 'Orbitron';">${AppState.performance.longestStreak}</span>
</div>
`;
}
// ========== BADGES RENDERER ==========
function renderBadges() {
const container = document.getElementById('badgesGrid');
if (!container) return;
container.innerHTML = '';
// Load cached achievements
const cachedBadges = JSON.parse(localStorage.getItem('badges'));
if (cachedBadges) {
AppState.badges = cachedBadges;
}
AppState.badges.forEach(badge => {
const badgeElement = document.createElement('div');
badgeElement.className = `badge ${badge.earned ? 'earned' : 'locked'}`;
badgeElement.innerHTML = `
<div class="badge-icon">${badge.icon}</div>
<div class="badge-name">${badge.name}</div>
<div class="badge-desc">${badge.description}</div>
`;
container.appendChild(badgeElement);
});
}
// ========== HISTORY RENDERER ==========
function renderHistory() {
const container = document.getElementById('wordHistory');
if (!container) return;
container.innerHTML = '';
// Display recent 10 entries
const recentHistory = AppState.history.slice(0, 10);
if (recentHistory.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 60px 30px; color: var(--muted);">
<i class="fas fa-history" style="font-size: 3rem; margin-bottom: 20px; opacity: 0.5;"></i>
<div style="font-size: 1.2rem; font-family: 'Rajdhani';">No history yet</div>
<div style="font-size: 0.9rem; margin-top: 10px;">Complete a training session to see history</div>
</div>
`;
return;
}
recentHistory.forEach(item => {
const historyElement = document.createElement('div');
historyElement.className = `history-item ${item.status === 'hint' ? 'hint' : ''}`;
historyElement.innerHTML = `
<div class="history-header">
<div class="history-word">${'█'.repeat(item.word.length)}</div>
<div class="history-stats">
<span style="color: ${item.status === 'perfect' ? 'var(--success)' : 'var(--secondary)'};">
${item.status === 'perfect' ? '⭐' : '💡'}
</span>
<span>+${item.points}</span>
</div>
</div>
<div class="history-details">
<span>${item.attempts} attempt${item.attempts > 1 ? 's' : ''}</span>
<span>${item.hints} hint${item.hints !== 1 ? 's' : ''}</span>
<span>${item.time}s</span>
</div>
`;
container.appendChild(historyElement);
});
}
// ========== TIME TRACKER ==========
function startTimeTracker() {
setInterval(() => {
if (AppState.startTime) {
AppState.sessionTime = Math.floor((Date.now() - AppState.startTime) / 1000);
document.getElementById('timeValue').textContent = `${AppState.sessionTime}s`;
}
}, 1000);
}
// ========== GLOBAL FUNCTIONS ==========
window.showToast = showToast;
window.activatePanel = activatePanel;
</script>
</body>
</html>