Mudrock10 commited on
Commit
d92c728
·
verified ·
1 Parent(s): 3c62519

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +641 -649
index.html CHANGED
@@ -1,669 +1,661 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
-
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Be Your Outpainter</title>
8
- <script src="https://cdn.jsdelivr.net/npm/react@18.0.0/umd/react.development.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/react-dom@18.0.0/umd/react-dom.development.js"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.js"></script>
11
- <script src="https://cdn.tailwindcss.com"></script>
12
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
13
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
14
- <style>
15
- :root {
16
- --primary-color: #6366f1;
17
- --primary-dark: #4f46e5;
18
- --secondary-color: #8b5cf6;
19
- --accent-color: #ec4899;
20
- --background: #0f172a;
21
- --surface: #1e293b;
22
- --surface-light: #334155;
23
- --text-primary: #f1f5f9;
24
- --text-secondary: #94a3b8;
25
- --border-color: #475569;
26
- }
27
-
28
- * {
29
- margin: 0;
30
- padding: 0;
31
- box-sizing: border-box;
32
- }
33
-
34
- body {
35
- font-family: 'Inter', sans-serif;
36
- background: var(--background);
37
- color: var(--text-primary);
38
- min-height: 100vh;
39
- display: flex;
40
- flex-direction: column;
41
- }
42
-
43
- .gradient-bg {
44
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color), var(--accent-color));
45
- background-size: 400% 400%;
46
- animation: gradient 15s ease infinite;
47
- }
48
-
49
- @keyframes gradient {
50
- 0% {
51
- background-position: 0% 50%;
52
- }
53
-
54
- 50% {
55
- background-position: 100% 50%;
56
- }
57
-
58
- 100% {
59
- background-position: 0% 50%;
60
- }
61
- }
62
-
63
- .glass-effect {
64
- background: rgba(30, 41, 59, 0.8);
65
- backdrop-filter: blur(10px);
66
- border: 1px solid rgba(75, 85, 99, 0.3);
67
- }
68
-
69
- .canvas-container {
70
- position: relative;
71
- background: var(--surface);
72
- border-radius: 12px;
73
- overflow: hidden;
74
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3);
75
- }
76
-
77
- .selection-area {
78
- position: absolute;
79
- border: 2px dashed var(--primary-color);
80
- background: rgba(99, 102, 241, 0.1);
81
- pointer-events: none;
82
- }
83
-
84
- .tool-button {
85
- transition: all 0.3s ease;
86
- border: 1px solid transparent;
87
- }
88
-
89
- .tool-button:hover {
90
- transform: translateY(-2px);
91
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
92
- }
93
-
94
- .tool-button.active {
95
- background: var(--primary-color);
96
- color: white;
97
- border-color: var(--primary-dark);
98
- }
99
-
100
- .loading-spinner {
101
- border: 3px solid rgba(255, 255, 255, 0.3);
102
- border-top: 3px solid var(--primary-color);
103
- border-radius: 50%;
104
- width: 40px;
105
- height: 40px;
106
- animation: spin 1s linear infinite;
107
- }
108
-
109
- @keyframes spin {
110
- 0% {
111
- transform: rotate(0deg);
112
- }
113
-
114
- 100% {
115
- transform: rotate(360deg);
116
- }
117
- }
118
-
119
- .pulse-animation {
120
- animation: pulse 2s infinite;
121
- }
122
-
123
- @keyframes pulse {
124
- 0% {
125
- transform: scale(1);
126
- }
127
-
128
- 50% {
129
- transform: scale(1.05);
130
- }
131
-
132
- 100% {
133
- transform: scale(1);
134
- }
135
- }
136
-
137
- .slide-in {
138
- animation: slideIn 0.5s ease-out;
139
- }
140
-
141
- @keyframes slideIn {
142
- from {
143
- transform: translateX(-100%);
144
- opacity: 0;
145
- }
146
-
147
- to {
148
- transform: translateX(0);
149
- opacity: 1;
150
- }
151
- }
152
-
153
- .fade-in {
154
- animation: fadeIn 0.5s ease-out;
155
- }
156
-
157
- @keyframes fadeIn {
158
- from {
159
- opacity: 0;
160
- }
161
-
162
- to {
163
- opacity: 1;
164
- }
165
- }
166
-
167
- .video-container {
168
- position: relative;
169
- width: 100%;
170
- background: var(--surface);
171
- border-radius: 8px;
172
- overflow: hidden;
173
- }
174
-
175
- .video-overlay {
176
- position: absolute;
177
- top: 0;
178
- left: 0;
179
- right: 0;
180
- bottom: 0;
181
- display: flex;
182
- align-items: center;
183
- justify-content: center;
184
- background: rgba(15, 23, 42, 0.7);
185
- }
186
-
187
- .video-controls {
188
- position: absolute;
189
- bottom: 0;
190
- left: 0;
191
- right: 0;
192
- background: rgba(30, 41, 59, 0.9);
193
- padding: 10px;
194
- display: flex;
195
- align-items: center;
196
- gap: 10px;
197
- }
198
-
199
- .custom-top {
200
- position: sticky;
201
- top: 0;
202
- z-index: 50;
203
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
204
- }
205
-
206
- .custom-bottom {
207
- position: sticky;
208
- bottom: 0;
209
- z-index: 50;
210
- box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.1);
211
- }
212
-
213
- .main-content {
214
- flex: 1;
215
- display: flex;
216
- flex-direction: column;
217
- padding: 1rem;
218
- max-width: 100%;
219
- width: 100%;
220
- }
221
-
222
- .tools-sidebar {
223
- width: 280px;
224
- margin-right: 1rem;
225
- }
226
-
227
- .canvas-area {
228
- flex: 1;
229
- min-width: 0;
230
- }
231
-
232
- @media (max-width: 1024px) {
233
- .tools-sidebar {
234
- width: 240px;
235
- }
236
- }
237
-
238
- @media (max-width: 768px) {
239
- .tools-sidebar {
240
- width: 100%;
241
- margin-right: 0;
242
- margin-bottom: 1rem;
243
- }
244
-
245
- .main-content {
246
- flex-direction: column;
247
- }
248
- }
249
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  </head>
251
-
252
  <body>
253
- <div id="root"></div>
254
-
255
- <script type="text/babel">
256
- const { useState, useRef, useEffect } = React;
257
-
258
- const App = () => {
259
- const [selectedVideo, setSelectedVideo] = useState(null);
260
- const [isProcessing, setIsProcessing] = useState(false);
261
- const [resultVideo, setResultVideo] = useState(null);
262
- const [selectedTool, setSelectedTool] = useState('rectangle');
263
- const [isSelecting, setIsSelecting] = useState(false);
264
- const [selection, setSelection] = useState(null);
265
- const [startPoint, setStartPoint] = useState(null);
266
- const [extensionDirection, setExtensionDirection] = useState('right');
267
- const [videoSize, setVideoSize] = useState({ width: 800, height: 600 });
268
- const [history, setHistory] = useState([]);
269
- const [historyIndex, setHistoryIndex] = useState(-1);
270
- const [videoDuration, setVideoDuration] = useState(0);
271
- const [currentTime, setCurrentTime] = useState(0);
272
-
273
- const videoRef = useRef(null);
274
- const fileInputRef = useRef(null);
275
- const canvasRef = useRef(null);
276
-
277
- const handleVideoUpload = (e) => {
278
- const file = e.target.files[0];
279
- if (file && file.type.startsWith('video/')) {
280
- const url = URL.createObjectURL(file);
281
- setSelectedVideo(url);
282
- setResultVideo(null);
283
- setHistory([]);
284
- setHistoryIndex(-1);
285
-
286
- // Load video to get dimensions
287
- const video = document.createElement('video');
288
- video.src = url;
289
- video.onloadedmetadata = () => {
290
- setVideoSize({ width: video.videoWidth, height: video.videoHeight });
291
- setVideoDuration(video.duration);
292
- };
293
- }
294
- };
295
 
296
- const handleVideoMouseDown = (e) => {
297
- if (!selectedVideo || isProcessing) return;
298
-
299
- const rect = videoRef.current.getBoundingClientRect();
300
- const x = e.clientX - rect.left;
301
- const y = e.clientY - rect.top;
302
-
303
- setIsSelecting(true);
304
- setStartPoint({ x, y });
305
- setSelection({ x, y, width: 0, height: 0 });
306
- };
307
 
308
- const handleVideoMouseMove = (e) => {
309
- if (!isSelecting || !startPoint) return;
 
 
 
 
310
 
311
- const rect = videoRef.current.getBoundingClientRect();
312
- const x = e.clientX - rect.left;
313
- const y = e.clientY - rect.top;
314
-
315
- const width = x - startPoint.x;
316
- const height = y - startPoint.y;
 
317
 
318
- setSelection({
319
- x: width < 0 ? x : startPoint.x,
320
- y: height < 0 ? y : startPoint.y,
321
- width: Math.abs(width),
322
- height: Math.abs(height)
323
- });
324
- };
325
-
326
- const handleVideoMouseUp = () => {
327
- if (!isSelecting) return;
328
- setIsSelecting(false);
329
- };
330
 
331
- const handleGenerate = async () => {
332
- if (!selectedVideo || !selection) return;
333
-
334
- setIsProcessing(true);
335
-
336
- // Simulate processing time
337
- setTimeout(() => {
338
- // In a real application, this would call an API
339
- const result = generateMockResult();
340
- setResultVideo(result);
341
- setIsProcessing(false);
342
-
343
- // Save to history
344
- const newHistory = [...history.slice(0, historyIndex + 1), selectedVideo];
345
- setHistory(newHistory);
346
- setHistoryIndex(newHistory.length - 1);
347
- }, 3000);
348
- };
349
-
350
- const generateMockResult = () => {
351
- // Generate a mock result video URL
352
- return URL.createObjectURL(new Blob([], { type: 'video/mp4' }));
353
- };
354
-
355
- const handleDownload = () => {
356
- if (!resultVideo) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
- const link = document.createElement('a');
359
- link.download = 'outpainted-video.mp4';
360
- link.href = resultVideo;
361
- link.click();
362
- };
363
-
364
- const handleUndo = () => {
365
- if (historyIndex > 0) {
366
- setHistoryIndex(historyIndex - 1);
367
- setSelectedVideo(history[historyIndex - 1]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  }
369
- };
370
 
371
- const handleRedo = () => {
372
- if (historyIndex < history.length - 1) {
373
- setHistoryIndex(historyIndex + 1);
374
- setSelectedVideo(history[historyIndex + 1]);
375
- }
376
- };
377
-
378
- const handlePlayPause = () => {
379
- if (videoRef.current) {
380
- if (videoRef.current.paused) {
381
- videoRef.current.play();
382
- } else {
383
- videoRef.current.pause();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  }
385
- }
386
- };
387
 
388
- const handleTimeUpdate = () => {
389
- if (videoRef.current) {
390
- setCurrentTime(videoRef.current.currentTime);
391
- }
392
- };
393
-
394
- const handleSeek = (e) => {
395
- if (videoRef.current) {
396
- videoRef.current.currentTime = e.target.value;
397
- }
398
- };
399
-
400
- const tools = [
401
- { id: 'rectangle', icon: 'fas fa-square', label: 'Rectangle' },
402
- { id: 'circle', icon: 'fas fa-circle', label: 'Circle' },
403
- { id: 'freehand', icon: 'fas fa-pen', label: 'Freehand' }
404
- ];
405
-
406
- const directions = [
407
- { id: 'right', icon: 'fas fa-arrow-right', label: 'Right' },
408
- { id: 'left', icon: 'fas fa-arrow-left', label: 'Left' },
409
- { id: 'top', icon: 'fas fa-arrow-up', label: 'Top' },
410
- { id: 'bottom', icon: 'fas fa-arrow-down', label: 'Bottom' }
411
- ];
412
-
413
- return (
414
- <div className="min-h-screen bg-gray-900 flex flex-col">
415
- {/* Custom Top - Header */}
416
- <div className="custom-top">
417
- <header className="gradient-bg text-white py-4 px-6">
418
- <div className="flex items-center justify-between">
419
- <div className="flex items-center space-x-4">
420
- <h1 className="text-2xl font-bold">Be Your Outpainter</h1>
421
- <span className="text-sm opacity-75">AI-Powered Video Extension</span>
422
- </div>
423
- <a
424
- href="https://huggingface.co/spaces/akhaliq/anycoder"
425
- className="text-sm hover:opacity-80 transition-opacity"
426
- target="_blank"
427
- rel="noopener noreferrer"
428
- >
429
- Built with anycoder
430
- </a>
431
- </div>
432
- </header>
433
- </div>
434
 
435
- {/* Main Content */}
436
- <div className="main-content">
437
- <div className="flex flex-1 gap-6">
438
- {/* Tools Sidebar */}
439
- <div className="tools-sidebar space-y-6">
440
- {/* Upload Section */}
441
- <div className="glass-effect rounded-xl p-6 slide-in">
442
- <h3 className="text-lg font-semibold mb-4">Upload Video</h3>
443
- <input
444
- ref={fileInputRef}
445
- type="file"
446
- accept="video/*"
447
- onChange={handleVideoUpload}
448
- className="hidden"
449
- />
450
- <button
451
- onClick={() => fileInputRef.current.click()}
452
- className="w-full bg-gradient-to-r from-indigo-500 to-purple-600 text-white py-3 px-4 rounded-lg hover:from-indigo-600 hover:to-purple-700 transition-all duration-300 flex items-center justify-center space-x-2"
453
- >
454
- <i className="fas fa-upload"></i>
455
- <span>Choose Video</span>
456
- </button>
457
- {selectedVideo && (
458
- <p className="text-sm text-gray-400 mt-3">Video loaded successfully</p>
459
- )}
460
- </div>
461
-
462
- {/* Selection Tools */}
463
- <div className="glass-effect rounded-xl p-6 slide-in" style={{ animationDelay: '0.1s' }}>
464
- <h3 className="text-lg font-semibold mb-4">Selection Tool</h3>
465
- <div className="space-y-2">
466
- {tools.map(tool => (
467
- <button
468
- key={tool.id}
469
- onClick={() => setSelectedTool(tool.id)}
470
- className={`tool-button w-full text-left px-4 py-3 rounded-lg flex items-center space-x-3 transition-all ${
471
- selectedTool === tool.id ? 'active' : 'bg-gray-700 hover:bg-gray-600'
472
- }`}
473
- >
474
- <i className={tool.icon}></i>
475
- <span>{tool.label}</span>
476
- </button>
477
- ))}
478
- </div>
479
- </div>
480
-
481
- {/* Extension Direction */}
482
- <div className="glass-effect rounded-xl p-6 slide-in" style={{ animationDelay: '0.2s' }}>
483
- <h3 className="text-lg font-semibold mb-4">Extension Direction</h3>
484
- <div className="grid grid-cols-2 gap-2">
485
- {directions.map(dir => (
486
- <button
487
- key={dir.id}
488
- onClick={() => setExtensionDirection(dir.id)}
489
- className={`tool-button px-3 py-2 rounded-lg flex items-center justify-center space-x-2 transition-all ${
490
- extensionDirection === dir.id
491
- ? 'active'
492
- : 'bg-gray-700 hover:bg-gray-600'
493
- }`}
494
- >
495
- <i className={dir.icon}></i>
496
- <span className="text-sm">{dir.label}</span>
497
- </button>
498
- ))}
499
- </div>
500
- </div>
501
-
502
- {/* Actions */}
503
- <div className="glass-effect rounded-xl p-6 slide-in" style={{ animationDelay: '0.3s' }}>
504
- <h3 className="text-lg font-semibold mb-4">Actions</h3>
505
- <div className="space-y-2">
506
- <button
507
- onClick={handleGenerate}
508
- disabled={!selectedVideo || !selection || isProcessing}
509
- className="w-full bg-gradient-to-r from-green-500 to-emerald-600 text-white py-3 px-4 rounded-lg hover:from-green-600 hover:to-emerald-700 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
510
- >
511
- {isProcessing ? (
512
- <>
513
- <div className="loading-spinner"></div>
514
- <span>Processing...</span>
515
- </>
516
- ) : (
517
- <>
518
- <i className="fas fa-magic"></i>
519
- <span>Generate</span>
520
- </>
521
- )}
522
- </button>
523
- <button
524
- onClick={handleDownload}
525
- disabled={!resultVideo}
526
- className="w-full bg-gradient-to-r from-blue-500 to-cyan-600 text-white py-3 px-4 rounded-lg hover:from-blue-600 hover:to-cyan-700 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
527
- >
528
- <i className="fas fa-download"></i>
529
- <span>Download</span>
530
- </button>
531
- </div>
532
- </div>
533
- </div>
534
-
535
- {/* Canvas Area */}
536
- <div className="canvas-area">
537
- <div className="glass-effect rounded-xl p-6 fade-in">
538
- <div className="flex items-center justify-between mb-4">
539
- <h2 className="text-xl font-semibold">Canvas</h2>
540
- <div className="flex space-x-2">
541
- <button
542
- onClick={handleUndo}
543
- disabled={historyIndex <= 0}
544
- className="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-all"
545
- >
546
- <i className="fas fa-undo"></i>
547
- </button>
548
- <button
549
- onClick={handleRedo}
550
- disabled={historyIndex >= history.length - 1}
551
- className="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-all"
552
- >
553
- <i className="fas fa-redo"></i>
554
- </button>
555
- </div>
556
- </div>
557
-
558
- <div className="canvas-container relative">
559
- {selectedVideo ? (
560
- <div className="relative">
561
- <video
562
- ref={videoRef}
563
- src={selectedVideo}
564
- className="max-w-full h-auto"
565
- style={{
566
- maxWidth: '100%',
567
- height: 'auto',
568
- cursor: isProcessing ? 'not-allowed' : 'crosshair'
569
- }}
570
- onMouseDown={handleVideoMouseDown}
571
- onMouseMove={handleVideoMouseMove}
572
- onMouseUp={handleVideoMouseUp}
573
- onMouseLeave={handleVideoMouseUp}
574
- onTimeUpdate={handleTimeUpdate}
575
- />
576
- {selection && (
577
- <div
578
- className="selection-area"
579
- style={{
580
- left: `${selection.x}px`,
581
- top: `${selection.y}px`,
582
- width: `${selection.width}px`,
583
- height: `${selection.height}px`
584
- }}
585
- />
586
- )}
587
- <div className="video-controls">
588
- <button onClick={handlePlayPause} className="text-white hover:text-gray-300">
589
- <i className={`fas ${videoRef.current?.paused ? 'fa-play' : 'fa-pause'}`}></i>
590
- </button>
591
- <input
592
- type="range"
593
- min="0"
594
- max={videoDuration || 100}
595
- value={currentTime}
596
- onChange={handleSeek}
597
- className="flex-1 mx-2"
598
- />
599
- <span className="text-sm text-gray-300">
600
- {Math.floor(currentTime)}s / {Math.floor(videoDuration)}s
601
- </span>
602
- </div>
603
- </div>
604
- ) : (
605
- <div className="flex items-center justify-center h-96 bg-gray-800 rounded-lg">
606
- <div className="text-center">
607
- <i className="fas fa-video text-6xl text-gray-600 mb-4"></i>
608
- <p className="text-gray-400">Upload a video to get started</p>
609
- </div>
610
- </div>
611
- )}
612
- </div>
613
-
614
- {/* Result Display */}
615
- {resultVideo && (
616
- <div className="mt-6 fade-in">
617
- <h3 className="text-lg font-semibold mb-3">Result</h3>
618
- <div className="relative">
619
- <video
620
- src={resultVideo}
621
- className="w-full rounded-lg shadow-lg"
622
- controls
623
- />
624
- <div className="absolute top-2 right-2 bg-green-500 text-white px-3 py-1 rounded-full text-sm">
625
- <i className="fas fa-check mr-1"></i>
626
- Generated
627
- </div>
628
- </div>
629
- </div>
630
- )}
631
- </div>
632
-
633
- {/* Status Bar */}
634
- <div className="mt-4 glass-effect rounded-xl p-4">
635
- <div className="flex items-center justify-between text-sm">
636
- <div className="flex items-center space-x-4">
637
- <span className="text-gray-400">Status:</span>
638
- <span className="text-green-400">
639
- {isProcessing ? 'Processing...' :
640
- resultVideo ? 'Ready' :
641
- selectedVideo ? 'Video loaded' : 'Waiting for input'}
642
- </span>
643
- </div>
644
- {selection && (
645
- <div className="text-gray-400">
646
- Selection: {Math.round(selection.width)} × {Math.round(selection.height)}px
647
- </div>
648
- )}
649
- </div>
650
- </div>
651
- </div>
652
- </div>
653
- </div>
654
 
655
- {/* Custom Bottom - Footer */}
656
- <div className="custom-bottom">
657
- <footer className="py-6 text-center text-gray-500 text-sm glass-effect">
658
- <p>© 2024 Be Your Outpainter. Powered by AI.</p>
659
- </footer>
660
- </div>
661
- </div>
662
- );
663
- };
 
 
664
 
665
- ReactDOM.render(<App />, document.getElementById('root'));
666
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  </body>
668
-
669
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
 
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Multi-frame Fusion for Video Stabilization - CVPR 2024</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,400&display=swap" rel="stylesheet">
12
+
13
+ <!-- FontAwesome -->
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
15
+
16
+ <style>
17
+ :root {
18
+ --primary-color: #2563eb;
19
+ --secondary-color: #1e40af;
20
+ --text-color: #1f2937;
21
+ --text-light: #4b5563;
22
+ --bg-color: #ffffff;
23
+ --bg-alt: #f3f4f6;
24
+ --border-radius: 12px;
25
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
26
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
27
+ --max-width: 1000px;
28
+ }
29
+
30
+ * {
31
+ box-sizing: border-box;
32
+ margin: 0;
33
+ padding: 0;
34
+ }
35
+
36
+ body {
37
+ font-family: 'Inter', sans-serif;
38
+ color: var(--text-color);
39
+ line-height: 1.6;
40
+ background-color: var(--bg-color);
41
+ overflow-x: hidden;
42
+ }
43
+
44
+ /* Header / AnyCoder Link */
45
+ .top-bar {
46
+ background: #111827;
47
+ color: white;
48
+ text-align: center;
49
+ padding: 0.5rem;
50
+ font-size: 0.875rem;
51
+ }
52
+ .top-bar a {
53
+ color: #60a5fa;
54
+ text-decoration: none;
55
+ font-weight: 500;
56
+ transition: color 0.2s;
57
+ }
58
+ .top-bar a:hover {
59
+ color: #93c5fd;
60
+ text-decoration: underline;
61
+ }
62
+
63
+ /* Layout Container */
64
+ .container {
65
+ max-width: var(--max-width);
66
+ margin: 0 auto;
67
+ padding: 2rem 1.5rem;
68
+ }
69
+
70
+ /* Hero Section */
71
+ header {
72
+ text-align: center;
73
+ padding: 3rem 0;
74
+ animation: fadeIn 0.8s ease-out;
75
+ }
76
+
77
+ h1 {
78
+ font-size: clamp(2rem, 5vw, 3rem);
79
+ font-weight: 800;
80
+ line-height: 1.1;
81
+ margin-bottom: 1rem;
82
+ background: linear-gradient(to right, #111827, #374151);
83
+ -webkit-background-clip: text;
84
+ -webkit-text-fill-color: transparent;
85
+ }
86
+
87
+ .conference {
88
+ display: inline-block;
89
+ background: #dbeafe;
90
+ color: #1e40af;
91
+ font-weight: 600;
92
+ padding: 0.25rem 0.75rem;
93
+ border-radius: 9999px;
94
+ margin-bottom: 1.5rem;
95
+ font-size: 1.1rem;
96
+ }
97
+
98
+ .authors {
99
+ display: flex;
100
+ flex-wrap: wrap;
101
+ justify-content: center;
102
+ gap: 1.5rem;
103
+ margin-bottom: 1.5rem;
104
+ font-size: 1.1rem;
105
+ }
106
+
107
+ .author a {
108
+ color: var(--primary-color);
109
+ text-decoration: none;
110
+ transition: color 0.2s;
111
+ }
112
+ .author a:hover {
113
+ color: var(--secondary-color);
114
+ text-decoration: underline;
115
+ }
116
+
117
+ .affiliations {
118
+ color: var(--text-light);
119
+ font-size: 0.95rem;
120
+ margin-bottom: 2rem;
121
+ }
122
+
123
+ /* Action Buttons */
124
+ .actions {
125
+ display: flex;
126
+ justify-content: center;
127
+ gap: 1rem;
128
+ flex-wrap: wrap;
129
+ margin-bottom: 3rem;
130
+ }
131
+
132
+ .btn {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ gap: 0.5rem;
136
+ background-color: #111827;
137
+ color: white;
138
+ padding: 0.75rem 1.5rem;
139
+ border-radius: 9999px;
140
+ text-decoration: none;
141
+ font-weight: 500;
142
+ transition: all 0.2s;
143
+ box-shadow: var(--shadow);
144
+ }
145
+
146
+ .btn:hover {
147
+ transform: translateY(-2px);
148
+ box-shadow: var(--shadow-lg);
149
+ background-color: #1f2937;
150
+ }
151
+
152
+ .btn.secondary {
153
+ background-color: white;
154
+ color: var(--text-color);
155
+ border: 1px solid #e5e7eb;
156
+ }
157
+ .btn.secondary:hover {
158
+ background-color: #f9fafb;
159
+ }
160
+
161
+ /* Teaser Video */
162
+ .teaser {
163
+ width: 100%;
164
+ border-radius: var(--border-radius);
165
+ overflow: hidden;
166
+ box-shadow: var(--shadow-lg);
167
+ background: #000;
168
+ aspect-ratio: 16/9;
169
+ position: relative;
170
+ margin-bottom: 3rem;
171
+ }
172
+
173
+ .teaser-placeholder {
174
+ width: 100%;
175
+ height: 100%;
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: center;
179
+ background: linear-gradient(45deg, #1f2937, #111827);
180
+ color: white;
181
+ flex-direction: column;
182
+ }
183
+
184
+ /* Abstract */
185
+ .abstract-section {
186
+ background: var(--bg-alt);
187
+ padding: 2.5rem;
188
+ border-radius: var(--border-radius);
189
+ margin-bottom: 3rem;
190
+ }
191
+
192
+ .section-title {
193
+ text-align: center;
194
+ font-size: 1.75rem;
195
+ font-weight: 700;
196
+ margin-bottom: 1.5rem;
197
+ position: relative;
198
+ display: inline-block;
199
+ left: 50%;
200
+ transform: translateX(-50%);
201
+ }
202
+
203
+ .section-title::after {
204
+ content: '';
205
+ display: block;
206
+ width: 60%;
207
+ height: 3px;
208
+ background: var(--primary-color);
209
+ margin: 0.5rem auto 0;
210
+ border-radius: 2px;
211
+ }
212
+
213
+ .abstract-text {
214
+ text-align: justify;
215
+ font-size: 1.05rem;
216
+ color: var(--text-light);
217
+ }
218
+
219
+ /* Method Section */
220
+ .method-section {
221
+ margin-bottom: 4rem;
222
+ }
223
+
224
+ .method-image {
225
+ width: 100%;
226
+ height: auto;
227
+ border-radius: var(--border-radius);
228
+ margin: 1.5rem 0;
229
+ border: 1px solid #e5e7eb;
230
+ box-shadow: var(--shadow);
231
+ }
232
+
233
+ /* Comparison Slider */
234
+ .comparison-section {
235
+ margin-bottom: 4rem;
236
+ }
237
+
238
+ .comparison-container {
239
+ position: relative;
240
+ width: 100%;
241
+ max-width: 800px;
242
+ aspect-ratio: 16/9;
243
+ margin: 0 auto;
244
+ border-radius: var(--border-radius);
245
+ overflow: hidden;
246
+ box-shadow: var(--shadow-lg);
247
+ cursor: ew-resize;
248
+ }
249
+
250
+ .img-comp-img {
251
+ position: absolute;
252
+ width: 100%;
253
+ height: 100%;
254
+ overflow: hidden;
255
+ }
256
+
257
+ .img-comp-img img {
258
+ display: block;
259
+ width: 100%;
260
+ height: 100%;
261
+ object-fit: cover;
262
+ }
263
+
264
+ .img-comp-overlay {
265
+ width: 50%; /* Initial position */
266
+ z-index: 2;
267
+ border-right: 3px solid white;
268
+ }
269
+
270
+ .slider-handle {
271
+ position: absolute;
272
+ top: 50%;
273
+ left: 50%; /* Should match overlay width */
274
+ transform: translate(-50%, -50%);
275
+ width: 40px;
276
+ height: 40px;
277
+ background: white;
278
+ border-radius: 50%;
279
+ z-index: 10;
280
+ display: flex;
281
+ align-items: center;
282
+ justify-content: center;
283
+ box-shadow: 0 0 10px rgba(0,0,0,0.5);
284
+ pointer-events: none; /* Let clicks pass to container */
285
+ }
286
+
287
+ .slider-labels {
288
+ position: absolute;
289
+ bottom: 1rem;
290
+ width: 100%;
291
+ display: flex;
292
+ justify-content: space-between;
293
+ padding: 0 1.5rem;
294
+ z-index: 20;
295
+ pointer-events: none;
296
+ color: white;
297
+ font-weight: 700;
298
+ text-shadow: 0 2px 4px rgba(0,0,0,0.8);
299
+ }
300
+
301
+ /* Results Grid */
302
+ .results-grid {
303
+ display: grid;
304
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
305
+ gap: 1.5rem;
306
+ margin-bottom: 3rem;
307
+ }
308
+
309
+ .result-item {
310
+ background: white;
311
+ border-radius: var(--border-radius);
312
+ overflow: hidden;
313
+ border: 1px solid #e5e7eb;
314
+ }
315
+
316
+ .result-video-placeholder {
317
+ width: 100%;
318
+ aspect-ratio: 16/9;
319
+ background: #eee;
320
+ display: flex;
321
+ align-items: center;
322
+ justify-content: center;
323
+ color: #999;
324
+ position: relative;
325
+ }
326
+
327
+ .result-video-placeholder::before {
328
+ content: '\f04b';
329
+ font-family: "Font Awesome 6 Free";
330
+ font-weight: 900;
331
+ font-size: 2rem;
332
+ opacity: 0.5;
333
+ }
334
+
335
+ .result-caption {
336
+ padding: 1rem;
337
+ text-align: center;
338
+ font-weight: 500;
339
+ font-size: 0.9rem;
340
+ }
341
+
342
+ /* BibTeX */
343
+ .bibtex-section {
344
+ background: #f8fafc;
345
+ padding: 2rem;
346
+ border-radius: var(--border-radius);
347
+ border: 1px solid #e2e8f0;
348
+ }
349
+
350
+ pre {
351
+ background: #1e293b;
352
+ color: #e2e8f0;
353
+ padding: 1.5rem;
354
+ border-radius: 8px;
355
+ overflow-x: auto;
356
+ font-family: 'Courier New', Courier, monospace;
357
+ font-size: 0.85rem;
358
+ position: relative;
359
+ }
360
+
361
+ .copy-btn {
362
+ position: absolute;
363
+ top: 0.5rem;
364
+ right: 0.5rem;
365
+ background: rgba(255,255,255,0.1);
366
+ border: none;
367
+ color: white;
368
+ padding: 0.25rem 0.5rem;
369
+ border-radius: 4px;
370
+ cursor: pointer;
371
+ font-size: 0.75rem;
372
+ }
373
+ .copy-btn:hover {
374
+ background: rgba(255,255,255,0.2);
375
+ }
376
+
377
+ /* Footer */
378
+ footer {
379
+ text-align: center;
380
+ padding: 3rem 0;
381
+ color: var(--text-light);
382
+ font-size: 0.9rem;
383
+ border-top: 1px solid #e5e7eb;
384
+ margin-top: 3rem;
385
+ }
386
+
387
+ /* Animations */
388
+ @keyframes fadeIn {
389
+ from { opacity: 0; transform: translateY(20px); }
390
+ to { opacity: 1; transform: translateY(0); }
391
+ }
392
+
393
+ /* Responsive */
394
+ @media (max-width: 768px) {
395
+ h1 { font-size: 2rem; }
396
+ .authors { flex-direction: column; gap: 0.5rem; align-items: center; }
397
+ .slider-labels { font-size: 0.8rem; }
398
+ }
399
+ </style>
400
  </head>
 
401
  <body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
+ <div class="top-bar">
404
+ Built with anycoder &mdash; <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">View on Hugging Face</a>
405
+ </div>
 
 
 
 
 
 
 
 
406
 
407
+ <div class="container">
408
+
409
+ <!-- Header -->
410
+ <header>
411
+ <div class="conference">CVPR 2024</div>
412
+ <h1>3D Multi-frame Fusion for<br>Video Stabilization</h1>
413
 
414
+ <div class="authors">
415
+ <div class="author"><a href="#">Alex Johnson*</a></div>
416
+ <div class="author"><a href="#">Sarah Chen*</a></div>
417
+ <div class="author"><a href="#">Michael Roberts</a></div>
418
+ <div class="author"><a href="#">Emily Davis</a></div>
419
+ <div class="author"><a href="#">David Zhang</a></div>
420
+ </div>
421
 
422
+ <div class="affiliations">
423
+ University of Computer Vision, Tech Institute AI Lab<br>
424
+ *Equal Contribution
425
+ </div>
 
 
 
 
 
 
 
 
426
 
427
+ <div class="actions">
428
+ <a href="#" class="btn"><i class="fa-regular fa-file-pdf"></i> Paper</a>
429
+ <a href="#" class="btn"><i class="fa-brands fa-arxiv"></i> arXiv</a>
430
+ <a href="#" class="btn secondary"><i class="fa-brands fa-github"></i> Code</a>
431
+ <a href="#" class="btn secondary"><i class="fa-solid fa-database"></i> Dataset</a>
432
+ </div>
433
+ </header>
434
+
435
+ <!-- Teaser Video -->
436
+ <div class="teaser">
437
+ <!-- In a real scenario, this would be a <video> tag -->
438
+ <div class="teaser-placeholder">
439
+ <i class="fa-solid fa-film fa-3x" style="margin-bottom: 1rem;"></i>
440
+ <h2>Teaser Video</h2>
441
+ <p>Demonstrating robust 3D stabilization across dynamic scenes</p>
442
+ <div style="margin-top: 1rem; font-size: 0.8rem; opacity: 0.7;">(Video playback simulated)</div>
443
+ </div>
444
+ </div>
445
+
446
+ <!-- Abstract -->
447
+ <section class="abstract-section">
448
+ <h2 class="section-title">Abstract</h2>
449
+ <p class="abstract-text">
450
+ Video stabilization remains a challenging problem, particularly for handheld footage with large motion and dynamic content. Traditional 2D methods often suffer from distortion and cropping, while existing 3D methods struggle with robustness in complex environments. We present a novel <strong>3D Multi-frame Fusion</strong> framework that leverages scene geometry and temporal consistency to generate high-quality stabilized video. By integrating a dense depth prior with a multi-frame optimization strategy, our method effectively decouples camera motion from scene dynamics. We introduce a differentiable warping module that synthesizes full-frame stabilized views by fusing information from adjacent frames, significantly reducing the need for cropping. Extensive experiments on public benchmarks demonstrate that our approach outperforms state-of-the-art methods in both stability and visual quality metrics.
451
+ </p>
452
+ </section>
453
+
454
+ <!-- Method -->
455
+ <section class="method-section">
456
+ <h2 class="section-title">Methodology</h2>
457
+ <div style="text-align: center; margin-bottom: 1rem;">
458
+ <svg width="100%" height="300" viewBox="0 0 800 300" style="background: white; border-radius: 12px; border: 1px solid #eee;">
459
+ <!-- Simplified SVG representation of a pipeline -->
460
+ <rect x="50" y="100" width="100" height="100" rx="10" fill="#dbeafe" stroke="#2563eb" stroke-width="2"/>
461
+ <text x="100" y="155" text-anchor="middle" font-size="14" fill="#1e40af">Input Frames</text>
462
+
463
+ <path d="M160 150 L200 150" stroke="#9ca3af" stroke-width="2" marker-end="url(#arrow)"/>
464
+
465
+ <rect x="210" y="80" width="120" height="140" rx="10" fill="#fef3c7" stroke="#d97706" stroke-width="2"/>
466
+ <text x="270" y="145" text-anchor="middle" font-size="14" fill="#92400e">3D Motion</text>
467
+ <text x="270" y="165" text-anchor="middle" font-size="14" fill="#92400e">Estimation</text>
468
+
469
+ <path d="M340 150 L380 150" stroke="#9ca3af" stroke-width="2" marker-end="url(#arrow)"/>
470
+
471
+ <rect x="390" y="80" width="140" height="140" rx="10" fill="#d1fae5" stroke="#059669" stroke-width="2"/>
472
+ <text x="460" y="145" text-anchor="middle" font-size="14" fill="#065f46">Multi-frame</text>
473
+ <text x="460" y="165" text-anchor="middle" font-size="14" fill="#065f46">Fusion Module</text>
474
+
475
+ <path d="M540 150 L580 150" stroke="#9ca3af" stroke-width="2" marker-end="url(#arrow)"/>
476
+
477
+ <rect x="590" y="100" width="120" height="100" rx="10" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
478
+ <text x="650" y="155" text-anchor="middle" font-size="14" fill="#5b21b6">Stabilized Output</text>
479
+
480
+ <defs>
481
+ <marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
482
+ <path d="M0,0 L0,6 L9,3 z" fill="#9ca3af" />
483
+ </marker>
484
+ </defs>
485
+ </svg>
486
+ </div>
487
+ <p class="abstract-text">
488
+ Our pipeline consists of three main stages: (1) <strong>Robust 3D Trajectory Estimation</strong> using a learned depth prior to handle dynamic objects; (2) <strong>Optimal Path Planning</strong> that smooths the camera trajectory while respecting field-of-view constraints; and (3) <strong>Neural Multi-frame Rendering</strong>, which fuses pixels from temporal neighbors to fill in missing regions caused by stabilization warping, ensuring full-frame output without cropping.
489
+ </p>
490
+ </section>
491
+
492
+ <!-- Interactive Comparison -->
493
+ <section class="comparison-section">
494
+ <h2 class="section-title">Visual Comparison</h2>
495
+ <p style="text-align: center; margin-bottom: 2rem; color: var(--text-light);">Drag the slider to compare Input vs. Our Stabilized Result.</p>
496
 
497
+ <div class="comparison-container" id="comparison1">
498
+ <div class="img-comp-img">
499
+ <!-- Placeholder for Stabilized Result (Right side reveals this) -->
500
+ <div style="width: 100%; height: 100%; background: url('https://images.unsplash.com/photo-1492691527719-9d1e07e534b4?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80') center/cover;">
501
+ <div style="position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: #4ade80; padding: 5px 10px; border-radius: 4px; font-weight: bold;">Ours (Stabilized)</div>
502
+ </div>
503
+ </div>
504
+ <div class="img-comp-img img-comp-overlay">
505
+ <!-- Placeholder for Input (Left side) -->
506
+ <div style="width: 100%; height: 100%; background: url('https://images.unsplash.com/photo-1492691527719-9d1e07e534b4?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80') center/cover; filter: blur(4px) hue-rotate(15deg);">
507
+ <div style="position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7); color: white; padding: 5px 10px; border-radius: 4px; font-weight: bold;">Input (Shaky)</div>
508
+ </div>
509
+ </div>
510
+ <div class="slider-handle">
511
+ <i class="fa-solid fa-arrows-left-right" style="color: #333;"></i>
512
+ </div>
513
+ </div>
514
+ </section>
515
+
516
+ <!-- Qualitative Results -->
517
+ <section>
518
+ <h2 class="section-title">Qualitative Results</h2>
519
+ <div class="results-grid">
520
+ <div class="result-item">
521
+ <div class="result-video-placeholder"></div>
522
+ <div class="result-caption">Walking Scene (Low Light)</div>
523
+ </div>
524
+ <div class="result-item">
525
+ <div class="result-video-placeholder"></div>
526
+ <div class="result-caption">Running Scene (Dynamic Objects)</div>
527
+ </div>
528
+ <div class="result-item">
529
+ <div class="result-video-placeholder"></div>
530
+ <div class="result-caption">Panning Shot (Parallax)</div>
531
+ </div>
532
+ </div>
533
+ </section>
534
+
535
+ <!-- BibTeX -->
536
+ <section class="bibtex-section">
537
+ <h2 class="section-title" style="margin-bottom: 1rem;">Citation</h2>
538
+ <pre><button class="copy-btn" onclick="copyBibtex()">Copy</button>@inproceedings{johnson20243dmultiframe,
539
+ title={3D Multi-frame Fusion for Video Stabilization},
540
+ author={Johnson, Alex and Chen, Sarah and Roberts, Michael and Davis, Emily and Zhang, David},
541
+ booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
542
+ year={2024}
543
+ }</pre>
544
+ </section>
545
+
546
+ <footer>
547
+ <div class="social-links" style="margin-bottom: 1rem;">
548
+ <a href="#" style="color: inherit; margin: 0 10px; font-size: 1.2rem;"><i class="fa-brands fa-twitter"></i></a>
549
+ <a href="#" style="color: inherit; margin: 0 10px; font-size: 1.2rem;"><i class="fa-brands fa-github"></i></a>
550
+ <a href="#" style="color: inherit; margin: 0 10px; font-size: 1.2rem;"><i class="fa-solid fa-envelope"></i></a>
551
+ </div>
552
+ <p>&copy; 2024 Project Team. All rights reserved.</p>
553
+ <p>The website template is designed for academic project pages.</p>
554
+ </footer>
555
+
556
+ </div>
557
+
558
+ <script>
559
+ // Image Comparison Slider Logic
560
+ function initComparisons() {
561
+ var x, i;
562
+ /* Find all elements with an "overlay" class: */
563
+ x = document.getElementsByClassName("img-comp-overlay");
564
+ for (i = 0; i < x.length; i++) {
565
+ /* Once for each "overlay" element:
566
+ pass the "overlay" element as a parameter when executing the compareImages function: */
567
+ compareImages(x[i]);
568
  }
 
569
 
570
+ function compareImages(img) {
571
+ var slider, clicked = 0, w, h;
572
+
573
+ /* Get the width and height of the img element */
574
+ w = img.offsetWidth;
575
+ h = img.offsetHeight;
576
+
577
+ /* Set the width of the img element to 50%: */
578
+ img.style.width = (w / 2) + "px";
579
+
580
+ /* Create slider handle: */
581
+ // Note: The handle is already in HTML, we just need to position it based on logic
582
+ // But for pure JS resizing, let's find the handle sibling
583
+ var container = img.parentElement;
584
+ var handle = container.querySelector('.slider-handle');
585
+
586
+ /* Execute a function when the mouse button is pressed: */
587
+ container.addEventListener("mousedown", slideReady);
588
+ /* And another function when the mouse button is released: */
589
+ window.addEventListener("mouseup", slideFinish);
590
+ /* Or touched (for touch screens: */
591
+ container.addEventListener("touchstart", slideReady);
592
+ /* And released (for touch screens: */
593
+ window.addEventListener("touchend", slideFinish);
594
+
595
+ function slideReady(e) {
596
+ /* Prevent any other actions that may occur when moving over the image: */
597
+ e.preventDefault();
598
+ /* The slider is now clicked and ready to move: */
599
+ clicked = 1;
600
+ /* Execute a function when the slider is moved: */
601
+ window.addEventListener("mousemove", slideMove);
602
+ window.addEventListener("touchmove", slideMove);
603
  }
 
 
604
 
605
+ function slideFinish() {
606
+ /* The slider is no longer clicked: */
607
+ clicked = 0;
608
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
 
610
+ function slideMove(e) {
611
+ var pos;
612
+ /* If the slider is no longer clicked, exit this function: */
613
+ if (clicked == 0) return false;
614
+ /* Get the cursor's x position: */
615
+ pos = getCursorPos(e);
616
+ /* Prevent the slider from being positioned outside the image: */
617
+ if (pos < 0) pos = 0;
618
+ if (pos > w) pos = w;
619
+ /* Execute a function that will resize the overlay image according to the cursor: */
620
+ slide(pos);
621
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
 
623
+ function getCursorPos(e) {
624
+ var a, x = 0;
625
+ e = (e.changedTouches) ? e.changedTouches[0] : e;
626
+ /* Get the x positions of the image: */
627
+ a = img.getBoundingClientRect();
628
+ /* Calculate the cursor's x coordinate, relative to the image: */
629
+ x = e.pageX - a.left;
630
+ /* Consider any page scrolling: */
631
+ x = x - window.pageXOffset;
632
+ return x;
633
+ }
634
 
635
+ function slide(x) {
636
+ /* Resize the image: */
637
+ img.style.width = x + "px";
638
+ /* Position the slider: */
639
+ handle.style.left = (x / w * 100) + "%";
640
+ }
641
+ }
642
+ }
643
+
644
+ // Initialize comparison sliders
645
+ initComparisons();
646
+
647
+ // Copy BibTeX Function
648
+ function copyBibtex() {
649
+ const text = document.querySelector('pre').innerText.replace('Copy', ''); // Remove button text
650
+ navigator.clipboard.writeText(text).then(() => {
651
+ const btn = document.querySelector('.copy-btn');
652
+ const originalText = btn.innerText;
653
+ btn.innerText = 'Copied!';
654
+ setTimeout(() => {
655
+ btn.innerText = originalText;
656
+ }, 2000);
657
+ });
658
+ }
659
+ </script>
660
  </body>
 
661
  </html>