GameSentenceMiner 2.15.10__py3-none-any.whl → 2.15.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. GameSentenceMiner/anki.py +31 -0
  2. GameSentenceMiner/ocr/owocr_helper.py +5 -5
  3. GameSentenceMiner/web/static/css/kanji-grid.css +107 -0
  4. GameSentenceMiner/web/static/css/search.css +14 -0
  5. GameSentenceMiner/web/static/css/shared.css +932 -0
  6. GameSentenceMiner/web/static/css/stats.css +499 -0
  7. GameSentenceMiner/web/static/js/anki_stats.js +84 -0
  8. GameSentenceMiner/web/static/js/database.js +541 -0
  9. GameSentenceMiner/web/static/js/kanji-grid.js +203 -0
  10. GameSentenceMiner/web/static/js/search.js +273 -0
  11. GameSentenceMiner/web/static/js/shared.js +506 -0
  12. GameSentenceMiner/web/static/js/stats.js +1427 -0
  13. GameSentenceMiner/web/templates/anki_stats.html +205 -0
  14. GameSentenceMiner/web/templates/components/navigation.html +16 -0
  15. GameSentenceMiner/web/templates/components/theme-styles.html +128 -0
  16. GameSentenceMiner/web/templates/stats.html +4 -0
  17. GameSentenceMiner/web/texthooking_page.py +50 -0
  18. {gamesentenceminer-2.15.10.dist-info → gamesentenceminer-2.15.12.dist-info}/METADATA +1 -1
  19. {gamesentenceminer-2.15.10.dist-info → gamesentenceminer-2.15.12.dist-info}/RECORD +23 -11
  20. GameSentenceMiner/web/templates/text_replacements.html +0 -449
  21. {gamesentenceminer-2.15.10.dist-info → gamesentenceminer-2.15.12.dist-info}/WHEEL +0 -0
  22. {gamesentenceminer-2.15.10.dist-info → gamesentenceminer-2.15.12.dist-info}/entry_points.txt +0 -0
  23. {gamesentenceminer-2.15.10.dist-info → gamesentenceminer-2.15.12.dist-info}/licenses/LICENSE +0 -0
  24. {gamesentenceminer-2.15.10.dist-info → gamesentenceminer-2.15.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,499 @@
1
+ /* Statistics Page Specific Styles */
2
+
3
+ .chart-container {
4
+ position: relative;
5
+ background: var(--bg-secondary);
6
+ padding: 20px;
7
+ border-radius: 8px;
8
+ box-shadow: 0 4px 12px var(--shadow-color);
9
+ margin-bottom: 30px;
10
+ border: 1px solid var(--border-color);
11
+ }
12
+
13
+ .heatmap-year {
14
+ margin-bottom: 30px;
15
+ }
16
+
17
+ .heatmap-year h3 {
18
+ margin-bottom: 20px;
19
+ color: var(--text-secondary);
20
+ }
21
+
22
+ .heatmap-streaks {
23
+ display: flex;
24
+ justify-content: flex-start;
25
+ gap: 40px;
26
+ margin-bottom: 20px;
27
+ font-size: 14px;
28
+ }
29
+
30
+ .heatmap-streak-item {
31
+ text-align: center;
32
+ }
33
+
34
+ .heatmap-streak-number {
35
+ font-size: 2em;
36
+ font-weight: bold;
37
+ color: var(--success-color);
38
+ margin-bottom: 5px;
39
+ }
40
+
41
+ .heatmap-streak-label {
42
+ color: var(--text-tertiary);
43
+ }
44
+
45
+ .heatmap-container-wrapper {
46
+ display: flex;
47
+ align-items: flex-start;
48
+ gap: 10px;
49
+ margin-bottom: 10px;
50
+ }
51
+
52
+ .heatmap-wrapper {
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: flex-start;
56
+ width: 100%;
57
+ }
58
+
59
+ .heatmap-day-labels {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 2px;
63
+ padding-top: 20px;
64
+ }
65
+
66
+ .heatmap-day-label {
67
+ width: 16px;
68
+ height: 16px;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ font-size: 12px;
73
+ color: var(--text-tertiary);
74
+ font-weight: normal;
75
+ }
76
+
77
+ .heatmap-month-labels {
78
+ display: grid;
79
+ grid-template-columns: repeat(53, minmax(10px, 1fr));
80
+ gap: 2px;
81
+ margin-bottom: 5px;
82
+ height: 15px;
83
+ }
84
+
85
+ .heatmap-month-label {
86
+ font-size: 10px;
87
+ color: var(--text-tertiary);
88
+ text-align: left;
89
+ align-self: end;
90
+ }
91
+
92
+ .heatmap-grid {
93
+ display: grid;
94
+ grid-template-columns: repeat(53, minmax(10px, 1fr));
95
+ grid-template-rows: repeat(7, minmax(10px, 1fr));
96
+ gap: 2px;
97
+ width: 100%;
98
+ max-width: 100%;
99
+ aspect-ratio: 53/7;
100
+ }
101
+
102
+ .heatmap-cell {
103
+ width: 100%;
104
+ height: 100%;
105
+ background-color: var(--bg-tertiary);
106
+ border-radius: 2px;
107
+ position: relative;
108
+ cursor: pointer;
109
+ min-width: 10px;
110
+ min-height: 10px;
111
+ }
112
+
113
+ .heatmap-cell.level-1 { background-color: #c6e48b; }
114
+ .heatmap-cell.level-2 { background-color: #7bc96f; }
115
+ .heatmap-cell.level-3 { background-color: #239a3b; }
116
+ .heatmap-cell.level-4 { background-color: #196127; }
117
+
118
+ .heatmap-cell:hover {
119
+ stroke: #1f2328;
120
+ stroke-width: 1px;
121
+ }
122
+
123
+ .heatmap-legend {
124
+ display: flex;
125
+ align-items: center;
126
+ margin-top: 10px;
127
+ font-size: 12px;
128
+ color: #586069;
129
+ }
130
+
131
+ .heatmap-legend-item {
132
+ width: 12px;
133
+ height: 12px;
134
+ margin: 0 2px;
135
+ border-radius: 2px;
136
+ }
137
+
138
+ /* Kanji Grid Styles - Moved to shared kanji-grid.css */
139
+
140
+ /* Delete Game Entry Section Styles */
141
+ .delete-controls {
142
+ display: flex;
143
+ gap: 10px;
144
+ margin-bottom: 20px;
145
+ flex-wrap: wrap;
146
+ align-items: center;
147
+ }
148
+
149
+ /* Dashboard Cards Styles */
150
+ .dashboard-container {
151
+ display: grid;
152
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
153
+ gap: 30px;
154
+ margin-bottom: 40px;
155
+ }
156
+
157
+ .dashboard-card {
158
+ background: var(--bg-secondary);
159
+ border-radius: 12px;
160
+ box-shadow: 0 4px 16px var(--shadow-color);
161
+ border: 1px solid var(--border-color);
162
+ padding: 24px;
163
+ transition: all 0.3s ease;
164
+ position: relative;
165
+ overflow: hidden;
166
+ }
167
+
168
+ .dashboard-card:hover {
169
+ transform: translateY(-2px);
170
+ box-shadow: 0 8px 24px var(--shadow-color);
171
+ }
172
+
173
+ .dashboard-card::before {
174
+ content: '';
175
+ position: absolute;
176
+ top: 0;
177
+ left: 0;
178
+ right: 0;
179
+ height: 4px;
180
+ background: linear-gradient(90deg, var(--accent-color), var(--success-color));
181
+ border-radius: 12px 12px 0 0;
182
+ }
183
+
184
+ .dashboard-card.current-game::before {
185
+ background: linear-gradient(90deg, var(--accent-color), var(--info-color));
186
+ }
187
+
188
+ .dashboard-card.all-games::before {
189
+ background: linear-gradient(90deg, var(--success-color), var(--warning-color));
190
+ }
191
+
192
+ .dashboard-card-header {
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: space-between;
196
+ margin-bottom: 20px;
197
+ }
198
+
199
+ .dashboard-card-title {
200
+ font-size: 20px;
201
+ font-weight: 600;
202
+ color: var(--text-primary);
203
+ margin: 0;
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 10px;
207
+ }
208
+
209
+ .dashboard-card-icon {
210
+ font-size: 24px;
211
+ opacity: 0.8;
212
+ }
213
+
214
+ .dashboard-card-subtitle {
215
+ font-size: 14px;
216
+ color: var(--text-tertiary);
217
+ margin: 4px 0 0 0;
218
+ }
219
+
220
+ .dashboard-stats-grid {
221
+ display: grid;
222
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
223
+ gap: 16px;
224
+ margin-bottom: 20px;
225
+ }
226
+
227
+ .dashboard-stat-item {
228
+ text-align: center;
229
+ padding: 12px;
230
+ background: var(--bg-tertiary);
231
+ border-radius: 8px;
232
+ transition: all 0.2s ease;
233
+ position: relative;
234
+ cursor: pointer;
235
+ }
236
+
237
+ .dashboard-stat-item:hover {
238
+ background: var(--border-color);
239
+ transform: scale(1.02);
240
+ }
241
+
242
+ .dashboard-stat-value {
243
+ font-size: 24px;
244
+ font-weight: bold;
245
+ color: var(--text-primary);
246
+ margin-bottom: 4px;
247
+ display: block;
248
+ }
249
+
250
+ .dashboard-stat-label {
251
+ font-size: 12px;
252
+ color: var(--text-tertiary);
253
+ text-transform: uppercase;
254
+ letter-spacing: 0.5px;
255
+ font-weight: 500;
256
+ }
257
+
258
+ .dashboard-progress-section {
259
+ margin-top: 20px;
260
+ padding-top: 20px;
261
+ border-top: 1px solid var(--border-color);
262
+ }
263
+
264
+ .dashboard-progress-title {
265
+ font-size: 14px;
266
+ font-weight: 600;
267
+ color: var(--text-secondary);
268
+ margin-bottom: 12px;
269
+ }
270
+
271
+ .dashboard-progress-items {
272
+ display: flex;
273
+ justify-content: space-between;
274
+ gap: 16px;
275
+ }
276
+
277
+ .dashboard-progress-item {
278
+ text-align: center;
279
+ flex: 1;
280
+ }
281
+
282
+ .dashboard-progress-value {
283
+ font-size: 18px;
284
+ font-weight: bold;
285
+ margin-bottom: 4px;
286
+ }
287
+
288
+ .dashboard-progress-value.positive {
289
+ color: var(--success-color);
290
+ }
291
+
292
+ .dashboard-progress-value.neutral {
293
+ color: var(--text-secondary);
294
+ }
295
+
296
+ .dashboard-progress-label {
297
+ font-size: 11px;
298
+ color: var(--text-tertiary);
299
+ text-transform: uppercase;
300
+ letter-spacing: 0.5px;
301
+ }
302
+
303
+ .dashboard-streak-indicator {
304
+ display: inline-flex;
305
+ align-items: center;
306
+ gap: 4px;
307
+ font-size: 12px;
308
+ color: var(--success-color);
309
+ background: rgba(40, 167, 69, 0.1);
310
+ padding: 4px 8px;
311
+ border-radius: 12px;
312
+ font-weight: 500;
313
+ }
314
+
315
+ .dashboard-streak-indicator::before {
316
+ content: '🔥';
317
+ font-size: 14px;
318
+ }
319
+
320
+ /* Tooltip Styles */
321
+ .tooltip {
322
+ position: relative;
323
+ cursor: help;
324
+ }
325
+
326
+ .tooltip::before {
327
+ content: attr(data-tooltip);
328
+ position: absolute;
329
+ bottom: 125%;
330
+ left: 50%;
331
+ transform: translateX(-50%);
332
+ background: rgba(0, 0, 0, 0.9);
333
+ color: white;
334
+ padding: 8px 12px;
335
+ border-radius: 6px;
336
+ font-size: 12px;
337
+ white-space: nowrap;
338
+ opacity: 0;
339
+ visibility: hidden;
340
+ transition: all 0.3s ease;
341
+ z-index: 1000;
342
+ pointer-events: none;
343
+ }
344
+
345
+ .tooltip::after {
346
+ content: '';
347
+ position: absolute;
348
+ bottom: 120%;
349
+ left: 50%;
350
+ transform: translateX(-50%);
351
+ border: 4px solid transparent;
352
+ border-top-color: rgba(0, 0, 0, 0.9);
353
+ opacity: 0;
354
+ visibility: hidden;
355
+ transition: all 0.3s ease;
356
+ z-index: 1000;
357
+ }
358
+
359
+ .tooltip:hover::before,
360
+ .tooltip:hover::after {
361
+ opacity: 1;
362
+ visibility: visible;
363
+ }
364
+
365
+ /* Loading State */
366
+ .dashboard-loading {
367
+ display: flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+ min-height: 200px;
371
+ color: var(--text-tertiary);
372
+ }
373
+
374
+ .dashboard-loading .spinner {
375
+ margin-right: 10px;
376
+ }
377
+
378
+ /* Error State */
379
+ .dashboard-error {
380
+ text-align: center;
381
+ padding: 40px 20px;
382
+ color: var(--danger-color);
383
+ }
384
+
385
+ .dashboard-error-icon {
386
+ font-size: 48px;
387
+ margin-bottom: 16px;
388
+ opacity: 0.7;
389
+ }
390
+
391
+ .dashboard-error-message {
392
+ font-size: 16px;
393
+ margin-bottom: 16px;
394
+ }
395
+
396
+ .dashboard-retry-btn {
397
+ background-color: var(--accent-color);
398
+ color: white;
399
+ border: none;
400
+ padding: 10px 20px;
401
+ border-radius: 6px;
402
+ cursor: pointer;
403
+ font-size: 14px;
404
+ transition: all 0.3s ease;
405
+ }
406
+
407
+ .dashboard-retry-btn:hover {
408
+ background-color: #0056b3;
409
+ transform: translateY(-1px);
410
+ }
411
+
412
+ /* Responsive Design for Dashboard */
413
+ @media (max-width: 768px) {
414
+ .dashboard-container {
415
+ grid-template-columns: 1fr;
416
+ gap: 20px;
417
+ margin-bottom: 30px;
418
+ }
419
+
420
+ .dashboard-card {
421
+ padding: 20px;
422
+ }
423
+
424
+ .dashboard-stats-grid {
425
+ grid-template-columns: repeat(2, 1fr);
426
+ gap: 12px;
427
+ }
428
+
429
+ .dashboard-stat-value {
430
+ font-size: 20px;
431
+ }
432
+
433
+ .dashboard-progress-items {
434
+ flex-direction: column;
435
+ gap: 12px;
436
+ }
437
+
438
+ .dashboard-card-title {
439
+ font-size: 18px;
440
+ }
441
+
442
+ .delete-controls {
443
+ flex-direction: column;
444
+ align-items: stretch;
445
+ }
446
+
447
+ .control-btn, .delete-btn {
448
+ width: 100%;
449
+ margin-bottom: 10px;
450
+ }
451
+ }
452
+
453
+ @media (max-width: 480px) {
454
+ .dashboard-stats-grid {
455
+ grid-template-columns: 1fr;
456
+ }
457
+
458
+ .dashboard-card {
459
+ padding: 16px;
460
+ }
461
+
462
+ .dashboard-stat-item {
463
+ padding: 16px;
464
+ }
465
+
466
+ .dashboard-stat-value {
467
+ font-size: 22px;
468
+ }
469
+ }
470
+
471
+ /* Navigation styles */
472
+ .navigation .nav-link:hover {
473
+ background-color: var(--accent-color) !important;
474
+ color: var(--bg-secondary) !important;
475
+ }
476
+
477
+ @media (max-width: 768px) {
478
+ .navigation {
479
+ padding: 10px;
480
+ flex-direction: column;
481
+ gap: 10px;
482
+ }
483
+
484
+ .navigation > div {
485
+ flex-direction: column;
486
+ gap: 10px;
487
+ }
488
+
489
+ .navigation .nav-link {
490
+ display: block !important;
491
+ text-align: center;
492
+ width: 100%;
493
+ }
494
+
495
+ .theme-toggle {
496
+ margin-left: 0;
497
+ align-self: center;
498
+ }
499
+ }
@@ -0,0 +1,84 @@
1
+ // anki_stats.js: Loads missing high-frequency kanji stats
2
+
3
+ document.addEventListener('DOMContentLoaded', function () {
4
+ console.log('Anki Stats JavaScript loaded!');
5
+
6
+ const loading = document.getElementById('ankiStatsLoading');
7
+ const error = document.getElementById('ankiStatsError');
8
+ const missingKanjiGrid = document.getElementById('missingKanjiGrid');
9
+ const missingKanjiCount = document.getElementById('missingKanjiCount');
10
+ const ankiTotalKanji = document.getElementById('ankiTotalKanji');
11
+ const gsmTotalKanji = document.getElementById('gsmTotalKanji');
12
+ const ankiCoverage = document.getElementById('ankiCoverage');
13
+
14
+ console.log('Found DOM elements:', {
15
+ loading, error, missingKanjiGrid, missingKanjiCount,
16
+ ankiTotalKanji, gsmTotalKanji, ankiCoverage
17
+ });
18
+
19
+ function showLoading(show) {
20
+ loading.style.display = show ? '' : 'none';
21
+ }
22
+ function showError(show) {
23
+ error.style.display = show ? '' : 'none';
24
+ }
25
+
26
+ // Initialize Kanji Grid Renderer (using shared component)
27
+ const kanjiGridRenderer = new KanjiGridRenderer({
28
+ containerSelector: '#missingKanjiGrid',
29
+ counterSelector: '#missingKanjiCount',
30
+ colorMode: 'frequency',
31
+ emptyMessage: '🎉 No missing kanji! You have all frequently used kanji in your Anki collection.'
32
+ });
33
+
34
+ // Function to render kanji grid (now using shared renderer)
35
+ function renderKanjiGrid(kanjiList) {
36
+ console.log('renderKanjiGrid called with', kanjiList.length, 'kanji');
37
+ kanjiGridRenderer.render(kanjiList);
38
+ console.log('Kanji grid rendered using shared renderer');
39
+ }
40
+
41
+ function updateStats(data) {
42
+ console.log('updateStats called with:', data);
43
+ console.log('DOM elements found:', {
44
+ ankiTotalKanji,
45
+ gsmTotalKanji,
46
+ ankiCoverage,
47
+ missingKanjiGrid,
48
+ missingKanjiCount
49
+ });
50
+
51
+ if (ankiTotalKanji) ankiTotalKanji.textContent = data.anki_kanji_count;
52
+ if (gsmTotalKanji) gsmTotalKanji.textContent = data.gsm_kanji_count;
53
+ if (ankiCoverage) {
54
+ const gsmCount = Number(data.gsm_kanji_count);
55
+ const missingCount = Array.isArray(data.missing_kanji) ? data.missing_kanji.length : 0;
56
+ let percent = 0;
57
+ if (gsmCount > 0) {
58
+ percent = ((gsmCount - missingCount) / gsmCount) * 100;
59
+ }
60
+ ankiCoverage.textContent = percent.toFixed(1) + '%';
61
+ }
62
+ renderKanjiGrid(data.missing_kanji);
63
+ }
64
+
65
+ async function loadStats() {
66
+ console.log('Loading Anki stats...');
67
+ showLoading(true);
68
+ showError(false);
69
+ try {
70
+ const resp = await fetch('/api/anki_stats');
71
+ if (!resp.ok) throw new Error('Failed to load');
72
+ const data = await resp.json();
73
+ console.log('Received data:', data);
74
+ updateStats(data);
75
+ } catch (e) {
76
+ console.error('Failed to load Anki stats:', e);
77
+ showError(true);
78
+ } finally {
79
+ showLoading(false);
80
+ }
81
+ }
82
+
83
+ loadStats();
84
+ });