ginipick commited on
Commit
7318db9
·
verified ·
1 Parent(s): cc434dc

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +237 -342
index.html CHANGED
@@ -3,22 +3,25 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Screen Monitor Pro</title>
 
7
  <script src="https://unpkg.com/[email protected]/dist/tesseract.min.js"></script>
8
  <style>
9
  * {
10
  margin: 0;
11
  padding: 0;
12
  box-sizing: border-box;
13
- font-family: -apple-system, system-ui, sans-serif;
14
  }
15
 
16
- .app-container {
 
 
 
 
 
17
  max-width: 1200px;
18
  margin: 0 auto;
19
- padding: 20px;
20
- background: #f8fafc;
21
- min-height: 100vh;
22
  }
23
 
24
  .header {
@@ -26,81 +29,61 @@
26
  padding: 20px;
27
  border-radius: 8px;
28
  margin-bottom: 20px;
29
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
30
  text-align: center;
 
31
  }
32
 
33
- .status-bar {
34
- display: flex;
35
- justify-content: space-between;
36
- align-items: center;
37
  background: white;
38
- padding: 15px;
39
  border-radius: 8px;
40
- margin-bottom: 20px;
41
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
42
- }
43
-
44
- .status-indicator {
45
- display: flex;
46
- align-items: center;
47
- gap: 8px;
48
- }
49
-
50
- .status-dot {
51
- width: 10px;
52
- height: 10px;
53
- border-radius: 50%;
54
- background: #cbd5e1;
55
- }
56
-
57
- .status-dot.active {
58
- background: #22c55e;
59
- animation: pulse 1s infinite;
60
- }
61
-
62
- @keyframes pulse {
63
- 0% { opacity: 1; }
64
- 50% { opacity: 0.5; }
65
- 100% { opacity: 1; }
66
- }
67
-
68
- .controls {
69
  display: flex;
70
  gap: 10px;
 
71
  }
72
 
73
  button {
74
  padding: 10px 20px;
75
  border: none;
76
- border-radius: 6px;
77
  cursor: pointer;
78
- font-weight: 500;
79
- transition: all 0.2s;
80
- }
81
-
82
- button:disabled {
83
- opacity: 0.5;
84
- cursor: not-allowed;
85
  }
86
 
87
  .start-btn {
88
- background: #22c55e;
89
  color: white;
90
  }
91
 
92
  .stop-btn {
93
- background: #ef4444;
94
  color: white;
95
  }
96
 
97
- .preview-container {
 
 
 
 
 
 
 
 
 
 
98
  background: black;
99
  border-radius: 8px;
100
  overflow: hidden;
101
  margin-bottom: 20px;
102
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
103
  position: relative;
 
104
  }
105
 
106
  #preview {
@@ -109,6 +92,23 @@
109
  object-fit: contain;
110
  }
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  .logs {
113
  background: white;
114
  border-radius: 8px;
@@ -116,16 +116,9 @@
116
  overflow: hidden;
117
  }
118
 
119
- .log-header {
120
- background: #1e293b;
121
- color: white;
122
- padding: 15px;
123
- font-weight: 500;
124
- }
125
-
126
  .log-entry {
127
  padding: 20px;
128
- border-bottom: 1px solid #e2e8f0;
129
  display: grid;
130
  grid-template-columns: 300px 1fr;
131
  gap: 20px;
@@ -143,10 +136,9 @@
143
 
144
  .change-highlight {
145
  position: absolute;
146
- border: 2px solid #ef4444;
147
- background: rgba(239, 68, 68, 0.2);
148
  pointer-events: none;
149
- transition: all 0.3s;
150
  }
151
 
152
  .info-panel {
@@ -156,57 +148,41 @@
156
  }
157
 
158
  .timestamp {
159
- color: #64748b;
160
- font-size: 0.9rem;
161
  }
162
 
163
- .analysis {
164
- background: #f8fafc;
165
  padding: 15px;
166
- border-radius: 6px;
167
- font-size: 0.95rem;
168
- line-height: 1.5;
169
- }
170
-
171
- .analysis h4 {
172
- margin-bottom: 8px;
173
- color: #1e293b;
174
- }
175
-
176
- .ocr-text {
177
- font-family: monospace;
178
- white-space: pre-wrap;
179
- background: #f1f5f9;
180
- padding: 10px;
181
  border-radius: 4px;
182
- margin-top: 8px;
 
 
 
183
  }
184
 
185
  .actions {
186
  display: flex;
187
  gap: 10px;
188
- margin-top: auto;
189
  }
190
 
191
  .download-btn {
192
- padding: 8px 16px;
193
- background: #3b82f6;
194
  color: white;
195
- text-decoration: none;
196
  border-radius: 4px;
197
- font-size: 0.9rem;
198
- display: flex;
199
  align-items: center;
200
- gap: 6px;
 
201
  }
202
 
203
- .error-message {
204
- background: #fef2f2;
205
- color: #ef4444;
206
- padding: 10px;
207
- border-radius: 4px;
208
- margin: 10px 0;
209
- display: none;
210
  }
211
 
212
  @media (max-width: 768px) {
@@ -217,283 +193,202 @@
217
  </style>
218
  </head>
219
  <body>
220
- <div class="app-container">
221
  <div class="header">
222
- <h1>Screen Monitor Pro</h1>
223
- <p>Advanced screen capture and analysis</p>
224
  </div>
225
 
226
- <div class="status-bar">
227
- <div class="status-indicator">
228
- <div class="status-dot" id="statusDot"></div>
229
- <span id="statusText">Ready</span>
230
- </div>
231
- <div class="controls">
232
- <button class="start-btn" id="startBtn">Start Capture</button>
233
- <button class="stop-btn" id="stopBtn" disabled>Stop</button>
234
- </div>
235
  </div>
236
 
237
- <div class="error-message" id="errorMessage"></div>
238
-
239
- <div class="preview-container">
240
  <video id="preview" autoplay></video>
 
241
  </div>
242
 
243
- <div class="logs">
244
- <div class="log-header">Change Log</div>
245
- <div id="logContainer"></div>
246
- </div>
247
  </div>
248
 
249
  <script>
250
- class ScreenAnalyzer {
251
- constructor() {
252
- this.mediaStream = null;
253
- this.captureInterval = null;
254
- this.lastImageData = null;
255
- this.worker = null;
256
- this.isProcessing = false;
257
-
258
- this.initializeOCR();
259
- this.bindElements();
260
- this.bindEvents();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  }
 
262
 
263
- async initializeOCR() {
264
- try {
265
- this.worker = await Tesseract.createWorker();
266
- await this.worker.loadLanguage('eng');
267
- await this.worker.initialize('eng');
268
- } catch (error) {
269
- this.showError('Failed to initialize OCR');
270
- }
271
- }
272
-
273
- bindElements() {
274
- this.elements = {
275
- startBtn: document.getElementById('startBtn'),
276
- stopBtn: document.getElementById('stopBtn'),
277
- preview: document.getElementById('preview'),
278
- logContainer: document.getElementById('logContainer'),
279
- statusDot: document.getElementById('statusDot'),
280
- statusText: document.getElementById('statusText'),
281
- errorMessage: document.getElementById('errorMessage')
282
- };
283
- }
284
-
285
- bindEvents() {
286
- this.elements.startBtn.addEventListener('click', () => this.start());
287
- this.elements.stopBtn.addEventListener('click', () => this.stop());
288
- }
289
-
290
- updateStatus(status, isError = false) {
291
- this.elements.statusDot.className = `status-dot ${status === 'recording' ? 'active' : ''}`;
292
- this.elements.statusText.textContent = status.charAt(0).toUpperCase() + status.slice(1);
293
-
294
- if (isError) {
295
- this.elements.errorMessage.textContent = status;
296
- this.elements.errorMessage.style.display = 'block';
297
- } else {
298
- this.elements.errorMessage.style.display = 'none';
299
- }
300
- }
301
-
302
- async start() {
303
- try {
304
- this.mediaStream = await navigator.mediaDevices.getDisplayMedia({
305
- video: { cursor: "always" }
306
- });
307
-
308
- this.elements.preview.srcObject = this.mediaStream;
309
- this.elements.startBtn.disabled = true;
310
- this.elements.stopBtn.disabled = false;
311
- this.updateStatus('recording');
312
-
313
- this.captureInterval = setInterval(() => this.capture(), 1000);
314
- this.mediaStream.getVideoTracks()[0].onended = () => this.stop();
315
-
316
- } catch (error) {
317
- this.showError('Failed to start screen capture');
318
- }
319
- }
320
-
321
- stop() {
322
- if (this.mediaStream) {
323
- this.mediaStream.getTracks().forEach(track => track.stop());
324
- this.elements.preview.srcObject = null;
325
- }
326
- clearInterval(this.captureInterval);
327
- this.elements.startBtn.disabled = false;
328
- this.elements.stopBtn.disabled = true;
329
- this.lastImageData = null;
330
- this.updateStatus('ready');
331
- }
332
-
333
- async capture() {
334
- if (this.isProcessing) return;
335
- this.isProcessing = true;
336
-
337
- try {
338
- const canvas = document.createElement('canvas');
339
- canvas.width = this.elements.preview.videoWidth;
340
- canvas.height = this.elements.preview.videoHeight;
341
-
342
- const ctx = canvas.getContext('2d');
343
- ctx.drawImage(this.elements.preview, 0, 0);
344
-
345
- const currentImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
346
- const changes = this.detectChanges(currentImageData);
347
-
348
- if (changes.length > 0 || !this.lastImageData) {
349
- const imageUrl = canvas.toDataURL('image/jpeg', 0.8);
350
- const ocrResults = await this.analyzeChanges(canvas, changes);
351
- this.addLogEntry(imageUrl, changes, ocrResults);
352
- }
353
-
354
- this.lastImageData = currentImageData;
355
- } catch (error) {
356
- this.showError('Failed to process capture');
357
- } finally {
358
- this.isProcessing = false;
359
- }
360
  }
 
 
 
 
 
 
361
 
362
- detectChanges(currentImageData) {
363
- if (!this.lastImageData) return [];
364
-
365
- const changes = [];
366
- const blockSize = 20;
367
- const threshold = 30;
368
- const width = currentImageData.width;
369
- const height = currentImageData.height;
370
-
371
- for (let y = 0; y < height; y += blockSize) {
372
- for (let x = 0; x < width; x += blockSize) {
373
- let diffCount = 0;
374
- const maxY = Math.min(y + blockSize, height);
375
- const maxX = Math.min(x + blockSize, width);
376
-
377
- for (let py = y; py < maxY; py++) {
378
- for (let px = x; px < maxX; px++) {
379
- const i = (py * width + px) * 4;
380
- if (Math.abs(currentImageData.data[i] - this.lastImageData.data[i]) > threshold ||
381
- Math.abs(currentImageData.data[i + 1] - this.lastImageData.data[i + 1]) > threshold ||
382
- Math.abs(currentImageData.data[i + 2] - this.lastImageData.data[i + 2]) > threshold) {
383
- diffCount++;
384
- }
385
  }
386
  }
 
387
 
388
- if (diffCount > (blockSize * blockSize * 0.3)) {
389
- changes.push({
390
- x: x / width * 100,
391
- y: y / height * 100,
392
- width: Math.min(blockSize / width * 100, 100 - x / width * 100),
393
- height: Math.min(blockSize / height * 100, 100 - y / height * 100),
394
- pixels: {x, y, width: maxX - x, height: maxY - y}
395
- });
396
- }
397
  }
398
  }
399
-
400
- return changes;
401
  }
402
 
403
- async analyzeChanges(canvas, changes) {
404
- const results = [];
405
-
406
- for (const change of changes) {
407
- const tempCanvas = document.createElement('canvas');
408
- tempCanvas.width = change.pixels.width;
409
- tempCanvas.height = change.pixels.height;
410
- const tempCtx = tempCanvas.getContext('2d');
411
-
412
- tempCtx.drawImage(
413
- canvas,
414
- change.pixels.x, change.pixels.y,
415
- change.pixels.width, change.pixels.height,
416
- 0, 0,
417
- change.pixels.width, change.pixels.height
418
- );
419
-
420
- try {
421
- const result = await this.worker.recognize(tempCanvas);
422
- if (result.data.text.trim()) {
423
- results.push({
424
- text: result.data.text.trim(),
425
- confidence: result.data.confidence,
426
- region: change
427
- });
428
- }
429
- } catch (error) {
430
- console.error('OCR Error:', error);
431
- }
432
- }
433
 
434
- return results;
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
 
437
- addLogEntry(imageUrl, changes, ocrResults) {
438
- const logEntry = document.createElement('div');
439
- logEntry.className = 'log-entry';
440
-
441
- const timestamp = new Date().toLocaleString();
442
-
443
- logEntry.innerHTML = `
444
- <div class="screenshot-container">
445
- <img class="screenshot" src="${imageUrl}" alt="Screenshot">
446
- ${changes.map((change, index) => `
447
- <div class="change-highlight" style="
448
- left: ${change.x}%;
449
- top: ${change.y}%;
450
- width: ${change.width}%;
451
- height: ${change.height}%;
452
- "></div>
453
- `).join('')}
454
- </div>
455
- <div class="info-panel">
456
- <div class="timestamp">📅 ${timestamp}</div>
457
- <div class="analysis">
458
- <h4>Changes Detected</h4>
459
- <p>${changes.length} regions changed</p>
460
- ${ocrResults.length > 0 ? `
461
- <h4>Text Content</h4>
462
- <div class="ocr-text">
463
- ${ocrResults.map(result =>
464
- `[Region ${Math.round(result.confidence)}% confidence]\n${result.text}`
465
- ).join('\n\n')}
466
- </div>
467
- ` : ''}
468
- </div>
469
- <div class="actions">
470
- <a href="${imageUrl}" download="screenshot-${Date.now()}.jpg"
471
- class="download-btn">
472
- 📸 Download Screenshot
473
- </a>
474
- </div>
475
- </div>
476
- `;
477
 
478
- this.elements.logContainer.insertBefore(logEntry, this.elements.logContainer.firstChild);
 
 
479
  }
 
480
 
481
- showError(message) {
482
- this.updateStatus(message, true);
483
- console.error(message);
 
 
 
 
484
  }
 
485
 
486
- async cleanup() {
487
- if (this.worker) {
488
- await this.worker.terminate();
489
- }
490
- this.stop();
491
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  }
493
 
494
- // Initialize the application
495
- const analyzer = new ScreenAnalyzer();
496
- window.addEventListener('beforeunload', () => analyzer.cleanup());
 
 
 
 
 
 
 
 
497
  </script>
498
  </body>
499
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Screen Analysis & OCR Tool</title>
7
+ <!-- Include Tesseract.js for OCR -->
8
  <script src="https://unpkg.com/[email protected]/dist/tesseract.min.js"></script>
9
  <style>
10
  * {
11
  margin: 0;
12
  padding: 0;
13
  box-sizing: border-box;
14
+ font-family: Arial, sans-serif;
15
  }
16
 
17
+ body {
18
+ background: #f0f2f5;
19
+ padding: 20px;
20
+ }
21
+
22
+ .container {
23
  max-width: 1200px;
24
  margin: 0 auto;
 
 
 
25
  }
26
 
27
  .header {
 
29
  padding: 20px;
30
  border-radius: 8px;
31
  margin-bottom: 20px;
 
32
  text-align: center;
33
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
34
  }
35
 
36
+ .controls {
 
 
 
37
  background: white;
38
+ padding: 20px;
39
  border-radius: 8px;
 
40
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
41
+ margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  display: flex;
43
  gap: 10px;
44
+ flex-wrap: wrap;
45
  }
46
 
47
  button {
48
  padding: 10px 20px;
49
  border: none;
50
+ border-radius: 4px;
51
  cursor: pointer;
52
+ font-weight: bold;
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 8px;
56
+ min-width: 120px;
57
+ justify-content: center;
 
58
  }
59
 
60
  .start-btn {
61
+ background: #4CAF50;
62
  color: white;
63
  }
64
 
65
  .stop-btn {
66
+ background: #f44336;
67
  color: white;
68
  }
69
 
70
+ .clear-btn {
71
+ background: #2196F3;
72
+ color: white;
73
+ }
74
+
75
+ button:disabled {
76
+ background: #ccc;
77
+ cursor: not-allowed;
78
+ }
79
+
80
+ .preview-area {
81
  background: black;
82
  border-radius: 8px;
83
  overflow: hidden;
84
  margin-bottom: 20px;
 
85
  position: relative;
86
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
87
  }
88
 
89
  #preview {
 
92
  object-fit: contain;
93
  }
94
 
95
+ .recording-dot {
96
+ position: absolute;
97
+ top: 20px;
98
+ right: 20px;
99
+ width: 12px;
100
+ height: 12px;
101
+ background: red;
102
+ border-radius: 50%;
103
+ animation: pulse 1s infinite;
104
+ }
105
+
106
+ @keyframes pulse {
107
+ 0% { opacity: 1; }
108
+ 50% { opacity: 0.5; }
109
+ 100% { opacity: 1; }
110
+ }
111
+
112
  .logs {
113
  background: white;
114
  border-radius: 8px;
 
116
  overflow: hidden;
117
  }
118
 
 
 
 
 
 
 
 
119
  .log-entry {
120
  padding: 20px;
121
+ border-bottom: 1px solid #eee;
122
  display: grid;
123
  grid-template-columns: 300px 1fr;
124
  gap: 20px;
 
136
 
137
  .change-highlight {
138
  position: absolute;
139
+ border: 2px solid red;
140
+ background: rgba(255,0,0,0.2);
141
  pointer-events: none;
 
142
  }
143
 
144
  .info-panel {
 
148
  }
149
 
150
  .timestamp {
151
+ color: #666;
152
+ font-size: 14px;
153
  }
154
 
155
+ .text-content {
156
+ background: #f8f9fa;
157
  padding: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  border-radius: 4px;
159
+ font-size: 14px;
160
+ line-height: 1.5;
161
+ max-height: 200px;
162
+ overflow-y: auto;
163
  }
164
 
165
  .actions {
166
  display: flex;
167
  gap: 10px;
168
+ margin-top: 10px;
169
  }
170
 
171
  .download-btn {
172
+ background: #4CAF50;
 
173
  color: white;
174
+ padding: 8px 16px;
175
  border-radius: 4px;
176
+ text-decoration: none;
177
+ display: inline-flex;
178
  align-items: center;
179
+ gap: 5px;
180
+ font-size: 14px;
181
  }
182
 
183
+ .processing {
184
+ color: #666;
185
+ font-style: italic;
 
 
 
 
186
  }
187
 
188
  @media (max-width: 768px) {
 
193
  </style>
194
  </head>
195
  <body>
196
+ <div class="container">
197
  <div class="header">
198
+ <h1>Screen Analysis & OCR Tool</h1>
199
+ <p>Capture, analyze, and extract text from your screen</p>
200
  </div>
201
 
202
+ <div class="controls">
203
+ <button id="startBtn" class="start-btn">▶ Start Capture</button>
204
+ <button id="stopBtn" class="stop-btn" disabled>⬛ Stop</button>
205
+ <button id="clearBtn" class="clear-btn">🗑 Clear Logs</button>
 
 
 
 
 
206
  </div>
207
 
208
+ <div class="preview-area">
 
 
209
  <video id="preview" autoplay></video>
210
+ <div class="recording-dot" style="display: none;"></div>
211
  </div>
212
 
213
+ <div class="logs" id="logContainer"></div>
 
 
 
214
  </div>
215
 
216
  <script>
217
+ let mediaStream = null;
218
+ let captureInterval = null;
219
+ let lastImageData = null;
220
+
221
+ // Initialize Tesseract
222
+ const worker = Tesseract.createWorker();
223
+
224
+ (async () => {
225
+ await worker.load();
226
+ await worker.loadLanguage('eng');
227
+ await worker.initialize('eng');
228
+ })();
229
+
230
+ const startBtn = document.getElementById('startBtn');
231
+ const stopBtn = document.getElementById('stopBtn');
232
+ const clearBtn = document.getElementById('clearBtn');
233
+ const preview = document.getElementById('preview');
234
+ const logContainer = document.getElementById('logContainer');
235
+ const recordingDot = document.querySelector('.recording-dot');
236
+
237
+ async function startCapture() {
238
+ try {
239
+ mediaStream = await navigator.mediaDevices.getDisplayMedia({
240
+ video: { cursor: "always" }
241
+ });
242
+
243
+ preview.srcObject = mediaStream;
244
+ startBtn.disabled = true;
245
+ stopBtn.disabled = false;
246
+ recordingDot.style.display = 'block';
247
+
248
+ captureInterval = setInterval(captureAndAnalyze, 1000);
249
+
250
+ mediaStream.getVideoTracks()[0].onended = stopCapture;
251
+
252
+ } catch (err) {
253
+ console.error("Error starting capture:", err);
254
  }
255
+ }
256
 
257
+ function stopCapture() {
258
+ if (mediaStream) {
259
+ mediaStream.getTracks().forEach(track => track.stop());
260
+ preview.srcObject = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  }
262
+ clearInterval(captureInterval);
263
+ startBtn.disabled = false;
264
+ stopBtn.disabled = true;
265
+ recordingDot.style.display = 'none';
266
+ lastImageData = null;
267
+ }
268
 
269
+ function detectChanges(current, last) {
270
+ const blockSize = 20;
271
+ const threshold = 30;
272
+ const changes = [];
273
+ const width = current.width;
274
+ const height = current.height;
275
+
276
+ for (let y = 0; y < height; y += blockSize) {
277
+ for (let x = 0; x < width; x += blockSize) {
278
+ let diffCount = 0;
279
+ const maxY = Math.min(y + blockSize, height);
280
+ const maxX = Math.min(x + blockSize, width);
281
+
282
+ for (let py = y; py < maxY; py++) {
283
+ for (let px = x; px < maxX; px++) {
284
+ const i = (py * width + px) * 4;
285
+ if (Math.abs(current.data[i] - last.data[i]) > threshold ||
286
+ Math.abs(current.data[i + 1] - last.data[i + 1]) > threshold ||
287
+ Math.abs(current.data[i + 2] - last.data[i + 2]) > threshold) {
288
+ diffCount++;
 
 
 
289
  }
290
  }
291
+ }
292
 
293
+ if (diffCount > (blockSize * blockSize * 0.3)) {
294
+ changes.push({
295
+ x: x / width * 100,
296
+ y: y / height * 100,
297
+ width: Math.min(blockSize / width * 100, 100 - x / width * 100),
298
+ height: Math.min(blockSize / height * 100, 100 - y / height * 100)
299
+ });
 
 
300
  }
301
  }
 
 
302
  }
303
 
304
+ return changes;
305
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
+ async function captureAndAnalyze() {
308
+ const canvas = document.createElement('canvas');
309
+ canvas.width = preview.videoWidth;
310
+ canvas.height = preview.videoHeight;
311
+
312
+ const ctx = canvas.getContext('2d');
313
+ ctx.drawImage(preview, 0, 0);
314
+
315
+ const currentImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
316
+ let changes = [];
317
+
318
+ if (lastImageData) {
319
+ changes = detectChanges(currentImageData, lastImageData);
320
  }
321
 
322
+ lastImageData = currentImageData;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
+ if (changes.length > 0 || !lastImageData) {
325
+ const imageUrl = canvas.toDataURL('image/jpeg', 0.8);
326
+ await addLogEntry(imageUrl, changes);
327
  }
328
+ }
329
 
330
+ async function performOCR(imageUrl) {
331
+ try {
332
+ const result = await worker.recognize(imageUrl);
333
+ return result.data.text;
334
+ } catch (error) {
335
+ console.error('OCR Error:', error);
336
+ return 'Error performing OCR';
337
  }
338
+ }
339
 
340
+ async function addLogEntry(imageUrl, changes) {
341
+ const logEntry = document.createElement('div');
342
+ logEntry.className = 'log-entry';
343
+
344
+ const timestamp = new Date().toLocaleString();
345
+
346
+ // Create initial structure with loading state
347
+ logEntry.innerHTML = `
348
+ <div class="screenshot-container">
349
+ <img class="screenshot" src="${imageUrl}" alt="Screenshot">
350
+ ${changes.map(change => `
351
+ <div class="change-highlight" style="
352
+ left: ${change.x}%;
353
+ top: ${change.y}%;
354
+ width: ${change.width}%;
355
+ height: ${change.height}%;
356
+ "></div>
357
+ `).join('')}
358
+ </div>
359
+ <div class="info-panel">
360
+ <div class="timestamp">${timestamp}</div>
361
+ <div class="text-content processing">Processing text extraction...</div>
362
+ <div class="actions">
363
+ <a href="${imageUrl}" download="screenshot-${Date.now()}.jpg" class="download-btn">
364
+ 💾 Download Image
365
+ </a>
366
+ </div>
367
+ </div>
368
+ `;
369
+
370
+ logContainer.insertBefore(logEntry, logContainer.firstChild);
371
+
372
+ // Perform OCR
373
+ const extractedText = await performOCR(imageUrl);
374
+
375
+ // Update text content
376
+ const textContent = logEntry.querySelector('.text-content');
377
+ textContent.classList.remove('processing');
378
+ textContent.textContent = extractedText || 'No text detected';
379
  }
380
 
381
+ clearBtn.addEventListener('click', () => {
382
+ logContainer.innerHTML = '';
383
+ });
384
+
385
+ startBtn.addEventListener('click', startCapture);
386
+ stopBtn.addEventListener('click', stopCapture);
387
+
388
+ // Cleanup when page is closed
389
+ window.addEventListener('beforeunload', () => {
390
+ worker.terminate();
391
+ });
392
  </script>
393
  </body>
394
  </html>