testdriverai 7.2.71 → 7.2.72

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.
@@ -0,0 +1,797 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
6
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
7
+ <link
8
+ href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
9
+ rel="stylesheet"
10
+ />
11
+ <link rel="icon" type="image/png" href="icon.png" />
12
+ <title>TestDriver - Debugger</title>
13
+ <style>
14
+ body {
15
+ background-color: #111;
16
+ background-image: url("bg.png");
17
+ background-repeat: repeat;
18
+ background-size: auto;
19
+ }
20
+
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ font-family: "IBM Plex Mono", monospace;
26
+ font-weight: 400;
27
+ font-style: normal;
28
+ font-size: 14px;
29
+ scrollbar-width: none; /* Hide scrollbars for Firefox */
30
+ pointer-events: none;
31
+ }
32
+
33
+ #overlay {
34
+ position: absolute;
35
+ top: 0;
36
+ left: 0;
37
+ transform-origin: top left;
38
+ overflow: hidden;
39
+ }
40
+
41
+ html,
42
+ body {
43
+ overflow: hidden;
44
+ width: 100%;
45
+ height: 100%;
46
+ }
47
+
48
+ *::-webkit-scrollbar {
49
+ display: none; /* Hide scrollbars for WebKit browsers */
50
+ }
51
+
52
+ @keyframes animate-glow {
53
+ 0% {
54
+ opacity: 0;
55
+ filter: brightness(3) saturate(3);
56
+ transform: scale(0.8, 0.8);
57
+ }
58
+
59
+ 30% {
60
+ opacity: 1;
61
+ filter: brightness(1) saturate(1);
62
+ transform: scale(1, 1);
63
+ }
64
+
65
+ 100% {
66
+ /* opacity: 0; */
67
+ opacity: 1;
68
+ transform: scale(1, 1);
69
+ }
70
+ }
71
+
72
+ .overlay {
73
+ position: relative;
74
+ overflow: hidden;
75
+ }
76
+
77
+ .screenshot {
78
+ position: absolute;
79
+ inset: 0;
80
+ z-index: 1;
81
+ opacity: 0;
82
+ }
83
+
84
+ .box {
85
+ border: 1px solid #b0cf34;
86
+ position: absolute;
87
+ border-radius: 5px;
88
+ animation-duration: 5s;
89
+ animation-delay: 0s;
90
+ animation-timing-function: cubic-bezier(0.26, 0.53, 0.74, 1.48);
91
+ animation-fill-mode: backwards;
92
+ animation-name: animate-glow;
93
+ animation-timing-function: ease;
94
+ animation-fill-mode: forwards;
95
+ border-radius: 5px;
96
+ }
97
+
98
+ .effects {
99
+ inset: 0;
100
+ position: absolute;
101
+ z-index: 20;
102
+ }
103
+
104
+ #mouse {
105
+ margin-left: -100px;
106
+ margin-top: -100px;
107
+ width: 50px;
108
+ height: 50px;
109
+ opacity: 0;
110
+ position: absolute;
111
+ transform: translate(-50%, -50%);
112
+ border-radius: 70%;
113
+ background: #b0cf34;
114
+ }
115
+
116
+ #mouse #dot {
117
+ width: 7px;
118
+ height: 7px;
119
+ position: absolute;
120
+ top: 50%;
121
+ left: 50%;
122
+ transform: translate(-50%, -50%);
123
+ border-radius: 50%;
124
+ background-color: black;
125
+ }
126
+
127
+ .single-click {
128
+ animation: singleClick 0.7s ease-in-out forwards;
129
+ }
130
+
131
+ .double-click {
132
+ animation: doubleClick 0.7s ease-in-out forwards;
133
+ }
134
+
135
+ @keyframes singleClick {
136
+ 0% {
137
+ opacity: 1;
138
+ transform: translate(-50%, -50%) scale(1);
139
+ }
140
+
141
+ 100% {
142
+ opacity: 0;
143
+ transform: translate(-50%, -50%) scale(2);
144
+ }
145
+ }
146
+
147
+ @keyframes doubleClick {
148
+ 0% {
149
+ opacity: 1;
150
+ transform: translate(-50%, -50%) scale(1);
151
+ }
152
+
153
+ 45% {
154
+ opacity: 0;
155
+ transform: translate(-50%, -50%) scale(1.2);
156
+ }
157
+
158
+ 55% {
159
+ opacity: 1;
160
+ transform: translate(-50%, -50%) scale(1);
161
+ }
162
+
163
+ 100% {
164
+ opacity: 0;
165
+ transform: scale(2);
166
+ }
167
+ }
168
+
169
+ #boxes {
170
+ position: absolute;
171
+ top: 25px;
172
+ left: 0;
173
+ right: 0;
174
+ bottom: 0;
175
+ z-index: 2;
176
+ }
177
+
178
+ #vm-iframe {
179
+ display: none;
180
+ border: none;
181
+ pointer-events: auto;
182
+ position: absolute;
183
+ top: 0px;
184
+ left: 0px;
185
+ width: 100%;
186
+ height: 100%;
187
+ z-index: 1;
188
+ overflow: hidden;
189
+ }
190
+
191
+ /* Loading screen styles */
192
+ .loading-screen {
193
+ position: absolute;
194
+ top: 0;
195
+ left: 0;
196
+ width: 100%;
197
+ height: 100%;
198
+ background-color: black;
199
+ display: flex;
200
+ flex-direction: column;
201
+ align-items: center;
202
+ justify-content: center;
203
+ z-index: 10;
204
+ transition: opacity 0.5s ease-out;
205
+ }
206
+
207
+ .loading-screen.hidden {
208
+ opacity: 0;
209
+ pointer-events: none;
210
+ }
211
+
212
+ .loading-text {
213
+ color: #b0cf34;
214
+ font-size: 18px;
215
+ font-weight: 500;
216
+ margin-top: 10px;
217
+ animation: fadeInOut 1.5s infinite;
218
+ }
219
+
220
+ @keyframes pulse {
221
+ 0% {
222
+ transform: scale(1);
223
+ box-shadow: 0 8px 32px rgba(176, 207, 52, 0.3);
224
+ }
225
+ 50% {
226
+ transform: scale(1.05);
227
+ box-shadow: 0 12px 48px rgba(176, 207, 52, 0.5);
228
+ }
229
+ 100% {
230
+ transform: scale(1);
231
+ box-shadow: 0 8px 32px rgba(176, 207, 52, 0.3);
232
+ }
233
+ }
234
+
235
+ @keyframes fadeInOut {
236
+ 0%,
237
+ 100% {
238
+ opacity: 0.6;
239
+ }
240
+ 50% {
241
+ opacity: 1;
242
+ }
243
+ }
244
+
245
+ .status {
246
+ position: absolute;
247
+ top: 20px;
248
+ left: 20px;
249
+ background: rgba(0, 0, 0, 0.8);
250
+ color: #b0cf34;
251
+ padding: 8px 12px;
252
+ border-radius: 4px;
253
+ font-size: 12px;
254
+ z-index: 11;
255
+ opacity: 0;
256
+ transition: opacity 0.3s ease;
257
+ pointer-events: none;
258
+ }
259
+
260
+ .status.visible {
261
+ opacity: 1;
262
+ }
263
+
264
+ .bounding-box {
265
+ position: absolute;
266
+ border: 2px solid #b0cf34;
267
+ background: rgba(176, 207, 52, 0.1);
268
+ pointer-events: none;
269
+ z-index: 9;
270
+ border-radius: 3px;
271
+ animation: animate-glow 0.6s ease-out;
272
+ }
273
+
274
+ .interaction-overlay {
275
+ position: absolute;
276
+ top: 0;
277
+ left: 0;
278
+ width: 100%;
279
+ height: 100%;
280
+ background: rgba(0, 0, 0, 0.5);
281
+ display: flex;
282
+ flex-direction: column;
283
+ align-items: center;
284
+ justify-content: center;
285
+ z-index: 12;
286
+ opacity: 0;
287
+ pointer-events: none;
288
+ transition: opacity 0.3s ease;
289
+ cursor: pointer;
290
+ color: white;
291
+ font-size: 16px;
292
+ font-weight: 500;
293
+ }
294
+
295
+ .interaction-overlay.visible {
296
+ opacity: 1;
297
+ pointer-events: auto;
298
+ }
299
+
300
+ .interaction-overlay .lock-icon {
301
+ width: 48px;
302
+ height: 48px;
303
+ margin-bottom: 12px;
304
+ fill: white;
305
+ }
306
+
307
+ .interaction-overlay .message {
308
+ text-align: center;
309
+ user-select: none;
310
+ }
311
+
312
+ .close-button {
313
+ position: fixed;
314
+ top: 12px;
315
+ right: 12px;
316
+ z-index: 100;
317
+ background: rgba(0, 0, 0, 0.8);
318
+ border: 1px solid #444;
319
+ color: #fff;
320
+ padding: 8px 16px;
321
+ border-radius: 6px;
322
+ cursor: pointer;
323
+ font-size: 13px;
324
+ font-weight: 500;
325
+ pointer-events: auto;
326
+ transition: all 0.2s ease;
327
+ display: flex;
328
+ align-items: center;
329
+ gap: 6px;
330
+ }
331
+
332
+ .close-button:hover {
333
+ background: rgba(220, 53, 69, 0.9);
334
+ border-color: #dc3545;
335
+ }
336
+
337
+ .close-button svg {
338
+ width: 14px;
339
+ height: 14px;
340
+ fill: currentColor;
341
+ }
342
+ </style>
343
+ </head>
344
+ <body>
345
+ <!-- Close window button -->
346
+ <button class="close-button" onclick="window.close()" title="Close this window">
347
+ <svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
348
+ Close
349
+ </button>
350
+
351
+ <!-- Loading screen -->
352
+ <div class="loading-screen" id="loading-screen">
353
+ <div class="testdriver-logo">
354
+ <img
355
+ src="https://framerusercontent.com/images/eMQDjLmOzfeu91XBo1VPlIc1WmY.png"
356
+ height="120"
357
+ alt="TestDriver Logo"
358
+ />
359
+ </div>
360
+ <div class="loading-text">Loading...</div>
361
+ </div>
362
+
363
+ <div class="overlay" id="overlay">
364
+ <div class="effects" id="effects">
365
+ <div class="mouse" id="mouse"></div>
366
+ <div class="screenshot" id="screenshot"></div>
367
+ <div class="status" id="status"></div>
368
+ <div class="interaction-overlay" id="interaction-overlay">
369
+ <svg class="lock-icon" viewBox="0 0 24 24">
370
+ <path
371
+ d="M18,8h-1V6c0-2.76-2.24-5-5-5S7,3.24,7,6v2H6c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V10C20,8.9,19.1,8,18,8z M12,17c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2S13.1,17,12,17z M15.1,8H8.9V6c0-1.71,1.39-3.1,3.1-3.1s3.1,1.39,3.1,3.1V8z"
372
+ />
373
+ </svg>
374
+ <div class="message">Click to interact with VM</div>
375
+ </div>
376
+ </div>
377
+ <iframe id="vm-iframe" src="" credentialless></iframe>
378
+ </div>
379
+
380
+ <script>
381
+ // get data from URL parameters
382
+ const urlParams = new URLSearchParams(window.location.search);
383
+ const data = urlParams.get("data");
384
+ let parsedData;
385
+ if (data) {
386
+ try {
387
+ parsedData = JSON.parse(atob(data));
388
+ console.log("Data from URL:", parsedData);
389
+ // You can use parsedData here if needed
390
+ } catch (error) {
391
+ console.error("Error parsing data from URL:", error);
392
+ }
393
+ } else {
394
+ alert(
395
+ "Improperly formatted URL. Please ensure the data parameter is present.",
396
+ );
397
+ }
398
+
399
+ const iframe = document.querySelector("#vm-iframe");
400
+
401
+ // Detect if OS is Linux
402
+ const isLinux = parsedData.os === "linux";
403
+ const topBarOffset = isLinux ? 14 : 0;
404
+
405
+ // set overlay width and height to match the given resolution
406
+ const overlayWidth = parsedData.resolution[0];
407
+ const overlayHeight = parsedData.resolution[1];
408
+
409
+ iframe.style.display = "block";
410
+ iframe.src = parsedData.url;
411
+ iframe.style.width = overlayWidth + "px";
412
+ // Increase iframe height by 14px for Linux to account for top bar
413
+ iframe.style.height = overlayHeight + topBarOffset + "px";
414
+
415
+ // Calculate scale factor to fit within window if needed
416
+ const windowWidth = window.innerWidth;
417
+ const windowHeight = window.innerHeight;
418
+ const padding = 20; // Add some padding to prevent cropping
419
+ const scaleX = (windowWidth - padding * 2) / overlayWidth;
420
+ const scaleY = (windowHeight - padding * 2) / overlayHeight;
421
+ const scale = Math.min(scaleX, scaleY, 1); // Don't scale up, only down
422
+
423
+ console.log("window:", {
424
+ width: windowWidth,
425
+ height: windowHeight,
426
+ });
427
+ console.log("overlay:", {
428
+ width: overlayWidth,
429
+ height: overlayHeight,
430
+ });
431
+
432
+ const overlay = document.getElementById("overlay");
433
+ overlay.style.width = overlayWidth + "px";
434
+ overlay.style.height = overlayHeight + "px";
435
+
436
+ console.log("scale", scale);
437
+ // Apply scaling if needed
438
+ if (scale < 1) {
439
+ overlay.style.transform = `scale(${scale})`;
440
+ }
441
+
442
+ // WebSocket connection
443
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
444
+ const ws = new WebSocket(`${protocol}//${window.location.host}`);
445
+
446
+ // WebSocket event handlers
447
+ const eventHandlers = new Map();
448
+
449
+ const addEventHandler = (event, callback) => {
450
+ if (!eventHandlers.has(event)) {
451
+ eventHandlers.set(event, []);
452
+ }
453
+ eventHandlers.get(event).push(callback);
454
+ };
455
+
456
+ // Activity tracking for title updates
457
+ let idleTimeout = null;
458
+ const IDLE_THRESHOLD = 5000; // 5 seconds
459
+
460
+ // Get test file name for title (use just filename, not full path)
461
+ const testFileName = parsedData?.testFile
462
+ ? parsedData.testFile.split('/').pop().split('\\\\').pop()
463
+ : 'TestDriver';
464
+
465
+ const setTitle = (status) => {
466
+ document.title = `[${status}] ${testFileName}`;
467
+ };
468
+
469
+ const resetIdleTimer = () => {
470
+ setTitle("Running");
471
+ if (idleTimeout) clearTimeout(idleTimeout);
472
+ idleTimeout = setTimeout(() => {
473
+ setTitle("Idle");
474
+ }, IDLE_THRESHOLD);
475
+ };
476
+
477
+ ws.addEventListener("message", (message) => {
478
+ resetIdleTimer();
479
+ try {
480
+ const data = JSON.parse(message.data);
481
+ console.log("WebSocket message received:", data);
482
+ const handlers = eventHandlers.get(data.event);
483
+ if (handlers) {
484
+ handlers.forEach((callback) => callback(null, data.data));
485
+ }
486
+ } catch (error) {
487
+ console.error("Error parsing WebSocket message:", error);
488
+ }
489
+ });
490
+
491
+ ws.addEventListener("open", () => {
492
+ console.log("WebSocket connected to TestDriver.ai overlay");
493
+
494
+ // Hide loading screen
495
+ const loadingScreen = document.getElementById("loading-screen");
496
+ loadingScreen.classList.add("hidden");
497
+
498
+ // Show connection status briefly
499
+ document.getElementById("status").textContent = "Connected";
500
+ document.getElementById("status").classList.add("visible");
501
+ setTimeout(() => {
502
+ document.getElementById("status").classList.remove("visible");
503
+ }, 2000);
504
+ });
505
+
506
+ ws.addEventListener("error", (error) => {
507
+ console.error("WebSocket error:", error);
508
+ document.getElementById("status").textContent = "Connection Error";
509
+ document.getElementById("status").classList.add("visible");
510
+ });
511
+
512
+ ws.addEventListener("close", () => {
513
+ console.log("WebSocket disconnected");
514
+ if (idleTimeout) clearTimeout(idleTimeout);
515
+ setTitle("Done");
516
+ document.getElementById("status").textContent = "Disconnected";
517
+ document.getElementById("status").classList.add("visible");
518
+ });
519
+
520
+ // Event handling (same as original)
521
+ const events = {
522
+ mouseClick: "mouse-click",
523
+ mouseMove: "mouse-move",
524
+ screenCapture: {
525
+ start: "screen-capture:start",
526
+ end: "screen-capture:end",
527
+ error: "screen-capture:error",
528
+ },
529
+ interactive: "interactive",
530
+ terminal: {
531
+ stdout: "terminal:stdout",
532
+ stderr: "terminal:stderr",
533
+ },
534
+ matches: {
535
+ show: "matches:show",
536
+ },
537
+ vm: {
538
+ show: "vm:show",
539
+ },
540
+ test: {
541
+ start: "test:start",
542
+ stop: "test:stop",
543
+ success: "test:success",
544
+ error: "test:error",
545
+ },
546
+ error: {
547
+ fatal: "error:fatal",
548
+ general: "error:general",
549
+ sdk: "error:sdk",
550
+ },
551
+ };
552
+
553
+ // Title state handlers for immediate feedback
554
+ addEventHandler(events.test.start, () => {
555
+ if (idleTimeout) clearTimeout(idleTimeout);
556
+ setTitle("Running");
557
+ });
558
+
559
+ addEventHandler(events.test.stop, () => {
560
+ if (idleTimeout) clearTimeout(idleTimeout);
561
+ setTitle("Stopped");
562
+ });
563
+
564
+ addEventHandler(events.test.success, () => {
565
+ if (idleTimeout) clearTimeout(idleTimeout);
566
+ setTitle("Passed");
567
+ });
568
+
569
+ addEventHandler(events.test.error, () => {
570
+ if (idleTimeout) clearTimeout(idleTimeout);
571
+ setTitle("Failed");
572
+ });
573
+
574
+ addEventHandler(events.error.fatal, () => {
575
+ if (idleTimeout) clearTimeout(idleTimeout);
576
+ setTitle("Error");
577
+ });
578
+
579
+ addEventHandler(events.error.sdk, () => {
580
+ if (idleTimeout) clearTimeout(idleTimeout);
581
+ setTitle("Error");
582
+ });
583
+
584
+ const effects = document.getElementById("effects");
585
+ const mouse = document.getElementById("mouse");
586
+ const screenshotElement = document.getElementById("screenshot");
587
+ const interactionOverlay = document.getElementById("interaction-overlay");
588
+
589
+ let boundingBoxesTimeout;
590
+ let interactionTimeout;
591
+ let isInteractionEnabled = false;
592
+
593
+ // Show interaction overlay on hover, hide on click, and bring back after 30 seconds
594
+ const showInteractionOverlay = () => {
595
+ if (!isInteractionEnabled) {
596
+ interactionOverlay.classList.add("visible");
597
+ }
598
+ };
599
+
600
+ const hideInteractionOverlay = () => {
601
+ interactionOverlay.classList.remove("visible");
602
+ isInteractionEnabled = true;
603
+
604
+ // Clear any existing timeout
605
+ if (interactionTimeout) {
606
+ clearTimeout(interactionTimeout);
607
+ }
608
+ };
609
+
610
+ // Event listeners for interaction overlay
611
+ overlay.addEventListener("mouseenter", showInteractionOverlay);
612
+ overlay.addEventListener("mouseleave", () => {
613
+ if (!isInteractionEnabled) {
614
+ interactionOverlay.classList.remove("visible");
615
+ }
616
+ });
617
+
618
+ interactionOverlay.addEventListener("click", hideInteractionOverlay);
619
+
620
+ const drawBoxes = (boxes) => {
621
+ // Remove existing boxes
622
+ document.querySelectorAll(".bounding-box").forEach((box) => {
623
+ box.remove();
624
+ });
625
+
626
+ // Add new boxes
627
+ boxes.forEach((box) => {
628
+ const boxElement = document.createElement("div");
629
+ boxElement.className = "bounding-box";
630
+ boxElement.style.left = toCss(box.x);
631
+ boxElement.style.top = toCss(box.y + topBarOffset);
632
+ boxElement.style.width = toCss(box.width);
633
+ boxElement.style.height = toCss(box.height);
634
+ effects.appendChild(boxElement);
635
+ });
636
+ };
637
+ // Screen capture event handlers
638
+ addEventHandler(events.screenCapture.start, (event, data) => {
639
+ if (data?.silent) return;
640
+ screenshotElement.style.opacity = 0;
641
+ });
642
+
643
+ addEventHandler(events.screenCapture.error, (event, data) => {
644
+ if (data?.silent) return;
645
+ screenshotElement.style.opacity = 1;
646
+ });
647
+
648
+ addEventHandler(events.screenCapture.end, (event, data) => {
649
+ if (data?.silent) return;
650
+ screenshotElement.classList.remove("screenshot");
651
+ // Force reflow
652
+ void screenshotElement.offsetWidth;
653
+ screenshotElement.classList.add("screenshot");
654
+ });
655
+
656
+ // Mouse event handlers
657
+ addEventHandler(events.mouseMove, (event, { x, y } = {}) => {
658
+ mouse.style.marginLeft = toCss(x);
659
+ mouse.style.marginTop = toCss(y + topBarOffset);
660
+ });
661
+
662
+ addEventHandler(
663
+ events.mouseClick,
664
+ (event, { x, y, click = "single" } = {}) => {
665
+ mouse.style.marginLeft = toCss(x);
666
+ mouse.style.marginTop = toCss(y + topBarOffset);
667
+ // Reset class so animation can restart
668
+ mouse.setAttribute("class", "mouse");
669
+ // Force reflow
670
+ void mouse.offsetWidth;
671
+ mouse.classList.add(`${click}-click`);
672
+ },
673
+ );
674
+
675
+ // Matches and VM event handlers
676
+ addEventHandler(events.matches.show, (event, closeMatches = []) => {
677
+ if (boundingBoxesTimeout) clearTimeout(boundingBoxesTimeout);
678
+ drawBoxes(closeMatches);
679
+ boundingBoxesTimeout = setTimeout(() => drawBoxes([]), 10000);
680
+ });
681
+
682
+ addEventHandler(events.terminal.stdout, (event, data) => {
683
+ console.log("Terminal stdout:", data);
684
+ // Could be used to display terminal output in UI
685
+ });
686
+
687
+ addEventHandler(events.terminal.stderr, (event, data) => {
688
+ console.log("Terminal stderr:", data);
689
+ // Could be used to display terminal errors in UI
690
+ });
691
+
692
+ const toCss = (size) => {
693
+ if (typeof size === "number") {
694
+ return `${size}px`;
695
+ }
696
+ return size;
697
+ };
698
+
699
+ // Throttle function to limit how often a function can run
700
+ function throttle(fn, wait) {
701
+ let lastTime = 0;
702
+ return function (...args) {
703
+ const now = Date.now();
704
+ if (now - lastTime >= wait) {
705
+ lastTime = now;
706
+ fn.apply(this, args);
707
+ }
708
+ };
709
+ }
710
+
711
+ const resizeOverlay = () => {
712
+ if (parsedData && parsedData.resolution) {
713
+ const overlayWidth = parsedData.resolution[0];
714
+ const overlayHeight = parsedData.resolution[1];
715
+ const windowWidth = window.innerWidth;
716
+ const windowHeight = window.innerHeight;
717
+ const scaleX = windowWidth / overlayWidth;
718
+ const scaleY = windowHeight / overlayHeight;
719
+ const scale = Math.min(scaleX, scaleY);
720
+
721
+ console.log("scale", scale);
722
+
723
+ console.log(
724
+ "resolution:",
725
+ JSON.stringify({
726
+ width: overlayWidth,
727
+ height: overlayHeight,
728
+ }),
729
+ );
730
+
731
+ console.log(
732
+ "window:",
733
+ JSON.stringify({
734
+ width: windowWidth,
735
+ height: windowHeight,
736
+ }),
737
+ );
738
+ const overlay = document.getElementById("overlay");
739
+ if (scale < 1) {
740
+ overlay.style.transform = `scale(${scale})`;
741
+ } else {
742
+ overlay.style.transform = "none";
743
+ }
744
+
745
+ // get the new width and height after scaling
746
+ const scaledWidth = overlayWidth * scale;
747
+ const scaledHeight = overlayHeight * scale;
748
+
749
+ console.log("scaledWidth", JSON.stringify(scaledWidth));
750
+ console.log("scaledHeight", JSON.stringify(scaledHeight));
751
+
752
+ // use the scaled width and height to center the overlay
753
+ overlay.style.position = "absolute";
754
+ if (scale < 1) {
755
+ // When scaling down, use the original dimensions and let CSS transform handle the scaling
756
+ overlay.style.width = `${overlayWidth}px`;
757
+ overlay.style.height = `${overlayHeight}px`;
758
+ overlay.style.left = `${(windowWidth - scaledWidth) / 2}px`;
759
+ overlay.style.top = `${(windowHeight - scaledHeight) / 2}px`;
760
+ } else {
761
+ // When no scaling is needed, just center the overlay at its original size
762
+ overlay.style.width = `${overlayWidth}px`;
763
+ overlay.style.height = `${overlayHeight}px`;
764
+ overlay.style.left = `${(windowWidth - overlayWidth) / 2}px`;
765
+ overlay.style.top = `${(windowHeight - overlayHeight) / 2}px`;
766
+ }
767
+
768
+ console.log(
769
+ "left,top",
770
+ JSON.stringify({
771
+ left:
772
+ scale < 1
773
+ ? (windowWidth - scaledWidth) / 2
774
+ : (windowWidth - overlayWidth) / 2,
775
+ top:
776
+ scale < 1
777
+ ? (windowHeight - scaledHeight) / 2
778
+ : (windowHeight - overlayHeight) / 2,
779
+ }),
780
+ );
781
+ }
782
+ };
783
+ resizeOverlay();
784
+
785
+ // Handle window resize to recalculate scale (throttled)
786
+ window.addEventListener("resize", throttle(resizeOverlay, 100));
787
+
788
+ setInterval(resizeOverlay, 1000);
789
+
790
+ // Handle window blur/focus for screen locking
791
+ window.addEventListener("blur", () => {
792
+ showInteractionOverlay();
793
+ isInteractionEnabled = false;
794
+ });
795
+ </script>
796
+ </body>
797
+ </html>