Archie0099 commited on
Commit
23f65ce
·
verified ·
1 Parent(s): ef3708d

Restructure layout and refresh palette

Browse files
Files changed (3) hide show
  1. static/app.js +1 -1
  2. static/index.html +140 -128
  3. static/styles.css +192 -257
static/app.js CHANGED
@@ -237,7 +237,7 @@
237
  showToast("Analysis failed (network).");
238
  } finally {
239
  suggestBtn.disabled = false;
240
- suggestBtn.textContent = "Suggest best settings";
241
  }
242
  }
243
 
 
237
  showToast("Analysis failed (network).");
238
  } finally {
239
  suggestBtn.disabled = false;
240
+ suggestBtn.textContent = "Auto-detect best settings";
241
  }
242
  }
243
 
static/index.html CHANGED
@@ -11,10 +11,10 @@
11
  var root = document.documentElement;
12
  try {
13
  var t = localStorage.getItem("theme");
14
- if (t !== "light" && t !== "dark") t = "dark";
15
  root.setAttribute("data-theme", t);
16
  } catch (e) {
17
- root.setAttribute("data-theme", "dark");
18
  }
19
  document.addEventListener("DOMContentLoaded", function () {
20
  var btn = document.getElementById("themeToggle");
@@ -60,7 +60,7 @@
60
  </header>
61
 
62
  <main class="layout">
63
- <!-- Sidebar: settings + drop zone -->
64
  <aside class="sidebar">
65
  <section class="panel">
66
  <h2 class="panel-title">Settings</h2>
@@ -82,133 +82,104 @@
82
  </select>
83
  </div>
84
 
85
- <div class="setting-group-label">Image &amp; pages</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- <div class="setting setting-row">
88
- <div>
89
- <label class="setting-label" for="preprocessToggle">Preprocessing</label>
90
- <p class="hint">Deskew, contrast-boost &amp; denoise before OCR. Best left on.</p>
91
- </div>
92
- <label class="switch">
93
- <input type="checkbox" id="preprocessToggle" checked />
94
- <span class="slider" aria-hidden="true"></span>
95
- </label>
96
- </div>
97
-
98
- <div class="setting setting-row">
99
- <div>
100
- <label class="setting-label" for="binarizeToggle">Binarize</label>
101
- <p class="hint">Force black-and-white. Helps some clean/high-contrast scans; usually leave off (grayscale is more accurate on most documents).</p>
102
- </div>
103
- <label class="switch">
104
- <input type="checkbox" id="binarizeToggle" />
105
- <span class="slider" aria-hidden="true"></span>
106
- </label>
107
- </div>
108
-
109
- <div class="setting setting-row">
110
- <div>
111
- <label class="setting-label" for="forceOcrToggle">Force OCR</label>
112
- <p class="hint">Re-read every page as an image, even pages that already have selectable text. Use only when the embedded text is wrong/garbled, or text is trapped inside images. Slower; off by default.</p>
113
- </div>
114
- <label class="switch">
115
- <input type="checkbox" id="forceOcrToggle" />
116
- <span class="slider" aria-hidden="true"></span>
117
- </label>
118
- </div>
119
 
120
- <div class="setting setting-row">
121
- <div>
122
- <label class="setting-label" for="removeHeadersToggle">Remove headers &amp; footers</label>
123
- <p class="hint">Strip repeating page headers, footers &amp; page numbers from multi-page documents (3+ pages). On by default; turn off to keep every line exactly as on the page.</p>
124
- </div>
125
- <label class="switch">
126
- <input type="checkbox" id="removeHeadersToggle" checked />
127
- <span class="slider" aria-hidden="true"></span>
128
- </label>
129
- </div>
130
-
131
- <div class="setting-group-label">OCR engine</div>
132
-
133
- <div class="setting setting-row">
134
- <div>
135
- <label class="setting-label" for="handwritingToggle">Handwriting (local) <span class="beta-tag">beta</span></label>
136
- <p class="hint">Read handwritten pages with a local model (English, ~73% on real cursive). Much slower; first use downloads a model once. For far better handwriting, use Online Gemini below instead &mdash; you don't need both.</p>
137
- </div>
138
- <label class="switch">
139
- <input type="checkbox" id="handwritingToggle" />
140
- <span class="slider" aria-hidden="true"></span>
141
- </label>
142
- </div>
143
 
144
- <div class="setting setting-row">
145
- <div>
146
- <label class="setting-label" for="onlineToggle">Online vision OCR (Gemini)</label>
147
- <p class="hint">Far higher accuracy on handwriting &amp; hard scans, via Google Gemini.
148
- <strong>Sends your page images to Google</strong> &mdash; off by default. Needs a free API key.</p>
149
- </div>
150
- <label class="switch">
151
- <input type="checkbox" id="onlineToggle" />
152
- <span class="slider" aria-hidden="true"></span>
153
- </label>
154
- </div>
155
- <div class="setting setting-sub" id="onlineRow" hidden>
156
- <label class="setting-label" for="onlineKey">Gemini API key</label>
157
- <input type="password" id="onlineKey" class="select online-input" placeholder="paste key from aistudio.google.com" autocomplete="off" spellcheck="false" />
158
- <div class="online-key-actions">
159
- <button type="button" class="btn btn-ghost btn-sm" id="onlineCheck">Check key</button>
160
- <span class="online-status" id="onlineStatus" data-role="onlineStatus"></span>
161
  </div>
162
- <label class="setting-label" for="onlineModel" style="margin-top:8px;">Model</label>
163
- <select id="onlineModel" class="select"></select>
164
- <p class="hint">Your key is stored only in this browser and sent with each upload &mdash; never saved on the server.
165
- Pages with a real text layer are still read locally (not sent).</p>
166
- </div>
167
 
168
- <div class="setting-group-label">Tools</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- <div class="setting">
171
- <button type="button" class="btn btn-ghost" id="suggestBtn">Suggest best settings</button>
172
- <p class="hint">Samples 1&ndash;2 pages of the next file you pick and recommends settings.
173
- Opt-in &middot; can take up to a minute on the CPU &middot; nothing is applied until you click Apply.</p>
174
- <input type="file" id="suggestInput" accept="application/pdf,.pdf" hidden />
175
- </div>
176
- <div class="setting" id="suggestResult" hidden>
177
- <div class="suggest-card">
178
- <p class="suggest-rationale" data-role="suggestRationale"></p>
179
- <div class="suggest-evidence" data-role="suggestEvidence"></div>
180
- <button type="button" class="btn btn-primary btn-sm" id="suggestApply">Apply recommended</button>
 
 
 
 
 
 
 
 
 
 
 
 
181
  </div>
182
- </div>
183
- </section>
184
-
185
- <section
186
- class="dropzone"
187
- id="dropzone"
188
- tabindex="0"
189
- role="button"
190
- aria-label="Drop PDF files here or click to browse">
191
- <svg viewBox="0 0 24 24" width="40" height="40" aria-hidden="true" class="dz-icon">
192
- <path d="M12 16V4m0 0l-4 4m4-4l4 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
193
- <path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
194
- </svg>
195
- <p class="dz-primary">Drop PDF files here</p>
196
- <p class="dz-secondary">or <span class="dz-link">browse</span> &middot; multiple files supported</p>
197
- <input type="file" id="fileInput" accept="application/pdf,.pdf" multiple hidden />
198
- </section>
199
-
200
- <section class="notes" aria-label="Honesty notes">
201
- <h3 class="notes-title">Good to know</h3>
202
- <ul>
203
- <li>Typed / born-digital pages are extracted instantly and exactly &mdash; no OCR.</li>
204
- <li>OCR accuracy depends on scan quality; handwriting is the hardest.</li>
205
- <li>For the best handwriting, use Online Gemini; everything else runs on your machine.</li>
206
- <li>The first local-OCR run downloads model weights once, then caches them.</li>
207
- </ul>
208
  </section>
209
  </aside>
210
 
211
- <!-- Main work area -->
212
  <section class="work">
213
  <div class="toolbar" id="toolbar" hidden>
214
  <div class="search-wrap">
@@ -220,6 +191,7 @@
220
  <span class="search-count" id="searchCount" hidden></span>
221
  </div>
222
  <div class="toolbar-actions">
 
223
  <button type="button" class="btn btn-ghost" id="copyAllBtn">Copy all</button>
224
  <div class="export-group">
225
  <button type="button" class="btn btn-ghost" data-export="txt">.txt</button>
@@ -230,21 +202,61 @@
230
  </div>
231
  </div>
232
 
 
233
  <div class="empty-state" id="emptyState">
234
- <svg viewBox="0 0 64 64" width="72" height="72" aria-hidden="true">
235
- <rect x="14" y="8" width="36" height="48" rx="4" fill="none" stroke="currentColor" stroke-width="2.5"></rect>
236
- <line x1="22" y1="22" x2="42" y2="22" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"></line>
237
- <line x1="22" y1="30" x2="42" y2="30" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"></line>
238
- <line x1="22" y1="38" x2="34" y2="38" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"></line>
239
- </svg>
240
- <h2>No documents yet</h2>
241
- <p>Drop one or more PDFs to extract their text.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  </div>
243
 
 
 
 
 
 
244
  <div id="filesContainer" class="files-container"></div>
245
  </section>
246
  </main>
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  <!-- Templates -->
249
  <template id="fileCardTemplate">
250
  <article class="file-card">
 
11
  var root = document.documentElement;
12
  try {
13
  var t = localStorage.getItem("theme");
14
+ if (t !== "light" && t !== "dark") t = "light";
15
  root.setAttribute("data-theme", t);
16
  } catch (e) {
17
+ root.setAttribute("data-theme", "light");
18
  }
19
  document.addEventListener("DOMContentLoaded", function () {
20
  var btn = document.getElementById("themeToggle");
 
60
  </header>
61
 
62
  <main class="layout">
63
+ <!-- Sidebar: settings only -->
64
  <aside class="sidebar">
65
  <section class="panel">
66
  <h2 class="panel-title">Settings</h2>
 
82
  </select>
83
  </div>
84
 
85
+ <details class="accordion">
86
+ <summary class="accordion-summary">
87
+ <span>Advanced preprocessing</span>
88
+ <svg class="accordion-chevron" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"></path></svg>
89
+ </summary>
90
+ <div class="accordion-body">
91
+ <div class="setting setting-row">
92
+ <div>
93
+ <label class="setting-label" for="preprocessToggle">Preprocessing</label>
94
+ <p class="hint">Deskew, contrast-boost &amp; denoise before OCR. Best left on.</p>
95
+ </div>
96
+ <label class="switch">
97
+ <input type="checkbox" id="preprocessToggle" checked />
98
+ <span class="slider" aria-hidden="true"></span>
99
+ </label>
100
+ </div>
101
 
102
+ <div class="setting setting-row">
103
+ <div>
104
+ <label class="setting-label" for="binarizeToggle">Binarize</label>
105
+ <p class="hint">Force black-and-white. Helps some clean/high-contrast scans; usually leave off (grayscale is more accurate on most documents).</p>
106
+ </div>
107
+ <label class="switch">
108
+ <input type="checkbox" id="binarizeToggle" />
109
+ <span class="slider" aria-hidden="true"></span>
110
+ </label>
111
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ <div class="setting setting-row">
114
+ <div>
115
+ <label class="setting-label" for="forceOcrToggle">Force OCR</label>
116
+ <p class="hint">Re-read every page as an image, even pages that already have selectable text. Use only when the embedded text is wrong/garbled, or text is trapped inside images. Slower; off by default.</p>
117
+ </div>
118
+ <label class="switch">
119
+ <input type="checkbox" id="forceOcrToggle" />
120
+ <span class="slider" aria-hidden="true"></span>
121
+ </label>
122
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ <div class="setting setting-row">
125
+ <div>
126
+ <label class="setting-label" for="removeHeadersToggle">Remove headers &amp; footers</label>
127
+ <p class="hint">Strip repeating page headers, footers &amp; page numbers from multi-page documents (3+ pages). On by default; turn off to keep every line exactly as on the page.</p>
128
+ </div>
129
+ <label class="switch">
130
+ <input type="checkbox" id="removeHeadersToggle" checked />
131
+ <span class="slider" aria-hidden="true"></span>
132
+ </label>
133
+ </div>
 
 
 
 
 
 
 
134
  </div>
135
+ </details>
 
 
 
 
136
 
137
+ <details class="accordion">
138
+ <summary class="accordion-summary">
139
+ <span>Handwriting &amp; online OCR</span>
140
+ <svg class="accordion-chevron" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"></path></svg>
141
+ </summary>
142
+ <div class="accordion-body">
143
+ <div class="setting setting-row">
144
+ <div>
145
+ <label class="setting-label" for="handwritingToggle">Handwriting (local) <span class="beta-tag">beta</span></label>
146
+ <p class="hint">Read handwritten pages with a local model (English, ~73% on real cursive). Much slower; first use downloads a model once. For far better handwriting, use Online Gemini below instead &mdash; you don't need both.</p>
147
+ </div>
148
+ <label class="switch">
149
+ <input type="checkbox" id="handwritingToggle" />
150
+ <span class="slider" aria-hidden="true"></span>
151
+ </label>
152
+ </div>
153
 
154
+ <div class="setting setting-row">
155
+ <div>
156
+ <label class="setting-label" for="onlineToggle">Online vision OCR (Gemini)</label>
157
+ <p class="hint">Far higher accuracy on handwriting &amp; hard scans, via Google Gemini.
158
+ <strong>Sends your page images to Google</strong> &mdash; off by default. Needs a free API key.</p>
159
+ </div>
160
+ <label class="switch">
161
+ <input type="checkbox" id="onlineToggle" />
162
+ <span class="slider" aria-hidden="true"></span>
163
+ </label>
164
+ </div>
165
+ <div class="setting setting-sub" id="onlineRow" hidden>
166
+ <label class="setting-label" for="onlineKey">Gemini API key</label>
167
+ <input type="password" id="onlineKey" class="select online-input" placeholder="paste key from aistudio.google.com" autocomplete="off" spellcheck="false" />
168
+ <div class="online-key-actions">
169
+ <button type="button" class="btn btn-ghost btn-sm" id="onlineCheck">Check key</button>
170
+ <span class="online-status" id="onlineStatus" data-role="onlineStatus"></span>
171
+ </div>
172
+ <label class="setting-label" for="onlineModel" style="margin-top:8px;">Model</label>
173
+ <select id="onlineModel" class="select"></select>
174
+ <p class="hint">Your key is stored only in this browser and sent with each upload &mdash; never saved on the server.
175
+ Pages with a real text layer are still read locally (not sent).</p>
176
+ </div>
177
  </div>
178
+ </details>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  </section>
180
  </aside>
181
 
182
+ <!-- Main work area: dropzone becomes the results pane after upload -->
183
  <section class="work">
184
  <div class="toolbar" id="toolbar" hidden>
185
  <div class="search-wrap">
 
191
  <span class="search-count" id="searchCount" hidden></span>
192
  </div>
193
  <div class="toolbar-actions">
194
+ <label for="fileInput" class="btn btn-ghost add-more" title="Add more PDFs">+ Add PDF</label>
195
  <button type="button" class="btn btn-ghost" id="copyAllBtn">Copy all</button>
196
  <div class="export-group">
197
  <button type="button" class="btn btn-ghost" data-export="txt">.txt</button>
 
202
  </div>
203
  </div>
204
 
205
+ <!-- Empty state == the single central dropzone + auto-detect -->
206
  <div class="empty-state" id="emptyState">
207
+ <section
208
+ class="dropzone"
209
+ id="dropzone"
210
+ tabindex="0"
211
+ role="button"
212
+ aria-label="Drop PDF files here or click to browse">
213
+ <div class="dz-icon-wrap">
214
+ <svg viewBox="0 0 24 24" width="34" height="34" aria-hidden="true" class="dz-icon">
215
+ <path d="M12 16V4m0 0l-4 4m4-4l4 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
216
+ <path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
217
+ </svg>
218
+ </div>
219
+ <p class="dz-primary">Drop your PDF here</p>
220
+ <p class="dz-secondary">or <span class="dz-link">browse files</span> &middot; multiple files supported</p>
221
+ </section>
222
+
223
+ <div class="dz-extra">
224
+ <button type="button" class="btn btn-ghost" id="suggestBtn">Auto-detect best settings</button>
225
+ <p class="hint dz-extra-hint">Samples 1&ndash;2 pages of a file and recommends settings &middot; can take up to a minute on the CPU &middot; nothing is applied until you click Apply.</p>
226
+ <div class="setting" id="suggestResult" hidden>
227
+ <div class="suggest-card">
228
+ <p class="suggest-rationale" data-role="suggestRationale"></p>
229
+ <div class="suggest-evidence" data-role="suggestEvidence"></div>
230
+ <button type="button" class="btn btn-primary btn-sm" id="suggestApply">Apply recommended</button>
231
+ </div>
232
+ </div>
233
+ </div>
234
  </div>
235
 
236
+ <!-- File pickers live here (always present, hidden) so the dropzone and the
237
+ toolbar "Add PDF" label can both trigger them regardless of view state. -->
238
+ <input type="file" id="fileInput" accept="application/pdf,.pdf" multiple hidden />
239
+ <input type="file" id="suggestInput" accept="application/pdf,.pdf" hidden />
240
+
241
  <div id="filesContainer" class="files-container"></div>
242
  </section>
243
  </main>
244
 
245
+ <footer class="app-footer">
246
+ <details class="footer-tips">
247
+ <summary>
248
+ <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4M12 8h.01"></path></svg>
249
+ Good to know
250
+ </summary>
251
+ <ul class="footer-tips-list">
252
+ <li>Typed / born-digital pages are extracted instantly and exactly &mdash; no OCR.</li>
253
+ <li>OCR accuracy depends on scan quality; handwriting is the hardest.</li>
254
+ <li>For the best handwriting, use Online Gemini; everything else runs on your machine.</li>
255
+ <li>The first local-OCR run downloads model weights once, then caches them.</li>
256
+ </ul>
257
+ </details>
258
+ </footer>
259
+
260
  <!-- Templates -->
261
  <template id="fileCardTemplate">
262
  <article class="file-card">
static/styles.css CHANGED
@@ -1,12 +1,11 @@
1
  /* ============================================================
2
  PDF Extractor - UI styles
3
- Two themes (dark default, light) driven by html[data-theme].
4
- Component class names are unchanged so the app behaviour and
5
- all JS hooks keep working; only the styling is new.
6
  ============================================================ */
7
 
8
  :root {
9
- /* shape + type (shared across themes) */
10
  --radius-lg: 18px;
11
  --radius: 13px;
12
  --radius-sm: 9px;
@@ -14,97 +13,95 @@
14
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
15
  --t-fast: 0.14s ease;
16
  --t: 0.22s cubic-bezier(0.2, 0.6, 0.2, 1);
17
-
18
- /* accent contrast text is white in both themes */
19
  --accent-contrast: #ffffff;
20
  }
21
 
22
- /* ---------------- DARK (default) ---------------- */
23
- html[data-theme="dark"] {
24
- --bg: #0a0d14;
25
- --bg-glow-1: rgba(118, 131, 255, 0.13);
26
- --bg-glow-2: rgba(38, 199, 154, 0.07);
27
-
28
- --surface: #141a26;
29
- --surface-2: #1c2433;
30
- --surface-3: #0f1521;
31
- --surface-raise: #1f2838;
32
-
33
- --border: #28313f;
34
- --border-soft: #1d2532;
35
- --border-strong: #36425380;
36
-
37
- --text: #e9eef6;
38
- --text-dim: #9eabbf;
39
- --text-faint: #66718390;
40
-
41
- --accent: #7e8cff;
42
- --accent-strong: #6b78f7;
43
- --accent-soft: rgba(126, 140, 255, 0.16);
44
- --accent-ring: rgba(126, 140, 255, 0.45);
45
-
46
- --green: #34d39e;
47
- --green-soft: rgba(52, 211, 158, 0.15);
48
- --green-line: rgba(52, 211, 158, 0.38);
49
- --amber: #f5b13d;
50
- --amber-soft: rgba(245, 177, 61, 0.15);
51
- --amber-line: rgba(245, 177, 61, 0.4);
52
- --red: #ff6171;
53
- --red-soft: rgba(255, 97, 113, 0.14);
54
- --red-line: rgba(255, 97, 113, 0.4);
55
- --violet: #b98cff;
56
- --cyan: #54c8f7;
57
-
58
- --shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.7);
59
- --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.35);
60
- --mark: #f5b13d;
61
- --mark-active: #ffd86b;
62
- --mark-text: #1a1205;
63
- }
64
-
65
- /* ---------------- LIGHT ---------------- */
66
  html[data-theme="light"] {
67
- --bg: #f4f6fb;
68
- --bg-glow-1: rgba(91, 102, 235, 0.08);
69
- --bg-glow-2: rgba(20, 175, 128, 0.06);
70
 
71
  --surface: #ffffff;
72
  --surface-2: #ffffff;
73
- --surface-3: #f1f4f9;
74
  --surface-raise: #ffffff;
75
 
76
- --border: #e3e8f0;
77
- --border-soft: #edf0f6;
78
- --border-strong: #d3dae6;
79
-
80
- --text: #18202e;
81
- --text-dim: #586377;
82
- --text-faint: #8a95a8;
83
-
84
- --accent: #5b63e6;
85
- --accent-strong: #4a52da;
86
- --accent-soft: rgba(91, 99, 230, 0.1);
87
- --accent-ring: rgba(91, 99, 230, 0.35);
88
-
89
- --green: #12a877;
90
- --green-soft: rgba(18, 168, 119, 0.12);
91
- --green-line: rgba(18, 168, 119, 0.32);
92
- --amber: #c77d18;
93
- --amber-soft: rgba(199, 125, 24, 0.12);
94
- --amber-line: rgba(199, 125, 24, 0.32);
 
95
  --red: #e0455c;
96
  --red-soft: rgba(224, 69, 92, 0.1);
97
  --red-line: rgba(224, 69, 92, 0.32);
98
- --violet: #8b5cf6;
99
- --cyan: #0ea5d9;
100
 
101
- --shadow: 0 16px 40px -20px rgba(28, 40, 66, 0.28);
102
- --shadow-sm: 0 1px 2px rgba(28, 40, 66, 0.06), 0 2px 6px rgba(28, 40, 66, 0.05);
103
  --mark: #ffd84d;
104
  --mark-active: #ffc21f;
105
  --mark-text: #3a2c00;
106
  }
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  * { box-sizing: border-box; }
109
 
110
  html, body {
@@ -132,7 +129,6 @@ body {
132
  }
133
 
134
  h1, h2, h3 { margin: 0; font-weight: 680; letter-spacing: -0.015em; }
135
-
136
  ::selection { background: var(--accent-soft); }
137
 
138
  /* ---------------- Header ---------------- */
@@ -159,7 +155,7 @@ h1, h2, h3 { margin: 0; font-weight: 680; letter-spacing: -0.015em; }
159
  width: 42px;
160
  height: 42px;
161
  border-radius: 12px;
162
- background: linear-gradient(145deg, var(--accent), var(--accent-strong));
163
  box-shadow: 0 6px 18px -6px var(--accent-ring), inset 0 1px 0 rgba(255, 255, 255, 0.25);
164
  }
165
  .brand-mark svg { width: 24px; height: 24px; }
@@ -192,13 +188,9 @@ html[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
192
  html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
193
 
194
  .offline-pill {
195
- font-size: 12px;
196
- font-weight: 600;
197
- color: var(--green);
198
- background: var(--green-soft);
199
- border: 1px solid var(--green-line);
200
- padding: 5px 12px;
201
- border-radius: 999px;
202
  }
203
 
204
  /* ---------------- Layout ---------------- */
@@ -221,14 +213,12 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
221
  border-radius: var(--radius-lg);
222
  padding: 20px;
223
  box-shadow: var(--shadow);
 
 
224
  }
225
  .panel-title {
226
- font-size: 12px;
227
- font-weight: 700;
228
- text-transform: uppercase;
229
- letter-spacing: 0.08em;
230
- color: var(--text-dim);
231
- margin-bottom: 16px;
232
  }
233
 
234
  .setting { margin-bottom: 18px; }
@@ -239,6 +229,24 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
239
  .setting-row .setting-label { margin-bottom: 3px; }
240
  .hint { margin: 5px 0 0; font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  .segmented {
243
  display: flex;
244
  background: var(--surface-3);
@@ -248,16 +256,9 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
248
  gap: 4px;
249
  }
250
  .seg {
251
- flex: 1;
252
- border: none;
253
- background: transparent;
254
- color: var(--text-dim);
255
- font: inherit;
256
- font-size: 13px;
257
- font-weight: 650;
258
- padding: 8px 8px;
259
- border-radius: 8px;
260
- cursor: pointer;
261
  transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
262
  }
263
  .seg:hover { color: var(--text); }
@@ -268,15 +269,9 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
268
  }
269
 
270
  .select {
271
- width: 100%;
272
- background: var(--surface-3);
273
- border: 1px solid var(--border);
274
- color: var(--text);
275
- font: inherit;
276
- font-size: 14px;
277
- padding: 10px 12px;
278
- border-radius: var(--radius-sm);
279
- cursor: pointer;
280
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
281
  }
282
  .select:hover { border-color: var(--border-strong); }
@@ -286,89 +281,36 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
286
  .switch { position: relative; display: inline-block; width: 46px; height: 26px; flex: none; }
287
  .switch input { opacity: 0; width: 0; height: 0; }
288
  .slider {
289
- position: absolute; inset: 0; cursor: pointer;
290
- background: var(--border); border-radius: 999px;
291
- transition: background var(--t);
292
- border: 1px solid var(--border-strong);
293
  }
294
  .slider::before {
295
  content: ""; position: absolute; height: 20px; width: 20px; left: 2px; top: 2px;
296
- background: #fff; border-radius: 50%;
297
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
298
  transition: transform var(--t);
299
  }
300
  .switch input:checked + .slider {
301
- background: linear-gradient(145deg, var(--accent), var(--accent-strong));
302
- border-color: transparent;
303
  }
304
  .switch input:checked + .slider::before { transform: translateX(20px); }
305
  .switch input:focus-visible + .slider { box-shadow: 0 0 0 3px var(--accent-ring); }
306
 
307
- /* dropzone */
308
- .dropzone {
309
- border: 1.5px dashed var(--border-strong);
310
- border-radius: var(--radius-lg);
311
- padding: 32px 18px;
312
- text-align: center;
313
- cursor: pointer;
314
- transition: border-color var(--t), background var(--t), transform var(--t-fast);
315
- color: var(--text-dim);
316
- background:
317
- radial-gradient(120% 120% at 50% 0%, var(--accent-soft), transparent 70%),
318
- var(--surface);
319
- box-shadow: var(--shadow);
320
- }
321
- .dropzone:hover { border-color: var(--accent); }
322
- .dropzone:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
323
- .dropzone.dragover { border-color: var(--accent); background: var(--accent-soft); transform: scale(1.012); }
324
- .dz-icon { color: var(--accent); margin-bottom: 10px; }
325
- .dz-primary { margin: 0; font-weight: 650; color: var(--text); font-size: 15px; }
326
- .dz-secondary { margin: 6px 0 0; font-size: 13px; color: var(--text-dim); }
327
- .dz-link { color: var(--accent); font-weight: 600; }
328
-
329
- /* notes */
330
- .notes {
331
- background: var(--surface);
332
- border: 1px solid var(--border);
333
- border-radius: var(--radius-lg);
334
- padding: 18px 20px;
335
- box-shadow: var(--shadow);
336
- }
337
- .notes-title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-dim); margin-bottom: 12px; }
338
- .notes ul { margin: 0; padding-left: 18px; }
339
- .notes li { font-size: 12px; color: var(--text-faint); margin-bottom: 7px; line-height: 1.55; }
340
- .notes li::marker { color: var(--accent); }
341
-
342
  /* ---------------- Work area ---------------- */
343
  .work { min-width: 0; display: flex; flex-direction: column; gap: 18px; }
344
 
345
  .toolbar {
346
- display: flex;
347
- align-items: center;
348
- gap: 14px;
349
- flex-wrap: wrap;
350
  background: color-mix(in srgb, var(--surface) 88%, transparent);
351
- backdrop-filter: blur(12px);
352
- -webkit-backdrop-filter: blur(12px);
353
- border: 1px solid var(--border);
354
- border-radius: var(--radius);
355
- padding: 12px 14px;
356
- position: sticky;
357
- top: 86px;
358
- z-index: 10;
359
  box-shadow: var(--shadow);
360
  }
361
  .search-wrap { position: relative; flex: 1; min-width: 220px; display: flex; align-items: center; }
362
  .search-icon { position: absolute; left: 12px; color: var(--text-faint); pointer-events: none; }
363
  .search-input {
364
- width: 100%;
365
- background: var(--surface-3);
366
- border: 1px solid var(--border);
367
- color: var(--text);
368
- font: inherit;
369
- font-size: 14px;
370
- padding: 10px 12px 10px 36px;
371
- border-radius: var(--radius-sm);
372
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
373
  }
374
  .search-input::placeholder { color: var(--text-faint); }
@@ -376,18 +318,13 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
376
  .search-count { position: absolute; right: 12px; font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
377
  .toolbar-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
378
  .export-group { display: flex; gap: 3px; background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 3px; }
 
379
 
380
  /* buttons */
381
  .btn {
382
- border: 1px solid var(--border);
383
- background: var(--surface-2);
384
- color: var(--text);
385
- font: inherit;
386
- font-size: 13px;
387
- font-weight: 650;
388
- padding: 9px 14px;
389
- border-radius: var(--radius-sm);
390
- cursor: pointer;
391
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast), opacity var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
392
  box-shadow: var(--shadow-sm);
393
  }
@@ -404,31 +341,45 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
404
  /* The HTML `hidden` attribute must always win over display rules below. */
405
  [hidden] { display: none !important; }
406
 
407
- /* empty state */
408
- .empty-state {
409
- display: flex;
410
- flex-direction: column;
411
- align-items: center;
412
- text-align: center;
413
- padding: 84px 24px;
414
- color: var(--text-faint);
415
  border: 1.5px dashed var(--border-strong);
416
  border-radius: var(--radius-lg);
417
- background: var(--surface);
 
 
 
 
 
 
 
418
  box-shadow: var(--shadow);
419
  }
420
- .empty-state svg { color: var(--accent); opacity: 0.55; margin-bottom: 18px; }
421
- .empty-state h2 { font-size: 18px; color: var(--text-dim); margin-bottom: 6px; }
422
- .empty-state p { margin: 0; max-width: 360px; font-size: 14px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
 
424
  /* files */
425
  .files-container { display: flex; flex-direction: column; gap: 18px; }
426
  .file-card {
427
- background: var(--surface);
428
- border: 1px solid var(--border);
429
- border-radius: var(--radius-lg);
430
- overflow: hidden;
431
- box-shadow: var(--shadow);
432
  }
433
  .file-head {
434
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
@@ -451,23 +402,16 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
451
  .progress-label { font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; flex: none; }
452
 
453
  .file-error {
454
- margin: 0 18px 15px;
455
- padding: 11px 13px;
456
- background: var(--red-soft);
457
- border: 1px solid var(--red-line);
458
- border-radius: var(--radius-sm);
459
- color: var(--red);
460
- font-size: 13px;
461
  }
462
 
463
  .pages { display: flex; flex-direction: column; gap: 13px; padding: 5px 18px 18px; }
464
 
465
  /* page card */
466
  .page-card {
467
- border: 1px solid var(--border-soft);
468
- border-radius: var(--radius);
469
- background: var(--surface-3);
470
- overflow: hidden;
471
  }
472
  .page-head { display: flex; align-items: center; gap: 10px; padding: 10px 13px; border-bottom: 1px solid var(--border-soft); }
473
  .page-num { font-size: 13px; font-weight: 700; color: var(--text-dim); }
@@ -476,35 +420,24 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
476
  .badge.text { color: var(--green); background: var(--green-soft); border: 1px solid var(--green-line); }
477
  .badge.ocr { color: var(--amber); background: var(--amber-soft); border: 1px solid var(--amber-line); }
478
  .badge.ocr.lowconf { color: var(--red); background: var(--red-soft); border-color: var(--red-line); }
479
- .badge.handwriting { color: var(--violet); background: color-mix(in srgb, var(--violet) 16%, transparent); border: 1px solid color-mix(in srgb, var(--violet) 40%, transparent); }
480
- .badge.online { color: var(--cyan); background: color-mix(in srgb, var(--cyan) 15%, transparent); border: 1px solid color-mix(in srgb, var(--cyan) 40%, transparent); }
481
  .beta-tag { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); background: var(--accent-soft); padding: 1px 5px; border-radius: 999px; vertical-align: middle; }
482
  .badge.error { color: var(--red); background: var(--red-soft); border: 1px solid var(--red-line); }
483
  .badge.pending { color: var(--text-faint); background: var(--surface-2); border: 1px solid var(--border); }
484
 
485
  .page-text {
486
- margin: 0;
487
- padding: 15px 17px;
488
- white-space: pre-wrap;
489
- word-break: break-word;
490
- font-family: var(--mono);
491
- font-size: 13px;
492
- line-height: 1.65;
493
- color: var(--text);
494
- max-height: 460px;
495
- overflow: auto;
496
  }
497
  .page-text:empty::before { content: "(no text on this page)"; color: var(--text-faint); font-style: italic; }
498
  .page-text mark { background: var(--mark); color: var(--mark-text); border-radius: 3px; padding: 0 2px; }
499
  .page-text mark.active { background: var(--mark-active); outline: 1.5px solid var(--amber); }
500
 
501
- /* Low-confidence line highlight ("which lines to check") */
502
  .lc-line { background: var(--amber-soft); border-bottom: 1px dashed var(--amber-line); border-radius: 3px; }
503
-
504
- /* "N to check" chip in the page header */
505
  .to-check { font-size: 11px; font-weight: 600; color: var(--amber); background: var(--amber-soft); border: 1px solid var(--amber-line); border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
506
 
507
- /* Inline edit box - matches the read-only text view */
508
  .page-edit {
509
  width: 100%; box-sizing: border-box; margin: 0;
510
  padding: 15px 17px; min-height: 180px; max-height: 460px; resize: vertical;
@@ -512,8 +445,7 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
512
  font-family: var(--mono); font-size: 13px; line-height: 1.65;
513
  color: var(--text); background: var(--surface-3);
514
  border: 1px solid var(--accent); border-radius: 0 0 var(--radius) var(--radius);
515
- outline: none;
516
- box-shadow: inset 0 0 0 3px var(--accent-soft);
517
  }
518
 
519
  .page-image-wrap { padding: 13px 17px; border-top: 1px solid var(--border-soft); background: var(--surface); }
@@ -522,36 +454,46 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
522
 
523
  /* toast */
524
  .toast {
525
- position: fixed;
526
- bottom: 26px;
527
- left: 50%;
528
- transform: translateX(-50%) translateY(12px);
529
- background: var(--surface-raise);
530
- border: 1px solid var(--border);
531
- color: var(--text);
532
- padding: 12px 20px;
533
- border-radius: 999px;
534
- font-size: 13.5px;
535
- font-weight: 600;
536
- box-shadow: var(--shadow);
537
- opacity: 0;
538
- transition: opacity var(--t), transform var(--t);
539
- z-index: 50;
540
- pointer-events: none;
541
  }
542
  .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  /* responsive */
545
  @media (max-width: 900px) {
546
  .layout { grid-template-columns: 1fr; padding: 16px; gap: 18px; }
 
547
  .toolbar { position: static; }
548
  .sidebar { order: 2; }
549
  .work { order: 1; }
 
550
  }
551
  @media (max-width: 520px) {
552
  .app-header { padding: 12px 16px; }
553
  .brand-text h1 { font-size: 17px; }
554
  .toolbar-actions { width: 100%; justify-content: space-between; }
 
555
  }
556
 
557
  /* scrollbars */
@@ -560,36 +502,29 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
560
  .page-text::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
561
  .page-text::-webkit-scrollbar-track { background: transparent; }
562
 
563
- /* Large-handwriting / online sub-setting: indented under its toggle */
564
  .setting-sub { margin-left: 4px; padding-left: 13px; border-left: 2px solid var(--accent-soft); }
565
 
566
- /* "Suggest best settings" recommendation panel */
567
  .btn-primary { background: linear-gradient(145deg, var(--accent), var(--accent-strong)); color: var(--accent-contrast); border-color: transparent; box-shadow: 0 6px 16px -6px var(--accent-ring); }
568
  .btn-primary:hover { filter: brightness(1.06); border-color: transparent; }
569
- .suggest-card { margin-top: 6px; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-3); }
570
  .suggest-rationale { font-size: 12px; line-height: 1.5; margin: 0 0 8px; color: var(--text-dim); }
571
  .suggest-evidence { font-size: 11px; font-family: var(--mono); margin-bottom: 8px; }
572
  .ev-row { display: grid; grid-template-columns: 1.5fr 1fr 0.9fr 0.9fr; gap: 6px; padding: 2px 0; opacity: 0.72; }
573
  .ev-row.best { opacity: 1; font-weight: 650; color: var(--text); }
574
 
575
- /* Online (Gemini) settings */
576
  .online-input { width: 100%; margin-bottom: 6px; font-family: var(--mono); font-size: 12px; }
577
  .online-key-actions { display: flex; align-items: center; gap: 8px; }
578
  .online-status { font-size: 11px; }
579
  .online-status.good { color: var(--green); }
580
  .online-status.bad { color: var(--red); }
581
 
582
- /* Remove (x) button on a file card */
583
  .btn-remove { color: var(--text-dim); font-size: 18px; line-height: 1; padding: 3px 9px; font-weight: 700; box-shadow: none; border-color: transparent; background: transparent; }
584
  .btn-remove:hover { color: var(--red); background: var(--red-soft); transform: none; }
585
 
586
- /* Settings group sub-headings */
587
- .setting-group-label {
588
- font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.09em;
589
- color: var(--text-faint); margin: 20px 0 6px; padding-top: 16px;
590
- border-top: 1px solid var(--border-soft);
591
- }
592
-
593
  @media (prefers-reduced-motion: reduce) {
594
  * { transition: none !important; animation: none !important; }
595
  }
 
1
  /* ============================================================
2
  PDF Extractor - UI styles
3
+ Two themes (light default, dark) driven by html[data-theme].
4
+ Neutral surfaces + a violet accent. Component class names are
5
+ unchanged so all JS hooks keep working; only the styling is new.
6
  ============================================================ */
7
 
8
  :root {
 
9
  --radius-lg: 18px;
10
  --radius: 13px;
11
  --radius-sm: 9px;
 
13
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
14
  --t-fast: 0.14s ease;
15
  --t: 0.22s cubic-bezier(0.2, 0.6, 0.2, 1);
 
 
16
  --accent-contrast: #ffffff;
17
  }
18
 
19
+ /* ---------------- LIGHT (default) ---------------- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  html[data-theme="light"] {
21
+ --bg: #f3f3f7;
22
+ --bg-glow-1: rgba(124, 58, 237, 0.08);
23
+ --bg-glow-2: rgba(217, 70, 239, 0.05);
24
 
25
  --surface: #ffffff;
26
  --surface-2: #ffffff;
27
+ --surface-3: #f2f2f6;
28
  --surface-raise: #ffffff;
29
 
30
+ --border: #e5e5ec;
31
+ --border-soft: #eeeef3;
32
+ --border-strong: #d6d6df;
33
+
34
+ --text: #1a1a24;
35
+ --text-dim: #585866;
36
+ --text-faint: #8a8a99;
37
+
38
+ --accent: #7c3aed;
39
+ --accent-strong: #6d28d9;
40
+ --accent-2: #9333ea;
41
+ --accent-soft: rgba(124, 58, 237, 0.1);
42
+ --accent-ring: rgba(124, 58, 237, 0.3);
43
+
44
+ --green: #0e9f6e;
45
+ --green-soft: rgba(14, 159, 110, 0.12);
46
+ --green-line: rgba(14, 159, 110, 0.32);
47
+ --amber: #c2790f;
48
+ --amber-soft: rgba(194, 121, 15, 0.12);
49
+ --amber-line: rgba(194, 121, 15, 0.32);
50
  --red: #e0455c;
51
  --red-soft: rgba(224, 69, 92, 0.1);
52
  --red-line: rgba(224, 69, 92, 0.32);
53
+ --teal: #0ea5b7;
 
54
 
55
+ --shadow: 0 16px 40px -22px rgba(30, 25, 50, 0.26);
56
+ --shadow-sm: 0 1px 2px rgba(30, 25, 50, 0.06), 0 2px 6px rgba(30, 25, 50, 0.05);
57
  --mark: #ffd84d;
58
  --mark-active: #ffc21f;
59
  --mark-text: #3a2c00;
60
  }
61
 
62
+ /* ---------------- DARK ---------------- */
63
+ html[data-theme="dark"] {
64
+ --bg: #0c0c11;
65
+ --bg-glow-1: rgba(139, 92, 246, 0.14);
66
+ --bg-glow-2: rgba(217, 70, 239, 0.07);
67
+
68
+ --surface: #16161e;
69
+ --surface-2: #20202b;
70
+ --surface-3: #111117;
71
+ --surface-raise: #24242f;
72
+
73
+ --border: #2a2a36;
74
+ --border-soft: #1f1f29;
75
+ --border-strong: #3a3a48;
76
+
77
+ --text: #ededf3;
78
+ --text-dim: #a2a2b2;
79
+ --text-faint: #6a6a79;
80
+
81
+ --accent: #8b5cf6;
82
+ --accent-strong: #7c3aed;
83
+ --accent-2: #a855f7;
84
+ --accent-soft: rgba(139, 92, 246, 0.18);
85
+ --accent-ring: rgba(139, 92, 246, 0.45);
86
+
87
+ --green: #34d399;
88
+ --green-soft: rgba(52, 211, 153, 0.15);
89
+ --green-line: rgba(52, 211, 153, 0.38);
90
+ --amber: #fbbf24;
91
+ --amber-soft: rgba(251, 191, 36, 0.15);
92
+ --amber-line: rgba(251, 191, 36, 0.4);
93
+ --red: #fb7185;
94
+ --red-soft: rgba(251, 113, 133, 0.14);
95
+ --red-line: rgba(251, 113, 133, 0.4);
96
+ --teal: #2dd4bf;
97
+
98
+ --shadow: 0 18px 44px -20px rgba(0, 0, 0, 0.72);
99
+ --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.35);
100
+ --mark: #fbbf24;
101
+ --mark-active: #ffd86b;
102
+ --mark-text: #1a1205;
103
+ }
104
+
105
  * { box-sizing: border-box; }
106
 
107
  html, body {
 
129
  }
130
 
131
  h1, h2, h3 { margin: 0; font-weight: 680; letter-spacing: -0.015em; }
 
132
  ::selection { background: var(--accent-soft); }
133
 
134
  /* ---------------- Header ---------------- */
 
155
  width: 42px;
156
  height: 42px;
157
  border-radius: 12px;
158
+ background: linear-gradient(145deg, var(--accent), var(--accent-2));
159
  box-shadow: 0 6px 18px -6px var(--accent-ring), inset 0 1px 0 rgba(255, 255, 255, 0.25);
160
  }
161
  .brand-mark svg { width: 24px; height: 24px; }
 
188
  html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
189
 
190
  .offline-pill {
191
+ font-size: 12px; font-weight: 600; color: var(--green);
192
+ background: var(--green-soft); border: 1px solid var(--green-line);
193
+ padding: 5px 12px; border-radius: 999px;
 
 
 
 
194
  }
195
 
196
  /* ---------------- Layout ---------------- */
 
213
  border-radius: var(--radius-lg);
214
  padding: 20px;
215
  box-shadow: var(--shadow);
216
+ position: sticky;
217
+ top: 90px;
218
  }
219
  .panel-title {
220
+ font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
221
+ color: var(--text-dim); margin-bottom: 16px;
 
 
 
 
222
  }
223
 
224
  .setting { margin-bottom: 18px; }
 
229
  .setting-row .setting-label { margin-bottom: 3px; }
230
  .hint { margin: 5px 0 0; font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
231
 
232
+ /* accordions (native <details>) */
233
+ .accordion { border-top: 1px solid var(--border-soft); }
234
+ .accordion + .accordion { margin-top: 0; }
235
+ .accordion-summary {
236
+ list-style: none;
237
+ cursor: pointer;
238
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
239
+ padding: 15px 2px;
240
+ font-size: 13px; font-weight: 650; color: var(--text);
241
+ user-select: none;
242
+ }
243
+ .accordion-summary::-webkit-details-marker { display: none; }
244
+ .accordion-summary:hover { color: var(--accent); }
245
+ .accordion-chevron { color: var(--text-faint); transition: transform var(--t); flex: none; }
246
+ .accordion[open] > .accordion-summary .accordion-chevron { transform: rotate(180deg); }
247
+ .accordion-body { padding: 2px 0 8px; }
248
+ .accordion-body .setting:last-child { margin-bottom: 0; }
249
+
250
  .segmented {
251
  display: flex;
252
  background: var(--surface-3);
 
256
  gap: 4px;
257
  }
258
  .seg {
259
+ flex: 1; border: none; background: transparent; color: var(--text-dim);
260
+ font: inherit; font-size: 13px; font-weight: 650; padding: 8px;
261
+ border-radius: 8px; cursor: pointer;
 
 
 
 
 
 
 
262
  transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
263
  }
264
  .seg:hover { color: var(--text); }
 
269
  }
270
 
271
  .select {
272
+ width: 100%; background: var(--surface-3); border: 1px solid var(--border);
273
+ color: var(--text); font: inherit; font-size: 14px; padding: 10px 12px;
274
+ border-radius: var(--radius-sm); cursor: pointer;
 
 
 
 
 
 
275
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
276
  }
277
  .select:hover { border-color: var(--border-strong); }
 
281
  .switch { position: relative; display: inline-block; width: 46px; height: 26px; flex: none; }
282
  .switch input { opacity: 0; width: 0; height: 0; }
283
  .slider {
284
+ position: absolute; inset: 0; cursor: pointer; background: var(--border);
285
+ border-radius: 999px; transition: background var(--t); border: 1px solid var(--border-strong);
 
 
286
  }
287
  .slider::before {
288
  content: ""; position: absolute; height: 20px; width: 20px; left: 2px; top: 2px;
289
+ background: #fff; border-radius: 50%; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
 
290
  transition: transform var(--t);
291
  }
292
  .switch input:checked + .slider {
293
+ background: linear-gradient(145deg, var(--accent), var(--accent-strong)); border-color: transparent;
 
294
  }
295
  .switch input:checked + .slider::before { transform: translateX(20px); }
296
  .switch input:focus-visible + .slider { box-shadow: 0 0 0 3px var(--accent-ring); }
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  /* ---------------- Work area ---------------- */
299
  .work { min-width: 0; display: flex; flex-direction: column; gap: 18px; }
300
 
301
  .toolbar {
302
+ display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
 
 
 
303
  background: color-mix(in srgb, var(--surface) 88%, transparent);
304
+ backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
305
+ border: 1px solid var(--border); border-radius: var(--radius);
306
+ padding: 12px 14px; position: sticky; top: 86px; z-index: 10;
 
 
 
 
 
307
  box-shadow: var(--shadow);
308
  }
309
  .search-wrap { position: relative; flex: 1; min-width: 220px; display: flex; align-items: center; }
310
  .search-icon { position: absolute; left: 12px; color: var(--text-faint); pointer-events: none; }
311
  .search-input {
312
+ width: 100%; background: var(--surface-3); border: 1px solid var(--border); color: var(--text);
313
+ font: inherit; font-size: 14px; padding: 10px 12px 10px 36px; border-radius: var(--radius-sm);
 
 
 
 
 
 
314
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
315
  }
316
  .search-input::placeholder { color: var(--text-faint); }
 
318
  .search-count { position: absolute; right: 12px; font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
319
  .toolbar-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
320
  .export-group { display: flex; gap: 3px; background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 3px; }
321
+ .add-more { display: inline-flex; align-items: center; }
322
 
323
  /* buttons */
324
  .btn {
325
+ border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
326
+ font: inherit; font-size: 13px; font-weight: 650; padding: 9px 14px;
327
+ border-radius: var(--radius-sm); cursor: pointer;
 
 
 
 
 
 
328
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast), opacity var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
329
  box-shadow: var(--shadow-sm);
330
  }
 
341
  /* The HTML `hidden` attribute must always win over display rules below. */
342
  [hidden] { display: none !important; }
343
 
344
+ /* empty state == centred dropzone hero + auto-detect */
345
+ .empty-state { display: flex; flex-direction: column; gap: 18px; }
346
+
347
+ .dropzone {
 
 
 
 
348
  border: 1.5px dashed var(--border-strong);
349
  border-radius: var(--radius-lg);
350
+ padding: 60px 28px;
351
+ text-align: center;
352
+ cursor: pointer;
353
+ transition: border-color var(--t), background var(--t), transform var(--t-fast), box-shadow var(--t);
354
+ color: var(--text-dim);
355
+ background:
356
+ radial-gradient(130% 130% at 50% -10%, var(--accent-soft), transparent 70%),
357
+ var(--surface);
358
  box-shadow: var(--shadow);
359
  }
360
+ .dropzone:hover { border-color: var(--accent); }
361
+ .dropzone:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
362
+ .dropzone.dragover { border-color: var(--accent); background: var(--accent-soft); transform: scale(1.008); box-shadow: 0 0 0 4px var(--accent-soft), var(--shadow); }
363
+ .dz-icon-wrap {
364
+ width: 66px; height: 66px; margin: 0 auto 18px;
365
+ display: flex; align-items: center; justify-content: center;
366
+ border-radius: 20px; color: var(--accent);
367
+ background: var(--accent-soft);
368
+ box-shadow: inset 0 0 0 1px var(--accent-ring);
369
+ }
370
+ .dz-primary { margin: 0; font-weight: 680; color: var(--text); font-size: 18px; letter-spacing: -0.01em; }
371
+ .dz-secondary { margin: 7px 0 0; font-size: 13.5px; color: var(--text-dim); }
372
+ .dz-link { color: var(--accent); font-weight: 650; }
373
+
374
+ .dz-extra { display: flex; flex-direction: column; align-items: center; gap: 8px; }
375
+ .dz-extra-hint { max-width: 480px; text-align: center; margin: 0; }
376
+ #suggestResult { width: 100%; max-width: 520px; margin: 4px auto 0; text-align: left; }
377
 
378
  /* files */
379
  .files-container { display: flex; flex-direction: column; gap: 18px; }
380
  .file-card {
381
+ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
382
+ overflow: hidden; box-shadow: var(--shadow);
 
 
 
383
  }
384
  .file-head {
385
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
 
402
  .progress-label { font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; flex: none; }
403
 
404
  .file-error {
405
+ margin: 0 18px 15px; padding: 11px 13px; background: var(--red-soft);
406
+ border: 1px solid var(--red-line); border-radius: var(--radius-sm); color: var(--red); font-size: 13px;
 
 
 
 
 
407
  }
408
 
409
  .pages { display: flex; flex-direction: column; gap: 13px; padding: 5px 18px 18px; }
410
 
411
  /* page card */
412
  .page-card {
413
+ border: 1px solid var(--border-soft); border-radius: var(--radius);
414
+ background: var(--surface-3); overflow: hidden;
 
 
415
  }
416
  .page-head { display: flex; align-items: center; gap: 10px; padding: 10px 13px; border-bottom: 1px solid var(--border-soft); }
417
  .page-num { font-size: 13px; font-weight: 700; color: var(--text-dim); }
 
420
  .badge.text { color: var(--green); background: var(--green-soft); border: 1px solid var(--green-line); }
421
  .badge.ocr { color: var(--amber); background: var(--amber-soft); border: 1px solid var(--amber-line); }
422
  .badge.ocr.lowconf { color: var(--red); background: var(--red-soft); border-color: var(--red-line); }
423
+ .badge.handwriting { color: var(--accent); background: var(--accent-soft); border: 1px solid var(--accent-ring); }
424
+ .badge.online { color: var(--teal); background: color-mix(in srgb, var(--teal) 15%, transparent); border: 1px solid color-mix(in srgb, var(--teal) 40%, transparent); }
425
  .beta-tag { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); background: var(--accent-soft); padding: 1px 5px; border-radius: 999px; vertical-align: middle; }
426
  .badge.error { color: var(--red); background: var(--red-soft); border: 1px solid var(--red-line); }
427
  .badge.pending { color: var(--text-faint); background: var(--surface-2); border: 1px solid var(--border); }
428
 
429
  .page-text {
430
+ margin: 0; padding: 15px 17px; white-space: pre-wrap; word-break: break-word;
431
+ font-family: var(--mono); font-size: 13px; line-height: 1.65; color: var(--text);
432
+ max-height: 460px; overflow: auto;
 
 
 
 
 
 
 
433
  }
434
  .page-text:empty::before { content: "(no text on this page)"; color: var(--text-faint); font-style: italic; }
435
  .page-text mark { background: var(--mark); color: var(--mark-text); border-radius: 3px; padding: 0 2px; }
436
  .page-text mark.active { background: var(--mark-active); outline: 1.5px solid var(--amber); }
437
 
 
438
  .lc-line { background: var(--amber-soft); border-bottom: 1px dashed var(--amber-line); border-radius: 3px; }
 
 
439
  .to-check { font-size: 11px; font-weight: 600; color: var(--amber); background: var(--amber-soft); border: 1px solid var(--amber-line); border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
440
 
 
441
  .page-edit {
442
  width: 100%; box-sizing: border-box; margin: 0;
443
  padding: 15px 17px; min-height: 180px; max-height: 460px; resize: vertical;
 
445
  font-family: var(--mono); font-size: 13px; line-height: 1.65;
446
  color: var(--text); background: var(--surface-3);
447
  border: 1px solid var(--accent); border-radius: 0 0 var(--radius) var(--radius);
448
+ outline: none; box-shadow: inset 0 0 0 3px var(--accent-soft);
 
449
  }
450
 
451
  .page-image-wrap { padding: 13px 17px; border-top: 1px solid var(--border-soft); background: var(--surface); }
 
454
 
455
  /* toast */
456
  .toast {
457
+ position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%) translateY(12px);
458
+ background: var(--surface-raise); border: 1px solid var(--border); color: var(--text);
459
+ padding: 12px 20px; border-radius: 999px; font-size: 13.5px; font-weight: 600;
460
+ box-shadow: var(--shadow); opacity: 0; transition: opacity var(--t), transform var(--t);
461
+ z-index: 50; pointer-events: none;
 
 
 
 
 
 
 
 
 
 
 
462
  }
463
  .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
464
 
465
+ /* footer tips */
466
+ .app-footer { max-width: 1440px; width: 100%; margin: 0 auto; padding: 4px 26px 28px; }
467
+ .footer-tips {
468
+ border: 1px solid var(--border); border-radius: var(--radius);
469
+ background: var(--surface); box-shadow: var(--shadow-sm); padding: 2px 16px;
470
+ }
471
+ .footer-tips > summary {
472
+ list-style: none; cursor: pointer; user-select: none;
473
+ display: flex; align-items: center; gap: 8px;
474
+ padding: 13px 2px; font-size: 13px; font-weight: 650; color: var(--text-dim);
475
+ }
476
+ .footer-tips > summary::-webkit-details-marker { display: none; }
477
+ .footer-tips > summary svg { color: var(--accent); flex: none; }
478
+ .footer-tips > summary:hover { color: var(--text); }
479
+ .footer-tips-list { margin: 0 0 14px; padding-left: 22px; }
480
+ .footer-tips-list li { font-size: 12.5px; color: var(--text-faint); margin-bottom: 6px; line-height: 1.55; }
481
+ .footer-tips-list li::marker { color: var(--accent); }
482
+
483
  /* responsive */
484
  @media (max-width: 900px) {
485
  .layout { grid-template-columns: 1fr; padding: 16px; gap: 18px; }
486
+ .panel { position: static; }
487
  .toolbar { position: static; }
488
  .sidebar { order: 2; }
489
  .work { order: 1; }
490
+ .app-footer { padding: 4px 16px 22px; }
491
  }
492
  @media (max-width: 520px) {
493
  .app-header { padding: 12px 16px; }
494
  .brand-text h1 { font-size: 17px; }
495
  .toolbar-actions { width: 100%; justify-content: space-between; }
496
+ .dropzone { padding: 44px 20px; }
497
  }
498
 
499
  /* scrollbars */
 
502
  .page-text::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
503
  .page-text::-webkit-scrollbar-track { background: transparent; }
504
 
505
+ /* online (Gemini) sub-setting */
506
  .setting-sub { margin-left: 4px; padding-left: 13px; border-left: 2px solid var(--accent-soft); }
507
 
508
+ /* "Auto-detect best settings" recommendation panel */
509
  .btn-primary { background: linear-gradient(145deg, var(--accent), var(--accent-strong)); color: var(--accent-contrast); border-color: transparent; box-shadow: 0 6px 16px -6px var(--accent-ring); }
510
  .btn-primary:hover { filter: brightness(1.06); border-color: transparent; }
511
+ .suggest-card { margin-top: 6px; padding: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-3); }
512
  .suggest-rationale { font-size: 12px; line-height: 1.5; margin: 0 0 8px; color: var(--text-dim); }
513
  .suggest-evidence { font-size: 11px; font-family: var(--mono); margin-bottom: 8px; }
514
  .ev-row { display: grid; grid-template-columns: 1.5fr 1fr 0.9fr 0.9fr; gap: 6px; padding: 2px 0; opacity: 0.72; }
515
  .ev-row.best { opacity: 1; font-weight: 650; color: var(--text); }
516
 
517
+ /* online (Gemini) settings */
518
  .online-input { width: 100%; margin-bottom: 6px; font-family: var(--mono); font-size: 12px; }
519
  .online-key-actions { display: flex; align-items: center; gap: 8px; }
520
  .online-status { font-size: 11px; }
521
  .online-status.good { color: var(--green); }
522
  .online-status.bad { color: var(--red); }
523
 
524
+ /* remove (x) button on a file card */
525
  .btn-remove { color: var(--text-dim); font-size: 18px; line-height: 1; padding: 3px 9px; font-weight: 700; box-shadow: none; border-color: transparent; background: transparent; }
526
  .btn-remove:hover { color: var(--red); background: var(--red-soft); transform: none; }
527
 
 
 
 
 
 
 
 
528
  @media (prefers-reduced-motion: reduce) {
529
  * { transition: none !important; animation: none !important; }
530
  }