vod jam and earl vod.atverkackt.de
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Overhaul player UI

+143 -56
+123 -3
src/lib/VideoPlayer.svelte
··· 5 5 import SpriteTime from "./SpriteTime.svelte"; 6 6 7 7 // HLS video source URL (m3u8 playlist) 8 - let { src }: { src: string } = $props(); 8 + let { 9 + src, 10 + title, 11 + speakerHandle, 12 + speakerName, 13 + speakerAvatarSprite, 14 + createdAt, 15 + }: { 16 + src: string; 17 + title?: string; 18 + speakerHandle?: string; 19 + speakerName?: string; 20 + speakerAvatarSprite?: string; 21 + createdAt?: string; 22 + } = $props(); 9 23 10 24 let videoEl: HTMLVideoElement | undefined = $state(); 11 25 let hls: Hls | null = null; ··· 256 270 if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`; 257 271 return `${m}:${String(sec).padStart(2, '0')}`; 258 272 } 273 + 274 + function formatDate(iso: string): string { 275 + return new Date(iso).toLocaleDateString('en-US', { 276 + month: 'short', 277 + day: 'numeric', 278 + year: 'numeric', 279 + timeZone: 'America/Los_Angeles', 280 + }); 281 + } 259 282 </script> 260 283 261 - <WavyBorder seed="player-main" padding={4}> 284 + <WavyBorder seed="player-main" padding={4} fillImage="/space.webp" fillImageMode="cover"> 262 285 <div 263 286 class="player-wrapper" 264 287 onmousemove={onMouseActivity} ··· 279 302 <track kind="captions" /> 280 303 </video> 281 304 305 + <!-- Speaker / talk info panel --> 306 + {#if title || speakerHandle || speakerName} 307 + <div class="video-info-panel"> 308 + {#if speakerAvatarSprite} 309 + <img src={speakerAvatarSprite} alt="" class="speaker-avatar" /> 310 + {/if} 311 + <div class="panel-text"> 312 + {#if title} 313 + <p class="panel-title">{title}</p> 314 + {/if} 315 + <div class="panel-meta"> 316 + {#if speakerName} 317 + <span class="panel-speaker-name">{speakerName}</span> 318 + {/if} 319 + {#if speakerHandle} 320 + <a 321 + href="https://bsky.app/profile/{speakerHandle}" 322 + target="_blank" 323 + rel="noopener" 324 + class="panel-handle" 325 + >@{speakerHandle}</a> 326 + {/if} 327 + {#if createdAt} 328 + <span class="panel-date">{formatDate(createdAt)}</span> 329 + {/if} 330 + </div> 331 + </div> 332 + </div> 333 + {/if} 334 + 282 335 <!-- Minimal controls: earl scrub bar + ship fullscreen --> 283 336 <div class="controls" class:visible={showControls || !playing}> 284 337 <div class="time-display"> ··· 334 387 .player-wrapper { 335 388 position: relative; 336 389 width: 100%; 337 - background: var(--color-space-black); 390 + background: transparent; 338 391 overflow: hidden; 392 + padding-bottom: 1.5rem; 339 393 } 340 394 341 395 .player-wrapper:fullscreen, ··· 461 515 border-radius: 6px; 462 516 font-family: 'Comic Sans MS', 'Comic Sans', cursive, system-ui; 463 517 font-size: 0.8rem; 518 + } 519 + 520 + /* Speaker / talk info panel */ 521 + .video-info-panel { 522 + display: flex; 523 + align-items: center; 524 + gap: 12px; 525 + padding: 10px 14px 12px; 526 + border-top: 2px solid var(--color-medium-blue); 527 + background: rgba(11, 14, 23, 0.82); 528 + } 529 + 530 + .speaker-avatar { 531 + width: 48px; 532 + height: 48px; 533 + flex-shrink: 0; 534 + image-rendering: pixelated; 535 + object-fit: cover; 536 + } 537 + 538 + .panel-text { 539 + flex: 1; 540 + min-width: 0; 541 + } 542 + 543 + .panel-title { 544 + margin: 0 0 4px; 545 + font-size: 0.9rem; 546 + text-transform: uppercase; 547 + letter-spacing: 0.04em; 548 + color: #39FF14; 549 + animation: font-switch 4s steps(1) infinite; 550 + white-space: nowrap; 551 + overflow: hidden; 552 + text-overflow: ellipsis; 553 + } 554 + 555 + .panel-meta { 556 + display: flex; 557 + align-items: center; 558 + gap: 8px; 559 + flex-wrap: wrap; 560 + } 561 + 562 + .panel-speaker-name { 563 + color: var(--color-lavender); 564 + font-family: monospace; 565 + font-size: 0.8rem; 566 + } 567 + 568 + .panel-handle { 569 + color: #4B9EFF; 570 + text-decoration: none; 571 + font-family: monospace; 572 + font-size: 0.8rem; 573 + } 574 + 575 + .panel-handle:hover { 576 + text-decoration: underline; 577 + } 578 + 579 + .panel-date { 580 + color: var(--color-orange); 581 + font-family: monospace; 582 + font-size: 0.75rem; 583 + font-style: italic; 464 584 } 465 585 </style>
+20 -53
src/routes/+page.svelte
··· 397 397 <button class="video-close-btn" onclick={handleCloseVideo}>✕ Close</button> 398 398 </div> 399 399 <div class="video-container"> 400 - <VideoPlayer src={getPlaylistUrl(getSelectedVideoUri())} /> 401 - <div class="video-info"> 402 - <h2 class="video-title">{selectedPresent.title}</h2> 403 - <p class="video-meta"> 404 - {#if selectedPresent.creatorHandle} 405 - <a 406 - href="https://bsky.app/profile/{selectedPresent.creatorHandle.replace('@', '')}" 407 - target="_blank" 408 - rel="noopener" 409 - class="creator-link" 410 - > 411 - {selectedPresent.creatorHandle} 412 - </a> 413 - {/if} 414 - </p> 415 - </div> 400 + <VideoPlayer 401 + src={getPlaylistUrl(getSelectedVideoUri())} 402 + title={selectedPresent.title} 403 + speakerHandle={selectedPresent.speakerHandle} 404 + speakerName={selectedPresent.speakerName} 405 + speakerAvatarSprite={selectedPresent.speakerAvatarSprite} 406 + createdAt={selectedPresent.createdAt} 407 + /> 416 408 </div> 417 409 </div> 418 410 {/if} ··· 480 472 position: fixed; 481 473 inset: 0; 482 474 z-index: var(--z-toast); 483 - background: var(--color-space-black); 475 + background: url('/background.webp') repeat; 476 + background-size: 256px 256px; 477 + image-rendering: pixelated; 484 478 overflow-y: auto; 485 479 display: flex; 486 480 flex-direction: column; ··· 496 490 497 491 .video-close-btn { 498 492 background: none; 499 - border: 2px solid #e94560; 500 - color: #e94560; 501 - padding: 0.5rem 1rem; 493 + border: 2px solid var(--color-orange); 494 + color: var(--color-yellow); 495 + padding: 0.15rem 0.4rem; 502 496 font-family: monospace; 503 - font-size: 0.9rem; 497 + font-size: 1rem; 504 498 cursor: pointer; 505 - border-radius: 4px; 506 - transition: all 0.2s; 499 + line-height: 1; 507 500 } 508 501 509 502 .video-close-btn:hover { 510 - background: #e94560; 511 - color: var(--color-lavender); 503 + background: var(--color-orange); 504 + color: var(--color-space-black); 512 505 } 513 506 514 507 .video-container { 515 508 flex: 1; 516 - max-width: 1000px; 509 + max-width: 1100px; 517 510 width: 100%; 518 511 margin: 0 auto; 519 - padding: 0 1rem 2rem; 520 - } 521 - 522 - .video-info { 523 - padding: 1rem 0; 524 - } 525 - 526 - .video-title { 527 - margin: 0; 528 - color: var(--color-lavender); 529 - font-size: 1.4rem; 530 - animation: font-switch 4s steps(1) infinite; 531 - } 532 - 533 - .video-meta { 534 - margin: 0.5rem 0 0; 535 - color: #888; 536 - font-family: monospace; 537 - font-size: 0.9rem; 512 + padding: clamp(0.75rem, 2vw, 1.5rem); 538 513 } 539 514 540 - .creator-link { 541 - color: var(--color-yellow); 542 - text-decoration: none; 543 - } 544 - 545 - .creator-link:hover { 546 - text-decoration: underline; 547 - } 548 515 </style>