GameSentenceMiner 2.19.16__py3-none-any.whl → 2.20.0__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.

Potentially problematic release.


This version of GameSentenceMiner might be problematic. Click here for more details.

Files changed (70) hide show
  1. GameSentenceMiner/__init__.py +39 -0
  2. GameSentenceMiner/anki.py +6 -3
  3. GameSentenceMiner/gametext.py +13 -2
  4. GameSentenceMiner/gsm.py +40 -3
  5. GameSentenceMiner/locales/en_us.json +4 -0
  6. GameSentenceMiner/locales/ja_jp.json +4 -0
  7. GameSentenceMiner/locales/zh_cn.json +4 -0
  8. GameSentenceMiner/obs.py +4 -1
  9. GameSentenceMiner/owocr/owocr/ocr.py +304 -134
  10. GameSentenceMiner/owocr/owocr/run.py +1 -1
  11. GameSentenceMiner/ui/anki_confirmation.py +4 -2
  12. GameSentenceMiner/ui/config_gui.py +12 -0
  13. GameSentenceMiner/util/configuration.py +6 -2
  14. GameSentenceMiner/util/cron/__init__.py +12 -0
  15. GameSentenceMiner/util/cron/daily_rollup.py +613 -0
  16. GameSentenceMiner/util/cron/jiten_update.py +397 -0
  17. GameSentenceMiner/util/cron/populate_games.py +154 -0
  18. GameSentenceMiner/util/cron/run_crons.py +148 -0
  19. GameSentenceMiner/util/cron/setup_populate_games_cron.py +118 -0
  20. GameSentenceMiner/util/cron_table.py +334 -0
  21. GameSentenceMiner/util/db.py +236 -49
  22. GameSentenceMiner/util/ffmpeg.py +23 -4
  23. GameSentenceMiner/util/games_table.py +340 -93
  24. GameSentenceMiner/util/jiten_api_client.py +188 -0
  25. GameSentenceMiner/util/stats_rollup_table.py +216 -0
  26. GameSentenceMiner/web/anki_api_endpoints.py +438 -220
  27. GameSentenceMiner/web/database_api.py +955 -1259
  28. GameSentenceMiner/web/jiten_database_api.py +1015 -0
  29. GameSentenceMiner/web/rollup_stats.py +672 -0
  30. GameSentenceMiner/web/static/css/dashboard-shared.css +75 -13
  31. GameSentenceMiner/web/static/css/overview.css +604 -47
  32. GameSentenceMiner/web/static/css/search.css +226 -0
  33. GameSentenceMiner/web/static/css/shared.css +762 -0
  34. GameSentenceMiner/web/static/css/stats.css +221 -0
  35. GameSentenceMiner/web/static/js/components/bar-chart.js +339 -0
  36. GameSentenceMiner/web/static/js/database-bulk-operations.js +320 -0
  37. GameSentenceMiner/web/static/js/database-game-data.js +390 -0
  38. GameSentenceMiner/web/static/js/database-game-operations.js +213 -0
  39. GameSentenceMiner/web/static/js/database-helpers.js +44 -0
  40. GameSentenceMiner/web/static/js/database-jiten-integration.js +750 -0
  41. GameSentenceMiner/web/static/js/database-popups.js +89 -0
  42. GameSentenceMiner/web/static/js/database-tabs.js +64 -0
  43. GameSentenceMiner/web/static/js/database-text-management.js +371 -0
  44. GameSentenceMiner/web/static/js/database.js +86 -718
  45. GameSentenceMiner/web/static/js/goals.js +79 -18
  46. GameSentenceMiner/web/static/js/heatmap.js +29 -23
  47. GameSentenceMiner/web/static/js/overview.js +1205 -339
  48. GameSentenceMiner/web/static/js/regex-patterns.js +100 -0
  49. GameSentenceMiner/web/static/js/search.js +215 -18
  50. GameSentenceMiner/web/static/js/shared.js +193 -39
  51. GameSentenceMiner/web/static/js/stats.js +1536 -179
  52. GameSentenceMiner/web/stats.py +1142 -269
  53. GameSentenceMiner/web/stats_api.py +2104 -0
  54. GameSentenceMiner/web/templates/anki_stats.html +4 -18
  55. GameSentenceMiner/web/templates/components/date-range.html +118 -3
  56. GameSentenceMiner/web/templates/components/html-head.html +40 -6
  57. GameSentenceMiner/web/templates/components/js-config.html +8 -8
  58. GameSentenceMiner/web/templates/components/regex-input.html +160 -0
  59. GameSentenceMiner/web/templates/database.html +564 -117
  60. GameSentenceMiner/web/templates/goals.html +41 -5
  61. GameSentenceMiner/web/templates/overview.html +159 -129
  62. GameSentenceMiner/web/templates/search.html +78 -9
  63. GameSentenceMiner/web/templates/stats.html +159 -5
  64. GameSentenceMiner/web/texthooking_page.py +280 -111
  65. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/METADATA +43 -2
  66. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/RECORD +70 -47
  67. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/WHEEL +0 -0
  68. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/entry_points.txt +0 -0
  69. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/licenses/LICENSE +0 -0
  70. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/top_level.txt +0 -0
@@ -24,14 +24,14 @@
24
24
  {% include 'components/navigation.html' %}
25
25
 
26
26
  <div class="management-grid">
27
- <!-- Games Management Card -->
28
- <div class="management-card games">
27
+ <!-- Manage Game Data Card -->
28
+ <div class="management-card game-data">
29
29
  <div class="card-header">
30
- <div class="card-icon">🗑️</div>
31
- <h2 class="card-title">Games Management</h2>
30
+ <div class="card-icon">🎮</div>
31
+ <h2 class="card-title">Manage Games & Data</h2>
32
32
  </div>
33
33
  <p class="card-description">
34
- Manage your game data by viewing statistics and selectively deleting games and their associated sentences.
34
+ Comprehensive game management: link to jiten.moe database, manage individual games, perform bulk operations like merging and deletion.
35
35
  </p>
36
36
  <div class="stats-row">
37
37
  <span class="stats-label">Total Games:</span>
@@ -41,30 +41,21 @@
41
41
  <span class="stats-label">Total Sentences:</span>
42
42
  <span class="stats-value" id="totalSentencesCount">Loading...</span>
43
43
  </div>
44
- <div class="card-actions">
45
- <button class="action-btn danger" data-action="openGameDeletionModal">
46
- Manage Games
47
- </button>
44
+ <div class="stats-row">
45
+ <span class="stats-label">Total Characters:</span>
46
+ <span class="stats-value" id="totalCharactersCount">Loading...</span>
48
47
  </div>
49
- </div>
50
-
51
-
52
- <!-- Text Lines Management Card -->
53
- <div class="management-card text-lines">
54
- <div class="card-header">
55
- <div class="card-icon">📄</div>
56
- <h2 class="card-title">Manage Text Lines</h2>
48
+ <div class="stats-row">
49
+ <span class="stats-label">Linked Games:</span>
50
+ <span class="stats-value" id="linkedGamesCount">Loading...</span>
57
51
  </div>
58
- <p class="card-description">
59
- Manage specific lines of text based on content matching.
60
- </p>
61
52
  <div class="stats-row">
62
- <span class="stats-label">Last Cleanup:</span>
63
- <span class="stats-value" id="lastCleanupDate">Never</span>
53
+ <span class="stats-label">Unlinked Games:</span>
54
+ <span class="stats-value" id="unlinkedGamesCount">Loading...</span>
64
55
  </div>
65
56
  <div class="card-actions">
66
- <button class="action-btn warning" data-action="openTextLinesModal">
67
- Manage Text Lines
57
+ <button class="action-btn primary" data-action="openGameDataModal">
58
+ Manage Games & Data
68
59
  </button>
69
60
  </div>
70
61
  </div>
@@ -78,10 +69,10 @@
78
69
  <p class="card-description">
79
70
  Manage duplicate sentences to improve data quality and reduce storage usage.
80
71
  </p>
81
- <div class="stats-row">
82
- <span class="stats-label">Potential Duplicates:</span>
83
- <span class="stats-value" id="duplicatesCount">Calculating...</span>
84
- </div>
72
+ <!-- <div class="stats-row"> -->
73
+ <!-- <span class="stats-label">Potential Duplicates:</span> -->
74
+ <!-- <span class="stats-value" id="duplicatesCount">Calculating...</span> -->
75
+ <!-- </div> -->
85
76
  <div class="card-actions">
86
77
  <button class="action-btn success" data-action="openDeduplicationModal">
87
78
  Manage Duplicates
@@ -91,48 +82,6 @@
91
82
  </div>
92
83
  </div>
93
84
 
94
- <!-- Games Deletion Modal (moved from stats.html) -->
95
- <div id="gamesDeletionModal" class="modal">
96
- <div class="modal-content" style="max-width: 800px;">
97
- <div class="modal-header">
98
- <h3>Manage Games</h3>
99
- <span class="close-btn" data-action="closeModal" data-modal="gamesDeletionModal">&times;</span>
100
- </div>
101
- <div class="modal-body">
102
- <p class="warning-text">Select games to delete. This will permanently remove all associated sentences and data.</p>
103
-
104
- <p class="warning-text">When Merging, the first selected game will be the target for all merged data.</p>
105
-
106
- <div style="margin-bottom: 15px; padding: 10px; background: var(--bg-tertiary); border-radius: 5px; border-left: 4px solid var(--accent-color);">
107
- <small style="color: var(--text-secondary); font-size: 13px;">
108
- <strong>Note:</strong> Game names come from the OBS Scene, to change the name for future stats, change the scene name in OBS.
109
- </small>
110
- </div>
111
-
112
- <div id="gamesLoadingIndicator" class="loading-indicator">
113
- <div class="spinner"></div>
114
- <span>Loading games...</span>
115
- </div>
116
-
117
- <div id="gamesContent" style="display: none;">
118
- <div style="margin-bottom: 20px;">
119
- <button class="action-btn primary" data-action="selectAllGames">Select All</button>
120
- <button class="action-btn primary" data-action="selectNoGames">Select None</button>
121
- </div>
122
- <div id="gamesList" style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 5px; padding: 10px;">
123
- <!-- Games will be populated here -->
124
- </div>
125
- </div>
126
- </div>
127
- <div class="modal-footer">
128
- <button class="action-btn" data-action="closeModal" data-modal="gamesDeletionModal">Cancel</button>
129
- <button class="action-btn warning" id="mergeSelectedGamesBtn" data-action="mergeSelectedGames" disabled>Merge Selected Games</button>
130
- <button class="action-btn danger" id="deleteSelectedGamesBtn" data-action="deleteSelectedGames" disabled>Delete Selected</button>
131
- </div>
132
- </div>
133
- </div>
134
-
135
-
136
85
  <!-- Text Lines Deletion Modal -->
137
86
  <div id="textLinesModal" class="modal">
138
87
  <div class="modal-content">
@@ -143,52 +92,8 @@
143
92
  <div class="modal-body">
144
93
  <p class="warning-text">Remove specific lines of text from all games using preset patterns or custom regex. This operation cannot be undone.</p>
145
94
 
146
- <div class="form-group">
147
- <label class="form-label">Preset Patterns:</label>
148
- <select id="presetPatterns" class="form-input">
149
- <option value="">Select a preset pattern...</option>
150
- <option value="lines_over_50">Lines over 50 characters</option>
151
- <option value="lines_over_100">Lines over 100 characters</option>
152
- <option value="non_japanese">Non-Japanese text</option>
153
- <option value="ascii_only">ASCII-only lines</option>
154
- <option value="empty_lines">Empty or whitespace-only lines</option>
155
- <option value="numbers_only">Lines with numbers only</option>
156
- <option value="single_char">Single character lines</option>
157
- <option value="repeated_chars">Lines with repeated characters (3+ times)</option>
158
- </select>
159
- <small style="color: var(--text-tertiary); font-size: 12px;">
160
- Select a common pattern or create a custom regex below.
161
- </small>
162
- </div>
163
-
164
- <div class="form-group">
165
- <label class="form-label">Custom Regex Pattern:</label>
166
- <input type="text" id="customRegex" class="form-input" placeholder="Enter custom regex pattern (e.g., ^.{0,10}$ for lines 10 chars or less)">
167
- <small style="color: var(--text-tertiary); font-size: 12px;">
168
- Use regex syntax. Leave empty to use exact text matching below.
169
- </small>
170
- </div>
171
-
172
- <div class="form-group">
173
- <label class="form-label">Exact Text to Delete:</label>
174
- <textarea id="textToDelete" class="form-textarea" rows="4" placeholder="Enter exact text lines to delete, one per line..."></textarea>
175
- <small style="color: var(--text-tertiary); font-size: 12px;">
176
- Each line will be treated as an exact match. Only used if no regex pattern is provided.
177
- </small>
178
- </div>
179
-
180
- <div class="form-group">
181
- <div class="checkbox-container">
182
- <input type="checkbox" id="caseSensitiveDelete" class="checkbox-input">
183
- <label for="caseSensitiveDelete" class="checkbox-label">Case sensitive matching</label>
184
- </div>
185
- </div>
186
-
187
- <div class="form-group">
188
- <div class="checkbox-container">
189
- <input type="checkbox" id="useRegexDelete" class="checkbox-input">
190
- <label for="useRegexDelete" class="checkbox-label">Treat custom pattern as regex</label>
191
- </div>
95
+ <div id="textLinesRegexComponent" data-show-exact-text="true">
96
+ {% include 'components/regex-input.html' %}
192
97
  </div>
193
98
 
194
99
  <div id="previewDeleteResults" style="display: none; background: var(--bg-tertiary); padding: 15px; border-radius: 5px; margin: 15px 0;">
@@ -291,6 +196,343 @@
291
196
  </div>
292
197
  </div>
293
198
 
199
+ <!-- Unified Game Data Management Modal -->
200
+ <div id="gameDataModal" class="modal">
201
+ <div class="modal-content" style="max-width: 95vw; max-height: 90vh; width: 95vw; height: 90vh;">
202
+ <div class="modal-header">
203
+ <h3>Manage Games & Data</h3>
204
+ <span class="close-btn" data-action="closeModal" data-modal="gameDataModal">&times;</span>
205
+ </div>
206
+
207
+ <!-- Tab Navigation -->
208
+ <div class="modal-tabs">
209
+ <button class="tab-btn active" data-tab="linkGames">🔗 Link Games</button>
210
+ <button class="tab-btn" data-tab="manageGames">⚙️ Manage Games</button>
211
+ <button class="tab-btn" data-tab="bulkOperations">📦 Bulk Operations</button>
212
+ </div>
213
+
214
+ <div class="modal-body" style="flex: 1; overflow: hidden; display: flex; flex-direction: column;">
215
+
216
+ <!-- Tab 1: Link Games -->
217
+ <div id="linkGamesTab" class="tab-content active" style="flex: 1; overflow-y: auto;">
218
+ <p class="info-text">Link your games to jiten.moe database for enhanced metadata, difficulty ratings, and cover images.</p>
219
+
220
+ <div id="gameDataLoadingIndicator" class="loading-indicator">
221
+ <div class="spinner"></div>
222
+ <span>Loading games...</span>
223
+ </div>
224
+
225
+ <div id="gameDataContent" style="display: none;">
226
+ <div class="game-data-filters" style="margin-bottom: 20px;">
227
+ <button class="action-btn primary" data-filter="all" id="filterAll">All Games</button>
228
+ <button class="action-btn" data-filter="linked" id="filterLinked">Linked</button>
229
+ <button class="action-btn" data-filter="unlinked" id="filterUnlinked">Unlinked</button>
230
+ </div>
231
+
232
+ <div id="gameDataList" style="max-height: none; overflow-y: auto;">
233
+ <!-- Games will be populated here -->
234
+ </div>
235
+ </div>
236
+ </div>
237
+
238
+ <!-- Tab 2: Manage Games -->
239
+ <div id="manageGamesTab" class="tab-content" style="flex: 1; overflow-y: auto; display: none;">
240
+ <div style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid var(--warning-color);">
241
+ <h4 style="margin: 0 0 10px 0; color: var(--warning-color);">⚠️ Important Distinctions</h4>
242
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; font-size: 14px;">
243
+ <div>
244
+ <strong style="color: var(--warning-color);">🔗 Unlink Game:</strong>
245
+ <ul style="margin: 5px 0 0 20px; color: var(--text-secondary);">
246
+ <li>Removes jiten.moe integration</li>
247
+ <li>Preserves all sentences</li>
248
+ <li>Can be re-linked later</li>
249
+ </ul>
250
+ </div>
251
+ <div>
252
+ <strong style="color: var(--danger-color);">🗑️ Delete Game Lines:</strong>
253
+ <ul style="margin: 5px 0 0 20px; color: var(--text-secondary);">
254
+ <li><strong style="color: var(--danger-color);">PERMANENTLY</strong> deletes all sentences</li>
255
+ <li><strong style="color: var(--danger-color);">CANNOT</strong> be undone</li>
256
+ <li>Game data is lost <strong style="color: var(--danger-color);">FOREVER</strong></li>
257
+ </ul>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <div id="manageGamesLoadingIndicator" class="loading-indicator">
263
+ <div class="spinner"></div>
264
+ <span>Loading games...</span>
265
+ </div>
266
+
267
+ <div id="manageGamesContent" style="display: none;">
268
+ <div id="manageGamesList" style="max-height: none; overflow-y: auto;">
269
+ <!-- Games with individual action buttons will be populated here -->
270
+ </div>
271
+ </div>
272
+ </div>
273
+
274
+ <!-- Tab 3: Bulk Operations -->
275
+ <div id="bulkOperationsTab" class="tab-content" style="flex: 1; overflow-y: auto; display: none;">
276
+ <div style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid var(--danger-color);">
277
+ <h4 style="margin: 0 0 10px 0; color: var(--danger-color);">🚨 Bulk Operations Warning</h4>
278
+ <p style="margin: 0; color: var(--text-secondary); font-size: 14px;">
279
+ These operations affect multiple games at once. <strong style="color: var(--danger-color);">Deletion operations cannot be undone.</strong>
280
+ When merging, the first selected game becomes the target for all merged data.
281
+ </p>
282
+ </div>
283
+
284
+ <div style="margin-bottom: 15px; padding: 10px; background: var(--bg-tertiary); border-radius: 5px; border-left: 4px solid var(--accent-color);">
285
+ <small style="color: var(--text-secondary); font-size: 13px;">
286
+ <strong>Note:</strong> Game names come from the OBS Scene, to change the name for future stats, change the scene name in OBS.
287
+ </small>
288
+ </div>
289
+
290
+ <div id="bulkGamesLoadingIndicator" class="loading-indicator">
291
+ <div class="spinner"></div>
292
+ <span>Loading games...</span>
293
+ </div>
294
+
295
+ <div id="bulkGamesContent" style="display: none;">
296
+ <div style="margin-bottom: 20px;">
297
+ <button class="action-btn primary" data-action="selectAllGames">Select All</button>
298
+ <button class="action-btn primary" data-action="selectNoGames">Select None</button>
299
+ </div>
300
+ <div id="bulkGamesList" style="max-height: 400px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 5px; padding: 10px;">
301
+ <!-- Games with checkboxes will be populated here -->
302
+ </div>
303
+
304
+ <div style="margin-top: 20px; display: flex; gap: 10px; justify-content: flex-end;">
305
+ <button class="action-btn warning" id="mergeSelectedGamesBtn" data-action="mergeSelectedGames" disabled>🔀 Merge Selected Games</button>
306
+ <button class="action-btn danger" id="deleteSelectedGamesBtn" data-action="deleteSelectedGames" disabled>🗑️ Delete Selected Lines FOREVER</button>
307
+ </div>
308
+ </div>
309
+ </div>
310
+
311
+ </div>
312
+
313
+ <div class="modal-footer">
314
+ <button class="action-btn" data-action="closeModal" data-modal="gameDataModal">Close</button>
315
+ </div>
316
+ </div>
317
+ </div>
318
+
319
+ <!-- Jiten.moe Search Modal -->
320
+ <div id="jitenSearchModal" class="modal">
321
+ <div class="modal-content" style="max-width: 800px;">
322
+ <div class="modal-header">
323
+ <h3>Search jiten.moe Database</h3>
324
+ <span class="close-btn" data-action="closeModal" data-modal="jitenSearchModal">&times;</span>
325
+ </div>
326
+ <div class="modal-body">
327
+ <div class="search-info" style="margin-bottom: 20px;">
328
+ <p><strong>Searching for:</strong> <span id="searchingForGame"></span></p>
329
+ </div>
330
+
331
+ <div class="form-group">
332
+ <label class="form-label">Search Term:</label>
333
+ <div style="display: flex; gap: 10px;">
334
+ <input type="text" id="jitenSearchInput" class="form-input" placeholder="Enter game title to search...">
335
+ <button class="action-btn primary" id="jitenSearchBtn">Search</button>
336
+ </div>
337
+ <small style="color: var(--text-tertiary); font-size: 12px;">
338
+ Search supports Japanese, romaji, and English titles.
339
+ </small>
340
+ </div>
341
+
342
+ <div id="jitenSearchResults" style="display: none;">
343
+ <h4>Search Results:</h4>
344
+ <div id="jitenResultsList" style="max-height: 400px; overflow-y: auto; margin-top: 15px;">
345
+ <!-- Search results will be populated here -->
346
+ </div>
347
+ </div>
348
+
349
+ <div id="jitenSearchError" class="error-text" style="display: none;"></div>
350
+ <div id="jitenSearchLoading" class="loading-indicator" style="display: none;">
351
+ <div class="spinner"></div>
352
+ <span>Searching jiten.moe...</span>
353
+ </div>
354
+
355
+ <div style="margin-top: 20px; padding: 15px; background: var(--bg-tertiary); border-radius: 8px; border-left: 4px solid var(--accent-color);">
356
+ <p style="margin: 0; color: var(--text-secondary); font-size: 14px;">
357
+ <strong>💡 Not found?</strong> Either contribute it to <a href="https://jiten.moe/" target="_blank" rel="noopener noreferrer" style="color: var(--accent-color); text-decoration: underline;">Jiten</a> or click Edit on the unlinked game to make a manual game.
358
+ </p>
359
+ </div>
360
+ </div>
361
+ <div class="modal-footer">
362
+ <button class="action-btn" data-action="closeModal" data-modal="jitenSearchModal">Cancel</button>
363
+ </div>
364
+ </div>
365
+ </div>
366
+
367
+ <!-- Game Link Confirmation Modal -->
368
+ <div id="gameLinkConfirmModal" class="modal">
369
+ <div class="modal-content" style="max-width: 700px;">
370
+ <div class="modal-header">
371
+ <h3>Confirm Game Link</h3>
372
+ <span class="close-btn" data-action="closeModal" data-modal="gameLinkConfirmModal">&times;</span>
373
+ </div>
374
+ <div class="modal-body">
375
+ <div class="link-preview">
376
+ <div class="current-game" style="margin-bottom: 20px;">
377
+ <h4>Your Game:</h4>
378
+ <div id="currentGamePreview" style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px;">
379
+ <!-- Current game info will be populated here -->
380
+ </div>
381
+ </div>
382
+
383
+ <div class="jiten-game" style="margin-bottom: 20px;">
384
+ <h4>jiten.moe Match:</h4>
385
+ <div id="jitenGamePreview" style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px;">
386
+ <!-- Jiten game info will be populated here -->
387
+ </div>
388
+ </div>
389
+
390
+ <div class="manual-overrides-warning" id="manualOverridesWarning" style="display: none; background: var(--warning-color); color: white; padding: 10px; border-radius: 5px; margin-bottom: 15px;">
391
+ <strong>⚠️ Manual Overrides Detected:</strong>
392
+ <div id="overriddenFieldsList"></div>
393
+ <small>These manually edited fields will NOT be overwritten.</small>
394
+ </div>
395
+ </div>
396
+
397
+ <div id="linkConfirmError" class="error-text" style="display: none;"></div>
398
+ <div id="linkConfirmLoading" class="loading-indicator" style="display: none;">
399
+ <div class="spinner"></div>
400
+ <span>Linking game...</span>
401
+ </div>
402
+ </div>
403
+ <div class="modal-footer">
404
+ <button class="action-btn" data-action="closeModal" data-modal="gameLinkConfirmModal">Cancel</button>
405
+ <button class="action-btn success" id="confirmLinkBtn">Link Game</button>
406
+ </div>
407
+ </div>
408
+ </div>
409
+
410
+ <!-- Edit Game Modal -->
411
+ <div id="editGameModal" class="modal">
412
+ <div class="modal-content" style="max-width: 800px;">
413
+ <div class="modal-header">
414
+ <h3>Edit Game</h3>
415
+ <span class="close-btn" data-action="closeModal" data-modal="editGameModal">&times;</span>
416
+ </div>
417
+ <div class="modal-body">
418
+ <p class="info-text">Edit game information. All changes will be marked as manual overrides and won't be overwritten by jiten.moe linking.</p>
419
+
420
+ <form id="editGameForm">
421
+ <input type="hidden" id="editGameId" value="">
422
+
423
+ <div class="form-group">
424
+ <label class="form-label">Original Title (Japanese) *</label>
425
+ <input type="text" id="editTitleOriginal" class="form-input" required>
426
+ </div>
427
+
428
+ <div class="form-group">
429
+ <label class="form-label">Romaji Title</label>
430
+ <input type="text" id="editTitleRomaji" class="form-input">
431
+ </div>
432
+
433
+ <div class="form-group">
434
+ <label class="form-label">English Title</label>
435
+ <input type="text" id="editTitleEnglish" class="form-input">
436
+ </div>
437
+
438
+ <div class="form-group">
439
+ <label class="form-label">Type</label>
440
+ <select id="editType" class="form-input">
441
+ <option value="">Select type...</option>
442
+ <option value="Visual Novel">Visual Novel</option>
443
+ <option value="Anime">Anime</option>
444
+ <option value="Manga">Manga</option>
445
+ <option value="Game">Game</option>
446
+ <option value="Light Novel">Light Novel</option>
447
+ <option value="Other">Other</option>
448
+ </select>
449
+ </div>
450
+
451
+ <div class="form-group">
452
+ <label class="form-label">Description</label>
453
+ <textarea id="editDescription" class="form-textarea" rows="4"></textarea>
454
+ </div>
455
+
456
+ <div class="form-group">
457
+ <label class="form-label">Difficulty (1-5)</label>
458
+ <input type="number" id="editDifficulty" class="form-input" min="1" max="5">
459
+ <small style="color: var(--text-tertiary); font-size: 12px;">
460
+ 1 = Easiest, 5 = Hardest
461
+ </small>
462
+ </div>
463
+
464
+ <div class="form-group">
465
+ <label class="form-label">Deck ID (jiten.moe)</label>
466
+ <input type="number" id="editDeckId" class="form-input">
467
+ <small style="color: var(--text-tertiary); font-size: 12px;">
468
+ The jiten.moe deck identifier
469
+ </small>
470
+ </div>
471
+
472
+ <div class="form-group">
473
+ <label class="form-label">Character Count</label>
474
+ <input type="number" id="editCharacterCount" class="form-input" min="0">
475
+ <small style="color: var(--text-tertiary); font-size: 12px;">
476
+ Total character count (usually auto-calculated)
477
+ </small>
478
+ </div>
479
+
480
+ <div class="form-group">
481
+ <label class="form-label">Cover Image</label>
482
+ <div id="editImagePreview" style="margin-bottom: 10px; display: none;">
483
+ <img id="editImagePreviewImg" style="max-width: 200px; max-height: 200px; border-radius: 8px; border: 1px solid var(--border-color);" alt="Cover preview">
484
+ </div>
485
+ <input type="file" id="editImageUpload" class="form-input" accept="image/png,image/jpeg,image/jpg,image/webp">
486
+ <small style="color: var(--text-tertiary); font-size: 12px;">
487
+ Upload a cover image (PNG, JPEG, or WebP - will be converted to PNG)
488
+ </small>
489
+ </div>
490
+
491
+ <div class="form-group">
492
+ <label class="form-label">Release Date</label>
493
+ <input type="date" id="editReleaseDate" class="form-input">
494
+ <small style="color: var(--text-tertiary); font-size: 12px;">
495
+ Game release date (will be marked as manual override if edited)
496
+ </small>
497
+ </div>
498
+
499
+ <div class="form-group" style="display: none;">
500
+ <label class="form-label">Links (JSON Array)</label>
501
+ <textarea id="editLinks" class="form-textarea" rows="4" placeholder='[{"linkType": 4, "url": "https://example.com", "deckId": 123}]'></textarea>
502
+ <small style="color: var(--text-tertiary); font-size: 12px;">
503
+ JSON array of link objects. Leave empty for no links.
504
+ </small>
505
+ </div>
506
+
507
+ <div class="form-group">
508
+ <label class="form-label">Links (list, converted to JSON)</label>
509
+ <textarea id="editLinksList" class="form-textarea" rows="6" placeholder="https://example.com&#10;https://vndb.org/v1234&#10;https://another-link.com"></textarea>
510
+ <small style="color: var(--text-tertiary); font-size: 12px;">
511
+ Enter one URL per line. These will be automatically converted to JSON format when saved.
512
+ </small>
513
+ </div>
514
+
515
+ <div class="form-group">
516
+ <div class="checkbox-container">
517
+ <input type="checkbox" id="editCompleted" class="checkbox-input">
518
+ <label for="editCompleted" class="checkbox-label">Mark as completed</label>
519
+ </div>
520
+ </div>
521
+ </form>
522
+
523
+ <div id="editGameError" class="error-text" style="display: none;"></div>
524
+ <div id="editGameLoading" class="loading-indicator" style="display: none;">
525
+ <div class="spinner"></div>
526
+ <span>Saving changes...</span>
527
+ </div>
528
+ </div>
529
+ <div class="modal-footer">
530
+ <button class="action-btn" data-action="closeModal" data-modal="editGameModal">Cancel</button>
531
+ <button class="action-btn success" id="saveGameEditsBtn" onclick="saveGameEdits()">Save Changes</button>
532
+ </div>
533
+ </div>
534
+ </div>
535
+
294
536
  <!-- Game Merge Confirmation Modal -->
295
537
  <div id="gameMergeModal" class="modal">
296
538
  <div class="modal-content" style="max-width: 700px;">
@@ -354,8 +596,213 @@
354
596
  </div>
355
597
  </div>
356
598
 
357
- <!-- Include shared JavaScript first (required dependency for database.js) -->
599
+ <!-- Individual Game Unlink Confirmation Modal -->
600
+ <div id="individualGameUnlinkModal" class="modal">
601
+ <div class="modal-content" style="max-width: 600px;">
602
+ <div class="modal-header">
603
+ <h3 style="color: var(--warning-color);">🔗 Unlink Game</h3>
604
+ <span class="close-btn" data-action="closeModal" data-modal="individualGameUnlinkModal">&times;</span>
605
+ </div>
606
+ <div class="modal-body">
607
+ <div style="background: var(--warning-color); color: #333; padding: 15px; border-radius: 8px; margin-bottom: 20px; text-align: center;">
608
+ <h4 style="margin: 0 0 10px 0;">⚠️ UNLINK GAME</h4>
609
+ <p style="margin: 0; font-weight: 600;">This removes jiten.moe integration but preserves all sentences</p>
610
+ </div>
611
+
612
+ <div style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; margin-bottom: 20px;">
613
+ <h4 style="margin: 0 0 10px 0; color: var(--text-primary);">Game Information:</h4>
614
+ <div class="stats-row">
615
+ <span class="stats-label">Game Name:</span>
616
+ <span class="stats-value" id="unlinkGameName">-</span>
617
+ </div>
618
+ <div class="stats-row">
619
+ <span class="stats-label">Sentences:</span>
620
+ <span class="stats-value" id="unlinkGameSentences">-</span>
621
+ </div>
622
+ <div class="stats-row">
623
+ <span class="stats-label">Characters:</span>
624
+ <span class="stats-value" id="unlinkGameCharacters">-</span>
625
+ </div>
626
+ <div class="stats-row">
627
+ <span class="stats-label">Release Date:</span>
628
+ <span class="stats-value" id="unlinkGameReleaseDate">-</span>
629
+ </div>
630
+ </div>
631
+
632
+ <div style="background: var(--bg-secondary); padding: 15px; border-radius: 8px; border: 1px solid var(--success-color);">
633
+ <h4 style="margin: 0 0 10px 0; color: var(--success-color);">✅ What will happen:</h4>
634
+ <ul style="margin: 0; padding-left: 20px; color: var(--text-secondary);">
635
+ <li>Removes jiten.moe metadata (cover image, difficulty, etc.)</li>
636
+ <li><strong style="color: var(--success-color);">All sentences are preserved</strong></li>
637
+ <li>Game can be re-linked to jiten.moe later</li>
638
+ <li>No data is permanently lost</li>
639
+ </ul>
640
+ </div>
641
+
642
+ <div id="individualUnlinkError" class="error-text" style="display: none;"></div>
643
+ <div id="individualUnlinkLoading" class="loading-indicator" style="display: none;">
644
+ <div class="spinner"></div>
645
+ <span>Unlinking game...</span>
646
+ </div>
647
+ </div>
648
+ <div class="modal-footer">
649
+ <button class="action-btn" data-action="closeModal" data-modal="individualGameUnlinkModal">Cancel</button>
650
+ <button class="action-btn warning" id="confirmIndividualUnlinkBtn">🔗 Unlink Game</button>
651
+ </div>
652
+ </div>
653
+ </div>
654
+
655
+ <!-- Individual Game Delete Lines Confirmation Modal -->
656
+ <div id="individualGameDeleteModal" class="modal">
657
+ <div class="modal-content" style="max-width: 700px;">
658
+ <div class="modal-header">
659
+ <h3 style="color: var(--danger-color);">🗑️ DELETE GAME LINES FOREVER</h3>
660
+ <span class="close-btn" data-action="closeModal" data-modal="individualGameDeleteModal">&times;</span>
661
+ </div>
662
+ <div class="modal-body">
663
+ <div style="background: var(--danger-color); color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; text-align: center; border: 3px solid #dc3545;">
664
+ <h2 style="margin: 0 0 10px 0; font-size: 24px;">🚨 DANGER ZONE 🚨</h2>
665
+ <h3 style="margin: 0 0 10px 0; font-size: 20px;">PERMANENT DELETION</h3>
666
+ <p style="margin: 0; font-weight: 600; font-size: 16px;">ALL SENTENCES WILL BE DELETED FOREVER</p>
667
+ <p style="margin: 5px 0 0 0; font-size: 14px;">THIS CANNOT BE UNDONE</p>
668
+ </div>
669
+
670
+ <div style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; margin-bottom: 20px;">
671
+ <h4 style="margin: 0 0 10px 0; color: var(--text-primary);">Game to be DELETED:</h4>
672
+ <div class="stats-row">
673
+ <span class="stats-label">Game Name:</span>
674
+ <span class="stats-value" id="deleteGameName">-</span>
675
+ </div>
676
+ <div class="stats-row">
677
+ <span class="stats-label">Sentences to DELETE:</span>
678
+ <span class="stats-value" style="color: var(--danger-color); font-weight: 700;" id="deleteGameSentences">-</span>
679
+ </div>
680
+ <div class="stats-row">
681
+ <span class="stats-label">Characters to DELETE:</span>
682
+ <span class="stats-value" style="color: var(--danger-color); font-weight: 700;" id="deleteGameCharacters">-</span>
683
+ </div>
684
+ </div>
685
+
686
+ <div style="background: var(--danger-color); color: white; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
687
+ <h4 style="margin: 0 0 10px 0;">💀 WHAT WILL BE LOST FOREVER:</h4>
688
+ <ul style="margin: 0; padding-left: 20px;">
689
+ <li><strong>ALL sentences from this game</strong></li>
690
+ <li><strong>ALL character progress data</strong></li>
691
+ <li><strong>ALL timestamps and metadata</strong></li>
692
+ <li><strong>EVERYTHING related to this game</strong></li>
693
+ </ul>
694
+ <p style="margin: 10px 0 0 0; font-weight: 700; text-align: center; font-size: 16px;">
695
+ ⚠️ THIS ACTION CANNOT BE REVERSED ⚠️
696
+ </p>
697
+ </div>
698
+
699
+ <div style="background: var(--bg-secondary); padding: 15px; border-radius: 8px; border: 2px solid var(--warning-color);">
700
+ <h4 style="margin: 0 0 10px 0; color: var(--warning-color);">💡 Alternative: Consider "Unlink" instead</h4>
701
+ <p style="margin: 0; color: var(--text-secondary);">
702
+ If you just want to remove jiten.moe integration, use "Unlink Game" instead.
703
+ This preserves all your sentences and can be reversed later.
704
+ </p>
705
+ </div>
706
+
707
+ <div id="individualDeleteError" class="error-text" style="display: none;"></div>
708
+ <div id="individualDeleteLoading" class="loading-indicator" style="display: none;">
709
+ <div class="spinner"></div>
710
+ <span>Deleting game lines...</span>
711
+ </div>
712
+ </div>
713
+ <div class="modal-footer">
714
+ <button class="action-btn" data-action="closeModal" data-modal="individualGameDeleteModal">Cancel</button>
715
+ <button class="action-btn danger" id="confirmIndividualDeleteBtn" style="background: var(--danger-color); font-weight: 700;">
716
+ 🗑️ DELETE FOREVER
717
+ </button>
718
+ </div>
719
+ </div>
720
+ </div>
721
+
722
+ <!-- Individual Game Unlink Confirmation Modal (Alternative) -->
723
+ <div id="individualGameUnlinkAltModal" class="modal">
724
+ <div class="modal-content" style="max-width: 500px;">
725
+ <div class="modal-header">
726
+ <h3>Unlink Game</h3>
727
+ <span class="close-btn" data-action="closeModal" data-modal="individualGameUnlinkAltModal">&times;</span>
728
+ </div>
729
+ <div class="modal-body">
730
+ <p class="warning-text">⚠️ You are about to unlink this game from the database.</p>
731
+
732
+ <div class="game-info" style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; margin: 15px 0;">
733
+ <h4 style="margin: 0 0 10px 0; color: var(--text-primary);" id="unlinkAltGameName">Game Name</h4>
734
+ <div class="stats-row">
735
+ <span class="stats-label">Sentences:</span>
736
+ <span class="stats-value" id="unlinkAltGameSentences">0</span>
737
+ </div>
738
+ <div class="stats-row">
739
+ <span class="stats-label">Characters:</span>
740
+ <span class="stats-value" id="unlinkAltGameCharacters">0</span>
741
+ </div>
742
+ </div>
743
+
744
+ <div style="background: var(--bg-tertiary); padding: 15px; border-radius: 5px; border-left: 4px solid var(--warning-color); margin: 15px 0;">
745
+ <p style="margin: 0; color: var(--text-secondary); font-size: 14px;">
746
+ <strong>Important:</strong> This will unlink the game but <strong>preserve all sentence data</strong>.
747
+ The sentences will become "orphaned" but can be re-linked to a game later if needed.
748
+ </p>
749
+ </div>
750
+
751
+ <div id="individualUnlinkAltError" class="error-text" style="display: none;"></div>
752
+ <div id="individualUnlinkAltLoading" class="loading-indicator" style="display: none;">
753
+ <div class="spinner"></div>
754
+ <span>Unlinking game...</span>
755
+ </div>
756
+ </div>
757
+ <div class="modal-footer">
758
+ <button class="action-btn" data-action="closeModal" data-modal="individualGameUnlinkAltModal">Cancel</button>
759
+ <button class="action-btn warning" id="confirmIndividualUnlinkAltBtn">Unlink Game</button>
760
+ </div>
761
+ </div>
762
+ </div>
763
+
764
+ <!-- Database Management Success/Error Popups -->
765
+ <div id="databaseSuccessPopup" class="dashboard-popup hidden">
766
+ <div class="dashboard-popup-content">
767
+ <div class="dashboard-popup-icon">✅</div>
768
+ <div class="dashboard-popup-message" id="databaseSuccessMessage">Operation completed successfully!</div>
769
+ <button id="closeDatabaseSuccessBtn" class="dashboard-popup-btn">OK</button>
770
+ </div>
771
+ </div>
772
+
773
+ <div id="databaseErrorPopup" class="dashboard-popup hidden">
774
+ <div class="dashboard-popup-content">
775
+ <div class="dashboard-popup-icon">❌</div>
776
+ <div class="dashboard-popup-message" id="databaseErrorMessage">An error occurred!</div>
777
+ <button id="closeDatabaseErrorBtn" class="dashboard-popup-btn">OK</button>
778
+ </div>
779
+ </div>
780
+
781
+ <div id="databaseConfirmPopup" class="dashboard-popup hidden">
782
+ <div class="dashboard-popup-content">
783
+ <div class="dashboard-popup-icon">❓</div>
784
+ <div class="dashboard-popup-message" id="databaseConfirmMessage">Are you sure?</div>
785
+ <div style="display: flex; gap: 10px; margin-top: 15px;">
786
+ <button id="databaseConfirmYesBtn" class="dashboard-popup-btn" style="background: var(--danger-color);">Yes</button>
787
+ <button id="databaseConfirmNoBtn" class="dashboard-popup-btn">Cancel</button>
788
+ </div>
789
+ </div>
790
+ </div>
791
+
792
+ <!-- Include shared JavaScript first (required dependency for all database modules) -->
358
793
  <script src="/static/js/shared.js"></script>
794
+
795
+ <!-- Load database modules in dependency order -->
796
+ <script src="/static/js/database-helpers.js"></script>
797
+ <script src="/static/js/database-popups.js"></script>
798
+ <script src="/static/js/database-tabs.js"></script>
799
+ <script src="/static/js/database-game-data.js"></script>
800
+ <script src="/static/js/database-bulk-operations.js"></script>
801
+ <script src="/static/js/database-text-management.js"></script>
802
+ <script src="/static/js/database-jiten-integration.js"></script>
803
+ <script src="/static/js/database-game-operations.js"></script>
804
+
805
+ <!-- Load main database.js last (orchestrator) -->
359
806
  <script src="/static/js/database.js"></script>
360
807
 
361
808
  </body>