ReneeHWT commited on
Commit
d02a4aa
·
verified ·
1 Parent(s): f6386f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +392 -0
app.py CHANGED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html><head><base href="https://example.com">
2
+ <meta charset="UTF-8">
3
+ <title>Personal English Vocabulary App</title>
4
+ <!-- Google Fonts -->
5
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
6
+ <!-- CSS for styling and animations -->
7
+ <style>
8
+ body {
9
+ font-family: 'Inter', sans-serif;
10
+ background: linear-gradient(135deg, #d6c8c0, #b3a9b8);
11
+ display: flex;
12
+ flex-direction: column;
13
+ align-items: center;
14
+ padding: 20px;
15
+ min-height: 100vh;
16
+ }
17
+
18
+ h1 {
19
+ color: #5a5a5a;
20
+ margin-bottom: 20px;
21
+ animation: fadeInDown 1s ease-in-out;
22
+ display: flex;
23
+ align-items: center;
24
+ gap: 10px;
25
+ }
26
+
27
+ #upload-section, #quiz-section {
28
+ background: #f5f5f5;
29
+ padding: 20px 30px;
30
+ border-radius: 10px;
31
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
32
+ width: 100%;
33
+ max-width: 600px;
34
+ margin-bottom: 20px;
35
+ animation: fadeInUp 1s ease-in-out;
36
+ }
37
+
38
+ label {
39
+ display: block;
40
+ margin-bottom: 10px;
41
+ color: #6b6b6b;
42
+ }
43
+
44
+ input[type="file"], input[type="text"], button {
45
+ width: 100%;
46
+ padding: 10px;
47
+ margin-bottom: 15px;
48
+ border: none;
49
+ border-radius: 5px;
50
+ font-size: 16px;
51
+ }
52
+
53
+ input[type="text"] {
54
+ background-color: #ffffff;
55
+ border: 1px solid #ccc;
56
+ }
57
+
58
+ button {
59
+ background-color: #a89fcb;
60
+ color: #fff;
61
+ cursor: pointer;
62
+ transition: background-color 0.3s ease;
63
+ }
64
+
65
+ button:hover {
66
+ background-color: #8e82b7;
67
+ }
68
+
69
+ #quiz-container {
70
+ display: none;
71
+ text-align: center;
72
+ width: 100%;
73
+ }
74
+
75
+ #matching-game {
76
+ display: flex;
77
+ justify-content: space-between;
78
+ gap: 20px;
79
+ flex-wrap: wrap;
80
+ }
81
+
82
+ .column {
83
+ width: 45%;
84
+ }
85
+
86
+ .column h3 {
87
+ color: #5a5a5a;
88
+ margin-bottom: 10px;
89
+ animation: fadeInDown 1s ease-in-out;
90
+ }
91
+
92
+ .list {
93
+ list-style: none;
94
+ padding: 0;
95
+ }
96
+
97
+ .list-item {
98
+ background: #cfcfcf;
99
+ padding: 10px;
100
+ margin: 5px 0;
101
+ border-radius: 5px;
102
+ cursor: pointer;
103
+ transition: background 0.3s ease, transform 0.3s ease;
104
+ color: #5a5a5a;
105
+ user-select: none;
106
+ }
107
+
108
+ .list-item:hover {
109
+ background: #bcbcbc;
110
+ transform: scale(1.05);
111
+ }
112
+
113
+ .selected {
114
+ background: #a89fcb;
115
+ color: #fff;
116
+ }
117
+
118
+ #result {
119
+ font-size: 1.2em;
120
+ margin-top: 20px;
121
+ color: #6ab04c;
122
+ animation: fadeIn 0.5s ease-in-out;
123
+ }
124
+
125
+ /* Animations */
126
+ @keyframes fadeInDown {
127
+ from {
128
+ opacity: 0;
129
+ transform: translateY(-20px);
130
+ }
131
+ to {
132
+ opacity: 1;
133
+ transform: translateY(0);
134
+ }
135
+ }
136
+
137
+ @keyframes fadeInUp {
138
+ from {
139
+ opacity: 0;
140
+ transform: translateY(20px);
141
+ }
142
+ to {
143
+ opacity: 1;
144
+ transform: translateY(0);
145
+ }
146
+ }
147
+
148
+ @keyframes fadeIn {
149
+ from { opacity: 0; }
150
+ to { opacity: 1; }
151
+ }
152
+
153
+ /* SVG Icon */
154
+ .icon {
155
+ width: 50px;
156
+ height: 50px;
157
+ margin-bottom: 10px;
158
+ fill: #a89fcb;
159
+ animation: rotate 4s linear infinite;
160
+ }
161
+
162
+ @keyframes rotate {
163
+ from { transform: rotate(0deg); }
164
+ to { transform: rotate(360deg); }
165
+ }
166
+
167
+ hr {
168
+ border: none;
169
+ border-top: 1px solid #ddd;
170
+ margin: 20px 0;
171
+ }
172
+
173
+ h2 {
174
+ color: #5a5a5a;
175
+ margin-bottom: 15px;
176
+ animation: fadeInDown 1s ease-in-out;
177
+ }
178
+ </style>
179
+ </head>
180
+ <body>
181
+ <h1>
182
+ 📚 Personal English Vocabulary App
183
+ </h1>
184
+ <div id="upload-section">
185
+ <svg class="icon" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
186
+ <path d="M48,4H16C10.477,4,6,8.477,6,14v36c0,5.523,4.477,10,10,10h32c5.523,0,10-4.477,10-10V14C58,8.477,53.523,4,48,4z M48,50H16c-2.209,0-4-1.791-4-4V14c0-2.209,1.791-4,4-4h32c2.209,0,4,1.791,4,4v32C52,48.209,50.209,50,48,50z"/>
187
+ <path d="M22,22h20v4H22V22z M22,30h20v4H22V30z M22,38h20v4H22V38z"/>
188
+ </svg>
189
+ <label for="vocab-file">Upload Your Vocabulary (CSV format: word, definition)</label>
190
+ <input type="file" id="vocab-file" accept=".csv">
191
+ <button id="start-quiz">Start Quiz</button>
192
+ <hr>
193
+ <h2>Add a Vocabulary Word Manually</h2>
194
+ <label for="manual-word">Word:</label>
195
+ <input type="text" id="manual-word" placeholder="Enter word">
196
+ <label for="manual-definition">Definition:</label>
197
+ <input type="text" id="manual-definition" placeholder="Enter definition">
198
+ <button id="add-word">Add Word</button>
199
+ </div>
200
+
201
+ <div id="quiz-section">
202
+ <div id="quiz-container">
203
+ <div id="matching-game">
204
+ <div class="column" id="words-column">
205
+ <h3>Words</h3>
206
+ <ul class="list" id="words-list">
207
+ <!-- Words will be generated here -->
208
+ </ul>
209
+ </div>
210
+ <div class="column" id="definitions-column">
211
+ <h3>Definitions</h3>
212
+ <ul class="list" id="definitions-list">
213
+ <!-- Definitions will be generated here -->
214
+ </ul>
215
+ </div>
216
+ </div>
217
+ <div id="result"></div>
218
+ <button id="reset-quiz" style="margin-top: 20px; display: none;">Reset Quiz</button>
219
+ </div>
220
+ </div>
221
+
222
+ <!-- JavaScript for functionality -->
223
+ <script>
224
+ const uploadSection = document.getElementById('upload-section');
225
+ const quizSection = document.getElementById('quiz-section');
226
+ const startQuizButton = document.getElementById('start-quiz');
227
+ const vocabFileInput = document.getElementById('vocab-file');
228
+ const wordsList = document.getElementById('words-list');
229
+ const definitionsList = document.getElementById('definitions-list');
230
+ const resultEl = document.getElementById('result');
231
+ const addWordButton = document.getElementById('add-word');
232
+ const manualWordInput = document.getElementById('manual-word');
233
+ const manualDefInput = document.getElementById('manual-definition');
234
+ const resetQuizButton = document.getElementById('reset-quiz');
235
+
236
+ let vocabulary = [];
237
+ let matches = {};
238
+ let selectedWord = null;
239
+ let selectedDefinition = null;
240
+ let score = 0;
241
+
242
+ startQuizButton.addEventListener('click', () => {
243
+ const file = vocabFileInput.files[0];
244
+ if (file) {
245
+ const reader = new FileReader();
246
+ reader.onload = function(e) {
247
+ const text = e.target.result;
248
+ parseCSV(text);
249
+ if(vocabulary.length > 0){
250
+ uploadSection.style.display = 'none';
251
+ quizSection.style.display = 'block';
252
+ startMatchingGame();
253
+ } else {
254
+ alert('Please upload a valid CSV file or add words manually.');
255
+ }
256
+ }
257
+ reader.readAsText(file);
258
+ } else if (vocabulary.length > 0) {
259
+ uploadSection.style.display = 'none';
260
+ quizSection.style.display = 'block';
261
+ startMatchingGame();
262
+ } else {
263
+ alert('Please upload a CSV file or add words manually.');
264
+ }
265
+ });
266
+
267
+ addWordButton.addEventListener('click', () => {
268
+ const word = manualWordInput.value.trim();
269
+ const definition = manualDefInput.value.trim();
270
+ if (word && definition) {
271
+ vocabulary.push({ word, definition });
272
+ manualWordInput.value = '';
273
+ manualDefInput.value = '';
274
+ alert(`Added "${word}" to your vocabulary.`);
275
+ } else {
276
+ alert('Please enter both word and definition.');
277
+ }
278
+ });
279
+
280
+ resetQuizButton.addEventListener('click', () => {
281
+ vocabulary = [];
282
+ matches = {};
283
+ selectedWord = null;
284
+ selectedDefinition = null;
285
+ score = 0;
286
+ wordsList.innerHTML = '';
287
+ definitionsList.innerHTML = '';
288
+ resultEl.innerText = '';
289
+ resetQuizButton.style.display = 'none';
290
+ uploadSection.style.display = 'block';
291
+ quizSection.style.display = 'none';
292
+ });
293
+
294
+ function parseCSV(text) {
295
+ const lines = text.split('\\n');
296
+ const parsed = lines.map(line => {
297
+ const [word, definition] = line.split(',');
298
+ return { word: word.trim(), definition: definition.trim() };
299
+ }).filter(item => item.word && item.definition);
300
+ vocabulary = vocabulary.concat(parsed);
301
+ }
302
+
303
+ function startMatchingGame() {
304
+ shuffleArray(vocabulary);
305
+ displayWords();
306
+ displayDefinitions();
307
+ }
308
+
309
+ function displayWords() {
310
+ wordsList.innerHTML = '';
311
+ vocabulary.forEach((item, index) => {
312
+ const li = document.createElement('li');
313
+ li.classList.add('list-item');
314
+ li.innerText = item.word;
315
+ li.dataset.word = item.word;
316
+ li.addEventListener('click', () => selectWord(li));
317
+ wordsList.appendChild(li);
318
+ });
319
+ }
320
+
321
+ function displayDefinitions() {
322
+ definitionsList.innerHTML = '';
323
+ const shuffledDefinitions = [...vocabulary.map(item => item.definition)];
324
+ shuffleArray(shuffledDefinitions);
325
+ shuffledDefinitions.forEach((def, index) => {
326
+ const li = document.createElement('li');
327
+ li.classList.add('list-item');
328
+ li.innerText = def;
329
+ li.dataset.definition = def;
330
+ li.addEventListener('click', () => selectDefinition(li));
331
+ definitionsList.appendChild(li);
332
+ });
333
+ }
334
+
335
+ function selectWord(element) {
336
+ if (selectedWord) {
337
+ selectedWord.classList.remove('selected');
338
+ }
339
+ selectedWord = element;
340
+ selectedWord.classList.add('selected');
341
+ checkMatch();
342
+ }
343
+
344
+ function selectDefinition(element) {
345
+ if (selectedDefinition) {
346
+ selectedDefinition.classList.remove('selected');
347
+ }
348
+ selectedDefinition = element;
349
+ selectedDefinition.classList.add('selected');
350
+ checkMatch();
351
+ }
352
+
353
+ function checkMatch() {
354
+ if (selectedWord && selectedDefinition) {
355
+ const word = selectedWord.dataset.word;
356
+ const definition = selectedDefinition.dataset.definition;
357
+ const vocabItem = vocabulary.find(item => item.word === word);
358
+ if (vocabItem.definition === definition) {
359
+ score++;
360
+ resultEl.style.color = '#6ab04c';
361
+ resultEl.innerText = 'Correct! 🎉';
362
+ selectedWord.style.backgroundColor = '#6ab04c';
363
+ selectedDefinition.style.backgroundColor = '#6ab04c';
364
+ } else {
365
+ resultEl.style.color = '#e74c3c';
366
+ resultEl.innerText = `Incorrect! "${word}" does not match the selected definition.`;
367
+ selectedWord.style.backgroundColor = '#e74c3c';
368
+ selectedDefinition.style.backgroundColor = '#e74c3c';
369
+ }
370
+ // Disable further selection
371
+ selectedWord.removeEventListener('click', selectWord);
372
+ selectedDefinition.removeEventListener('click', selectDefinition);
373
+ selectedWord = null;
374
+ selectedDefinition = null;
375
+
376
+ // Check if all matches are done
377
+ if (score === vocabulary.length) {
378
+ resultEl.innerText += ' You matched all correctly!';
379
+ resetQuizButton.style.display = 'block';
380
+ }
381
+ }
382
+ }
383
+
384
+ function shuffleArray(array) {
385
+ for (let i = array.length - 1; i > 0; i--) {
386
+ const j = Math.floor(Math.random() * (i + 1));
387
+ [array[i], array[j]] = [array[j], array[i]];
388
+ }
389
+ }
390
+ </script>
391
+ </body>
392
+ </html>