coursewatcher 1.3.1 → 2.0.0

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.
Files changed (47) hide show
  1. package/README.md +26 -23
  2. package/dist/app/cli/main.js +36 -0
  3. package/dist/app/server/create-app.js +151 -0
  4. package/dist/app/server/start-server.js +72 -0
  5. package/dist/modules/catalog/catalog-mappers.js +49 -0
  6. package/dist/modules/catalog/catalog-repository.js +168 -0
  7. package/dist/modules/catalog/catalog-service.js +76 -0
  8. package/dist/modules/notes/notes-repository.js +25 -0
  9. package/dist/modules/notes/notes-service.js +28 -0
  10. package/dist/modules/playback/playback-repository.js +32 -0
  11. package/dist/modules/playback/playback-service.js +89 -0
  12. package/dist/platform/config/app-config.js +24 -0
  13. package/dist/platform/config/package-info.js +21 -0
  14. package/dist/platform/database/database-manager.js +101 -0
  15. package/dist/platform/errors/app-error.js +30 -0
  16. package/dist/platform/logging/logger.js +21 -0
  17. package/dist/shared/contracts/api.js +2 -0
  18. package/dist/web/assets/api-client-hFlLSS3K.js +1 -0
  19. package/dist/web/assets/catalog-route-pOIhR3yd.js +1 -0
  20. package/dist/web/assets/index-CwspbIw1.js +10 -0
  21. package/dist/web/assets/index-VjwsJnuQ.css +1 -0
  22. package/dist/web/assets/jsx-runtime-C2ZT__TU.js +4 -0
  23. package/dist/web/assets/playback-route-Bmy_Z7k7.js +2 -0
  24. package/dist/web/assets/search-route-CeGVOVPT.js +1 -0
  25. package/dist/web/index.html +14 -0
  26. package/package.json +75 -57
  27. package/public/css/styles.css +0 -1375
  28. package/public/js/player.js +0 -359
  29. package/src/cli.js +0 -45
  30. package/src/controllers/video-controller.js +0 -189
  31. package/src/models/database.js +0 -169
  32. package/src/server.js +0 -179
  33. package/src/services/notes-service.js +0 -97
  34. package/src/services/progress-service.js +0 -169
  35. package/src/services/video-service.js +0 -354
  36. package/src/utils/config.js +0 -57
  37. package/src/utils/errors.js +0 -57
  38. package/src/utils/logger.js +0 -48
  39. package/views/layouts/main.ejs +0 -13
  40. package/views/pages/error.ejs +0 -25
  41. package/views/pages/index.ejs +0 -101
  42. package/views/pages/player.ejs +0 -161
  43. package/views/pages/search.ejs +0 -63
  44. package/views/partials/footer.ejs +0 -3
  45. package/views/partials/head.ejs +0 -8
  46. package/views/partials/header.ejs +0 -14
  47. package/views/partials/video-card.ejs +0 -36
@@ -1,1375 +0,0 @@
1
- /**
2
- * CourseWatcher Styles
3
- *
4
- * Modern dark theme with vibrant accents.
5
- */
6
-
7
- /* ==========================================
8
- CSS Variables
9
- ========================================== */
10
-
11
- :root {
12
- /* Colors */
13
- --color-bg: #0f0f1a;
14
- --color-bg-secondary: #1a1a2e;
15
- --color-bg-tertiary: #252540;
16
- --color-surface: #2a2a45;
17
- --color-surface-hover: #35355a;
18
-
19
- --color-text: #e8e8f0;
20
- --color-text-secondary: #a0a0b8;
21
- --color-text-muted: #6b6b85;
22
-
23
- --color-primary: #7c3aed;
24
- --color-primary-hover: #8b5cf6;
25
- --color-primary-glow: rgba(124, 58, 237, 0.3);
26
-
27
- --color-success: #10b981;
28
- --color-success-bg: rgba(16, 185, 129, 0.15);
29
-
30
- --color-warning: #f59e0b;
31
- --color-warning-bg: rgba(245, 158, 11, 0.15);
32
-
33
- --color-error: #ef4444;
34
-
35
- --color-border: #3a3a55;
36
-
37
- /* Typography */
38
- --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
39
- --font-mono: 'Fira Code', 'Consolas', monospace;
40
-
41
- /* Spacing */
42
- --space-xs: 0.25rem;
43
- --space-sm: 0.5rem;
44
- --space-md: 1rem;
45
- --space-lg: 1.5rem;
46
- --space-xl: 2rem;
47
- --space-2xl: 3rem;
48
-
49
- /* Border Radius */
50
- --radius-sm: 4px;
51
- --radius-md: 8px;
52
- --radius-lg: 12px;
53
- --radius-xl: 16px;
54
-
55
- /* Transitions */
56
- --transition-fast: 150ms ease;
57
- --transition-normal: 250ms ease;
58
- }
59
-
60
- /* ==========================================
61
- Base Styles
62
- ========================================== */
63
-
64
- *,
65
- *::before,
66
- *::after {
67
- box-sizing: border-box;
68
- margin: 0;
69
- padding: 0;
70
- }
71
-
72
- html {
73
- font-size: 16px;
74
- scroll-behavior: smooth;
75
- }
76
-
77
- body {
78
- font-family: var(--font-sans);
79
- background: var(--color-bg);
80
- color: var(--color-text);
81
- line-height: 1.6;
82
- min-height: 100vh;
83
- display: flex;
84
- flex-direction: column;
85
- }
86
-
87
- a {
88
- color: var(--color-primary);
89
- text-decoration: none;
90
- transition: color var(--transition-fast);
91
- }
92
-
93
- a:hover {
94
- color: var(--color-primary-hover);
95
- }
96
-
97
- /* ==========================================
98
- Container
99
- ========================================== */
100
-
101
- .container {
102
- width: 100%;
103
- max-width: 1200px;
104
- margin: 0 auto;
105
- padding: var(--space-lg);
106
- flex: 1;
107
- }
108
-
109
- /* ==========================================
110
- Header
111
- ========================================== */
112
-
113
- .header {
114
- background: var(--color-bg-secondary);
115
- border-bottom: 1px solid var(--color-border);
116
- padding: var(--space-md) var(--space-lg);
117
- position: sticky;
118
- top: 0;
119
- z-index: 100;
120
- backdrop-filter: blur(10px);
121
- }
122
-
123
- .header-content {
124
- max-width: 1200px;
125
- margin: 0 auto;
126
- display: flex;
127
- align-items: center;
128
- justify-content: space-between;
129
- gap: var(--space-lg);
130
- }
131
-
132
- .logo {
133
- display: flex;
134
- align-items: center;
135
- gap: var(--space-sm);
136
- color: var(--color-text);
137
- font-weight: 700;
138
- font-size: 1.25rem;
139
- }
140
-
141
- .logo:hover {
142
- color: var(--color-text);
143
- }
144
-
145
- .logo-icon {
146
- font-size: 1.5rem;
147
- }
148
-
149
- .nav {
150
- display: flex;
151
- align-items: center;
152
- gap: var(--space-md);
153
- }
154
-
155
- .search-form {
156
- display: flex;
157
- align-items: center;
158
- background: var(--color-bg-tertiary);
159
- border-radius: var(--radius-lg);
160
- overflow: hidden;
161
- border: 1px solid var(--color-border);
162
- transition: border-color var(--transition-fast);
163
- }
164
-
165
- .search-form:focus-within {
166
- border-color: var(--color-primary);
167
- box-shadow: 0 0 0 3px var(--color-primary-glow);
168
- }
169
-
170
- .search-input {
171
- background: transparent;
172
- border: none;
173
- padding: var(--space-sm) var(--space-md);
174
- color: var(--color-text);
175
- font-size: 0.9rem;
176
- width: 200px;
177
- outline: none;
178
- }
179
-
180
- .search-input::placeholder {
181
- color: var(--color-text-muted);
182
- }
183
-
184
- .search-button {
185
- background: transparent;
186
- border: none;
187
- padding: var(--space-sm) var(--space-md);
188
- cursor: pointer;
189
- font-size: 1rem;
190
- opacity: 0.7;
191
- transition: opacity var(--transition-fast);
192
- }
193
-
194
- .search-button:hover {
195
- opacity: 1;
196
- }
197
-
198
- /* ==========================================
199
- Footer
200
- ========================================== */
201
-
202
- .footer {
203
- background: var(--color-bg-secondary);
204
- border-top: 1px solid var(--color-border);
205
- padding: var(--space-lg);
206
- text-align: center;
207
- color: var(--color-text-muted);
208
- font-size: 0.85rem;
209
- }
210
-
211
- /* ==========================================
212
- Stats Section
213
- ========================================== */
214
-
215
- .stats-section {
216
- margin-bottom: var(--space-2xl);
217
- }
218
-
219
- .stats-grid {
220
- display: grid;
221
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
222
- gap: var(--space-md);
223
- }
224
-
225
- .stat-card {
226
- background: var(--color-surface);
227
- border-radius: var(--radius-lg);
228
- padding: var(--space-lg);
229
- text-align: center;
230
- border: 1px solid var(--color-border);
231
- transition: transform var(--transition-fast), box-shadow var(--transition-fast);
232
- }
233
-
234
- .stat-card:hover {
235
- transform: translateY(-2px);
236
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
237
- }
238
-
239
- .stat-card-highlight {
240
- background: linear-gradient(135deg, var(--color-primary) 0%, #9333ea 100%);
241
- border-color: transparent;
242
- }
243
-
244
- .stat-value {
245
- display: block;
246
- font-size: 2rem;
247
- font-weight: 700;
248
- margin-bottom: var(--space-xs);
249
- }
250
-
251
- .stat-label {
252
- font-size: 0.85rem;
253
- color: var(--color-text-secondary);
254
- }
255
-
256
- .stat-card-highlight .stat-label {
257
- color: rgba(255, 255, 255, 0.8);
258
- }
259
-
260
- /* ==========================================
261
- Modules Section
262
- ========================================== */
263
-
264
- .section-title {
265
- font-size: 1.5rem;
266
- margin-bottom: var(--space-lg);
267
- color: var(--color-text);
268
- }
269
-
270
- .section-header {
271
- display: flex;
272
- justify-content: space-between;
273
- align-items: center;
274
- flex-wrap: wrap;
275
- gap: var(--space-md);
276
- margin-bottom: var(--space-lg);
277
- }
278
-
279
- .section-header .section-title {
280
- margin-bottom: 0;
281
- }
282
-
283
- .sort-controls {
284
- display: flex;
285
- align-items: center;
286
- gap: var(--space-sm);
287
- }
288
-
289
- .sort-label {
290
- color: var(--color-text-muted);
291
- font-size: 0.85rem;
292
- }
293
-
294
- .sort-buttons {
295
- display: flex;
296
- gap: var(--space-xs);
297
- }
298
-
299
- .sort-btn {
300
- padding: var(--space-xs) var(--space-sm);
301
- background: var(--color-bg-secondary);
302
- border: 1px solid var(--color-border);
303
- border-radius: var(--radius-sm);
304
- color: var(--color-text-secondary);
305
- font-size: 0.8rem;
306
- transition: all var(--transition-fast);
307
- }
308
-
309
- .sort-btn:hover {
310
- background: var(--color-bg-tertiary);
311
- color: var(--color-text);
312
- }
313
-
314
- .sort-btn.active {
315
- background: var(--color-primary);
316
- border-color: var(--color-primary);
317
- color: white;
318
- }
319
-
320
- .modules-list {
321
- display: flex;
322
- flex-direction: column;
323
- gap: var(--space-md);
324
- }
325
-
326
- .module-item {
327
- background: var(--color-surface);
328
- border-radius: var(--radius-lg);
329
- border: 1px solid var(--color-border);
330
- overflow: hidden;
331
- }
332
-
333
- .module-header {
334
- display: flex;
335
- align-items: center;
336
- gap: var(--space-md);
337
- padding: var(--space-lg);
338
- cursor: pointer;
339
- user-select: none;
340
- transition: background var(--transition-fast);
341
- }
342
-
343
- .module-header:hover {
344
- background: var(--color-surface-hover);
345
- }
346
-
347
- .module-icon {
348
- font-size: 1.25rem;
349
- }
350
-
351
- .module-name {
352
- flex: 1;
353
- font-weight: 600;
354
- }
355
-
356
- .module-count {
357
- color: var(--color-text-muted);
358
- font-size: 0.85rem;
359
- }
360
-
361
- .module-toggle {
362
- transition: transform var(--transition-normal);
363
- }
364
-
365
- .module-item[open] .module-toggle {
366
- transform: rotate(180deg);
367
- }
368
-
369
- .module-content {
370
- padding: 0 var(--space-lg) var(--space-lg);
371
- display: flex;
372
- flex-direction: column;
373
- gap: var(--space-sm);
374
- }
375
-
376
- /* ==========================================
377
- Video Card
378
- ========================================== */
379
-
380
- .video-card {
381
- display: flex;
382
- align-items: center;
383
- gap: var(--space-md);
384
- padding: var(--space-md);
385
- background: var(--color-bg-secondary);
386
- border-radius: var(--radius-md);
387
- color: var(--color-text);
388
- transition: all var(--transition-fast);
389
- border: 1px solid transparent;
390
- }
391
-
392
- .video-card:hover {
393
- background: var(--color-bg-tertiary);
394
- border-color: var(--color-primary);
395
- color: var(--color-text);
396
- transform: translateX(4px);
397
- }
398
-
399
- .video-card-icon {
400
- flex-shrink: 0;
401
- }
402
-
403
- .status-icon {
404
- display: flex;
405
- align-items: center;
406
- justify-content: center;
407
- width: 28px;
408
- height: 28px;
409
- border-radius: 50%;
410
- font-size: 0.85rem;
411
- }
412
-
413
- .status-icon.completed {
414
- background: var(--color-success-bg);
415
- color: var(--color-success);
416
- }
417
-
418
- .status-icon.in-progress {
419
- background: var(--color-warning-bg);
420
- color: var(--color-warning);
421
- }
422
-
423
- .status-icon.unwatched {
424
- background: var(--color-bg-tertiary);
425
- color: var(--color-text-muted);
426
- }
427
-
428
- .video-card-content {
429
- flex: 1;
430
- min-width: 0;
431
- }
432
-
433
- .video-card-title {
434
- font-size: 0.95rem;
435
- font-weight: 500;
436
- white-space: nowrap;
437
- overflow: hidden;
438
- text-overflow: ellipsis;
439
- }
440
-
441
- .video-card-progress {
442
- display: flex;
443
- align-items: center;
444
- gap: var(--space-sm);
445
- margin-top: var(--space-xs);
446
- }
447
-
448
- .progress-bar {
449
- flex: 1;
450
- height: 4px;
451
- background: var(--color-bg-tertiary);
452
- border-radius: 2px;
453
- overflow: hidden;
454
- }
455
-
456
- .progress-fill {
457
- height: 100%;
458
- background: var(--color-primary);
459
- border-radius: 2px;
460
- transition: width var(--transition-normal);
461
- }
462
-
463
- .progress-text {
464
- font-size: 0.75rem;
465
- color: var(--color-text-muted);
466
- min-width: 35px;
467
- }
468
-
469
- .video-card-badge {
470
- flex-shrink: 0;
471
- }
472
-
473
- /* ==========================================
474
- Badges
475
- ========================================== */
476
-
477
- .badge {
478
- display: inline-block;
479
- padding: var(--space-xs) var(--space-sm);
480
- border-radius: var(--radius-sm);
481
- font-size: 0.75rem;
482
- font-weight: 500;
483
- text-transform: uppercase;
484
- letter-spacing: 0.5px;
485
- }
486
-
487
- .badge-success {
488
- background: var(--color-success-bg);
489
- color: var(--color-success);
490
- }
491
-
492
- .badge-warning {
493
- background: var(--color-warning-bg);
494
- color: var(--color-warning);
495
- }
496
-
497
- .badge-default {
498
- background: var(--color-bg-tertiary);
499
- color: var(--color-text-muted);
500
- }
501
-
502
- /* ==========================================
503
- Video Player Page
504
- ========================================== */
505
-
506
- .player-container {
507
- max-width: 1600px;
508
- }
509
-
510
- .player-section {
511
- margin-bottom: var(--space-xl);
512
- }
513
-
514
- .video-wrapper {
515
- position: relative;
516
- background: #000;
517
- border-radius: var(--radius-lg);
518
- overflow: hidden;
519
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
520
- }
521
-
522
- .video-player {
523
- width: 100%;
524
- display: block;
525
- max-height: 70vh;
526
- }
527
-
528
- .player-nav {
529
- display: flex;
530
- justify-content: center;
531
- gap: var(--space-md);
532
- margin-top: var(--space-lg);
533
- }
534
-
535
- .nav-button {
536
- display: inline-flex;
537
- align-items: center;
538
- gap: var(--space-sm);
539
- padding: var(--space-sm) var(--space-lg);
540
- background: var(--color-surface);
541
- border-radius: var(--radius-md);
542
- color: var(--color-text);
543
- font-weight: 500;
544
- transition: all var(--transition-fast);
545
- border: 1px solid var(--color-border);
546
- }
547
-
548
- .nav-button:hover:not(.disabled) {
549
- background: var(--color-primary);
550
- border-color: var(--color-primary);
551
- color: white;
552
- }
553
-
554
- .nav-button.disabled {
555
- opacity: 0.4;
556
- cursor: not-allowed;
557
- }
558
-
559
- /* ==========================================
560
- Video Info Section
561
- ========================================== */
562
-
563
- .video-info-section {
564
- background: var(--color-surface);
565
- border-radius: var(--radius-lg);
566
- padding: var(--space-xl);
567
- margin-bottom: var(--space-xl);
568
- border: 1px solid var(--color-border);
569
- }
570
-
571
- .video-title {
572
- font-size: 1.75rem;
573
- margin-bottom: var(--space-lg);
574
- }
575
-
576
- .status-controls {
577
- display: flex;
578
- align-items: center;
579
- gap: var(--space-md);
580
- margin-bottom: var(--space-lg);
581
- flex-wrap: wrap;
582
- }
583
-
584
- .status-label {
585
- color: var(--color-text-secondary);
586
- font-weight: 500;
587
- }
588
-
589
- .status-buttons {
590
- display: flex;
591
- gap: var(--space-sm);
592
- flex-wrap: wrap;
593
- }
594
-
595
- .status-btn {
596
- padding: var(--space-sm) var(--space-md);
597
- background: var(--color-bg-secondary);
598
- border: 1px solid var(--color-border);
599
- border-radius: var(--radius-md);
600
- color: var(--color-text-secondary);
601
- cursor: pointer;
602
- font-size: 0.85rem;
603
- transition: all var(--transition-fast);
604
- }
605
-
606
- .status-btn:hover {
607
- background: var(--color-bg-tertiary);
608
- color: var(--color-text);
609
- }
610
-
611
- .status-btn.active {
612
- background: var(--color-primary);
613
- border-color: var(--color-primary);
614
- color: white;
615
- }
616
-
617
- /* ==========================================
618
- Plyr Customization
619
- ========================================== */
620
-
621
- :root {
622
- --plyr-color-main: var(--color-primary);
623
- --plyr-video-background: #000;
624
- --plyr-menu-background: var(--color-surface);
625
- --plyr-menu-color: var(--color-text);
626
- --plyr-menu-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
627
- --plyr-font-family: var(--font-sans);
628
- }
629
-
630
- .plyr--video {
631
- border-radius: var(--radius-lg);
632
- overflow: hidden;
633
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
634
- }
635
-
636
- /* ==========================================
637
- Notes Section
638
- ========================================== */
639
-
640
- .notes-section {
641
- background: var(--color-surface);
642
- border-radius: var(--radius-lg);
643
- padding: var(--space-xl);
644
- border: 1px solid var(--color-border);
645
- }
646
-
647
- .notes-editor {
648
- width: 100%;
649
- min-height: 200px;
650
- padding: var(--space-md);
651
- background: var(--color-bg-secondary);
652
- border: 1px solid var(--color-border);
653
- border-radius: var(--radius-md);
654
- color: var(--color-text);
655
- font-family: var(--font-mono);
656
- font-size: 0.9rem;
657
- line-height: 1.6;
658
- resize: vertical;
659
- outline: none;
660
- transition: border-color var(--transition-fast);
661
- }
662
-
663
- .notes-editor:focus {
664
- border-color: var(--color-primary);
665
- box-shadow: 0 0 0 3px var(--color-primary-glow);
666
- }
667
-
668
- .notes-editor::placeholder {
669
- color: var(--color-text-muted);
670
- }
671
-
672
- .notes-actions {
673
- display: flex;
674
- align-items: center;
675
- gap: var(--space-md);
676
- margin-top: var(--space-md);
677
- }
678
-
679
- .save-status {
680
- font-size: 0.85rem;
681
- color: var(--color-success);
682
- }
683
-
684
- /* ==========================================
685
- Buttons
686
- ========================================== */
687
-
688
- .btn {
689
- display: inline-flex;
690
- align-items: center;
691
- justify-content: center;
692
- gap: var(--space-sm);
693
- padding: var(--space-sm) var(--space-lg);
694
- border: none;
695
- border-radius: var(--radius-md);
696
- font-size: 0.9rem;
697
- font-weight: 500;
698
- cursor: pointer;
699
- transition: all var(--transition-fast);
700
- }
701
-
702
- .btn-primary {
703
- background: var(--color-primary);
704
- color: white;
705
- }
706
-
707
- .btn-primary:hover {
708
- background: var(--color-primary-hover);
709
- transform: translateY(-1px);
710
- box-shadow: 0 4px 15px var(--color-primary-glow);
711
- }
712
-
713
- /* ==========================================
714
- Search Page
715
- ========================================== */
716
-
717
- .page-title {
718
- font-size: 1.75rem;
719
- margin-bottom: var(--space-xl);
720
- }
721
-
722
- .search-form-large {
723
- display: flex;
724
- gap: var(--space-md);
725
- margin-bottom: var(--space-lg);
726
- }
727
-
728
- .search-input-large {
729
- flex: 1;
730
- padding: var(--space-md) var(--space-lg);
731
- background: var(--color-surface);
732
- border: 1px solid var(--color-border);
733
- border-radius: var(--radius-md);
734
- color: var(--color-text);
735
- font-size: 1rem;
736
- outline: none;
737
- transition: border-color var(--transition-fast);
738
- }
739
-
740
- .search-input-large:focus {
741
- border-color: var(--color-primary);
742
- box-shadow: 0 0 0 3px var(--color-primary-glow);
743
- }
744
-
745
- .search-meta {
746
- color: var(--color-text-secondary);
747
- margin-bottom: var(--space-lg);
748
- }
749
-
750
- .search-results {
751
- display: flex;
752
- flex-direction: column;
753
- gap: var(--space-sm);
754
- }
755
-
756
- .search-result-card {
757
- display: flex;
758
- align-items: center;
759
- gap: var(--space-md);
760
- padding: var(--space-lg);
761
- background: var(--color-surface);
762
- border-radius: var(--radius-md);
763
- color: var(--color-text);
764
- border: 1px solid var(--color-border);
765
- transition: all var(--transition-fast);
766
- }
767
-
768
- .search-result-card:hover {
769
- background: var(--color-surface-hover);
770
- border-color: var(--color-primary);
771
- transform: translateX(4px);
772
- }
773
-
774
- .result-content {
775
- flex: 1;
776
- }
777
-
778
- .result-title {
779
- font-size: 1rem;
780
- font-weight: 500;
781
- margin-bottom: var(--space-xs);
782
- }
783
-
784
- .result-module {
785
- font-size: 0.85rem;
786
- color: var(--color-text-muted);
787
- }
788
-
789
- /* ==========================================
790
- Empty State
791
- ========================================== */
792
-
793
- .empty-state {
794
- text-align: center;
795
- padding: var(--space-2xl);
796
- color: var(--color-text-secondary);
797
- }
798
-
799
- .empty-hint {
800
- color: var(--color-text-muted);
801
- font-size: 0.9rem;
802
- margin-top: var(--space-sm);
803
- }
804
-
805
- /* ==========================================
806
- Error Page
807
- ========================================== */
808
-
809
- .error-section {
810
- display: flex;
811
- justify-content: center;
812
- align-items: center;
813
- min-height: 50vh;
814
- }
815
-
816
- .error-content {
817
- text-align: center;
818
- }
819
-
820
- .error-code {
821
- font-size: 6rem;
822
- font-weight: 700;
823
- line-height: 1;
824
- background: linear-gradient(135deg, var(--color-primary) 0%, #9333ea 100%);
825
- -webkit-background-clip: text;
826
- -webkit-text-fill-color: transparent;
827
- background-clip: text;
828
- }
829
-
830
- .error-message {
831
- font-size: 1.25rem;
832
- color: var(--color-text-secondary);
833
- margin: var(--space-lg) 0 var(--space-xl);
834
- }
835
-
836
- /* ==========================================
837
- Responsive
838
- ========================================== */
839
-
840
- @media (max-width: 768px) {
841
- .header-content {
842
- flex-wrap: wrap;
843
- }
844
-
845
- .search-input {
846
- width: 150px;
847
- }
848
-
849
- .stats-grid {
850
- grid-template-columns: repeat(2, 1fr);
851
- }
852
-
853
- .playback-controls {
854
- flex-direction: column;
855
- gap: var(--space-md);
856
- }
857
-
858
- .player-nav {
859
- flex-wrap: wrap;
860
- }
861
-
862
- .nav-button {
863
- flex: 1;
864
- justify-content: center;
865
- min-width: 120px;
866
- }
867
- }
868
-
869
- /* ==========================================
870
- Video Player Overlay Controls
871
- ========================================== */
872
-
873
- .video-wrapper {
874
- position: relative;
875
- overflow: hidden;
876
- /* Ensure controls don't spill out */
877
- group: video-group;
878
- /* For potential future container queries */
879
- background: black;
880
- /* Prevent white flashes */
881
- }
882
-
883
- .video-wrapper:hover .overlay-controls,
884
- .video-wrapper:focus-within .overlay-controls,
885
- .video-player.paused+.overlay-controls,
886
- .overlay-controls:hover {
887
- opacity: 1;
888
- pointer-events: auto;
889
- }
890
-
891
- .overlay-controls {
892
- position: absolute;
893
- bottom: 0;
894
- left: 0;
895
- right: 0;
896
- background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.6) 60%, transparent 100%);
897
- padding: var(--space-md);
898
- opacity: 0;
899
- transition: opacity var(--transition-normal);
900
- pointer-events: none;
901
- /* Let clicks pass through when hidden */
902
- display: flex;
903
- flex-direction: column;
904
- gap: var(--space-sm);
905
- z-index: 10;
906
- }
907
-
908
- .controls-row {
909
- display: flex;
910
- align-items: center;
911
- justify-content: space-between;
912
- gap: var(--space-md);
913
- }
914
-
915
- .control-group {
916
- display: flex;
917
- align-items: center;
918
- gap: var(--space-md);
919
- }
920
-
921
- .control-group.right {
922
- justify-content: flex-end;
923
- }
924
-
925
- /* Icon Buttons */
926
- .control-btn-icon {
927
- background: transparent;
928
- border: none;
929
- color: white;
930
- font-size: 1.2rem;
931
- cursor: pointer;
932
- padding: var(--space-xs);
933
- border-radius: var(--radius-sm);
934
- transition: all var(--transition-fast);
935
- display: flex;
936
- align-items: center;
937
- justify-content: center;
938
- width: 32px;
939
- height: 32px;
940
- }
941
-
942
- .control-btn-icon:hover {
943
- background: rgba(255, 255, 255, 0.2);
944
- transform: scale(1.1);
945
- }
946
-
947
- /* Text Buttons (Speed) */
948
- .control-btn-text {
949
- background: rgba(255, 255, 255, 0.1);
950
- border: 1px solid rgba(255, 255, 255, 0.2);
951
- color: white;
952
- font-size: 1rem;
953
- cursor: pointer;
954
- padding: 0 var(--space-sm);
955
- border-radius: var(--radius-sm);
956
- height: 24px;
957
- transition: all var(--transition-fast);
958
- display: flex;
959
- align-items: center;
960
- justify-content: center;
961
- }
962
-
963
- .control-btn-text:hover {
964
- background: var(--color-primary);
965
- border-color: var(--color-primary);
966
- }
967
-
968
- /* Speed Control */
969
- .speed-control {
970
- display: flex;
971
- align-items: center;
972
- gap: var(--space-sm);
973
- background: rgba(0, 0, 0, 0.5);
974
- padding: var(--space-xs) var(--space-sm);
975
- border-radius: var(--radius-md);
976
- }
977
-
978
- .speed-value {
979
- color: white;
980
- font-weight: 600;
981
- font-size: 0.9rem;
982
- min-width: 40px;
983
- text-align: center;
984
- }
985
-
986
- /* Time Display */
987
- .time-display {
988
- color: white;
989
- font-size: 0.9rem;
990
- font-feature-settings: "tnum";
991
- font-variant-numeric: tabular-nums;
992
- margin-left: var(--space-sm);
993
- }
994
-
995
- /* Seek Bar */
996
- .seek-bar-container {
997
- position: relative;
998
- height: 6px;
999
- width: 100%;
1000
- cursor: pointer;
1001
- display: flex;
1002
- align-items: center;
1003
- margin-bottom: var(--space-xs);
1004
- }
1005
-
1006
- .seek-bar-container:hover .seek-bar-bg {
1007
- height: 8px;
1008
- }
1009
-
1010
- .seek-bar-bg {
1011
- background: rgba(255, 255, 255, 0.3);
1012
- height: 4px;
1013
- width: 100%;
1014
- border-radius: 2px;
1015
- transition: height var(--transition-fast);
1016
- position: absolute;
1017
- top: 50%;
1018
- transform: translateY(-50%);
1019
- pointer-events: none;
1020
- }
1021
-
1022
- .seek-bar-fill {
1023
- background: var(--color-primary);
1024
- height: 100%;
1025
- width: 0%;
1026
- border-radius: 2px;
1027
- }
1028
-
1029
- .seek-slider {
1030
- position: absolute;
1031
- width: 100%;
1032
- height: 100%;
1033
- opacity: 0;
1034
- cursor: pointer;
1035
- margin: 0;
1036
- z-index: 2;
1037
- }
1038
-
1039
- /* Hide default controls on fullscreen if needed (we rely on custom ones now) */
1040
- /* .video-player::-webkit-media-controls { display:none !important; } */
1041
-
1042
- /* ==========================================
1043
- Player Layout with Queue
1044
- ========================================== */
1045
-
1046
- .player-layout {
1047
- display: flex;
1048
- gap: var(--space-md);
1049
- /* Reduced gap */
1050
- align-items: flex-start;
1051
- }
1052
-
1053
- .player-main {
1054
- flex: 1;
1055
- min-width: 0;
1056
- }
1057
-
1058
- /* ==========================================
1059
- Queue Panel
1060
- ========================================== */
1061
-
1062
- .queue-panel {
1063
- width: 30%;
1064
- /* Slightly narrower width */
1065
- min-width: 300px;
1066
- max-width: 450px;
1067
- flex-shrink: 0;
1068
- background: var(--color-bg-secondary);
1069
- border-radius: var(--radius-lg);
1070
- border: 1px solid var(--color-border);
1071
- overflow: hidden;
1072
- position: sticky;
1073
- top: 100px;
1074
- max-height: calc(100vh - 120px);
1075
- display: flex;
1076
- flex-direction: column;
1077
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
1078
- }
1079
-
1080
- .queue-header {
1081
- padding: var(--space-md) var(--space-lg);
1082
- border-bottom: 1px solid var(--color-border);
1083
- background: rgba(255, 255, 255, 0.03);
1084
- }
1085
-
1086
- .queue-title {
1087
- font-size: 1rem;
1088
- font-weight: 600;
1089
- margin-bottom: 2px;
1090
- color: var(--color-text);
1091
- text-transform: uppercase;
1092
- letter-spacing: 0.5px;
1093
- }
1094
-
1095
- .queue-module-name {
1096
- font-size: 0.8rem;
1097
- color: var(--color-text-muted);
1098
- display: block;
1099
- white-space: nowrap;
1100
- overflow: hidden;
1101
- text-overflow: ellipsis;
1102
- }
1103
-
1104
- .queue-list {
1105
- overflow-y: auto;
1106
- flex: 1;
1107
- padding: var(--space-sm);
1108
- scrollbar-width: thin;
1109
- scrollbar-color: var(--color-border) transparent;
1110
- }
1111
-
1112
- .queue-list::-webkit-scrollbar {
1113
- width: 6px;
1114
- }
1115
-
1116
- .queue-list::-webkit-scrollbar-track {
1117
- background: transparent;
1118
- }
1119
-
1120
- .queue-list::-webkit-scrollbar-thumb {
1121
- background-color: var(--color-border);
1122
- border-radius: 3px;
1123
- }
1124
-
1125
- .queue-item {
1126
- display: flex;
1127
- align-items: center;
1128
- gap: var(--space-md);
1129
- padding: var(--space-md);
1130
- /* More comfortable padding */
1131
- border-radius: var(--radius-md);
1132
- color: var(--color-text-secondary);
1133
- transition: all var(--transition-fast);
1134
- margin-bottom: 2px;
1135
- text-decoration: none;
1136
- border: 1px solid transparent;
1137
- }
1138
-
1139
- .queue-item:hover {
1140
- background: var(--color-bg-tertiary);
1141
- color: var(--color-text);
1142
- transform: translateX(2px);
1143
- }
1144
-
1145
- .queue-item.active {
1146
- background: var(--color-surface);
1147
- color: var(--color-primary);
1148
- border-color: var(--color-primary);
1149
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
1150
- }
1151
-
1152
- .queue-item.active:hover {
1153
- background: var(--color-surface);
1154
- transform: none;
1155
- }
1156
-
1157
- .queue-item-number {
1158
- flex-shrink: 0;
1159
- width: 28px;
1160
- height: 28px;
1161
- display: flex;
1162
- align-items: center;
1163
- justify-content: center;
1164
- font-size: 0.8rem;
1165
- font-weight: 600;
1166
- background: rgba(255, 255, 255, 0.05);
1167
- border-radius: 50%;
1168
- /* Circle instead of square */
1169
- color: var(--color-text-muted);
1170
- transition: all var(--transition-fast);
1171
- }
1172
-
1173
- .queue-item.active .queue-item-number {
1174
- background: var(--color-primary);
1175
- color: white;
1176
- }
1177
-
1178
- .queue-item-content {
1179
- flex: 1;
1180
- min-width: 0;
1181
- display: flex;
1182
- flex-direction: column;
1183
- gap: 2px;
1184
- }
1185
-
1186
- .queue-item-title {
1187
- font-size: 0.9rem;
1188
- font-weight: 500;
1189
- display: block;
1190
- /* Multi-line clamp instead of single line ellipsis for better readability */
1191
- display: -webkit-box;
1192
- -webkit-line-clamp: 2;
1193
- -webkit-box-orient: vertical;
1194
- overflow: hidden;
1195
- line-height: 1.4;
1196
- color: inherit;
1197
- }
1198
-
1199
- .queue-item.active .queue-item-title {
1200
- color: var(--color-text);
1201
- font-weight: 600;
1202
- }
1203
-
1204
- .queue-item-meta {
1205
- display: flex;
1206
- align-items: center;
1207
- gap: var(--space-sm);
1208
- margin-top: 2px;
1209
- }
1210
-
1211
- .queue-item-status {
1212
- font-size: 0.75rem;
1213
- display: flex;
1214
- align-items: center;
1215
- }
1216
-
1217
- .queue-item-status.status-completed {
1218
- color: var(--color-success);
1219
- }
1220
-
1221
- .queue-item-status.status-in-progress {
1222
- color: var(--color-warning);
1223
- }
1224
-
1225
- .queue-item-status.status-unwatched {
1226
- color: var(--color-text-muted);
1227
- }
1228
-
1229
- .queue-item.active .queue-item-status {
1230
- /* color: var(--color-primary); */
1231
- }
1232
-
1233
- .queue-item-playing {
1234
- font-size: 0.65rem;
1235
- font-weight: 700;
1236
- text-transform: uppercase;
1237
- letter-spacing: 0.5px;
1238
- color: var(--color-primary);
1239
- background: rgba(124, 58, 237, 0.1);
1240
- padding: 2px 6px;
1241
- border-radius: 4px;
1242
- }
1243
-
1244
- /* ==========================================
1245
- Autoplay Countdown Overlay
1246
- ========================================== */
1247
-
1248
- .autoplay-overlay {
1249
- position: absolute;
1250
- top: 0;
1251
- left: 0;
1252
- right: 0;
1253
- bottom: 0;
1254
- background: rgba(0, 0, 0, 0.85);
1255
- display: flex;
1256
- align-items: center;
1257
- justify-content: center;
1258
- z-index: 100;
1259
- backdrop-filter: blur(8px);
1260
- }
1261
-
1262
- .autoplay-overlay.hidden {
1263
- display: none;
1264
- }
1265
-
1266
- .autoplay-content {
1267
- text-align: center;
1268
- color: white;
1269
- }
1270
-
1271
- .autoplay-countdown-ring {
1272
- position: relative;
1273
- width: 120px;
1274
- height: 120px;
1275
- margin: 0 auto var(--space-lg);
1276
- background: none;
1277
- border: none;
1278
- padding: 0;
1279
- cursor: pointer;
1280
- transition: transform var(--transition-fast);
1281
- }
1282
-
1283
- .autoplay-countdown-ring:hover {
1284
- transform: scale(1.05);
1285
- }
1286
-
1287
- .autoplay-countdown-ring:hover .countdown-progress {
1288
- stroke: var(--color-primary-hover);
1289
- }
1290
-
1291
- .autoplay-countdown-ring svg {
1292
- width: 100%;
1293
- height: 100%;
1294
- transform: rotate(-90deg);
1295
- }
1296
-
1297
- .countdown-bg {
1298
- fill: none;
1299
- stroke: rgba(255, 255, 255, 0.2);
1300
- stroke-width: 6;
1301
- }
1302
-
1303
- .countdown-progress {
1304
- fill: none;
1305
- stroke: var(--color-primary);
1306
- stroke-width: 6;
1307
- stroke-linecap: round;
1308
- stroke-dasharray: 283;
1309
- stroke-dashoffset: 0;
1310
- transition: stroke-dashoffset 1s linear;
1311
- }
1312
-
1313
- .countdown-number {
1314
- position: absolute;
1315
- top: 50%;
1316
- left: 50%;
1317
- transform: translate(-50%, -50%);
1318
- font-size: 2.5rem;
1319
- font-weight: 700;
1320
- color: white;
1321
- text-shadow: 0 0 20px rgba(124, 58, 237, 0.8), 0 2px 4px rgba(0, 0, 0, 0.5);
1322
- }
1323
-
1324
- .autoplay-text {
1325
- font-size: 0.9rem;
1326
- color: var(--color-text-secondary);
1327
- margin-bottom: var(--space-xs);
1328
- text-transform: uppercase;
1329
- letter-spacing: 1px;
1330
- }
1331
-
1332
- .autoplay-next-title {
1333
- font-size: 1.25rem;
1334
- font-weight: 600;
1335
- margin-bottom: var(--space-lg);
1336
- max-width: 400px;
1337
- overflow: hidden;
1338
- text-overflow: ellipsis;
1339
- white-space: nowrap;
1340
- }
1341
-
1342
- .autoplay-cancel {
1343
- background: rgba(255, 255, 255, 0.1);
1344
- border: 1px solid rgba(255, 255, 255, 0.3);
1345
- color: white;
1346
- padding: var(--space-sm) var(--space-xl);
1347
- font-size: 0.9rem;
1348
- }
1349
-
1350
- .autoplay-cancel:hover {
1351
- background: rgba(255, 255, 255, 0.2);
1352
- border-color: rgba(255, 255, 255, 0.5);
1353
- transform: none;
1354
- box-shadow: none;
1355
- }
1356
-
1357
- /* Responsive: Queue below player on smaller screens */
1358
- @media (max-width: 1024px) {
1359
- .player-layout {
1360
- flex-direction: column;
1361
- }
1362
-
1363
- .queue-panel {
1364
- width: 100%;
1365
- position: static;
1366
- max-height: 400px;
1367
- }
1368
- }
1369
-
1370
- /* Fullscreen autoplay overlay */
1371
- :fullscreen .autoplay-overlay,
1372
- :-webkit-full-screen .autoplay-overlay,
1373
- :-moz-full-screen .autoplay-overlay {
1374
- position: fixed;
1375
- }