jerrycans commited on
Commit
6864d4c
·
verified ·
1 Parent(s): d279b59

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -525
app.py CHANGED
@@ -75,7 +75,6 @@ def load_links():
75
  return links
76
 
77
  def download_images():
78
- # Check if already downloaded
79
  existing_images = get_images()
80
  if existing_images:
81
  update_state(
@@ -88,7 +87,6 @@ def download_images():
88
  )
89
  return
90
 
91
- # Load links
92
  links = load_links()
93
  if not links:
94
  update_state(status="error", message="No links found in links.txt")
@@ -133,7 +131,6 @@ def download_images():
133
  print(f"Failed to download {url}: {e}")
134
  continue
135
 
136
- # Complete
137
  images = get_images()
138
  if images:
139
  update_state(
@@ -145,12 +142,11 @@ def download_images():
145
  else:
146
  update_state(status="error", message="No images downloaded")
147
 
148
- # HTML Template - Enhanced Cool UI
149
  HTML_PAGE = '''<!DOCTYPE html>
150
  <html lang="en">
151
  <head>
152
  <meta charset="UTF-8">
153
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
154
  <title>EPSTEIN FILES</title>
155
  <style>
156
  * {
@@ -166,33 +162,14 @@ HTML_PAGE = '''<!DOCTYPE html>
166
  color: #fff;
167
  }
168
 
169
- /* Scanline effect */
170
- body::before {
171
- content: "";
172
- position: fixed;
173
- top: 0;
174
- left: 0;
175
- width: 100%;
176
- height: 100%;
177
- pointer-events: none;
178
- background: repeating-linear-gradient(
179
- 0deg,
180
- rgba(0, 0, 0, 0.15),
181
- rgba(0, 0, 0, 0.15) 1px,
182
- transparent 1px,
183
- transparent 2px
184
- );
185
- z-index: 10000;
186
- }
187
-
188
  #header {
189
  background: #000;
190
  color: #fff;
191
  text-align: center;
192
- padding: 25px 20px;
193
- font-size: 28px;
194
  font-weight: 800;
195
- letter-spacing: 12px;
196
  position: sticky;
197
  top: 0;
198
  z-index: 100;
@@ -200,33 +177,14 @@ HTML_PAGE = '''<!DOCTYPE html>
200
  text-transform: uppercase;
201
  }
202
 
203
- #header span {
204
- display: inline-block;
205
- animation: glitch 3s infinite;
206
- }
207
-
208
- @keyframes glitch {
209
- 0%, 90%, 100% { transform: translate(0); opacity: 1; }
210
- 91% { transform: translate(-2px, 1px); opacity: 0.8; }
211
- 92% { transform: translate(2px, -1px); opacity: 0.9; }
212
- 93% { transform: translate(-1px, 2px); opacity: 0.8; }
213
- 94% { transform: translate(0); opacity: 1; }
214
- }
215
-
216
- .classified-banner {
217
  background: #fff;
218
  color: #000;
219
  text-align: center;
220
- padding: 8px;
221
  font-size: 11px;
222
  font-weight: 700;
223
- letter-spacing: 4px;
224
- animation: flash 2s infinite;
225
- }
226
-
227
- @keyframes flash {
228
- 0%, 100% { opacity: 1; }
229
- 50% { opacity: 0.7; }
230
  }
231
 
232
  /* ===== LOADING SCREEN ===== */
@@ -235,17 +193,15 @@ HTML_PAGE = '''<!DOCTYPE html>
235
  flex-direction: column;
236
  align-items: center;
237
  justify-content: center;
238
- min-height: calc(100vh - 100px);
239
  padding: 40px 20px;
240
- position: relative;
241
  }
242
 
243
- /* Epstein Image Loading Animation */
244
  .epstein-loader {
245
  position: relative;
246
- width: 200px;
247
- height: 200px;
248
- margin-bottom: 40px;
249
  }
250
 
251
  .epstein-image {
@@ -253,116 +209,42 @@ HTML_PAGE = '''<!DOCTYPE html>
253
  height: 100%;
254
  object-fit: cover;
255
  filter: grayscale(100%) contrast(1.2);
256
- animation: imagePulse 2s ease-in-out infinite;
257
- border: 4px solid #fff;
258
- }
259
-
260
- @keyframes imagePulse {
261
- 0%, 100% {
262
- filter: grayscale(100%) contrast(1.2) brightness(1);
263
- transform: scale(1);
264
- }
265
- 50% {
266
- filter: grayscale(100%) contrast(1.5) brightness(1.3);
267
- transform: scale(1.02);
268
- }
269
- }
270
-
271
- .scan-line {
272
- position: absolute;
273
- top: 0;
274
- left: 0;
275
- width: 100%;
276
- height: 4px;
277
- background: linear-gradient(90deg, transparent, #fff, transparent);
278
- animation: scan 2s linear infinite;
279
- box-shadow: 0 0 20px #fff, 0 0 40px #fff;
280
- }
281
-
282
- @keyframes scan {
283
- 0% { top: 0; opacity: 1; }
284
- 100% { top: 100%; opacity: 0.5; }
285
- }
286
-
287
- .corner-bracket {
288
- position: absolute;
289
- width: 20px;
290
- height: 20px;
291
  border: 3px solid #fff;
292
  }
293
 
294
- .corner-bracket.tl { top: -8px; left: -8px; border-right: none; border-bottom: none; }
295
- .corner-bracket.tr { top: -8px; right: -8px; border-left: none; border-bottom: none; }
296
- .corner-bracket.bl { bottom: -8px; left: -8px; border-right: none; border-top: none; }
297
- .corner-bracket.br { bottom: -8px; right: -8px; border-left: none; border-top: none; }
298
-
299
- .rotating-ring {
300
  position: absolute;
301
- top: -20px;
302
- left: -20px;
303
- right: -20px;
304
- bottom: -20px;
305
- border: 2px dashed rgba(255,255,255,0.3);
306
- animation: rotate 10s linear infinite;
307
  }
308
 
309
- @keyframes rotate {
310
- from { transform: rotate(0deg); }
311
- to { transform: rotate(360deg); }
312
- }
313
 
314
  .progress-card {
315
  width: 100%;
316
- max-width: 500px;
317
  background: #000;
318
  border: 3px solid #fff;
319
- padding: 40px;
320
- position: relative;
321
- }
322
-
323
- .progress-card::before {
324
- content: "TOP SECRET";
325
- position: absolute;
326
- top: -12px;
327
- left: 50%;
328
- transform: translateX(-50%);
329
- background: #fff;
330
- color: #000;
331
- padding: 4px 20px;
332
- font-size: 10px;
333
- font-weight: 800;
334
- letter-spacing: 3px;
335
- }
336
-
337
- .progress-title {
338
- font-size: 14px;
339
- font-weight: 700;
340
- letter-spacing: 4px;
341
- color: #888;
342
- margin-bottom: 25px;
343
- text-transform: uppercase;
344
- text-align: center;
345
  }
346
 
347
  .progress-status {
348
- font-size: 18px;
349
  font-weight: 700;
350
- letter-spacing: 6px;
351
  color: #fff;
352
  margin-bottom: 20px;
353
  text-transform: uppercase;
354
  text-align: center;
355
- animation: textPulse 1.5s ease-in-out infinite;
356
- }
357
-
358
- @keyframes textPulse {
359
- 0%, 100% { opacity: 1; }
360
- 50% { opacity: 0.6; }
361
  }
362
 
363
  .progress-bar-outer {
364
  width: 100%;
365
- height: 40px;
366
  background: #111;
367
  border: 2px solid #fff;
368
  position: relative;
@@ -374,29 +256,6 @@ HTML_PAGE = '''<!DOCTYPE html>
374
  background: #fff;
375
  width: 0%;
376
  transition: width 0.3s ease-out;
377
- position: relative;
378
- }
379
-
380
- .progress-bar-inner::after {
381
- content: "";
382
- position: absolute;
383
- top: 0;
384
- left: 0;
385
- right: 0;
386
- bottom: 0;
387
- background: repeating-linear-gradient(
388
- -45deg,
389
- transparent,
390
- transparent 10px,
391
- rgba(0,0,0,0.1) 10px,
392
- rgba(0,0,0,0.1) 20px
393
- );
394
- animation: moveStripes 1s linear infinite;
395
- }
396
-
397
- @keyframes moveStripes {
398
- from { background-position: 0 0; }
399
- to { background-position: 40px 0; }
400
  }
401
 
402
  .progress-percent {
@@ -404,29 +263,11 @@ HTML_PAGE = '''<!DOCTYPE html>
404
  top: 50%;
405
  left: 50%;
406
  transform: translate(-50%, -50%);
407
- font-size: 16px;
408
  font-weight: 800;
409
  color: #fff;
410
  z-index: 2;
411
  mix-blend-mode: difference;
412
- letter-spacing: 2px;
413
- }
414
-
415
- .progress-detail {
416
- margin-top: 25px;
417
- font-size: 12px;
418
- color: #aaa;
419
- text-align: center;
420
- min-height: 20px;
421
- letter-spacing: 1px;
422
- }
423
-
424
- .progress-subdetail {
425
- margin-top: 10px;
426
- font-size: 11px;
427
- color: #666;
428
- text-align: center;
429
- font-family: 'Courier New', monospace;
430
  }
431
 
432
  .file-counter {
@@ -434,157 +275,55 @@ HTML_PAGE = '''<!DOCTYPE html>
434
  justify-content: center;
435
  gap: 5px;
436
  margin-top: 20px;
437
- font-size: 24px;
438
  font-weight: 800;
439
- letter-spacing: 2px;
440
- }
441
-
442
- .file-counter .current {
443
- color: #fff;
444
- }
445
-
446
- .file-counter .separator {
447
- color: #444;
448
- }
449
-
450
- .file-counter .total {
451
- color: #666;
452
- }
453
-
454
- .phase-indicator {
455
- display: flex;
456
- justify-content: center;
457
- gap: 40px;
458
- margin-top: 30px;
459
- padding-top: 25px;
460
- border-top: 1px solid #333;
461
- }
462
-
463
- .phase {
464
- display: flex;
465
- flex-direction: column;
466
- align-items: center;
467
- gap: 10px;
468
- font-size: 10px;
469
- color: #444;
470
- text-transform: uppercase;
471
- letter-spacing: 2px;
472
- }
473
-
474
- .phase.active {
475
- color: #fff;
476
- }
477
-
478
- .phase.done {
479
- color: #4f4;
480
- }
481
-
482
- .phase-dot {
483
- width: 16px;
484
- height: 16px;
485
- border: 2px solid currentColor;
486
- background: transparent;
487
- position: relative;
488
- }
489
-
490
- .phase.active .phase-dot {
491
- background: #fff;
492
- box-shadow: 0 0 20px #fff;
493
- animation: phasePulse 1s infinite;
494
- }
495
-
496
- .phase.done .phase-dot {
497
- background: #4f4;
498
- box-shadow: 0 0 10px #4f4;
499
- }
500
-
501
- .phase.done .phase-dot::after {
502
- content: "✓";
503
- position: absolute;
504
- top: 50%;
505
- left: 50%;
506
- transform: translate(-50%, -50%);
507
- font-size: 10px;
508
- color: #000;
509
- }
510
-
511
- @keyframes phasePulse {
512
- 0%, 100% { box-shadow: 0 0 10px #fff; }
513
- 50% { box-shadow: 0 0 30px #fff, 0 0 50px #fff; }
514
- }
515
-
516
- .loading-dots {
517
- display: inline-block;
518
  }
519
 
520
- .loading-dots::after {
521
- content: '';
522
- animation: dots 1.5s steps(4, end) infinite;
523
- }
524
 
525
- @keyframes dots {
526
- 0% { content: ''; }
527
- 25% { content: '.'; }
528
- 50% { content: '..'; }
529
- 75% { content: '...'; }
530
- 100% { content: ''; }
531
  }
532
 
533
  .error-message {
534
  color: #f44 !important;
535
- animation: errorFlash 0.5s infinite;
536
- }
537
-
538
- @keyframes errorFlash {
539
- 0%, 100% { opacity: 1; }
540
- 50% { opacity: 0.5; }
541
  }
542
 
543
  /* ===== GALLERY ===== */
544
  #gallery-screen {
545
  display: none;
546
- padding: 30px;
547
- background: #0a0a0a;
548
  }
549
 
550
  .gallery-header {
551
  text-align: center;
552
- margin-bottom: 30px;
553
- padding-bottom: 20px;
554
  border-bottom: 1px solid #333;
555
  }
556
 
557
- .gallery-title {
558
- font-size: 14px;
559
- color: #888;
560
- letter-spacing: 4px;
561
- margin-bottom: 10px;
562
- text-transform: uppercase;
563
- }
564
-
565
  .gallery-count {
566
- font-size: 36px;
567
  font-weight: 800;
568
  color: #fff;
569
- letter-spacing: 4px;
570
  }
571
 
572
  .gallery-count span {
573
  color: #666;
574
- font-size: 18px;
575
- }
576
-
577
- .gallery-subtitle {
578
- font-size: 11px;
579
- color: #555;
580
- margin-top: 10px;
581
- letter-spacing: 2px;
582
  }
583
 
584
  .gallery-grid {
585
  display: grid;
586
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
587
- gap: 15px;
588
  }
589
 
590
  .gallery-item {
@@ -597,78 +336,32 @@ HTML_PAGE = '''<!DOCTYPE html>
597
  position: relative;
598
  }
599
 
600
- .gallery-item::before {
601
- content: "";
602
- position: absolute;
603
- top: 0;
604
- left: 0;
605
- right: 0;
606
- bottom: 0;
607
- border: 2px solid transparent;
608
- z-index: 1;
609
- transition: border-color 0.2s;
610
- }
611
-
612
  .gallery-item:hover {
613
- transform: translateY(-5px);
614
  border-color: #fff;
615
- box-shadow: 0 10px 40px rgba(255,255,255,0.1);
616
  }
617
 
618
- .gallery-item:hover::before {
619
- border-color: rgba(255,255,255,0.5);
620
  }
621
 
622
  .gallery-item img {
623
  width: 100%;
624
  height: 100%;
625
  object-fit: cover;
626
- transition: all 0.3s;
627
- filter: grayscale(30%);
628
- }
629
-
630
- .gallery-item:hover img {
631
- filter: grayscale(0%);
632
- transform: scale(1.05);
633
- }
634
-
635
- .gallery-item.loading-item {
636
- display: flex;
637
- align-items: center;
638
- justify-content: center;
639
- }
640
-
641
- .gallery-item.loading-item::after {
642
- content: "";
643
- width: 30px;
644
- height: 30px;
645
- border: 2px solid #333;
646
- border-top-color: #fff;
647
- border-radius: 50%;
648
- animation: spin 1s linear infinite;
649
- }
650
-
651
- @keyframes spin {
652
- to { transform: rotate(360deg); }
653
  }
654
 
655
  .item-number {
656
  position: absolute;
657
- bottom: 8px;
658
- right: 8px;
659
- background: #000;
 
660
  color: #fff;
661
- padding: 4px 8px;
662
- font-size: 10px;
663
  font-weight: 700;
664
  letter-spacing: 1px;
665
- z-index: 2;
666
- opacity: 0;
667
- transition: opacity 0.2s;
668
- }
669
-
670
- .gallery-item:hover .item-number {
671
- opacity: 1;
672
  }
673
 
674
  /* ===== LIGHTBOX ===== */
@@ -676,51 +369,48 @@ HTML_PAGE = '''<!DOCTYPE html>
676
  display: none;
677
  position: fixed;
678
  inset: 0;
679
- background: rgba(0,0,0,0.98);
680
  z-index: 1000;
681
- align-items: center;
682
- justify-content: center;
683
  }
684
 
685
  #lightbox.active {
686
  display: flex;
687
  }
688
 
689
- .lightbox-content {
690
- position: relative;
691
- max-width: 90vw;
692
- max-height: 85vh;
 
 
 
 
693
  }
694
 
695
- #lightbox-img {
696
- max-width: 90vw;
697
- max-height: 85vh;
698
- border: 4px solid #fff;
699
- object-fit: contain;
700
- box-shadow: 0 0 100px rgba(255,255,255,0.1);
701
  }
702
 
703
- .lb-info {
704
- position: absolute;
705
- bottom: -40px;
706
- left: 0;
707
- right: 0;
708
- text-align: center;
709
- color: #888;
710
- font-size: 11px;
711
- letter-spacing: 2px;
712
  }
713
 
714
  .lb-btn {
715
- position: absolute;
716
  background: none;
717
  border: 2px solid #fff;
718
  color: #fff;
719
  cursor: pointer;
720
- font-size: 24px;
721
- padding: 15px 20px;
722
- transition: all 0.2s;
723
  font-family: inherit;
 
 
 
724
  }
725
 
726
  .lb-btn:hover {
@@ -728,118 +418,141 @@ HTML_PAGE = '''<!DOCTYPE html>
728
  color: #000;
729
  }
730
 
731
- #lb-close {
732
- top: 20px;
733
- right: 20px;
734
- font-size: 20px;
735
- padding: 10px 15px;
736
  }
737
 
738
- #lb-prev {
739
- left: 20px;
740
- top: 50%;
741
- transform: translateY(-50%);
742
  }
743
 
744
- #lb-next {
745
- right: 20px;
746
- top: 50%;
747
- transform: translateY(-50%);
 
 
 
748
  }
749
 
750
- #lb-counter {
751
- position: absolute;
752
- top: 25px;
753
- left: 50%;
754
- transform: translateX(-50%);
755
- color: #fff;
756
- font-size: 14px;
757
- font-weight: 700;
758
- letter-spacing: 4px;
759
- background: #000;
760
- padding: 10px 25px;
761
- border: 2px solid #fff;
762
  }
763
 
764
- .keyboard-hint {
765
- position: absolute;
766
- bottom: 20px;
767
- left: 50%;
768
- transform: translateX(-50%);
769
- display: flex;
770
- gap: 20px;
771
  }
772
 
773
- .key-hint {
774
- display: flex;
775
- align-items: center;
776
- gap: 8px;
777
- color: #555;
778
  font-size: 10px;
 
779
  letter-spacing: 1px;
780
  }
781
 
782
- .key {
783
- background: #222;
784
- border: 1px solid #444;
785
- padding: 5px 10px;
786
- font-size: 11px;
787
- color: #888;
788
- }
789
-
790
- /* Mobile adjustments */
791
  @media (max-width: 600px) {
792
  #header {
793
- font-size: 18px;
794
- letter-spacing: 6px;
795
- padding: 20px 15px;
796
  }
797
 
798
- .epstein-loader {
799
- width: 150px;
800
- height: 150px;
801
  }
802
 
803
  .gallery-grid {
804
- grid-template-columns: repeat(2, 1fr);
805
- gap: 10px;
806
  }
807
 
808
- .progress-card {
809
- padding: 30px 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
810
  }
811
 
812
  .lb-btn {
813
- padding: 10px 15px;
814
- font-size: 18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
  }
816
 
817
- .keyboard-hint {
818
- display: none;
 
 
 
 
 
 
819
  }
820
  }
821
  </style>
822
  </head>
823
  <body>
824
- <div id="header"><span>EPSTEIN FILES</span></div>
825
- <div class="classified-banner">★ CLASSIFIED DOCUMENTS AUTHORIZED ACCESS ONLY ★</div>
826
 
827
  <!-- Loading Screen -->
828
  <div id="loading-screen">
829
- <!-- Epstein Image Loader -->
830
  <div class="epstein-loader">
831
- <div class="rotating-ring"></div>
832
  <div class="corner-bracket tl"></div>
833
  <div class="corner-bracket tr"></div>
834
  <div class="corner-bracket bl"></div>
835
  <div class="corner-bracket br"></div>
836
- <img class="epstein-image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Epstein-mugshot.jpg/440px-Epstein-mugshot.jpg" alt="Loading...">
837
- <div class="scan-line"></div>
838
  </div>
839
 
840
  <div class="progress-card">
841
- <div class="progress-title">ACCESSING SECURE FILES</div>
842
- <div class="progress-status" id="status-text">INITIALIZING<span class="loading-dots"></span></div>
843
 
844
  <div class="progress-bar-outer">
845
  <div class="progress-bar-inner" id="progress-bar"></div>
@@ -852,50 +565,33 @@ HTML_PAGE = '''<!DOCTYPE html>
852
  <span class="total" id="total-count">0</span>
853
  </div>
854
 
855
- <div class="progress-detail" id="detail-text">Establishing secure connection...</div>
856
- <div class="progress-subdetail" id="subdetail-text">Please wait</div>
857
-
858
- <div class="phase-indicator">
859
- <div class="phase" id="phase-connect">
860
- <div class="phase-dot"></div>
861
- <span>Connect</span>
862
- </div>
863
- <div class="phase" id="phase-download">
864
- <div class="phase-dot"></div>
865
- <span>Download</span>
866
- </div>
867
- <div class="phase" id="phase-ready">
868
- <div class="phase-dot"></div>
869
- <span>Ready</span>
870
- </div>
871
- </div>
872
  </div>
873
  </div>
874
 
875
  <!-- Gallery Screen -->
876
  <div id="gallery-screen">
877
  <div class="gallery-header">
878
- <div class="gallery-title">Declassified Images</div>
879
  <div class="gallery-count" id="gallery-count">0 <span>FILES</span></div>
880
- <div class="gallery-subtitle">Click any image to enlarge • Use arrow keys to navigate</div>
881
  </div>
882
  <div class="gallery-grid" id="gallery-grid"></div>
883
  </div>
884
 
885
  <!-- Lightbox -->
886
  <div id="lightbox">
887
- <button class="lb-btn" id="lb-close">✕</button>
888
- <button class="lb-btn" id="lb-prev"></button>
889
- <button class="lb-btn" id="lb-next">►</button>
890
- <div id="lb-counter">1 / 1</div>
891
- <div class="lightbox-content">
 
 
 
 
892
  <img id="lightbox-img" src="" alt="">
893
- <div class="lb-info" id="lb-info"></div>
894
  </div>
895
- <div class="keyboard-hint">
896
- <div class="key-hint"><span class="key"></span> Previous</div>
897
- <div class="key-hint"><span class="key">→</span> Next</div>
898
- <div class="key-hint"><span class="key">ESC</span> Close</div>
899
  </div>
900
  </div>
901
 
@@ -909,15 +605,8 @@ HTML_PAGE = '''<!DOCTYPE html>
909
  const progressPct = document.getElementById('progress-pct');
910
  const statusText = document.getElementById('status-text');
911
  const detailText = document.getElementById('detail-text');
912
- const subdetailText = document.getElementById('subdetail-text');
913
  const currentCount = document.getElementById('current-count');
914
  const totalCount = document.getElementById('total-count');
915
- const phaseConnect = document.getElementById('phase-connect');
916
- const phaseDownload = document.getElementById('phase-download');
917
- const phaseReady = document.getElementById('phase-ready');
918
-
919
- // Start with connect phase active
920
- phaseConnect.classList.add('active');
921
 
922
  function updateUI(data) {
923
  progressBar.style.width = data.progress + '%';
@@ -926,33 +615,17 @@ HTML_PAGE = '''<!DOCTYPE html>
926
  currentCount.textContent = data.processed_files || 0;
927
  totalCount.textContent = data.total_files || 0;
928
 
929
- // Reset phases
930
- [phaseConnect, phaseDownload, phaseReady].forEach(p => {
931
- p.classList.remove('active', 'done');
932
- });
933
-
934
  if (data.status === 'idle') {
935
- statusText.innerHTML = 'INITIALIZING<span class="loading-dots"></span>';
936
- phaseConnect.classList.add('active');
937
- subdetailText.textContent = 'Please wait...';
938
  } else if (data.status === 'downloading') {
939
- statusText.innerHTML = 'DOWNLOADING<span class="loading-dots"></span>';
940
- phaseConnect.classList.add('done');
941
- phaseDownload.classList.add('active');
942
- subdetailText.textContent = 'Retrieving classified files...';
943
  } else if (data.status === 'complete') {
944
- statusText.textContent = 'ACCESS GRANTED';
945
- phaseConnect.classList.add('done');
946
- phaseDownload.classList.add('done');
947
- phaseReady.classList.add('done');
948
- subdetailText.textContent = 'All files retrieved successfully';
949
-
950
- setTimeout(() => showGallery(data.images), 800);
951
  } else if (data.status === 'error') {
952
- statusText.textContent = 'ACCESS DENIED';
953
  statusText.classList.add('error-message');
954
  detailText.classList.add('error-message');
955
- subdetailText.textContent = 'Error retrieving files';
956
  }
957
  }
958
 
@@ -969,19 +642,16 @@ HTML_PAGE = '''<!DOCTYPE html>
969
 
970
  images.forEach((img, idx) => {
971
  const item = document.createElement('div');
972
- item.className = 'gallery-item loading-item';
973
 
974
  const imgEl = document.createElement('img');
975
  imgEl.loading = 'lazy';
976
  imgEl.alt = 'File ' + (idx + 1);
977
- imgEl.onload = () => {
978
- item.classList.remove('loading-item');
979
- };
980
  imgEl.src = '/media/' + encodeURIComponent(img);
981
 
982
  const number = document.createElement('div');
983
  number.className = 'item-number';
984
- number.textContent = '#' + String(idx + 1).padStart(3, '0');
985
 
986
  item.appendChild(imgEl);
987
  item.appendChild(number);
@@ -993,7 +663,7 @@ HTML_PAGE = '''<!DOCTYPE html>
993
  const lightbox = document.getElementById('lightbox');
994
  const lbImg = document.getElementById('lightbox-img');
995
  const lbCounter = document.getElementById('lb-counter');
996
- const lbInfo = document.getElementById('lb-info');
997
 
998
  function openLightbox(idx) {
999
  currentIdx = idx;
@@ -1010,7 +680,7 @@ HTML_PAGE = '''<!DOCTYPE html>
1010
  function updateLightbox() {
1011
  lbImg.src = '/media/' + encodeURIComponent(images[currentIdx]);
1012
  lbCounter.textContent = (currentIdx + 1) + ' / ' + images.length;
1013
- lbInfo.textContent = 'FILE #' + String(currentIdx + 1).padStart(3, '0');
1014
  }
1015
 
1016
  function navigate(dir) {
@@ -1019,9 +689,8 @@ HTML_PAGE = '''<!DOCTYPE html>
1019
  }
1020
 
1021
  document.getElementById('lb-close').onclick = closeLightbox;
1022
- document.getElementById('lb-prev').onclick = (e) => { e.stopPropagation(); navigate(-1); };
1023
- document.getElementById('lb-next').onclick = (e) => { e.stopPropagation(); navigate(1); };
1024
- lightbox.onclick = e => { if (e.target === lightbox) closeLightbox(); };
1025
 
1026
  document.addEventListener('keydown', e => {
1027
  if (lightbox.classList.contains('active')) {
@@ -1035,19 +704,21 @@ HTML_PAGE = '''<!DOCTYPE html>
1035
  let touchStartX = 0;
1036
  let touchStartY = 0;
1037
 
1038
- lightbox.addEventListener('touchstart', e => {
 
 
1039
  touchStartX = e.touches[0].clientX;
1040
  touchStartY = e.touches[0].clientY;
1041
- });
1042
 
1043
- lightbox.addEventListener('touchend', e => {
1044
  const diffX = touchStartX - e.changedTouches[0].clientX;
1045
  const diffY = touchStartY - e.changedTouches[0].clientY;
1046
 
1047
  if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
1048
  navigate(diffX > 0 ? 1 : -1);
1049
  }
1050
- });
1051
 
1052
  function poll() {
1053
  fetch('/status')
@@ -1061,7 +732,6 @@ HTML_PAGE = '''<!DOCTYPE html>
1061
  .catch(() => setTimeout(poll, 1000));
1062
  }
1063
 
1064
- // Start polling
1065
  poll();
1066
  </script>
1067
  </body>
 
75
  return links
76
 
77
  def download_images():
 
78
  existing_images = get_images()
79
  if existing_images:
80
  update_state(
 
87
  )
88
  return
89
 
 
90
  links = load_links()
91
  if not links:
92
  update_state(status="error", message="No links found in links.txt")
 
131
  print(f"Failed to download {url}: {e}")
132
  continue
133
 
 
134
  images = get_images()
135
  if images:
136
  update_state(
 
142
  else:
143
  update_state(status="error", message="No images downloaded")
144
 
 
145
  HTML_PAGE = '''<!DOCTYPE html>
146
  <html lang="en">
147
  <head>
148
  <meta charset="UTF-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
150
  <title>EPSTEIN FILES</title>
151
  <style>
152
  * {
 
162
  color: #fff;
163
  }
164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  #header {
166
  background: #000;
167
  color: #fff;
168
  text-align: center;
169
+ padding: 20px 15px;
170
+ font-size: 22px;
171
  font-weight: 800;
172
+ letter-spacing: 8px;
173
  position: sticky;
174
  top: 0;
175
  z-index: 100;
 
177
  text-transform: uppercase;
178
  }
179
 
180
+ .info-banner {
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  background: #fff;
182
  color: #000;
183
  text-align: center;
184
+ padding: 10px 15px;
185
  font-size: 11px;
186
  font-weight: 700;
187
+ letter-spacing: 2px;
 
 
 
 
 
 
188
  }
189
 
190
  /* ===== LOADING SCREEN ===== */
 
193
  flex-direction: column;
194
  align-items: center;
195
  justify-content: center;
196
+ min-height: calc(100vh - 120px);
197
  padding: 40px 20px;
 
198
  }
199
 
 
200
  .epstein-loader {
201
  position: relative;
202
+ width: 120px;
203
+ height: 120px;
204
+ margin-bottom: 30px;
205
  }
206
 
207
  .epstein-image {
 
209
  height: 100%;
210
  object-fit: cover;
211
  filter: grayscale(100%) contrast(1.2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  border: 3px solid #fff;
213
  }
214
 
215
+ .corner-bracket {
 
 
 
 
 
216
  position: absolute;
217
+ width: 15px;
218
+ height: 15px;
219
+ border: 2px solid #fff;
 
 
 
220
  }
221
 
222
+ .corner-bracket.tl { top: -6px; left: -6px; border-right: none; border-bottom: none; }
223
+ .corner-bracket.tr { top: -6px; right: -6px; border-left: none; border-bottom: none; }
224
+ .corner-bracket.bl { bottom: -6px; left: -6px; border-right: none; border-top: none; }
225
+ .corner-bracket.br { bottom: -6px; right: -6px; border-left: none; border-top: none; }
226
 
227
  .progress-card {
228
  width: 100%;
229
+ max-width: 400px;
230
  background: #000;
231
  border: 3px solid #fff;
232
+ padding: 30px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  }
234
 
235
  .progress-status {
236
+ font-size: 16px;
237
  font-weight: 700;
238
+ letter-spacing: 4px;
239
  color: #fff;
240
  margin-bottom: 20px;
241
  text-transform: uppercase;
242
  text-align: center;
 
 
 
 
 
 
243
  }
244
 
245
  .progress-bar-outer {
246
  width: 100%;
247
+ height: 35px;
248
  background: #111;
249
  border: 2px solid #fff;
250
  position: relative;
 
256
  background: #fff;
257
  width: 0%;
258
  transition: width 0.3s ease-out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  }
260
 
261
  .progress-percent {
 
263
  top: 50%;
264
  left: 50%;
265
  transform: translate(-50%, -50%);
266
+ font-size: 14px;
267
  font-weight: 800;
268
  color: #fff;
269
  z-index: 2;
270
  mix-blend-mode: difference;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  }
272
 
273
  .file-counter {
 
275
  justify-content: center;
276
  gap: 5px;
277
  margin-top: 20px;
278
+ font-size: 20px;
279
  font-weight: 800;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  }
281
 
282
+ .file-counter .current { color: #fff; }
283
+ .file-counter .separator { color: #444; }
284
+ .file-counter .total { color: #666; }
 
285
 
286
+ .progress-detail {
287
+ margin-top: 15px;
288
+ font-size: 11px;
289
+ color: #888;
290
+ text-align: center;
 
291
  }
292
 
293
  .error-message {
294
  color: #f44 !important;
 
 
 
 
 
 
295
  }
296
 
297
  /* ===== GALLERY ===== */
298
  #gallery-screen {
299
  display: none;
300
+ padding: 15px;
301
+ padding-bottom: 30px;
302
  }
303
 
304
  .gallery-header {
305
  text-align: center;
306
+ margin-bottom: 20px;
307
+ padding-bottom: 15px;
308
  border-bottom: 1px solid #333;
309
  }
310
 
 
 
 
 
 
 
 
 
311
  .gallery-count {
312
+ font-size: 28px;
313
  font-weight: 800;
314
  color: #fff;
315
+ letter-spacing: 3px;
316
  }
317
 
318
  .gallery-count span {
319
  color: #666;
320
+ font-size: 14px;
 
 
 
 
 
 
 
321
  }
322
 
323
  .gallery-grid {
324
  display: grid;
325
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
326
+ gap: 10px;
327
  }
328
 
329
  .gallery-item {
 
336
  position: relative;
337
  }
338
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  .gallery-item:hover {
 
340
  border-color: #fff;
 
341
  }
342
 
343
+ .gallery-item:active {
344
+ transform: scale(0.98);
345
  }
346
 
347
  .gallery-item img {
348
  width: 100%;
349
  height: 100%;
350
  object-fit: cover;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  }
352
 
353
  .item-number {
354
  position: absolute;
355
+ bottom: 0;
356
+ left: 0;
357
+ right: 0;
358
+ background: rgba(0,0,0,0.8);
359
  color: #fff;
360
+ padding: 4px;
361
+ font-size: 9px;
362
  font-weight: 700;
363
  letter-spacing: 1px;
364
+ text-align: center;
 
 
 
 
 
 
365
  }
366
 
367
  /* ===== LIGHTBOX ===== */
 
369
  display: none;
370
  position: fixed;
371
  inset: 0;
372
+ background: #000;
373
  z-index: 1000;
374
+ flex-direction: column;
 
375
  }
376
 
377
  #lightbox.active {
378
  display: flex;
379
  }
380
 
381
+ .lb-header {
382
+ display: flex;
383
+ justify-content: space-between;
384
+ align-items: center;
385
+ padding: 12px 15px;
386
+ background: #000;
387
+ border-bottom: 2px solid #fff;
388
+ flex-shrink: 0;
389
  }
390
 
391
+ .lb-counter {
392
+ font-size: 14px;
393
+ font-weight: 700;
394
+ letter-spacing: 3px;
395
+ color: #fff;
 
396
  }
397
 
398
+ .lb-nav {
399
+ display: flex;
400
+ gap: 10px;
 
 
 
 
 
 
401
  }
402
 
403
  .lb-btn {
 
404
  background: none;
405
  border: 2px solid #fff;
406
  color: #fff;
407
  cursor: pointer;
408
+ font-size: 16px;
409
+ padding: 8px 15px;
 
410
  font-family: inherit;
411
+ font-weight: 700;
412
+ transition: all 0.15s;
413
+ -webkit-tap-highlight-color: transparent;
414
  }
415
 
416
  .lb-btn:hover {
 
418
  color: #000;
419
  }
420
 
421
+ .lb-btn:active {
422
+ transform: scale(0.95);
 
 
 
423
  }
424
 
425
+ .lb-close {
426
+ font-size: 18px;
427
+ padding: 8px 12px;
 
428
  }
429
 
430
+ .lb-image-container {
431
+ flex: 1;
432
+ display: flex;
433
+ align-items: center;
434
+ justify-content: center;
435
+ padding: 15px;
436
+ overflow: hidden;
437
  }
438
 
439
+ #lightbox-img {
440
+ max-width: 100%;
441
+ max-height: 100%;
442
+ object-fit: contain;
443
+ border: 3px solid #fff;
 
 
 
 
 
 
 
444
  }
445
 
446
+ .lb-footer {
447
+ padding: 10px 15px;
448
+ text-align: center;
449
+ background: #000;
450
+ border-top: 1px solid #333;
451
+ flex-shrink: 0;
 
452
  }
453
 
454
+ .lb-filename {
 
 
 
 
455
  font-size: 10px;
456
+ color: #666;
457
  letter-spacing: 1px;
458
  }
459
 
460
+ /* Mobile optimizations */
 
 
 
 
 
 
 
 
461
  @media (max-width: 600px) {
462
  #header {
463
+ font-size: 16px;
464
+ letter-spacing: 5px;
465
+ padding: 15px 10px;
466
  }
467
 
468
+ .info-banner {
469
+ font-size: 10px;
470
+ padding: 8px 10px;
471
  }
472
 
473
  .gallery-grid {
474
+ grid-template-columns: repeat(3, 1fr);
475
+ gap: 6px;
476
  }
477
 
478
+ .gallery-header {
479
+ margin-bottom: 15px;
480
+ padding-bottom: 12px;
481
+ }
482
+
483
+ .gallery-count {
484
+ font-size: 22px;
485
+ }
486
+
487
+ .item-number {
488
+ font-size: 8px;
489
+ padding: 3px;
490
+ }
491
+
492
+ .lb-header {
493
+ padding: 10px 12px;
494
+ }
495
+
496
+ .lb-counter {
497
+ font-size: 12px;
498
+ letter-spacing: 2px;
499
  }
500
 
501
  .lb-btn {
502
+ font-size: 14px;
503
+ padding: 6px 12px;
504
+ }
505
+
506
+ .lb-close {
507
+ font-size: 16px;
508
+ padding: 6px 10px;
509
+ }
510
+
511
+ .lb-image-container {
512
+ padding: 10px;
513
+ }
514
+
515
+ #lightbox-img {
516
+ border-width: 2px;
517
+ }
518
+
519
+ .epstein-loader {
520
+ width: 100px;
521
+ height: 100px;
522
+ }
523
+
524
+ .progress-card {
525
+ padding: 25px 20px;
526
  }
527
 
528
+ .progress-status {
529
+ font-size: 14px;
530
+ }
531
+ }
532
+
533
+ @media (max-width: 380px) {
534
+ .gallery-grid {
535
+ grid-template-columns: repeat(2, 1fr);
536
  }
537
  }
538
  </style>
539
  </head>
540
  <body>
541
+ <div id="header">EPSTEIN FILES</div>
542
+ <div class="info-banner">★ HERE ARE ALL THE IMAGES ★</div>
543
 
544
  <!-- Loading Screen -->
545
  <div id="loading-screen">
 
546
  <div class="epstein-loader">
 
547
  <div class="corner-bracket tl"></div>
548
  <div class="corner-bracket tr"></div>
549
  <div class="corner-bracket bl"></div>
550
  <div class="corner-bracket br"></div>
551
+ <img class="epstein-image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Epstein-mugshot.jpg/440px-Epstein-mugshot.jpg" alt="Loading">
 
552
  </div>
553
 
554
  <div class="progress-card">
555
+ <div class="progress-status" id="status-text">LOADING</div>
 
556
 
557
  <div class="progress-bar-outer">
558
  <div class="progress-bar-inner" id="progress-bar"></div>
 
565
  <span class="total" id="total-count">0</span>
566
  </div>
567
 
568
+ <div class="progress-detail" id="detail-text">Connecting...</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  </div>
570
  </div>
571
 
572
  <!-- Gallery Screen -->
573
  <div id="gallery-screen">
574
  <div class="gallery-header">
 
575
  <div class="gallery-count" id="gallery-count">0 <span>FILES</span></div>
 
576
  </div>
577
  <div class="gallery-grid" id="gallery-grid"></div>
578
  </div>
579
 
580
  <!-- Lightbox -->
581
  <div id="lightbox">
582
+ <div class="lb-header">
583
+ <div class="lb-counter" id="lb-counter">1 / 1</div>
584
+ <div class="lb-nav">
585
+ <button class="lb-btn" id="lb-prev"></button>
586
+ <button class="lb-btn" id="lb-next">►</button>
587
+ <button class="lb-btn lb-close" id="lb-close">✕</button>
588
+ </div>
589
+ </div>
590
+ <div class="lb-image-container">
591
  <img id="lightbox-img" src="" alt="">
 
592
  </div>
593
+ <div class="lb-footer">
594
+ <div class="lb-filename" id="lb-filename"></div>
 
 
595
  </div>
596
  </div>
597
 
 
605
  const progressPct = document.getElementById('progress-pct');
606
  const statusText = document.getElementById('status-text');
607
  const detailText = document.getElementById('detail-text');
 
608
  const currentCount = document.getElementById('current-count');
609
  const totalCount = document.getElementById('total-count');
 
 
 
 
 
 
610
 
611
  function updateUI(data) {
612
  progressBar.style.width = data.progress + '%';
 
615
  currentCount.textContent = data.processed_files || 0;
616
  totalCount.textContent = data.total_files || 0;
617
 
 
 
 
 
 
618
  if (data.status === 'idle') {
619
+ statusText.textContent = 'CONNECTING';
 
 
620
  } else if (data.status === 'downloading') {
621
+ statusText.textContent = 'DOWNLOADING';
 
 
 
622
  } else if (data.status === 'complete') {
623
+ statusText.textContent = 'COMPLETE';
624
+ showGallery(data.images);
 
 
 
 
 
625
  } else if (data.status === 'error') {
626
+ statusText.textContent = 'ERROR';
627
  statusText.classList.add('error-message');
628
  detailText.classList.add('error-message');
 
629
  }
630
  }
631
 
 
642
 
643
  images.forEach((img, idx) => {
644
  const item = document.createElement('div');
645
+ item.className = 'gallery-item';
646
 
647
  const imgEl = document.createElement('img');
648
  imgEl.loading = 'lazy';
649
  imgEl.alt = 'File ' + (idx + 1);
 
 
 
650
  imgEl.src = '/media/' + encodeURIComponent(img);
651
 
652
  const number = document.createElement('div');
653
  number.className = 'item-number';
654
+ number.textContent = '#' + (idx + 1);
655
 
656
  item.appendChild(imgEl);
657
  item.appendChild(number);
 
663
  const lightbox = document.getElementById('lightbox');
664
  const lbImg = document.getElementById('lightbox-img');
665
  const lbCounter = document.getElementById('lb-counter');
666
+ const lbFilename = document.getElementById('lb-filename');
667
 
668
  function openLightbox(idx) {
669
  currentIdx = idx;
 
680
  function updateLightbox() {
681
  lbImg.src = '/media/' + encodeURIComponent(images[currentIdx]);
682
  lbCounter.textContent = (currentIdx + 1) + ' / ' + images.length;
683
+ lbFilename.textContent = 'FILE #' + (currentIdx + 1);
684
  }
685
 
686
  function navigate(dir) {
 
689
  }
690
 
691
  document.getElementById('lb-close').onclick = closeLightbox;
692
+ document.getElementById('lb-prev').onclick = () => navigate(-1);
693
+ document.getElementById('lb-next').onclick = () => navigate(1);
 
694
 
695
  document.addEventListener('keydown', e => {
696
  if (lightbox.classList.contains('active')) {
 
704
  let touchStartX = 0;
705
  let touchStartY = 0;
706
 
707
+ const lbImageContainer = document.querySelector('.lb-image-container');
708
+
709
+ lbImageContainer.addEventListener('touchstart', e => {
710
  touchStartX = e.touches[0].clientX;
711
  touchStartY = e.touches[0].clientY;
712
+ }, { passive: true });
713
 
714
+ lbImageContainer.addEventListener('touchend', e => {
715
  const diffX = touchStartX - e.changedTouches[0].clientX;
716
  const diffY = touchStartY - e.changedTouches[0].clientY;
717
 
718
  if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
719
  navigate(diffX > 0 ? 1 : -1);
720
  }
721
+ }, { passive: true });
722
 
723
  function poll() {
724
  fetch('/status')
 
732
  .catch(() => setTimeout(poll, 1000));
733
  }
734
 
 
735
  poll();
736
  </script>
737
  </body>