Awab-Elsadig Claude Sonnet 4.6 commited on
Commit
ceeaf3d
Β·
1 Parent(s): b148513

Add dark mode toggle with system preference detection

Browse files

Toggle in the header switches between light and dark themes.
Preference is saved to localStorage and restored on reload.
Falls back to the OS color scheme on first visit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. main.py +78 -2
main.py CHANGED
@@ -167,11 +167,66 @@ _HTML = """<!DOCTYPE html>
167
  .img-modal.open { display: flex; }
168
  .img-modal img { max-width: min(1100px, 96vw); max-height: 90vh; border-radius: 8px; box-shadow: 0 16px 40px rgba(15, 23, 42, 0.45); background: white; object-fit: contain; }
169
  .img-modal-close { position: absolute; top: 14px; right: 14px; border: 1px solid #94a3b8; background: #0f172a; color: white; border-radius: 6px; font-size: 0.78rem; padding: 6px 10px; cursor: pointer; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  </style>
171
  </head>
172
  <body>
173
  <div class="container">
174
- <h1>Problem Extractor</h1>
 
 
 
 
 
 
175
  <p class="subtitle">Extract text &rarr; Gemini structures problems &rarr; review &amp; export.</p>
176
 
177
  <div id="error-box" class="error-box"></div>
@@ -311,7 +366,28 @@ _HTML = """<!DOCTYPE html>
311
  document.getElementById('pdf-ready-name').textContent = name;
312
  }
313
 
314
- window.addEventListener('DOMContentLoaded', initPdfState);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
  var KATEX_OPTS = {
317
  delimiters: [
 
167
  .img-modal.open { display: flex; }
168
  .img-modal img { max-width: min(1100px, 96vw); max-height: 90vh; border-radius: 8px; box-shadow: 0 16px 40px rgba(15, 23, 42, 0.45); background: white; object-fit: contain; }
169
  .img-modal-close { position: absolute; top: 14px; right: 14px; border: 1px solid #94a3b8; background: #0f172a; color: white; border-radius: 6px; font-size: 0.78rem; padding: 6px 10px; cursor: pointer; }
170
+ /* Dark mode toggle */
171
+ .dm-toggle { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 6px; border: 1px solid #e2e8f0; background: #f1f5f9; color: #475569; cursor: pointer; transition: background 0.15s, border-color 0.15s, color 0.15s; flex-shrink: 0; }
172
+ .dm-toggle:hover { background: #e2e8f0; color: #1e293b; }
173
+ /* Dark mode */
174
+ body.dark { background: #0f172a; color: #e2e8f0; }
175
+ body.dark .card { background: #1e293b; border-color: #334155; }
176
+ body.dark .btn-secondary { background: #1e293b; color: #94a3b8; border-color: #334155; }
177
+ body.dark .btn-secondary:hover { background: #334155; color: #e2e8f0; }
178
+ body.dark .btn-danger { background: #450a0a; color: #f87171; border-color: #7f1d1d; }
179
+ body.dark .subtitle { color: #64748b; }
180
+ body.dark .card-title { color: #64748b; }
181
+ body.dark .config-sep { color: #475569; }
182
+ body.dark #extract-status { color: #64748b; }
183
+ body.dark .config-bar input[type=number] { background: #0f172a; border-color: #334155; color: #e2e8f0; }
184
+ body.dark .pdf-picker-btn { background: #1e293b; border-color: #475569; color: #94a3b8; }
185
+ body.dark .pdf-picker-btn:hover { border-color: #2563eb; color: #93c5fd; background: #1e3a5f; }
186
+ body.dark .pdf-change-link { color: #64748b; }
187
+ body.dark .pdf-change-link:hover { color: #93c5fd; }
188
+ body.dark .info-box { background: #1e3a5f; border-color: #1d4ed8; color: #93c5fd; }
189
+ body.dark .error-box { background: #450a0a; border-color: #7f1d1d; color: #f87171; }
190
+ body.dark .step-label { color: #64748b; }
191
+ body.dark textarea.paste-area { background: #0f172a; border-color: #334155; color: #e2e8f0; }
192
+ body.dark .images-toggle { color: #64748b; }
193
+ body.dark .images-toggle:hover { color: #94a3b8; }
194
+ body.dark .filter-btn { background: #1e293b; color: #64748b; border-color: #334155; }
195
+ body.dark .filter-btn.active { background: #2563eb; color: white; border-color: #2563eb; }
196
+ body.dark .q-section-title { color: #64748b; }
197
+ body.dark .q-card { background: #1e293b; border-color: #334155; }
198
+ body.dark .q-card.active-card { border-color: #2563eb; box-shadow: 0 0 0 4px #1e3a5f; background: #172554; }
199
+ body.dark .q-num { background: #0f172a; color: #93c5fd; }
200
+ body.dark .badge-page { background: #1e3a5f; color: #93c5fd; }
201
+ body.dark .badge-warn { background: #422006; color: #fcd34d; }
202
+ body.dark .badge-ready { background: #14532d; color: #86efac; }
203
+ body.dark .badge-skip { background: #1e293b; color: #64748b; }
204
+ body.dark .q-rendered { color: #e2e8f0; }
205
+ body.dark .q-text { background: #0f172a; border-color: #334155; color: #e2e8f0; }
206
+ body.dark .q-text:focus { background: #0f172a; }
207
+ body.dark .q-notes { color: #475569; }
208
+ body.dark .exam-toggle { color: #64748b; }
209
+ body.dark .img-chip { background: #0f172a; border-color: #334155; color: #94a3b8; }
210
+ body.dark .img-chip img { border-color: #334155; }
211
+ body.dark .paste-target-pill { background: #1e3a5f; border-color: #1d4ed8; color: #93c5fd; }
212
+ body.dark .export-bar { background: #1e293b; border-color: #334155; }
213
+ body.dark .export-bar-left { color: #64748b; }
214
+ body.dark .export-bar input[type=number], body.dark .export-bar input[type=text] { background: #0f172a; border-color: #334155; color: #e2e8f0; }
215
+ body.dark .paste-hint { background: #1e3a5f; color: #93c5fd; }
216
+ body.dark .dm-toggle { background: #1e293b; border-color: #334155; color: #94a3b8; }
217
+ body.dark .dm-toggle:hover { background: #334155; color: #e2e8f0; }
218
+ body.dark .workflow-busy { color: #64748b; }
219
  </style>
220
  </head>
221
  <body>
222
  <div class="container">
223
+ <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:3px;">
224
+ <h1>Problem Extractor</h1>
225
+ <button class="dm-toggle" id="dm-toggle-btn" onclick="toggleDark()" title="Toggle dark mode" aria-label="Toggle dark mode">
226
+ <svg id="dm-icon-moon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
227
+ <svg id="dm-icon-sun" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
228
+ </button>
229
+ </div>
230
  <p class="subtitle">Extract text &rarr; Gemini structures problems &rarr; review &amp; export.</p>
231
 
232
  <div id="error-box" class="error-box"></div>
 
366
  document.getElementById('pdf-ready-name').textContent = name;
367
  }
368
 
369
+ // ── Dark mode ─────────────────────────────────────────────────────────────
370
+
371
+ function applyDark(dark) {
372
+ document.body.classList.toggle('dark', dark);
373
+ document.getElementById('dm-icon-moon').style.display = dark ? 'none' : '';
374
+ document.getElementById('dm-icon-sun').style.display = dark ? '' : 'none';
375
+ }
376
+
377
+ function toggleDark() {
378
+ var isDark = document.body.classList.contains('dark');
379
+ applyDark(!isDark);
380
+ try { localStorage.setItem('dm', !isDark ? '1' : '0'); } catch(e) {}
381
+ }
382
+
383
+ window.addEventListener('DOMContentLoaded', function() {
384
+ initPdfState();
385
+ try {
386
+ var saved = localStorage.getItem('dm');
387
+ var prefersDark = saved !== null ? saved === '1' : window.matchMedia('(prefers-color-scheme: dark)').matches;
388
+ applyDark(prefersDark);
389
+ } catch(e) {}
390
+ });
391
 
392
  var KATEX_OPTS = {
393
  delimiters: [